diff --git a/CLAUDE.md b/CLAUDE.md index 8cc0c4f..e5ecb90 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -96,15 +96,38 @@ policy must not need to know the trigger mode. ## Backlog -**Events over Kafka.** The target for the reactive half. The estate emits no -domain event today — `classic-models-api` has no signals, no outbound webhook, -and there is no bus in the cluster — so a workflow can only poll, which is what -made a nightly sweep look like the only option. Until Kafka is in place, -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. +**Events over Kafka, emitted by `classic-models-api`.** The target for the +reactive half. The estate emits no domain event today — the API has no signals +and no outbound webhook, and there is no bus in the cluster — which is what made +a nightly sweep look like the only option. -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