Repository navigation
Narrow ironclaw_common and shed the product→runner edge (WS1.6 + WS1.7) - #6982
Conversation
…ire the turns shims (WS1.1) `ironclaw_host_api::turn` becomes the complete canonical turn vocabulary: it absorbs `TurnStatus` (with the inseparable `GateKind`/`BlockedReason` gate correspondence), `EventCursor`, and `RunOriginAdapter`. The three `ironclaw_turns` re-export shims named by CHECKLIST WS1.1 are deleted — `src/ids.rs`, `src/scope.rs`, and the whole `src/product_adapter/` module, whose `fakes.rs` moves beside the traits it implements in `host_api::product_adapter::test_support`. `ids.rs` carried `pub type GateRef = TurnGateRef`: a second name for a host_api type that collided with the unrelated `ironclaw_host_api::ids::GateRef` (an opaque uuid GateRecord key, versus turns' bounded `gate:`-prefixed routing string). The alias is retired rather than relocated, so the workspace now has exactly one `GateRef`. The six vocabulary-only consumers — auth, event_streams, outbound, telegram_extension, triggers, event_projections — import from `ironclaw_host_api::turn` and drop their `ironclaw_turns` dependency entirely. Five `*→turns` LAYER_MATRIX_EXCEPTIONS are therefore not waived but obsolete: the edges no longer exist. The §11.2.2 ratchet baseline moves 20 → 15. No behavior change. `RunOriginAdapter`'s validation error becomes `Result<_, String>` (matching every other bounded ref in `host_api::turn`) with a byte-identical message pinned by a test, so both production `e.to_string()` call sites are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WS1.1's box is ticked with what the change actually landed, including the three lead-sheet corrections it turned up: the row named `TurnStatus` but not `EventCursor`/`RunOriginAdapter` (which the six consumers genuinely needed), `GateKind`/`BlockedReason` could not be left behind without duplicating the single `GateKind -> TurnStatus` match table, and deleting `ids.rs` forced retiring its `GateRef` alias rather than relocating it. Two decisions confirmed outside the doc and never recorded: - Strategy B (family dirs + focused crates) — confirmed 2026-07-31 by the owner, recorded retroactively; it was made in practice at program start. - The `tools/` row's `default-members` trim — resolved as no trim. Also surfaces #6963 on the WS0 blocking-prerequisite row's first line (it was already cited mid-paragraph) and records the §11.2.2 exception ratchet moving 20 -> 15. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…TurnGateRef contract CodeRabbit review round on #6967. Import repoints (accepted): every `use` line this PR already rewrote now names `ironclaw_host_api::turn` directly instead of routing through `ironclaw_turns`' prelude — 57 files across extension_host, product, composition, runner, loop_host, conversations, the integration harness, and the stress tool, plus three inside `ironclaw_turns` itself so the crate stops consuming its own facade. Import lines this PR did not touch are left for their consumer's own repoint slot. TurnGateRef contract pinned (refutation): two review comments claimed `TurnGateRef::new` only accepts `gate:approval-`/`gate:auth-` prefixes and that fixtures like "gate-alpha" and "stress-gate:{run_id}" fail construction. They do not — `TurnGateRef` is `bounded_ref!` (non-empty, <= 256 bytes, no control characters); `LoopGateRef` is the prefix- validated family via `loop_ref!(.., "gate:")`. The misreading traces to this PR's own AGENTS.md wording ("bounded `gate:`-prefixed routing string"), which stated a minting convention as if it were validation. That wording is corrected and the distinction is now pinned by a test. Also: drop a stale cross-file line reference in a product test comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `tools/` row resolved the `default-members` trim as "no trim" but kept a trailing "The `tools/`/`default-members` half is still open." from before that decision, so the row asserted both states. Drop the stale sentence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Finishes the remediation deferred last round, now that the settled run (job 91246493989) provides authoritative line numbers. Manifest-only — no .rs file changes, so the changed-line set the gate computes is unchanged and these numbers stay valid for the next run. Derived, not transcribed: the gate was replayed locally against its own merged lcov from that run, reproducing CI's failure byte-identically first (95.17%, 138/145, same 13 files, same 7 lines), then re-run after each entry. Final local result: 100.00% (138/138), branch 100% (4/4), exit 0. All 45 gate self-tests pass. Two classes, both verified rather than asserted: - 13 files x 20 lines - declaration lines (fn params, return types, struct fields) whose only edit is the type NAME: GateRef -> TurnGateRef, or ironclaw_turns::X -> ironclaw_host_api::turn::X. Declarations are not executable, so these files contribute a zero denominator and trip the fail-closed empty_denominator branch. - 7 lines x 3 files - executable, instrumented, and genuinely not exercised by the integration tier. Each checked against the base merged lcov (main @ 67088a4, the PR's own base sha): identical 0 hits before and after, so no coverage was lost. approval_prompt_ context_view is uncovered across its whole signature at base (lines 505-511); the background spawn-mode arm and the invalid-gate- ref error path likewise. This includes the two entries I refused to guess last round - turn_events.rs (three identical candidate lines by text; the settled run disambiguates it as 510) and await_edge/store.rs (no verbatim twin after the repoint; authoritatively 268-272). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oop (WS1.2) Carve the loop tier's neutral contracts out of the turn kernel into a new contracts-layer crate per PROPOSAL 6.1.4, and repoint every consumer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…new crate Enforcement, CI registration, and guidance for the WS1.2 extraction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he two crates The claim types moved to ironclaw_loop_contracts with WS1.2; the validator policy and the trusted applier stayed in the turn kernel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two HostManagedLoop*Port impls stayed in ironclaw_turns, so same-crate links to them no longer resolve; the TurnRunId link target became redundant when the import repoint fully qualified it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removing the ironclaw_runner re-export (required by the new port-location scan) left two workspace-root test-support files importing the turn kernel's evidence types through it. They now import from ironclaw_turns::loop_exit directly, which is the single sanctioned path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#6967 landed as a squash, so this branch carries the parent's original commits while main carries their collapsed equivalent. Merging reconciles the two shapes; the result must be main plus exactly the WS1.2 delta. # Conflicts: # crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs # crates/ironclaw_host_api/src/turn.rs # crates/ironclaw_host_runtime/tests/memory_prompt_context.rs # crates/ironclaw_loop_contracts/src/host/checkpoint.rs # crates/ironclaw_loop_contracts/src/memory_context.rs # crates/ironclaw_loop_contracts/tests/memory_prompt_context_service.rs # crates/ironclaw_loop_host/src/subagent_spawn_port.rs # crates/ironclaw_product/src/communication_context.rs # crates/ironclaw_product/src/projection/tests.rs # crates/ironclaw_product/src/projection/turn_events.rs # crates/ironclaw_product/src/reborn_services/types.rs # crates/ironclaw_reborn_composition/src/llm_admin/openai_compat_serve.rs # crates/ironclaw_reborn_composition/src/runtime.rs # crates/ironclaw_reborn_composition/src/runtime/capability_host/tests.rs # crates/ironclaw_reborn_composition/src/runtime/tests/core.rs # crates/ironclaw_runner/src/loop_exit_applier/tests/mod.rs # crates/ironclaw_runner/src/loop_exit_applier/tests/support.rs # crates/ironclaw_runner/src/subagent/await_edge/resolver.rs # crates/ironclaw_turns/src/agent_turn_runtime.rs # crates/ironclaw_turns/src/coordinator.rs # crates/ironclaw_turns/src/lib.rs # crates/ironclaw_turns/src/loop_exit.rs # crates/ironclaw_turns/src/loop_exit/tests/mod.rs # crates/ironclaw_turns/src/origin.rs # crates/ironclaw_turns/src/process_projection/runtime.rs # crates/ironclaw_turns/src/process_projection/tests.rs # crates/ironclaw_turns/src/request.rs # crates/ironclaw_turns/src/status.rs # crates/ironclaw_turns/tests/agent_loop_host_contract.rs # docs/reborn/target-architecture/CHECKLIST.md # tests/integration/support/comm_context.rs # tests/integration/support/harness/mod.rs # tests/integration/support/harness/recorder.rs # tests/integration/support/triggered_submit.rs # tests/support/reborn_parity_qa/binary_e2e.rs # tools/ironclaw_stress/src/user_turn.rs
…scan Two artifacts of collapsing onto main: - Cargo.lock pinned thiserror 2.0.18 for the new ironclaw_loop_contracts entry while main's dependency bump moved the workspace to 2.0.19. The auto-merge kept the stale pin because the bump predates the crate, so --locked builds failed. - ironclaw_product's failure-summary test reaches into another crate's source with include_str! and scans it for 'impl LoopFailureKind'. WS1.2 moved that impl to ironclaw_loop_contracts, so the include still resolved and matched nothing. Repointed to follow the code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he dual import paths (WS1.3) Carve the extension tier's neutral contracts out of the host API into a new contracts-layer crate per PROPOSAL 6.1.2, repoint every consumer, and pin the boundary with the 11.2.3 purity allowlist and the 11.2.4 location scan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
scripts/reborn-e2e-rust.sh pins exact test names for the deterministic gate. The capability-failure rehydration test moved from ironclaw_turns::run_profile::host::capability to ironclaw_loop_contracts::host::capability, so its selector matched zero tests and the gate failed closed. Swept all 10 pinned selectors in that script (4 lib + 6 integration target); this was the only stale one. Each now resolves to exactly one test, verified by running the selector. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The extension_contracts lock entry was generated before the parent branch collapsed against main, so it pinned thiserror 2.0.18 and toml 1.1.2 — versions that no longer have [[package]] blocks. --locked lanes would have failed to resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…merate deps The composition pub-use snapshot still carried product's PreferenceTargetCodec re-export, and the CLI's exact-dependency allowlist did not know the extension tier's contracts crate. Both are enumerating gates, so both failed loudly rather than passing vacuously. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The visibility kit's sealed-strategy template exists to close a strategy set; every trait here exists to be implemented outside the crate. State that, so the absence reads as a decision rather than an omission. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The changed-coverage manifest carried a WS1.1 exemption for crates/ironclaw_turns/src/run_profile/runtime_context.rs:575-576. WS1.2 moved that file into ironclaw_loop_contracts, so the gate's fail-closed path validator rejected the manifest before reaching its line-level verdict. Deleted rather than repointed: WS1.1 merged, so those lines are baseline on main, and this PR's diff pairs the file as a 99%-similarity rename whose only changed lines are imports. Repointing would re-exempt lines the gate no longer flags. All 19 remaining entries verified to resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tions Merge brings in tests/e2e/scenarios/test_reborn_webui_v2_custom_mcp.py, added on main after the last merge-down; the WebUI-smoke and E2E roll-up reds were purely the missing file. Exemptions derived by replaying scripts/ci/reborn_changed_coverage.py against this PR's own merged lcov artifact until it exits 0 (100% line 244/244, 100% branch 8/8) - never estimated. Three classes: - type-path repoints on declaration/expression fragments; - verbatim-moved bodies in the new crate. Explicitly NOT counter-attribution: those files are instrumented in this lcov and partially hit (loop_exit 195/109, model 86/52, checkpoint_payload 32/16), which proves the crate is measured. The same bodies were equally unexercised by the integration tier before the move, when they sat in ironclaw_turns and simply were not changed lines; - one crate-root inner attribute the uninstrumentable-line classifier does not recognise on a declaration-only facade. Also adds the #6524 declaration-only facade entry for the new crate's lib.rs to the informational per-crate coverage summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve the eight content conflicts and relocate host_api::hosted_mcp into ironclaw_extension_contracts: it and package_lifecycle reference each other, so once package_lifecycle moved, leaving hosted_mcp behind would have required host_api to depend on a workspace crate. That cycle produces no git conflict -- the two sides are different files -- so it is recorded in the crate guide, the checklist row, and the location scan's frozen names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…WS1.3 adapter half (WS1.4) Carve the product tier's neutral contracts out of `ironclaw_host_api` into `crates/ironclaw_product_contracts`, and — in the same change, because it is what unblocks them — move `ChannelAdapter`/`ToolAdapter`/`RestrictedEgress` into `ironclaw_extension_contracts` where PROPOSAL §6.1.2 assigns them. `host_api::product_adapter` is one connected component: `channel_adapter` names `inbound::ProductTriggerReason`, `inbound` names both `channel_adapter::ChannelAttachmentRef` and `outbound::ProjectionCursor`, `projection` names inbound and outbound, `interaction_commands` names inbound, and `product_surface` names all of them. Since `ironclaw_host_api` may hold no internal dependency, the adapters could only leave once nothing that stays behind names them — so the product DTO modules moved with them. `git mv` moved 15 modules at 83-100% similarity, so the diff reads as a move. Regression coverage: `reborn_product_contract_location_scan.rs` (7 tests, 2 real gates + 5 self-tests with positive and negative fixtures) pins one home and one import path for the product tier's ports; it fails on the four re-export chains this change deletes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…1/product-contracts # Conflicts: # crates/ironclaw_extension_contracts/CLAUDE.md # crates/ironclaw_extension_contracts/src/lib.rs # crates/ironclaw_extension_host/src/extension_lifecycle_command.rs # crates/ironclaw_extension_host/src/product_lifecycle.rs
…S1.4 row The WS1.4 ruling on package_lifecycle survived the parent's #6930 reconciliation; the LifecyclePackageId split is the recorded resolution, now stated as what happened rather than what was recommended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ss grants (WS1.5) CHECKLIST WS1's evidence-mint row: one sealed seam owns construction of protocol-auth evidence, every other construction path is deleted, and the refute-tests land with it. The `host-auth-mint` cargo feature was not a seal. Cargo unifies features across the packages selected in one invocation, so `ironclaw_webui`'s opt-in (-> turns -> host_api) compiled `ironclaw_host_api` once with the gate ON for every other crate in the same build. Measured before touching anything: a probe in `ironclaw_agent_loop` — whose manifest names `ironclaw_host_api` with no features — minted a verified bearer claim; it failed to compile alone and passed as soon as `ironclaw_webui` joined the `cargo test`. Every workspace build is the second case. Replaced with the repo's existing witness-token idiom (`host_api::authorized`), which no other crate's manifest can switch on: - `HostAuthenticationGrant` <- `HostProtocolAuthenticator`, sole implementor `ironclaw_webui` (module-private `AuthLayerState`, trust stage T1). - `VerifiedInboundGrant` <- `ChannelIngressVerifier`, sole implementor `ironclaw_extension_host` (`VerifiedEvidenceMint`, trust stage T2). - Channel/webhook mint family -> `ironclaw_extension_contracts::verified_inbound` (§6.1.2); bearer/session family stays in `host_api` (§6.1.1). The evidence type does not move; `extension_contracts` reaches the private verified variant through the grant-gated `ProtocolAuthEvidence::seal_verified_inbound`. Closed: the feature in 5 manifests + 1 CI recipe; four re-export chains (`host_api::product_adapter`, `ironclaw_product` root, `ironclaw_product::auth`, and composition's zero-consumer re-export). Enforcement: `reborn_sealed_evidence_mint_ratchet` (10 tests, §11.2.5) plus `host_api/tests/protocol_auth_evidence_seal.rs` (5) and `extension_contracts/tests/verified_inbound_seal.rs` (4). +19 tests, 0 removed, no surviving assertion edited. The production-struct dead-code baseline shrinks 81/282 -> 80/277: the five `dead_code` suppressions in `auth.rs` existed only because the constructors were feature-gated.
`ironclaw_common` now matches PROPOSAL §6.1.5's *Owns* list exactly, plus three named exceptions that a pinned rule blocks from moving. Deleted, not relocated — each re-verified with `git grep` over tracked files on this base: * `event.rs` (1,234 lines). All seven exported symbols have zero consumers outside the crate; the only live workspace references are negative ones (the architecture test asserting the OpenAI-compatible routes must *not* stream `AppEvent`, one doc comment, one Python docstring). This confirms WS1.4's disposition 7 rather than repeating it, and closes the WS8 deletion-candidate row. The `AppEvent` `ForbiddenUse` entry stays as a reintroduction pin with its reason updated to say the enum is gone — the convention the file already applies to the retired v1 `ironclaw::` crate. * `platform.rs`. `PlatformInfo` has zero references anywhere; §6.1.5's "→ its consumer" is unsatisfiable because there is no consumer. * The four budget constants. Zero consumers workspace-wide, and `ironclaw_resources` already governs the same concern with a live, differently-shaped mechanism (`ResourceLimits::max_wall_clock_ms`) that references none of them — so "→ `resources`" would have seeded four unreachable constants beside a working governor. Moved: `automation` → `ironclaw_triggers`, which owns automations and had already defined `MAX_TRIGGER_NAME_BYTES` as an alias of the common constant. This eviction is in §6.1.5 but missing from the CHECKLIST row. `ironclaw_product`'s cross-crate `pub use` of the newtype (a second import path with zero external consumers) is deleted rather than re-sourced, following the WS1.3/WS1.4 dual-path rule. Already done, so recorded rather than redone: `trust_boundary` went with #6943, and the `AttachmentRef` collision is already resolved as `ChannelAttachmentRef`. That rename left a wrong cross-reference in its own doc comment (`ironclaw_common::ChannelAttachmentRef` for `ironclaw_common::AttachmentRef`), fixed here. Not moved, with evidence: `provider_transcript` (its `agent_loop` consumer is contracts-only by pinned rule; `ironclaw_llm` is substrates), `model_selection` (`openai_compat`'s boundary rule forbids `ironclaw_llm` outright, and `llm` never uses the module), and `llm_costs` (`llm` uses 2 of its 7 public items; moving it would hand `ironclaw_product` a reqwest/rig-core cone for a pricing table — the `ModelCostTable` port is the real seam, and that is a WS4 design change). All three are documented in `crates/ironclaw_common/AGENTS.md`, which also stops claiming ownership of the long-deleted `trust_boundary`. Un-masking: `ironclaw_common` 123 → 110 tests (10 `event::tests::*`, each classified to a deleted symbol; 3 `automation::tests::*` reappearing verbatim in `ironclaw_triggers`, 166 → 169). Zero unclassified, no surviving test edited. The extension-specificity gate demanded its now-stale `platform.rs` carve-out be deleted — the property it was built with. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🚅 Deployed to the ironclaw-pr-6982 environment in ironclaw-ci-preview
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughChangesCommon crate boundary
Failure-summary ownership
Automation-name ownership
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Product
participant HostApi
participant Runner
participant CLI
Product->>HostApi: resolve failure category and detail
Runner->>HostApi: validate checkpoint rejection detail
HostApi-->>Product: return sanitized failure summary
CLI->>HostApi: resolve failed-reply summary
HostApi-->>CLI: return category summary
Possibly related issues
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔎 Review · PR #6982
Submitted review →Reviewed the complete trusted base-to-head comparison. No concrete correctness, security, architectural, or test-coverage defects were found. The failure-category and summary relocation preserves producer classification, summary mappings, checkpoint-envelope validation, and product projection behavior; deleted common types/constants had no live consumers; automation ownership and prompt/import rewrites were consistently applied. Automatic · PR opened · attempt 1 of 3 · completed in 1m 34s Run details
|
There was a problem hiding this comment.
🔍 Review complete · PR #6982
✅ No actionable findings
Reviewed the complete trusted base-to-head comparison. No concrete correctness, security, architectural, or test-coverage defects were found. The failure-category and summary relocation preserves producer classification, summary mappings, checkpoint-envelope validation, and product projection behavior; deleted common types/constants had no live consumers; automation ownership and prompt/import rewrites were consistently applied.
Validation and technical details
- Inspected all 41 changed files across refs/ironloop/base (889b3a1) to refs/ironloop/head (f9e74c9), including surrounding call sites and crate-local guidance.
- Verified base-tree consumers for deleted AppEvent/platform/budget exports; no live code consumers were found outside the deleted definitions. Remaining AppEvent mentions are documentation or architecture reintroduction pins.
- Compared the moved failure category constants and summary functions with their base runner implementations, including model-invalid-output detail mapping and pinned category summaries.
- Validated checkpoint-rejection writer/reader compatibility across the four checkpoint stages and confirmed LoopSafeSummary delegates to the same canonical SafeSummary validator; its sole sentinel bypass currently passes canonical validation.
- Checked automation type relocation, product/CLI/composition dependency rewrites, prompt asset ownership, architecture scanner exclusions, and the remaining small import/path changes.
- git diff --check refs/ironloop/base..refs/ironloop/head completed cleanly.
- Focused Cargo tests could not be rerun because cargo is unavailable in the review environment (
cargo: command not found); test coverage and source assertions were inspected directly. - Base:
ws1/evidence-mint - Head:
ws1/wave-closeatf9e74c9 - Run:
16d661ad-6be8-4a21-b31a-a5fa82a09207
# Conflicts: # crates/AGENTS.md # crates/ironclaw_architecture/tests/reborn_extension_contract_location_scan.rs # crates/ironclaw_architecture/tests/reborn_loop_port_location_scan.rs # crates/ironclaw_architecture/tests/reborn_product_contract_location_scan.rs # crates/ironclaw_extension_contracts/CLAUDE.md # crates/ironclaw_extension_contracts/src/auth_prompt.rs # crates/ironclaw_extension_contracts/src/lifecycle_id.rs # crates/ironclaw_product/src/lib.rs # crates/ironclaw_product/src/projection/tests/failure_explanation.rs # crates/ironclaw_product/tests/reborn_services_contract.rs # crates/ironclaw_runner/src/failure_summary.rs # crates/ironclaw_runner/src/planned_driver.rs # docs/reborn/target-architecture/CHECKLIST.md # tests/integration/changed-coverage-exemptions.toml
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ironclaw_host_api/src/failure/categories.rs`:
- Around line 1-21: The documentation references the nonexistent producer crate
name `ironclaw_turn_runner`; replace it with `ironclaw_runner` in
`crates/ironclaw_host_api/src/failure/categories.rs` lines 1-21,
`crates/ironclaw_host_api/src/failure.rs` lines 22-33, and
`crates/ironclaw_host_api/src/failure/summary.rs` lines 1-21, including the
module-qualified reference in the summary documentation. No other changes are
required.
In `@crates/ironclaw_host_api/src/failure/summary.rs`:
- Around line 82-90: Remove the unreachable CHECKPOINT_REJECTED_CATEGORY arm
from the match in reborn_failure_summary_for_category. Keep
pinned_failure_summary_for_category as the sole mapping for that category and
preserve all other category mappings unchanged.
In `@crates/ironclaw_triggers/src/automation.rs`:
- Around line 78-104: Add coverage in the existing automation_name tests for
serialized output and UTF-8 byte boundaries: serialize a valid AutomationName
with serde_json and assert the expected JSON string, then exercise a multibyte
name at the allowed MAX_AUTOMATION_NAME_BYTES limit and one byte beyond it,
preserving the expected acceptance and TooLong rejection.
- Around line 23-32: Canonicalize trigger names before validation and
persistence in create_trigger: construct an AutomationName from input.name,
extract its canonical value with into_inner(), and store that value in the
TriggerRecord before calling validate() and upsert_trigger. This ensures trimmed
names are persisted and length checks apply to the canonical value; leave the
existing rename flow unchanged.
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 146: Update the checklist entry’s executed deletion wording to state that
ironclaw_common/src/event.rs was deleted, removing the contradictory claim that
it remains in common as a deletion candidate. Preserve the surrounding evidence
and WS1 cross-reference.
In `@docs/reborn/target-architecture/PLAN.md`:
- Line 37: Update the Wave 1 closure sentence in the plan to replace the
inaccurate “two product edges” claim with the completed product → runner
migration and the product-owned prompt move. Keep product → loop_host explicitly
open, consistent with CHECKLIST.md.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 91f22109-6dfa-4125-8eef-687a4721eedf
📒 Files selected for processing (41)
crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rscrates/ironclaw_architecture/tests/reborn_extension_specificity.rscrates/ironclaw_architecture/tests/reborn_loop_port_location_scan.rscrates/ironclaw_common/AGENTS.mdcrates/ironclaw_common/src/event.rscrates/ironclaw_common/src/lib.rscrates/ironclaw_common/src/platform.rscrates/ironclaw_extension_contracts/src/channel_adapter.rscrates/ironclaw_host_api/src/failure.rscrates/ironclaw_host_api/src/failure/categories.rscrates/ironclaw_host_api/src/failure/summary.rscrates/ironclaw_loop_host/src/lib.rscrates/ironclaw_product/Cargo.tomlcrates/ironclaw_product/prompts/failure_explanation.mdcrates/ironclaw_product/src/automation_product_service.rscrates/ironclaw_product/src/automation_product_service/tests/mutation_tests.rscrates/ironclaw_product/src/lib.rscrates/ironclaw_product/src/projection/tests/failure_explanation.rscrates/ironclaw_product/src/projection/turn_events.rscrates/ironclaw_product/src/reborn_services.rscrates/ironclaw_product/tests/reborn_services_contract.rscrates/ironclaw_reborn_cli/src/runtime/mod.rscrates/ironclaw_reborn_composition/src/lib.rscrates/ironclaw_runner/src/failure_categories.rscrates/ironclaw_runner/src/failure_lane.rscrates/ironclaw_runner/src/failure_summary.rscrates/ironclaw_runner/src/lib.rscrates/ironclaw_runner/src/model_failure_mapping.rscrates/ironclaw_runner/src/planned_driver.rscrates/ironclaw_runner/src/retry_disposition.rscrates/ironclaw_runner/src/text_loop_driver.rscrates/ironclaw_runner/src/turn_runner.rscrates/ironclaw_runner/src/turn_scheduler.rscrates/ironclaw_triggers/src/automation.rscrates/ironclaw_triggers/src/lib.rscrates/ironclaw_triggers/src/libsql.rscrates/ironclaw_triggers/src/postgres.rscrates/ironclaw_triggers/tests/repository_contract.rsdocs/plans/composition-pubuse.snapshotdocs/reborn/target-architecture/CHECKLIST.mddocs/reborn/target-architecture/PLAN.md
💤 Files with no reviewable changes (7)
- crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
- docs/plans/composition-pubuse.snapshot
- crates/ironclaw_common/src/platform.rs
- crates/ironclaw_loop_host/src/lib.rs
- crates/ironclaw_product/src/lib.rs
- crates/ironclaw_common/src/event.rs
- crates/ironclaw_reborn_composition/src/lib.rs
| //! Failure-category identifiers — the closed key vocabulary of the | ||
| //! category→summary tables in [`super::summary`]. | ||
| //! | ||
| //! These are pure data: stable `&'static str` identifiers that producers stamp | ||
| //! onto a failed run and that projections key user-facing copy from. They live | ||
| //! here, not in the crate that produces them, because both sides of that | ||
| //! relationship are above this crate — the producer (`ironclaw_turn_runner`) | ||
| //! and the consumer (the product projection) — and a category constant is | ||
| //! kernel vocabulary, not runner implementation (PROPOSAL §6.1.1: "failure-summary | ||
| //! data … joins the existing `failure` module so product stops depending on | ||
| //! runner"). | ||
| //! | ||
| //! **Classification is not data and did not move.** The functions that decide | ||
| //! *which* category a failure gets (`host_stage_unavailable_category`, | ||
| //! `failure_lane`, the retry disposition) stay with the producer: they are typed | ||
| //! on loop/agent-loop vocabulary that this crate must never depend on. | ||
| //! | ||
| //! Adding a constant here obliges a matching arm in [`super::summary`]; the | ||
| //! product projection's `failure_summary_covers_reborn_failure_category_constants` | ||
| //! scans this file and fails when the two drift. | ||
|
|
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Fix the stale ironclaw_turn_runner crate name in the new doc comments.
Three new doc blocks name the producer crate ironclaw_turn_runner. Every other file in this cohort — crates/ironclaw_runner/src/planned_driver.rs, crates/ironclaw_runner/src/failure_categories.rs, crates/ironclaw_runner/src/failure_summary.rs, and crates/ironclaw_product/Cargo.toml — names the actual producer crate ironclaw_runner. ironclaw_turn_runner does not appear anywhere else in the reviewed cohort.
Update the doc text to name the real crate, so future readers and the "search Markdown/doc references for stale paths" step after a move do not chase a nonexistent crate.
crates/ironclaw_host_api/src/failure/categories.rs#L1-L21: replace "the producer (ironclaw_turn_runner)" and the repeated mention in the classification paragraph withironclaw_runner.crates/ironclaw_host_api/src/failure.rs#L22-L33: replace "moved here fromironclaw_turn_runner" withironclaw_runner.crates/ironclaw_host_api/src/failure/summary.rs#L1-L21: replace "Moved here fromironclaw_turn_runner::failure_summary" and "stayed with the producer (ironclaw_turn_runner)" withironclaw_runner.
As per path instructions, "After moving or renaming code, search .claude/, AGENTS.md, CLAUDE.md, crates/AGENTS.md, docs/reborn/contracts/, and Markdown references for stale paths."
📍 Affects 3 files
crates/ironclaw_host_api/src/failure/categories.rs#L1-L21(this comment)crates/ironclaw_host_api/src/failure.rs#L22-L33crates/ironclaw_host_api/src/failure/summary.rs#L1-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/ironclaw_host_api/src/failure/categories.rs` around lines 1 - 21, The
documentation references the nonexistent producer crate name
`ironclaw_turn_runner`; replace it with `ironclaw_runner` in
`crates/ironclaw_host_api/src/failure/categories.rs` lines 1-21,
`crates/ironclaw_host_api/src/failure.rs` lines 22-33, and
`crates/ironclaw_host_api/src/failure/summary.rs` lines 1-21, including the
module-qualified reference in the summary documentation. No other changes are
required.
Source: Path instructions
| pub fn reborn_failure_summary_for_category(category: Option<&str>) -> &'static str { | ||
| let Some(category) = category else { | ||
| return unknown_failure_summary(); | ||
| }; | ||
|
|
||
| if let Some(summary) = pinned_failure_summary_for_category(category) { | ||
| return summary; | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Remove the unreachable CHECKPOINT_REJECTED_CATEGORY arm.
pinned_failure_summary_for_category (Lines 311-331) returns Some(CHECKPOINT_REJECTION_FALLBACK) for CHECKPOINT_REJECTED_CATEGORY before the match at Line 91 runs. The CHECKPOINT_REJECTED_CATEGORY => CHECKPOINT_REJECTION_FALLBACK arm at Line 186 never executes.
This module's own doc states the category table must stay a single source of truth: a constant should map to exactly one summary location, and drift between duplicate mappings must fail a test. This dead arm is a second, unreachable mapping for the same category. If a future edit changes only one of the two spots, the change has no effect, and the drift check does not catch it, because the live value always comes from pinned_failure_summary_for_category.
Remove the dead arm to keep one definition per category, as the module doc requires.
🧹 Proposed fix
- CHECKPOINT_REJECTED_CATEGORY => CHECKPOINT_REJECTION_FALLBACK,
- "checkpoint_unavailable" => {
+ "checkpoint_unavailable" => {Also applies to: 186-186, 311-331
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/ironclaw_host_api/src/failure/summary.rs` around lines 82 - 90, Remove
the unreachable CHECKPOINT_REJECTED_CATEGORY arm from the match in
reborn_failure_summary_for_category. Keep pinned_failure_summary_for_category as
the sole mapping for that category and preserve all other category mappings
unchanged.
| - [ ] Crate: `ironclaw_first_party_extension_ports` dissolved **after** WS3 removes the `host_runtime→first_party_extensions` edge (activation machinery → loop_host/skills; observer vocab → skills; bundle asset reader → package). | ||
| - [ ] Modules: ~~`skills::{registry,catalog,v2,gating}`~~, ~~`auth::loopback_oauth` (+`urlencoding` dep)~~, ~~`auth::fakes` gated `test-support`~~, ~~`event_projections::{EventStreamManager, PendingGateProjection, DurableMemoryAuditSink}`~~, ~~`events::{parse_jsonl, replay_jsonl}`~~, ~~`memory_native::EmbeddingProvider` + its path-shim re-exports~~, ~~`common::trust_boundary`~~ — **landed in #6943** (seven crates; +231 / −9,000); `filesystem::HsmBackend` (or feature-gate); `secrets::placeholder` (until the egress proxy is built); `host_runtime` sandbox CA moves-or-deletes with WS3; `trust::{SignedRegistry, DevTrustOverride, BundledRegistry}` **[decision — commit or delete]**; `runner::production_readiness` (see WS4); composition `product_live_adapters` exports (see WS6). | ||
| - [ ] Deletion candidate (recorded 2026-07-31 by WS1.4): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it stays in `common` as a deletion candidate rather than moving. Evidence in PR #6980's body; it also belongs to the "Narrow `ironclaw_common`" row in WS1. | ||
| - [x] ~~Deletion candidate (recorded 2026-07-31 by WS1.4)~~ — **executed by the WS1.6 PR** (see the `ironclaw_common` narrowing row in WS1 for the re-verification and un-masking): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it stays in `common` as a deletion candidate rather than moving. Evidence in PR #6980's body; it also belongs to the "Narrow `ironclaw_common`" row in WS1. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the executed deletion wording.
Line 146 marks event.rs as deleted, but then says it “stays in common as a deletion candidate.” State that the file was deleted rather than leaving a stale follow-up action.
Proposed wording
- ... it stays in `common` as a deletion candidate rather than moving.
+ ... it was deleted rather than moved.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - [x] ~~Deletion candidate (recorded 2026-07-31 by WS1.4)~~ — **executed by the WS1.6 PR** (see the `ironclaw_common` narrowing row in WS1 for the re-verification and un-masking): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it stays in `common` as a deletion candidate rather than moving. Evidence in PR #6980's body; it also belongs to the "Narrow `ironclaw_common`" row in WS1. | |
| - [x] ~~Deletion candidate (recorded 2026-07-31 by WS1.4)~~ — **executed by the WS1.6 PR** (see the `ironclaw_common` narrowing row in WS1 for the re-verification and un-masking): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it was deleted rather than moved. Evidence in PR `#6980`'s body; it also belongs to the "Narrow `ironclaw_common`" row in WS1. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reborn/target-architecture/CHECKLIST.md` at line 146, Update the
checklist entry’s executed deletion wording to state that
ironclaw_common/src/event.rs was deleted, removing the contradictory claim that
it remains in common as a deletion candidate. Preserve the surrounding evidence
and WS1 cross-reference.
| - Order inside the wave: turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence-mint consolidation ⚠ (security-sensitive; its refute-tests land in the same PR) → `common` narrowing → the two single-symbol product edges (`runner`, `loop_host`). | ||
| - Each new crate lands with its §11.2.3 allowlist + §11.2.4 port-location scan in the same PR (new-crate-adds-rule discipline). | ||
| - **Milestone:** exceptions 20 → 12 (all W4.3 + `auth→turns` gone); `agent_loop` passes contracts-only with zero exceptions; webui/openai/channel crates *can* now compile against contracts (the flips happen in Wave 2). | ||
| - ✎ **Wave 1 closed 2026-07-31** across seven PRs (turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence mint → `common` narrowing + the two product edges): all three contracts crates exist and `agent_loop` is contracts-only with zero exceptions, but **exceptions ended at 13, not 12** — `conversations → turns` is turn *admission authority*, not vocabulary, so it falls in WS5 and the 12 in this milestone was never reachable here. Two findings worth carrying: the `host-auth-mint` cargo feature was never a seal (feature unification compiled the mint gate on for the whole workspace), and every slot found its lead-sheet row partly stale — re-measure rows against your own base, never inherit a predecessor's count. Full end-state in CHECKLIST's WS1 verify row. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the Wave 1 closure scope.
Line 37 says the seven PRs completed “the two product edges.” CHECKLIST.md Line 58 states that product → loop_host remains open; only the prompt asset moved. Name the completed product → runner migration and the product-owned prompt move instead.
Proposed wording
- ... common narrowing + the two product edges):
+ ... common narrowing + the product → runner failure-summary move and the product-owned prompt asset):📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - ✎ **Wave 1 closed 2026-07-31** across seven PRs (turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence mint → `common` narrowing + the two product edges): all three contracts crates exist and `agent_loop` is contracts-only with zero exceptions, but **exceptions ended at 13, not 12** — `conversations → turns` is turn *admission authority*, not vocabulary, so it falls in WS5 and the 12 in this milestone was never reachable here. Two findings worth carrying: the `host-auth-mint` cargo feature was never a seal (feature unification compiled the mint gate on for the whole workspace), and every slot found its lead-sheet row partly stale — re-measure rows against your own base, never inherit a predecessor's count. Full end-state in CHECKLIST's WS1 verify row. | |
| - ✎ **Wave 1 closed 2026-07-31** across seven PRs (turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence mint → `common` narrowing + the product → runner failure-summary move and the product-owned prompt asset): all three contracts crates exist and `agent_loop` is contracts-only with zero exceptions, but **exceptions ended at 13, not 12** — `conversations → turns` is turn *admission authority*, not vocabulary, so it falls in WS5 and the 12 in this milestone was never reachable here. Two findings worth carrying: the `host-auth-mint` cargo feature was never a seal (feature unification compiled the mint gate on for the whole workspace), and every slot found its lead-sheet row partly stale — re-measure rows against your own base, never inherit a predecessor's count. Full end-state in CHECKLIST's WS1 verify row. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reborn/target-architecture/PLAN.md` at line 37, Update the Wave 1
closure sentence in the plan to replace the inaccurate “two product edges” claim
with the completed product → runner migration and the product-owned prompt move.
Keep product → loop_host explicitly open, consistent with CHECKLIST.md.
Source: Path instructions
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ironclaw_host_api/src/failure/categories.rs`:
- Around line 1-21: The documentation references the nonexistent producer crate
name `ironclaw_turn_runner`; replace it with `ironclaw_runner` in
`crates/ironclaw_host_api/src/failure/categories.rs` lines 1-21,
`crates/ironclaw_host_api/src/failure.rs` lines 22-33, and
`crates/ironclaw_host_api/src/failure/summary.rs` lines 1-21, including the
module-qualified reference in the summary documentation. No other changes are
required.
In `@crates/ironclaw_host_api/src/failure/summary.rs`:
- Around line 82-90: Remove the unreachable CHECKPOINT_REJECTED_CATEGORY arm
from the match in reborn_failure_summary_for_category. Keep
pinned_failure_summary_for_category as the sole mapping for that category and
preserve all other category mappings unchanged.
In `@crates/ironclaw_triggers/src/automation.rs`:
- Around line 78-104: Add coverage in the existing automation_name tests for
serialized output and UTF-8 byte boundaries: serialize a valid AutomationName
with serde_json and assert the expected JSON string, then exercise a multibyte
name at the allowed MAX_AUTOMATION_NAME_BYTES limit and one byte beyond it,
preserving the expected acceptance and TooLong rejection.
- Around line 23-32: Canonicalize trigger names before validation and
persistence in create_trigger: construct an AutomationName from input.name,
extract its canonical value with into_inner(), and store that value in the
TriggerRecord before calling validate() and upsert_trigger. This ensures trimmed
names are persisted and length checks apply to the canonical value; leave the
existing rename flow unchanged.
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 146: Update the checklist entry’s executed deletion wording to state that
ironclaw_common/src/event.rs was deleted, removing the contradictory claim that
it remains in common as a deletion candidate. Preserve the surrounding evidence
and WS1 cross-reference.
In `@docs/reborn/target-architecture/PLAN.md`:
- Line 37: Update the Wave 1 closure sentence in the plan to replace the
inaccurate “two product edges” claim with the completed product → runner
migration and the product-owned prompt move. Keep product → loop_host explicitly
open, consistent with CHECKLIST.md.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 91f22109-6dfa-4125-8eef-687a4721eedf
📒 Files selected for processing (41)
crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rscrates/ironclaw_architecture/tests/reborn_extension_specificity.rscrates/ironclaw_architecture/tests/reborn_loop_port_location_scan.rscrates/ironclaw_common/AGENTS.mdcrates/ironclaw_common/src/event.rscrates/ironclaw_common/src/lib.rscrates/ironclaw_common/src/platform.rscrates/ironclaw_extension_contracts/src/channel_adapter.rscrates/ironclaw_host_api/src/failure.rscrates/ironclaw_host_api/src/failure/categories.rscrates/ironclaw_host_api/src/failure/summary.rscrates/ironclaw_loop_host/src/lib.rscrates/ironclaw_product/Cargo.tomlcrates/ironclaw_product/prompts/failure_explanation.mdcrates/ironclaw_product/src/automation_product_service.rscrates/ironclaw_product/src/automation_product_service/tests/mutation_tests.rscrates/ironclaw_product/src/lib.rscrates/ironclaw_product/src/projection/tests/failure_explanation.rscrates/ironclaw_product/src/projection/turn_events.rscrates/ironclaw_product/src/reborn_services.rscrates/ironclaw_product/tests/reborn_services_contract.rscrates/ironclaw_reborn_cli/src/runtime/mod.rscrates/ironclaw_reborn_composition/src/lib.rscrates/ironclaw_runner/src/failure_categories.rscrates/ironclaw_runner/src/failure_lane.rscrates/ironclaw_runner/src/failure_summary.rscrates/ironclaw_runner/src/lib.rscrates/ironclaw_runner/src/model_failure_mapping.rscrates/ironclaw_runner/src/planned_driver.rscrates/ironclaw_runner/src/retry_disposition.rscrates/ironclaw_runner/src/text_loop_driver.rscrates/ironclaw_runner/src/turn_runner.rscrates/ironclaw_runner/src/turn_scheduler.rscrates/ironclaw_triggers/src/automation.rscrates/ironclaw_triggers/src/lib.rscrates/ironclaw_triggers/src/libsql.rscrates/ironclaw_triggers/src/postgres.rscrates/ironclaw_triggers/tests/repository_contract.rsdocs/plans/composition-pubuse.snapshotdocs/reborn/target-architecture/CHECKLIST.mddocs/reborn/target-architecture/PLAN.md
💤 Files with no reviewable changes (7)
- crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
- docs/plans/composition-pubuse.snapshot
- crates/ironclaw_common/src/platform.rs
- crates/ironclaw_loop_host/src/lib.rs
- crates/ironclaw_product/src/lib.rs
- crates/ironclaw_common/src/event.rs
- crates/ironclaw_reborn_composition/src/lib.rs
🛑 Comments failed to post (2)
crates/ironclaw_triggers/src/automation.rs (2)
23-32: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash set -euo pipefail rg -n -U -C 6 \ 'AutomationName::new|validate_trigger_name|TriggerRecord\s*\{|record\.name|name\.into_inner\(\)' \ crates/ironclaw_triggers crates/ironclaw_productRepository: nearai/ironclaw
Length of output: 35301
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== candidate files ==" fd -a 'automation.rs|lib.rs|postgres.rs|libsql.rs|reborn_services.rs' crates/ironclaw_triggers crates/ironclaw_product | sed 's#^\./##' echo "== automation.rs ==" sed -n '1,140p' crates/ironclaw_triggers/src/automation.rs echo "== lib.rs trigger section ==" sed -n '300,430p' crates/ironclaw_triggers/src/lib.rs echo "== postgres triggers ==" rg -n -C 8 'fn upsert_trigger|upsert.*trigger|INSERT|UPDATE|REPLACE SCOPED|rename_scoped_trigger|record\.name|validation' crates/ironclaw_triggers/src/postgres.rs crates/ironclaw_triggers/src/libsql.rs echo "== product automation mutation ==" rg -n -C 10 'AutomationMutationAction|create_|automation|AutomationName::new|Request.*name|reborn_services.rs|product_service.rs' crates/ironclaw_product/src/automation_product_service.rs crates/ironclaw_product/src/reborn_services.rs echo "== migrations/schema references ==" rg -n -C 5 'CREATE TABLE.*trigger|trigger.*name|name.*trigger|autos|automation|trigger_name|max_length|CHARACTER VARYING|TRIGGER' crates scripts docs --glob '*.rs' --glob '*.sql' --glob '*.md' 2>/dev/null | head -n 240Repository: nearai/ironclaw
Length of output: 50374
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== constants and AutomationName ==" sed -n '1,120p' crates/ironclaw_triggers/src/automation.rs echo "== validate_trigger_name and TriggerRecord ==" sed -n '333,405p' crates/ironclaw_triggers/src/lib.rs echo "== in_memory write sections around name ==" sed -n '120,195p' crates/ironclaw_triggers/src/in_memory.rs echo "== PostgreSQL upsert/rename/write section ==" sed -n '120,175p' crates/ironclaw_triggers/src/postgres.rs sed -n '360,405p' crates/ironclaw_triggers/src/postgres.rs sed -n '1320,1360p' crates/ironclaw_triggers/src/postgres.rs echo "== libSQL read/write sections ==" sed -n '580,635p' crates/ironclaw_triggers/src/libsql.rs sed -n '1340,1385p' crates/ironclaw_triggers/src/libsql.rs sed -n '1470,1510p' crates/ironclaw_triggers/src/libsql.rs sed -n '1680,1725p' crates/ironclaw_triggers/src/libsql.rs echo "== schema/migration mentions ==" rg -n -C 4 --glob '*.sql' --glob '*.rs' 'TRIGGER TABLE|trigger_name|CHARACTER VARYING|name.*trigger|CREATE TABLE.*trigger|MAX_TRIGGER_NAME_BYTES|MAX_AUTOMATION_NAME_BYTES' crates scripts docs 2>/dev/nullRepository: nearai/ironclaw
Length of output: 50372
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== locate trigger creation APIs ==" rg -n -C 12 'create_scoped_trigger|upsert_scoped_trigger|insert_scoped_trigger|fn .*trigger|TriggerCreate|create_trigger|create.*automation|Request.*name|automation_request|name.*AutomationName|name: Option' \ crates/ironclaw_triggers crates/ironclaw_product crates/ironclaw_host_runtime/src/first_party_tools/trigger_management.rs \ --glob '*.rs' | sed -n '1,260p' echo "== automation request DTO definitions ==" rg -n -C 8 'struct .*Automation.*Request|AutomationCreate|TriggerCreate|TriggerRecord.*name|name: String|name: Option<String>' crates --glob '*.rs' | sed -n '1,260p' echo "== deterministic bound behavior probe ==" python3 - <<'PY' from pathlib import Path text = Path('crates/ironclaw_triggers/src/automation.rs').read_text() for start in text.index('pub fn new('), text.index('fn validate('), text.index('impl TriggerRecord'): print(text[start:start+800]) PYRepository: nearai/ironclaw
Length of output: 50371
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== trigger_management create_trigger ==" sed -n '446,570p' crates/ironclaw_host_runtime/src/first_party_tools/trigger_management.rs echo "== TriggerRepository trait and upsert signature ==" grep -n "trait TriggerRepository|upsert_trigger|rename_scoped_trigger|create_scoped_trigger" crates/ironclaw_triggers/src/lib.rs crates/ironclaw_triggers/src/*.rs echo "== repository implementation signatures ==" rg -n -A 8 -B 3 'impl .*TriggerRepository|async fn upsert_trigger|async fn create_scoped_trigger|rename_scoped_trigger' crates/ironclaw_triggers crates/ironclaw_product/src/automation_product_service.rs --glob '*.rs' echo "== deterministic trim-before-validation behavior ==" python3 - <<'PY' MAX = 256 for raw in ['x' * MAX + ' ', ' ' + 'x' * MAX, ' ' + 'x' * (MAX + 1), 'x' * MAX + ' ']: trimmed = raw.strip() print({ 'raw_len': len(raw), 'trimmed_len': len(trimmed), 'raw': repr(raw), 'trimmed': repr(trimmed), 'accepts_trimmed': 0 < len(trimmed) <= MAX, 'rejected_by_trim_then_validate': not (0 < len(trimmed) <= MAX), }) PYRepository: nearai/ironclaw
Length of output: 4919
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== repo trait/impl quick list ==" rg -n "trait TriggerRepository|upsert_trigger|create_scoped_trigger|rename_scoped_trigger|impl TriggerRepository" crates/ironclaw_triggers --glob '*.rs' echo "== trigger_management validation and repo write calls ==" rg -n -C 4 'record\.validate|validate_trigger_name|upsert_trigger|remove_trigger|TriggerCreateHook|trigger_create' crates/ironclaw_host_runtime/src/first_party_tools/trigger_management.rs crates/ironclaw_product/src/automation_product_service.rs --glob '*.rs' echo "== product creation endpoint candidates ==" python3 - <<'PY' from pathlib import Path for p in Path('crates').rglob('*.rs'): txt= p.read_text(errors='ignore') if 'AutomationCreate' in txt or 'create_automation' in txt or 'TriggerRecord<' in txt or 'validate_trigger_name' in txt: print(p) PYRepository: nearai/ironclaw
Length of output: 46857
Canonicalize trigger names before all writes.
TriggerRecord::validateusesvalidate_trigger_name(&self.name), which trims inAutomationName::newbut does not updateself.name; thenupsert_triggerwritesrecord.nameunchanged.create_triggerpassesinput.nameintoTriggerRecorddirectly, so a 256-byte name plus a trailing space is accepted and persisted before the 256-bytename.len() > MAX_AUTOMATION_NAME_BYTEScheck rejects it. Store the trimmedAutomationName::new(input.name).into_inner()beforerecord.validate()/upsert_trigger. Rename already usesAutomationNameand persistsname.into_inner().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ironclaw_triggers/src/automation.rs` around lines 23 - 32, Canonicalize trigger names before validation and persistence in create_trigger: construct an AutomationName from input.name, extract its canonical value with into_inner(), and store that value in the TriggerRecord before calling validate() and upsert_trigger. This ensures trimmed names are persisted and length checks apply to the canonical value; leave the existing rename flow unchanged.Source: Path instructions
78-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Add serialization and UTF-8 boundary coverage.
The tests cover deserialization but not serialized output. The ASCII overflow case does not lock down the documented byte limit for multibyte names. Add both checks.
Proposed regression coverage
+ #[test] + fn automation_name_serializes_canonically_and_enforces_utf8_bytes() { + let name = AutomationName::new(" Daily status ").expect("valid name"); + assert_eq!( + serde_json::to_string(&name).expect("serialize name"), + "\"Daily status\"" + ); + assert!(AutomationName::new("é".repeat(128)).is_ok()); + assert_eq!( + AutomationName::new(format!("{}x", "é".repeat(128))), + Err(AutomationNameError::TooLong) + ); + }This protects the public Serde and byte-limit contract.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.#[cfg(test)] mod tests { use super::*; #[test] fn automation_name_trims_and_validates() { let name = AutomationName::new(" Daily status ").expect("valid name"); assert_eq!(name.as_str(), "Daily status"); } #[test] fn automation_name_rejects_blank_and_too_long() { assert_eq!(AutomationName::new(" "), Err(AutomationNameError::Empty)); assert_eq!( AutomationName::new("x".repeat(MAX_AUTOMATION_NAME_BYTES + 1)), Err(AutomationNameError::TooLong) ); } #[test] fn automation_name_deserializes_through_validation() { let name: AutomationName = serde_json::from_str("\" Daily status \"").expect("valid serialized automation name"); assert_eq!(name.as_str(), "Daily status"); assert!(serde_json::from_str::<AutomationName>("\" \"").is_err()); } #[test] fn automation_name_serializes_canonically_and_enforces_utf8_bytes() { let name = AutomationName::new(" Daily status ").expect("valid name"); assert_eq!( serde_json::to_string(&name).expect("serialize name"), "\"Daily status\"" ); assert!(AutomationName::new("é".repeat(128)).is_ok()); assert_eq!( AutomationName::new(format!("{}x", "é".repeat(128))), Err(AutomationNameError::TooLong) ); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ironclaw_triggers/src/automation.rs` around lines 78 - 104, Add coverage in the existing automation_name tests for serialized output and UTF-8 byte boundaries: serialize a valid AutomationName with serde_json and assert the expected JSON string, then exercise a multibyte name at the allowed MAX_AUTOMATION_NAME_BYTES limit and one byte beyond it, preserving the expected acceptance and TooLong rejection.
|
Coordinator sign-off (Wave 1 slot 6 — WS1.6
Ready to merge on settle (50 success / 0 failure at time of writing, four routine lanes finishing; if the changed-coverage gate fires on the coverage-report job, the steward lands the replay-derived closure — its preparatory analysis shows no genuine untested holes in the new summary surface, only tier mismatch). This is Wave 1's final merge. |
Coverage ratchetReborn integration-tier coverageLine coverage (Reborn crates): 85.92% — 323406 / 376404 lines Per-crate breakdown (63 crates, lowest-covered first)
This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors. Exemptions (18 entry/entries excluded from the accounting above)
|
…d it overstated Six review findings triaged against the built tree; four were real. - `families/contracts.md` landing marker claimed "everything else in this file was built as written". Three `ironclaw_loop_contracts` divergences contradict it and are now named at the marker and marked at the entry: the manifest holds `ironclaw_extension_contracts` and holds neither `ironclaw_common` nor `ironclaw_prompt_envelope`; the `tokio` carve-out; the embedded prompt asset. - The evidence-mint guarantee said "compile-time impossibility" and "enforced by constructor visibility plus a workspace string-scan pin" in one breath. Split: the compiler enforces no-mint-without-a-grant (so nothing outside a workspace crate can mint at all); an architecture test — a line-oriented substring scan with two named evasions — decides which workspace crate may hold one. - That deferral had no home. §12.1a said "hardening the scan is WS10 work, listed there"; it was not listed. Added to the WS10 guardrail row with both evasions and the call-site census that backstops them. - CHECKLIST WS8's `common/src/event.rs` row still closed with "stays in `common` as a deletion candidate" under an "executed by #6982" header. The file is deleted; the tail now says so. Plus two clarity fixes where a reader could reach a wrong number: the `(66)`/`(64)` figures in §9/§13 are that table's 2026-07-30 measurement and now say so beside the live 67, and the row-1 edge count now states that six of row 1's seven fell while the register moved by seven, because `auth → turns` is row 9. Dated amendments only; every replaced phrase is quoted in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n record with shipped reality (#6995) * docs(target-architecture): Wave 1 truth audit — reconcile the decision record with shipped reality Wave 1 merged as seven PRs (#6967, #6975, #6977, #6979, #6980, #6981, #6982). This audits the north-star docs against merged `main` at `a50ad0638` and closes every gap where the decision record no longer matches what shipped. Docs-only: five `.md` files under `docs/reborn/target-architecture/`. House style throughout — dated ✎ amendments, prior text quoted where a clause is corrected, no silent rewrites (`git diff --word-diff` removes nothing but the words each amendment quotes back). Highlights: - §8.3's exception-dissolution proof corrected: `conversations → turns` is turn admission authority, not vocabulary; the wave's "20 → 12" milestone was wrong by construction and the true end-state is 13. - §6.1.1–§6.1.4 gain as-built module inventories; the #6930 amendment placing `hosted_mcp` in `host_api` is superseded by #6977's relocation. - §12.1a records the evidence-mint finding: the `host-auth-mint` feature seal was vacuous, replaced by witness grants — plus two residuals, one from the slice and one this audit verified against the ratchet source. - The coverage-governance gap (~14k lines now ungated by the floor file's opt-in design) moves from a sign-off comment onto the ratchet row. - Duplicate-type findings, the `llm_costs`/`ModelCostTable` deferral, the surviving `product → loop_host` sites, and issues #6945/#6978 all gain owning rows. Verification: docs-only diff; `cargo test -p ironclaw_architecture` green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(target-architecture): tighten the Wave 1 audit where review found it overstated Six review findings triaged against the built tree; four were real. - `families/contracts.md` landing marker claimed "everything else in this file was built as written". Three `ironclaw_loop_contracts` divergences contradict it and are now named at the marker and marked at the entry: the manifest holds `ironclaw_extension_contracts` and holds neither `ironclaw_common` nor `ironclaw_prompt_envelope`; the `tokio` carve-out; the embedded prompt asset. - The evidence-mint guarantee said "compile-time impossibility" and "enforced by constructor visibility plus a workspace string-scan pin" in one breath. Split: the compiler enforces no-mint-without-a-grant (so nothing outside a workspace crate can mint at all); an architecture test — a line-oriented substring scan with two named evasions — decides which workspace crate may hold one. - That deferral had no home. §12.1a said "hardening the scan is WS10 work, listed there"; it was not listed. Added to the WS10 guardrail row with both evasions and the call-site census that backstops them. - CHECKLIST WS8's `common/src/event.rs` row still closed with "stays in `common` as a deletion candidate" under an "executed by #6982" header. The file is deleted; the tail now says so. Plus two clarity fixes where a reader could reach a wrong number: the `(66)`/`(64)` figures in §9/§13 are that table's 2026-07-30 measurement and now say so beside the live 67, and the row-1 edge count now states that six of row 1's seven fell while the register moved by seven, because `auth → turns` is row 9. Dated amendments only; every replaced phrase is quoted in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…7) (nearai#6982) * refactor(contracts): complete the turn vocabulary in host_api and retire the turns shims (WS1.1) `ironclaw_host_api::turn` becomes the complete canonical turn vocabulary: it absorbs `TurnStatus` (with the inseparable `GateKind`/`BlockedReason` gate correspondence), `EventCursor`, and `RunOriginAdapter`. The three `ironclaw_turns` re-export shims named by CHECKLIST WS1.1 are deleted — `src/ids.rs`, `src/scope.rs`, and the whole `src/product_adapter/` module, whose `fakes.rs` moves beside the traits it implements in `host_api::product_adapter::test_support`. `ids.rs` carried `pub type GateRef = TurnGateRef`: a second name for a host_api type that collided with the unrelated `ironclaw_host_api::ids::GateRef` (an opaque uuid GateRecord key, versus turns' bounded `gate:`-prefixed routing string). The alias is retired rather than relocated, so the workspace now has exactly one `GateRef`. The six vocabulary-only consumers — auth, event_streams, outbound, telegram_extension, triggers, event_projections — import from `ironclaw_host_api::turn` and drop their `ironclaw_turns` dependency entirely. Five `*→turns` LAYER_MATRIX_EXCEPTIONS are therefore not waived but obsolete: the edges no longer exist. The §11.2.2 ratchet baseline moves 20 → 15. No behavior change. `RunOriginAdapter`'s validation error becomes `Result<_, String>` (matching every other bounded ref in `host_api::turn`) with a byte-identical message pinned by a test, so both production `e.to_string()` call sites are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(target-architecture): tick WS1.1 and close PLAN decision round #1 WS1.1's box is ticked with what the change actually landed, including the three lead-sheet corrections it turned up: the row named `TurnStatus` but not `EventCursor`/`RunOriginAdapter` (which the six consumers genuinely needed), `GateKind`/`BlockedReason` could not be left behind without duplicating the single `GateKind -> TurnStatus` match table, and deleting `ids.rs` forced retiring its `GateRef` alias rather than relocating it. Two decisions confirmed outside the doc and never recorded: - Strategy B (family dirs + focused crates) — confirmed 2026-07-31 by the owner, recorded retroactively; it was made in practice at program start. - The `tools/` row's `default-members` trim — resolved as no trim. Also surfaces nearai#6963 on the WS0 blocking-prerequisite row's first line (it was already cited mid-paragraph) and records the §11.2.2 exception ratchet moving 20 -> 15. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(contracts): repoint touched imports to host_api and pin the TurnGateRef contract CodeRabbit review round on nearai#6967. Import repoints (accepted): every `use` line this PR already rewrote now names `ironclaw_host_api::turn` directly instead of routing through `ironclaw_turns`' prelude — 57 files across extension_host, product, composition, runner, loop_host, conversations, the integration harness, and the stress tool, plus three inside `ironclaw_turns` itself so the crate stops consuming its own facade. Import lines this PR did not touch are left for their consumer's own repoint slot. TurnGateRef contract pinned (refutation): two review comments claimed `TurnGateRef::new` only accepts `gate:approval-`/`gate:auth-` prefixes and that fixtures like "gate-alpha" and "stress-gate:{run_id}" fail construction. They do not — `TurnGateRef` is `bounded_ref!` (non-empty, <= 256 bytes, no control characters); `LoopGateRef` is the prefix- validated family via `loop_ref!(.., "gate:")`. The misreading traces to this PR's own AGENTS.md wording ("bounded `gate:`-prefixed routing string"), which stated a minting convention as if it were validation. That wording is corrected and the distinction is now pinned by a test. Also: drop a stale cross-file line reference in a product test comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(target-architecture): remove the row-97 self-contradiction The `tools/` row resolved the `default-members` trim as "no trim" but kept a trailing "The `tools/`/`default-members` half is still open." from before that decision, so the row asserted both states. Drop the stale sentence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(coverage): complete the WS1.1 changed-coverage exemptions Finishes the remediation deferred last round, now that the settled run (job 91246493989) provides authoritative line numbers. Manifest-only — no .rs file changes, so the changed-line set the gate computes is unchanged and these numbers stay valid for the next run. Derived, not transcribed: the gate was replayed locally against its own merged lcov from that run, reproducing CI's failure byte-identically first (95.17%, 138/145, same 13 files, same 7 lines), then re-run after each entry. Final local result: 100.00% (138/138), branch 100% (4/4), exit 0. All 45 gate self-tests pass. Two classes, both verified rather than asserted: - 13 files x 20 lines - declaration lines (fn params, return types, struct fields) whose only edit is the type NAME: GateRef -> TurnGateRef, or ironclaw_turns::X -> ironclaw_host_api::turn::X. Declarations are not executable, so these files contribute a zero denominator and trip the fail-closed empty_denominator branch. - 7 lines x 3 files - executable, instrumented, and genuinely not exercised by the integration tier. Each checked against the base merged lcov (main @ 67088a4, the PR's own base sha): identical 0 hits before and after, so no coverage was lost. approval_prompt_ context_view is uncovered across its whole signature at base (lines 505-511); the background spawn-mode arm and the invalid-gate- ref error path likewise. This includes the two entries I refused to guess last round - turn_events.rs (three identical candidate lines by text; the settled run disambiguates it as 510) and await_edge/store.rs (no verbatim twin after the repoint; authoritatively 268-272). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(contracts): extract ironclaw_loop_contracts and flip agent_loop (WS1.2) Carve the loop tier's neutral contracts out of the turn kernel into a new contracts-layer crate per PROPOSAL 6.1.4, and repoint every consumer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(contracts): pin the loop-contract boundary and register the new crate Enforcement, CI registration, and guidance for the WS1.2 extraction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(reborn): split the loop-exit contract's ownership claim across the two crates The claim types moved to ironclaw_loop_contracts with WS1.2; the validator policy and the trusted applier stayed in the turn kernel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(contracts): repoint the three intra-doc links the crate split broke The two HostManagedLoop*Port impls stayed in ironclaw_turns, so same-crate links to them no longer resolve; the TurnRunId link target became redundant when the import repoint fully qualified it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): repoint the loop-exit evidence imports the port rule moved Removing the ironclaw_runner re-export (required by the new port-location scan) left two workspace-root test-support files importing the turn kernel's evidence types through it. They now import from ironclaw_turns::loop_exit directly, which is the single sanctioned path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(collapse): reconcile the lock pin and the moved failure-category scan Two artifacts of collapsing onto main: - Cargo.lock pinned thiserror 2.0.18 for the new ironclaw_loop_contracts entry while main's dependency bump moved the workspace to 2.0.19. The auto-merge kept the stale pin because the bump predates the crate, so --locked builds failed. - ironclaw_product's failure-summary test reaches into another crate's source with include_str! and scans it for 'impl LoopFailureKind'. WS1.2 moved that impl to ironclaw_loop_contracts, so the include still resolved and matched nothing. Repointed to follow the code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(contracts): extract ironclaw_extension_contracts and close the dual import paths (WS1.3) Carve the extension tier's neutral contracts out of the host API into a new contracts-layer crate per PROPOSAL 6.1.2, repoint every consumer, and pin the boundary with the 11.2.3 purity allowlist and the 11.2.4 location scan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): repoint the exact-test selector WS1.2 moved scripts/reborn-e2e-rust.sh pins exact test names for the deterministic gate. The capability-failure rehydration test moved from ironclaw_turns::run_profile::host::capability to ironclaw_loop_contracts::host::capability, so its selector matched zero tests and the gate failed closed. Swept all 10 pinned selectors in that script (4 lib + 6 integration target); this was the only stale one. Each now resolves to exactly one test, verified by running the selector. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(collapse): reconcile the new crate's lock pins with main's dep bumps The extension_contracts lock entry was generated before the parent branch collapsed against main, so it pinned thiserror 2.0.18 and toml 1.1.2 — versions that no longer have [[package]] blocks. --locked lanes would have failed to resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(arch): register the new contracts crate in the two gates that enumerate deps The composition pub-use snapshot still carried product's PreferenceTargetCodec re-export, and the CLI's exact-dependency allowlist did not know the extension tier's contracts crate. Both are enumerating gates, so both failed loudly rather than passing vacuously. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(contracts): record why the extension tier's traits are not sealed The visibility kit's sealed-strategy template exists to close a strategy set; every trait here exists to be implemented outside the crate. State that, so the absence reads as a decision rather than an omission. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(coverage): drop the exemption WS1.2 made stale The changed-coverage manifest carried a WS1.1 exemption for crates/ironclaw_turns/src/run_profile/runtime_context.rs:575-576. WS1.2 moved that file into ironclaw_loop_contracts, so the gate's fail-closed path validator rejected the manifest before reaching its line-level verdict. Deleted rather than repointed: WS1.1 merged, so those lines are baseline on main, and this PR's diff pairs the file as a 99%-similarity rename whose only changed lines are imports. Repointing would re-exempt lines the gate no longer flags. All 19 remaining entries verified to resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(coverage): merge main and derive the WS1.2 changed-coverage exemptions Merge brings in tests/e2e/scenarios/test_reborn_webui_v2_custom_mcp.py, added on main after the last merge-down; the WebUI-smoke and E2E roll-up reds were purely the missing file. Exemptions derived by replaying scripts/ci/reborn_changed_coverage.py against this PR's own merged lcov artifact until it exits 0 (100% line 244/244, 100% branch 8/8) - never estimated. Three classes: - type-path repoints on declaration/expression fragments; - verbatim-moved bodies in the new crate. Explicitly NOT counter-attribution: those files are instrumented in this lcov and partially hit (loop_exit 195/109, model 86/52, checkpoint_payload 32/16), which proves the crate is measured. The same bodies were equally unexercised by the integration tier before the move, when they sat in ironclaw_turns and simply were not changed lines; - one crate-root inner attribute the uninstrumentable-line classifier does not recognise on a declaration-only facade. Also adds the nearai#6524 declaration-only facade entry for the new crate's lib.rs to the informational per-crate coverage summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(contracts): extract ironclaw_product_contracts and land the WS1.3 adapter half (WS1.4) Carve the product tier's neutral contracts out of `ironclaw_host_api` into `crates/ironclaw_product_contracts`, and — in the same change, because it is what unblocks them — move `ChannelAdapter`/`ToolAdapter`/`RestrictedEgress` into `ironclaw_extension_contracts` where PROPOSAL §6.1.2 assigns them. `host_api::product_adapter` is one connected component: `channel_adapter` names `inbound::ProductTriggerReason`, `inbound` names both `channel_adapter::ChannelAttachmentRef` and `outbound::ProjectionCursor`, `projection` names inbound and outbound, `interaction_commands` names inbound, and `product_surface` names all of them. Since `ironclaw_host_api` may hold no internal dependency, the adapters could only leave once nothing that stays behind names them — so the product DTO modules moved with them. `git mv` moved 15 modules at 83-100% similarity, so the diff reads as a move. Regression coverage: `reborn_product_contract_location_scan.rs` (7 tests, 2 real gates + 5 self-tests with positive and negative fixtures) pins one home and one import path for the product tier's ports; it fails on the four re-export chains this change deletes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(checklist): record the hosted-MCP merge-down resolution on the WS1.4 row The WS1.4 ruling on package_lifecycle survived the parent's nearai#6930 reconciliation; the LifecyclePackageId split is the recorded resolution, now stated as what happened rather than what was recommended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(contracts): consolidate sealed evidence minting behind witness grants (WS1.5) CHECKLIST WS1's evidence-mint row: one sealed seam owns construction of protocol-auth evidence, every other construction path is deleted, and the refute-tests land with it. The `host-auth-mint` cargo feature was not a seal. Cargo unifies features across the packages selected in one invocation, so `ironclaw_webui`'s opt-in (-> turns -> host_api) compiled `ironclaw_host_api` once with the gate ON for every other crate in the same build. Measured before touching anything: a probe in `ironclaw_agent_loop` — whose manifest names `ironclaw_host_api` with no features — minted a verified bearer claim; it failed to compile alone and passed as soon as `ironclaw_webui` joined the `cargo test`. Every workspace build is the second case. Replaced with the repo's existing witness-token idiom (`host_api::authorized`), which no other crate's manifest can switch on: - `HostAuthenticationGrant` <- `HostProtocolAuthenticator`, sole implementor `ironclaw_webui` (module-private `AuthLayerState`, trust stage T1). - `VerifiedInboundGrant` <- `ChannelIngressVerifier`, sole implementor `ironclaw_extension_host` (`VerifiedEvidenceMint`, trust stage T2). - Channel/webhook mint family -> `ironclaw_extension_contracts::verified_inbound` (§6.1.2); bearer/session family stays in `host_api` (§6.1.1). The evidence type does not move; `extension_contracts` reaches the private verified variant through the grant-gated `ProtocolAuthEvidence::seal_verified_inbound`. Closed: the feature in 5 manifests + 1 CI recipe; four re-export chains (`host_api::product_adapter`, `ironclaw_product` root, `ironclaw_product::auth`, and composition's zero-consumer re-export). Enforcement: `reborn_sealed_evidence_mint_ratchet` (10 tests, §11.2.5) plus `host_api/tests/protocol_auth_evidence_seal.rs` (5) and `extension_contracts/tests/verified_inbound_seal.rs` (4). +19 tests, 0 removed, no surviving assertion edited. The production-struct dead-code baseline shrinks 81/282 -> 80/277: the five `dead_code` suppressions in `auth.rs` existed only because the constructors were feature-gated. * refactor(common): narrow ironclaw_common to its §6.1.5 contract (WS1.6) `ironclaw_common` now matches PROPOSAL §6.1.5's *Owns* list exactly, plus three named exceptions that a pinned rule blocks from moving. Deleted, not relocated — each re-verified with `git grep` over tracked files on this base: * `event.rs` (1,234 lines). All seven exported symbols have zero consumers outside the crate; the only live workspace references are negative ones (the architecture test asserting the OpenAI-compatible routes must *not* stream `AppEvent`, one doc comment, one Python docstring). This confirms WS1.4's disposition 7 rather than repeating it, and closes the WS8 deletion-candidate row. The `AppEvent` `ForbiddenUse` entry stays as a reintroduction pin with its reason updated to say the enum is gone — the convention the file already applies to the retired v1 `ironclaw::` crate. * `platform.rs`. `PlatformInfo` has zero references anywhere; §6.1.5's "→ its consumer" is unsatisfiable because there is no consumer. * The four budget constants. Zero consumers workspace-wide, and `ironclaw_resources` already governs the same concern with a live, differently-shaped mechanism (`ResourceLimits::max_wall_clock_ms`) that references none of them — so "→ `resources`" would have seeded four unreachable constants beside a working governor. Moved: `automation` → `ironclaw_triggers`, which owns automations and had already defined `MAX_TRIGGER_NAME_BYTES` as an alias of the common constant. This eviction is in §6.1.5 but missing from the CHECKLIST row. `ironclaw_product`'s cross-crate `pub use` of the newtype (a second import path with zero external consumers) is deleted rather than re-sourced, following the WS1.3/WS1.4 dual-path rule. Already done, so recorded rather than redone: `trust_boundary` went with nearai#6943, and the `AttachmentRef` collision is already resolved as `ChannelAttachmentRef`. That rename left a wrong cross-reference in its own doc comment (`ironclaw_common::ChannelAttachmentRef` for `ironclaw_common::AttachmentRef`), fixed here. Not moved, with evidence: `provider_transcript` (its `agent_loop` consumer is contracts-only by pinned rule; `ironclaw_llm` is substrates), `model_selection` (`openai_compat`'s boundary rule forbids `ironclaw_llm` outright, and `llm` never uses the module), and `llm_costs` (`llm` uses 2 of its 7 public items; moving it would hand `ironclaw_product` a reqwest/rig-core cone for a pricing table — the `ModelCostTable` port is the real seam, and that is a WS4 design change). All three are documented in `crates/ironclaw_common/AGENTS.md`, which also stops claiming ownership of the long-deleted `trust_boundary`. Un-masking: `ironclaw_common` 123 → 110 tests (10 `event::tests::*`, each classified to a deleted symbol; 3 `automation::tests::*` reappearing verbatim in `ironclaw_triggers`, 166 → 169). Zero unclassified, no surviving test edited. The extension-specificity gate demanded its now-stale `platform.rs` carve-out be deleted — the property it was built with. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(host-api): move failure-summary data out of the runner (WS1.7) PROPOSAL §6.1.1 assigns the category→summary tables to `host_api::failure` "so product stops depending on runner". They are now `ironclaw_host_api::failure::{categories, summary}`, and `ironclaw_product`'s manifest no longer names `ironclaw_runner` under `[dependencies]` — the dev-dep stays and is documented, because product's harnesses legitimately build a full turn stack. The row calls these "the two single-symbol product edges". Measured on this base, neither is single-symbol: * `product → runner` was two modules — a category constant plus four `failure_summary` items. **Severed.** What could not follow, because `ironclaw_host_api` may hold no internal dependency: the envelope *writer* (typed on `agent_loop`'s `CheckpointKind` and `loop_contracts`' `LoopSafeSummary`) and every classifier. Both runner modules are now private. * `product → loop_host` has **three** production sites, and only the prompt constant was one. `FAILURE_EXPLANATION_SYSTEM_PROMPT` and its asset are now product-owned (prompt *content* is out of charter for the loop tier, §6.1.4/§6.7.2), but `project_create_capability.rs` (the nearai#6691 arrival §2.3 already flags) and — not previously recorded anywhere — `scoped_fs/attachment_landing.rs`, which consumes `LoopAttachmentReadPort`, both carry real behavior. The edge survives; severing it is WS5/WS6 work. One disposition worth review: the envelope reader moved and now revalidates its cause with `SafeSummary::new` rather than `LoopSafeSummary::new`. That is behavior-preserving, and the claim is pinned rather than asserted — `validate_loop_safe_summary` delegates to `SafeSummary::new` with exactly one bypass, the fixed `INPUT_ENCODE_HUMAN_SUMMARY` literal, which independently satisfies the canonical rule (`loop_input_encode_sentinel_needs_no_bypass_here`). Splitting writer from reader also split the checkpoint-stage vocabulary across two crates, so the pre-existing round-trip (which covered only `BeforeModel`) gains a sibling driving all four `CheckpointKind`s writer→reader across the boundary. Neither edge was ever a `LAYER_MATRIX_EXCEPTION` — `products → kernel` and `products → loops` are matrix-legal — so this cannot move the count, which stays at 13. The value is graph narrowing and prompt-content placement. Enumerating gates, all shrink-only: the composition pub-use snapshot loses exactly one line, because the `reborn_failure_summary_for_category` re-export is deleted rather than re-sourced (its sole consumer, the CLI, already depends on `ironclaw_host_api`, so the facade hop bought nothing). The product-side category-coverage `include_str!` is repointed, not added, so the §11.2.7 inventory is unchanged at 19. Also hardens `reborn_loop_port_location_scan`'s directory walk, which excluded only `target` and so descended the whole npm tree on any checkout with the frontend installed — the same defect already fixed in the sibling scanners. Un-masking: 10 table tests moved runner → host_api with identical names and no content edits (runner 467 → 458, host_api 248 → 260; deltas are the 10 moved plus 1 new round-trip and 2 new pins). loop_host and product rosters are byte-identical at 572 and 1032. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(checklist): tick WS1.6, record WS1.7's partial sever, close Wave 1 Ticks the `ironclaw_common` narrowing row with the seven dispositions that differ from how it was written (five of six clauses moved differently: two deletions where the row said relocate, one unsatisfiable "→ its consumer", two clauses already done, one eviction present in §6.1.5 but missing from the row), and the three LLM-data evictions each refuted by a pinned rule. Leaves the WS1.7 row **open** rather than ticking it. Two of three clauses landed — the data move and the `product → runner` sever — but the `product → loop_host` sever did not and cannot here: the edge has three production sites, two of them behavioral, one of which (`attachment_landing.rs`'s `LoopAttachmentReadPort`) was not recorded anywhere before. Ticking it would over-claim. Records the Wave 1 exit state on the WS1 verify row. The milestone's "20 → 12" is not met and **the 12 was wrong**: the true end-state is 13, because the 13th survivor, `conversations → turns`, is turn *admission authority* rather than vocabulary, so no contracts crate can dissolve it and it falls in WS5. The wave's other clauses did land — three contracts crates, `agent_loop` contracts-only with zero exceptions, evidence mint sealed. The mint row's measurement is carried forward as the reusable finding: the `host-auth-mint` cargo feature was never a seal, because feature unification compiled the gate on for every crate in any workspace-wide build. Adds the dated one-line Wave 1 summary to PLAN's Wave 1 section, including the discipline every slot in this wave independently confirmed: re-measure rows against your own base, never inherit a predecessor's count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…n record with shipped reality (nearai#6995) * docs(target-architecture): Wave 1 truth audit — reconcile the decision record with shipped reality Wave 1 merged as seven PRs (nearai#6967, nearai#6975, nearai#6977, nearai#6979, nearai#6980, nearai#6981, nearai#6982). This audits the north-star docs against merged `main` at `a50ad0638` and closes every gap where the decision record no longer matches what shipped. Docs-only: five `.md` files under `docs/reborn/target-architecture/`. House style throughout — dated ✎ amendments, prior text quoted where a clause is corrected, no silent rewrites (`git diff --word-diff` removes nothing but the words each amendment quotes back). Highlights: - §8.3's exception-dissolution proof corrected: `conversations → turns` is turn admission authority, not vocabulary; the wave's "20 → 12" milestone was wrong by construction and the true end-state is 13. - §6.1.1–§6.1.4 gain as-built module inventories; the nearai#6930 amendment placing `hosted_mcp` in `host_api` is superseded by nearai#6977's relocation. - §12.1a records the evidence-mint finding: the `host-auth-mint` feature seal was vacuous, replaced by witness grants — plus two residuals, one from the slice and one this audit verified against the ratchet source. - The coverage-governance gap (~14k lines now ungated by the floor file's opt-in design) moves from a sign-off comment onto the ratchet row. - Duplicate-type findings, the `llm_costs`/`ModelCostTable` deferral, the surviving `product → loop_host` sites, and issues nearai#6945/nearai#6978 all gain owning rows. Verification: docs-only diff; `cargo test -p ironclaw_architecture` green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(target-architecture): tighten the Wave 1 audit where review found it overstated Six review findings triaged against the built tree; four were real. - `families/contracts.md` landing marker claimed "everything else in this file was built as written". Three `ironclaw_loop_contracts` divergences contradict it and are now named at the marker and marked at the entry: the manifest holds `ironclaw_extension_contracts` and holds neither `ironclaw_common` nor `ironclaw_prompt_envelope`; the `tokio` carve-out; the embedded prompt asset. - The evidence-mint guarantee said "compile-time impossibility" and "enforced by constructor visibility plus a workspace string-scan pin" in one breath. Split: the compiler enforces no-mint-without-a-grant (so nothing outside a workspace crate can mint at all); an architecture test — a line-oriented substring scan with two named evasions — decides which workspace crate may hold one. - That deferral had no home. §12.1a said "hardening the scan is WS10 work, listed there"; it was not listed. Added to the WS10 guardrail row with both evasions and the call-site census that backstops them. - CHECKLIST WS8's `common/src/event.rs` row still closed with "stays in `common` as a deletion candidate" under an "executed by nearai#6982" header. The file is deleted; the tail now says so. Plus two clarity fixes where a reader could reach a wrong number: the `(66)`/`(64)` figures in §9/§13 are that table's 2026-07-30 measurement and now say so beside the live 67, and the row-1 edge count now states that six of row 1's seven fell while the register moved by seven, because `auth → turns` is row 9. Dated amendments only; every replaced phrase is quoted in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Collapsed to
main2026-07-31 — #6981 merged, so this now targetsmaindirectly. It is the last merge in Wave 1.Collapse verification: post-merge
git diff origin/mainis byte-identical to the pre-merge delta (41 files, +855/−1902), modulo exactly twoindex <blob>..<blob>lines — onCHECKLIST.mdandPLAN.md, the only two files whose base content moved under me. Content-only comparison withindexlines stripped: 3,436 lines, identical.CHECKLIST.mdline-diffed againstmain: exactly 3 of main's 191 lines are absent, and all three are the rows this PR deliberately rewrites (the WS1.6 row, the WS1.7 row, the WS8event.rsdeletion-candidate row) — all five prior slices' ticks and all three of #6979's annotations (themcphosted-MCP note, the renames-row prefix note, the WS10 quiet-gate amendment) survive verbatim.Closes Wave 1 with its last two rows: the
ironclaw_commonnarrowing (WS1.6) and the two "single-symbol" product edges (WS1.7). Neither row survived contact with the code as written — five of WS1.6's six clauses and both of WS1.7's edges measured differently on this base — so the corrections, with evidence, are most of the value here.WS1.6 —
ironclaw_commonnarrowingironclaw_commonnow matches §6.1.5's Owns list exactly, plus three named exceptions. Eviction table:event.rs(1,234 lines)product_contractsplatform.rsPlatformInfohas zero references anywhere in the tree — the assignment is unsatisfiable because there is no consumer.ironclaw_resourcesresourcesalready governs the concern with a live, differently-shaped mechanism (ResourceLimits::max_wall_clock_ms) referencing none of them.trust_boundaryAGENTS.mdstill claimed ownership — fixed.)AttachmentRefcollisionChannelAttachmentRef. The rename left a wrong cross-reference in its own doc comment (ironclaw_common::ChannelAttachmentRefwhere it meansAttachmentRef); fixed. Clearedironclaw_threads::AttachmentRefas a plain re-export, not a third collision.automationironclaw_triggers. Not in the CHECKLIST row, only in §6.1.5. Triggers is unambiguously the owner. Product's cross-cratepub useof the newtype (zero external consumers) deleted, not re-sourced.llm_costs/provider_transcript/model_selectionironclaw_llmThe three LLM-data evictions are refuted by pinned rules
This is the one substantive disagreement with §6.1.5, recorded rather than forced:
provider_transcript—ironclaw_agent_loopcallsis_only_provider_transcript_artifact_lines, and its boundary rule admits contracts-layer normal dependencies only.ironclaw_llmislayer = "substrates", so the move either breaks the zero-exception property WS1.2 just bought or needs a newLAYER_MATRIX_EXCEPTION— which the ratchet forbids.model_selection—ironclaw_reborn_openai_compat's boundary rule listsironclaw_llmas forbidden outright, andironclaw_llmhas zero uses of the module. The move would place a symbol where it is unused, to serve two crates that may not reach it.llm_costs—ironclaw_llmuses 2 of its 7 public items (4 of 7 call sites are tests). The real consumers areturn_runner,composition, andproduct(RunCost, a product wire DTO whose §6.1.3 home cannot depend onllmeither). Moving it handsironclaw_product— the crate §6.9.1 exists to narrow — a reqwest/rig-core/Bedrock cone for a pricing table. The seam already exists:ModelCostTable, already an injectable override in composition. Routing the static table behind it is a design change belonging with the WS4 sheds.All three are written into
crates/ironclaw_common/AGENTS.mdwith the measurements, so the next agent does not re-litigate them.WS1.7 — the two product edges
product → runner: SEVERED. It was two modules, not one symbol. Data moved toironclaw_host_api::failure::{categories, summary};ironclaw_product's manifest no longer namesironclaw_runnerunder[dependencies](dev-dep stays, now documented). What could not follow, becausehost_apimay hold no internal dependency: the envelope writer (typed onagent_loop'sCheckpointKind+loop_contracts'LoopSafeSummary) and every classifier. Both runner modules are now private.product → loop_host: NOT severed; prompt clause done. Three production sites, only one of which was the prompt constant.FAILURE_EXPLANATION_SYSTEM_PROMPTand its asset are now product-owned (prompt content is out of charter for the loop tier). The other two carry real behavior:project_create_capability.rs(the #6691 arrival §2.3 already flags for a Wave 4 re-shed) and — not previously recorded anywhere —scoped_fs/attachment_landing.rs, which consumesLoopAttachmentReadPort. Severing is WS5/WS6 work, so the row stays unticked.One disposition that wants a reviewer's eye
The envelope reader moved and now revalidates its cause with
SafeSummary::newinstead ofLoopSafeSummary::new. This is behavior-preserving, and the claim is pinned rather than asserted:validate_loop_safe_summarydelegates toSafeSummary::newwith exactly one bypass — the fixedINPUT_ENCODE_HUMAN_SUMMARYliteral — which independently satisfies the canonical rule, so the two validators accept the same set of causes.loop_input_encode_sentinel_needs_no_bypass_herefails if that ever stops being true. Splitting writer from reader also split the checkpoint-stage vocabulary across two crates, so the pre-existing round-trip (which covered onlyBeforeModel) gained a sibling driving all fourCheckpointKinds writer→reader across the boundary.Neither edge was ever an exception
products → kernelandproducts → loopsare matrix-legal, so this row cannot move the exception count — it stays at 13, nothing fell, nothing added. Worth stating because the wave milestone is written in exceptions.Un-masking
ironclaw_commonevent::tests::*(each classified to a deleted symbol) + 3automation::tests::*relocatedironclaw_triggersironclaw_runnerironclaw_host_apiironclaw_loop_hostironclaw_productZero unclassified. No surviving test edited for content (moved tests differ only in imports).
Enumerating gates — all shrink-only
reborn_failure_summary_for_categoryre-export is deleted, not re-sourced — its sole consumer, the CLI, already depends onironclaw_host_api, so the facade hop bought nothing.PATH_TERM_COLLISIONSlost its now-staleironclaw_common/src/platform.rscarve-out — the gate itself demanded this, failing on carve-outs that match nothing (the property it was built with).include_str!was repointed, not added.LAYER_MATRIX_EXCEPTIONS13, unchanged.AppEventForbiddenUseentry is kept as a reintroduction pin with its reason updated to say the enum is gone — the convention the file already applies to the retired v1ironclaw::root crate.Carry item
reborn_loop_port_location_scan's directory walk excluded onlytarget, so it descended the entire npm tree on any checkout with the frontend installed — the same defect already fixed in the sibling scanners. Now matches their exclusion set, with the measurement in the comment (this worktree: 516 directories undercrates/uninstalled; 23 declared frontend dependencies whose closure is what gets walked afternpm install).Wave 1 exit state
Recorded on the WS1 verify row and summarized in PLAN. The milestone's "20 → 12" is not met, and the 12 was wrong: the true end-state is 13, because the 13th survivor
conversations → turnsis turn admission authority, not vocabulary — no contracts crate dissolves it, and it falls in WS5. Everything else in the milestone landed: three contracts crates,agent_loopcontracts-only with zero exceptions, evidence mint sealed. The mint row's finding is carried forward as reusable: thehost-auth-mintcargo feature was never a seal — feature unification compiled the gate on for every crate in any workspace-wide build.Verification
cargo fmt; per-crate clippy-D warnings --all-targets --all-featureson all 10 touched crates (clean); per-crate unfiltered suites (common 110, triggers 169, host_api 260, runner 458, loop_host 572, product 1032, extension_contracts 92, composition 923 (+1 ignored), CLI 616 — all green re-run on the collapsed tree; every roster identical to pre-merge exceptextension_contracts77→92, which is main's own additions taken wholesale); full architecture suite 127/127 on the merged tree; workspacecargo check --all-targets --all-featuresclean;cargo metadata --lockedclean with an unchanged lockfile;check-include-str-paths.shOK (119 references);critical_mutation_gate.py --selection-only --allexit 0; all 10reborn-e2e-rust.shexact selectors executed under bash — each matches exactly one test.Hazards for review
SafeSummaryvsLoopSafeSummaryin the moved reader — the one place this PR changes which validator runs. Argued behavior-preserving and pinned by a test; worth a second reader given it gates user-facing failure copy.product → loop_hostsurvives on two behavioral sites, one of them (LoopAttachmentReadPortinattachment_landing.rs) previously unrecorded in any doc. WS5 should plan around it.llm_costsdeferral has a real successor task — theModelCostTableport already exists; someone should own routing the static table behind it rather than leaving the module incommonindefinitely.crates/ironclaw_reborn_openai_compat/CLAUDE.mdstill namesAppEventin a denylist of retired v1 concepts (alongsideSseManager). Left alone rather than half-auditing that list; a WS11 drift item.🤖 Generated with Claude Code