Skip to content

reborn: no run-borking failures — collapsed recoverability stack (#4841 + #5389/#5390/#5403/#5613) - #5692

Merged
serrrfirat merged 49 commits into
mainfrom
reborn-collapse-integration
Jul 7, 2026
Merged

serrrfirat merged 49 commits into
mainfrom
reborn-collapse-integration

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

What this is

The integrated no-borking-failures release train: #4841's refreshed head plus the four upper-stack PRs (#5389 recoverable-error batches, #5390 FailureLane classifier + retry disposition, #5403 model-visible failure detail, #5613 LoopFailureKind fault matrix), replayed as one branch. The per-PR branches went stale against #4841's refresh (~147 commits of drift incl. #5445/#5585/#5633) and stopped being independently rebasable — this collapse supersedes them for canary and landing. Review-in-slices remains available on the original PRs; this is the land-as-one vehicle.

Restack reconciliations (each isolated in its own commit — reviewer attention here)

  1. failure_category_demasked pin model_error → model_unavailable — base was internally inconsistent (own test vs own mapping); TraceLlm exhaustion classifies as ModelErrorClass::Unavailable under the batch-2 fidelity mapping. Scenario intent (de-masked, non-sentinel category) unchanged.
  2. Egress contract pin request_denied → mcp_denied_credential_source — security boundary verified intact: SecretStoreLease guard fires before any transport (zero-requests assert still enforced); only the denial token got per-cause.
  3. Dropped two stale "raw payload"-rejection asserts — they pinned the pre-loosening SENSITIVE_SUMMARY_MARKERS; credential rejection is re-pinned by the stack's replacement tests.
  4. Outbound set-target routes through outbound_delivery_outcome — strict superset of Make missing outbound delivery targets recoverable #5445's NotFound special-case, unit-pinned.
  5. Fault-matrix rows run on 16MiB test threads — debug-only future-size overflow; production loop threads run 8MiB (ironclaw_reborn_cli serve runtime); repo-standard pattern.

Two base-bug fixes (bare #4841 is currently red on these): retry_run stub on the webui_v2 smoke fake, TurnLifecycleEvent.detail on the turn_stream_auth fixture — cherry-pick candidates for #4841 if it lands separately.

Validated locally (debug)

  • cargo clippy --all --benches --tests --examples --all-features — zero warnings
  • Fault-matrix e2e 13/13 · executor matrix 13/13 · group approvals 8/8 (pg+libsql) · 9-crate unit sweep (~1.2k tests) · egress contracts 73/73 · trace-error-path + lock-free-submit parity green
  • Not run locally: full flat reborn_integration_* lanes (compile-verified via clippy; behavior on CI here), PinchBench

Landing plan

Draft until: full CI green here → /canary on this PR → PinchBench ≥ 0.768. Then land as one squash; close #5389/#5390/#5403/#5613 as superseded-with-links, and #4841 folds in or retargets.

🤖 Generated with Claude Code

serrrfirat and others added 30 commits June 28, 2026 01:15
…lRequest

#4841 added the `inline_messages` field (serde default) to LoopModelRequest but
missed this one construction in the thread_loop_support_contract integration
test, breaking that test target's compile. Production builds default it to
Vec::new(); match that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… finer failure categories)

The main→#4841 merge surfaced two semantic conflicts the auto-merge missed:

- Clippy: main added `TurnCoordinator::retry_turn`; the StaticTurnCoordinator
  test stub in openai_compat_serve/tests.rs didn't implement it. Add the stub
  (returns Unavailable, matching its other methods).
- Test ironclaw_reborn: main's chaos tests (#5296) assert the coarse failure
  categories "driver_unavailable"/"model_error", but #4841 refined production to
  finer, accurate categories — a full checkpoint-state disk now yields
  "host_stage_unavailable_checkpoint" and an offline model provider yields
  "model_unavailable" (both deliberate named categories with dedicated
  failure_summary messages). Update the two stale assertions to match #4841's
  intended categorization.

Verified: the two turn_runner_worker_full_reborn_fails_* tests pass; clippy
ironclaw_reborn_composition --all-features clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onflicts

# Conflicts:
#	crates/ironclaw_reborn/tests/llm_gateway.rs
…ch 1)

Turns recoverable→bork mis-mappings into model-visible tool errors so the
agent self-corrects instead of the run dying. On top of #4841.

- agent_loop keystone: capability_error_class re-buckets Dispatcher /
  InvalidOutput / Unknown(_) / non-exhaustive default from Permanent (Abort)
  to OperationFailed (ToolErrorResult), aligning with the host_runtime
  disposition layer (which never intends a capability failure to abort).
  Cancelled / Permanent stay terminal. This makes "model called a nonexistent
  tool" (UnknownCapability/UnknownProvider -> InvalidOutput) recoverable.
- outbound_delivery: outbound_delivery_outcome routes recoverable
  RebornServicesErrorCode to Ok(Failed/Denied) (only Internal -> Err); fixed
  the safe_summary that interpolated the model-supplied target_id (Invariant 2);
  expired/not-yet-approved approval-lease arms -> Ok(Denied) instead of terminal.
