Skip to content

ci(gates): WS10 path-keyed gate rewrites — tree-shape-agnostic discovery, fail-closed - #6946

Merged
BenKurrek merged 5 commits into
mainfrom
ws0/path-gate-rewrites
Jul 31, 2026
Merged

BenKurrek merged 5 commits into
mainfrom
ws0/path-gate-rewrites

Conversation

@BenKurrek

@BenKurrek BenKurrek commented Jul 31, 2026 •

Copy link
Copy Markdown
Collaborator

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 family git 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

Gate Old keying Failure mode confirmed at this base New discovery Fail-closed assertion Equivalence proof
scripts/ci/reborn-coverage-merge-lcov.sh re: (?:^|/)crates/(ironclaw_[A-Za-z0-9_]+)/ Silently green. With one crate nested, a lane tracefile containing that crate's sources merged 0 records, wrote a 0-byte tracefile and exited 0. With all 65 crates nested: 0 of 1637 files kept, exit 0. Filter built from the crate inventory (scripts/ci/lib/crate_tree.py — outermost crates/**/Cargo.toml owners), matched on a path-segment boundary. Filesystem-based deliberately: the coverage-report job installs Python only, no Rust toolchain. Empty crate inventory → error; empty merged tracefile → error (was exit 0 + empty file); per-input kept-file counts printed to stderr. Byte-identical merged output on a 3-lane synthetic fixture built from all 1640 real repo sources + registry/rustc/tools paths (byte-for-byte identical, 1640 SF records both sides).
scripts/check_no_panics.py manifest.parent.parent == crates/ (manifest exactly one level down) + shipping package pinned to crates/ironclaw_reborn_cli/Cargo.toml Silently green. With ironclaw_events nested, the gate scanned 1156 files instead of 1164 and printed OK: … matches. Crates with no baseline entries would drop out of scope entirely and unnoticed. cargo metadata (toolchain is available in the no-panics job): reachable workspace members whose manifest is under crates/ at any depth; the shipping package is resolved by name (ironclaw), which survives the planned crates/ironclaw_reborn_cli → crates/app/ironclaw_cli rename. A shipping workspace member whose manifest is outside the crate tree → error naming it; a shipping crate with no lib/bin target → error; missing/ambiguous ironclaw package → error. stdout byte-identical (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.yml push paths: crates/ironclaw_*/** + mirrored changes-job regex ^(crates/ironclaw_[^/]+/|…) Silently green. Nested crate paths resolved has_e2e_scope=false → every E2E job skipped and the reborn-e2e roll-up (a required check) passes. paths: crates/** and grep -Eq '^(crates/|…)' — both depth-independent. scripts/ci/ws12_workflow_contracts.py now extracts the changes-job regex from the workflow text and replays 10 probe paths through it (including crates/substrates/ironclaw_events/src/lib.rs and crates/extensions/packages/slack/manifest.toml), and requires - "crates/**" in the push filter. 4 sabotage tests cover re-narrowing, over-broadening, and deletion. Trigger delta computed over all 4187 tracked files — see below.
scripts/ci/classify-test-scope.sh case arms keyed to crates/ironclaw_<name>/* Silently green. A nested ironclaw_events change classified has_reborn_tests=false — the entire Reborn suite skipped on a green PR. A nested ironclaw_webui change classified as legacy-only. Every crates/ path is normalized to crates/<crate>/… before the arms run, resolving the owning crate directory outward-in against a crates/**/Cargo.toml inventory (so the outermost crate wins, exactly as crates/<crate>/* did for ironclaw_safety/fuzz and the assets/*/wasm-src guests). Arms stay keyed to crate identity and are otherwise untouched. Missing crates/ tree or an inventory under the floor (20) → error; a crates/ 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 to crate_tree.py's. Per-path verdict compared for all 4187 tracked files — byte-identical. Also verified identical for the crate paths #6942 deletes (ironclaw_dispatcher, ironclaw_embeddings) and for a crate that does not exist.
scripts/dev_metrics.py COMPOSITION = "crates/ironclaw_reborn_composition/src", glob("crates/*"), _prod_lines("crates/*/src"), literal architecture-test path Silently green (worst of the five). With all crates nested: crate_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%). Composition, architecture and denominator paths resolve through 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. tier-3 JSON snapshot (fixed now) diff-identical against a clean checkout of the base.

One intentional behavior delta

reborn-coverage-merge-lcov.sh previously 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 every SF: 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 into crates/substrates/ on a throwaway branch, ran the origin/main gate vs the rewritten gate on the same tree, then discarded the branch. cargo metadata was kept healthy in both phases (root members, path deps, tools/ironclaw_stress refs rewritten).

