Skip to content

fix(host-runtime): classify HTTP error responses as failures - #7330

Closed
serrrfirat wants to merge 4 commits into
nearai:mainfrom
serrrfirat:codex/fix-http-error-semantics
Closed

serrrfirat wants to merge 4 commits into
nearai:mainfrom
serrrfirat:codex/fix-http-error-semantics

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

Summary

  • classify builtin.http and builtin.http.save HTTP 4xx/5xx responses as recoverable OperationFailed capability outcomes
  • preserve the bounded, sanitized HTTP response as model-visible diagnostic context so the model can inspect status, body, and recovery hints
  • keep informational, successful, and redirect responses as inspectable successful results
  • add caller-level regression coverage and wire it into the documented architecture-runtime gate

Change Type

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

Linked Issue

None. Reproduced from an exported production thread where an HTTP 403 was reported to the model as a successful tool result.

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all --benches --tests --examples --all-features -- -D warnings
  • cargo build
  • Relevant tests pass: all 46 builtin_http_* tests and the complete architecture-runtime gate
  • cargo test -p <owning-crate> --features integration if database-backed or runtime-integration behavior changed (the root integration feature is empty — the flag is per-crate)
  • Manual testing: Not applicable; deterministic host-runtime tests cover the response classification
  • If a coding agent was used and supports it, review-pr or pr-shepherd --fix was run before requesting review

Additional scoped validation:

  • cargo clippy -p ironclaw_host_runtime --all-targets --all-features -- -D warnings
  • git diff --check
  • bash -n scripts/reborn-e2e-rust.sh

Test Strategy

User behavior:

Given an authorized builtin.http call whose server returns HTTP 403, when the response reaches the capability boundary, then the model receives a recoverable failed tool outcome with the sanitized status, body, and authentication hint instead of a successful tool result. HTTP redirects remain inspectable results.

Risk areas:

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

Tests added or updated:

  • Unit or contract: added caller-path host-runtime tests for HTTP 403 failure classification and HTTP 302 compatibility
  • Reborn integration: Not applicable; the behavior is deterministic at the public HostRuntime::invoke_capability seam and does not require model orchestration
  • Recorded fixture: Not applicable; tool selection and argument shape are unchanged
  • Browser E2E: Not applicable; no browser-visible behavior changed
  • Backend or runtime: ran scripts/reborn-e2e-rust.sh architecture-runtime
  • Live canary: Not applicable; no live provider is required to verify status classification

What the tests prove:

  • HTTP 403 produces FailureKind::OperationFailed
  • the sanitized response body, status, and authentication recovery hint remain available as diagnostic context
  • redirects remain successful inspectable results
  • existing HTTP limits, redaction, credential mediation, saved-body behavior, and capability dispatch contracts remain green

Commands run:

  • cargo fmt --all -- --check
  • cargo test -p ironclaw_host_runtime --test first_party_builtin_tools builtin_http_
  • cargo clippy -p ironclaw_host_runtime --all-targets --all-features -- -D warnings
  • scripts/reborn-e2e-rust.sh architecture-runtime
  • git diff --check
  • bash -n scripts/reborn-e2e-rust.sh

Security Impact

Changes model-visible network tool execution semantics but does not change permissions, allowlists, credential injection, redaction, or transport policy. HTTP error bodies continue through the existing bounded, sanitized diagnostic channel and are re-scrubbed and injection-fenced before model observation.

Reborn Trust-Boundary Checklist

  • Public policy/evidence/trust-bearing types: no new public or trust-bearing types; the existing OperationFailed and diagnostic contracts are reused
  • Untrusted content enters prompts only through an envelope/escaping primitive: HTTP output remains sanitized and crosses the existing untrusted diagnostic scrub/fence path
  • Hashes declare purpose; trust/binding/authenticity uses SHA-256/BLAKE3 or separate authenticity check: Not applicable; no hashes changed
  • New/changed status, exit, policy, runtime, or error variants: downstream match sites audited. Command/output: no new variant; scripts/reborn-e2e-rust.sh architecture-runtime passed using the existing OperationFailed mapping
  • Security/durability serde(default) fields fail closed or have migration tests: Not applicable; no serialized fields changed
  • Queues/maps/buffers/counters have bounds and overflow-safe arithmetic: no new collection; diagnostic output retains existing response and model-observation bounds
  • Driver/operator-visible errors have stable class semantics (Transient, Permanent, Misconfigured, PolicyDenied or equivalent): HTTP 4xx/5xx consistently use existing OperationFailed
  • Sandbox/native/host names accurately describe trust boundary: Not applicable; no trust-boundary naming changed

Database Impact

None.

Blast Radius

builtin.http and builtin.http.save response classification only. Clients that intentionally inspect 4xx/5xx responses now receive a recoverable failed capability outcome rather than success, but retain the sanitized response as diagnostic context. 1xx, 2xx, and 3xx behavior is unchanged.

Rollback Plan

Revert this commit to restore the previous behavior where every received HTTP response was returned as a successful capability result. No data migration or persisted schema rollback is required.

Review Follow-Through

Reviewer judgment requested on the deliberate compatibility change for expected 4xx statuses such as 404 or 409. The model retains the full bounded diagnostic and can continue, but the verdict is now correctly non-successful.


Review track: C (runtime error semantics)

@github-actions github-actions Bot added scope: docs Documentation size: S 10-49 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 90568549-5b55-41cd-9c51-65eb21a971be

📥 Commits

Reviewing files that changed from the base of the PR and between 8b32989 and 78b95ad.

