Specification · 001-position-core

Position keeping core

A hexagonal position keeper: trade events in, positions out, idempotent on tradeId.

Status Accepted Spec ID 001-position-core Owner Position Keeping team Reviewers — Created Principlesv1.0.0 Smallest demoableBooking a trade updates the position; re-booking the same tradeId does not double-count. Read time

The domain core: apply trade events to positions, idempotent on tradeId, behind ports so persistence is swappable. JDK-only domain, in-memory repository for this first milestone. The ports-and-adapters shape here is what lets a later feature (002) make the adapter the single writer.

I Independent
A self-contained feature in the position-keeper timeline.
N Negotiable
Outcomes contract; the mechanism is the design's.
V Valuable
Unlocks SC-001.
E Estimable
Plan derivable.
S Small
4 requirements.
T Testable
Observable via its success criterion.
Exec / PM
§Context, §Success criteria.
Reviewer
§Requirements, §Open questions.
Implementer
§Requirements, §Data model.

1 · Context

A position keeper turns a stream of trade events into current positions. It must be idempotent (a redelivered trade cannot double-count) and keep the domain free of infrastructure so persistence can change later. This is the base every other feature builds on.

2 · User scenarios & testing

A trade updates a position. Booking a buy of 100 sets the position to 100.

Idempotent on tradeId. Re-booking the same tradeId leaves the position at 100.

3 · Requirements

Applying a trade event MUST update the instrument’s position by the trade quantity. Applying a trade event whose tradeId was already applied MUST NOT change the position (idempotent). The domain MUST be reachable only through ports; the repository is an adapter behind a port. The domain MUST depend on the JDK only — no framework, no persistence library imported into the core.
  • Emitting events to consumers — the next feature.
  • Durable persistence — in-memory here; JDBC later.

4 · Data model

Position
an instrument and a signed quantity; the running sum of applied trades.
Trade event
an instrument, a signed quantity, and a unique tradeId (the idempotency key).

5 · Success criteria

Booking a trade updates the position, and re-booking the same tradeId leaves it unchanged.
Observed at

A unit test applying a trade, then the same trade again.

The domain compiles with no import of any framework or persistence library.
Observed at

An ArchUnit rule asserting the core package imports only JDK + domain types.

6 · Assumptions

7 · Open questions

None outstanding.

8 · Conformance index

9 · Change log

  1. Status flipped Draft → Accepted on 18 Sep 2026 — zero remaining unchecked tasks (13/13); ./gradlew clean build green with PositionServiceTest (SC-001) and CoreRulesTest (SC-002) verified passing per author confirmation. Sibling bundle (REQ-LIFECYCLE-005).
  2. Reset to Draft — re-driven through tasks → implement → verify against a real Gradle build; the 2024–25 dates in docs/timeline.md remain the narrated history.
  3. Accepted — the hexagonal core with an in-memory repo; ports-and-adapters and a JDK-only domain.