Skip to content

feat: add Azure DevOps PR support and safer merge cleanup - #4543

Open
martinstroher wants to merge 40 commits into
kunchenguid:mainfrom
martinstroher:fm/ado-pr-support
Open

martinstroher wants to merge 40 commits into
kunchenguid:mainfrom
martinstroher:fm/ado-pr-support

Conversation

@martinstroher

@martinstroher martinstroher commented Sep 15, 2026 •

Copy link
Copy Markdown

Intent

Enable Azure DevOps support in Firstmate's existing PR workflow: recognize and track Azure PRs, verify required approvals and checks before an authorized merge, and confirm merged work before cleanup. The motivating Azure repository requires users//... branch naming instead of fm/.... Keep Firstmate's normal automated review before publication and the repository's colleague-approval requirements before merging.

Okay; then we got you your first task; do we ahve to configure something in order to do that? no-mistakes, yolo, etc?

For this specific case, i prefer if we used my personal gh account; martinstroher

No, do the more extensive automatic cleanup

The selected cleanup approach must reliably handle rebased and squash-merged work automatically while preserving later local changes that were not merged, rather than resolving the issue by leaving ordinary rewritten-history cases for manual cleanup. Cleanup must still refuse whenever safety cannot be proved.

Do the recommeneed

Replace the unsuccessful automatic-cleanup implementation rather than continue patching it.
Track locally changed regions, including later restorations, and verify their final contents at the correct locations in the merged result.
Preserve work whenever the available history cannot establish that those changes were merged.
Safely stop the obsolete review, preserve all existing work, and fully validate the replacement before publication.
No merge or activation is authorized.

What Changed

  • Add Azure DevOps Services PR recognition, monitoring, live review diffs, and revision-bound completion guarded by required approvals, policies, and checks.
  • Replace cleanup heuristics with history- and location-aware final-content verification for rebased and squash-merged work. Preserve unmatched edits, later restorations, and dirty submodules; refuse incomplete proofs and require confirmed Azure completion before cleanup.
  • Update forge-aware delivery instructions, document Azure setup and users/<username>/... branch-prefix configuration, and add regression coverage for Azure workflows and rewritten-history cleanup.

Risk Assessment

⚠️ Medium: No blocking source issue was found, but Azure completion and history-based destructive-cleanup verification remain substantial, safety-sensitive changes with accepted validation limitations.

Testing

Diff inspection, live local CLI scenarios, focused regressions, and synthetic Azure checks demonstrated branch selection and cleanup safeguards. Initial bounded batches were incomplete; focused reruns covered the critical cases. CLI transcripts and generated briefs were captured; screenshots are not applicable to this CLI-only surface. Live Azure remains untested under the accepted scope restrictions; no publication, merge, activation, or broad-suite validation was performed.

  • Live validation: ⚠️ inconclusive - 7 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Select a personal ship prefix while retaining ordinary local-only naming and automatic-review instructions ✅ pass live Live CLI evidence and generated personal/default/local-opt-in briefs.
Reject unsafe branch prefixes before creating task instructions ✅ pass live live-cli.txt: traversal, doubled-separator, and .lock prefix refusals.
Review the recorded personal branch after the checkout moves back to main ✅ pass live live-cli.txt: recorded-branch review output.
Recognize rebased and squash-merged changes despite independent upstream edits ✅ pass live live-cli.txt and live-results.json: rewritten-history, merge-main, large-file, and path-provenance cases.
Reject containment for later local restorations, deletions, replacements, or partial landings ✅ pass live live-cli.txt: negative history cases; teardown-proof-product.txt: restored-path cleanup refusal.
Report unavailable containment evidence instead of accepting an incomplete proof ✅ pass live live-cli.txt: missing-tree and alignment-bound diagnostics; teardown-proof-product.txt: fallback classification controls.
Preserve nested untracked source hidden by Git status and ancestor ignore settings ✅ pass live live-nested-dirt.txt.
Register, review, and monitor a real Azure PR across active, completed, and abandoned states ⏸️ untested no Synthetic transport covered registration, source selection, polling, and expired-monitor dispositions. Although az is on PATH, no approved real PR URL or repository scope was supplied, and operator da…
Complete a real Azure PR only after current mandatory approvals and checks pass for the pinned head ⏸️ untested no Synthetic public-entrypoint checks passed for matching/mismatched expected heads, mandatory policies, optional statuses, revision races, and unconfirmed completion. Real Azure mutation remains explici…
Confirm real Azure completion before cleanup, including missing or reassigned local copies ⏸️ untested no Disposable Git repositories and synthetic Azure responses verified retained monitoring/backlog for active, abandoned, and unreadable PRs, and preservation of reassigned work. Real containment and dirt…
Evidence: Live branch, review, and rewritten-history CLI evidence

Source: Live branch, review, and rewritten-history CLI evidence

$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-lab-home.sh create ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab
~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab
exit=0

=== Branch prefix selection and actual branch creation ===
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-brief.sh personal disposable-project --mode no-mistakes --branch-prefix users/example/
scaffolded: ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/data/personal/brief.md (ship, mode=no-mistakes; replace {TASK} and {FIRSTMATE_SPEC})
exit=0
$ bash -c git checkout -b users/example/personal --
Switched to a new branch 'users/example/personal'
exit=0
observed branch: users/example/personal
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-brief.sh default disposable-project --mode local-only
scaffolded: ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/data/default/brief.md (ship, mode=local-only; replace {TASK} and {FIRSTMATE_SPEC})
exit=0
$ bash -c git checkout -b fm/default --
Switched to a new branch 'fm/default'
exit=0
observed branch: fm/default
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-brief.sh local-opt-in disposable-project --mode local-only --branch-prefix users/example/
scaffolded: ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/data/local-opt-in/brief.md (ship, mode=local-only; replace {TASK} and {FIRSTMATE_SPEC})
exit=0
$ bash -c git checkout -b users/example/local-opt-in --
Switched to a new branch 'users/example/local-opt-in'
exit=0
observed branch: users/example/local-opt-in
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-brief.sh release disposable-project --mode direct-PR --branch-prefix users/example/ --base-branch release/1
scaffolded: ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/data/release/brief.md (ship, mode=direct-PR; replace {TASK} and {FIRSTMATE_SPEC})
exit=0
$ bash -c git checkout -b users/example/release --
Switched to a new branch 'users/example/release'
exit=0
observed branch: users/example/release
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-brief.sh invalid0 disposable-project --mode no-mistakes --branch-prefix ../bad/
error: --branch-prefix and task id must form a valid git branch (got '../bad/invalid0')
exit=1
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-brief.sh invalid1 disposable-project --mode no-mistakes --branch-prefix users//name/
error: --branch-prefix and task id must form a valid git branch (got 'users//name/invalid1')
exit=1
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-brief.sh invalid2 disposable-project --mode no-mistakes --branch-prefix users/name.lock/
error: --branch-prefix and task id must form a valid git branch (got 'users/name.lock/invalid2')
exit=1

=== Review the recorded personal branch, not the current checkout ===
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-review-diff.sh review
diff base: main
 feature.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/feature.txt b/feature.txt
index df967b9..42838cd 100644
--- a/feature.txt
+++ b/feature.txt
@@ -1 +1 @@
-base
+personal branch edit
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  WATCHER DOWN - SUPERVISION IS OFF
●  1 task(s) in flight, but no watcher has a fresh beacon (last beat: never, grace 300s).
●  Trust the emitted supervision protocol for this harness; do not use shell & for watcher repair.
●  This is a supervision warning only; the guarded operation WILL still run.
●  repair missing watcher supervision according to the session-start block for this harness; do not use shell &.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
exit=0

=== Final-content proof on real rewritten Git histories ===

{"scenario": "unlanded-delete", "base": "fc6c28ab20a5386924591ee1ed5f1ceb91b64e2b", "local_head": "b3226f8ca24b3e681e6b25bf238cb5286a5b048d", "merged_head": "fc6c28ab20a5386924591ee1ed5f1ceb91b64e2b", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/unlanded-delete/wt fc6c28ab20a5386924591ee1ed5f1ceb91b64e2b
exit=1

{"scenario": "landed-delete", "base": "ea032e1769a7892aa4894eecfe3024c58bb912c0", "local_head": "8dfbd099dfb229e7085e8101af70d4a4ccc2756b", "merged_head": "4708b0021f52daf5df09a7e2529a99b64214f841", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/landed-delete/wt 4708b0021f52daf5df09a7e2529a99b64214f841
exit=0

{"scenario": "unlanded-replace", "base": "3f1547cc16c08002db9cc96ef00c319a6728438d", "local_head": "4a41e1818ec8b496e597c4a5645f2b2daf3b9c35", "merged_head": "3f1547cc16c08002db9cc96ef00c319a6728438d", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/unlanded-replace/wt 3f1547cc16c08002db9cc96ef00c319a6728438d
exit=1

{"scenario": "same-file-restoration", "base": "1084756914f6b8de7f79cb4358048c235f248b69", "local_head": "5015f6ad8716a2fea4a428f781309d7869ac51fe", "merged_head": "b4ff32424f56c70576a9292fa17a82f70eaef906", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/same-file-restoration/wt b4ff32424f56c70576a9292fa17a82f70eaef906
exit=1

{"scenario": "literal-path", "base": "abec39b2d149b0b40c458ca22dd0ba6da145eb7d", "local_head": "41710eea41e29ed43ffd2ecb5f12cbe08b5241f8", "merged_head": "b5f01ae59bdd8691ab9768c5c90627fe97b77822", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/literal-path/wt b5f01ae59bdd8691ab9768c5c90627fe97b77822
exit=1


... [5133 bytes truncated] ...

pport/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/large-rebased-upstream/wt 6eedf577ec0880d7872e61fe2483f0b38d94bea3
exit=0

{"scenario": "large-rebased-unlanded", "base": "e56863f0529d34ac4932286b4f58135a2395f346", "local_head": "53de33ef5c09b4682a7c388ab2eac00ea8868bda", "merged_head": "bf4a805493e0da94c3d38686530f127ded26bc8e", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/large-rebased-unlanded/wt bf4a805493e0da94c3d38686530f127ded26bc8e
exit=1

{"scenario": "large-alignment-bound", "base": "8b5d592866f611ca0cff2b4108e27239ff0d0806", "local_head": "75fb8061dfb7ef4047c2c9b3283f2811172f71b4", "merged_head": "f931770475f7ce68b362eab425f595188677a434", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/large-alignment-bound/wt f931770475f7ce68b362eab425f595188677a434
containment unknown: text alignment exceeds the exact-proof memory bound
exit=2

{"scenario": "merge-main-upstream", "base": "c354b17c388d1f327e4bdfb9a5be0094d1913015", "local_head": "ee35e2078e380842a13a91ff730889ef1f7460e0", "merged_head": "2e82cc38f367e3484224a5d8917463a6923efebf", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/merge-main-upstream/wt 2e82cc38f367e3484224a5d8917463a6923efebf
exit=0

{"scenario": "merge-main-same-file", "base": "08c27643a89172d999ab980a8c3fa81da216a4fc", "local_head": "9f99ead4faa0185b9121346a8d8c9f5d21ca8666", "merged_head": "258ac5884322e60f5639df145cef9d34addb4b2c", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/merge-main-same-file/wt 258ac5884322e60f5639df145cef9d34addb4b2c
exit=0

{"scenario": "merge-main-resolution", "base": "cb0a093bd1a29105f4ad29f6754a1aec926de695", "local_head": "e37f202a5fef7e362b3d244e04b346eab74b29a3", "merged_head": "39a7f2e23f2d4c3d2e000696dfcd301e1a2ae859", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/merge-main-resolution/wt 39a7f2e23f2d4c3d2e000696dfcd301e1a2ae859
exit=1

{"scenario": "merge-main-merge-restoration", "base": "f57c2b041fae5236a64eaecdda7634fe44fafe72", "local_head": "ce6f1fb345a3cf6d45a26f539d35e554ae668149", "merged_head": "a413a5752ef2604abd1f5118f1cf2c5dbc414443", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/merge-main-merge-restoration/wt a413a5752ef2604abd1f5118f1cf2c5dbc414443
exit=1

{"scenario": "merge-main-local-restoration", "base": "4c3c33bd962c145a01dca4e98f7de70200fdc588", "local_head": "e02a90939adbb770a4bc26f523abd47e5b2a0704", "merged_head": "ce7bc9b1d332dd6bd01b07cad3e1c276c431693c", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/merge-main-local-restoration/wt ce7bc9b1d332dd6bd01b07cad3e1c276c431693c
exit=1

{"scenario": "path-provenance-created", "base": "6fad3d87ccfcd3edb3bcacde25b02dab4818c7e4", "local_head": "02cd9552dd97cec0f5762f16bab6b626b3891833", "merged_head": "6e52212a4932f1924643ebe1803753bbd65c7670", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/path-provenance-created/wt 6e52212a4932f1924643ebe1803753bbd65c7670
exit=0
02cd9552dd97cec0f5762f16bab6b626b3891833:f.txt
one
6e52212a4932f1924643ebe1803753bbd65c7670:f.txt
one
02cd9552dd97cec0f5762f16bab6b626b3891833:g.txt
one
anchor
tail0
6e52212a4932f1924643ebe1803753bbd65c7670:g.txt
one
anchor
tail1

{"scenario": "path-provenance-replaced", "base": "dc0a4d9e11f85492b00a5f6a90ca829009068676", "local_head": "6e4b0dd54acb84547b33eed91ffd24426c453004", "merged_head": "545b257a0ef73816190939f2137a1dd05c479834", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/path-provenance-replaced/wt 545b257a0ef73816190939f2137a1dd05c479834
exit=0
6e4b0dd54acb84547b33eed91ffd24426c453004:f.txt
one
545b257a0ef73816190939f2137a1dd05c479834:f.txt
one
6e4b0dd54acb84547b33eed91ffd24426c453004:g.txt
one
anchor
tail0
545b257a0ef73816190939f2137a1dd05c479834:g.txt
one
anchor
tail1

{"scenario": "path-provenance-restored", "base": "f9f84e2c50a877f41656e08716a605aac718b308", "local_head": "50474f103ded74b92581f28a02c27c2b44968aec", "merged_head": "c1ee25116aef2bedefdd54180506f3734635112c", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/path-provenance-restored/wt c1ee25116aef2bedefdd54180506f3734635112c
exit=1
50474f103ded74b92581f28a02c27c2b44968aec:f.txt
one
c1ee25116aef2bedefdd54180506f3734635112c:f.txt
one
50474f103ded74b92581f28a02c27c2b44968aec:g.txt
zero
anchor
tail0
c1ee25116aef2bedefdd54180506f3734635112c:g.txt
one
anchor
tail1

{"scenario": "path-provenance-deleted", "base": "bc9ae9171fc4b0e2cac2681b026085fa47c420b9", "local_head": "ee2e2544dba52956e5cc1b516f5b95a9971bceaa", "merged_head": "d1df9f1d3fdc08b8018524026364f904d9b2665b", "missing_tree_object": null}
$ /opt/homebrew/opt/python@3.14/bin/python3.14 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/path-provenance-deleted/wt d1df9f1d3fdc08b8018524026364f904d9b2665b
exit=1
ee2e2544dba52956e5cc1b516f5b95a9971bceaa:f.txt
one
d1df9f1d3fdc08b8018524026364f904d9b2665b:f.txt
one
ee2e2544dba52956e5cc1b516f5b95a9971bceaa:g.txt
fatal: path 'g.txt' does not exist in 'ee2e2544dba52956e5cc1b516f5b95a9971bceaa'
d1df9f1d3fdc08b8018524026364f904d9b2665b:g.txt
one
anchor
tail1
Evidence: Live hidden nested-submodule cleanup refusal

Source: Live hidden nested-submodule cleanup refusal

$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-lab-home.sh create ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/dirty-live/lab
~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/dirty-live/lab
exit=0
Ordinary Git status hides the nested untracked source:
$ git -C ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/dirty-live/wt status --porcelain
exit=0
$ git -C ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/dirty-live/wt/module/nested status --porcelain
exit=0
$ ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-teardown.sh nested
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/dirty-live/lab
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  WATCHER DOWN - SUPERVISION IS OFF
●  1 task(s) in flight, but no watcher has a fresh beacon (last beat: never, grace 300s).
●  Trust the emitted supervision protocol for this harness; do not use shell & for watcher repair.
●  This is a supervision warning only; the guarded operation WILL still run.
●  repair missing watcher supervision according to the session-start block for this harness; do not use shell &.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
REFUSED: local-only worktree ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/dirty-live/wt has work not yet merged into main and not on any remote.
uncommitted changes present (includes tracked edits)
Merge the branch into local main first (bin/fm-merge-local.sh after the captain approves), or push to a fork/remote, or get the captain's explicit OK to discard, then --force.
exit=1
Preserved source bytes, exact task record and task head after refusal.
$ python3 ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-content-containment.py ~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/dirty-live/wt 62895498dddcbd8700ac8ae43d20bd9df85d2470
exit=0
Evidence: Generated provider-aware release-target instructions

Source: Generated provider-aware release-target instructions

You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
## Captain's intent
{TASK}

## Firstmate spec
{FIRSTMATE_SPEC}

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text filled in above.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

# Setup
You are in a disposable git worktree of disposable-project, at a detached HEAD on a clean copy of its base branch.
Base branch: release/1

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable task worktree you were launched in, such as a treehouse pool path or an Orca-managed worktree, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked [at=<epoch>]: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b users/example/release --`

# Rules
1. Never push to the base branch `release/1` or the default branch (push only your `users/example/release` branch). Never merge a PR.
2. Keep project edits inside this worktree; keep proof and scratch output outside it, under `~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/data/release/` or a temporary directory.
   Outside the worktree, write only that task material and the status and steering-inbox records authorized below.
   Leave the worktree clean before reporting done.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state} [at=<epoch>]: {one short line}" >> '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/state/release.status' && { [ ! -e '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/config/fleet-ledger' ] || '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-fleet-ledger.sh' appended '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/config' '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/state/release.status' >/dev/null 2>&1 || true; }`
   States: working, needs-decision, blocked, paused, done, failed.
   Substitute `<epoch>` with the current Unix time in seconds - run `date +%s` and write the number it printed; a stamp that is not plain digits records no time at all.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
   Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full
   https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate
   copies that URL from your line rather than assembling one.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - when deliberately waiting for work or an external condition expected to clear on its own, including your own validation round.
   Before ending your turn with your own background shell or monitor still running, or before waiting on your own pipeline run or a long foreground command, append `paused [at=<epoch>]: {job and completion condition}` to the status file.
   Name what you are waiting for and what will let you resume; do not repeat the declaration on every poll.
   Do not declare active implementation or reasoning as a wait.
   Firstmate may still raise one first-sight alert; the declared wait then uses the existing long recheck cadence instead of repeated possible-wedge alarms.
   When you know when the wait clears, include `until <YYYY-MM-DDTHH:MMZ>` (UTC) for a recheck at that time; for your own background job or command with a timeout, that time is its start plus its timeout, rounded up to the minute.
   Follow the resolution rule below when the wait clears, then resume the task.
   Use `blocked:` when you are stuck and need help.

5. If you hit the same obstacle twice, append `blocked [at=<epoch>]: {why}` and stop; firstmate will help.
6. If a decision belongs above the implementation worker (product choices, destructive actions),
   append `needs-decision [at=<epoch>]: {summary of options}` and stop. Firstmate will reply with the decision.

   A decision or blocker you opened stays open until a `resolved` line carrying its exact key lands; a later `done:` or `working:` line never closes it, even when the answer is what started that work.
   Firstmate's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a firstmate reply, append `resolved [at=<epoch>]: {how it cleared}` yourself (same `[key=<slug>]` if you opened it with one) as you resume.
7. Never administer infrastructure that every lane shares. Two things are shared:
   - The `no-mistakes` daemon - one instance serving every lane/home, so stopping, restarting, or
     updating it kills other lanes' in-flight pipeline runs; only firstmate manages the daemon.
     Before you append `blocked:` about the pipeline, run `no-mistakes daemon status` and
     `no-mistakes axi status`. If the daemon socket refuses connections or is missing, append
     `blocked [at=<epoch>]: {the daemon error}` and stop even when the local run record still says running or
     fixing, because that record can be stale after the daemon exits. A run record failed with a
     daemon error is also a real block.
     Only after ruling out socket refusal, if the run is still running or fixing, reattach and keep
     going. A drive-call error, timeout, slow read, or generic unreachability is NOT a daemon error:
     the daemon accepts `respond` immediately and runs the round in the background, so a killed or
     timed-out call was only waiting for a read while the run kept working.
   - The worktree pool your own worktree came from, and the repository every lane's worktree
     shares. Never create, remove, return, prune, move, or reassign a worktree or pool slot, and
     never write into a sibling slot's directory. Rule 2 does not cover this: removing a worktree
     is administration rather than an edit outside your directory, and it lands on lanes that are
     running right now. The act is the rule and commands are only examples of it - `treehouse`
     get/return/remove/prune, the equivalent operations on any other worktree provider or runtime
     backend, and `git worktree add|remove|move|prune`. A slot that looks unused is not evidence
     that it is free, and returning your own worktree is firstmate's job at cleanup, not yours.
   If you genuinely need a second checkout, another slot, or the daemon touched, append
   `blocked [at=<epoch>]: {what you need}` and stop; firstmate arranges it.
8. Never start a shell command with `cd`, and never run a search whose path depends on the current directory.
   Use absolute paths, `git -C <abs>`, or a `( cd <abs> && ... )` subshell.
   Pass this rule to every sub-agent you launch.

# Firstmate instruction inbox
Firstmate steers you through durable message files in '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/state/release.inbox'.
When a terminal message says an instruction is waiting there - and at any natural checkpoint when you are unsure - list them with `find '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/state/release.inbox' -maxdepth 1 -name '*.msg' 2>/dev/null | sort -V`, read the complete contents of each record (including its final line even when it is not newline-terminated) and act on each message in numeric order, then acknowledge each handled message by moving it: `mv '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/state/release.inbox'/NNN.msg '~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/state/test-validation/live/lab/state/release.inbox'/handled/`.
Prefer the format-aware `fm_task_inbox_body` reader from `bin/fm-task-inbox-lib.sh` when it is available. If you read manually, a header line `body-terminator=1` means the writer added exactly one synthetic final newline to terminate the record file; that marked newline is not part of the logical message body, so remove only it and preserve any intentional trailing newlines. Legacy records without that marker may end without a newline; read them through the file end.
The move IS the acknowledgement: without it firstmate rings again and eventually treats you as stuck. An empty or absent inbox needs no action.

# Project memory
A project's `AGENTS.md` or `CLAUDE.md` is loaded into every agent session in that project, so edit it only to correct information that is factually wrong - including information your own change made wrong - and never to add knowledge because it is missing.
A correction edits only the wrong text: do not run `~/dev/firstmate/data/ado-pr-support/nm/worktrees/324a78b3a755/01M6PM8Z12CR1T9WRTP3CZXSCJ/bin/fm-ensure-agents-md.sh`, create either file, or add sections, headings, or pointers alongside it.

# Definition of done
Delivery contract: mode=direct-PR
Ship branch: users/example/release
This task ships **direct-PR**: you raise the PR yourself, without the no-mistakes pipeline.
The task is complete only when committed on your branch.
When it is implemented and committed, push your branch and open a PR with the forge's CLI (`gh-axi` for GitHub, `az repos pr` for Azure DevOps) that is ready for review, not a draft, against the base branch `release/1` (`--base release/1` on GitHub; `--target-branch release/1` on Azure DevOps), not the repository default.
Before you report done, read the PR back from the forge and confirm it is not a draft; if it is a draft, mark it ready with that forge's CLI.
On GitHub, `gh-axi pr view <number>` must print `draft: no` and `gh-axi pr ready <number>` marks it ready, where <number> is the PR number from your PR URL.
A draft cannot be merged, so a done report on one leaves the merge unasked.
Then append `done [at=<epoch>]: PR {url}` to the status file and stop.
That `done:` is accepted only when this copy's HEAD - your latest commit - is pushed to your PR branch; the check tests that commit, not merely that a branch moved.
If you deliberately keep the PR a draft, append `paused [at=<epoch>]: {why the draft is held}` instead of done.
Do NOT run /no-mistakes. The configured merge authority decides whether to merge the PR; firstmate relays the outcome.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 11 issues found → auto-fixed ✅
  • ⚠️ README.md - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-dod-lib.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-pr-check.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-pr-lib.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-pr-merge.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-pr-poll.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-teardown.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-test-run.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-pr-merge.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-review-diff.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-teardown.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

🔧 **Review** - 5 issues found → auto-fixed ✅
  • 🚨 bin/fm-content-containment.py:285 - The previously accepted path-provenance fix is still absent. Concrete sequence: B lacks g; local C edits f; upstream U adds g containing 'zero\nanchor\ntail0\n'; M merges U into C; local D changes g's first line to 'one'. Publication rebases onto U2, which independently changes g's last line to 'tail1', then squash-merges. All local edits landed, but histories includes B/C where g is absent, so containment returns false. Removing this check alone is insufficient: unchanged B→C comparisons also erase g's anchors. Build path-specific obligations from actual local changes while retaining restoration protection. Related sites: bin/fm-content-containment.py:265 (unfiltered history), :292 (every comparison supplied for every path), :120 and :133 (irrelevant versions constrain anchors); bin/fm-teardown.sh:1656, :1774 and :1800 (Azure, PR and branch-content consumers).
  • 🚨 bin/fm-teardown.sh:2181 - The previously accepted nested-untracked fix is still absent. Recursive status does not request --untracked-files=all, so an initialized nested module with status.showUntrackedFiles=no and only an untracked source file reports clean. Ancestor submodule ignore settings can hide it from the superproject check too. With committed work landed, cleanup can proceed toward forced return despite that uncommitted file. Request all untracked files at every recursive level, matching bin/fm-teardown.sh:2168. This shared guard must cover the initial check at :3775, stale-lock retry at :3782, post-reaper check at :3928 and return-retry callback at :4075. The regression at tests/fm-teardown.test.sh:823 currently exercises a tracked modification, not this hidden-untracked case.
  • 🚨 bin/fm-teardown.sh:2159 - The previously accepted missing/reassigned-copy fix is still absent. Both callers at bin/fm-teardown.sh:3775 and :3928 require an owned, existing directory before entering this function. Consequently a non-Orca ship with an active, abandoned or unreadable registered Azure PR bypasses completion verification when its copy is missing or its pool slot was reassigned. Cleanup can then remove monitoring at :4165 and close the backlog item at :4224. Enforce registered Azure completion at the task-record cleanup boundary independently of copy ownership/existence. Keep content and dirt checks restricted to an owned, present copy; do not inspect another task's reassigned slot. The completion read at :1651 and missing-copy return at :1652 currently cannot protect these callers.
  • 🚨 bin/fm-pr-merge.sh:1884 - Pinned Azure completion is unusable: a valid fm-pr-merge.sh task URL --expected-head H forwards --expected-head H, but bin/fm-azure-pr.py:306-308 accepts only action and URL. Argument parsing therefore rejects even a matching, fully approved revision before verification or PATCH. This contradicts the recorded requirement to preserve the 'expected-head, branch-prefix/base and safety contracts' and the changed header promising Azure support at bin/fm-pr-merge.sh:60 and :156. Restore the required contract by accepting and validating the optional expected head in the helper, comparing it with the verified source, and retaining that exact value through completion—not by dropping the flag. Related boundaries: bin/fm-azure-pr.py:182 (verification) and :289-292 (completion/CAS).
  • ⚠️ bin/fm-teardown.sh:1730 - An unavailable PR containment proof can be mislabeled as unlanded work. For example, the PR proof exceeds the alignment bound, setting CONTENT_LANDED_PROOF_ERROR here; the subsequent branch-content fallback clears it at bin/fm-teardown.sh:1790, then returns early because the selected branch cannot be fetched. The final refusal at :2229-2233 now claims 'not landed' instead of preserving the unavailable-proof classification. Retain unknown-proof evidence across failed fallback attempts and distinguish it from a completed negative proof. Related paths: :1774 and :1809 (PR-to-branch fallback), :1656 (Azure's equivalent fallback), and :1800 (shared proof invocation).

🔧 Fix applied.
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 warning
  • ⚠️ live validation verdict: inconclusive (7 of 10 scenarios were driven live against the product); untested: Register, review, and monitor a real Azure PR across active, completed, and abandoned states, Complete a real Azure PR only after current mandatory approvals and checks pass for the pinned head, Confirm real Azure completion before cleanup, including missing or reassigned local copies
  • Live validation: ⚠️ inconclusive - 7 of 10 scenarios driven live against the product
Scenario Result Live Evidence
Select a personal ship prefix while retaining ordinary local-only naming and automatic-review instructions ✅ pass live Live CLI evidence and generated personal/default/local-opt-in briefs.
Reject unsafe branch prefixes before creating task instructions ✅ pass live live-cli.txt: traversal, doubled-separator, and .lock prefix refusals.
Review the recorded personal branch after the checkout moves back to main ✅ pass live live-cli.txt: recorded-branch review output.
Recognize rebased and squash-merged changes despite independent upstream edits ✅ pass live live-cli.txt and live-results.json: rewritten-history, merge-main, large-file, and path-provenance cases.
Reject containment for later local restorations, deletions, replacements, or partial landings ✅ pass live live-cli.txt: negative history cases; teardown-proof-product.txt: restored-path cleanup refusal.
Report unavailable containment evidence instead of accepting an incomplete proof ✅ pass live live-cli.txt: missing-tree and alignment-bound diagnostics; teardown-proof-product.txt: fallback classification controls.
Preserve nested untracked source hidden by Git status and ancestor ignore settings ✅ pass live live-nested-dirt.txt.
Register, review, and monitor a real Azure PR across active, completed, and abandoned states ⏸️ untested no Synthetic transport covered registration, source selection, polling, and expired-monitor dispositions. Although az is on PATH, no approved real PR URL or repository scope was supplied, and operator da…
Complete a real Azure PR only after current mandatory approvals and checks pass for the pinned head ⏸️ untested no Synthetic public-entrypoint checks passed for matching/mismatched expected heads, mandatory policies, optional statuses, revision races, and unconfirmed completion. Real Azure mutation remains explici…
Confirm real Azure completion before cleanup, including missing or reassigned local copies ⏸️ untested no Disposable Git repositories and synthetic Azure responses verified retained monitoring/backlog for active, abandoned, and unreadable PRs, and preservation of reassigned work. Real containment and dirt…
  • git status --short, targeted diffs, and interface documentation inspection for b77b66cb999876c4db19da36b9af5baaacdd52e5..b7aa4b93c8e44f4d0d13e12ffccce21e8245843f.
  • python3 tests/azure-pr-contract.py -v: interrupted at the 850-second bound after 31 passing methods; the two remaining methods passed in a separate targeted invocation.
  • tests/fm-task-delivery.test.sh selected functions: project prefix selection, delivered definition of done, persisted promotion branch, shell-safe promotion command, and registry prefix resolution—all passed.
  • bin/fm-test-run.sh --jobs 1 --per-script-timeout-secs 300 tests/fm-brief.test.sh tests/fm-review-diff.test.sh: incomplete due to command bounds; captured assertions passed before interruption.
  • tests/fm-brief.test.sh focused rerun: test_base_branch_is_rendered_and_bounded, test_ship_branch_prefix_defaults_to_legacy_fm, and test_branch_prefix_command_is_shell_safe—passed.
  • python3 state/test-validation/live-checks.py: executed generated branch commands, reviewed the recorded personal branch, and drove the real containment executable against 24 disposable Git histories—all passed.
  • python3 state/test-validation/live-dirty.py: real cleanup CLI refused hidden nested-submodule source changes and preserved file bytes, task head, and metadata.
  • tests/fm-teardown.test.sh focused reruns: missing/reassigned-copy Azure completion guards, nested tracked/untracked/clean controls, unavailable-proof fallback classification, and path-provenance-created/restored cleanup—passed. The initial larger selected batch exceeded its bound.
  • python3 tests/containment-contract.py -v: independent alignment and history-oracle checks passed.
  • Captured public CLI responses, generated briefs, synthetic completion requests, cleanup diagnostics, and retained-state evidence. A supplemental Azure transcript-capture batch was interrupted after four passing methods; its cases had already passed in the primary contract run.
  • Removed this turn's disposable homes, repositories, drivers, and caches; confirmed unchanged tracked files and HEAD b7aa4b93c8e44f4d0d13e12ffccce21e8245843f.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: re-opening to re-queue CI — this tip never received a workflow run. Conflict vs main remains; not merging.

@kunchenguid kunchenguid reopened this Sep 15, 2026
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

First look on HEAD 0fdf41c46ea8aaed76cea6d866aa0b563a6c0426 vs main (pass start 8b10b61e…; tip also behind after #4259 merge). CONFLICTING / DIRTY. Author martinstroher not blocked. Tip today — not stale.

Contract-class: opt-in. Azure DevOps PR transport (bin/fm-azure-pr.py) and project branch-prefix support activate only for Azure remotes / explicit --branch-prefix; GitHub/GitLab default paths stay as-is. Aligns with “fleet outlives any vendor.”

VISION.md per-rule (inspected bin/fm-azure-pr.py, PR lib/merge/poll/check/teardown/review-diff wiring, docs/azure-devops.md, contract tests):

  • One captain, one interface — aligns (same PR workflow outcomes for Azure forges).
  • Authority explicit — aligns (non-bypass completion; required reviewers/policies re-checked before PATCH).
  • Scripts own mechanics — aligns (az devops invoke transport + refusals on partial continuation pages).
  • Restart non-event — aligns (poll/teardown confirmation before cleanup).
  • Delegation with spine — aligns (teardown preserves work when Azure completion unproven).
  • Fleet outlives vendor — aligns (new forge adapter, not a harness lock-in).
  • Scope — aligns (delivery forge support in the command layer).

Attestation: MATCH (body binds tip). CI never queued on this head (0 workflow runs / 0 check suites) even after close/reopen and a no-op body PATCH — cannot approve what does not exist. Require no-mistakes likewise absent.

Blockers: merge conflicts vs main; no CI/NM to green. Not otherwise completely auto-merge-ready, so not resolving conflicts from here. waiting-captain / Firstmate flag: close-vs-leave vs conflict resolution path, and how to get Actions to attach to this fork tip.

Security skim: subprocess → az devops invoke with ephemeral PATCH file; continuation_token incompleteness refused; no beads / no credential exfil in diff.

@martinstroher martinstroher changed the title feat: support Azure DevOps pull requests in the PR workflow feat: support Azure DevOps PRs and safe rewritten-history cleanup Oct 7, 2026
@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[High impact] Both numbered findings are fixed, and no new blocking issue was found.

Reviews (5) · Last reviewed commit: "no-mistakes(ci): Fixed both selected fin..." · Reviewed by Greptile

Comment thread bin/fm-azure-pr.py Resolved
Comment thread tests/fm-task-delivery.test.sh Resolved
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate (OSS triage, not the captain).

Thanks for the big rework, @martinstroher. I re-read the whole thread and inspected the new tip against main 99da16d9. The tip is 37 ahead and 1 behind, touching 30 files.

What changed since my 2026-09-15 stamp (head 0fdf41c4):

  • New head: 86137d8c.
  • Conflicts: resolved. GitHub now reports MERGEABLE (UNSTABLE).
  • Actions: they now attach to the fork tip. Both runs were held for first-time approval. I reviewed the diff for security: there are no workflow edits, no secrets handling, and no network fetches beyond az devops invoke/git fetch. I approved CI 37556715450 and Require no-mistakes 37556715377.
  • Attestation: MATCH.
  • Greptile: the P1 "optional statuses block completion" is fixed in 86137d8c. The P2 about local-only branch prefixes is open, and you declined it with reasons in the commit message.

Contract-class: new-default (changed from my earlier opt-in)

The Azure DevOps forge path itself is still opt-in. It only engages for Azure PR URLs and Azure origins, plus an explicit --branch-prefix/project branch=.
But the tip now also replaces the default teardown "has this work landed?" proof for every project, GitHub included:

  • fm-teardown.sh drops the patch-id (unpushed_patches_are_in_pr_head) and merge-tree content checks. It now proves containment with a new bin/fm-content-containment.py against the PR's merge commit (mergeCommit, previously headRefOid) and against the default branch.
  • It adds a recursive submodule-dirty refusal, an Azure-origin refusal when no PR is recorded, and a different fetch order in ensure_commit_object.
  • Crewmate briefs (fm-dod-lib.sh) now name a forge CLI instead of gh-axi only.

So the unconfigured run now cleans up rebased or squash-merged worktrees that main refused, and refuses some cases main accepted. That replaces the specified default teardown path, which makes this new-default. Teardown is destructive (hard-reset, worktree removal), so the captain decides.

VISION.md, rule by rule

  • One captain, one interface: aligns. Azure PRs get the same outcomes.
  • Authority is explicit: aligns for Azure. Completion is attended-only and non-bypass, with --allow-red/--allow-missing refused and approvals and policies re-checked before the PATCH. The new auto-cleanup of rewritten history is a default change, which is the captain's call.
  • Scripts own the mechanics: aligns. Deterministic containment proof that fails closed on unknown (exit 2 refuses).
  • A restart is a non-event: aligns.
  • Delegation with a spine: cannot tell, captain call. "Unlanded work is never torn down." The new proof claims to be stricter about restorations but looser about rewritten history. That needs the captain's judgment on default teardown.
  • The fleet outlives any vendor: aligns. A new forge adapter.
  • Scope: aligns. Delivery-forge support in the command layer.

Where this stands

waiting-ci. CI and no-mistakes are queued on 86137d8c. This does not auto-merge. If everything goes green on this head, it becomes a captain merge-or-leave decision on the default-teardown change, and I'll flag it then. No action is needed from you right now beyond keeping CI green.
FYI: the body's Intent section includes some conversational setup lines ("i prefer if we used my personal gh account…"). You may want to trim them. They don't affect triage.

martinstroher and others added 19 commits November 5, 2026 17:04
…unchenguid#5427)

Speaking as Kun's firstmate: squash-merging — opt-in (forge=gerrit registry-gated; default project-mode stdout restored to two words), attestation MATCH, CI+NM green, safe review, MERGEABLE.
* fix(bin): use gh-axi for the ship DoD draft check

* no-mistakes(review): use PR number not URL in gh-axi draft check
martinstroher and others added 20 commits November 5, 2026 17:07
…-azure-pr.py that treated every Azure status as mandatory. Required statuses remain selected by enabled, blocking policy evaluations and must uniquely match the current iteration with successful results. Required approvals, policies, builds, revision-race guards, and non-bypass completion remain intact. Updated the corresponding documentation. Behavioral evidence: the guarded merge CLI previously refused optional pending, failed, stale, and unbound statuses while absent/green controls succeeded. Those cases now pass using synthetic transport. New regressions failed before the fix and pass afterward. A one-condition isBlocking counterfactual confirms the same failed status blocks only when mandatory. ci-4 declined; no implementation or test changes for this finding. bin/fm-project-mode.sh:152,166 defaults to fm/ and changes it only for an explicit project branch= annotation; bin/fm-brief.sh:196 and bin/fm-promote.sh:69 retain fm/ defaults; bin/fm-merge-local.sh:97-98 honors the recorded branch with the legacy fm/ fallback. Disposable-repository CLI tests verified default and explicit-prefix branch creation, promotion, and local landing. An unrelated project remained on fm/. Verification passed: all 29 Azure contract tests, all 28 delivery-contract test groups, documentation structural checks, and git diff --check. Before/after evidence and detailed dispositions are retained in state/ci-review/report.md and adjacent logs. ci-1 and ci-2 remain pending maintainer workflow approval. No approval was requested, granted, waived, or bypassed; no maintainer contact, workflow/authentication changes, live Azure mutations, real PR merge, activation, or pipeline control occurred. Live-Azure validation remains inconclusive and broad-suite coverage remains incomplete
…he exact tested merge c50c34d13479108a14dbeb26f6af0680a28dcb9d, comparing its actual base 99da16d with source 86137d8. Parallel 2, job 112600382607: confirmed a PR assertion regression, not a generated branch-targeting defect. The real brief CLI emits the correct base metadata, safety rule, GitHub --base and Azure --target-branch options. Two expectations still required the old GitHub-only wording. Updated only tests/fm-brief.test.sh to require both provider targets and preserved shell quoting, without relaxing existing safeguards. The original full suite passed on the actual base, failed on the tested merge with the reported error, and passed after the fix. Generated options preserved literal release/$HOTFIX when executed through Bash. One-condition mutations introducing incorrect GitHub/Azure targets or missing Azure quoting were rejected. The full fm-dod-lib suite, pinned targeted ShellCheck, and git diff --check also passed. Serial 2, job 112600382954: classified as inherited runtime variability exhausting the 30-minute job budget, not an established PR regression. Its host suite passed in 1615.124s; four later scripts passed before cancellation during board rendering. Historical CI on the actual base passed the unchanged host suite in 1409.781s and completed the lane. Its predecessor e06a46f passed the same host suite in 1570.991s but also suffered a serial-job deadline cancellation without this PR. All three host executions reported the same 73 successful checkpoints. Actual-base and tested-merge lane membership/order are identical. No speculative product, workflow, timeout, or scheduling change was made. Local serial counterfactuals remain explicitly inconclusive: base and tested merge both failed at the same dialog-wake fixture wait, and standalone board-render attempts both failed at fixture listener startup. These are retained as limitations, not passes or equivalents of the CI cancellation. The precise infrastructure mechanism behind timing variance is unproven. Evidence, reproduction scripts, complete logs, disconfirming results, and applied patch are preserved in ignored state/ci-046/report.md and adjacent files. CI is not declared green and no check was waived. Live-Azure validation remains inconclusive and broad-suite coverage incomplete. No live Azure mutation, merge, activation, publication, maintainer request, configuration change, or pipeline-control operation occurred; the outer executor owns publication and subsequent CI
@martinstroher martinstroher changed the title feat: support Azure DevOps PRs and safe rewritten-history cleanup feat: add Azure DevOps PR support and safer merge cleanup Nov 6, 2026
Comment thread bin/fm-dod-lib.sh Resolved
Comment thread bin/fm-teardown.sh Resolved
…st/<n> reports now enter the existing published-head check; behavioral tests reject unpublished work and accept remote-published or forge-recorded heads across modern and legacy Azure URLs. ci-2: the shared backlog converter records Azure PRs as notes, covering completion and sibling replay/retention paths. A behavioral cleanup test confirms a completed Azure task closes its active backlog item. Existing GitHub, GitLab, Gerrit and Forgejo cleanup cases passed. The full fm-dod-lib test script, pinned ShellCheck 0.11.0, shell syntax checks and git diff --check passed. Earlier bounded batches timed out; focused reruns passed, but the full teardown/broad suite was not completed. Live Azure validation remains untested/inconclusive. No workflow changes, pipeline control, live Azure mutation, merge or activation occurred. Evidence: state/ci-ready-backlog/result.md
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: tip moved to 38c77df2 since the 2026-10-07 waiting-ci stamp. I re-read the thread and inspected it against main 8b1618a4.

What is new: attestation now binds 38c77df2 with every step completed (pr/ci in flight). Greptile is 5/5 on this head; the prior P1s (optional Azure statuses blocking completion; DoD Azure URL / backlog note gaps) are resolved with no open threads. MERGEABLE, 1 behind / 40 ahead; any conflict is ours. Live validation is still disclosed as inconclusive 7/10 — the three untested rows are real-Azure PR lifecycle/completion/cleanup, covered synthetically; same accepted limitation as last stamp. I reviewed the fork diff (Azure transport + teardown containment; no workflow/credential/installer edits) and approved CI 42139785905 and Require no-mistakes 42139785982.

Contract-class: new-default (unchanged). The Azure forge path itself stays URL/origin gated, but the tip still replaces the default teardown "has this work landed?" proof for every project (including GitHub) via fm-content-containment.py against the merge commit. That is a new unconfigured destructive path, so Kun decides.

VISION.md, rule by rule

  • One captain, one interface: aligns — Azure PRs get the same outcomes language.
  • Authority is explicit: aligns for Azure completion (attended); teardown change is why this is new-default, not a silent widen of standing autonomy beyond what the class already flags.
  • Scripts vs agents: aligns — forge/teardown mechanics stay in scripts.
  • A restart is a non-event: aligns.
  • Delegation with a spine: aligns — ship path stays evidence-backed.
  • The fleet outlives any vendor: aligns — another forge adapter.
  • Scope: aligns — command-layer forge support, not a hosted service.

Next: waiting on the freshly approved CI / no-mistakes runs, not on you. Path when green: new-default → captain flag after Full (draft only until then).

yterry added a commit to yterry/firstmate that referenced this pull request Nov 7, 2026
A project hosted on Azure DevOps Services could not ship through a pull
request, because Firstmate recognized pull request links only from
github.com, GitLab, Gerrit, and Forgejo. An Azure DevOps pull request now
gets the same lifecycle: the worker opens it, Firstmate records the link
it reports, watches it, completes it when approved, and confirms the work
landed before cleanup.

Both Azure DevOps Services URL forms parse, dev.azure.com and
<org>.visualstudio.com with or without DefaultCollection, keeping the URL
as written and validating project and repository as canonical
percent-encoded segments. Because a pull request id is unique per
organization, every az read binds the answer to the URL's project and
repository.

forge=azure-devops joins the closed registry binding; detection proposes
it from an Azure DevOps origin host and the captain confirms it. The
binding changes only the worker contract, which names az repos pr for
opening, draft read-back, and the reported web URL.

Completion verifies the pull request is active, not a draft, merges
cleanly, and every applicable branch policy passes at the live source
commit, then sends one az rest PATCH naming that commit as
lastMergeSourceCommit, never bypassing policy or deleting the source
branch, behind the existing merge-authority and green-check guards.
Teardown proves landing from the completed record and the source commit,
which holds for squash completions; other forges keep their cleanup
proof. Review diffs compare the source commit behind refs/pull/<id>/merge.

Builds on the open work in kunchenguid#4543 and kunchenguid#4871, which conflict with current
main; docs/azure-devops-integration.md records the live verification.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants