ci(gates): WS10 path-keyed gate rewrites — tree-shape-agnostic discovery, fail-closed - #6946
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
🚅 Deployed to the ironclaw-pr-6946 environment in ironclaw-ci-preview
|
Records what #6946 landed and, on the WS0 blocking-prerequisite row, the five same-class gates the sweep found outside the WS10 list. That box stays open: its five named gates are done, but `code_style.yml`'s dist-build regex, `platform-and-compat.yml`'s WASM-ABI regex, `ironclaw-stress.yml`'s push filter, `regression-test-check.yml`'s high-risk-path list, and `build-wasm-extensions.sh`'s assets glob all still go silently green under nested crate directories — ticking it would tell the next slot the first family `git mv` is safe when it is not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ery, fail-closed Five CI/dev gates resolved their scope from the literal `crates/ironclaw_*` tree shape. Each one fails SILENTLY the moment the target-architecture restructure nests crates in family directories (`crates/<family>/ironclaw_*`, PROPOSAL §5): the pattern stops matching, the gate scans nothing, and it reports success. Coverage goes dark, the Reborn suite is skipped, the panic baseline shrinks, a metric renders 0% of a 0-line codebase — all green. Each gate now discovers crates from the tree itself and refuses to report success on an empty scan: - scripts/ci/reborn-coverage-merge-lcov.sh — filter derived from the crate inventory; an empty merged tracefile is now an error, not an exit-0 no-op. - scripts/check_no_panics.py — shipping-closure crates are matched at any depth under crates/; a shipping workspace member outside the crate tree or without a source root is an error. - scripts/ci/classify-test-scope.sh — crate paths are normalized to `crates/<crate>/...` before the case arms; an unattributable crates/ path is refused instead of silently bucketed legacy-only. - scripts/dev_metrics.py — composition/architecture/denominator paths resolve through the crate tree instead of hardcoded globs. - .github/workflows/reborn-e2e.yml — both scope filters are depth-independent, and scripts/ci/ws12_workflow_contracts.py replays a nested crate path through the `changes`-job regex so re-narrowing it fails loudly. Zero behavior change on today's tree: the panic gate's stdout, discovered roots, production/test file sets and 51 violations are unchanged (no baseline re-keying needed); the classifier's per-path verdict is identical across all 4179 tracked files; the lcov merge is byte-identical on a 3-lane fixture; dev_metrics tier3 is identical; the e2e filters gain 3 crates-root markdown files and lose nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe changes replace fixed-depth crate assumptions with filesystem and metadata discovery. Test-scope classification, panic checks, coverage merging, development metrics, and Reborn E2E workflow validation now support nested crates and fail closed on invalid or empty discovery results. ChangesNested crate tooling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ChangedPaths
participant classify-test-scope.sh
participant crate_tree.py
ChangedPaths->>classify-test-scope.sh: provide raw paths
classify-test-scope.sh->>crate_tree.py: discover crate inventory
crate_tree.py-->>classify-test-scope.sh: return crate directories
classify-test-scope.sh->>classify-test-scope.sh: normalize and classify paths
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Records what #6946 landed and, on the WS0 blocking-prerequisite row, the five same-class gates the sweep found outside the WS10 list. That box stays open: its five named gates are done, but `code_style.yml`'s dist-build regex, `platform-and-compat.yml`'s WASM-ABI regex, `ironclaw-stress.yml`'s push filter, `regression-test-check.yml`'s high-risk-path list, and `build-wasm-extensions.sh`'s assets glob all still go silently green under nested crate directories — ticking it would tell the next slot the first family `git mv` is safe when it is not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7c325ba to
9e6fb28
Compare
Records what #6946 landed and, on the WS0 blocking-prerequisite row, the five same-class gates the sweep found outside the WS10 list. That box stays open: its five named gates are done, but `code_style.yml`'s dist-build regex, `platform-and-compat.yml`'s WASM-ABI regex, `ironclaw-stress.yml`'s push filter, `regression-test-check.py`'s HIGH_RISK_PATTERNS, and `build-wasm-extensions.sh`'s assets glob all still go silently green under nested crate directories — ticking it would tell the next slot the first family `git mv` is safe when it is not. The same sweep turned up a live pre-existing bug, filed as #6947: classify-test-scope.sh's `crates/ironclaw_product_*/*` arm requires a literal `_` after "product" and so can never match the merged `crates/ironclaw_product/` (#6583 folded the four `ironclaw_product_*` crates into it without repointing the glob). A product-only diff therefore reports has_reborn_tests=false and the reborn-tests roll-up passes fast having skipped all eight Reborn lanes, `cargo test -p ironclaw_product` included. Left unfixed in #6946 because that PR is behavior-free by mandate and this fix changes CI behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9e6fb28 to
7f96b78
Compare
|
Coordinator sign-off (Wave 0 slot 4 — WS10 path-keyed gate rewrites). Review protocol ran: independent verification, one bundled feedback round, fixes verified.
Ready for Ben to merge. Two-commit shape: |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 17: Create or link a tracked follow-up issue for each of the five
remaining silent-failure gates listed in the checklist: the workflow regexes and
push filter, HIGH_RISK_PATTERNS/frontend prefixes, and the WASM extension
manifest glob. Each issue must specify the required path rewrite and include a
regression test, then update this checklist entry with all five issue
references.
In `@scripts/ci/classify-test-scope.sh`:
- Around line 132-140: Update the deleted-path fallback in the
path-classification case around NORMALIZED_PATH so it removes all family-level
segments up to and including the final ironclaw_* directory, rather than
stripping only the first segment with ${tail#*/}. Preserve the canonical
crates/<crate>/... result for nested layouts such as
crates/app/cli/ironclaw_cli/src/main.rs, allowing subsequent crate matching to
classify the path correctly.
In `@scripts/ci/lib/crate_tree.py`:
- Around line 137-150: Remove the unused owning_crate_directory function from
the merge-gating module. Do not retain its boundary-matching logic there; keep
that logic localized to scripts/ci/reborn-coverage-merge-lcov.sh unless a future
caller and coverage tests require relocating it.
- Around line 85-90: Replace the crates_root.rglob traversal in the manifest
collection flow with os.walk so skipped directories are removed from the walk’s
mutable directory list before recursion. Add the os import, preserve
relative-path construction and _is_skipped filtering for manifests, and continue
collecting only non-skipped Cargo.toml paths.
In `@scripts/ci/test-classify-test-scope.sh`:
- Around line 486-508: Update the parity test around bash_inventory and
python_inventory to invoke the classifier’s discover_crate_dirs function, or its
--print-crate-dirs interface, instead of duplicating the find expression. Also
assert that the classifier’s min_crate_directories value matches crate_tree.py’s
MIN_CRATE_DIRECTORIES, preserving the existing inventory comparison.
In `@scripts/ci/ws12_workflow_contracts.py`:
- Around line 83-85: Update E2E_SCOPE_REGEX in
scripts/ci/ws12_workflow_contracts.py to recognize grep -Eq scope guards split
across escaped-newline continuations while preserving one-line matching. Add a
regression test in scripts/ci/test_ws12_workflow_contracts.py covering the
multiline form, ensuring it fails with the current regex and passes after the
fix.
In `@scripts/dev_metrics.py`:
- Around line 397-404: Import shlex and replace the hand-written or missing
shell quoting for filesystem-derived paths in _prod_lines, _exact_lines, and the
code at the later matching path with shlex.quote before interpolation into find
commands. Ensure every discovered directory/path, including the resolved
composition value passed to _exact_lines, is safely quoted while preserving the
existing command behavior.
- Around line 256-270: Memoize the crate-tree helper calls to avoid repeated
filesystem walks during tier3. Add the functools import, decorate
_crate_src_dirs and composition_src with functools.cache, and reuse the cached
crate inventory when computing res["crate_count"]; preserve the existing
directory-resolution behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5bf37065-6e78-4028-be96-9cd30edd7f83
📒 Files selected for processing (11)
.github/workflows/reborn-e2e.ymldocs/reborn/target-architecture/CHECKLIST.mdscripts/check_no_panics.pyscripts/ci/classify-test-scope.shscripts/ci/lib/crate_tree.pyscripts/ci/reborn-coverage-merge-lcov.shscripts/ci/test-classify-test-scope.shscripts/ci/test-reborn-coverage.shscripts/ci/test_ws12_workflow_contracts.pyscripts/ci/ws12_workflow_contracts.pyscripts/dev_metrics.py
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 127: Revise the checklist item to clearly label the five silent outcomes
as base-tree failures from the pre-rewrite reproduction, not results after the
fixes. Then replace “re-run against the rewrite” with the actual post-change
outcome for each gate and name the enforcing command or test supporting each
claim, including the panic gate, coverage merge, classifier, dev_metrics, and
E2E scope filter. Keep the documented guarantees aligned with the referenced
implementations and verification tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ed1bee00-9323-458e-833e-2a56bea13568
📒 Files selected for processing (1)
docs/reborn/target-architecture/CHECKLIST.md
Coverage ratchetReborn integration-tier coverageLine coverage (Reborn crates): 85.78% — 320159 / 373235 lines Per-crate breakdown (60 crates, lowest-covered first)
This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors. Exemptions (18 entry/entries excluded from the accounting above)
|
Folds #6889's coverage-merger change with the WS10 rewrite. The two intents are orthogonal and both are kept whole: * theirs — merge algebra: sum BRDA branch records per (file, line, block, branch), treat `-` as 0, recompute BRF/BRH alongside LF/LH instead of trusting any single lane's summary. * ours — file discovery + fail-closed: scope resolved from the crate tree (scripts/ci/lib/crate_tree.py) rather than a `crates/ironclaw_*` regex, per- input kept-file counts on stderr, and an empty merged tracefile is an error instead of an exit-0 empty file. Resolved by taking main's `{"lines": {}, "branches": {}}` record shape together with our `kept_here` accounting, and main's "summaries are recomputed below" comment (ours claimed BRDA was ignored, which #6889 made untrue). Equivalence re-proven against the NEW baseline, since the old proof predated branch merging: on a 3-lane fixture built from all 1641 real workspace sources plus registry/rustc/tools paths and carrying 14769 BRDA records — including `-` not-taken values, branches present in only one lane, and deliberately wrong LF/LH/BRF/BRH inputs — origin/main's merger and this one produce byte-identical output. The other three gates were re-compared on this same tree: identical panic-gate stdout, identical per-path classifier verdict across all 4207 tracked files, identical dev_metrics tier-3 snapshot. One test fixture repaired, not a behavior change: #6889 promoted ironclaw_extension_host into the Reborn arm, so the "nested unlisted crate" case in test-classify-test-scope.sh needed a crate that is still in neither list — ironclaw_mcp, with a comment on how to re-pick it. Sweep of the new commits (CHECKLIST WS0 row updated, not fixed here — the behavior-free rule still holds): #6889 added two more flat-tree-keyed gates. scripts/ci/reborn_changed_coverage.py is silent — its `crates/ironclaw_*/src/**/*.rs` diff pathspec matches nothing once crates nest, so the changed-coverage gate finds no production files and enforces nothing. scripts/ci/critical_mutation_gate.py is loud — its PRODUCTION_PATH regex and `crates/<package>/` prefix are manifest schema validation, so it raises GateError instead of passing quietly, but it still needs repointing. scripts/ci/check-reborn-branch-coverage-flags.py and #6954's mutation-audit changes are clean. Also recorded: #6889 independently fixed the headline bug in #6947 by adding `crates/ironclaw_product/*` to the Reborn arm. #6947 stays open for the residue it did not touch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
scripts/ci/classify-test-scope.sh (1)
132-140: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winThe deleted-path fallback still strips one family level only.
Line 137 keeps
NORMALIZED_PATH="crates/${tail#*/}". For a two-level layout such ascrates/app/cli/ironclaw_cli/src/main.rsthe result iscrates/cli/ironclaw_cli/src/main.rs, no crate arm matches, andis_code_pathbuckets it legacy-only. That is the silent misbucketing this rewrite targets. Strip up to the lastironclaw_*segment so the fallback matches the depth-independent tree lookup above it.🐛 Depth-independent fallback
*/ironclaw_*/*) - NORMALIZED_PATH="crates/${tail#*/}" + # Strip every leading family segment, not just the first one. + NORMALIZED_PATH="crates/ironclaw_${tail##*/ironclaw_}" return 0 ;;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/ci/classify-test-scope.sh` around lines 132 - 140, Update the deleted-path fallback in the case handling for */ironclaw_*/* so NORMALIZED_PATH removes all leading family-level segments through the last ironclaw_* directory, rather than only stripping the first path component. Preserve the direct ironclaw_*/* branch and ensure depth-independent paths such as crates/app/cli/ironclaw_cli/... normalize to the matching crates/ironclaw_cli/... tree lookup.docs/reborn/target-architecture/CHECKLIST.md (1)
131-131: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winBase-tree failures and post-rewrite results still read as one sequence.
The clause "then re-run against the rewrite:" is immediately followed by the five silent outcomes —
exited 0,printed OK,has_reborn_testsfalse,crate_count=1,has_e2e_scope=false. As written, the rewritten gates appear to still produce them. Label those five as base results, then state the post-change result per gate with the enforcing command or test name (scripts/ci/test-reborn-coverage.sh,scripts/ci/test-classify-test-scope.sh,scripts/ci/test_ws12_workflow_contracts.py).As per coding guidelines, "comments/documentation promising guarantees must match code and tests."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reborn/target-architecture/CHECKLIST.md` at line 131, Rewrite the checklist sentence to clearly separate the five reproduced failures on the base tree from the post-rewrite validation results. Label the listed silent outcomes as base-tree results, then identify the rewritten gates’ passing validation using scripts/ci/test-reborn-coverage.sh, scripts/ci/test-classify-test-scope.sh, and scripts/ci/test_ws12_workflow_contracts.py, while preserving the existing per-gate behavior claims.Source: Coding guidelines
scripts/ci/test-classify-test-scope.sh (1)
529-546: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winThe parity test still pins a copy of the rule, not the classifier.
bash_inventoryre-implements thefindexpression inline. It never callsdiscover_crate_dirsinscripts/ci/classify-test-scope.sh, so a change to the classifier's prune list or root keeps this test green. The floors are also unpinned:min_crate_directories=20in the classifier andMIN_CRATE_DIRECTORIESinscripts/ci/lib/crate_tree.pyare never asserted equal, yetcrate_tree.pyclaims the self-test keeps them from drifting. Source the classifier's function (or add a--print-crate-dirsflag) and assert both floors match.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/ci/test-classify-test-scope.sh` around lines 529 - 546, The parity test must invoke the classifier’s actual discover_crate_dirs logic instead of duplicating its find expression. Update the test around bash_inventory to source and call discover_crate_dirs from classify-test-scope.sh (or use its equivalent print interface), then assert that the classifier’s min_crate_directories and crate_tree.py’s MIN_CRATE_DIRECTORIES values are equal.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 17: For each of the six gates identified in the checklist item
(code_style.yml's has_dist_build regex, platform-and-compat.yml's
has_direct_wasm_abi_risk regex, ironclaw-stress.yml's push filter,
regression-test-check.py's HIGH_RISK_PATTERNS list, build-wasm-extensions.sh's
manifest glob, and reborn_changed_coverage.py's pathspec), file or link a
separate tracked issue that documents the specific path rewrite required and its
corresponding regression test. Ensure each issue references the gate it
addresses rather than consolidating into `#6947`, which tracks the unrelated
classifier bug.
In `@scripts/ci/test-reborn-coverage.sh`:
- Around line 260-274: Update the M5 coverage fixture setup to define and pass a
synthetic IRONCLAW_REPO_ROOT, matching the established M6 pattern, so crate-path
filtering resolves against a stable repository tree rather than the live layout.
Keep the vendored crates exclusion and workspace crate inclusion assertions
unchanged.
---
Duplicate comments:
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 131: Rewrite the checklist sentence to clearly separate the five
reproduced failures on the base tree from the post-rewrite validation results.
Label the listed silent outcomes as base-tree results, then identify the
rewritten gates’ passing validation using scripts/ci/test-reborn-coverage.sh,
scripts/ci/test-classify-test-scope.sh, and
scripts/ci/test_ws12_workflow_contracts.py, while preserving the existing
per-gate behavior claims.
In `@scripts/ci/classify-test-scope.sh`:
- Around line 132-140: Update the deleted-path fallback in the case handling for
*/ironclaw_*/* so NORMALIZED_PATH removes all leading family-level segments
through the last ironclaw_* directory, rather than only stripping the first path
component. Preserve the direct ironclaw_*/* branch and ensure depth-independent
paths such as crates/app/cli/ironclaw_cli/... normalize to the matching
crates/ironclaw_cli/... tree lookup.
In `@scripts/ci/test-classify-test-scope.sh`:
- Around line 529-546: The parity test must invoke the classifier’s actual
discover_crate_dirs logic instead of duplicating its find expression. Update the
test around bash_inventory to source and call discover_crate_dirs from
classify-test-scope.sh (or use its equivalent print interface), then assert that
the classifier’s min_crate_directories and crate_tree.py’s MIN_CRATE_DIRECTORIES
values are equal.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7c008c5f-17df-49e0-8cfb-8d45a06e1ac7
📒 Files selected for processing (7)
.github/workflows/reborn-e2e.ymldocs/reborn/target-architecture/CHECKLIST.mdscripts/ci/classify-test-scope.shscripts/ci/reborn-coverage-merge-lcov.shscripts/ci/test-classify-test-scope.shscripts/ci/test-reborn-coverage.shscripts/ci/test_ws12_workflow_contracts.py
| - [x] Record baselines for the ratchets that must not regress during the restructure: composition mass, production-struct dead-code, integration coverage floor, `LAYER_MATRIX_EXCEPTIONS` count (=20), extension-specificity allowlist size. **Landed with #6936**, every number measured from `origin/main` @ `ae0989c37` rather than copied from these docs: `LAYER_MATRIX_EXCEPTIONS` **20** (the recount matched the documented 20), extension-specificity allowlist **130** pairs, production-struct dead-code **82 frozen paths / 283 members**, composition mass **43,936 / 667,978 production LOC = 6.58% (658 bp)** with **827** governed `Arc<dyn>` sites, integration-coverage floor **85.54%** (a counting mechanism already existed — `tests/integration/coverage-floor.toml` + `scripts/ci/reborn-coverage-ratchet.sh` — so none was invented). The three list-shaped baselines sit beside the lists they measure (`reborn_dependency_boundaries.rs`, `reborn_extension_specificity.rs`, `reborn_struct_test_support_ratchet.rs`), each now shrink-only; the two enforced by shell gates are recorded in `reborn_restructure_baselines.rs`, which also pins that both gates stay armed. | ||
| - [ ] Confirm the team decision on Strategy B (family dirs + focused crates). Rename scope is fully decided (PROPOSAL §12.10; naming rule §5.1). **[decision]** | ||
| - [ ] ⚠ Blocking prerequisite for WS7: the WS10 path-keyed-gate rewrites land before the first family `git mv` (they fail silently under nested dirs). | ||
| - [ ] ⚠ Blocking prerequisite for WS7: the WS10 path-keyed-gate rewrites land before the first family `git mv` (they fail silently under nested dirs). *The five gates the WS10 row names landed with #6946 and that row is ticked — but the sweep that PR ran (`rg "crates/ironclaw" .github/workflows/ scripts/`) found six more of the same mechanical class, none of them in that row, all of which still go silently green under nesting. This box stays open until they are repointed too, because the first `git mv` is exactly what breaks them:* `.github/workflows/code_style.yml`'s `has_dist_build` scope regex (dist-build lane skips), `.github/workflows/platform-and-compat.yml`'s `has_direct_wasm_abi_risk` regex (WASM ABI checks skip; also already stale — it names the deleted `ironclaw_wasm_product_adapters`), `.github/workflows/ironclaw-stress.yml`'s `crates/ironclaw_*/**` push filter (workflow stops triggering), `scripts/ci/regression-test-check.py`'s `HIGH_RISK_PATTERNS` prefix list plus its `crates/ironclaw_webui/frontend/` prefixes (the regression-test requirement quietly relaxes; this list moved out of `regression-test-check.yml` with #6884 and is already stale — it still names the deleted `ironclaw_run_state`), `scripts/build-wasm-extensions.sh`'s `nullglob`-guarded `crates/ironclaw_first_party_extensions/assets/*/manifest.toml` (builds nothing, exits 0), and — arriving with #6889 while #6946 was open — `scripts/ci/reborn_changed_coverage.py`, whose `git diff -- "crates/ironclaw_*/src/**/*.rs"` pathspec (plus the `PRODUCTION_PATH = ^crates/ironclaw_[^/]+/src/.+\.rs$` filter it applies to `+++ b/` lines) matches nothing under nesting, so the changed-coverage gate sees zero changed production files and has nothing to enforce. Verified already safe and needing no change: `scripts/ci/discover-reborn-package-crates.sh` (name-keyed `cargo metadata`, already errors on an empty discovery), `scripts/ci/check-include-str-paths.sh` (`rglob`), and `scripts/ci/check-reborn-branch-coverage-flags.py` (#6889 — no path keying at all). Verified loud, not silent, but still flat-keyed and so still owed a repoint: `scripts/ci/check-composition-budget.sh` (its `crates/*/src` denominator hits the existing `denominator LOC is 0` guard and exits 1) and `scripts/ci/critical_mutation_gate.py` (#6889 — its `PRODUCTION_PATH` regex and `package_root = f"crates/{package}/"` are manifest *schema validation*, so a moved tree makes it raise `GateError` and block rather than pass quietly). *The same sweep also turned up a live pre-existing bug, filed as **#6947**: `classify-test-scope.sh`'s `crates/ironclaw_product_*/*` arm cannot match the merged `crates/ironclaw_product/` (#6583 folded the four `ironclaw_product_*` crates into it and never repointed the glob), so a product-only diff classified `has_reborn_tests=false` and the `reborn-tests` roll-up passed fast having skipped all eight Reborn lanes. **#6889 independently fixed the headline bug** by adding `crates/ironclaw_product/*` (and `crates/ironclaw_extension_host/*`) to the Reborn arm; #6947 stays open for the residue it did not touch — the still-dead `crates/ironclaw_product_*/*` and `crates/ironclaw_oauth/*` arms, the phantom `crates/ironclaw_product_storage/src/lib.rs` self-test case that hid the rot for two releases, and the durable fix of pinning the arm inventory against the crate inventory so the next merged-or-renamed crate cannot silently fall out of a glob.* |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
The six remaining gates still carry no follow-up issue.
Line 17 names six path-keyed gates that go silently green under nesting: code_style.yml, platform-and-compat.yml, ironclaw-stress.yml, regression-test-check.py, build-wasm-extensions.sh, and reborn_changed_coverage.py. Only #6947 is linked, and it tracks the classifier bug, not these gates. File or link one tracked issue per gate, each stating the required path rewrite and its regression test.
As per coding guidelines, "discovered problems need follow-up issues."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reborn/target-architecture/CHECKLIST.md` at line 17, For each of the six
gates identified in the checklist item (code_style.yml's has_dist_build regex,
platform-and-compat.yml's has_direct_wasm_abi_risk regex, ironclaw-stress.yml's
push filter, regression-test-check.py's HIGH_RISK_PATTERNS list,
build-wasm-extensions.sh's manifest glob, and reborn_changed_coverage.py's
pathspec), file or link a separate tracked issue that documents the specific
path rewrite required and its corresponding regression test. Ensure each issue
references the gate it addresses rather than consolidating into `#6947`, which
tracks the unrelated classifier bug.
Source: Coding guidelines
| # M5: third-party sources whose own path contains `crates/` are NOT workspace | ||
| # crates and must stay out of the report (the vendored-`crates/` trap that a | ||
| # naive `crates/<anything>/` pattern would fall into). | ||
| cat > "${fixtures_dir}/m5_vendored.lcov" <<'EOF' | ||
| SF:/home/runner/.cargo/registry/src/index.crates.io-1949cf8c/wasmtime-46.0.1/crates/wasmtime/src/lib.rs | ||
| DA:1,9 | ||
| LF:1 | ||
| LH:1 | ||
| end_of_record | ||
| SF:/work/ironclaw/crates/ironclaw_runner/src/runtime.rs | ||
| DA:1,3 | ||
| LF:1 | ||
| LH:1 | ||
| end_of_record | ||
| EOF |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
M5 resolves the crate filter against the live repository tree.
M5 omits IRONCLAW_REPO_ROOT, so the fixture SF: path crates/ironclaw_runner/src/runtime.rs only matches while ironclaw_runner sits at that exact depth. The first family git mv breaks this case, and the vendored-exclusion contract is what it is meant to protect. M6 already shows the pattern: build a synthetic root and pass IRONCLAW_REPO_ROOT. The failure is loud, not silent, so this is hygiene rather than a gate hole.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/ci/test-reborn-coverage.sh` around lines 260 - 274, Update the M5
coverage fixture setup to define and pass a synthetic IRONCLAW_REPO_ROOT,
matching the established M6 pattern, so crate-path filtering resolves against a
stable repository tree rather than the live layout. Keep the vendored crates
exclusion and workspace crate inclusion assertions unchanged.
Six of nine comments taken, three declined. Each was verified against the code
before acting; nothing was accepted on the reviewer's word.
* classify-test-scope.sh — the deleted-path fallback stripped exactly one
family segment, so a two-level layout stayed un-canonical and fell through to
legacy-only, the misbucketing this rewrite exists to stop. Now anchored on the
FIRST `ironclaw_` segment. The suggested `${tail##*/ironclaw_}` was not used:
it matches greedily and folds
`crates/f/ironclaw_events/src/ironclaw_helper.rs` down to
`crates/ironclaw_helper.rs`. The arm is unreachable on today's flat tree, and
the per-path differential over all 4192 tracked files is unchanged.
* test-classify-test-scope.sh — the inventory parity check re-implemented the
classifier's `find` inline, so it compared a third copy against Python and
would have stayed green while the classifier's own expression drifted. It now
sources the classifier and reads its actual `crate_dirs`, and asserts the two
discovery floors equal. Sabotage-verified: changing the classifier's prune now
fails the test.
* ws12_workflow_contracts.py — E2E_SCOPE_REGEX only matched a one-line
`grep -Eq`, so a guard split across an escaped-newline continuation was
reported as missing entirely. .claude/rules/review-discipline.md requires
guardrails to handle multiline syntax; fixed with a red-then-green regression.
* crate_tree.py — `owning_crate_directory` had zero callers and no test. Deleted
rather than shipped as speculative public surface.
* dev_metrics.py — inventory memoized (tier3 walked the crate tree four times)
and every shell interpolation quoted with `shlex.quote`; those paths are
filesystem-derived now, not literal constants, and `_exact_lines` had no
quoting at all. Tier-3 output still identical to origin/main.
* CHECKLIST WS10 row — the base-vs-after results read as one list, so the old
silent outcomes could be mistaken for the rewrite's. Split explicitly, with
the enforcing command or test named for each post-change claim.
Declined: the `rglob`→`os.walk` prune (the memoization above removes the
repeated walks, and the traversal is pinned by a differential test), and two
duplicate analysis-chain notes.
Follow-up tracking for the eight gates this PR does not rewrite is now #6963,
linked from the WS0 blocking-prerequisite row.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Note for anyone following the nine review threads above: they cite All ten review fixes are confirmed present on Remaining path-gate inventory is tracked in #6963; the pointer to it is already in CHECKLIST row 17. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/dev_metrics.py`:
- Around line 258-269: Replace the functools.lru_cache(maxsize=None) decorators
with functools.cache for the cached helper functions in this section, including
_crate_dirs and the adjacent cached function. Preserve their existing cache
behavior and function implementations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 82a94524-c627-46b4-9792-ca17de493130
📒 Files selected for processing (7)
docs/reborn/target-architecture/CHECKLIST.mdscripts/ci/classify-test-scope.shscripts/ci/lib/crate_tree.pyscripts/ci/test-classify-test-scope.shscripts/ci/test_ws12_workflow_contracts.pyscripts/ci/ws12_workflow_contracts.pyscripts/dev_metrics.py
💤 Files with no reviewable changes (1)
- scripts/ci/lib/crate_tree.py
| @functools.lru_cache(maxsize=None) | ||
| def _crate_dirs() -> tuple[str, ...]: | ||
| """Crate inventory, walked once per process. | ||
|
|
||
| `tier3` needs it four times (composition root, denominator, crate count, | ||
| architecture crate) and the walk is not free on a developer machine with | ||
| local build outputs under `crates/`. | ||
| """ | ||
| return tuple(crate_directories(".")) | ||
|
|
||
|
|
||
| @functools.lru_cache(maxsize=None) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Use functools.cache instead of functools.lru_cache(maxsize=None).
Ruff flags both decorators (UP033). functools.cache is lru_cache(maxsize=None) with less bookkeeping. Behavior is identical here.
♻️ Modernize the decorators
-@functools.lru_cache(maxsize=None)
+@functools.cache
def _crate_dirs() -> tuple[str, ...]:
@@
-@functools.lru_cache(maxsize=None)
+@functools.cache
def _crate_dir(name: str) -> str:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @functools.lru_cache(maxsize=None) | |
| def _crate_dirs() -> tuple[str, ...]: | |
| """Crate inventory, walked once per process. | |
| `tier3` needs it four times (composition root, denominator, crate count, | |
| architecture crate) and the walk is not free on a developer machine with | |
| local build outputs under `crates/`. | |
| """ | |
| return tuple(crate_directories(".")) | |
| @functools.lru_cache(maxsize=None) | |
| `@functools.cache` | |
| def _crate_dirs() -> tuple[str, ...]: | |
| """Crate inventory, walked once per process. | |
| `tier3` needs it four times (composition root, denominator, crate count, | |
| architecture crate) and the walk is not free on a developer machine with | |
| local build outputs under `crates/`. | |
| """ | |
| return tuple(crate_directories(".")) | |
| `@functools.cache` |
🧰 Tools
🪛 Ruff (0.16.0)
[warning] 258-258: Use @functools.cache instead of @functools.lru_cache(maxsize=None)
Rewrite with `@functools.cache
(UP033)
[warning] 269-269: Use @functools.cache instead of @functools.lru_cache(maxsize=None)
Rewrite with `@functools.cache
(UP033)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/dev_metrics.py` around lines 258 - 269, Replace the
functools.lru_cache(maxsize=None) decorators with functools.cache for the cached
helper functions in this section, including _crate_dirs and the adjacent cached
function. Preserve their existing cache behavior and function implementations.
Source: Linters/SAST tools
…ile its neighbours Row 17 is the WS7-blocking prerequisite; it stayed open until #6963 closed. #6946 landed the five gates the WS10 row names and #6996 closed the rest, so the box is ticked citing both PRs and the issue. The row's prose is reconciled rather than merely ticked: the two staleness notes it carried (ironclaw_wasm_product_adapters, ironclaw_run_state) are now historical, and the two places #6963's inventory was wrong are recorded - build-wasm-extensions.sh already had its empty-set guard, and check-composition-budget.sh is silently green under a PARTIAL move, which is the batch shape WS7 will actually use. Two neighbouring rows described the registration-boundary gate as a silent trap and are updated to match what it now is: row 124 (the WS6 rename) still owes it a repoint, but a missed rename now fails loudly; the WS10 loud-inventory row's amendment records what #6996 fixed there and narrows what remains to the named-path keying the row was always about, across the 20 gates that fail loudly at the git mv. Residue recorded, not hidden: #6947 stays open, and #6999 was filed for the server-lifecycle rule's WebChat v2 gap this sweep uncovered. Neither blocks WS7. Regression test: docs-only reconciliation of prose whose subject is the gates landed in the preceding commits of this PR; those gates carry the tests (scripts/ci/test_ws12_workflow_contracts.py and eight sibling suites, plus 19 new architecture-crate tests). Refs #6963, #6996, #6999
…oss the remaining path-keyed gates (#6996) * ci(gates): add owning-crate path attribution to crate_tree Shared discovery helper for the #6963 gates: resolve which crate directory owns a repo-relative path, outermost-wins, from where Cargo.toml files actually are. Callers that classify production sources need this to stop keying on the flat crates/ironclaw_* shape. Refs #6963 * ci(gates): inventory-driven discovery + fail-closed for the script and workflow gates Closes the script/workflow half of #6963. Every gate below resolved its scope from the literal flat `crates/ironclaw_*` tree shape and stops matching at the first family `git mv`; six of them then reported success having scanned nothing. Each now discovers through the crate inventory (scripts/ci/lib/crate_tree.py), asserts it measured something, and carries positive + negative fixtures. Workflow scope filters (code_style has_reborn_cli, platform-and-compat has_direct_wasm_abi_risk, ironclaw-stress paths) are matched by crate NAME at any depth and pinned in scripts/ci/ws12_workflow_contracts.py against the real inventory, so a renamed, moved or deleted crate fails loudly in Code Style instead of quietly unhooking a lane. Two stale terms removed, both matching nothing today: ironclaw_wasm_product_adapters (crate deleted) from the WASM ABI filter and ironclaw_run_state (deleted with #6696) from HIGH_RISK_PATTERNS. Regression tests: test_ws12_workflow_contracts.py (+11 sabotage cases), test-regression-test-check.sh, test-check-composition-budget.sh (51), test-build-wasm-extensions.sh (new, 14), test-reborn-changed-coverage.sh (56), test-critical-mutation-gate.sh (60). Refs #6963 * fix(ci): repoint the CLI smoke pin on the dist-build scope regex to the crate name The Reborn CLI smoke contract greps code_style.yml for a `grep -Eq` line containing the flat literal `crates/ironclaw_reborn_cli/`. Making that scope regex depth-agnostic broke the needle, and the test failed loudly — which is the point: it is a fourth pin on the same regex and the only reason a one-sided edit could not land silently. Repointed to the crate name (`ironclaw_reborn_cli/`), which survives the family move for the same reason the regex now does. Regression coverage: the existing release_ci_publishes_reborn_without_enabling_legacy_or_docker_paths is the regression test — it went red on the one-sided edit and green on the repoint, verified locally. Refs #6963 * test(architecture): inventory-driven roots + fail-closed censuses in the gate crate Three slices of the #6963 class inside crates/ironclaw_architecture. 1. reborn_registration_pipeline_boundary (#6963 comment, arrived with #6930). workspace_root() walked up a fixed two levels, so under a family move the "root" resolved to crates/, the scan targeted crates/crates, and the gate passed having visited ZERO files. Its two hardcoded hosted_mcp_ prefixes also stopped matching, which would have false-positived against the registration pipeline's own files with baseline 0 blocking the fix. Now inventory-driven, with measured_scan() asserting inventory size, scanned file count, and that every owned scope resolves to at least one real file. Its self-test now exercises is_owned(), flat and nested. 2. reborn_sealed_evidence_mint_ratchet. HostProtocolAuthenticator and ChannelIngressVerifier are unsealed traits whose mint methods are provided, so a bare "impl Trait for X {}" anywhere confers the power to mint ProtocolAuthEvidence::Verified. The source census IS the enforcement, and it evaded on a multiline impl header, on "use ... as" aliases (plain, braced, and raw-identifier), and across a re-export split over two files. Headers are now extracted and whitespace-collapsed, in-file aliases resolved, matching is identifier-bounded, a re-export guard removes the cross-file shape, and a headers-parsed floor keeps the new normalizer from degrading silently. Closes the #6995 fail-open; seam origin PR #6981. 3. The shared root idiom. 23 of 24 gate files resolved the workspace root by walking up a fixed number of levels. ratchet_support::workspace_root() now searches for the nearest ancestor holding both crates/ and Cargo.toml, and 11 private copies were deleted in its favour. Two gates that went silently green under nesting (reborn_authorized_seal_ratchet - worst under a PARTIAL move, 1309 -> 45 files scanned with no error; reborn_retired_taxonomy - 1492 -> 0) gained measurement assertions. Two vacuous assert!(!path.exists()) absence checks in telegram_extension_gates now require their containing directory to exist. Five stale entries removed, each matching zero files today and therefore behavior-free: crates/ironclaw_gateway/ and extension_host/ extension_installation_store.rs from two SANCTIONED_PATHS allowlists (both now carry stale-entry detection), crates/ironclaw_reborn_api/src and two duplicate crates/ironclaw_product/src entries from the dependency-boundary roots, and the deleted repo-root src/ monolith from the manifest reparse scan. Regression tests: +8 in the family sweep, +7 in the registration boundary, +4 in the sealed-evidence census; every added assertion sabotage-tested red then green. 26 binaries / 146 passed / 0 failed; clippy -D warnings clean. Refs #6963, #6995 * docs(checklist): tick the WS0 path-keyed-gate prerequisite and reconcile its neighbours Row 17 is the WS7-blocking prerequisite; it stayed open until #6963 closed. #6946 landed the five gates the WS10 row names and #6996 closed the rest, so the box is ticked citing both PRs and the issue. The row's prose is reconciled rather than merely ticked: the two staleness notes it carried (ironclaw_wasm_product_adapters, ironclaw_run_state) are now historical, and the two places #6963's inventory was wrong are recorded - build-wasm-extensions.sh already had its empty-set guard, and check-composition-budget.sh is silently green under a PARTIAL move, which is the batch shape WS7 will actually use. Two neighbouring rows described the registration-boundary gate as a silent trap and are updated to match what it now is: row 124 (the WS6 rename) still owes it a repoint, but a missed rename now fails loudly; the WS10 loud-inventory row's amendment records what #6996 fixed there and narrows what remains to the named-path keying the row was always about, across the 20 gates that fail loudly at the git mv. Residue recorded, not hidden: #6947 stays open, and #6999 was filed for the server-lifecycle rule's WebChat v2 gap this sweep uncovered. Neither blocks WS7. Regression test: docs-only reconciliation of prose whose subject is the gates landed in the preceding commits of this PR; those gates carry the tests (scripts/ci/test_ws12_workflow_contracts.py and eight sibling suites, plus 19 new architecture-crate tests). Refs #6963, #6996, #6999 * fix(ci): make the unattributable-path refusal reachable in the mode CI runs Review catch on #6996, verified before fixing and worth stating plainly: the fail-closed check this PR added to the changed-coverage gate could not fire in production. git_diff() narrows the diff to per-crate src/ pathspecs, so a Rust file under crates/ that belongs to no discovered crate was filtered out of the diff text before parse_diff ever saw it. Only --diff-file, which is handed an un-narrowed diff, reached reject_unattributable - and that is the mode the self-test used. The workflow runs --base/--head. Measured on a real git fixture carrying an orphaned crates/not_a_crate/src/lib.rs: --diff-file refused it; --base/--head printed "no Reborn production lines added" and exited 0. screen_unattributable() now walks the unfiltered changed-file list under crates/ before the narrowing pathspecs are applied, so both modes refuse. A fail-closed check that cannot fail in the mode that matters is exactly the defect class this PR exists to close, so it is fixed rather than documented. Three smaller review items in the same pass: - both bash callers of crate_tree.py captured stdout with stderr merged in, so a Python warning would have been folded into the inventory itself and read as a crate directory. Captured separately now. - crate_tree's memoized inventory sorted longest-first while owning_crate_directory documents outermost-wins. Order is provably irrelevant today (no entry is a prefix of another), but the code now reads the way the rule is written. - regression-test-check probed for the workspace manifest twice; resolve_prefixes owns that decision and main reads its result. Declined, with reason: high-risk matching keeps `prefix in path` rather than `startswith`. Switching would narrow the match set, and equivalence with the pre-existing behavior is this PR's whole contract; the substring form can only over-match, which makes the gate stricter, never fail-open. Regression test: "an unattributable path is refused through --base/--head too" plus its message assertion in test-reborn-changed-coverage.sh, driven through a real git fixture. Verified red against this PR's own pre-fix gate (rc=0, path absent from output) and green after. Suite is now 58 cases. Refs #6963 * fix(architecture): close the gate crate's own fail-open reads and censuses Review triage on #6996. The headline finding is the embarrassing one: several of the gates this PR hardens were themselves reading fail-open, which is precisely the defect class the PR exists to eliminate. Fixed first, and proven. Fail-open I/O, now fatal (10 sites across 5 gates): - reborn_sealed_evidence_mint_ratchet.rs: seven `read_to_string(..). unwrap_or_default()` plus three swallowed `read_dir`/entry errors. An unreadable file contributed no impl headers and no offenders, so it scanned exactly like a clean one. This census IS the enforcement for two unsealed traits whose mint methods are provided, so a `impl Trait for X {}` it cannot see is forged `ProtocolAuthEvidence::Verified`. - reborn_authorized_seal_ratchet.rs: the same shape on the gate that polices the sole minter of `AuthorizationGrant`. - reborn_registration_pipeline_boundary.rs: two dropped `read_dir` errors and one dropped source read, threaded into the `Result<ScanOutcome, String>` `measured_scan` already returned. - reborn_retired_taxonomy.rs: `scan_dir` now propagates, matching its twin `reborn_memory_retired_vocabulary.rs`, which already did. - reborn_manifest_reparse_gate.rs: same. The floors could not cover any of this: one unreadable crate `src/` tree leaves every count comfortably above its floor while the gate reports "no violations" for a subtree it never read. Absent-vs-unreadable is kept distinct — a missing scan root still fails, and the retired-taxonomy floor test now pins the *partial* tree (the staged-family-move shape), which is the only thing a floor can still catch that an I/O error cannot. Two matcher fail-opens in the evidence census, both verified realizable before fixing: - `header_implements` did not skip whitespace before a trait's generic arguments. `impl ChannelIngressVerifier <> for Rogue {}` compiles (checked against rustc: empty angle brackets after a space are accepted on a non-generic trait) and the header collapse *creates* that space whenever a line break falls there. Undetected, and it mints. - `reexports_a_grant_trait` was line-based, so rustfmt's own output for a long braced import — `pub(crate) use ..::auth::{\n ChannelIngressVerifier as V,\n};` — evaded it: line 1 has no trait name, line 3 does not start with `pub`. That guard is what removes the census's two-file alias blind spot. Replaced with a brace-balanced item scan that reports the item's own line. Both proven red-then-green: sabotage the fix, exactly the self-test that pins it goes red, the whole-workspace censuses stay green (so both are behavior-free on today's tree). Also closed, same class: - The evidence census walked `crates/` only. `tools/ironclaw_stress` is a workspace member that depends on `ironclaw_host_api`, so it can implement a witness trait and mint — invisibly, with the `> 500` file floor comfortably cleared. Scan roots now come from the root manifest's `members` list (1309 -> 1332 files); a new member root joins automatically. - `node_modules` excluded from both registration-boundary walks. - The twelfth private `workspace_root()` copy, in the registration-boundary gate, deleted in favour of `ratchet_support` — it had survived behind a comment claiming it needed one, which was never true. The crate now has exactly one definition of the rule, and the CHECKLIST row that claimed "11 private copies ... across the whole crate" is corrected to 12 and is now true. - `SANCTIONED_PATHS` fragments in the memory vocabulary gate must resolve to exactly ONE scanned file; ambiguity is a refusal, not a silent widening. Kept as fragments rather than workspace-relative literals on purpose: a literal would re-key the list to the flat `crates/<name>/` depth this PR exists to remove. - `extract_paths_globs` refuses two `paths:` blocks instead of pinning the first unconditionally, matching `extract_scope_regex`. Without it a workflow that grew a second filter validated GREEN against the wrong block (measured: zero errors). - Both fixture suites derive the crate-discovery floor from `crate_tree.py`'s own `MIN_CRATE_DIRECTORIES` instead of copying `24`. Measured: raise the floor to 40 and the literal form breaks 37 of 51 cases with an error pointing at the fixture; the derived form passes 51/51. Regression tests: +7, each a negative probe that fails for a deterministic, platform-independent reason (`read_dir` on a regular file, `read_to_string` on a directory, a dangling symlink) rather than a chmod that root ignores inside a container. `cargo test -p ironclaw_architecture` 26 binaries / 146 -> 153 passed / 0 failed — exactly +7, so no pre-existing test changed its verdict. Declined, with evidence, in the review replies: anchoring high-risk matching (substring can only over-trigger, which is the fail-closed direction for a trigger; measured zero delta over all tracked paths, and anchoring would break this PR's equivalence contract), splitting `validate_crate_scope_filters` for a Ruff branch-count gate this repo does not have, and making the CLI smoke pin multiline-safe (it fails loudly, which is the documented intent). Verification: cargo fmt --all --check clean; cargo clippy -p ironclaw_architecture --tests --all-features -D warnings clean; composition budget byte-identical at 6.42% (642 bp) - 43251 / 673642 LOC, 836 Arc<dyn>; ws12 contracts 25 cases; composition-budget 51; build-wasm 14; changed-coverage 58; critical-mutation 60; regression-test-check all pass. Refs #6963 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(architecture): repoint the sanctioned-paths doc at its renamed test The memory vocabulary gate's SANCTIONED_PATHS doc still named sanctioned_paths_all_match_real_files after that test became sanctioned_paths_each_resolve_to_exactly_one_file, and it described only the stale half of a check that now also refuses ambiguity. Documentation promising a guarantee has to match the test that enforces it. Refs #6963 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ery, fail-closed (nearai#6946) * ci(gates): WS10 path-keyed gate rewrites — tree-shape-agnostic discovery, fail-closed Five CI/dev gates resolved their scope from the literal `crates/ironclaw_*` tree shape. Each one fails SILENTLY the moment the target-architecture restructure nests crates in family directories (`crates/<family>/ironclaw_*`, PROPOSAL §5): the pattern stops matching, the gate scans nothing, and it reports success. Coverage goes dark, the Reborn suite is skipped, the panic baseline shrinks, a metric renders 0% of a 0-line codebase — all green. Each gate now discovers crates from the tree itself and refuses to report success on an empty scan: - scripts/ci/reborn-coverage-merge-lcov.sh — filter derived from the crate inventory; an empty merged tracefile is now an error, not an exit-0 no-op. - scripts/check_no_panics.py — shipping-closure crates are matched at any depth under crates/; a shipping workspace member outside the crate tree or without a source root is an error. - scripts/ci/classify-test-scope.sh — crate paths are normalized to `crates/<crate>/...` before the case arms; an unattributable crates/ path is refused instead of silently bucketed legacy-only. - scripts/dev_metrics.py — composition/architecture/denominator paths resolve through the crate tree instead of hardcoded globs. - .github/workflows/reborn-e2e.yml — both scope filters are depth-independent, and scripts/ci/ws12_workflow_contracts.py replays a nested crate path through the `changes`-job regex so re-narrowing it fails loudly. Zero behavior change on today's tree: the panic gate's stdout, discovered roots, production/test file sets and 51 violations are unchanged (no baseline re-keying needed); the classifier's per-path verdict is identical across all 4179 tracked files; the lcov merge is byte-identical on a 3-lane fixture; dev_metrics tier3 is identical; the e2e filters gain 3 crates-root markdown files and lose nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(target-architecture): tick the WS10 path-keyed-gate row Records what nearai#6946 landed and, on the WS0 blocking-prerequisite row, the five same-class gates the sweep found outside the WS10 list. That box stays open: its five named gates are done, but `code_style.yml`'s dist-build regex, `platform-and-compat.yml`'s WASM-ABI regex, `ironclaw-stress.yml`'s push filter, `regression-test-check.py`'s HIGH_RISK_PATTERNS, and `build-wasm-extensions.sh`'s assets glob all still go silently green under nested crate directories — ticking it would tell the next slot the first family `git mv` is safe when it is not. The same sweep turned up a live pre-existing bug, filed as nearai#6947: classify-test-scope.sh's `crates/ironclaw_product_*/*` arm requires a literal `_` after "product" and so can never match the merged `crates/ironclaw_product/` (nearai#6583 folded the four `ironclaw_product_*` crates into it without repointing the glob). A product-only diff therefore reports has_reborn_tests=false and the reborn-tests roll-up passes fast having skipped all eight Reborn lanes, `cargo test -p ironclaw_product` included. Left unfixed in nearai#6946 because that PR is behavior-free by mandate and this fix changes CI behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(gates): address CodeRabbit review on the WS10 path-gate rewrites Six of nine comments taken, three declined. Each was verified against the code before acting; nothing was accepted on the reviewer's word. * classify-test-scope.sh — the deleted-path fallback stripped exactly one family segment, so a two-level layout stayed un-canonical and fell through to legacy-only, the misbucketing this rewrite exists to stop. Now anchored on the FIRST `ironclaw_` segment. The suggested `${tail##*/ironclaw_}` was not used: it matches greedily and folds `crates/f/ironclaw_events/src/ironclaw_helper.rs` down to `crates/ironclaw_helper.rs`. The arm is unreachable on today's flat tree, and the per-path differential over all 4192 tracked files is unchanged. * test-classify-test-scope.sh — the inventory parity check re-implemented the classifier's `find` inline, so it compared a third copy against Python and would have stayed green while the classifier's own expression drifted. It now sources the classifier and reads its actual `crate_dirs`, and asserts the two discovery floors equal. Sabotage-verified: changing the classifier's prune now fails the test. * ws12_workflow_contracts.py — E2E_SCOPE_REGEX only matched a one-line `grep -Eq`, so a guard split across an escaped-newline continuation was reported as missing entirely. .claude/rules/review-discipline.md requires guardrails to handle multiline syntax; fixed with a red-then-green regression. * crate_tree.py — `owning_crate_directory` had zero callers and no test. Deleted rather than shipped as speculative public surface. * dev_metrics.py — inventory memoized (tier3 walked the crate tree four times) and every shell interpolation quoted with `shlex.quote`; those paths are filesystem-derived now, not literal constants, and `_exact_lines` had no quoting at all. Tier-3 output still identical to origin/main. * CHECKLIST WS10 row — the base-vs-after results read as one list, so the old silent outcomes could be mistaken for the rewrite's. Split explicitly, with the enforcing command or test named for each post-change claim. Declined: the `rglob`→`os.walk` prune (the memoization above removes the repeated walks, and the traversal is pinned by a differential test), and two duplicate analysis-chain notes. Follow-up tracking for the eight gates this PR does not rewrite is now nearai#6963, linked from the WS0 blocking-prerequisite row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ed across the remaining path-keyed gates (nearai#6996) * ci(gates): add owning-crate path attribution to crate_tree Shared discovery helper for the nearai#6963 gates: resolve which crate directory owns a repo-relative path, outermost-wins, from where Cargo.toml files actually are. Callers that classify production sources need this to stop keying on the flat crates/ironclaw_* shape. Refs nearai#6963 * ci(gates): inventory-driven discovery + fail-closed for the script and workflow gates Closes the script/workflow half of nearai#6963. Every gate below resolved its scope from the literal flat `crates/ironclaw_*` tree shape and stops matching at the first family `git mv`; six of them then reported success having scanned nothing. Each now discovers through the crate inventory (scripts/ci/lib/crate_tree.py), asserts it measured something, and carries positive + negative fixtures. Workflow scope filters (code_style has_reborn_cli, platform-and-compat has_direct_wasm_abi_risk, ironclaw-stress paths) are matched by crate NAME at any depth and pinned in scripts/ci/ws12_workflow_contracts.py against the real inventory, so a renamed, moved or deleted crate fails loudly in Code Style instead of quietly unhooking a lane. Two stale terms removed, both matching nothing today: ironclaw_wasm_product_adapters (crate deleted) from the WASM ABI filter and ironclaw_run_state (deleted with nearai#6696) from HIGH_RISK_PATTERNS. Regression tests: test_ws12_workflow_contracts.py (+11 sabotage cases), test-regression-test-check.sh, test-check-composition-budget.sh (51), test-build-wasm-extensions.sh (new, 14), test-reborn-changed-coverage.sh (56), test-critical-mutation-gate.sh (60). Refs nearai#6963 * fix(ci): repoint the CLI smoke pin on the dist-build scope regex to the crate name The Reborn CLI smoke contract greps code_style.yml for a `grep -Eq` line containing the flat literal `crates/ironclaw_reborn_cli/`. Making that scope regex depth-agnostic broke the needle, and the test failed loudly — which is the point: it is a fourth pin on the same regex and the only reason a one-sided edit could not land silently. Repointed to the crate name (`ironclaw_reborn_cli/`), which survives the family move for the same reason the regex now does. Regression coverage: the existing release_ci_publishes_reborn_without_enabling_legacy_or_docker_paths is the regression test — it went red on the one-sided edit and green on the repoint, verified locally. Refs nearai#6963 * test(architecture): inventory-driven roots + fail-closed censuses in the gate crate Three slices of the nearai#6963 class inside crates/ironclaw_architecture. 1. reborn_registration_pipeline_boundary (nearai#6963 comment, arrived with nearai#6930). workspace_root() walked up a fixed two levels, so under a family move the "root" resolved to crates/, the scan targeted crates/crates, and the gate passed having visited ZERO files. Its two hardcoded hosted_mcp_ prefixes also stopped matching, which would have false-positived against the registration pipeline's own files with baseline 0 blocking the fix. Now inventory-driven, with measured_scan() asserting inventory size, scanned file count, and that every owned scope resolves to at least one real file. Its self-test now exercises is_owned(), flat and nested. 2. reborn_sealed_evidence_mint_ratchet. HostProtocolAuthenticator and ChannelIngressVerifier are unsealed traits whose mint methods are provided, so a bare "impl Trait for X {}" anywhere confers the power to mint ProtocolAuthEvidence::Verified. The source census IS the enforcement, and it evaded on a multiline impl header, on "use ... as" aliases (plain, braced, and raw-identifier), and across a re-export split over two files. Headers are now extracted and whitespace-collapsed, in-file aliases resolved, matching is identifier-bounded, a re-export guard removes the cross-file shape, and a headers-parsed floor keeps the new normalizer from degrading silently. Closes the nearai#6995 fail-open; seam origin PR nearai#6981. 3. The shared root idiom. 23 of 24 gate files resolved the workspace root by walking up a fixed number of levels. ratchet_support::workspace_root() now searches for the nearest ancestor holding both crates/ and Cargo.toml, and 11 private copies were deleted in its favour. Two gates that went silently green under nesting (reborn_authorized_seal_ratchet - worst under a PARTIAL move, 1309 -> 45 files scanned with no error; reborn_retired_taxonomy - 1492 -> 0) gained measurement assertions. Two vacuous assert!(!path.exists()) absence checks in telegram_extension_gates now require their containing directory to exist. Five stale entries removed, each matching zero files today and therefore behavior-free: crates/ironclaw_gateway/ and extension_host/ extension_installation_store.rs from two SANCTIONED_PATHS allowlists (both now carry stale-entry detection), crates/ironclaw_reborn_api/src and two duplicate crates/ironclaw_product/src entries from the dependency-boundary roots, and the deleted repo-root src/ monolith from the manifest reparse scan. Regression tests: +8 in the family sweep, +7 in the registration boundary, +4 in the sealed-evidence census; every added assertion sabotage-tested red then green. 26 binaries / 146 passed / 0 failed; clippy -D warnings clean. Refs nearai#6963, nearai#6995 * docs(checklist): tick the WS0 path-keyed-gate prerequisite and reconcile its neighbours Row 17 is the WS7-blocking prerequisite; it stayed open until nearai#6963 closed. nearai#6946 landed the five gates the WS10 row names and nearai#6996 closed the rest, so the box is ticked citing both PRs and the issue. The row's prose is reconciled rather than merely ticked: the two staleness notes it carried (ironclaw_wasm_product_adapters, ironclaw_run_state) are now historical, and the two places nearai#6963's inventory was wrong are recorded - build-wasm-extensions.sh already had its empty-set guard, and check-composition-budget.sh is silently green under a PARTIAL move, which is the batch shape WS7 will actually use. Two neighbouring rows described the registration-boundary gate as a silent trap and are updated to match what it now is: row 124 (the WS6 rename) still owes it a repoint, but a missed rename now fails loudly; the WS10 loud-inventory row's amendment records what nearai#6996 fixed there and narrows what remains to the named-path keying the row was always about, across the 20 gates that fail loudly at the git mv. Residue recorded, not hidden: nearai#6947 stays open, and nearai#6999 was filed for the server-lifecycle rule's WebChat v2 gap this sweep uncovered. Neither blocks WS7. Regression test: docs-only reconciliation of prose whose subject is the gates landed in the preceding commits of this PR; those gates carry the tests (scripts/ci/test_ws12_workflow_contracts.py and eight sibling suites, plus 19 new architecture-crate tests). Refs nearai#6963, nearai#6996, nearai#6999 * fix(ci): make the unattributable-path refusal reachable in the mode CI runs Review catch on nearai#6996, verified before fixing and worth stating plainly: the fail-closed check this PR added to the changed-coverage gate could not fire in production. git_diff() narrows the diff to per-crate src/ pathspecs, so a Rust file under crates/ that belongs to no discovered crate was filtered out of the diff text before parse_diff ever saw it. Only --diff-file, which is handed an un-narrowed diff, reached reject_unattributable - and that is the mode the self-test used. The workflow runs --base/--head. Measured on a real git fixture carrying an orphaned crates/not_a_crate/src/lib.rs: --diff-file refused it; --base/--head printed "no Reborn production lines added" and exited 0. screen_unattributable() now walks the unfiltered changed-file list under crates/ before the narrowing pathspecs are applied, so both modes refuse. A fail-closed check that cannot fail in the mode that matters is exactly the defect class this PR exists to close, so it is fixed rather than documented. Three smaller review items in the same pass: - both bash callers of crate_tree.py captured stdout with stderr merged in, so a Python warning would have been folded into the inventory itself and read as a crate directory. Captured separately now. - crate_tree's memoized inventory sorted longest-first while owning_crate_directory documents outermost-wins. Order is provably irrelevant today (no entry is a prefix of another), but the code now reads the way the rule is written. - regression-test-check probed for the workspace manifest twice; resolve_prefixes owns that decision and main reads its result. Declined, with reason: high-risk matching keeps `prefix in path` rather than `startswith`. Switching would narrow the match set, and equivalence with the pre-existing behavior is this PR's whole contract; the substring form can only over-match, which makes the gate stricter, never fail-open. Regression test: "an unattributable path is refused through --base/--head too" plus its message assertion in test-reborn-changed-coverage.sh, driven through a real git fixture. Verified red against this PR's own pre-fix gate (rc=0, path absent from output) and green after. Suite is now 58 cases. Refs nearai#6963 * fix(architecture): close the gate crate's own fail-open reads and censuses Review triage on nearai#6996. The headline finding is the embarrassing one: several of the gates this PR hardens were themselves reading fail-open, which is precisely the defect class the PR exists to eliminate. Fixed first, and proven. Fail-open I/O, now fatal (10 sites across 5 gates): - reborn_sealed_evidence_mint_ratchet.rs: seven `read_to_string(..). unwrap_or_default()` plus three swallowed `read_dir`/entry errors. An unreadable file contributed no impl headers and no offenders, so it scanned exactly like a clean one. This census IS the enforcement for two unsealed traits whose mint methods are provided, so a `impl Trait for X {}` it cannot see is forged `ProtocolAuthEvidence::Verified`. - reborn_authorized_seal_ratchet.rs: the same shape on the gate that polices the sole minter of `AuthorizationGrant`. - reborn_registration_pipeline_boundary.rs: two dropped `read_dir` errors and one dropped source read, threaded into the `Result<ScanOutcome, String>` `measured_scan` already returned. - reborn_retired_taxonomy.rs: `scan_dir` now propagates, matching its twin `reborn_memory_retired_vocabulary.rs`, which already did. - reborn_manifest_reparse_gate.rs: same. The floors could not cover any of this: one unreadable crate `src/` tree leaves every count comfortably above its floor while the gate reports "no violations" for a subtree it never read. Absent-vs-unreadable is kept distinct — a missing scan root still fails, and the retired-taxonomy floor test now pins the *partial* tree (the staged-family-move shape), which is the only thing a floor can still catch that an I/O error cannot. Two matcher fail-opens in the evidence census, both verified realizable before fixing: - `header_implements` did not skip whitespace before a trait's generic arguments. `impl ChannelIngressVerifier <> for Rogue {}` compiles (checked against rustc: empty angle brackets after a space are accepted on a non-generic trait) and the header collapse *creates* that space whenever a line break falls there. Undetected, and it mints. - `reexports_a_grant_trait` was line-based, so rustfmt's own output for a long braced import — `pub(crate) use ..::auth::{\n ChannelIngressVerifier as V,\n};` — evaded it: line 1 has no trait name, line 3 does not start with `pub`. That guard is what removes the census's two-file alias blind spot. Replaced with a brace-balanced item scan that reports the item's own line. Both proven red-then-green: sabotage the fix, exactly the self-test that pins it goes red, the whole-workspace censuses stay green (so both are behavior-free on today's tree). Also closed, same class: - The evidence census walked `crates/` only. `tools/ironclaw_stress` is a workspace member that depends on `ironclaw_host_api`, so it can implement a witness trait and mint — invisibly, with the `> 500` file floor comfortably cleared. Scan roots now come from the root manifest's `members` list (1309 -> 1332 files); a new member root joins automatically. - `node_modules` excluded from both registration-boundary walks. - The twelfth private `workspace_root()` copy, in the registration-boundary gate, deleted in favour of `ratchet_support` — it had survived behind a comment claiming it needed one, which was never true. The crate now has exactly one definition of the rule, and the CHECKLIST row that claimed "11 private copies ... across the whole crate" is corrected to 12 and is now true. - `SANCTIONED_PATHS` fragments in the memory vocabulary gate must resolve to exactly ONE scanned file; ambiguity is a refusal, not a silent widening. Kept as fragments rather than workspace-relative literals on purpose: a literal would re-key the list to the flat `crates/<name>/` depth this PR exists to remove. - `extract_paths_globs` refuses two `paths:` blocks instead of pinning the first unconditionally, matching `extract_scope_regex`. Without it a workflow that grew a second filter validated GREEN against the wrong block (measured: zero errors). - Both fixture suites derive the crate-discovery floor from `crate_tree.py`'s own `MIN_CRATE_DIRECTORIES` instead of copying `24`. Measured: raise the floor to 40 and the literal form breaks 37 of 51 cases with an error pointing at the fixture; the derived form passes 51/51. Regression tests: +7, each a negative probe that fails for a deterministic, platform-independent reason (`read_dir` on a regular file, `read_to_string` on a directory, a dangling symlink) rather than a chmod that root ignores inside a container. `cargo test -p ironclaw_architecture` 26 binaries / 146 -> 153 passed / 0 failed — exactly +7, so no pre-existing test changed its verdict. Declined, with evidence, in the review replies: anchoring high-risk matching (substring can only over-trigger, which is the fail-closed direction for a trigger; measured zero delta over all tracked paths, and anchoring would break this PR's equivalence contract), splitting `validate_crate_scope_filters` for a Ruff branch-count gate this repo does not have, and making the CLI smoke pin multiline-safe (it fails loudly, which is the documented intent). Verification: cargo fmt --all --check clean; cargo clippy -p ironclaw_architecture --tests --all-features -D warnings clean; composition budget byte-identical at 6.42% (642 bp) - 43251 / 673642 LOC, 836 Arc<dyn>; ws12 contracts 25 cases; composition-budget 51; build-wasm 14; changed-coverage 58; critical-mutation 60; regression-test-check all pass. Refs nearai#6963 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(architecture): repoint the sanctioned-paths doc at its renamed test The memory vocabulary gate's SANCTIONED_PATHS doc still named sanctioned_paths_all_match_real_files after that test became sanctioned_paths_each_resolve_to_exactly_one_file, and it described only the stale half of a check that now also refuses ambiguity. Documentation promising a guarantee has to match the test that enforces it. Refs nearai#6963 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Five CI/dev gates resolved their scope from the literal
crates/ironclaw_*tree shape. Each one fails silently the moment the target-architecture restructure nests crates into family directories (crates/<family>/ironclaw_*, PROPOSAL §5): the pattern stops matching, the gate scans nothing, and it reports success. This PR changes how each gate discovers paths — never what it enforces — and makes "scanned nothing → green" structurally impossible. It lands before the first familygit mv, as CHECKLIST WS0's blocking prerequisite requires.None of the ratchet numbers from #6936 are touched: exceptions=20, composition ceilings, struct baselines, coverage floor 85.54%, specificity allowlist are all unchanged.
Per-gate table
scripts/ci/reborn-coverage-merge-lcov.shre: (?:^|/)crates/(ironclaw_[A-Za-z0-9_]+)/scripts/ci/lib/crate_tree.py— outermostcrates/**/Cargo.tomlowners), matched on a path-segment boundary. Filesystem-based deliberately: thecoverage-reportjob installs Python only, no Rust toolchain.scripts/check_no_panics.pymanifest.parent.parent == crates/(manifest exactly one level down) + shipping package pinned tocrates/ironclaw_reborn_cli/Cargo.tomlironclaw_eventsnested, the gate scanned 1156 files instead of 1164 and printedOK: … matches. Crates with no baseline entries would drop out of scope entirely and unnoticed.cargo metadata(toolchain is available in theno-panicsjob): reachable workspace members whose manifest is undercrates/at any depth; the shipping package is resolved by name (ironclaw), which survives the plannedcrates/ironclaw_reborn_cli→crates/app/ironclaw_clirename.ironclawpackage → error.OK: … (1167 files, 51 reviewed invariant(s))), and a full dump of discovered roots (61), production files (1167), test files (137) and violations (51) is diff-identical..github/workflows/reborn-e2e.ymlpaths: crates/ironclaw_*/**+ mirroredchanges-job regex^(crates/ironclaw_[^/]+/|…)has_e2e_scope=false→ every E2E job skipped and thereborn-e2eroll-up (a required check) passes.paths: crates/**andgrep -Eq '^(crates/|…)'— both depth-independent.scripts/ci/ws12_workflow_contracts.pynow extracts thechanges-job regex from the workflow text and replays 10 probe paths through it (includingcrates/substrates/ironclaw_events/src/lib.rsandcrates/extensions/packages/slack/manifest.toml), and requires- "crates/**"in the push filter. 4 sabotage tests cover re-narrowing, over-broadening, and deletion.scripts/ci/classify-test-scope.shcasearms keyed tocrates/ironclaw_<name>/*ironclaw_eventschange classifiedhas_reborn_tests=false— the entire Reborn suite skipped on a green PR. A nestedironclaw_webuichange classified as legacy-only.crates/path is normalized tocrates/<crate>/…before the arms run, resolving the owning crate directory outward-in against acrates/**/Cargo.tomlinventory (so the outermost crate wins, exactly ascrates/<crate>/*did forironclaw_safety/fuzzand theassets/*/wasm-srcguests). Arms stay keyed to crate identity and are otherwise untouched.crates/tree or an inventory under the floor (20) → error; acrates/path attributable to no crate → error rather than the old silent fall-through to legacy-only. Deleted-crate paths are still attributed by naming convention, so a crate-deletion PR is unaffected. Self-test pins the bash inventory equal tocrate_tree.py's.ironclaw_dispatcher,ironclaw_embeddings) and for a crate that does not exist.scripts/dev_metrics.pyCOMPOSITION = "crates/ironclaw_reborn_composition/src",glob("crates/*"),_prod_lines("crates/*/src"), literal architecture-test pathcrate_count=1,composition_share_gate=0.0%,composition_kloc_now=0.0,boundary_test_count=0— a flawless report of nothing. With one crate nested it silently drifts (denominator quietly loses a crate: 6.40% → 6.42%).crate_tree.crate_directory()/crate_directories().crate_directory()raises when a named crate is absent;crate_directories()raises below the discovery floor — the tool dies loudly instead of rendering zeros.now) diff-identical against a clean checkout of the base.One intentional behavior delta
reborn-coverage-merge-lcov.shpreviously wrote an empty tracefile and exited 0 when no input file matched the filter (self-test case M3 asserted exactly that). That outcome is indistinguishable from "the crate tree moved and everySF:record now falls outside the filter" — it is the silent-dark failure. It is now an error. Nothing healthy produces it: the lanes instrument the whole workspace build. M3 is rewritten to assert the refusal.A stricter per-input rule ("every lane must contribute ≥1 file") was deliberately not added: the nesting failure is global, not per-lane, so it buys almost nothing, and I cannot verify locally that every crate-bucket lcov carries ≥1 workspace source. Per-input counts are printed to stderr instead, so a lane going quiet is visible before it moves a coverage number.
Nested-move simulation (scratch branch, not pushed)
git mv'd real crates intocrates/substrates/on a throwaway branch, ran the origin/main gate vs the rewritten gate on the same tree, then discarded the branch.cargo metadatawas kept healthy in both phases (rootmembers, path deps,tools/ironclaw_stressrefs rewritten).Both transcripts below are verbatim from runs at
cd5d3fed8, before this branch was rebased ontoec64182bb.mainhas since grown a few files, so the absolute counts shifted (the panic gate's clean-tree total is 1167 today, not 1164; a lane tracefile carries 1640 sources, not 1637). Nothing else moved, and every equivalence proof in the table above was re-run from scratch on the rebased base.Phase 1 — one crate moved (
crates/ironclaw_events→crates/substrates/ironclaw_events), the realistic WS7 batch shape:Phase 2 — the WS7 end state, all 65 crates moved into
crates/substrates/:Phase 2's panic-gate result is the point of the
no_panics_reborn_baseline.txtclause in the checklist row: the rewritten gate finds all 51 violations under their new paths and demands an atomic baseline regeneration, loudly, instead of crashing or under-scanning. This PR changes no keying, so no regeneration was owed here — the baseline file is untouched and the violation count is 51 before and after. Whoever performs the actualgit mvstill owes the regeneration in that same commit.E2E path-filter trigger delta
Both filters were computed against all 4179 tracked files:
crates/AGENTS.md,crates/Architecture.md,crates/README.mdCost rationale for choosing
crates/**over an enumeratedcrates/ironclaw_*/** + crates/*/ironclaw_*/**pair: the enumerated form would have been a literal zero-delta today, but it only survives one level of nesting with anironclaw_-prefixed directory name — and WS2 colocates extension packages ascrates/extensions/packages/<ext>/, which carries extension ids, not crate names. That form would silently drop them, recreating the exact bug in a year.crates/**cannot. The cost is that a change touching only a repo-root markdown file undercrates/(edited a handful of times a year) now triggers the E2E push run, and under the target tree the ten familyAGENTS.mdfiles will too — one extra CI run on a docs edit, against a gate whose entire job is to not be skippable.Sweep (
rg "crates/ironclaw" .github/workflows/ scripts/)Fixed here (beyond the five named gates): none — deliberately. The five below are the same mechanical class but belong to CHECKLIST rows 128/129 and would have doubled this diff; they are recorded on the WS0 blocking-prerequisite row so the next
git mvcannot start without seeing them.Recorded as follow-up — silently green under nesting:
.github/workflows/code_style.ymlhas_dist_buildscope regex (^(crates/ironclaw_runner/|crates/ironclaw_reborn_cli/|…)) — dist-build lane silently skips..github/workflows/platform-and-compat.ymlhas_direct_wasm_abi_riskregex — WASM ABI checks silently skip. Already partly stale: it names the deletedcrates/ironclaw_wasm_product_adapters/..github/workflows/ironclaw-stress.ymlpushpaths:(crates/ironclaw_filesystem/**, …) — the workflow stops triggering.scripts/ci/regression-test-check.pyHIGH_RISK_PATTERNSprefix list plus itscrates/ironclaw_webui/frontend/prefixes (relocated out of the workflow by feat(testing): add regression promotion loop #6884; already stale — it still names the deletedironclaw_run_state) — the "this change needs a regression test" requirement quietly relaxes.scripts/build-wasm-extensions.shcrates/ironclaw_first_party_extensions/assets/*/manifest.tomlundershopt -s nullglob— builds nothing, exits 0.Verified already safe (discipline 1 — no change made):
scripts/ci/discover-reborn-package-crates.sh— already name-keyedcargo metadata, and already errors onNo Reborn workspace crates discovered.scripts/ci/check-include-str-paths.sh—rgloboversrc/andcrates/, depth-agnostic.Verified loud, not silent:
scripts/ci/check-composition-budget.sh— itscrates/*/srcdenominator hits the pre-existingdenominator LOC is 0 — no crates/*/src trees foundguard and exits 1.Stale entries found and left alone — now filed as #6947.
classify-test-scope.shstill listscrates/ironclaw_oauth/*andcrates/ironclaw_product_*/*, neither of which matches anything today (ironclaw_oauthwas removed by #5874; #6583 folded the fourironclaw_product_*crates intoironclaw_product, whose path the surviving glob can never match because it requires a literal_afterproduct). The consequence is a real bug, not just rot: a diff touching onlycrates/ironclaw_product/**(97 files, ~61k lines) classifieshas_reborn_tests=false,reborn-tests.ymlskips all eight Reborn lanes — includingcargo test -p ironclaw_productitself — and the roll-up reports success via itsNo Reborn test scope detectedfast path. Proven pre-existing:origin/main's classifier and this branch's produce identical output for that input. Not fixed here because the fix changes CI behavior (correctly, toward running more) and would have destroyed this PR's behavior-free equivalence proof; #6947 carries the one-line fix, the dead-arm cleanup, and the phantomcrates/ironclaw_product_storage/src/lib.rsself-test case that let the rot survive. This PR's fail-closed default arm does NOT cover this case — that path is attributable to a crate, so it normalizes fine and simply matches no arm; catching it would need the arm inventory pinned against the crate inventory, which is a behavior change and couples to #6942's crate deletions. #6947 says so explicitly.Verification
python3 scripts/check_no_panics.py --self-test— 31 tests OK (was 26; +5 for nested discovery, the name-keyed shipping anchor, and both fail-closed paths).python3 scripts/check_no_panics.py --reborn-baseline— stdout byte-identical to base.bash scripts/ci/test-classify-test-scope.sh— exit 0, +7 cases (nested shared/reborn/unlisted crates, unattributable path, unreadable tree, below-floor tree, bash↔python inventory agreement over 65 crate directories).bash scripts/ci/test-reborn-coverage.sh— 143/155 (was 135/147). The same 12 section-C failures fail before and after (reborn-coverage-comment.shagainst its fakegh; pre-existing on this machine, unrelated). All M-section cases pass, +10 new.python3 scripts/ci/test_ws12_workflow_contracts.py— 10 tests OK (was 5).python3 scripts/ci/ws12_workflow_contracts.pypasses.python3 scripts/test_dev_metrics.py— 9 passed, 0 failed.cargo test -p ironclaw_architecture— 94 tests, 0 failures, includingreborn_restructure_baselines::reborn_restructure_baseline_ratchets_stay_armed.bash -nclean on all four touched shell scripts;shellcheck -S warningreports only the pre-existingSC2034 ratchet_shintest-reborn-coverage.sh(present onorigin/maintoo).python3 -m py_compileclean on all touched Python.tools//root-tests/paths — stated per the mission's fixture allowance.Merge with
main(2026-07-31)mainmoved 24 commits under this branch and was merged in (two merge commits:ec046a22cfor the first 23, then62a37b503when #6943 landed mid-merge). Exactly one real conflict, and it was a semantic fold rather than a textual one.scripts/ci/reborn-coverage-merge-lcov.sh— #6889 vs WS10. #6889 (17b068366) extended the merger to sumBRDA:branch records per(file, line, block, branch), treat-as 0, and recomputeBRF/BRH; it still carried thecrates/ironclaw_*regex this PR replaces. The two intents are orthogonal and both are kept whole: main's record shape{"lines": {}, "branches": {}}plus this PR'skept_hereaccounting, and main's "summaries are recomputed below" comment (ours claimed BRDA was ignored, which #6889 made untrue).Equivalence re-proven against the new baseline — the original proof predated branch merging and no longer certified. On a 3-lane fixture built from all 1628 real workspace sources plus registry/rustc/
tools/paths, carrying 14652 BRDA records including-not-taken values, branches present in only one lane, and deliberately wrongLF/LH/BRF/BRHinputs that must be recomputed:origin/main's merger and this one produce byte-identical output. The other three gates were re-compared on the same final tree: identical panic-gate stdout (1157 files, 51 reviewed invariant(s)), identical per-path classifier verdict across all 4192 tracked files, identicaldev_metricstier-3 snapshot. Fail-closed behavior re-verified post-fold: no-match input still exits 1 and writes no file.One test fixture repaired (not a behavior change): #6889 promoted
ironclaw_extension_hostinto the Reborn arm, so this PR's "nested unlisted crate" case needed a crate still in neither list — nowironclaw_mcp, with a comment on how to re-pick it.Sweep of the new commits. #6889 added two more flat-tree-keyed gates, recorded on the CHECKLIST WS0 row and not fixed here (same behavior-free rule):
scripts/ci/reborn_changed_coverage.py— silent. Itsgit diff -- "crates/ironclaw_*/src/**/*.rs"pathspec matches nothing once crates nest, so the changed-coverage gate sees zero changed production files and enforces nothing. Same for thePRODUCTION_PATH = ^crates/ironclaw_[^/]+/src/.+\.rs$filter it applies to+++ b/lines.scripts/ci/critical_mutation_gate.py— loud, but still flat-keyed. ItsPRODUCTION_PATHregex andpackage_root = f"crates/{package}/"are manifest schema validation, so a moved tree raisesGateErrorand blocks rather than passing quietly. Still owed a repoint.scripts/ci/check-reborn-branch-coverage-flags.py(no path keying) and fix(ci): create nested mutation audit output directory #6954'sscripts/mutation-audit.sh/test-mutation-audit.sh(comment text only).#6947 status corrected. #6889 independently fixed that issue's headline bug by adding
crates/ironclaw_product/*to the Reborn arm — a product-only diff now classifieshas_reborn_tests=true. The issue is updated and stays open for the residue #6889 did not touch: the still-deadironclaw_product_*andironclaw_oautharms, the phantomironclaw_product_storageself-test case, and the durable inventory pin.Interaction with the other Wave 0 PRs
ironclaw_dispatcher,ironclaw_embeddings, rootfuzz/): no coupling. The classifier arms that name those crates are globs, not assertions, so a deletion just stops matching — verified by running both classifiers over the deleted-crate paths and getting identical output. Thecrates/ironclaw_embeddings/*arm is left in place because the crate exists at this base.scripts/or.github/workflows/files in common).docs/reborn/target-architecture/CHECKLIST.mdis edited in a separate trailingdocs:commit, touching only the WS10 path-keyed-gate row and the WS0 blocking-prerequisite row.The WS0 blocking-prerequisite box is deliberately left unticked: its five named gates are done, but the sweep proves five more of the same class still die silently at the first
git mv, and ticking it would tell the next slot the coast is clear.🤖 Generated with Claude Code