Skip to content

refactor(reborn): producers emit Resolution directly; delete CapabilityOutcome (§5.3 Stage 2b — collapse complete) - #6293

Merged
ilblackdragon merged 12 commits into
integration/reborn-flip-basefrom
refactor/reborn-delete-capability-outcome
Jul 20, 2026
Merged

ilblackdragon merged 12 commits into
integration/reborn-flip-basefrom
refactor/reborn-delete-capability-outcome

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

What

§5.3 Stage 2b — the collapse complete: every capability producer now emits host_api::Resolution directly, and the overloaded loop-facing CapabilityOutcome is deleted along with the transitional mapping.

Producers migrated (all emit Resolution/GatedResolution via constructors)

  • ironclaw_turns — new run_profile::resolution module: completed/failed/spawned_process/spawned_child_run return Resolution; approval_required/auth_required/resource_blocked/await_dependent_run/external_tool_pending return GatedResolution { resolution, gate_record }; denied returns DeniedResolution { resolution, deny_record }. The non-lossy redaction moved verbatim from resolution_mapping (24 KiB word-boundary ModelResultPreview, ModelFailureDiagnostic, deterministic GateRef::for_auth_gate auth key, resume-token carry, inline DependentRunResult, DenyReason mapping).
  • ironclaw_loop_host — invoke_capability_dispatch returns GatedResolution; the seam persists the durable GateRecord from .gate_record (auth for_auth_gate key + replay-payload persistence intact) and returns .resolution — the capability_outcome_to_resolution re-map is gone. runtime_outcome_to_loop, the synthetic/inline dispatch arms, the empty-surface denial, subagent-spawn (batch coalescing now keyed on the Suspension::DependentRun channel origin), and the surface/model-view denials all emit Resolution directly. A private LoopFailureClass (Failed | Denied) keeps the per-tool display-preview staging from the raw fields.
  • ironclaw_hooks — decision_to_outcome/fail_closed_gate_ref_unavailable emit Resolution (deny/approval/auth) directly.
  • ironclaw_reborn_composition — the SyntheticCapabilityHandler::invoke trait returns Resolution; outbound-delivery (approval gate + denial/failure + internal ApprovedResumeDecision/approval_lease_outcome), skill_activation, project_create, result_read (parse error boxed for result_large_err), external_tool_capability.
  • ironclaw_runner — tool-disclosure bridge helpers + subagent flavors.
  • ironclaw_agent_loop — executor test fixtures + MockHost store Resolution/ResolutionBatch; scripted_capability_outcome maps ScriptedCapabilityOutcome → Resolution.

Deletion — CapabilityOutcome is fully gone from production

  • Deleted: CapabilityOutcome, CapabilityBatchOutcome, CapabilityDenied, ProcessHandleSummary, and the whole resolution_mapping module (capability_outcome_to_resolution / MappedResolution / RefBindings).
  • Kept (resume requests): CapabilityApprovalResume / CapabilityAuthResume / CapabilityResumeToken.
  • Kept (scope correction): CapabilityResultMessage and CapabilityFailure are not dead — the ironclaw_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 now loop-internal working vocabulary (no producer emits them; documented in host.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 (only RuntimeCapabilityOutcome, the distinct runtime lane that stays, and doc/string mentions).

Ratchet

reborn_capability_dto_collapse_ratchet.rs (#6238, shrink-only): CapabilityOutcome trimmed from FROZEN_COLLAPSE_DTOS per the file's own instructions; self-test re-anchored on a still-frozen request name. cargo test -p ironclaw_architecture green.

Verification

  • Crates (cargo test --all-features on turns / reborn_composition / host_runtime / loop_host / agent_loop / capabilities / host_api / runner): green, except the pre-existing env-dependent llm_admin nearai/provider tests the task flags as unrelated (e.g. detect_env_llm_is_none_with_no_llm_env_vars_set).
  • Clippy --all-targets --all-features -- -D warnings on every migrated crate + ironclaw_architecture: clean (exit 0).
  • Integration (tests/integration/) — the shared RecordingTestCapabilityPort double 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-s2b advanced by 2 commits (#6287: map-once seam fix + dependent-run observation) after this branch's 3dfb858fb base. 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 (runner turn_run_executor.rs); a rebase onto the current base tip resolves the capability_port.rs invoke_capability overlap in favor of this end-state.

🤖 Generated with Claude Code

@ironloopai

ironloopai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: 5ec51081919cbe3d3542d93bedd09dfe86411f83
Result: One or more review results were superseded by a newer PR head.
Next: Run @ironloopai review on the latest PR head.
Updated: 2026-07-20T00:44:20.569Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-19T23:12:47.874Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: cba2ee2. Previous verdict: Changes requested.
Recent activity
Time Reviewer State Detail
2026-07-19T23:06:34.748Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head a875a29.
2026-07-19T23:06:34.748Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-19T23:06:34.922Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-19T23:06:37.722Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (head_ref) at a875a29.
2026-07-19T23:08:01.262Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (cba2ee2).
2026-07-19T23:12:47.874Z ironloop/common-reviewer (reviewer) Result captured Changes requested; 3 blocking findings.
2026-07-19T23:12:47.874Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (cba2ee2).
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent>
Run metadata

Admission: webhook accepted the request and IronLoop persisted reviewer state before this projection.

@github-actions github-actions Bot added the size: XL 500+ changed lines label Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • staging
  • reborn-integration

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1150efb3-a987-41ff-9b88-9a95309eda05

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions github-actions Bot added risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 19, 2026

@gemini-code-assist gemini-code-assist 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.

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.

@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-delete-capability-outcome branch 2 times, most recently from 227302a to cb21ec6 Compare July 19, 2026 23:37
Base automatically changed from refactor/reborn-loop-resolution-flip-s2b to integration/reborn-flip-base July 20, 2026 00:35
ilblackdragon and others added 12 commits July 20, 2026 00:37
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>
@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-delete-capability-outcome branch from cb21ec6 to 5ec5108 Compare July 20, 2026 00:44
@ilblackdragon
ilblackdragon merged commit 32dda49 into integration/reborn-flip-base Jul 20, 2026
21 of 22 checks passed
@ilblackdragon
ilblackdragon deleted the refactor/reborn-delete-capability-outcome branch July 20, 2026 00:52
ilblackdragon added a commit that referenced this pull request Jul 20, 2026
… 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>
ilblackdragon added a commit that referenced this pull request Jul 20, 2026
…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>
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 size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant