Withdraw support bearer email rewrite
Split out of ENAMEL-69 (legs 1, 2, 3 and 5 shipped at 1f90be4 + migration enamel_078). Leg 4 was "fn_correctUserEmail exposed through the same API". It cannot be exposed as it stands. MEASURED 2026-10-05 live on eprpywqwzsvrjeaforei: fn_correctUserEmail(uuid,text,text,text,text) is prosecdef=false (INVOKER), proconfig search_path=public,pg_temp, owner postgres, proacl {postgres=X/postgres,service_role=X/postgres}. The app pool's tenant-qualified postgres ALREADY reaches it - no GRANT is needed and none should be written. Reach is not the blocker. The blocker is that it carries no actor argument and no internal gate. Its five args are userIdArg, newEmailArg, reasonArg, performedByArg, appKeyArg, and the last two are caller-asserted free text. Exposed directly behind the support bearer it would let the pinned service actor rewrite the email of record of ANY user row in a co-tenanted database with self-asserted attribution, and fn_correctUserEmail's own appEvents row would record whatever performedBy string the caller sent. Per ENAMEL-69 item 5 the gate for a service actor is a NEW predicate, and the precedent set on that row is that the predicate lives in SQL, not in app code. ASKED OF THE DB LANE: a wrapper fn_supportCorrectTerraUserEmail(actorUserIdArg uuid, userIdArg uuid, newEmailArg text, reasonArg text) returns table(outcome text, correctedUserId uuid), INVOKER, search_path pinned to public,pg_temp, EXECUTE to postgres only, that (a) reuses enamel_078's actor gate BYTE-IDENTICALLY - exactly one live grant fleet-wide, and that grant bedel@terra on group 97889e74-9e3c-45c5-a501-09c1c660fd59 - raising 42501 otherwise; (b) requires the TARGET user to hold a live non-synthetic terra grant under the same stable-key membership block enamel_078 uses at its lines 156-165 and 221-230, so the capability cannot reach a non-terra person, refusing with its own SQLSTATE rather than silently; (c) pins appKeyArg to the constant 'terra' and derives performedByArg inside the function from the gated actor, never from an argument; (d) delegates to fn_correctUserEmail and returns its outcome verbatim (corrected, noChange, conflict, userDeleted, noSuchUser), inventing and collapsing nothing. CLASS-A, same discipline as enamel_078: audit design-ping before implementing, source review and a forced-red of every acceptance control before the apply GO, exact-bytes GO from coder-enamel-cc. The acceptance probes write real rows, so the residue assert goes OUTSIDE the aborted subtransaction - see the ENAMEL-69 event log, a ROLLBACK rehearsal structurally cannot evidence that property. Consumer (route plus tests under /api/soporte/terra, no predicate in TypeScript, no idempotencyKey) stays in coder-enamel-cc's lane and follows the APPLIED signature, never the proposed one.
Activity
-
parent=ENAMEL-69
-
DB lane accepted. Live fn_correctUserEmail signature/body and enamel_078 actor/target predicates read. Class-A design ping sent to audit-general-ca and coder-enamel-cc under hub tag db-enamel-ca-muvl6ljk4qf3 before implementation. No migration drafted or applied yet.
-
Design audit BLOCK tag audit-general-ca-muvl8f4vquoc: wrapper must refuse self-target to protect pinned actor email, prevent free-text reason PII reaching shared appEvents, and get PM ruling on Terra-admin targets. Proposed P4031 self/non-Terra refusal and exact operational reason code support:terra-email-correction with P4004 for other inputs; coder-enamel-cc asked for admin scope under hub tag db-enamel-ca-muvl9859vh00. No migration drafted/applied.
-
Audit-general-ca confirmed at tag audit-general-ca-muvl8utke997: target precheck makes fn_correctUserEmail noSuchUser/userDeleted unreachable through wrapper; do not synthesize them. Absent, deleted, synthetic, non-Terra and other ineligible targets all receive P4031 with no correctedUserId or target-disclosing detail. Return only callee outcomes actually reached, verbatim. Self-target and free-text-reason HIGH findings remain; Terra-admin target policy awaits coder-enamel-cc.
-
DB draft pushed 43073ec7e69350983b37211c6207ca5cb62e58fe, blob 2452f59e543509288fa30d956e0678e767e01ba7, MD5 8023284915670c2390daa6b0f9b8aeba; enamel_079_support_correct_terra_user_email.sql path clean. Three-arg owner-only INVOKER wrapper; actor gate byte-identical to enamel_078; self/non-Terra/deleted/synthetic/admin targets denied P4031, fixed operational reason and actor UUID passed to fn_correctUserEmail; no existing-function GRANT. Whole-file live rollback rehearsal fed COMMIT->ROLLBACK variant MD5 b5b4839e8fb2f2a814d247147a839cb7, isError=false result []; postflight wrapper/ledger/probe users 0 and actor email unchanged. Exact Class-A audit and consumer review requested under hub tag db-enamel-ca-muvli16v2nkf. Forced-red controls and apply GO remain; no DB apply.
-
Forced-red residue assertion on exact source 43073ec/MD5 8023284915670c2390daa6b0f9b8aeba: temporary COMMIT->ROLLBACK variant removed only inner enamel_079_probe_rollback raise; fed MD5 dc8b38d703b922f88626b6d3ca3b47b7, 17029 bytes. Live execute_sql failed as intended with SQLSTATE P0001, exact error enamel_079: acceptance probe left persistent state, inline_code_block line 173. Postflight wrapper count any arity 0, ledger count 0, probe users 0, actor email unchanged. No apply; reported to coder-enamel-cc under hub tag db-enamel-ca-muvljmlubc73.
-
BLOCKED on an Elazar ruling, raised by audit-general-ca (audit-general-ca-muvlklpoci7u) and accepted in full by me as PM. Not a defect in enamel_079: the draft implements every bound asked of it. The blocker is the capability itself. THE CONSEQUENCE. fn_correctUserEmail sets users.email and its canonical form, and bindSupabaseAuthId resolves an UNBOUND profile by users."emailCanonical" alone - stated on the live userEmails table comment from enamel_048, which also records that the binding must never be repointed at the history table. So a caller holding the support bearer plus a target uuid can make a mailbox of its choosing the login match for an unbound person. The operation is named and documented as correcting a typo; the mechanism is repointing a login identity. The fixed operational reason in appEvents records THAT an edit happened, never that anyone verified the new address belongs to that person. WHY THE OBVIOUS FIXES DO NOT APPLY. Binding a verified-address fact inside the write transaction is the right shape and the fact does not exist: this database has no attested-address store, and TERRA-111 already established that nothing backs emailCanonical with a verification trail. Per the standing rule, a control that requires knowing X when no call returns X is a rule written over a missing primitive - fix the primitive, do not write the rule. Refusing bound targets inverts the exposure rather than removing it, because the vector needs supabaseAuthId IS NULL, and unbound people are precisely the population the capability exists to serve (db-enamel-ca found seven of them under different enrollment emails). WHAT IS ACTUALLY NEW. fn_correctUserEmail has had this property since enamel_048, reachable by service_role, and TERRA-104 exercised it once as a deliberate one-off under Elazar's eye. ENAMEL-73 does not create the property; it moves it from a human DB operator acting once to an API bearer that any agent can call repeatedly and scriptably. That change in blast radius is the decision, and it is not mine to take alone: it governs who can take over an unbound account. ASKED OF ELAZAR, one of: (a) accept it as specced, on the record, with the account-binding consequence named - the operational control then being that the bearer is agent-only and every call leaves an appEvents row; (b) restrict the capability to targets that already hold a live Supabase binding, accepting that it then cannot serve the seven-person population that motivated it; (c) fund the missing primitive first - an attested institutional address per person - and gate the correction on it; (d) keep email correction out of the API entirely and leave it where TERRA-104 left it, a per-case human-authorised DB action. Surfaced in the CLI reply, since Elazar works CLI-only and cannot be DM'd. No apply, no GO, no consumer work until this is answered. enamel_079 stays at its draft blob and still carries my two unaddressed review items (re-prefix the copied actor-gate raises, deny a live terra 'client' grant alongside admin) whose fix will void the existing rehearsal evidence.
-
Coder review BLOCK on 43073ec addressed in pushed 4d7fea0 / MD5 a4f174dc4cf5c522e027c1d9db79db9d: actor refusal messages now identify correction wrapper; target gate denies live Terra admin/client by stable ID or current value, including mixed grants; client-only/mixed rollback probes and residue coverage added. Per coder live census, service actor is Terra's sole bedel member and is separately excluded by self-target P4031. Wrapper remains INVOKER while postgres pool can call fn_correctUserEmail directly; it narrows our route call path, not the pool's DB privilege boundary. Exact-digest audit/consumer source reviews requested; no DDL apply.
-
Live exact-byte rollback rehearsal of 4d7fea0 / source MD5 a4f174dc4cf5c522e027c1d9db79db9d: COMMIT-to-ROLLBACK variant MD5 1699f3cd1e973c32964cbc25a010cd7b returned success/[]; all in-file probes including client-only and mixed-client passed. Forced-red variant MD5 ee784509217a1a22d78ddf054c443e46 removed only inner rollback raise and changed terminal COMMIT to ROLLBACK; raised expected P0001 enamel_079: acceptance probe left persistent state. Postflight wrapper=0 any arity, ledger=0, probe users=0, actor email unchanged. Source review/GO pending; nothing applied.
-
Audit BLOCK (audit-general-ca-muvlklpoci7u) applies to current 4d7fea0 too: support bearer plus target UUID can set arbitrary new email on an unbound Terra profile, changing bindSupabaseAuthId's later emailCanonical match. Fixed appEvents reason proves attribution but not target-to-address approval. Awaiting Enamel PM choice: trusted exact-target+new-address approval fact in the same write transaction, or explicit human risk ruling with operational control. No DDL applied; current digest's source/rehearsal is not an apply GO.
-
SOURCE REVIEW of 4d7fea0 / blob a60c89a / md5 a4f174dc4cf5c522e027c1d9db79db9d / 18356 bytes - digest verified by me from the blob, not on report. My two review items are both resolved: zero occurrences of 'supportCreateTerraUser' anywhere in the file (all three actor raises and the carried comments now name supportCorrectTerraUserEmail), and the privileged-target denial is live-admin OR live-client by stable group id (d2a96c96, 5a2c92af) OR current value, with alumno/docente reach preserved and mixed-role targets denied, plus client-only and mixed-client P4031 probes. Residue assert sits outside the aborted probe subtransaction and covers users, userGroups, userEmails and appEvents for all five seeded ids; the forced-red variant returned the expected P0001. THIS IS NOT A GO. The source is reviewable and reviewed; the capability is not authorized. ENAMEL-73 stays BLOCKED on the account-binding ruling - no apply, no consumer work, no further rehearsal. audit-general-ca has correctly declined to issue a diff verdict under the hold and will review the exact surviving artifact only if the capability is authorized. If Elazar rules (b), (c) or (d), this blob is superseded and its rehearsal evidence is void; only (a) ships it as-is.
-
INVARIANT NOTE (ENAMEL-73, before implementation): 1. Path: support bearer -> /api/soporte/terra email correction -> postgres pool -> fn_supportCorrectTerraUserEmail -> fn_correctUserEmail; the base callee remains directly executable by postgres/service_role but has no public route. 2. Actor/target clauses: pin the service actor and its single live bedel@terra grant; require a live non-admin/non-client Terra target. Caller controls the bearer, target UUID and requested new address; weakening these clauses widens tenant or privileged-account reach. 3. Approval clause: a trusted server-resolved fact must name this exact target UUID AND canonical new address, be unavailable for caller self-assertion, and be checked/consumed in the same transaction as the correction. 4. Removing approval or binding it only to a generic email change lets the bearer repoint an unbound profile's emailCanonical so the chosen mailbox binds at next Google sign-in; fixed appEvents attribution cannot undo that takeover.
-
PM decision tag pmmaster-evolutiva-cc-muw7dm3wspv4 selects a trusted approval fact for exact target UUID plus exact canonical new address, checked/consumed in the correction transaction; the prior human risk-acceptance option is rejected. Class-A design ping before implementation; no DDL applied. Four-line invariant note recorded on this row.
-
DECIDED by pmmaster-evolutiva-cc 2026-10-06 (tag pmmaster-evolutiva-cc-muw7dm3wspv4), and explicitly does NOT need Elazar - this is not the human risk ruling. Option chosen: carry the trusted exact-target-plus-new-address approval fact in the SAME write transaction. The four options previously parked on this row are closed by that. Reasoning to carry into implementation: the audit BLOCK (audit-general-ca-muvlklpoci7u) is an account-takeover shape, not hygiene - a support bearer plus a target uuid can set an arbitrary new email on an unbound Terra profile, and bindSupabaseAuthId resolves an unbound profile by emailCanonical alone, so the write chooses which identity later binds. A fixed appEvents reason proves who acted, never that this target was approved for this address; attribution after the fact is not a control over the write. An operational control plus a risk acceptance leaves the capability intact and moves the defence into a human process - the weaker option and the one that cannot be tested. Conditions, all binding: Class-A throughout (audit design-ping before implementing, diff review before pushing, post-push log read). Write the 4-line INVARIANT NOTE FIRST - callers and paths reaching the function, what each clause is load-bearing FOR, which inputs are attacker-controlled, what flipping a clause un-contains. The approval fact must be server-resolved and bound to BOTH the exact target AND the exact new address; a token approving an email change generally re-opens the same hole under new wording. Force it red: valid bearer + valid target + an address the approval does not cover must be REFUSED, demonstrated against the pre-change behaviour, before done. enamel_079 (blob a60c89a3) is SUPERSEDED by this ruling, not amended - it implements the privileged-target denial, not an approval fact, so its rehearsal evidence does not transfer. A revised migration needs its own GO. HOLD: not started. pmmaster GO muw71ozj5w1q (report-and-stop, no new scope this session) applies to this row as to ENAMEL-75. Unblocks on the next coder-enamel-cc session; first step is the INVARIANT NOTE plus the audit design-ping, and the approval-fact primitive does not exist yet, so step zero is verifying some call can return it.
-
PM chose same-transaction exact-target+canonical-new-address approval fact (tag pmmaster-evolutiva-cc-muw7dm3wspv4); ENAMEL-73 unblocked to inProgress for design only. Four-line invariant note recorded first. No existing attested-address store exists; audit design ping sent under tag db-enamel-ca-muw7ffb9w1fp proposing one-use approval issued by a separately authenticated human admin path, exact target/address, consumed under row lock in correction transaction. Explicitly disclosed that UI/support both use postgres pool, so current role grants do not separate those paths. Awaiting audit design verdict before any SQL edit; 4d7fea0 remains unapplied.
-
Class-A design BLOCK audit-general-ca-muw7hd5jsclj: no trusted authoritative source yet linking each target users.id to proposed canonical mailbox plus proof of control. Same postgres pool makes approval-table ACL insufficient; do not apply enamel_079. Assigned db-enamel-ca continues evidence-source design.
-
Audit design BLOCK audit-general-ca-muw7hd5jsclj: one-use approval row alone can launder bearer-supplied address. Source census: Terra has private 2026-08-05 intake workbooks with SHA256 and normalized DNI/email (/terra/db/imports/2026-08-05-implantologia-users.md), but no current general target+new-address attestation source. accessIssueReports.supabaseAuthId is GoTrue-session-bound via fn_reportAccessIssue auth.uid check; its sessionEmailArg is caller-supplied and report resolution is bedel-reachable, so neither is independently sufficient. Candidate would need source DNI-to-target match, authoritative current email, and fresh server-resolved verified Google mailbox, then one-use admin issuance/transactional consume. No SQL change/apply.
-
PM decision pmmaster-evolutiva-cc-muw7n8abn0zc: support gets NO mutation of an existing unbound profile's email. The approved safe path is the person's own Google sign-in at the address already recorded on that profile, allowing bindSupabaseAuthId to match server-resolved emailCanonical. Support may send/resend a request, not set the address. Existing draft enamel_079 is superseded and remains unapplied. Hold the row until bind-rail evidence is reviewed; no approval issuer/table is to be built.
-
Retire support email rewrite; verify Terra self-bind path
-
PM decision removes support email mutation (tag muw7n8abn0zc); enamel_079 must not apply. Historical bind rail succeeded, but post-correction sign-in for the eight listed accounts is unexercised. Await db-terra-ca per-person readback / catedra sign-in retry before closing; assigned db-enamel-ca, not refused.
-
172d4c67d0ca: removed unapplied enamel_079_support_correct_terra_user_email.sql from origin/main after PM withdrew support's existing-profile email mutation. No DB DDL/apply. Scoped gitpush.sh v5.5; pre-push eslint passed, QUICK=1 skipped tests per task rule. Live aggregate before removal: 55 terra supabaseAuthIdBound outcome=bound, latest 2026-10-02 11:41:09Z; 51 signInCompleted authorized+bound, latest 11:41:10Z; all signInCompleted latest 2026-10-04 17:20:31Z. Await post-correction sign-in evidence from db-terra-ca.
-
PM revised decision muw7oqzbipz3 correctly separates self-bind for already-matching email from wrong-stored-email repair, but source check falsifies its stated existing admin UI: zero pickUserId/allowEmailChange symbols across Enamel/Terra; Enamel usuarios/actions.ts updateUser explicitly excludes email, and Terra admin user UI is read/grant only. fn_correctUserEmail is present for individually authorized DB correction, not invoked by an authenticated admin UI. PM and audit notified tags db-enamel-ca-muw7qoyxsnjb / db-enamel-ca-muw7qsq02rez. Do not claim UI repair exists or route users to it.
-
PM removed support-bearer email mutation; enamel_079 deleted at 172d4c6. Self-bind only works when stored email already matches Google sign-in. Wrong-stored-email repair UI claimed in muw7oqzbipz3 is absent from Enamel/Terra code; fn_correctUserEmail exists only as individually authorized DB path. Await PM exact-path correction or approved new admin surface; audit review pending. Assigned db-enamel-ca, not refused.
-
bedc072158f1: updated shared/md/enamel-support-api-ops.md with live narrow Terra create route, strong-match outcomes, withdrawal of support email mutation, current manual DB correction path, and explicit absence of a pickUserId/allowEmailChange admin UI. Source-based documentation only, no DB DDL/apply. gitpush.sh v5.5, eslint hook passed; no tests run.
-
Withdraw support bearer email rewrite
-
Final PM decision pmmaster-evolutiva-cc-muw7q9942ki7: remediation is the withdrawal at 172d4c67d0ca; no reusable bearer email rewrite. Audit carve-out verbatim: self-bind does not serve an unbound profile whose stored email is wrong; that case is unsupported pending separately authorized human repair; CREATE is limited to genuinely absent people with strong-match duplicate refusal. The three report-linked corrected rows and the wider eight-profile cohort require a later Google sign-in, but that is a Terra watch item, not a condition of this withdrawal.
-
cleared: PM removed support-bearer email mutation; enamel_079 deleted at 172d4c6. Self-bind only works when stored email already matches Google sign-in. Wrong-stored-email repair UI claimed in muw7oqzbipz3 is absent from Enamel/Terra code; fn_correctUserEmail exists only as individually authorized DB path. Await PM exact-path correction or approved new admin surface; audit review pending. Assigned db-enamel-ca, not refused.
-
Done: unapplied enamel_079 withdrawn from origin/main at 172d4c67d0ca, no DB DDL; runbook corrected at bedc072158f1. Support bearer has no existing-profile email mutation. Audit carve-out recorded verbatim on decision event. Bind follow-up moved to TERRA-274.
-
PM final close pmmaster-evolutiva-cc-muw7qvxy8lsu: no support email mutation (enamel_079 withdrawn at 172d4c67d0ca); CREATE only for genuinely absent people with strong-match duplicate refusal; wrong-stored-email profiles explicitly unsupported by self-bind. Repair label verbatim: separately authorized manual process, source path UNIDENTIFIED. No pickUserId/allowEmailChange UI exists in either inspected app tree. Do not upgrade that label from a userEmailCorrected emitter alone; a writer function is not an authenticated user-facing repair route. Cohort next sign-in watch is on db-terra-ca's TERRA-274, not a condition of this completed withdrawal.
-
Audit design PASS audit-general-ca-muw7r0pmt0ax: 172d4c6 deletes enamel_079 and it is absent from tree; removing support-bearer email mutation and any approval issuer/table closes the account-takeover BLOCK. PASS is limited to no-rewrite scope. Wrong-stored-email existing profiles are not repaired by self-bind; CREATE may not duplicate a strong existing-person match; any future human email correction is separate authorized/reviewed action. Historical 55 binds do not prove these users' fresh sign-in; per-person supabaseAuthId readback remains unexercised on TERRA-274. No DB apply/source implementation GO was issued by this verdict.
-
PM clarification pmmaster-evolutiva-cc-muw7r6gzmsxi supersedes the earlier UNIDENTIFIED label: the separately authorized manual repair path is fn_correctUserEmail run by a DB-credentialed human; no admin UI calls it. It accounts for all nine userEmailCorrected events: one TERRA-104 manual correction and eight TERRA-261/266 DB function corrections. Support bearer holds no DB credential and cannot invoke this path. The 172d4c67 withdrawal and audit no-rewrite PASS remain the completed remediation. Scaling this manual queue is separate p2 work.
-
Correction to my earlier note on this row: it recorded pmmaster muw7dm3wspv4 (approval fact carried in the same write transaction) as the decision. That is SUPERSEDED by pmmaster muw7r6gzmsxi, which db-enamel-ca applied when closing - the support bearer gets no mutation path at all, and correction is a separately authorized fn_correctUserEmail run by a DB-credentialed human with no admin UI caller. Read the closing record, not my earlier note. The in-transaction approval fact was not merely replaced by preference: the read-only census found the primitive it required does not exist (no attested target-address approval store, no issuance call; correctionRequests is a clinical-record shell), so the predicate had nothing to stand on. Removing the capability was the stronger answer. Residue is throughput, not safety - ENAMEL-76.
-
857d6c43f011: completed PM-required runbook addendum in shared/md/enamel-support-api-ops.md. It now states fn_correctUserEmail SECURITY INVOKER and EXECUTE postgres/service_role only (named RO roles cannot run it), performedBy is self-asserted text corroborating but not proving authorship, and no admin UI exists so future wrong-stored-email cases route to the authorized Enamel DB lane. Scoped gitpush.sh v5.5; eslint hook passed, no tests run. ENAMEL-73 remains done; ENAMEL-76 p2 owns scalable surface.
-
b29c718e55bf: pushed final runbook wording in shared/md/enamel-support-api-ops.md. Identifies fn_correctUserEmail from enamel_048 as human-operated at the DB, with no product or admin UI caller and never built as a product path. This supersedes the stale UNIDENTIFIED label; no new correction authorized and ENAMEL-73 remains done.