Sprint 2 (accounting + observability parity, PINK scope):
- Verified pink_clickhouse.py writes the 8 BLUE-legacy row families at
matching schema and that capital authority in pink_direct.step() is
solely kernel.account (no balance-poll overwrite in the hot loop).
- Report: prod/clean_arch/dita_v2/SPRINT2_ACCOUNTING_PARITY.md.
Sprint 3 offline groundwork (no exchange contact):
- Add _write_trade_exit_leg to pink_clickhouse.py: one BLUE-schema-faithful
trade_exit_legs row per exit leg, with isolated (non-cumulative) per-leg
deltas tracked via _leg_state (reset on ENTER). Closes the docstring gap.
- New offline suite test_pink_multi_exit_groundwork.py (3 passed):
* Flaw 4 — two-leg exit closes once, realized accrues per leg, closed
slot rejects further EXIT (no double-close).
* Overshoot invariant — a final EXIT requesting more than the remaining
size CLAMPS (size to 0, no oversell), retiring the Sprint 0 cumulative-
ratio risk empirically.
* trade_exit_legs delta + full BLUE column-set assertions.
- Persistence regression after edits: 10 passed.
BLUE untouched: no changes to dolphin.* / DOLPHIN_*_BLUE / nautilus_event_trader.py.
Live VST multi-leg run remains deferred pending explicit authorization.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.8 KiB
Sprint 2 — Accounting + observability parity verification
Date: 2026-05-30
Scope: Verify (no behaviour change) that the DITAv2 PINK runtime preserves
BLUE-legacy-compatible ClickHouse row shapes in dolphin_pink, and that capital
authority in the hot loop is solely the kernel's AccountProjection. Offline only
(MockVenue / unit), no exchange contact. Continues [SPRINT0_FLAW_VERIFICATION.md].
1. Row-shape parity — clean_arch/persistence/pink_clickhouse.py
BLUE-legacy row families written, same schema / no new columns:
| Row family | Writer | Status |
|---|---|---|
policy_events + v7_decision_events |
_write_policy_event |
✅ |
account_events |
_write_account_event |
✅ |
position_state |
_write_position_state |
✅ |
status_snapshots |
_write_status_snapshot |
✅ |
trade_events |
_write_trade_event |
✅ (terminal close) |
trade_reconstruction |
_write_trade_reconstruction |
✅ (ENTRY/PARTIAL/EXIT) |
anomaly_events |
_write_anomaly / record_anomaly |
✅ |
trade_exit_legs |
— | ⚠️ listed in docstring, no writer |
trade_exit_legs has no emitter. It is a multi-leg row family → relevant to
Sprint 3 (DOLPHIN_PINK_PHASE=multi_exit), not single-leg MARKET. Not a
Sprint 1/2 blocker. Action: add the writer when Sprint 3 is taken up, or confirm
BLUE TUI/observability does not require it for single-leg trades.
2. Capital authority — single source = kernel AccountProjection
clean_arch/runtime/pink_direct.py hot loop (step, L309-408):
- Capital is read only from
kernel.snapshot()["account"](L320, L370, L395). - Capital is mutated only by
kernel.process_intent()→account.settle()on fill. - No balance-poll overwrite anywhere in
step(). ✅
External capital writes (all outside the hot loop, by design):
_reconcile_position_slot(L188-194) — the single place an exchange balance snapshot seedsaccount.snapshot.capital; called at startup/recovery only.connect()(L230) seeds from the env defaultinitial_capital, not an exchange poll (per code comment L228-229).recover_account()(L431) re-seeds fromkernel.account.snapshot.capital(the kernel's own value) — not an exchange poll.
Doc/code note (no change made): reconcile_account() (L453) docstring says it
"re-seeds capital from the exchange balance as a guard against drift," but the code
path (recover_account) actually re-seeds from the kernel's own capital — i.e. it
does not overwrite from an exchange poll. Behaviour is the safe one; only the
comment overstates. Flagged for accuracy; not edited (no behaviour change w/o auth).
pink_clickhouse.py reads capital/peak/seq solely from account.snapshot
(_capital/_peak_capital/_trade_seq, L193-201) — no duplicate tracking. ✅
3. Offline test results
siloqy_env, PYTHONPATH=/mnt/dolphinng5_predict, run from repo root.
| Suite | Result |
|---|---|
test_pink_clickhouse_persistence.py |
✅ pass |
test_pink_ditav2_accounting_invariants.py |
✅ pass |
test_pink_direct_runtime.py |
✅ pass |
| DITAv2 PINK Sprint-2 scope | 14 passed |
test_bingx_capital_accounting_battery.py |
❌ 2 failed — legacy path, out of scope |
The 2 failures are in the legacy Nautilus BingX execution/journal path
(prod/bingx/execution.py + prod/bingx/journal.py, imported via
launch_dolphin_live) — not a DITAv2 PINK file, untracked/pre-existing, not
modified by this engagement. Root cause: the fuzz/equivalence tests reuse
fingerprint="fp" across iterations, so bingx_journal.write_snapshot fingerprint-
dedup short-circuits the sink and captured["row"] is never set (KeyError). This
lives on the legacy side of the BLUE do-not-touch boundary → not fixed here.
GATE decision
PASS (DITAv2 PINK scope). Row-shape parity holds for single-leg MARKET; capital
authority is single (kernel AccountProjection) with no hot-loop balance overwrite;
all PINK-scoped offline suites green.
Carry-forward (Sprint 3)
- ✅ CLOSED (offline groundwork, 2026-05-30):
trade_exit_legswriter added topink_clickhouse.py(_write_trade_exit_leg, BLUE-schema-faithful, isolated per-leg deltas tracked viaself._leg_state, reset on ENTER). Fires once per exit leg. - ✅ CLOSED (offline groundwork): cumulative-ratio exit sizing overshoot validated —
test_pink_multi_exit_groundwork.py::test_final_leg_overshoot_does_not_oversellproves a final EXIT requesting more than the remaining size clamps (size→0, no oversell, closes once). Validation suite: 3 passed; persistence regression: 10 passed. - ⏳ PENDING (live): the on-exchange multi-leg run (successive MARKET exits on VST to confirm Flaw 4 end-to-end) is deferred — requires explicit authorization for additional live testnet orders beyond the single Sprint 1 round trip.