- host_runtime: malformed model-supplied SandboxProcessPlan -> recoverable
  Failed{InvalidInput} outcome (defense-in-depth; the live gate in
  loop_support's host_runtime_input_for_capability is fixed in a follow-up).

Lib tests green: agent_loop 354, host_runtime 297, loop_support 337, reborn 259;
outbound_delivery 26.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the sandbox-plan fix. The live terminal gate is loop_support's
host_runtime_input_for_capability: a malformed/invalid model-supplied
SandboxProcessPlan returned AgentLoopHostError::InvalidInvocation, which
capability_host_error maps to terminal HostUnavailable{Capability} (run dies).

Now the invoke path downgrades that InvalidInvocation to a model-visible
Ok(CapabilityOutcome::Failed{InvalidInput}) so the agent can correct the
plan and the run continues. The helper only emits InvalidInvocation for the
sandbox-plan parse/validation case; its host-internal serialization failure
keeps its Internal Err. Updated both locked tests to assert the recoverable
outcome. loop_support lib: 337 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h 2)

Maps provider failures to the right LlmError variant so model-call errors
are explained accurately and context overflow recovers via context-shrink
instead of borking.

- rig_adapter (OpenAI/Anthropic/Ollama/Tinfoil/openai_compatible): map_rig_error
  now detects auth failures (401/403/invalid key) -> AuthFailed (non-retryable,
  non-breaker-tripping) instead of generic RequestFailed, so a bad key surfaces
  as a credentials problem rather than wasted retries + opaque run-bork.
- Codex (openai_codex_provider + codex_chatgpt): a stream ending without
  response.completed is now a retryable InvalidResponse/EmptyResponse instead of
  a silent successful Stop; codex_chatgpt now maps SSE error/response.failed
  events; both detect 413/context-overflow -> ContextLengthExceeded.
- github_copilot + anthropic_oauth: detect 413 (and 400+context body) ->
  ContextLengthExceeded so context-shrink recovery fires (401/429/5xx untouched).

ironclaw_llm lib: 915 passed, 0 failed. clippy + fmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…le error (batch 3)

Sweep finding: a method/capability the model named that does not exist
(RuntimeDispatchErrorKind::MethodMissing / UndeclaredCapability) mapped to
RuntimeFailureKind::Backend -> RetrySameCall, so it burned the retry budget
before becoming model-visible. Retrying never resolves a nonexistent target.

Now maps to InvalidInput -> ModelVisibleToolError: the model gets an immediate
"no such method/capability" tool error and self-corrects. Updated the pinning
table entries. host_runtime lib: 297 passed.

Batch-3 sweep conclusion: after the keystone + batches 1-2, no remaining
recoverable->bork CORRECTNESS defects exist (tool backends fully clean; no
hard-Err bypass on model-fixable conditions; nothing mapped to terminal
Cancelled/Permanent). This was the last shape-#3 quality nit worth fixing now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Item #2 foundation, built ON TOP of #4841's failure-surfacing machinery
(reuses category + FailureExplanationProvider + retryable rather than a
parallel RunFailureReason taxonomy).

- FailureLane enum (Retriable | Explainable | Security), wire-stable snake_case.
- failure_lane(category, retryable): retryable -> Retriable, else Explainable.
  Security is reserved for the ingress safety/leak refusal path (minimal
  security-stop policy) and is never produced at the run boundary; the match on
  category is the seam for a future mid-run safety-abort category.
- ALL_RUN_FAILURE_CATEGORIES: canonical list of every category the run boundary
  can produce.
- ENFORCEMENT TEST (every_failure_category_is_explainable_and_classified): locks
  the two-bucket invariant — every failure category resolves to a SPECIFIC user
  explanation (never the generic fallback) AND a definite lane. A new category
  that forgets its sentence, or regresses to the generic fallback, fails here.
  Plus canonical_list_covers_loop_failure_kinds guards against list drift.

reborn_composition failure_lane: 5 passed. clippy clean (the one pre-existing
needless_return in local_runtime_profile.rs is unrelated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Operationalizes the hybrid retry decision on top of the FailureLane classifier.
Pure decision function; the auto-redrive scheduler is its consumer.

- RetryDisposition { Auto | UserInitiated | NoRetry }, wire-stable snake_case.
- retry_disposition(category, retryable): no checkpoint -> NoRetry; transient
  host/lease/store/provider/tool faults -> Auto (silent re-drive from checkpoint,
  bounded by the scheduler); model/provider/config/model-fixable faults ->
  UserInitiated (retry affordance; a silent re-drive would just re-fail).
  Conservative Auto allowlist (anything not clearly transient -> UserInitiated).
- RetryDisposition::failure_lane() ties it back to FailureLane; a test asserts
  the two layers agree for every category in ALL_RUN_FAILURE_CATEGORIES.

reborn_composition retry_disposition: 5 passed. clippy + fmt clean.
Follow-up: the scheduler wiring that calls retry_disposition() to auto-requeue
(the behavior-flipping "Auto" half) — this is its tested decision core.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reborn over-sanitized capability/host failures: a real cause like
`missing input_schema_ref at /system/extensions/.../list_calendars.input.v1.json`
was collapsed to the generic "host runtime rejected capability request"
because there was no model-visible field to carry the raw cause and the
summary validator rejected any string containing `/`.

Policy shift: redact secret VALUES only; let paths, codes, schema refs,
and raw error text reach the model so it can retry or explain.

Foundation + Tier-1 vertical:

- AgentLoopHostError gains an optional model-visible `detail: Option<String>`
  channel (+ `with_detail`).
- CapabilityFailureDetail gains a free-text `Diagnostic { text }` variant.
- ToolObservationDetail::GenericFailure gains a bounded, leniently-validated
  `detail` (allows `/ { } [ ] < >`, rejects NUL/control + caps length) — the
  channel that already reaches the model and bypasses the strict summary
  validator.
- Relax ONLY the false-positive word bans in validate_loop_safe_summary and
  validate_tool_result_safe_summary (drop "provider error", "stack trace",
  "tool input", "traceback", "host path", "raw runtime", "invalid api key");
  keep the delimiter ban, control-char ban, length cap, and credential markers.
- Tier-1 producers stop dropping the cause: raw_agent_loop_host_error threads
  the value-scrubbed raw_detail into AgentLoopHostError.detail; the runtime
  model-visible failure path carries a value-scrubbed Diagnostic when the
  strict summary validator drops the reason; capability_helpers forwards the
  diagnostic into the model-visible observation.
- Boxed ProviderArgumentError.error to keep result_large_err quiet after the
  AgentLoopHostError/CapabilityFailureDetail size growth.

Tests cover the anchor (path string reaches the model-visible detail), secret
value redaction, the relaxed/retained summary markers, and legacy GenericFailure
JSON round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(tiers 2a, 3.2)

- ironclaw_mcp: replace the flat "response_error"/"request_denied" literals with
  per-cause diagnostic tokens (mcp_http_status_<code>, mcp_jsonrpc_error code=...,
  mcp_parse_failed, ...), bounded + control-char-stripped, no public signature
  change. The model now learns the real HTTP status / JSON-RPC code.
- reborn_composition: FailureExplanationInput gains a `detail` field rendered into
  the failure-explanation prompt (secret-scrubbed via sanitize_model_visible_text).
  Wired end-to-end in the projection; sourced once TurnLifecycleEvent carries detail
  (upstream chain in a follow-up commit).

mcp --lib 18 passed; reborn_composition --lib failure_explanation tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(tiers 2b, 3.1)

Complete the model-error-detail chain so the failure explainer (and the
model) receive the real cause of a model/provider/driver fault instead of
only a sanitized category. Only secret VALUES are withheld (scrubbed via the
existing value-level redactors); the descriptive cause now flows end-to-end.

Carrier `detail: Option<String>` (serde default + skip_serializing_if, so
pre-detail persisted rows rehydrate as None) added and threaded through:

- ironclaw_loop_support: HostManagedModelError.detail + with_detail; threaded
  in model_gateway_error into AgentLoopHostError.detail.
- ironclaw_agent_loop: AgentLoopExecutorError::HostUnavailableWithDiagnostics
  gains detail; model-stage construction carries error.detail.
- ironclaw_turns: AgentLoopDriverError::Failed.detail; TurnLifecycleEvent.detail
  (Failed events only, via failure_detail_for_event in the runner/memory path).
- ironclaw_reborn: map_provider_error puts the scrubbed provider reason into
  HostManagedModelError.detail; planned_driver carries HostUnavailable detail
  into AgentLoopDriverError::Failed; turn_runner/turn_run_executor carry it into
  the failure record.
- ironclaw_reborn_composition: detail_for_turn_event sources from
  event.detail, feeding the FailureExplanationInput.detail already rendered in
  the explainer prompt.

Construction-site churn: detail added to TurnLifecycleEvent / AgentLoopDriverError
test fixtures and the event_projections pending-gate test support.

Verified per crate (--lib): turns 355, loop_support 342, agent_loop 261,
reborn 175, event_projections 26 — all green; composition --lib 1042 passed
(1 pre-existing live_progress_stream failure, unrelated). turns integration
contracts compile; clippy clean across the chain. Pre-existing base-branch
breakage in loop_support thread_loop_support_contract (inline_messages) is
unrelated to this change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail channel (TurnLifecycleEvent.detail) intentionally carries a
secret-scrubbed description of the real failure cause to the model/explainer;
update the guardrail so the spec matches the behavior. Only secret values are
withheld; raw unscrubbed backend strings still stay behind host adapters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tion tests

The tier-2b/3.1 detail field on AgentLoopDriverError::Failed broke construction
and pattern sites in reborn's integration test targets (concurrent_workers,
loop_driver_host) that the original --lib gate never compiled. Constructions get
detail: None; the driver_host_error helper carries error.detail; exhaustive
match patterns bind detail: _.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… detail channel

Two integration test targets asserted pre-refinement model-visible strings that
the --lib gate never compiled (test-through-the-caller gap):

- mcp_adapter_contract: 5 assertions expected the flat "response_error"/
  "request_denied" tokens; update them to the per-cause tokens the Tier 2a
  change now emits (mcp_invalid_protocol_version, mcp_jsonrpc_id_mismatch,
  mcp_invalid_session_id, mcp_http_status_500, mcp_denied_credential_source).
- llm_gateway: the offline-provider test asserted the error Debug leaked NO
  provider detail at all. Tier 2b deliberately surfaces the secret-scrubbed
  non-secret cause on the detail channel. Rewrite (and rename) the test to the
  current policy: assert the non-secret reason ("connection refused", endpoint
  URL) reaches the model via `detail`, while the credential token
  (sk-provider-secret) is scrubbed from both `detail` and the full Debug. This
  strengthens the secret-scrubbing guard.

mcp full suite green; llm_gateway full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Backend

first_party_missing_handler_fails_closed_without_side_effect_handler asserted
the dispatch failure kind was Backend, but #5389 deliberately reclassified an
UndeclaredCapability/MethodMissing dispatch failure (a capability the model
named that has no registered handler) to InvalidInput — a model-fixable,
model-visible tool error that must not burn the retry budget on a call that can
never resolve by retrying (see the From<DispatchFailureKind> mapping in
production.rs). The test still fails closed (Failed outcome) and still carries
"dispatch failed: UndeclaredCapability"; only the kind assertion is updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ive category table

Phase 1 of the per-error coverage harness (docs/plans/2026-07-03-loop-failure-matrix.md):

- turns: all_failure_kinds category table now exhaustive (13/13 — adds the
  previously-missing CheckpointUnavailable + CompactionUnavailable) with a
  same-crate exhaustive-match guard so a new variant breaks compilation.
