Skip to main content

Automatic projection recovery tasks

Status: implemented; review R1/R2 corrected, local review pending, 2026-09-25. Levels: source (code read/review), generated (tests run on the generated tree), fixture (real MariaDB, synthetic source pages, injected faults), local-live (real single-member Aeron runs with archives and HTTP gateways, real NATS, MariaDB from the generated ConfigMap 900 schema + three additive migrations with ddl-auto=validate, trade-processors as child JVMs). No HA claim.

  • Worktree/branch from 5ceb0be3, CLAIM, operative YU18 layers identified (see plan).
  • Member page, consumer worker, migration, disposable profile, status route.
  • Fixture suite 19/19 after R1/R2 (15 before); explicit EventRecoveryPersistenceIT 17/17 retained; trade-processor unit 102/102; matcher ClusterRecon*/RunIdentity* 27/27 (incl. real 3-member archive consensus test).
  • Live proof through the launcher: PASS; generated parity 198/198; five gates pass.
  • Docs, RI06, backlog, evidence manifest, commit, READY_FOR_REVIEW.

Acceptance mapping​

CaseEvidenceLevel
Unchanged baseline failureAuto disabled: consumer restart keeps 2 of 4 legs, no cursor (APR_BASELINE_UNCHANGED)local-live
Ongoing trading throughoutTrader active during every recovery phase; 61 pages committed while trading, 27 inserting missed trades (APR_TRADER_STOP)local-live
Consumer outage/restartStop + restart with profile; converges, archive matchlocal-live
Publisher/NATS outage, consumer connectedNATS container stopped/started, consumer alive throughout; periodic path repairslocal-live
Process killExternal SIGKILL mid-stream, restart convergeslocal-live
Restart before/after commitJVM halt(137) at before-commit (log/cursor unchanged) and after-commit (exactly one page)local-live (exact-point halt via test-only hook)
Injected exceptions before/after commitrollback + retry; post-commit retry idempotentfixture
Interior and per-command gapsInterior + seq with order but no tradefixture; interior also live
Whole-command paging, zero-output commandspage boundaries [1,3,4,5] at size 2fixture
Duplicate / out-of-order deliveryre-delivery after catch-up changes nothingfixture
R1: all trades retained, out of order through flatbasis 200 -> 300 (source order), CURRENT, one notification, idle rerun silentfixture
R1: flip without flatconverges with no corrective write or notificationfixture
R1: retained-only key with unexplained balanceBLOCKED, SQL unchanged, no notificationfixture
R2: normal catch-up notifies final positionone publish qty 20 / basis 150 after commitfixture
Newer live state not overwrittenorder at newer seq kept; newer trade kept in rebuilt position; basis in authoritative orderfixture
Two workers, fenced stale ownerSIGSTOP owner, takeover (fence 6->7, 7.1 s), SIGCONT: 0 commits after resume, fences monotoniclocal-live
Owner stalled inside transactionserver aborts after lease TTL; only new fence in log; control without the bound fails (stale fence committed)fixture
Selected-run transition during catch-upfreeze while CATCHING_UP; VERIFY passes with no operator call; select/activate; next-live converges; sealed and legacy rows unchangedlocal-live; mid-page supersede fixture
DRAINING beyond frozenrefuses RUN_SCOPE_NOT_WRITABLEfixture
Conflict refusalAltered retained trade: BLOCKED, SQL unchanged; resumes after reviewed repairlocal-live + fixture
Orphan, unexplained balance, history change, checkpoint/cursor identity, missing archiveBLOCKED, no writesfixture (archive refusal via peer response; member strict replay is the reviewed existing code, source)
Canonical chain independent of publish clockClusterReconCatchupTestgenerated unit
SQL timestamp precision 0/3/6live DB is DATETIME(0); auto fixture at precision 0 incl. stored-corruption refusal; explicit 0/3/6 suite retainedlocal-live + fixture
Exact economics / final orders / positionsarchive /recon/recovery-events comparison: fresh-live 946 legs, 946 final orders, +1441/-1441 basis 100; next-live 186, +276/-276local-live
No operator catch-upprojection_recovery has 0 rows; test class has no catch-up calllocal-live

Failures and corrections during the lane​

  1. Fixture: page loop gated on the thread flag (never ran when called directly). Fixed (stopped flag).
  2. Fixture: at DATETIME(0), order rows were verified after the page wrote them (JPA returned unrounded values). Fixed: verify before writing.
  3. Live 1-2: harness; child JVM inherited test-classpath H2/create-drop settings; NATS restart remapped its port. Fixed in the harness.
  4. Live 3: the member chain hashed the NATS envelope, whose trade date is the publish clock; the cursor correctly refused. Fixed: canonical type+payload; unit test added.
  5. Live 4/6: an owner SIGSTOPped inside its transaction blocked takeover (61 s once, >180 s once) and stalled live booking. Fixed with the idle-transaction bound; fixture control proves it.
  6. Live 5: test picked a victim below the cursor (documented limitation). Live 7: transition VERIFY hit max_allowed_packet (pre-existing; disposable DB raised).

Coordinator review R1/R2 (2026-09-25)​

Reproduced by the coordinator (review-evidence/automatic-recovery-coordinator). R1: only keys with inserted trades were checked/re-derived, so arrival-order basis could be certified CURRENT and retained-only keys skipped the balance check. R2: notification compared against the post-insertion row. Fix: every page key is balance-checked and re-derived; notify vs pre-page value. Control: the corrected tests on the delivered b5b12bbb code fail 3 (flat-basis, retained-only balance, R2 notification); corrected code 19/19. Launcher rerun final-r1: PASS all. In final-r1 the freeze landed after the worker was CURRENT; the freeze-while-CATCHING_UP observation is from the earlier final run (same launcher, pre-R1 code).