Skip to content

feat(host_api): Slice C.1 — Invocation payload vocabulary (#6168) - #6223

Merged
ilblackdragon merged 5 commits into
mainfrom
refactor/reborn-slicec-invocation-vocabulary
Jul 18, 2026
Merged

ilblackdragon merged 5 commits into
mainfrom
refactor/reborn-slicec-invocation-vocabulary

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

What

First sub-slice of Slice C — the capability-path DTO/dyn collapse from docs/reborn/2026-07-17-architecture-simplification-dto-dyn-local.md (§3, §5). Per the migration plan (§9), Slice C lands the kernel vocabulary in host_api first, ahead of any wiring. This PR adds the single data-plane payload that replaces the request side of the ~14-hop re-wrap (§1.1).

Stacked on #6222 (C3, deployment-mode ratchet).

Types added

  • ids.rs (via the crate's own uuid_id!/string_id! macros):
    • ActivityId — the invocation's idempotency identity (§11.3); what §1.1's dead-future idempotency_key becomes, unified not deleted.
    • ProductKind / RoutineId — validated string newtypes for the two non-loop origins (kept strings, not enums, while the product/routine sets are still evolving per §5.8; noted as future-enum candidates).
  • invocation.rs (new module):
    • InvocationOrigin { LoopRun(RunId) | Product(ProductKind) | Automation(RoutineId) } — sealed-at-membrane origin (§5.2.1); snake_case wire-tagged; .kind() discriminant pinned to the tag for per-origin accounting views (§5.3.3).
    • Invocation { activity_id, capability, input, scope, actor, origin, estimate } — the "one payload" (§3/§4.1) every layer will reference instead of re-declaring.

Design reconciliations (documented in the code)

  • The doc names LoopRun(TurnRunId), but host_api cannot depend on ironclaw_turns. Modeled as RunId — this crate's prompt-visible turn-run identity (= ExecutionContext::run_id); the alias relationship is noted in the doc comment.
  • Invocation.actor is a required UserId (vs CapabilityDispatchRequest.authenticated_actor_user_id: Option<UserId>) — the sealed-at-membrane intent.
  • mounts / resource_reservation are deliberately absent — they are outputs of authorize(), not request inputs, so they move into the sealed Authorized witness in a later slice.

Why additive is safe (§9)

Nothing is wired into the dispatch path. The five old request DTOs still exist and function; the doc is explicit that the type count rises before it falls (~14 → ~18 → ~11) while the new vocabulary and old shapes coexist. python3 scripts/check-type-duplicates.py does not flag Invocation against CapabilityDispatchRequest — it is a genuinely distinct state, not a mirror.

Testing

  • 5 new crate-tier tests: origin serde round-trip + snake_case tags, .kind()↔tag agreement, id validation, ActivityId stable-carry (idempotency), and one-payload-per-origin construction.
  • Crate-tier only because the types aren't production-wired yet; integration coverage is owed when a later slice threads &Invocation through the four capability mediators (testing.md: crate-tier is appropriate when the integration harness cannot reach the path).

Checks

  • cargo test -p ironclaw_host_api — 38 + 57 + 5 green
  • cargo clippy -p ironclaw_host_api --all-targets --all-features -- -D warnings — clean
  • cargo test -p ironclaw_architecture — all boundary + the localdev/deployment-mode/inmemory ratchets green
  • scripts/pre-commit-safety.sh — clean

Next sub-slices (planned, stacked)

  • C.2 — Resolution/Blocked/Suspension/HostFailure/Outcome result channels (the §5.3 acceptance table maps all 10 CapabilityOutcome variants; kills §1.2's Ok(Failed)-vs-Err ambiguity).
  • C.3 — LoopRequest + resolve() membrane.
  • C.4 — sealed Authorized + authorize() (security milestone — the seal becomes load-bearing when the last policy check is inlined, §9).
  • Then thread &Invocation/&Authorized through the four mediators without merging crates (§9 step 3) and measure type/dyn counts (step 4).

🤖 Generated with Claude Code

@ironloopai

ironloopai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: 2dfc33d66a50b1fe9a9c4495d6653dd8d02a2a4f
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-18T03:49:51.947Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-18T03:18:45.343Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: c70f7fd. Previous verdict: Review declined.
Recent activity
Time Reviewer State Detail
2026-07-18T03:09:41.752Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head 277f774.
2026-07-18T03:09:41.752Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-18T03:09:42.000Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-18T03:09:44.521Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (head_ref) at 277f774.
2026-07-18T03:11:16.283Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (50e1c2e).
2026-07-18T03:15:27.533Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (c70f7fd).
2026-07-18T03:18:45.343Z ironloop/common-reviewer (reviewer) Result captured Skipped; 0 blocking findings.
2026-07-18T03:18:45.343Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (c70f7fd).
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-6223 July 18, 2026 03:09 Destroyed
@github-actions github-actions Bot added the size: XL 500+ changed lines label Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 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: 2c45bb76-d611-4376-a4f4-323ed91184d2

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 scope: dependencies Dependency updates risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs and removed size: XL 500+ changed lines labels Jul 18, 2026
@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-slicec-invocation-vocabulary branch from 277f774 to 50e1c2e Compare July 18, 2026 03:11
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6223 July 18, 2026 03:11 Destroyed

@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 consolidates the outbound state stores by removing several parallel in-memory implementations (such as InMemoryOutboundStateStore and InMemoryDeliveredGateRouteStore) and replacing them with the production FilesystemOutboundStateStore running over an in-memory filesystem backend for tests. It also introduces Slice-C kernel capability vocabulary types (Invocation, InvocationOrigin, ProductKind, RoutineId, and ActivityId) in ironclaw_host_api as part of the capability-path DTO collapse. Feedback is provided to improve test assertions in invocation.rs by asserting against specific error variants instead of using generic is_err() calls.

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 +182 to +185
assert!(ProductKind::new("").is_err());
assert!(RoutineId::new("").is_err());
// Uppercase-leading is rejected by the name-segment validator.
assert!(ProductKind::new("Settings").is_err());

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

According to the general rules, we should avoid using generic is_err() assertions in tests when verifying that an operation fails. Instead, assert against the specific expected error message or variant to prevent infrastructure or harness-level failures from causing false positives.

Suggested change
assert!(ProductKind::new("").is_err());
assert!(RoutineId::new("").is_err());
// Uppercase-leading is rejected by the name-segment validator.
assert!(ProductKind::new("Settings").is_err());
assert!(matches!(ProductKind::new(""), Err(crate::ids::IdError::InvalidFormat { .. })));
assert!(matches!(RoutineId::new(""), Err(crate::ids::IdError::InvalidFormat { .. })));
// Uppercase-leading is rejected by the name-segment validator.
assert!(matches!(ProductKind::new("Settings"), Err(crate::ids::IdError::InvalidFormat { .. })));
References
  1. Avoid using generic is_err() assertions in tests when verifying that an operation fails. Instead, assert against the specific expected error message or variant to prevent infrastructure or harness-level failures from causing false positives.

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.

Applied in the follow-up commit: the rejection tests now assert the specific kind + reason from HostApiError::InvalidId's message (product/routine + "must not be empty", and the uppercase-leading case pins the kind) instead of bare is_err().

@github-actions github-actions Bot added size: L 200-499 changed lines risk: low Changes to docs, tests, or low-risk modules and removed risk: medium Business logic, config, or moderate-risk modules labels Jul 18, 2026
ilblackdragon added a commit that referenced this pull request Jul 18, 2026
Gemini note on #6223: pin the kind + reason of each expected rejection
instead of bare is_err(), so an infrastructure failure can't masquerade
as a validation pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6223 July 18, 2026 03:15 Destroyed
@railway-app

railway-app Bot commented Jul 18, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 18, 2026 at 4:07 am

@ilblackdragon

Copy link
Copy Markdown
Member Author

✅ Ready for merge

Reviewed, finding fixed with reply, CI fully green (19 pass / 0 fail) on head c70f7fdac. Stacked on #6222 — merges last in the chain.

  • Review verdict: a clean additive Slice-C.1 vocabulary landing — correctly placed in ironclaw_host_api per its vocabulary-only guardrail (no wiring, no runtime behavior), the Invocation/InvocationOrigin/ActivityId/ProductKind/RoutineId shapes follow the crate's existing string_id!/uuid_id! conventions, the origin-sealing and authorize-fold design is documented against the plan's §3/§5.2.1/§11.3, and the coexistence-during-migration story (type count rises before it falls) is stated honestly. Tests pin the serde tags against the kind() discriminant so accounting keys can't drift.
  • Gemini finding fixed (c70f7fdac): validation-rejection tests assert the specific kind + reason from HostApiError::InvalidId instead of bare is_err().

🤖 Generated with Claude Code

ilblackdragon added a commit that referenced this pull request Jul 18, 2026
Gemini note on #6223: pin the kind + reason of each expected rejection
instead of bare is_err(), so an infrastructure failure can't masquerade
as a validation pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-deployment-mode-typename-ratchet branch from b94edc5 to 361f06e Compare July 18, 2026 03:30
@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-slicec-invocation-vocabulary branch from c70f7fd to a581269 Compare July 18, 2026 03:30
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6223 July 18, 2026 03:30 Destroyed
ilblackdragon and others added 5 commits July 18, 2026 03:49
Second §4.4 de-prefix slice (after C1's LocalDevRootFilesystem inline). Advances
the doc's §4.4 enforcement endgame — "no public type name contains
Local/LocalDev/Hosted/Enterprise" — one more type.

`LocalDevOutboundStores` is a plain bundle struct (four outbound-store handle
fields), NOT a cfg-switched policy/mode type. Its own constructor comment says it
"works in both durable (libsql/postgres) and no-durable (in-memory backend)
builds" — so the `LocalDev` prefix is factually wrong (it is used in libsql/
postgres PRODUCTION builds, not just local-dev). This is a bucket-2-style
mis-prefix: a genuine composition type that only LOOKS like a deployment-mode
leak, so the fix is a de-prefix rename, not the bucket-1 DeploymentConfig
resolution the cfg-switched `LocalDev*Store` aliases need.

Renamed the struct + its 3 use sites (all in factory.rs) to `OutboundStores`
(name was free); trimmed it from the R2 `reborn_localdev_typename` ratchet
allowlist. The `local_dev_outbound_store` builder fn keeps its name (fn names are
not type-name leaks; the ratchet inventories types only).

Pure type rename, semantically identical. Verified: localdev ratchet 4; `cargo
build -p ironclaw_reborn_composition` (default + libsql+slack+telegram) clean;
clippy -D warnings clean; fmt + pre-commit clean.

Stacked on #6218.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…name ratchet (§4.4/§10)

Completes the §4.4-mandated enforcement — "no public type name contains
Local/LocalDev/Hosted/Enterprise" (the doc says: "Enforce it with an
ironclaw_architecture test"). The existing `reborn_localdev_typename_ratchet`
owns `LocalDev*` (shrinking to empty as Slice B lands) and explicitly scopes out
"the broader Local*/Hosted* audit ... as a separate concern." This companion
ratchet owns that separate concern — the OTHER three prefixes:

- `Enterprise*` — NONE exist (achieved). Empty allowlist locks it in: a new
  `EnterpriseTierPolicy`-style mode leak fails the "no new" check.
- `Hosted*` — all `HostedMcp*`/discovery/egress, a Bucket-3 FALSE POSITIVE
  ("hosted MCP" is a real domain concept — a platform-hosted MCP server — not a
  hosted-TIER deployment mode). Frozen/justified so a genuine `HostedTierRuntime`
  leak can't slip in behind them.
- `Local*` (excluding `LocalDev*` → sibling ratchet, and `Locale*` → localization
  false positive) — the `LocalTriggerAccess*` family is genuine Bucket-1 debt
  (§4.4 folds `local_trigger_access` into "seed owner grant from config at boot,"
  a policy value); `LocalInvocationServicesResolver` awaits a design-call rename.

Reuses the shared `ratchet_support` scanner (comments/strings stripped,
visibility-aware, skips tests/examples/benches). Same frozen-set contract as the
sibling ratchets: no new type, no duplicate definition, trim on delete/rename.
Predicate uses `starts_with` (not `contains`) so mid-word `Local` (`HookLocalId`)
is not flagged; a self-test pins that + the `LocalDev`/`Locale` exclusions.

Test-only, no production change. Verified: 2 tests pass (frozen allowlist +
predicate self-test); clippy -D warnings clean; fmt + pre-commit clean.

Stacked on #6220.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…efixes

Addresses all three review findings on #6222:

- IronLoop: §4.4's rule is "no public type name CONTAINS Local/Hosted/
  Enterprise" — the predicate now matches the terms at any CamelCase word
  boundary (followed by uppercase/digit/underscore/end), which surfaces
  and freezes 16 previously-invisible mode-shaped mid-names: the
  RebornLocal* composition family, the Reborn*LocalTriggerAccess*
  backends, mid-name LocalDev types, and HookLocalId (justified-keep:
  hook-local id is a domain concept, annotated as such).
- Gemini (both): localization names need no hand-listed exceptions under
  boundary matching — Locale*/Localization*/Localised* continue lowercase
  so the word is not "Local"; the self-test now pins those exclusions plus
  the mid-name positives (RebornLocalRuntimeServices, HookLocalId,
  SelfHostedMcpClient).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First sub-slice of Slice C (arch-simplification §3/§5) — the capability-path
DTO collapse. Per the migration plan (§9), the kernel vocabulary lands in
`ironclaw_host_api` *first*, ahead of any wiring; this PR adds the single
data-plane payload every layer will reference, replacing the request side of
the ~14-hop re-wrap (§1.1).

- ids.rs (via the crate's own `uuid_id!`/`string_id!` macros):
  - `ActivityId` — the invocation's idempotency identity (§11.3); what §1.1's
    dead-future `idempotency_key` becomes, unified rather than deleted.
  - `ProductKind` / `RoutineId` — validated string newtypes for the two non-loop
    origins (kept strings, not enums, while the product/routine sets evolve, §5.8).
- invocation.rs (new):
  - `InvocationOrigin { LoopRun(RunId) | Product(ProductKind) | Automation(RoutineId) }`
    — sealed-at-membrane origin (§5.2.1); snake_case wire-tagged; `.kind()`
    discriminant pinned to the tag for per-origin accounting views (§5.3.3).
  - `Invocation { activity_id, capability, input, scope, actor, origin, estimate }`
    — the "one payload" (§3/§4.1). Reuses existing host_api types; `actor` is
    required (sealed), and authorization *outputs* (`mounts`, `resource_reservation`)
    are deliberately absent — they move into the sealed `Authorized` in a later slice.

Additive only — nothing is wired into the dispatch path yet. The five old request
DTOs still exist and function; per §9 the type count rises before it falls
(~14 → ~18 → ~11) as the new vocabulary and old shapes coexist.
`check-type-duplicates.py` does not flag `Invocation` against
`CapabilityDispatchRequest` (it is a genuinely distinct state, not a mirror).

Tests are crate-tier because the types are not production-wired yet; integration
coverage is owed when a later slice threads `&Invocation` through the four
capability mediators (testing.md: crate-tier when the harness cannot reach the path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gemini note on #6223: pin the kind + reason of each expected rejection
instead of bare is_err(), so an infrastructure failure can't masquerade
as a validation pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-deployment-mode-typename-ratchet branch from 361f06e to 74e84c9 Compare July 18, 2026 03:49
@ilblackdragon
ilblackdragon force-pushed the refactor/reborn-slicec-invocation-vocabulary branch from a581269 to 2dfc33d Compare July 18, 2026 03:49
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6223 July 18, 2026 03:49 Destroyed
@ilblackdragon

Copy link
Copy Markdown
Member Author

Post-restack confirmation (after #6216/#6218 merges): rebased onto main via the chain, CI fully green — 19 pass / 0 fail on head 2dfc33d66. Still ready for merge — last in the chain after #6220 and #6222.

Base automatically changed from refactor/reborn-deployment-mode-typename-ratchet to main July 18, 2026 04:09
@ilblackdragon
ilblackdragon merged commit 2a1e4be into main Jul 18, 2026
24 checks passed
@ilblackdragon
ilblackdragon deleted the refactor/reborn-slicec-invocation-vocabulary branch July 18, 2026 04:36

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6223 — 2dfc33d6 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 scope: dependencies Dependency updates size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant