refactor(contracts): complete the turn vocabulary in host_api and retire the turns shims (WS1.1) - #6967
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>
|
🚅 Deployed to the ironclaw-pr-6967 environment in ironclaw-ci-preview
|
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR centralizes canonical turn vocabulary in ChangesCanonical turn vocabulary
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
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 #6967
Submitted review →No actionable findings. The complete trusted base-to-head comparison consistently relocates turn vocabulary into Automatic · PR opened + CI failed · attempt 1 of 3 · completed in 1m 59s Run details
|
There was a problem hiding this comment.
🔍 Review complete · PR #6967
✅ No actionable findings
No actionable findings. The complete trusted base-to-head comparison consistently relocates turn vocabulary into ironclaw_host_api, preserves serialized representations and validation limits, distinguishes routing TurnGateRef from the unrelated kernel GateRef, relocates test-support fakes without losing behavior, removes only obsolete dependency exceptions, and updates consumers and manifests coherently.
Validation and technical details
- Reviewed refs/ironloop/base (31f42b7) through refs/ironloop/head (a8e8bfa): 142 files, +1148/-1120.
- Inspected all changed-area categories: canonical vocabulary moves, GateRef rename exceptions, RunOriginAdapter error/serde behavior, shim deletions, fake relocation, consumer dependency removals, lockfile, architecture ratchet, tests, and documentation.
- Confirmed no stale live references to the removed
ironclaw_turns::{ids,scope,product_adapter}module paths orironclaw_turns::GateRef; remaining kernelGateRefuses resolve throughironclaw_host_api::ids. - Verified the six vocabulary-only consumers no longer reference
ironclaw_turns, matching their manifest and Cargo.lock dependency removals. git diff --check refs/ironloop/base..refs/ironloop/headpassed.- The repository knowledge graph was unavailable, so live-code searches and crate guidance were used as the prescribed fallback.
- Targeted Cargo tests could not be independently run because
cargois not installed in the review environment (cargo: command not found); static inspection found no defect in the changed test coverage or implementation. - Base:
main - Head:
ws1/turn-vocabularyata8e8bfa - Run:
ea1a3661-46bd-469c-b67f-79c134a99c15
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 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_extension_host/src/channel_host/e2e_tests.rs`:
- Around line 86-89: Update the imports in the e2e test around the turn
vocabulary list: import TurnGateRef, EventCursor, TurnStatus, TurnRunId, and
TurnScope from ironclaw_host_api::turn, while retaining only coordination-kernel
types under ironclaw_turns. Remove the moved shared types from the
ironclaw_turns import so the new API ownership is explicit.
In `@crates/ironclaw_extension_host/src/run_delivery_ports.rs`:
- Line 18: Update the imports and usages of TurnGateRef and TurnScope in
run_delivery_ports.rs to use the canonical ironclaw_host_api::turn module
instead of ironclaw_turns. After confirming no other coordination types from
ironclaw_turns remain in the crate, remove its dependency declaration.
In `@crates/ironclaw_product/src/approval_interaction/gate_ref.rs`:
- Line 2: Complete the canonical turn-vocabulary import migration by replacing
compatibility imports from ironclaw_turns with contract-owner imports from
ironclaw_host_api::turn. Update TurnGateRef in
crates/ironclaw_product/src/approval_interaction/gate_ref.rs:2-2,
read_model.rs:6-6, service.rs:14-15, types.rs:10-10, approval_prompt.rs:14-14,
auth_continuation.rs:14-16, auth_interaction/service.rs:9-10,
auth_interaction/types.rs:6-8, and gate_state.rs:2-2; import EventCursor from
ironclaw_host_api::turn in inbound_turn/tests.rs:33-36, preserving all other
imports and usage.
In `@crates/ironclaw_product/src/reborn_services.rs`:
- Around line 54-57: Replace every use of the compatibility-path TurnGateRef
with the canonical ironclaw_host_api::turn::TurnGateRef. Update imports and
public type references at crates/ironclaw_product/src/reborn_services.rs:54-57
and :6842, crates/ironclaw_product/src/reborn_services/types.rs:10-11,
crates/ironclaw_product/src/run_delivery.rs:41-42,
crates/ironclaw_product/tests/prompt_projection_contract.rs:15,
crates/ironclaw_product/tests/reborn_services_contract.rs:177-182,
crates/ironclaw_product/tests/run_delivery_contract.rs:50-55,
crates/ironclaw_reborn_composition/src/blocked_auth_resume.rs:252-255,
crates/ironclaw_reborn_composition/src/factory/auth_tests.rs:27-30, :210, and
:993, tests/integration/group_approvals/scenario_gate_ref_edge_cases.rs:17-19,
tests/integration/support/harness/mod.rs:62, and
tests/integration/support/builder.rs:52-55; preserve the existing APIs and
behavior while removing all ironclaw_turns::TurnGateRef references.
In `@crates/ironclaw_product/src/workflow.rs`:
- Around line 30-31: Move all TurnGateRef imports and constructor references in
workflow.rs, product_surface_inbound.rs, projection/tests.rs,
projection/turn_events.rs, run_delivery/prompts.rs,
approval_interaction_contract.rs, auth_interaction_contract.rs, and
product_surface_contract.rs (including both cited constructor sites) to
ironclaw_host_api::turn. Split mixed ironclaw_turns imports so TurnRunId and
other vocabulary remain sourced there while TurnGateRef uses its canonical path.
In `@crates/ironclaw_product/tests/product_surface_contract.rs`:
- Around line 2817-2820: Update the comment describing the InvalidGateRef branch
in resolve_via_delivered_auth_route by removing the approximate numeric
source-line reference and referring only to the TurnGateRef::new symbol instead.
In `@crates/ironclaw_product/tests/reborn_services_contract.rs`:
- Line 5376: Respect the validated TurnGateRef contract across all listed sites:
in crates/ironclaw_product/tests/reborn_services_contract.rs at lines 5376,
5408, 5440, 5472, 5507, 5542, 5580, 5944, 6058, and 6188, replace each bare gate
fixture and matching request payload with valid gate:-prefixed values; in
crates/ironclaw_product/tests/prompt_projection_contract.rs at line 128, replace
approval:missing with a valid gate reference; in
crates/ironclaw_product/tests/run_delivery_contract.rs at lines 65-68, update
the helper to accept Option<TurnGateRef> or return a fallible construction
result instead of using raw &str; and in
crates/ironclaw_reborn_composition/src/blocked_auth_resume.rs at line 416,
construct a valid reference such as gate:auth-{run_id}.
In `@crates/ironclaw_turns/src/lib.rs`:
- Around line 65-70: Remove the ironclaw_host_api::turn compatibility pub use
from the crate root in lib.rs, then update all consumers of the re-exported turn
types to import them directly from ironclaw_host_api::turn. Preserve the
canonical turn vocabulary ownership there and eliminate public ironclaw_turns
paths for these types.
In `@crates/ironclaw_turns/src/status.rs`:
- Around line 9-12: Remove remaining crate-root turn-vocabulary imports and use
the canonical host API ownership: in crates/ironclaw_turns/src/status.rs lines
9-12, import migrated types from ironclaw_host_api::turn; in
crates/ironclaw_turns/src/run_profile/runtime_context.rs lines 575-576,
reference TurnActor and TurnScope through ironclaw_host_api::turn; in
crates/ironclaw_turns/tests/agent_loop_host_contract.rs lines 17-22, retain
coordinator imports from ironclaw_turns while importing host-owned vocabulary
from ironclaw_host_api::turn. Do not define or re-alias these types.
In `@tools/ironclaw_stress/src/user_turn.rs`:
- Line 1677: Update the TurnGateRef construction near TurnGateRef::new to use an
accepted prefix: format the value as gate:approval-{run_id} for an approval
gate, or gate:auth-{run_id} when use_auth_gate selects the auth flow, so
block_run receives a valid gate reference.
🪄 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: 3042f507-0ce7-475b-9342-73f81ba26609
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock,!**/Cargo.lock
📒 Files selected for processing (141)
crates/Architecture.mdcrates/ironclaw_architecture/tests/reborn_dependency_boundaries.rscrates/ironclaw_architecture/tests/reborn_restructure_baselines.rscrates/ironclaw_auth/Cargo.tomlcrates/ironclaw_auth/src/product_auth/api/auth.rscrates/ironclaw_auth/src/product_auth/api/auth/tests.rscrates/ironclaw_auth/src/product_auth/oauth/oauth_gate.rscrates/ironclaw_conversations/tests/inbound_contract.rscrates/ironclaw_event_projections/Cargo.tomlcrates/ironclaw_event_projections/src/lib.rscrates/ironclaw_event_streams/Cargo.tomlcrates/ironclaw_event_streams/src/admission.rscrates/ironclaw_event_streams/src/manager.rscrates/ironclaw_event_streams/src/types.rscrates/ironclaw_event_streams/src/update_source.rscrates/ironclaw_event_streams/tests/event_stream_manager_contract/core.rscrates/ironclaw_event_streams/tests/event_stream_manager_contract/subscription.rscrates/ironclaw_event_streams/tests/event_stream_manager_contract/support/fakes.rscrates/ironclaw_event_streams/tests/event_stream_manager_contract/support/imports.rscrates/ironclaw_extension_host/src/channel_host/e2e_tests.rscrates/ironclaw_extension_host/src/run_delivery_ports.rscrates/ironclaw_host_api/AGENTS.mdcrates/ironclaw_host_api/CLAUDE.mdcrates/ironclaw_host_api/src/ids.rscrates/ironclaw_host_api/src/product_adapter/test_support.rscrates/ironclaw_host_api/src/product_adapter/test_support/fakes.rscrates/ironclaw_host_api/src/turn.rscrates/ironclaw_host_runtime/src/memory_context.rscrates/ironclaw_host_runtime/tests/memory_prompt_context.rscrates/ironclaw_loop_host/src/subagent_spawn_port.rscrates/ironclaw_outbound/Cargo.tomlcrates/ironclaw_outbound/src/communication_preferences.rscrates/ironclaw_outbound/src/delivered_gate_routes.rscrates/ironclaw_outbound/src/delivery_resolution.rscrates/ironclaw_outbound/src/delivery_targets.rscrates/ironclaw_outbound/src/ids.rscrates/ironclaw_outbound/src/outbound_state_store.rscrates/ironclaw_outbound/src/resolution_engine.rscrates/ironclaw_outbound/src/run_delivery_cleanup.rscrates/ironclaw_outbound/src/run_final_reply_handoff.rscrates/ironclaw_outbound/src/run_final_reply_target.rscrates/ironclaw_outbound/src/service.rscrates/ironclaw_outbound/src/store.rscrates/ironclaw_outbound/src/triggered_run_delivery.rscrates/ironclaw_outbound/src/types.rscrates/ironclaw_outbound/src/validation.rscrates/ironclaw_outbound/tests/outbound_policy_service_contract.rscrates/ironclaw_outbound/tests/outbound_state_store_contract.rscrates/ironclaw_product/src/approval_interaction/gate_ref.rscrates/ironclaw_product/src/approval_interaction/read_model.rscrates/ironclaw_product/src/approval_interaction/service.rscrates/ironclaw_product/src/approval_interaction/types.rscrates/ironclaw_product/src/approval_prompt.rscrates/ironclaw_product/src/auth_continuation.rscrates/ironclaw_product/src/auth_interaction/service.rscrates/ironclaw_product/src/auth_interaction/types.rscrates/ironclaw_product/src/communication_context.rscrates/ironclaw_product/src/gate_state.rscrates/ironclaw_product/src/inbound_turn/tests.rscrates/ironclaw_product/src/lib.rscrates/ironclaw_product/src/product_surface_inbound.rscrates/ironclaw_product/src/projection/tests.rscrates/ironclaw_product/src/projection/tests/turn_stream.rscrates/ironclaw_product/src/projection/tests/turn_stream_auth.rscrates/ironclaw_product/src/projection/turn_events.rscrates/ironclaw_product/src/reborn_services.rscrates/ironclaw_product/src/reborn_services/types.rscrates/ironclaw_product/src/run_delivery.rscrates/ironclaw_product/src/run_delivery/prompts.rscrates/ironclaw_product/src/workflow.rscrates/ironclaw_product/tests/approval_interaction_contract.rscrates/ironclaw_product/tests/auth_interaction_contract.rscrates/ironclaw_product/tests/product_surface_contract.rscrates/ironclaw_product/tests/prompt_projection_contract.rscrates/ironclaw_product/tests/reborn_services_contract.rscrates/ironclaw_product/tests/run_delivery_contract.rscrates/ironclaw_reborn_composition/src/blocked_auth_resume.rscrates/ironclaw_reborn_composition/src/factory/auth_tests.rscrates/ironclaw_reborn_composition/src/llm_admin/openai_compat_serve.rscrates/ironclaw_reborn_composition/src/llm_admin/openai_compat_serve/tests.rscrates/ironclaw_reborn_composition/src/process_gate_turn_view.rscrates/ironclaw_reborn_composition/src/runtime.rscrates/ironclaw_reborn_composition/src/runtime/approval.rscrates/ironclaw_reborn_composition/src/runtime/approval_interaction_assembly.rscrates/ironclaw_reborn_composition/src/runtime/auth_interaction.rscrates/ironclaw_reborn_composition/src/runtime/capability_host/tests.rscrates/ironclaw_reborn_composition/src/runtime/tests/core.rscrates/ironclaw_reborn_composition/tests/auth_lifecycle.rscrates/ironclaw_reborn_composition/tests/budget_approval_e2e.rscrates/ironclaw_runner/src/loop_exit_applier/tests/mod.rscrates/ironclaw_runner/src/loop_exit_applier/tests/support.rscrates/ironclaw_runner/src/subagent/await_edge/mod.rscrates/ironclaw_runner/src/subagent/await_edge/resolver.rscrates/ironclaw_runner/src/subagent/await_edge/store.rscrates/ironclaw_runner/src/subagent/prompt_material.rscrates/ironclaw_telegram_extension/Cargo.tomlcrates/ironclaw_telegram_extension/src/preference_targets.rscrates/ironclaw_triggers/Cargo.tomlcrates/ironclaw_triggers/src/lib.rscrates/ironclaw_triggers/src/libsql.rscrates/ironclaw_triggers/src/postgres.rscrates/ironclaw_triggers/src/worker/active_cleanup.rscrates/ironclaw_triggers/src/worker/ports.rscrates/ironclaw_triggers/src/worker/report.rscrates/ironclaw_triggers/src/worker/tests.rscrates/ironclaw_triggers/tests/repository_contract.rscrates/ironclaw_turns/AGENTS.mdcrates/ironclaw_turns/CLAUDE.mdcrates/ironclaw_turns/src/agent_turn_runtime.rscrates/ironclaw_turns/src/coordinator.rscrates/ironclaw_turns/src/events.rscrates/ironclaw_turns/src/ids.rscrates/ironclaw_turns/src/lib.rscrates/ironclaw_turns/src/loop_exit.rscrates/ironclaw_turns/src/loop_exit/tests/mod.rscrates/ironclaw_turns/src/origin.rscrates/ironclaw_turns/src/process_projection/event_projection.rscrates/ironclaw_turns/src/process_projection/runtime.rscrates/ironclaw_turns/src/process_projection/tests.rscrates/ironclaw_turns/src/product_adapter/mod.rscrates/ironclaw_turns/src/request.rscrates/ironclaw_turns/src/response.rscrates/ironclaw_turns/src/run_profile/host/checkpoint.rscrates/ironclaw_turns/src/run_profile/memory_context.rscrates/ironclaw_turns/src/run_profile/runtime_context.rscrates/ironclaw_turns/src/scope.rscrates/ironclaw_turns/src/status.rscrates/ironclaw_turns/tests/agent_loop_host_contract.rscrates/ironclaw_turns/tests/memory_prompt_context_service.rsdocs/reborn/target-architecture/CHECKLIST.mdtests/integration/auth/auth_gate.rstests/integration/generated_restart_sequences.rstests/integration/group_approvals/scenario_gate_ref_edge_cases.rstests/integration/subagent_await_edge.rstests/integration/support/builder.rstests/integration/support/comm_context.rstests/integration/support/harness/mod.rstests/integration/support/harness/recorder.rstests/integration/support/triggered_submit.rstests/support/reborn_parity_qa/binary_e2e.rstools/ironclaw_stress/src/user_turn.rs
💤 Files with no reviewable changes (9)
- crates/ironclaw_event_projections/Cargo.toml
- crates/ironclaw_auth/Cargo.toml
- crates/ironclaw_outbound/Cargo.toml
- crates/ironclaw_event_streams/Cargo.toml
- crates/ironclaw_turns/src/ids.rs
- crates/ironclaw_triggers/Cargo.toml
- crates/ironclaw_turns/src/product_adapter/mod.rs
- crates/ironclaw_telegram_extension/Cargo.toml
- crates/ironclaw_turns/src/scope.rs
Coverage ratchetReborn integration-tier coverageLine coverage (Reborn crates): 85.78% — 318535 / 371337 lines Per-crate breakdown (60 crates, lowest-covered first)
This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors. Exemptions (17 entry/entries excluded from the accounting above)
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/reborn/target-architecture/CHECKLIST.md (2)
136-136: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winMake the WS1 exception counts consistent.
Line [136] records
20 → 15. Line [30] still requires removal of seven W4.3 exceptions plusauth→turnsand sets the target at≤12. Reconcile the checklist with the actualLAYER_MATRIX_EXCEPTIONSlist and ratchet test.🤖 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 136, Reconcile the WS1 exception-count claims in the checklist with the current LAYER_MATRIX_EXCEPTIONS contents and reborn_layer_matrix_exceptions_ratchet_down_only test. Update the conflicting “20 → 15” and “≤12” entries, including the W4.3 and auth→turns removal requirements, so the checklist reflects the actual list size and ratchet ceiling.Source: Coding guidelines
21-21: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winUpdate the stale
event_projections → turnsedge.
document/reborn/target-architecture/CHECKLIST.mdline 111 saysProjectionError::TurnEventRebaseRequiredcarriesironclaw_turns::EventCursor, butTurnEventCursoris nowironclaw_host_api::turn::EventCursorandironclaw_event_projections/Cargo.tomlno longer depends onironclaw_turns. Repoint the row text toironclaw_host_apior remove this edge from WS1 complete criteria if the real live edge concern has been addressed.🤖 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 21, Update the stale event_projections-to-turns reference in the WS1 completion criteria: change the documented EventCursor type to ironclaw_host_api::turn::EventCursor and remove the obsolete ironclaw_turns dependency edge, or delete that edge if no live dependency remains. Preserve the existing ProjectionError::TurnEventRebaseRequired requirement.
🤖 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.
Outside diff comments:
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 136: Reconcile the WS1 exception-count claims in the checklist with the
current LAYER_MATRIX_EXCEPTIONS contents and
reborn_layer_matrix_exceptions_ratchet_down_only test. Update the conflicting
“20 → 15” and “≤12” entries, including the W4.3 and auth→turns removal
requirements, so the checklist reflects the actual list size and ratchet
ceiling.
- Line 21: Update the stale event_projections-to-turns reference in the WS1
completion criteria: change the documented EventCursor type to
ironclaw_host_api::turn::EventCursor and remove the obsolete ironclaw_turns
dependency edge, or delete that edge if no live dependency remains. Preserve the
existing ProjectionError::TurnEventRebaseRequired requirement.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2dadcab7-c23d-4af3-be5f-7fc1729306ba
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock,!**/Cargo.lock
📒 Files selected for processing (1)
docs/reborn/target-architecture/CHECKLIST.md
…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>
|
placeholder |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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_product/src/projection/tests.rs`:
- Around line 34-37: Update the imports in the test module so EventCursor is
imported from ironclaw_host_api::turn without the TurnEventCursor alias. Remove
it from the ironclaw_turns import list, leaving only coordination and projection
types there.
In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 97: Remove the unrelated default-members and fuzz/ checklist changes from
the target-architecture checklist, leaving only edits relevant to the
TurnGateRef and turn-vocabulary migration; alternatively, document this
additional scope in the PR body and split it into a separate focused change if
appropriate.
🪄 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: 92ad7cba-a245-4140-bad1-dc2235125794
📒 Files selected for processing (64)
crates/ironclaw_conversations/tests/inbound_contract.rscrates/ironclaw_extension_host/src/channel_host/e2e_tests.rscrates/ironclaw_extension_host/src/run_delivery_ports.rscrates/ironclaw_host_api/AGENTS.mdcrates/ironclaw_host_api/src/turn.rscrates/ironclaw_loop_host/src/subagent_spawn_port.rscrates/ironclaw_product/src/approval_interaction/gate_ref.rscrates/ironclaw_product/src/approval_interaction/read_model.rscrates/ironclaw_product/src/approval_interaction/service.rscrates/ironclaw_product/src/approval_interaction/types.rscrates/ironclaw_product/src/approval_prompt.rscrates/ironclaw_product/src/auth_continuation.rscrates/ironclaw_product/src/auth_interaction/service.rscrates/ironclaw_product/src/auth_interaction/types.rscrates/ironclaw_product/src/gate_state.rscrates/ironclaw_product/src/inbound_turn/tests.rscrates/ironclaw_product/src/product_surface_inbound.rscrates/ironclaw_product/src/projection/tests.rscrates/ironclaw_product/src/projection/turn_events.rscrates/ironclaw_product/src/reborn_services.rscrates/ironclaw_product/src/reborn_services/types.rscrates/ironclaw_product/src/run_delivery.rscrates/ironclaw_product/src/run_delivery/prompts.rscrates/ironclaw_product/src/workflow.rscrates/ironclaw_product/tests/approval_interaction_contract.rscrates/ironclaw_product/tests/auth_interaction_contract.rscrates/ironclaw_product/tests/product_surface_contract.rscrates/ironclaw_product/tests/prompt_projection_contract.rscrates/ironclaw_product/tests/reborn_services_contract.rscrates/ironclaw_product/tests/run_delivery_contract.rscrates/ironclaw_reborn_composition/src/blocked_auth_resume.rscrates/ironclaw_reborn_composition/src/factory/auth_tests.rscrates/ironclaw_reborn_composition/src/llm_admin/openai_compat_serve.rscrates/ironclaw_reborn_composition/src/llm_admin/openai_compat_serve/tests.rscrates/ironclaw_reborn_composition/src/process_gate_turn_view.rscrates/ironclaw_reborn_composition/src/runtime.rscrates/ironclaw_reborn_composition/src/runtime/approval.rscrates/ironclaw_reborn_composition/src/runtime/approval_interaction_assembly.rscrates/ironclaw_reborn_composition/src/runtime/auth_interaction.rscrates/ironclaw_reborn_composition/src/runtime/capability_host/tests.rscrates/ironclaw_reborn_composition/src/runtime/tests/core.rscrates/ironclaw_reborn_composition/tests/auth_lifecycle.rscrates/ironclaw_reborn_composition/tests/budget_approval_e2e.rscrates/ironclaw_runner/src/loop_exit_applier/tests/mod.rscrates/ironclaw_runner/src/loop_exit_applier/tests/support.rscrates/ironclaw_runner/src/subagent/await_edge/mod.rscrates/ironclaw_runner/src/subagent/await_edge/resolver.rscrates/ironclaw_runner/src/subagent/await_edge/store.rscrates/ironclaw_runner/src/subagent/prompt_material.rscrates/ironclaw_turns/src/run_profile/runtime_context.rscrates/ironclaw_turns/src/status.rscrates/ironclaw_turns/tests/agent_loop_host_contract.rsdocs/reborn/target-architecture/CHECKLIST.mdtests/integration/auth/auth_gate.rstests/integration/changed-coverage-exemptions.tomltests/integration/generated_restart_sequences.rstests/integration/group_approvals/scenario_gate_ref_edge_cases.rstests/integration/subagent_await_edge.rstests/integration/support/builder.rstests/integration/support/harness/mod.rstests/integration/support/harness/recorder.rstests/integration/support/triggered_submit.rstests/support/reborn_parity_qa/binary_e2e.rstools/ironclaw_stress/src/user_turn.rs
| CancelRunRequest, CancelRunResponse, EventCursor as TurnEventCursor, GetRunStateRequest, | ||
| ResumeTurnRequest, ResumeTurnResponse, SubmitTurnRequest, SubmitTurnResponse, | ||
| TurnBlockedGateKind, TurnBlockedGateMetadata, TurnError, TurnEventKind, TurnEventPage, | ||
| TurnLifecycleEvent, TurnRunId, TurnRunState, TurnStatus, | ||
| TurnLifecycleEvent, TurnRunState, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Import EventCursor from its canonical owner.
Line 34 still imports EventCursor as TurnEventCursor from ironclaw_turns. Import it from ironclaw_host_api::turn and leave only coordination and projection types under ironclaw_turns.
This change is required to complete the ownership migration and avoid compatibility-boundary imports.
As per path instructions, consumers must import shared types from their owning crate. Based on learnings, modified import lines must reference the canonical contract owner.
Proposed import fix
use ironclaw_host_api::turn::{
- AcceptedMessageRef, RunProfileId, RunProfileVersion, SourceBindingRef, TurnGateRef, TurnRunId,
+ AcceptedMessageRef, EventCursor as TurnEventCursor, RunProfileId, RunProfileVersion,
+ SourceBindingRef, TurnGateRef, TurnRunId,
TurnStatus,
};
use ironclaw_turns::{
- CancelRunRequest, CancelRunResponse, EventCursor as TurnEventCursor, GetRunStateRequest,
+ CancelRunRequest, CancelRunResponse, GetRunStateRequest,
ResumeTurnRequest, ResumeTurnResponse, SubmitTurnRequest, SubmitTurnResponse,
TurnBlockedGateKind, TurnBlockedGateMetadata, TurnError, TurnEventKind, TurnEventPage,
TurnLifecycleEvent, TurnRunState,
};📝 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.
| CancelRunRequest, CancelRunResponse, EventCursor as TurnEventCursor, GetRunStateRequest, | |
| ResumeTurnRequest, ResumeTurnResponse, SubmitTurnRequest, SubmitTurnResponse, | |
| TurnBlockedGateKind, TurnBlockedGateMetadata, TurnError, TurnEventKind, TurnEventPage, | |
| TurnLifecycleEvent, TurnRunId, TurnRunState, TurnStatus, | |
| TurnLifecycleEvent, TurnRunState, | |
| use ironclaw_host_api::turn::{ | |
| AcceptedMessageRef, EventCursor as TurnEventCursor, RunProfileId, RunProfileVersion, | |
| SourceBindingRef, TurnGateRef, TurnRunId, TurnStatus, | |
| }; | |
| use ironclaw_turns::{ | |
| CancelRunRequest, CancelRunResponse, GetRunStateRequest, | |
| ResumeTurnRequest, ResumeTurnResponse, SubmitTurnRequest, SubmitTurnResponse, | |
| TurnBlockedGateKind, TurnBlockedGateMetadata, TurnError, TurnEventKind, TurnEventPage, | |
| TurnLifecycleEvent, TurnRunState, | |
| }; |
🤖 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_product/src/projection/tests.rs` around lines 34 - 37, Update
the imports in the test module so EventCursor is imported from
ironclaw_host_api::turn without the TurnEventCursor alias. Remove it from the
ironclaw_turns import list, leaving only coordination and projection types
there.
Sources: Path instructions, Learnings
|
|
||
| - [ ] Family directories created; every crate `git mv`'d to its §5 path **with** its narrowing milestone (retain-as-is crates may move in early batches); root `members` uses family paths; CI selectors/scripts/`Cargo.toml` path deps updated per batch. | ||
| - [ ] `tools/` unchanged (`stress` stays a member; optional `default-members` trim **[decision]**); ~~root `fuzz/` deleted or re-pointed (currently unresolvable)~~ — **the `fuzz/` half landed with the WS8 dead-crates PR** (deleted, not re-pointed: it declared `[dependencies.ironclaw] path = ".."` and fuzzed `ironclaw::safety` / `ironclaw::tools`, but the root package has had no lib target since the v1 monolith went, so it could never resolve; its workspace `exclude` entry went with it); `ironclaw_safety/fuzz` untouched. The `tools/`/`default-members` half is still open. | ||
| - [ ] `tools/` unchanged (`stress` stays a member; **`default-members` trim resolved — owner decision 2026-07-31: no trim; root `default-members` stays as-is, revisit only if default-build times become a complaint**); ~~root `fuzz/` deleted or re-pointed (currently unresolvable)~~ — **the `fuzz/` half landed with the WS8 dead-crates PR** (deleted, not re-pointed: it declared `[dependencies.ironclaw] path = ".."` and fuzzed `ironclaw::safety` / `ironclaw::tools`, but the root package has had no lib target since the v1 monolith went, so it could never resolve; its workspace `exclude` entry went with it); `ironclaw_safety/fuzz` untouched. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove the unrelated checklist edit or document it in the PR scope.
Line 97 records a default-members decision and a fuzz/ deletion. These changes are unrelated to the TurnGateRef and turn-vocabulary migration described in the PR objectives. Remove this edit or split and describe the additional scope in the PR body.
As per coding guidelines: “Keep pull requests focused and avoid mixing unrelated concerns,” and the PR body must describe the full diff.
🤖 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 97, Remove the
unrelated default-members and fuzz/ checklist changes from the
target-architecture checklist, leaving only edits relevant to the TurnGateRef
and turn-vocabulary migration; alternatively, document this additional scope in
the PR body and split it into a separate focused change if appropriate.
Source: Coding guidelines
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>
|
Coordinator sign-off (Wave 1 slot 1 — WS1.1 turn vocabulary completion). Review protocol ran across three passes: independent verification, one bundled feedback round (CodeRabbit's 10 threads + coordinator items), and the CI-derived coverage-manifest completion. All claims verified against the branch, not the report:
Ready to merge. Note for the stack: |
#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
…oop (WS1.2) (#6975) * 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 #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 #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> * 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(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 #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> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…he dual import paths (WS1.3) (#6977) * 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 #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 #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 #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> * fix(collapse): drop the import the squash-collapse duplicated Both sides independently rewrote the same ironclaw_turns::run_profile import into ironclaw_loop_contracts, so the textual merge kept both copies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * review: correct the module count and bound the scan walk Both CodeRabbit findings verified against the tree and real: the crate guide still said nine modules after hosted_mcp joined on the merge-down (lib.rs declares ten), and crates/ironclaw_webui/frontend/node_modules really is present -- 2,506 directories the location scan descended on every run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(contracts): cover two arms the changed-coverage gate exposed, exempt the rest The gate flagged nine sites. Two were genuine gaps in this crate's own contract surface and are now tested rather than exempted: the body_json_pointer egress injection arm (the existing shape test grew the rejecting and accepting cases) and ExtensionContract::capability. The remaining seven are declaration-only lines, or a type path inside a body that was already fully uncovered, and are exempted with their per-site evidence. Every line number was derived by replaying the failing run's own merged lcov against the gate until it reproduced byte-identically -- none was guessed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…adapter half (WS1.4) (#6980) * 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 #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 #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 #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 #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> * test(contracts): cover the extracted auth-prompt and lifecycle-id surface The changed-coverage gate on #6980 flagged 134 uncovered lines and 22 uncovered branch arms, all in the two modules WS1.4 created. That was a real regression, not an attribution artifact: the code moved out of host_api::product_adapter::outbound and host_api::package_lifecycle, and the owning crates' unit suites did not move with it. Fourteen tests close every one of those lines: render_channel_auth_prompt in both the DM and mention shapes and with/without a pairing deep link, the AuthPromptContextView constructors and wire round-trip, each nested validator arm rejecting independently, and the bounded-id accessor and rejection surface. Verified by replaying reborn_changed_coverage.py against the PR's own merged lcov (byte-identical to CI) and re-measuring with cargo llvm-cov at this head: 134 -> 0. Three sites remain exempted with per-site evidence, all unreachable by test: a declaration-only crate facade's inner attribute, and two pre-existing error arms whose only change is a repointed type path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(contracts): cover the newline/tab carve-out in bounded prompt text The changed-coverage gate reached 100% line but left three branch arms on validate_bounded_text's control-character predicate. Newline and tab are deliberately legal in prompt copy — channels render multi-line instructions — and every other control character is a rejection; both halves are now driven. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(coverage): exempt the four type-position lines the gate cannot instrument With the auth-prompt and lifecycle-id holes tested, the gate reached 100% line and 100% branch but still failed on four files 'contributing no instrumented lines'. Each is a single type-position line — an enum variant field, two parameter types, a struct field — whose only change is the repointed path for a moved contract, wrapped onto its own line by rustfmt. LLVM emits no coverage region for a type annotation, so no test can reach them. Replayed against this PR's own merged lcov: exit 0, 285/285 lines, 38/38 branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ss grants (WS1.5) (#6981) * 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 #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 #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 #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 #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. * test(contracts): cover the extracted auth-prompt and lifecycle-id surface The changed-coverage gate on #6980 flagged 134 uncovered lines and 22 uncovered branch arms, all in the two modules WS1.4 created. That was a real regression, not an attribution artifact: the code moved out of host_api::product_adapter::outbound and host_api::package_lifecycle, and the owning crates' unit suites did not move with it. Fourteen tests close every one of those lines: render_channel_auth_prompt in both the DM and mention shapes and with/without a pairing deep link, the AuthPromptContextView constructors and wire round-trip, each nested validator arm rejecting independently, and the bounded-id accessor and rejection surface. Verified by replaying reborn_changed_coverage.py against the PR's own merged lcov (byte-identical to CI) and re-measuring with cargo llvm-cov at this head: 134 -> 0. Three sites remain exempted with per-site evidence, all unreachable by test: a declaration-only crate facade's inner attribute, and two pre-existing error arms whose only change is a repointed type path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(contracts): cover the newline/tab carve-out in bounded prompt text The changed-coverage gate reached 100% line but left three branch arms on validate_bounded_text's control-character predicate. Newline and tab are deliberately legal in prompt copy — channels render multi-line instructions — and every other control character is a rejection; both halves are now driven. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(coverage): exempt the four type-position lines the gate cannot instrument With the auth-prompt and lifecycle-id holes tested, the gate reached 100% line and 100% branch but still failed on four files 'contributing no instrumented lines'. Each is a single type-position line — an enum variant field, two parameter types, a struct field — whose only change is the repointed path for a moved contract, wrapped onto its own line by rustfmt. LLVM emits no coverage region for a type annotation, so no test can reach them. Replayed against this PR's own merged lcov: exit 0, 285/285 lines, 38/38 branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…7) (#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 #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 #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 #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 #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 #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 #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 (#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>
…ire the turns shims (WS1.1) (nearai#6967) * 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> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…oop (WS1.2) (nearai#6975) * 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> * 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(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> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…he dual import paths (WS1.3) (nearai#6977) * 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> * fix(collapse): drop the import the squash-collapse duplicated Both sides independently rewrote the same ironclaw_turns::run_profile import into ironclaw_loop_contracts, so the textual merge kept both copies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * review: correct the module count and bound the scan walk Both CodeRabbit findings verified against the tree and real: the crate guide still said nine modules after hosted_mcp joined on the merge-down (lib.rs declares ten), and crates/ironclaw_webui/frontend/node_modules really is present -- 2,506 directories the location scan descended on every run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(contracts): cover two arms the changed-coverage gate exposed, exempt the rest The gate flagged nine sites. Two were genuine gaps in this crate's own contract surface and are now tested rather than exempted: the body_json_pointer egress injection arm (the existing shape test grew the rejecting and accepting cases) and ExtensionContract::capability. The remaining seven are declaration-only lines, or a type path inside a body that was already fully uncovered, and are exempted with their per-site evidence. Every line number was derived by replaying the failing run's own merged lcov against the gate until it reproduced byte-identically -- none was guessed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…adapter half (WS1.4) (nearai#6980) * 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> * test(contracts): cover the extracted auth-prompt and lifecycle-id surface The changed-coverage gate on nearai#6980 flagged 134 uncovered lines and 22 uncovered branch arms, all in the two modules WS1.4 created. That was a real regression, not an attribution artifact: the code moved out of host_api::product_adapter::outbound and host_api::package_lifecycle, and the owning crates' unit suites did not move with it. Fourteen tests close every one of those lines: render_channel_auth_prompt in both the DM and mention shapes and with/without a pairing deep link, the AuthPromptContextView constructors and wire round-trip, each nested validator arm rejecting independently, and the bounded-id accessor and rejection surface. Verified by replaying reborn_changed_coverage.py against the PR's own merged lcov (byte-identical to CI) and re-measuring with cargo llvm-cov at this head: 134 -> 0. Three sites remain exempted with per-site evidence, all unreachable by test: a declaration-only crate facade's inner attribute, and two pre-existing error arms whose only change is a repointed type path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(contracts): cover the newline/tab carve-out in bounded prompt text The changed-coverage gate reached 100% line but left three branch arms on validate_bounded_text's control-character predicate. Newline and tab are deliberately legal in prompt copy — channels render multi-line instructions — and every other control character is a rejection; both halves are now driven. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(coverage): exempt the four type-position lines the gate cannot instrument With the auth-prompt and lifecycle-id holes tested, the gate reached 100% line and 100% branch but still failed on four files 'contributing no instrumented lines'. Each is a single type-position line — an enum variant field, two parameter types, a struct field — whose only change is the repointed path for a moved contract, wrapped onto its own line by rustfmt. LLVM emits no coverage region for a type annotation, so no test can reach them. Replayed against this PR's own merged lcov: exit 0, 285/285 lines, 38/38 branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ss grants (WS1.5) (nearai#6981) * 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. * test(contracts): cover the extracted auth-prompt and lifecycle-id surface The changed-coverage gate on nearai#6980 flagged 134 uncovered lines and 22 uncovered branch arms, all in the two modules WS1.4 created. That was a real regression, not an attribution artifact: the code moved out of host_api::product_adapter::outbound and host_api::package_lifecycle, and the owning crates' unit suites did not move with it. Fourteen tests close every one of those lines: render_channel_auth_prompt in both the DM and mention shapes and with/without a pairing deep link, the AuthPromptContextView constructors and wire round-trip, each nested validator arm rejecting independently, and the bounded-id accessor and rejection surface. Verified by replaying reborn_changed_coverage.py against the PR's own merged lcov (byte-identical to CI) and re-measuring with cargo llvm-cov at this head: 134 -> 0. Three sites remain exempted with per-site evidence, all unreachable by test: a declaration-only crate facade's inner attribute, and two pre-existing error arms whose only change is a repointed type path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(contracts): cover the newline/tab carve-out in bounded prompt text The changed-coverage gate reached 100% line but left three branch arms on validate_bounded_text's control-character predicate. Newline and tab are deliberately legal in prompt copy — channels render multi-line instructions — and every other control character is a rejection; both halves are now driven. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(coverage): exempt the four type-position lines the gate cannot instrument With the auth-prompt and lifecycle-id holes tested, the gate reached 100% line and 100% branch but still failed on four files 'contributing no instrumented lines'. Each is a single type-position line — an enum variant field, two parameter types, a struct field — whose only change is the repointed path for a moved contract, wrapped onto its own line by rustfmt. LLVM emits no coverage region for a type annotation, so no test can reach them. Replayed against this PR's own merged lcov: exit 0, 285/285 lines, 38/38 branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 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>
ironclaw_host_api::turnbecomes the complete canonical turn vocabulary, the threeironclaw_turnsre-export shims named by CHECKLIST WS1.1 are deleted, and the six vocabulary-only consumers drop theirironclaw_turnsdependency entirely. FiveLAYER_MATRIX_EXCEPTIONSfall out as a consequence: 20 → 15.No behavior change. 142 files, +1148/−1120.
🔍 Headline finding — the workspace had two different types named
GateRefStart here; it is the largest single piece of the diff and nothing in the WS1.1 row predicted it.
turns/src/ids.rscould not be deleted without resolving its last line:pub type GateRef = ironclaw_host_api::turn::TurnGateRef. That alias gave a second name to a host_api type — and that name already belonged to an unrelated type in the same workspace:ironclaw_host_api::ids::GateRefuuid_id!), minted byfor_approval_request/for_auth_gateGateRecordstorage keyironclaw_turns::GateRefhost_api::turn::TurnGateRef, a boundedgate:-prefixed stringhost_api/src/ids.rsalready carried a doc comment warning that the two are "deliberately distinct" — the codebase knew about the collision and lived with it. The alias is now retired rather than relocated, soTurnGateRefis the one and only name for the routing ref: 336 occurrences across 62 files.A mechanical find-and-replace would have been silently wrong. Every occurrence was classified by its import source first, and 14 files were excluded as kernel-
GateRefusers —approvals×2,capabilities::host,host_api×4,composition×3,runner::turn_run_executor, thewebuicontract test,loop_host::capability_port, and the trap:turns::run_profile::resolution, a file insideironclaw_turnsitself that imports the kernelGateReffromhost_api::ids. Renaming it would have swapped a uuid key for a bounded string. Two further files use both types and were hand-edited.The rename is compiler-verified end to end: because the two are genuinely different types, any misclassification is a type error rather than a silent swap. Detail in §3.
1. Vocabulary moved into
host_api::turnTurnStatusturns::statusGateKindturns::statusTurnStatus::is_blockedis defined asGateKind::from_status(self).is_some(), andGateKinddocuments itself as the singleGateKind -> TurnStatuscorrespondence that ~6 other blocked-gate representations map through. Splitting it fromTurnStatuswould have required duplicating that match table — the exact vocabulary pooling this workstream exists to remove.BlockedReasonturns::statusGateKind::into_blocked_reasonreturns it; you cannot moveGateKindwithout it (no inherent impls on foreign types). Depends only onTurnGateRef+host_api::decision::RuntimeCredentialAuthRequirement, both already inhost_api.EventCursorturns::eventsevent_projections(production),outbound(production), andevent_streams(tests) all need it; without it three of the five exceptions could not fall. Self-contained 5-line newtype.RunOriginAdapter(+MAX_RUN_ORIGIN_ADAPTER_BYTES, nowpub)turns::originoutbound::run_delivery_cleanupcarries it in production struct fields.is_recoverability_criticaldeliberately stayed inturns::status: it is write-behind durability policy overTurnStatus, not vocabulary. Same forTurnRunState,TurnError,TurnRunProfile, and the admission-rejection types.turnsre-exports the moved names from its prelude (joining theSanitizedFailure/TurnOwner/… line that was already there) so the ~15 crates that legitimately keep the turns dependency are untouched. That re-export is explicitly documented as not an ownership claim; §11.2.4 (WS10) is the row that retires it.2. Shim-deletion inventory
turns/src/ids.rs(8 lines)pub usehalf moved tolib.rspointing atironclaw_host_api::turndirectly. Itspub type GateRef = TurnGateRefhalf is retired, not relocated — see §3.turns/src/scope.rs(1 line)ironclaw_turns::scope::…module-path consumers (product::communication_context,host_runtimedoc links + its test,turns' own test, the integration harness, andrunner's await-edge resolver — the onlyTurnThreadOwnerconsumer) now import fromironclaw_host_api::turn.turns/src/product_adapter/mod.rswaspub use ironclaw_host_api::product_adapter::*plus atest_supportwildcard passthrough — pure shim.fakes.rs(261 lines) is real code, so it moved (git mv) tocrates/ironclaw_host_api/src/product_adapter/test_support/fakes.rs, beside the four traits it implements. Its only consumer,ironclaw_product's#[cfg(test-support)]re-export, points there now.3. The one rename:
GateRef→TurnGateRef(336 occurrences, 62 files)See the headline finding above for the full rationale, the two colliding types, and the exclusion list. Mechanically, what changed here:
turns/src/ids.rs'spub type GateRef = TurnGateRefis deleted, not relocated tolib.rs— relocating it would have kept the shim at a new address and kept the collision.ironclaw_turns::GateRefnow nameTurnGateRef(still reachable asironclaw_turns::TurnGateRefvia the prelude re-export, orironclaw_host_api::turn::TurnGateRefdirectly for the six repointed crates).BlockedReason/GateKind::into_blocked_reasonsignatures inhost_api::turnuseTurnGateRefnatively, so the rename was forced by the vocabulary move regardless of the alias question.host_api/src/ids.rs's doc comment, which pointed readers at "ironclaw_turns::GateRef", now linkscrate::turn::TurnGateRef.Nothing outside this rename changed in the 14 excluded kernel-
GateReffiles, and the historical design records underdocs/reborn/subagent-spawn/keep the old spelling (see §8.6).4. Per-consumer repoint table
All six turned out to be vocabulary-only exactly as the lead sheet said, so all six dropped
ironclaw_turnsfromCargo.toml.ironclaw_turnsironclaw_authTurnRunId,TurnScopeironclaw_host_api::turnironclaw_event_streamsTurnActor,ReplyTargetBindingRef,TurnRunId,TurnScope,EventCursor(tests)ironclaw_host_api::turnironclaw_outboundReplyTargetBindingRef,TurnActor,TurnRunId,TurnScope,EventCursor,RunOriginAdapterironclaw_host_api::turnironclaw_telegram_extensionReplyTargetBindingRefironclaw_host_api::turnironclaw_triggersTurnRunId,TurnScopeironclaw_host_api::turnironclaw_event_projectionsEventCursorironclaw_host_api::turn76
ironclaw_turns::references rewritten across 38 files in those six crates.5. Exceptions delta — 20 → 15
WS0_LAYER_MATRIX_EXCEPTION_BASELINElowered 20 → 15 with the rationale inline. These five are removed because their edges no longer exist, not because they were waived — the stale-exception detector inreborn_workspace_crates_declare_layers_and_follow_layer_matrixwould fail on them if they stayed.ironclaw_event_projections → ironclaw_turnsEventCursoris inhost_api::turn; manifest dep goneironclaw_triggers → ironclaw_turnsTurnRunId/TurnScopefromhost_api::turn; manifest dep goneironclaw_outbound → ironclaw_turnsEventCursor+RunOriginAdapterfromhost_api::turn; manifest dep goneironclaw_event_streams → ironclaw_turnsEventCursorfromhost_api::turn; manifest dep goneironclaw_auth → ironclaw_turnsTurnRunId/TurnScopefromhost_api::turn; manifest dep gone. Itsremoves_inread "follow-up: neutral auth/turn gate host API port" — this is that follow-upDeliberately still standing (3 of the 8
*→turnsentries):conversations,hooks,agent_loop. Each reachesturnsfor more than vocabulary (SubmitTurnRequest/TurnCoordinator/TurnError, hook payload contracts, loop ports). They fall with the port repoints in later WS1 slots —agent_loop's withloop_contracts.6. Un-masking evidence
Full unfiltered
cargo test -p <crate>for every touched crate, before on31f42b790in a pristine worktree, after on this branch.ironclaw_host_apiironclaw_turnsironclaw_authironclaw_event_streamsironclaw_outboundironclaw_telegram_extensionironclaw_triggersironclaw_event_projectionsironclaw_productironclaw_conversationsironclaw_extension_hostironclaw_host_runtimeironclaw_loop_hostironclaw_runnerironclaw_reborn_compositionironclaw_stressZero failures on both sides. Every one of the 17 tests
turnslost is accounted for — all 17 moved verbatim intohost_api::turn, none deleted, none weakened:turns/src/status.rs's test module. Notably 11 of those 14 were already testing host_api types (ModelInvalidOutputDetailReason×4,SanitizedFailure×7) from insideturns—host_api::turnhad no test module at all before this PR. The other 3 coverTurnStatus/BlockedReason, which moved.turns/src/origin.rs(run_origin_adapter_rejects_empty,..._accepts_at_max_bytes,..._rejects_over_512_bytes), which moved withRunOriginAdapter. The threeProductTurnContextserde tests in that file stayed inturns(they testProductTurnContext, which stayed) and now import the newly-pubMAX_RUN_ORIGIN_ADAPTER_BYTES.The
+2net is two genuinely new host_api tests, both pinning the one API change:run_origin_adapter_rejection_message_is_the_one_callers_render— pins the rejection string byte-for-byte;run_origin_adapter_round_trips_as_a_plain_string_and_validates_on_read— pins the#[serde(try_from)]boundary, which now yields aStringerror.7. The one API change, and why it is behavior-preserving
RunOriginAdapter::new/TryFrom<String>returnedResult<_, TurnError>(specificallyTurnError::InvalidRunOriginAdapter).TurnErroris a turn-kernel type and could not follow the value intohost_api. It now returnsResult<_, String>, matching every other bounded ref inhost_api::turn(AcceptedMessageRef,TurnGateRef,IdempotencyKey, …).Both production callers —
product/src/inbound_turn.rsandconversations/src/inbound.rs— only ever dide.to_string(), and the message is unchanged ("invalid run-origin adapter: must be 1..=512 bytes", now exposed asRunOriginAdapter::INVALID_MESSAGEand pinned by a test). So the rendered errors are byte-identical.8. Lead-sheet corrections and findings for later slots
TurnStatus.EventCursorandRunOriginAdapterwere also required — without themevent_projections,outbound, andevent_streamscould not drop the dep, and three of the five exceptions could not fall.GateKind/BlockedReasonwere required to moveTurnStatuscoherently.turns::product_adapteris not purely a re-export shim. It also held 261 lines of live test-support fakes. They were relocated to the trait owner rather than deleted.ids.rsis not just a re-export — itsGateRefalias collided with an unrelatedhost_api::ids::GateRef. This is the largest single piece of the diff and was not visible in the row text.turns::run_profile::resolutionimports the kernelGateRef, not turns' alias — a same-crate false positive a mechanical rename would have broken.TurnError::InvalidRunOriginAdapteris now constructed only by tests. Its four production match arms (conversations::inbound,conversations::trusted_trigger,composition::trigger_poller_trusted_submit,runner::loop_driver_host) are exhaustive-match completeness, and threeconversationstests construct it through a fake coordinator to pin submit-key-rotation behavior. Retiring the variant means editing those tests, so it belongs with theconversationsport repoint — same class as the WS8ProjectionError::TurnEventRebaseRequiredrow.docs/reborn/contracts/host-api.md§3 "Proposed module layout" lists 15 of ~48 modules and did not gainturn.rs; the full list is an explicit WS11 row. The historical design records underdocs/reborn/subagent-spawn/still show the oldGateRefspelling — they are dated design documents, not live guidance.9. Guidance updated in the same diff
crates/ironclaw_host_api/CLAUDE.md+AGENTS.md—turnis now listed as an owned module and described as the complete vocabulary; adds the "turn::TurnGateRefandids::GateRefare different types, never re-alias one to the other" rule.crates/ironclaw_turns/CLAUDE.md+AGENTS.md— the two claims this PR falsified are corrected: the crate no longer owns "canonical typed IDs and references … turn scope/actor" or "Status/error vocabulary:TurnStatus…". Both now say where the vocabulary actually lives and that the prelude re-export is not an ownership claim.crates/Architecture.md— the cross-boundary record table's Owner column forTurnScope,TurnActor,AcceptedMessageRef, the binding refs,TurnId,TurnRunId, andEventCursor(several of which were already wrong, since the ids lived inhost_apibehind the shim), plus theironclaw_turnscrate row.crates/ironclaw_architecture/tests/reborn_restructure_baselines.rs— the WS0 baseline table records "WS0 20, now 15".10. Verification
cargo fmt --check— cleancargo clippy -p <each touched crate> --all-targets --all-features -- -D warnings— clean; alsocargo clippy --all --benches --tests --examples --all-featurescleancargo test -p <each of the 16 touched crates>, unfiltered — table in §6, zero failurescargo test -p ironclaw_architecture— all suites green, including the exception ratchet at the new baseline of 15 and the stale-exception detector with the five entries removedpython3 scripts/check_no_panics.py --reborn-baseline—OK (1154 files, 51 reviewed invariants); thefakes.rsmove needed no baseline change (the module stays#[cfg(any(test, feature = "test-support"))]-gated)cargo build --workspace --all-features --tests --benches --examples— cleanPer the standing local-verification policy, the full
cargo test --workspacewas not run locally; CI runs the full matrix.11. Trailing docs commit
Separate
docs:commit ticks the WS1.1 row with the corrections above, and closes PLAN decision round #1 per the owner: Strategy B confirmed (retroactive record), and thetools/row'sdefault-memberstrim resolved as no trim. It also surfaces #6963 on the WS0 blocking-prerequisite row's first line (it was already cited mid-paragraph) and records the §11.2.2 ratchet moving 20 → 15.🤖 Generated with Claude Code