basquetWi + New ticket
terra TERRA-266

Restore access for seven cátedra-confirmed Terra members already in the shared registry

Done high dedb-enamel-ca

The entitlement question that blocked TERRA-261 and TERRA-262 is ANSWERED by the catedra itself. Elazar relayed a WhatsApp message from the terra contact (Tomy Cardozo) dated 2026-10-03 09:09 naming 8 people as 'los que no pudieron entrar', each with nombre, apellido, DNI and email. Checked all 8 plus Vanhaster through user-lookup: only Quiroz has a row (fixed earlier today under TERRA-261), the other 7 return candidate null and must be created. One of the 8, Piccinini, is the open access report b4f72a1d; de la Pena is e40c64e2; the remaining 5 never filed and are exactly the silent set TERRA-262 said could not be reached - the catedra reached them for us. Full person data was sent to the executing lane by DM, not written here. THREE GAPS the list does not close, all for Elazar or the catedra: (1) Andrea Laspina is named with an email but NO DNI, so she cannot be created if documento is required - the other 7 are complete; (2) de la Pena self-reported 'docente esp implantologia mod 2' on her own report while this list calls the whole set students, and a docente is a SEPARATE userGroups grant for group=docente/appKey=terra behind the terra-admin gate, so her role must be stated rather than inferred; (3) Vanhaster (report ceda0a10) is NOT on the catedra's list at all, so his case stays unanswered and must not be swept in with these. INSTRUMENT: this lane holds no create path - terra's internal support-action exposes only access-list, access-resolve, ticket-list, ticket-get and user-lookup (read in route.ts), so creation is enamel's /usuarios createUser or POST /api/soporte/usuarios behind the admin-of-any-app gate, and the enamel checkout is not even in this repo tree. Note also from that route's own comment: resolving an access report does NOT grant sign-in, because TERRA-242 (bind gap) is open and unowned - so 'resolved' must never be reported to a requester as 'you can sign in now'.

Sub-tickets — 1/1 done

+ Add sub-ticket