📒 Files selected for processing (5)
  • crates/kernel/ironclaw_host_runtime/src/first_party_tools/http.rs
  • crates/kernel/ironclaw_host_runtime/src/first_party_tools/http_output.rs
  • crates/kernel/ironclaw_host_runtime/tests/first_party_builtin_tools.rs
  • docs/reborn/contracts/host-runtime.md
  • scripts/reborn-e2e-rust.sh

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • HTTP 4xx and 5xx responses are now reported as failed operations with bounded, safe diagnostic details.
    • Saved responses retain body metadata even when the request fails.
    • Successful responses, informational statuses, and redirects remain available for inspection; redirects are not followed.
  • Documentation

    • Clarified HTTP response handling, diagnostic limits, authentication guidance, and redirect behavior.
  • Tests

    • Added coverage for HTTP errors, redirects, saved responses, diagnostic truncation, and end-to-end runtime behavior.

Walkthrough

First-party HTTP tools classify HTTP 4xx and 5xx responses as OperationFailed outcomes with bounded, sanitized diagnostics. Save mode preserves saved-body metadata. Other status ranges, including redirects, remain inspectable successful results. Tests, documentation, and E2E coverage define this contract.

Changes

HTTP outcome handling

Layer / File(s) Summary
HTTP response classification
crates/kernel/ironclaw_host_runtime/src/first_party_tools/http.rs, crates/kernel/ironclaw_host_runtime/src/first_party_tools/http_output.rs
The dispatcher shapes each response once. Classification converts statuses 400–599 into OperationFailed errors with serialized diagnostics and network-egress usage. Other statuses remain successful results.
Diagnostic bounding and runtime validation
crates/kernel/ironclaw_host_runtime/src/first_party_tools/http_output.rs, crates/kernel/ironclaw_host_runtime/tests/first_party_builtin_tools.rs
Diagnostics trim bodies and headers to the model budget while preserving valid JSON, status information, truncation metadata, and authentication hints. Save failures retain saved-body metadata without inline content. Tests cover status boundaries, redirects, failures, save mode, and truncation.
Contract and architecture E2E coverage
docs/reborn/contracts/host-runtime.md, scripts/reborn-e2e-rust.sh
The host-runtime contract documents classification, redirects, save behavior, retry behavior, authentication handling, and diagnostic limits. The architecture E2E lane runs the HTTP outcome tests.

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

Suggested reviewers: benkurrek

Sequence Diagram(s)

sequenceDiagram
  participant HTTPServer
  participant HTTPDispatcher
  participant StatusClassifier
  participant ModelVisibleOutcome
  HTTPServer->>HTTPDispatcher: Return status, headers, and body
  HTTPDispatcher->>HTTPDispatcher: Shape response once
  HTTPDispatcher->>StatusClassifier: Classify status and shaped output
  alt Status is 400-599
    StatusClassifier->>ModelVisibleOutcome: Return OperationFailed with bounded JSON diagnostics
  else Other status
    StatusClassifier->>ModelVisibleOutcome: Return successful inspectable response
  end
Loading
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses Conventional Commits format and accurately summarizes HTTP error classification in the host runtime.
Description check ✅ Passed The description completes the required sections and documents scope, validation, tests, security impact, rollback, and review follow-up.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ironloopai

ironloopai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

🧭 IronLoop Run · Review

This comment updates in place as the Run moves through its stages.

🟩 Final result · Completed

🟨 Queued → 🟦 Working → 🟦 Posting results → 🟩 Completed

Automatic trigger · attempt 1 of 3 · completed in 5m 4s

IronLoop completed the review and posted it to GitHub.

🔗 Result

Open submitted review →

Run details

Run: 71a3ce2c-1dbe-4c85-98c4-efe93de2098f
Base: main at 389aa99
Head: codex/fix-http-error-semantics at 6281128
Created: 2026-08-07 10:24 UTC
Updated: 2026-08-07 10:29 UTC

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

