fix(pm): make remote reachability a measured precondition of the governed-merge sweep - #13421
Merged
os-project-manager merged 3 commits intoAug 30, 2026
Merged
Conversation
…rned-merge sweep A governed repo whose checkout is present and healthy but whose remote is unreachable was audited from the local snapshot and reported `0 mainline commit(s) in window` — a false zero that could never advance, because the remedy the row prescribed was the command that failed. One `git ls-remote` per repo now answers reachability and mirror freshness together, feeding the existing UNAUDITED refusal register rather than a second mechanism. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
…own call site Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
…he first Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
This was referenced Aug 30, 2026
os-project-manager
marked this pull request as ready for review
August 30, 2026 11:39
os-project-manager
enabled auto-merge
August 30, 2026 11:39
os-project-manager
deleted the
claude/issue-13307-governed-merge-reachability
branch
August 30, 2026 12:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13307
The defect, and the precondition the card's repro needs
A governed repo whose checkout is present and healthy but whose remote is unreachable was audited from the local snapshot. Every clause of the row was literally true about that snapshot, and the conclusion a reader draws — no governed merge happened there in this window — was unsupported. It could never correct itself: the remedy the row prescribed,
git fetch origin main, is exactly the command that fails./home/user/cloudclone at all — there the row readsno git checkout, an honest refusal. Baseline measured at this branch's base, unmodified: exit 1, all four rows refusing (two on the #9902 shallow-history guard, two on absent checkouts). So the symptom was reproduced synthetically, against the unmodified script, with a checkout whose origin bare repo had been deleted:exit 0, while
git ls-remoteagainst that same remote exits 128. The sweep did not merely call the repo quiet — it reported having scanned two commits and found the window clean, over a remote it never consulted.The shape: a parallel leg in the same refusal register
The PM's premise note read the existing shallow-history refusal as the path to extend, flagged as a hypothesis from one run rather than from the code. Reading the code, the register is the right thing to reuse and
historyHorizonis the wrong thing to extend — so this takes the parallel leg, as that note's Zone 2 allowed. Three reasons, in the order that decided it:historyHorizonruns only where a DATE window is in force — a topological--since-refwindow answers its own completeness question and skips it. Keyed onto that branch, reachability would be silently absent from exactly the invocation the card's own re-check command uses.historyHorizonlives ingit-history.mjs, shared with other adopters precisely so the predicate cannot drift, and zero-network by construction.What is reused is the register itself:
status: 'unaudited'plus a stated reason, rendered⚠️ UNAUDITED, counted by the #4690 note, classified INCOMPLETE by the existing exit contract. No second mechanism, no new exit code, no new report section, no new flag.What it measures
One
git ls-remote --exit-codeper otherwise-auditable repo — git wire protocol, so the API budget is unchanged — asked before enumeration, in every window mode. It answers both halves at once:NOT MEASURED⭐ The second row is the card's requirement 3 answered more sharply than it was asked. A tip date older than the window cannot separate a quiet repo from a dead mirror — which is why the old row could only hedge, "if that tip predates your last fetch, run
git fetch". Tip identity separates them exactly, so that hedge is now a reading and the advisory is gone.⛔ Not done, per the card's point 4 and the dispatch ruling: dropping
cloudfrom the governed-repo list. That trades a loud hole for a silent one. Whether the repo is still in scope is a maintainer question, and until it is answered the audit's honest reading is NOT MEASURED.✓ auditedwithout it would reintroduce the false green.Non-vacuity control — synthetic fixtures, five legs
objectoshas no checkout,objectuiis behind the shallow refusal, and the stalecloudsnapshot is absent. The fixtures are local bare repos whose paths end in the GitHub-shaped segments, so the governed slug parses whilegit ls-remotecan actually reach them. ⛔ No--unshallowor--shallow-sincewas run against any shared checkout.All five at head
47535e81:✓ audited✅ clean window--since-ref0 mainline commit(s) in window … a MEASURED zeroAGENTS.md+skills/**committhis mirror is BEHIND its remotenaming both tipsThe third leg matters twice: it is the true-zero requirement, and it is the proof that the reachability leg runs in topological mode, where the horizon guard is skipped entirely.
Ablations — both legs rebuilt, both mutations proven on disk, restore proven
Run from a committed state, mutated by an anchor-checked splicer that aborts on anything but exactly one match, restored by a trap pinned to an absolute path and
HEAD(never a bare checkout, which reads the index). Each mutation proven on disk by agit hash-objectcomparison with both sides non-empty; each restore proven by the blob equalling theHEADblob andgit diff HEADbeing empty.return null: self-test exit 1, 11 named failures across the pure verdicts, the report words, the live prober and the end-to-end sweep.⭐ Ablation B is the measurement that earned an extra pin. Every assertion on a pure verdict or on the prober stays green when
main()simply stops consulting them — an instrument that cannot fail in the direction it exists to detect, which is this card's own subject one level down. So the call site is now pinned by running the sweep against the fixtures, both directions. That claim is written into the source as measured, and B is the measurement.Predictions I got wrong, recorded
0 mainline commit(s) in windowplus the stale-tip advisory. It printed2 mainline commit(s) in windowwith no advisory — the seed commits were inside the window. The false green was stronger than predicted, not weaker.TypeErrorat the third new assertion instead, because two assertions dereferenced.reasonon a verdict whose job is to be non-null. Still red, but a self-test that dies at the first casualty reports neither green nor red for everything after it — the [finding] lint.yml's shallow-history step runs three self-tests underbash -e— a failure in the first silently prevents the other two from running at all #10814 collector lesson in a different hat. Fixed in the second test commit; A then produced the 11-failure list above.A third correction came from a red run worth keeping: the end-to-end assertion first failed because the refusal's own reason quotes the string
✓ audited … 0 mainline commit(s) in windowwhile explaining what it refuses to print, so a bare substring test read the explanation as the symptom. The row test is now line-anchored, matching the file's existing "assert on the tick, not the phrase" pin.One wording fix came out of the fixtures too: git's remote failures end on boilerplate, and quoting the last stderr line put the fragment
(and the repository exists.)inside a NOT MEASURED row. The reason now quotes the first line git marks as the fault.Verification
pnpm check:pm-governed-merges(this script's own self-test, and its whole test suite): 228 assertions, exit 0 — up from 204 on the base file measured the same way.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackat47535e81, not from the dispatch brief. 17 green, including both convention-triggered obligations the path derivation cannot name and the brief did not carry —bare-root-worklist --self-testandcheck:pm-dispatch-gates, both incurred by editing a gate script.node scripts/check-test-completeness.mjsexits 3, its ownPREREQUISITE NOT MET— it grades a saved turbo test log that only CI produces. Not a finding, and not a measurement either way.pnpm lint(repo-wideeslint . --no-inline-config): exit 0, 68 s, run under the shared verify lock at this head. No narrowing, so no narrowing argument is owed.No changeset: the diff is exactly one file under
scripts/pm/, which publishes nothing from any package — theskip-changesetclosed list. The label is applied on this PR.Generated by Claude Code