Skip to content

Record which order each paper cancel request was for; pair sim-to-paper cancels exactly - #242

Merged
seathatflowsinourveins merged 4 commits into
mainfrom
claude/cancel-identity-20260925
Sep 25, 2026
Merged

seathatflowsinourveins merged 4 commits into
mainfrom
claude/cancel-identity-20260925

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

What

Paper runs now record which owned order each cancel request was for, and sim-paper-compare pairs every cancel with its order exactly. Before this, a cancel request was logged as {"timestamp", "kind"} only, so the replay inferred the order from timing and refused the whole log whenever two orders were open at the same time.

How

File Change
adaptive-paper/transport.py AlpacaPaperTransport.cancel() announces (broker order id, client order id) on its GuardedSession for the one DELETE it sends, under the existing operation lock, and clears it in a finally. The session passes client_id to the budget hook only when the DELETE path's broker id matches.
adaptive-paper/transport.py _budget_sync The submission-only path (0.25 s owner deadline, no freeze on timeout) now applies only to kind == "submit". A cancel that names its order keeps the ordinary waiting path, so naming a cancel can never make it fail fast.
adaptive-paper/runner.py Controller.before_request records client_id in requests[], which becomes paper-output.json. The durable ledger row still binds only submits: requests.client_id is a foreign key to intents, and binding a cancel to an unknown id raised IntegrityError in testing. Metadata must never be able to fail a cancel.
adaptive-paper/simulation.py, mover_simulation.py Pass the cancelled order's client id.
sim-paper-compare/replay_compare.py When cancel requests name their order, each canceled order resolves to its first named request (a retried cancel keeps its first request). A canceled order with no named request falls back to submit + timeout, with its own source marker. Older ledgers keep the existing all-or-nothing inference.

Receipt regeneration

receipts/20260923g-main-passed.json pins replay_compare.py's sha256, which ReceiptConsistencyTests enforces.

  • Check first. Following the README's procedure, I replayed with --replay from the retained private page cache, using the pinned rc5 runtime, into a scratch receipt. It equalled the committed receipt except for runner_sha256 and the documented run-local fields.
  • Then regenerate. I then regenerated the committed receipt the same way. The diff is exactly runner_sha256, started_utc and completed_utc; the results are byte-identical, because that trial has no named cancels.

Frozen-source re-pin (sim-capacity)

validate_convergence.py requires every {path, sha256} in a recorded experiment to match the current file. sim-capacity/fetcher.py imports replay_compare.py, so the sim-capacity record (#235) pinned it. Rather than silently update the digest, I checked equality first:

  • The check. The five names sim-capacity uses from it (DATA, PageFetcher, chmod_private_dir, digest_bytes and ts_ns), and every module-level definition they reference (8 in all), are byte-identical between the frozen revision fab6a7c49082 and this PR's 3d5b4e9d1188. That comes from an AST source comparison.
  • Tests. tests.test_sim_capacity passes on system Python and on the pinned runtime (62 tests).
  • The record. The frozen digest is re-pinned in 4e78492. A dated limitation records the superseded revision, the check, and that the committed sim-capacity receipts were produced with fab6a7c49082.

Checks

Check Result
Pinned rc5 runtime: 23 paper modules plus test_sim_paper_compare (e41d385) 1,432 tests OK (2 skipped, the exchange_calendars agreement tests)
Full python3 -m unittest (e41d385) 4,950 tests OK
Mutation check: 16 mutants, each killed by a behavioural test Transport: an unnamed DELETE, an unannounced cancel, an uncleared expectation, a named cancel on the submission path, an overridden owner deadline or freeze, no case folding, a name helper that can raise. Runner: a dropped log id, a foreign-key-bound durable row. The two simulation ports. Replay: names ignored, submits read as cancels, list order instead of time order, retry counts dropped.
validate.py, validate_convergence.py --all-recorded, evidence_manifest.py --check, source-hashes manifest test, gitleaks pre-commit (4e78492) Pass

Not covered

  • The exact path activates only for runs made after this lands. Retained trials keep the inference and its documented uncertainty.
  • The host clock is still the cancel clock; see clock_provenance.

Review

One independent Claude review round (Opus 5.5) on aa515ad. Verdict: needs changes, none safety-blocking. It found no path where this makes a cancel less likely to go out.

  • Checked and clean: every before_request implementer accepts client_id; the capacity port and read-only sessions are unaffected; the recovery-timeout cancellation path still sends an unnamed DELETE; no requests[] consumer assumes {timestamp, kind}; the regenerated receipt is justified.
  • Fixed in the repair round (38602cd, e41d385):
    • a never-raising name helper and a private expectation attribute, with the announce moved inside cancel()'s try;
    • named_request_count per order and named_cancel_requests counts, so retries and ignored unnamed cancels are visible;
    • the README's stale passages;
    • tests for every mutant the review found surviving: the replay's kind filter and sort, the default owner deadline and freeze, case folding with a hex-letter id, and the two simulation cancel sites.
  • Mutation check after the repair: 16 of 16 mutants are killed by behavioural tests. The receipt's source-hash pin is excluded from that count.

Codex cross-family review is unavailable until 2026-09-30, so it was skipped.

🤖 Generated with Claude Code

Scout and others added 3 commits September 25, 2026 05:54
…cels exactly

Paper runs logged a cancel request as {"timestamp", "kind"} only, so
sim-paper-compare had to infer which order each cancel was for from
timing, and it refused the whole log whenever two orders were open at
once.

- transport.py: AlpacaPaperTransport.cancel() announces (broker order id,
  client order id) on its GuardedSession for the one DELETE it sends, and
  clears it in a finally. The session passes client_id to the budget hook
  only when the DELETE path's broker id matches.
- _budget_sync keeps the submission-only 0.25 s never-wait deadline for
  submits. A named cancel stays on the ordinary waiting path, so a cancel
  can never fail fast because it names its order.
- runner.py: Controller.before_request records client_id in requests[],
  which becomes paper-output.json. The durable ledger row still binds
  only submits: requests.client_id is a foreign key to intents, so naming
  a cancel can never make its reservation fail.
- simulation.py and mover_simulation.py pass the client id too.
- replay_compare.py: when requests name their order, each canceled order
  resolves to its first named request, and one without a named request
  falls back per order to submit + timeout with its own source marker.
  Older ledgers keep the existing all-or-nothing inference. The
  20260923g receipt was regenerated by --replay from the retained pages
  with the new source. It differs from the committed receipt only in
  runner_sha256 and its run timestamps; the results are byte-identical.

Tests cover every change, and 7 mutants were killed: an unnamed DELETE,
an unannounced cancel, an uncleared expectation, a named cancel on the
submission path, a dropped log id, a foreign-key-bound durable row, and a
replay that ignores names.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…er tests, visible retries

Review round on aa515ad (verdict: no cancel-safety defect; coverage,
one defensive guard and stale docs).

- transport.py: the expectation is private (_cancel_expectation) behind
  announce_cancel() and withdraw_cancel(). The session resolves the name
  in _cancel_client_id(), which never raises, so a malformed expectation
  only drops the name and never blocks the DELETE. The announce moved
  inside cancel()'s try, so a failure there still freezes as
  cancellation_unresolved.
- replay_compare.py: cancel_request_names() also returns per-order named
  request counts and the number of unnamed cancel requests. Exact entries
  carry named_request_count (above 1 marks a retry). clock_provenance
  gains named_cancel_requests and says which orders fell back to submit +
  timeout. Legacy output is unchanged.
- The 20260923g receipt was regenerated by --replay. It differs from the
  original only in runner_sha256 and the run timestamps.
- README: the exact path, its source markers and the new counts replace
  three stale "no client_order_id" passages.
- New or strengthened tests:
  - a UUID with hex letters, so case folding is really exercised;
  - a malformed expectation still sends the DELETE;
  - an _on_owner spy pins the named cancel to the 65 s default deadline
    and freeze, with no overrides;
  - the replay fixture has submit entries carrying client_id and an
    out-of-order retry;
  - the runner's submit entries keep client_id, and both simulation ports
    name the cancelled order.
- Mutation check: all 16 mutants are killed by behavioural tests (the
  receipt hash pin excluded), including the 7 the review found surviving.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
validate.py rejects any literal UUID in published files (a possible local
session identifier). The case-folding test now builds its hex-letter id
with uuid.UUID(int=...) like the file's existing ID. All 16 mutants are
still killed.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

…ity evidence

validate_convergence.py requires each {path, sha256} in a recorded
experiment to match the current file, and this PR changes
sim-paper-compare/replay_compare.py, which sim-capacity's fetcher
imports.

The five names sim-capacity uses from it (DATA, PageFetcher,
chmod_private_dir, digest_bytes and ts_ns) and every module-level
definition they reference, 8 in all, are byte-identical between the
frozen revision fab6a7c49082 and 3d5b4e9d1188 (AST source comparison).
tests.test_sim_capacity passes on system Python and on the pinned
runtime (62 tests).

The frozen digest is re-pinned, and a dated limitation records the
superseded revision, the check, and that the committed receipts were
produced with fab6a7c49082.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@seathatflowsinourveins
seathatflowsinourveins merged commit d9c2c6e into main Sep 25, 2026
26 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/cancel-identity-20260925 branch September 25, 2026 10:54
seathatflowsinourveins pushed a commit that referenced this pull request Sep 25, 2026
…eck; pin hftbacktest's oracle agreement

Merge of main at 4b594d4. sim-capacity/experiment.json keeps both
sides' appended limitations (this branch's two cross-check entries,
then main's #242 frozen-source re-pin note) and main's re-pinned
replay_compare.py digest. manifests/evidence.json is main's registry
plus this branch's 11 registrations, with every digest recomputed.

The independent re-check of 08a684e returned "ready": its own oracle
reproduced all 8 configurations exactly, and it confirmed the fix from
hftbacktest source. Its minor corrections are recorded in a README
section. The run's code and receipt bytes are left as produced, since
the receipt pins their hashes. The corrections: only the isolation test
is a reliable use-after-free guard; the Nautilus-to-oracle residual is
short-sale rejects plus timing; locked quotes changed no outcome; and
three citation nits.

The receipt test now pins hftbacktest's oracle agreement at its
measured 100% (Nautilus keeps the 99% floor).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
seathatflowsinourveins added a commit that referenced this pull request Sep 25, 2026
… engine merges and credential blocker (#248)

- Simulation lane: hftbacktest agrees with NautilusTrader within 0.36%
  once our driver's use-after-free is fixed (#245). The evidence
  reference points to the cross-check receipt.
- Paper lane: the engine P0 release (#221) and exact cancel identity
  (#242) merged today. The native-fault rerun and the 1x ladder attempt
  are blocked: at the open, the stored paper keys were rejected with
  HTTP 401 on the trading account and on both data feeds, so they need
  rotating through the credential terminal. No order was sent.

Co-authored-by: Scout <scout@local>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
seathatflowsinourveins added a commit that referenced this pull request Sep 26, 2026
#242 and #284 (#350)

* Paper engine: fix the retroactive cross-family review findings on #221, #242 and #284

GPT-6 (gpt-6-astra, effort max) reviewed three merged paper-engine PRs on main ca8e37f; #243 passed.
- #221: activity-based gap recovery booked fills natively without persisting them to the durable ledger (native 3,
  durable 1); a continuing LULD pause auto-cleared after twelve minutes and let cancel_expired cancel the exit it
  protected; cash and realized P&L depended on REST-snapshot versus stream delivery order.
- #242: replay_compare scheduled a named cancel stamped before its submit and silently skipped it; cancelling the
  transport task cleared the cancel identity before the HTTP worker sent the DELETE.
- #284: a clean exporter stop released the port before deregistering, so it could remove a replacement exporter's
  target; configure.py's initialization could overwrite a concurrent registration; malformed nested labels passed
  validation and would make Prometheus reject the whole target list.

Each fix was implemented test-first by a GPT-6 implementer in its own worktree; every new regression failed before
its fix. Coordinator integration: source-hashes.json and evidence pins refreshed; the 20260923g sim-paper-compare
receipt replayed from its retained pages with the new code (identical apart from run-local fields and runner_sha256,
updated as in #242); sim-capacity's frozen replay_compare source re-pinned with a dated limitation (AST check: the
8 definitions sim-capacity uses are unchanged).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* Paper engine repair round: persist recovered fills through the transport's real observation hook

Independent verification of cad7faf found #221 finding 1 unresolved in production: reconcile_fills persisted recovered
fills only through a port.controller attribute that no real Alpaca port has (AlpacaPaperTransport and the native-faults
FaultTransport are wired with sink_observation=controller.observe); only the test-only SimulatedPort carried it.
- native_adapter.py: persist through the port's sink_observation (the hook the transport already uses for stream rows),
  after validating that the recovered activities tile the fill contiguously (fail closed otherwise).
- simulation.py: SimulatedPort exposes the same sink_observation hook, so tests and production share one path.
- Tests: a real-shaped port (no .controller) regression that failed on cad7faf with Decimal('1') != Decimal('3'), restart
  persistence, incomplete tiling; the two accounting-replay guards in safety.py; fast build_decisions-level tests for
  the cancel-before-submit refusal and its equality boundary.
Implemented by a GPT-6 implementer test-first; 514 tests across the five affected modules pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Scout <scout@local>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
seathatflowsinourveins added a commit that referenced this pull request Sep 26, 2026
…ealized-loss delivery-order major; follow-up to #350) (#355)

* Paper engine: fix the retroactive cross-family review findings on #221, #242 and #284

GPT-6 (gpt-6-astra, effort max) reviewed three merged paper-engine PRs on main ca8e37f; #243 passed.
- #221: activity-based gap recovery booked fills natively without persisting them to the durable ledger (native 3,
  durable 1); a continuing LULD pause auto-cleared after twelve minutes and let cancel_expired cancel the exit it
  protected; cash and realized P&L depended on REST-snapshot versus stream delivery order.
- #242: replay_compare scheduled a named cancel stamped before its submit and silently skipped it; cancelling the
  transport task cleared the cancel identity before the HTTP worker sent the DELETE.
- #284: a clean exporter stop released the port before deregistering, so it could remove a replacement exporter's
  target; configure.py's initialization could overwrite a concurrent registration; malformed nested labels passed
  validation and would make Prometheus reject the whole target list.

Each fix was implemented test-first by a GPT-6 implementer in its own worktree; every new regression failed before
its fix. Coordinator integration: source-hashes.json and evidence pins refreshed; the 20260923g sim-paper-compare
receipt replayed from its retained pages with the new code (identical apart from run-local fields and runner_sha256,
updated as in #242); sim-capacity's frozen replay_compare source re-pinned with a dated limitation (AST check: the
8 definitions sim-capacity uses are unchanged).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* Paper engine repair round: persist recovered fills through the transport's real observation hook

Independent verification of cad7faf found #221 finding 1 unresolved in production: reconcile_fills persisted recovered
fills only through a port.controller attribute that no real Alpaca port has (AlpacaPaperTransport and the native-faults
FaultTransport are wired with sink_observation=controller.observe); only the test-only SimulatedPort carried it.
- native_adapter.py: persist through the port's sink_observation (the hook the transport already uses for stream rows),
  after validating that the recovered activities tile the fill contiguously (fail closed otherwise).
- simulation.py: SimulatedPort exposes the same sink_observation hook, so tests and production share one path.
- Tests: a real-shaped port (no .controller) regression that failed on cad7faf with Decimal('1') != Decimal('3'), restart
  persistence, incomplete tiling; the two accounting-replay guards in safety.py; fast build_decisions-level tests for
  the cancel-before-submit refusal and its equality boundary.
Implemented by a GPT-6 implementer test-first; 514 tests across the five affected modules pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* Paper engine: book realized P&L and gross loss per exact execution in accounting replay

The accounting replay summed a status delta's executions into one notional
before computing P&L, so a sell filled at a gain and a loss inside one delta
netted the loss away, and the result depended on whether the stream or the
REST snapshot arrived first. Replay now books each execution that tiles the
delta separately (QuantConnect/Lean 985ef30: SecurityPortfolioModel.cs per-fill
closing P&L; TradeStatistics.cs accumulates losses separately), falling back to
the delta notional only while executions are incomplete.

Two regressions: mixed-profit gross loss across stream-first, REST-first,
duplicate and restart deliveries, and basis preservation at each booking.
With the fix reverted, 4 cases fail (rest_first, duplicates, restart, basis).
Paper suites (safety, runner, native, transport): 407 tests OK.
Implemented by GPT-6 (gpt-6-astra, effort max) test-first; verified by the
coordinator. Pins: source-hashes.json safety.py and manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* Paper engine repair round (#355 review): replay executions in execution order, versioned marker

Independent review of c81a3d5 (needs_changes):
- Finding 1 (major): the replay booked fills in observation order. Stream fills
  now carry Alpaca's own trade_updates timestamp (event_at_ns; FILL recovery
  forwards transaction_time_ns), the ledger keeps it in a new optional
  executions.at_ns column (added on open for existing ledgers), and replay
  fills each symbol's timed execution slots in execution order, tie-broken by
  event id then cumulative quantity. Adoptions, untimed executions and
  uncovered provisional bookings keep their journal positions.
- Finding 3: the marker is execution_accounting:v2:<client_id>, so ledgers
  replayed under the observation-order rule re-derive once.
- Finding 4: a test for an uncovered REST-only booking beside another order's
  late executions.
- Finding 2 (adopted open orders) is covered by #356's startup persistence.

The A/B/C case gives cash 70, realized -15, gross loss 25, and 1 share at 115
under stream, REST-ahead, recovered, late-buy and nanosecond deliveries.
Reverting the three production files fails 10 cases (4 new tests, 2 strengthened).
Paper suites: 411 OK; trading gates 26 OK; validate.py passed.
Implemented by GPT-6 (gpt-6-astra, effort max) test-first; verified by the
coordinator. Pins: source-hashes.json (safety, transport, native_adapter) and
manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* Re-pin the changed paper test files in adaptive-paper/source-hashes.json

source-hashes.json also pins test files; CI's
test_adaptive_paper_source_hashes_manifest caught the stale entries. Pins only,
no code change: the changed tests' entries and their manifests/evidence.json
registration.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* Reduce #355 to the reviewed-safe subset; defer execution-order replay

The re-check of cc6d7cb found two new majors in the execution-order replay:
- Moving an earlier-executed fill into another order's slot can make an
  uncovered REST-only sell go short. The replay then raises, the fill rolls
  back and the transport freezes, although the real history is consistent.
- Removing the early exit let that failure block fills in every symbol and
  added a full replay to every fill.
Under the bounded review loop (one review, one repair, then land with
residuals), this commit lands the parts both reviews confirmed.

It keeps:
- c81a3d5's per-execution P&L and gross loss within a delta;
- the versioned execution_accounting:v2 marker (F3);
- the uncovered-fallback test (F4), with its oracle stated for
  observation-order replay.

It reverts:
- the slot-substitution replay and the early-exit removal;
- the executions.at_ns column, which also avoids the unversioned schema
  change (re-check F3);
- the stream and recovery timestamp plumbing.

Execution-time ordering is a recorded follow-up, with the reviewer's
constraints: never reorder into a short (fall back to observation order), a
targeted replay trigger, and a schema version bump.

Revert checks: the v1 marker fails the F3 test; skipping the uncovered
fallback errors the F4 test. Paper suites: 409 OK; source-hashes and trading
gates: 33 OK; validate.py passed. Pins: safety.py and its test file only.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Scout <scout@local>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
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