🤖 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/kernel/ironclaw_host_runtime/src/first_party_tools/http.rs`:
- Around line 251-255: Update the serde_json serialization error handling in the
diagnostic construction to bind the serialization error and preserve it through
the existing cause-preserving FirstPartyCapabilityError constructor, rather than
discarding it with map_err(|_| ...). Keep the OutputDecode classification and
network egress usage unchanged.

In `@crates/kernel/ironclaw_host_runtime/tests/first_party_builtin_tools.rs`:
- Around line 4436-4470: Extend the caller-level HTTP error coverage around
builtin_http_surfaces_http_error_status_as_failed_outcome to include a 500
response through the builtin.http.save capability. Assert that the result is
FailureKind::OperationFailed and that its diagnostic JSON reports status 500,
while preserving the existing 403 builtin.http assertions.
🪄 Autofix

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: 40787561-5bb0-4622-9502-4c305421c448

📥 Commits

Reviewing files that changed from the base of the PR and between 389aa99 and 6281128.

📒 Files selected for processing (4)
  • crates/kernel/ironclaw_host_runtime/src/first_party_tools/http.rs
  • crates/kernel/ironclaw_host_runtime/tests/first_party_builtin_tools.rs
  • docs/reborn/contracts/host-runtime.md
  • scripts/reborn-e2e-rust.sh

Comment thread crates/kernel/ironclaw_host_runtime/src/first_party_tools/http.rs Outdated

@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

🟢 No actionable findings

Reviewed the complete merge-base-to-head change across HTTP response shaping, failure propagation, diagnostics, tests, documentation, and runtime-gate wiring. No actionable defects found.

Validation

  • ✅ Focused host-runtime HTTP tests — `cargo test -p ironclaw_host_runtime --test first_party_builtin_tools builtin_http_` passed all 46 selected tests.
  • ✅ Diff whitespace check — `git diff --check refs/ironloop/merge-base refs/ironloop/head` completed successfully.
  • ⚪ Full workspace validation — Not run. Not run because the focused owning-crate suite and existing CI evidence provided proportionate coverage for this four-file change.
Review details
  • Run: 71a3ce2c-1dbe-4c85-98c4-efe93de2098f
  • Workflow: Review
  • Attempts: 1

@serrrfirat serrrfirat left a comment

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.

Code Review (multi-agent)

Intent: Classify builtin.http and builtin.http.save HTTP 4xx/5xx responses as recoverable OperationFailed capability outcomes, preserving sanitized response as model-visible diagnostic context while keeping 1xx/2xx/3xx as successful inspectable results.

Shape: normal primary mode + mechanical modifier (auto-computed from preflight rename scan; no renames in this diff — modifier verified clean).

Coverage: complete. Diff source: local-git (exact base...head commits). 4 files: 2 production, 1 tests, 1 docs. No packetization needed (9.4KB diff). Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, approach. Reviewers failed: none. Body-only: 0.

Stats: 12 findings (from 15 raw, 12 after dedup) across 1 file. 6 inline comment groups (same-line merged).

Security

  1. Low HTTP error diagnostic truncated to 4096 bytes at model boundary; large error bodies lost (crates/kernel/ironclaw_host_runtime/src/first_party_tools/http.rs:250-263, confidence 75) — MODEL_DIAGNOSTIC_MAX_BYTES=4096 truncation can cut the serialized diagnostic mid-JSON; status/truncation/redaction markers not guaranteed to survive. Safe direction (truncation caps exposure) but partially contradicts the PR goal. Fix: cap/trim before serialization, keep markers first, add >4KiB regression test.
  2. Low 429/503 responses now model-visible OperationFailed; retry without backoff can amplify load on third-party targets (http.rs:250-263, confidence 55) — model may immediately re-invoke on 429/503; no host auto-retry, per-call bounds mitigate. Fix: backoff guidance in diagnostic for 429/503 (e.g. Retry-After).
  3. Low Failure verdict now contextualizes auth_hint; 401/403 from any host nudges extension install and credentialed retry (http.rs:250-263, confidence 50) — pre-existing nudge strengthened by failure verdict; install is approval-gated, no direct exfiltration. Fix: consider suppressing auth_hint when no manifest-known audience for target host.
  4. Nit builtin.http.save: 4xx/5xx body already persisted before OperationFailed is reported (http.rs:256-263, confidence 60) — save-mode body on disk before failure verdict; blind retry may duplicate writes. Fix: document save-before-failure ordering; note "body was saved despite error status" in diagnostic.

Bugs

  1. Low builtin.http.save 4xx/5xx diagnostic carries no response body (http.rs:250-263, confidence 55) — save-mode diagnostic has status/headers/saved_body but no body_text; doc/claim gap vs PR body. Fix: state in docs that save-mode error body is recovered via read_file, or include bounded preview.

Tests

  1. Medium builtin.http.save 4xx/5xx classification has no test (http.rs:248-264, confidence 85) — every builtin.http.save test uses status 200; save-mode 403 verdict and body-save-on-failure unpinned. Fix: add save-mode 403 test asserting OperationFailed + saved_body metadata.
  2. Low No test for 5xx responses though contract claims 4xx and 5xx (http.rs:250-263, confidence 75) — 403 only pins the branch; 5xx claim in docs untested. Fix: add 500-status test.
  3. Low Status range boundaries 400/599/600 and informational 1xx untested (http.rs:250-265, confidence 60) — off-by-one regression at edges would pass CI. Fix: parametrized boundary test (400, 599 fail; 100, 304, 600 succeed).
  4. Low New serde_json::to_string OutputDecode error path never exercised (http.rs:251-256, confidence 50) — merged with finding 10 (same code path); unreachable branch unpinned.

Conventions

  1. Low map_err(||) drops serde_json cause on HTTP diagnostic serialization (http.rs:251-255, confidence 65) — violates .claude/rules/error-handling.md (map_err(||) clause); also flagged by bugs (dead-branch), maintainability (dead-code), tests (unexercised path). Fix: log bound error before mapping; bind usage once. Also flags duplicated ResourceUsage construction (lines 252-254 vs 261-263).

Local Patterns

  1. Low New 4xx/5xx failure path emits no tracing::debug unlike sibling error mappings (http.rs:256-264, confidence 65) — http_error and sibling sites log kind/status; new path silent. Fix: emit tracing::debug! with dispatch_error_kind + http_status.
  2. Low HTTP error classification block lacks the WHY comment sibling code carries (http.rs:248-265, confidence 55) — 4xx-vs-3xx rationale only in docs; add one-line WHY comment.

Maintainability

  1. Low HTTP status-to-outcome policy inlined in translator dispatch, split from status shaping in http_output (http.rs:248-265, confidence 60) — dispatch() claims "translator only" but now holds status policy; duplicated usage construction + re-serialization in error path. Fix: extract classify_status into http_output.rs.
  2. Nit Unreachable OutputDecode branch: to_string of shaped output cannot fail (http.rs:251-256, confidence 55) — merged with finding 10; dead arm mislabels error kind.

Approach

No findings.

Notes

  • Deliberate compatibility change (4xx/5xx no longer successful results) is correctly scoped: 1xx/2xx/3xx unchanged, OperationFailed reuse, no new variants, redaction/credential/allowlist untouched.
  • docs/reborn/contracts/host-runtime.md updated and e2e gate wired via run_test_exact in scripts/reborn-e2e-rust.sh; gate currently pins only 403.
  • Review packet (stack/shape context) not required for this single-layer PR; body already documents scope, blast radius, rollback.

.await?
.map_err(|error| http_error(error, save_mode))?;
Ok(shape_response(response, response_body_limit))
let status = response.status;

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.

Medium — builtin.http.save 4xx/5xx classification has no test.

The new error classification branch runs for both builtin.http and builtin.http.save (dispatch is shared). PR title/body explicitly claim both capabilities classify 4xx/5xx as OperationFailed, but every builtin.http.save test uses status 200. Save-mode diagnostic shape differs materially from inline mode: shape_response emits compact saved_body metadata with no body_text, so the model-facing diagnostic and failure verdict for a save-mode 403 are unpinned. Also untested: whether egress saves the error body to disk while the outcome is OperationFailed.

Fix: Add builtin_http_save_surfaces_http_error_status_as_failed_outcome covering builtin.http.save with 403 response and save_to: assert FailureKind::OperationFailed, Diagnostic contains status 403 and saved_body metadata, and strict host egress used.


Low — Status range boundaries 400/599/600 and informational 1xx untested.

The classification is a numeric range (400..=599). Tests cover 200, 302, and 403 only. Lower bound 400, upper bound 599, just-outside 600, and informational 1xx (100/101) are untested, so a future off-by-one or inverted-range regression at the edges would pass CI.

Fix: Add builtin_http_classifies_status_range_boundaries covering statuses 400, 599 (OperationFailed) and 100, 600, 304 (inspectable success) in one parametrized test.

Also flagged by: tests/Low

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.

Addressed in b78ed32: added builtin_http_save_surfaces_http_error_status_as_failed_outcome asserting OperationFailed + saved_body metadata in the diagnostic + strict host egress usage.

.map_err(|error| http_error(error, save_mode))?;
Ok(shape_response(response, response_body_limit))
let status = response.status;
let shaped = shape_response(response, response_body_limit);

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.

Nit — builtin.http.save: 4xx/5xx body already persisted before OperationFailed is reported; model blind-retry risks duplicate writes.

In save mode the egress applies body disposition (writes the sanitized body to the scoped mount) before this status check runs, so a 4xx/5xx response is already on disk when OperationFailed is emitted. The diagnostic does carry saved_body metadata (path, bytes_written), so a careful model can see the side effect completed, but a model treating the verdict as 'nothing happened' may re-invoke and duplicate the write.

Fix: Document the save-before-failure ordering in the contract and/or include an explicit 'body was saved despite the error status' note in the diagnostic.

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.

Addressed in b78ed32: documented save-before-failure ordering in docs/reborn/contracts/host-runtime.md (retry must inspect saved_body; also corrected in e055cac).

Ok(shape_response(response, response_body_limit))
let status = response.status;
let shaped = shape_response(response, response_body_limit);
if (400..=599).contains(&status) {

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.

Low — 429/503 responses now model-visible OperationFailed; retry without backoff can amplify load on third-party targets.

Before this diff, an HTTP 429/503 response returned as a successful tool result. Now every 4xx/5xx (including rate-limit 429 and overload 503) maps to OperationFailed whose fate is ModelVisible. The model may re-invoke immediately with no backoff guidance, amplifying request pressure against the target. Prompt-injected agent directed at a victim URL receives 4xx/5xx and retries in a tight loop. Mitigations: no host auto-retry (RetrySameCall not used for OperationFailed), per-call bounds, network_egress accounting on the error, model decides. Real but low-impact behavior change versus base.

Fix: Keep the verdict but include explicit backoff guidance in the diagnostic for 429/503 (e.g., surface Retry-After), or document that the model must not blindly retry rate-limit/overload responses.


Low — Failure verdict now contextualizes auth_hint; 401/403 from any host nudges extension install and credentialed retry.

shape_response already embeds auth_hint (extension_search/extension_install + 'extension injects the required credentials') for 401/403/407. Pre-diff that hint rode a successful result; post-diff it rides a failure diagnostic that also tells the model to retry through the extension's tools. A 401/403 from an attacker-controlled endpoint now produces an OperationFailed verdict plus an install-and-retry nudge the model is more likely to act on. Bounded: extension install is approval-gated and credential mediation is audience-scoped, so no direct secret exfiltration. Diff enables a stronger version of a pre-existing nudge; worth an explicit risk note, not a blocker.

Fix: Consider suppressing auth_hint (or downgrading the nudge) when the request carried no manifest-known audience for the target host.

Also flagged by: security/Low

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.

Addressed in b78ed32: documented in host-runtime.md that the host never auto-retries and the model should apply backoff for 429/503.

let status = response.status;
let shaped = shape_response(response, response_body_limit);
if (400..=599).contains(&status) {
let diagnostic = serde_json::to_string(&shaped.output).map_err(|_| {

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.

Low — map_err(|_|) drops serde_json cause on HTTP diagnostic serialization.

New line 251 serializes shaped.output and maps failure with map_err(|| ...), discarding the serde_json::Error binding. .claude/rules/error-handling.md: 'A map_err(|| ...) (a closure discarding the error binding) is not silent-ok-exemptible — a comment does not make the dropped cause reappear. Fix it by carrying the cause or by logging the bound error before mapping. Reject the line otherwise.' Sibling instances pre-exist in this file (http.rs:353, 357, 417), but this diff adds a new instance. Practical impact low: shaped.output is built by shape_response from bounded strings/numbers/bools, so to_string cannot realistically fail — however the added line violates the explicit rule, drops the only cause that would explain an OutputDecode outcome, and duplicates the ResourceUsage construction (lines 252-254 and 261-263).

Fix: Log the bound error before mapping and bind the usage value once instead of constructing it twice.


Low — builtin.http.save 4xx/5xx diagnostic carries no response body.

For HttpSaveMode::Required, shape_response inserts only saved_body metadata when response.saved_body is present and never emits body_text/body_base64. The new error path serializes shaped.output as the diagnostic, so a save-mode 4xx/5xx failure diagnostic contains status/headers/saved_body path but no error body. PR body and docs claim the sanitized response stays model-visible; that holds for inline builtin.http but not builtin.http.save, where the model must issue an extra read_file. Behavior is consistent with the existing save-mode compact-metadata design, so this is a doc/claim gap more than a code fault.

Fix: In docs/reborn/contracts/host-runtime.md and the PR body, state that save-mode error diagnostics carry only saved_body path metadata and the error body is recovered via read_file, or include a bounded body preview in the save-mode diagnostic.

Also flagged by: bugs/Low

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.

Addressed in b78ed32: map_err(|_|) removed; cause logged, fixed fallback diagnostic payload.

let shaped = shape_response(response, response_body_limit);
if (400..=599).contains(&status) {
let diagnostic = serde_json::to_string(&shaped.output).map_err(|_| {
FirstPartyCapabilityError::new(RuntimeDispatchErrorKind::OutputDecode).with_usage(

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.

Low — New 4xx/5xx failure path emits no tracing::debug unlike sibling error mappings.

Every sibling first-party error-mapping site logs at debug with the dispatch kind before returning FirstPartyCapabilityError: http_error logs 'first-party HTTP egress failed' with runtime_http_reason + dispatch_error_kind (http.rs:509-512), skill_management_error and invalid_trigger_input log it too. The new HTTP 4xx/5xx classification returns OperationFailed with a status-specific summary but no host-side debug log.

Fix: Emit a tracing::debug! with dispatch_error_kind and http_status before returning the classified error, matching the sibling idiom in http_error.

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.

Addressed in b78ed32: classify_status now emits tracing::debug! with http_status + dispatch_error_kind, matching sibling error mappings.

})?;
return Err(FirstPartyCapabilityError::dispatch_with_diagnostic(
RuntimeDispatchErrorKind::OperationFailed,
Some(format!("HTTP request returned status {status}")),

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.

Low — HTTP error diagnostic truncated to 4096 bytes at model boundary; large error bodies lost.

The new failure path serializes the full shaped output (status, auth_hint, headers, body_text) into the Diagnostic detail, but the model boundary truncates diagnostics to MODEL_DIAGNOSTIC_MAX_BYTES=4096 (result_meta.rs:924) via ModelDiagnostic::truncating (resolution.rs:387). Default inline response_body_limit is 48KiB, max 256KiB, so for any typical 4xx/5xx error body the model receives a char-boundary-cut JSON fragment: body largely dropped, and because serde_json::Map sorts keys, the tail keys (status, truncation, redaction_applied markers) can be cut off entirely. Partially contradicts the PR goal 'preserve the bounded, sanitized response as diagnostic context'. Security direction is safe (truncation caps exposure), but the model can misread a partial JSON fragment and the truncation/redaction markers are not guaranteed to survive. The new test uses a tiny body and stops at the invoke_capability seam, so it cannot pin this.

Fix: Cap the inline error diagnostic to MODEL_DIAGNOSTIC_MAX_BYTES before serialization (or trim the shaped body to fit while keeping status/auth_hint/truncation markers first) and add a regression test with a >4KiB error body asserting status and markers survive.


Low — No test for 5xx responses though contract claims 4xx and 5xx.

Branch covers (400..=599), and docs/reborn/contracts/host-runtime.md states 'HTTP 4xx and 5xx responses' are classified as OperationFailed, pinned by builtin_http_surfaces_http_error_status_as_failed_outcome. That test and the scripts/reborn-e2e-rust.sh gate run only a 403 response. A 5xx exercises the same branch but nothing proves the claimed 5xx behavior or that the e2e pin covers the documented claim.

Fix: Add builtin_http_surfaces_server_error_status_as_failed_outcome covering status 500 with body and asserting FailureKind::OperationFailed plus diagnostic carrying body_text.


Low — HTTP status-to-outcome policy inlined in translator dispatch, split from status shaping in http_output.

dispatch() self-describes as 'a translator only', but the new 4xx/5xx -> OperationFailed policy is inlined there while status-dependent shaping already lives in http_output.rs::shape_response (unauthorized_extension_hint for 401/403/407 plus status tests). Status policy for one capability now spans two modules. The inlined branch also constructs ResourceUsage::default().set_network_egress_bytes(...) twice and re-serializes the just-shaped output, duplicating usage bookkeeping inside the error path.

Fix: Extract a small outcome classifier into http_output.rs next to shape_response/unauthorized_extension_hint, e.g. classify_status(shaped, status) returning Ok for 1xx-3xx and building the diagnostic error once for 4xx/5xx; dispatch then ends with classify_status(shape_response(response, response_body_limit), response.status), deleting the duplicated usage construction, the dead map_err branch, and keeping status policy in one module.


Low — HTTP error classification block lacks the WHY comment sibling code carries.

The 4xx/5xx-vs-3xx split is the crux of this change (transport completion is not capability success; redirects stay inspectable and never followed), yet the block carries no comment. This file's idiom is dense WHY comments. The rationale lives only in docs/reborn/contracts/host-runtime.md; a future reader of dispatch() gets no in-code signal that drawing the failure line at 400 is deliberate and that 3xx must stay successful.

Fix: Add a one-line WHY comment above the status check, matching the file's comment idiom.

Also flagged by: tests/Low

Also flagged by: maintainability/Low

Also flagged by: local-patterns/Low

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.

Addressed in b78ed32: bounded_failure_diagnostic trims to MODEL_DIAGNOSTIC_MAX_BYTES before serialization; regression test builtin_http_error_diagnostic_respects_model_diagnostic_budget added.

@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Addressed multi-agent review feedback

Round 1 review (6 inline threads + body summary) addressed in commit b78ed324e.

Code — http.rs / http_output.rs:

  • Extracted status classification into http_output.rs::classify_status (status policy now lives with shape_response in one module; dispatch() stays a translator only)
  • ResourceUsage now bound once; duplicated construction removed
  • map_err(|_|) removed: serialization failures log the bound serde_json::Error cause and fall back to a fixed verdict payload ({"status":N,"error":"diagnostic unavailable"}) instead of fabricating OutputDecode
  • Added tracing::debug! with http_status + dispatch_error_kind on the failure path, matching sibling error mappings
  • Added WHY comment documenting the deliberate 4xx/5xx-vs-3xx failure line (429/503 never success, redirects never followed)
  • Diagnostic budget fix: failure diagnostics are now trimmed to MODEL_DIAGNOSTIC_MAX_BYTES before serialization (body_text → body_base64 → headers, re-measured including truncation markers/envelope). Previously the resolution boundary truncated the diagnostic at 4096 bytes mid-JSON, and sorted serde_json::Map keys cut status/auth_hint/truncation envelope entirely. Now the verdict fields survive as valid JSON; full sanitized body stays reachable via builtin.http.save + read_file

Tests (4 new integration + 3 new unit):

  • builtin_http_save_surfaces_http_error_status_as_failed_outcome — save-mode 403 (was untested; all save tests used 200)
  • builtin_http_surfaces_server_error_status_as_failed_outcome — 500 (contract claims 5xx but only 403 was pinned)
  • builtin_http_classifies_status_range_boundaries — 400/599 fail; 100/304/600 inspectable (off-by-one protection)
  • builtin_http_error_diagnostic_respects_model_diagnostic_budget — 16KiB error body → diagnostic ≤ 4096 bytes, valid JSON, status + truncation markers survive
  • Unit: large-body trim, save-mode metadata preservation, untrimmed small diagnostic

Gate wiring: scripts/reborn-e2e-rust.sh architecture-runtime now pins all five status-classification tests; full architecture-runtime group green locally.

Docs — docs/reborn/contracts/host-runtime.md: documents diagnostic budget trimming, save-mode diagnostic shape (saved_body only; body via read_file), save-before-failure ordering (retry must inspect saved_body to avoid duplicate writes), no host auto-retry + backoff guidance for 429/503, and the approval-gated auth_hint nudge on failure verdicts.

Validation: cargo fmt --check, cargo clippy -p ironclaw_host_runtime --all-targets --all-features -- -D warnings, all 221 first_party_builtin_tools tests, scripts/reborn-e2e-rust.sh architecture-runtime, bash scripts/pre-commit-safety.sh — all green. (2 pre-existing trace_commons test failures on the PR commit, unrelated to this change.)

Deferred: suppressing auth_hint for hosts without a manifest-known audience — kept as documented risk note; changing nudge behavior needs product decision.

@github-actions github-actions Bot added size: L 200-499 changed lines and removed size: S 10-49 changed lines labels Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@docs/reborn/contracts/host-runtime.md`:
- Around line 108-116: The host-runtime documentation incorrectly implies that a
later builtin.http.save call can retrieve a body from a prior failed request.
Update the saved-body sequence to state that callers must invoke
builtin.http.save on the original request; only that call writes saved_body
metadata, which builtin.read_file can subsequently read.
🪄 Autofix

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: 1ddb9afd-4817-4ca2-b25c-5e103042b550

