Canonical-store erasure leg — measurement and verdict (2026-08-12)

Question: does the canonical side expose a subject-erasure surface the gateway's compliance runner could call today — or does Cell 33's erasure cascade already cover connector-landed canonical data?

Verdict: NEITHER — branch (c). No code changed in this landing. The canonical_store_erasure leg stays honestly pending_in_build and keeps customers_redact jobs OPEN (rule 01: fail-closed needs somewhere to close onto; wiring a pretend-eraser would be worse than the open job). The minimal canonical-service change is proposed below for that service's own lane.

Everything below is measured (file:line, this tree at 4f5a0d4bc); nothing is live-verified.

1. No erasure surface exists on the canonical service — branch (a) is out

2. Cell 33's cascade does NOT cover the canonical store — branch (b) is out

3. Current gateway posture (correct today, unchanged by this landing)

POST /api/v1/shopify/compliance/run (services/service-marketing-connectors/main.py:1137-1174) invokes process_compliance_job without a canonical_eraser (main.py:1152-1159), so the third customers_redact leg reports pending_in_build and the job stays OPEN and visible (services/service-marketing-connectors/shopify_install.py:949-1011; leg order is LAW per the §6 comment at shopify_install.py:966-969; the pending_in_build branch at :986-990). Test-asserted in both suites: tests/connectors/test_shopify_oauth_gateway.py:1078 and services/service-marketing-connectors/test_shopify_install.py:655. The same injectable pattern already covers the sibling gaps (shop_eraser for tenant-wide erasure, exporter for customers/data_request — both also None today).

4. Proposed minimal canonical-service change (its own lane — NOT built here)

No side door: the gateway must never write BigQuery/Firestore deletes itself. The erasure leg needs the canonical service to grow a governed subject-erasure surface, roughly:

  1. A store deletion/redaction primitive in mizoki_contracts.store (none exists — §1), tenant-scoped, audit-chained (an erasure is itself an auditable action; the append-only audit chain rows reference event_ids, not payloads, so the chain survives).
  2. POST /api/v1/subjects/erase on service-canonical-ingestion (IAM-locked via verify_caller, tenant via resolve_tenant), taking the compliance job's identifiers (subject_key, order_ids, entity ids). Envelope matching is feasible today: gateway-landed records carry customer/order ids in entity_ids (services/service-marketing-connectors/main.py:411-444 sync, :1091-1093 webhook) which persist on the envelope (contracts/mizoki_contracts/envelope.py:58).
  3. All three copies: the events collection, events_outbox, and events_dlq rows carry the full envelope/envelope_json (outbox.py:73-88) — an eraser that misses the outbox leaves the PII in a replayable row. Any future BigQuery landing joins this list.
  4. Redact-vs-delete is a design decision for that lane: envelopes are content-addressed (event_id = stable_hash(source, payload_hash, tenant), envelope.py:112-114) and the platform promises deterministic replay, so in-place redaction breaks hash integrity while deletion breaks replay completeness — the canonical lane must choose (e.g. a bitemporal redaction event + payload tombstone) rather than have the gateway improvise it.
  5. Per-leg receipts, fail-closed: 2xx only when every matched row is confirmed gone/redacted; anything unconfirmable is a non-2xx so the gateway job stays open (the Cell 33/35 receipt pattern).

Once that surface exists, the gateway side is the small, already-designed branch (a): an OIDC canonical_eraser client following the existing outbound pattern (outbound_headers, cf. _cell33_cascade main.py:1114-1130), injected in the job-runner wiring behind CANONICAL_ERASER_URL (unset ⇒ leg stays pending_in_build, never fake-confirms; only a 2xx confirming receipt marks the leg confirmed), with both-direction tests.

Coordinator decision requested: register the §4 build on the canonical service's lane; the gateway leg stays pending_in_build until then.

← All docsView source on GitHub →