ORACLE / LII Governance — the enforced gates, with citations
Scope: Cells 33–36 (src/cells/cell33–cell36) and the shared package
src/shared/mizoki_intent/.
claim_label for the whole platform: built, pre-benchmark.
Authority: CONSTITUTION.md (supreme), then TRUTH.md / AGENTS.md /
OPERATING_SYSTEM.md / GOVERNANCE.md, then the plan of record
docs/INTENT_API_PLATFORM_BLUEPRINT.md §8.
Every claim below names the file and line that enforces it. Where a gate is documented but not implemented in code, this document says so explicitly.
1. Absolute prohibition on audio-derived signals
Rule (blueprint §8.1): no microphone-, voice-, or speech-derived features in the taxonomy, ingestion, or models. Ever.
Enforced twice — and one of the two is a boot refusal.
Where the enforcement actually lives — and where it does NOT. The refusal is
hand-rolled, not schematic. It is check_audio_prohibition /
validate_signal_payload in src/shared/mizoki_intent/signal.py, plus the taxonomy
loader's boot-time refusal, plus the ingest orchestrator's call ordering. Pydantic does
not enforce it. CanonicalEventEnvelope.payload is declared
payload: Dict[str, Any] = Field(default_factory=dict)
(contracts/mizoki_contracts/envelope.py:78) — an unconstrained dict with no validator on
it. The envelope's only @field_validator / @model_validator cover timestamp
tz-awareness and bitemporal ordering (envelope.py:80-94); none of them look at
signal_type. A future code path that constructs a CanonicalEventEnvelope directly,
rather than going through build_intent_signal (signal.py:222-280) or the Cell 33
ingest route, would bypass the audio gate entirely. Today no such path exists in Cells
33–36; the constraint is that none may be added. Describing this rule as "rejected at
Pydantic validation" is wrong and should not be repeated.
How much defense in depth there actually is — stated neither high nor low. On the write path there are two independent layers, not more:
- the explicit
check_audio_prohibitioncalls (§(a)); and - the taxonomy signal-type registry, made audio-free by the loader's boot refusal (§(b)).
These are genuinely independent, and that is worth recording precisely: with the
explicit audio check disabled, an audio signal_type is still refused — it fails
taxonomy.is_valid_signal_type and comes back as invalid_signal_type
(signal.py:170-174), because no audio-family entry can be in the registry of a taxonomy
that loaded at all. The second layer is real, not decorative.
What is not a third write-path layer: the three check_audio_prohibition call sites
listed in §(a) are the same layer invoked on three construction paths. §(c) below is a
genuine additional check but sits on the read path — it scrubs what is returned, and
cannot stop anything being written.
(a) At request validation
check_audio_prohibition — src/shared/mizoki_intent/signal.py:83-100. Raises
SignalValidationError("audio_signal_prohibited", …).
It runs first, before anything else, at the Cell 33 ingest boundary:
src/cells/cell33/ingest_cell/orchestrator.py:218-219 —
# 1. AUDIO GATE — absolute prohibition, before anything else. It therefore fires even
for a signal that would have been dropped for lack of consent (proven by
test_audio_rejected_even_without_consent, src/cells/cell33/tests/test_ingest_api.py:205).
It is called again inside the payload validator (signal.py:169) and once more in
build_intent_signal (signal.py:261) — defense in depth on the three construction
paths.
Coverage is the whole term family, not a prefix. The blueprint's stated minimum is
^audio; the implementation keeps that (AUDIO_SIGNAL_PATTERN,
src/shared/mizoki_intent/taxonomy.py:41) and adds AUDIO_DENY_TERMS
(taxonomy.py:42-45): audio, voice, speech, spoken, mic, microphone, utterance, asr,
stt, wake word, sound recording, call recording. Matching is case-insensitive,
word-boundary, after normalizing _ - / , . to spaces (taxonomy.py:83-89) — so
voice_note_upload, ambient_audio_capture and speech_to_text all refuse. Terms of
≥ 4 characters are suffix-tolerant; shorter terms match exactly so mic cannot match
michigan (taxonomy.py:94, 97-120).
(b) At taxonomy load — the service refuses to BOOT
load_taxonomy lints every entry of the signal_types registry and raises
TaxonomyError("audio_signal_type_prohibited", …) on any audio-family hit —
src/shared/mizoki_intent/taxonomy.py:299-307.
Serving cells construct the taxonomy at boot, so a taxonomy containing an audio
signal type means the service does not start. This is deliberate and fail-closed
(taxonomy.py:6-8, taxonomy.py:275-278).
(c) On the way out, too
Subject access re-checks: scrub_deny_listed calls audio_match on every stored
record before returning it (src/shared/mizoki_intent/subject.py:267-270) — "a corrupt
row naming an audio-derived signal type must not leave through subject access any more
than it could enter."
Negative tests: test_audio_signal_rejected and
test_audio_signal_rejected_case_insensitive
(src/cells/cell33/tests/test_ingest_api.py:193, 199), test_voice_family_signal_rejected
(:357), test_audio_signal_type_refuses_to_load and
test_audio_family_signal_type_refuses_to_load
(src/cells/cell33/tests/test_taxonomy.py:136, 148).
2. Consent fail-closed
Rule: CONSTITUTION.md Art. II.6 — "Consent checks precede persistence for all
behavioral micro-signals… Without intent_modeling consent, the signal is never
persisted." Blueprint §8.2 adds: region defaults deny for EU/EEA/UK; denials are
counted and payload-free.
Enforced: evaluate_consent — src/shared/mizoki_intent/signal.py:103-129.
consentmust be a mapping withintent_processingexactlyTrue(signal.py:117-121). Anything else — missing,False, truthy-but-not-True— denies.- The strict branch is the default; the lenient branch is the exception
(
signal.py:123-128). Lenient applies only to a well-formed ISO-3166 alpha-2 code (^[A-Z]{2}$,signal.py:48) that is verifiably not inEU_EEA_UK_REGIONS(EU-27 + IS/LI/NO + GB/UK + a literalEU,signal.py:57-64). - Everything else — EU codes, locale tags like
de-DE, ISO-3 likeDEU, country names, garbage, missing — takes the strict branch requiring an explicit basis (consent/explicit/contract;legitimate_interestis not sufficient there,signal.py:52-53). A non-canonical region string can therefore never strip EU protection.
Ordering at ingest (src/cells/cell33/ingest_cell/orchestrator.py:218-234): audio
gate → consent gate → envelope → validate → persist. A consent denial is a processed
outcome, not a 422: it is recorded in the denial counter and returns
{"status": "denied", "reason", "region", "claim_label"} — the signal is never
persisted.
Denials are payload-free. The counter records only {region, reason, source_system}
(orchestrator.py:222-228), asserted by test_denial_record_values_sanitized
(src/cells/cell33/tests/test_ingest_api.py:396).
The payload contract is closed. ALLOWED_PAYLOAD_KEYS / ALLOWED_CONSENT_KEYS
(signal.py:42-46) reject unknown keys outright (signal.py:148-164), so nothing
beyond the consent-gated §4.1 projection can ride into persistence, the outbox, or
Pub/Sub — test_unexpected_payload_keys_rejected_and_never_published
(test_ingest_api.py:326) and test_published_notification_is_projection_only (:343).
Negative tests: test_no_consent_dropped_and_counted (:117),
test_consent_false_dropped (:136), test_eu_region_without_explicit_basis_dropped
(:143), test_eu_region_with_explicit_consent_accepted (:163),
test_unknown_region_treated_strictly (:172),
test_noncanonical_eu_region_strings_stay_strict (:367),
test_lenient_branch_only_for_wellformed_non_eu_codes (:384).
3. Sensitive-topic deny-list
Rule (blueprint §8.3): no intent categories over health/medical conditions, sexual orientation or life, religion, political affiliation or opinions, ethnicity, immigration status, financial distress/hardship, union membership, or anything concerning minors.
Built-in terms: BASE_DENY_TERMS — src/shared/mizoki_intent/taxonomy.py:49-71.
The nine categories above are all represented in code.
YAML may EXTEND, never shrink. load_taxonomy composes the effective list as
(*BASE_DENY_TERMS, *yaml deny_list) — taxonomy.py:286-290. The in-code list is
always enforced; nothing in the YAML can remove a term. config/intent_taxonomy.yaml
states the same rule in its header.
A topic must declare sensitive: false EXPLICITLY or the taxonomy refuses to load:
- key missing →
TaxonomyError("sensitive_flag_missing", …)—taxonomy.py:338-342. - any value other than
False→TaxonomyError("sensitive_topic", …)—taxonomy.py:343-347.
Because serving cells load the taxonomy at boot, either case means the service refuses to boot.
The deny-list is applied to more than the topic id. At load: topic_id, label,
and description are all scanned (taxonomy.py:348-354), as is every signal_types
entry (taxonomy.py:308-313). At write: Taxonomy.deny_match is checked on topic_id
before the "is this topic known?" check, so a deny-listed topic is refused as
deny-listed even though it could never be in a loadable taxonomy
(src/shared/mizoki_intent/signal.py:180-186). At read: scrub_deny_listed
(subject.py:244-282) drops any stored record whose topic_id, topic, signal_type,
outcome_type or label matches, and reports a count only, never the matched term
(subject.py:239-241, subject.py:556).
Deny lists over-block by design: ≥ 4-character terms are suffix-tolerant, so
"teens", "childrens", "healthcare", "unionized" are caught by their stems
(taxonomy.py:97-120). Normalization closes the health_insurance hole that a bare
\bhealth\b would miss (taxonomy.py:16-19, 83-89).
Negative tests: test_sensitive_true_refuses_to_load (test_taxonomy.py:73),
test_sensitive_missing_refuses_to_load (:82), test_deny_listed_topic_refuses_to_load
(:95), test_deny_matches_through_underscores (:108),
test_deny_listed_label_refuses_to_load (:123),
test_deny_listed_signal_type_refuses_to_load (:165),
test_plural_and_compound_deny_terms_refuse_to_load (:178),
test_deny_listed_topic_rejected (test_ingest_api.py:215),
test_access_can_never_return_a_deny_listed_topic
(src/cells/cell34/tests/test_subject_rights.py:235).
4. Probabilistic identities are excluded from causal math
Rule (blueprint §8.4): Cell 36 admits deterministic identities only into treatment/control and outcome joins. There is no operator override (blueprint Appendix A.6).
Enforced at three points in Cell 36:
- Holdout assignment —
src/cells/cell36/causal_cell/holdouts.py:255-268. An identity unit whoseidentity_kind != "deterministic"raisesHoldoutError("non_deterministic_identity", …)and incrementsintent_holdout_rejections_total. Refused, not silently skipped. - The causal join —
src/cells/cell36/causal_cell/orchestrator.py:227-238. Score rows with a non-deterministicidentity_kindare excluded and counted intoexcluded["probabilistic_identity"], which is reported in the incrementality report. - Outcome ingestion —
src/cells/cell36/causal_cell/storage.py:147-154. Only deterministic rows enter the estimator path; the rest incrementintent_causal_outcomes_excluded_total{reason="probabilistic_identity"}.
Cohort activation refuses too: a probabilistic identity in the cohort is rejected
(src/cells/cell36/causal_cell/activation.py:14, 75).
Probabilistic identities may still be scored. The gate governs what enters the
incrementality denominator, not what may be scored or what a data subject may see
about themselves — stated explicitly at src/cells/cell36/causal_cell/storage.py:184-190
and proven by test_probabilistic_identity_scores_allowed
(src/cells/cell33/tests/test_ingest_api.py:87).
Negative tests: test_probabilistic_identity_refused
(src/cells/cell36/tests/test_holdouts.py:89),
test_probabilistic_scores_excluded_from_report and
test_probabilistic_outcome_never_joins
(src/cells/cell36/tests/test_causal_api.py:159, 179),
test_probabilistic_identity_still_refused_after_erasure
(src/cells/cell36/tests/test_subject_rights.py:288).
5. GDPR/CCPA subject access and erasure (CONSTITUTION Art. II.6)
Rule: CONSTITUTION.md:50 — "GDPR/CCPA subject access and erasure are honored
immediately."
Status: implemented; BigQuery path live-verified 2026-08-10. As originally written
this section said the streaming-buffer path was fault-injected in tests
(src/cells/cell34/tests/test_subject_rights.py:83, 329, 469) and had never been
observed against real BigQuery. That was true when written and is no longer: a full
DSAR round-trip ran against production on 2026-08-10, observing a 409
pending_streaming_buffer and then a clean 200 with remaining_total: 0 after the
buffer flushed (evidence: .claude/memory/inbox/2026-08.md; 39 rows in
intent_erasure_audit). Claims in this section are otherwise still not live-verified.
Neo4j is a different case: retired, not merely unverified. The owner decided on
2026-08-09 that Neo4j is not being re-provisioned — the platform stays on Firestore
as its knowledge-graph backend, and Cell 35 runs its in-memory graph permanently.
No deployment sets NEO4J_URI, so _Neo4jBackend is never constructed and the live
Cypher is unreachable dead code, not an untested risk awaiting a first run. See
docs/lii/RUNBOOK.md §1 "Cell 35 graph backend".
No test executes either live backend path. Neither neo4j nor google-cloud-bigquery is installed in the environment these
tests run in (both imports raise ModuleNotFoundError; verified 2026-08-09), and every
live path is behind a lazy, function-local import taken only when a real client is
present. Nothing under src/cells/cell33–cell36/tests/ references
bq_select_subject_rows, bq_count_subject_rows, bq_delete_subject_rows,
_count_subject_live, _causal_join_live or _Neo4jBackend (grep, zero hits).
Concretely, these statements have never run, never been parsed at runtime, and never
been type-checked against a live driver:
| Live path | Location | Executed by a test? |
|---|---|---|
BigQuery SELECT / COUNT / DELETE for a subject |
subject.py:469-537 |
No |
| Cell 36 holdout live subject count | src/cells/cell36/causal_cell/holdouts.py:167-188 |
No |
| Cell 36 live causal join | src/cells/cell36/causal_cell/storage.py:160-179 |
No |
Neo4j DETACH DELETE of the subject subgraph — unreachable, see above |
src/cells/cell35/graph_cell/graph_store.py:500-507 |
No |
Everything asserted about erasure below is verified against in-memory doubles only; the table records test coverage, not live status. Current status: BigQuery implemented, live-verified 2026-08-10; Neo4j retired — the branch cannot execute.
No vector index exists in this stack. External descriptions of the platform have
said erasure "cascades BQ + Neo4j + vector index" (the skillpack line that carried this
has since been corrected — skills/miz-oki-platform-expert/SKILL.md:1457-1458 now
states the real legs). In Cells 33–36 and src/shared/mizoki_intent/ there is no
vector index, vector store, or embedding of any kind — a case-insensitive grep for
vector index|vector store|vector search|vector db|embedding|faiss|pinecone|weaviate|
qdrant|milvus|chroma|pgvector across those paths returns zero hits (verified
2026-08-09). That limb of the cascade requirement is therefore not-applicable, NOT
satisfied: the cascade covers the two stores that exist. Adding a vector index later
REQUIRES extending the cascade — a new store needs its own erase_with_receipt call and
its own receipt line, or erasure silently stops being complete.
The contract
Identical in all four cells (src/shared/mizoki_intent/subject.py:9-13):
GET /v1/intent/subject/{identity_id} -> everything THIS cell stores
DELETE /v1/intent/subject/{identity_id} -> erase from THIS cell's stores
POST /v1/intent/subject/{identity_id}:erase -> CASCADE (Cell 34 only)
Access is deliberately not a fan-out — each store answers for itself so no cell can
claim to speak for another's contents
(src/cells/cell34/scoring_cell/orchestrator.py:581-586).
Erasure honesty is structural: count → delete → RE-COUNT
erase_with_receipt — src/shared/mizoki_intent/subject.py:133-201. The order is not
negotiable and there is no path around it:
- count — a count that fails is
failed(subject.py:157-161). - delete — UNCONDITIONALLY. There is no
before == 0short-circuit: a zero pre-count does not skip the DML (subject.py:157-164). The code gives the reason (subject.py:148-155) — the count predicate and the store's real contents can diverge (a coercedtenant_id, a NULL key, a legacy row theWHEREclause misses), and short-circuiting would then reporterasedwhile the subject's row is still in the table. A BigQuery streaming-buffer refusal becomespending_streaming_bufferwith the surviving row count (subject.py:165-175); any other exception isfailed(subject.py:176-180). - RE-COUNT — UNCONDITIONALLY, including after a zero-row pre-count
(
subject.py:182-192): "the recount is what makes 'erased' a measurement rather than an assumption." If the recount itself fails, the receipt isfailedwith detail "delete issued but could not be verified". An erasure that cannot be verified is never claimed (subject.py:145-146). remaining > 0→pending_streaming_buffer, nevererased(subject.py:194-200).remaining == 0→erased(subject.py:201). Everyerasedreceipt — including the zero-count idempotent one — is therefore backed by a post-delete observation that returned 0. Erasing an unknown or already-erased identity is still a zero-count success and never a 404, because a 404 would leak whether an identity exists to anyone who can guess an id (subject.py:29-32); the difference is that the success is now measured rather than assumed. Cost: one extra idempotent DML per store per DSAR.
This is the guarantee subject.py:25-27 states, and it now holds without exception. An
earlier revision of this document and of docs/lii/ADR-LII-001.md described a
before == 0 branch that returned erased without issuing the delete and without
recounting; that branch has been removed from the code and the description corrected.
Three statuses, never collapsed (subject.py:53-58); the overall status is the worst
across stores (subject.py:204-221); only a fully verified erasure is 2xx —
200 / 409 / 502 (subject.py:64-71). A partial cascade is deliberately non-2xx so no
caller can mistake an incomplete erasure for a completed one.
No receipts is a FAILURE, not a success. overall_status([]) returns failed
(subject.py:204-221) — the fold's identity element would otherwise be erased, so an
empty receipt list would have rendered as 200 erased: an erasure asserted across
zero stores checked. erasure_response adds the detail "no store receipts — nothing was
verified, so this erasure is unproven; no store was counted, deleted, or recounted"
(subject.py:590-597). No current route can produce an empty list anyway — all four cells
build fixed, non-empty receipt lists, and the cascade's _erase_peer returns a failed
receipt on every non-answer rather than an empty list
(src/cells/cell34/scoring_cell/orchestrator.py:701-760) — so this closed a latent
contract defect, not a live one.
Erasure is a DELETE, not an UPDATE: bitemporal event columns are append-only facts
and are never rewritten in place (AGENTS.md 6.2), so erasure removes the rows
themselves (subject.py:511-522). SQL is parameterized and the one interpolated
identifier — the subject key column — is constrained to a frozen allowlist
({identity_id, unit_id}, subject.py:449-457), because BigQuery cannot parameterize
identifiers.
The audit cannot re-identify the subject
ErasureAuditLog (subject.py:332-442) writes a salted sha256 subject_ref, never
the identity — "an erasure audit that stored the erased identifier would defeat the
erasure it records" (subject.py:34-36, subject.py:312-323). The row schema is a
hard allowlist re-applied in code (subject.py:342-345, 396) so nothing beyond it can
ever be written. The BigQuery DDL says the same at
src/cells/cell33/schema/intent_bigquery_ddl.sql:192-199.
Auditing never blocks the request path (subject.py:397-408): an insert failure is
logged and swallowed; the receipt returned to the caller is the primary record.
Trust boundary on peer receipts
The cascade coordinator coerces any peer status outside the three known outcomes to
failed — an unrecognized status is never counted as a completed erasure
(src/cells/cell34/scoring_cell/orchestrator.py:664-687). An unconfigured or
unreachable peer produces a failed receipt, never a skipped store
(orchestrator.py:693-711).
Operator dependencies that are still OPEN
See docs/lii/RUNBOOK.md §0. Until INTENT_INGEST_URL and INTENT_CAUSAL_URL are set
on Cell 34, a DSAR would not fully erase — the cascade honestly returns 502 with
failed receipts for cells 33 and 36, and GET /health reports
cascade_peers_configured so the condition is visible before a request arrives
(orchestrator.py:818-825).
6. OBSERVE-ONLY posture and the promotion criteria
Rule: CONSTITUTION.md:74 (Art. III.6) — "LII/ORACLE outputs recommend by default.
Promotion beyond observe-only requires Brier ≤ 0.20, AUC ≥ 0.72, and stable lift
across ≥ 2 purchase cycles — and only via human approval. If Brier exceeds 0.20 after
promotion, the output is demoted to observe-only and an incident is opened."
These are documented gates, NOT defaults. Verified by search (re-run 2026-08-09):
across src/cells/cell33–cell36, src/shared/mizoki_intent/ and
src/shared/miz_oki_source_of_truth.py, the literal 0.72 does not occur at all, and
every occurrence of 0.20 is the top_k_pct cohort-size default — not a Brier
threshold (src/cells/cell36/causal_cell/activation.py:42,
causal_cell/orchestrator.py:336, causal_cell/main.py:187). Neither promotion
criterion exists as a threshold constant anywhere in the intent code. Promotion is a
human decision recorded against a signed phase exit (blueprint §9 / A.8), not a code
path that flips when a metric crosses a line. Nothing in the repository can promote
LII out of observe-only automatically.
What the code does enforce, structurally:
- Intent scores are evidence, never authorization.
IntentScore.as_evidence()emits"advisory_only": Trueand the docstring binds the sole activation path touplift_export_cohort→ guardrails → DecisionControlPlane (src/shared/mizoki_intent/client.py:72-102, blueprint §6.6). - Every payload carries
claim_label: "built, pre-benchmark"—CLAIM_LABELintaxonomy.py:34,subject.py:50,client.py:35, and in the §6.1 response envelope on Cell 34. - Reports stay non-acceptable until a named approver signs a real-corpus run:
{claim_label: "built, pre-benchmark", acceptance_eligible: false, corpus: "illustrative scenario"}(blueprint A.6). - The UI surface is labeled observe-only:
miz-oki-command-center-ui/app/signal-intelligence/intent/page.tsx:190renders anobserve-onlybadge, asserted atpage.test.tsx:97; the SSE hook states "The hook READS. It cannot activate anything (LII is observe-only)" (components/signal-intelligence/useIntentStream.ts:34) and the incrementality panel states "NO ACTIVATION" (IncrementalityPanel.tsx:53).
A promotion decision today would fail on its own evidence — see §7. The AUC gate (≥ 0.72) has one measurement on the only real corpus available, and that measurement is 0.5749 (verified result). It does not meet the criterion, and the criterion is not the failed gate — see §7 for the gate that actually failed.
7. The Phase A ranking gate: FAILED, and OPEN
Source of record: docs/reports/INTENT_PHASE_A_GATE_REPORT_2026-08-01.md.
Elevated to binding case law by TRUTH.md Article 4.1.
The intent model FAILED its ranking gate on the real mycocoons corpus.
On the only real corpus available, a boosted-tree intent model does not beat a naive recency sort at predicting 30-day repeat purchase:
| Figure | Value | Label |
|---|---|---|
| Eval AUC | 0.5749 | verified result |
| Model cumulative-gain area | 0.5703 | verified result |
| Naive-recency baseline gain area | 0.7492 | verified result |
The baseline is a plain sort by days_since_last_signal ascending. The model loses to
it. Gate state: FAILED.
Three attempts, constant protocol, then STOPPED (all verified results):
| Attempt | Config | Eval AUC | Model gain area | Recency gain area |
|---|---|---|---|---|
| 1 | fv1 (5 features), unweighted | 0.5749 | 0.5789 † | 0.7414 † |
| 2 | fv1, auto_class_weights |
0.5749 | 0.5454 † | 0.7329 † |
| 3 | fv2 (9 RFM features), weighted | 0.5749 | 0.5771 † | 0.7436 † |
| 3 (clean) | fv2, weighted, deduplicated eval | 0.5749 | 0.5703 | 0.7492 |
† Attempts 1–3 as first computed were contaminated by the calibration range-join row multiplication (defect 7 in the gate report) — eval rows were inflated roughly threefold. The clean deduplicated re-measurement (104,782 rows / 61 positives) reaches the same conclusion under the same protocol.
Protocol held constant throughout: identical corpus (383,582 examples, 655 positives — 0.17%), identical time split (most recent 20% of weekly cutoffs, 2022-01 → 2025-09), identical baseline.
Calibration — and the caveat that must travel with it. ECE 0.0006 / Brier
0.0006 (verified result, fv2, calibrated, deduplicated eval). TRUTH.md Article 4.4
binds the caveat to the number: at a ~0.06% eval base rate these are near-trivially
small — the calibration is honest but weakly informative. The numbers are never to be
quoted without that sentence. In particular, a Brier of 0.0006 is not evidence
against the Art. III.6 promotion bar; a near-constant predictor at that base rate gets a
small Brier for free.
Binding rule — TRUTH.md Article 4.1: no metric-chasing, no synthetic positives,
no baseline weakening, no protocol change mid-eval; a failed gate is recorded as failed,
and frozen-corpus evals are not re-run expecting different results.
The gate stays OPEN until live pre-purchase signals flow. The report's own interpretation (labeled there as assessment, not measurement) is that the corpus is order-anchored — the only behavioral signals are prior purchases and their acquisition context — so "bought recently" is close to the whole signal, and no GA4 raw export exists in the project. That is an explanation of the measurement, not a prediction that a rerun would pass. Nothing in this document should be read as implying the gate will pass later.
Phase A exit status: 5 of 7 gates closed, 1 failed (AUUC vs naive-recency baseline), 1 pending (cost actuals — needs a billing window). Phase A remains open in Shadow. Named-human sign-off on the report is still blank.
8. Language discipline (TRUTH.md Article 6) — binding on all LII copy
| Say | Never say |
|---|---|
| "anticipatory intent with proof of causal lift" | "mind-reading" (6.1) |
| calibrated probabilities, with confidence and an explanation path | "will buy" (6.2) |
| iROAS — the incrementality language of DECIDE | platform ROAS standing in for an incremental figure (6.7) |
| labeled design targets | targets presented as achieved, promised, or guaranteed (6.3) |
| "Preview · in development" for in-development capability | described as deployed (6.4) |
| — | any guaranteed outcome, in any copy, on any surface (6.5) |
Every figure carries exactly one of the five labels — verified result | benchmark
result | pilot result | design target | illustrative scenario — within two lines
(TRUTH.md Article 2; enforced by tools/claims_lint.py).
The ceiling rule: the platform ceiling is "built, pre-benchmark". Status words are distinct and never upgraded: designed / implemented / configured / deployed-unverified / partially operational / live-verified / blocked.
9. What this document verified, and what it did not
Verified by reading the cited files: every line reference above; the constitutional
text at CONSTITUTION.md:50, 74; TRUTH.md Articles 4.1, 4.4, 6; the gate report's
figures; the absence of any Brier/AUC promotion threshold in intent code.
Re-verified 2026-08-09, after src/shared/mizoki_intent/subject.py changed. The
before == 0 short-circuit in erase_with_receipt was removed and overall_status([])
was changed from erased to failed while this document was being reviewed. §5 was
rewritten against the new file and every subject.py line citation in this document was
recomputed — the change shifted them by roughly sixteen lines. Two claims that this
document and docs/lii/ADR-LII-001.md previously made — that the pre-count-zero branch
returned erased without a delete or recount, and that an empty receipt list rendered as
200 erased — describe code that no longer exists and have been corrected rather than
carried forward.
Not verified when this record was written: any live runtime behavior. No service was
called, no BigQuery query was run, no deployment state was inspected — gcloud was not
available in that environment. Amended 2026-08-10: the subject-rights implementation
against real BigQuery, streaming buffer included, has since been exercised end to end and
is live-verified (see the status note in the subject-rights section). Cell 35's graph
leg was exercised against its in-memory store; there is no Neo4j to verify against and,
per the 2026-08-09 owner decision, there will not be.
SUPERSEDED 2026-08-10 — the streaming-buffer path is now
live-verified. The paragraph above was accurate when written on 2026-08-09 and is kept rather than rewritten, because a dated verification record is evidence and editing it in place would destroy the audit trail. A full DSAR round-trip has since run against production: the cascade returned 8 receipts across all four cells covering all six identity-linked tables plus the graph, and answered HTTP 409pending_streaming_buffer,remaining=1,retry_after_seconds=5400for a row still held in the BigQuery streaming buffer — it did not claimerased. 13 audit rows landed under a saltedsubject_ref. Live governance probes returned 422 for audio (3 variants), 422 for a deny-listed topic, and consent fail-closed.This is the single most load-bearing result in this document: the count → delete → recount contract was designed for exactly that condition, and it held on first contact with a real streaming buffer rather than reporting a false success.
Still NOT live-verified: the Neo4j Cypher erasure path (the platform stays on Firestore by owner decision, so it is inert rather than pending), and the correctness of the DSAR readiness probe's verdict.
Correction, 2026-08-10: an earlier revision of this line said the probe "has never been called". It has — three times on 2026-08-10, and those calls exposed a false
blockedon the first (cold-start) call that no test would have caught. What remains unverified is narrower: no green run's legs have been checked one by one against the deployment's actual configuration. Detail and the operator consequence are indocs/lii/RUNBOOK.md, "Start here".
Verified absent, by grep, on 2026-08-09 (recorded because an absence is a claim too):
no vector index / vector store / embedding anywhere in Cells 33–36 or
src/shared/mizoki_intent/; no test anywhere under src/cells/cell33–cell36/tests/
that references the live BigQuery or Neo4j erasure helpers; neither neo4j nor
google-cloud-bigquery importable in this environment.