📥 Commits

Reviewing files that changed from the base of the PR and between 6281128 and b78ed32.

📒 Files selected for processing (5)
  • crates/kernel/ironclaw_host_runtime/src/first_party_tools/http.rs
  • crates/kernel/ironclaw_host_runtime/src/first_party_tools/http_output.rs
  • crates/kernel/ironclaw_host_runtime/tests/first_party_builtin_tools.rs
  • docs/reborn/contracts/host-runtime.md
  • scripts/reborn-e2e-rust.sh

Comment thread docs/reborn/contracts/host-runtime.md
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@serrrfirat
serrrfirat enabled auto-merge August 7, 2026 13:02
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Railway preview QA — BLOCKED

Tested head SHA: 78b95addf44a22319bf03d370a2fa9959359f3ca
Railway state: missing — no Railway status or check-run bound to this head; no ironclaw-ci-preview deployment exists for this PR
Preview URL: https://preview-river-lantern-copper-orbit-matrix-velvet-signal-2026.up.railway.app (supplied) — returns Railway fallback 404 (x-railway-fallback: true); no service attached
Relevant route: none — no browser-visible behavior changed (backend host-runtime classification change only)

Given/When/Then matrix

# Classification Intended contract Actual contract exercised Evidence
1 Required builtin.http response with HTTP 4xx (e.g. 403) surfaces to the model as recoverable OperationFailed tool outcome with sanitized status/body/auth hint Not executed — no live preview build No deployment for PR head; supplied preview URL fallback-404s
2 Required HTTP 3xx redirects remain successful inspectable results Not executed — no live preview build same
3 Supplemental Preview deployment health Failed preview-river-lantern-...-2026.up.railway.app → HTTP 404 with x-railway-fallback: true on repeated probes (16:02–16:03 UTC); ironclaw-ironclaw-pr-7330.up.railway.app and ironclaw-pr-7330.up.railway.app also 404

Status derivation

  • Required cases passed: 0
  • Required cases failed: 0
  • Required cases blocked / not executed: 2 (no deployed preview build exists)
  • Overall: BLOCKED — the intended build is not live. No browser evidence was gathered, and none of the required cases could be executed against the intended contract.

Diagnostics

  • preview_state.sh 7330 nearai/ironclaw <preview-url> → railway_state=missing, asset=unavailable across repeated polls (3×, ~4 min).
  • GitHub commit status + check-runs for head 78b95add contain no Railway context; CI checks (Tests, E2E, Clippy, Reborn E2E, etc.) are green but no preview deployment is bound.
  • Repo deployments API shows the ironclaw-ci-preview environment actively deploying sibling PRs (e.g. ironclaw-pr-7277, ironclaw-pr-7341, ironclaw-pr-7338 at 12:29–12:59 UTC on 2026-08-07) — PR 7330 has no deployment record.
  • DNS for the supplied hostname resolves to the Railway edge, but the service returns the platform's fallback 404, i.e. no deployment is attached to that domain.

Remaining risks / blockers

  • Primary blocker: PR 7330 has no Railway preview deployment. Likely the CI preview job did not run or was skipped for this PR; the preview system names deployments ironclaw-pr-<PR>, and none exists for 7330.
  • If a deployment is created, the browser journey would require a preview configured with a model that can call builtin.http against a controlled 4xx endpoint; the PR itself declares browser E2E "Not applicable" (deterministic host-runtime seam covered by cargo test -p ironclaw_host_runtime --test first_party_builtin_tools builtin_http_ and the architecture-runtime gate, both green on CI).

Skipped cases

  • All browser recipes skipped: no live build exists; per the status gate, no proxy journey was invented to produce a browser PASS.

Cleanup status

  • No test data created; nothing to clean up. Browser sessions: none opened.

@serrrfirat

serrrfirat commented Aug 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Reopened from the main repository as #7342 (same head commit 78b95add) so the Railway CI preview can deploy — Railway previews are only created for same-repo branch PRs, and this PR's head lived on the serrrfirat fork, which never deploys.

@serrrfirat serrrfirat closed this Aug 7, 2026
auto-merge was automatically disabled August 7, 2026 13:11

Pull request was closed

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7342 August 7, 2026 13:11 Destroyed
github-merge-queue Bot pushed a commit that referenced this pull request Aug 10, 2026
* fix(host-runtime): classify HTTP error responses as failures

* fix(host-runtime): address review — bound HTTP error diagnostics and add status regression tests (#7330)

* docs(host-runtime): correct saved-body retrieval sequence for failed HTTP calls (#7330)

* fix(host-runtime): converge failure-diagnostic trim, scrub controls, cover header/base64 branches

- share one budget-trim engine (fit_output_to_budget) between the success
  path and the failure diagnostic; the diagnostic trim now converges in a
  strict-progress loop instead of an 8-iteration cap, and the headers
  branch stays reachable when the inline body is empty or absent, so
  header-heavy 4xx/5xx diagnostics keep status/auth_hint/truncation
  instead of collapsing to the fallback verdict
- scrub DEL/C1 control bytes (U+007F..U+009F) from the serialized
  diagnostic: serde_json does not escape them, and ModelDiagnostic
  validation would otherwise replace the whole diagnostic with the fixed
  fallback sentence at the resolution boundary
- move the shaped output by value instead of cloning it per error
  response
- add unit coverage for the empty-body/header trim, base64 alignment on
  the failure path, and control-char scrubbing; wire the redirect
  regression test into the architecture-runtime gate

* fix(host-runtime): shape errors at diagnostic budget, attach wall clock, pin envelope size

- shape 4xx/5xx responses at the 4 KiB diagnostic budget instead of the
  success inline limit so the discarded success-budget trim pass (and its
  serializations) no longer runs on the failure path
- attach dispatch wall_clock_ms to the OperationFailed usage, matching
  the sibling first-party dispatches' failure-path accounting
- pin the truncation envelope below its reserved budget with a unit test
- correct the fit_output_to_budget doc to describe the incremental
  re-measure loop rather than a fixed three-serialization bound

* fix(host-runtime): pin error-status predicate, failure usage, and fallback shape

- single is_error_status predicate shared by the dispatch shape-limit
  selection and classify_status, so the 400..=599 boundary cannot drift
- pin failure usage accounting: classify_status unit test asserts egress
  bytes + wall_clock_ms on the OperationFailed outcome; the 403
  integration test asserts egress bytes reach the governor for failed
  calls
- pin the fallback diagnostic payload shape for non-object output and
  note why the serde-failure branch is unreachable by construction

* fix(host-runtime): migrate stale 5xx-success integration test to failed-outcome contract

- reborn_integration_http_matcher asserted the pre-change contract that a
  scripted HTTP 500 surfaces as a successful tool result; the new
  classification makes it a recoverable OperationFailed outcome, so the
  test now asserts ToolErrorClass::Failed with the operation_failed kind
  (run still completes; docstring updated to the contract doc)
- pin the post-fit fallback safety valve with an oversized untrimmable
  key test; correct the governor-accounting comment; dedupe the 400
  boundary rationale onto is_error_status

* docs(host-runtime): sync matcher guide to failed-outcome contract, document fence interaction

- tests/integration/CLAUDE.md .with_status entry now states 4xx/5xx
  classify as a Failed tool outcome (operation_failed) with sanitized
  diagnostic context; other statuses remain Completed results
- host-runtime contract doc records the loop-host injection-fence
  interaction: verdict semantics never depend on the fenced diagnostic
  surviving the observation bound (OperationFailed + safe summary always
  reach the model)
- document the fit_output_to_budget convergence bound (<= 3 passes)

* docs(host-runtime): correct serialize_diagnostic guard rationale

serde_json serializes every Value string without revalidating UTF-8
(probed: even an unsafe lone-surrogate string serializes Ok), so the
serde-failure arm is a pure defensive guard for future Value shapes, not
a reachable lone-surrogate path. Correct the doc comment and the
fallback test note to state the empirical fact.

* fix(host-runtime): keep shape-stage truncation flags in failure-diagnostic envelope

The re-inserted truncation envelope carried only the diagnostic-budget
trim state, so a 4xx/5xx response whose shape stage had already marked
headers or body as truncated (e.g. more than 32 headers) could end up
with headers_truncated:true beside an envelope claiming headers:false.
OR the surviving keys into the envelope and pin with a regression test;
boundary doc now states the full complement (outside 400..=599 stays
inspectable, including out-of-spec 600+).

* docs(host-runtime): sync support-module doc to failed-outcome contract, fix trim rationale

- tests/integration/support/http_matcher.rs module doc still claimed
  .with_status non-2xx stays Completed; now documents 4xx/5xx as a
  model-visible Failed operation_failed outcome
- bounded_failure_diagnostic doc: head-keeping truncation cuts only the
  last-sorted keys (status, truncation envelope); auth_hint sorts first
  and survives

* fix(host-runtime): address coderabbitai/ironloopai review — egress cap, fence headroom, saved-body fallback (#7342)

- shape failed responses at the caller's response_body_limit again so
  egress-truncation accounting (body_was_truncated_by_egress) stays
  correct; the diagnostic display budget is applied separately. Pinned by
  builtin_http_error_diagnostic_preserves_egress_truncation_flag.
- reserve MODEL_DIAGNOSTIC_FENCE_HEADROOM_BYTES so a diagnostic wrapped
  in the loop-host external-content fence still fits the observation
  budget; pinned by failure_diagnostic_stays_within_budget_when_fenced.
- retain compact saved_body evidence (bounded path prefix +
  bytes_written) in the fallback verdict instead of dropping the save
  destination; pinned by failure_diagnostic_fallback_retains_saved_body_evidence.
- rename failure_diagnostic_falls_back_on_unserializable_output to the
  non-object contract it actually exercises (Value::Null serializes).
- host-runtime contract: correct the save-mode body-retention wording and
  document retry_after_ms semantics (None does not permit immediate retry).
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
…7342)

* fix(host-runtime): classify HTTP error responses as failures

* fix(host-runtime): address review — bound HTTP error diagnostics and add status regression tests (nearai#7330)

* docs(host-runtime): correct saved-body retrieval sequence for failed HTTP calls (nearai#7330)

* fix(host-runtime): converge failure-diagnostic trim, scrub controls, cover header/base64 branches

- share one budget-trim engine (fit_output_to_budget) between the success
  path and the failure diagnostic; the diagnostic trim now converges in a
  strict-progress loop instead of an 8-iteration cap, and the headers
  branch stays reachable when the inline body is empty or absent, so
  header-heavy 4xx/5xx diagnostics keep status/auth_hint/truncation
  instead of collapsing to the fallback verdict
- scrub DEL/C1 control bytes (U+007F..U+009F) from the serialized
  diagnostic: serde_json does not escape them, and ModelDiagnostic
  validation would otherwise replace the whole diagnostic with the fixed
  fallback sentence at the resolution boundary
- move the shaped output by value instead of cloning it per error
  response
- add unit coverage for the empty-body/header trim, base64 alignment on
  the failure path, and control-char scrubbing; wire the redirect
  regression test into the architecture-runtime gate

* fix(host-runtime): shape errors at diagnostic budget, attach wall clock, pin envelope size

- shape 4xx/5xx responses at the 4 KiB diagnostic budget instead of the
  success inline limit so the discarded success-budget trim pass (and its
  serializations) no longer runs on the failure path
- attach dispatch wall_clock_ms to the OperationFailed usage, matching
  the sibling first-party dispatches' failure-path accounting
- pin the truncation envelope below its reserved budget with a unit test
- correct the fit_output_to_budget doc to describe the incremental
  re-measure loop rather than a fixed three-serialization bound

* fix(host-runtime): pin error-status predicate, failure usage, and fallback shape

- single is_error_status predicate shared by the dispatch shape-limit
  selection and classify_status, so the 400..=599 boundary cannot drift
- pin failure usage accounting: classify_status unit test asserts egress
  bytes + wall_clock_ms on the OperationFailed outcome; the 403
  integration test asserts egress bytes reach the governor for failed
  calls
- pin the fallback diagnostic payload shape for non-object output and
  note why the serde-failure branch is unreachable by construction

* fix(host-runtime): migrate stale 5xx-success integration test to failed-outcome contract

- reborn_integration_http_matcher asserted the pre-change contract that a
  scripted HTTP 500 surfaces as a successful tool result; the new
  classification makes it a recoverable OperationFailed outcome, so the
  test now asserts ToolErrorClass::Failed with the operation_failed kind
  (run still completes; docstring updated to the contract doc)
- pin the post-fit fallback safety valve with an oversized untrimmable
  key test; correct the governor-accounting comment; dedupe the 400
  boundary rationale onto is_error_status

* docs(host-runtime): sync matcher guide to failed-outcome contract, document fence interaction

- tests/integration/CLAUDE.md .with_status entry now states 4xx/5xx
  classify as a Failed tool outcome (operation_failed) with sanitized
  diagnostic context; other statuses remain Completed results
- host-runtime contract doc records the loop-host injection-fence
  interaction: verdict semantics never depend on the fenced diagnostic
  surviving the observation bound (OperationFailed + safe summary always
  reach the model)
- document the fit_output_to_budget convergence bound (<= 3 passes)

* docs(host-runtime): correct serialize_diagnostic guard rationale

serde_json serializes every Value string without revalidating UTF-8
(probed: even an unsafe lone-surrogate string serializes Ok), so the
serde-failure arm is a pure defensive guard for future Value shapes, not
a reachable lone-surrogate path. Correct the doc comment and the
fallback test note to state the empirical fact.

* fix(host-runtime): keep shape-stage truncation flags in failure-diagnostic envelope

The re-inserted truncation envelope carried only the diagnostic-budget
trim state, so a 4xx/5xx response whose shape stage had already marked
headers or body as truncated (e.g. more than 32 headers) could end up
with headers_truncated:true beside an envelope claiming headers:false.
OR the surviving keys into the envelope and pin with a regression test;
boundary doc now states the full complement (outside 400..=599 stays
inspectable, including out-of-spec 600+).

* docs(host-runtime): sync support-module doc to failed-outcome contract, fix trim rationale

- tests/integration/support/http_matcher.rs module doc still claimed
  .with_status non-2xx stays Completed; now documents 4xx/5xx as a
  model-visible Failed operation_failed outcome
- bounded_failure_diagnostic doc: head-keeping truncation cuts only the
  last-sorted keys (status, truncation envelope); auth_hint sorts first
  and survives

* fix(host-runtime): address coderabbitai/ironloopai review — egress cap, fence headroom, saved-body fallback (nearai#7342)

- shape failed responses at the caller's response_body_limit again so
  egress-truncation accounting (body_was_truncated_by_egress) stays
  correct; the diagnostic display budget is applied separately. Pinned by
  builtin_http_error_diagnostic_preserves_egress_truncation_flag.
- reserve MODEL_DIAGNOSTIC_FENCE_HEADROOM_BYTES so a diagnostic wrapped
  in the loop-host external-content fence still fits the observation
  budget; pinned by failure_diagnostic_stays_within_budget_when_fenced.
- retain compact saved_body evidence (bounded path prefix +
  bytes_written) in the fallback verdict instead of dropping the save
  destination; pinned by failure_diagnostic_fallback_retains_saved_body_evidence.
- rename failure_diagnostic_falls_back_on_unserializable_output to the
  non-object contract it actually exercises (Value::Null serializes).
- host-runtime contract: correct the save-mode body-retention wording and
  document retry_after_ms semantics (None does not permit immediate retry).

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-7342 — 78b95add Deployed Aug 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: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant