Repository navigation
guard: register tools/INDEX.md as a generated path (prevents the predicted batch wedge) - #757
Merged
Merged
Conversation
PR #751 moved the per-tool index out of tools/CLAUDE.md into the generated tools/INDEX.md, but did not add that file to the GENERATED_PATHS registry or give it a merge_queue REGENERATORS entry. That left the exact hole the registry was created to close, one file over. The wedge it sets up: two queued PRs each add a tool with its own `INDEX:` line. Each branch's tools/INDEX.md is byte-identical to its own sources, so both are green. Their union is missing the other's entry, so the byte-identity gate fail-closes on an integration branch no member ever tested. Unregistered, the queue cannot repair it -- regenerate_on_batch classifies the dirty tools/INDEX.md as unregistered, rows regenerator_overreach and commits nothing, and worktree_is_safe refuses the whole pass over a path the repo itself generated. The queue wedges holding a batch it can never publish. That is the suite-count jam of 2026-08-03, verbatim, on a different file. * tools/generated_paths.py: register tools/INDEX.md, and write down that registration is only half the contract -- every entry needs a matching regenerator or it still wedges the queue. * tools/merge_queue.py: REGENERATORS entry only (gen_tool_index.py --regenerate). No logic touched; #753 owns the rest of this file. * tests/test_generated_paths.py: 15 tests. The union-drift jam is reproduced against the real generator in a hermetic temp git repo before the fix. This does not weaken the byte-identity gate. A registered artifact is a pure function of unregistered sources, so the only edit automation may discard is an edit to the derived bytes, which regenerating reproduces exactly. Every edit that carries meaning lives in a source file that is not registered and still faces the same gate: a tools/ file with no `INDEX:` line still fails gen_tool_index closed and no index is written, and a tampered `INDEX:` line is propagated INTO the regenerated index where the diff shows it. Registration adds a repair path on the integration branch; it removes no check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merged
matt82198
added a commit
that referenced
this pull request
Oct 6, 2026
…-coded copy (#758) * guard: auto_merge reads the generated-path registry instead of a hard-coded copy tools/auto_merge.py hard-coded ["tests/CLAUDE.md", "tools/CLAUDE.md"] when resolving merge conflicts -- a re-typed copy of tools/generated_paths.py, the exact duplication that registry exists to prevent. PR #757 adds tools/INDEX.md to the registry; auto_merge would never have known. fix_branch() now imports generated_paths as a MODULE and calls generated_paths() at call time, so the tool tracks whatever the registry currently holds rather than freezing a copy at import. tests/test_auto_merge_registry.py adds two guards: - behavioural: injects a sentinel entry into the registry at runtime and asserts fix_branch resolves it (a copy cannot see the sentinel); shrinking the registry to empty resolves nothing - structural (durable): AST source scan over tools/ failing any module that re-types the registry as a literal collection, with its own falsifiability tests (planted copy is caught; a broader overlapping list such as merge_telemetry contended-files is not a false positive) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: regenerate suite counts after merge (post-merge count was stale) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
The predicted jam
PR #751 moved the per-tool index out of
tools/CLAUDE.mdinto the generatedtools/INDEX.md. That file was not added totools/generated_paths.py::GENERATED_PATHS, and notools/merge_queue.py::REGENERATORSentry was created for it — leaving the exact hole the registry exists to close, one file over.The failure it sets up, verbatim:
INDEX:header line.tools/INDEX.mdis byte-identical to its own sources, so both are individually green.gen_tool_index.py --checkfail-closes on an integration branch no member ever tested.regenerate_on_batchclassifies the dirtytools/INDEX.mdas unregistered, rowsregenerator_overreachand commits nothing;worktree_is_saferefuses the whole pass over a path the repo itself generated.That is the suite-count jam of 2026-08-03 — the one documented in
generated_paths.py's own docstring — on a different file.Repro before the fix (TDD)
tests/test_generated_paths.pybuilds a hermetic temp git repo containing the realgen_tool_index.pyand drives the actual gate. Against unfixedmain, 3 of 15 tests failed while the jam-repro test itself passed — i.e. the drift is real today and nothing repairs it:test_union_of_two_green_members_drifts_the_index_gate— PASSED pre-fix. Two members each add a tool; the union returns exit 1,out of date.test_index_md_is_a_registered_generated_path— FAILED:'tools/INDEX.md' not found in ('tests/CLAUDE.md', 'tools/CLAUDE.md').test_unregistered_drift_would_be_rejected_as_overreach— FAILED againstregenerate_on_batch's own predicate:['tools/INDEX.md'] != [].test_a_registered_regenerator_repairs_the_drifted_union— FAILED: no regenerator existed.All 15 pass after the change.
This PR also demonstrates the union-drift mechanism live: adding one test file moved the tracked Python suite count 237 -> 238, which is precisely the class of drift a batch produces and no member sees.
Does registration weaken the byte-identity gate?
No — and the tests pin it. The membership criterion is that the file is derived.
tools/INDEX.mdis a pure function of theINDEX:lines intools/sources, so the only edit automation is permitted to discard is an edit to the derived bytes — information-free by construction, since regenerating reproduces them exactly. Every edit that carries meaning lives in a source file, which is not registered and still faces the unchanged gate:tools/file with noINDEX:line still failsgen_tool_index.pyclosed, and--regeneraterefuses to write — registration cannot launder an undocumented tool into a green index (test_a_tool_with_no_index_line_still_fails_closed, which also asserts the tree is left untouched).INDEX:line is propagated into the regenerated index, where the diff shows it under review — not swallowed (test_editing_a_source_index_line_propagates_it_visibly).test_the_gate_itself_is_unchanged_by_registration).Registration adds a repair path on the integration branch. It removes no check anywhere.
regenerate_on_batchstill commits only registry paths, and overreach onto anything else still commits nothing.Sequencing vs #753
#753 (
guard/daemon-safety-fixes) is open and editstools/merge_queue.py. Its hunks are at lines 1-6 and 244+;REGENERATORSlives at line 143. Disjoint, so both halves shipped here. Themerge_queue.pymodule docstring/INDEX:line — which #753 rewrites wholesale — was deliberately left untouched to avoid a conflict on that 4000-char line; the accurate contract text lives ingenerated_paths.pyand in a comment directly aboveREGENERATORS, where a reader actually looks. Nomerge_queue.pylogic was modified.Gates
claudemd_lintOK -gen_tool_index --checkOK (146 tools, in sync) -claudemd_sync_gate --checkOK -verify_test_suite_count --checkOK -verify_gates_wiredOK -encoding_lint --checkOK -subprocess_guardclean for the new file -secret_scan --stagedexit 0.Suites: Python
npm run test:pyexit 0 (238 suite files) - Node 321/321 - Shell 14/14 files, pre-push-policy 18/18 subtests.Follow-ups found (not in scope here)
tools/auto_merge.py:97still hard-codes['tests/CLAUDE.md', 'tools/CLAUDE.md']instead of importinggenerated_paths— the duplicate the registry docstring says should not exist. It will now drift from the registry.tools/commit_lint.py --rangecrashes:run_subprocess() got an unexpected keyword argument 'capture_output'. It is not wired into CI or the pre-push hook, so nothing caught it.commit_lintrejects theguardtype this repo uses in practice (guard(queue): restore the kill switch + atomic lock + worktree/timeout failure containment (deep-scan A1-A3,A7) #753, this PR); allowed set isbuild, chore, ci, docs, feat, fix, perf, refactor, style, test. Either the type list or the convention should move.🤖 Generated with Claude Code