Skip to content

test(reborn): wave-3 integration coverage — journeys, multi-user isolation, triggered/outbound, budget/comm-context/hooks seams, golden payloads, denied-edge contracts - #5584

Merged
henrypark133 merged 37 commits into
mainfrom
reborn-cov-wave3
Jul 3, 2026

Conversation

@henrypark133

Copy link
Copy Markdown
Collaborator

test(reborn): wave-3 integration coverage — journeys, multi-user isolation, triggered/outbound, budget/comm-context/hooks seams, golden payloads, denied-edge contracts

Summary

Behavior-neutral coverage wave for the Reborn backend integration harness. Zero production-behavior changes: the diff is tests, test-support, and #[cfg(any(test, feature = "test-support"))]-gated composition accessors (compiled out of default builds — verified). Every test double substitutes only at seams production actively wires.

What's covered (new)

  • Multi-turn journeys (reborn_group_journeys): the canary twin — auth gate → resolve → approval gate → approve → action with the authed+approved tool → follow-up with context carry-over; deny arms (auth deny → model-visible failure, run continues); deny-then-retry; multi-actor gate isolation (gate resolution + resume state bound to the raising actor).
  • Multi-user isolation (reborn_group_multiuser): memory and auto-approve/approval-settings non-leak across actors, via a harness flag mirroring production's owner→actor resolution. (Confirmed [QA] Memories in the WebUI workspace are visible to every user in the workspace #5460 is a WebUI read-surface bug; the tool path isolates correctly.)
  • Triggered runs: origin propagation contrast arm (ScheduledTrigger vs Inbound, mutation-verified at the sole production propagation site); gates raised mid-fire (approve + deny arms with side-effect proofs); triggered completion + reply persistence through a production-mirroring prompt materializer.
  • Slack gate loop (crate tier): trigger fire → DM approval prompt (fake ProtocolHttpEgress at the production seam) → driver-recorded gate route → inbound bare "approve" resolves the gate on the run's foreign scope.
  • Outbound target tools (reborn_integration_outbound_target): list/set happy paths, settings-Deny → policy_denied, facade NotFound → invalid_input, approval-gated set (approve applies / deny leaves state untouched) — real production capability code, double only at the facade trait seam.
  • Budget + communication-context wiring (reborn_integration_budget, reborn_integration_comm_context): production build_default_budget_accountant and communication_context_provider seams live in the harness (semantics remain crate-tier — not re-authored).
  • Hooks infra (reborn_integration_hooks): recording/denying hook factories + wiring; scenarios land #[ignore]d RED on production bug reborn: HookedLoopCheckpointPort does not forward stage_checkpoint_payload/load_checkpoint_payload — any hooks-enabled coordinator turn fails at Checkpoint stage #5572 (checkpoint-forward gap) — un-ignore with the fix.
  • Golden inference payloads (reborn_integration_golden_payload): exact-match canonicalized full model-request payloads (insta snapshots; single volatile field = loop-start clock) + exact final-reply asserts — pins end-to-end prompt construction.
  • Disabled-capability contract (in reborn_integration_tool_call): disabled ids never offered in the tool surface; hallucinated call to a disabled id → pinned terminal model_error contract (UX follow-up: reborn: hallucinated call to a disabled capability fails the run as model_error instead of a model-visible denial #5583).
  • Blocked/denied edge batch (C-DENYEDGE): wrong-scope triggered resume ("turn run not found", with non-vacuity control), cross-tenant secret read (fail-closed UnknownSecret + correct-tenant re-read control), triggered self-create deny (int-tier fix(reborn): scheduled-trigger fires cannot create/mutate triggers (#5505) #5515 twin: never dispatched + trigger_list proves nothing created), busy-lock release after a Failed run (accepted resubmit, wedge-class), double-resolve (NotPending) + stale gate-ref resume ("gate resolution reference mismatch"), MissingGate bare-resolve, MCP output-too-large (output_too_large, run recovers). Four rows skipped with documented findings: never-registered capability ids are silently dropped pre-stage (no observable), extension installs are tenant-wide by design (no user-scoped seam), no drain seam on the int harness, HostInternal visibility deferred.
  • MCP wire-framing matrix: crate-tier framing tests + int-tier SSE-framed handshake/tool-call over the loopback mock; salvaged int-tier regressions for the [codex] fix exa mcp sse initialize parsing #5573 fix.
  • Multi-turn assertion infra: baseline-sliced assert_tool_error*_since, conversation-history containment asserts (fail-loud on out-of-range baselines), script-discipline docs.

Assertion meticulousness + de-bloat

A dedicated audit reviewed all 113 wave-3 assertions against the #5573 failure class (weak asserts letting legal-but-untested behavior slip). All 9 HIGH findings fixed (2 adapted with traced justification), 13/16 MED, 6 LOW. Separate commits trim comment bloat suite-wide (−362/+170 across 29 files): play-by-play and history narration removed; why-pins, product decisions, and seam contracts kept.

Production findings from this wave (issues, not fixes here)

Verification

  • cargo build --tests --all-features; cargo clippy --all --tests --examples --all-features -- -D warnings
  • All wave-3 bins + regression sweep green under --features integration (0 failures)
  • Composition crate --lib (incl. slack-v2-host-beta lane) green at --test-threads=4
  • Behavior-neutral proof: default-features cargo build -p ironclaw_reborn_composition compiles all additions out
  • Mutation/flip spot-checks per new mechanic (RED for the right reason, reverted)

Follow-ups (tracked, not in this PR)

Consolidation task (group.rs setter extraction, persisted_tool_error_summaries → _since(0) dedup, harness.rs split); chained triggered-origin gated journey; golden parallel-tool-calls + image-parts scenarios; compaction golden (blocked on #5582); needs-seam edge cases (lease-TTL clock, real egress pipeline under harness, admission-cap builder, reopen-resume-through-gate).

🤖 Generated with Claude Code

henrypark133 and others added 30 commits July 2, 2026 13:34
Add the discriminating contrast to the E-TRIGGERED-SUBMIT origin test:
a normal interactive submit_turn records TurnOriginKind::Inbound, read
through the same coordinator.get_run_state(...).product_context.origin
boundary the trigger test uses. Without a contrasting turn on the same
wire, the existing ScheduledTrigger assertion could pass on a hardcoded
origin; the pair proves the origin is genuinely propagated from the
submission path. Mutation-verified: breaking the production
TrustedTrigger->ScheduledTrigger mapping turns the trigger test RED while
the interactive contrast stays GREEN.

Also refresh the reborn_group_triggers TODO to record the current
disposition: origin coverage DONE (flat test, not this multi-thread
group), mid-fire approval gate STILL OUT (now authorable), and
outbound-delivery (C-TRIGGERED-DELIVERY) BLOCKED at int tier
(deliver_triggered_run is a private fn reachable only via a detached
tokio::spawn, unwired in any harness turn lifecycle; branch logic already
densely pinned by slack_delivery.rs's #[cfg(test)] module +
product_workflow outbound_delivery_contract.rs).

Tests-only; no production behavior change. E-OUTBOUND/C-OUTBOUND/
C-TRIGGERED-DELIVERY deferred: the outbound delivery sink is not wired
into any harness-reachable production composition path, so per the
no-wire-what-production-doesnt-wire rule it is reported, not wired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cipline doc (Wave-3 infra)

Adds the harness-infra baseline items from the reborn coverage roadmap:

- assertions.rs (additive impl block): history_len() + fail-loud
  history_slice, assert_tool_error_since / assert_no_tool_error_since /
  assert_tool_error_summary_contains_since (baseline-sliced multi-turn
  variants of the documented single-turn-only tool-error asserts), and
  assert_conversation_history_contains{,_since} +
  assert_conversation_history_role_contains (general persisted-transcript
  containment, role-filterable).
- reborn_integration_http_matcher.rs: multi_turn_baseline_sliced_history_assertions
  demo/regression — two error-raising turns on one thread; positive,
  slice-exclusion, role-discrimination, and out-of-range-baseline
  fail-checks.
- tests/support/reborn/CLAUDE.md: documents the new asserts and the
  script-discipline rule that a gated tool-call turn consumes exactly
  2 script entries (tool call + one post-resume model call) whether the
  gate is approved or denied.

Existing asserts untouched (strictly additive for parallel-lane
union-merge). No new harness wiring; helpers read already-persisted
thread history through the existing accessor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extends tests/reborn_group_multiuser with the C-MULTIUSER matrix: proof
that two distinct actors sharing the group's ONE capability backend
isolate their capability-side state per owner, the way production does.

E-MULTIUSER (#5526, `with_actor_id`) isolates only thread HISTORY (turn
store subtrees). Capability-side state — memory, auto-approve, approval
settings — was NOT isolated in the harness: the capability port factory
hardcoded one fixed execution user for every actor. Production instead
derives the capability `(tenant, user)` from the run owner/actor
(`local_dev_visible_capability_request`, wired via
`LocalDevLoopCapabilityPortFactory` at runtime.rs → capability_wiring),
falling back to a fixed id only when no owner is present.

Seam (tests/support/reborn, additive, default-off so every existing test
is byte-identical): `HostRuntimeCapabilityHarness` gains an opt-in
`scope_capability_by_run_owner` flag + `dispatch_user_for_run` that
mirrors production's owner→actor→fallback resolution; the harness
capability port factory now uses it. Two self-contained group
constructors (`multiuser_memory_tools`, `multiuser_approvals`) enable it,
plus per-owner auto-approve `enable/disable_auto_approve_for_owner`
helpers (same `AutoApproveSettingStore` + `(tenant,user)` key production
uses).

Scenarios:
- memory_isolation_across_actors: A writes memory, distinct actor B
  cannot search it, A still can (pins the tool-path isolation #5460's
  reporter confirmed; #5460 itself is the WebUI read surface).
- auto_approve_isolation_across_actors: A's always-allow grant lets A's
  write_file skip the gate while B (own always-allow OFF) still raises a
  real BlockedApproval on the identical call.

Mutation-verified: forcing the fixed user (ignoring the flag) turns BOTH
isolation scenarios RED for the right reasons (B reads A's memory; B no
longer gates), while two_actors_own_threads stays green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
skill_activate ContextBudgetExceeded is the one synthetic-capability
failure route coverable with ZERO new harness wiring: an oversized
system skill seeded through the existing seed_system_skill_for_test
seam drives the real selection path (reserve_skill_budget →
ContextBudgetExceeded → CapabilityOutcome::Failed) and surfaces as a
model-visible recoverable Failed tool error; the run completes and the
failed skill's instructions are proven NOT injected.

Remaining C-SYNTH routes (outbound_delivery_* — unwired, needs facade
double + constructor enabler; project_create Conflict/Unavailable —
needs whitebox service-error injection; skill AmbiguousSkill + source
B-routes) are deferred per the spike inventory; project_create
Denied/NotFound arms are unreachable from the real create_project path
(no require_role call) and stay unit-tier only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ET/C-COMMCTX/E-HOOK-INFRA/C-HOOKS)

Wave-3 rev-3 + hooks int-tier coverage. All changes are tests-only; the three
DefaultPlannedRuntimeParts seams wired here are production-active (verified
against build_reborn_runtime: model_budget_accountant, communication_context_provider,
hook_dispatcher_builder_factory are all Some in production), so wiring them in
the harness closes genuine drift (A1 audit), not new behavior.

- C-BUDGET: RebornIntegrationGroupBuilder::budget_accounting() +
  with_budget_accounting() wire the production build_default_budget_accountant
  (in-memory governor/gate-store/zero-cost-table + compiled-default seeding) into
  the group's one planned runtime; assert_budget_user_cap_seeded reads back the
  seeded daily cap. Wiring-liveness only — semantics stay at crate tier
  (budget_e2e.rs). tests/reborn_integration_budget.rs (+ negative guard).

- C-COMMCTX: RecordingCommunicationContextProvider double +
  communication_context_provider()/with_communication_context_provider() seams;
  assert the delivery-target + connected-channel slice renders into the model
  request. tests/reborn_integration_comm_context.rs (+ negative guard). The
  provider's facade->context mapping stays crate-tier covered.

- E-HOOK-INFRA: recording hook doubles (RecordingHookLog + observer/before-cap
  hooks) + recording_hook_factory/denying_hook_factory builders, wired via
  hook_dispatcher_builder_factory()/with_hook_factory().

- C-HOOKS: two scenarios are #[ignore]d RED regressions pinning a genuine
  cross-crate production bug discovered here: HookedLoopCheckpointPort
  (crates/ironclaw_hooks/src/middleware/checkpoint_port.rs) overrides only
  checkpoint() and does NOT forward stage_checkpoint_payload/load_checkpoint_payload
  to its inner port, so those fall through to the LoopCheckpointPort trait's
  fail-closed defaults. Any hook-dispatcher-active coordinator turn dies
  driver_unavailable at checkpoint_before_model. Hooks are off by default in
  production so this latent wrapper bug was never exercised — this is the first
  full-turn-with-hooks path. TODO(reborn-hooks-checkpoint-forward). Fix is
  cross-crate, out of scope for this tests-only lane; un-ignore once forwarded.

Shared support-file edits (group.rs/builder.rs/assertions.rs) are strictly
additive (new fns/fields appended) for clean union-merge with sibling lanes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…int tier

Extend the E-TRIGGERED-SUBMIT seam with submit_triggered_turn_scripted:
mirrors the production trigger poller's materializer
(ConversationContentRefMaterializer::materialize_prompt — pub(crate),
hence mirrored) instead of the for_fire shortcut: resolve the fire's
conversation binding (mints the scope pre-submit), record the prompt as
a real inbound thread message (without this the run dies
driver_unavailable on "unknown thread" — found empirically), build the
real thread-message:<id> content ref, register a scripted gateway for
the EXACT resolved scope (no fallback mechanism, no race), then submit
through the production TrustedTriggerFireSubmitter. Adds scope-aware
wait_for_status_in_scope / approve_gate_in_scope / deny_gate_in_scope /
resume_run_in_scope twins (builder.rs twins poll self.turn_scope;
additive-only constraint on shared support files defers consolidation).

New coverage:
- triggered_run_completes_and_persists_reply_in_trigger_thread
  (reborn_integration_triggered_submit): a triggered run completes and
  its final reply persists in the trigger's OWN thread — the
  int-tier-observable half of triggered delivery (the state production's
  deliver_triggered_run reads before pushing). Probe finding pinned in
  the test doc: no harness composition routes a completed run through
  render_outbound/OutboundDeliverySink; the push leg stays with the
  services-shell spike.
- triggered_gate_group (reborn_group_triggers,
  scenario_triggered_gate::{run_approve,run_deny}): a triggered fire
  raises a REAL BlockedApproval gate mid-fire; approve re-runs the gated
  write (file persisted), deny surfaces a non-retryable authorization
  failure (file absent). Finding: nothing in the scheduled_trigger
  surface/deny-map (#5505) suppresses approval gates — trigger-origin
  runs gate exactly like interactive runs.

Once{at} post-fire completion derivation deliberately NOT re-authored:
already pinned at crate tier (repository_contract.rs clear_active_fire →
Completed; trigger_poller_e2e.rs settle).

Mutation-verified: (1) skipping the scope-gateway registration turns the
completion test RED (model_error via the scope-miss sentinel);
(2) resuming in the harness thread scope instead of the fire's scope
turns both gate arms RED ("turn run not found"). Both reverted.

Tests-only; no production behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… slackloop)

Add a deterministic crate-tier twin of the live Slack canary flow to
slack_serve/e2e_tests.rs: a triggered run (personal, foreign thread
scope) blocks on approval; the production TriggeredRunDeliveryDriver
posts the approval prompt to the creator's Slack DM through a fake
protocol egress and auto-records a delivered gate route; an inbound bare
`approve` in that DM then resolves the gate on the run's foreign scope
via the DRIVER-recorded route (not a hand-seeded one).

This welds two production assemblies that were previously pinned only in
isolation: the triggered-delivery route recording (slack_delivery
cfg(test)) and the inbound delivered-route resolution
(bare_approve_in_dm_resolves_gate_recorded_by_observer). Both the driver
and the workflow share one DeliveredGateRouteStore; test doubles
substitute only at seams the production triggered factory
(build_triggered_run_delivery_hook_from_parts) fills (egress real,
binding_service Noop).

The final-reply tail after approve is pinned separately by
slack_approval_reply_resumes_and_delivers_final_reply; stitching it here
would race the triggered driver's own delivery loop against the live
observer's (two independent active_delivery_run_ids sets), a
cross-assembly dedup question outside this test's scope.

Test-only; zero production changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire the two production local-dev synthetic capabilities
builtin.outbound_delivery_targets_list / builtin.outbound_delivery_target_set
into the Reborn integration harness at the production-wired
OutboundPreferencesProductFacade trait seam:

- test_support wrap accessor reusing the REAL outbound_delivery_capabilities
  + wrap_local_dev_synthetic_capabilities + StoreApprovalSettingsProvider
  (mirrors the project_create / skill_activate seam precedent; all additions
  #[cfg(feature = "test-support")]-gated, zero production-behavior change)
- FakeOutboundPreferencesFacade double (in-memory targets; unknown id -> NotFound)
- outbound_target_tools() harness preset + group constructor;
  disable_auto_approve() harness method (gate arm);
  RecordingApprovalRequestStore so port-level synthetic gates record their
  approval scope for approve/deny_local_dev_gate
- New test bin tests/reborn_integration_outbound_target.rs covering:
  targets_list happy path; target_set happy path with facade read-back;
  settings-Deny -> Failed/policy_denied; facade NotFound -> Failed/invalid_input;
  approval gate approve -> resume applies; deny -> facade never reached

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add exact-match "golden inference payload" assertions to the Reborn
integration tier. Where assert_system_prompt_contains proves a substring
reached the model, this pins end-to-end prompt construction: the FULL
model-visible payload per inference iteration (system prompt, all turns,
and tool-call/tool-result messages) plus a compact ordered tool surface,
snapshotted with insta (the repo's established snapshot tool), and the
exact final user-visible reply.

Canonicalization renders the captured request to sorted-key JSON and
normalizes exactly one genuinely nondeterministic value — the runtime
context's model-visible wall clock — to <TIMESTAMP>. Everything else is
byte-exact: tool-call ids (deterministic call-N) and the surface sha256
content hash stay verbatim so real drift is caught.

Three representative scenarios: single-turn greeting (base prompt), a
tool-call turn (both iterations — pins tool-result feed-back with matching
tool_call_id), and a two-user-turn thread (pins history accumulation).
Tool JSON schemas are excluded from the golden deliberately (they are
pinned by the surface sha256 in the exact-matched system prompt and by
each tool's own tests) to avoid coupling these goldens to unrelated
builtin-tool edits. Regenerate drift with `cargo insta review`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tive-origin contrast

# Conflicts:
#	tests/reborn_group_triggers/main.rs
# Conflicts:
#	crates/ironclaw_reborn_composition/src/factory.rs
#	tests/support/reborn/harness.rs
…ture

Enablers for multi-turn journey coverage (auth gate -> resolve -> approval
gate -> approve -> follow-up on ONE conversation):

- HostRuntimeCapabilityHarness::file_and_github_auth_tools(): ONE
  build_reborn_services local-dev runtime surfacing BOTH file tools at
  PermissionMode::Ask (BlockedApproval) and an unseeded github.get_repo
  (BlockedAuth via the REAL ProductAuthRuntimeCredentialResolver). Making
  github.* genuinely dispatchable needed two test-support-gated composition
  accessors (publish into the active-extension registry + real asset-dir
  copy into the harness mount) — no production wiring changes.
- RebornIntegrationHarness::resolve_auth_gate(): the 'user submitted
  credentials' happy arm — seeds a real GitHub credential account WITH
  secret material through the production manual-token flow, then resumes
  with BlockedAuthGate precondition so the parked capability re-dispatches.
- RebornIntegrationGroup::live_auth_and_approval() preset
  (group_constructors.rs), subject-user-aligned like live_approvals.
- HARNESS BUG FIX: RecordingHostRuntime never forwarded the defaulted
  auth_resume_capability, so every auth resume died on the trait's
  fail-loud default ('auth-resume is unsupported by this host runtime') —
  latent until the first happy-path auth resume exercised it.
- HARNESS BUG FIX: resume_run idempotency key was run_id-only, so a run
  resuming through TWO gates (approval then auth) replayed the first
  resume's cached response and wedged at BlockedAuth; key is now
  (run_id, gate_ref)-scoped.
- assert_model_request_contains_all(): multi-turn context-carryover
  assertion (all needles in ONE captured model request).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…urneys)

Deterministic twins of the live canary multi-turn use cases, chaining
gate -> resume -> next turn on ONE conversation over the group's one
shared runtime:

- interactive_approval_journey: approve turn -> deny turn -> follow-up;
  asserts per-turn side effects (approved write persists, denied write
  absent, no cross-turn bleed) and context carryover (turn 3's ONE model
  request carries turn 1's and turn 3's user text).
- auth_then_approval_journey: turn 1 github.get_repo chains BlockedApproval
  -> approve -> BlockedAuth -> resolve (real manual-token flow) -> the SAME
  parked capability re-dispatches and its result carries the scripted
  network fixture body; turn 2 approval gate; turn 3 follow-up sees history
  across both gate classes.
- auth_deny_then_retry_journey (mixed arm): turn 1 auth gate DENIED (run
  continues, #4944 semantics); turn 2 raises fresh approval+auth gates,
  resolve succeeds — a denied auth gate does not poison a later resolve.
- multi_actor_gate_isolation: RED #[ignore]d — blocked on the unmerged
  C-MULTIUSER scope_capability_by_run_owner harness seam (distinct actor's
  gated dispatch is scoped to the canonical user on this base);
  TODO(reborn-multiuser-gate) pins the coverage for un-ignore.

Mutation-verified: seeding disabled -> journeys wedge at BlockedAuth (RED);
bogus needle -> context-carryover assert RED. Single-gate mechanics stay
pinned by reborn_group_approvals / reborn_integration_auth_gate (docs
cross-referenced); journey value is the CHAINING.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	tests/support/reborn/group.rs
#	tests/support/reborn/harness.rs
…vate over-budget routing (infra)

# Conflicts:
#	tests/reborn_integration_skill_activate.rs
#	tests/support/reborn/assertions.rs
…ET/C-COMMCTX/E-HOOK-INFRA/C-HOOKS)

# Conflicts:
#	tests/support/reborn/builder.rs
#	tests/support/reborn/group.rs
…sers (C-WIREFMT)

Pins every legal wire framing at first-party external-response parse
sites, per the rule: if a request's Accept header names N content types,
cover >= N response-format fixtures.

- ironclaw_mcp: crate-tier matrix through the unified parse_mcp_response
  dispatch (plain JSON, SSE single-event, SSE multi-event w/ keepalive,
  error-object in both framings, empty-body rejection in both framings).
  main already parses both framings via one shared entry — pinned so a
  future JSON-only sibling parse site can't ship untested.
- mock_mcp_server + reborn_integration_mcp: additive enable_sse_framing()
  toggle (default off; existing tests unaffected) and an int-tier case
  driving the real MCP client over an SSE-framed initialize/tools/list/
  tools/call handshake on loopback HTTP.
- gsuite_core: empty-body 204 framing through the real
  google-calendar.delete_event handler (response_body_json Null branch).
- oauth_provider_client: malformed 200 token-exchange body surfaces
  TokenExchangeFailed instead of panicking/succeeding.

Fixtures are hand-authored (no live-captured MCP/Google bodies exist
under tests/fixtures/); SSE shapes mirror the production unit fixtures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…EFMT)

Int-tier twins of #5573's crate-tier SSE-framing coverage: SSE-framed MCP
initialize through the real Reborn web-access handler + a both-legs-SSE
sibling-parity case. #5573 (already on base) shipped only the crate-tier
matrix; these exercise the full dispatch pipeline. Tests-only; the fix
itself rides in via #5573.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…olation (journey)

# Conflicts:
#	crates/ironclaw_reborn_composition/src/factory.rs
#	tests/support/reborn/CLAUDE.md
#	tests/support/reborn/harness.rs
The C-MULTIUSER scope_capability_by_run_owner harness seam merged in this
fold, so the RED #[ignore]d journey now runs on multiuser_approvals() (per-
actor capability dispatch). The scenario disables auto-approve per owner so
both actors raise a real BlockedApproval, pinning that gate resolution +
resume state stay bound to the raising actor. Green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pted seam

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lowError

Journey's publish_bundled_extension_for_test is the sole user of
ProductWorkflowError in factory.rs and is test-support-gated, so importing
it unconditionally warned (unused) under default features. Fully-qualify at
the use site to keep the default-features build warning-clean and the
addition fully behavior-neutral.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All 9 HIGH rows plus the mechanical MED/LOW rows from the wave-3
assertion audit: exact-token pins for gate-declined and hook-deny
summaries, payload-shape asserts on outbound target set/list, bounded
shape-filtered polling for the slack approval prompt (deterministic
under the ScriptedTriggerCoordinator auto-advance race), deny-arm
reply/egress pins in the auth journey, load-bearing auto-approve
grant proof, sliced-history fail-checks, SSE handshake method-order
and egress-count pins. Makes FakeOutboundPreferencesFacade stateful
and consolidates the duplicated trigger-fire/actor-pairing setup in
triggered_submit.rs now that the sibling lanes have landed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prose-only pass over wave-3-added doc-comments and test-body comments:
drops lane-history narration, play-by-play that restates adjacent
asserts, and cross-file duplicate explanations (one canonical copy +
cross-reference kept). Compresses the CLAUDE.md script-discipline and
sliced-history sections, corrects the now-stale "must add baseline
scoping first" caveats to point at the landed *_since variants, fixes
the stale ignored-test module doc on multi_actor_gate_isolation, and
scopes the mock MCP server's enable_sse_framing doc to what callers
actually exercise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns and support

Prose-only pass over the whole Reborn suite (integration bins, group
scenarios, qa, parity, test-support helpers): deletes play-by-play
narration, lane/wave/PR-process history, comments restating adjacent
asserts, and cross-file duplicate mechanism explanations (one canonical
copy kept with cross-references). Why-pins, seam contracts, mock-fidelity
caveats, and C-XXX tags retained. Net ~-200 comment lines; mechanically
verified comment/blank-only via -U0 diffs; zero code changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two contracts for the global DISABLED_CAPABILITY_IDS deny
(CapabilitySurfaceDenyFilter, outermost): the id is stripped from the
model-facing tool surface (non-vacuous — the manifest stub and spawn
decorator would otherwise surface it; builtin__http as presence
control), and a hallucinated call to it anyway is rejected at the model
gateway before registration, failing the run terminally with failure
category model_error and zero capability dispatch. General-surface
sibling of the scheduled-trigger deny coverage (#5515).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds golden_context_surfacing to the dedicated full-payload monitoring
suite: a wired communication-context provider plus the real builtin
capability surface on one turn, snapshotting byte-for-byte how the
communication section and capability surface render together in the
system prompt. Module doc now states the suite's purpose (watching
prompt-construction drift on a deliberately small scenario set).

Compaction golden is blocked and documented in-file: the byte-cap
overflow force-compact flag (CapabilityResultOverflow) is a dead letter
under ActiveTaskPreservingCompactionStrategy, which never reads
force_compact_on_next_iteration — needs a production decision first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The panic-lint scans files standalone and does not follow the parent's
#[cfg(test)] mod attribute, so the three test-only .expect() calls in
wait_for_gate_route / wait_for_approval_prompt_messages need explicit
// safety: suppression comments, matching sibling handler_tests.rs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 06:05
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5584 July 3, 2026 06:05 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

rustfmt moves trailing comments off an if-let scrutinee, which breaks
the lint's same-line suppression. Bind the loaded route first so the
// safety: comment sits on a plain statement rustfmt leaves alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5584 July 3, 2026 06:08 Destroyed

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
tests/support/reborn/assertions.rs (1)

270-303: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Duplicate collector logic — unify on [baseline..].

persisted_tool_error_summaries and persisted_tool_error_summaries_since are identical except for the slice (full history vs. [baseline..]). The full-history variant can just delegate to the baseline-scoped one with baseline = 0, removing ~30 duplicated lines of decode/truncate logic that would otherwise need synchronized edits.

♻️ Proposed dedup
     async fn persisted_tool_error_summaries(&self) -> HarnessResult<Vec<String>> {
-        let history = self
-            .thread_harness
-            .history(self.binding.thread_id.clone())
-            .await?;
-        history
-            .iter()
-            .filter(|message| message.kind == ironclaw_threads::MessageKind::ToolResultReference)
-            .map(|message| {
-                let Some(content) = message.content.as_deref() else {
-                    return Err("ToolResultReference message missing content".into());
-                };
-                serde_json::from_str::<ironclaw_threads::ToolResultReferenceEnvelope>(content)
-                    .map(|envelope| envelope.safe_summary.as_str().to_string())
-                    .map_err(|err| {
-                        let truncated = match content.char_indices().nth(200) {
-                            Some((cutoff, _)) => format!("{}...[truncated]", &content[..cutoff]),
-                            None => content.to_string(),
-                        };
-                        format!(
-                            "failed to decode ToolResultReferenceEnvelope: {err}; raw: {truncated}"
-                        )
-                        .into()
-                    })
-            })
-            .collect()
+        self.persisted_tool_error_summaries_since(0).await
     }

Also applies to: 693-722

🤖 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 `@tests/support/reborn/assertions.rs` around lines 270 - 303, The two
collectors duplicate the same history filtering and ToolResultReferenceEnvelope
decode/truncation logic; make `persisted_tool_error_summaries` delegate to
`persisted_tool_error_summaries_since` with a baseline of 0. Keep the shared
implementation in `persisted_tool_error_summaries_since` and have both methods
use the same `thread_harness.history` handling,
`MessageKind::ToolResultReference` filter, and error formatting so future edits
stay in one place.
🤖 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 906-943: Add the required arch-exempt marker immediately above the
`#[allow(clippy::too_many_arguments)]` on
`wrap_outbound_delivery_capabilities_for_test`; the forwarder currently has only
a doc comment, so include a `// arch-exempt: too_many_args, ... , plan `#NNNN``
tag with a short rationale. Keep the annotation paired with the existing
test-only wrapper in `runtime.rs`, and ensure the tag is placed directly before
the allow attribute so the exemption is recognized.

In
`@crates/ironclaw_reborn_composition/src/runtime/local_dev/outbound_delivery.rs`:
- Around line 82-133: The `wrap_outbound_delivery_capabilities_for_test`
function has a `#[allow(clippy::too_many_arguments)]` without the required
arch-exempt tag. Add the missing `// arch-exempt: too_many_args, <reason>, plan
`#NNNN`` comment directly above the allow on this function, using the function
name to locate it; this is the root implementation in
`runtime/local_dev/outbound_delivery.rs`, and the same rule still applies
separately to any downstream forwarders that also carry their own
`#[allow(...)]`.

In `@crates/ironclaw_reborn_composition/src/test_support/outbound_delivery.rs`:
- Around line 26-59: The wrap_outbound_delivery_capabilities_for_test function
is missing the required arch-exempt note for its clippy suppression. Add the
repo-standard `// arch-exempt: too_many_args, <reason>, plan `#NNNN`` comment
immediately associated with the existing `#[allow(clippy::too_many_arguments)]`
in this test-support wrapper so it matches the pattern used in the other
outbound_delivery helpers.

In `@tests/reborn_integration_golden_payload.rs`:
- Around line 136-171: The blocker note documents a real production gap in
ActiveTaskPreservingCompactionStrategy::should_compact, where the
force-compaction flag from
state.compaction_state.force_compact_on_next_iteration is never consulted.
Please verify there is a tracking issue for this dead-letter path and, if not,
add one and reference it from the reborn_integration_golden_payload test comment
so the finding is not left only as narrative text.

In `@tests/reborn_integration_hooks.rs`:
- Around line 1-146: The tests are intentionally RED and should stay ignored
until the checkpoint wrapper bug is fixed; keep the module doc, `#[ignore]`
reasons, and `TODO(reborn-hooks-checkpoint-forward)` aligned on the same root
cause in `HookedLoopCheckpointPort`. If you are doing the follow-up, implement
forwarding for `stage_checkpoint_payload` and `load_checkpoint_payload` through
`ironclaw_hooks::middleware::checkpoint_port::HookedLoopCheckpointPort` to its
inner port, then remove the `#[ignore]`s and update the regression notes so the
tests describe the now-working coordinator-path hook flow.

In `@tests/reborn_integration_skill_activate.rs`:
- Around line 143-187: The final assertion in
skill_activate_over_budget_surfaces_recoverable_failed uses a bare is_err() on
assert_model_request_contains, which can hide unrelated
request-capture/serialization failures. Update that check to follow the same
expect_err(...) plus message-prefix validation pattern already used in
skill_criteria_auto_activation_stays_off_on_coordinator_path, so the test only
passes when the skill instructions were truly not injected and not because of an
infra error.

---

Outside diff comments:
In `@tests/support/reborn/assertions.rs`:
- Around line 270-303: The two collectors duplicate the same history filtering
and ToolResultReferenceEnvelope decode/truncation logic; make
`persisted_tool_error_summaries` delegate to
`persisted_tool_error_summaries_since` with a baseline of 0. Keep the shared
implementation in `persisted_tool_error_summaries_since` and have both methods
use the same `thread_harness.history` handling,
`MessageKind::ToolResultReference` filter, and error formatting so future edits
stay in one place.
🪄 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: 0600beab-9405-4f6e-8f58-cae3d4af9dec

📥 Commits

Reviewing files that changed from the base of the PR and between 2b2e63f and 692f913.

⛔ Files ignored due to path filters (5)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
  • tests/snapshots/golden_payload__context_surfacing.snap is excluded by !**/*.snap, !tests/snapshots/**
  • tests/snapshots/golden_payload__greeting.snap is excluded by !**/*.snap, !tests/snapshots/**
  • tests/snapshots/golden_payload__multi_turn.snap is excluded by !**/*.snap, !tests/snapshots/**
  • tests/snapshots/golden_payload__tool_call.snap is excluded by !**/*.snap, !tests/snapshots/**
📒 Files selected for processing (72)
  • Cargo.toml
  • crates/ironclaw_first_party_extensions/tests/gsuite_core.rs
  • crates/ironclaw_mcp/src/lib.rs
  • crates/ironclaw_reborn_composition/src/extension_lifecycle.rs
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/oauth_provider_client/tests.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/outbound_delivery.rs
  • crates/ironclaw_reborn_composition/src/slack_serve/e2e_tests.rs
  • crates/ironclaw_reborn_composition/src/test_support/mod.rs
  • crates/ironclaw_reborn_composition/src/test_support/outbound_delivery.rs
  • tests/reborn_group_approvals/main.rs
  • tests/reborn_group_approvals/scenario_approve_always_persists_cross_thread.rs
  • tests/reborn_group_approvals/scenario_concurrent_dual_gate_resume.rs
  • tests/reborn_group_approvals/scenario_failure_category_demasked.rs
  • tests/reborn_group_approvals/scenario_gate_ref_edge_cases.rs
  • tests/reborn_group_approvals/scenario_gate_then_approve.rs
  • tests/reborn_group_extensions/scenario_remove_then_absent_cross_thread.rs
  • tests/reborn_group_journeys/main.rs
  • tests/reborn_group_journeys/scenario_auth_deny_then_retry_journey.rs
  • tests/reborn_group_journeys/scenario_auth_then_approval_journey.rs
  • tests/reborn_group_journeys/scenario_interactive_approval_journey.rs
  • tests/reborn_group_journeys/scenario_multi_actor_gate_isolation.rs
  • tests/reborn_group_multiuser/main.rs
  • tests/reborn_group_multiuser/scenario_auto_approve_isolation_across_actors.rs
  • tests/reborn_group_multiuser/scenario_memory_isolation_across_actors.rs
  • tests/reborn_group_triggers/main.rs
  • tests/reborn_group_triggers/scenario_trigger_self_create_denied.rs
  • tests/reborn_group_triggers/scenario_triggered_gate.rs
  • tests/reborn_integration_auth_failure.rs
  • tests/reborn_integration_auth_gate.rs
  • tests/reborn_integration_backend_matrix.rs
  • tests/reborn_integration_budget.rs
  • tests/reborn_integration_cancel.rs
  • tests/reborn_integration_comm_context.rs
  • tests/reborn_integration_durable.rs
  • tests/reborn_integration_golden_payload.rs
  • tests/reborn_integration_hooks.rs
  • tests/reborn_integration_http_matcher.rs
  • tests/reborn_integration_mcp.rs
  • tests/reborn_integration_oauth_connect.rs
  • tests/reborn_integration_oauth_refresh.rs
  • tests/reborn_integration_outbound_target.rs
  • tests/reborn_integration_profile.rs
  • tests/reborn_integration_safety.rs
  • tests/reborn_integration_secret_injection.rs
  • tests/reborn_integration_secrets.rs
  • tests/reborn_integration_skill_activate.rs
  • tests/reborn_integration_tool_call.rs
  • tests/reborn_integration_triggered_submit.rs
  • tests/reborn_integration_web_access.rs
  • tests/reborn_trace_first_party_tool_coverage.rs
  • tests/support/mock_mcp_server.rs
  • tests/support/reborn/CLAUDE.md
  • tests/support/reborn/assertions.rs
  • tests/support/reborn/builder.rs
  • tests/support/reborn/capability_backend.rs
  • tests/support/reborn/comm_context.rs
  • tests/support/reborn/github.rs
  • tests/support/reborn/golden.rs
  • tests/support/reborn/group.rs
  • tests/support/reborn/group_constructors.rs
  • tests/support/reborn/harness.rs
  • tests/support/reborn/harness_mcp.rs
  • tests/support/reborn/harness_web_access.rs
  • tests/support/reborn/hooks.rs
  • tests/support/reborn/mod.rs
  • tests/support/reborn/outbound_preferences.rs
  • tests/support/reborn/session_thread.rs
  • tests/support/reborn/triggered_submit.rs
  • tests/support/trace_llm.rs
💤 Files with no reviewable changes (3)
  • tests/reborn_integration_durable.rs
  • tests/reborn_integration_backend_matrix.rs
  • tests/reborn_integration_safety.rs

Comment thread crates/ironclaw_reborn_composition/src/runtime.rs
Comment thread tests/reborn_integration_golden_payload.rs
Comment thread tests/reborn_integration_hooks.rs
Comment thread tests/reborn_integration_skill_activate.rs

@serrrfirat serrrfirat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ran code-review-multi --force plus a thermonuclear code-quality pass on 1df3266a8a5bd939e489942c69b92a34eb0ddeb6.

Posted 4 inline findings:

  • Medium maintainability: the central Reborn harness absorbs several new capability-specific seams instead of following the existing split-module pattern.
  • Medium maintainability: triggered submit test support hand-mirrors the trusted trigger materializer path and can drift from the owning layer.
  • Medium test coverage: MCP SSE framing coverage still does not drive tools/list discovery.
  • Low concurrency/design: the outbound preferences facade records related state under separate mutexes.

Reviewer summary:

  • Security: no findings.
  • Bugs: no findings; that reviewer timed out initially and returned [] after a best-effort stop request.
  • Performance/concurrency: 1 low finding posted.
  • Tests: 1 medium finding posted. I did not post the ignored-hooks finding because the file documents the production checkpoint-forwarding blocker and the removal path.
  • Conventions + thermonuclear review: 2 structural findings posted.

Note: the codebase graph was present but still incomplete after reindexing in this worktree, so I used targeted PR-worktree inspection for the final review. No local test suite was run by this review pass.

/// both over); `None` for the lower-level constructors and the Echo backend.
/// Read back via `attachment_test_support_for_test`.
attachment_test_support: Option<ironclaw_reborn_composition::AttachmentTestSupport>,
/// Backing handles for the synthetic `outbound_delivery_*` capabilities

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thermo/code-quality: this PR adds outbound state, product-auth, bundled-extension activation, synthetic wrappers, and GitHub auth setup into the central harness, taking it from 4709 to 5326 lines. The support guide already treats harness_mcp.rs and group_constructors.rs as split precedent and says harness_auth.rs is tracked. Can we decompose this before merge? A cleaner shape is harness_outbound.rs for OutboundTargetToolsParts/wrapping/assertion handles and harness_auth.rs for GitHub/auth/bundled-extension setup, leaving harness.rs as the shared state/delegation shell. Anchors: tests/support/reborn/CLAUDE.md:99-108, tests/support/reborn/CLAUDE.md:109-114.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed the numbers — harness.rs is 4709→5326 lines this PR. That's genuinely large, but the harness_auth.rs split is already tracked independent of this PR (the CLAUDE.md note predates this branch — verified against origin/main), and the PR body's Follow-ups section explicitly lists the harness.rs split under the consolidation task. Given this branch is 30+ commits deep and every other comment on this file is anchored to exact line numbers, a structural move now would invalidate those anchors mid-review with no correctness gain. Sharpening the follow-up commitment though: new capability-specific state should be born in harness_outbound.rs / harness_auth.rs as pub(super) factories going forward (the harness_mcp.rs pattern) instead of landing inline in harness.rs — so the file stops regrowing wave over wave rather than getting a one-time retroactive split.

/// are poller-side pre-flight already pinned by
/// `trigger_poller_trusted_submit.rs`'s own crate-tier tests, and neither
/// affects the submit→run wire this seam exists to drive.
pub(crate) async fn submit_triggered_turn_scripted(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This helper now hand-mirrors most of ConversationContentRefMaterializer::materialize_prompt and then intentionally omits authorize_trigger_fire and validate_trusted_trigger_prompt. Even though this is test-only, trusted-trigger materialization is an ownership boundary called out in AGENTS.md:61, and duplicating it here means future changes to trigger binding/thread recording can drift from the integration path. Can we move a crate-owned test-support helper beside the real materializer that returns (TriggerMaterializedPrompt, TurnScope) for scripted gateway registration, then have this test support call that instead of rebuilding the resolve/thread/content-ref sequence by hand? That keeps trusted ingress construction in the owning layer and deletes most of this long function.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed this is a real mirror, not a coincidence — submit_triggered_turn_scripted reconstructs the exact shape of trigger_resolve_request + record_trigger_prompt + the thread-message content ref from materialize_prompt (trigger_poller_trusted_submit.rs:171-213), and both helpers are already pub(crate) in that same file, alongside an existing TenantScopedTrustedTriggerFireAuthorizer test double. So a materialize_trigger_prompt_for_test(..) -> (TriggerMaterializedPrompt, TurnScope) helper reusing all three — running the REAL authorize+validate steps instead of skipping them — is a small mechanical extraction, and that's the exact shape planned. Holding it out of THIS diff deliberately: the branch's bar is zero new production-crate surface mid-review, and the extraction deserves its own test-first cycle with a mutation check proving harness output tracks production. Tracking as a fast-follow with this exact shape, not the general backlog.

Comment thread tests/reborn_integration_mcp.rs
Comment thread tests/support/reborn/outbound_preferences.rs Outdated

@serrrfirat serrrfirat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thermo-nuclear maintainability review: behaviorally this looks test/support scoped, but I think there are a few structural cleanups worth considering before this harness gets harder to evolve.

/// are poller-side pre-flight already pinned by
/// `trigger_poller_trusted_submit.rs`'s own crate-tier tests, and neither
/// affects the submit→run wire this seam exists to drive.
pub(crate) async fn submit_triggered_turn_scripted(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] This helper mirrors production trigger materialization instead of reusing it. It reconstructs the trusted inbound binding, resolve request, thread recording, thread-message:<id> content ref, and gateway setup by hand, while the production path already owns most of that shape in ConversationContentRefMaterializer / record_trigger_prompt. This is a drift trap: future trigger-thread shape changes can break production while this harness keeps passing, or vice versa. Can we expose a #[cfg(feature = "test-support")] materializer helper/result from ironclaw_reborn_composition instead of duplicating the field-by-field flow in test support?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed this is a real mirror, not a coincidence — submit_triggered_turn_scripted reconstructs the exact shape of trigger_resolve_request + record_trigger_prompt + the thread-message content ref from materialize_prompt (trigger_poller_trusted_submit.rs:171-213), and both helpers are already pub(crate) in that same file, alongside an existing TenantScopedTrustedTriggerFireAuthorizer test double. So a materialize_trigger_prompt_for_test(..) -> (TriggerMaterializedPrompt, TurnScope) helper reusing all three — running the REAL authorize+validate steps instead of skipping them — is a small mechanical extraction, and that's the exact shape planned. Holding it out of THIS diff deliberately: the branch's bar is zero new production-crate surface mid-review, and the extraction deserves its own test-first cycle with a mutation check proving harness output tracks production. Tracking as a fast-follow with this exact shape, not the general backlog.

/// (C-SYNTH outbound seam). `Some` only for `outbound_target_tools()`;
/// `create_capability_port` wraps the port with the two capabilities via
/// `apply_synthetic_capability_wrappers` when this is `Some`.
outbound_target_tools: Option<OutboundTargetToolsParts>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P1] HostRuntimeCapabilityHarness is turning into an omnibus fixture with unrelated optional seams: outbound targets, run-owner scoping, product auth, network egress overrides, bundled extension activation, synthetic wrappers, and approval bookkeeping. This file is already over 5k lines after the PR, and this continues the pattern of making it a second composition framework. Can we decompose this first into fixture-family modules/structs, e.g. auth+extension harness, outbound-target harness, approval recording wrapper, and run-owner dispatch policy?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed the numbers — harness.rs is 4709→5326 lines this PR. That's genuinely large, but the harness_auth.rs split is already tracked independent of this PR (the CLAUDE.md note predates this branch — verified against origin/main), and the PR body's Follow-ups section explicitly lists the harness.rs split under the consolidation task. Given this branch is 30+ commits deep and every other comment on this file is anchored to exact line numbers, a structural move now would invalidate those anchors mid-review with no correctness gain. Sharpening the follow-up commitment though: new capability-specific state should be born in harness_outbound.rs / harness_auth.rs as pub(super) factories going forward (the harness_mcp.rs pattern) instead of landing inline in harness.rs — so the file stops regrowing wave over wave rather than getting a one-time retroactive split.

// ones `outbound_delivery_capabilities` consumes in production (the
// auto-approve + approval-request/lease stores are reused from the
// sibling `auto_approve_settings` / `approval_parts` harness fields).
if let Some(parts) = &self.outbound_target_tools {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] This adds another capability-specific branch into apply_synthetic_capability_wrappers. The function is becoming the synthetic-capability dispatcher with per-feature special cases. If more synthetic capabilities land, this becomes the central spaghetti switch. A small registry/list of wrapper objects would let each fixture contribute a wrapper without editing this function every wave.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Looked hard at whether a registry earns its keep here. The three branches share only the outer 'if let Some(state) = &self.field { wrap_x(...) }' skeleton — the real complexity is the non-uniform wrap_* argument lists (project_create ~6 args; outbound 12 across facade/approval/auto-approve/overrides/policies; skill_activate a different backing-source type). A trait-object registry needs each wrapper to close over its backing state at construction, which just moves today's if-let into a build_wrappers() constructor — every new capability still needs a new struct + wire-up line, so per-wave editing cost is unchanged and we add a trait + boxes + dynamic dispatch on the same amount of code. Declining at n=3; will reconsider if a 4th capability lands with the same single-field shape. The outbound 12-arg signature is being fixed directly (typed parts bundle) per the sibling thread.

…ing over-budget assert, SSE tools/list contract, atomic fake state

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 17:09
@ironloopai

ironloopai Bot commented Jul 3, 2026 •

Copy link
Copy Markdown
Contributor

IronLoop Review Status

Head: 705934b2d046cbea0989a0ceab3d6ddbf86aad5b
Updated: 2026-07-03T17:29:26.994Z
Admission: webhook accepted the request and IronLoop persisted review state before this projection.

Current reviewers:

Reviewer State What it means Last update
none Queued No reviewer jobs scheduled yet. n/a

Recent activity:

Time Reviewer State Detail
n/a n/a Waiting No progress events recorded yet.

Commands:

  • @ironloop review
  • @ironloop review <agent-alias>
  • @ironloop status

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5584 July 3, 2026 17:09 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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.

Caution

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

⚠️ Outside diff range comments (1)
tests/support/reborn/harness.rs (1)

2582-2584: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Retain network_egress on the harness

file_and_github_auth_tools() moves the injected Arc<dyn NetworkHttpEgress> into RebornBuildInput, but Self { network_egress: None } drops the harness-side handle. network_http_requests() then stays empty for this fixture, so tests can’t assert the actual outbound GitHub calls. Clone the Arc before passing it into with_network_http_egress_for_test and store the clone on Self.

🤖 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 `@tests/support/reborn/harness.rs` around lines 2582 - 2584, The harness is
dropping its `network_egress` handle in `file_and_github_auth_tools`, so
`network_http_requests()` cannot observe outbound GitHub traffic for this
fixture. Update the `RebornBuildInput` setup to clone the injected `Arc<dyn
NetworkHttpEgress>` before calling `with_network_http_egress_for_test`, and keep
that clone on `Self { network_egress: ... }` so the harness retains access to
the same requests.
🤖 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.

Outside diff comments:
In `@tests/support/reborn/harness.rs`:
- Around line 2582-2584: The harness is dropping its `network_egress` handle in
`file_and_github_auth_tools`, so `network_http_requests()` cannot observe
outbound GitHub traffic for this fixture. Update the `RebornBuildInput` setup to
clone the injected `Arc<dyn NetworkHttpEgress>` before calling
`with_network_http_egress_for_test`, and keep that clone on `Self {
network_egress: ... }` so the harness retains access to the same requests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 41afe109-b337-4eee-a9ff-b3c1495d16b6

📥 Commits

Reviewing files that changed from the base of the PR and between 1df3266 and 819e66b.

📒 Files selected for processing (8)
  • crates/ironclaw_mcp/tests/mcp_adapter_contract.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/local_dev/outbound_delivery.rs
  • crates/ironclaw_reborn_composition/src/test_support/mod.rs
  • crates/ironclaw_reborn_composition/src/test_support/outbound_delivery.rs
  • tests/reborn_integration_skill_activate.rs
  • tests/support/reborn/harness.rs
  • tests/support/reborn/outbound_preferences.rs

…5574

Merge brings the engine-v2 removal (#5545) and step-efficient tool
guidance (#5574). The tool-guidance change shifts the tool-surface
content hash inside the golden system prompts — the goldens' only
diff is the surface sha256 line, which is exactly the drift they
exist to pin. Regenerated both affected snapshots; all wave-3 bins
re-verified green on the merged base.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5584 July 3, 2026 17:29 Destroyed

@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.

Caution

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

⚠️ Outside diff range comments (1)
Cargo.toml (1)

283-285: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Gate ironclaw_hooks out of the default dependency graph. It’s added as a normal [dependencies] entry, so every default build pulls it in; move it behind test-support or into [dev-dependencies] if it’s only for the new test/support 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 `@Cargo.toml` around lines 283 - 285, The Cargo manifest currently pulls
ironclaw_hooks into the default dependency graph through a normal dependency
entry. Move the ironclaw_hooks declaration out of the main [dependencies]
section and place it behind the test-support path or into [dev-dependencies] if
it is only used by tests/support code, keeping the existing intent around the
hooks builders intact.
🤖 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.

Outside diff comments:
In `@Cargo.toml`:
- Around line 283-285: The Cargo manifest currently pulls ironclaw_hooks into
the default dependency graph through a normal dependency entry. Move the
ironclaw_hooks declaration out of the main [dependencies] section and place it
behind the test-support path or into [dev-dependencies] if it is only used by
tests/support code, keeping the existing intent around the hooks builders
intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f5791a5-9c32-4233-af45-1bf096f2f75b

📥 Commits

Reviewing files that changed from the base of the PR and between 819e66b and 705934b.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
  • tests/snapshots/golden_payload__context_surfacing.snap is excluded by !**/*.snap, !tests/snapshots/**
  • tests/snapshots/golden_payload__tool_call.snap is excluded by !**/*.snap, !tests/snapshots/**
📒 Files selected for processing (1)
  • Cargo.toml

@henrypark133
henrypark133 merged commit 6c63b3c into main Jul 3, 2026
109 checks passed
@henrypark133
henrypark133 deleted the reborn-cov-wave3 branch July 3, 2026 17:45
henrypark133 added a commit that referenced this pull request Jul 4, 2026
…pport (#5609)

* test(reborn): extract trigger-prompt materializer test-support helper (wave-4 lane C)

Committed follow-up on PR #5584's review thread: submit_triggered_turn_scripted
hand-mirrored ConversationContentRefMaterializer::materialize_prompt
(trigger_resolve_request + record_trigger_prompt + the content-ref shape,
field-by-field) instead of reusing it, and — as flagged — deliberately
SKIPPED authorize_trigger_fire and validate_trusted_trigger_prompt. Flagged
as a drift trap (trusted-trigger materialization is an ownership boundary,
AGENTS.md:61); the review agreed the fix is a #[cfg(feature = "test-support")]
materializer helper returning (TriggerMaterializedPrompt, TurnScope) living
beside the real materializer, held out of #5584 as a fast-follow with this
exact shape.

New production-crate (test-support-gated, compiles out of default builds)
surface in ironclaw_reborn_composition:
- trigger_poller_trusted_submit.rs: materialize_trigger_prompt_for_test,
  #[cfg(any(test, feature = "test-support"))] — runs the REAL production
  pipeline via ConversationContentRefMaterializer::materialize_prompt
  (authorize + validate + resolve + record + content-ref), then an
  idempotent second resolve_or_create_binding_with_trusted_scope call (safe
  — same request, same already-created binding) to also return the
  TurnScope the trait method computes internally but never exposes. Plus
  two crate-tier unit tests: positive (returned scope/content-ref match an
  independent ground-truth resolve) and negative (an unsafe prompt is
  rejected by the REAL safety validator).
- test_support/trigger_materializer.rs: pub, feature="test-support"-gated
  thin wrapper re-exported from test_support/mod.rs — the established
  wrap_project_create_capability_for_test-style pattern.

tests/support/reborn/triggered_submit.rs: submit_triggered_turn_scripted now
calls this ONE production-owned helper instead of hand-mirroring; deletes
~90 net lines of duplicated resolve/thread-record/content-ref logic.

Verified default-features build of ironclaw_reborn_composition stays
warning-free (function/import correctly compile out). Flip-checked at the
INTEGRATION level (not just the new crate-unit tests): forced an
injection-pattern prompt through submit_triggered_turn_scripted — every
triggered-gate scenario correctly failed with "rejected by safety scan",
proving the old hand-mirrored path's skip of validate_trusted_trigger_prompt
is now closed. Reverted before commit. All touched integration test bins
(reborn_group_triggers, reborn_integration_triggered_submit, plus every
other wave-4 lane-C bin) rerun green after the extraction.

* test(reborn): address trigger materializer review
henrypark133 added a commit that referenced this pull request Jul 4, 2026
… pack, triggered auth delivery, attachments, golden/synthetic expansions (#5610)

* test(reborn): doc/text + multi-attachment coverage (W4-ATTACH-VARIANTS)

Adds submit_turn_with_attachments (generalizes the image-only
submit_turn_with_image_attachment to N attachments of any mime type)
and two int-tier tests: a text/plain attachment's extracted text
reaching the model, and two attachments in one turn both reaching the
model with distinct index ordinals. Closes the doc/multi-attachment
gap in C-ATTACH (only single-image coverage existed before).

* test(reborn): W4-AUTHGATE-WIRE — runtime-401 provider-gate + cancel-no-replay (wave-4 row 1)

Pins the #5174/#5180 bug class (empty credential_requirements leaving
AuthPromptView.provider null, "Could not save the token" with no network
request) through the FULL scripted-gateway integration harness — a tier
below the existing crate-level pins, which drive CapabilityHost::invoke_json
or HostRuntimeServices::invoke_capability directly and never exercise the
real submit_turn -> BlockedAuth wire the WebUI depends on.

- tests/reborn_integration_auth_gate.rs: new
  runtime_401_after_injection_populates_provider_credential_requirement
  (github credential resolves OK but the runtime HTTP call 401s; asserts
  the resulting BlockedAuth gate's credential_requirements carries
  provider=github + ManualToken setup), cancel_blocked_auth_gate_leaves_no_stale_replay
  (cancelling a BlockedAuth run lands directly on Cancelled with no active
  worker, and the SAME real gate ref can no longer resume it afterward —
  closes the #5067/#4957 class of gates staying "live"), and
  deny_auth_gate_rejects_a_non_auth_gate_ref_prefix (negative companion).
  Flip-check: temporarily bypassed the host.rs enrichment call site,
  confirmed the flagship test fails with the exact pre-fix empty-list
  shape, restored (crates/ironclaw_capabilities/src/host.rs left
  byte-identical — no production diff).

- tests/support/reborn/harness.rs: RecordingNetworkHttpEgress gains an
  additive FIFO status_queue (default empty -> unchanged hardcoded-200
  behavior) + install_network_status_script accessor. Needed because
  GithubIssueTools' real WASM HTTP call flows through the network-egress
  lane, not the runtime-egress lane the existing ScriptedHttpResponse
  matcher scripts (try_with_host_http_egress overwrites the runtime port —
  see reborn_integration_secret_injection.rs's module doc) — the prior
  double had no way to script a non-200 status on that lane at all.
- tests/support/reborn/builder.rs: with_github_network_status(status)
  builder method (FIFO) threading github_network_statuses through
  RebornCapabilityBackend::install.
- tests/support/reborn/capability_backend.rs: wires keyed_http_responses
  (previously dropped for this backend) and the new github_network_statuses
  into the GithubIssueTools install arm; no-op for existing empty-vec callers.
- tests/support/reborn/assertions.rs: assert_network_egress_count, sibling
  of assert_egress_count for the network-lane call-count proofs above.

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

* test(reborn): unknown extension_id fails extension_install safely (W4-EXT-MANIFEST-ERR)

Narrowed from the originally-scoped manifest-content arms (schema
mismatch/reserved id/forbidden trust level): extension_install's only
input is a catalog-resolved extension_id over a fixed,
compile-time-embedded bundled catalog, so raw manifest TOML never
reaches ManifestV2Error validation through this capability in
production. The one reachable, wired arm is an unknown extension_id,
which fails Failed{invalid_input} rather than panicking or no-oping.

* test(reborn): W4-PROVIDER-VALIDATE — password/traceback caller-gap coverage

#5001 (PinchBench bucket D) removed the crude SENSITIVE_PROVIDER_TEXT_MARKERS
substring scan on provider reasoning/response_reasoning/signature text (bare
words like "password"/"traceback" were false-positive-rejected, driving
retry/give-up loops); the entropy-based LeakDetector is the real guard now.
That contract was pinned only at the private free-function level
(capability_port/provider_validation.rs's own unit test calling
validate_provider_tool_call directly) — the #5001 caller gap.

Adds provider_tool_call_registration_accepts_password_and_traceback_reasoning_text
in crates/ironclaw_loop_support/src/capability_port.rs's existing test module,
alongside the crate's other caller-level `port.validate_provider_tool_call(&call)`
tests: drives the REAL production caller
(LoopCapabilityPort::validate_provider_tool_call / register_provider_tool_call
/ invoke_capability on HostRuntimeLoopCapabilityPort, the same port the agent
loop calls) with "password"/"traceback" in all three metadata fields, and
proves genuine acceptance through to a real Completed dispatch (not just a
non-error return).

Flip-check: temporarily bloated response_reasoning past
PROVIDER_METADATA_TEXT_MAX_BYTES to confirm the assertion mechanism
discriminates a genuine rejection (fails with the expected
"exceeds 16384 bytes" error), then restored the password/traceback content.

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

* test(reborn): W4-MCP-SSO-WIRING — NEAR AI host-managed fallback through build_reborn_services

#5439 fixed NEAR AI MCP token resolution for SSO users: a Google-SSO user in
the same tenant/agent as the boot owner, with no NEAR AI token of their own,
now falls back to the host-managed (boot-owner) NEAR AI credential instead of
being prompted for one. That contract was pinned only at the private
rule/selector level (product_auth_runtime_credentials/tests.rs never calls
build_reborn_services) — the composition-wiring gap this row targets.

Adds local_dev_nearai_runtime_selection_falls_back_to_host_managed_account_for_sso_user
to extension_lifecycle_capabilities_auth_tests.rs (extending the existing
in-crate #[cfg(test)] composition-test file — same pattern as the sibling
github manual-token test above, template: product_auth_refresh_composition.rs's
"drive build_reborn_services directly" style). Drives ONLY the public surface:
build_reborn_services (local-dev always derives nearai_mcp_host_managed_scope
from the boot owner, so no live NEAR AI config injection is needed) plus the
crate-internal runtime_credential_account_selection_service() accessor this
file already had precedent for calling. Two discriminating arms on one
composed `services`: an SSO user in the owner's tenant/agent (different
project -- local-dev's host scope is project-unscoped by design) resolves via
fallback; an SSO user under a different tenant does not (CredentialMissing) --
proving the positive arm is a real scope match, not the selector always
succeeding.

Flip-check: temporarily short-circuited
RebornProductAuthServices::runtime_credential_account_selection_service to
always return the un-decorated selector (pre-#5439 behavior), confirmed the
new test's positive arm fails with CredentialMissing, restored (auth.rs left
byte-identical -- no production diff).

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

* test(reborn): C-SYNTH deferred arms — AmbiguousSkill seeding + project_create fault-injection (wave-4 lane C)

Two carry-over arms deferred from wave-3 PR #5584:

- skill_activate AmbiguousSkill: seed a system-scoped AND a user-scoped
  skill sharing one name (both SkillTrust::Trusted per
  FilesystemSkillBundleRoot::system/user) so the real
  validate_explicit_mentions_are_unambiguous reject path fires end-to-end,
  not just at the skill_activation.rs unit-test level. New
  seed_user_skill_for_test harness helper (additive, mirrors
  seed_system_skill_for_test).

- project_create fault-injection: new FaultInjectingProjectService test
  double (project_service_fault.rs) wrapping the real ProjectService at the
  production-wired Arc<dyn ProjectService> seam, forcing
  ProjectServiceError::Denied for a sentinel project name and delegating
  everything else to the real store. New
  project_tools_with_fault_injection()/project_lifecycle_fault_injected()
  harness+group constructors (additive).

  Deliberately NOT ProjectServiceError::Unavailable/Internal: investigation
  found both route through DefaultRecoveryStrategy's capability-retry
  branch, whose retry re-dispatch hits a real, confirmed production bug for
  provider-tool-call-originated invocations under local-dev composition —
  LocalDevCapabilityIo::resolve_capability_input rejects the reused
  input_ref on the retry with InvalidInvocation/"capability input ref was
  not staged for this loop run", collapsing the documented "retry twice,
  then a model-visible Failed" contract into an immediate terminal
  driver_unavailable. Documented in project_service_fault.rs; reported
  separately (not fixed — production change, out of this lane's scope).

Both flip-checked (mutated seed/fault-injection to prove discriminating
failure) and reverted before commit.

* test(reborn): golden payload expansions — parallel tool_calls, image attachment, gated-turn resume (wave-4 lane C)

Three scenario expansions to tests/reborn_integration_golden_payload.rs
(carry-over from wave-3 PR #5584):

- golden_parallel_tool_calls: new RebornScriptedReply::tool_calls([..])
  constructor (additive to reply.rs) scripts ONE assistant response with
  TWO tool_calls[] entries, pinning that multiple calls in one turn each
  get a distinct id and each following tool-role message's tool_call_id
  lines up in order — a shape the existing single-call golden_tool_call_feedback
  can't exercise.

- golden_image_attachment_turn: an inline image landed through the real
  submit_inbound_with_attachments entry point
  (RebornIntegrationGroup::attachment_tools()), routed through a
  vision-pattern model id, pinning the multimodal ContentPart::ImageUrl
  data: URL alongside the text part byte-for-byte.

- golden_gated_turn_approve: a real BlockedApproval gate raised, approved,
  and resumed (RebornIntegrationGroup::live_approvals()), snapshotting BOTH
  inference calls around the gate — proving the resume doesn't drop,
  duplicate, or reorder accumulated turn history.

Two normalization fixes to golden.rs, both needed for these scenarios to be
reproducible (discovered while authoring, not pre-existing regressions):

- Attachment-landing scenarios embed today's real UTC date in the landed
  project path (chrono::Utc::now(), no test seam) — added a second
  <DATE> filter alongside the existing loop-start-clock <TIMESTAMP> filter,
  or the image golden would bit-rot on every day boundary.

- Tool-call ids come from a NEXT_TOOL_CALL_ID counter shared by every test
  in this one compiled binary; running more than one tool-call-scripting
  golden test concurrently (the default `cargo test` thread pool) makes the
  raw id values order-dependent. Added normalize_tool_call_ids: renumbers
  every call-<N> to a canonical call-1, call-2, … in order of first
  appearance per rendered payload, preserving the id/tool_call_id linkage
  the golden actually cares about without depending on the racy raw value.
  Confirmed behavior-preserving for the four pre-existing snapshots (no
  diff) and confirmed the race is fixed (5 consecutive full-suite green
  runs). Flip-checked (forced two parallel tool_calls to share one id;
  golden correctly failed) and reverted before commit.

* test(reborn): W4-ASK-EACH-ONCE — ask-each-time approval resumes exactly once

#5306 fixed an unresumable BlockedApproval loop: require_approval_for_profile_policy
checked the explicit ask_each_time override (and the hard-floor force-approval
class) BEFORE consulting the matching one-shot approval lease a resume
carries, so an approved AskEachTime-gated resume re-hit the ask_each_time
branch and re-gated instead of completing. Only a Python E2E test
(test_tool_approval.py) exercised this class before; no Rust harness
coverage existed.

Adds scenario_ask_each_time_resumes_once.rs to the reborn_group_approvals
binary (both approvals_group_e2e and its libsql variant), run LAST because it
installs a persistent, group-wide ToolPermissionOverride::AskEachTime
override on builtin.write_file that would force-gate every sibling
scenario's plain-Ask-mode writes. Submits under the override, approves the
resulting BlockedApproval gate, and proves the resume reaches Completed in
ONE round trip with the write actually persisted — plus a companion
"resumes exactly once" proof that re-approving the same now-resolved gate_ref
fails NotPending (not a fresh re-raised gate).

tests/support/reborn/harness.rs: adds a generic
tool_permission_overrides: Option<Arc<dyn ToolPermissionOverrideStore>> field
(mirrors the existing auto_approve_settings field's pattern — populated only
by new_with_options, None elsewhere) and
set_ask_each_time_override_for_test, generalizing
disable_outbound_target_set_tool's override-store access beyond
outbound_target_tools() to any host-runtime-backed harness/group.

Flip-check: temporarily restored the pre-#5306 check order in
profile_approval_authorization.rs (ask_each_time/hard-floor before the
one-shot lease), confirmed the new scenario fails (the approved write never
persists), restored (file left byte-identical — no production diff).

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

* test(reborn): triggered-origin chained gated journey (wave-4 lane C)

Carry-over from wave-3 PR #5584: a triggered fire whose run raises a
BlockedApproval gate, gets resolved, then CHAINS into a SECOND
BlockedApproval gate in the SAME run (the post-resume model call issues
another gated tool call instead of finalizing), driven through
submit_triggered_turn_scripted (E-TRIGGERED-SUBMIT).

New scenario_triggered_chained_gate::run_chained_approve, registered as its
own live_approvals group in reborn_group_triggers::triggered_gate_group.
Re-reads TurnOriginKind::ScheduledTrigger fresh at the coordinator boundary
at THREE checkpoints (first park, second/chained park, final Completed) —
not just trusting the initial TriggeredSubmission — closing the gap that a
resume path rebuilding product_context from a non-trigger-aware default on
the SECOND hop would otherwise slip through undetected. Also asserts both
gate_refs are genuinely distinct, both chained writes persisted, and the
final reply persisted in the trigger's own thread.

Flip-checked (asserted the wrong origin kind; the checkpoint helper
correctly failed with the real ScheduledTrigger value in the diagnostic)
and reverted before commit.

Also folds in `cargo fmt` whitespace-only fixes surfaced while formatting
this new file (golden.rs, harness.rs, reply.rs, and two golden/skill-activate
test files touched by prior lane-C commits) — no semantic change, reran
their test bins green after formatting.

* test(reborn): extract trigger-prompt materializer test-support helper (wave-4 lane C)

Committed follow-up on PR #5584's review thread: submit_triggered_turn_scripted
hand-mirrored ConversationContentRefMaterializer::materialize_prompt
(trigger_resolve_request + record_trigger_prompt + the content-ref shape,
field-by-field) instead of reusing it, and — as flagged — deliberately
SKIPPED authorize_trigger_fire and validate_trusted_trigger_prompt. Flagged
as a drift trap (trusted-trigger materialization is an ownership boundary,
AGENTS.md:61); the review agreed the fix is a #[cfg(feature = "test-support")]
materializer helper returning (TriggerMaterializedPrompt, TurnScope) living
beside the real materializer, held out of #5584 as a fast-follow with this
exact shape.

New production-crate (test-support-gated, compiles out of default builds)
surface in ironclaw_reborn_composition:
- trigger_poller_trusted_submit.rs: materialize_trigger_prompt_for_test,
  #[cfg(any(test, feature = "test-support"))] — runs the REAL production
  pipeline via ConversationContentRefMaterializer::materialize_prompt
  (authorize + validate + resolve + record + content-ref), then an
  idempotent second resolve_or_create_binding_with_trusted_scope call (safe
  — same request, same already-created binding) to also return the
  TurnScope the trait method computes internally but never exposes. Plus
  two crate-tier unit tests: positive (returned scope/content-ref match an
  independent ground-truth resolve) and negative (an unsafe prompt is
  rejected by the REAL safety validator).
- test_support/trigger_materializer.rs: pub, feature="test-support"-gated
  thin wrapper re-exported from test_support/mod.rs — the established
  wrap_project_create_capability_for_test-style pattern.

tests/support/reborn/triggered_submit.rs: submit_triggered_turn_scripted now
calls this ONE production-owned helper instead of hand-mirroring; deletes
~90 net lines of duplicated resolve/thread-record/content-ref logic.

Verified default-features build of ironclaw_reborn_composition stays
warning-free (function/import correctly compile out). Flip-checked at the
INTEGRATION level (not just the new crate-unit tests): forced an
injection-pattern prompt through submit_triggered_turn_scripted — every
triggered-gate scenario correctly failed with "rejected by safety scan",
proving the old hand-mirrored path's skip of validate_trusted_trigger_prompt
is now closed. Reverted before commit. All touched integration test bins
(reborn_group_triggers, reborn_integration_triggered_submit, plus every
other wave-4 lane-C bin) rerun green after the extraction.

* test(reborn): W4-TRIGSLACK-SETTLE — auth-gate coverage for TriggeredRunDeliveryDriver

TriggeredRunDeliveryDriver was exercised by exactly one crate-tier test
(triggered_approval_prompt_route_resolves_dm_approve_on_foreign_scope),
covering only the approval-gate path. Add the auth-gate twin: a
BlockedAuth triggered run whose auth-prompt preference resolves to the
creator's DM must carry the OAuth setup link
(triggered_auth_prompt_route_delivers_dm_setup_link_on_foreign_scope),
mirroring slack_dm_delivers_auth_prompt_with_setup_link_after_immediate_ack's
assertion shape but driven through the real triggered-delivery driver.

TriggeredRunDeliveryDriver only ever targets the creator's personal DM
(never a channel), so there is no literal "channel" arm to mirror
slack_channel_auth_prompt_omits_setup_link_after_immediate_ack. The
discriminating negative arm instead exercises the driver's own
send-time OAuth-DM backstop
(triggered_auth_prompt_oauth_target_not_dm_suppresses_setup_link_and_cancels_run):
when the resolved auth-prompt target is not a personal DM, the setup
link must never be posted and the blocked run must be cancelled
instead.

ScriptedTriggerCoordinator gains an additive
new_with_first_poll constructor (script an arbitrary first-poll
status/gate_ref instead of the hardcoded BlockedApproval/GATE pair)
and a functional cancel_run (previously unreachable!, since the
approval-only scenario never called it) to support the OAuth-not-DM
arm. Test code only; no production changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(reborn): extract trigger-prompt materializer test-support helper (wave-4 lane C)

Committed follow-up on PR #5584's review thread: submit_triggered_turn_scripted
hand-mirrored ConversationContentRefMaterializer::materialize_prompt
(trigger_resolve_request + record_trigger_prompt + the content-ref shape,
field-by-field) instead of reusing it, and — as flagged — deliberately
SKIPPED authorize_trigger_fire and validate_trusted_trigger_prompt. Flagged
as a drift trap (trusted-trigger materialization is an ownership boundary,
AGENTS.md:61); the review agreed the fix is a #[cfg(feature = "test-support")]
materializer helper returning (TriggerMaterializedPrompt, TurnScope) living
beside the real materializer, held out of #5584 as a fast-follow with this
exact shape.

New production-crate (test-support-gated, compiles out of default builds)
surface in ironclaw_reborn_composition:
- trigger_poller_trusted_submit.rs: materialize_trigger_prompt_for_test,
  #[cfg(any(test, feature = "test-support"))] — runs the REAL production
  pipeline via ConversationContentRefMaterializer::materialize_prompt
  (authorize + validate + resolve + record + content-ref), then an
  idempotent second resolve_or_create_binding_with_trusted_scope call (safe
  — same request, same already-created binding) to also return the
  TurnScope the trait method computes internally but never exposes. Plus
  two crate-tier unit tests: positive (returned scope/content-ref match an
  independent ground-truth resolve) and negative (an unsafe prompt is
  rejected by the REAL safety validator).
- test_support/trigger_materializer.rs: pub, feature="test-support"-gated
  thin wrapper re-exported from test_support/mod.rs — the established
  wrap_project_create_capability_for_test-style pattern.

tests/support/reborn/triggered_submit.rs: submit_triggered_turn_scripted now
calls this ONE production-owned helper instead of hand-mirroring; deletes
~90 net lines of duplicated resolve/thread-record/content-ref logic.

Verified default-features build of ironclaw_reborn_composition stays
warning-free (function/import correctly compile out). Flip-checked at the
INTEGRATION level (not just the new crate-unit tests): forced an
injection-pattern prompt through submit_triggered_turn_scripted — every
triggered-gate scenario correctly failed with "rejected by safety scan",
proving the old hand-mirrored path's skip of validate_trusted_trigger_prompt
is now closed. Reverted before commit. All touched integration test bins
(reborn_group_triggers, reborn_integration_triggered_submit, plus every
other wave-4 lane-C bin) rerun green after the extraction.

* test(reborn): review fixes — consolidate slack e2e poll helpers, cite #5608 in fault-injection rationale

Factor the three near-identical bounded-poll-for-chat.postMessage
helpers in slack_serve/e2e_tests.rs into one predicate-parameterized
wait_for_post_messages_matching, and replace "Lane C final report"
citations with the filed issue (#5608) in the local-dev retry-path
rationale comments.

* test(reborn): address wave4 review comments

* test(reborn): relax auth gate harness wait

---------

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

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5584 — 705934b2 Deployed Jul 3, 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: medium Business logic, config, or moderate-risk modules scope: dependencies Dependency updates scope: docs Documentation size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants