Skip to content

Narrow ironclaw_common and shed the product→runner edge (WS1.6 + WS1.7) - #6982

Merged
BenKurrek merged 33 commits into
mainfrom
ws1/wave-close
Aug 1, 2026
Merged

BenKurrek merged 33 commits into
mainfrom
ws1/wave-close

Conversation

@BenKurrek

@BenKurrek BenKurrek commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator

Collapsed to main 2026-07-31 — #6981 merged, so this now targets main directly. It is the last merge in Wave 1.

Collapse verification: post-merge git diff origin/main is byte-identical to the pre-merge delta (41 files, +855/−1902), modulo exactly two index <blob>..<blob> lines — on CHECKLIST.md and PLAN.md, the only two files whose base content moved under me. Content-only comparison with index lines stripped: 3,436 lines, identical. CHECKLIST.md line-diffed against main: exactly 3 of main's 191 lines are absent, and all three are the rows this PR deliberately rewrites (the WS1.6 row, the WS1.7 row, the WS8 event.rs deletion-candidate row) — all five prior slices' ticks and all three of #6979's annotations (the mcp hosted-MCP note, the renames-row prefix note, the WS10 quiet-gate amendment) survive verbatim.

Closes Wave 1 with its last two rows: the ironclaw_common narrowing (WS1.6) and the two "single-symbol" product edges (WS1.7). Neither row survived contact with the code as written — five of WS1.6's six clauses and both of WS1.7's edges measured differently on this base — so the corrections, with evidence, are most of the value here.

WS1.6 — ironclaw_common narrowing

ironclaw_common now matches §6.1.5's Owns list exactly, plus three named exceptions. Eviction table:

item §6.1.5 home what actually happened
event.rs (1,234 lines) product_contracts DELETED. Zero consumers outside the crate for all seven exported symbols. Confirms WS1.4's disposition 7 rather than repeating it; closes the WS8 deletion-candidate row.
platform.rs "its consumer" DELETED. PlatformInfo has zero references anywhere in the tree — the assignment is unsatisfiable because there is no consumer.
budget constants ×4 ironclaw_resources DELETED. Zero consumers workspace-wide; resources already governs the concern with a live, differently-shaped mechanism (ResourceLimits::max_wall_clock_ms) referencing none of them.
trust_boundary delete ALREADY DONE in #6943. Stale clause. (AGENTS.md still claimed ownership — fixed.)
AttachmentRef collision rename channel-facing one ALREADY DONE — it is ChannelAttachmentRef. The rename left a wrong cross-reference in its own doc comment (ironclaw_common::ChannelAttachmentRef where it means AttachmentRef); fixed. Cleared ironclaw_threads::AttachmentRef as a plain re-export, not a third collision.
automation "→ owner" (§6.1.5 only) MOVED → ironclaw_triggers. Not in the CHECKLIST row, only in §6.1.5. Triggers is unambiguously the owner. Product's cross-crate pub use of the newtype (zero external consumers) deleted, not re-sourced.
llm_costs / provider_transcript / model_selection ironclaw_llm ALL THREE BLOCKED — stay, documented. See below.

The three LLM-data evictions are refuted by pinned rules

This is the one substantive disagreement with §6.1.5, recorded rather than forced:

  • provider_transcript — ironclaw_agent_loop calls is_only_provider_transcript_artifact_lines, and its boundary rule admits contracts-layer normal dependencies only. ironclaw_llm is layer = "substrates", so the move either breaks the zero-exception property WS1.2 just bought or needs a new LAYER_MATRIX_EXCEPTION — which the ratchet forbids.
  • model_selection — ironclaw_reborn_openai_compat's boundary rule lists ironclaw_llm as forbidden outright, and ironclaw_llm has zero uses of the module. The move would place a symbol where it is unused, to serve two crates that may not reach it.
  • llm_costs — ironclaw_llm uses 2 of its 7 public items (4 of 7 call sites are tests). The real consumers are turn_runner, composition, and product (RunCost, a product wire DTO whose §6.1.3 home cannot depend on llm either). Moving it hands ironclaw_product — the crate §6.9.1 exists to narrow — a reqwest/rig-core/Bedrock cone for a pricing table. The seam already exists: ModelCostTable, already an injectable override in composition. Routing the static table behind it is a design change belonging with the WS4 sheds.

All three are written into crates/ironclaw_common/AGENTS.md with the measurements, so the next agent does not re-litigate them.

WS1.7 — the two product edges

product → runner: SEVERED. It was two modules, not one symbol. Data moved to ironclaw_host_api::failure::{categories, summary}; ironclaw_product's manifest no longer names ironclaw_runner under [dependencies] (dev-dep stays, now documented). What could not follow, because host_api may hold no internal dependency: the envelope writer (typed on agent_loop's CheckpointKind + loop_contracts' LoopSafeSummary) and every classifier. Both runner modules are now private.

