Register item 16 — Step-0 verification + build record (2026-08-20)

Read-only verification of "gemini-kg-pipeline FirestoreKGSink deviation — migrate it onto the governed path" before building it, and the record of what was then built. Claims are measured in-repo at file:line unless labeled otherwise. Companion to docs/reports/ITEM15_STEP0_VERIFICATION_2026-08-20.md (which built the canonical→KG projector this item's register line pointed at).

Headline

The deviation is real but narrower than the register implied, carries measured zero traffic, and its stated landing surface was wrong: the item-26 projector is entity-only by construction (no person nodes), so journey/person projection can never move into it. The correct migration is the Shopify single-ingress shape — the canonical door comes FIRST, and the pipeline's own PII-governed projection stays local as the post-ingestion hook.

Measured surface of the deviation

Two FirestoreKGSink construction sites only: pipeline.py:434 (build_default_pipeline, reached by /api/v1/journey-events/{ingest,sync,meta/sync}, /webhooks/gemini, batch_worker.py, the meta-worker Job) and virtuoso_ingest.py:153 (/api/v1/journey-events/virtuoso/{source}, the only person-adjacent path — virtuoso_kg.py:96-101 emits PERFORMED_BY edges keyed on actor.user_id). The nightly/weekly mycocoons jobs are NOT this deviation — they write the disjoint nodes/edges pair via FirebaseKGClient (item 15/8a's lane).

Zero live traffic, positively evidenced: no scheduler targets any journey-events route (setup_ingest_schedulers.sh:74,93,112), the Boss registry exposes none of them as tools, and the platform record measures JourneyEvent nodes at 0 with the projection wiring deployed (shared-memory record …-starved-aea9fe2f; connectors memory rule-6c row).

Preconditions the register line did not name

  1. IAM: the service pins NO runtime SA (default compute SA) and canonical ingestion's allowlist is exactly mizoki-platform@… — every forward would 403 until the operator either pins this service's SA (after auditing what the default SA currently grants it) or extends the allowlist deliberately. Widening ALLOWED_CALLER_SA to the default compute SA would be a least-privilege regression (≈78 services run as it) — do not.
  2. No tenant_id concept anywhere in the service; canonical ingestion requires one (TENANT-001).
  3. mizoki_contracts is not in the image (Dockerfile copies requirements + src/ only) — the forwarder therefore mirrors the D17-fixed OIDC mint (origin audience + format=full) in stdlib, pinned by test.
  4. No CI test gate ran the in-service suites before this change.
  5. Erasure trap (sharpest risk): canonical-store subject erasure matches ONLY on entity_ids intersection (mizoki_contracts/erasure.py). Forwarding virtuoso payloads verbatim (actor.email/ip/ua — persisted today in virtuoso_journey_events) with empty entity_ids would create UN-ERASABLE subject data in the canonical store — strictly worse than the deviation being fixed.

What was built (this change set)

Honest end condition

This is zero-traffic surgery: the governed order is pinned by tests and flag-ready, not live-proven — no real journey events flow to prove the path end-to-end, and none can until the operator activates it. Remaining operator work, in order: (1) resolve the runtime-SA question (pin vs. allowlist — an owner/operator decision with a grant audit), (2) grant run.invoker on service-canonical-ingestion to that SA and add it to ALLOWED_CALLER_SA in deploy-service-canonical-ingestion.yml (reviewed commit), (3) register the mycocoons tenant mapping for that caller, (4) flip JOURNEY_EVENTS_CANONICAL_FORWARD=1 in cloudbuild.yaml (reviewed commit). Until then the deviation remains OPEN-but-instrumented: the direct write still serves (unchanged, zero traffic), and the governed path is one flag away.

← All docsView source on GitHub →