Skip to content

fix(coord): make the claim gate satisfiable from a second repository (BACKLOG #1346) - #798

Merged
wshallwshall merged 4 commits into
mainfrom
worktree-agent-a9051692500267199
Sep 4, 2026
Merged

fix(coord): make the claim gate satisfiable from a second repository (BACKLOG #1346)#798
wshallwshall merged 4 commits into
mainfrom
worktree-agent-a9051692500267199

Conversation

@wshallwshall

@wshallwshall wshallwshall commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Closes BACKLOG #1346.

The unpassable gate reproduced, by execution, before anything was changed

tests/test_claim_shared_registry.py git-inits two independent checkouts, claims
from one and commits in the other. Before the fix: 5 of 6 arms red, and the
single-repository arm GREEN in the same run
as the positive control that both
the harness and the gate work.

The measured refusal was is NOT CLAIMED -- not claimed by ANOTHER worktree. That is the proof the gate was reading an empty registry rather than
adjudicating one, and it is why the "must fail" control arm asserts the refusal
TEXT: without that, the arm is green and blind, because a gate that cannot see
refuses everything and scores as a gate that works.

Re-measured rather than trusted. The row's engine-side line numbers all hold:
claim.ps1 anchors $repo on $PSScriptRoot and derives the claims directory
from it; claim_check.py::_claims_dir resolved the committing tree's own
git-common-dir; install-git-hooks.ps1 anchors RepoRoot on its own script and
ships only claim_check.py and push_guard.py, so it cannot install a writer
into a second repository.

The root cause is narrower than the row states, and naming it changed the fix

claim.ps1 used one value for two questions: where the registry is and
who holds the claim. Inside one repository those are always the same tree, so
the conflation is invisible. Across two they diverge.

This matters for the design ruling. The Console directed the first option --
the gate reads the registry the tool writes -- and that option is correct, but it
is necessary and not sufficient on its own. The gate also compares the
record's worktree against the tree being committed, so a claim taken by the
engine's tool still reads as held by another worktree from the second
repository. The gate stays unpassable with the pointer alone.

So both halves shipped. This is still the FIRST option, completed -- it is not
the second. Nothing writes a vault-local registry; there is one registry, and
both gates read it.

What was built

  • git config mefor.claimsRoot <path>, set in the repository that does NOT host
    the registry. Both halves resolve from the repository the claim is FOR,
    never from the tree a script happens to live in, so the tool and the gate
    cannot disagree about where to look.
  • claim.ps1 -AsWorktree <tree> names the holder when the tool is run from a
    tree it does not live in. This is not a retreat from the $PSScriptRoot
    anchoring of #1060: that defect was a SILENT read of the caller's cwd; this is
    an explicit argument, recorded in the claim, printed on every surface that
    shows a holder, and refused outright when it names no repository.
  • Unset, behaviour is what shipped before, and that is asserted rather than
    asserted about, so no existing claim is invalidated.
  • An unresolvable pointer FAILS CLOSED on both sides. The silent fallback
    would send the gate to a directory nothing writes, where a misconfigured
    pointer presents as an honestly unclaimed item -- worse than a refusal.
  • The gate now says where it looked and prints a remedy that can be run from
    the repository it is printed in (absolute tool path, quoted, with the holder to
    record). install-git-hooks.ps1 -Status reports the same directory. The
    absence of that diagnostic is most of why the split went unnoticed: a refusal
    against a registry in another repository is indistinguishable, from outside,
    from an item nobody claimed.

Proven by mutation, both halves

Mutation Result
gate ignores mefor.claimsRoot the original 5 failures reproduce exactly
-AsWorktree becomes a no-op 1 failure, exactly the arm it serves

A fix to a gate that is not first shown failing is not evidence, so both arms
were reverted and re-run rather than argued.

I did not open the MessageFoundry-vault checkout

CLAUDE.md limits reading that tree to roles/. Whether the vault carries its
own claim.ps1 is still the one unmeasured fact
, exactly as the row said, and
nothing here should be read as a claim about that tree's contents.

The fix is therefore built not to depend on the answer. The pass arm is
tested in BOTH shapes the second repository can be in: one test runs that
repository's own copy of the tool, the other runs this repository's copy by
absolute path with -AsWorktree. Both land one record, in one registry, that the
second repository's gate accepts.

What remains for whoever may open that tree: set mefor.claimsRoot there,
and re-run install-git-hooks.ps1 so the shared .git/hooks payload is not the
pre-change copy.

The distinction the row insists on

Citing the item in the commit BODY to get past this gate was not the same act
as the 2026-08-06 evasion a sibling row records. There, claiming properly was
possible and the body was a way around a PASSABLE gate. Here it was the only
route through an unpassable one. The distinction is whether a correct alternative
existed -- it did not, and now one does.

Checks

Ran, all green:

  • ruff check . and ruff format --check . (whole repo)
  • mypy messagefoundry (strict, 267 files) and mypy scripts/hooks/claim_check.py
  • pytest over the nine coordination, claim, hook and ledger suites --
    test_claim_shared_registry (7), test_claim_check, test_script_root_anchoring,
    test_coord_claim_refresh, test_coord_claim_release_history,
    test_coord_claim_adjudicate, test_hook_prose_folding,
    test_worktree_prune_merged, test_ledger_check: 191 passed
  • scripts/docs/backlog_status_check.py (657 items, one status each)
  • the local pre-commit battery, on both commits
  • the LIVE claim gate: the first commit's subject cites BACKLOG #1346 and touches
    code, and it passed against the claim held for this worktree

Not run here, and they must be read on the runner: the full pytest suite and
every hosted-only leg.

One pre-existing red a reviewer will hit locally, and it is NOT this change

tests/test_installed_coord_hooks.py reports the INSTALLED
.git/hooks/push_guard.py as stale against source. push_guard.py is untouched
by this branch (git diff HEAD -- scripts/hooks/push_guard.py is empty) -- the
installed copy on this box was already behind. The same module SKIPS
claim_check.py with an explicit reason while its source has uncommitted
changes, which is the designed behaviour.

I did not re-install the hooks. That writes into the COMMON .git/hooks,
shared by every worktree and every running session on this box -- outside this
worktree, and not something git can undo. After merge, an operator should run
pwsh -NoProfile -File scripts\coord\install-git-hooks.ps1 so the payload that
actually fires is this one.

wshallwshall added 2 commits September 3, 2026 17:14
…(BACKLOG #1346)

claim.ps1 used ONE value for TWO questions -- where the registry is, and who
holds the claim. Inside one repository those are the same tree, so the
conflation was invisible; across two they diverge. The vault's installed
claim_check.py therefore read a registry the claim tool never wrote, and no
code-touching vault commit whose SUBJECT cited a ledger number could pass,
however honestly the item was held. The only route through was to cite the item
in the commit BODY, which the gate permits by design -- a gate whose sole remedy
is a sanctioned way around it is the state that manufactures evasion.

Option taken: ONE SHARED REGISTRY, both gates reading it. `git config
mefor.claimsRoot <path>` is set in the repository that does not host the
registry, and BOTH halves resolve from the repository the claim is FOR, so they
cannot disagree about where to look. `claim.ps1 -AsWorktree <tree>` names the
holder when the tool runs from a tree it does not live in -- the half the
pointer alone does not cover, because the gate also compares the record's
worktree against the tree being committed.

Unset, behaviour is what shipped before, and that is asserted rather than
asserted about, so no existing claim is invalidated. An unresolvable pointer
FAILS CLOSED on both sides: the silent fallback would send the gate to a
directory nothing writes, where a misconfigured pointer presents as an honestly
unclaimed item. The gate now also says WHERE it looked and prints a remedy that
runs from the repository it is printed in; install-git-hooks.ps1 -Status reports
the same directory. That missing diagnostic is most of why the split went
unnoticed.

REPRODUCED FIRST, then proven by mutation. tests/test_claim_shared_registry.py
git-inits two independent checkouts: 5 of 6 arms red before the fix, with the
single-repository arm green in the same run as the positive control. The
measured refusal was "is NOT CLAIMED", not "claimed by ANOTHER worktree" -- the
proof the gate was reading an empty registry rather than adjudicating one.
Reverting the pointer reproduces all 5; reverting the holder split kills exactly
the one arm it serves.

The vault half is unverified by construction: CLAUDE.md limits reading that tree
to roles/, so whether it carries its own claim.ps1 is still unmeasured. The fix
is built not to depend on the answer -- the pass arm is tested in both shapes.

claim.ps1's divergence note was reworded because -AsWorktree makes its old
sentence false in exactly the case the flag exists for. The behaviour it asserts
is unchanged.
@wshallwshall wshallwshall added the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 3, 2026
@github-actions github-actions Bot removed the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 3, 2026
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

LANDER: applying reviewed under an explicit owner override. The owner has suspended the reviewer process and directed that this label be applied as needed to clear the gate.

SO READ THIS LABEL AS A BYPASS TOKEN, NOT A REVIEW. It records that the gate was cleared by instruction. It does not assert that anyone read this diff, and no later reader should infer one from it.

What I can say: this PR appeared in a read-only pre-verification sweep of all 59 non-draft open PRs (per-PR defect read, plus pairwise git merge-tree against every PR sharing a non-BACKLOG file), and no blocking defect was recorded against it. That sweep did not run any tests, and CI has been down for hours, so nothing here has a current green.

-- Lander

@wshallwshall wshallwshall added the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 4, 2026
@github-actions github-actions Bot added the ci-red A required check went red. Attribute it before retrying. label Sep 4, 2026
tests/test_tooling_partition.py::test_every_non_engine_test_is_classified reds
all three required test legs when a test that does not import the engine is
absent from tests/tooling_manifest.txt. This PR adds such a test, so CI could
not go green as it stood.

The manifest is read as a set, so this is a single inserted line at its
alphabetical slot; no existing line moves.
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander: added the missing tests/tooling_manifest.txt line for the test this PR introduces.

tests/test_tooling_partition.py::test_every_non_engine_test_is_classified reds all three required
test legs when a test that does not import the engine is absent from the manifest, and it is not
marked tooling, so -m 'not tooling' does not deselect it. This PR could not go green as it stood.

Verified before pushing: the test fails on the previous head naming this exact file, and passes with
the line added. The manifest is read as a set(), so the change is one inserted line at its
alphabetical slot -- no existing line moves.

Re-applied the reviewed label, which the push stripped.

@github-actions github-actions Bot removed the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 4, 2026
@wshallwshall wshallwshall added the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 4, 2026
The conflict is mine, not this branch's. During the manifest repair I inserted a line into
this pull request and into 825 at the SAME alphabetical slot, between
tests/test_claim_check.py and tests/test_gate_ci_mirror_parity.py. Each insert was correct
alone; together they conflict, which surfaced once 825 landed.

Both lines are needed, so both are kept in alphabetical order. Verified with
tests/test_tooling_partition.py and by confirming each path appears exactly once.
@github-actions github-actions Bot removed the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 4, 2026
@wshallwshall wshallwshall added the reviewed A reviewer has read this. Removed automatically when new commits arrive. label Sep 4, 2026
@wshallwshall
wshallwshall added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 8650833 Sep 4, 2026
42 of 43 checks passed
@wshallwshall
wshallwshall deleted the worktree-agent-a9051692500267199 branch September 4, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-red A required check went red. Attribute it before retrying. reviewed A reviewer has read this. Removed automatically when new commits arrive.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant