Flow
Decisions

0005 — PII split by event type

PII is declared per event type and routed to a separate broker path.

Decided. An event never mixes PII and non-PII fields. A fact involving personal data is two events with the same entity_id: a core event with no PII, and a separate PII event. contains_pii is declared per event type in a table; the publisher routes to core-events or pii-events based on that column.

Alternatives

  • A contains_pii flag consumers check. Enforcement lives in every consumer, so every consumer is a place to get it wrong.
  • Field-level filtering at publish time. The publisher decides what to strip per subscriber. One bug leaks, and the leak is invisible.
  • A pii. routing-key prefix on one shared exchange. Relies on every binding pattern being right forever, with nothing behind it. Binding # picks up PII.

Why

Because no event is partially sensitive, routing becomes a whole-message decision, and a whole-message decision can be enforced by broker permissions. Consumers not cleared for PII get credentials with no read access to pii-events. A misconfigured binding fails with an access error rather than quietly delivering personal data.

Everything above that layer — the declaration, the publisher branch, the CI check — is discipline. The permission grant is enforcement.

Consequences

  • A consumer needing both binds both exchanges and correlates on entity_id.
  • The event feed (GET /v1/events?after=) bypasses broker permissions, so a feed serving PII event types is authorized like any other PII read.
  • The same pii:"true" struct tag drives log and trace redaction.
  • Re-identification risk is not detected: a core event with no PII fields can still identify someone in combination with others. Reviewer responsibility.

On Azure, per-domain fanout topics replace the shared exchanges; see 0016.

On this page