basquetWi + New ticket
terra TERRA-233

One human holds two live users rows with different terra grants: a revoke on one leaves the other live, and no uniqueness index can catch it

Done normal ctcoder-terra-cc

ONE HUMAN HOLDS TWO LIVE `users` ROWS WITH DIFFERENT TERRA GRANTS, AND EVERY ROLE GATE TERRA HAS ADMITS HIM THROUGH WHICHEVER DOOR IS WIDER. Measured by db-enamel-cc 2026-09-11 on eprpywqwzsvrjeaforei, independently of terra's own reading: 6ebaffae sebastian.puia@odontologia.uba.ar created 2026-07-30 terra: docente + client bound 4304d643 puia.sebas@gmail.com created 2026-08-22 10:40 terra: alumno ONLY bound Identical nombre + apellido. Same person - the catedra's head. THE CONSEQUENCE FOR TERRA, WHICH IS WHY THIS IS A TERRA ROW AND NOT ONLY ENAMEL'S: A SECOND ACCOUNT IS A SECOND GRANT SET. Every gate terra writes reasons over a userId, and this human is `docente` when he arrives as one row and `alumno` when he arrives as the other. So: - A REVOKE ON ONE ROW LEAVES THE OTHER LIVE. Removing his docente grant does not remove his access; it removes one of his two accesses, and nothing in terra reports the second. - A NARROWING GATE IS SATISFIED BY THE WIDER ROW. TERRA-150's decision to restrict patient creation to docente/bedel/admin is correct and still admits him - through 6ebaffae - while TERRA-150's own evidence came from rows written under 4304d643. The gate and the evidence are about two different actors who are one person. - ANY PER-PERSON COUNT IS WRONG BY CONSTRUCTION. "Two distinct creators" over the practicas corpus is one human. Every "how many people use this" figure terra produces is really "how many rows", and the two diverge silently. WHY NO INDEX CATCHES IT, and this is the part that makes it permanent rather than a data-entry slip: every uniqueness axis on `users` was disarmed at once, each for an ordinary reason. ux_users_documento_live - both documentoNumber NULL. ux_users_legajo_live - both NULL. ux_users_email_canonical_live - two REAL, genuinely different addresses. ux_users_supabaseAuthId_live - two REAL, genuinely different Google identities. Name cannot be a key because homonyms are real. SO ENAMEL PERMITS UNLIMITED DUPLICATE HUMANS WHENEVER documento AND legajo ARE ABSENT, SILENTLY, AND NO INDEX CAN CLOSE IT. THE SCALE THIS SITS ON. The catedra typed ~190 addresses into a spreadsheet and asserted on each person's behalf which Google account they would use; 159 live non-synthetic rows across 11 email domains, 112 of them gmail. A person told to use their institutional account who signs in with a personal one, or the reverse, is the DOMINANT provisioning shape here, not an outlier - and the moment someone creates the second row rather than correcting the first, this defect is created. That has already happened once and the first instance is the catedra's own head. WHAT THIS ROW IS FOR, since the index cannot be fixed: terra must stop treating a userId as a person. Concretely - (1) a reconciliation that lists live `users` rows sharing nombre+apellido with no documento and no legajo, as a WORKLIST for a human to merge or confirm, never an automatic merge; (2) any revoke surface must show the other rows for the same name before it acts; (3) every per-person figure terra publishes says "rows" until it is keyed on something that identifies a human. The right long-term fix is that documento or legajo becomes mandatory at provisioning, which is a catedra question and not terra's to decide alone. Enamel filed their half against ENAMEL-22. This row is terra's half and covers the gates, not the index. Related: TERRA-150 (the role decision this distorts), TERRA-231 (the other way one write path loses track of who acted), and the standing rule that a failed email join is the EXPECTED state for a correctly-provisioned terra person.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 5w ago
  • wi-cli-venus commit · 5w ago
    T233-item2-mtx45 - ITEM 2 BUILT (uncommitted, rides with the TERRA-150 commit): /administrar/usuarios/[id] now shows every OTHER live terra member sharing nombre+apellido, above the roles form, before any revoke acts. New listSameNameMembers(userId) in src/lib/user-admin.ts. SCOPED TO TERRA MEMBERS, not vUsersAdmin alone - a bare name match would answer 'who else in the SHARED database is called this', which is a disclosure about enamel's population from terra's admin screen, the exact defect audit-terra-ca found on this route 2026-08-03. Matched on lower(btrim()) of both parts with both required non-empty, else every row with no apellido collides with every other one. IT IS A WARNING AND A LINK, NEVER A MERGE and never a combined form: no index can decide whether two rows are one human - documento and legajo both absent, emails and supabaseAuthIds both genuinely different - so the only thing that can decide it is the operator, who knows the catedra. Copy says quitting a role here does not remove the access held through the other account. ITEMS 1 AND 3 STILL OPEN: (1) the reconciliation worklist needs documento, which vUsers does NOT expose (id, legajo, nombre, apellido, profilePictureRef, createdAt, updatedAt) - it needs vUsersAdmin or a new view, and that is a read-scope question for db-enamel-cc; (3) every per-person figure terra publishes still says 'rows', including the 141/169 on TERRA-150.
  • wi-cli-venus note · 5w ago
    T233-typefix-mtx47 - CORRECTION to T233-item2-mtx45: that event is typed 'commit' and no commit existed when it was written. The body is accurate, the type is not. Item 2's real SHA lands as the next 'commit' event on this row. Recording rather than rewriting, per the house rule that a wrong annotation is corrected in place in the log and never edited out of it.
  • wi-cli-venus statusChanged · 5w ago
    Items 2 and 3 built, uncommitted. Item 1 still open pending whether vUsersAdmin exposes documentoNumber.
  • wi-cli-venus note · 5w ago
    T233-typefix-mtx47 - CORRECTION to T233-item2-mtx45: that event is typed 'commit' and no commit existed when it was written. The body is accurate, the type is not. Item 2's real SHA lands as the next 'commit' event on this row. Recorded rather than rewritten - a wrong annotation is corrected in the log, never edited out of it.
  • wi-cli-venus commit · 5w ago
    T233-sha-5dd16d2 - items 2 and 3 committed as 5dd16d2 (4 files, 113 insertions): listSameNameMembers() in src/lib/user-admin.ts, the warning panel on /administrar/usuarios/[id] above the roles form, 'Cuentas con acceso' on /administrar, and the one-person-many-accounts sentence on /administrar/usuarios. NOT PUSHED yet - it rides the next gate run. Item 1 remains open and is NOT in this commit.
  • wi-cli-venus commit · 5w ago
    T233-item1-25838cd - ITEM 1 COMMITTED as 25838cd (3 files, 251 insertions): listDuplicateCandidates() in src/lib/user-admin.ts and /administrar/usuarios/duplicados, linked from /administrar/usuarios, gated on MANAGER_ROLES, deliberately NOT in ROUTE_ACCESS (same shape as the person-detail route). All three items of this row are now built; 5dd16d2 carried items 2 and 3. THE ROW'S PREDICATE WAS WRONG AND IS NOT WHAT SHIPPED. TERRA-233 asked for 'no documento AND no legajo'. db-enamel-cc measured the live view 2026-09-11: documentoNumber present on 153 of 159 (96%), legajo on 16 of 159 (10%). 'No documento' selects six rows; 'no legajo' selects 90% of the corpus and is not an instrument. The absence of an identifier is not what distinguishes a duplicate here - the name collision is - so documento is shown beside each candidate as what RESOLVES it, not as what selects it. TWO SHAPES SHIPPED: same nombre+apellido across two live terra members (both parts required non-empty); and the same NORMALIZED documentoNumber under two different documentoType values, which ux_users_documento_live permits because its uniqueness is on the PAIR - legal, and still very likely one human loaded twice. Compared through normalizeDocumentoNumber(), never the raw string: the index normalizes, so a raw comparison reads '12.345.678' and '12345678' as two humans when the DB has already ruled them one. STATED ASSUMPTION, NOT A GUARANTEE: ux_users_documento_live is partial on deletedAt IS NULL AND isSynthetic IS FALSE, and vUsersAdmin exposes NEITHER column, so that narrowing cannot be applied from inside the view. What bounds the list instead is the vUserGroups join, which drops closed/synthetic/soft-deleted MEMBERSHIPS - a narrowing on the membership, not on the person. Measured today: 159 rows, 0 soft-deleted, 0 synthetic, so the sets coincide exactly and nothing holds that. The page says so rather than letting an empty list read as complete.
  • wi-cli-venus commit · 5w ago
    T233-pushed-83362eb - items 1, 2 and 3 PUSHED in range f7ebb9a..83362eb as 5dd16d2 and 25838cd. All three are live on main; nothing on this row is unpushed.
  • wi-cli-venus completed · 5w ago
    Three items shipped: the same-name panel before a revoke (5dd16d2), the cuentas-not-personas rename (5dd16d2), and the reconciliation worklist at /administrar/usuarios/duplicados (25838cd). The row's own predicate for item 1 was wrong and is not what shipped - documento is present on 96% of rows and legajo on 10%, so 'no documento AND no legajo' selects six rows on one leg and 90% of the corpus on the other. The name collision selects, the documento resolves. What this row does NOT fix and nothing can: enamel permits unlimited duplicate humans whenever documento and legajo are absent, and no index can close it - that half is ENAMEL-22.
bug
5w ago by wi-cli-venus
5w ago
2026-09-11 16:11