Skip to content

Unify Reborn runtime composition - #6442

Merged
ilblackdragon merged 43 commits into
mainfrom
agent/unify-runtime-store-graph
Jul 23, 2026
Merged

ilblackdragon merged 43 commits into
mainfrom
agent/unify-runtime-store-graph

Conversation

@ilblackdragon

@ilblackdragon ilblackdragon commented Jul 21, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Collapse local and production runtime assembly onto the single production-shaped composition path.
  • Remove the dead build_local_runtime path, RuntimeSubstrate::Local, and profile predicates that only existed to describe the old split.
  • Wire the production-shaped path with the surfaces local previously owned separately: approval/auto-approve stores, outbound/channel delivery services, skill context, extension lifecycle/management, admin configuration, generic extension host, channel ingress/pairing, and delivery coordinator.
  • Keep local-dev storage configuration support, including libSQL local storage and local-yolo host process/workspace aliases, as deployment configuration feeding the unified builder.
  • Move factory test-support bodies out of factory.rs and remove the fixture-trusting extension catalog loader.
  • Fix pure production runtime assembly so default-system-prompt identity context is only required when local storage metadata is present.

Change Type

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI/Infrastructure
  • Security
  • Dependencies

Linked Issue

Related #6274 and #6389

Validation

  • cargo fmt --all -- --check (Equivalent formatting run: cargo fmt)
  • cargo clippy --all --benches --tests --examples --all-features -- -D warnings (Not run: scoped checks/tests below were used for this large composition refactor.)
  • cargo build (Not run: cargo check -p ironclaw_reborn_composition --all-targets covered the changed package and dependent test targets.)
  • Relevant tests pass: see Commands run below.
  • cargo test --features integration if database-backed or integration behavior changed (Not run: no schema migration; targeted composition/runtime persistence and production smoke tests were used.)
  • Manual testing: Not applicable: no manual UI workflow changed.
  • If a coding agent was used and supports it, review-pr or pr-shepherd --fix was run before requesting review (Not applicable: no such local command is available in this harness.)

Test Strategy

User behavior:

All active Reborn profiles now build through one production-shaped runtime assembly. Local development remains configured by deployment/storage policy, but it no longer has a separate runtime creation path. Local profiles use the production approval model with auto-approve policy defaults, and production-shaped runtime assembly now exposes extension lifecycle, skill management, channel config/ingress, outbound delivery, identity, projects, trigger poller, and product-auth services consistently.

Risk areas:

  • Model behavior
  • Browser
  • Side effect
  • Persistence
  • Security or permissions
  • External provider
  • Cross-component behavior

Tests added or updated:

  • Unit or contract: Updated profile/readiness tests for the single production-shaped semantics; moved factory test-support implementation out of the production factory body.
  • Reborn integration: Not run end-to-end; targeted runtime and facade tests covered the changed composition seams.
  • Recorded fixture: Not applicable: no model request shape changed.
  • Browser E2E: Not applicable: no browser-visible UI behavior changed.
  • Backend or runtime: Scoped package check and focused runtime/facade/production smoke tests listed below.
  • Live canary: Not applicable: no live provider behavior changed.

What the tests prove:

The composition crate and all its targets type-check cleanly; local-dev and local-yolo profiles build through the unified production-shaped path; profile readiness no longer depends on local-vs-production substrate predicates; WebUI services can read automations from the core store without a local-runtime branch; production identity and trigger poller wiring still work; and local product-auth/facade behavior remains redaction-safe.

Commands run:

  • cargo fmt
  • cargo check -p ironclaw_reborn_composition --all-targets
  • cargo test -p ironclaw_reborn_composition --test profile_acceptance -- --nocapture
  • cargo test -p ironclaw_reborn_composition --test facade_factory local_dev_builds_facades_without_production_claim -- --nocapture
  • cargo test -p ironclaw_reborn_composition --test facade_factory local_dev_product_auth_entrypoint_redacts_manual_token_submit -- --nocapture
  • cargo test -p ironclaw_reborn_composition runtime::tests::build_webui_services_without_local_runtime_still_lists_automations_from_core_store -- --nocapture
  • cargo test -p ironclaw_reborn_composition --test production_runtime_identity -- --nocapture
  • cargo test -p ironclaw_reborn_composition --test production_runtime_trigger_poller -- --nocapture

Security Impact

No security policy is intentionally weakened. This removes the separate local runtime assembly path and makes active profiles use the production-shaped authorization/approval/runtime wiring. Local-dev auto-approval remains a policy/default behavior over the production approval model. Extension lifecycle, channel ingress, outbound delivery, product auth, secret mediation, trigger pairing, and host-runtime execution continue through typed composition-owned services.

Reborn Trust-Boundary Checklist

  • Public policy/evidence/trust-bearing types: who can construct them? N/A: no new public policy/evidence/trust-bearing type added.
  • Untrusted content enters prompts only through an envelope/escaping primitive. N/A: no prompt content changed.
  • Hashes declare purpose; trust/binding/authenticity uses SHA-256/BLAKE3 or separate authenticity check. N/A: no hashing changed.
  • New/changed status, exit, policy, runtime, or error variants: downstream match sites audited. Command/output: local runtime substrate/profile predicates removed; callers/tests updated.
  • Security/durability serde(default) fields fail closed or have migration tests. N/A: no serde fields changed.
  • Queues/maps/buffers/counters have bounds and overflow-safe arithmetic. N/A: no queues/maps/buffers/counters changed.
  • Driver/operator-visible errors have stable class semantics (Transient, Permanent, Misconfigured, PolicyDenied or equivalent). N/A: no new driver/operator-visible error classes added.
  • Sandbox/native/host names accurately describe trust boundary. N/A: no sandbox/native/host boundary naming changed.

Database Impact

No migrations or schema changes. Local-dev still uses its configured libSQL storage by default, and hosted/local-volume storage can still use Postgres. The change is composition-level: storage configuration feeds the unified production-shaped runtime builder instead of selecting a separate local runtime creation path.

Blast Radius

Touches Reborn composition, deployment profile/substrate selection, runtime construction, WebUI facade wiring, product auth accessors, extension lifecycle/admin/channel wiring, outbound delivery, trigger poller dependencies, factory test-support organization, and tests that previously assumed local-vs-production substrate variability. Regressions would likely show as runtime boot failures, missing extension/channel/WebUI surfaces, approval behavior drift, or production profile smoke-test failures.

Rollback Plan

Revert this PR to restore the previous local-vs-production runtime assembly split. No data migration rollback is required because schemas and stored key formats are unchanged.

Review Follow-Through

This update removes the old local runtime creation path rather than keeping a low-risk compatibility branch. Production-shaped runtime assembly now owns the services local previously assembled separately. Remaining reviewer judgment: whether the remaining factory-level cfg fields/re-exports for test-support should be migrated further into dedicated harness modules in a follow-up.


Review track: C (runtime composition refactor)

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@ironloopai

