Waves 0–4 batch 2: register to zero, adapter-registry move, ruled decisions (accumulating the fleet) - #7181
Merged
Conversation
…contract (WS3)
Deletes the two `-> ironclaw_extensions` layer-matrix exceptions
(`ironclaw_mcp`, `ironclaw_scripts`) by giving the runtimes-layer lanes a
contracts home for the descriptors they read, instead of the registry crate
they may not depend on. Exceptions 13 -> 11; baseline lowered in the same
change.
Moved to `ironclaw_extension_contracts`:
- `runtime::{ExtensionRuntime, ExtensionAssetPath, ExtensionAssetPathError}`
- `hosted_mcp::{HostedMcpDiscoveredTool, HostedMcpDiscoveredToolAnnotations}`
`ExtensionPackage`/`ExtensionManifest` deliberately stay in
`ironclaw_extensions`: they carry the whole parsed manifest tree and a
`PackageRootBinding` typed on `ironclaw_filesystem::VirtualPath`, which the
§11.2.3 contracts-purity allowlist (`{ironclaw_host_api}` only) forbids the
contracts crate from naming. Measured instead: both lanes read exactly three
things off the package — `id`, `capabilities`, `manifest.runtime` — so the
lane request structs now take those three and the caller (which owns the
package) projects them.
Also repointed `ResourceReceipt` to its real owner: `ironclaw_resources`
only re-exports `ironclaw_host_api::resource::ResourceReceipt`, so the lanes'
import was a §11.2.4 two-import-paths hop, not a dependency.
No `pub use` shims (§11.3): every consumer is repointed in this change, and
`resolve_under` becomes the free function `ironclaw_extensions::resolve_asset_under`
because the orphan rule forbids an inherent impl on the moved type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Creates `ironclaw_sandbox` (runtimes) from the three halves of "run an already-authorized command away from the host", and deletes the two crates PROPOSAL §6.6.4 marks for merge: - `ironclaw_process_sandbox` (plan contract) -> `src/plan.rs`, `src/validation.rs` - `ironclaw_host_runtime::sandbox_process` -> `src/sandbox_process/**` - `ironclaw_scripts` (script lane + Docker path) -> `src/script.rs` The kernel sheds the Docker/CA cone: `bollard`, `rcgen`, `x509-parser` and `time` are gone from `ironclaw_host_runtime`'s manifest, and `bollard`/`rcgen` are now declared by exactly one crate in the workspace. Two migration details PROPOSAL §6.6.4 and CHECKLIST WS10 call load-bearing: - `PROCESS_SANDBOX_CAPABILITY_ID` -> `ironclaw_host_api::capability`, so `ironclaw_loop_host` drops its lane dependency (production dep gone; a dev-dep remains for the tests that build plans). - `SandboxCommandTransport` -> `ironclaw_host_api::process`, with the shapes it names (`CommandExecutionRequest`/`Output`, `RuntimeProcessError`, `SavedCommandOutput`, `SavedCommandOutputSanitization`). Without this the runtimes-layer lane could not implement what the kernel consumes. Enumerating gates were repointed, never relaxed: the specificity carve-outs and the struct/test-support ratchet entries moved with their files (both baselines unchanged at 129 and their prior values), the panic-gate baseline row moved, `reborn-crate-test-buckets.sh` registers the new crate, and the three `reborn-e2e-rust.sh` script selectors follow the tests (plus `docker_security`, which had no selector before). One gate would have gone silently vacuous and was fixed rather than moved: the script-lane surface scan in `reborn_dependency_boundaries.rs` read a hardcoded `src/lib.rs`, which after the merge no longer holds the lane. It now scans the whole crate source tree with a fatal-read walk and a non-vacuity assertion. One deletion, recorded: `RebornScopedSandboxCommandTransport::into_process_port` returned a kernel type a runtimes crate may not name. It had zero callers workspace-wide; the kernel wraps the transport, which is the direction the port inversion requires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ence Three dated amendments, each quoting the text it replaces: 1. CHECKLIST WS3 sandbox row + PROPOSAL §6.6.4 — "all pieces currently unwired/test-only" is REFUTED. Three production paths cross the merged crate (spawn-path plan validation, the process_executor routing check, and the saved-command-output scope digest). The accurate claim is narrower: no production *execution backend*. Behavior preservation is therefore argued at the diff (11 of 26 moved files byte-identical, 9 more differing by one import line, +63/-36 overall), not inferred from deadness. 2. CHECKLIST WS3 mcp row + PROPOSAL §6.6.3 — the prior wave's "structurally blocked" finding is half right, and the wrong half is load-bearing: only `ExtensionPackage` is un-absorbable, and no lane ever needed it (both read `id`, `capabilities`, `manifest.runtime` and nothing else). The registry half of the flip is done; the `resources` half is refuted as phrased — the estimate/usage vocabulary the row asks about is already in `host_api::resource` and already imported from there, while the real blocker is the `ResourceGovernor` authority port and `ResourceError`'s denial cone. 3. Recorded as a structural finding, not a note: the sandbox row and the mcp row are ONE problem. `ironclaw_scripts` imports the identical DTO set, so the merge alone deletes zero exceptions and only the mcp carve-out lets either lane shed the registry edge. Also reconciled: PROPOSAL §6.1.2's as-built inventory gains the two modules WS3 landed (and states why `ExtensionPackage` stayed); §2's package count 66 -> 65; the §9 disposition rows for `ironclaw_scripts`/`ironclaw_process_sandbox`/ `ironclaw_mcp`; the §11.2.2 ratchet rows (13 -> 11); the WS3 verify row; the stale WS1.3 sentence asserting the blocker as settled fact; and `reborn_restructure_baselines.rs`'s doc table, which still read 15. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`process_port.rs` no longer names `MountView` or `thiserror::Error` (both went to `host_api::process` with the types that used them), and `sandbox_process.rs` no longer needs `sync::Arc` after `into_process_port` was deleted. Found by per-crate `clippy --all-targets --all-features -D warnings`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tions Three fail-closed gaps in `reborn_pr_test_plan.py`, all hit by this PR and all live on `main` today — any PR with the same change shape is unplannable. 1. `.claude/**` was unclassified, so the planner refused outright. It is agent guidance in exactly the sense `docs/**` is human guidance: no Rust test reads either as data (the only in-tree references are prose citations in test doc comments). Added to `IGNORED_PREFIXES`. Without this, "guidance travels with the change" — the restructure's own discipline — cannot be satisfied in a single PR. 2. `crates/AGENTS.md`, `crates/README.md`, `crates/Architecture.md` raised "unmapped crate path": they sit under `crates/` but belong to no package. Now classified as crate-tree prose, matched by "Markdown no package directory owns" so a genuinely unmapped crate path is unaffected. 3. An unmapped crate path used to raise. `git diff` reports a deleted crate's old paths and CI feeds the planner that diff, so **every crate deletion or rename was unplannable** — including the six deletions PROPOSAL §2 plans. It now widens to the exhaustive plan. This is a semantic change and it is the safe direction: the full plan is a superset of any narrowing, so an unattributable path can never cause under-selection, whereas refusing to plan blocks the PR instead of protecting it. Malformed input is still rejected by the unclassified-path branch. Each lands with fixtures per WS10's rule, positive and negative: guidance paths select nothing while non-guidance paths still fail closed; crate-tree prose selects nothing while crate *code* under the same unmapped directory widens to `full` (so the Markdown carve-out cannot swallow code). The pre-existing `test_unmapped_crate_path_fails_fast` is renamed and rewritten to pin the new contract rather than deleted. Verified against this PR's real 130-path diff: the planner returns `mode: full`, and the workflow's own exhaustiveness guard passes on that output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… a wave Review (#7065) caught that both surviving `-> ironclaw_resources` exceptions declared `removes_in = "WS3"` — the wave this PR *is*, which does not remove them. That is precisely the defect §11.2.2 already records against `conversations -> turns` ("`removes_in = "WS5"` and WS5 has partly shipped without it falling"), and it would have been repeated here. Both now point at issue #7067, which owns the design work that actually clears them: replacing the `ResourceGovernor` dependency with a narrow reserve/reconcile/release port. The issue carries the measurements — 3 of 10 methods used, zero implementors, and the `ResourceError` denial cone — plus the two open questions (error shape, port home) that make it a design slice rather than a move. An owning issue is also what §11.2.2 asks for and what the ratchet still cannot enforce (there is no `owning_issue` field yet), so this is the strongest form currently expressible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on_contracts `validate_asset_path` moved here with `ExtensionAssetPath`, the type it constructs. In `ironclaw_extensions` it was only ever reached indirectly through manifest parsing, so its six rejection branches had no direct test — and a contracts crate that carries validation owes that validation one. Two tests: every reject branch with its exact reason and `Display` output (empty, NUL/control, URL, absolute, Windows drive and backslash, and the empty/`.`/`..` segment cases) plus the manifest-relative shapes that must keep being accepted; and `ExtensionRuntime::kind()` over all five variants, since that projection is what every lane uses to reject a runtime it does not serve. Also removes a changed-line coverage risk this PR would otherwise carry into the merge queue: the gate does not run on ordinary PRs (#7036), so ~100 newly-added lines of validator would first be measured where a failure is expensive to diagnose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…andbox lane
`RATCHET FAIL: ironclaw_host_runtime` — observed 18854 covered vs a
`floor_covered_lines` of 20538. This is the shrinkage case the ratchet's own
"To fix" text describes, not a coverage regression: `sandbox_process/**` moved
to `ironclaw_sandbox`, so the crate's denominator fell 23277 -> 21267 (-2010
instrumented lines) and its covered lines fell with it.
The percentage floor is **raised, not lowered**: observed 88.65% against an old
floor of 88.23%, so the entry now reads 88.65. Only the absolute line count
moves down, and it must — those lines are no longer in this crate.
To keep that from being a net loss of protection, `ironclaw_sandbox` is floored
on arrival at its observed 87.09% (3185 / 3657). This is a net *increase* in
ratchet coverage: neither `ironclaw_scripts` nor `ironclaw_process_sandbox` was
ever floored, and the `sandbox_process` half was protected only as part of
host_runtime's line count, which this PR necessarily reduces. Floored crates
16 -> 17.
Verified by replaying the ratchet arithmetic against CI's observed numbers:
both crates pass on percentage and on covered lines. Numbers taken from the
failing run's own report (job 91740733521), which is the authority for this
gate.
The `Tests (Reborn)` roll-up failed solely on this sub-job
("coverage-report result 'failure' did not match planned=true"); no other lane
failed — 50 pass, 2 fail, both this root cause and its roll-up.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…itive gate WS3 hit a gate no move row had named. `tests/integration/coverage-floor.toml` is keyed on crate identity plus absolute covered-line counts, so it is invisible to WS10's path-keyed gate audit and yet it fails on every crate move, merge, rename, or family `git mv` that shifts instrumented lines between crates — as it did here, while the percentage floor was *improving*. Recorded on WS10 with the three rules WS7 will need: re-capture in the same PR, raise the percentage floor rather than leaving it, and floor the destination crate or the move silently drops that code out of the ratchet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in #6780 (ironhub deep-link register/install + the REBORN_COV_COLLECT hermetic-env allowlist fix), #7050, and #7033. Two conflicts, both resolved as a union with each side's contribution verified present afterwards: - `crates/ironclaw_extension_host/src/available_extension_import.rs` — main added `use ironclaw_extension_contracts::recipe::VendorAuthRecipe;` at the same import position this branch added `use ironclaw_extension_contracts::runtime::ExtensionAssetPath;`. Both kept. - `scripts/ci/test_reborn_pr_test_plan.py` — main added `test_selected_integration_lane_keeps_msrv_override` and kept `test_unmapped_crate_path_fails_fast`; this branch had replaced the latter with `test_unmapped_crate_path_widens_instead_of_refusing`. Resolution keeps main's new test verbatim and this branch's rewrite, and drops the superseded original — it asserts the exact behavior this branch deliberately changed (an unmapped crate path now widens instead of refusing, so crate deletions are plannable). Keeping both would have been contradictory, not a union. 37 tests pass (36 here + main's 1). Post-merge verification of both sides: exceptions 11 with baseline 11 and `ironclaw_scripts` absent from the matrix (this branch); 9 #7033 decision markers and the `REBORN_COV_COLLECT` allowlist entry present (main). Note on the coverage floors this branch re-captured: #6780's fix stops the hermetic env filter stripping `REBORN_COV_COLLECT`, which gates *whether* a lane collects coverage. This branch's numbers were captured on a `full` plan, where every lane collects either way, so they are expected to hold — CI re-measures and will say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Path A semantic conflict the merge could not see: #6780 landed `ironhub/{package,catalog}.rs` importing `ExtensionAssetPath` from `ironclaw_extensions`, while this branch moved that type to `ironclaw_extension_contracts::runtime`. Different files, so git auto-merged cleanly and the breakage surfaced only at `cargo check`. Repointed both sites to the contracts crate (no shim, per §11.3). The manifest already named `ironclaw_extension_contracts`, so this is imports only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gate The changed-lines coverage gate went red on four files while changed-line coverage was 95.35% against a 90% floor: the failure was its two fail-closed STRUCTURAL assertions, not any percentage. Every line below was derived by replaying scripts/ci/reborn_changed_coverage.py against this PR's own merged lcov (run 30831658659) with the base lcov the gate itself resolved (run 30828540055 @ b89fcd3), until the replay reproduced the CI verdict byte-identically. Line numbers come from the gate's own `candidate_lines - mechanically_uninstrumentable_lines()`, not from the log. - host_api/src/process.rs (31 lines): new placement-neutral process vocabulary with no function body anywhere in the file; rustc emits no LCOV record for it at all. Same shape already exempted for product_contracts/loop_contracts. - extension_contracts/src/hosted_mcp.rs (12): field declarations of the two new tools/list descriptor structs. The file is plainly instrumented (191 DA, 164 hit), so this is a no-region artifact, not an instrumentation gap. - host_runtime/src/services/runtime_adapters.rs (13): continuation lines of three rewritten calls, all PROVEN EXECUTING by their region-start heads (lines 380/434/977 score 24/16/63 hits). The four genuinely-uncovered lines in the same rewrite are deliberately NOT exempted -- the gate already subtracts them as pre-existing debt inherited from base. - composition capability_host_tests/approval_gates.rs (6): type positions in a test double whose body region scores 1 hit. The last one is a finding, not just a waiver: that file is 100% test code behind `#[cfg(test)] mod capability_host_tests;`, but the gate's test_only_path() recognises /tests/, /test_support/, */tests.rs and *_tests.rs and NOT a cfg(test) module DIRECTORY, so it measures it as production. It is the only such directory in crates/ today. Docs (target-architecture, same PR per the docs-truth rule): - CHECKLIST WS10 gains the changed-lines gate beside the ratchet row, cross- referencing the WS2.1 note rather than restating it: percentages are not what fail a move; derive lines by byte-identical replay (--fetch-base-coverage silently degrades without --github-repo); and a stranded exemption path is an ABORT with no verdict, not a loud failure. - CHECKLIST WS10 exception-ratchet row: the constant was cited at :4063 and sits at :4164 -- corrected by removing the line pin, since the file is edited every wave. Records that the baseline is a UNION across parallel WS3 lanes. - families/contracts.md: records extension_contracts' new ownership of the runtime descriptor vocabulary -- the carve-out that let BOTH lanes drop the registry edge -- and the orphan-rule seam that keeps resolve_asset_under in the registry crate. - families/lanes.md: two "Never" claims were reading as satisfied when they are not. ironclaw_mcp's "never depends on the resource-governor crate directly" is refuted (the compiled edge survives; #7067 tracks the narrow port), and ironclaw_sandbox's "no direct process spawning outside the transport seam" is aspirational -- script.rs:454 still builds Command::new("docker"). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tion cost Two review findings verified against the tree; three refuted with evidence in the PR threads. Valid — the sandbox wiring inventory was self-contradictory. `CLAUDE.md` said "Two production call paths ... and both are plan validation" directly above a list of THREE bullets, and `lib.rs` omitted the third entirely. The third is real and is not validation: `host_runtime/src/process_output.rs:482` derives the scoped saved-output directory through `RebornSandboxScopeKey::from_scope`. That inventory is what tells a future agent which paths are live, so an undercount invites deleting a production path as dead code. Both surfaces now say three and no longer claim they are all plan validation (the `loop_host` capability-id comparison never was either). Valid, and recorded rather than redesigned — the registry carve-out cost a type-level invariant. Replacing `package: &ExtensionPackage` with independent `extension` / `capabilities` / `runtime` borrows is what deleted the `mcp -> extensions` and `scripts -> extensions` exceptions, but it also means the type no longer guarantees the three came from one package. `execute_extension_json` re-checks the descriptor half (`descriptor.provider == extension`); the runtime half cannot be re-derived, because nothing in an `&ExtensionRuntime` names its owning extension. No caller can trip it today -- there is exactly one production caller (`runtime_adapters`) and it projects all three from one package in one expression -- so this is a latent structural weakening, not a live defect. Restoring the compile-time binding needs a sealed projection minted by the package owner; a check inside the lane cannot express it, and re-taking the registry edge would undo the carve-out. Both request types now carry the caller obligation in their field docs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…pport (WS3)
WS3's first-party-tools row, family 1 of 6: skill management / URL install.
`skill_url_install.rs` and its `bundle`/`github`/`zip_bundle` submodules,
plus the install-input normalizer, move out of
`ironclaw_host_runtime::first_party_tools` into
`ironclaw_extension_support::skills::{url_install, resolve_install_input}`,
where the skill executor half already lived. Move-only: no behavior change,
no test edited for content.
`ironclaw_host_runtime -> ironclaw_skills` is deleted from
LAYER_MATRIX_EXCEPTIONS — the edge is gone, not waived (exceptions 13 -> 12,
WS0_LAYER_MATRIX_EXCEPTION_BASELINE drops with it). `ironclaw_skills` and
`zip` survive as dev-dependencies for host_runtime's own tests; dev edges are
outside the matrix by construction.
Two doc ambiguities are resolved in the same diff, as dated PROPOSAL
amendments quoting the text they replace:
- §6.8.4's "the builtin first-party tool handlers absorbed from
host_runtime/first_party_tools" contradicted §8.2's "kernel: ✗ (ports only)"
row and the enforced BoundaryRule. Resolution: the seam splits executor from
adapter — the executor moves behind a neutral request/error pair, the
FirstPartyCapabilityHandler / CapabilityManifest / registry wiring stay
host-side. Same shape the groupware and web-access tools already ship.
- §8.2's "ports only" cell now says what it means: contracts-layer ports the
kernel also consumes, not permission to name a kernel trait.
Two cost corrections recorded for the remaining families:
`host_runtime -> extension_support` is not divisible family-by-family (mod.rs
holds it via `extension_support::coding`), and
`host_runtime -> ironclaw_extensions` is not reachable by this row at all.
PATH_TERM_COLLISIONS shrinks by two: the installer's github carve-outs now sit
inside a scan-exempt crate.
Test accounting (un-masking discipline), unfiltered `--list` over both crates:
1398 -> 1398, with exactly two tests renamed by module path and none lost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nothing Review asked why the migrated docker_security test can pass with no daemon. The skip is pre-existing (the file differs from its pre-merge original by one import line); WS3 only enrolled it in the required Rust e2e lane, where it was not run at all before. The real defect the question surfaced is worse and also pre-existing: this crate's tests/support/docker_gate.rs states that IRONCLAW_REQUIRE_DOCKER_TESTS=1 makes a missing daemon a hard failure and that "CI sets this" -- and nothing sets it. Repo-wide the name occurs only in docker_gate.rs and attribution_tests.rs, here and on main. So every real-Docker test in the crate skips-and-passes everywhere, which is exactly the gap the gate's own comment says let sandbox security bugs ship unnoticed. docker_security.rs additionally open-codes its own check rather than using the gate, so it would stay fail-open even once something did set the variable. Recorded rather than fixed: setting the variable is a CI-behavior change that would hard-fail any lane without a daemon or the ironclaw-worker image, which is not verifiable from inside a move PR whose evidence claim is behavior preservation. Filed as the #6945 guardrail-claim-vs-reality class with the two-part fix stated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The crate's CLAUDE.md said "first-party runtime tools belong under `first_party_tools/`" without saying that only the host half does. WS3 moves each tool's executor into `ironclaw_extension_support`, which may not name this crate, so the rule now names both halves and points at the skill-install family as the worked example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The moved executor returns `SkillManagementCapabilityError`, and routing it through `skill_management_error` would have added a `debug!` line to a path that had none before the move. A move-only change must not add one, so the install-input arm maps the kind directly and the `dispatch` arm keeps the record it already had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…move The ratchet does not run on `pull_request` (`reborn_pr_test_plan.py:21`; issue #7036), so this PR's green checks were not evidence on this axis. A full-plan `workflow_dispatch` run on this exact head reported: RATCHET FAIL: ironclaw_host_runtime observed: 88.59% (20485 / 23124 lines) floor: 88.23% ... floor_covered_lines: 20538 (effective floor 20518) The percentage went UP while `floor_covered_lines` went DOWN — shedding well-covered code lowers the absolute numerator, which is a separate assertion from the percentage one. Re-captured to the observed numbers (floor raised 88.23 -> 88.59, not merely held). Verified locally against that run's own merged lcov artifact: ENFORCING mode, 17 PASS / 0 FAIL, exit 0. run: https://github.com/nearai/ironclaw/actions/runs/30858257594 head: e07b3b0 The destination crate is deliberately not floored, because it cannot be: every crate under `crates/extensions/` is invisible to the coverage tooling — `reborn_coverage_lcov.py:19`'s CRATE_RE still requires a crate directory directly under `crates/`, which #7037's colocation broke. Filed as #7083 with the measurement; the global floor is left alone rather than re-captured onto that hole. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CHECKLIST WS4 + WS10 `wit/` rows. `wit/{tool,channel}.wit` moves from the
repo root to `crates/ironclaw_wasm/wit/` — the crate that owns the ABI —
per PROPOSAL §6.6.1. Behavior-free: same bytes, same generated bindings.
Wave-3 coordinates: the docs write the destination as
`crates/lanes/ironclaw_wasm/wit/`, but `crates/lanes/` does not exist until
WS7. Because the files now sit *inside* the crate, the WS7 family move
carries them with no further path edit anywhere — which is the whole point
of putting them there.
Ten wit-bindgen `path:` args repointed (the host plus nine guests: six under
`crates/extensions/packages/*/wasm-src/`, three under `test-tools/*/wasm-src/`
— the CHECKLIST row said six). All nine guests verified building against the
moved WIT on wasm32-wasip2.
The four `include_str!` readers of the ABI text do NOT get repointed
literals. Doing that would turn the two `ironclaw_host_runtime` sites from
repo-root reach-ins into *cross-crate* ones — §11.2.7's strict class, the
one WS2 turns into hard failures — taking the scan from 19 to 21 while
ticking a box that says "§11.2.7 scan passes". Instead the ABI text gets one
owner, `ironclaw_wasm::TOOL_WIT` (`src/config.rs`, beside `WIT_TOOL_VERSION`),
and all four sites read the const over cargo edges that already exist.
Measured with the scan: 133 -> 129 escaping sites, cross-crate 19 -> 19,
zero `wit/` entries remaining.
Path-keyed gates repointed: `scripts/check-version-bumps.sh` (both ABI
paths), `.githooks/pre-commit`, and `platform-and-compat.yml`'s
`has_direct_wasm_abi_risk` filter — where the bare `wit/` alternative is
*deleted* rather than rewritten, because the filter's existing
`crates/([^/]+/)*ironclaw_wasm/` alternative already matches both the
Wave-3 and the WS7 location. `scripts/ci/ws12_workflow_contracts.py`
anchored on that deleted string, so its anchor moves to
`build-wasm-extensions` and its in-scope probe now pins both locations.
`Dockerfile` loses two `COPY wit/ wit/` lines in the planner and builder
stages: both already run `COPY crates/ crates/`, so the files arrive with
the crate and the old line would COPY a path that no longer exists.
Docs: the WS4 row's `crates/lanes/wit/` destination was the only doc site
placing the directory beside the crate rather than inside it; corrected
there and in README's tree, with dated amendments in CHECKLIST, PROPOSAL
§6.6.1 and PLAN Wave 3 recording what the move found.
Test accounting (unfiltered `--list`, name-by-name, quiescent tree):
ironclaw_wasm 51 -> 51, ironclaw_host_runtime 1246 -> 1246,
ironclaw_architecture 198 -> 198. Zero diff, no test edited for content.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Forced by the previous commit, not incidental to it. `scripts/ci/check-wasm-artifact-freshness.py` keys each package's committed `wasm/<name>.wasm` to a digest of the `wasm-src/` tree that produced it, so editing a guest's `wit_bindgen::generate!` `path:` — which the `wit/` move requires in all six shipped guests — invalidates the recorded digest and fails the gate. The gate's own contract forbids the shortcut: "Re-record only after `./scripts/build-wasm-extensions.sh --first-party` and committing the rebuilt artifact — the digest asserts a claim about the artifact, and updating it without rebuilding launders a stale one." So the artifacts are genuinely rebuilt (`--first-party`, exit 0, 6 OK / 2 host-native SKIP), not re-recorded in place. Byte sizes move by more than the source change accounts for because these builds are not reproducible by design — the guests pin no toolchain and resolve their own `Cargo.lock` at build time, which is the documented reason the gate hashes sources rather than artifact bytes. Verified: `check-wasm-artifact-freshness.py` OK (6 packages), and `cargo test -p ironclaw_extension_support` green (102/46/4) — that crate `include_bytes!`s these artifacts, so it exercises the rebuilt components. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… edits The `wit/` move had to rebuild six shipped WASM binaries because `check-wasm-artifact-freshness.py` digests each guest's whole `wasm-src/` tree. WS7 hits the same wall from the other direction: the six package guests reach the ABI across two trees, so moving either `ironclaw_wasm` or `extensions/packages` rewrites all six `path:` literals and forces the same rebuild. Recorded on CHECKLIST WS10's `wit/` row (point 6), on the loud-path-pattern row that owns the WS7 repoint (also corrected six -> nine guests there), and on PLAN's Wave 5 block with the cheap mitigation: move the two crates in one PR and pay it once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # docs/reborn/target-architecture/CHECKLIST.md
Reconciles this lane with #7064, which landed the parallel WS3/WS4 runner sheds and edited the same coordination files. Five conflicts, all in shared coordination surfaces — no code move in this PR was altered (all 117 PR-only files are byte-identical to the pre-merge tip; the 5 apparent diffs are deletions absent on both sides). - `reborn_dependency_boundaries.rs`: the array auto-merged to the union of both sides' removals; only `WS0_LAYER_MATRIX_EXCEPTION_BASELINE` conflicted. Recomputed as `len()` of the merged list — 13 base, minus #7064's three (`hooks -> wasm_limiter`, `runner -> agent_loop`, `runner -> loop_host`) and this PR's three (`mcp -> extensions`, `scripts -> extensions`, `scripts -> resources`), plus this PR's justified `ironclaw_sandbox -> ironclaw_resources` = **8**. Counted by parsing only the entries between the const and its closing `];`, so the struct definition and the four test fixtures are excluded. - `loop_host/Cargo.toml`: both sides added a `[dev-dependencies]` line; kept both (`http` from main, `ironclaw_sandbox` from this PR). - `CHECKLIST.md`: kept both dated amendments in date order — #7064's `13 -> 10` and this PR's, with its count corrected from the authored-in-isolation `11` to the merged `8` exactly as the §11.2.2 row instructs. Also kept this PR's two new coverage-gate rows beside main's amended loud-inventory row. - `reborn_pr_test_plan.py`: both sides made the same `.claude/` fix; took main's landed wording (`startswith` makes tuple order irrelevant). - `test_reborn_pr_test_plan.py`: union of both sides' new tests, no name collisions — 46 tests pass. Also repointed the one PR-authored `changed-coverage-exemptions.toml` entry the merge shifted: `hosted_mcp.rs` moved +1 because main added a doc-comment line, so its line-keyed exemption now resolves to byte-identical source lines. The other stale entries in that manifest are inherited and already stale on main; left untouched. Verified: architecture suite 206 passed / 0 failed, `cargo check --all-targets` clean, `cargo fmt` a no-op, zero conflict markers, and both sides' `coverage-floor.toml` recaptures intact (runner 82.53 from #7064; host_runtime 88.65 and the new ironclaw_sandbox 87.09 from this PR). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reconciles this lane with #7064, which landed the parallel WS3/WS4 runner sheds and edited the same coordination files. One conflict: `WS0_LAYER_MATRIX_EXCEPTION_BASELINE` in `reborn_dependency_boundaries.rs`. The array itself auto-merged to the union of both sides' removals — #7064's three (`hooks -> wasm_limiter`, `runner -> agent_loop`, `runner -> loop_host`) and this PR's one (`host_runtime -> skills`). Recomputed the constant as `len()` of the merged list: main is at 10, so this slice takes it to **9**. Counted by parsing only the entries between the const and its closing `];`, which excludes the struct definition and the four test fixtures. This slice was authored off 13 and computed `13 -> 12` in isolation; the ratchet narrative and the two doc rows that quoted that figure (PLAN's "First-party tools" bullet and CHECKLIST's W7-progress row) now read `10 -> 9` and record why, per the union rule on the CHECKLIST §11.2.2 row. The edge deleted is unchanged; only the total moved. Everything else auto-merged and was verified rather than assumed: both sides' `coverage-floor.toml` recaptures are intact (runner 82.53 and the new `ironclaw_loop_host` 90.89 from #7064; `host_runtime` 88.59 from this PR), and both sides' dated amendments survive in CHECKLIST, PROPOSAL and PLAN. All 14 PR-only files are byte-identical to the pre-merge tip, so no code move was altered. Verified: architecture suite 206 passed / 0 failed, `cargo check --all-targets` clean, `cargo fmt` a no-op, zero conflict markers, and the changed-coverage manifest validates with no exemption stranded or shifted by this merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Detect Reborn test scope` exits 1 on any pull request whose diff holds a path `reborn_pr_test_plan.py` has no rule for, which made this PR unmergeable: it must edit `Dockerfile` (the moved directory's `COPY wit/ wit/` no longer resolves) and `scripts/check-version-bumps.sh` (the ABI gate would otherwise grep dead paths and silently stop enforcing). 18 of its 46 paths were unclassified. Same class as the `.claude/` gap #7064 fixed, and classified the same way — one rule per class, recorded beside the constant: * `Dockerfile` / `.dockerignore` — `platform-and-compat.yml` keys `has_docker_risk` off exactly this pair and owns the image build. * `.githooks/**` — Code Style triggers on the tree and lints its contents (`test-ci-comm-locale-pin.sh`); no Reborn lane runs a hook. * `scripts/{build-wasm-extensions,check-version-bumps}.sh` — `platform-and-compat.yml`'s `has_direct_wasm_abi_risk` classifier both scopes and runs them. * markdown owned by no crate (`crates/AGENTS.md`, `test-tools/README.md`) — prose, like `docs/` and `.claude/`. A crate-resident doc still selects its own crate's lane. The first-party extension package assets are deliberately NOT ignored. `crates/extensions/packages/*/wasm/*.wasm` is a shipped artifact that `ironclaw_extension_support` embeds with `include_bytes!`, and `test-tools/*/manifest.toml` is `include_str!`d by `ironclaw_extension_host`. Calling either prose would convert today's loud failure into a silent under-schedule of a change to production output — the WS10 failure mode. `EMBEDDED_ASSET_OWNERS` routes each tree to the crate that compiles it instead, so this PR now additionally schedules `ironclaw_extension_{support,host,manager}`: the crates that consume the six rebuilt WASM artifacts. Also fixes #7085 in a file this PR already touches. The WIT version extractors used the GNU-only BRE `\+`, so on BSD sed (macOS) they matched nothing, and because the `WIT_TOOL_VERSION` cross-check is guarded on a non-empty version the hook printed "All version checks passed" having compared nothing. `[[:space:]][[:space:]]*` is identical under GNU sed, so the enforced Linux CI lane is unchanged; verified on BSD sed that both `wit/tool.wit` (0.3.0) and `wit/channel.wit` (0.3.1) now extract. Regression tests: every classified class gets a case in `test_reborn_pr_test_plan.py`, including the paired assertion that the embedded assets *select a lane* rather than merely being accepted (the inverse of the `.claude/` prose test), and a staleness pin that fails if an asset tree or its owning crate moves. All ten new cases fail against the planner on `main`. `test_unclassified_build_input_fails_fast` moves off `Dockerfile` onto a still-undecided input so the fail-closed arm stays exercised. Refs #7087, #7085 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ners (WS3)
`crates/ironclaw_host_runtime/src/obligations.rs` was 3,122 lines fusing the
three owners PROPOSAL §6.5.9 charters separately, held apart only by an
`// arch-exempt: large_file` waiver. It is now one module per owner:
- `obligations::handler` — which obligations apply and what each does
before/after dispatch, plus the audit/redaction/ceiling/mount validation.
- `obligations::staged_handoffs` — material staged for a later consumer:
the runtime-secret and network-policy stores and the credential-account
resolver port.
- `obligations::process_store` — post-start handoff discard and reservation
reconciliation.
- `obligations::mod` — only `BuiltinObligationServices`, the assembly seam,
and deliberately the one place naming all three at once.
Every module is under the 1,500-line gate, so the waiver is deleted rather
than carried forward: re-fusing the owners now trips `pre-commit-safety.sh`.
`mod obligations;` stays private and the crate's `pub use obligations::{…}`
names are unchanged, so no consumer outside the crate sees this.
Behavior-free. Cross-owner access is `pub(super)` (three methods), not
`pub(crate)`. The split revealed one narrowing in the other direction:
`secret_present` was `pub(crate)` with no caller outside its own file and is
now private.
Also from the same CHECKLIST row, the bounded half of "shrink
`services/builder.rs` toward composition-facing factories": three builder
methods whose only callers are inside the crate's `src` narrow to
`pub(crate)`. The rest of that clause is measured and deferred in the
CHECKLIST amendment — 17 methods need a `test-support` cargo feature, three
are callerless and belong to WS8, and the remaining 33 are a redesign of the
fluent surface rather than a shrink of it. `+production_wiring` is refuted
there: it is readiness diagnostics, not assembly.
Two loud path-keyed gates fired and were repointed, not relaxed:
`reborn_host_runtime_services_do_not_expose_lower_substrate_handles` now
scans the whole `obligations/` directory and asserts it read ≥ 4 files
(`collect_runtime_rs` returns a count; both its callers now assert non-zero),
and `reborn_struct_test_support_ratchet`'s frozen per-file count moves to
`staged_handoffs.rs` with its count unchanged at 1.
Test accounting (un-masking discipline): `cargo test -p ironclaw_host_runtime
--all-targets -- --list` is 1,246 before and 1,246 after, name-by-name
identical — zero added, removed or renamed. `LAYER_MATRIX_EXCEPTIONS` is 10
before and after; an intra-crate split cannot move the register.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t_contracts port (WS3) `ironclaw_operator` is a products-tier crate and held `ironclaw_secrets`, the substrate that owns CAS one-shot leases, AAD/crypto and the OS keychain master key. PROPOSAL §8.2's product row says the products tier loses that edge, and §12.1b requires the port replacement to land before the edge is removed. Both happen here, in that order. - Port: `ironclaw_product_contracts::operator_secrets::OperatorSecretValueStore`. - Implementor: `ironclaw_reborn_composition::RuntimeOperatorSecretValueStore`, the same placement as `OperatorStatusService` — assembly is the only layer that may name both a products-tier port and a substrate. Registered in `INVERTED_PORTS` beside it. - `ironclaw_secrets` is gone from the operator manifest under every dependency kind, and `"ironclaw_secrets"` is now in the crate's `boundary_rules()` forbidden list. That gate's comment previously said the entry was deliberately absent because "the row owns it"; the row now owns it. The port is deliberately narrower than the substrate, so this is a tightening rather than a relocation: it takes no `ResourceScope` (the implementor fixes the operator scope, where the caller used to pass one), exposes no lease/consume protocol, and carries only a `&'static str` classification instead of the substrate's error `Display` — asserted, including that the backend message and the handle name are both absent from what crosses. Two tests travelled with the behavior rather than being pointed at a fake: `read_is_repeatable_across_reloads` (repeatability is a property of the lease protocol) and the #4673 production-store reproduction (its value is wiring the store exactly as production does, which now means the real store *behind the adapter*). Two `FaultInjecting`-over-real-store fixtures became per-operation port fakes, with the substrate error mapping re-pinned at the adapter; a third assertion got stronger — batched-vs-N+1 stored-key lookup is now observed at the port rather than by counting filesystem ops. Test accounting: operator 154 -> 153, product_contracts 142 -> 143, composition 937 -> 942 with zero removed; name-by-name diffs on a quiescent tree. Two findings the row could not have anticipated, both recorded in the CHECKLIST amendment: - The `webui` half of the row was already closed and was never a production edge. `ironclaw_secrets` has been a dev-dependency of `ironclaw_webui` since the commit that added it (#6619), both src mentions are `#[cfg(test)]`, and webui's boundary rule already forbade it. - `ironclaw_extension_manager` (layer `products`) still holds a normal `ironclaw_secrets` edge in `admin_configuration.rs`. §8.2 covers it; the row does not, because the crate landed with WS2.4 after the row was written, and the substrate sits in the service's type parameters so it is not a like-for-like swap. Filed as #7095. `LAYER_MATRIX_EXCEPTIONS` is 10 before and after: `products -> substrates` is matrix-legal, so this edge was always an §8.2 rule and never a layer exception. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review asked why the required Rust e2e lane can report `docker_security` as passing with no daemon. Half of that is #7081 (nothing sets IRONCLAW_REQUIRE_DOCKER_TESTS=1, so the switch is inert) and is not fixable from here -- arming it hard-fails any lane lacking a daemon or the worker image, which needs a runner guaranteed to have both. The other half is fixable here and is fixed: docker_security.rs open-coded its own `docker version` / `image inspect` checks with three bare `return`s, so it sat entirely outside docker_gate and would have stayed fail-open even once something did set the variable. It now takes both preconditions from docker_gate::{docker_available, docker_image_available} and skips with the visible `SKIP:` line that gate's module doc requires. Measured, same machine, image absent: before, IRONCLAW_REQUIRE_DOCKER_TESTS=1 -> "skipping ..." / 1 passed after, IRONCLAW_REQUIRE_DOCKER_TESTS=1 -> panic at docker_gate.rs:74 / FAILED after, variable unset -> "SKIP: ..." / 1 passed The third line is the no-op proof: the variable is set nowhere in this tree or on main, so no lane's behavior changes today. The daemon-down path already reached the image check and skipped there, so the outcome is identical; only the branch it takes differs. Two stale comments in docker_gate.rs corrected with it (they claimed docker_security used its own gate, and that docker_image_available had no consumer), and the crate's Known debt entry now splits the done half from the #7081 half instead of describing both as open. cargo test -p ironclaw_sandbox: 193 passed, 0 failed cargo clippy -p ironclaw_sandbox --tests --all-features -- -D warnings: exit 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two review findings, both correct, both artifacts of this PR's own renames.
1. engine-v2-to-reborn-parity.md note 4 read "a native script/software
execution lane (`ironclaw_sandbox`, `RuntimeKind::Script`) sandboxed via
`ironclaw_sandbox`" -- self-referential after the merge collapsed
ironclaw_scripts and ironclaw_process_sandbox into one crate, and it
contradicts note 5 four paragraphs down ("no production execution backend
is wired for it"). Re-stated as the typed runtime contract it is, citing
the measurement: `with_script_runtime` has zero production callers
(`rg` finds only the builder itself, docs, and 30 test call sites).
2. CHECKLIST WS10 ratchet note 2 said "raise the percentage floor ...; only
the line count should fall". That generalises WS3's sandbox merge, where
observed coverage happened to rise. It is wrong as guidance for WS7, and
the counterexample is in this same file: the 2026-08-03 entry from #7064
records ironclaw_runner falling 85.55% -> 82.53% because the shed removed
the crate's better-covered half, holding the floor, and RATCHET FAILing in
the merge queue. Note 2 now says re-capture from the merged artifact, and
lower only with that entry's move-not-regression counterfactual (add the
moved files back, confirm the union clears the old floor, plus a zero-tests-
lost name set-diff).
cargo test -p ironclaw_architecture: 32 targets, 206 passed, 0 failed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three review findings on the `wit/` move, each verified before it was acted on.
1. `ws12_workflow_contracts.py` probed `crates/ironclaw_wasm/wit/host.wit` and
its nested twin. No `host.wit` exists in this repository — `git ls-files
'*.wit'` returns only `tool.wit` and `channel.wit` — so both probes sat
under the `crates/([^/]+/)*ironclaw_wasm/` alternative and re-asserted the
crate-name term while saying nothing about the canonical ABI contracts. In
a validator whose stated design is "probe derived from reality rather than
from a guessed layout", a fabricated filename is a defect on its own terms.
Replaced with a `crate_globs` entry, `("ironclaw_wasm", "wit/*.wit")`, which
discovers the contracts on disk, requires each in scope, and synthesises the
nested WS7 form — so a third contract, or the directory leaving the crate,
fails the pin instead of passing on a stale name. Verified non-vacuous:
narrowing the workflow alternative to `.../ironclaw_wasm/src/` now reports
`tool.wit`, `channel.wit` and the nested probe as out of scope.
2. The embedded-asset routing test substituted `alpha`/`beta` owners so it
could reuse the synthetic workspace. That exercised the real prefix strings
through the real routing, but left the prefix->owner *pairing* — the table's
entire semantic content — asserted nowhere: swapping
`ironclaw_extension_support` and `ironclaw_extension_host` passed. Fixed in
two halves. The routing test now drives the real `EMBEDDED_ASSET_OWNERS`
against a workspace carrying the real owners' names and real manifest paths
(the synthetic one could not: `build_plan` rejects a changed package outside
the canonical set), asserting the real owner is selected. And the not-stale
test now derives the same pairing from the tree instead of restating the
constant: it resolves every literal `include_str!`/`include_bytes!` in every
workspace crate through `crate_tree`, keeps the targets no crate owns — the
ones that actually reach the table — and asserts that every crate compiling
one of them is the routed owner or a dependent of it.
That surfaced a property worth pinning: `crates/extensions/packages/` is
embedded by four crates, not one. `ironclaw_extension_host`,
`ironclaw_extension_manager` and `ironclaw_reborn_composition` reach into it
alongside `ironclaw_extension_support`, and routing to the support crate
covers them only because each depends on it. If that edge goes, a shipped
artifact change stops scheduling a crate that embeds it — the silent
under-schedule the table exists to prevent.
Regression coverage verified red by sabotage, all three wrong tables:
owners swapped (7 failures), `packages/` -> `ironclaw_llm` ("embeds nothing
from it"), and the hardest case, `packages/` -> `ironclaw_reborn_composition`
— a real embedder that the other embedders do not depend on
("...does not depend on..., so routing there never schedules it").
3. CHECKLIST WS10 claimed each of the nine `wit_bindgen` guest edits forces a
committed WASM artifact rebuild. Only six do:
`scripts/ci/check-wasm-artifact-freshness.py` scans
`crates/extensions/packages/*/wasm-src` alone, `wasm-src-digests.toml` holds
exactly six entries, and `git ls-files '*.wasm'` returns exactly those six.
The three `test-tools/*/wasm-src/` guests commit no artifact; the tenth site
is the host's `bindings.rs`, not a guest. Corrected, and the `wit/` row now
states the boundary rather than implying it.
Guest paths, `wit/` contents and the six rebuilt artifacts are untouched.
Verified: `test_reborn_pr_test_plan.py` 46/46, `test_ws12_workflow_contracts.py`
25/25, `ws12_workflow_contracts.py` green on the real tree,
`cargo test -p ironclaw_architecture` 206/206 across 32 binaries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 5, 2026
Open
BenKurrek
added a commit
that referenced
this pull request
Aug 5, 2026
Refreshes #7152 onto the moved world: `main` after #7170 and #7181, plus the D-A factory port and the `extension_host` products -> loops flip. Merged rename-aware, never rebased. A plain 3-way merge sees the 13 crate renames as content, so every line that names a renamed crate conflicts with whatever `main` did nearby: 224 conflicted paths, most of them noise. Folding the rename into the merge base instead -- R(base) and R(theirs), where R is the whole-word rename applied to paths and blobs -- cut that to 66 real content conflicts and zero rename/location conflicts. Of the 66, 52 were provably `cargo fmt` churn on our side: the token multiset (whitespace and commas ignored) of our whole file equals the base's, so the branch changed nothing semantic there and the incoming side is taken verbatim. The remaining 14 were reconciled by hand, incoming semantics winning and the rename mapping re-applied.
7 tasks
This was referenced Aug 5, 2026
Closed
25 tasks
This was referenced Aug 5, 2026
This was referenced Aug 13, 2026
l3ocifer
pushed a commit
to l3ocifer/frick-ironclaw
that referenced
this pull request
Sep 3, 2026
…isions (accumulating the fleet) (nearai#7181) * refactor(contracts): move extension runtime descriptors to a neutral contract (WS3) Deletes the two `-> ironclaw_extensions` layer-matrix exceptions (`ironclaw_mcp`, `ironclaw_scripts`) by giving the runtimes-layer lanes a contracts home for the descriptors they read, instead of the registry crate they may not depend on. Exceptions 13 -> 11; baseline lowered in the same change. Moved to `ironclaw_extension_contracts`: - `runtime::{ExtensionRuntime, ExtensionAssetPath, ExtensionAssetPathError}` - `hosted_mcp::{HostedMcpDiscoveredTool, HostedMcpDiscoveredToolAnnotations}` `ExtensionPackage`/`ExtensionManifest` deliberately stay in `ironclaw_extensions`: they carry the whole parsed manifest tree and a `PackageRootBinding` typed on `ironclaw_filesystem::VirtualPath`, which the §11.2.3 contracts-purity allowlist (`{ironclaw_host_api}` only) forbids the contracts crate from naming. Measured instead: both lanes read exactly three things off the package — `id`, `capabilities`, `manifest.runtime` — so the lane request structs now take those three and the caller (which owns the package) projects them. Also repointed `ResourceReceipt` to its real owner: `ironclaw_resources` only re-exports `ironclaw_host_api::resource::ResourceReceipt`, so the lanes' import was a §11.2.4 two-import-paths hop, not a dependency. No `pub use` shims (§11.3): every consumer is repointed in this change, and `resolve_under` becomes the free function `ironclaw_extensions::resolve_asset_under` because the orphan rule forbids an inherent impl on the moved type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(sandbox): merge the sandbox lane into one crate (WS3) Creates `ironclaw_sandbox` (runtimes) from the three halves of "run an already-authorized command away from the host", and deletes the two crates PROPOSAL §6.6.4 marks for merge: - `ironclaw_process_sandbox` (plan contract) -> `src/plan.rs`, `src/validation.rs` - `ironclaw_host_runtime::sandbox_process` -> `src/sandbox_process/**` - `ironclaw_scripts` (script lane + Docker path) -> `src/script.rs` The kernel sheds the Docker/CA cone: `bollard`, `rcgen`, `x509-parser` and `time` are gone from `ironclaw_host_runtime`'s manifest, and `bollard`/`rcgen` are now declared by exactly one crate in the workspace. Two migration details PROPOSAL §6.6.4 and CHECKLIST WS10 call load-bearing: - `PROCESS_SANDBOX_CAPABILITY_ID` -> `ironclaw_host_api::capability`, so `ironclaw_loop_host` drops its lane dependency (production dep gone; a dev-dep remains for the tests that build plans). - `SandboxCommandTransport` -> `ironclaw_host_api::process`, with the shapes it names (`CommandExecutionRequest`/`Output`, `RuntimeProcessError`, `SavedCommandOutput`, `SavedCommandOutputSanitization`). Without this the runtimes-layer lane could not implement what the kernel consumes. Enumerating gates were repointed, never relaxed: the specificity carve-outs and the struct/test-support ratchet entries moved with their files (both baselines unchanged at 129 and their prior values), the panic-gate baseline row moved, `reborn-crate-test-buckets.sh` registers the new crate, and the three `reborn-e2e-rust.sh` script selectors follow the tests (plus `docker_security`, which had no selector before). One gate would have gone silently vacuous and was fixed rather than moved: the script-lane surface scan in `reborn_dependency_boundaries.rs` read a hardcoded `src/lib.rs`, which after the merge no longer holds the lane. It now scans the whole crate source tree with a fatal-read walk and a non-vacuity assertion. One deletion, recorded: `RebornScopedSandboxCommandTransport::into_process_port` returned a kernel type a runtimes crate may not name. It had zero callers workspace-wide; the kernel wraps the transport, which is the direction the port inversion requires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(target-architecture): record the WS3 corrections with their evidence Three dated amendments, each quoting the text it replaces: 1. CHECKLIST WS3 sandbox row + PROPOSAL §6.6.4 — "all pieces currently unwired/test-only" is REFUTED. Three production paths cross the merged crate (spawn-path plan validation, the process_executor routing check, and the saved-command-output scope digest). The accurate claim is narrower: no production *execution backend*. Behavior preservation is therefore argued at the diff (11 of 26 moved files byte-identical, 9 more differing by one import line, +63/-36 overall), not inferred from deadness. 2. CHECKLIST WS3 mcp row + PROPOSAL §6.6.3 — the prior wave's "structurally blocked" finding is half right, and the wrong half is load-bearing: only `ExtensionPackage` is un-absorbable, and no lane ever needed it (both read `id`, `capabilities`, `manifest.runtime` and nothing else). The registry half of the flip is done; the `resources` half is refuted as phrased — the estimate/usage vocabulary the row asks about is already in `host_api::resource` and already imported from there, while the real blocker is the `ResourceGovernor` authority port and `ResourceError`'s denial cone. 3. Recorded as a structural finding, not a note: the sandbox row and the mcp row are ONE problem. `ironclaw_scripts` imports the identical DTO set, so the merge alone deletes zero exceptions and only the mcp carve-out lets either lane shed the registry edge. Also reconciled: PROPOSAL §6.1.2's as-built inventory gains the two modules WS3 landed (and states why `ExtensionPackage` stayed); §2's package count 66 -> 65; the §9 disposition rows for `ironclaw_scripts`/`ironclaw_process_sandbox`/ `ironclaw_mcp`; the §11.2.2 ratchet rows (13 -> 11); the WS3 verify row; the stale WS1.3 sentence asserting the blocker as settled fact; and `reborn_restructure_baselines.rs`'s doc table, which still read 15. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(sandbox): drop imports the merge left unused `process_port.rs` no longer names `MountView` or `thiserror::Error` (both went to `host_api::process` with the types that used them), and `sandbox_process.rs` no longer needs `sync::Arc` after `into_process_port` was deleted. Found by per-crate `clippy --all-targets --all-features -D warnings`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ci): let the Reborn PR planner plan guidance edits and crate deletions Three fail-closed gaps in `reborn_pr_test_plan.py`, all hit by this PR and all live on `main` today — any PR with the same change shape is unplannable. 1. `.claude/**` was unclassified, so the planner refused outright. It is agent guidance in exactly the sense `docs/**` is human guidance: no Rust test reads either as data (the only in-tree references are prose citations in test doc comments). Added to `IGNORED_PREFIXES`. Without this, "guidance travels with the change" — the restructure's own discipline — cannot be satisfied in a single PR. 2. `crates/AGENTS.md`, `crates/README.md`, `crates/Architecture.md` raised "unmapped crate path": they sit under `crates/` but belong to no package. Now classified as crate-tree prose, matched by "Markdown no package directory owns" so a genuinely unmapped crate path is unaffected. 3. An unmapped crate path used to raise. `git diff` reports a deleted crate's old paths and CI feeds the planner that diff, so **every crate deletion or rename was unplannable** — including the six deletions PROPOSAL §2 plans. It now widens to the exhaustive plan. This is a semantic change and it is the safe direction: the full plan is a superset of any narrowing, so an unattributable path can never cause under-selection, whereas refusing to plan blocks the PR instead of protecting it. Malformed input is still rejected by the unclassified-path branch. Each lands with fixtures per WS10's rule, positive and negative: guidance paths select nothing while non-guidance paths still fail closed; crate-tree prose selects nothing while crate *code* under the same unmapped directory widens to `full` (so the Markdown carve-out cannot swallow code). The pre-existing `test_unmapped_crate_path_fails_fast` is renamed and rewritten to pin the new contract rather than deleted. Verified against this PR's real 130-path diff: the planner returns `mode: full`, and the workflow's own exhaustiveness guard passes on that output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(arch): give the retained resource exceptions an owning issue, not a wave Review (#7065) caught that both surviving `-> ironclaw_resources` exceptions declared `removes_in = "WS3"` — the wave this PR *is*, which does not remove them. That is precisely the defect §11.2.2 already records against `conversations -> turns` ("`removes_in = "WS5"` and WS5 has partly shipped without it falling"), and it would have been repeated here. Both now point at issue #7067, which owns the design work that actually clears them: replacing the `ResourceGovernor` dependency with a narrow reserve/reconcile/release port. The issue carries the measurements — 3 of 10 methods used, zero implementors, and the `ResourceError` denial cone — plus the two open questions (error shape, port home) that make it a design slice rather than a move. An owning issue is also what §11.2.2 asks for and what the ratchet still cannot enforce (there is no `owning_issue` field yet), so this is the strongest form currently expressible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(contracts): pin the asset-path validator that moved into extension_contracts `validate_asset_path` moved here with `ExtensionAssetPath`, the type it constructs. In `ironclaw_extensions` it was only ever reached indirectly through manifest parsing, so its six rejection branches had no direct test — and a contracts crate that carries validation owes that validation one. Two tests: every reject branch with its exact reason and `Display` output (empty, NUL/control, URL, absolute, Windows drive and backslash, and the empty/`.`/`..` segment cases) plus the manifest-relative shapes that must keep being accepted; and `ExtensionRuntime::kind()` over all five variants, since that projection is what every lane uses to reject a runtime it does not serve. Also removes a changed-line coverage risk this PR would otherwise carry into the merge queue: the gate does not run on ordinary PRs (#7036), so ~100 newly-added lines of validator would first be measured where a failure is expensive to diagnose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(coverage): re-capture the host_runtime floor and floor the new sandbox lane `RATCHET FAIL: ironclaw_host_runtime` — observed 18854 covered vs a `floor_covered_lines` of 20538. This is the shrinkage case the ratchet's own "To fix" text describes, not a coverage regression: `sandbox_process/**` moved to `ironclaw_sandbox`, so the crate's denominator fell 23277 -> 21267 (-2010 instrumented lines) and its covered lines fell with it. The percentage floor is **raised, not lowered**: observed 88.65% against an old floor of 88.23%, so the entry now reads 88.65. Only the absolute line count moves down, and it must — those lines are no longer in this crate. To keep that from being a net loss of protection, `ironclaw_sandbox` is floored on arrival at its observed 87.09% (3185 / 3657). This is a net *increase* in ratchet coverage: neither `ironclaw_scripts` nor `ironclaw_process_sandbox` was ever floored, and the `sandbox_process` half was protected only as part of host_runtime's line count, which this PR necessarily reduces. Floored crates 16 -> 17. Verified by replaying the ratchet arithmetic against CI's observed numbers: both crates pass on percentage and on covered lines. Numbers taken from the failing run's own report (job 91740733521), which is the authority for this gate. The `Tests (Reborn)` roll-up failed solely on this sub-job ("coverage-report result 'failure' did not match planned=true"); no other lane failed — 50 pass, 2 fail, both this root cause and its roll-up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(target-architecture): record the coverage ratchet as a move-sensitive gate WS3 hit a gate no move row had named. `tests/integration/coverage-floor.toml` is keyed on crate identity plus absolute covered-line counts, so it is invisible to WS10's path-keyed gate audit and yet it fails on every crate move, merge, rename, or family `git mv` that shifts instrumented lines between crates — as it did here, while the percentage floor was *improving*. Recorded on WS10 with the three rules WS7 will need: re-capture in the same PR, raise the percentage floor rather than leaving it, and floor the destination crate or the move silently drops that code out of the ratchet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(extension-manager): repoint ironhub onto the moved ExtensionAssetPath A semantic conflict the merge could not see: #6780 landed `ironhub/{package,catalog}.rs` importing `ExtensionAssetPath` from `ironclaw_extensions`, while this branch moved that type to `ironclaw_extension_contracts::runtime`. Different files, so git auto-merged cleanly and the breakage surfaced only at `cargo check`. Repointed both sites to the contracts crate (no shim, per §11.3). The manifest already named `ironclaw_extension_contracts`, so this is imports only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(coverage): exempt the WS3 move's no-region lines and record the gate The changed-lines coverage gate went red on four files while changed-line coverage was 95.35% against a 90% floor: the failure was its two fail-closed STRUCTURAL assertions, not any percentage. Every line below was derived by replaying scripts/ci/reborn_changed_coverage.py against this PR's own merged lcov (run 30831658659) with the base lcov the gate itself resolved (run 30828540055 @ b89fcd3575), until the replay reproduced the CI verdict byte-identically. Line numbers come from the gate's own `candidate_lines - mechanically_uninstrumentable_lines()`, not from the log. - host_api/src/process.rs (31 lines): new placement-neutral process vocabulary with no function body anywhere in the file; rustc emits no LCOV record for it at all. Same shape already exempted for product_contracts/loop_contracts. - extension_contracts/src/hosted_mcp.rs (12): field declarations of the two new tools/list descriptor structs. The file is plainly instrumented (191 DA, 164 hit), so this is a no-region artifact, not an instrumentation gap. - host_runtime/src/services/runtime_adapters.rs (13): continuation lines of three rewritten calls, all PROVEN EXECUTING by their region-start heads (lines 380/434/977 score 24/16/63 hits). The four genuinely-uncovered lines in the same rewrite are deliberately NOT exempted -- the gate already subtracts them as pre-existing debt inherited from base. - composition capability_host_tests/approval_gates.rs (6): type positions in a test double whose body region scores 1 hit. The last one is a finding, not just a waiver: that file is 100% test code behind `#[cfg(test)] mod capability_host_tests;`, but the gate's test_only_path() recognises /tests/, /test_support/, */tests.rs and *_tests.rs and NOT a cfg(test) module DIRECTORY, so it measures it as production. It is the only such directory in crates/ today. Docs (target-architecture, same PR per the docs-truth rule): - CHECKLIST WS10 gains the changed-lines gate beside the ratchet row, cross- referencing the WS2.1 note rather than restating it: percentages are not what fail a move; derive lines by byte-identical replay (--fetch-base-coverage silently degrades without --github-repo); and a stranded exemption path is an ABORT with no verdict, not a loud failure. - CHECKLIST WS10 exception-ratchet row: the constant was cited at :4063 and sits at :4164 -- corrected by removing the line pin, since the file is edited every wave. Records that the baseline is a UNION across parallel WS3 lanes. - families/contracts.md: records extension_contracts' new ownership of the runtime descriptor vocabulary -- the carve-out that let BOTH lanes drop the registry edge -- and the orphan-rule seam that keeps resolve_asset_under in the registry crate. - families/lanes.md: two "Never" claims were reading as satisfied when they are not. ironclaw_mcp's "never depends on the resource-governor crate directly" is refuted (the compiled edge survives; #7067 tracks the narrow port), and ironclaw_sandbox's "no direct process spawning outside the transport seam" is aspirational -- script.rs:454 still builds Command::new("docker"). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(sandbox,mcp): correct the wiring inventory and record the projection cost Two review findings verified against the tree; three refuted with evidence in the PR threads. Valid — the sandbox wiring inventory was self-contradictory. `CLAUDE.md` said "Two production call paths ... and both are plan validation" directly above a list of THREE bullets, and `lib.rs` omitted the third entirely. The third is real and is not validation: `host_runtime/src/process_output.rs:482` derives the scoped saved-output directory through `RebornSandboxScopeKey::from_scope`. That inventory is what tells a future agent which paths are live, so an undercount invites deleting a production path as dead code. Both surfaces now say three and no longer claim they are all plan validation (the `loop_host` capability-id comparison never was either). Valid, and recorded rather than redesigned — the registry carve-out cost a type-level invariant. Replacing `package: &ExtensionPackage` with independent `extension` / `capabilities` / `runtime` borrows is what deleted the `mcp -> extensions` and `scripts -> extensions` exceptions, but it also means the type no longer guarantees the three came from one package. `execute_extension_json` re-checks the descriptor half (`descriptor.provider == extension`); the runtime half cannot be re-derived, because nothing in an `&ExtensionRuntime` names its owning extension. No caller can trip it today -- there is exactly one production caller (`runtime_adapters`) and it projects all three from one package in one expression -- so this is a latent structural weakening, not a live defect. Restoring the compile-time binding needs a sealed projection minted by the package owner; a check inside the lane cannot express it, and re-taking the registry edge would undo the carve-out. Both request types now carry the caller obligation in their field docs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(extensions): move the skill-install executor to extension_support (WS3) WS3's first-party-tools row, family 1 of 6: skill management / URL install. `skill_url_install.rs` and its `bundle`/`github`/`zip_bundle` submodules, plus the install-input normalizer, move out of `ironclaw_host_runtime::first_party_tools` into `ironclaw_extension_support::skills::{url_install, resolve_install_input}`, where the skill executor half already lived. Move-only: no behavior change, no test edited for content. `ironclaw_host_runtime -> ironclaw_skills` is deleted from LAYER_MATRIX_EXCEPTIONS — the edge is gone, not waived (exceptions 13 -> 12, WS0_LAYER_MATRIX_EXCEPTION_BASELINE drops with it). `ironclaw_skills` and `zip` survive as dev-dependencies for host_runtime's own tests; dev edges are outside the matrix by construction. Two doc ambiguities are resolved in the same diff, as dated PROPOSAL amendments quoting the text they replace: - §6.8.4's "the builtin first-party tool handlers absorbed from host_runtime/first_party_tools" contradicted §8.2's "kernel: ✗ (ports only)" row and the enforced BoundaryRule. Resolution: the seam splits executor from adapter — the executor moves behind a neutral request/error pair, the FirstPartyCapabilityHandler / CapabilityManifest / registry wiring stay host-side. Same shape the groupware and web-access tools already ship. - §8.2's "ports only" cell now says what it means: contracts-layer ports the kernel also consumes, not permission to name a kernel trait. Two cost corrections recorded for the remaining families: `host_runtime -> extension_support` is not divisible family-by-family (mod.rs holds it via `extension_support::coding`), and `host_runtime -> ironclaw_extensions` is not reachable by this row at all. PATH_TERM_COLLISIONS shrinks by two: the installer's github carve-outs now sit inside a scan-exempt crate. Test accounting (un-masking discipline), unfiltered `--list` over both crates: 1398 -> 1398, with exactly two tests renamed by module path and none lost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(sandbox): record that the Docker fail-closed switch is wired to nothing Review asked why the migrated docker_security test can pass with no daemon. The skip is pre-existing (the file differs from its pre-merge original by one import line); WS3 only enrolled it in the required Rust e2e lane, where it was not run at all before. The real defect the question surfaced is worse and also pre-existing: this crate's tests/support/docker_gate.rs states that IRONCLAW_REQUIRE_DOCKER_TESTS=1 makes a missing daemon a hard failure and that "CI sets this" -- and nothing sets it. Repo-wide the name occurs only in docker_gate.rs and attribution_tests.rs, here and on main. So every real-Docker test in the crate skips-and-passes everywhere, which is exactly the gap the gate's own comment says let sandbox security bugs ship unnoticed. docker_security.rs additionally open-codes its own check rather than using the gate, so it would stay fail-open even once something did set the variable. Recorded rather than fixed: setting the variable is a CI-behavior change that would hard-fail any lane without a daemon or the ironclaw-worker image, which is not verifiable from inside a move PR whose evidence claim is behavior preservation. Filed as the #6945 guardrail-claim-vs-reality class with the two-part fix stated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(host_runtime): record the executor/adapter seam in crate guidance The crate's CLAUDE.md said "first-party runtime tools belong under `first_party_tools/`" without saying that only the host half does. WS3 moves each tool's executor into `ironclaw_extension_support`, which may not name this crate, so the rule now names both halves and points at the skill-install family as the worked example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(host_runtime): keep the install-input error path log-free The moved executor returns `SkillManagementCapabilityError`, and routing it through `skill_management_error` would have added a `debug!` line to a path that had none before the move. A move-only change must not add one, so the install-input arm maps the kind directly and the `dispatch` arm keeps the record it already had. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci(coverage): re-capture the host_runtime floor for the WS3 executor move The ratchet does not run on `pull_request` (`reborn_pr_test_plan.py:21`; issue #7036), so this PR's green checks were not evidence on this axis. A full-plan `workflow_dispatch` run on this exact head reported: RATCHET FAIL: ironclaw_host_runtime observed: 88.59% (20485 / 23124 lines) floor: 88.23% ... floor_covered_lines: 20538 (effective floor 20518) The percentage went UP while `floor_covered_lines` went DOWN — shedding well-covered code lowers the absolute numerator, which is a separate assertion from the percentage one. Re-captured to the observed numbers (floor raised 88.23 -> 88.59, not merely held). Verified locally against that run's own merged lcov artifact: ENFORCING mode, 17 PASS / 0 FAIL, exit 0. run: https://github.com/nearai/ironclaw/actions/runs/30858257594 head: e07b3b0299b0add11117e9591da71d46d7a7c832 The destination crate is deliberately not floored, because it cannot be: every crate under `crates/extensions/` is invisible to the coverage tooling — `reborn_coverage_lcov.py:19`'s CRATE_RE still requires a crate directory directly under `crates/`, which #7037's colocation broke. Filed as #7083 with the measurement; the global floor is left alone rather than re-captured onto that hole. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(wasm): move wit/ inside its owning crate (Wave 3) CHECKLIST WS4 + WS10 `wit/` rows. `wit/{tool,channel}.wit` moves from the repo root to `crates/ironclaw_wasm/wit/` — the crate that owns the ABI — per PROPOSAL §6.6.1. Behavior-free: same bytes, same generated bindings. Wave-3 coordinates: the docs write the destination as `crates/lanes/ironclaw_wasm/wit/`, but `crates/lanes/` does not exist until WS7. Because the files now sit *inside* the crate, the WS7 family move carries them with no further path edit anywhere — which is the whole point of putting them there. Ten wit-bindgen `path:` args repointed (the host plus nine guests: six under `crates/extensions/packages/*/wasm-src/`, three under `test-tools/*/wasm-src/` — the CHECKLIST row said six). All nine guests verified building against the moved WIT on wasm32-wasip2. The four `include_str!` readers of the ABI text do NOT get repointed literals. Doing that would turn the two `ironclaw_host_runtime` sites from repo-root reach-ins into *cross-crate* ones — §11.2.7's strict class, the one WS2 turns into hard failures — taking the scan from 19 to 21 while ticking a box that says "§11.2.7 scan passes". Instead the ABI text gets one owner, `ironclaw_wasm::TOOL_WIT` (`src/config.rs`, beside `WIT_TOOL_VERSION`), and all four sites read the const over cargo edges that already exist. Measured with the scan: 133 -> 129 escaping sites, cross-crate 19 -> 19, zero `wit/` entries remaining. Path-keyed gates repointed: `scripts/check-version-bumps.sh` (both ABI paths), `.githooks/pre-commit`, and `platform-and-compat.yml`'s `has_direct_wasm_abi_risk` filter — where the bare `wit/` alternative is *deleted* rather than rewritten, because the filter's existing `crates/([^/]+/)*ironclaw_wasm/` alternative already matches both the Wave-3 and the WS7 location. `scripts/ci/ws12_workflow_contracts.py` anchored on that deleted string, so its anchor moves to `build-wasm-extensions` and its in-scope probe now pins both locations. `Dockerfile` loses two `COPY wit/ wit/` lines in the planner and builder stages: both already run `COPY crates/ crates/`, so the files arrive with the crate and the old line would COPY a path that no longer exists. Docs: the WS4 row's `crates/lanes/wit/` destination was the only doc site placing the directory beside the crate rather than inside it; corrected there and in README's tree, with dated amendments in CHECKLIST, PROPOSAL §6.6.1 and PLAN Wave 3 recording what the move found. Test accounting (unfiltered `--list`, name-by-name, quiescent tree): ironclaw_wasm 51 -> 51, ironclaw_host_runtime 1246 -> 1246, ironclaw_architecture 198 -> 198. Zero diff, no test edited for content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * build(wasm): rebuild first-party artifacts for the moved wit/ path Forced by the previous commit, not incidental to it. `scripts/ci/check-wasm-artifact-freshness.py` keys each package's committed `wasm/<name>.wasm` to a digest of the `wasm-src/` tree that produced it, so editing a guest's `wit_bindgen::generate!` `path:` — which the `wit/` move requires in all six shipped guests — invalidates the recorded digest and fails the gate. The gate's own contract forbids the shortcut: "Re-record only after `./scripts/build-wasm-extensions.sh --first-party` and committing the rebuilt artifact — the digest asserts a claim about the artifact, and updating it without rebuilding launders a stale one." So the artifacts are genuinely rebuilt (`--first-party`, exit 0, 6 OK / 2 host-native SKIP), not re-recorded in place. Byte sizes move by more than the source change accounts for because these builds are not reproducible by design — the guests pin no toolchain and resolve their own `Cargo.lock` at build time, which is the documented reason the gate hashes sources rather than artifact bytes. Verified: `check-wasm-artifact-freshness.py` OK (6 packages), and `cargo test -p ironclaw_extension_support` green (102/46/4) — that crate `include_bytes!`s these artifacts, so it exercises the rebuilt components. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(target-arch): record the WS7 artifact-rebuild cost of guest path edits The `wit/` move had to rebuild six shipped WASM binaries because `check-wasm-artifact-freshness.py` digests each guest's whole `wasm-src/` tree. WS7 hits the same wall from the other direction: the six package guests reach the ABI across two trees, so moving either `ironclaw_wasm` or `extensions/packages` rewrites all six `path:` literals and forces the same rebuild. Recorded on CHECKLIST WS10's `wit/` row (point 6), on the loud-path-pattern row that owns the WS7 repoint (also corrected six -> nine guests there), and on PLAN's Wave 5 block with the cheap mitigation: move the two crates in one PR and pay it once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci(planner): classify the path classes that blocked the wit/ move `Detect Reborn test scope` exits 1 on any pull request whose diff holds a path `reborn_pr_test_plan.py` has no rule for, which made this PR unmergeable: it must edit `Dockerfile` (the moved directory's `COPY wit/ wit/` no longer resolves) and `scripts/check-version-bumps.sh` (the ABI gate would otherwise grep dead paths and silently stop enforcing). 18 of its 46 paths were unclassified. Same class as the `.claude/` gap #7064 fixed, and classified the same way — one rule per class, recorded beside the constant: * `Dockerfile` / `.dockerignore` — `platform-and-compat.yml` keys `has_docker_risk` off exactly this pair and owns the image build. * `.githooks/**` — Code Style triggers on the tree and lints its contents (`test-ci-comm-locale-pin.sh`); no Reborn lane runs a hook. * `scripts/{build-wasm-extensions,check-version-bumps}.sh` — `platform-and-compat.yml`'s `has_direct_wasm_abi_risk` classifier both scopes and runs them. * markdown owned by no crate (`crates/AGENTS.md`, `test-tools/README.md`) — prose, like `docs/` and `.claude/`. A crate-resident doc still selects its own crate's lane. The first-party extension package assets are deliberately NOT ignored. `crates/extensions/packages/*/wasm/*.wasm` is a shipped artifact that `ironclaw_extension_support` embeds with `include_bytes!`, and `test-tools/*/manifest.toml` is `include_str!`d by `ironclaw_extension_host`. Calling either prose would convert today's loud failure into a silent under-schedule of a change to production output — the WS10 failure mode. `EMBEDDED_ASSET_OWNERS` routes each tree to the crate that compiles it instead, so this PR now additionally schedules `ironclaw_extension_{support,host,manager}`: the crates that consume the six rebuilt WASM artifacts. Also fixes #7085 in a file this PR already touches. The WIT version extractors used the GNU-only BRE `\+`, so on BSD sed (macOS) they matched nothing, and because the `WIT_TOOL_VERSION` cross-check is guarded on a non-empty version the hook printed "All version checks passed" having compared nothing. `[[:space:]][[:space:]]*` is identical under GNU sed, so the enforced Linux CI lane is unchanged; verified on BSD sed that both `wit/tool.wit` (0.3.0) and `wit/channel.wit` (0.3.1) now extract. Regression tests: every classified class gets a case in `test_reborn_pr_test_plan.py`, including the paired assertion that the embedded assets *select a lane* rather than merely being accepted (the inverse of the `.claude/` prose test), and a staleness pin that fails if an asset tree or its owning crate moves. All ten new cases fail against the planner on `main`. `test_unclassified_build_input_fails_fast` moves off `Dockerfile` onto a still-undecided input so the fail-closed arm stays exercised. Refs #7087, #7085 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(host-runtime): split obligations into its three chartered owners (WS3) `crates/ironclaw_host_runtime/src/obligations.rs` was 3,122 lines fusing the three owners PROPOSAL §6.5.9 charters separately, held apart only by an `// arch-exempt: large_file` waiver. It is now one module per owner: - `obligations::handler` — which obligations apply and what each does before/after dispatch, plus the audit/redaction/ceiling/mount validation. - `obligations::staged_handoffs` — material staged for a later consumer: the runtime-secret and network-policy stores and the credential-account resolver port. - `obligations::process_store` — post-start handoff discard and reservation reconciliation. - `obligations::mod` — only `BuiltinObligationServices`, the assembly seam, and deliberately the one place naming all three at once. Every module is under the 1,500-line gate, so the waiver is deleted rather than carried forward: re-fusing the owners now trips `pre-commit-safety.sh`. `mod obligations;` stays private and the crate's `pub use obligations::{…}` names are unchanged, so no consumer outside the crate sees this. Behavior-free. Cross-owner access is `pub(super)` (three methods), not `pub(crate)`. The split revealed one narrowing in the other direction: `secret_present` was `pub(crate)` with no caller outside its own file and is now private. Also from the same CHECKLIST row, the bounded half of "shrink `services/builder.rs` toward composition-facing factories": three builder methods whose only callers are inside the crate's `src` narrow to `pub(crate)`. The rest of that clause is measured and deferred in the CHECKLIST amendment — 17 methods need a `test-support` cargo feature, three are callerless and belong to WS8, and the remaining 33 are a redesign of the fluent surface rather than a shrink of it. `+production_wiring` is refuted there: it is readiness diagnostics, not assembly. Two loud path-keyed gates fired and were repointed, not relaxed: `reborn_host_runtime_services_do_not_expose_lower_substrate_handles` now scans the whole `obligations/` directory and asserts it read ≥ 4 files (`collect_runtime_rs` returns a count; both its callers now assert non-zero), and `reborn_struct_test_support_ratchet`'s frozen per-file count moves to `staged_handoffs.rs` with its count unchanged at 1. Test accounting (un-masking discipline): `cargo test -p ironclaw_host_runtime --all-targets -- --list` is 1,246 before and 1,246 after, name-by-name identical — zero added, removed or renamed. `LAYER_MATRIX_EXCEPTIONS` is 10 before and after; an intra-crate split cannot move the register. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(operator,contracts): route operator secrets through a product_contracts port (WS3) `ironclaw_operator` is a products-tier crate and held `ironclaw_secrets`, the substrate that owns CAS one-shot leases, AAD/crypto and the OS keychain master key. PROPOSAL §8.2's product row says the products tier loses that edge, and §12.1b requires the port replacement to land before the edge is removed. Both happen here, in that order. - Port: `ironclaw_product_contracts::operator_secrets::OperatorSecretValueStore`. - Implementor: `ironclaw_reborn_composition::RuntimeOperatorSecretValueStore`, the same placement as `OperatorStatusService` — assembly is the only layer that may name both a products-tier port and a substrate. Registered in `INVERTED_PORTS` beside it. - `ironclaw_secrets` is gone from the operator manifest under every dependency kind, and `"ironclaw_secrets"` is now in the crate's `boundary_rules()` forbidden list. That gate's comment previously said the entry was deliberately absent because "the row owns it"; the row now owns it. The port is deliberately narrower than the substrate, so this is a tightening rather than a relocation: it takes no `ResourceScope` (the implementor fixes the operator scope, where the caller used to pass one), exposes no lease/consume protocol, and carries only a `&'static str` classification instead of the substrate's error `Display` — asserted, including that the backend message and the handle name are both absent from what crosses. Two tests travelled with the behavior rather than being pointed at a fake: `read_is_repeatable_across_reloads` (repeatability is a property of the lease protocol) and the #4673 production-store reproduction (its value is wiring the store exactly as production does, which now means the real store *behind the adapter*). Two `FaultInjecting`-over-real-store fixtures became per-operation port fakes, with the substrate error mapping re-pinned at the adapter; a third assertion got stronger — batched-vs-N+1 stored-key lookup is now observed at the port rather than by counting filesystem ops. Test accounting: operator 154 -> 153, product_contracts 142 -> 143, composition 937 -> 942 with zero removed; name-by-name diffs on a quiescent tree. Two findings the row could not have anticipated, both recorded in the CHECKLIST amendment: - The `webui` half of the row was already closed and was never a production edge. `ironclaw_secrets` has been a dev-dependency of `ironclaw_webui` since the commit that added it (#6619), both src mentions are `#[cfg(test)]`, and webui's boundary rule already forbade it. - `ironclaw_extension_manager` (layer `products`) still holds a normal `ironclaw_secrets` edge in `admin_configuration.rs`. §8.2 covers it; the row does not, because the crate landed with WS2.4 after the row was written, and the substrate sits in the service's type parameters so it is not a like-for-like swap. Filed as #7095. `LAYER_MATRIX_EXCEPTIONS` is 10 before and after: `products -> substrates` is matrix-legal, so this edge was always an §8.2 rule and never a layer exception. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(sandbox): put the Docker security check behind the fail-closed gate Review asked why the required Rust e2e lane can report `docker_security` as passing with no daemon. Half of that is #7081 (nothing sets IRONCLAW_REQUIRE_DOCKER_TESTS=1, so the switch is inert) and is not fixable from here -- arming it hard-fails any lane lacking a daemon or the worker image, which needs a runner guaranteed to have both. The other half is fixable here and is fixed: docker_security.rs open-coded its own `docker version` / `image inspect` checks with three bare `return`s, so it sat entirely outside docker_gate and would have stayed fail-open even once something did set the variable. It now takes both preconditions from docker_gate::{docker_available, docker_image_available} and skips with the visible `SKIP:` line that gate's module doc requires. Measured, same machine, image absent: before, IRONCLAW_REQUIRE_DOCKER_TESTS=1 -> "skipping ..." / 1 passed after, IRONCLAW_REQUIRE_DOCKER_TESTS=1 -> panic at docker_gate.rs:74 / FAILED after, variable unset -> "SKIP: ..." / 1 passed The third line is the no-op proof: the variable is set nowhere in this tree or on main, so no lane's behavior changes today. The daemon-down path already reached the image check and skipped there, so the outcome is identical; only the branch it takes differs. Two stale comments in docker_gate.rs corrected with it (they claimed docker_security used its own gate, and that docker_image_available had no consumer), and the crate's Known debt entry now splits the done half from the #7081 half instead of describing both as open. cargo test -p ironclaw_sandbox: 193 passed, 0 failed cargo clippy -p ironclaw_sandbox --tests --all-features -- -D warnings: exit 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(reborn): stop calling the unwired script lane an execution lane Two review findings, both correct, both artifacts of this PR's own renames. 1. engine-v2-to-reborn-parity.md note 4 read "a native script/software execution lane (`ironclaw_sandbox`, `RuntimeKind::Script`) sandboxed via `ironclaw_sandbox`" -- self-referential after the merge collapsed ironclaw_scripts and ironclaw_process_sandbox into one crate, and it contradicts note 5 four paragraphs down ("no production execution backend is wired for it"). Re-stated as the typed runtime contract it is, citing the measurement: `with_script_runtime` has zero production callers (`rg` finds only the builder itself, docs, and 30 test call sites). 2. CHECKLIST WS10 ratchet note 2 said "raise the percentage floor ...; only the line count should fall". That generalises WS3's sandbox merge, where observed coverage happened to rise. It is wrong as guidance for WS7, and the counterexample is in this same file: the 2026-08-03 entry from #7064 records ironclaw_runner falling 85.55% -> 82.53% because the shed removed the crate's better-covered half, holding the floor, and RATCHET FAILing in the merge queue. Note 2 now says re-capture from the merged artifact, and lower only with that entry's move-not-regression counterfactual (add the moved files back, confirm the union clears the old floor, plus a zero-tests- lost name set-diff). cargo test -p ironclaw_architecture: 32 targets, 206 passed, 0 failed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ci): pin the WIT scope probes and the embedded-asset owner pairing Three review findings on the `wit/` move, each verified before it was acted on. 1. `ws12_workflow_contracts.py` probed `crates/ironclaw_wasm/wit/host.wit` and its nested twin. No `host.wit` exists in this repository — `git ls-files '*.wit'` returns only `tool.wit` and `channel.wit` — so both probes sat under the `crates/([^/]+/)*ironclaw_wasm/` alternative and re-asserted the crate-name term while saying nothing about the canonical ABI contracts. In a validator whose stated design is "probe derived from reality rather than from a guessed layout", a fabricated filename is a defect on its own terms. Replaced with a `crate_globs` entry, `("ironclaw_wasm", "wit/*.wit")`, which discovers the contracts on disk, requires each in scope, and synthesises the nested WS7 form — so a third contract, or the directory leaving the crate, fails the pin instead of passing on a stale name. Verified non-vacuous: narrowing the workflow alternative to `.../ironclaw_wasm/src/` now reports `tool.wit`, `channel.wit` and the nested probe as out of scope. 2. The embedded-asset routing test substituted `alpha`/`beta` owners so it could reuse the synthetic workspace. That exercised the real prefix strings through the real routing, but left the prefix->owner *pairing* — the table's entire semantic content — asserted nowhere: swapping `ironclaw_extension_support` and `ironclaw_extension_host` passed. Fixed in two halves. The routing test now drives the real `EMBEDDED_ASSET_OWNERS` against a workspace carrying the real owners' names and real manifest paths (the synthetic one could not: `build_plan` rejects a changed package outside the canonical set), asserting the real owner is selected. And the not-stale test now derives the same pairing from the tree instead of restating the constant: it resolves every literal `include_str!`/`include_bytes!` in every workspace crate through `crate_tree`, keeps the targets no crate owns — the ones that actually reach the table — and asserts that every crate compiling one of them is the routed owner or a dependent of it. That surfaced a property worth pinning: `crates/extensions/packages/` is embedded by four crates, not one. `ironclaw_extension_host`, `ironclaw_extension_manager` and `ironclaw_reborn_composition` reach into it alongside `ironclaw_extension_support`, and routing to the support crate covers them only because each depends on it. If that edge goes, a shipped artifact change stops scheduling a crate that embeds it — the silent under-schedule the table exists to prevent. Regression coverage verified red by sabotage, all three wrong tables: owners swapped (7 failures), `packages/` -> `ironclaw_llm` ("embeds nothing from it"), and the hardest case, `packages/` -> `ironclaw_reborn_composition` — a real embedder that the other embedders do not depend on ("...does not depend on..., so routing there never schedules it"). 3. CHECKLIST WS10 claimed each of the nine `wit_bindgen` guest edits forces a committed WASM artifact rebuild. Only six do: `scripts/ci/check-wasm-artifact-freshness.py` scans `crates/extensions/packages/*/wasm-src` alone, `wasm-src-digests.toml` holds exactly six entries, and `git ls-files '*.wasm'` returns exactly those six. The three `test-tools/*/wasm-src/` guests commit no artifact; the tenth site is the host's `bindings.rs`, not a guest. Corrected, and the `wit/` row now states the boundary rather than implying it. Guest paths, `wit/` contents and the six rebuilt artifacts are untouched. Verified: `test_reborn_pr_test_plan.py` 46/46, `test_ws12_workflow_contracts.py` 25/25, `ws12_workflow_contracts.py` green on the real tree, `cargo test -p ironclaw_architecture` 206/206 across 32 binaries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(host-runtime): state the obligation visibility rule as it holds Review catch (#7090): the guardrail sentence promised "cross-owner access is `pub(super)`, never `pub(crate)`", which is stronger than the code. Verified: `RuntimeSecretInjectionStore::{insert, take, clone_material, discard_for_capability}`, `NetworkObligationPolicyStore::{insert, get, take, discard_for_capability}` and both constructors are `pub(crate)` and must stay so — `src/egress/{mod,host_port,credential}.rs` call them, and that is host-runtime composition outside `obligations/`. The rule is restated as the property that actually holds: a method whose only callers are inside `obligations/` is `pub(super)` (the three that are), and `pub(crate)` is what the stores expose to the egress pipeline they exist to serve. A future agent reading the old sentence would have read the existing `pub(crate)` methods as violations. Guidance-only; no code change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(architecture): put the operator secrets boundary entry on the right rule Review catch (#7096), and it is the serious kind: the `"ironclaw_secrets"` entry landed in `ironclaw_extension_contracts`'s forbidden vector, not `ironclaw_operator`'s. The suite still passed, because `extension_contracts` has no such dependency and `ironclaw_operator` then had no entry at all — so the guard this row exists to add was inert, and a green architecture suite was evidence of nothing. Reintroducing the edge would have passed every check. Moved to `ironclaw_operator`'s vector; `extension_contracts` restored to its `origin/main` content byte-for-byte. Negative-probed rather than assumed. With `ironclaw_secrets` temporarily re-added to `crates/ironclaw_operator/Cargo.toml`: reborn_crate_dependency_boundaries_hold ... FAILED ironclaw_operator must not have a normal dependency on ironclaw_secrets and with the manifest restored, 35/35 pass. Two further review findings, both verified before being accepted: - `ironclaw_extension_manager` **does** have a `boundary_rules()` entry (`:3543-3556`, added with WS2.4). The CHECKLIST residue note and PROPOSAL §8.2's 2026-08-02 amendment both said it had none; §8.2's sentence is stale and is marked superseded. The real gap is narrower and now stated: the rule exists and simply does not forbid `ironclaw_secrets` (#7095). - `ironclaw_product_contracts`'s guide claimed "twenty-four shipped modules". Measured: `src/lib.rs` has 26 shipped (27 `pub mod` less the gated `test_support`), and the table was missing `ironhub` **before** this branch touched it. Count corrected to twenty-six and the missing `ironhub` row added, so the inventory matches `lib.rs`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(sandbox): state the Docker-gate claim as the search that checks it Review caught a false inventory in the Known debt entry, and the previous commit is what made it false: "the name appears only in docker_gate.rs and attribution_tests.rs" stopped holding the moment docker_security.rs gained a module doc naming the variable, and CLAUDE.md itself was already a third counterexample. The narrower claim is the one that was always meant and is the one that matters, so it now carries its own reproduction: no workflow, script, env file or manifest mentions the name at all -- `git grep` over *.yml/*.yaml/*.sh/ *.toml/*.py/*.json/.env* is empty here and on main -- and the sole code reference is a read, std::env::var(...) at docker_gate.rs:23. Every other occurrence is a doc comment or a panic message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(coverage): re-anchor the exemptions the merge shifted tests/integration/changed-coverage-exemptions.toml is exact-line-keyed and auto-merges silently. #7096's additions to ironclaw_reborn_composition moved four entries' subject lines by +2 without anything flagging it; a stranded entry makes the changed-coverage validator abort with no verdict at all. Re-anchored by content (difflib line map from the #7065 tree, which the file was validated against, to the union) rather than by arithmetic: runtime.rs [4068..4073, 4082, 4083] -> [4070..4075, 4084, 4085] runtime.rs [3701] -> [3703] ; runtime.rs [3433] -> [3435] lib.rs [616] -> [618] All 142 entries / 1124 line references re-verified against the merged tree: 0 drift, 0 out-of-bounds, 0 missing paths. * refactor(layers): re-layer processes -> kernel and skills -> substrates (WS3/WS4) Two CHECKLIST rows, both of which were a one-line manifest correction rather than a code move: the family docs already placed both crates where the rows want them and only `Cargo.toml`'s `layer =` disagreed. processes -> kernel (WS3). families/kernel.md already lists ironclaw_processes among the kernel crates. The re-layer makes processes -> resources a kernel -> kernel edge, so its LAYER_MATRIX_EXCEPTION went STALE and the gate said so itself: Stale IronClaw crate layer matrix exceptions: ironclaw_processes -> ironclaw_resources from 2026-07-09 should be removed in W7: runtime process management still depends on resource contracts currently classed with kernel behavior That is the gate's verdict, not a judgement call - deleting the entry is the only way to make it pass. Baseline 5 -> 4, recomputed as len(merged list). Checked the direction both ways: all nine crates that take a normal dependency on processes (capabilities, turns, host_runtime, extension_host, loop_host, extension_manager, runner, reborn_composition, stress) are kernel or above, so the move legalizes an edge without forbidding an existing one. skills -> substrates (WS4 SS3.D). families/domains.md already lists ironclaw_skills under 'Layer(s): substrates'. Its only two normal dependencies are ironclaw_filesystem (substrates) and ironclaw_host_api (contracts), both at or below substrates, and its six consumers are all loops or above. No exception moves in either direction. cargo test -p ironclaw_architecture: 206 passed, 0 failed. cargo check --workspace --all-targets: clean. * docs(target-arch): close the WS3/WS4 rows this work satisfies, with evidence Every tick was verified against the merged tree, never against a PR title. TICKED: - sandbox lane merge: ironclaw_sandbox exists, ironclaw_scripts and ironclaw_process_sandbox absent, bollard/rcgen declared by exactly one manifest in the workspace. - mcp drops the registry dep: ironclaw_extensions is [dev-dependencies] only, 0 production ironclaw_extensions:: refs in src/. - skills -> substrates: landed here. - hooks libSQL/Postgres [decision]: ADR recorded - keep both, with the four rejected alternatives and the evidence they are already converged on one trait plus a shared conformance suite. #6945 read first as the row demands, and explicitly NOT discharged: this PR changes nothing in the dispatch path. - WS3 verify row: the row conflated Wave 3 with Wave 5 work (9 of its 10 exceptions carried removes_in = W7). Corrected with the replaced text quoted, the Wave-3 half satisfied edge by edge, and the Wave-5 remainder named with its owning field value. Ticked on the corrected condition. LEFT OPEN OR PARTIAL, each with measurements rather than a hand-wave: - first_party_tools: 1 of 6 families moved; 15 modules still in host_runtime. Ticking would be false. - processes/capabilities row: re-layer DONE; the capabilities/host.rs split is deferred with every module boundary already computed (4,560 lines, the six workflow ranges, and the arch-exempt waiver that must be deleted with it). - host_runtime binding/catalog-defaults: binding half REFUTED (moving it needs RuntimeLaneExecutor/RuntimeLaneRequest made pub, contradicting the same section's Keeps clause; zero external references to either). Catalog half cannot go to extension_host at all - host_runtime is itself a production consumer at memory_native_extension.rs:96,101, so the move is a kernel -> products edge and a Cargo cycle. Correct destination is downward. - network test_rewrite: NOT executed. Recorded the security shape (production binaries compile the seam and honour the rewrite env var at runtime) and the full 6-step plan, because the env var is how the entire E2E suite redirects vendor traffic through the production binary and the change needs feature forwarding into CI lanes I cannot verify here. cargo test -p ironclaw_architecture: 206 passed, 0 failed. * ci(coverage): recapture the two composed floors from a real measurement The provisional values were arithmetic - the sum of the two slices' recorded deltas - and the dispatch caught them, which is the whole reason the brief demanded a measurement rather than a reconciliation. Dispatch run 30907774036 at 4512e03e28f1df15b419d2e36f9f38f8f55d62fd: 26 success / 1 skipped / 2 failure, judged by per-job tally per #6978. The one skip is the pull_request-gated mutation gate; the two failures are the coverage report and the roll-up it drags down, i.e. this file doing its job. ironclaw_host_runtime: predicted 89.05% (18801 / 21114), MEASURED 88.63% (17562 / 19814). The composition was wrong by 1300 denominator lines because both slices measured their delta under the pre-#7083 aggregator, which could not see crates/extensions/** at all - lines leaving host_runtime for extension_support vanished from the tree it could measure, so neither branch's recorded delta describes the post-#7094 world. ironclaw_extension_support: MEASURED 75.31% (7142 / 9484) against #7094's 82.64% (6826 / 8260), captured before #7080's executor lines arrived. floor_percent FALLS 7.33pp and that is flagged in the file for an owner's eye rather than written quietly. Evidence it is composition and not lost tests: floor_covered_lines RISES 6826 -> 7142, so the crate is protected by more absolute lines than before, and #7080's un-masking accounting was 1398 -> 1398 with zero test names lost. Same shape as #7094's own ironclaw_runner recapture. ironclaw_sandbox passed unchanged at its arrival capture (87.09%, 3185 / 3657). The [global] entry is untouched: both moves are crate-to-crate inside the set the fixed aggregator sees. * fix(network): compile the test rewrite seam out of production builds (WS3) Closes the WS3 network row. Also RETRACTS an overstatement I made in this row's earlier annotation. CORRECTION FIRST. The earlier note claimed production binaries compile the seam and honour IRONCLAW_REBORN_TEST_HTTP_REWRITE_MAP at runtime, so anyone able to set it could redirect all credentialed vendor egress. That was WRONG. RewriteNetworkTransport::from_env_value already returned UnavailableInRelease when !cfg!(debug_assertions) (test_rewrite.rs:150), and neither [profile.release] nor [profile.dist] sets debug-assertions, so a shipped binary with the variable set REFUSES TO BOOT. It was fail-closed before this PR. I had read the ungated `mod test_rewrite;` declaration as an ungated runtime path. What was genuinely wrong, and is fixed: 1. The guard was a RUNTIME check keyed on cfg!(debug_assertions) - a profile proxy, not a build-kind guarantee. A release profile with debug-assertions turned on (normal when chasing a production bug) silently re-arms it. 2. The refusal arm had NO TEST. The one guard between a shipped binary and redirectable vendor egress was unpinned. Fix: compile-time exclusion instead of a runtime check. mod test_rewrite and its four re-exports are now cfg(any(debug_assertions, feature=test-support)), and default_host_http_egress is a compile-time pair - production builds PolicyNetworkHttpEgress<ReqwestNetworkTransport> directly, with the rewrite wrapper absent from the binary. The runtime check stays as defence in depth. E2E needs no change: those harnesses build DEBUG binaries, so they satisfy debug_assertions and keep redirecting with no feature flag and no workflow edit. The feature-forwarding-into-CI risk I flagged earlier does not arise. test-support is still forwarded composition -> network for a release-PROFILE build that needs the seam. Both halves proven rather than assumed: (a) release refuses - new regression test a_set_rewrite_map_activates_only_in_debug_and_is_refused_in_release feeds a well-formed map and asserts on profile. Under 'cargo test --release -p ironclaw_network --features test-support' it passes on the UnavailableInRelease branch; under debug 'cargo test -p ironclaw_network' it passes on the active branch. 56 passed, 0 failed. (b) production compiles without the seam - 'cargo check --release -p ironclaw_reborn_composition' (no test-support) is clean, which only compiles if the cfg(not(..)) arm is right. Also: WS0_EXTENSION_SPECIFICITY_ALLOWLIST_BASELINE 129 -> 127. The constant had drifted ABOVE the real list length; the ratchet is shrink-only so it passed silently while buying back two unearned slots. Measured off the compiler (set baseline to 0, read the reported length), identical on main and on every slice, so pre-existing drift rather than something this PR caused. cargo test -p ironclaw_architecture: 206 passed, 0 failed. cargo check --workspace --all-targets: clean. * docs(coverage): verify the extension_support floor drop is composition, independently The 82.64 -> 75.31 recapture carried a rationale that was recorded but explicitly NOT verified. Re-derived it from scratch between the two capture refs (f946a93fae -> 939af4847d) rather than inheriting the claim: - 0 test names lost in the crate (158 -> 160 test fns; both new names belong to the arriving executor). - 0 test names lost WORKSPACE-WIDE (13836 -> 13843 test fns, 13752 -> 13759 unique). This is the check that separates a relocation from a deletion: host_runtime's roster drops 156 names over the same range and every one reappears in another crate. - Exactly four files arrived, 1367 source lines, all of them the family-1 skill-install executor (src/skills/url_install.rs + url_install/{github, zip_bundle,bundle}.rs). No pre-existing file left the crate. - The arithmetic closes with the pre-existing numerator held CONSTANT: (6826+316)/(8260+1224) = 75.31% exactly, so the pre-existing code lost zero covered lines. The arriving block's own coverage is 316/1224 = 25.82%. Composition, confirmed rather than assumed. No test regression to fix; the 25.82% arrival is what earns the follow-up already recorded above the entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(host_runtime): collapse a duplicated obligation predicate and quiet a background warn! Three verified review findings from the #7141 round. Each was confirmed against the code before being acted on; nothing was changed on assertion alone. 1. obligations/handler.rs — `obligation_supported_before_dispatch` and `obligation_supported_after_dispatch` had BYTE-IDENTICAL 19-line bodies (verified by exact line-by-line comparison). Both were private, each called exactly once, both taking the same `phase` argument. The two names asserted a pre/post-dispatch distinction the code never implemented, while the pair gates admission of RedactOutput, EnforceOutputLimit and EnforceResourceCeiling — so editing one copy alone would have left the other stage accepting an obligation the host cannot honour (a fail-open). Collapsed to one `obligation_supported`, with the reasoning recorded so the pair is not reintroduced. 2. obligations/process_store.rs — `cleanup_terminal` is reached from `observe_process_commit` (an async background journal callback, call sites at :363/:379/:394), so its `tracing::warn!` violates the repo rule that background tasks never use info!/warn! — they corrupt the REPL/TUI display. Lowered to `debug!`; the error is still returned to the caller on the next line, so nothing is swallowed. 3. reborn_restructure_baselines.rs — the doc table said the LAYER_MATRIX_EXCEPTIONS count was "now 11". Recomputed on this ref by anchoring on the `= &[` of the value (the `&[LayerMatrixException]` type annotation opens a bracket on the same line and silently yields 0): the real count is 4, matching WS0_LAYER_MATRIX_EXCEPTION_BASELINE = 4. Corrected. Verification: cargo check --all-targets -p ironclaw_host_runtime exit 0; obligation tests 13+26 passed, 0 failed; reborn_restructure_baselines 1 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ci): a shipped package prompt is an asset, not prose — it was selecting no lane Review finding on #7141, confirmed empirically before acting. The Markdown prose carve-out in the planner ran BEFORE the `EMBEDDED_ASSET_OWNERS` lookup. A prompt is a `.md` file that no package *directory* owns, so a change to `crates/extensions/packages/*/prompts/**.md` took the prose arm and planned: mode=none crate_buckets=[] "crate-tree guidance changed: ..." while its sibling `manifest.toml` in the same package planned `mode=selected` onto ironclaw_extension_support + ironclaw_extension_host. Prompts are shipped production output that `ironclaw_extension_support` compiles in, and the comment above `EMBEDDED_ASSET_OWNERS` names "manifests, prompts, schemas and built wasm/*.wasm" as exactly what that table owns — so this was the "silent under-schedule of a change to production output" that comment forbids. 145 of the 149 `.md` files under `packages/` are prompts. The rule is keyed on the `prompts/` path segment, not on the asset prefixes. That distinction is load-bearing: the first attempt yielded to the asset prefixes wholesale and broke `test-tools/README.md`, which is documentation of the fixture bundles and is deliberately pinned as prose. Of the four asset kinds the table owns, only a prompt is Markdown (manifests are .toml, schemas .json, wasm .wasm), so `.md` asset <=> prompt is exact. Sabotage-tested in both directions: * `_is_package_prompt` -> False (reinstates the bug): RED, "AssertionError: 'none' != 'selected'". * `_is_package_prompt` -> any .md under an asset prefix (over-broad): RED on both the new test and the pre-existing `test_markdown_owned_by_no_crate_is_prose`, at `test-tools/README.md`. * restored: 52 passed, 51 subtests, green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(harness): refresh the latency-runner lockfile after the sandbox consolidation Review finding on #7141, reproduced before fixing. The latency harness keeps its own committed `Cargo.lock`, separate from the workspace lockfile, and the crate consolidation that replaced `ironclaw_scripts` + `ironclaw_process_sandbox` with `ironclaw_sandbox` never regenerated it. It still carried entries for both removed packages (lines 3244 and 3602) and the old host-runtime/loop-host dependency graphs. Reproduced exactly as reported: $ cargo metadata --locked --manifest-path harness/latency/runner/Cargo.toml error: cannot update the lock file ... because --locked was passed exit 101 so any reproducible invocation of the harness was broken, while the documented unlocked command silently rewrote the lockfile as a side effect of running. Regenerated with `cargo update --workspace`, which re-resolves the path dependencies. Verified after: `--locked` exits 0, the two removed packages are gone (0 entries), and `ironclaw_sandbox` is present (1 entry). Note: the re-resolve also carried three registry deps forward (wasmtime-wasi 46.0.1 -> 47.0.3, wasmtime-wasi-io likewise, wit-parser 0.251.0 -> 0.252.0). That is contained — this lockfile governs only the standalone benchmark harness and is not the workspace lockfile, and it was already unusable under `--locked` before this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> …
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The second and final content batch for Waves 0–4 — stacked on #7170 (merge order: #7170 first; this PR's diff collapses to its own work when #7170 lands). Assembled per the program's consolidation protocol; all three initial components merged with zero conflicts (the fleet's minimal-edit discipline working as designed).
Components so far
ironclaw_extension_supporttoruntimes(the activation-shed premise was measured false — twousesites, and the designed executor seam makes the kernel a legitimate consumer; prominently flagged as an owner-reviewable layer amendment, one-line revert). Ratchet rewritten tautology-free at zero; sabotage-tested; DowngradePin added.[product_adapter.*]schema toextension_contracts, the resolved projection to the registry; product-reference ledger 9 → 6; a real guidance-vs-code contradiction resolved in the code's favor (the un-enforced forbidden-dep now enforced); four measured-dead error variants deleted.Still folding in (agents in flight, same branch)
Extension-host residue (+ the coordinator's one-line
extension_host → loopsflip once it lands — closing Wave 2's last row), WS6 policy evictions + theproduct → loop_hostsever, WS6 service evictions, RebornRuntime slimming + typedExtensionId, and the module charters. Each fold-in re-verifies the full bar and updates this body.Verification (assembled tip
9e2aa183eb)Full
ironclaw_architecturesuite: 37 binaries, zero failures (register 0/0 confirmed) · clippy-D warnings --all-targets --all-featureson the arch crate clean ·cargo fmt --all --checkclean ·cargo check --workspace --all-targetsclean. Each component's own full verification is in its source PR (#7175/#7174/#7172, left open until this merges).🤖 Generated with Claude Code