Skip to content

Report-sink typed failure, opt-in report descriptor and run profile diagnostics, dependency-scratch storage fault code - #536

Merged
luisleo526 merged 33 commits into
mainfrom
feat/run-profile-diagnostics
Oct 26, 2026
Merged

luisleo526 merged 33 commits into
mainfrom
feat/run-profile-diagnostics

Conversation

@luisleo526

@luisleo526 luisleo526 commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

What

One change set that carries four reviewed pull requests together, so the release gate and the parity sweep run once on the exact tree that ships. Each of the four was reviewed and green on its own; this branch is the first, #536, plus merges of the other three. No line was rewritten.

Dependency scratch fault (from #533)

A read, write or resize fault on the scratch that holds waiting requests now ends the run as scratch_io_failed, a non-retryable resource failure, instead of engine_invariant, as the exit-scratch consumer already did for the same storage status. A failure the storage layer had already recorded keeps its own code. The run-failure catalog is unchanged. The change is in require_dependency_scratch (src/native_execution_consumer.cpp) and adds no net lines, so the file keeps its line count and the documentation line anchors stay valid.

Typed failure for a refused internal report sink (from #534)

On the default route run_json.py installs an internal report sink. When the engine refused it, for example because the scratch arena descriptor, size or profile flag in the environment was unusable, the host reported engine_invariant and dropped the failure the engine had recorded. It now reads that record before the strategy state is freed and reports it (report_sink_failed with reason arena_backing for an unusable arena). If the engine recorded nothing, the previous message and code are kept. No success path changes.

Opt-in report descriptor in the container entrypoint (from #535)

PINEFORGE_REPORT_SINK_FD=<n> makes the entrypoint pass --report-sink-fd <n> to run_json.py, for a caller that opened the descriptor for the report records itself. The value is forwarded unchanged, an empty value as an empty argument, and run_json.py stays the only validator (a number below 3, 5, a phase descriptor and a non-number are refused with its usual text and exit 2, mapped to 4 by the entrypoint). With the variable unset nothing changes.

Run profile in the report diagnostics (from #536)

Every executed success report carries two additive diagnostics keys: run_profile ("jailed" or "unjailed", fixed when the run starts) and scratch_arena_jailed (true only when the jailed profile ran and the native scratch arena admission succeeded). The flag comes from the actual admission result, never from the environment. It records the checked profile and the admission; it is not an independent check of operating-system confinement. Failure, capability and validate-only documents, and every existing key and its order, are unchanged. The replay converter keeps both keys. Python only: no native export, no C++ change.

Tests

Each change keeps the tests it was reviewed with, committed before the fix so each fails on the unchanged tree for the stated reason: the dependency-scratch fault rows (engine_invariant observed), the unjailed sink refusal rows (twelve real-module rows and a free-order double), the entrypoint forwarding rows (the logged argument vector lacks the option), and the profile rows (both keys absent). The faults in the first group are injected through a backing double; no claim is made that a real disk EFBIG reaches that helper.

Compatibility

  • A host that matched engine_invariant for a dependency-scratch storage fault now sees scratch_io_failed (class resource, not retryable).
  • A host that matched engine_invariant for a refused internal report sink on the default route now sees report_sink_failed / arena_backing for the cases above.
  • Two additive keys in the success report diagnostics object; one opt-in environment variable. The 1.8.0 changelog describes all four.

🤖 Generated with Claude Code

luisleo526 and others added 29 commits October 26, 2026 20:12
A bare I/O error from the dependency scratch backing (no carried
exception) reaches require_dependency_scratch as a plain logic error and is
reported as an internal inconsistency, while the exit-scratch consumer
reports the same status as scratch_io_failed. These rows drive a real native
run (a reversal with 64 waiting descendants, so the dependency pages exceed
the two-page cache) over a status-only fault backing:

- unarmed_control: the identical run without a fault completes, with real
  backing reads and writes and an empty store after the drain.
- write_fault, read_fault, growth_fault, byte_cap_refusal: one bare io_error
  after setup must end the run failed with scratch_io_failed and no
  arguments. They fail on the current tree with the observed class, which
  the check prints.
- carried_failure: a carried typed exception keeps its code, arguments and
  errno.
- exit_scratch_sibling: the same status through the exit-scratch consumer
  stays scratch_io_failed.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Preserve carried scratch exceptions and raise scratch_io_failed with no
arguments when a dependency scratch operation returns a bare I/O error, as
the exit-scratch consumer already does for the same status. Successful
operations and contract-error behaviour are unchanged.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ailure

The default route installs an internal report sink. When the engine refuses
it (an unusable scratch arena descriptor, size or profile flag in the
environment, or a descriptor that is not a sealed memfd), the host reports a
generic internal inconsistency instead of the typed failure the engine
recorded. These rows run the real run_json.py against a real strategy module
with the arena environment set up per row, and expect report_sink_failed with
reason arena_backing:

- bad descriptor number, missing / empty / zero / negative / non-decimal /
  overflowing byte count, and an invalid jailed flag;
- descriptor 5 being a pipe, closed, a memfd with no seals, and a memfd with
  only some of the seals;
- a double whose setter fails with a recorded typed failure that is cleared
  when the strategy is freed: the failure must be read before the free.

Controls that must hold before and after: the default route with no arena
environment, a fully sealed memfd, and three doubles where the engine
recorded nothing (the generic internal-inconsistency line is kept).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The entrypoint builds the extra run_json arguments from environment
variables. A caller that opens a descriptor for the report records itself has
no way to pass it through the container entrypoint. These rows drive the real
entrypoint with a private prefix and a logged run_json argv:

- variable unset: the argv is exactly the old one, for Pine and C++ input;
- variable set: the descriptor number is forwarded once as
  --report-sink-fd, an empty value is forwarded as an empty argument, and the
  position among the other forwarded options is fixed;
- invalid values (0, 1, 2, a negative number, 5, a non-number) are forwarded
  unchanged and refused by run_json itself with its own text; the entrypoint
  maps the refusal to its usual exit status 4.

A native row checks that the records of a run reach the caller's descriptor
and that standard output carries only the small document.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…rt sink is refused

When the engine refuses the internal report sink on the default route (for
example because the scratch arena descriptor, size or profile flag in the
environment is unusable), the host reported a generic internal
inconsistency and dropped the typed failure the engine had recorded. It now
reads that record, before the strategy state is freed, and reports it
(report_sink_failed with reason arena_backing for an unusable arena). When
the engine recorded nothing, the previous message is kept. No success path
changes.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…ailed arena was admitted

A success report carries only arena sizes, which show that an arena is
attached but not which profile ran or whether the jailed arena was admitted.
These rows expect two additive keys in the diagnostics object of every
executed success report (ordinary and selected windows, array output, path
and descriptor sinks): run_profile, the string "jailed" or "unjailed" fixed
when the run starts, and scratch_arena_jailed, a boolean that is true only
when the jailed profile ran and the native arena admission succeeded.

- the profile is the one frozen at startup, not a later environment read, and
  PF_SCRATCH_ARENA_JAILED alone never relabels an unjailed run;
- a real jailed run with an admitted sealed arena reads "jailed" / true, the
  same run without the profile reads "unjailed" / false;
- failure documents, capability documents and validate-only documents carry
  neither key;
- the replay converter keeps both keys and rejects wrong types;
- where a pinned test compares a whole document, it now asserts the two new
  keys first and then compares every other key exactly.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
PINEFORGE_REPORT_SINK_FD=<n> makes the container entrypoint pass
--report-sink-fd <n> to run_json.py, for a caller that opened the descriptor
for the report records itself. The value is forwarded unchanged and run_json
remains the only validator, so its refusals (a number below 3, 5, a phase
descriptor, a non-number) apply as they do for the flag. With the variable
unset the entrypoint behaves exactly as before.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…e diagnostics

Every executed success report now carries two additive diagnostics keys:
run_profile ("jailed" or "unjailed", the profile fixed when the run started)
and scratch_arena_jailed (true only when the jailed profile ran and the
native scratch arena admission succeeded). The flag comes from the actual
admission result, not from the environment. The replay converter keeps both
keys. Failure, capability and validate-only documents are unchanged, as is
every existing key and its order. The documentation states what the marker
proves and what it does not (it is not an independent OS jail check).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@luisleo526
luisleo526 marked this pull request as ready for review October 26, 2026 19:09
@luisleo526 luisleo526 changed the title run_json: report the run profile and jailed arena admission in the diagnostics Report-sink typed failure, opt-in report descriptor and run profile diagnostics, dependency-scratch storage fault code Oct 26, 2026
assert actual["diagnostics"]["scratch_arena_bytes"] == size
assert 0 <= actual["diagnostics"]["scratch_arena_high_water_bytes"] <= size
for name in ARENA_DIAGNOSTICS:
assert reference["diagnostics"].pop(name) == 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant