Added opt-in canonical observer polling lifecycle

This commit is contained in:
2026-10-20 20:11:57 -04:00
parent 8cfcf6c025
commit 7025c37390
8 changed files with 200 additions and 15 deletions
+5 -2
View File
@@ -35,7 +35,9 @@ A submitted history that conflicts with, removes or makes immature an already po
The same explicitly disposable PostgreSQL instance now has `observeCanonicalEvents()` and `observeLedgerCanonicalEvents(ledger)`. The ledger adapter requires an actual in-process `Ledger` object, checks its network/chain identity, and fetches one page at a time from its locally validated [canonical event API](../server/testnet/canonical-index.ts)—no caller/HTTP/peer page can enter through that adapter. The inbox is append-only and bounded. It checks stream identity, page limits, predecessor anchors, event hashes, sequence continuity, connect parent/tip and tip-first disconnect rules against a reconstructed canonical hash stack. It accepts exact retries and overlapping pages from concurrent consumers only after matching their persisted overlap; stale retries report the durable cursor/head, not the stale page head. The startup/periodic financial audit replays and verifies the stored inbox; a changed stream, gap or malformed/tampered event creates the normal durable integrity hold. Inbox records use namespaced `custody_meta` keys and the existing runtime role; there is no schema migration or mutable cursor.
The adapter is not automatically scheduled: there is no HTTP route or background worker. PostgreSQL tests include synthetic event pages for malformed/retry cases and one integration using blocks accepted by a local `Ledger` configured with a **test-only fake PoW engine**, including an equal-work side branch and a reorg. This tests the local adapter/API boundary, not real RandomX validation, external provenance or production multi-node behavior. Hash-anchor consistency is not proof of honest observation; the stream ID and unkeyed hashes are stored beside the events, so a schema owner able to coherently rewrite the complete history can also recompute the hashes. The included corruption test detects a partial edit, not a fully rewritten valid history. Production must anchor custody checkpoints outside the database's mutable trust domain. The disposable inbox is capped at 4,096 total events, stores the append-only event history and fully rescans it during audits. That is intentionally a small-fixture design, not production storage/cursor architecture or a scale claim.
An opt-in `CanonicalObserver` now schedules one bounded poll cycle on the server runtime, resumes from its durable cursor after restart, and stops/drains before PostgreSQL and the ledger close. Each cycle processes at most four pages of 64 events; lag is exposed on the private operations listener. Poll failures or sale-review events stop the loop fail-closed; it does not retry a failed observer automatically. The runtime opt-in requires `WIKIDECK_CUSTODY_CANONICAL_REHEARSAL=1`, PostgreSQL custody and `WIKIDECK_PG_DISPOSABLE_TEST=1`; the configured and actual database must also be `custody_test`, and `NODE_ENV=production` is explicitly rejected. Invalid/missing combinations fail closed, including direct construction of the polling controller. Shutdown stops scheduling immediately and drains the current page before custody/ledger close. This is **not enabled in the persistent sandbox or any regular deployment** and has no public HTTP control endpoint.
PostgreSQL tests include synthetic event pages for malformed/retry cases and local `Ledger` blocks configured with a **test-only fake PoW engine**, including an equal-work side branch and a reorg. They exercise the adapter and bounded scheduler lifecycle, not real RandomX validation, external provenance or production multi-node behavior. Hash-anchor consistency is not proof of honest observation; the stream ID and unkeyed hashes are stored beside the events, so a schema owner able to coherently rewrite the complete history can also recompute the hashes. The included corruption test detects a partial edit, not a fully rewritten valid history. Production must anchor custody checkpoints outside the database's mutable trust domain. The disposable inbox is capped at 4,096 total events, stores the append-only event history and fully rescans it during audits. That is intentionally a small-fixture design, not production storage/cursor architecture or a scale claim.
The synthetic PostgreSQL event-page seam now links an explicitly terminal candidate transaction ID to its posted order. A later **submitted disconnect event** containing that exact ID commits both the canonical event page and a durable per-order `sale-reorg-review` record plus global `posted-sale-terminal-disconnected` mutation hold in one transaction; the original journal is not reversed or paid again. This remains true if the transaction reappears later in the same replacement-branch page: review does not auto-clear on apparent re-inclusion. Generic incident resolution re-audits the retained review record and therefore cannot clear it. This exercises order-to-event linkage and atomic review behavior. The hold only detects disconnects actually delivered to this seam; it cannot detect an unseen intermediate fork.
@@ -43,7 +45,7 @@ The local-ledger adapter is **not** linked to these terminal candidate IDs: acti
## Validation and remaining gates
TypeScript/Vite Docker build passed; **172 offline regressions and 28 PostgreSQL integration tests passed, zero failures/skips**. [Candidate tests](../tests/sale-candidate.test.ts) cover signature compatibility/recipient tampering and candidate transition rules. [PostgreSQL tests](../tests/postgres/custody.test.ts) add:
TypeScript/Vite Docker build passed; **172 offline regressions and 29 PostgreSQL integration tests passed, zero failures/skips**. [Candidate tests](../tests/sale-candidate.test.ts) cover signature compatibility/recipient tampering and candidate transition rules. [PostgreSQL tests](../tests/postgres/custody.test.ts) add:
- explicit instance/database consent and exact signed reservation matching;
- seed-binding changes, malformed/cyclic/oversized collections, and denial of ordinary cancellation after binding;
@@ -53,6 +55,7 @@ TypeScript/Vite Docker build passed; **172 offline regressions and 28 PostgreSQL
- one-atomic settlement and signed custom-recipient revenue across current-policy changes;
- conflicting outcome → durable cross-instance/restart hold without a second credit;
- anchored canonical-event connect/disconnect paging, overlapping/stale consumer retries, reconnect, partial history mutation audit, forged disconnect-ancestor rejection and tamper-triggered hold; actual locally validated blocks using test-only fake PoW, excluded equal-work branch and a canonical reorg through the adapter (no candidate sale link);
- opt-in HTTP runtime startup, initial observer checkpoint, 64-event bounded catch-up, clean shutdown, durable restart resume and invalid-flag rejection;
- synthetic exact candidate-terminal transaction disconnect/reappearance in one page → committed event history plus order-linked durable hold, generic incident resolution denied, one preserved seller credit and no second payout;
- injected terminal-evidence failure → complete rollback and safe retry; corrupted evidence → audit hold.