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?

Status Accepted Spec ID 002-downstream-consumers Owner Position Keeping team Reviewers Risk, P&L Created Principlesv1.0.0 Smallest demoableA trade booked through the use case emits a PositionChanged event that a Risk subscriber receives. Read time

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

Every position mutation MUST emit a PositionChanged event carrying the instrument, the new quantity, and the originating tradeId. The Risk consumer MUST receive every PositionChanged event and update its exposure view. The P&L consumer MUST receive every PositionChanged event and revalue. Position mutation MUST flow through a single persistence adapter that is the sole emitter of the event; no other code may write the position store. This is the property ADR-0007 governs. Consumers SHOULD be idempotent on tradeId, so a redelivered event does not double-count. The event MUST be emitted synchronously within the mutation's transaction boundary, so a committed position and its event never diverge.
  • 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

A trade booked through the use case results in the Risk consumer observing the new quantity, and there exists no code path that mutates a position without emitting the event.
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.

A redelivered 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

7 · Open questions

None outstanding — the emission-guarantee question was answered by ADR-0007 in the design.

8 · Conformance index

9 · Change log

  1. 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).
  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 — 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.