Mindbridge Β· track status

VIEWER-TRUTH

“Which person node is this user?” had five independent implementations and three delete paths that destroyed the answer. Those three delete paths are now hardened (ADR-142); one adjacent path is not β€” two humans sharing a name still fuse into one node. The opposite failure β€” one human arriving through two connectors becomes two people β€” has now been MEASURED, end to end: detector run fleet-wide, Regina sized, 8 pairs judged, the one same verdict owner-confirmed a false positive. Zero confirmed cross-connector splits on all of production β€” and the measurement showed why the number is so hard to move: 81% of identities are single-connector and cannot even express a split.

Re-measured 2026-08-19; re-derived 2026-08-25 after the detector-runs/judge-run/jid-minting wave (PRs #3530, #3538, #3555, #3561, #3566); identity figures re-measured 2026-08-23 against production. Verify before starting β€” this page's prose decays as the track moves.

Next → Two owner calls, then the pairing window. (1) #3416 — re-scope or close: the measurement question is answered (zero confirmed splits, judge = reliable veto / poor proposer), and the recommendation on the table is re-scope, not close. (2) Wave 3 (the reconciler) — re-opened as a QUESTION, not a build: had it been live and auto-applying, its first action would have merged two different humans, one of them the owner. Behind those: the pair-before-recover ordering window — Regina onboards ~30 WhatsApp staff and the connect-time JID→identity binding is shipped, so pairing each employee's auth email to their Regina identity before any history recovery is free now and unrecoverable later; Regina still holds 0 JID mappings (08-24). The client lane (#3148) and the 17-expression detector (#3162) both closed 2026-08-29/08-31 — see History.
1 open β€” spine live (10 of 14 linked accounts bound Β· 4 unresolvable) Β· tier reconciler live, 0 of 14 diverging Β· one unowned gap: Gmail signup still lands on an empty product, permanently.
What is wrong: there is ONE identity resolver, and the rung that decides is email. The five email-authoritative sources β€” gmail, maildir, exchange and both calendars β€” offer the resolver nothing else. WhatsApp offers it no email at all: 99.7% of WhatsApp identities carry none, and 116 have a phone number where the name should be (re-derived 2026-08-19 against the resolver's OWN digit-ratio predicate, not a regex). So a human present in one source of each kind becomes two people, permanently. Because those two halves share no key, every key-based duplicate census returns clean β€” that clean number was always an artifact, not health.
The 2026-08-20β†’25 wave β€” the measurement question is answered. The sizing pass ran --no-judge on 8 of 9 tenants (Regina timed out β€” PR #3538's chunked exact KNN fixed that, and Regina's first-ever successful run then read 3,915 identities, 88 invisible, 0 cross-key-space). PR #3530 first unioned provenance_person_ids onto merge survivors β€” invisible identities fell 459 β†’ 217 fleet-wide (Regina 283 β†’ 58) and multi-identity nodes went 0 β†’ 18, all different-name. The owner-gated judge run then adjudicated all 8 cross-key-space pairs: one same verdict β€” “yasmine el-daly” vs the owner's email local-part, cosine 0.833 β€” owner-confirmed a FALSE POSITIVE (a surname match, not a human). Honest count: zero confirmed cross-connector splits on all of production. πŸ›‘ Structural caveat that bounds every future run: only 974 of 5,199 identities can even express a cross-connector split; 3,912 of Regina's 3,915 identities are maildir-only, so her “0 candidates” is an artifact of having no second connector β€” and the 3-candidate floor elsewhere is embedding-bounded. The judge, measured, is a reliable VETO and a poor PROPOSER.
Shipped 2026-08-19 β€” the detector, and a gate on the fork count. The detector (PR #3470, merged) reuses the dedup pass's own candidate search and its own label-only judge, adding no new inference, and asks the question the keys cannot. πŸ›‘ It reports a FLOOR: candidates are embedding-bounded, and β€” worst β€” an identity whose graph person dedup MERGED AWAY points at no node and is never probed. Separately, censusing that question mechanically found it answered in 14 independent places across two id-spaces, several contradicting each other: the nodes_v2 unique index on lower(canonical_name) fuses two same-named humans with no judge. PR #3484 gates the count so a 15th cannot appear unnoticed β€” it does not yet collapse the 14. ⚠️ Only the Python lane is enforced; the SQL and migration lanes are detection-only.
The WhatsApp side now has its own sub-fleet record. The connect-time JIDβ†’identity binding was verified already shipped (email match β†’ atomic link_person_to_user β†’ properties.phone stamped), refuting the mandate's ground-truth item 4 β€” the binding constraint is ORDERING: pair all staff before any history recovery. The external-contact minting problem (every external JID mints a duplicate person today; ~4,500–9,000 expected from 30 accounts) was split to a focused fleet under this track: PR #3555 (matched_by/match_confidence on every JID mapping β€” the measurement instrument), PR #3561 (profile-fetch eligibility re-pointed onto its own state machine; genuinely-eligible population measured at exactly 59), PR #3566 (LLM signature/contact-phone extraction, attribution enforced by code β€” never run against prod; even the 50-candidate pilot is an owner decision), with the pilot-fix PR #3572 still open. A production pilot dry-run surfaced: 95/137 phones discarded for lack of a region, shared-number handling missing, a $0.0000 telemetry bug β€” all fixed in #3572. Two lanes ruled OUT by the owner's own BANNED rule (regex text analysis on a multilingual corpus); all their figures reclassified background-only.
The remedy is a choke-point, and NOTHING in the codebase RECORDS it: one place that records “these two identities are the same human.” Censused β€” viewer_bindings, owner_identities, node_aliases and person_sources all key within a SINGLE key-space; none bridges an email-keyed node to a name-keyed one. ⚠️ Corrected 2026-08-19: graph_merge_redirects DOES durably record a sameness assertion β€” but at the GRAPH layer and keyed on (tenant_id, node_type, canonical_key) β€” a NAME, not an identity id β€” so it cannot bridge two differently-named identity rows. That matters for the shape of the fix: the endgame may be re-keying that table to the identity layer rather than building a new one. 🩸 And the schema's own comment on person_identities is false β€” it says “the merge map lives on nodes_v2.provenance_person_ids”, while dedup never writes that array at all: it deletes the loser node and carries no identity linkage to the survivor. The evidence to build it is already in hand and thrown away: when someone connects a second connector, the ACCOUNT is proof both identities are one person, and nothing writes that down. πŸ›‘ POST /api/identity/link is not that seam, though it is a real gap β€” it binds an account to ONE email-matched person, so restoring its caller would bind the Gmail node and leave the WhatsApp node separate forever. Until the merge write exists, every fix is a per-pair repoint the next connector re-forks. And to size it rather than list journeys: stories have no denominator, which is why nobody could say whether nine was all of them β€” so count connector PAIRS that cannot bridge: 2 name-only Γ— 5 email-only = 10, of which the nine journeys covered one. That is checkable, and it grows by a row per connector added. 4 are live today; Regina is the concentration risk β€” 3,865 identities from a single connector, so the day a second lands there, thousands split at once. Derivation in History Β· forensics.
1open issues
0confirmed cross-connector splits (08-23)
8cross-key pairs judged Β· 1 false positive
974 / 5,199identities that CAN express a split (08-23)
10/14linked accounts resolve (08-19)
17expressions of “which person is this ACCOUNT?”
14expressions of “are these the SAME HUMAN?”
509canonical_names holding an email/phone (08-23)
0Regina JID mappings β€” pairing window open (08-24)
Open work Why this order Rulings History

Open work

Identity fusion β€” one human, two people (re-derived 2026-08-18)

p1 #3416 No choke-point records that two identities are the same human β€” 10 connector pairs split permanently measured 08-23: zero confirmed splits Β· re-scope-or-close ruling owed All
Censused: viewer_bindings, owner_identities, graph_merge_redirects, node_aliases and person_sources all key WITHIN one key-space; nothing bridges an email-keyed node to a name-keyed one. Moved 2026-08-23: the detector now runs everywhere including Regina (PR #3538), the judge adjudicated every cross-key pair, and the single same verdict was owner-confirmed a false positive β€” so the measured count is zero confirmed splits, with the structural caveat that 81% of identities cannot express one and the floor is embedding-bounded. What remains is the ruling the measurement bought: re-scope (the recommendation) or close. The wave-3 reconciler that would have consumed a sameness write is re-opened as a QUESTION β€” as an auto-applier its first action would have merged two different humans.
p1 #3546 A canonical_name holding an email or phone is not a name β€” 509 identities (9.8% of prod) feed every name-similarity mechanism as if it were filed 2026-08-23 off the confirmed false positive All
The judge's one same verdict matched a family surname against an email local-part because a canonical_name held the email address. 509 identities (9.8% of prod) carry an email or phone where a name belongs, and every name-similarity mechanism β€” the fusion index included β€” reads them as names. This is the non-name-identity root cause the false-positive confirmation surfaced; fixing it shrinks the false-positive class the judge exists to veto.
p2 #3473 WhatsApp phone-match rung 1 can only match persons created by two narrow paths β€” 7 of 5,180 identities carry properties.phone owner-gated: widening matching Β· urgency reduced All
The phone rung reads properties.phone, which only two narrow creation paths ever write β€” 7 of 5,198 identities carried one when filed. Owner-gated because a fix widens matching (the exact thing the false positive warns about). Urgency reduced 2026-08-23: the connect-time binding stamps properties.phone at pairing time, and LANE S's signature extraction (PR #3566) is the sanctioned producer for the rest β€” about the population that will never pair. The jid-minting fleet re-measured the eligible profile-fetch population at exactly 59 rows fleet-wide (1,026 of 1,139 JIDs are @lid/@g.us and never fetchable).
p1 #3417 POST /api/identity/link has had no caller since the enterprise app was deleted regression, not a design gap Mizo
Live, registered, rate-limited, and called by nothing since 2026-07-31. Commit 456d5d7c7 (PR #2889) deleted the one call site along with the legacy enterprise web app; no equivalent was added to client/. Three of the seven live arrival paths produce a zero-binding account unconditionally, so this is the seam that closes the “Gmail signup lands on an empty product” symptom. πŸ›‘ It is NOT the identity-fusion fix (#3416) β€” it resolves by email and binds ONE node, so restoring it binds the Gmail node and leaves the WhatsApp node separate forever.

πŸ›‘ The calendar identity gap (#3418) left this track on 2026-08-30 β€” it now lives on CONNECTOR-TRUTH. It was filed here as a viewer-resolution question, but the messages do carry person ids; what is missing is the person_sources write that links them to the connector, which is a connector defect. Calendar remains one of the five email-authoritative source types, so if it is fixed the live-pair count on this page changes with it.

The remaining lanes β€” both closed 2026-08-29/08-31

βœ… #3148 (client viewer resolution disagreed with the server) and #3162 (17 expressions, no detector) both closed β€” one accessor now stands per lane, backed by a cross-lane parity test and a firing check.py rule. Detail in History.

Blocks calling done β€” the uncensused questions

p1 #3173 VIEWER-TRUTH: the viewer's display NAME has 5-6 implementations that disagree, and no detector name differs by surface All
“What do we call the signed-in human?” has 5-6 independent implementations returning different strings for the same account β€” the link response says "Free Palestine πŸ‡΅πŸ‡Έ" while the very next profile read says "Ahmed Eldaly". Collapsing the ladders is product-visible and needs a before/after, but no detector was added either, so a seventh ladder can land tomorrow with nothing firing. One accessor; _resolve_owner_name stays excluded with its reason (an owner who only ever sent from_me is never materialised); a check.py rule fires on a new coalesce-ladder.
p2 #3174 VIEWER-TRUTH: the identity→node reverse join is forked across ~20 sites in 4 syntaxes reverse join forked twenty ways Dev only
The inverse lookup is written ~20 times in 4 syntaxes; one shared helper (substrate_version.person_node_join) has exactly 1 caller. The bulk CTE must stay a bulk variant (measured 240s → 4.3s). Trap: node→identity looks 1:1 today only because merges currently drop the loser's provenance_person_ids; the day that is unioned, every [0]-style pick becomes ambiguous. Unblocks #3172.
p1 #3172 VIEWER-TRUTH: a suppression filter that filters nothing β€” 1,316 persons leak into the attribution index filter excludes zero of 1,316 blocked
load_unambiguous_name_index passed exclude_suppressed=True over person_identities, where the three flags are never written β€” they live on nodes_v2.properties. Measured: 0 of 5,568 identity rows carry a flag while 1,316 nodes are genuinely suppressed, so the filter excluded zero. PR #3169 set the call to False (zero behaviour change β€” it never filtered) so the code stops asserting a guarantee it does not provide. The real fix resolves each identity to its node to read the flags β€” which is the reverse join. Blocked on #3174.
p1 #3179 VIEWER-TRUTH: no real-DB test covers list_tenant_users, and the 50-id chunk boundary is untested no real-DB coverage Dev only
PlatformService.list_tenant_users β€” whose field mapping shipped the tier-edit break β€” is covered only by a mocked unit test. ViewerBindingRepository.find_bindings_for_users chunks at 50 (the .in() ban) and nothing exercises >50 ids. Add a real-DB test asserting both id fields land in their own fields, and a 51+-account chunk test.

Tier-claim follow-ups β€” the reconciler is live, the create path is now too

βœ… #3265 (create-with-no-person tier loss) closed 2026-08-19 β€” create_tenant_user now finds-or-creates the person_identities row through the ADR-084 single writer and writes the tier there. Its concurrent-create residue stays open below.

p2 #3270 Two concurrent creates onto one person: the LOSER writes the winner's tier claim loser writes winner's tier Enterprise
Two admins creating for the same person: the losing request re-reads the row after the winner links, writes the winner's account claim to the loser's tier, then fails and is deleted β€” an access-control write from a failed operation onto a third party. Low likelihood, bounded blast radius (both are admins in the same tenant), and the #3175 reconciler will not repair it because it makes the copy match the (also-wrong) column. Opened when #3234 moved the write above the link.

Live bug triage

p1 #3276 disclose() enforced on a PERSONAL tenant and blanked the surface β€” an unresolved viewer removes the exemption that would have spared it personal tenant surface blanked Mizo
Measured in production: all three open_asks audit rows are a personal tenant with viewer_person_id null β€” 12 of 12 candidates withheld, the surface empty. An unresolved viewer gets INVISIBLE on every candidate, and the PERSONAL short-circuit is applied by the resolver β€” so the same failure that leaves the viewer unresolved also removes the exemption that would have made it harmless. The default is correct against leakage, catastrophic for availability. Fix: resolve deployment_mode from the tenant row, not resolver success; a personal tenant does not run ADR-058 enforcement; an unresolved enterprise viewer becomes a counted, alertable condition.
p1 #3243 A linked account that resolves to no person node is silently dropped from the nightly nudge audience β€” Regina's audience is halved half Regina's audience dropped Enterprise
Regina has 2 bound accounts; one resolves to no person node, so half the tenant's nudge audience gets nothing β€” silently, indistinguishable from a quiet week, the run reporting success. Its third question decides scope: if the binding is broken everywhere, this is viewer-resolution repair, not a nudge fix. Shared with NIGHTLY-NUDGE.
p1 #3080 nodes_v2.properties.user_id owner-stamp is never cleared once written, and a stale stamp overrides identity silently stale stamp overrides identity silently Latent
The materializer writes the owner stamp only when an owner is present; absent keys survive the JSONB merge, so nothing anywhere unsets it once written. A node stamped with a now-stale user A, whose provenance bridges user B, keeps A indefinitely β€” no log, no warning (the unstamped ambiguity case does log and skip). Stamp-wins is defensible; the silent provenance/stamp mismatch is not. Census prod for the size before changing policy.
p1 #1314 fix(compose): proactive nudge + AMA narrative prose must attribute the sender by resolved display_name compose anonymizes the resolved sender Mizo
The substrate has the right name (“Lina El-Daly”), but the compose layer collapses both roles into one and outputs “Someone shared…” instead of “Lina mentioned…” β€” the prompt does not enforce sender attribution. Thread the resolved display_name through provenance, and reject “someone”/“they” as the subject when a sender resolves (Prime Directive β€” no silent anonymous default).

βœ… #3176 (viewer-resolution detector had 4 bypasses) closed 2026-08-31 β€” all four now fire, each pinned by an armed test. Detail in History.

The identity-substrate tail β€” un-rowed until the 2026-08-26 census (filed 2026-08-18β†’19)

βœ… #3424 (alias index missing tenant_id) closed 2026-08-31 β€” tenant-scoped index live in prod plus a boundary guard on every write; #3446 (its recovery census) closed 2026-08-27 as measured-no-damage; #3489 (dedup ignored deterministic identity signal) closed 2026-08-31 β€” shipped as a partition gate, verified reachable against a live tenant. Detail in History.

p2 #3425 person_sources.person_id has no FK since the 2026-06-03 emergency drop β€” orphans undetectable and unprevented integrity held by application code alone Dev only
The FK was dropped 2026-06-03 as an emergency production fix β€” a 23503 abort was freezing ingestion platform-wide β€” and never restored. Referential integrity has been held by application code alone since: orphaned person_sources rows are undetectable by constraint and unprevented by the database. Restoring it means solving the original abort's cause, not just re-adding the constraint.
p2 #3406 One confirmation flag authorizes the WHOLE tier update batch inert today β€” fix belongs to the batch's future caller Latent
Found by a panel lens on the tier-0 confirmation fix (#3267, closed): PATCH /api/admin/tiers takes a LIST of updates and a SINGLE confirm_tier_zero_downgrade boolean, so one flag waved for the batch authorizes every tier-0 downgrade in it β€” including entries the operator never individually confirmed. Inert today (no batch caller); filed so the builder of that caller inherits the constraint rather than the trap.
Why is it in this order?

Finish the collapse, then the blockers, then the create path, then triage. Re-read 2026-09-02: the identity-fusion cohort is no longer unsized β€” it is measured (zero confirmed splits, judge = veto-not-proposer), so the head of the queue is the two owner rulings that measurement bought (#3416 re-scope-or-close; wave-3 build-or-not) and the free-but-expiring pairing window before Regina's WhatsApp onboarding. The client lane (#3148) and the missing detector (#3162) β€” which finished ADR-142 and stopped an 18th expression β€” both closed (08-29/08-31), as did the detector-bypass follow-up (#3176, 08-31) and the identity-substrate tail (#3424, #3446, #3489, all by 08-31). Remaining: the four uncensused questions that block calling done (#3173, #3174, #3172, #3179), ordered so #3174 unblocks #3172; the tier-claim residue (#3270 only β€” #3265 and #3267 both closed); then live-bug triage, ordered by whether exposure is live (#3276 blanks a Mizo surface, #3243 halves Regina's audience) before silent or latent defects.

Deliberately ignored: priority labels alone (all but three are p1, so the label cannot order them); filing order and age (#1314 is the oldest, filed 2026-05, and sorts last); how cheap or well-understood a fix looks; and the severity a body asserts before exposure was measured.

What the audit found β€” generated

⚠︎ The prose above cites 7 issues that are no longer open

#3546, #3418, #3174, #3172, #3276, #3080, #1314. A closed issue named as current work is the drift this section exists to surface β€” re-derive the prose, or move the reference into History.

Evidence below is generated from the read-only audit of 2026-08-29 and reconciled against the open issues at build time. Every claim cites the issue it came from. Nothing here is a decision β€” rulings stay in the prose above, which is written by hand.

#3214 [L2-g4-w4] "Which text names this node in one line?" is the same expression written nine…codehigh confidence

No shared expression exists and the site count is LARGER than filed. Absence of a helper: `git grep -nE "def (node_display_text|display_text_for_node|one_line_text|node_one_line)" origin/main -- backend/app` β†’ zero hits. Live Python sites of the exact three-field fallback (`git grep -n "searchable_text or " origin/main -- backend/app`, population = all of backend/app): services/analysis/conversation.py:808, services/analysis/conversation_matching.py:150, services/ask/connection_resolver.py:452 and :499, services/ask/content_resolver.py:370 and :684, services/ask/decision_resolver.py:325, services/ask/question_resolver.py:299, services/ask/tools/_unmet_needs.py:333/:404/:459, services/connections/pattern_matchers/duplicate_work.py:162/:163, services/connections/pattern_matchers/problem_solution.py:266/:268 β€” 16 occurrences, versus the three Python sites the issue names. Python-embedded SQL lane still forked: backend/app/repositories/feed_intelligence_repository.py:261, :320, :436 carry `COALESCE(NULLIF(tgt.searchable_text, ''), tgt.display_name, tgt.canonical_name, '')` verbatim. SQL lane: the same expression appears three times per redefinition of the needs-you view, latest at supabase/migrations/20260819104315_fix_viewer_needs_you_labeled_passthrough_3389.sql:99, :159, :211 β€” the issue's cited migration (20260807105944) has been superseded five times since, which is exactly the drift the issue predicts.

Open questions β€” what the audit could not settle
  • #3214 Whether the client/platform lanes carry a fourth expression of the same fallback. Closes with: `git grep -nE "searchable_text\s*\|\|\s*.*display_name" origin/main -- client platform/src`.

23 assessed issues closed since the audit and are not shown β€” a closed issue's evidence is history, and printing it as current is how a page starts lying.

Standing rulings

History Β· forensics

2026-08-29β†’31 β€” ADR-142's last two lanes, the detector hardening, and the identity-substrate tail all closed. Finish-the-collapse: #3148 (client viewer resolution disagreed with the server) closed 2026-08-29 β€” client/services/viewer.ts now calls the current_viewer_person_id RPC instead of bridging through provenance_person_ids itself. #3162 (17 expressions, no detector) closed 2026-08-31 after independent re-verification of all five acceptance bullets: one accessor per lane, a cross-lane parity test, the duplicate find_person_node_ids_by_user_ids gone, and the viewer-resolution-single-reader check.py rule live in CI. Detector hardening: #3176 (4 bypasses) closed the same day β€” split/concatenated literals, the receiver-name gate, getattr indirection and the PostgREST alternates all now fire, each with an armed test. The identity-substrate tail: #3424 (alias index missing tenant_id) closed 2026-08-31 β€” tenant-scoped index live in prod plus a choke-point boundary guard on every add_alias call; its residue census #3446 closed 2026-08-27 as measured-no-damage (zero confirmed lost alias rows across all 9 tenants). #3489 (dedup ignored deterministic identity signal) closed 2026-08-31 β€” shipped as a partition gate consulting email/JID/domain/address identifiers plus graph-relationship corroboration, verified reachable against Regina's live, unpaused tenant. The tier-claim ruling shipped: #3265 (create-with-no-person tier loss) closed 2026-08-19 β€” the console now finds-or-creates the identity row through the ADR-084 single writer rather than refusing the tier or losing it to the reconciler; its concurrent-create race split to #3270, still open. Every one of these went through the same verification discipline: triage proposed 26 closures fleet-wide and an adversarial critic re-derived each p0/p1 verdict independently rather than accepting it, holding 13.

The 2026-08-20β†’25 wave β€” from “unrun detector” to a measured answer. PR #3530 (another session, 08-22) unioned provenance_person_ids onto merge survivors in both consolidation write paths β€” a permanent, non-self-healing identity drop on every merge that had ever run; the detector's under-count fell with it (invisible 459 β†’ 217, multi-identity nodes 0 β†’ 18). PR #3538 (08-23) made the detector runnable at Regina scale (chunked exact KNN over the query_ids seam) and re-labelled its fusion counters after #3530 falsified their docstring. The owner-gated judge run followed: 8 pairs, 8 LLM calls, one same verdict β€” confirmed false positive (“yasmine el-daly” vs “[email protected]”, cosine 0.833 β€” a surname embedding-matched an email local-part), which also filed #3546. The strategic finding stands on its own: the judge is a reliable VETO and a poor PROPOSER. The jid-minting sub-fleet then took the WhatsApp half (see the note block above; #3555/#3561/#3566 merged, #3572 open, Regina still 0 JIDs, the pairing window unexpired).

The spine is merged and live. W0 stop-loss (PR #3103) stopped disconnect+merge from deleting a user's identity. W1 the spine (PR #3122) built viewer_bindings β€” one row per (tenant, account), composite FK, ON DELETE RESTRICT, one reconciler β€” and backfilled 10 rows. W2 the resolver (PR #3152) minted current_viewer_person_id(), repointed 7 SQL functions and 2 Python sites, shipped the platform resolvability console, and carried the disclosure fix. Verified from the database, not run metadata.

W2 carried a cross-user disclosure fix β€” W1's reconciler counted its refusal over a de-duplicated set and its warning over the raw one, so an account bridging two nodes would bind while the log said it had not.

The census, run after W2 merged (#3162): 17 expressions on main, 3 of 12 corrected, no cross-lane detector β€” get_viewer_person_id is mocked in every test touching it, which is how every lane looked closed from inside itself. The real-DB parity test shipped in the sweep below.

The choke-point sweep (PR #3169, merged 2026-08-08) found three defects in its own code, all one shape β€” a repoint carried the new source over and left behind a guarantee the old code had: a dropped runaway-page bound, a dropped sort order (one concurrent insert silently drops an account from the recipient list), and a roster field needing a different id space that broke every tier edit (0 of 4,978 nodes carry a shared id). The gate itself could be silenced by a string literal β€” the suppression token matched the raw line.

The four uncensused questions, now measured: display NAME (now 5-6 implementations, #3173); suppression scope (ruled per-viewer, #3177); access TIER β€” the reconciler is live (PR #3249 β†’ 7dfedb9e7, pg_cron PR #3258 β†’ ff097766a, hourly at :26), the write gap that fed the drift closed (PR #3272 for #3234), re-measured 0 of 14 diverging; identity subsumption (the reverse join, #3174).

“The nine ways a user arrives” was the wrong unit, and it undercounted (re-derived 2026-08-18). Journeys are stories, and stories do not enumerate β€” which is why nobody could say whether nine was all of them. What decides whether one human becomes two people is not which story they lived but which key their connector can hand the resolver, and there is exactly ONE resolver: find_or_create_person_by_alias, a six-rung ladder β€” group alias β†’ cross-group alias β†’ email β†’ canonical name β†’ first-name prefix β†’ base name. An earlier trace read WhatsApp and email ingest as separate mechanisms; they are not. slices/node/materializer.py routes every PERSON intent through that same ladder. The split is not caused by different code. It is caused by which rungs are reachable.

Two key-spaces, declared per source type in slices/message/model.py: email-authoritative β€” gmail, maildir, exchange, google_calendar, exchange_calendar β€” and name-resolved with no email β€” whatsapp, whatsapp_live. (slack is neither β€” its key is the Slack user id, not an email β€” so it is NOT counted below.) For a human present in one of each, the email rung is measured never to fire: WhatsApp identities are 99.7% email-less on production (1,145 of 1,149) while gmail and maildir identities are ~100% email-bearing. πŸ›‘ Measured, not structural. _get_sender_emails reads from_address/from_email off raw_metadata for ANY message and never consults the source type β€” the partition is a fact about what each PARSER populates (grep confirms the WhatsApp connector writes neither field, 0 occurrences), not an invariant anything enforces. A parser change moves this silently, and no detector would fire. The only bridge left is exact canonical_name β€” an LLM-merged pushName against an email display name β€” and for the 115 WhatsApp identities whose name IS a phone number, no rung can match at all. So the countable unit is connector PAIRS that guarantee a split: 2 Γ— 5 = 10. The nine journeys covered exactly ONE of them (WhatsApp Γ— Gmail, the old journey 3).

Why the duplicate census looks clean and is not: 2 name-duplicates and 2 email-duplicates out of 5,137 identities, and zero identities spanning two source types. That reads as health; it is an artifact of the detector. A split pair shares no key, so no census the system can currently run can see it. Live today: only gmail/maildir and whatsapp/whatsapp_live currently MINT person identities in production, so 4 of the 10 pairs are live and the rest are latent. exchange and slack have no production sources at all; google_calendar does have sources and ingests messages, but resolves zero persons, so it cannot split anyone and is not counted among the four. The concentration risk is Regina: 3,865 maildir identities and no WhatsApp β€” maildir is the single largest identity producer on the fleet and appeared in none of the nine journeys. The day a second connector lands there, thousands split at once.

What the arrival paths actually are: 7 live production routes to an auth.users row, and 3 produce a zero-binding user unconditionally β€” Mizo form signup, Mizo Google-OAuth signup, and admin-create-without-a-person. Every ordinary product signup starts unbound. POST /api/identity/link is live, registered and rate-limited with no caller in any frontend β€” and that is a regression, not a gap: commit 456d5d7c7 (“delete the legacy enterprise web app and every live reference to it”, PR #2889, 2026-07-31) removed the one call site, which lived only in the deleted app; no equivalent was added to client/.

What this re-derivation CLOSED: the old “merge two people” family (journeys 5, 9) is largely fixed and this page said otherwise. ADR-142's viewer_bindings hardened all three delete paths, the dedup merge now re-points bindings onto the survivor, and the node prune excludes bound nodes β€” converting what was silent corruption into DENIAL rather than EXPOSURE, deliberately. What it did NOT close: nodes_v2 still upserts on (tenant_id, node_type, lower(canonical_name)), so two different humans sharing one name in a tenant still become ONE node. That is the exposure direction, and its only defence is application-layer “decline to bind either” β€” not a constraint the database enforces. Unresolved: google_calendar has 2,766 ingested messages across 3 tenants and attributes zero persons, though its resolver is wired unconditionally.

Cron delivery is now observed (#3259, PR #3394, merged 2026-08-17). Both halves of the check had shipped and neither had ever taken effect: the middleware ran on every internal request but only UPDATEs cron_route_heartbeats, the rows are CREATED by a sweep, and nothing scheduled the sweep β€” so every arrival matched no row, production showed 0 heartbeat rows, and that read as a healthy fleet. The issue was CLOSED with no state:not-in-effect label, which is why it looked finished. The PR carried the migration and its route-census test, and scheduled the sweep hourly at :19; it fired at 20:19:01Z and created a heartbeat row for each of the 15 POSTing jobs β€” the 14 already scheduled, plus the sweep itself. Registration is not arrival: a row exists for a route from the moment it is censused, and carries an arrival only once that job next fires β€” so at the first pass the two longest-period jobs, the 03:00 nightly and the 12-hourly Exchange renewal, had no arrival simply because neither was due yet. An empty arrival on a long-period route is not evidence of silence until its own period has elapsed, which is the distinction the sweep's grace window encodes. The tier reconciler above POSTs its own route, so it is now watched by this. Residual, filed: the sweep's unreadable finding class is dropped before Sentry (#3393) β€” dormant while every scheduled job stays parseable.

What this report does not claim: W2 made no production account newly resolvable β€” re-measured 14 linked, 10 bound, 4 unresolvable, unchanged, and all ten that resolve already resolved before it. An earlier version said Regina went from zero to one resolvable; that was true only of the bridge, not the serving path. And the zeros are two different zeros β€” the latent measure (no node fuses two linked identities, no account bridges to two nodes) is the load-bearing one, and it is exactly the precondition for both the disclosure defect and the client divergence.