Tasks · 007-reconciliation

Nightly reconciliation

Ordered, parallelizable work. Tests precede implementation. Drive through with /spectastic.implement — one task per invocation.

Status Accepted Spec 007-reconciliation Design design Branch build-out Created Read time

11 tasks across three phases — nothing to set up. Foundational (a correct use case, the file parser, a fixture) → US1 (a drifted position corrected audibly) → US2 (re-run, no double correction) → Polish. Tasks marked [P] can run in parallel. Tests for a story MUST be written and failing before its implementation tasks start. This is the happy path the design describes — corrections through the use case; the only persistence-package addition is a read-side reader. The bypass lives on feat/nightly-reconciliation (PR #1).

1 · Execution strategy

Foundational → US1 → US2 → Polish. This is the tab in use.

US1 alone reconciles; US2 is the re-run safety.

Not applicable.

2 · Phase 2 — Foundational

Add correct to the position use case — Position correct(String instrument, long target, String tradeId): the delta is target − current; a zero delta books nothing; otherwise book(new TradeEvent(tradeId, instrument, delta)), so the correction is idempotent on the supplied tradeId and emits through the adapter like every other mutation (spec FR-002, FR-003; the spec's assumption that the use case accepts an external tradeId becomes true here). src/main/java/xyz/briancorbin/pk/hex/app/PositionService.java
Write the custodian fixture — header instrument,quantity; rows for AAPL (100), MSFT (250) and VOD.L (0). src/test/resources/custodian/eod-2026-09-13.csv
Implement the custodian file parser — record CustodianRecord(String instrument, long quantity) and CustodianFileParser.parse(Path) → List<CustodianRecord> plus CustodianFileParser.sha8(Path), the first eight hex characters of the file's SHA-256 — the run identifier. src/main/java/xyz/briancorbin/pk/hex/recon/

3 · Phase 3a — US1 · A drifted position is corrected, audibly

Tests (write & fail first)

Test the run over a file with drift — in-memory repository over a SyncEventBus with a RiskExposureView and a recorder; book AAPL 90 and MSFT 250 first; run the job over the fixture; assert exactly one PositionChanged (AAPL, 100, recon:<sha8>:AAPL) — none for MSFT (no drift) and none for VOD.L (0 vs absent); assert current("AAPL") is 100 and Risk's exposure is 100; assert the run report lists one correction. src/test/java/xyz/briancorbin/pk/hex/recon/ReconciliationJobTest.java

Implementation

Implement the read-side snapshot reader — CustodianSnapshotReader(PositionRepository) with long quantityOf(String instrument) via find (zero when absent). Read-only, through the existing adapter; in the persistence package as the design's structure declares (002-downstream-consumers/D-007 acknowledged there). src/main/java/xyz/briancorbin/pk/hex/persistence/CustodianSnapshotReader.java
Implement the job — ReconciliationJob(CustodianSnapshotReader, PositionService); Report run(Path file): parse, compute sha8, for each record where the custodian quantity differs from the snapshot call positions.correct(instrument, quantity, "recon:" + sha8 + ":" + instrument); the report lists the corrections made (spec FR-001, NFR-001). src/main/java/xyz/briancorbin/pk/hex/recon/ReconciliationJob.java
Add a reconcile subcommand and exercise it — Main reconcile <file> wires the H2 store and runs the job once, printing the report, and exits; run ./gradlew test --tests '*ReconciliationJobTest' green, then ./gradlew run --args='reconcile src/test/resources/custodian/eod-2026-09-13.csv' and confirm one PositionChanged line per drifted instrument. src/main/java/xyz/briancorbin/pk/Main.java

Closes FR-001, FR-002, NFR-001, SC-001.

4 · Phase 3b — US2 · Re-running the same file corrects nothing twice

Tests (write & fail first)

Test the re-run — run the job twice over the fixture; assert the second run's report is empty and the recorder still holds one event; assert the event's tradeId is recon: + the fixture's sha8 + :AAPL and that wasApplied is true for it. src/test/java/xyz/briancorbin/pk/hex/recon/ReconciliationJobTest.java

Implementation

Exercise the re-run — nothing to implement if T-010's idempotency holds; run ./gradlew run --args='reconcile src/test/resources/custodian/eod-2026-09-13.csv' a second time against the same data/pk store and confirm zero corrections (spec FR-004).

Closes FR-003, FR-004, SC-002.

5 · Phase 4 — Polish

Confirm the build and the retrieval — ./gradlew build green (ADR-0007's SARIF still empty: the job never touches java.sql); spectastic adrs --for src/main/java/xyz/briancorbin/pk/hex/persistence/CustodianSnapshotReader.java returns D-007 — the retrieval demonstration 1 captures. src/main/java/xyz/briancorbin/pk/hex/

6 · Dependencies

Foundational    → US1      (T-010 first; T-011, T-012 in parallel)
US1             → US2      (the job exists)
US1, US2        → Polish

5 · Accept corrections from a downstream service over HTTP (applied change 2026-09-18-http-corrections)

Folded from the applied proposal's §6 (the archive is frozen).

Test the corrections endpoint — start HttpApi on port 0 with CorrectionsHandler over an in-memory repository, bus and recorder; POST /corrections {"instrument":"AAPL","quantity":100,"tradeId":"c-1"} → 200 {"instrument":"AAPL","quantity":100} and exactly one PositionChanged(AAPL, 100, c-1); the same body again → 200 and still one event; {"instrument":"AAPL"} → 400; GET /corrections → 405 src/test/java/xyz/briancorbin/pk/hex/http/CorrectionsEndpointTest.java
Implement the corrections handler — POST only (405 with Allow: POST); parse the three fields from the JSON body (400 naming the missing or malformed field); positions.correct(instrument, quantity, tradeId); 200 with Json.position src/main/java/xyz/briancorbin/pk/hex/http/CorrectionsHandler.java
Register the route and exercise it — Main adds .handle("/corrections", new CorrectionsHandler(positions)); ./gradlew run then curl -si -X POST localhost:8080/corrections -d '{"instrument":"AAPL","quantity":100,"tradeId":"c-1"}' → 200 and a PositionChanged log line; the same request again → 200 and no new line src/main/java/xyz/briancorbin/pk/Main.java

8 · Change log

  1. 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).
  2. Initial task breakdown — 11 tasks, 2 parallel; corrections through a new correct use case keyed by recon:<sha8>:<instrument>.