Tasks · 007-reconciliation
Nightly reconciliation
Ordered, parallelizable work. Tests precede implementation. Drive through with
/spectastic.implement — one task per invocation.
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
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.javainstrument,quantity; rows for AAPL (100), MSFT (250) and VOD.L (0). src/test/resources/custodian/eod-2026-09-13.csvrecord 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)
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.javaImplementation
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.javaReconciliationJob(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.javareconcile 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.java4 · Phase 3b — US2 · Re-running the same file corrects nothing twice
Tests (write & fail first)
recon: + the fixture's sha8 + :AAPL and that wasApplied is true for it. src/test/java/xyz/briancorbin/pk/hex/recon/ReconciliationJobTest.javaImplementation
./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).5 · Phase 4 — Polish
./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).
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.javaAllow: 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.javaMain 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.java8 · 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).
- Initial task breakdown — 11 tasks, 2 parallel; corrections through a new
correctuse case keyed byrecon:<sha8>:<instrument>.