refactor(reborn): producers emit Resolution directly; delete CapabilityOutcome (§5.3 Stage 2b — collapse complete) - #6293
Conversation
🔎 IronLoop Review StatusHead: Current reviewers:
Reviewer summaries
Recent activity
Available commands
Run metadataAdmission: webhook accepted the request and IronLoop persisted reviewer state before this projection. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request completes the result-lane collapse (§5.3 Stage 2b) by deleting the transitional CapabilityOutcome enum and its mapping helper capability_outcome_to_resolution. In their place, it introduces direct producer-facing constructors in a new resolution module that emit host_api::Resolution directly. All capability producers, synthetic handlers, and extensive test suites across the codebase have been refactored to use these new constructors. There are no review comments to assess, so no further feedback is provided.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
227302a to
cb21ec6
Compare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…claw_turns (§5.3 Stage 2b) New run_profile::resolution module with completed/failed/spawned_process/ spawned_child_run (return Resolution), approval_required/auth_required/ resource_blocked/await_dependent_run/external_tool_pending (return GatedResolution), and denied (returns DeniedResolution). Moves the non-lossy redaction verbatim from resolution_mapping (ModelResultPreview 24 KiB word-boundary redaction, ModelFailureDiagnostic, deterministic GateRef::for_auth_gate auth key, resume-token carry, DependentRunResult inline staged result, DenyReason mapping). resolution_mapping:: capability_outcome_to_resolution is retained transitionally as a thin delegator so unmigrated producers keep compiling; RefBindings is now empty (loop refs ride the channel origin). 17 retargeted constructor tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Stage 2b) capability_port.rs: invoke_capability_dispatch now returns GatedResolution; the seam persists the durable GateRecord from .gate_record (GateRef::for_auth_gate key + replay-payload persistence intact) and returns .resolution — deleting the capability_outcome_to_resolution re-map. runtime_outcome_to_loop and the synthetic/inline dispatch arms emit Resolution/GatedResolution via the new constructors; the runtime failure classifier returns a private LoopFailureClass (Failed | Denied) so the per-tool display preview still stages from the raw fields. lib.rs empty-surface denial, subagent_spawn_port (spawn gates + batch coalescing keyed on the DependentRun channel origin), and capability_surface_filter denials all emit Resolution directly. 378 loop_host tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…age 2b) HookedLoopCapabilityPort::decision_to_outcome and fail_closed_gate_ref_unavailable now return host_api Resolution (deny/approval/auth) via the producer constructors instead of building CapabilityOutcome and re-mapping; single and batch invoke paths consume the Resolution directly. Test double emits resolution::completed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rectly (§5.3 Stage 2b) SyntheticCapabilityHandler::invoke now returns host_api Resolution; the synthetic port and external-tool port delegate it straight through (no capability_outcome_to_resolution re-map). outbound_delivery (approval gate + denial/failure paths, internal ApprovedResumeDecision + approval_lease_outcome now carry Resolution), skill_activation, project_create, result_read (parse error boxed for result_large_err), and external_tool_capability all build Resolution via the producer constructors. Tests assert Resolution::Done recoverable-failure verdicts and Resolution::Denied. 230 local_dev tests green; crate clippy -D warnings clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lution directly (§5.3 Stage 2b) tool_disclosure_port bridge helpers (invoke_bridge/tool_search/describe/ describe_first/completed_bridge_result, failed_invalid_input) and the test double now build host_api Resolution via the producer constructors; subagent flavors test double likewise. No capability_outcome_to_resolution re-map remains in runner producers. Runner tests + clippy -D warnings green (all-features). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…via constructors (§5.3 Stage 2b) shared_await_dependent_gate fixtures (await_dependent/completed/approval) now use the producer constructors directly instead of mapping a CapabilityOutcome. 401 agent_loop tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tcome (§5.3 Stage 2b) MockHost stores Resolution/ResolutionBatch directly; scripted_capability_outcome maps ScriptedCapabilityOutcome -> Resolution via the producer constructors; ~90 executor test fixtures rebuilt as resolution::* + ironclaw_host_api::ResolutionBatch (transformed by a comment/brace-aware script). resolution_from_scripted_outcome deleted; resolution_batch_from_scripted takes Resolutions. 401 agent_loop tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng (§5.3 Stage 2b — collapse complete) Deletes CapabilityOutcome, CapabilityBatchOutcome, CapabilityResultMessage, CapabilityFailure, CapabilityDenied, ProcessHandleSummary from run_profile::host, the resolution_mapping delegator (capability_outcome_to_resolution / MappedResolution / RefBindings), and their re-exports. Retargets the last serde-fixture tests (turn_coordinator, agent_loop_host_contract, content_digest) onto the surviving vocabulary (CapabilityDeniedReasonKind / CapabilityProgress / Resolution). CapabilityApprovalResume/AuthResume/ResumeToken kept (resume requests). Architecture ratchet trims CapabilityOutcome from FROZEN_COLLAPSE_DTOS per its own shrink-only instructions. No production code references CapabilityOutcome; turns + architecture ratchet green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… executor-internal vocabulary Scope correction: these two are NOT dead payloads — the agent_loop executor reconstructs them from host_api::Outcome (capability_result_from_outcome / capability_failure_from_recoverable, added by the flip) and consumes them across gates/capability_helpers/strategies. They are loop-internal working types now (no producer emits them; documented as such). The genuinely-dead CapabilityOutcome/CapabilityBatchOutcome/CapabilityDenied/ProcessHandleSummary stay deleted. Workspace builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ilityOutcome (§5.3 Stage 2b) RecordingTestCapabilityPort (the shared tests/integration double) builds host_api::Resolution via the producer constructors; completed_result returns Resolution, approval/echo paths emit resolution::approval_required/completed directly. Unblocks every reborn_integration_* binary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ge 2b) The seam TODO to delete CapabilityOutcome is done; dispatch emits GatedResolution directly. Comment-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cb21ec6 to
5ec5108
Compare
32dda49
into
integration/reborn-flip-base
… delete CapabilityOutcome/mirror DTOs (§5.3 Slice C) (#6299) * refactor(reborn): resume replay payload moves host-side via ReplayPayloadStore; wire real stores (§5.3 Stage 2a-i) Moves the raw gate/auth resume replay payload out of the untrusted loop and into the host-private `ReplayPayloadStore`, and wires the real durable stores in production composition. The loop-facing result stays `CapabilityOutcome` (no `Resolution` flip — that is Stage 2a-ii). Security fix: the loop checkpoint no longer stores raw tool args, retiring the charter violation in `crates/ironclaw_agent_loop/CLAUDE.md` ("State stores refs... Do not store raw prompts, raw model output, tool args ... in state"). Write (two gate-raise paths): at a FRESH runtime gate raise (`HostRuntimeLoopCapabilityPort::invoke_capability_dispatch`, gated on `is_fresh_dispatch` before `finish_runtime_outcome`) and at the synthetic outbound-delivery approval-gate raise (`outbound_delivery.rs::request_approval`), the host `save`s a `ReplayPayload{input, estimate, prior_approval:None, input_ref, correlation_id}` keyed by `InvocationId` (write-once; a benign same-invocation duplicate is tolerated). Read on resume: `runtime_outcome_to_loop` no longer embeds input/estimate; `invocation_replay_input` is replaced by `ReplayPayloadStore::load` keyed by the `InvocationId` recovered from the resume token, in both the runtime seam (`replay_payload_for_resume`) and the synthetic decorator/handler. Fail closed on a miss: an absent payload (including a wrong-scope read) is a sanitized terminal `Unavailable`, never a silent empty-input dispatch. The fingerprinted approval lease claim ordering is preserved. Dropped loop fields (security): `input`/`estimate` from `CapabilityApprovalResume` and `PendingApprovalResume`; the whole `CapabilityAuthResumeReplay` type and the `replay` field on `CapabilityAuthResume`/`PendingAuthResume`. `approval_request_id`/ `correlation_id`/`input_ref`/`prior_approval` stay on the wire (they move in 2a-ii). Composition (real stores): `capability_wiring` builds `FilesystemGateRecordStore` (closes the #6245 production gap where it defaulted to `NoopGateRecordStore`) and `FilesystemReplayPayloadStore` over the shared scoped filesystem and threads both through the refreshing capability port. Adds the `/replay-payloads` per-user mount alias. `loop_host` gains a dependency-inversion dep on `ironclaw_capabilities` for the port trait. Tests (all failed first): fail-closed on missing payload (loop_host seam); extended the #6245 approval-resume seam test to assert the store persisted the payload and the resume redispatches the correct original input; the `ReplayPayloadStore` contract (round-trip / write-once / cross-tenant / within-tenant) rides the base commit. Full-infra resume edges pass through the production-shaped harness: group_approvals (runtime approval resume), group_journeys (approval→auth convergence), outbound_target (synthetic approval resume). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(turns): move the removed-replay design note out of prior_approval's rustdoc Reported-by: gemini-code-assist (PR #6271 review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(reborn): Resolution carries model-visible failure diagnostic + denial content (§5.3 flip prep / PR-B) Additive vocabulary slice (§3/§5.2.9/§5.3): make `host_api::Resolution` carry the model-visible result content the executor's `handle_capability_outcome` reads off `CapabilityOutcome` today, so a later slice (PR-C) can flip `invoke_capability` to `Resolution` without the loop reading host storage (its charter forbids that). host_api additions (redacted vocabulary only — charter fit): - `ModelInputIssue`: redacted mirror of the loop's `CapabilityInputIssue` — `DispatchInputIssueCode` (existing host_api enum) + bounded redacted `SafeSummary` path/expected/received/schema_path. - `ModelFailureDiagnostic { InvalidInput { issues }, Diagnostic { text } }`: redacted mirror of `CapabilityFailureDetail`; rides `ToolVerdict::RecoverableFailure.diagnostic` (additive Option). - `Denial { deny, reason_kind, summary }`: `Resolution::Denied(DenyRef)` → `Resolution::Denied(Denial)`, carrying the model-visible `DenyReason` + redacted `SafeSummary` on the channel (a projection of the sibling DenyRecord). Mapping (`resolution_mapping.rs`): `failed_outcome` now redacts the loop `detail` into the diagnostic (per-field SafeSummary; path-shaped free text degrades to the placeholder); the Denied arm populates the Denial channel from the same reason/summary the DenyRecord holds. Tests (failed first, then green): structured `InvalidInput` issues and free-text `Diagnostic` round-trip through the mapping; Denied carries reason_kind + redacted summary; redaction proof — path- and secret-shaped content is redacted in the `Resolution` output, never carried raw. GateRecord/ DenyRecord persistence round-trips (ironclaw_run_state) stay green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(host_api): enforce the 16-issue diagnostic cap in the type, not one producer ModelFailureDiagnostic::InvalidInput now carries a bounded ModelInputIssues newtype: validated at construction, revalidated on the wire (try_from), with a truncating() producer constructor for the mapping. A 17-item payload is rejected at every entry point — pinned by model_input_issues_cap_is_enforced_at_construction_and_on_the_wire. Reported-by: ironloopai (PR #6273 review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(reborn): ResolutionBatch + parks() loop-suspension predicate + scripted-outcome fixture (§5.3 flip prep / Stage 1) Additive vocabulary + test-fixture slice ahead of the capability-result flip. Nothing consumes the new items yet, so the tree stays green. Closes a verified correctness hazard: `Resolution::is_suspension()` is `Suspended(_)` ONLY, but the batch loops the flip rewrites must also stop on the re-entrant gate variants (`Resolution::Blocked` — Approval/Auth/ Resource), which map to `Blocked`, not `Suspended`. A naive `is_suspension()` batch guard would silently let a gate fall through as if the call had completed (the #6137 bug class). This provides the correct predicate now so the flip has one canonical, tested site to call. - `ironclaw_host_api::Resolution::parks()`: the loop-semantics suspension predicate — true for every `Blocked` variant AND every `Suspended` variant; false for `Done` (incl. non-suspending `ChildSpawned`) and terminal `Denied`. `parks()` ⊋ `is_suspension()`: a `Blocked::Approval` parks but is not a suspension. `is_suspension()` keeps its narrow meaning unchanged. - `ironclaw_host_api::ResolutionBatch { resolutions: Vec<Resolution>, stopped_on_suspension: bool }`: the loop-facing batch result the flip adopts, mirroring `CapabilityBatchOutcome`'s shape/semantics over `Resolution`. Wire-stable serde. - `ironclaw_agent_loop::test_support::{resolution_from_scripted_outcome, resolution_batch_from_scripted}` (behind the existing `test-support` feature): convert existing `CapabilityOutcome` fixtures to `Resolution`/`ResolutionBatch` via the production mapping, for the flip's ~150 test sites. Tests (written first, watched fail): - host_api: acceptance table extended with a `parks` column across all 10 CapabilityOutcome→Resolution rows; a dedicated exhaustive `match` (compile-fails if a new `Resolution` variant is added) proving `parks()` ⊋ `is_suspension()`; a `ResolutionBatch` round-trip. Verified red: a naive `is_suspension()`-based `parks()` fails ("parks() disagrees for blocked"). - agent_loop test-support: round-trips scripted Completed/Failed/ ApprovalRequired → the right Resolution channel, and a batch preserving order + stop flag. No trait flip, no consumer change, `CapabilityOutcome` untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(host_api): Resolution::parks uses an exhaustive match, not matches! A new Resolution variant must be a compile error in parks() (§11.9 no-wildcard) so its parking behavior is decided deliberately rather than silently defaulting to false — the exact class of silent fall-through parks() exists to prevent (#6137). Reported-by: gemini-code-assist (PR #6275 review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(runner): drop removed Pending{Approval,Auth}Resume replay fields (inherited base debt) The replay-field removal lands in a lower stack slice; until this branch's base picks it up, planned_driver.rs's cfg(test) fixtures still set the removed fields and fail the workspace clippy lane. Harmless once the base merges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(reborn): complete host-side gate reconstitution — resume input_ref, local-dev gate persistence, GateRef reconcile (§5.3 flip prep / Stage 0) Stage 0 no-flip host-reconstitution slice: closes two verified correctness hazards ahead of the atomic capability-result flip. The loop-facing trait and `CapabilityOutcome` are UNCHANGED; only host-side plumbing moves. Fix 1 — resume-time input_ref reconstitution (hazard 3). On a gate/auth resume the effective input_ref used for the idempotency key + validation is now derived from the host-private `ReplayPayload` persisted at the fresh gate raise (loaded by the resume's invocation id), not the advisory loop-supplied `approval_resume.input_ref`. A new `resume_replay_payload` helper centralizes the derivation so `invoke_capability` (seam) and `invoke_capability_dispatch` compute the SAME byte-stable key. The key is now derived lazily inside `persist_gate_record_for_outcome` (after dispatch, only for a gate outcome) so a missing/stale resume payload never pre-empts dispatch's own resume identity/activity validation — a malformed resume still surfaces `InvalidInvocation`, and a genuinely-missing payload still fails closed (2a-i missing-payload test stays green). Test proves a resume derives the same input_ref/key from the store even when the loop-supplied ref differs (a second resume differing only in that advisory ref replays the cached outcome instead of re-dispatching); failed first (re-dispatched, count 2) before the fix. Fix 2 — gate-record persistence for the local-dev synthetic approval producer (hazard 2). `OutboundDeliveryTargetSetHandler::request_approval` raises its gate OUTSIDE the loop-host persist seam, so it now persists a `GateRecord::Approval` itself at the raise (via the wired `GateRecordStore`), keyed by the canonical `GateRef::for_approval_request`, so host-side gate rendering (§5.2.9) has a record to read. Test proves the record persists and round-trips; failed first (load returned None) before the fix. Fix 3 — reconcile the two approval GateRef encodings. Canonicalized on the typed `GateRef::for_approval_request(id) = GateRef::from_uuid(id.as_uuid())` — a new `ironclaw_host_api` helper — as the GateRecordStore key. This is the host_api uuid GateRef (record key), a DIFFERENT type from the loop-facing `gate:approval-{id}` routing ref (`ironclaw_turns::GateRef`), so reconciling here touches NO persisted wire format and does not break the `is_approval_gate_ref` prefix-routing predicate. `ironclaw_capabilities::host` now uses the helper at both authorize sites. Test proves a host-persisted approval gate ref resolves through the read model: the routing ref recovers the approval id (`approval_request_id_from_gate_ref`), the canonical key re-derives, and the persisted GateRecord is found. Note: the task suggested canonicalizing on `from_uuid`; that is exactly what this does for the record-key GateRef. The RISKY direction (rewriting the `gate:approval-{id}` routing string to a bare uuid) was NOT taken — it would break the `is_approval_gate_ref` prefix predicate used in production routing, projection, and channel delivery, plus persisted adapter command strings and delivered-gate route records. Tests: loop_host 378 lib (incl. new + updated resume/seam/2a-i); host_api helper round-trip; composition 1629 (env-clean); capabilities/product_workflow/run_state green; architecture green; integration outbound_target/auth_gate/ reopen_resume_through_gate/group_approvals green; clippy -D warnings clean on all touched crates; pre-commit-safety clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): Suspension::DependentRun carries the staged child result (§5.3 flip prep / Stage 1b) Closes a verified hazard blocking the atomic capability-result flip (§5.3): `host_api::Resolution`'s `Suspension::DependentRun` channel could not carry the dependent child's staged result, which the loop needs on resume. After the flip the executor gets only `Resolution::Suspended(Suspension::DependentRun(..))`; the mapping folded `result_ref`/`byte_len`/`safe_summary` into the host-persisted `GateRecord::DependentRun` (the loop cannot read host storage) and dropped `model_observation` entirely — so byte caps, model-visible child text, and the model observation would silently regress ("never drop model output"). This ADDITIVE slice gives `Suspension::DependentRun` an inline staged-result payload alongside its `GateWaypoint`, mirroring how `Done(Outcome)` carries a spawned child run's content: - New `DependentRunResult { byte_len, summary: SafeSummary, observation: Option<SafeSummary>, origin: Option<LoopRef> }` — plain redacted host_api vocabulary; the full child bytes stay host-owned behind the record's `ResultRef`. `Suspension::DependentRun` becomes a struct variant `{ waypoint, result }`; accessors split from `ExternalTool`, plus a `dependent_result()` accessor. - `capability_outcome_to_resolution`'s `AwaitDependentRun` arm populates it from the loop's `result_ref`/`byte_len`/`safe_summary`/`model_observation` (redaction applied), and the "model_observation has no home … dropped" behavior is deleted — it now rides the inline observation preview (the same `observation_preview` reduction Completed/SpawnedChildRun use). The durable `GateRecord::DependentRun` sidecar is still persisted (host durability); the inline payload is the loop-visible copy — the same dual pattern as PR-B. Nothing consumes the new payload yet: `LoopCapabilityPort` is not flipped, `CapabilityOutcome` is not deleted, executor consumers are untouched — the tree stays green. Test-first: extended the `resolution` + `resolution_mapping` acceptance tables and added dedicated round-trip + redaction tests — an `AwaitDependentRun` outcome round-trips its `result_ref` origin, `byte_len`, `safe_summary`, AND `model_observation` onto `Suspension::DependentRun`, and a secret/path-shaped summary/observation degrades (dropped / placeholder). These fail against the pre-change tuple variant (fields absent / model_observation dropped). The `GateRecord::DependentRun` persistence round-trip stays green. Verified: `cargo test -p ironclaw_host_api -p ironclaw_turns -p ironclaw_run_state --all-features`; `cargo clippy -p ironclaw_host_api -p ironclaw_turns --all-targets --all-features -- -D warnings`; `cargo test -p ironclaw_architecture`; `scripts/pre-commit-safety.sh`; downstream build of loop_host/agent_loop/runner/reborn_composition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): loop-facing result becomes host_api::Resolution (§5.3 Stage 2 — the flip) (#6287) * refactor(reborn): loop-facing result becomes host_api::Resolution (§5.3 Stage 2 — the flip) Collapses the loop-facing capability result — the overloaded ten-variant `CapabilityOutcome`/`CapabilityBatchOutcome` — into the five-channel `host_api::Resolution`/`ResolutionBatch` at the `LoopCapabilityPort` trait boundary. `CapabilityOutcome` is retained (Stage 2b deletes it); producers convert at their boundary via `capability_outcome_to_resolution(..).resolution`. The trait flip: `invoke_capability -> Result<Resolution, _>`, `invoke_capability_batch -> Result<ResolutionBatch, _>`. Batch loops gate on `Resolution::parks()` (a re-entrant `Blocked` gate stops the batch too, H1), replacing `CapabilityOutcome::is_suspension()`. All ~49 impls + decorators/ factories migrated in dep order (turns -> loop_host -> hooks -> composition -> runner -> agent_loop), plus every test double and the `tests/integration` doubles. Executor consumer rewrite (ironclaw_agent_loop): `handle_capability_outcome`, the batch fast-path drain, `handle_capability_error` retry, `shared_await_dependent_gate`, and `capability_batch_counts` match exhaustively over `Resolution` (no wildcard, §11.9). host_api->loop inverse reconstruction: loop refs from the channel's preserved `origin`; `Done` re-split on `ToolVerdict`; model-visible failure/denial content from PR-B (`RecoverableFailure.diagnostic`, `Denial`); the DependentRun staged result from `Suspension::dependent_result()`. Resume identity stays byte-stable: `approval_request_id` reconstructed deterministically from the `gate:approval-{id}` routing ref (fingerprinted lease byte-identical); `prior_approval` kept on the wire (host-side move deferred to §5.3 Stage 2a-ii); `input_ref` advisory on resume (host reconstitutes from `ReplayPayloadStore`, Stage 0); `correlation_id` observability-only, regenerated at the loop boundary. Stage-0 resume + missing-payload fail-closed + cross-tenant tests stay green. Auth `credential_requirements` render-from-record: moved off the loop-facing `Blocked::Auth` channel onto `GateRecord::Auth` (§5.2.9), keyed deterministically by the new name-based `GateRef::for_auth_gate` (the auth gate id is `auth-{sha256hex}`, not a uuid, so it derives a stable v5 uuid); the runner re-reads them from the durable record at the blocked-exit application into `TurnRunRecord.credential_requirements` — the auth analogue of approval's Stage-0 render-from-record. Model-visible first-look preview (#5838) fixed correctly, not dropped: the collapse mis-routed tool-result CONTENT through `SafeSummary`, a caption type that rejects `{ } [ ] /` delimiters (all structured output) and — via a substring `"secret"` match — scrubbed the ordinary word "Secretary" (the #6129 bug: an amnesia re-read loop that tanked benchmark scores). Ports the canonical word-boundary credential matcher into host_api (`credential_redaction`) and applies it to `SafeSummary` so captions like "Secretary" survive; adds `ModelResultPreview` — the correct 24 KiB content vehicle that tolerates delimiters/newlines and redacts only genuine credentials (word-boundary markers + secret-like tokens) — and re-points `Outcome.refs.preview` at it, plus `ResultPreviewMeta` carrying the truncated-preview continuation metadata (referenced ref, total bytes, next offset, item count) so `result_read` pagination survives. The executor reconstructs the `ResultReference` observation from real content, preserving the inline first-look optimization (no extra `result_read` round-trip). Resume-core completions surfaced driving the production integration tiers green (the flip drops the loop-facing `correlation_id`, so every resume consumer must reconstitute it host-side, not read it off the wire): - Loop-host runtime resume reconstitutes `correlation_id` from the persisted `ReplayPayload` (keyed by invocation id) and applies it to the invocation context before the fingerprinted approval/auth lease is matched — the loop's post-flip value is advisory. Without this the lease's correlation match fails ("approval request does not match invocation: correlation_id") and the resume terminalizes (caught by `reborn_group_approvals`, `reborn_group_journeys`). - The synthetic outbound-delivery handler (raises its OWN approval gate outside the loop-host persist seam) reconstitutes `correlation_id` from its own replay payload for the approval cross-check instead of trusting the executor's now- fresh `resume.correlation_id`, completing the same §5.3 Stage 2a-i render-from- record pattern it already applied to `{input, estimate}` (caught by `reborn_integration_outbound_target`). - Deterministic gate-record keys (`for_auth_gate`, and `for_approval_request` on the authorize path) make a re-raised gate derive the SAME content-addressed key; the write-once `GateRecordStore` reports `GateRecordAlreadyExists`, which the host now treats as benign (first-write-wins, record byte-identical), mirroring the existing `ReplayPayloadAlreadyExists` tolerance — it strengthens, not weakens, the write-once contract (caught by the auth-convergence journey). - The completed-result `ResultReference` observation's own producer-authored `summary` (the truncation/continuation hint, distinct from the generic result- message caption) is carried through the collapse on `ResultPreviewMeta.summary` so the executor rebuilds the observation with the producer's exact text rather than the caption (caught by `reborn_integration_tool_call`'s truncated-preview and array-item-count transcript assertions). Full gate green: workspace `cargo clippy --all-targets --all-features -D warnings`; `ironclaw_architecture`; the changed-crate unit suites (the only red is the 3 pre-existing `llm_admin::nearai_mcp` env-var tests, unrelated); and all 51 Reborn integration bins (706 scenarios) including every §11.9 per-channel / gate / resume / auth tier. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(reborn): gate record keyed to returned Resolution + fail unsubmittable auth block (#6287 IronLoop) Two blocking gate-record correctness fixes on the §5.3 Stage 2 flip: 1. Map once (capability_port.rs). `invoke_capability` mapped the outcome twice — once inside `persist_gate_record_for_outcome` to derive the key it saved under, and once via `capability_outcome_to_resolution` to build the return value. The approval/resource/dependent/external channels mint a fresh random `GateRef` per mapping (only auth is deterministic), so the persisted record and the returned `Resolution` pointed at different refs and a resume could never load the record. Now maps once and persists/returns the same `MappedResolution` (`persist_gate_record_for_outcome` -> `persist_gate_record_for_mapped`, no internal re-map). 2. Fail the exit on unsourceable auth requirements (turn_run_executor.rs). The flip made the durable `GateRecord::Auth` the ONLY source of `credential_requirements`, so a lookup that does not yield the record is a real regression: applying the block leaves an unsubmittable, provider-null auth gate. `enrich_auth_block_credential_requirements` now returns `Result`; the three lookup-result miss arms (store fault, `Ok(None)`, wrong kind) return `Err`, and `apply_exit` records a terminal failure (shared `record_exit_failure` helper) instead of applying the incomplete block. The two tolerant pre-conditions (no store, non-auth ref) stay warn+empty. Regression tests: extend `approval_gate_outcome_persists_gate_record_at_the_seam` to assert the returned Resolution's gate ref equals the persisted key; new `auth_block_with_unsourceable_requirements_fails_the_exit` drives `apply_exit` with a missing-record store and asserts a terminal failure is recorded (this test also caught a `&'static str` lifetime bug in the helper). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(reborn): forward dependent-run observation caption to the resumed parent (#6287 IronLoop) `dependent_run_result_message` hardcoded `model_observation: None`, so `append_capability_result_ref`'s synthesize-fallback re-dropped the child's staged observation and the resumed parent saw only a bare success sentinel — not the caption/result reference the `AwaitDependentRun` channel forwarded before the flip. The mapping already preserves the caption on `DependentRunResult.observation` ("model_observation now rides the inline observation preview (was dropped entirely)"); the consumer just dropped it. Forward it as a Success `ResultReference` observation carrying the caption as its summary and pointing at the staged child result (`result_ref` + `byte_len`), so the parent can `result_read` the child output. The inline first-look preview content stays host-owned (the completed-`Outcome` path), per the mapping's "bounded SafeSummary caption" design — so `preview` stays `None` here. Test: `await_dependent_run_preserves_model_observation_for_replay` now asserts the forwarded `ResultReference` observation instead of the previously-pinned synthesized `GenericFailure` sentinel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(reborn): replay returns the record-backed gate ref, not a fresh mint (#6287 IronLoop) The map-once fix made the first invocation's persisted key and returned gate ref agree, but the replay path still diverged: on an idempotent replay the dispatch cache returns the same CapabilityOutcome, invoke_capability re-maps it (a fresh random GateRef for the approval/resource/dependent/external channels), and the set-based guard then skipped the persist — so the replayed Resolution carried a gate ref no record was ever saved under. Turn the replay guard into a resolution cache: `persisted_gate_resolutions: HashMap<IdempotencyKey, Resolution>`. The first invocation reserves the key with its resolution (atomic check-and-insert under one lock) and persists exactly one record under that resolution's gate ref; a repeat (or concurrent duplicate) finds the reservation and returns the SAME resolution — the one whose gate ref the record is under — instead of re-minting. Rollback-on-failed-save is preserved so the next replay retries. Regression: extend `replayed_gate_invocation_does_not_persist_a_duplicate_record` to assert the replayed Resolution's gate ref equals the first invocation's and the single persisted record's key, and that the record loads by the replayed ref. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(reborn): gate resolution reservation waits for durable persist (#6287 IronLoop) The reserve-then-save replay cache had a concurrency window: a concurrent duplicate could find the reservation and return the resolution before the owner's save durably completed, so a transient store fault (owner rolls back and errors) left the waiter holding a blocked resolution whose gate record was never persisted — an unresumable gate. Make the reservation a proper in-flight/persisted state machine mirroring the crate's own DispatchRecordStore + wait_for_dispatch_completion: - persisted_gate_resolutions: HashMap<IdempotencyKey, GateResolutionState>, where GateResolutionState = InFlight(Arc<Notify>) | Persisted(Box<Resolution>). - The first caller reserves InFlight and is the sole persister. A concurrent duplicate / later replay finds InFlight and WAITS on the notify, using the lost-wakeup-safe pattern (create notified(), re-check the same reservation under the lock via gate_resolution_in_flight_matches, await only if still in-flight). - The owner publishes only AFTER the durable save resolves: Persisted on Ok/benign GateRecordAlreadyExists, cleared on a transient fault; it wakes the waiters either way, so a woken waiter receives the now-durable resolution or re-owns and retries. No waiter receives a resolution before durable persist. Covered by the existing replay/rollback regression tests (replayed_gate_invocation_does_not_persist_a_duplicate_record, failed_gate_record_persist_is_retried_on_replay), which still hold. [skip-regression-check] deterministic single-threaded coverage is unchanged; the concurrency window is a narrow race the existing tests already pin the sequential behavior of, and a hermetic multi-waiter race harness is infeasible to make reliably deterministic here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(reborn): wire durable gate/replay stores into the ProductLive capability port (#6287 IronLoop) The ProductLive planned-runtime path built its capability port with only runtime/input/result/milestone/mounts — never `.with_gate_record_store(..)` / `.with_replay_payload_store(..)` — so on that path the gate record was never persisted and the replay payload never reconstituted, breaking approval/auth resumes (the local-dev path already wires both). - `ProductLivePlannedRuntimeAdapterConfig` + `ProductLiveLoopCapabilityPortFactory` gain `gate_record_store` + `replay_payload_store`; `create_capability_port` now calls `.with_gate_record_store(..).with_replay_payload_store(..)` on the host-runtime factory, exactly as the local-dev path does. - The product-workflow planned-loop harness builds both stores over ONE in-memory filesystem (production mount view via `wrap_scoped`) so a raise and its resume round-trip through the same records; the composition adapter tests supply in-memory-backed stores through the config. - Adds `ironclaw_capabilities` to product-workflow dev-deps for the harness's `FilesystemReplayPayloadStore`. Verified: `cargo test -p ironclaw_reborn_composition --test product_live_adapters` passes; product-workflow planned-loop harness tests build; clippy --all-features clean on both crates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(reborn): RAII guard clears orphaned gate-resolution reservation on cancel/fault (#6287 IronLoop) The in-flight gate-resolution reservation cleaned up only on the paths that ran to completion. If the owning persist future was cancelled (dropped mid-`save`), or returned early before the publish block, the `InFlight` entry was orphaned and same-key replays waited on it forever; the transient-failure cleanup relied on code after the `await` running. Protect the reservation with an RAII drop guard mirroring the crate's own `DispatchReservationGuard`: - On reserving `InFlight`, the owner holds `GateResolutionReservationGuard`. Any drop without `commit()` — cancellation, transient store fault, or an early error — calls `clear_gate_resolution_reservation`, which removes the entry (only if still `InFlight`, never a published resolution) and wakes its waiters so one re-owns and retries. - The success / benign `GateRecordAlreadyExists` path publishes the durable `Persisted` resolution (`publish_gate_resolution`), wakes waiters, then `commit()`s the guard so its drop is a no-op. No waiter can be left blocked on an orphaned reservation regardless of how the owner exits. Regression: `cancelled_gate_persist_clears_reservation_so_replay_can_re_own` blocks the first `save` on a barrier, cancels the invocation while parked in `save`, and asserts a replay re-owns, completes within a timeout (no hang), and persists exactly one record. `failed_gate_record_persist_is_retried_on_replay` covers the transient-fault cleanup path through the guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(reborn): share the ProductLive gate store with the turn executor too (#6287 IronLoop) The prior ProductLive wiring fix only wired the gate-record store into the capability port (the persist side). The turn executor (`RebornTurnRunExecutor`, via `DefaultPlannedRuntimeParts.gate_record_store`) still got `None`, so its render-from-record path (`enrich_auth_block_credential_requirements`, §5.2.9) could not read the record — an auth block was applied with empty requirements / failed the exit on the ProductLive path. The harness now shares ONE gate-record store between both sides: the store built for the capability port is captured into `turn_executor_gate_store` and passed to `DefaultPlannedRuntimeParts.gate_record_store`, so a persisted auth gate record round-trips to the executor read. Stays `None` for the non-ProductLive capability fakes (which do not persist gate records), preserving the executor's tolerant "no store wired" path; `inbound_turn_contract.rs` uses `EmptyCapabilityFactory`, so its `None` is correct and unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(reborn): commit the dispatch reservation only after the replay-payload write (#6287 IronLoop) `guard.commit()` ran before the fallible `persist_replay_payload_for_fresh_gate` store write. On a transient store error the `?` returned with the dispatch reservation still `InFlight`, and the already-committed guard skipped its cleanup — stranding retries/duplicates of the same idempotency key waiting on the reservation forever. Move `guard.commit()` to AFTER the replay-payload write succeeds, so a store error leaves the guard uncommitted; its drop then clears the reservation and wakes waiters so one re-dispatches. The finish-runtime-outcome ordering is unchanged. Covered by `failed_gate_record_persist_is_retried_on_replay` and the `replayed_gate` seam tests (the reservation-clears-then-retry path). [skip-regression-check] the store-write-failure reservation-clear path is exercised by the existing retry/replay seam tests; a dedicated dispatch-cancel harness for this specific reordering is redundant with them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): producers emit Resolution directly; delete CapabilityOutcome (§5.3 Stage 2b — collapse complete) (#6293) * chore(reborn): anchor s2b worktree at flip base Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): add producer-facing Resolution constructors in ironclaw_turns (§5.3 Stage 2b) New run_profile::resolution module with completed/failed/spawned_process/ spawned_child_run (return Resolution), approval_required/auth_required/ resource_blocked/await_dependent_run/external_tool_pending (return GatedResolution), and denied (returns DeniedResolution). Moves the non-lossy redaction verbatim from resolution_mapping (ModelResultPreview 24 KiB word-boundary redaction, ModelFailureDiagnostic, deterministic GateRef::for_auth_gate auth key, resume-token carry, DependentRunResult inline staged result, DenyReason mapping). resolution_mapping:: capability_outcome_to_resolution is retained transitionally as a thin delegator so unmigrated producers keep compiling; RefBindings is now empty (loop refs ride the channel origin). 17 retargeted constructor tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): loop_host producers emit Resolution directly (§5.3 Stage 2b) capability_port.rs: invoke_capability_dispatch now returns GatedResolution; the seam persists the durable GateRecord from .gate_record (GateRef::for_auth_gate key + replay-payload persistence intact) and returns .resolution — deleting the capability_outcome_to_resolution re-map. runtime_outcome_to_loop and the synthetic/inline dispatch arms emit Resolution/GatedResolution via the new constructors; the runtime failure classifier returns a private LoopFailureClass (Failed | Denied) so the per-tool display preview still stages from the raw fields. lib.rs empty-surface denial, subagent_spawn_port (spawn gates + batch coalescing keyed on the DependentRun channel origin), and capability_surface_filter denials all emit Resolution directly. 378 loop_host tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): hooks middleware emits Resolution directly (§5.3 Stage 2b) HookedLoopCapabilityPort::decision_to_outcome and fail_closed_gate_ref_unavailable now return host_api Resolution (deny/approval/auth) via the producer constructors instead of building CapabilityOutcome and re-mapping; single and batch invoke paths consume the Resolution directly. Test double emits resolution::completed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): local_dev synthetic capabilities emit Resolution directly (§5.3 Stage 2b) SyntheticCapabilityHandler::invoke now returns host_api Resolution; the synthetic port and external-tool port delegate it straight through (no capability_outcome_to_resolution re-map). outbound_delivery (approval gate + denial/failure paths, internal ApprovedResumeDecision + approval_lease_outcome now carry Resolution), skill_activation, project_create, result_read (parse error boxed for result_large_err), and external_tool_capability all build Resolution via the producer constructors. Tests assert Resolution::Done recoverable-failure verdicts and Resolution::Denied. 230 local_dev tests green; crate clippy -D warnings clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): runner tool-disclosure + subagent flavors emit Resolution directly (§5.3 Stage 2b) tool_disclosure_port bridge helpers (invoke_bridge/tool_search/describe/ describe_first/completed_bridge_result, failed_invalid_input) and the test double now build host_api Resolution via the producer constructors; subagent flavors test double likewise. No capability_outcome_to_resolution re-map remains in runner producers. Runner tests + clippy -D warnings green (all-features). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): agent_loop executor test fixtures build Resolution via constructors (§5.3 Stage 2b) shared_await_dependent_gate fixtures (await_dependent/completed/approval) now use the producer constructors directly instead of mapping a CapabilityOutcome. 401 agent_loop tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(reborn): migrate agent_loop capability fixtures off CapabilityOutcome (§5.3 Stage 2b) MockHost stores Resolution/ResolutionBatch directly; scripted_capability_outcome maps ScriptedCapabilityOutcome -> Resolution via the producer constructors; ~90 executor test fixtures rebuilt as resolution::* + ironclaw_host_api::ResolutionBatch (transformed by a comment/brace-aware script). resolution_from_scripted_outcome deleted; resolution_batch_from_scripted takes Resolutions. 401 agent_loop tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): delete CapabilityOutcome and the transitional mapping (§5.3 Stage 2b — collapse complete) Deletes CapabilityOutcome, CapabilityBatchOutcome, CapabilityResultMessage, CapabilityFailure, CapabilityDenied, ProcessHandleSummary from run_profile::host, the resolution_mapping delegator (capability_outcome_to_resolution / MappedResolution / RefBindings), and their re-exports. Retargets the last serde-fixture tests (turn_coordinator, agent_loop_host_contract, content_digest) onto the surviving vocabulary (CapabilityDeniedReasonKind / CapabilityProgress / Resolution). CapabilityApprovalResume/AuthResume/ResumeToken kept (resume requests). Architecture ratchet trims CapabilityOutcome from FROZEN_COLLAPSE_DTOS per its own shrink-only instructions. No production code references CapabilityOutcome; turns + architecture ratchet green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(reborn): retain CapabilityResultMessage/CapabilityFailure as executor-internal vocabulary Scope correction: these two are NOT dead payloads — the agent_loop executor reconstructs them from host_api::Outcome (capability_result_from_outcome / capability_failure_from_recoverable, added by the flip) and consumes them across gates/capability_helpers/strategies. They are loop-internal working types now (no producer emits them; documented as such). The genuinely-dead CapabilityOutcome/CapabilityBatchOutcome/CapabilityDenied/ProcessHandleSummary stay deleted. Workspace builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(reborn): migrate integration harness capability double off CapabilityOutcome (§5.3 Stage 2b) RecordingTestCapabilityPort (the shared tests/integration double) builds host_api::Resolution via the producer constructors; completed_result returns Resolution, approval/echo paths emit resolution::approval_required/completed directly. Unblocks every reborn_integration_* binary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(reborn): refresh loop_host seam comments post-collapse (§5.3 Stage 2b) The seam TODO to delete CapabilityOutcome is done; dispatch emits GatedResolution directly. Comment-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(reborn): cargo fmt the collapse tree (import ordering + line wrapping) Formatting-only. The capability-result collapse and the -s ours main merge left import ordering and long-line wrapping unformatted across the loop-host, agent-loop executor tests, composition local_dev, and turns run_profile files; `cargo fmt --all` resolves them. No logic change. Fixes the red Formatting lane on PR #6299. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(reborn): retain gate-record + replay-payload stores on ProductLivePlannedRuntimeAdapters (#6299 IronLoop) `ProductLivePlannedRuntimeAdapters::from_services` moved the host-private `gate_record_store` / `replay_payload_store` into the capability factory but did not retain them on the bundle. The bundle is the production API for composing a product-live planned runtime, so a consumer building the runner (`DefaultPlannedRuntimeParts`) from it had no way to wire the SAME store the capability port persists `GateRecord::Auth` into — it would pass `gate_record_store: None`, and on an `AuthRequired` resume the turn executor reloads an empty requirement set and applies an unactionable auth block (the §5.2.9 render-from-record seam, same class as #6287 IronLoop f6/f8). Expose both stores as `pub` fields, populated from cheap `Arc` clones taken before the originals move into the factory, so both sides share one durable store. Additive, no behavior change to existing consumers. Note: the only current bundle consumer (the product-workflow planned-loop test harness) extracts just `.capability_factory`; its runner already wires the same store via `turn_executor_gate_store`, so the end-to-end product-live auth-gate tests through that path pass unchanged. This closes the bundle's API gap so a future/production runner consumer wires the store, not None. cargo check + clippy (test-support,libsql) clean on ironclaw_reborn_composition. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uest-side) collapse plan (#6306) §14 status log was stale — it listed the §5.3 five-channel flip as "in flight on integration/reborn-flip-base" and said "CapabilityOutcome is retained for Stage 2b to delete", but that work has landed on main: - Move the §5.3 flip stack from "In flight" to "Merged"; record #6293 (Stage 2b — CapabilityOutcome + all result mirrors DELETED), #6299 (the stack squash-landed on main, reconciled with #6279/#6277/#6292/#6296), and #6303 (auth-gate setup fingerprint fix + injective encoding). - Fix the Slice C.1 bullet: the Resolution/Blocked/Suspension/HostFailure channel enums are now merged too. - Add the remaining work under "Not started": the Slice C down-path (request-side) collapse — the 9 request mirrors still frozen in FROZEN_COLLAPSE_DTOS — with the concrete risk-ordered slice sequence (D1 dispatch→Authorized, D2 authorize(&Invocation), D3 loop membrane mints Invocation, D4 resume/auth-resume, D5 security-milestone seal inline, D6 ratchet-to-empty + measure). Docs-only; the frozen contract (§1–§13) is unchanged, only the mutable §14 log. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
§5.3 Stage 2b — the collapse complete: every capability producer now emits
host_api::Resolutiondirectly, and the overloaded loop-facingCapabilityOutcomeis deleted along with the transitional mapping.Producers migrated (all emit
Resolution/GatedResolutionvia constructors)ironclaw_turns— newrun_profile::resolutionmodule:completed/failed/spawned_process/spawned_child_runreturnResolution;approval_required/auth_required/resource_blocked/await_dependent_run/external_tool_pendingreturnGatedResolution { resolution, gate_record };deniedreturnsDeniedResolution { resolution, deny_record }. The non-lossy redaction moved verbatim fromresolution_mapping(24 KiB word-boundaryModelResultPreview,ModelFailureDiagnostic, deterministicGateRef::for_auth_gateauth key, resume-token carry, inlineDependentRunResult,DenyReasonmapping).ironclaw_loop_host—invoke_capability_dispatchreturnsGatedResolution; the seam persists the durableGateRecordfrom.gate_record(authfor_auth_gatekey + replay-payload persistence intact) and returns.resolution— thecapability_outcome_to_resolutionre-map is gone.runtime_outcome_to_loop, the synthetic/inline dispatch arms, the empty-surface denial, subagent-spawn (batch coalescing now keyed on theSuspension::DependentRunchannel origin), and the surface/model-view denials all emitResolutiondirectly. A privateLoopFailureClass(Failed | Denied) keeps the per-tool display-preview staging from the raw fields.ironclaw_hooks—decision_to_outcome/fail_closed_gate_ref_unavailableemitResolution(deny/approval/auth) directly.ironclaw_reborn_composition— theSyntheticCapabilityHandler::invoketrait returnsResolution; outbound-delivery (approval gate + denial/failure + internalApprovedResumeDecision/approval_lease_outcome), skill_activation, project_create, result_read (parse error boxed forresult_large_err), external_tool_capability.ironclaw_runner— tool-disclosure bridge helpers + subagent flavors.ironclaw_agent_loop— executor test fixtures +MockHoststoreResolution/ResolutionBatch;scripted_capability_outcomemapsScriptedCapabilityOutcome → Resolution.Deletion —
CapabilityOutcomeis fully gone from productionCapabilityOutcome,CapabilityBatchOutcome,CapabilityDenied,ProcessHandleSummary, and the wholeresolution_mappingmodule (capability_outcome_to_resolution/MappedResolution/RefBindings).CapabilityApprovalResume/CapabilityAuthResume/CapabilityResumeToken.CapabilityResultMessageandCapabilityFailureare not dead — theironclaw_agent_loopexecutor reconstructs them fromhost_api::Outcome(capability_result_from_outcome/capability_failure_from_recoverable, added by the flip) and consumes them across gates/capability_helpers/strategies. They are now loop-internal working vocabulary (no producer emits them; documented inhost.rs). Retiring them is a separate executor-consumer refactor, out of scope for "producers emit Resolution."rg '(?<![A-Za-z])CapabilityOutcome|CapabilityBatchOutcome' crates --glob '!*/tests/*'→ nothing (onlyRuntimeCapabilityOutcome, the distinct runtime lane that stays, and doc/string mentions).Ratchet
reborn_capability_dto_collapse_ratchet.rs(#6238, shrink-only):CapabilityOutcometrimmed fromFROZEN_COLLAPSE_DTOSper the file's own instructions; self-test re-anchored on a still-frozen request name.cargo test -p ironclaw_architecturegreen.Verification
cargo test --all-featureson turns / reborn_composition / host_runtime / loop_host / agent_loop / capabilities / host_api / runner): green, except the pre-existing env-dependentllm_adminnearai/provider tests the task flags as unrelated (e.g.detect_env_llm_is_none_with_no_llm_env_vars_set).--all-targets --all-features -- -D warningson every migrated crate +ironclaw_architecture: clean (exit 0).tests/integration/) — the sharedRecordingTestCapabilityPortdouble was the only integration file needing a change; 22 binaries run green covering all §11.9 channels: greeting, auth_gate, auth_failure, reopen_resume_through_gate, cancel, idempotent_replay, hooks, attach, group_approvals, group_journeys, group_skills, group_extensions, group_triggers, group_multiuser, group_memory, skill_activate, budget, durable, lease_wedge, backend_matrix, extension_visibility, golden_payload.scripts/pre-commit-safety.sh: only an ARCH-SPRAWL large-file advisory on pre-existing >1,500-line files the collapse touches — net line deltas are neutral/negative (+2, −20, +4, −6, 0), so the collapse does not grow them.Note on the base
origin/refactor/reborn-loop-resolution-flip-s2badvanced by 2 commits (#6287: map-once seam fix + dependent-run observation) after this branch's3dfb858fbbase. Stage 2b subsumes #6287's fix #1 (the double-map bug cannot occur — the seam maps once by construction now) and does not touch its fix #2 (runnerturn_run_executor.rs); a rebase onto the current base tip resolves thecapability_port.rsinvoke_capabilityoverlap in favor of this end-state.🤖 Generated with Claude Code