ironloopai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: fc7e5c89bcc3cadbd1327a900df167931dab7b8e
Result: 1 reviewer declined to produce a review for this head.
Next: Narrow the change or provide the missing context, then re-run the declined reviewer.
Updated: 2026-07-23T15:28:02.192Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Completed Review declined Not reviewed 2026-07-23T15:28:02.180Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Review declined: The diff is oversized and cross-cutting (164 files; 11,019 additions and 9,517 deletions), with core factory/runtime rewrites plus security- and persistence-sensitive boundary cha…
Recent activity
Time Reviewer State Detail
2026-07-23T08:58:10.246Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-23T09:37:11.461Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (f74d173).
2026-07-23T15:27:16.100Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head fc7e5c8.
2026-07-23T15:27:16.100Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-23T15:27:16.168Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-23T15:27:20.337Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (merge_ref) at ab7b83e.
2026-07-23T15:28:02.180Z ironloop/common-reviewer (reviewer) Result captured Skipped; 0 blocking findings.
2026-07-23T15:28:02.180Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
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.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0e9543df-7643-4647-a81e-c205d958681d

📥 Commits

Reviewing files that changed from the base of the PR and between fc83f9d and fc7e5c8.

📒 Files selected for processing (32)
  • crates/ironclaw_first_party_extensions/assets/gmail/manifest.toml
  • crates/ironclaw_first_party_extensions/assets/google-calendar/manifest.toml
  • crates/ironclaw_first_party_extensions/src/lib.rs
  • crates/ironclaw_reborn_cli/src/commands/extension.rs
  • crates/ironclaw_reborn_cli/src/first_party/gsuite.rs
  • crates/ironclaw_reborn_cli/src/first_party/mod.rs
  • crates/ironclaw_reborn_cli/src/runtime/mod.rs
  • crates/ironclaw_reborn_composition/AGENTS.md
  • crates/ironclaw_reborn_composition/Cargo.toml
  • crates/ironclaw_reborn_composition/src/extension_host/available_extensions.rs
  • crates/ironclaw_reborn_composition/src/extension_host/first_party.rs
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/factory/tests.rs
  • crates/ironclaw_reborn_composition/src/input.rs
  • crates/ironclaw_reborn_composition/src/lib.rs
  • crates/ironclaw_reborn_composition/src/product_auth/api/auth.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/test_support.rs
  • crates/ironclaw_reborn_composition/src/test_support/channel_connection.rs
  • crates/ironclaw_reborn_composition/src/webui/facade.rs
  • crates/ironclaw_reborn_composition/tests/runtime.rs
  • crates/ironclaw_reborn_composition/tests/webui_v2_e2e.rs
  • docs/plans/composition-pubuse.snapshot
  • docs/reborn/production-cutover-readiness-closeout.md
  • tests/integration/extension_delivery.rs
  • tests/integration/group_extensions/main.rs
  • tests/integration/group_extensions/scenario_slack_channel_lifecycle_state_machine.rs
  • tests/integration/support/group_constructors.rs
  • tests/integration/support/harness/mod.rs
  • tests/integration/support/harness/options.rs
  • tests/integration/support/harness/profiles/skill.rs
  • tests/integration/webui_v2_product_api.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added per-tool network destination allowlists and outbound data limits for supported extensions, including Gmail, Google Calendar, and web access.
    • First-party extensions are now bundled and discoverable through the CLI, with improved search aliases.
    • Added runtime support for first-party capability handling and configurable credential-account visibility.
  • Bug Fixes
    • Startup now fails clearly when required SSO identity resolution is unavailable.
    • Added PostgreSQL TLS validation before connection setup, helping prevent insecure remote connections.
  • Documentation
    • Updated extension and runtime guidance to reflect current configuration entry points.

Walkthrough

This PR renames RebornBuildInput/RebornServices to RebornHostBindings/RebornRuntimeStores/RebornRuntime across composition, factory, and runtime modules; extracts GSuite/web-access first-party capability wiring from composition into ironclaw_reborn_cli; adds max_egress_bytes capability/manifest plumbing; reshapes DeploymentConfig/readiness; adds Postgres TLS pool validation; and migrates a large body of tests to the new seams.

Changes

Reborn composition and runtime rewiring

Layer / File(s) Summary
Docs and naming updates
.claude/skills/..., CLAUDE.md, docs/..., crates/ironclaw_reborn_composition/AGENTS.md, harness/latency/runner/*
Comments, checklists, AGENTS.md and pubuse snapshot updated to reflect RebornHostBindings/build_runtime/first_party naming.
Architecture boundary tests
crates/ironclaw_architecture/tests/*
Allowlists/forbidden-symbol scans updated for first_party module and build_runtime seam.
max_egress_bytes capability contract
ironclaw_host_api/src/capability.rs, ironclaw_extensions/src/{v2,v3,lib,hosted_mcp_discovery}.rs, manifests (gmail, google-calendar, web-access), many test descriptor fixtures
Adds optional per-capability egress byte cap to descriptors/manifests, threaded into discovery and extension network policy.
First-party extraction into CLI
ironclaw_reborn_cli/src/first_party/*, ironclaw_reborn_composition/src/extension_host/{first_party,available_extensions*,mod}.rs, removed gsuite.rs/web_access.rs
GSuite and web-access first-party handlers move out of composition into CLI-owned registrars/bundles; composition gains a neutral FirstPartyPackageBundle/registrar seam and reserved-id/search-alias wiring.
RebornBuildInput → RebornHostBindings + DeploymentConfig
input.rs, deployment.rs, runtime_input.rs, ironclaw_reborn_cli/src/{commands/*,runtime/mod.rs}
Renames the composition input type, moves deployment fields onto DeploymentConfig, reshapes storage connection configs, and updates CLI runtime construction.
factory.rs: RebornRuntimeStores
factory.rs, factory/*.rs, extension_host/extension_lifecycle*
Replaces RebornServices with RebornRuntimeStores, reworks production/local-dev build, first-party registrar injection, and migrates factory unit tests.
runtime.rs unification
runtime.rs, runtime/*.rs, llm_admin/openai_compat_serve.rs
Unifies build_reborn_runtime/build_runtime, replaces optional local-substrate accessors with concrete handles, reworks extension network-policy logic.
Readiness/profile/storage-catalog validation
readiness.rs, root/{profile,product_live_adapters}.rs, storage_catalog.rs
Reworks readiness diagnostic constructors, removes substrate predicates, adds storage-plane validation.
Test-support and WebUI facade
test_support/*, webui/{facade,product_capability}.rs, product_auth/*
Migrates test-support helpers and WebUI facade to RebornRuntime handles; adds injectable RuntimeCredentialAccountVisibilityPolicy.
Postgres TLS pool validation
ironclaw_reborn_event_store/src/lib.rs
Adds a preflight TLS-policy validator and enforces remote-cleartext rejection during pool construction.
Composition crate test migration
ironclaw_reborn_composition/tests/*.rs
Migrates integration tests to build_runtime/RebornHostBindings/RebornRuntime accessors.
Top-level integration harness migration
tests/integration/*, tests/reborn_qa*, tests/support/reborn_parity_qa/*
Migrates integration harness/QA support to new seams; adds trajectory-observer wiring and a durable-HTTP-tools capability backend.

Estimated code review effort: 5 (Critical) | ~150 minutes

Possibly related issues

Suggested reviewers: serrrfirat


Terse notes given tone constraints, skipping clippy/rustfmt/deny-gated items:

  • input.rs: PostgresConnectionConfig/pool_source now deferred to build-time open — verify no path silently drops secret_master_key validation before pool open (fail-closed secret handling).
  • runtime.rs: open_reborn_identity_resolver/reborn_user_directory etc. went from Option→infallible Arc. Confirm every caller that previously matched None for "local substrate absent" was actually updated, not just unwrapped — production paths must still fail closed, not construct a resolver over an unscoped filesystem.
  • extension_surface.rs: max_egress_bytes now dropped to None whenever network_targets is empty — confirm this can't be reached for capabilities with credential audiences but zero declared targets, which would silently remove the byte cap (egress-first invariant).
  • ironclaw_reborn_event_store: validate_remote_tls_policy gates cleartext to remote hosts only — double check "local" detection isn't spoofable via a resolvable-but-remote hostname alias (secrets/egress boundary).
  • deployment.rs: DeploymentConfig dropped PartialEq/Eq — any snapshot/golden test relying on equality elsewhere in the workspace not touched by this PR could now silently compile with == removed via trait bound change; check for orphaned test helpers.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the PR’s main change: unifying Reborn runtime composition.
Description check ✅ Passed The description follows the template and covers summary, change type, linked issue, validation, test strategy, security, impact, rollback, and review track.
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.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 21, 2026 23:18 Destroyed
@github-actions github-actions Bot added size: M 50-199 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 21, 2026

@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
✅ Approved 0 0 0 a695c102904d

Head: a695c102904d752343d9532af58937e66f2aa771
Next: No reviewer action needed.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

Approved after focused review of the 5-file runtime-composition refactor. The selected graph is exclusive, both runtime consumers use it, and the scheduler-wake guard remains equivalent for current production profiles.

Findings

None.

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.

@github-actions

github-actions Bot commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 86.22% (309774 / 359298 lines)
  floor:    86.27% (tolerance 0.5pp -> effective floor 85.77%)
  denominator: 359298 lines now vs 354049 at floor capture (+5249 lines, +1.48%) — not a material change

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

Reborn integration-tier coverage

Line coverage (Reborn crates): 86.22% — 309774 / 359298 lines

Per-crate breakdown (62 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_telegram_extension 34.88% 60 / 172
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_product_context 57.78% 26 / 45
ironclaw_observability 61.54% 16 / 26
ironclaw_authorization 62.98% 609 / 967
ironclaw_dispatcher 64.17% 77 / 120
ironclaw_memory 69.2% 773 / 1117
ironclaw_trust 73.21% 664 / 907
ironclaw_filesystem 73.5% 4543 / 6181
ironclaw_capabilities 74.07% 2717 / 3668
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_extractors 74.72% 538 / 720
ironclaw_projects 76.48% 400 / 523
ironclaw_triggers 77.33% 2531 / 3273
ironclaw_mcp 77.56% 736 / 949
ironclaw_reborn_cli 78.12% 10632 / 13609
ironclaw_llm 78.63% 20824 / 26485
ironclaw_wasm 79.72% 735 / 922
ironclaw_process_sandbox 80.46% 671 / 834
ironclaw_memory_native 81.38% 3203 / 3936
ironclaw_first_party_extensions 82.21% 6610 / 8040
ironclaw_events 82.47% 1604 / 1945
ironclaw_telegram_v2_adapter 83.07% 2017 / 2428
ironclaw_processes 83.16% 933 / 1122
ironclaw_product_adapter_registry 83.43% 574 / 688
ironclaw_reborn_identity 83.8% 450 / 537
ironclaw_secrets 83.8% 2550 / 3043
ironclaw_reborn_config 84.17% 1962 / 2331
ironclaw_common 84.48% 1769 / 2094
ironclaw_product_adapters 84.65% 3308 / 3908
ironclaw_auth 85.01% 4011 / 4718
ironclaw_run_state 85.61% 458 / 535
ironclaw_network 85.97% 913 / 1062
ironclaw_product_workflow 86.51% 16271 / 18809
ironclaw_reborn_event_store 86.51% 1251 / 1446
ironclaw_hooks 86.58% 9930 / 11469
ironclaw_extensions 86.98% 3808 / 4378
ironclaw_threads 87.2% 4851 / 5563
ironclaw_host_api 87.55% 5407 / 6176
ironclaw_reborn_composition 87.55% 57105 / 65223
ironclaw_skills 87.58% 4470 / 5104
ironclaw_reborn_traces 88.11% 11972 / 13587
ironclaw_turns 88.62% 14580 / 16452
ironclaw_host_runtime 88.71% 18483 / 20835
ironclaw_reborn_openai_compat 88.92% 3580 / 4026
ironclaw_slack_extension 89.36% 2444 / 2735
ironclaw_extension_host 89.59% 2856 / 3188
ironclaw_approvals 90.18% 1598 / 1772
ironclaw_conversations 90.39% 3123 / 3455
ironclaw_webui 90.42% 9102 / 10066
ironclaw_resources 90.85% 4477 / 4928
ironclaw_event_streams 91.24% 1063 / 1165
ironclaw_runner 91.27% 17073 / 18707
ironclaw_loop_host 92.26% 16261 / 17625
ironclaw_attachments 93.06% 630 / 677
ironclaw_outbound 93.98% 3733 / 3972
ironclaw_agent_loop 94.93% 9840 / 10365
ironclaw_safety 95.15% 3749 / 3940
ironclaw_first_party_extension_ports 95.62% 3672 / 3840
ironclaw_runtime_policy 96.55% 811 / 840

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

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 21, 2026 23:33 Destroyed
@github-actions github-actions Bot added size: L 200-499 changed lines and removed size: M 50-199 changed lines labels Jul 21, 2026
@railway-app

railway-app Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 23, 2026 at 11:00 am

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 21, 2026 23:48 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Jul 21, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 21, 2026 23:57 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 22, 2026 00:32 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 22, 2026 00:43 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 22, 2026 00:57 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 22, 2026 01:02 Destroyed

@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 2 fc83f9dd5b7d

Head: fc83f9dd5b7dab32d3df753e80057934c50d2b3b
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 refactor disables the end-to-end tests for channel ingress, pairing, and outbound delivery without a replacement, leaving changed runtime wiring effectively untested.

Findings

Blocking: 1 / Notes: 0

Blocking findings

1. ❌ [MEDIUM] Restore channel delivery and pairing integration coverage

Location: tests/integration/extension_delivery.rs:8
#![cfg(any())] disables the entire registered reborn_integration_extension_delivery target, including the real Slack/Telegram inbound-to-turn-to-DeliveryCoordinator-to-egress and pairing coverage. This PR rewires those same production-shaped composition surfaces and also removes the Slack channel lifecycle scenario. Restore the needed test-support seam or replace these with equivalent active integration tests before merging.

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 tests/integration/extension_delivery.rs Outdated
Comment thread tests/integration/group_extensions/main.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_reborn_composition/src/input.rs (1)

385-417: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Propagate policy-resolution failures.

At Line 403, .ok().flatten() discards DeploymentConfig::resolve() errors, then downstream construction reports only a missing policy. Return/map the resolver error with context and add a regression for the failing resolution path.

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

In `@crates/ironclaw_reborn_composition/src/input.rs` around lines 385 - 417,
Update local_dev_from_deployment to propagate DeploymentConfig::resolve()
failures instead of converting them with .ok().flatten(); return or map the
resolver error with useful context while preserving the acknowledged
yolo/no-policy override behavior. Adjust the constructor’s result type and
callers as needed, and add a regression test covering a failing resolution path.

Source: Path instructions

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

Inline comments:
In `@crates/ironclaw_reborn_composition/src/runtime.rs`:
- Around line 4222-4224: Do not occupy channel_facade_slot in build_runtime when
creating the generic channel facade; preserve the test injection seam or expose
the runtime-owned facade for reuse. In
crates/ironclaw_reborn_composition/src/test_support/channel_connection.rs:119-126,
update build_channel_connection_for_test to accept the expected runtime-owned
slot instead of treating it as an error, and add regression coverage through
that caller.

---

Outside diff comments:
In `@crates/ironclaw_reborn_composition/src/input.rs`:
- Around line 385-417: Update local_dev_from_deployment to propagate
DeploymentConfig::resolve() failures instead of converting them with
.ok().flatten(); return or map the resolver error with useful context while
preserving the acknowledged yolo/no-policy override behavior. Adjust the
constructor’s result type and callers as needed, and add a regression test
covering a failing resolution path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3a6ada91-0944-40a6-a567-3e2f3d37c411

📥 Commits

Reviewing files that changed from the base of the PR and between 6480b5b and fc83f9d.

📒 Files selected for processing (9)
  • crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
  • crates/ironclaw_reborn_composition/src/approval_test_support.rs
  • crates/ironclaw_reborn_composition/src/deployment.rs
  • crates/ironclaw_reborn_composition/src/extension_host/first_party.rs
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/input.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/test_support.rs
  • crates/ironclaw_reborn_composition/src/test_support/channel_connection.rs

Comment thread crates/ironclaw_reborn_composition/src/runtime.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 23, 2026 09:37 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 23, 2026 09:48 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6442 July 23, 2026 10:51 Destroyed
@ilblackdragon
ilblackdragon marked this pull request as ready for review July 23, 2026 15:27
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@ilblackdragon
ilblackdragon merged commit 5b78e95 into main Jul 23, 2026
64 of 65 checks passed
@ilblackdragon
ilblackdragon deleted the agent/unify-runtime-store-graph branch July 23, 2026 15:27

@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 Declined: reviewer

Review at a glance

Disposition Head
⏭️ Review declined fc7e5c89bcc3

Head: fc7e5c89bcc3cadbd1327a900df167931dab7b8e
Reason: The diff is oversized and cross-cutting (164 files; 11,019 additions and 9,517 deletions), with core factory/runtime rewrites plus security- and persistence-sensitive boundary changes. Completing coverage sufficient to approve or request changes reliably would exceed the available review budget.
Next: Split into smaller independently reviewable commits/PRs (for example: runtime unification, first-party extraction, capability manifest changes, and TLS validation), or provide a dedicated full-review budget with focused design notes and complete validation evidence for each security/persistence boundary.

Run details

Status: Current
Trustworthy review produced: no

Summary

Skipped: this is a mega runtime-composition refactor whose 164 changed files and 20,536 changed lines span production wiring, authorization/approvals, persistence, extension networking, CLI, and integration harnesses. A reliable complete review is not feasible within the configured review scope.

BenKurrek added a commit that referenced this pull request Jul 23, 2026
…test-suite reconciliation deferred to audit (see merge-handoff.md)

Merge main's #6442 (unify Reborn runtime composition) into PR #6520. The
unified single-path assembly (#6442) is re-wired onto #6520's models:
admin-configuration (channel_config folded into admin_configuration_resolver),
relocated channel pairing (ironclaw_product_workflow), and event-driven
delivery (RunDeliveryEventRouter as a TurnEventSink, DeliveryCoordinator kept).
Caller-scoped extension surface, the redirectable trigger-source seam, and the
first-party/CLI account-setup + oauth-setup model changes are reconciled.

`cargo check --workspace --lib --bins` is GREEN. The #6442-authored test
modules and the integration harness still reference several APIs #6520 removed
or renamed (extension "activate" action/capability, tenant-operator accessor,
the harness's local_dev_active_extension_authority_for_test arity, etc.);
that test-suite reconciliation is intentionally left un-weakened for the audit
agent and documented in scratchpad/merge-handoff.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf
BenKurrek added a commit that referenced this pull request Jul 23, 2026
…racts

Production wiring restored (dropped in the merge, surfaced as dead code):
boot-time hosted-MCP reconcile after restore, the composed
RecipeAuthChallengeProvider (pairing + product-auth challenges) feeding
projections and channel delivery, current-delivery-target resolver assembly
attach, and manifest-derived account-setup descriptors (catalog + injected
extras) so /start pairing and connect notices compose again.

Test reconciliation (no test deleted or weakened): #6442-side suites
rewritten to the no-Activate three-state lifecycle (install drives
readiness; installs are caller-private, so surface tests install as the
surface user), caller-scoped 3-arg project with the production credential
gate, LifecyclePublicState assertions, admin-configuration seeding via the
composed resolver, RunFinalReplyDestination::External, and the 1-arg
active-extension authority in the integration harness. Three #6520 test
accessors ported onto the flat RebornRuntime. Ratchet maintenance:
HostedMcpDiscoveryOutcome frozen-name entry, specificity allowlist
re-pathing for merge-moved files, stale entries dropped, and the
banned "mcp_server" literal swapped out of a lifecycle test fixture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV
BenKurrek added a commit that referenced this pull request Jul 24, 2026
…6520)

* fix(reborn): unify extension lifecycle and channel delivery

* Finish generic extension lifecycle and delivery correctness

* fix(reborn): repair integration + triggers test compilation

The generic extension/channel refactor renamed/removed several APIs but did
not propagate the changes into the shared integration test-support harness or
two new journey tests, so `cargo check --workspace --all-targets` failed with
101 errors (the whole `ironclaw_reborn_integration_tests` suite plus the
`ironclaw_triggers` lib-test), meaning none of the P0 journey tests could
build.

- Drop the removed `inbound_payload_classifier` / `classifier` fields from the
  test-support `ChannelExtensionBinding` and two `ChannelInboundSinkConfig`
  literals — gate-command classification now runs generically in
  `GenericChannelInboundSink`, so the per-binding classifier is obsolete.
- Remove the now-unused `RunDeliverySettings` and `ProductWorkflow` imports and
  point the stale `RebornServicesApi` import at the current `ProductSurface`
  trait that exposes `query`.
- Widen the `InMemoryTriggerRepository` history/lock helpers to `pub(crate)` so
  the relocated `src/tests.rs` module can reach them.
- `cargo fmt` (also fixes a pre-existing import-order drift in
  composition `outbound/mod.rs`).

No production behavior changes; this restores the deleted-then-relocated test
coverage so the suite compiles and can run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(mcp): skip shape-invalid tools instead of bricking the catalog

Hosted-MCP discovery rejected the entire advertised catalog on the first
shape-nonconforming tool (e.g. a non-lowercase name), so 23 valid tools +
1 bad tool made the whole integration unusable on first install with no
prior generation to fall back to.

parse_tools_list_result now distinguishes shape-only, non-security defects
(invalid tool name / description / annotations) from security/bounds
violations (missing or unsafe input schema, over-cap catalog). Shape-only
defects skip just the offending tool and publish the rest, emitting a
bounded debug record (tool index + stable cause token only). Security and
bounds violations still fail the whole generation with the unchanged stable
subcause; per-tool checks evaluate the schema first so a co-occurring
cosmetic defect cannot downgrade a security failure to a skip. A catalog
where every tool is shape-invalid still fails non-retryably (nothing to
publish), while an empty provider list stays an empty result.

Test-first regressions in mcp_adapter_contract.rs (survivors publish
end-to-end through the real client) and lib unit tests (skip+record,
security-amid-valid fails whole catalog, all-invalid fails, empty preserved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(outbound): guard crash-recovery Sending->Unknown against clobbering a delivered send

`recover_interrupted_deliveries` listed `Sending` attempts from a point-in-time
snapshot and then blindly called `update_delivery_status(Unknown)` for each,
which unconditionally set the status. A concurrent worker that completed egress
and wrote `Delivered` could then be overwritten back to `Unknown` by a stale
recovery, durably losing a successful delivery (latent duplicate-send risk).

Add a dedicated `recover_interrupted_delivery_attempt` store method that
re-reads the attempt inside the same CAS the write commits against and
transitions `Sending -> Unknown` only when it is still `Sending` (mirroring the
`Prepared` guard on `claim_delivery_attempt_for_send`), returning `false`
otherwise. Route the coordinator's recovery through it so the guard, not the
list snapshot, decides. `update_delivery_status` stays an unconditional setter
for legitimate forward egress-result writes.

Regression: `recovery_transition_never_clobbers_delivered` in
`outbound_state_store_contract` drives record->claim(Sending)->Delivered, then
recovery, and asserts the row stays `Delivered` (and that a still-`Sending`
attempt is still recovered to `Unknown`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(extension-host): fence hosted-MCP discovery on stable credential authority

The discovery authority fence compared the whole `Vec<CredentialAccount>`
via the fence's derived `PartialEq`, which includes each account's volatile
`created_at`/`updated_at`. A benign write to the credential row between the
pre-discovery capture and the post-discovery recheck therefore tripped the
fence and forced a spurious `discovery_recheck` transient — a hard retry
loop if anything keeps touching the row mid-discovery.

`still_authorizes` now compares a stable, timestamp-free projection of each
account (id, status, access/refresh secret handles, scopes) alongside the
existing package, manifest digest, and tool ceiling. The fence stays
fail-closed on any real authority change (scope, secret, or status). The
fence's derived `PartialEq` is removed so no caller can reintroduce the
timestamp-sensitive comparison by accident. `CredentialAccount`'s own
derived `PartialEq` is deliberately left unchanged.

Test-first regressions through the real caller (`run_extension_activation`):
a benign `updated_at` bump activates without a spurious recheck; a scope,
secret, or status change still fails the fence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(run-delivery): gate completed-run handoff materialization + freeze Final dedup cursor

Two durable-delivery correctness fixes in the lifecycle-event router.

Fix 1 (HIGH) — unsettled completed-run handoffs leak durably and are re-scanned
every drain. `materialize_completed_handoffs` wrote a `RunFinalReplyHandoffRecord`
for EVERY `Completed` event on the global bus, but a handoff is only settled by a
live SOURCE channel handler. Runs no such handler owns — pure WebUI→WebApp
completions (no adapter), ScheduledTrigger runs (served by the volatile triggered
driver), and context-less runs — never settled, so under normal WebUI chat usage
every completion leaked one permanent pending row, and every drain (fires on every
lifecycle publish, from cursor 0) re-read and re-fanned-out all N leaked rows.
Unbounded durable growth + O(N)-per-event work that can starve real inbound
delivery. Gate materialization: give the durable replay a `TurnStateStore` seam and
materialize a handoff only when the completed run actually needs channel delivery —
`Inbound` origin always (source route or external target; blocked-on-OAuth channel
runs still deliver), or `WebUi` origin only with a sealed `External` target. Skip
WebApp/None-destination WebUI answers, scheduled triggers, and context-less runs.
The classification is fail-open toward delivery: any run-state/target lookup error
materializes, so a real channel reply is never dropped. The cursor still advances
past skipped events, so they are never re-scanned.

Fix 2 (MED) — the Final delivery's durable at-most-once identity was derived from
the re-fetched live `state.event_cursor`, stable only because Completed is currently
terminal. A future post-Completed cursor bump would drift the `ProjectionUpdateRef`
→ a new `delivery_id` → the CAS dedup misses → double final send. Key the Final
projection epoch off the frozen `event.cursor` the drain already loaded and
validated, making at-most-once structural rather than incidental.

Tests (run_delivery_contract), red-then-green:
- completed_webui_webapp_run_does_not_leak_a_durable_handoff and
  completed_scheduled_trigger_run_does_not_leak_a_durable_handoff — assert
  list_pending_run_final_reply_handoffs stays empty and the cursor advances; both
  FAIL (leaked pending handoff) with the gate neutered.
- completed_final_delivery_dedups_across_a_post_completed_cursor_advance — replays a
  Completed event after a simulated later cursor advance and asserts one send; FAILS
  (double "exactly-once final") when the epoch keys off live state.
- Existing channel-delivery journeys (delayed-OAuth final, duplicate-events-deliver-
  once, cross-channel, crash-replay) unchanged; the paginated-drain test reworked to
  keep Inbound fillers (which still materialize) instead of context-less ones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* style(product-workflow): clear pre-existing clippy 1.96 findings blocking -D warnings

The corrective PR's own commits left clippy 1.96 findings that fail the required
`cargo clippy -p ironclaw_product_workflow -p ironclaw_outbound --all-targets --
-D warnings` gate (unrelated to the delivery bug fixes; surfaced once the gate was
run). Cleared as drive-bys so the gate is green:

- lifecycle_auth_continuation.rs: collapse nested `if let { if }` into a let-chain
  (collapsible_if); let-chains are already used elsewhere in the workspace.
- reborn_services_contract.rs: drop `.clone()` on the `Copy` `ActivityId` (clone_on_copy).
- channel_pairing_contract.rs: allow type_complexity on a test-only field.

No behavior change; all touched test suites still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(reborn): route discovery-time provider 401/403 back to OAuth

Discovery credentials are checked present pre-discovery, but if the provider
rejects them mid-`tools/list` (token expired/revoked) the concrete MCP client
returns `McpClientError::AuthRequired`. The composition discovery classifier
folded every non-catalog error (via a catch-all) into `Transient`, so the
extension stayed `setup_needed` and was retried forever, re-hitting the same
401 and never escalating to re-OAuth.

`classify_discovery_error` now matches `AuthRequired` explicitly and maps it to
a new `HostedMcpDiscoveryError::ReAuthRequired`; genuinely transient failures
(timeouts, 5xx) stay `Transient` and an invalid catalog stays `Permanent`. The
activation transaction's discovery step returns a `HostedMcpDiscoveryOutcome`
so the composition impl can turn `ReAuthRequired` into the same
credentials-missing outcome the pre-discovery missing-credential path uses
(re-deriving the extension's declared requirements from the package). The user
is routed back through OAuth with credential blockers, nothing publishes
(no false activation), the staged discovery authority is still revoked, and
credentials are not discarded from the store.

Test-first regressions: classifier unit test (AuthRequired -> ReAuthRequired,
not Transient), transaction-level routing test (CredentialsRejected ->
CredentialsMissing, no false-active, authority revoked), and a composition
end-to-end test driving a real 401 mid-`tools/list` through the concrete
client (Ok re-auth response with credential blockers, capability unpublished)
-- both shown red against the old fold. The `..._when_credential_epoch_changes`
fixture now rotates a real authority input (access secret) rather than only a
timestamp, matching the corrected discovery-fence semantics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(webui): correct chat gate/card affordances, prune dead auth+i18n paths

Lane F of PR #6520 review polish (all under ironclaw_webui/frontend, test-first):

- gates.ts/chat.tsx: derive the channel-connection (pairing) gate through one
  shared `channelConnectionFromGate` predicate so the composer affordance and
  the pairing-card selector can never disagree. A `manual_token` gate carrying
  a stray `connection` now stays token-paste in both places instead of the
  composer promising pairing. Pinned by a caller-level chat.test.ts regression
  (mutation-verified) + gates.test.ts unit coverage; backend invariant
  (auth_prompt.rs: connection only on challenge_kind==pairing) documented.

- auth-generic-card.tsx: delete the dead `gate?.remediation ||` branch — neither
  AuthPromptView nor AuthPromptContextView carries a remediation field, so
  gates.ts never populates one. Test now pins the neutral fallback is always used.

- onboarding-pairing-card.tsx + i18n: rename the stale "paste"/"code" fallbacks
  (pairing.openAndPaste -> pairing.connectInstructions, pairing.checkCodeAndRetry
  -> pairing.connectFailedRetry) to match the QR/deep-link/web-code flow, and
  drop orphaned keys (pairing.title/instructions/placeholder/approve/success/
  error/none/resumeFailed, pairing.web.copyUsername) consistently across all 11
  locales. New i18n.test.ts case pins retired-absent / renamed-present parity.

- configuration-tab.test.ts: cover the post-save reseed branch where `save`
  resolves WITH the saved group — reseeds non-secret values from the SAVED
  group, secrets stay blank (mutation-verified; matches production save shape).

pnpm test (849 pass), pnpm lint, pnpm build all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* test(reborn): cover OAuth install Blocked::Auth path; stop boot re-attempt of Failed extensions

Item 1 (coverage): install_extension_on_surface accepts a dispatch-time
Blocked::Auth as success only when the caller-scoped membership readback
proves the exact package is now visible — pinning the join-before-block
ordering OAuth installs depend on. Adds two contract cases through the
real RebornServices seam (BlockedAuthExtensionInstallInvoker):
Blocked::Auth WITH membership visible -> success; WITHOUT membership ->
retryable 503. Break-to-confirm: removing the Blocked::Auth arm turns
case (a) into a 503, proving the test pins that branch. Also drops two
pre-existing clone_on_copy on ActivityId (Copy) in this owned test file.

Item 2 (fix): generic extension-host boot restore re-published every
installed non-hosted-MCP extension regardless of durable state, which
re-ran activation for a terminally-failed (Unhealthy) installation on
every boot and — on success — masked the failure as active, breaking the
InstallationState::Failed "does not auto-retry" contract (overview
§6.1). build_generic_extension_host now skips boot re-publication of an
installation whose durable health is Unhealthy, leaving it
installed-but-not-served and remediable; healthy and hosted-MCP restore
paths are unchanged. Regression test asserts a durably-failed install is
not re-published while a healthy sibling still restores.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(reborn): trust-gate [admin_configuration] to first-party manifests

An untrusted extension could declare an `[admin_configuration]` group
colliding with a first-party group id (e.g. `extension.slack`). The
composition fold registered admin descriptors from every catalog package
— including filesystem-discovered, non-first-party ones — so a colliding
third-party manifest either aborted every boot with a DescriptorConflict
(byte-different) or was silently registered as a consumer of the
first-party group's non-secret routing (byte-identical): a boot-DoS and a
non-secret confused-deputy.

Fail closed toward the safer behavior:
- Trust-gate `[admin_configuration]` at parse (v3): only host-bundled
  (first-party) manifests may declare an admin group. A filesystem
  manifest that declares one now fails to parse and is skipped by the
  existing fail-open catalog loader instead of aborting boot.
- Filter the composition fold (`admin_configuration_uses`) to first-party
  sources as defense in depth: a non-first-party package can never be
  folded as a consumer or reach the descriptor service.

Coverage:
- The v3 trust gate rejects InstalledLocal/RegistryInstalled admin
  groups; a filesystem package is skipped without aborting boot while
  the first-party group still resolves; the fold excludes non-first-party
  sources.
- The four previously-uncovered validate_channel_admin_configuration
  branches (egress credential, egress body credential, connection
  deep-link placeholder, wrong secret flag) now fail closed under test.
- The ordinary-user (lifecycle/setup) projection carries no admin
  material; the admin-configuration routes 403 a non-operator caller.

test_support::resolve now stamps HostBundled: its channel fixtures
declare admin-config-backed signing secrets, which only first-party
manifests may do; resolved output is source-independent for their
third-party trust class.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(reborn): stop the shared ingress classifier from swallowing normal chat

The channel-neutral gate-command classifier runs generically on every
inbound message. Any message whose first token was a command verb
(`approve`/`deny`/`auth`) but which was NOT the reserved gate-command
shape classified to `NoOp` and settled silently — no turn, no
user-visible feedback. A normal chat message like "approve this design"
was lost, and a bare `auth deny` (missing ref) or `auth deny gate:x
extra` was pulled out of the conversation the same way.

Distinguish a *confident* gate command — the reserved shape the system
advertises: a bare `approve`/`deny`, or any verb carrying a `gate:<ref>`
(`approve gate:<ref>`, `auth deny gate:<ref>`) — from ambiguous natural
language that merely starts with a verb. Confident commands still
classify to their resolution payload, so the real approve/deny/auth gate
flow is unchanged. Everything else now returns `Ok(None)` and falls
through to normal turn handling instead of being silently classified out
of the conversation as a no-op. (`Err` is still returned only when a
confident command carries a hostile/invalid ref that fails payload
validation.)

Because the classifier is the single channel-neutral definition, the fix
applies uniformly across Slack, Telegram, and the generic sink — no
per-channel drift.

Tested at two seams:
- classifier grammar: ambiguous verb-first text ("approve this design",
  bare `auth deny`, `auth deny gate:x extra`, "deny that idea") routes as
  a user message; confident `approve gate:<ref>`, bare `deny`, and
  `auth deny gate:<ref>` still parse to their resolution payloads.
- GenericChannelInboundSink ingress: ambiguous verb-first chat reaches
  the workflow as a UserMessage (a turn IS submitted, not swallowed);
  confident `approve gate:<ref>` still reaches it as an ApprovalResolution
  and bare `deny` as a ScopedApprovalResolution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(6520): harden trigger self-mutation + OAuth reconcile + origin/delivery coverage (lane T)

Review-pass items 1-6 for PR #6520:

1 (serve.rs): document that poller-on wires the TenantMembership grant
  unconditionally by design — fire access is gated on ACTIVE canonical
  membership at fire time (IdentityMembershipTriggerFireChecker denies
  suspended/unknown/wrong-tenant), not the auth method. Enforcement is already
  tested at the checker seam and the policy-shape regression test already exists.

2 (trigger_management.rs): broaden the routine self-mutation backstop to deny
  the Automation origin as well as ScheduledLoopRun (matches the descriptors'
  automation=Forbidden gate matrix); document the defense layering (the runner's
  scheduled_trigger surface-deny and the subagent flavor tool allowlist are the
  structural guarantees; this origin check is the backstop, which cannot see a
  subagent's lost ScheduledTrigger lineage — the surface exclusion protects that
  path, and spawn_subagent is globally disabled pending #4147). +5 crate-tier
  tests through dispatch().

3 (lifecycle_auth_continuation.rs): do NOT durably fence a SetupIncomplete OAuth
  continuation — return a retryable error so the caller leaves the flow
  un-fenced and a later cross-replica reconcile completes the fan-out once
  readiness is Active. auth.rs documents the caller side. +unit re-drive test +
  composition route reconcile-recovery test.

4 (scope.rs): document resolved_origin's LoopRun-only fallback invariant — a
  scheduled run always stamps ScheduledLoopRun upstream, so it never reaches or
  is downgraded by the fallback. +tests. A debug_assert/fail-closed was rejected:
  it breaks the pinned transitional-compat contract in ironclaw_capabilities::host.

5 (trigger_management.rs): test that an explicit delivery_target_id wins over a
  source run context and the implicit resolver is never consulted.

6 (invocation.rs): correct the stale "nothing wired into the dispatch path yet"
  doc (Invocation/InvocationOrigin are consumed live). The group_triggers
  external-source scenario now asserts the persisted delivery_target_id EQUALS
  the registered source target, not just is_some().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* chore(reborn): recapture composition pub-use snapshot + drop dead OperatorSetupCall alias

Two pre-existing merge blockers exposed by the strict gates (both on the base
PR head, not introduced by the correctness fixes):

- docs/plans/composition-pubuse.snapshot still listed `InboundPayloadClassifier`,
  which the generic refactor removed from the composition public surface;
  `ironclaw_architecture::composition_public_pub_use_surface_matches_snapshot`
  failed. Recaptured to match `lib.rs`.
- `type OperatorSetupCall` in webui_v2_handlers_contract.rs was unused, tripping
  `clippy -D warnings` (dead_code). Removed.

Result: `cargo clippy --workspace --all-targets --all-features -- -D warnings`
now exits clean across the workspace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK1qoiKDmXh2KzrgeMfTPf

* fix(reborn): finish #6442 test-suite reconciliation to the #6520 contracts

Production wiring restored (dropped in the merge, surfaced as dead code):
boot-time hosted-MCP reconcile after restore, the composed
RecipeAuthChallengeProvider (pairing + product-auth challenges) feeding
projections and channel delivery, current-delivery-target resolver assembly
attach, and manifest-derived account-setup descriptors (catalog + injected
extras) so /start pairing and connect notices compose again.

Test reconciliation (no test deleted or weakened): #6442-side suites
rewritten to the no-Activate three-state lifecycle (install drives
readiness; installs are caller-private, so surface tests install as the
surface user), caller-scoped 3-arg project with the production credential
gate, LifecyclePublicState assertions, admin-configuration seeding via the
composed resolver, RunFinalReplyDestination::External, and the 1-arg
active-extension authority in the integration harness. Three #6520 test
accessors ported onto the flat RebornRuntime. Ratchet maintenance:
HostedMcpDiscoveryOutcome frozen-name entry, specificity allowlist
re-pathing for merge-moved files, stale entries dropped, and the
banned "mcp_server" literal swapped out of a lifecycle test fixture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* chore(reborn): relocate channel-host e2e tests under the test-only path convention

The no-panics checker exempts src/**/tests/* and cannot see cross-file
cfg(test) gating; a #[path] attribute keeps the module name and its
super:: references unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix(reborn): honor typed auth denial end-to-end + compose the lifecycle OAuth continuation

Executor/runner denial tests rewritten to the typed-denial contract (the
host terminalizes the exact durable invocation through the capability
port; unrelated batch calls dispatch alongside). Three harness
HostRuntime wrappers forward the new decline_auth_capability trait
method (its default fails closed and was killing every denied-auth-resume
run at the integration tier).

Production fixes surfaced by the suites: the admin-configuration
resolver treats an extension with no declared [admin_configuration] (or
one imported after boot) as empty config instead of failing activation
with UnknownExtension; the restored hosted-MCP boot reconcile skips
unresolvable orphan installation rows instead of failing boot (§6.5);
and the factory now composes lifecycle_auth_continuation_dispatcher over
the base product-auth dispatcher via a two-phase build (dependencies
clone first, dispatcher wrap after the lifecycle facade exists) — an
extension-card OAuth completion re-enters the canonical lifecycle
install/readiness command instead of being durably fenced un-activated.
Regression: completed_lifecycle_activation_continuation_installs_the_extension
drives flow create → claim → complete → reconcile through the composed
runtime and pins install + membership + published tools + the fence.

Golden payload snapshots regenerated for the intended
ResultReference success-observation shape, with minted result refs
normalized alongside the existing volatile fields. Isolation fixture
supplies the bundled first-party surface (test-support builds skip the
cfg(test)-only injection); restart test expects caller-private scope
per the membership model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* style(reborn): format merged sources; carry frontend test reconciliation

The merge commit missed the unstaged frontend test rewrites (client
action id wire, no-Activate) and post-merge rustfmt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix(reborn): burn down post-merge CI failures — restore dropped wirings, reconcile tests to #6520 contracts

Production fixes (dropped in the main merge, restored from the PR side):
- factory.rs: register the product-owned RunFinalReplyRoutingService over the
  fail-closed UnavailableRunFinalReplyRouter so the model-facing
  builtin.outbound_delivery_target_route_current capability can route a run's
  final reply again
- factory.rs: seed the host-owned WebApp final-reply target in the outbound
  delivery target registry (host_owned_outbound_delivery_target_registry)

Test/harness reconciliations to the merged #6520 + client_action_id contracts:
- webui handlers contract: prime membership read-back for second installs,
  add required client_action_id to install bodies, reconcile retired
  "unsupported" phase literal to setup_needed (118/118)
- webui lib: update SPA shape tests to the Lane F channelConnectionFromGate
  predicate + split configure-modal expression (187/187)
- frontend: drop dead ./pairing-api import (tsc/lint green)
- composition: auth_tests lifecycle-activation callback test installs github
  first and asserts Active projection; core.rs auth-gate test seeds the
  caller-phase notion account (secretless) so the surface is model-visible
- webui_v2_e2e: uninstalled-phase literals, no-Activate reconciliations
  (install owns fail-closed provider-instance 400; OAuth-complete asserts
  active projection instead of the deleted activate route) (14/14)
- webui_v2_serve: setup projection phase literal (62/62)
- CLI extension: search envelope neutral phase (6/6)
- integration: client_action_id on isolation/product_api lifecycle bodies;
  harness github fixture gains a /tenants mount + filesystem run-state store so
  typed auth-gate deny/resume terminalizes durably (auth_gate 17/17); fake
  outbound facade mirrors the production always-present web_app target;
  QA smoke counts distinct install gestures (hosted-MCP bounded retries
  re-dispatch the same activity id)
- live-QA canary harness: send required client_action_id on install/setup
  submit (4 sites) — root cause of the 12/12 canary wipeout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix(e2e): skip quarantined retired-activation traces in manifest loaders

The PR quarantined the retired-activation fixtures (files moved under
quarantined_retired_activation/, manifest gained quarantined_model_cases)
but the three python selected_cases consumers still read every case file at
collection time, so pytest died on FileNotFoundError before running a single
scenario (the WebUI v2 smoke lane wipeout).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* merge: fold main c6696ba (#6583 product-surface vocabulary, #6580, #6588) into the PR branch

Adopts the agent-resolved merge commit 461e348 (78 conflicted files:
ironclaw_product_workflow -> ironclaw_product crate rename, adapters folded
into ironclaw_product/host_api, ProductSurfaceCaller/ProductSurfaceError
vocabulary, generic ProductSurface trait in host_api) and repoints the
post-merge test fixes to the renamed crate. #6520 semantics preserved: no
Activate anywhere, required client_action_id install gesture, event-driven
delivery, channel-config deletion upheld.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix(reborn): route channel-pairing completions through the lifecycle continuation dispatcher

Pairing completions dispatch AuthContinuationRef::LifecycleActivation, but
composition handed every ChannelPairingService a freshly built base
turn-resume dispatcher instead of the lifecycle-wrapped one product-auth
uses, so completing a pairing never re-ran readiness reconciliation /
runtime publication. Live repro on the demo stack: telegram remove →
install (activation parks on the connection requirement, unpublished) →
pair (bot replies paired) → card stuck at setup_needed forever.

Both product-auth and the pairing registry now share one wrapped dispatcher.
Regression pinned by pointer identity at the composition seam
(channel_pairing_completions_run_the_lifecycle_wrapped_continuation_dispatcher;
verified red pre-fix), via new test-support dispatcher accessors.

Also: live-QA canary harness reconciled to the post-#6520 wire (slack OAuth
start sends the manifest requirement handle; retired readiness booleans
dropped in favor of installation_state; non-secret setup values route
through the operator extension-configuration surface).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix(ci): burn down the remaining red lanes at 6e948cb

Production wiring restored (dropped in the merges):
- factory.rs: LocalRuntimeTriggerCreatorPairingHook regains the #6520
  resolve_implicit_delivery_target override through a restored
  TriggerFinalReplyTargetService, reading a new late-bound
  TurnStateStore slot (trigger_source_turn_state_store) that the
  test-support repoint seam swaps alongside the snapshot slot — a
  trigger created from an externally-sourced turn inherits that turn's
  reply destination again
- trigger_final_reply_target.rs: the implicit-inference arm seals no
  target when the source run's reply binding maps to no current
  outbound target (explicit targets still fail closed); previously it
  failed the whole trigger_create

Test reconciliations to the post-#6520 contracts:
- composition core.rs: webui bundle target listing pins the
  always-present host-owned web_app destination (was: empty)
- scenario_trigger_self_create_denied: typed Resolution::Denied
  (PolicyDenied) pin + denied-before-dispatch re-pinned on capability
  RESULT absence (the authorize consolidation records port invocations)
- outbound_target: inventory = two seeded targets + builtin:web_app
- mcp.rs (main #6580): nearai journey reconciled to no-Activate —
  account seeded before install, install owns readiness and publication
- qa smoke bundled-surface: hosted-MCP nearai.web_search excluded from
  the binary-tier static-surface assert (no discovery egress seam
  there); its contract is pinned end-to-end by reborn_integration_mcp
- event-driven delivery wire asserts poll with the files' bounded 30s
  deadline instead of one post-idle snapshot (4 sites: telegram pairing
  + coordinated reply, slack final reply, cross-channel immediate)
- model_replay assert_provider_tools re-checks the surface for up to
  10s (hosted-MCP discovery publishes asynchronously)

New executable evidence (inventory gate):
- ProviderOperationCase entries for github get_authenticated_user /
  get_repo / list_releases and google-calendar list_calendars, whose
  harvested journeys were quarantined with the retired activation flow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* merge: fold CI burn-down batch — trigger delivery-target capture restored, red lanes reconciled

Folds the agent-resolved CI fixes (2026299): restores the dropped
trigger-create implicit delivery-target capture (TriggerFinalReplyTargetService
over a late-bound TurnStateStore slot — the live "joke trigger delivered to
the web app instead of telegram" defect), reconciles the retired-contract
asserts (typed Denied verdicts, web_app always-present inventory, no-Activate
nearai evidence), converts four post-idle wire snapshots to the established
bounded poll under event-driven delivery, and re-covers the four
quarantine-orphaned provider capabilities with typed cases. Post-merge fmt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix: drop concrete extension name from generic composition comment; withdraw red repro pending its fix

The specificity gate rejects generic composition code naming a concrete
extension even in comments. Also withdraws the deliberately-red
member-remove repro test that was pushed prematurely in the previous
commit — it returns together with the identity fix that turns it green
(in flight), keeping the branch honest about what is currently pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix(ci): cycle-2 burn-down at 7011672

- facade/tests.rs: restore the `Blocked` import lost in the fold — the
  test-support/all-features compile error behind BOTH the Code Style
  clippy lane and the composition-core bucket (exact lane re-run: zero
  findings; bucket flags compile clean; facade tests 11/11)
- first-party coverage ratchet: `builtin.outbound_delivery_target_route_current`
  is a real registered builtin again (#6520 router restore); its e2e
  coverage is named — reborn_integration_delivery_user_journeys
  ROUTE_CURRENT journeys (26/26 ratchet suite green)
- webui_v2_product_api legacy canonicalization test: install body gains
  the required #6520 client_action_id gesture (29/29)

Responses API `test_reborn_responses_rejects_wrong_external_tool_call_id`:
unreproducible at this head — green locally alone, full-file, and in the
CI two-file shared-session shape (28/28); the one CI occurrence failed
server-side fast with the response error field truncated from the log.
Left untouched; if it recurs, capture the response error detail first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

* fix(webui): carry the client gesture id on extension remove — stop permanent input deduplication

Root cause of the live "remove says success but stays installed" defect: the
#6596 merge restored main's remove_extension, which derives its ActivityId
from (caller, capability, input) with no client gesture component. The
product-capability invoker replays the durably recorded resolution per
activity id, so after one successful remove of an extension, EVERY later
remove of the same extension by the same caller — including after a
reinstall — replayed the first remove's recorded success without
dispatching: HTTP 200, no new product-result row, durable membership
untouched. First-vs-repeat remove per (user, extension) exactly matched the
live timeline (notion's first remove worked and recorded; telegram/slack
replayed their 21:5xZ successes forever).

remove_extension now takes RemoveExtensionBody with a required
client_action_id and derives extension_lifecycle_activity_id, mirroring
install (#6520 gesture idempotency: distinct gestures dispatch, response-lost
retries replay).

Regression tests (all verified red before their fix):
- webui_v2_handlers_contract: remove gesture-idempotency test (distinct
  gestures -> distinct activity ids; retry -> same id)
- facade/tests: channel-extension remove through the real product dispatch
  asserts the DURABLE membership row is deleted (the prior test asserted only
  the resolution verdict), plus caller-scoped projection read-back (installer
  sees telegram, another member does not — acceptance-contract member
  binding)
- factory/tests: re-land the withdrawn member-remove repro, green over the
  factory-tier channel-disconnect slot fill

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6442 — fc7e5c89 Deployed Jul 23, 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 scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant