Repository navigation
feat(reconciliation): design + canonical comparison foundation - #9
Conversation
First slice of the ledger-pinned reconciliation control plane (issue OBP-ORG#8). The issue calls for a design document plus several reviewable PRs rather than one monolithic change; this is design + the comparison core everything else builds on. - docs/reconciliation-control-plane.md: full architecture — run model + pin, canonical comparison, evidence/forensics, repair safety, shadow rebuild + zero-downtime cutover, observability, and the phased delivery plan. - src/reconciliation/types.ts: run/discrepancy data model (RunPin, RunStatus, ProtocolDomain, DiscrepancyKind, CanonicalEntity, Discrepancy, RepairMode). - src/reconciliation/canonical.ts: exact base-unit comparison (bigint; rejects floats/NaN/unsafe ints — the no-floating-point-money guard), canonical normalization (sorted keys, dropped optionals, bigint→string, dates), and deterministic sha256 fingerprints. - src/reconciliation/compare.ts: pure, report-only domain comparison that classifies missing / unexpected / stale / value_mismatch discrepancies with field-level detail, in deterministic order. - Tests (node:test via existing ts-node) for the comparison core; `test:unit` script added. `npm run build` (tsc) passes; 14 unit tests pass. Closes OBP-ORG#8
diiabblo
left a comment
There was a problem hiding this comment.
Review: Reconciliation foundation
Clean, well-structured first slice. The pure-functional design (zero side effects, exact bigint arithmetic, deterministic normalization, SHA-256 fingerprints) is exactly what a reconciliation control plane needs. The 14 tests cover the core invariants well.
Strengths
toBaseUnitscorrectly rejects floats, NaN, unsafe integers at the boundarynormalizeis key-order independent and drops nullish/absent fields consistentlyfingerprint(sha256) is deterministic and sensitive to any value changecompareDomainis pure, classifies discrepancies correctly (missing/unexpected/mismatch/stale), emits in stable order- Zero new dependencies -- uses
node:cryptoandnode:test - Design doc maps clearly to the code
Notes (non-blocking)
diffFields is one level deep (src/reconciliation/compare.ts:25)
String() is used for field comparison, which works for flat key-value entities. If a future domain has nested canonical values (e.g. {config: {threshold: 5}}), diffFields would compare "[object Object]" strings and miss internal diffs. Not an issue for the current flat projection schema, but worth documenting as a design constraint.
toBaseUnits accepts negative values (src/reconciliation/canonical.ts:38)
toBaseUnits("-5") parses successfully. Soroban amounts are non-negative on-chain, but accepting negatives is convenient for delta comparisons in follow-up PRs. Fine as-is.
sortBy stability (src/reconciliation/canonical.ts:129)
If two items produce the same sort key, Array.sort() order is implementation-defined. For order-insensitive collections keyed by unique IDs this is fine, but worth noting in the docstring.
Integration note
This PR defines test:unit with ts-node/register/transpile-only. When PR #7 merges (adding jest-based test), the two test systems will coexist. The reconciliation tests use node:test which is fine, but a future PR should consider consolidating under one test runner for CI simplicity.
Summary
First slice of the ledger-pinned reconciliation control plane (issue #8).
The issue is explicitly Complex and asks to be "delivered as a design document plus several reviewable implementation PRs rather than one monolithic change." This PR is that first step: the design document plus the canonical comparison core that every later stage builds on — kept pure, exact-integer, and report-only.
What's here
docs/reconciliation-control-plane.md— the architecture: the run model + reproducibleRunPin(network, RPC identity, contract registry/WASM hashes, pinned ledger, decoder/schema versions), distributed lease, resumable shards, canonical comparison, evidence/forensics, report-only repair safety (gated, planned, checksummed, compare-and-swap), shadow rebuild + zero-downtime cutover, observability, and a 7-step delivery plan.src/reconciliation/types.ts— the run/discrepancy data model (RunPin,RunStatus,ProtocolDomain,DiscrepancyKind,CanonicalEntity,Discrepancy,RepairMode).src/reconciliation/canonical.ts— the heart of "compare exact base-unit integers; never use floating point":toBaseUnitscoerces tobigintand rejects floats, NaN/Infinity, non-integer strings, and unsafe-integer numbers;normalizesorts keys, drops optional/default-absent fields, renders bigints as exact decimal strings, normalizes dates;fingerprint= sha256 of the canonical string (deterministic, key-order independent).src/reconciliation/compare.ts— pure, report-only domain comparison classifyingmissing/unexpected/stale/value_mismatchwith field-level detail, in deterministic order.Scope / acceptance criteria
This PR covers architecture requirement #2 (canonical comparison) and the data model for #1, and is wired to nothing destructive. Per the design's delivery plan, the follow-ups add: Prisma persistence of runs/discrepancies/actions, the pinned-ledger evidence collector, the distributed lease + resumable shards, the report-only worker + metrics, the gated repair planner/executor, and shadow rebuild + cutover.
Test plan
npm run build(tsc) — cleannpm run test:unit— 14 passed (canonical exactness/normalization/fingerprint + comparison classification), vianode:testover the existingts-node(no new dependency)Closes #8