Both transcripts below are verbatim from runs at cd5d3fed8, before this branch was rebased onto ec64182bb. main has 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:

========== 1. scripts/ci/reborn-coverage-merge-lcov.sh ==========
--- OLD:   exit=0  output bytes=0    SF records=0
--- NEW:   exit=0  output bytes=122  SF records=1
           reborn-coverage-merge-lcov: sim_lane.lcov: 1 crate source file(s)

========== 2. scripts/check_no_panics.py --reborn-baseline ==========
--- OLD:   OK: Reborn production panic baseline matches (1156 files, 51 reviewed invariant(s).)   exit=0
--- NEW:   OK: Reborn production panic baseline matches (1164 files, 51 reviewed invariant(s).)   exit=0

========== 3. scripts/ci/classify-test-scope.sh  (crates/substrates/ironclaw_events/src/lib.rs) ==========
--- OLD:   docs_only=false has_core_code=true has_legacy_tests=true has_reborn_tests=false
--- NEW:   docs_only=false has_core_code=true has_legacy_tests=true has_reborn_tests=true

========== 4. scripts/dev_metrics.py (tier 3) ==========
--- OLD:   crate_count=65  composition_share_gate=6.42 %  composition_kloc_now=68.1  boundary_test_count=32
--- NEW:   crate_count=65  composition_share_gate=6.4 %   composition_kloc_now=68.1  boundary_test_count=32

========== 5. .github/workflows/reborn-e2e.yml scope regex ==========
changed files probed: crates/substrates/ironclaw_events/{AGENTS.md,CLAUDE.md,Cargo.toml}
--- OLD regex has_e2e_scope=false   (jobs skipped when false; roll-up still reports success)
--- NEW regex has_e2e_scope=true

Phase 2 — the WS7 end state, all 65 crates moved into crates/substrates/:

========== 1. reborn-coverage-merge-lcov.sh (lane tracefile: 1637 workspace source files) ==========
--- OLD:   exit=0  SF records kept=0
--- NEW:   exit=0  SF records kept=1637

========== 2. check_no_panics.py --reborn-baseline ==========
--- OLD:   RuntimeError: expected exactly one shipping Reborn package at
           .../crates/ironclaw_reborn_cli/Cargo.toml, found 0        (crashes before scanning)
--- NEW:   ::error::Reborn production panic baseline changed.
           New or changed panic-style calls:
           crates/substrates/ironclaw_agent_loop/src/executor/capabilities.rs:1130: …
           … and 31 more
           Stale baseline entries (remove them to ratchet downward): … and 30 more

========== 3. classify-test-scope.sh  (crates/substrates/ironclaw_webui/src/lib.rs) ==========
--- OLD:   docs_only=false has_core_code=true has_legacy_tests=true  has_reborn_tests=false
--- NEW:   docs_only=false has_core_code=true has_legacy_tests=false has_reborn_tests=true

========== 4. dev_metrics.py (tier 3) ==========
--- OLD:   crate_count=1   composition_share_gate=0.0 %  composition_kloc_now=0.0   boundary_test_count=0
--- NEW:   crate_count=65  composition_share_gate=6.4 %  composition_kloc_now=68.1  boundary_test_count=32

Phase 2's panic-gate result is the point of the no_panics_reborn_baseline.txt clause 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 actual git mv still owes the regeneration in that same commit.

E2E path-filter trigger delta

Both filters were computed against all 4179 tracked files:

  • Newly in scope (3): crates/AGENTS.md, crates/Architecture.md, crates/README.md
  • Newly out of scope (0): none

Cost rationale for choosing crates/** over an enumerated crates/ironclaw_*/** + crates/*/ironclaw_*/** pair: the enumerated form would have been a literal zero-delta today, but it only survives one level of nesting with an ironclaw_-prefixed directory name — and WS2 colocates extension packages as crates/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 under crates/ (edited a handful of times a year) now triggers the E2E push run, and under the target tree the ten family AGENTS.md files 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 mv cannot start without seeing them.

