docs: the events are emitted by the API, and what that implies

Recording where the work lands and the two facts about that codebase that
shape it: every write passes through one BaseModelViewSet, so one hook covers
every domain; and the models are managed = False, so a transactional outbox
introduces the estate's first Django-managed table beside the pre-existing
schema.

Emission belongs in the API rather than a database CDC or a gateway plugin,
because only the API knows which change is a business event and only it holds
the sub/azp that the event must carry for a downstream agent to act under the
same authority.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-29 13:49:18 +02:00
parent 18a8bf38a7
commit 95b6e3a1c9
+31 -8
View File
@@ -96,15 +96,38 @@ policy must not need to know the trigger mode.
## Backlog ## Backlog
**Events over Kafka.** The target for the reactive half. The estate emits no **Events over Kafka, emitted by `classic-models-api`.** The target for the
domain event today — `classic-models-api` has no signals, no outbound webhook, reactive half. The estate emits no domain event today — the API has no signals
and there is no bus in the cluster — so a workflow can only poll, which is what and no outbound webhook, and there is no bus in the cluster — which is what made
made a nightly sweep look like the only option. Until Kafka is in place, a nightly sweep look like the only option.
detection stays unbuilt rather than being approximated by a periodic scan: a
batch that pretends to be reactive is worse than an absent feature, because it
looks finished.
The periodic ordering run does not wait on this. The emission belongs **in the API**, not in a database CDC and not in a gateway
plugin. Only the API knows which change is a business event rather than a row
update, and only it holds the identity that made it — `sub` and `azp` from the
caller's token, which the event has to carry if a downstream agent is to act
under the same authority as the person who triggered it.
Two facts about that codebase shape the work:
- Every write goes through one `BaseModelViewSet` in
`api/v1/classicmodels/views.py`. One hook there covers products, orders,
payments and the rest — Django signals scattered across models would be the
harder version of the same thing.
- **The models are `managed = False`.** The tables are the pre-existing
classicmodels schema and Django owns no migrations for them. A transactional
outbox means introducing the estate's first managed table beside unmanaged
ones — which works, and is worth deciding deliberately rather than
discovering.
An outbox is the part that matters: emitting from a viewset after `save()` drops
the event if the process dies between the two, and an agent that never hears
about a stock movement is worse than one that hears late.
Until this exists, detection stays unbuilt rather than being approximated by a
periodic scan: a batch that pretends to be reactive is worse than an absent
feature, because it looks finished.
The periodic ordering run does not wait on any of it.
## Two controls that become mandatory here ## Two controls that become mandatory here