Design · 007-reconciliation
Nightly reconciliation
How we will build it — the happy path, which acknowledges ADR-0007 and corrects through the use case.
feat/nightly-reconciliation
Created
Read time
The job reads the custodian file and the current store (through the persistence adapter's read side), computes deltas, and
applies each correction through the position use case — so every correction emits PositionChanged. This
plan touches the persistence package, so retrieval surfaces ADR-0007; the plan acknowledges it and resolves
to route corrections through the use case rather than writing the store.
1 · Principles check
The reconciliation stays on the sanctioned path: reads via the adapter, writes via the use case. No new writer of the positions store, so ADR-0007 holds by construction.
Governing decisions acknowledged
This plan's surface touches the persistence package, governed by 002-downstream-consumers/D-007 (ADR-0007 —
only the persistence adapter touches the data store; every position change must emit PositionChanged). The plan honours it:
corrections are applied through the position use case (which emits the event), and the only persistence-package addition is a
read-side snapshot reader that uses the existing adapter — it adds no new writer. Retrieval:
spectastic adrs --for src/main/java/xyz/briancorbin/pk/hex/persistence/CustodianSnapshotReader.java returns D-007; this section
is its acknowledgment.
2 · Technical context
- Language / runtime
- Java 21, hexagonal.
- Architecture
- A scheduled job in the application layer; reads via the adapter, writes via the use case.
3 · Data model & contracts
The job consumes the custodian file (instrument, quantity) and emits corrections as ordinary use-case
calls carrying a reconciliation tradeId. Since change 2026-09-18-http-corrections the same correction
path is also reachable over HTTP, for a downstream service that computes its own deltas — the contract below.
POST /corrections with {instrument, quantity, tradeId} brings the instrument to
quantity through PositionService.correct — the same path the nightly job uses — and answers the
resulting position. A caller may rely on: a repeated tradeId is a 200 that changes nothing; every
applied correction emitted PositionChanged before the response; the caller never touches the store. Authored at
the proposed location and promoted by /spectastic.apply.
5 · Grounding & evidence
| Claim | Source | Status | Finding |
|---|---|---|---|
| ADR-0007 governs the positions store and this plan touches it | 002-downstream-consumers/design.html#D-007 | verified | Scope src/**/persistence/**; the snapshot reader lives there, so the decision is in-scope and acknowledged. |
| Reconciliation is against the custodian's settled snapshot; drift lives in the settlement window | Settlement domain — KB-0001@2026-09-13 | verified | The book is trade-date, the custodian settlement-date; the delta the job corrects is exactly that window's drift. |
6 · Approach
A scheduled job parses the custodian file, reads current quantities through a persistence-package snapshot reader (using the
existing adapter, not a new writer), computes per-instrument deltas, and applies each as a use-case correction carrying the run's
tradeId. Every correction therefore emits PositionChanged, and ADR-0007's boundary is untouched.
7 · Alternatives considered
| Option | Emits events | Honours ADR-0007 | Simple | Score |
|---|---|---|---|---|
| Correct through the use case (chosen) | ✓ | ✓ | ✓ | 9 |
| Write the positions table directly (the bypass) | ✗ silent | ✗ violates ADR-0007 | ✓ fast | 1 |
The direct write is faster and is exactly what the guardrail exists to stop — it is the bypass the demonstrations exercise.
8 · Decisions
D-001 · Corrections flow through the position use case
Context. Reconciliation touches positions, governed by 002-downstream-consumers/D-007; the domain rationale is
settlement-window drift (KB-0001@2026-09-13). Decision. Apply every correction through the use case
(emitting the event); read the current store through the adapter; add no new writer. Consequences. + ADR-0007 holds;
Risk/P&L/audit see every correction. − One event per corrected instrument (fine at end-of-day volumes).
9 · Project structure
src/main/java/xyz/briancorbin/pk/hex/recon/ReconciliationJob.java # the job (application layer)
src/main/java/xyz/briancorbin/pk/hex/recon/CustodianFileParser.java # parse the EOD file
src/main/java/xyz/briancorbin/pk/hex/persistence/CustodianSnapshotReader.java # read current positions via the adapter (read-only)
src/main/java/xyz/briancorbin/pk/hex/http/CorrectionsHandler.java # POST /corrections → PositionService.correct (change 2026-09-18-http-corrections)
specs/007-reconciliation/contracts/corrections.openapi.yaml # the contract, proposed (→ api/corrections.openapi.yaml at apply)
10 · Risks & mitigations
A future maintainer optimises by writing the store directly
Likelihood medium, impact high. Mitigation: ADR-0007's merge verdict catches a direct UPDATE positions outside the adapter — the demonstrations prove it.
11 · Complexity tracking
None.
12 · Open questions
None outstanding.
13 · Change log
- Declares the
correctionscontract (api/corrections.openapi.yaml) and theCorrectionsHandlerpath for change2026-09-18-http-corrections: a downstream service reaches the same use-case correction path overPOST /corrections. No decision changes; ADR-0007 is honoured the same way. - Status flipped Draft → Accepted on 18 Sep 2026 — zero remaining unchecked tasks (11/11); ./gradlew build green with ReconciliationJobTest (SC-001, SC-002) verified passing and the reconcile subcommand exercised twice against one store (2 corrections, then 0) per author pre-confirmation for 003–007. Sibling bundle (REQ-LIFECYCLE-005).
- Design drafted (happy path) — corrections through the use case; acknowledges ADR-0007 (002-downstream-consumers/D-007); grounds settlement-window drift in KB-0001. The demonstrations also exercise a bypass variant (caught at plan) and a direct-write commit (caught at merge).