Skip to content

feat(reconciliation): design + canonical comparison foundation - #9

Merged
diiabblo merged 1 commit into
OBP-ORG:mainfrom
Akpolo:feat/issue-8-reconciliation-foundation
Jun 21, 2026
Merged

diiabblo merged 1 commit into
OBP-ORG:mainfrom
Akpolo:feat/issue-8-reconciliation-foundation

Conversation

@Akpolo

@Akpolo Akpolo commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

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 + reproducible RunPin (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":
    • toBaseUnits coerces to bigint and rejects floats, NaN/Infinity, non-integer strings, and unsafe-integer numbers;
    • normalize sorts 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 classifying missing / unexpected / stale / value_mismatch with 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.

Note: the current projection schema stores amounts as Float; the design treats that as a migration target and the canonical layer guards against any value that can't be represented exactly. That column migration lands with the persistence PR.

Test plan

  • npm run build (tsc) — clean
  • npm run test:unit — 14 passed (canonical exactness/normalization/fingerprint + comparison classification), via node:test over the existing ts-node (no new dependency)

Closes #8

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 diiabblo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • toBaseUnits correctly rejects floats, NaN, unsafe integers at the boundary
  • normalize is key-order independent and drops nullish/absent fields consistently
  • fingerprint (sha256) is deterministic and sensitive to any value change
  • compareDomain is pure, classifies discrepancies correctly (missing/unexpected/mismatch/stale), emits in stable order
  • Zero new dependencies -- uses node:crypto and node: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.

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.

Add ledger-pinned reconciliation, drift forensics, and zero-downtime projection repair

2 participants