mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-03 08:06:01 +08:00
* 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>