product → loop_host: NOT severed; prompt clause done. Three production sites, only one of which was the prompt constant. FAILURE_EXPLANATION_SYSTEM_PROMPT and its asset are now product-owned (prompt content is out of charter for the loop tier). The other two carry real behavior: project_create_capability.rs (the #6691 arrival §2.3 already flags for a Wave 4 re-shed) and — not previously recorded anywhere — scoped_fs/attachment_landing.rs, which consumes LoopAttachmentReadPort. Severing is WS5/WS6 work, so the row stays unticked.

One disposition that wants a reviewer's eye

The envelope reader moved and now revalidates its cause with SafeSummary::new instead of LoopSafeSummary::new. This 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, so the two validators accept the same set of causes. loop_input_encode_sentinel_needs_no_bypass_here fails if that ever stops being true. Splitting writer from reader also split the checkpoint-stage vocabulary across two crates, so the pre-existing round-trip (which covered only BeforeModel) gained a sibling driving all four CheckpointKinds writer→reader across the boundary.

Neither edge was ever an exception

products → kernel and products → loops are matrix-legal, so this row cannot move the exception count — it stays at 13, nothing fell, nothing added. Worth stating because the wave milestone is written in exceptions.

Un-masking

crate before → after accounting
ironclaw_common 123 → 110 10 event::tests::* (each classified to a deleted symbol) + 3 automation::tests::* relocated
ironclaw_triggers 166 → 169 the 3 relocated tests, names identical
ironclaw_runner 467 → 458 10 table tests moved out, 1 new round-trip
ironclaw_host_api 248 → 260 the 10 moved in + 2 new pins
ironclaw_loop_host 572 → 572 byte-identical roster
ironclaw_product 1032 → 1032 byte-identical roster

Zero unclassified. No surviving test edited for content (moved tests differ only in imports).

Enumerating gates — all shrink-only

  • Composition pub-use snapshot 127 → 126: the reborn_failure_summary_for_category re-export is deleted, not re-sourced — its sole consumer, the CLI, already depends on ironclaw_host_api, so the facade hop bought nothing.
  • Extension-specificity PATH_TERM_COLLISIONS lost its now-stale ironclaw_common/src/platform.rs carve-out — the gate itself demanded this, failing on carve-outs that match nothing (the property it was built with).
  • §11.2.7 cross-crate include inventory unchanged at 19: the product-side category-coverage include_str! was repointed, not added.
  • LAYER_MATRIX_EXCEPTIONS 13, unchanged.
  • The AppEvent ForbiddenUse entry is kept as a reintroduction pin with its reason updated to say the enum is gone — the convention the file already applies to the retired v1 ironclaw:: root crate.

Carry item

reborn_loop_port_location_scan's directory walk excluded only target, so it descended the entire npm tree on any checkout with the frontend installed — the same defect already fixed in the sibling scanners. Now matches their exclusion set, with the measurement in the comment (this worktree: 516 directories under crates/ uninstalled; 23 declared frontend dependencies whose closure is what gets walked after npm install).

Wave 1 exit state

Recorded on the WS1 verify row and summarized in PLAN. The milestone's "20 → 12" is not met, and the 12 was wrong: the true end-state is 13, because the 13th survivor conversations → turns is turn admission authority, not vocabulary — no contracts crate dissolves it, and it falls in WS5. Everything else in the milestone landed: three contracts crates, agent_loop contracts-only with zero exceptions, evidence mint sealed. The mint row's finding is carried forward as reusable: the host-auth-mint cargo feature was never a seal — feature unification compiled the gate on for every crate in any workspace-wide build.

Verification

cargo fmt; per-crate clippy -D warnings --all-targets --all-features on all 10 touched crates (clean); per-crate unfiltered suites (common 110, triggers 169, host_api 260, runner 458, loop_host 572, product 1032, extension_contracts 92, composition 923 (+1 ignored), CLI 616 — all green re-run on the collapsed tree; every roster identical to pre-merge except extension_contracts 77→92, which is main's own additions taken wholesale); full architecture suite 127/127 on the merged tree; workspace cargo check --all-targets --all-features clean; cargo metadata --locked clean with an unchanged lockfile; check-include-str-paths.sh OK (119 references); critical_mutation_gate.py --selection-only --all exit 0; all 10 reborn-e2e-rust.sh exact selectors executed under bash — each matches exactly one test.

Hazards for review

  1. SafeSummary vs LoopSafeSummary in the moved reader — the one place this PR changes which validator runs. Argued behavior-preserving and pinned by a test; worth a second reader given it gates user-facing failure copy.
  2. product → loop_host survives on two behavioral sites, one of them (LoopAttachmentReadPort in attachment_landing.rs) previously unrecorded in any doc. WS5 should plan around it.
  3. The llm_costs deferral has a real successor task — the ModelCostTable port already exists; someone should own routing the static table behind it rather than leaving the module in common indefinitely.
  4. crates/ironclaw_reborn_openai_compat/CLAUDE.md still names AppEvent in a denylist of retired v1 concepts (alongside SseManager). Left alone rather than half-auditing that list; a WS11 drift item.

🤖 Generated with Claude Code

BenKurrek and others added 30 commits July 31, 2026 11:59
…ire the turns shims (WS1.1)

`ironclaw_host_api::turn` becomes the complete canonical turn vocabulary:
it absorbs `TurnStatus` (with the inseparable `GateKind`/`BlockedReason`
gate correspondence), `EventCursor`, and `RunOriginAdapter`. The three
`ironclaw_turns` re-export shims named by CHECKLIST WS1.1 are deleted —
`src/ids.rs`, `src/scope.rs`, and the whole `src/product_adapter/`
module, whose `fakes.rs` moves beside the traits it implements in
`host_api::product_adapter::test_support`.

`ids.rs` carried `pub type GateRef = TurnGateRef`: a second name for a
host_api type that collided with the unrelated
`ironclaw_host_api::ids::GateRef` (an opaque uuid GateRecord key, versus
turns' bounded `gate:`-prefixed routing string). The alias is retired
rather than relocated, so the workspace now has exactly one `GateRef`.

The six vocabulary-only consumers — auth, event_streams, outbound,
telegram_extension, triggers, event_projections — import from
`ironclaw_host_api::turn` and drop their `ironclaw_turns` dependency
entirely. Five `*→turns` LAYER_MATRIX_EXCEPTIONS are therefore not
waived but obsolete: the edges no longer exist. The §11.2.2 ratchet
baseline moves 20 → 15.

No behavior change. `RunOriginAdapter`'s validation error becomes
`Result<_, String>` (matching every other bounded ref in
`host_api::turn`) with a byte-identical message pinned by a test, so
both production `e.to_string()` call sites are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WS1.1's box is ticked with what the change actually landed, including the
three lead-sheet corrections it turned up: the row named `TurnStatus` but
not `EventCursor`/`RunOriginAdapter` (which the six consumers genuinely
needed), `GateKind`/`BlockedReason` could not be left behind without
duplicating the single `GateKind -> TurnStatus` match table, and deleting
`ids.rs` forced retiring its `GateRef` alias rather than relocating it.

Two decisions confirmed outside the doc and never recorded:

- Strategy B (family dirs + focused crates) — confirmed 2026-07-31 by the
  owner, recorded retroactively; it was made in practice at program start.
- The `tools/` row's `default-members` trim — resolved as no trim.

Also surfaces #6963 on the WS0 blocking-prerequisite row's first line
(it was already cited mid-paragraph) and records the §11.2.2 exception
ratchet moving 20 -> 15.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…TurnGateRef contract

CodeRabbit review round on #6967.

Import repoints (accepted): every `use` line this PR already rewrote now
names `ironclaw_host_api::turn` directly instead of routing through
`ironclaw_turns`' prelude — 57 files across extension_host, product,
composition, runner, loop_host, conversations, the integration harness,
and the stress tool, plus three inside `ironclaw_turns` itself so the
crate stops consuming its own facade. Import lines this PR did not touch
are left for their consumer's own repoint slot.

TurnGateRef contract pinned (refutation): two review comments claimed
`TurnGateRef::new` only accepts `gate:approval-`/`gate:auth-` prefixes
and that fixtures like "gate-alpha" and "stress-gate:{run_id}" fail
construction. They do not — `TurnGateRef` is `bounded_ref!` (non-empty,
<= 256 bytes, no control characters); `LoopGateRef` is the prefix-
validated family via `loop_ref!(.., "gate:")`. The misreading traces to
this PR's own AGENTS.md wording ("bounded `gate:`-prefixed routing
string"), which stated a minting convention as if it were validation.
That wording is corrected and the distinction is now pinned by a test.

Also: drop a stale cross-file line reference in a product test comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The `tools/` row resolved the `default-members` trim as "no trim" but
kept a trailing "The `tools/`/`default-members` half is still open."
from before that decision, so the row asserted both states. Drop the
stale sentence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Finishes the remediation deferred last round, now that the settled run
(job 91246493989) provides authoritative line numbers. Manifest-only —
no .rs file changes, so the changed-line set the gate computes is
unchanged and these numbers stay valid for the next run.

Derived, not transcribed: the gate was replayed locally against its own
merged lcov from that run, reproducing CI's failure byte-identically
first (95.17%, 138/145, same 13 files, same 7 lines), then re-run after
each entry. Final local result: 100.00% (138/138), branch 100% (4/4),
exit 0. All 45 gate self-tests pass.

Two classes, both verified rather than asserted:

- 13 files x 20 lines - declaration lines (fn params, return types,
  struct fields) whose only edit is the type NAME: GateRef ->
  TurnGateRef, or ironclaw_turns::X -> ironclaw_host_api::turn::X.
  Declarations are not executable, so these files contribute a zero
  denominator and trip the fail-closed empty_denominator branch.

- 7 lines x 3 files - executable, instrumented, and genuinely not
  exercised by the integration tier. Each checked against the base
  merged lcov (main @ 67088a4, the PR's own base sha): identical 0
  hits before and after, so no coverage was lost. approval_prompt_
  context_view is uncovered across its whole signature at base
  (lines 505-511); the background spawn-mode arm and the invalid-gate-
  ref error path likewise.

This includes the two entries I refused to guess last round -
turn_events.rs (three identical candidate lines by text; the settled
run disambiguates it as 510) and await_edge/store.rs (no verbatim twin
after the repoint; authoritatively 268-272).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oop (WS1.2)

Carve the loop tier's neutral contracts out of the turn kernel into a new
contracts-layer crate per PROPOSAL 6.1.4, and repoint every consumer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…new crate

Enforcement, CI registration, and guidance for the WS1.2 extraction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he two crates

The claim types moved to ironclaw_loop_contracts with WS1.2; the validator
policy and the trusted applier stayed in the turn kernel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two HostManagedLoop*Port impls stayed in ironclaw_turns, so same-crate
links to them no longer resolve; the TurnRunId link target became redundant
when the import repoint fully qualified it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removing the ironclaw_runner re-export (required by the new port-location
scan) left two workspace-root test-support files importing the turn kernel's
evidence types through it. They now import from ironclaw_turns::loop_exit
directly, which is the single sanctioned path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#6967 landed as a squash, so this branch carries the parent's original
commits while main carries their collapsed equivalent. Merging reconciles
the two shapes; the result must be main plus exactly the WS1.2 delta.

# Conflicts:
#	crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs
#	crates/ironclaw_host_api/src/turn.rs
#	crates/ironclaw_host_runtime/tests/memory_prompt_context.rs
#	crates/ironclaw_loop_contracts/src/host/checkpoint.rs
#	crates/ironclaw_loop_contracts/src/memory_context.rs
#	crates/ironclaw_loop_contracts/tests/memory_prompt_context_service.rs
#	crates/ironclaw_loop_host/src/subagent_spawn_port.rs
#	crates/ironclaw_product/src/communication_context.rs
#	crates/ironclaw_product/src/projection/tests.rs
#	crates/ironclaw_product/src/projection/turn_events.rs
#	crates/ironclaw_product/src/reborn_services/types.rs
#	crates/ironclaw_reborn_composition/src/llm_admin/openai_compat_serve.rs
#	crates/ironclaw_reborn_composition/src/runtime.rs
#	crates/ironclaw_reborn_composition/src/runtime/capability_host/tests.rs
#	crates/ironclaw_reborn_composition/src/runtime/tests/core.rs
#	crates/ironclaw_runner/src/loop_exit_applier/tests/mod.rs
#	crates/ironclaw_runner/src/loop_exit_applier/tests/support.rs
#	crates/ironclaw_runner/src/subagent/await_edge/resolver.rs
#	crates/ironclaw_turns/src/agent_turn_runtime.rs
#	crates/ironclaw_turns/src/coordinator.rs
#	crates/ironclaw_turns/src/lib.rs
#	crates/ironclaw_turns/src/loop_exit.rs
#	crates/ironclaw_turns/src/loop_exit/tests/mod.rs
#	crates/ironclaw_turns/src/origin.rs
#	crates/ironclaw_turns/src/process_projection/runtime.rs
#	crates/ironclaw_turns/src/process_projection/tests.rs
#	crates/ironclaw_turns/src/request.rs
#	crates/ironclaw_turns/src/status.rs
#	crates/ironclaw_turns/tests/agent_loop_host_contract.rs
#	docs/reborn/target-architecture/CHECKLIST.md
#	tests/integration/support/comm_context.rs
#	tests/integration/support/harness/mod.rs
#	tests/integration/support/harness/recorder.rs
#	tests/integration/support/triggered_submit.rs
#	tests/support/reborn_parity_qa/binary_e2e.rs
#	tools/ironclaw_stress/src/user_turn.rs
…scan

Two artifacts of collapsing onto main:

- Cargo.lock pinned thiserror 2.0.18 for the new ironclaw_loop_contracts
  entry while main's dependency bump moved the workspace to 2.0.19. The
  auto-merge kept the stale pin because the bump predates the crate, so
  --locked builds failed.
- ironclaw_product's failure-summary test reaches into another crate's
  source with include_str! and scans it for 'impl LoopFailureKind'. WS1.2
  moved that impl to ironclaw_loop_contracts, so the include still resolved
  and matched nothing. Repointed to follow the code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he dual import paths (WS1.3)

Carve the extension tier's neutral contracts out of the host API into a new
contracts-layer crate per PROPOSAL 6.1.2, repoint every consumer, and pin the
boundary with the 11.2.3 purity allowlist and the 11.2.4 location scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
scripts/reborn-e2e-rust.sh pins exact test names for the deterministic
gate. The capability-failure rehydration test moved from
ironclaw_turns::run_profile::host::capability to
ironclaw_loop_contracts::host::capability, so its selector matched zero
tests and the gate failed closed.

Swept all 10 pinned selectors in that script (4 lib + 6 integration
target); this was the only stale one. Each now resolves to exactly one
test, verified by running the selector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The extension_contracts lock entry was generated before the parent branch
collapsed against main, so it pinned thiserror 2.0.18 and toml 1.1.2 — versions
that no longer have [[package]] blocks. --locked lanes would have failed to
resolve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…merate deps

The composition pub-use snapshot still carried product's PreferenceTargetCodec
re-export, and the CLI's exact-dependency allowlist did not know the extension
tier's contracts crate. Both are enumerating gates, so both failed loudly rather
than passing vacuously.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The visibility kit's sealed-strategy template exists to close a strategy set;
every trait here exists to be implemented outside the crate. State that, so the
absence reads as a decision rather than an omission.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The changed-coverage manifest carried a WS1.1 exemption for
crates/ironclaw_turns/src/run_profile/runtime_context.rs:575-576. WS1.2
moved that file into ironclaw_loop_contracts, so the gate's fail-closed
path validator rejected the manifest before reaching its line-level
verdict.

Deleted rather than repointed: WS1.1 merged, so those lines are baseline
on main, and this PR's diff pairs the file as a 99%-similarity rename
whose only changed lines are imports. Repointing would re-exempt lines the
gate no longer flags. All 19 remaining entries verified to resolve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tions

Merge brings in tests/e2e/scenarios/test_reborn_webui_v2_custom_mcp.py,
added on main after the last merge-down; the WebUI-smoke and E2E roll-up
reds were purely the missing file.

Exemptions derived by replaying scripts/ci/reborn_changed_coverage.py
against this PR's own merged lcov artifact until it exits 0 (100% line
244/244, 100% branch 8/8) - never estimated. Three classes:

- type-path repoints on declaration/expression fragments;
- verbatim-moved bodies in the new crate. Explicitly NOT counter-attribution:
  those files are instrumented in this lcov and partially hit (loop_exit
  195/109, model 86/52, checkpoint_payload 32/16), which proves the crate is
  measured. The same bodies were equally unexercised by the integration tier
  before the move, when they sat in ironclaw_turns and simply were not
  changed lines;
- one crate-root inner attribute the uninstrumentable-line classifier does
  not recognise on a declaration-only facade.

Also adds the #6524 declaration-only facade entry for the new crate's
lib.rs to the informational per-crate coverage summary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve the eight content conflicts and relocate host_api::hosted_mcp into
ironclaw_extension_contracts: it and package_lifecycle reference each other, so
once package_lifecycle moved, leaving hosted_mcp behind would have required
host_api to depend on a workspace crate. That cycle produces no git conflict --
the two sides are different files -- so it is recorded in the crate guide, the
checklist row, and the location scan's frozen names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…WS1.3 adapter half (WS1.4)

Carve the product tier's neutral contracts out of `ironclaw_host_api` into
`crates/ironclaw_product_contracts`, and — in the same change, because it is
what unblocks them — move `ChannelAdapter`/`ToolAdapter`/`RestrictedEgress`
into `ironclaw_extension_contracts` where PROPOSAL §6.1.2 assigns them.

`host_api::product_adapter` is one connected component: `channel_adapter`
names `inbound::ProductTriggerReason`, `inbound` names both
`channel_adapter::ChannelAttachmentRef` and `outbound::ProjectionCursor`,
`projection` names inbound and outbound, `interaction_commands` names inbound,
and `product_surface` names all of them. Since `ironclaw_host_api` may hold no
internal dependency, the adapters could only leave once nothing that stays
behind names them — so the product DTO modules moved with them.

`git mv` moved 15 modules at 83-100% similarity, so the diff reads as a move.

Regression coverage: `reborn_product_contract_location_scan.rs` (7 tests, 2
real gates + 5 self-tests with positive and negative fixtures) pins one home
and one import path for the product tier's ports; it fails on the four
re-export chains this change deletes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…1/product-contracts

# Conflicts:
#	crates/ironclaw_extension_contracts/CLAUDE.md
#	crates/ironclaw_extension_contracts/src/lib.rs
#	crates/ironclaw_extension_host/src/extension_lifecycle_command.rs
#	crates/ironclaw_extension_host/src/product_lifecycle.rs
…S1.4 row

The WS1.4 ruling on package_lifecycle survived the parent's #6930
reconciliation; the LifecyclePackageId split is the recorded resolution,
now stated as what happened rather than what was recommended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ss grants (WS1.5)

CHECKLIST WS1's evidence-mint row: one sealed seam owns construction of
protocol-auth evidence, every other construction path is deleted, and the
refute-tests land with it.

The `host-auth-mint` cargo feature was not a seal. Cargo unifies features
across the packages selected in one invocation, so `ironclaw_webui`'s opt-in
(-> turns -> host_api) compiled `ironclaw_host_api` once with the gate ON for
every other crate in the same build. Measured before touching anything: a probe
in `ironclaw_agent_loop` — whose manifest names `ironclaw_host_api` with no
features — minted a verified bearer claim; it failed to compile alone and
passed as soon as `ironclaw_webui` joined the `cargo test`. Every workspace
build is the second case.

Replaced with the repo's existing witness-token idiom (`host_api::authorized`),
which no other crate's manifest can switch on:

- `HostAuthenticationGrant` <- `HostProtocolAuthenticator`, sole implementor
  `ironclaw_webui` (module-private `AuthLayerState`, trust stage T1).
- `VerifiedInboundGrant` <- `ChannelIngressVerifier`, sole implementor
  `ironclaw_extension_host` (`VerifiedEvidenceMint`, trust stage T2).
- Channel/webhook mint family -> `ironclaw_extension_contracts::verified_inbound`
  (§6.1.2); bearer/session family stays in `host_api` (§6.1.1). The evidence
  type does not move; `extension_contracts` reaches the private verified variant
  through the grant-gated `ProtocolAuthEvidence::seal_verified_inbound`.

Closed: the feature in 5 manifests + 1 CI recipe; four re-export chains
(`host_api::product_adapter`, `ironclaw_product` root, `ironclaw_product::auth`,
and composition's zero-consumer re-export).

Enforcement: `reborn_sealed_evidence_mint_ratchet` (10 tests, §11.2.5) plus
`host_api/tests/protocol_auth_evidence_seal.rs` (5) and
`extension_contracts/tests/verified_inbound_seal.rs` (4). +19 tests, 0 removed,
no surviving assertion edited. The production-struct dead-code baseline shrinks
81/282 -> 80/277: the five `dead_code` suppressions in `auth.rs` existed only
because the constructors were feature-gated.
`ironclaw_common` now matches PROPOSAL §6.1.5's *Owns* list exactly, plus
three named exceptions that a pinned rule blocks from moving.

Deleted, not relocated — each re-verified with `git grep` over tracked files
on this base:

* `event.rs` (1,234 lines). All seven exported symbols have zero consumers
  outside the crate; the only live workspace references are negative ones
  (the architecture test asserting the OpenAI-compatible routes must *not*
  stream `AppEvent`, one doc comment, one Python docstring). This confirms
  WS1.4's disposition 7 rather than repeating it, and closes the WS8
  deletion-candidate row. The `AppEvent` `ForbiddenUse` entry stays as a
  reintroduction pin with its reason updated to say the enum is gone — the
  convention the file already applies to the retired v1 `ironclaw::` crate.
* `platform.rs`. `PlatformInfo` has zero references anywhere; §6.1.5's
  "→ its consumer" is unsatisfiable because there is no consumer.
* The four budget constants. Zero consumers workspace-wide, and
  `ironclaw_resources` already governs the same concern with a live,
  differently-shaped mechanism (`ResourceLimits::max_wall_clock_ms`) that
  references none of them — so "→ `resources`" would have seeded four
  unreachable constants beside a working governor.

Moved: `automation` → `ironclaw_triggers`, which owns automations and had
already defined `MAX_TRIGGER_NAME_BYTES` as an alias of the common constant.
This eviction is in §6.1.5 but missing from the CHECKLIST row.
`ironclaw_product`'s cross-crate `pub use` of the newtype (a second import
path with zero external consumers) is deleted rather than re-sourced,
following the WS1.3/WS1.4 dual-path rule.

Already done, so recorded rather than redone: `trust_boundary` went with
#6943, and the `AttachmentRef` collision is already resolved as
`ChannelAttachmentRef`. That rename left a wrong cross-reference in its own
doc comment (`ironclaw_common::ChannelAttachmentRef` for
`ironclaw_common::AttachmentRef`), fixed here.

Not moved, with evidence: `provider_transcript` (its `agent_loop` consumer
is contracts-only by pinned rule; `ironclaw_llm` is substrates),
`model_selection` (`openai_compat`'s boundary rule forbids `ironclaw_llm`
outright, and `llm` never uses the module), and `llm_costs` (`llm` uses 2 of
its 7 public items; moving it would hand `ironclaw_product` a
reqwest/rig-core cone for a pricing table — the `ModelCostTable` port is the
real seam, and that is a WS4 design change). All three are documented in
`crates/ironclaw_common/AGENTS.md`, which also stops claiming ownership of
the long-deleted `trust_boundary`.

Un-masking: `ironclaw_common` 123 → 110 tests (10 `event::tests::*`, each
classified to a deleted symbol; 3 `automation::tests::*` reappearing
verbatim in `ironclaw_triggers`, 166 → 169). Zero unclassified, no surviving
test edited. The extension-specificity gate demanded its now-stale
`platform.rs` carve-out be deleted — the property it was built with.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@railway-app

railway-app Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Aug 1, 2026 at 4:06 am

@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added clearer, consistent explanations for failed agent runs, including checkpoint and model-related failures.
    • Added validation for automation names, including trimming and length checks.
  • Improvements
    • Failure summaries now provide safer, more predictable messaging while avoiding internal implementation details.
    • Shared attachment and identity capabilities remain available with clearer ownership documentation.
  • Documentation
    • Updated architecture guidance and release planning records to reflect completed platform simplification work.

Walkthrough

Changes

Common crate boundary

Layer / File(s) Summary
Common ownership contract
crates/ironclaw_common/AGENTS.md, crates/ironclaw_common/src/lib.rs, crates/ironclaw_extension_contracts/src/channel_adapter.rs
The common crate documents domain-free ownership and retained LLM-data exceptions. It removes application-domain modules, re-exports, constants, and an obsolete documentation reference.
Boundary scans and architecture records
crates/ironclaw_architecture/tests/*, docs/reborn/target-architecture/*
Architecture scans skip .git and node_modules. Architecture records document the completed Wave 1 boundary changes and AppEvent deletion.

Failure-summary ownership

Layer / File(s) Summary
Host failure vocabulary and summaries
crates/ironclaw_host_api/src/failure*
The host API adds stable failure categories, sanitized summary lookup, checkpoint-detail validation, pinned summaries, and regression tests.
Runner writer and visibility migration
crates/ironclaw_runner/src/*
Runner code uses host API failure categories and summary constants. The runner retains checkpoint writing and makes its former summary modules private.
Product failure-summary integration
crates/ironclaw_product/Cargo.toml, crates/ironclaw_product/prompts/*, crates/ironclaw_product/src/projection/*
Product code resolves summaries through host API, removes its production runner dependency, and owns the failure-explanation prompt.
CLI and facade consumer cleanup
crates/ironclaw_reborn_cli/src/runtime/mod.rs, crates/ironclaw_reborn_composition/src/lib.rs
The CLI uses the host API summary resolver. The composition facade removes its summary re-export.

Automation-name ownership

Layer / File(s) Summary
Validated automation-name type
crates/ironclaw_triggers/src/automation.rs
Triggers adds validated AutomationName and AutomationNameError types with serde support, conversions, and tests.
Triggers exports and storage wiring
crates/ironclaw_triggers/src/*, crates/ironclaw_triggers/tests/*
Triggers re-exports the local types and updates database adapters and repository tests to use them.
Product automation imports
crates/ironclaw_product/src/*, crates/ironclaw_product/tests/*
Product removes automation-name re-exports and imports the types directly from ironclaw_triggers.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Product
  participant HostApi
  participant Runner
  participant CLI
  Product->>HostApi: resolve failure category and detail
  Runner->>HostApi: validate checkpoint rejection detail
  HostApi-->>Product: return sanitized failure summary
  CLI->>HostApi: resolve failed-reply summary
  HostApi-->>CLI: return category summary
Loading

Possibly related issues

  • nearai/ironclaw#6284 — Failure-category and summary relocation supports model-visible error recoverability.
  • nearai/ironclaw#4470 — Common-crate ownership tightening and architecture checks enforce crate boundaries.
  • nearai/ironclaw#6198 — AppEvent deletion and failure-summary relocation advance Reborn cleanup.

Suggested reviewers: serrrfirat, ilblackdragon, think-in-universe

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description gives detailed change and verification evidence but omits most required template sections, including change type, test strategy, security, database, blast radius, rollback, and review follow-through. Rewrite the description using every template heading; mark non-applicable sections explicitly and retain the current change, risk, and verification details.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately identifies the ironclaw_common narrowing and product-to-runner edge removal covered by the changeset.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6982 August 1, 2026 00:37 Destroyed
@github-actions github-actions Bot added scope: docs Documentation size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Aug 1, 2026
@ironloopai

ironloopai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Review · PR #6982

🟢 Completed · Review submitted

Submitted review →

Reviewed the complete trusted base-to-head comparison. No concrete correctness, security, architectural, or test-coverage defects were found. The failure-category and summary relocation preserves producer classification, summary mappings, checkpoint-envelope validation, and product projection behavior; deleted common types/constants had no live consumers; automation ownership and prompt/import rewrites were consistently applied.

Automatic · PR opened · attempt 1 of 3 · completed in 1m 34s

Run details
  • Repository: nearai/ironclaw
  • Base: ws1/evidence-mint at 889b3a1
  • Head: ws1/wave-close at f9e74c9
  • Created: Aug 1, 2026, 12:42 AM UTC
  • Updated: Aug 1, 2026, 12:43 AM UTC
  • Run: 16d661ad-6be8-4a21-b31a-a5fa82a09207
  • Latest attempt: 1 · Completed · 442daeb9-a91f-493e-b094-500690cba148

@ironloopai ironloopai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Review complete · PR #6982

✅ No actionable findings

Reviewed the complete trusted base-to-head comparison. No concrete correctness, security, architectural, or test-coverage defects were found. The failure-category and summary relocation preserves producer classification, summary mappings, checkpoint-envelope validation, and product projection behavior; deleted common types/constants had no live consumers; automation ownership and prompt/import rewrites were consistently applied.

Validation and technical details
  • Inspected all 41 changed files across refs/ironloop/base (889b3a1) to refs/ironloop/head (f9e74c9), including surrounding call sites and crate-local guidance.
  • Verified base-tree consumers for deleted AppEvent/platform/budget exports; no live code consumers were found outside the deleted definitions. Remaining AppEvent mentions are documentation or architecture reintroduction pins.
  • Compared the moved failure category constants and summary functions with their base runner implementations, including model-invalid-output detail mapping and pinned category summaries.
  • Validated checkpoint-rejection writer/reader compatibility across the four checkpoint stages and confirmed LoopSafeSummary delegates to the same canonical SafeSummary validator; its sole sentinel bypass currently passes canonical validation.
  • Checked automation type relocation, product/CLI/composition dependency rewrites, prompt asset ownership, architecture scanner exclusions, and the remaining small import/path changes.
  • git diff --check refs/ironloop/base..refs/ironloop/head completed cleanly.
  • Focused Cargo tests could not be rerun because cargo is unavailable in the review environment (cargo: command not found); test coverage and source assertions were inspected directly.
  • Base: ws1/evidence-mint
  • Head: ws1/wave-close at f9e74c9
  • Run: 16d661ad-6be8-4a21-b31a-a5fa82a09207

# Conflicts:
#	crates/AGENTS.md
#	crates/ironclaw_architecture/tests/reborn_extension_contract_location_scan.rs
#	crates/ironclaw_architecture/tests/reborn_loop_port_location_scan.rs
#	crates/ironclaw_architecture/tests/reborn_product_contract_location_scan.rs
#	crates/ironclaw_extension_contracts/CLAUDE.md
#	crates/ironclaw_extension_contracts/src/auth_prompt.rs
#	crates/ironclaw_extension_contracts/src/lifecycle_id.rs
#	crates/ironclaw_product/src/lib.rs
#	crates/ironclaw_product/src/projection/tests/failure_explanation.rs
#	crates/ironclaw_product/tests/reborn_services_contract.rs
#	crates/ironclaw_runner/src/failure_summary.rs
#	crates/ironclaw_runner/src/planned_driver.rs
#	docs/reborn/target-architecture/CHECKLIST.md
#	tests/integration/changed-coverage-exemptions.toml
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6982 August 1, 2026 03:56 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_host_api/src/failure/categories.rs`:
- Around line 1-21: The documentation references the nonexistent producer crate
name `ironclaw_turn_runner`; replace it with `ironclaw_runner` in
`crates/ironclaw_host_api/src/failure/categories.rs` lines 1-21,
`crates/ironclaw_host_api/src/failure.rs` lines 22-33, and
`crates/ironclaw_host_api/src/failure/summary.rs` lines 1-21, including the
module-qualified reference in the summary documentation. No other changes are
required.

In `@crates/ironclaw_host_api/src/failure/summary.rs`:
- Around line 82-90: Remove the unreachable CHECKPOINT_REJECTED_CATEGORY arm
from the match in reborn_failure_summary_for_category. Keep
pinned_failure_summary_for_category as the sole mapping for that category and
preserve all other category mappings unchanged.

In `@crates/ironclaw_triggers/src/automation.rs`:
- Around line 78-104: Add coverage in the existing automation_name tests for
serialized output and UTF-8 byte boundaries: serialize a valid AutomationName
with serde_json and assert the expected JSON string, then exercise a multibyte
name at the allowed MAX_AUTOMATION_NAME_BYTES limit and one byte beyond it,
preserving the expected acceptance and TooLong rejection.
- Around line 23-32: Canonicalize trigger names before validation and
persistence in create_trigger: construct an AutomationName from input.name,
extract its canonical value with into_inner(), and store that value in the
TriggerRecord before calling validate() and upsert_trigger. This ensures trimmed
names are persisted and length checks apply to the canonical value; leave the
existing rename flow unchanged.

In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 146: Update the checklist entry’s executed deletion wording to state that
ironclaw_common/src/event.rs was deleted, removing the contradictory claim that
it remains in common as a deletion candidate. Preserve the surrounding evidence
and WS1 cross-reference.

In `@docs/reborn/target-architecture/PLAN.md`:
- Line 37: Update the Wave 1 closure sentence in the plan to replace the
inaccurate “two product edges” claim with the completed product → runner
migration and the product-owned prompt move. Keep product → loop_host explicitly
open, consistent with CHECKLIST.md.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 91f22109-6dfa-4125-8eef-687a4721eedf

📥 Commits

Reviewing files that changed from the base of the PR and between 6daf0b7 and 4c85360.

📒 Files selected for processing (41)
  • crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs
  • crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
  • crates/ironclaw_architecture/tests/reborn_loop_port_location_scan.rs
  • crates/ironclaw_common/AGENTS.md
  • crates/ironclaw_common/src/event.rs
  • crates/ironclaw_common/src/lib.rs
  • crates/ironclaw_common/src/platform.rs
  • crates/ironclaw_extension_contracts/src/channel_adapter.rs
  • crates/ironclaw_host_api/src/failure.rs
  • crates/ironclaw_host_api/src/failure/categories.rs
  • crates/ironclaw_host_api/src/failure/summary.rs
  • crates/ironclaw_loop_host/src/lib.rs
  • crates/ironclaw_product/Cargo.toml
  • crates/ironclaw_product/prompts/failure_explanation.md
  • crates/ironclaw_product/src/automation_product_service.rs
  • crates/ironclaw_product/src/automation_product_service/tests/mutation_tests.rs
  • crates/ironclaw_product/src/lib.rs
  • crates/ironclaw_product/src/projection/tests/failure_explanation.rs
  • crates/ironclaw_product/src/projection/turn_events.rs
  • crates/ironclaw_product/src/reborn_services.rs
  • crates/ironclaw_product/tests/reborn_services_contract.rs
  • crates/ironclaw_reborn_cli/src/runtime/mod.rs
  • crates/ironclaw_reborn_composition/src/lib.rs
  • crates/ironclaw_runner/src/failure_categories.rs
  • crates/ironclaw_runner/src/failure_lane.rs
  • crates/ironclaw_runner/src/failure_summary.rs
  • crates/ironclaw_runner/src/lib.rs
  • crates/ironclaw_runner/src/model_failure_mapping.rs
  • crates/ironclaw_runner/src/planned_driver.rs
  • crates/ironclaw_runner/src/retry_disposition.rs
  • crates/ironclaw_runner/src/text_loop_driver.rs
  • crates/ironclaw_runner/src/turn_runner.rs
  • crates/ironclaw_runner/src/turn_scheduler.rs
  • crates/ironclaw_triggers/src/automation.rs
  • crates/ironclaw_triggers/src/lib.rs
  • crates/ironclaw_triggers/src/libsql.rs
  • crates/ironclaw_triggers/src/postgres.rs
  • crates/ironclaw_triggers/tests/repository_contract.rs
  • docs/plans/composition-pubuse.snapshot
  • docs/reborn/target-architecture/CHECKLIST.md
  • docs/reborn/target-architecture/PLAN.md
💤 Files with no reviewable changes (7)
  • crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
  • docs/plans/composition-pubuse.snapshot
  • crates/ironclaw_common/src/platform.rs
  • crates/ironclaw_loop_host/src/lib.rs
  • crates/ironclaw_product/src/lib.rs
  • crates/ironclaw_common/src/event.rs
  • crates/ironclaw_reborn_composition/src/lib.rs

Comment on lines +1 to +21
//! Failure-category identifiers — the closed key vocabulary of the
//! category→summary tables in [`super::summary`].
//!
//! These are pure data: stable `&'static str` identifiers that producers stamp
//! onto a failed run and that projections key user-facing copy from. They live
//! here, not in the crate that produces them, because both sides of that
//! relationship are above this crate — the producer (`ironclaw_turn_runner`)
//! and the consumer (the product projection) — and a category constant is
//! kernel vocabulary, not runner implementation (PROPOSAL §6.1.1: "failure-summary
//! data … joins the existing `failure` module so product stops depending on
//! runner").
//!
//! **Classification is not data and did not move.** The functions that decide
//! *which* category a failure gets (`host_stage_unavailable_category`,
//! `failure_lane`, the retry disposition) stay with the producer: they are typed
//! on loop/agent-loop vocabulary that this crate must never depend on.
//!
//! Adding a constant here obliges a matching arm in [`super::summary`]; the
//! product projection's `failure_summary_covers_reborn_failure_category_constants`
//! scans this file and fails when the two drift.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Fix the stale ironclaw_turn_runner crate name in the new doc comments.

Three new doc blocks name the producer crate ironclaw_turn_runner. Every other file in this cohort — crates/ironclaw_runner/src/planned_driver.rs, crates/ironclaw_runner/src/failure_categories.rs, crates/ironclaw_runner/src/failure_summary.rs, and crates/ironclaw_product/Cargo.toml — names the actual producer crate ironclaw_runner. ironclaw_turn_runner does not appear anywhere else in the reviewed cohort.

Update the doc text to name the real crate, so future readers and the "search Markdown/doc references for stale paths" step after a move do not chase a nonexistent crate.

  • crates/ironclaw_host_api/src/failure/categories.rs#L1-L21: replace "the producer (ironclaw_turn_runner)" and the repeated mention in the classification paragraph with ironclaw_runner.
  • crates/ironclaw_host_api/src/failure.rs#L22-L33: replace "moved here from ironclaw_turn_runner" with ironclaw_runner.
  • crates/ironclaw_host_api/src/failure/summary.rs#L1-L21: replace "Moved here from ironclaw_turn_runner::failure_summary" and "stayed with the producer (ironclaw_turn_runner)" with ironclaw_runner.

As per path instructions, "After moving or renaming code, search .claude/, AGENTS.md, CLAUDE.md, crates/AGENTS.md, docs/reborn/contracts/, and Markdown references for stale paths."

📍 Affects 3 files
  • crates/ironclaw_host_api/src/failure/categories.rs#L1-L21 (this comment)
  • crates/ironclaw_host_api/src/failure.rs#L22-L33
  • crates/ironclaw_host_api/src/failure/summary.rs#L1-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_host_api/src/failure/categories.rs` around lines 1 - 21, The
documentation references the nonexistent producer crate name
`ironclaw_turn_runner`; replace it with `ironclaw_runner` in
`crates/ironclaw_host_api/src/failure/categories.rs` lines 1-21,
`crates/ironclaw_host_api/src/failure.rs` lines 22-33, and
`crates/ironclaw_host_api/src/failure/summary.rs` lines 1-21, including the
module-qualified reference in the summary documentation. No other changes are
required.

Source: Path instructions

Comment on lines +82 to +90
pub fn reborn_failure_summary_for_category(category: Option<&str>) -> &'static str {
let Some(category) = category else {
return unknown_failure_summary();
};

if let Some(summary) = pinned_failure_summary_for_category(category) {
return summary;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the unreachable CHECKPOINT_REJECTED_CATEGORY arm.

pinned_failure_summary_for_category (Lines 311-331) returns Some(CHECKPOINT_REJECTION_FALLBACK) for CHECKPOINT_REJECTED_CATEGORY before the match at Line 91 runs. The CHECKPOINT_REJECTED_CATEGORY => CHECKPOINT_REJECTION_FALLBACK arm at Line 186 never executes.

This module's own doc states the category table must stay a single source of truth: a constant should map to exactly one summary location, and drift between duplicate mappings must fail a test. This dead arm is a second, unreachable mapping for the same category. If a future edit changes only one of the two spots, the change has no effect, and the drift check does not catch it, because the live value always comes from pinned_failure_summary_for_category.

Remove the dead arm to keep one definition per category, as the module doc requires.

🧹 Proposed fix
-        CHECKPOINT_REJECTED_CATEGORY => CHECKPOINT_REJECTION_FALLBACK,
-        "checkpoint_unavailable" => {
+        "checkpoint_unavailable" => {

Also applies to: 186-186, 311-331

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_host_api/src/failure/summary.rs` around lines 82 - 90, Remove
the unreachable CHECKPOINT_REJECTED_CATEGORY arm from the match in
reborn_failure_summary_for_category. Keep pinned_failure_summary_for_category as
the sole mapping for that category and preserve all other category mappings
unchanged.

- [ ] Crate: `ironclaw_first_party_extension_ports` dissolved **after** WS3 removes the `host_runtime→first_party_extensions` edge (activation machinery → loop_host/skills; observer vocab → skills; bundle asset reader → package).
- [ ] Modules: ~~`skills::{registry,catalog,v2,gating}`~~, ~~`auth::loopback_oauth` (+`urlencoding` dep)~~, ~~`auth::fakes` gated `test-support`~~, ~~`event_projections::{EventStreamManager, PendingGateProjection, DurableMemoryAuditSink}`~~, ~~`events::{parse_jsonl, replay_jsonl}`~~, ~~`memory_native::EmbeddingProvider` + its path-shim re-exports~~, ~~`common::trust_boundary`~~ — **landed in #6943** (seven crates; +231 / −9,000); `filesystem::HsmBackend` (or feature-gate); `secrets::placeholder` (until the egress proxy is built); `host_runtime` sandbox CA moves-or-deletes with WS3; `trust::{SignedRegistry, DevTrustOverride, BundledRegistry}` **[decision — commit or delete]**; `runner::production_readiness` (see WS4); composition `product_live_adapters` exports (see WS6).
- [ ] Deletion candidate (recorded 2026-07-31 by WS1.4): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it stays in `common` as a deletion candidate rather than moving. Evidence in PR #6980's body; it also belongs to the "Narrow `ironclaw_common`" row in WS1.
- [x] ~~Deletion candidate (recorded 2026-07-31 by WS1.4)~~ — **executed by the WS1.6 PR** (see the `ironclaw_common` narrowing row in WS1 for the re-verification and un-masking): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it stays in `common` as a deletion candidate rather than moving. Evidence in PR #6980's body; it also belongs to the "Narrow `ironclaw_common`" row in WS1.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the executed deletion wording.

Line 146 marks event.rs as deleted, but then says it “stays in common as a deletion candidate.” State that the file was deleted rather than leaving a stale follow-up action.

Proposed wording
- ... it stays in `common` as a deletion candidate rather than moving.
+ ... it was deleted rather than moved.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- [x] ~~Deletion candidate (recorded 2026-07-31 by WS1.4)~~ — **executed by the WS1.6 PR** (see the `ironclaw_common` narrowing row in WS1 for the re-verification and un-masking): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it stays in `common` as a deletion candidate rather than moving. Evidence in PR #6980's body; it also belongs to the "Narrow `ironclaw_common`" row in WS1.
- [x] ~~Deletion candidate (recorded 2026-07-31 by WS1.4)~~ — **executed by the WS1.6 PR** (see the `ironclaw_common` narrowing row in WS1 for the re-verification and un-masking): `ironclaw_common/src/event.rs` — the 1,234-line `AppEvent` wire enum has **zero consumers outside `ironclaw_common` itself**, the only other workspace reference being an architecture test asserting the Reborn OpenAI-compatible routes must *not* stream it; §6.1.3 had assigned it to `product_contracts` on the premise that a transport streams it, which the re-measurement refuted, so it was deleted rather than moved. Evidence in PR `#6980`'s body; it also belongs to the "Narrow `ironclaw_common`" row in WS1.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/reborn/target-architecture/CHECKLIST.md` at line 146, Update the
checklist entry’s executed deletion wording to state that
ironclaw_common/src/event.rs was deleted, removing the contradictory claim that
it remains in common as a deletion candidate. Preserve the surrounding evidence
and WS1 cross-reference.

- Order inside the wave: turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence-mint consolidation ⚠ (security-sensitive; its refute-tests land in the same PR) → `common` narrowing → the two single-symbol product edges (`runner`, `loop_host`).
- Each new crate lands with its §11.2.3 allowlist + §11.2.4 port-location scan in the same PR (new-crate-adds-rule discipline).
- **Milestone:** exceptions 20 → 12 (all W4.3 + `auth→turns` gone); `agent_loop` passes contracts-only with zero exceptions; webui/openai/channel crates *can* now compile against contracts (the flips happen in Wave 2).
- ✎ **Wave 1 closed 2026-07-31** across seven PRs (turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence mint → `common` narrowing + the two product edges): all three contracts crates exist and `agent_loop` is contracts-only with zero exceptions, but **exceptions ended at 13, not 12** — `conversations → turns` is turn *admission authority*, not vocabulary, so it falls in WS5 and the 12 in this milestone was never reachable here. Two findings worth carrying: the `host-auth-mint` cargo feature was never a seal (feature unification compiled the mint gate on for the whole workspace), and every slot found its lead-sheet row partly stale — re-measure rows against your own base, never inherit a predecessor's count. Full end-state in CHECKLIST's WS1 verify row.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the Wave 1 closure scope.

Line 37 says the seven PRs completed “the two product edges.” CHECKLIST.md Line 58 states that product → loop_host remains open; only the prompt asset moved. Name the completed product → runner migration and the product-owned prompt move instead.

Proposed wording
- ... common narrowing + the two product edges):
+ ... common narrowing + the product → runner failure-summary move and the product-owned prompt asset):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- ✎ **Wave 1 closed 2026-07-31** across seven PRs (turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence mint → `common` narrowing + the two product edges): all three contracts crates exist and `agent_loop` is contracts-only with zero exceptions, but **exceptions ended at 13, not 12** — `conversations → turns` is turn *admission authority*, not vocabulary, so it falls in WS5 and the 12 in this milestone was never reachable here. Two findings worth carrying: the `host-auth-mint` cargo feature was never a seal (feature unification compiled the mint gate on for the whole workspace), and every slot found its lead-sheet row partly stale — re-measure rows against your own base, never inherit a predecessor's count. Full end-state in CHECKLIST's WS1 verify row.
- ✎ **Wave 1 closed 2026-07-31** across seven PRs (turn vocabulary → `loop_contracts` → `extension_contracts` → `product_contracts` → evidence mint → `common` narrowing + the product → runner failure-summary move and the product-owned prompt asset): all three contracts crates exist and `agent_loop` is contracts-only with zero exceptions, but **exceptions ended at 13, not 12** — `conversations → turns` is turn *admission authority*, not vocabulary, so it falls in WS5 and the 12 in this milestone was never reachable here. Two findings worth carrying: the `host-auth-mint` cargo feature was never a seal (feature unification compiled the mint gate on for the whole workspace), and every slot found its lead-sheet row partly stale — re-measure rows against your own base, never inherit a predecessor's count. Full end-state in CHECKLIST's WS1 verify row.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/reborn/target-architecture/PLAN.md` at line 37, Update the Wave 1
closure sentence in the plan to replace the inaccurate “two product edges” claim
with the completed product → runner migration and the product-owned prompt move.
Keep product → loop_host explicitly open, consistent with CHECKLIST.md.

Source: Path instructions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_host_api/src/failure/categories.rs`:
- Around line 1-21: The documentation references the nonexistent producer crate
name `ironclaw_turn_runner`; replace it with `ironclaw_runner` in
`crates/ironclaw_host_api/src/failure/categories.rs` lines 1-21,
`crates/ironclaw_host_api/src/failure.rs` lines 22-33, and
`crates/ironclaw_host_api/src/failure/summary.rs` lines 1-21, including the
module-qualified reference in the summary documentation. No other changes are
required.

In `@crates/ironclaw_host_api/src/failure/summary.rs`:
- Around line 82-90: Remove the unreachable CHECKPOINT_REJECTED_CATEGORY arm
from the match in reborn_failure_summary_for_category. Keep
pinned_failure_summary_for_category as the sole mapping for that category and
preserve all other category mappings unchanged.

In `@crates/ironclaw_triggers/src/automation.rs`:
- Around line 78-104: Add coverage in the existing automation_name tests for
serialized output and UTF-8 byte boundaries: serialize a valid AutomationName
with serde_json and assert the expected JSON string, then exercise a multibyte
name at the allowed MAX_AUTOMATION_NAME_BYTES limit and one byte beyond it,
preserving the expected acceptance and TooLong rejection.
- Around line 23-32: Canonicalize trigger names before validation and
persistence in create_trigger: construct an AutomationName from input.name,
extract its canonical value with into_inner(), and store that value in the
TriggerRecord before calling validate() and upsert_trigger. This ensures trimmed
names are persisted and length checks apply to the canonical value; leave the
existing rename flow unchanged.

In `@docs/reborn/target-architecture/CHECKLIST.md`:
- Line 146: Update the checklist entry’s executed deletion wording to state that
ironclaw_common/src/event.rs was deleted, removing the contradictory claim that
it remains in common as a deletion candidate. Preserve the surrounding evidence
and WS1 cross-reference.

In `@docs/reborn/target-architecture/PLAN.md`:
- Line 37: Update the Wave 1 closure sentence in the plan to replace the
inaccurate “two product edges” claim with the completed product → runner
migration and the product-owned prompt move. Keep product → loop_host explicitly
open, consistent with CHECKLIST.md.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 91f22109-6dfa-4125-8eef-687a4721eedf

📥 Commits

Reviewing files that changed from the base of the PR and between 6daf0b7 and 4c85360.

📒 Files selected for processing (41)
  • crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs
  • crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
  • crates/ironclaw_architecture/tests/reborn_loop_port_location_scan.rs
  • crates/ironclaw_common/AGENTS.md
  • crates/ironclaw_common/src/event.rs
  • crates/ironclaw_common/src/lib.rs
  • crates/ironclaw_common/src/platform.rs
  • crates/ironclaw_extension_contracts/src/channel_adapter.rs
  • crates/ironclaw_host_api/src/failure.rs
  • crates/ironclaw_host_api/src/failure/categories.rs
  • crates/ironclaw_host_api/src/failure/summary.rs
  • crates/ironclaw_loop_host/src/lib.rs
  • crates/ironclaw_product/Cargo.toml
  • crates/ironclaw_product/prompts/failure_explanation.md
  • crates/ironclaw_product/src/automation_product_service.rs
  • crates/ironclaw_product/src/automation_product_service/tests/mutation_tests.rs
  • crates/ironclaw_product/src/lib.rs
  • crates/ironclaw_product/src/projection/tests/failure_explanation.rs
  • crates/ironclaw_product/src/projection/turn_events.rs
  • crates/ironclaw_product/src/reborn_services.rs
  • crates/ironclaw_product/tests/reborn_services_contract.rs
  • crates/ironclaw_reborn_cli/src/runtime/mod.rs
  • crates/ironclaw_reborn_composition/src/lib.rs
  • crates/ironclaw_runner/src/failure_categories.rs
  • crates/ironclaw_runner/src/failure_lane.rs
  • crates/ironclaw_runner/src/failure_summary.rs
  • crates/ironclaw_runner/src/lib.rs
  • crates/ironclaw_runner/src/model_failure_mapping.rs
  • crates/ironclaw_runner/src/planned_driver.rs
  • crates/ironclaw_runner/src/retry_disposition.rs
  • crates/ironclaw_runner/src/text_loop_driver.rs
  • crates/ironclaw_runner/src/turn_runner.rs
  • crates/ironclaw_runner/src/turn_scheduler.rs
  • crates/ironclaw_triggers/src/automation.rs
  • crates/ironclaw_triggers/src/lib.rs
  • crates/ironclaw_triggers/src/libsql.rs
  • crates/ironclaw_triggers/src/postgres.rs
  • crates/ironclaw_triggers/tests/repository_contract.rs
  • docs/plans/composition-pubuse.snapshot
  • docs/reborn/target-architecture/CHECKLIST.md
  • docs/reborn/target-architecture/PLAN.md
💤 Files with no reviewable changes (7)
  • crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
  • docs/plans/composition-pubuse.snapshot
  • crates/ironclaw_common/src/platform.rs
  • crates/ironclaw_loop_host/src/lib.rs
  • crates/ironclaw_product/src/lib.rs
  • crates/ironclaw_common/src/event.rs
  • crates/ironclaw_reborn_composition/src/lib.rs
🛑 Comments failed to post (2)
crates/ironclaw_triggers/src/automation.rs (2)

23-32: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -U -C 6 \
  'AutomationName::new|validate_trigger_name|TriggerRecord\s*\{|record\.name|name\.into_inner\(\)' \
  crates/ironclaw_triggers crates/ironclaw_product

Repository: nearai/ironclaw

Length of output: 35301


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'automation.rs|lib.rs|postgres.rs|libsql.rs|reborn_services.rs' crates/ironclaw_triggers crates/ironclaw_product | sed 's#^\./##'

echo "== automation.rs =="
sed -n '1,140p' crates/ironclaw_triggers/src/automation.rs

echo "== lib.rs trigger section =="
sed -n '300,430p' crates/ironclaw_triggers/src/lib.rs

echo "== postgres triggers =="
rg -n -C 8 'fn upsert_trigger|upsert.*trigger|INSERT|UPDATE|REPLACE SCOPED|rename_scoped_trigger|record\.name|validation' crates/ironclaw_triggers/src/postgres.rs crates/ironclaw_triggers/src/libsql.rs

echo "== product automation mutation =="
rg -n -C 10 'AutomationMutationAction|create_|automation|AutomationName::new|Request.*name|reborn_services.rs|product_service.rs' crates/ironclaw_product/src/automation_product_service.rs crates/ironclaw_product/src/reborn_services.rs

echo "== migrations/schema references =="
rg -n -C 5 'CREATE TABLE.*trigger|trigger.*name|name.*trigger|autos|automation|trigger_name|max_length|CHARACTER VARYING|TRIGGER' crates scripts docs --glob '*.rs' --glob '*.sql' --glob '*.md' 2>/dev/null | head -n 240

Repository: nearai/ironclaw

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== constants and AutomationName =="
sed -n '1,120p' crates/ironclaw_triggers/src/automation.rs

echo "== validate_trigger_name and TriggerRecord =="
sed -n '333,405p' crates/ironclaw_triggers/src/lib.rs

echo "== in_memory write sections around name =="
sed -n '120,195p' crates/ironclaw_triggers/src/in_memory.rs

echo "== PostgreSQL upsert/rename/write section =="
sed -n '120,175p' crates/ironclaw_triggers/src/postgres.rs
sed -n '360,405p' crates/ironclaw_triggers/src/postgres.rs
sed -n '1320,1360p' crates/ironclaw_triggers/src/postgres.rs

echo "== libSQL read/write sections =="
sed -n '580,635p' crates/ironclaw_triggers/src/libsql.rs
sed -n '1340,1385p' crates/ironclaw_triggers/src/libsql.rs
sed -n '1470,1510p' crates/ironclaw_triggers/src/libsql.rs
sed -n '1680,1725p' crates/ironclaw_triggers/src/libsql.rs

echo "== schema/migration mentions =="
rg -n -C 4 --glob '*.sql' --glob '*.rs' 'TRIGGER TABLE|trigger_name|CHARACTER VARYING|name.*trigger|CREATE TABLE.*trigger|MAX_TRIGGER_NAME_BYTES|MAX_AUTOMATION_NAME_BYTES' crates scripts docs 2>/dev/null

Repository: nearai/ironclaw

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate trigger creation APIs =="
rg -n -C 12 'create_scoped_trigger|upsert_scoped_trigger|insert_scoped_trigger|fn .*trigger|TriggerCreate|create_trigger|create.*automation|Request.*name|automation_request|name.*AutomationName|name: Option' \
  crates/ironclaw_triggers crates/ironclaw_product crates/ironclaw_host_runtime/src/first_party_tools/trigger_management.rs \
  --glob '*.rs' | sed -n '1,260p'

echo "== automation request DTO definitions =="
rg -n -C 8 'struct .*Automation.*Request|AutomationCreate|TriggerCreate|TriggerRecord.*name|name: String|name: Option<String>' crates --glob '*.rs' | sed -n '1,260p'

echo "== deterministic bound behavior probe =="
python3 - <<'PY'
from pathlib import Path
text = Path('crates/ironclaw_triggers/src/automation.rs').read_text()
for start in text.index('pub fn new('), text.index('fn validate('), text.index('impl TriggerRecord'):
    print(text[start:start+800])
PY

Repository: nearai/ironclaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== trigger_management create_trigger =="
sed -n '446,570p' crates/ironclaw_host_runtime/src/first_party_tools/trigger_management.rs

echo "== TriggerRepository trait and upsert signature =="
grep -n "trait TriggerRepository|upsert_trigger|rename_scoped_trigger|create_scoped_trigger" crates/ironclaw_triggers/src/lib.rs crates/ironclaw_triggers/src/*.rs

echo "== repository implementation signatures =="
rg -n -A 8 -B 3 'impl .*TriggerRepository|async fn upsert_trigger|async fn create_scoped_trigger|rename_scoped_trigger' crates/ironclaw_triggers crates/ironclaw_product/src/automation_product_service.rs --glob '*.rs'

echo "== deterministic trim-before-validation behavior =="
python3 - <<'PY'
MAX = 256
for raw in ['x' * MAX + ' ', ' ' + 'x' * MAX, '  ' + 'x' * (MAX + 1), 'x' * MAX + '  ']:
    trimmed = raw.strip()
    print({
        'raw_len': len(raw),
        'trimmed_len': len(trimmed),
        'raw': repr(raw),
        'trimmed': repr(trimmed),
        'accepts_trimmed': 0 < len(trimmed) <= MAX,
        'rejected_by_trim_then_validate': not (0 < len(trimmed) <= MAX),
    })
PY

Repository: nearai/ironclaw

Length of output: 4919


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo trait/impl quick list =="
rg -n "trait TriggerRepository|upsert_trigger|create_scoped_trigger|rename_scoped_trigger|impl TriggerRepository" crates/ironclaw_triggers --glob '*.rs'

echo "== trigger_management validation and repo write calls =="
rg -n -C 4 'record\.validate|validate_trigger_name|upsert_trigger|remove_trigger|TriggerCreateHook|trigger_create' crates/ironclaw_host_runtime/src/first_party_tools/trigger_management.rs crates/ironclaw_product/src/automation_product_service.rs --glob '*.rs'

echo "== product creation endpoint candidates =="
python3 - <<'PY'
from pathlib import Path
for p in Path('crates').rglob('*.rs'):
    txt= p.read_text(errors='ignore')
    if 'AutomationCreate' in txt or 'create_automation' in txt or 'TriggerRecord<' in txt or 'validate_trigger_name' in txt:
        print(p)
PY

Repository: nearai/ironclaw

Length of output: 46857


Canonicalize trigger names before all writes.

TriggerRecord::validate uses validate_trigger_name(&self.name), which trims in AutomationName::new but does not update self.name; then upsert_trigger writes record.name unchanged. create_trigger passes input.name into TriggerRecord directly, so a 256-byte name plus a trailing space is accepted and persisted before the 256-byte name.len() > MAX_AUTOMATION_NAME_BYTES check rejects it. Store the trimmed AutomationName::new(input.name).into_inner() before record.validate() / upsert_trigger. Rename already uses AutomationName and persists name.into_inner().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_triggers/src/automation.rs` around lines 23 - 32,
Canonicalize trigger names before validation and persistence in create_trigger:
construct an AutomationName from input.name, extract its canonical value with
into_inner(), and store that value in the TriggerRecord before calling
validate() and upsert_trigger. This ensures trimmed names are persisted and
length checks apply to the canonical value; leave the existing rename flow
unchanged.

Source: Path instructions


78-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add serialization and UTF-8 boundary coverage.

The tests cover deserialization but not serialized output. The ASCII overflow case does not lock down the documented byte limit for multibyte names. Add both checks.

Proposed regression coverage
+    #[test]
+    fn automation_name_serializes_canonically_and_enforces_utf8_bytes() {
+        let name = AutomationName::new("  Daily status  ").expect("valid name");
+        assert_eq!(
+            serde_json::to_string(&name).expect("serialize name"),
+            "\"Daily status\""
+        );
+        assert!(AutomationName::new("é".repeat(128)).is_ok());
+        assert_eq!(
+            AutomationName::new(format!("{}x", "é".repeat(128))),
+            Err(AutomationNameError::TooLong)
+        );
+    }

This protects the public Serde and byte-limit contract.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn automation_name_trims_and_validates() {
        let name = AutomationName::new("  Daily status  ").expect("valid name");
        assert_eq!(name.as_str(), "Daily status");
    }

    #[test]
    fn automation_name_rejects_blank_and_too_long() {
        assert_eq!(AutomationName::new("  "), Err(AutomationNameError::Empty));
        assert_eq!(
            AutomationName::new("x".repeat(MAX_AUTOMATION_NAME_BYTES + 1)),
            Err(AutomationNameError::TooLong)
        );
    }

    #[test]
    fn automation_name_deserializes_through_validation() {
        let name: AutomationName =
            serde_json::from_str("\"  Daily status  \"").expect("valid serialized automation name");
        assert_eq!(name.as_str(), "Daily status");
        assert!(serde_json::from_str::<AutomationName>("\"   \"").is_err());
    }

    #[test]
    fn automation_name_serializes_canonically_and_enforces_utf8_bytes() {
        let name = AutomationName::new("  Daily status  ").expect("valid name");
        assert_eq!(
            serde_json::to_string(&name).expect("serialize name"),
            "\"Daily status\""
        );
        assert!(AutomationName::new("é".repeat(128)).is_ok());
        assert_eq!(
            AutomationName::new(format!("{}x", "é".repeat(128))),
            Err(AutomationNameError::TooLong)
        );
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_triggers/src/automation.rs` around lines 78 - 104, Add
coverage in the existing automation_name tests for serialized output and UTF-8
byte boundaries: serialize a valid AutomationName with serde_json and assert the
expected JSON string, then exercise a multibyte name at the allowed
MAX_AUTOMATION_NAME_BYTES limit and one byte beyond it, preserving the expected
acceptance and TooLong rejection.

@BenKurrek

Copy link
Copy Markdown
Collaborator Author

Coordinator sign-off (Wave 1 slot 6 — WS1.6 common narrowing + WS1.7 edges; the wave-closer).

  • The second-reader pass on the validator swap is done and CONFIRMED — re-derived from source, not accepted from the write-up: validate_loop_safe_summary's entire body is one sentinel early-return plus delegation to the canonical SafeSummary::new; the sentinel (a fixed 35-byte lowercase sentence) independently satisfies every canonical rejection rule, so the bypass was redundant and the two validators accept identical input sets. The pin test (loop_input_encode_sentinel_needs_no_bypass_here) turns any future divergence into a loud failure instead of a silent narrowing of user-facing failure copy.
  • Net −1,902 lines: AppEvent (1,234 lines) and two more zero-consumer modules deleted with full un-masking; automation re-homed; the three LLM evictions refuted by pinned boundary rules with the real seam recorded as WS4 design work; product → runner severed; product → loop_host honestly NOT severed — the row stays unticked with both behavioral sites documented, one previously recorded nowhere.
  • Collapse proven byte-exactly (content-hash identical after index-line strip; every prior slice's checklist ticks and docs(target-architecture): reconcile with #6930 hosted-MCP registration #6979's annotations verified surviving by line-diff; marker sweep including staged content clean).
  • Wave-exit docs verified against the merged tree: exceptions enumerate to exactly 13 in source, matching the docs; both carve-outs recorded; PLAN carries the dated Wave 1 close.
  • ironloopai's independent review: no defects found, relocation semantics and zero-consumer deletions explicitly validated.

Ready to merge on settle (50 success / 0 failure at time of writing, four routine lanes finishing; if the changed-coverage gate fires on the coverage-report job, the steward lands the replay-derived closure — its preparatory analysis shows no genuine untested holes in the new summary surface, only tier mismatch). This is Wave 1's final merge.

@BenKurrek
BenKurrek merged commit a50ad06 into main Aug 1, 2026
62 of 63 checks passed
@BenKurrek
BenKurrek deleted the ws1/wave-close branch August 1, 2026 04:14
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.92% (323406 / 376404 lines)
  floor:    85.11% (tolerance 0.5pp -> effective floor 84.61%)
  denominator: 376404 lines now vs 375097 at floor capture (+1307 lines, +0.35%) — not a material change

RATCHET PASS: ironclaw_runner
  observed: 85.93% (14917 / 17359 lines)
  floor:    85.55% (tolerance 0.5pp -> effective floor 85.05%)
  floor_covered_lines: 14658 (tolerance 20 lines -> effective floor 14638)
  denominator: 17359 lines now vs 17133 at floor capture (+226 lines, +1.32%) — not a material change

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

RATCHET PASS: ironclaw_turns
  observed: 88.46% (3709 / 4193 lines)
  floor:    85.11% (tolerance 0.5pp -> effective floor 84.61%)

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

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

RATCHET PASS: ironclaw_secrets
  observed: 85.81% (2896 / 3375 lines)
  floor:    84.01% (tolerance 0.5pp -> effective floor 83.51%)
  floor_covered_lines: 2795 (tolerance 20 lines -> effective floor 2775)
  denominator: 3375 lines now vs 3327 at floor capture (+48 lines, +1.44%) — not a material change

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

RATCHET PASS: ironclaw_llm
  observed: 79.22% (20885 / 26364 lines)
  floor:    79.22% (tolerance 0.5pp -> effective floor 78.72%)
  floor_covered_lines: 20885 (tolerance 20 lines -> effective floor 20865)
  denominator: 26364 lines now vs 26364 at floor capture (+0 lines, +0%) — not a material change

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

RATCHET PASS: ironclaw_product
  observed: 87.38% (22831 / 26129 lines)
  floor:    86.94% (tolerance 0.5pp -> effective floor 86.44%)
  floor_covered_lines: 21367 (tolerance 20 lines -> effective floor 21347)
  denominator: 26129 lines now vs 24576 at floor capture (+1553 lines, +6.32%) — material change (>5%)

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

RATCHET PASS: ironclaw_extension_host
  observed: 84.99% (24343 / 28641 lines)
  floor:    83.82% (tolerance 0.5pp -> effective floor 83.32%)
  floor_covered_lines: 22271 (tolerance 20 lines -> effective floor 22251)
  denominator: 28641 lines now vs 26569 at floor capture (+2072 lines, +7.8%) — material change (>5%)

RATCHET PASS: ironclaw_events
  observed: 80.55% (1197 / 1486 lines)
  floor:    80.55% (tolerance 0.5pp -> effective floor 80.05%)
  floor_covered_lines: 1197 (tolerance 20 lines -> effective floor 1177)
  denominator: 1486 lines now vs 1486 at floor capture (+0 lines, +0%) — not a material change

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

RATCHET PASS: ironclaw_host_runtime
  observed: 88.41% (21338 / 24135 lines)
  floor:    88.23% (tolerance 0.5pp -> effective floor 87.73%)
  floor_covered_lines: 20538 (tolerance 20 lines -> effective floor 20518)
  denominator: 24135 lines now vs 23277 at floor capture (+858 lines, +3.69%) — not a material change

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.92% — 323406 / 376404 lines

Per-crate breakdown (63 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_host_ingress 42.5% 17 / 40
ironclaw_memory 53.48% 630 / 1178
ironclaw_projects 72.36% 233 / 322
ironclaw_capabilities 74.59% 2876 / 3856
ironclaw_trust 75.79% 748 / 987
ironclaw_extractors 75.88% 538 / 709
ironclaw_reborn_cli 76.1% 11084 / 14566
ironclaw_observability 76.19% 32 / 42
ironclaw_filesystem 77.04% 5911 / 7673
ironclaw_wasm 78.84% 704 / 893
ironclaw_llm 79.22% 20885 / 26364
ironclaw_product_contracts 79.28% 2066 / 2606
ironclaw_events 80.55% 1197 / 1486
ironclaw_loop_contracts 82.4% 5637 / 6841
ironclaw_first_party_extensions 82.57% 6784 / 8216
ironclaw_memory_native 82.85% 2850 / 3440
ironclaw_libsql_runtime 83.3% 384 / 461
ironclaw_auth 83.95% 6699 / 7980
ironclaw_operator 84.47% 5309 / 6285
ironclaw_hooks 84.57% 9896 / 11702
ironclaw_event_projections 84.81% 854 / 1007
ironclaw_reborn_event_store 84.93% 1206 / 1420
ironclaw_extension_host 84.99% 24343 / 28641
ironclaw_reborn_config 85.29% 2110 / 2474
ironclaw_network 85.31% 894 / 1048
ironclaw_reborn_composition 85.51% 21794 / 25488
ironclaw_extension_contracts 85.64% 2546 / 2973
ironclaw_secrets 85.81% 2896 / 3375
ironclaw_runner 85.93% 14917 / 17359
ironclaw_host_api 86.05% 6367 / 7399
ironclaw_authorization 86.59% 723 / 835
ironclaw_webui 86.95% 11937 / 13729
ironclaw_wasm_limiter 87.06% 74 / 85
ironclaw_common 87.07% 1152 / 1323
ironclaw_product 87.38% 22831 / 26129
ironclaw_reborn_traces 87.61% 11720 / 13377
ironclaw_scripts 87.87% 420 / 478
ironclaw_threads 88.14% 5189 / 5887
ironclaw_host_runtime 88.41% 21338 / 24135
ironclaw_turns 88.46% 3709 / 4193
ironclaw_telegram_extension 88.55% 588 / 664
ironclaw_skills 88.58% 2784 / 3143
ironclaw_process_sandbox 88.64% 281 / 317
ironclaw_processes 88.76% 5889 / 6635
ironclaw_reborn_openai_compat 89.4% 3644 / 4076
ironclaw_telegram_v2_adapter 89.47% 1580 / 1766
ironclaw_extensions 89.55% 6249 / 6978
ironclaw_loop_host 90.47% 18043 / 19944
ironclaw_resources 90.76% 4084 / 4500
ironclaw_approvals 91.05% 1820 / 1999
ironclaw_reborn_identity 91.3% 451 / 494
ironclaw_mcp 92% 1426 / 1550
ironclaw_conversations 92.08% 2383 / 2588
ironclaw_event_streams 92.5% 1048 / 1133
ironclaw_safety 92.75% 4468 / 4817
ironclaw_agent_loop 93.52% 10430 / 11153
ironclaw_slack_extension 93.95% 3697 / 3935
ironclaw_first_party_extension_ports 94.66% 3758 / 3970
ironclaw_outbound 94.68% 4271 / 4511
ironclaw_triggers 94.68% 3134 / 3310
ironclaw_prompt_envelope 97.46% 192 / 197
ironclaw_runtime_policy 97.6% 855 / 876
ironclaw_attachments 98.23% 831 / 846

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

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

@ironclaw-ci ironclaw-ci Bot mentioned this pull request Aug 1, 2026
BenKurrek added a commit that referenced this pull request Aug 1, 2026
…d it overstated

Six review findings triaged against the built tree; four were real.

- `families/contracts.md` landing marker claimed "everything else in
  this file was built as written". Three `ironclaw_loop_contracts`
  divergences contradict it and are now named at the marker and marked
  at the entry: the manifest holds `ironclaw_extension_contracts` and
  holds neither `ironclaw_common` nor `ironclaw_prompt_envelope`; the
  `tokio` carve-out; the embedded prompt asset.
- The evidence-mint guarantee said "compile-time impossibility" and
  "enforced by constructor visibility plus a workspace string-scan pin"
  in one breath. Split: the compiler enforces no-mint-without-a-grant
  (so nothing outside a workspace crate can mint at all); an
  architecture test — a line-oriented substring scan with two named
  evasions — decides which workspace crate may hold one.
- That deferral had no home. §12.1a said "hardening the scan is WS10
  work, listed there"; it was not listed. Added to the WS10 guardrail
  row with both evasions and the call-site census that backstops them.
- CHECKLIST WS8's `common/src/event.rs` row still closed with "stays in
  `common` as a deletion candidate" under an "executed by #6982"
  header. The file is deleted; the tail now says so.

Plus two clarity fixes where a reader could reach a wrong number: the
`(66)`/`(64)` figures in §9/§13 are that table's 2026-07-30 measurement
and now say so beside the live 67, and the row-1 edge count now states
that six of row 1's seven fell while the register moved by seven,
because `auth → turns` is row 9.

Dated amendments only; every replaced phrase is quoted in place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BenKurrek added a commit that referenced this pull request Aug 1, 2026
…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>
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…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>
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…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>

This branch was successfully deployed

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

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant