ops.domain_event_outbox) carries facts other
lanes react to: document_card.updated, matter_assertions.updated, and five
more. But THE READ — the document insight brief — landed silently. No
insight.completed fact existed. So the Insights page had to poll "Read pending"; the Brief
rail and the Context Engine had no signal to rebuild on; and the planned live per-lane fan-out page
(Epic-2423) had nothing to listen to. A pipeline that produces a result nobody can hear about is half a
pipeline. This Story gives THE READ a voice — and nothing more: it announces, it does not
add consumers.
The card lane already does exactly this. When Blake's card is verified and upserted, the card ingest
boundary emits document_card.updated in the same transaction as the row write
(16 live receipts prove it). This Story copies that pattern one seam over, onto the brief write. There is
no new mechanism to invent — the transactional outbox
(src.shared.domain_events.insert_domain_event) is the only door to the bus (R7), and it was
built for precisely this.
insight.completed is registered in the audit-registry vocabulary
(src/shared/audit/registry.py): the verb completed and the subject kind
insight. Adding a verb or subject there is a data edit to two dicts — the
detail_type columns are bounded VARCHAR, validated in code, never a Postgres
enum (the migration 0144 docstring makes this explicit). The domain-event grammar is single-dot
<subject_kind>.<verb>, so insight.completed means subject kind
insight, verb completed — and insert_domain_event
derives the subject kind from the token, so a subject/detail-type mismatch is impossible by
construction.
document_card (not document) with subject_id = document_id, the
insight lane's subject kind is insight — the L2 brief facet of a document — with
subject_id = document_id. Same document, different facet. The idempotency key composes to
insight.completed:<firm>:<document>, so a re-read coalesces onto one pending
event per document, matching the card and matter-assertions lanes (the outbox's own idempotency
semantics — no bespoke dedup).
In src/shared/context/insight_brief.py, the write phase now calls one small helper,
_store_and_announce_brief, inside its open write transaction:
await _upsert_brief_row(session, firm_id=firm_id, brief=brief)
await insert_domain_event(
session,
detail_type=INSIGHT_COMPLETED_DETAIL_TYPE, # "insight.completed"
firm_id=firm_id,
subject_id=document_id, # the insight facet of this document
actor_kind="system", # background enrichment, no acting user
actor_id=None,
)
The store precedes the emit deliberately: a store failure aborts the whole transaction
before any event row exists. traceparent is carried automatically — the helper sources
it from the ambient OTel context and callers cannot supply it (EB-20), so trace continuity across the
handoff is free. actor_kind="system" because this is a background write with no acting user;
the producing agent run rides the brief row, not the event actor.
AGENT_INVOKE_BUILD span, and insert_domain_event opens its own approved
DOMAIN_EVENT_OUTBOX_ENQUEUE span. Nothing hand-rolled is added.AGENTS.md: no green counts until it has been red. The DB-backed atomicity test was observed failing on a planted negative (the emit disabled) before the suite was allowed to pass.
| Test | What it proves |
|---|---|
test_read_store_failure_emits_no_event( tests/unit/context/test_insight_brief.py, pure, no DB) |
The AC directly: the store precedes the emit on the same session, so a failed brief store aborts
before any domain_event_outbox INSERT is reached — READ store fails → no event row. |
test_insight_announce_atomicity( tests/integration/test_insight_brief_store.py, real Postgres) |
Positive: one brief → one insight.completed row with subject_kind='insight',
subject_id=document_id, actor_kind='system', traceparent carried,
dispatched_at NULL (the relay ships it post-commit). Negative: a txn that aborts after
the store leaves NEITHER a brief NOR an event.Red-first: with the emit disabled, the positive gate ( _count_events == 1)
was observed failing, then reverted. |
insight.completed fact, emitted in the same transaction that stores the brief, so it can
never lie about a landing that did not commit. The consumers that listen are the next tickets; this one
just makes sure there is something to hear.