- agent_loop: new table-driven executor failure matrix
  (executor/tests/failure_matrix.rs) driving every executor-reachable
  LoopFailureKind at its real origin via MockHost seams, asserting per row:
  P1 (reason_kind + sanitized category/safe_summary), P3 (no fabricated
  final assistant reply), and explanation_message_refs presence per the
  explainable set. New fail_transcript_with test knob on MockHost +
  DriverMockHost (test code only).
- Four divergences found and documented (doc §5a), asserted as actual
  behavior, none silently fixed: Approval+SkipAndContinue completes (gate
  enforcement gap), NoProgressDetected missing its explanation attach,
  single Denied recovers-and-completes (no-borking working as designed),
  TranscriptWriteFailed/CheckpointRejected legacy-only enum origins.

Validated (bounded): cargo test -p ironclaw_turns all_failure_kinds;
cargo test -p ironclaw_agent_loop failure_matrix; check/clippy -D warnings/
fmt on ironclaw_agent_loop — all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2 of the per-error coverage harness (docs/plans/2026-07-03-loop-failure-matrix.md):

- planned_driver: resume with missing checkpoint payload asserts
  LoopFailureKind::CheckpointUnavailable + "checkpoint_unavailable";
  in-flight model Cancelled (no cooperative cancel signal) asserts
  map_executor_error yields "interrupted_unexpectedly".
- e2e: binary-level divergence lock — the same in-flight Cancelled run
  projects "driver_failed" at the runner boundary (category overwritten;
  doc §5a.5, candidate follow-up to preserve the driver-mapped category).
- e2e: non-model P4 row — capability-stage invocation failure is
  retryable ("host_stage_unavailable_capability", checkpoint preserved,
  no fabricated reply) and retry_run resumes to completion, so P4 is no
  longer proven only through the model stage. New scripted
  capability-invocation-error mode in the test harness (test support only).

Validated (bounded): targeted planned_driver tests + full
reborn_failure_retry_resume_e2e (15 passed) + clippy -D warnings + fmt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@serrrfirat
serrrfirat added this pull request to the merge queue Jul 6, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 6, 2026
serrrfirat and others added 2 commits July 7, 2026 00:42
…main

Merging origin/main brought two new TurnCoordinator test doubles
(UnusedTurnCoordinator in src/runtime.rs, SpyTurnCoordinator in
tests/runtime.rs) that predate this stack's retry_turn addition to the
TurnCoordinator trait. Add the impls (unimplemented!/delegate, matching
each double's existing method style) so the merged tree compiles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5692 July 6, 2026 23:18 Destroyed
…ref)

Newly published advisory (after this branch and main), transitive via
crossbeam-epoch. The affected path is the `fmt::Pointer`/`Debug` impl for
`Atomic`/`Shared` when the pointer is already invalid — a formatting path
we do not exercise. Ignore with justification per the existing advisories
convention; remove when the fixed crossbeam-utils release propagates.
Verified `cargo deny check advisories` = ok locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5692 July 6, 2026 23:21 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

Verdict: ✅ Approved
Findings: 0 blocking / 0 notes
Next: No reviewer action needed.
Head: 65721963236cbf657fd7137ca104278d1866d584

Run details

Status: Current
Needs human: no
Needs validation: no

**Inline candidates:** 0

Summary

No concrete blocking issues found in the reviewed diff. The change adds failed-run retry plumbing, retryable projection metadata, failure summaries/details, and supporting tests across the turn store, coordinator, Reborn runtime, product workflow, and WebUI route layers.

Findings

None.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloop review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloop review when the fix may affect multiple areas.
  4. Use @ironloop status to check queued/running/completed/stale/stalled state while reviewers run.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5692 July 7, 2026 01:06 Destroyed
ironloopai[bot]
ironloopai Bot previously requested changes Jul 7, 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

Verdict: ❌ Changes requested
Findings: 1 blocking / 0 notes
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.
Head: b4b4f63ccff0b667fc1626d165bce1f3fdb25019

Run details

Status: Current
Needs human: no
Needs validation: no

**Inline candidates:** 1

Summary

Found one blocking issue: the new failure-detail path is dropped before the scheduler records driver failures, so the user-facing failure explanation path cannot receive the detail this PR threads through the driver.

Findings

1. ❌ [MEDIUM] Preserve failure detail when returning executor errors

Location: crates/ironclaw_reborn/src/turn_run_executor.rs:197
sanitized_driver_failure(...) now builds a SanitizedFailure with the scrubbed detail, but this line converts it back to TurnRunExecutorError::new(failure.category()), which discards that detail. The scheduler records error.failure() via record_runner_failure, so production driver failures from this path will persist only the category and TurnLifecycleEvent.detail remains empty. That means the new failure-explanation flow gets the fallback summary instead of the real model/provider cause for common AgentLoopDriverError::Failed cases. Add a TurnRunExecutorError constructor that accepts the full SanitizedFailure (or record the runner failure here) so the detail survives into turn state.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloop review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloop review when the fix may affect multiple areas.
  4. Use @ironloop status to check queued/running/completed/stale/stalled state while reviewers run.

Comment thread crates/ironclaw_reborn/src/turn_run_executor.rs Outdated
…or (IronLoop)

The driver-failed Err path in execute_claimed_run converted the computed
SanitizedFailure back to TurnRunExecutorError::new(category), dropping the
scrubbed model-visible detail. The scheduler records error.failure(), so
production driver failures persisted only the category and
TurnLifecycleEvent.detail stayed empty — the failure explainer got the
fallback summary instead of the real provider/model cause.

