Specification · 002-downstream-consumers
Downstream consumers
Risk and P&L must react to every position change — which forces the question this feature exists to answer: what guarantees a position never changes silently?
Risk and P&L consume a PositionChanged event whenever a position moves. The design question — "what
guarantees every position change emits one?" — is answered by an architectural decision, ADR-0007,
born in this feature's design: all position mutation flows through the persistence adapter, which is the single place the
event is emitted, and nothing else may touch the data store. That decision is what every later feature is measured against.
- I Independent
- Builds on 001; no other feature.
- N Negotiable
- Outcomes contract; the event bus is the design's to choose.
- V Valuable
- Unlocks SC-001 — Risk and P&L stay consistent with positions.
- E Estimable
- Plan derivable; the core and its ports already exist.
- S Small
- One event, two consumers, one governing decision. Six requirements.
- T Testable
- Every requirement observable via an emitted event or a consumer's reaction.
- Exec / PM
- §Context, §Success criteria.
- Reviewer
- §Requirements, §Open questions.
- Implementer
- §Requirements, §Data model.
1 · Context
The position core (001) records positions and is idempotent on tradeId. Risk and P&L now need to react
whenever a position moves. Letting each consumer poll the store was rejected — a missed poll is a silent inconsistency in a
regulated system. So the requirement became: every position change emits a PositionChanged event, and the
design has to guarantee that emission can never be bypassed. That guarantee is the architectural decision this feature is really
about.
2 · User scenarios & testing
A booked trade reaches Risk. A trade booked through the use case emits PositionChanged; the Risk
subscriber receives it with the new quantity.
Nothing changes a position silently. There is no path to mutate a position that does not go through the adapter that emits the event — the property ADR-0007 exists to hold.
3 · Requirements
PositionChanged event carrying the instrument, the new quantity, and the originating tradeId.PositionChanged event and update its exposure view.PositionChanged event and revalue.tradeId, so a redelivered event does not double-count.- Durable/JDBC persistence — in-memory here; JDBC is the next feature.
- Event replay / a durable log — not needed for the in-memory milestone.
4 · Data model
- PositionChanged
- An event: instrument, new quantity, originating tradeId — the fact that a position moved.
- Emission invariant
- Exactly one code path (the persistence adapter) writes a position, and it always emits.
5 · Success criteria
Observed at
An integration test booking a trade and asserting the Risk subscriber's view, plus an ArchUnit rule proving the adapter is the only writer.
PositionChanged event leaves P&L's revaluation unchanged (idempotent on tradeId).
Observed at
A test delivering the same event twice and asserting one revaluation.
6 · Assumptions
- The core's ports (001) isolate the domain from persistence; the adapter is the natural single emitter.
7 · Open questions
None outstanding — the emission-guarantee question was answered by ADR-0007 in the design.
8 · Conformance index
9 · Change log
- Status flipped Draft → Accepted on 18 Sep 2026 — zero remaining unchecked tasks (14/14); ./gradlew build green with DownstreamConsumersTest (SC-001), PnlRevaluerTest (SC-002), DataAccessRulesTest and GrantBackstopTest verified passing per author confirmation. Sibling bundle (REQ-LIFECYCLE-005).
- 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.
- Accepted — Risk and P&L consume PositionChanged; the emission guarantee is recorded as ADR-0007 (D-007) in the design, with its ArchUnit fitness function authored at the same time.