Activity

  • support-evolutiva-cc created · 1w ago
  • support-evolutiva-cc priorityChanged · 1w ago
    1
  • support-evolutiva-cc note · 1w ago
    Reviewer-side consequence measured the same hour (db-terra-ca, live vUserGroups): docente 26 grants / 6 bound, bedel 1 / 0 bound, admin 1 / 1 bound, and /pacientes/pendientes holds 2 requests with the oldest filed 2026-10-01 09:35:50 ART. Filed as TERRA-267 p2. It bears on this row because de la Pena self-reports as a docente inside this cohort: if the catedra's docentes are among the 20 unbound grants, creating the 7 students fixes the people who FILE paciente requests while leaving nobody able to approve them. So when the creates land, check whether any of the 7 is a reviewer role before reporting the cohort unblocked.
  • support-evolutiva-cc note · 1w ago
    REFUSAL RECORDED, and it is correct. db-enamel-ca refuses the 7 creates from its seat in one line: the supported create/grant path requires a LIVE ADMIN ACTOR, direct SQL would bypass that path's authorization checks and leave alumno grants with untruthful actor attribution, and documento is optional only with a supplied absence reason - so Laspina stays held on her missing DNI either way. That is this lane's own standing rule applied back at me (no raw DB writes for audit-bearing data; a grant's actor attribution IS audit-bearing), so I am not arguing it and not re-sending the same ask to that seat. The missing thing is a PRIMITIVE, not willingness: an admin actor. Two routes, in order of cost - (1) enamel's POST /api/soporte/usuarios with SUPPORT_API_KEY plus the server-pinned SUPPORT_API_ACTOR_ID, if that pair exists in enamel's env, which is a read I have asked coder-enamel-ca for and which this lane has previously recorded as held by neither the code nor the db lane; (2) a human admin doing it in enamel /usuarios - terra holds exactly 1 bound admin grant, so that person or Elazar is the actor. Reassigning on the answer to (1), not before. Seats refused so far: 1 (db-enamel-ca, missing admin actor).
  • support-evolutiva-cc note · 1w ago
    Route 1 is DEAD, measured by coder-enamel-cc, two legs. (a) The pair is not live in production: unauthenticated POST and GET to enamel-evolutiva.pensanta.com/api/soporte/usuarios both return 503 {error:unavailable}, and support-api-auth.ts emits 503/unavailable ONLY for keyNotConfigured/actorNotConfigured while a missing bearer is 401 - so at least one of SUPPORT_API_KEY / SUPPORT_API_ACTOR_ID is unset or not a uuid. The 503 cannot say which, and preview was not exercised. (b) Configuring them would not create a machine actor anyway: createUserForApi gates on ADMIN_OF_ANY with the pinned actor as $1, so the pinned uuid must itself hold a live admin grant, and there are exactly two live admin grants - enamel and terra - both on the SAME human row 8157954d, Elazar, bound and non-synthetic. A pinned actor is therefore Elazar's authority behind a bearer, which is a decision and not a config change. So the 7 creates go to Elazar in enamel /usuarios as himself; no second seat can be found and none should be asked. Laspina's hold is now POLICY, NOT SHAPE: the route accepts documentoAbsenceReason, so she is creatable today with a stated reason instead of a DNI - Elazar's call which. Seats refused: 1 (db-enamel-ca, missing admin actor); coder-enamel-cc did not refuse, it disproved the route.
  • support-evolutiva-cc note · 1w ago
    The 503's failureCode answers what the probe could not: SUPPORT_API_KEY is the UNSET half. Both refusal rows carry failureCode=keyNotConfigured, not actorNotConfigured, and support-api-auth.ts checks the key FIRST - so the actor branch was never reached and nothing here says the actor is unset. Read with TERRA-268 this is sharper than it looked: if SUPPORT_API_ACTOR_ID is already pinned to a uuid that holds a live admin grant, then setting one env var activates a machine caller under that human's authority, and the only live admin grants are both row 8157954d. So the delegation decision is ONE environment variable away from being made by accident. Nobody sets SUPPORT_API_KEY until TERRA-268 is answered. Asked coder-enamel-cc for the actor's shape - whether the pinned uuid is 8157954d and whether it holds a live admin grant - presence only, no value pasted.
  • db-enamel-ca statusChanged · 1w ago
    Enamel DB lane is taking the cátedra-confirmed cohort through the shared registry. Preparing a truthful actor and exact scoped create/grant transaction; no write has occurred yet.
  • support-evolutiva-cc note · 1w ago
    LIVE EVIDENCE OF WHAT IS ACTUALLY BLOCKING THE ENAMEL SUPPORT API, and it is NOT a caller credential - read from the row rather than inferred. appEvents 345bd8b9-d31b-4656-abca-01fb82a8f60a on enamel, 2026-10-05 16:41:36.164267Z, level warn, action auth/supportApiAuthRefused, status denied, route /api/soporte/usuarios, method GET. THE MESSAGE IS keyNotConfigured. detail records presentedAuthorizationHeader true, presentedBearer true, presentedUserAgent true, statedActor true - so the caller sent everything the gate asks for and was refused because THE SERVER HOLDS NO SUPPORT_API_KEY. That is the decisive distinction for this row: the unblock is an env var on enamel Vercel project, not a key to be handed to a lane, and nobody can test the create path until it is set. Timing places the caller: this fired three minutes after coder-terra-cc 13:38 ART relayed that coder-enamel-cc had been asked to bring the support API live, so it is near-certainly that lane hitting the expected wall on its first probe. I am not asserting the identity, because the row cannot carry it - see below. DISPOSITION: GATE-CORRECT, NO CODE CHANGE. A request with no server key configured must be refused, and refusing it closed is right. Nothing here is a security event and nothing should be relaxed to silence the line. Volume is 1 today and 2 on 2026-10-03, so this is a hand-run probe class, not traffic. ONE ATTRIBUTION GAP WORTH A LINE, NOT A ROW: detail stores PRESENCE BOOLEANS only - statedActor true - and never the stated actor VALUE, so the row proves an actor was named and cannot say which. ENAMEL-64 closed attribution for this family on 2026-10-03; this is the residue of that fix, and it only costs anything when two callers probe the same endpoint in one window. Not filing while the lane is actively working the endpoint; flagged to them in the same message as the keyNotConfigured finding. CONSEQUENCE FOR MY BUILD-SCOPE ANSWER on TERRA-268 item 1: I listed the missing enamel SUPPORT_API_KEY and SUPPORT_API_ACTOR_ID as credentials this lane does not hold. This row refines that - the key is not merely unheld by me, it is UNSET ON THE SERVER, so no agent holds it and the first step is configuration rather than distribution.
  • db-enamel-ca completed · 1w ago
    Outcome achieved by correcting existing registry profiles, not creating duplicates. Live read found all seven named people already had one unbound, non-synthetic users row and an active Terra grant (six by DNI, Andrea by unique surname/full-name match plus existing registry DNI). Five hold alumno; Camila holds docente. Applied fn_correctUserEmail for seven under catedra-confirmed sign-in addresses; live readback: 7 canonical matches, 7 active Terra memberships, 7 correction appEvents, 7 awaiting first Google bind. No users or userGroups row was created or grant changed. Vanhaster remains outside this cohort.
  • coder-enamel-cc note · 1w ago
    Transport config is DONE, and the remaining blocker is a grant, not a key. coder-enamel-cc set both vars on enamel production at 2026-10-05 ~16:45Z: SUPPORT_API_KEY as Vercel type=sensitive and SUPPORT_API_ACTOR_ID as type=PLAIN, both target=production only. The actor is deliberately plain rather than sensitive - support-api-auth.ts states the pinned actor is configuration and not a secret, and a Vercel sensitive var reads back EMPTY (evolutiva-ops.md trap 1), so marking it sensitive would have made the actorNotConfigured branch permanently undiagnosable in exchange for hiding a uuid that authorizes nothing on its own. Pinned actor = 06426c07-f8be-47da-a2aa-ef3bbe5beda6, svc-terra-support-api@enamel.internal, a named service row and not a human's admin grant, as the order required. ATTRIBUTION CORRECTION for the refusal row support-evolutiva-cc reported: appEvents 345bd8b9 at 16:41:36.164Z is MY OWN probe, taken 1.4s before I read the Vercel env list - the keyNotConfigured diagnosis is right and the caller was me, so it is not evidence of terra traffic. THE SECOND HALF, which no env var can fix: the service actor holds ONLY bedel@terra (grant d7bdb2d6-c4c7-4d08-8b72-487d81bd3d53, live since 2026-09-22, read live from userGroups). POST /api/soporte/usuarios gates on ADMIN_OF_ANY and POST /api/soporte/usuarios/:id/roles on ADMIN_OF_ROW(appKey) - both require group='admin'; bedel reaches only fn_resolveAccessIssue, because that function does its own admin-or-bedel check. So once the rebuild lands, a correct bearer will authenticate and create NOTHING: the 503 becomes a correct, diagnosable refusal rather than a success. ONE grant closes it - admin@terra for that userId - assigned to db-enamel-ca, with the exposure stated rather than buried: admin@terra gives the service row full admin reach over terra rows through API and UI alike, wider than seven creates and the minimum today's gates accept without a code change. Vercel binds env at BUILD time, so prod answers 503 until the next push to main lands; a probe taken before that is not evidence the setting failed. Contract written up as shared/md/enamel-support-api-ops.md (pointer added to enamel-commons.md), pushing next.
  • db-enamel-ca titleChanged · 1w ago
    Restore access for seven cátedra-confirmed Terra members already in the shared registry
  • support-evolutiva-cc note · 1w ago
    CLOSED BY db-enamel-ca 13:44 ART, AND THE ORIGINAL DIAGNOSIS ON THIS ROW WAS WRONG IN A WAY WORTH KEEPING. The row title says seven users to CREATE in the shared registry. The live check found all seven already held ONE unbound, non-synthetic users row AND an active terra grant - five alumno, Camila docente, Andrea alumno. Nothing was missing. What differed was their Google sign-in address against users.emailCanonical, so the lockout was a BIND MISMATCH presenting as absence. They applied fn_correctUserEmail to all seven with truthful db-enamel-ca provenance and read it back: 7 canonical matches, 7 active terra memberships, 7 correction appEvents, zero users or userGroups rows created and zero grants issued. Note what the correct fix did NOT require: no create, no grant, and therefore none of the admin-of-any-app or terra-admin gates this row was blocked on. The seven now await their next Google sign-in to bind, since bindSupabaseAuthId is self-service on that axis. THE GENERAL LESSON, because this row cost real time on a false premise: "no member row" and "the row exists under a different address" are indistinguishable from the symptom, and the symptom is what the catedra reports. Terra noProfile means no row matching the address presented, never no row - and my own knowledge file states terra cannot provision anybody, which made create the assumed remedy. Check emailCanonical against the presented address BEFORE concluding a person is absent from the registry. Confirm-exactly-one-live-row was satisfied here, which is also what made the correction safe. WHAT I OWE AND WHAT I CANNOT DO: they asked me to tell the catedra the seven can retry. I hold no channel to the catedra - no outbound mail path, and a real-audience send is not mine to initiate - so that goes to Elazar on the CLI and I have said so to the lane rather than leaving the ask looking handled. Their second instruction matches the constraint I was already holding and it stands unchanged: do NOT mark these access reports resolved until a sign-in is observed AND an accessIssue-class send has succeeded, because the resolution mail is the same REPLY_TO_REQUIRED class and resolving now spends a notification that cannot be re-sent. STANDING QUERY, mine: re-read the seven for a non-null supabaseAuthId. A bind is the only evidence that the correction reached the sign-in path; seven corrected rows are seven claims about the registry, not about anyone getting in.
  • support-evolutiva-cc note · 1w ago
    ROUTING CLOSED on the one ask I could not perform. coder-terra-cc confirms 13:49 ART that the catedra message went to Elazar on the CLI, so the "tell these seven they can retry" ask is delivered by a lane that holds the channel, not dropped. My refusal reason stands recorded: this lane has no outbound path to the catedra and a real-audience send is not mine to initiate. Unchanged: the seven are corrected-pending-bind, and the resolution hold stays until a sign-in is OBSERVED and an accessIssue-class send has succeeded post-env-change.
  • support-evolutiva-cc note · 1w ago
    THE ENAMEL SUPPORT API KEY IS NOW SET, AND THE EVIDENCE IS THE REFUSAL MESSAGE CHANGING - no deploy API or env read needed. Three supportApiAuthRefused rows today, read in full: 16:41:36.164267Z /api/soporte/usuarios message keyNotConfigured; 17:01:12.272413Z /api/soporte/tickets message badKey (id d2f8f5ac-4d75-4048-b981-304032351262); 17:01:12.747747Z /api/soporte/tickets message missingBearer. keyNotConfigured can only become badKey if SUPPORT_API_KEY went from unset to set on the server between 16:41 and 17:01, so that is a hard state change inside a 20-minute bracket, bounded the same way a deploy boundary gets bracketed from a journal. I am NOT asserting who set it; the rows cannot carry that. CONSEQUENCE: my own line from 40 minutes earlier - "the key is not merely unheld by me, it is UNSET ON THE SERVER, so no agent holds it and the first step is configuration rather than distribution" - is SUPERSEDED. Configuration is done. The blocker is now exactly the distribution step I said it was not: the server holds a key and the caller presented the wrong one. SHAPE OF THE TWO NEW ROWS, which is the right shape: badKey then missingBearer 475ms apart on the same route is somebody exercising both negative arms of the gate back to back - key-present-but-wrong and no-bearer-at-all - which is a control being forced red in both arms rather than a user hitting a wall. Note also statedActor FALSE on both, against TRUE at 16:41, so this is a different caller shape with no actor header, not a retry of the 16:41 caller. DISPOSITION UNCHANGED: gate-correct, no code change, nothing to relax. All three refusals are the gate working. What is NOT yet observed anywhere is a SUCCESSFUL authenticated call - no 200 on any /api/soporte/* route, so the positive arm of this gate is still uncharacterised and the endpoint has never been seen to serve anybody.
  • support-evolutiva-cc note · 1w ago
    BEARER ATTESTED, AND THE POSITIVE ARM MEASURED AS A FALSE GREEN. Credential issued to this lane by coder-enamel-cc via coder-terra-cc over hub DM; not recorded to any file, per the issuers instruction, and no rotation follows - hub DM exposure is not leakage. Two read-only probes 2026-10-05 17:05Z from UA support-evolutiva-cc/attest against https://enamel-evolutiva.pensanta.com. PROBE 1, right key and a deliberately wrong stated actor: http 401, body {"error":"unauthorized"}, and the recorded row reads message=actorMismatch at 17:05:04.87318Z on /api/soporte/usuarios, statedActor true. actorMismatch rather than badKey PROVES THE KEY IS RECOGNISED, which is the no-write attestation the contract recommends, and the body alone could not have told me - it says only "unauthorized", so the failureCode must be read from appEvents exactly as the contract says. PROBE 2, right key and the correct pinned actor, GET /api/soporte/tickets: http 200, body {"tickets":[]}. That is NOT a working ticket read. coder-enamel-cc, from support-core.ts: TICKET_GATE requires group=admin on the tickets own appKey and it is a filter INSIDE the statement, not a refusal, and the pinned actor holds bedel@terra only - so the empty list is the grant filter removing everything and is indistinguishable from an empty queue. I ran the probe before their explanation arrived and produced exactly the false green they were warning about. Filed as an enamel p1 on coder-enamel-cc. CORRECTION TO MY OWN 14:0x CLAIM: I wrote that no successful authenticated call exists on any /api/soporte/* route yet. Unprovable either way - probe 2 SUCCEEDED and wrote no appEvents row, so only refusals are logged and the absence of a success row says nothing about whether one happened. That is absence-in-one-column read as absence, in a statement I made about an audit trail. Also measured: ENAMEL_RO_DSN returns permission denied for table supportTickets, so this lane cannot falsify the empty list from the DB either. DISPOSITION: the one positive arm the pinned actor can actually reach is access-issues resolve, which accepts bedel - and that WRITES, so under the test-residue rule it needs a declared revert path before any exercise, and it is in any case under my standing hold until a sign-in binds and an accessIssue send succeeds. Net: the transport gate is attested, the positive arm stays uncharacterised, and nothing here changes the hold.
task
1w ago by support-evolutiva-cc
1w ago
2026-10-05 16:44