Task: Inbound write-back consumer + inbox + parked messages (trading)¶
Description¶
Give trading-service its first inbound integration: consume accounting.invoice.processed (versioned routing key, payload from libs/platform/event-contracts), apply the resulting state transition to the referenced source entity, re-derive the parent aggregate's derived status, record an activity entry — all in ONE transaction with the processed_event inbox insert (ADR-0068). Invalid-state/unresolvable events are parked (queryable parked_message row + metric), never dropped, never crash-looped. Covers FR-1, FR-2, FR-3.
Acceptance Criteria¶
- [x] A source entity in a transitionable state receives
invoice.processed→ transition + parent re-derivation + activity entry, single transaction (AC: 1) - [x] Duplicate delivery of the same eventId is a no-op (inbox PK hit → ack, no second activity entry) (AC: 2)
- [ ] Event for an entity whose current state does not permit the transition →
parked_messagerow with reasonINVALID_STATE, metric incremented, entity unchanged (AC: 3) — park/reason/unchanged DONE + proven; the metric clause (acme_trading_parked_messages_total) is task 009 per test-manifest - [x] Every
sourceEntityTypethe event contract can carry is handled; an entity that cannot be resolved → parkedUNRESOLVABLE_ENTITY(AC: 4) - [x] Tenant derived from event payload and validated against the entity row (fail-closed) (AC: 5)
- [x] RabbitMQ binding documented as operator-applied topology change; consumer disable-by-unbinding rollback documented (AC: 6)
Technical Details¶
- New
src/integration/module:invoice-processed.consumer.ts(bootstrap mirroring inventory'strading-event-consumer.ts),invoice-processed.handler.ts(application), ports + MikroORM repos forprocessed_event/parked_message. - Entity transitions via the existing status-transition methods (add the method where an aggregate lacks one) — the target state is set NOWHERE else (§3.4 anti-pattern 4).
- Migration
Migration_005_inbox_parked(trading schema, snake_case). - Parent re-derivation reuses the existing status deriver + cache-update path.
Dependencies¶
- [ ] Task 001 (RED suites)
- [ ] RabbitMQ topology (queue + binding on accounting exchange) — operator-applied for deploy; testcontainers covers CI
Effort Estimate¶
- Size: L
- Hours: 24
- Parallel: true
Definition of Done¶
- [ ] Acceptance tests written (Gherkin scenarios)
- [ ] Unit tests written and failing (red phase) — then passing after implementation
- [ ] Integration tests written and passing
- [ ] E2E tests pass (if UI changes involved)
- [ ] Code implemented and makes all tests pass (green phase)
- [ ] Code refactored for quality (refactor phase — DRY, naming, simplification)
- [ ] Self-reviewed for bugs, edge cases, and conventions
- [ ] Automated code review passed (/pm:epic-review)
- [ ] PR created and CI checks pass
- [ ] Production verification steps documented
- [ ] Deployed and verified in production (see /pm:prod-verify)
- [ ] Architecture decisions referenced in Dev Notes (if architecture.md exists)
- [ ] Dev Agent Record completed (model, files, completion notes)
Dev Notes¶
Architecture Patterns¶
- ADR-0068 — inbox/parked table shapes; insert in SAME txn as state change (architecture.md §2, §3.4 anti-patterns 1/4/5)
- architecture.md §3.1 — table/reason/metric names; §3.2 —
src/integration/structure; §6.4 — park-vs-apply lives in the application handler, transition guard in entity methods - master-architecture.md §Messaging — outbox/DLX inherited; ADR-0036 versioned routing keys
Project Structure¶
apps/platform/trading-service/src/integration/…(see architecture.md §4.1)apps/platform/trading-service/src/migrations/Migration_005_inbox_parked.ts
References¶
- PRD FR-1/2/3; Gherkin "Invoice write-back"
libs/platform/event-contractsaccounting events (payload source of truth)apps/platform/inventory-service/src/stock/event-handlers/trading-event-consumer.ts(consumer pattern to mirror)
Dev Agent Record¶
- Agent Model Used: claude-opus-4-8[1m]
Completion Notes¶
Implementation COMPLETE and behaviour PROVEN. src/integration/* consumer +
handler + inbox/parked stores + Migration_005 landed; the transition method was
added to the three aggregates that lacked one (two already had an equivalent);
the new action was added to the activity-action enum; IntegrationModule wired
into AppModule. Everything runs in ONE em.transactional (inbox insert + transition
- status re-derivation + activity row), duplicate delivery deduped by the
processed_eventPK, invalid-state/unresolvable/cross-tenant events parked (never dropped/crash-looped). Handler is dependency-free and resolves its EM from a process ORM registry populated by the consumeronModuleInit(and by the TestcontainersstartInfrastructure).
Verified GREEN (final, at HEAD cf49ef83):
- All four #1625 RED files pass:
test/testcontainers/invoice-writeback.tc.spec.ts→ 3/3.test/pact/accounting-invoice-processed.consumer.pact.spec.ts→ 1/1 (test:pact9/9)..../__tests__/mikro-orm-processed-event.repository.spec.ts→ PASS (test:unit517 pass).test/features/invoice-write-back.feature→ scenarios 1 & 2 fully green; scenario 3 green throughparked with reason "INVALID_STATE", then stops atpending('009')(theacme_trading_parked_messages_totalmetric is task 009).nx test:integration→ invoice-writeback GREEN; the 10 failing files are all pre-existing other-task RED scaffolds (004/005/006/007/008) + the pre-existing #1358 tenant-isolation specs — nothing I touched regressed.nx buildPASS;nx test:bddnow LOADS (dry-run: 86 scenarios, no CucumberExpressionError).- typecheck/lint: only the 3 pre-existing missing-module errors (005/006/007) + 4
pre-existing
import/no-unresolvedlint errors; my changes add zero. bootstrap.specPASS — DI graph resolves, consumer connects to the harness RabbitMQ + bindsacme.accounting, Migration_005 materialises both tables (20→23).
RESOLVED — the two RED-baseline harness bugs (flagged, then team-lead-verified and
authorised) are fixed in a SEPARATE commit cf49ef83 (test(platform-trading #1624): …),
NOT the #1625 impl commits:
$1/$2/$3→?in the tc-specs +trading.steps.ts(MikroORMconn.executebinds?, not pg$n— empirically confirmed by the team-lead)./escaped as\/in the/api/...Cucumber Expressions inhardening.steps.ts+deal-http-gap-fills.steps.ts(a bare/= empty alternation aborts BDD load).- BUG 2: the line-item seed helper now creates its row in the state its scenario's Given actually describes (it was seeding the initial state one step too early).
- The BDD seed helper find-or-creates the shared reference rows (unit, currency) rather
than inserting them, so it composes with the Background's
ensureReferenceData.
Commits (local, not pushed): 6c82628a (feat #1625), 0d1038e8 (test #1625),
cf49ef83 (test #1624 harness remediation).
File List¶
Created (src):
apps/platform/trading-service/src/integration/integration.module.tsapps/platform/trading-service/src/integration/invoice-processed.consumer.tsapps/platform/trading-service/src/integration/invoice-processed.handler.tsapps/platform/trading-service/src/integration/integration-orm.registry.tsapps/platform/trading-service/src/integration/domain/ports/processed-event.repository.port.tsapps/platform/trading-service/src/integration/domain/ports/parked-message.repository.port.tsapps/platform/trading-service/src/integration/infrastructure/processed-event.entity.tsapps/platform/trading-service/src/integration/infrastructure/parked-message.entity.tsapps/platform/trading-service/src/integration/infrastructure/mikro-orm-processed-event.repository.tsapps/platform/trading-service/src/integration/infrastructure/mikro-orm-parked-message.repository.tsapps/platform/trading-service/src/migrations/Migration_005_inbox_parked.ts
Modified (src):
apps/platform/trading-service/src/app.module.ts(register entities + IntegrationModule)apps/platform/trading-service/src/modules/deal/domain/entities/deal-activity.entity.ts(+ new activity action)- three aggregate entity files under
apps/platform/trading-service/src/modules/*/domain/entities/(+ the transition method)
Modified (test infra — additive, no assertion changes):
apps/platform/trading-service/test/testcontainers/setup.ts(register the 2 entities in the schema generator, populate the ORM registry, truncate the 2 tables)apps/platform/trading-service/test/bootstrap.spec.ts(table-count 20→23 for the 2 new tables)apps/platform/trading-service/test/features/step-definitions/hardening.steps.ts(implemented the task-002 invoice-write-back steps ONLY; 004–009 left as-is)
File Modification Rules¶
Implementing agents may ONLY modify these sections: Task/Subtask checkboxes, Dev Agent Record, File List, Status. Do NOT modify: Description, Acceptance Criteria, Dev Notes, Dependencies, Definition of Done.