Recorded as follow-up — silently green under nesting:

  1. .github/workflows/code_style.yml has_dist_build scope regex (^(crates/ironclaw_runner/|crates/ironclaw_reborn_cli/|…)) — dist-build lane silently skips.
  2. .github/workflows/platform-and-compat.yml has_direct_wasm_abi_risk regex — WASM ABI checks silently skip. Already partly stale: it names the deleted crates/ironclaw_wasm_product_adapters/.
  3. .github/workflows/ironclaw-stress.yml push paths: (crates/ironclaw_filesystem/**, …) — the workflow stops triggering.
  4. scripts/ci/regression-test-check.py HIGH_RISK_PATTERNS prefix list plus its crates/ironclaw_webui/frontend/ prefixes (relocated out of the workflow by feat(testing): add regression promotion loop #6884; already stale — it still names the deleted ironclaw_run_state) — the "this change needs a regression test" requirement quietly relaxes.
  5. scripts/build-wasm-extensions.sh crates/ironclaw_first_party_extensions/assets/*/manifest.toml under shopt -s nullglob — builds nothing, exits 0.

Verified already safe (discipline 1 — no change made):

  • scripts/ci/discover-reborn-package-crates.sh — already name-keyed cargo metadata, and already errors on No Reborn workspace crates discovered.
  • scripts/ci/check-include-str-paths.sh — rglob over src/ and crates/, depth-agnostic.

Verified loud, not silent: scripts/ci/check-composition-budget.sh — its crates/*/src denominator hits the pre-existing denominator LOC is 0 — no crates/*/src trees found guard and exits 1.

Stale entries found and left alone — now filed as #6947. classify-test-scope.sh still lists crates/ironclaw_oauth/* and crates/ironclaw_product_*/*, neither of which matches anything today (ironclaw_oauth was removed by #5874; #6583 folded the four ironclaw_product_* crates into ironclaw_product, whose path the surviving glob can never match because it requires a literal _ after product). The consequence is a real bug, not just rot: a diff touching only crates/ironclaw_product/** (97 files, ~61k lines) classifies has_reborn_tests=false, reborn-tests.yml skips all eight Reborn lanes — including cargo test -p ironclaw_product itself — and the roll-up reports success via its No Reborn test scope detected fast 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 phantom crates/ironclaw_product_storage/src/lib.rs self-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.sh against its fake gh; 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.py passes.
  • python3 scripts/test_dev_metrics.py — 9 passed, 0 failed.
  • cargo test -p ironclaw_architecture — 94 tests, 0 failures, including reborn_restructure_baselines::reborn_restructure_baseline_ratchets_stay_armed.
  • bash -n clean on all four touched shell scripts; shellcheck -S warning reports only the pre-existing SC2034 ratchet_sh in test-reborn-coverage.sh (present on origin/main too). python3 -m py_compile clean on all touched Python.
  • The coverage merge cannot be driven by a real CI run locally, so its equivalence proof is a synthetic 3-lane lcov fixture built from every real workspace source path plus registry/rustc/tools//root-tests/ paths — stated per the mission's fixture allowance.

Merge with main (2026-07-31)

main moved 24 commits under this branch and was merged in (two merge commits: ec046a22c for the first 23, then 62a37b503 when #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 sum BRDA: branch records per (file, line, block, branch), treat - as 0, and recompute BRF/BRH; it still carried the crates/ironclaw_* regex this PR replaces. The two intents are orthogonal and both are kept whole: main's record shape {"lines": {}, "branches": {}} plus this PR's 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 — 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 wrong LF/LH/BRF/BRH inputs 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, identical dev_metrics tier-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_host into the Reborn arm, so this PR's "nested unlisted crate" case needed a crate still in neither list — now ironclaw_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. Its git 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 the PRODUCTION_PATH = ^crates/ironclaw_[^/]+/src/.+\.rs$ filter it applies to +++ b/ lines.
  • scripts/ci/critical_mutation_gate.py — loud, but still flat-keyed. Its PRODUCTION_PATH regex and package_root = f"crates/{package}/" are manifest schema validation, so a moved tree raises GateError and blocks rather than passing quietly. Still owed a repoint.
  • Clean, no change needed: scripts/ci/check-reborn-branch-coverage-flags.py (no path keying) and fix(ci): create nested mutation audit output directory #6954's scripts/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 classifies has_reborn_tests=true. The issue is updated and stays open for the residue #6889 did not touch: the still-dead ironclaw_product_* and ironclaw_oauth arms, the phantom ironclaw_product_storage self-test case, and the durable inventory pin.

Interaction with the other Wave 0 PRs

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

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@railway-app

railway-app Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-6946 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw 🕒 Building (View Logs) Web Jul 31, 2026 at 2:52 pm

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6946 July 31, 2026 08:04 Destroyed
BenKurrek added a commit that referenced this pull request Jul 31, 2026
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>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6946 July 31, 2026 08:04 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs scope: ci CI/CD workflows scope: docs Documentation labels Jul 31, 2026
…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>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • CI Improvements

    • Improved end-to-end testing, coverage reporting, test classification, and development metrics for nested, renamed, deleted, and vendored crate paths.
    • Added reliable crate discovery across workspace layouts.
    • Added safeguards that fail clearly when crate discovery, path attribution, or coverage matching is incomplete.
  • Validation

    • Added workflow-filter checks and regression coverage for invalid configurations, missing paths, and nested crate layouts.
  • Documentation

    • Updated the architecture checklist with additional path-safety gates and validation results.

Walkthrough

The 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.

Changes

Nested crate tooling

Layer / File(s) Summary
Crate inventory and path normalization
scripts/ci/lib/crate_tree.py, scripts/ci/classify-test-scope.sh, scripts/ci/test-classify-test-scope.sh
Crate discovery, ownership resolution, canonical path normalization, nested-layout tests, refusal tests, and Bash/Python inventory parity checks were added.
Shipping package validation
scripts/check_no_panics.py
Shipping-package selection now uses the ironclaw package name. Production roots are discovered across nested workspace crates, with validation for unsupported layouts.
Coverage and metrics integration
scripts/ci/reborn-coverage-merge-lcov.sh, scripts/ci/test-reborn-coverage.sh, scripts/dev_metrics.py
Coverage and metrics now use discovered crate paths. Vendored paths are excluded, nested crates are included, and empty coverage matches fail closed.
E2E workflow scope contracts
.github/workflows/reborn-e2e.yml, scripts/ci/ws12_workflow_contracts.py, scripts/ci/test_ws12_workflow_contracts.py, docs/reborn/target-architecture/CHECKLIST.md
E2E filters accept nested crates/** paths. Contract tests validate required scope behavior, and the checklist records related gates and WS10 completion.

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
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: serrrfirat, ilblackdragon

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides strong technical detail and validation evidence but omits required template sections, including change type, linked issue, security, database, rollback, and review follow-through. Add all required template sections, mark applicable change types, state security and database impact, document the CI rollback plan, and summarize review follow-through.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses Conventional Commits style and accurately describes the tree-shape-agnostic CI gate changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

BenKurrek added a commit that referenced this pull request Jul 31, 2026
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>
@BenKurrek
BenKurrek force-pushed the ws0/path-gate-rewrites branch from 7c325ba to 9e6fb28 Compare July 31, 2026 08:13
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6946 July 31, 2026 08:13 Destroyed
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>
@BenKurrek
BenKurrek force-pushed the ws0/path-gate-rewrites branch from 9e6fb28 to 7f96b78 Compare July 31, 2026 08:19
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6946 July 31, 2026 08:19 Destroyed
@BenKurrek

Copy link
Copy Markdown
Collaborator Author

Coordinator sign-off (Wave 0 slot 4 — WS10 path-keyed gate rewrites).

Review protocol ran: independent verification, one bundled feedback round, fixes verified.

  • The five silent failure modes are real and were reproduced at base before fixing — coverage merge kept 0/1,640 files at exit 0; the panic scanner under-scanned silently; nested paths turned the e2e scope filter, the test classifier, and dev-metrics quietly dark. The rewrites move discovery to cargo metadata/inventory-driven forms with fail-closed assertions, proven behavior-free on today's tree (byte-identical lcov, identical 51-violation panic set with unchanged keying — no baseline regen owed, identical classifier verdicts across 4,187 tracked files), plus a real git mv simulation transcript (one crate, then all 65) showing old-dark vs new-loud.
  • Deliberate honesty in the checklist: the WS10 row is ticked; the WS7-blocking-prerequisite row is NOT — the sweep found five more same-class gates (code_style.yml, platform-and-compat.yml, ironclaw-stress.yml, regression-test-check.py, build-wasm-extensions.sh), inventoried on that row so the first family git mv can't be green-lit on a false all-clear.
  • Issue classify-test-scope.sh: ironclaw_product mis-bucketed as legacy-only (glob predates the product-crate merge) #6947 filed for the pre-existing classifier bug this work surfaced (crates/ironclaw_product_*/* glob predates Use shared product surface vocabulary #6583's crate merge — product-only diffs skip all eight Reborn-gated jobs while the roll-up reports success; I verified the glob myself). Correctly NOT fixed here: the one-line fix changes CI behavior and would have destroyed the equivalence proof this PR stands on.
  • One intentional behavior delta, called out: an empty coverage merge was exit 0 + empty tracefile, now an error. Fail-closed by design; nothing healthy produces that input. E2e filter trigger delta on the current tree: +3 (crates/{AGENTS,Architecture,README}.md), −0.
  • CodeRabbit posted no review (0 comments, 0 reviews — same as refactor: delete verified-dead modules across seven crates (WS0) #6943/docs(guidance): WS11.3 drift hotfixes — make agent guidance true against HEAD #6944). Architecture suite 94/0 at the gates commit; classifier/coverage/panic/workflow-contract self-tests green; bash -n/shellcheck/py_compile clean. No test(architecture): baselines + shrink-only exception ratchet (WS0) #6936 ratchet number touched.

Ready for Ben to merge. Two-commit shape: ea2ac3bff (gates) + 7f96b78fe (docs). CHECKLIST overlap with the other Wave 0 PRs is confined to rows 17 and 127 — trivial rebases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between ec64182 and 9e6fb28.

📒 Files selected for processing (11)
  • .github/workflows/reborn-e2e.yml
  • docs/reborn/target-architecture/CHECKLIST.md
  • scripts/check_no_panics.py
  • scripts/ci/classify-test-scope.sh
  • scripts/ci/lib/crate_tree.py
  • scripts/ci/reborn-coverage-merge-lcov.sh
  • scripts/ci/test-classify-test-scope.sh
  • scripts/ci/test-reborn-coverage.sh
  • scripts/ci/test_ws12_workflow_contracts.py
  • scripts/ci/ws12_workflow_contracts.py
  • scripts/dev_metrics.py

Comment thread docs/reborn/target-architecture/CHECKLIST.md Outdated
Comment thread scripts/ci/classify-test-scope.sh
Comment thread scripts/ci/lib/crate_tree.py
Comment thread scripts/ci/lib/crate_tree.py Outdated
Comment thread scripts/ci/test-classify-test-scope.sh Outdated
Comment thread scripts/ci/ws12_workflow_contracts.py
Comment thread scripts/dev_metrics.py Outdated
Comment thread scripts/dev_metrics.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9e6fb28 and 7f96b78.

📒 Files selected for processing (1)
  • docs/reborn/target-architecture/CHECKLIST.md

Comment thread docs/reborn/target-architecture/CHECKLIST.md Outdated
@github-actions

github-actions Bot commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.78% (320159 / 373235 lines)
  floor:    85.11% (tolerance 0.5pp -> effective floor 84.61%)
  denominator: 373235 lines now vs 375097 at floor capture (-1862 lines, -0.5%) — not a material change

RATCHET PASS: ironclaw_runner
  observed: 86% (15127 / 17590 lines)
  floor:    85.55% (tolerance 0.5pp -> effective floor 85.05%)
  floor_covered_lines: 14658 (tolerance 20 lines -> effective floor 14638)
  denominator: 17590 lines now vs 17133 at floor capture (+457 lines, +2.67%) — not a material change

RATCHET PASS: ironclaw_processes
  observed: 88.76% (5889 / 6635 lines)
  floor:    88.07% (tolerance 0.5pp -> effective floor 87.57%)
  floor_covered_lines: 5839 (tolerance 20 lines -> effective floor 5819)
  denominator: 6635 lines now vs 6630 at floor capture (+5 lines, +0.08%) — not a material change

RATCHET PASS: ironclaw_turns
  observed: 86.54% (9863 / 11397 lines)
  floor:    85.11% (tolerance 0.5pp -> effective floor 84.61%)
  floor_covered_lines: 9515 (tolerance 20 lines -> effective floor 9495)
  denominator: 11397 lines now vs 11179 at floor capture (+218 lines, +1.95%) — not a material change

RATCHET PASS: ironclaw_authorization
  observed: 86.59% (723 / 835 lines)
  floor:    62.51% (tolerance 0.5pp -> effective floor 62.01%)
  floor_covered_lines: 612 (tolerance 20 lines -> effective floor 592)
  denominator: 835 lines now vs 979 at floor capture (-144 lines, -14.71%) — material change (>5%)

RATCHET PASS: ironclaw_approvals
  observed: 91.05% (1820 / 1999 lines)
  floor:    85.86% (tolerance 0.5pp -> effective floor 85.36%)
  floor_covered_lines: 1822 (tolerance 20 lines -> effective floor 1802)
  denominator: 1999 lines now vs 2122 at floor capture (-123 lines, -5.8%) — material change (>5%)

RATCHET PASS: ironclaw_secrets
  observed: 85.74% (2886 / 3366 lines)
  floor:    84.01% (tolerance 0.5pp -> effective floor 83.51%)
  floor_covered_lines: 2795 (tolerance 20 lines -> effective floor 2775)
  denominator: 3366 lines now vs 3327 at floor capture (+39 lines, +1.17%) — not a material change

RATCHET PASS: ironclaw_filesystem
  observed: 75.93% (5826 / 7673 lines)
  floor:    75.93% (tolerance 0.5pp -> effective floor 75.43%)
  floor_covered_lines: 5826 (tolerance 20 lines -> effective floor 5806)
  denominator: 7673 lines now vs 7673 at floor capture (+0 lines, +0%) — not a material change

RATCHET PASS: ironclaw_llm
  observed: 79.96% (22644 / 28318 lines)
  floor:    79.92% (tolerance 0.5pp -> effective floor 79.42%)
  floor_covered_lines: 22566 (tolerance 20 lines -> effective floor 22546)
  denominator: 28318 lines now vs 28235 at floor capture (+83 lines, +0.29%) — not a material change

RATCHET PASS: ironclaw_triggers
  observed: 94.88% (3092 / 3259 lines)
  floor:    86.04% (tolerance 0.5pp -> effective floor 85.54%)
  floor_covered_lines: 2804 (tolerance 20 lines -> effective floor 2784)
  denominator: 3259 lines now vs 3259 at floor capture (+0 lines, +0%) — not a material change

RATCHET PASS: ironclaw_product
  observed: 87.49% (22827 / 26090 lines)
  floor:    86.94% (tolerance 0.5pp -> effective floor 86.44%)
  floor_covered_lines: 21367 (tolerance 20 lines -> effective floor 21347)
  denominator: 26090 lines now vs 24576 at floor capture (+1514 lines, +6.16%) — material change (>5%)

RATCHET PASS: ironclaw_outbound
  observed: 94.68% (4271 / 4511 lines)
  floor:    93.49% (tolerance 0.5pp -> effective floor 92.99%)
  floor_covered_lines: 4105 (tolerance 20 lines -> effective floor 4085)
  denominator: 4511 lines now vs 4391 at floor capture (+120 lines, +2.73%) — not a material change

RATCHET PASS: ironclaw_extension_host
  observed: 83.82% (22271 / 26569 lines)
  floor:    83.82% (tolerance 0.5pp -> effective floor 83.32%)
  floor_covered_lines: 22271 (tolerance 20 lines -> effective floor 22251)
  denominator: 26569 lines now vs 26569 at floor capture (+0 lines, +0%) — not a material change

RATCHET FAIL: ironclaw_events
  observed: 80.55% (1197 / 1486 lines)
  floor:    81.04% (tolerance 0.5pp -> effective floor 80.54%)
  floor_covered_lines: 1252 (tolerance 20 lines -> effective floor 1232)
  denominator: 1486 lines now vs 1545 at floor capture (-59 lines, -3.82%) — not a material change
  To fix:
    - If this is a real coverage regression: add tests, don't touch the floor file.
    - If this is a legitimate denominator/numerator shift EITHER direction —
      growth (new/renamed module entering instrumentation, e.g. #5656) OR
      shrinkage (a code+test deletion legitimately lowering covered lines,
      even when the denominator barely moves): update
      tests/integration/coverage-floor.toml's [[crate]] entry for
      ironclaw_events IN THIS PR — bump (or lower) captured_total_lines and
      floor_percent/floor_covered_lines to the new observed numbers, set
      captured_date, and add a one-line rationale + issue link.
      See the file's own header for the schema.

RATCHET PASS: ironclaw_safety
  observed: 92.75% (4468 / 4817 lines)
  floor:    92.44% (tolerance 0.5pp -> effective floor 91.94%)
  floor_covered_lines: 3973 (tolerance 20 lines -> effective floor 3953)
  denominator: 4817 lines now vs 4298 at floor capture (+519 lines, +12.08%) — material change (>5%)

RATCHET PASS: ironclaw_host_runtime
  observed: 88.33% (21072 / 23855 lines)
  floor:    88.23% (tolerance 0.5pp -> effective floor 87.73%)
  floor_covered_lines: 20538 (tolerance 20 lines -> effective floor 20518)
  denominator: 23855 lines now vs 23277 at floor capture (+578 lines, +2.48%) — not a material change

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.78% — 320159 / 373235 lines

Per-crate breakdown (60 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_host_ingress 42.5% 17 / 40
ironclaw_memory 53.48% 630 / 1178
ironclaw_projects 72.36% 233 / 322
ironclaw_trust 73.71% 670 / 909
ironclaw_capabilities 73.92% 2854 / 3861
ironclaw_extractors 75.88% 538 / 709
ironclaw_filesystem 75.93% 5826 / 7673
ironclaw_reborn_cli 76.1% 11084 / 14566
ironclaw_observability 76.19% 32 / 42
ironclaw_wasm 78.84% 704 / 893
ironclaw_llm 79.96% 22644 / 28318
ironclaw_events 80.55% 1197 / 1486
ironclaw_auth 82.03% 5960 / 7266
ironclaw_first_party_extensions 82.57% 6784 / 8216
ironclaw_memory_native 82.85% 2850 / 3440
ironclaw_libsql_runtime 83.3% 384 / 461
ironclaw_host_api 83.42% 9718 / 11649
ironclaw_extension_host 83.82% 22271 / 26569
ironclaw_operator 84.47% 5309 / 6285
ironclaw_hooks 84.58% 9906 / 11712
ironclaw_event_projections 84.81% 854 / 1007
ironclaw_network 84.92% 890 / 1048
ironclaw_reborn_event_store 84.93% 1206 / 1420
ironclaw_reborn_config 85.29% 2110 / 2474
ironclaw_reborn_composition 85.5% 21778 / 25470
ironclaw_secrets 85.74% 2886 / 3366
ironclaw_runner 86% 15127 / 17590
ironclaw_turns 86.54% 9863 / 11397
ironclaw_authorization 86.59% 723 / 835
ironclaw_webui 86.93% 11821 / 13598
ironclaw_wasm_limiter 87.06% 74 / 85
ironclaw_common 87.33% 1641 / 1879
ironclaw_product 87.49% 22827 / 26090
ironclaw_reborn_traces 87.61% 11720 / 13377
ironclaw_extensions 87.84% 4870 / 5544
ironclaw_scripts 87.87% 420 / 478
ironclaw_threads 88.14% 5189 / 5887
ironclaw_skills 88.16% 2771 / 3143
ironclaw_host_runtime 88.33% 21072 / 23855
ironclaw_telegram_extension 88.52% 586 / 662
ironclaw_process_sandbox 88.64% 281 / 317
ironclaw_processes 88.76% 5889 / 6635
ironclaw_reborn_openai_compat 89.4% 3644 / 4076
ironclaw_telegram_v2_adapter 89.43% 1573 / 1759
ironclaw_loop_host 90.47% 18045 / 19946
ironclaw_resources 90.76% 4084 / 4500
ironclaw_approvals 91.05% 1820 / 1999
ironclaw_reborn_identity 91.3% 451 / 494
ironclaw_mcp 91.91% 1318 / 1434
ironclaw_conversations 92.08% 2383 / 2588
ironclaw_event_streams 92.5% 1048 / 1133
ironclaw_safety 92.75% 4468 / 4817
ironclaw_agent_loop 93.52% 10428 / 11151
ironclaw_slack_extension 93.94% 3689 / 3927
ironclaw_first_party_extension_ports 94.66% 3758 / 3970
ironclaw_outbound 94.68% 4271 / 4511
ironclaw_triggers 94.88% 3092 / 3259
ironclaw_prompt_envelope 97.46% 192 / 197
ironclaw_runtime_policy 97.6% 855 / 876
ironclaw_attachments 98.23% 831 / 846

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)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657
crates/ironclaw_attachments/src/lib.rs Declarative crate facade: module declarations, constants, and re-exports only; executable attachment modules remain covered. #6524
crates/ironclaw_extension_host/src/ingress/mod.rs Declarative ingress module facade and documentation only; executable router modules remain covered. #6524
crates/ironclaw_host_api/src/lib.rs Declarative crate facade: module declarations and re-exports only; executable host API modules remain covered. #6524
crates/ironclaw_host_api/src/product_adapter/mod.rs Declarative product-adapter facade: module declarations and re-exports only; executable adapter modules remain covered. #6524
crates/ironclaw_llm/src/rig_adapter/tests/finish_reason_tests.rs Test-only module stored under src/ for private adapter access; cargo-llvm-cov omits test harness source from production LCOV while the exercised rig_adapter.rs production lines remain coverage-gated. #6284
crates/ironclaw_outbound/src/error.rs Declarative error vocabulary only; variants have no LLVM-instrumentable production statements. #6524
crates/ironclaw_outbound/src/lib.rs Declarative crate facade: module declarations and re-exports only; executable outbound modules remain covered. #6524
crates/ironclaw_product/src/lib.rs Declaration-only public facade with no executable Rust statements; rustc emits no LCOV source record. Executable product behavior remains covered in the owned implementation modules. #6524
crates/ironclaw_product/src/lib.rs Declarative crate facade: module declarations and re-exports only; executable product modules remain covered. #6524
crates/ironclaw_product/src/scoped_fs/mod.rs Declarative scoped-filesystem facade and documentation only; executable scoped filesystem modules remain covered. #6524
crates/ironclaw_reborn_composition/src/support/fs/mod.rs Declarative composition support facade: module declarations and re-exports only; executable filesystem adapters remain covered. #6524
crates/ironclaw_slack_extension/src/lib.rs Declarative Slack crate facade: module declarations and re-exports only; executable Slack modules remain covered. #6524
crates/ironclaw_telegram_extension/src/lib.rs Declarative Telegram crate facade: module declarations and re-exports only; executable Telegram modules remain covered. #6524
crates/ironclaw_threads/src/lib.rs Declaration-only public facade with no executable Rust statements; rustc emits no LCOV source record. Executable thread behavior remains covered in the owned implementation modules. #6524
crates/ironclaw_webui/src/webui_v2/mod.rs Declaration-only WebUI v2 facade with no executable Rust statements; rustc emits no LCOV source record. Executable route behavior remains covered in the owned implementation modules. #6524

BenKurrek and others added 2 commits July 31, 2026 10:35
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>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6946 July 31, 2026 14:39 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
scripts/ci/classify-test-scope.sh (1)

132-140: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The deleted-path fallback still strips one family level only.

Line 137 keeps NORMALIZED_PATH="crates/${tail#*/}". For a two-level layout such as crates/app/cli/ironclaw_cli/src/main.rs the result is crates/cli/ironclaw_cli/src/main.rs, no crate arm matches, and is_code_path buckets it legacy-only. That is the silent misbucketing this rewrite targets. Strip up to the last ironclaw_* 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 win

Base-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_tests false, 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 win

The parity test still pins a copy of the rule, not the classifier.

bash_inventory re-implements the find expression inline. It never calls discover_crate_dirs in scripts/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=20 in the classifier and MIN_CRATE_DIRECTORIES in scripts/ci/lib/crate_tree.py are never asserted equal, yet crate_tree.py claims the self-test keeps them from drifting. Source the classifier's function (or add a --print-crate-dirs flag) 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f96b78 and 62a37b5.

📒 Files selected for processing (7)
  • .github/workflows/reborn-e2e.yml
  • docs/reborn/target-architecture/CHECKLIST.md
  • scripts/ci/classify-test-scope.sh
  • scripts/ci/reborn-coverage-merge-lcov.sh
  • scripts/ci/test-classify-test-scope.sh
  • scripts/ci/test-reborn-coverage.sh
  • scripts/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.*

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment on lines +260 to +274
# 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6946 July 31, 2026 14:52 Destroyed
@BenKurrek
BenKurrek merged commit ce1490d into main Jul 31, 2026
18 of 20 checks passed
@BenKurrek
BenKurrek deleted the ws0/path-gate-rewrites branch July 31, 2026 14:54
@BenKurrek

Copy link
Copy Markdown
Collaborator Author

Note for anyone following the nine review threads above: they cite ab6a98551 as the fix commit. This PR was squash-merged, so that content landed on main as ce1490dd2 — the branch SHA will not resolve.

All ten review fixes are confirmed present on main, and the gates are green there after #6942's crate deletions took the inventory from 65 to 63 crate directories: test-classify-test-scope.sh exit 0 (including the new classifier-sourced inventory pin and the floor pin), test-reborn-coverage.sh 148/160 with all 28 M-section cases passing and only the 12 pre-existing reborn-coverage-comment.sh fake-gh failures, test_ws12_workflow_contracts.py 12 OK, check_no_panics.py --self-test 31 OK and --reborn-baseline clean at 1157 files / 51 invariants, test_dev_metrics.py 9/0.

Remaining path-gate inventory is tracked in #6963; the pointer to it is already in CHECKLIST row 17.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 62a37b5 and ab6a985.

📒 Files selected for processing (7)
  • docs/reborn/target-architecture/CHECKLIST.md
  • scripts/ci/classify-test-scope.sh
  • scripts/ci/lib/crate_tree.py
  • scripts/ci/test-classify-test-scope.sh
  • scripts/ci/test_ws12_workflow_contracts.py
  • scripts/ci/ws12_workflow_contracts.py
  • scripts/dev_metrics.py
💤 Files with no reviewable changes (1)
  • scripts/ci/lib/crate_tree.py

Comment thread scripts/dev_metrics.py
Comment on lines +258 to +269
@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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Suggested change
@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

BenKurrek added a commit that referenced this pull request Aug 1, 2026
…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
BenKurrek added a commit that referenced this pull request Aug 1, 2026
…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>
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…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>
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…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>

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6946 — ab6a9855 Deployed Jul 31, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: ci CI/CD workflows scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant