Skip to content

docker: queue encoded column rows and cut the per-batch work of the equity proof - #652

Merged
luisleo526 merged 3 commits into
mainfrom
perf/report-wrapper-c2
Oct 30, 2026
Merged

luisleo526 merged 3 commits into
mainfrom
perf/report-wrapper-c2

Conversation

@luisleo526

@luisleo526 luisleo526 commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Report wrapper: queue the encoded trade rows and cut the per-batch work of the equity proof

No report byte changes. Two pieces of per-row Python work in the report wrapper (docker/run_json.py) are removed; every output (stdout, sink bytes, receipt, error text and order) is identical to the previous commit.

What changes

  • Trade spool. Every ordinary trade row cost two logical writes to its column (the separator and the row) and one dispatch through the generic JSON streamer. The row is now encoded once with the same json.dumps configuration the scalar fast path used and queued, with its separator, in the artifact file. The queue is joined inside the existing bounds (512 records, 256 KiB; no new bound, no new constant) and drained before any write, flush, read, rewind or close of that file, and after the checked reader close of a fused verification. Logical writes per trade column fall from 2T-1 to ceil(T/512); generic streamer dispatches per trade fall to zero. The physical byte stream, the aligned windows and the sync count are unchanged.
  • Equity proof. The permitted repeat of the original run pattern is derived once with the pattern instead of by parsing the pattern text for every batch, and the first and last endpoints of the curve are decoded on demand (two for the whole curve) instead of building two dictionaries for every batch. Equity rows are interleaved with trades, so the old cost was paid about once per twelve rows. Every per-row numeric proof stays: the timestamp range, the finite check, and the canonical repr comparison of every number. The repeat bound still belongs to the run pattern alone.
  • Not changed: window sizes, sync policy, the final-artifact copy, pf_sink.py, the native engine.

Failure behaviour

A write can now be attempted later than the row that queued it. The published refusal is the same: a failed physical write clears the queue first, so the same text is never retried; a failure while draining the tail becomes the same typed report_sink_failed / scratch_io refusal with the OSError as its cause, remembered and raised at the same point relative to the format, trailer, receipt, fd-identity, trade, summary, metric and equity refusals. Fault rows cover a write, flush, close and short write failing at the first, a middle and the last row or batch, each against every competing refusal.

Memory

The queue is one bounded list per artifact file, bounded by the existing 512-record and 256 KiB constants (a single oversized row is written alone). Nothing grows with the number of trades or bars.

Tests

docker/run_json_spool_work_test.py (166 cases) with its reference module docker/run_json_spool_reference.py (not collected by the harness glob; verbatim copies of the previous functions). Counters come from the interpreter (sys.setprofile on the real frames and wrapped pattern objects), each with a positive control on the reference and a mutation row that puts the removed work back. Byte identity of complete reports is checked over the existing families plus the finite float shapes and the flush boundary at 1, 511, 512, 513, 1024 and 4097 trades.

On the previous tree plus only these two files, 19 cases fail, as designed: 18 of them on the named work counter (SPOOL_ASCII_WRITES, SPOOL_STREAM_CALLS, EQUITY_BOUND_WORK). The 19th, test_equity_endpoint_dictionaries_belong_only_to_curve_endpoints, is red for the right work (the previous code builds eager endpoint dictionaries, which the counter records as eager rather than as endpoint dictionaries) but fails at a plain assertion on the counter value before the named check runs; the same counter's detection power is shown by the control inside that row and by two mutation rows. A follow-up reorders the assertion.

Notes for the record

  • Because trade rows now reach their column through the queue, the existing mid-write failure test in run_json_bounded_transport_test.py (it patches _BoundedJSONFile.write) no longer exercises the trade spool: the first file it pushes past one window is now the final artifact. Every assertion in it still holds; the trade-spool write failure path is covered by the new file. A follow-up also patches write_ascii in that test.
  • The strings the new tests put into entry_id, exit_id and exit_comment do not reach the changed encoder: those are trade fields the report drops, and the only string in the trade JSON is side. They test the report path as a whole, not the encoder's escaping; the encoder is covered by the finite-float and byte-identity rows.
  • close() of the artifact file now closes the underlying file even when closing its buffer raises (before, the file close was skipped in that case). Benign; outside the queue itself.
  • Doc pins into docker/run_json.py are repinned (digits only, 11 anchors, two pages).

🤖 Generated with Claude Code

luisleo526 and others added 3 commits October 30, 2026 19:06
…quity proof

Trade spool: every ordinary trade row cost two logical writes to its column
(the separator and the row) and one dispatch through the generic JSON
streamer. Encode the row once with the same dumps configuration the scalar
fast path used and queue it, with its separator, in the artifact file; the
queue is drained inside the existing 512-record and 256 KiB bounds, before any
write, flush, read, rewind or close of that file, and after the checked reader
close of a fused verification. A failed physical write clears the queue before
writing so the same text is never retried; a drain failure becomes the same
typed report_sink_failed refusal, remembered and raised at the same point
relative to the other refusals. The byte stream, the aligned windows and the
sync count are unchanged.

Equity proof: the permitted repeat of the original run pattern is derived once
with the pattern instead of by parsing the pattern text for every batch, and
the first and last endpoints of the curve are decoded on demand instead of
building two dictionaries for every batch of interleaved rows. Every per-row
numeric proof stays: the timestamp range, the finite check and the canonical
repr comparison of every number, and the repeat bound still belongs to the run
pattern alone.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
…the equity proof

Count the logical writes and the generic streamer dispatches per trade column,
the joins per bounded batch at 1, 511, 512, 513, 1024 and 4097 trades, the
interleaved equity batches, the repeat-bound work and the endpoint
dictionaries of the proof, each against a verbatim copy of the previous code
as a positive control. Pin byte identity of complete reports (stdout, sink,
digest, receipt) over the existing families plus every finite float shape and
every escape class, the strict fallback of noncanonical equity lines, the
ordering of queued text against every other artifact operation, and the typed
failure envelope of write, flush, close and short-write faults at the first,
middle and last row or batch, including against each competing refusal.
Mutation rows put each removed piece of work back and each dropped check out.
The reference copies live in a support module that the harness glob does not
collect.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Only the file:line pins into docker/run_json.py change, following the lines
the previous commits moved; no sentence is edited.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
_BULK_E_LITERAL_BYTES = run_json._BULK_E_LITERAL_BYTES


class _BulkEquity(run_json._BulkEquity):
Comment on lines +17 to +21
from run_json import (
RunFailure, ReportSinkReceiptC, _engine_invariant, _open_uncached_sink,
_sink_summary, _sink_trade, _stream_json, _SinkReportDiagnostics,
_filesystem_type, _MEMORY_FILESYSTEM_TYPES,
)
import run_json_wrapper_test_support as fixtures
from run_json_wrapper_work_test import _EquityWork, _mutate, _EQUITY_WIRES
from run_json_fused_test import _sink_case, _configure_outputs, _synthetic_case
from run_json_trade_scan_test import _point, _trade, _event
import stat
import sys

import run_json
@luisleo526
luisleo526 marked this pull request as ready for review October 30, 2026 12:08
@luisleo526
luisleo526 merged commit 5b4df17 into main Oct 30, 2026
18 of 20 checks passed

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

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.

Not checked: tests were not run; formal verification proved 0 of 8 changed function(s) (1 sampled, not proven, 7 not verified).

Formal verification. PR-changed functions: 1/8 verified (0 proven, 1 may-equivalent, 0 distinguished) · 7 not verified (1 vacuous, 6 unsupported).

Not verified on this run: \_append\_trade\_record (unsupported), \_verify\_fused (unsupported), append\_ascii (unsupported), flush (unsupported), trades (vacuous: never exercised), write (unsupported), write\_ascii (unsupported).


Graphify review — findings

Speeds up the fused report-sink path by batching column writes and deferring equity endpoint construction. Column rows are now joined in memory up to the streaming batch bounds and written together, with a drain step (_finish_fused_columns) before assembly so a pending-write failure still surfaces as the same deferred spool error. Trade records take a pre-encoded JSON fast path, and _bulk_equity caches the default run-pattern bounds so batches skip the per-call pattern parsing, with endpoints kept as tuples until read and the diagnostics' last point set from the final batch only.

Worth a look

  • Fast trade path calls append_ascii without row_count, so it always fails the trades spool — docker/run_json.py:6731 · Escalate · high
    • NOT verified (no proof, no reproducing execution) — needs human review

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 696 functions depend on the 682 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: _sink_summary() — 4 callers, 4 callees
  • worse: _open_uncached_sink() — 5 callers, 3 callees
  • new: test_trade_spool_joins_replace_row_writes() — 0 callers, 6 callees

Verification — 696 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 689 function(s) in the blast radius were not formally verified this run

Docs that may be stale (advisory)

…and 10 more.

Formal verification

Could not verify: Could not verify \_append\_trade\_record.

The verifier did not have enough to check \_append\_trade\_record, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

No difference found (not proven): No behavior difference found in \_bulk\_equity (not a proof).

The verifier ran both versions of \_bulk\_equity on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_verify\_fused.

The verifier did not have enough to check \_verify\_fused, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify append\_ascii.

The verifier did not have enough to check append\_ascii, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify flush.

The verifier did not have enough to check flush, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify trades.

The verifier did not have enough to check trades, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 109 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous

Could not verify: Could not verify write.

The verifier did not have enough to check write, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify write\_ascii.

The verifier did not have enough to check write\_ascii, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

· 1 grounded finding(s) anchored inline below; 2 more finding(s) on lines outside this diff (see the check run).



@pytest.mark.parametrize("count", (511, 512, 513, 1024, 4097))
def test_trade_spool_joins_replace_row_writes(tmp_path, monkeypatch, count):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression — test_trade_spool_joins_replace_row_writes()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

luisleo526 added a commit that referenced this pull request Oct 30, 2026
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