Skip to content

feat(host_api): Slice C.3 — Blocked/Suspension gate & suspension channels (#6168) - #6226

Merged
ilblackdragon merged 2 commits into
mainfrom
refactor/reborn-slicec-gate-suspension-channels
Jul 18, 2026
Merged

ilblackdragon merged 2 commits into
mainfrom
refactor/reborn-slicec-gate-suspension-channels

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

What

Third sub-slice of Slice C (capability-path result collapse, §3/§5.3). Stacked on C.2 (#6225). Adds two of the five result channels that replace the overloaded ten-variant CapabilityOutcome (§1.2).

  • ids.rs: GateRef / ProcessRef / DenyRef — opaque handles into durably-stored control-plane records. Model-visible content is rendered from the referenced record (§5.2.9 gate-rendering contract), never carried inline — same shape as C.2's ErrRef.
  • resolution.rs (new, grows across slices):
    • Blocked { Approval | Auth | Resource }(GateRef) — re-entrant gates. is_dispatch_time_permitted() pins §5.3.1: dispatch() may surface only Auth (a lane discovers a credential demand by calling the thing — MCP 401, WASM fault); an Approval/Resource gate from a lane is a HostFailure::Permanent, never a gate.
    • Suspension { Process(ProcessRef) | DependentRun(GateRef) | ExternalTool(GateRef) } — parked work. SpawnedChildRun is deliberately excluded (non-suspending, §5.3 table).

Why two separate types

Blocked waits on a decision (resolving it re-enters authorize()); Suspension waits on a result (the effect is already in flight or handed off). Confusing them is the #6137 bug class — separate types make it a compile error.

Additive (§9)

Nothing produces these yet; they fold into Resolution once Outcome lands (next slices). check-type-duplicates.py: no collision.

Testing

4 crate-tier tests: snake_case serde round-trip, kind()↔tag agreement, gate_ref() reachability, and the dispatch-time-only-Auth contract (§5.3.1). Crate-tier because unwired; integration coverage owed at wiring (testing.md).

Checks

cargo test -p ironclaw_host_api green · clippy -D warnings clean · ironclaw_architecture ratchets green · pre-commit clean.

Next

  • C.4 — move validate_loop_safe_summary → host_api as SafeSummary, then ToolVerdict + ResultRef/OutcomeRefs + Outcome.
  • C.5 — the Resolution umbrella (Done/Denied/Blocked/Suspended) with an acceptance-table test mapping all 10 CapabilityOutcome rows (§5.3).
  • C.6 — sealed Authorized + authorize() (security milestone), then wire the mediators (§9 steps 3–4).

🤖 Generated with Claude Code

@ironloopai

ironloopai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: eb5e580b3cc736dc58f83830a8d81af3050e8157
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-18T05:17:31.054Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-18T05:17:31.043Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: eb5e580. Previous verdict: Changes requested.
Recent activity
Time Reviewer State Detail
2026-07-18T05:01:37.162Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head 1e45f5d.
2026-07-18T05:01:37.162Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-18T05:01:37.390Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-18T05:01:40.000Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (head_ref) at 1e45f5d.
2026-07-18T05:04:31.870Z ironloop/common-reviewer (reviewer) Result captured Changes requested; 1 blocking finding.
2026-07-18T05:04:31.870Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-18T05:17:31.043Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (eb5e580).
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.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6226 July 18, 2026 05:01 Destroyed
@github-actions github-actions Bot added the size: M 50-199 changed lines label Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e91b8882-95a9-4cca-a36d-42d3c55b7a26

📥 Commits

Reviewing files that changed from the base of the PR and between fe5e6cd and eb5e580.

📒 Files selected for processing (3)
  • crates/ironclaw_host_api/src/ids.rs
  • crates/ironclaw_host_api/src/lib.rs
  • crates/ironclaw_host_api/src/resolution.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added standardized reference identifiers for gates, processes, and denied actions.
    • Introduced resolution states for approvals, authentication, resources, dependent runs, and external tools.
    • Added stable resolution type labels and accessors for related references.
    • Added serialization support for exchanging resolution states consistently.

Walkthrough

Adds UUID-backed reference types and introduces publicly exported Blocked and Suspension resolution channels with snake_case serde tags, stable kind helpers, shape accessors, dispatch-time validation, and unit tests.

Changes

Resolution contracts

Layer / File(s) Summary
Reference ID types
crates/ironclaw_host_api/src/ids.rs
Adds UUID-backed GateRef, ProcessRef, and DenyRef types through the existing macro.
Resolution channels and helpers
crates/ironclaw_host_api/src/resolution.rs
Defines Blocked and Suspension, their stable discriminators and reference accessors, the dispatch-time permission rule, and serialization/helper tests.
Public module exports
crates/ironclaw_host_api/src/lib.rs
Registers resolution and re-exports its public contract items.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: serrrfirat

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning Content is detailed, but it doesn't follow the required template sections or include the required linked issue and review metadata. Restructure it to match the template: add Summary, Change Type, Linked Issue, Validation, Security/DB/Blast Radius, Rollback, Review Follow-Through, and Review track.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Conventional Commits format is used and the subject accurately summarizes the host_api Blocked/Suspension additions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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 18, 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 introduces the 'resolution' module in 'ironclaw_host_api', defining 'Blocked' and 'Suspension' enums to represent distinct result channels for re-entrant gates and parked work, respectively. It also adds new string ID types ('GateRef', 'ProcessRef', 'DenyRef'). The feedback suggests adding ergonomic accessor methods ('gate_ref' and 'process_ref') to the 'Suspension' enum to match the pattern of 'Blocked::gate_ref' and avoid repetitive pattern matching boilerplate.

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.

Comment on lines +109 to +118
impl Suspension {
/// Stable discriminant (matches the serde tag) for logs/routing.
pub fn kind(&self) -> &'static str {
match self {
Suspension::Process(_) => "process",
Suspension::DependentRun(_) => "dependent_run",
Suspension::ExternalTool(_) => "external_tool",
}
}
}

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.

medium

To match the convenience of Blocked::gate_ref, Suspension should provide safe, ergonomic accessor methods to retrieve the underlying GateRef or ProcessRef when applicable. This prevents downstream consumers from having to write repetitive pattern matching boilerplate just to extract these references.

impl Suspension {
    /// Stable discriminant (matches the serde tag) for logs/routing.
    pub fn kind(&self) -> &'static str {
        match self {
            Suspension::Process(_) => "process",
            Suspension::DependentRun(_) => "dependent_run",
            Suspension::ExternalTool(_) => "external_tool",
        }
    }

    /// Returns the underlying gate reference if this suspension is gate-backed.
    pub fn gate_ref(&self) -> Option<&GateRef> {
        match self {
            Suspension::Process(_) => None,
            Suspension::DependentRun(g) | Suspension::ExternalTool(g) => Some(g),
        }
    }

    /// Returns the underlying process reference if this suspension is process-backed.
    pub fn process_ref(&self) -> Option<&ProcessRef> {
        match self {
            Suspension::Process(p) => Some(p),
            Suspension::DependentRun(_) | Suspension::ExternalTool(_) => None,
        }
    }
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added in eb5e580: Suspension::gate_ref() -> Option<&GateRef> (answers for the gate-shaped DependentRun/ExternalTool) and Suspension::process_ref() -> Option<&ProcessRef> (answers for Process), mirroring Blocked::gate_ref — with the shape-exclusivity of the two accessors pinned in the serde-tag test.

@ironloopai ironloopai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❌ IronLoop Review: reviewer

Review at a glance

Verdict Blocking Notes Inline Head
❌ Changes requested 1 0 1 1e45f5dd7fe0

Head: 1e45f5dd7fe045e894341cc870dfa8e05f81f948
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

The head reverses the supplied base’s ErrRef leak-prevention fix, reopening a raw-error/secret exposure through serialized and displayed HostFailure values. No other actionable issues found in the small stack-layer diff.

Findings

Blocking: 1 / Notes: 0

Blocking findings

1. ❌ [HIGH] Restore ErrRef as a UUID-backed opaque handle

Location: crates/ironclaw_host_api/src/ids.rs:238
Changing ErrRef back to string_id! reverts the security fix present in the supplied base. HostFailure serializes and displays this value, while ErrRef::new accepts arbitrary non-path text and inherited from_trusted bypasses validation entirely; a raw backend error or secret can therefore cross the sanitized failure boundary. Restore the UUID-backed ErrRef and its regression test rather than carrying free-form correlation text.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloopai review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloopai review when the fix may affect multiple areas.

Comment thread crates/ironclaw_host_api/src/ids.rs Outdated
@@ -236,6 +236,15 @@ string_id!(RoutineId, "routine", validate_name_segment);
// scope-id validation (bounded, no path separators / control chars) so it can
// carry an existing id (invocation/correlation) verbatim.
string_id!(ErrRef, "err_ref", validate_scope_id);

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.

This reverts the base branch’s ErrRef hardening. Since HostFailure serializes and displays this value, a string-backed ref (especially with public from_trusted) can carry raw backend text or a secret across the sanitized boundary. Please retain the UUID-backed ErrRef and its regression coverage.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in eb5e580: rebased onto main keeping #6225's uuid_id!(ErrRef) hardening (the branch had been cut from the pre-fix head), and — same reasoning applied consistently — the three refs this slice introduces (GateRef/ProcessRef/DenyRef) are now uuid_id!s too: they ride serialized Blocked/Suspension/verdict values across sanitized boundaries, so free text is structurally unrepresentable rather than merely scope-validated (from_trusted doesn't exist for uuid ids). Fixtures use fixed UUIDs for deterministic wire assertions.

@railway-app

railway-app Bot commented Jul 18, 2026

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw 🕒 Building (View Logs) Web Jul 18, 2026 at 5:01 am

Base automatically changed from refactor/reborn-slicec-hostfailure-channel to main July 18, 2026 05:12
ilblackdragon and others added 2 commits July 18, 2026 05:15
…nels (#6168)

Third sub-slice of Slice C (arch-simplification §3/§5.3). Adds two of the five
result channels that replace the overloaded ten-variant `CapabilityOutcome`
(§1.2): the re-entrant gate channel and the parked-work channel.

- ids.rs: `GateRef` / `ProcessRef` / `DenyRef` (via `string_id!`) — opaque
  handles into durably-stored control-plane records. Model-visible content (what
  the approver sees, the deny reason, the process summary) is rendered from the
  referenced record (§5.2.9), never carried inline — same shape as `ErrRef`.
- resolution.rs (new): the result-channel family, built up across slices.
  - `Blocked { Approval | Auth | Resource }(GateRef)` — re-entrant gates.
    `is_dispatch_time_permitted()` pins §5.3.1: dispatch() may surface only
    `Auth`; an Approval/Resource gate from a lane is a contract violation.
  - `Suspension { Process(ProcessRef) | DependentRun(GateRef) | ExternalTool(GateRef) }`
    — parked work. `SpawnedChildRun` is deliberately excluded (non-suspending,
    §5.3 table). Keeping Blocked (waiting on a decision) distinct from Suspension
    (waiting on a result) makes the #6137 mis-route class a compile error.

Additive only — nothing produces these yet; they fold into `Resolution` once
`Outcome` lands (next slices). Stacked on C.2 (#6225). No dup-scan collision.

Crate-tier tests (4): snake_case serde round-trip, kind()↔tag agreement,
gate_ref() reachability, and the dispatch-time-only-Auth contract (§5.3.1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses both review findings on #6226 and the rebase across the merged
ErrRef hardening:

- IronLoop: the branch reverted #6225's ErrRef fix (it was cut from the
  pre-fix head) — rebased onto main keeping uuid_id!(ErrRef), and the
  three NEW refs this slice adds (GateRef/ProcessRef/DenyRef) get the
  same structural guarantee for the same reason: they ride serialized
  Blocked/Suspension/verdict values across sanitized boundaries, so free
  text must be unrepresentable, not merely validated. Test fixtures use
  fixed UUIDs for deterministic wire assertions.
- Gemini: Suspension gains gate_ref()/process_ref() accessors mirroring
  Blocked::gate_ref, with the shape-exclusivity pinned in the serde test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-slicec-gate-suspension-channels branch from 1e45f5d to eb5e580 Compare July 18, 2026 05:17
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6226 July 18, 2026 05:17 Destroyed
@github-actions github-actions Bot added size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Jul 18, 2026
@ilblackdragon
ilblackdragon merged commit 4569396 into main Jul 18, 2026
62 of 63 checks passed
@ilblackdragon
ilblackdragon deleted the refactor/reborn-slicec-gate-suspension-channels branch July 18, 2026 05:22
@ilblackdragon

Copy link
Copy Markdown
Member Author

✅ Ready for merge

Reviewed, both findings fixed with replies, all CI checks green (55 pass / 0 fail; the red row is a superseded Railway preview deploy cancelled by the fix push).

  • IronLoop (ErrRef hardening revert): rebased onto main keeping feat(host_api): Slice C.2 — HostFailure infra-error channel (#6168) #6225's uuid_id!(ErrRef), and the same structural guarantee applied to this slice's three new refs (GateRef/ProcessRef/DenyRef) — they ride serialized Blocked/Suspension/verdict values across sanitized boundaries, so free text is unrepresentable rather than merely scope-validated.
  • Gemini (accessors): Suspension::gate_ref()/process_ref() added mirroring Blocked::gate_ref, shape-exclusivity pinned in the serde test.
  • Review verdict: clean additive C.3 vocabulary — the three-gate Blocked / three-shape Suspension split with dispatch-time-only-Auth pinned by test matches §5.3.1, and the render-from-record ref design keeps model-visible content out of the refs.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.59% (306742 / 358375 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 358375 lines now vs 320188 at floor capture (+38187 lines, +11.93%) — material change (>5%)

⚠️ 2 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.59% — 306742 / 358375 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_runtime_policy 31.75% 80 / 252
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_observability 61.54% 16 / 26
ironclaw_authorization 62.46% 604 / 967
ironclaw_mcp 64.89% 595 / 917
ironclaw_triggers 65.44% 2142 / 3273
ironclaw_dispatcher 67.15% 92 / 137
ironclaw_filesystem 67.69% 3932 / 5809
ironclaw_channel_host 68.65% 219 / 319
ironclaw_memory 69.2% 773 / 1117
ironclaw_reborn_migration 71.64% 1551 / 2165
ironclaw_trust 72.88% 661 / 907
ironclaw_reborn_cli 73.98% 7095 / 9591
ironclaw_capabilities 74.36% 1685 / 2266
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.67% 958 / 1283
ironclaw_extractors 74.72% 538 / 720
ironclaw_projects 76.48% 400 / 523
ironclaw_llm 78.27% 20216 / 25827
ironclaw_product_context 78.57% 11 / 14
ironclaw_telegram_extension 80.18% 4842 / 6039
ironclaw_wasm_product_adapters 80.36% 1448 / 1802
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_first_party_extensions 81.06% 5965 / 7359
ironclaw_memory_native 81.22% 3205 / 3946
ironclaw_events 81.43% 1539 / 1890
ironclaw_secrets 82.79% 2794 / 3375
ironclaw_network 82.98% 673 / 811
ironclaw_reborn_identity 83.59% 433 / 518
ironclaw_processes 83.76% 939 / 1121
ironclaw_run_state 83.96% 424 / 505
ironclaw_reborn_config 84.02% 1830 / 2178
ironclaw_wasm 84.44% 1069 / 1266
ironclaw_auth 84.81% 3233 / 3812
ironclaw_product_workflow 84.91% 11031 / 12992
ironclaw_turns 85.04% 13722 / 16136
ironclaw_channel_delivery 85.79% 1383 / 1612
ironclaw_common 86.13% 1714 / 1990
ironclaw_host_api 86.33% 2923 / 3386
ironclaw_threads 86.93% 4708 / 5416
ironclaw_slack_v2_adapter 87.3% 1491 / 1708
ironclaw_skills 87.58% 4470 / 5104
ironclaw_hooks 87.78% 9921 / 11302
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_product_adapters 88.1% 3384 / 3841
ironclaw_reborn_traces 88.2% 11946 / 13544
ironclaw_reborn_composition 88.33% 70125 / 79386
ironclaw_webui 88.42% 7333 / 8293
ironclaw_host_runtime 88.76% 18005 / 20284
ironclaw_extensions 89.38% 2971 / 3324
ironclaw_reborn_openai_compat 89.5% 3778 / 4221
ironclaw_runner 89.65% 17365 / 19370
ironclaw_telegram_v2_adapter 89.7% 2717 / 3029
ironclaw_approvals 90.18% 1598 / 1772
ironclaw_conversations 90.39% 3123 / 3455
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_resources 91.65% 4476 / 4884
ironclaw_loop_host 92.25% 15051 / 16316
ironclaw_attachments 93.06% 630 / 677
ironclaw_agent_loop 94.88% 9184 / 9680
ironclaw_safety 95.04% 3677 / 3869
ironclaw_outbound 95.52% 3451 / 3613
ironclaw_first_party_extension_ports 95.62% 3672 / 3840

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

Exemptions (3 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

This branch was successfully deployed

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

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant