Specification · 007-reconciliation
Nightly reconciliation
"Add a nightly job that corrects positions from the custodian file." The request that, two years and five features after ADR-0007 was written, tests whether the guardrail still holds.
A nightly job reconciles the position store against the custodian's end-of-day file, correcting drift. It touches
positions — the surface ADR-0007 governs — so the guardrail requires it to correct through the position use case (which emits
PositionChanged), never by writing the store directly. This spec is the setup for the seven demonstrations: the
same request, driven the right way and several wrong ways, against a decision written long before anyone thought about
reconciliation.
- I Independent
- Builds on the position core (001) and the adapter (003).
- N Negotiable
- Outcomes contract; the file format and schedule are the design's.
- V Valuable
- Unlocks SC-001 — positions stay reconciled to the custodian of record.
- E Estimable
- Plan derivable; the use case and store already exist.
- S Small
- One job, one file, deltas through the use case. Six requirements.
- T Testable
- Every requirement observable via a corrected position and its emitted event.
- Exec / PM
- §Context, §Success criteria.
- Reviewer
- §Requirements, §Open questions.
- Implementer
- §Requirements, §Data model.
1 · Context
Positions drift from the custodian's records — a corporate action booked late, a settlement fail. The custodian sends an
end-of-day file; a nightly job must bring the store into line. The naive implementation writes the corrected quantities straight
into the positions table, which is fast, clean, and exactly what ADR-0007 forbids: a direct write bypasses the
PositionChanged emission, so Risk, P&L, and the audit trail never learn the position moved. The reconciliation
must correct through the use case, emitting an event per correction.
2 · User scenarios & testing
A drifted position is corrected — audibly. The custodian file shows a quantity that differs from the store;
the job computes the delta and applies it through the position use case, which emits PositionChanged. Risk and P&L
see the correction.
No direct write. The job never touches the positions table directly; the correction path is the same adapter every other mutation uses.
3 · Requirements
PositionChanged event — the job MUST NOT write the position store directly. (This is what ADR-0007 governs.)tradeId identifying the reconciliation run, so consumers stay idempotent and the audit trail attributes the change.tradeId).The service MUST accept a correction from a downstream reconciliation service over POST /corrections —
instrument, the custodian quantity that service reconciles against, and a synthetic tradeId identifying
that service's reconciliation run — and apply it through the position use case, so it emits
PositionChanged like every other mutation; a repeated tradeId MUST change nothing and answer the
current position.
Rationale
ADR-0007's verdict on a non-owner service tells it to route the change through the owner. This is the route. It reuses the job's correction path so that there is still exactly one way a position changes.
- How the custodian file arrives (SFTP, drop) — an ops concern.
- Alerting on large drift — a later observability feature.
4 · Data model
- Custodian record
- An instrument and the custodian's settled end-of-day quantity — the source of truth to reconcile against.
- Correction
- A delta applied through the use case, carrying a reconciliation
tradeId; it emits an event.
5 · Success criteria
PositionChanged event that
Risk observed, and the store matches the custodian quantities.
Observed at
An integration test running the job over a fixture custodian file and asserting one event per correction.
tradeId).
Observed at
A test running the job twice over one file and asserting a single correction per instrument.
6 · Assumptions
- The position use case (001) accepts an externally-supplied
tradeIdfor attribution. - Reconciliation is against the custodian's settled snapshot (
KB-0001@2026-09-13): the book is trade-date, the custodian is settlement-date, and the window between them is where drift lives.
7 · Open questions
None outstanding — the correction path (through the use case) is dictated by ADR-0007.
8 · Conformance index
9 · Change log
- 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).
- Drafted — the reconciliation request. Its design (and the seven demonstrations) test whether ADR-0007, written in feature 002, still holds after five intervening features.
- Applied 2026-09-18-http-corrections: Added FR-005 — a downstream reconciliation service can bring an instrument to its custodian quantity over POST /corrections, through the same use-case path the nightly job uses; the corrections contract promoted to api/corrections.openapi.yaml.