Add TurnRunExecutorError::from_failure(SanitizedFailure) (the struct already
holds a full SanitizedFailure) and use it at the call site so detail
survives across the host-runtime boundary. Caller regression test:
driver Failed{detail: Some(..)} -> execute_claimed_run -> err.failure().detail()
is preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5692 July 7, 2026 01:28 Destroyed
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

@IronLoop review --agent reviewer

Addressed the failure-detail-drop finding in f5e3937 (TurnRunExecutorError::from_failure preserves the scrubbed detail; caller regression test added).

ironloopai[bot]
ironloopai Bot previously requested changes Jul 7, 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

Verdict: ❌ Changes requested
Findings: 1 blocking / 0 notes
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.
Head: f5e3937252a110825d3106a2aeeb3d2ade8c1c2f

Run details

Status: Current
Needs human: no
Needs validation: no

**Inline candidates:** 1

Summary

Found one security-sensitive prompt handling issue in the new failure explanation path. I could not run Rust tests because cargo is not installed in this review environment.

Findings

1. ❌ [MEDIUM] Frame untrusted failure detail before sending it to the explainer model

Location: crates/ironclaw_reborn_composition/src/projection/turn_events.rs:995
detail is sourced from provider/tool/runtime error text, so it is attacker-controlled in cases like MCP/server errors or provider bodies. sanitize_model_visible_text only redacts credential-shaped tokens and preserves newlines/instructions, so a detail such as \nfallback_summary: ...\nIgnore previous instructions... is injected directly into the explainer prompt as additional fields/instructions. Since the explainer output becomes the public failure_summary, this creates a prompt-injection path into user-visible failure messaging. Encode or quote the detail as data (for example JSON-string escaping, line-prefixing every line, or using the repo's prompt-envelope pattern for untrusted text) before adding it to the prompt.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloop review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloop review when the fix may affect multiple areas.
  4. Use @ironloop status to check queued/running/completed/stale/stalled state while reviewers run.

Comment thread crates/ironclaw_reborn_composition/src/projection/turn_events.rs Outdated
…mpt (IronLoop)

detail is untrusted provider/tool/runtime error text (e.g. MCP server or
provider bodies). sanitize_model_visible_text redacts credential tokens but
keeps newlines/instructions, so appending it raw let a crafted error inject
extra prompt fields or directives (a fake fallback_summary:, an 'ignore
previous instructions') into the failure explainer — whose output becomes
the public failure_summary. That is a prompt-injection path into
user-visible messaging, widened by the detail-preservation fix.

Frame detail as data: JSON-string-escape it so newlines/quotes are escaped
and it stays a single quoted 'detail: "..."' value. failure_category and
fallback_summary are host-authored (category-derived) and unchanged.
Regression test: a detail embedding newline+fallback_summary+directive is
neutralized (exactly one real fallback_summary line, no directive line,
detail present as an escaped quoted value).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5692 July 7, 2026 01:39 Destroyed
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

@IronLoop review --agent reviewer

Addressed the prompt-injection finding in a79fa86 (untrusted detail is now JSON-string-framed before the explainer prompt; regression test added).

@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

Verdict: ✅ Approved
Findings: 0 blocking / 0 notes
Next: No reviewer action needed.
Head: a79fa8663d40fb00165591452e569981e3055e28

Run details

Status: Current
Needs human: no
Needs validation: no

**Inline candidates:** 0

Summary

No concrete blocking issues found in the reviewed diff. The retry/resume, failure projection, WebUI endpoint, provider error mapping, and MCP diagnostic changes appear to be covered by targeted contract and regression tests in the PR.

Findings

None.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloop review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloop review when the fix may affect multiple areas.
  4. Use @ironloop status to check queued/running/completed/stale/stalled state while reviewers run.

@ironloopai
ironloopai Bot dismissed stale reviews from themself July 7, 2026 01:43

Superseded by a later IronLoop approved review for this reviewer.

@serrrfirat
serrrfirat merged commit d9a3b75 into main Jul 7, 2026
61 checks passed
@serrrfirat
serrrfirat deleted the reborn-collapse-integration branch July 7, 2026 07:32
henrypark133 added a commit that referenced this pull request Jul 7, 2026
Post-merge fix: main's #5692 recoverability-stack refactor made the
abort path report the precise ModelErrorClass instead of falling
through to the generic model_error default. Fail-closed behavior
(Failed status, zero network egress) is unchanged — only the category
label narrowed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
henrypark133 added a commit that referenced this pull request Jul 29, 2026
…e narrowed by allow-set (3 leak vectors), with regression + trust-boundary tests (#5659)

* test(reborn): narrowed capability allow-set override for bridged-disclosure groups

Adds an opt-in with_narrowed_capability_allow_set_for_bridged_test()
seam so a test can request a genuinely narrowed CapabilityAllowSet
while in Bridged tool-disclosure mode, instead of into_group's
existing forced CapabilityAllowSet::All workaround (which mirrors
production for every other bridged test and stays the default here).
Fails fast if the override is set without also selecting Bridged
mode, since it would otherwise silently no-op. Threaded through both
RebornIntegrationGroupBuilder and RebornIntegrationHarnessBuilder,
mirroring the existing with_tool_disclosure_bridged/off pattern.

No behavior change for any existing test: default None resolves to
today's forced-All path byte-for-byte.

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

* test(reborn): red-pin #5647 — bridged disclosure strips tool_search under a narrowed allow-set

Adds #[ignore]d bridged_mode_survives_narrowed_capability_allow_set,
using the new harness seam: Bridged mode + a >32-tool GithubIssueTools
catalog + a narrowed allow-set (github.get_repo only, not forced All).
CapabilitySurfaceProfileFilter runs outside (after)
ToolDisclosureCapabilityDecorator, so the synthetic ironclaw.tool_search
bridge id — not a real granted capability — gets stripped by the
narrowed allow-set, leaving the model with zero tools.

Verified RED at authoring time: fails at
assert_model_tools_contains(TOOL_SEARCH_NAME) with "saw []", not at
harness construction or the secondary assertion. Ships #[ignore]d
(delivery option (a)): both fix directions in issue #5647 are real
trust-boundary changes to a security-relevant filter, not one-liners,
and deserve their own review rather than a bundled coverage-lane PR.

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

* docs: trim comments to context-economy rule

Compress #5647 RED-pin doc comments (field docs, misuse-guard note,
test doc comment) to dense 1-3 line notes carrying the crux + issue
ref, per repo comment-economy convention. No test logic or #[ignore]
attribute changed.

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

* fix(reborn): #5647 — bridge meta-tool ids survive narrowed capability allow-sets

PRODUCTION BEHAVIOR CHANGE: a narrowed-allowlist caller (subagent /
capability-surface profile) crossing the bridged tool-disclosure
threshold now keeps the synthetic ironclaw.* bridge ids
(tool_search/tool_describe/tool_call) for both disclosure AND
invocation, instead of shipping the model zero tools.
CapabilitySurfaceProfileFilter gains a constructor-injected
host-exempt id set (empty via new() — every existing call site
unchanged), folded into one private permits() mirroring the
Visible/Deny sibling filters; the sole production construction site
(loop_driver_host.rs) supplies tool_disclosure::bridge_capability_ids().

Trust boundary unchanged and tested: bridged/forgiving dispatch
resolves to the REAL underlying capability id, which the allow-set
still gates. New denial test proves a non-allowlisted underlying tool
never dispatches (zero network egress) for a narrowed profile; a
filter unit test pins bypass-without-widening. The #5647 RED pin is
un-ignored with zero test-body edits. Mutation-verified both ways:
exemption reverted -> regression test red ("saw []"); enforcement
broken -> denial test red ("saw 1" egress).

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

* refactor(test-support): type narrowed bridged allow-set as CapabilityId at the boundary

Harness builder now converts &str -> CapabilityId in its public setter
and carries the domain type through to group_options (which takes
CapabilityId directly); drop a needless clone in into_group where self
is consumed.

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

* fix(reborn): narrow tool_search/tool_describe payloads by the caller's allow-set

PRODUCTION BEHAVIOR CHANGE: under a narrowed CapabilityAllowSet, the
tool-disclosure bridge no longer discloses metadata (names, descriptions,
schemas) for non-allowlisted capabilities. ToolDisclosureCapabilityPort sits
inside CapabilitySurfaceProfileFilter, so its catalog is the full unnarrowed
base surface; tool_search results and tool_describe lookups previously
served that catalog to every caller. The port now holds the SAME
CapabilitySurfaceProfileResolver the composition root uses for the profile
filter and lazily re-resolves the caller's allow-set (before the turn_state
sync mutex) to filter search results and gate describe. A non-allowlisted
tool_describe target reads as "unknown" — identical to a nonexistent name —
so existence itself is not disclosed.

Unchanged: unnarrowed (All) callers see the full catalog; the 32-tool
bridging-threshold computation still runs against the unnarrowed surface;
invocation enforcement (outer profile-filter permits recheck) and the
3-bridge-id host-exempt set (#5647) are untouched. Zero edits to
ironclaw_loop_support.

Also fixes the stale setter index in the group_options.rs module doc
(review nit on #5659).

Closes #5712

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

* fix(reborn): tighten #5712 field-doc to comment-economy convention

Post-implementation review nit — the surface_resolver field doc on
ToolDisclosureCapabilityDecorator ran 4 lines; repo convention is
1-2 dense lines (invariant + issue ref).

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

* fix(reborn): narrow tool_search's own advertised description by allow-set

PRODUCTION CHANGE: closes a third metadata-leak vector flagged on PR #5659
(ironloopai, 2026-07-06T17:34): the tool_search bridge's advertised
`description` doubles as an always-on catalog index of every discoverable
tool name (`catalog_index_tool_search_description`). It was built from the
full, unnarrowed catalog inside the sync `tool_definitions()` path, so a
narrowed capability allow-set (subagent flavor, scheduled-trigger surface,
etc.) still read every tool name straight out of tool_search's own
description — bypassing the #5712/88eb669da narrowing already applied to
tool_search/tool_describe results, since the bridge id is host-exempt from
the outer CapabilitySurfaceProfileFilter (#5647) and nothing else touches
that description text.

Root cause: `tool_definitions()` is a synchronous LoopCapabilityPort method
with no `.await` point, so the async-resolved CapabilityAllowSet could never
reach it under the old lazy-per-call resolve pattern. Fix: make
`LoopCapabilityPortDecorator::decorate` async (3 production + 2 test impls,
already-async caller) so ToolDisclosureCapabilityDecorator can resolve the
allow-set once, eagerly, before any turn/port method runs — fails closed to
an empty allow-set on resolve error. This also deletes the "MUST be awaited
before turn_state() locks its sync mutex" ordering hazard the old lazy
resolve required.

Threaded the allow-set through select_active_set /
advertised_bridge_tool_definitions / catalog_index_tool_search_description /
CapabilityCatalog::discoverable_tool_names so the index only lists
allow-set-permitted tool names. New unit test
(tool_search_description_is_narrowed_by_allow_set) and integration test
(bridged_mode_tool_search_description_is_narrowed_by_allow_set) pin the
narrowing; both were verified red against the pre-fix code before the fix
was restored.

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

* test(reborn): report seen tool names when description-exclusion target is absent

Mirrors assert_model_tools_contains's missing-name diagnostic so a
bridged-surface failure shows which tools were actually sent.

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

* fix(reborn): update tool_disclosure failure category for #5692 rename

Post-merge fix: main's #5692 recoverability-stack refactor made the
abort path report the precise ModelErrorClass instead of falling
through to the generic model_error default. Fail-closed behavior
(Failed status, zero network egress) is unchanged — only the category
label narrowed.

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

* fix(reborn): single-resolve tool-disclosure allow-set + narrow test/doc gaps

Addresses 5 PR review findings on the disclosure-narrowing seam:

- Decorator no longer owns a CapabilitySurfaceProfileResolver: the
  host-build boundary (create_host) resolves the caller's allow-set
  exactly once and primes it into both the tool-disclosure decorator
  and the CapabilitySurfaceProfileFilter, so a transient resolver
  failure can no longer let the two observe different profiles.
- Extend the narrowed tool_search description test with a positive
  assertion (allowlisted tool present), not just the negative one.
- Extend the narrowed tool_describe test to compare the full persisted
  ToolResultReferenceEnvelope for a non-allowlisted vs a nonexistent
  target, modulo the run-scoped result_ref, closing an existence-oracle
  gap a safe_summary substring check alone would miss.
- Cite the production wiring the narrowed-allow-set harness seam mirrors.
- Fix a stale doc comment: the allow-set-filtered tool index is stable
  per (CapabilitySurfaceVersion, allow_set) pair, not per surface
  version alone.

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

* fix(runner): clear primed disclosure state on factory failure (#5659)

* fix(runner): close bridged tool existence oracle (#5659)

* refactor(runner): pass disclosure profile directly (#5659)

* docs(runner): update disclosure decorator reference (#5659)

* fix(runner): reserve disclosure bridge capability ids (#5659)

* fix(runner): budget disclosure from effective surface (#5659)

* test(runner): lock disclosure authorization contracts (#5659)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…osure surface narrowed by allow-set (3 leak vectors), with regression + trust-boundary tests (nearai#5659)

* test(reborn): narrowed capability allow-set override for bridged-disclosure groups

Adds an opt-in with_narrowed_capability_allow_set_for_bridged_test()
seam so a test can request a genuinely narrowed CapabilityAllowSet
while in Bridged tool-disclosure mode, instead of into_group's
existing forced CapabilityAllowSet::All workaround (which mirrors
production for every other bridged test and stays the default here).
Fails fast if the override is set without also selecting Bridged
mode, since it would otherwise silently no-op. Threaded through both
RebornIntegrationGroupBuilder and RebornIntegrationHarnessBuilder,
mirroring the existing with_tool_disclosure_bridged/off pattern.

No behavior change for any existing test: default None resolves to
today's forced-All path byte-for-byte.

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

* test(reborn): red-pin nearai#5647 — bridged disclosure strips tool_search under a narrowed allow-set

Adds #[ignore]d bridged_mode_survives_narrowed_capability_allow_set,
using the new harness seam: Bridged mode + a >32-tool GithubIssueTools
catalog + a narrowed allow-set (github.get_repo only, not forced All).
CapabilitySurfaceProfileFilter runs outside (after)
ToolDisclosureCapabilityDecorator, so the synthetic ironclaw.tool_search
bridge id — not a real granted capability — gets stripped by the
narrowed allow-set, leaving the model with zero tools.

Verified RED at authoring time: fails at
assert_model_tools_contains(TOOL_SEARCH_NAME) with "saw []", not at
harness construction or the secondary assertion. Ships #[ignore]d
(delivery option (a)): both fix directions in issue nearai#5647 are real
trust-boundary changes to a security-relevant filter, not one-liners,
and deserve their own review rather than a bundled coverage-lane PR.

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

* docs: trim comments to context-economy rule

Compress nearai#5647 RED-pin doc comments (field docs, misuse-guard note,
test doc comment) to dense 1-3 line notes carrying the crux + issue
ref, per repo comment-economy convention. No test logic or #[ignore]
attribute changed.

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

* fix(reborn): nearai#5647 — bridge meta-tool ids survive narrowed capability allow-sets

PRODUCTION BEHAVIOR CHANGE: a narrowed-allowlist caller (subagent /
capability-surface profile) crossing the bridged tool-disclosure
threshold now keeps the synthetic ironclaw.* bridge ids
(tool_search/tool_describe/tool_call) for both disclosure AND
invocation, instead of shipping the model zero tools.
CapabilitySurfaceProfileFilter gains a constructor-injected
host-exempt id set (empty via new() — every existing call site
unchanged), folded into one private permits() mirroring the
Visible/Deny sibling filters; the sole production construction site
(loop_driver_host.rs) supplies tool_disclosure::bridge_capability_ids().

Trust boundary unchanged and tested: bridged/forgiving dispatch
resolves to the REAL underlying capability id, which the allow-set
still gates. New denial test proves a non-allowlisted underlying tool
never dispatches (zero network egress) for a narrowed profile; a
filter unit test pins bypass-without-widening. The nearai#5647 RED pin is
un-ignored with zero test-body edits. Mutation-verified both ways:
exemption reverted -> regression test red ("saw []"); enforcement
broken -> denial test red ("saw 1" egress).

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

* refactor(test-support): type narrowed bridged allow-set as CapabilityId at the boundary

Harness builder now converts &str -> CapabilityId in its public setter
and carries the domain type through to group_options (which takes
CapabilityId directly); drop a needless clone in into_group where self
is consumed.

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

* fix(reborn): narrow tool_search/tool_describe payloads by the caller's allow-set

PRODUCTION BEHAVIOR CHANGE: under a narrowed CapabilityAllowSet, the
tool-disclosure bridge no longer discloses metadata (names, descriptions,
schemas) for non-allowlisted capabilities. ToolDisclosureCapabilityPort sits
inside CapabilitySurfaceProfileFilter, so its catalog is the full unnarrowed
base surface; tool_search results and tool_describe lookups previously
served that catalog to every caller. The port now holds the SAME
CapabilitySurfaceProfileResolver the composition root uses for the profile
filter and lazily re-resolves the caller's allow-set (before the turn_state
sync mutex) to filter search results and gate describe. A non-allowlisted
tool_describe target reads as "unknown" — identical to a nonexistent name —
so existence itself is not disclosed.

Unchanged: unnarrowed (All) callers see the full catalog; the 32-tool
bridging-threshold computation still runs against the unnarrowed surface;
invocation enforcement (outer profile-filter permits recheck) and the
3-bridge-id host-exempt set (nearai#5647) are untouched. Zero edits to
ironclaw_loop_support.

Also fixes the stale setter index in the group_options.rs module doc
(review nit on nearai#5659).

Closes nearai#5712

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

* fix(reborn): tighten nearai#5712 field-doc to comment-economy convention

Post-implementation review nit — the surface_resolver field doc on
ToolDisclosureCapabilityDecorator ran 4 lines; repo convention is
1-2 dense lines (invariant + issue ref).

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

* fix(reborn): narrow tool_search's own advertised description by allow-set

PRODUCTION CHANGE: closes a third metadata-leak vector flagged on PR nearai#5659
(ironloopai, 2026-07-06T17:34): the tool_search bridge's advertised
`description` doubles as an always-on catalog index of every discoverable
tool name (`catalog_index_tool_search_description`). It was built from the
full, unnarrowed catalog inside the sync `tool_definitions()` path, so a
narrowed capability allow-set (subagent flavor, scheduled-trigger surface,
etc.) still read every tool name straight out of tool_search's own
description — bypassing the nearai#5712/88eb669da narrowing already applied to
tool_search/tool_describe results, since the bridge id is host-exempt from
the outer CapabilitySurfaceProfileFilter (nearai#5647) and nothing else touches
that description text.

Root cause: `tool_definitions()` is a synchronous LoopCapabilityPort method
with no `.await` point, so the async-resolved CapabilityAllowSet could never
reach it under the old lazy-per-call resolve pattern. Fix: make
`LoopCapabilityPortDecorator::decorate` async (3 production + 2 test impls,
already-async caller) so ToolDisclosureCapabilityDecorator can resolve the
allow-set once, eagerly, before any turn/port method runs — fails closed to
an empty allow-set on resolve error. This also deletes the "MUST be awaited
before turn_state() locks its sync mutex" ordering hazard the old lazy
resolve required.

Threaded the allow-set through select_active_set /
advertised_bridge_tool_definitions / catalog_index_tool_search_description /
CapabilityCatalog::discoverable_tool_names so the index only lists
allow-set-permitted tool names. New unit test
(tool_search_description_is_narrowed_by_allow_set) and integration test
(bridged_mode_tool_search_description_is_narrowed_by_allow_set) pin the
narrowing; both were verified red against the pre-fix code before the fix
was restored.

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

* test(reborn): report seen tool names when description-exclusion target is absent

Mirrors assert_model_tools_contains's missing-name diagnostic so a
bridged-surface failure shows which tools were actually sent.

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

* fix(reborn): update tool_disclosure failure category for nearai#5692 rename

Post-merge fix: main's nearai#5692 recoverability-stack refactor made the
abort path report the precise ModelErrorClass instead of falling
through to the generic model_error default. Fail-closed behavior
(Failed status, zero network egress) is unchanged — only the category
label narrowed.

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

* fix(reborn): single-resolve tool-disclosure allow-set + narrow test/doc gaps

Addresses 5 PR review findings on the disclosure-narrowing seam:

- Decorator no longer owns a CapabilitySurfaceProfileResolver: the
  host-build boundary (create_host) resolves the caller's allow-set
  exactly once and primes it into both the tool-disclosure decorator
  and the CapabilitySurfaceProfileFilter, so a transient resolver
  failure can no longer let the two observe different profiles.
- Extend the narrowed tool_search description test with a positive
  assertion (allowlisted tool present), not just the negative one.
- Extend the narrowed tool_describe test to compare the full persisted
  ToolResultReferenceEnvelope for a non-allowlisted vs a nonexistent
  target, modulo the run-scoped result_ref, closing an existence-oracle
  gap a safe_summary substring check alone would miss.
- Cite the production wiring the narrowed-allow-set harness seam mirrors.
- Fix a stale doc comment: the allow-set-filtered tool index is stable
  per (CapabilitySurfaceVersion, allow_set) pair, not per surface
  version alone.

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

* fix(runner): clear primed disclosure state on factory failure (nearai#5659)

* fix(runner): close bridged tool existence oracle (nearai#5659)

* refactor(runner): pass disclosure profile directly (nearai#5659)

* docs(runner): update disclosure decorator reference (nearai#5659)

* fix(runner): reserve disclosure bridge capability ids (nearai#5659)

* fix(runner): budget disclosure from effective surface (nearai#5659)

* test(runner): lock disclosure authorization contracts (nearai#5659)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

This branch was successfully deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants