EVO-83 central build: client CR + questions on the extended supportTickets queue, portal UI, and the appKey-VALIDATED SECURITY DEFINER RPC - the only EVO-83 leg still outstanding
THE ENAMEL LEG OF EVO-83, AND THE ONLY LEG STILL OUTSTANDING. Elazar ruled the architecture on 2026-07-31, verbatim: '2 yes central'. Store in enamel's DB, appKey-scoped rows, ONE portal UI; clinical apps carry a link and nothing else; per-app copies are OFF, his cited precedent being the per-project SOP ticket copy - 'have to maintain 3 things that should work the same'. Enamel is the OWNER and BUILDS. The other two legs are done: TERRA-1 (row usable plus link, no authoring surfaces) and VENUS-317 (client role slice plus nav link). EVO-83's 2026-08-19 unblock event states it plainly - both legs done, only enamel's ownership and build remain. It has had no coder since, which is the entire reason this has sat 23 days. He asked for it 'pronto' and said 'i need venus and terra to use this within the next few hours'; that was 2026-07-31. READ EVO-83'S EVENT LOG, NOT ITS DESCRIPTION - the description still carries the 2026-07-30 framing where the client principal is open and each app builds its own surface, and four Elazar decisions on 2026-07-31 supersede it. I filed a terra copy against that stale description on 2026-09-12 and coder03-terra-cc caught it before any DDL; do not repeat it from the same source. THIS DESCRIPTION WAS REWRITTEN 2026-09-12 01:0x BY pmmaster-evolutiva-cc. Three blocks below are CORRECTIONS of text that stood here until now and that audit-terra-cc, db-enamel-cc and coder02-enamel-cc each tripped over within nine minutes of a superseding ruling. The prior text is preserved in this item's event log. If you are reading a claim here that a live measurement contradicts, the measurement wins and you file the correction here, not only in an event. SETTLED, DO NOT RELITIGATE: principal is a DEDICATED group value spelled 'client' (Elazar confirmed the spelling directly; public.groups already carries admin/alumno/bedel/client/docente/jtp and terra already uses it), carrying CR and questions authoring and NOTHING ELSE - never a repurposed titular or docente role, never a native enum, never a second users-shaped table, never a second stored email. 'The Director is simply an admin' is explicitly barred as a modelling shortcut: it is a permission grant, not a convenience, and anything added to the admin gate later rides along. TABLE SHAPE - OVERTURNED 2026-09-12, EXTEND supportTickets. The prior text here read 'TWO TABLES, NOT ONE' and 'Do NOT extend supportTickets'. Both are WITHDRAWN. Full reasoning in the 00:28 note event; the short form is that both were MY constraints and coder-terra-cc's, recorded under LEG SCOPES in EVO-83's 2026-07-31 event, never Elazar's words, and db-enamel-cc measured that supportTickets ALREADY ships this across six applied migrations with supportTicketCategoryStatuses carrying the exact terminal sets the ruling specified. A prohibition is a STATE CLAIM whenever its force depends on what the tree currently contains. Do NOT create changeRequests/clientQuestions tables and do NOT retire the six applied migrations. The 2026-07-30 triage-collision objection is answered one level up by separate resolver RPCs (resolveScopedSupportTicketsClientQueue / resolveScopedSupportTicketsStaffQueue), and the binding condition on that is unchanged: demonstrate the separation at the DIGEST and NOTIFICATION level, not only the query level. STATUS AND TERMINALITY - CORRECTED. The prior text said 'terminality via lookupOptions.params.terminal exactly as supportTicketStatus does'. lookupOptions HAS NO params COLUMN; coder02-enamel-cc and audit-terra-cc both verified this live. Terminality lives in supportTicketCategoryStatuses.isTerminal, which is the only deployed mechanism there has ever been, and supportTicketCategoryStatuses has zero lookupOptions dependency. A ruling cannot be satisfied by building what it names when what it names is not buildable as written. No enums in DB or TS still holds. Soft-delete plus vActive* view. updatedAt maintained by a BEFORE UPDATE trigger FROM THE FIRST MIGRATION. Mutation-wrapper plus friction telemetry. English camelCase identifiers, Spanish UI copy. Portal route placement is decided internally and reported, not escalated. APPKEY SCOPING FROM THE FIRST LINE, not a later hardening pass: enamel shares a Supabase project with terra and has never written an appEvents row, so every unscoped query is positively reinforced right now and enamel's first write retroactively falsifies them. THE GRANT SPEC IS BINDING AND IT IS THE CLASS-A SURFACE. No raw DB credentials to venus/mars/pluto lanes, ever. THE RPC - appKey IS A VALIDATED PARAMETER, RULED 2026-09-12, REVERSING THE LITERAL WORDING OF THE PRIOR TEXT. The prior text said the RPC 'MUST DERIVE THE CALLER'S appKey FROM THE AUTHENTICATED IDENTITY AND MUST NEVER ACCEPT IT AS A PARAMETER'. Derivation from userId alone is NOT POSSIBLE: db-enamel-cc measured that the one live client, user 6ebaffae-a195-459e-afe8-b89c3fd73fc0, holds group=client under BOTH terra AND venus, so a refuse-on-ambiguity derivation refuses the only real user and zero tickets are creatable on day one. That is not a bug in the derivation, it is the model - a client is a person-IN-AN-APP and a person can be a client of two apps, which is also exactly what a CENTRAL portal is for. THE PROPERTY THE OLD WORDING WAS PROTECTING IS NOT 'never a parameter', IT IS 'a parameter must never grant access the authenticated identity does not already hold'. So: the RPC ACCEPTS appKeyArg and REFUSES unless the authenticated caller holds a LIVE, NON-SYNTHETIC client membership under exactly that key in vUserGroups. Asserting an app you are not a client of still fails closed, so the escalation the old rule existed to stop is still stopped; what changes is only that the derivation no longer has to be unambiguous on userId alone. Trusted precedent in this ref is bindSupabaseAuthId(text,uuid,text), which is DEFINER and takes appKey as a literal first param. The prior citation 'same shape as resolveOrCreatePaciente and bindSupabaseAuthId' was WRONG on the first half and is withdrawn: resolveOrCreatePacienteForApp is prosecdef FALSE, not DEFINER at all. Three-way revoke is UNCHANGED: REVOKE ALL FROM PUBLIC, anon and authenticated, then GRANT to the intended caller role only. THE MUST-FAIL FIXTURE, RESPECIFIED so it is forcible. The old arm ('app A presenting app B's userId is REFUSED') is unbuildable from synthetic data because vUserGroups filters isSynthetic IS FALSE on both userGroups and users. The NEGATIVE arm needs no synthetic data and must be forced red before acceptance: authenticate as the live client and assert an appKey that identity holds NO client membership under (mars or pluto) - expect REFUSED. The POSITIVE arm needs real rows and runs inside the enamel lane's own transactional test path with rollback, never as an ad-hoc psql write by any agent. coder02's fixture (2) is VACUOUS and is dropped: vActive/vUserGroups already filter toAt IS NULL and deletedAt IS NULL on all three joined relations, so an expired-or-soft-deleted membership returns zero rows - the identical input and branch as fixture (1), not a second arm. The genuinely untested branch is AMBIGUITY, which is the live case, and the validated-parameter design is what resolves it. A fixture that has never been seen to fail is a green line, not a control. FAIL-OPEN ON fn_supportTicketsStatusMatrix: FOLD IT IN. audit asking for the tightening in the same file IS terra's consent to it; my ask was not broader than that. PRIORITY COLUMN, RULED 2026-09-12: if supportTickets ALREADY carries priority, it is in v1 by inheritance and costs nothing - use it. If it does NOT, it is OUT of v1 and no column is added; refile it. Nobody escalates this again. THE CUT FOR A FIRST SHIP, agreed with pm-enamel-cc and stated to Elazar: store, write and read path, minimal portal route, and the RPC, with venus and terra linking. Triage workflow, digests, notification rails and status automation follow later. Binding reporting condition: report which of that cut is ALREADY satisfied by shipped structure versus what you actually build, so the remaining work is visible rather than absorbed. Class-A: audit design-ping before implementing, diff review before push, roster-check the auditor at ask time, and confirm 'admin' is not silently reachable from the new value. Definition of done is code pushed and working.
Activity
-
parent=EVO-83
-
Starting: reading EVO-83 event log + settled rulings before design. Next: draft design-ping (schema, RPC, portal route) for pre-implementation audit review.
-
RULING, pmmaster-evolutiva-cc 2026-09-12: EXTEND THE EXISTING supportTickets QUEUE. Do NOT create changeRequests/clientQuestions tables. Do NOT retire the six applied migrations. AUTHORITY, stated first because it decides whether I may rule at all: 'two tables, not one' and 'do NOT extend supportTickets' are MY constraints and coder-terra-cc's, recorded under LEG SCOPES in EVO-83's 2026-07-31 event. They are not Elazar's words. His verbatim ruling that day was '2 yes central. sebastian.puia@odontologia.uba.ar 3 Client ok. 4 grant it' - architecture central, address, spelling, grant. The table shape was never his. So revising it needs no escalation, and I am revising it. WHY THE CONSTRAINT DOES NOT SURVIVE CONTACT WITH THE SCHEMA. It had exactly two stated reasons and db-enamel-cc's measurement satisfies both. (1) 'Different terminal conditions', which was the whole argument for two tables: supportTicketCategoryStatuses already encodes terminal sets PER CATEGORY, 27 rows, and already carries the exact sets the ruling specified - changeRequest open[init] -> enAnalisis -> {aceptado, rechazado, implementado}[term], clientQuestion open[init] -> respondida[term]. The matrix is the mechanism that lets ONE table express different terminal conditions, so the premise 'different terminal conditions therefore different tables' is false here. (2) 'Triage collision' (coder-terra-cc, 2026-07-30) - the real objection, and stronger than the view-pollution version it replaced: terra shipped the first consumer of supportTickets for /sin-acceso access requests, and a client change-request must not land in the same staff queue as a user locked out of the app. That objection was written against VIEW FILTERING. The live design answers it one level up, with separate resolver RPCs - resolveScopedSupportTicketsClientQueue and resolveScopedSupportTicketsStaffQueue - which is a stronger separation than the one the objection asked for. WHAT SETTLES IT: building new tables creates a SECOND STORE and gives five status values TWO SSOTs, which is precisely the defect Elazar's central ruling exists to prevent - his own words, 'have to maintain 3 things that should work the same'. Rebuilding to satisfy a constraint whose reasons are already met would reproduce the per-project-copy failure inside a single app. ZERO ROWS IS NOT AN ARGUMENT FOR REBUILDING: it is true of every enamel table, so it distinguishes nothing. BINDING ACCEPTANCE CONDITIONS, because I am overruling a real objection rather than dismissing it: (a) demonstrate the triage separation at the DIGEST and NOTIFICATION level, not only the query level - the 2026-07-30 objection's exact words were that a digest mixing them is wrong even if every view filters correctly, and that remains true; show the accesoCuenta staff digest and the client CR queue are separable there, or make them so. (b) The appKey scoping and the SECURITY DEFINER RPC constraints are UNCHANGED and remain binding - the RPC derives appKey from the authenticated identity and never accepts it as a parameter, three-way revoke, must-fail fixture asserting app A with app B's userId is REFUSED, forced red before acceptance. (c) Report which of the v1 cut - store, write/read path, minimal portal route, RPC - is already satisfied by shipped structure versus what you actually build, so the remaining work is visible rather than absorbed. THE PATTERN, fourth instance by me tonight and the most expensive: 'do NOT extend supportTickets' reads as a design principle and is in fact a STATE CLAIM - it presumes supportTickets does not already do this. It did, across six applied migrations, seeded to this ruling's own terminal values. A prohibition is a state claim whenever its force depends on what the tree currently contains.
-
audit-terra-cc BLOCKED db-enamel-cc/coder02's supportTickets-extension design for ENAMEL-47: WI's own settled text (TWO TABLES NOT ONE, do not extend supportTickets; status+priority from lookupOptions groups) conflicts with the proposal, verified live that supportTicketCategoryStatuses has zero lookupOptions dependency and no CR/vActive table exists yet. RPC identity mechanism (callerUserIdArg trusted via 3-way REVOKE, appKey derived from vUserGroups) is sound and approved on its own merits. No DDL until table-shape is reconciled with pm.
-
audit-terra-cc: table-shape and lookupOptions blocks WITHDRAWN after independent verification (pmmaster's 00:28 event predates my 00:37 read of the stale description; lookupOptions has no params column, confirmed live - terminality correctly lives in supportTicketCategoryStatuses.isTerminal instead). RPC identity mechanism approved. Real open blocker per db-enamel-cc, unverified by me (no vUserGroups reach): the one live client userId holds group=client in both terra and venus, so appKey is not derivable from userId alone for the only real case - routed to pmmaster, not a DDL question.
-
was: THE ENAMEL LEG OF EVO-83, AND THE ONLY LEG STILL OUTSTANDING. Elazar ruled the architecture on 2026-07-31, verbatim: '2 yes central'. Store in enamel's DB, appKey-scoped rows, ONE portal UI; clinical apps carry a link and nothing else; per-app copies are OFF, his cited precedent being the per-project SOP ticket copy - 'have to maintain 3 things that should work the same'. Enamel is the OWNER and BUILDS. The other two legs are done: TERRA-1 (row usable plus link, no authoring surfaces) and VENUS-317 (client role slice plus nav link). EVO-83's 2026-08-19 unblock event states it plainly - both legs done, only enamel's ownership and build remain. It has had no coder since, which is the entire reason this has sat 23 days. He asked for it 'pronto' and said 'i need venus and terra to use this within the next few hours'; that was 2026-07-31. READ EVO-83'S EVENT LOG, NOT ITS DESCRIPTION. The description still carries the 2026-07-30 framing where the client principal is open and each app builds its own surface. Four Elazar decisions on 2026-07-31 supersede it. I filed a terra copy against that stale description on 2026-09-12 and coder03-terra-cc caught it before any DDL; do not repeat it from the same source. SETTLED, DO NOT RELITIGATE: principal is a DEDICATED group value spelled 'client' (Elazar confirmed the spelling directly; public.groups already carries admin/alumno/bedel/client/docente/jtp and terra already uses it), carrying CR and questions authoring and NOTHING ELSE - never a repurposed titular or docente role, never a native enum, never a second users-shaped table, never a second stored email. 'The Director is simply an admin' is explicitly barred as a modelling shortcut: it is a permission grant, not a convenience, and anything added to the admin gate later rides along. TWO TABLES, NOT ONE - change requests and client questions have OPPOSITE authorship direction and different terminal conditions (accepted/rejected/shipped versus answered). Do NOT extend supportTickets: terra shipped the first real consumer of it for /sin-acceso access requests, so folding CRs in puts client change-requests into the same staff queue as users locked out of the app - different urgency, different responders, wrong digest even if every view filters correctly. No enums in DB or TS; status and priority from lookupOptions groups with terminality via lookupOptions.params.terminal exactly as supportTicketStatus does, so a new status is an INSERT not a deploy. Soft-delete plus vActive* view. updatedAt maintained by a BEFORE UPDATE trigger FROM THE FIRST MIGRATION. Mutation-wrapper plus friction telemetry. English camelCase identifiers, Spanish UI copy. Portal route placement is decided internally and reported, not escalated. APPKEY SCOPING FROM THE FIRST LINE, not a later hardening pass: enamel shares a Supabase project with terra and has never written an appEvents row, so every unscoped query is positively reinforced right now and enamel's first write retroactively falsifies them. THE GRANT SPEC IS BINDING AND IT IS THE CLASS-A SURFACE. No raw DB credentials to venus/mars/pluto lanes, ever. A SECURITY DEFINER RPC with the caller-versus-owner split, same shape as resolveOrCreatePaciente and bindSupabaseAuthId. THE RPC MUST DERIVE THE CALLER'S appKey FROM THE AUTHENTICATED IDENTITY AND MUST NEVER ACCEPT IT AS A PARAMETER - a definer function runs with owner privileges, so a caller-supplied key lets any app write into a sibling's scope, and that passes every review that only checks the function exists. Three-way revoke: REVOKE ALL FROM PUBLIC, anon and authenticated, then GRANT to the intended caller role only. Ship a MUST-FAIL FIXTURE asserting that app A presenting app B's userId is REFUSED, and force it red before accepting it - a fixture that has never been seen to fail is a green line, not a control. If what reaches a calling lane accepts an appKey argument, that lane refuses to call it. THE CUT FOR A FIRST SHIP, agreed with pm-enamel-cc and stated to Elazar: store, write and read path, minimal portal route, and the RPC, with venus and terra linking. Triage workflow, digests, notification rails and status automation follow later. Class-A: audit design-ping before implementing, diff review before push, roster-check the auditor at ask time, and confirm 'admin' is not silently reachable from the new value. Definition of done is code pushed and working.
-
EVO-83 central build: client CR + questions on the extended supportTickets queue, portal UI, and the appKey-VALIDATED SECURITY DEFINER RPC - the only EVO-83 leg still outstanding
-
pmmaster ruled and unblocked ENAMEL-47 (21:41): appKey is a VALIDATED parameter (accepted + membership-checked, not derived-only), three-way revoke unchanged, fixture respecified against the live client (assert wrong app, expect REFUSED). Priority: verified live, supportTickets has NO priority column - out of v1 per pmmaster's conditional ruling. audit-terra-cc has no further block; design gate closed.
-
RULING 2026-09-12, pmmaster-evolutiva-cc: NO NEW SECURITY DEFINER POSTGRES FUNCTION. The v1 write path is the EXISTING lib/support-core.ts createTicket, reached through a NEW session-resolving server action. This supersedes 'the RPC' as a build item in the v1 cut; it does not supersede any of the security properties that spec was carrying, all of which are already satisfied. FOUND BY coder02-enamel-cc, and the finding matters more than the ruling: nobody had grepped app/src for an existing writer. Everyone searched DB migrations. That is the SAME BLIND SPOT that produced the supportTickets table error three hours ago - we looked in one layer and concluded the thing did not exist. Second instance in one night, same item, different layer. WHAT IS ALREADY LIVE: createTicket (ENAMEL-27, agent-operable via POST /api/soporte/tickets) already implements the validated-appKey-parameter gate I ruled onto the RPC tonight, in the same shape and - the part that makes it ACCEPTABLE rather than merely similar - IN-STATEMENT: insert ... select ... where exists (select 1 from vUserGroups where userId=$1 and group='client' and appKey=$2). An in-statement predicate is atomic, so there is no read-then-write TOCTOU window between the membership check and the row landing; a separate check followed by an insert would NOT have been equivalent and would not have been accepted. Same caps 200/5000, same origin-discriminator handling, audit-enamel-ca's findings already baked in (ownership predicate on lookupOptions, live+active checks). BUILDING A NEW DEFINER FUNCTION WOULD BE A SECOND IMPLEMENTATION OF A LIVE, AUDITED GATE - the identical two-SSOTs defect I ruled against for the table, one layer up, and the identical thing Elazar's central ruling exists to prevent ('have to maintain 3 things that should work the same'). WHY THE CROSS-LANE GRANT IS NOT NEEDED AT ALL, which is the load-bearing half: the RPC and its three-way revoke were specified to keep raw DB credentials out of the venus/mars/pluto lanes. Under Elazar's 2026-07-31 ruling those apps CARRY A LINK AND NOTHING ELSE - they never write. The client writes from the portal under their OWN browser session. So there is no cross-lane caller to grant to, and the Class-A surface is not a to-be-built grant, it is the existing gated INSERT. WHAT v1 ACTUALLY NEEDS: (1) wire isPortalPenCategory into the client-facing entrypoint to close categoryValue to (changeRequest, clientQuestion) - the vocabulary ALREADY EXISTS in app-keys.ts as PORTAL_PEN_CATEGORIES with Spanish labels; (2) a new browser-session server action in the shape of (protected)/soporte/actions.ts's resolveSessionUser -> core call, because createTicket today has exactly ONE live caller and it is the bearer API route, not a session. A DORMANT CONTROL DESCRIBED AS A LIVE ONE: isPortalPenCategory is referenced only inside a COMMENT in createTicket, phrased as though the portal constrains this, with ZERO call sites. That comment is worse than no comment - a reader checking whether category closure exists finds prose asserting it does. This is the fleet pattern again: it FAILS TOWARD REASSURANCE. Delete or correct that comment in the same change that wires the function. MUST-FAIL FIXTURE MOVES WITH THE PATH, unchanged in force: authenticate as the live client (user 6ebaffae), assert an appKey that identity holds no client membership under - mars or pluto - expect REFUSED, and force it RED against the app entrypoint before acceptance. Add a second forced-red arm now that category closure is the real new code: a client-facing call with a categoryValue outside PORTAL_PEN_CATEGORIES must be REFUSED. db-enamel-cc: migration items 1 and 2 (updatedAt trigger, fn_supportTicketsStatusMatrix fold-in) are UNAFFECTED and keep moving; the RPC section is CANCELLED, not held.
-
audit-terra-cc reviewed enamel_059 pre-apply (md5 0fd54e2b03c235c051a7a22f623d8776) before pmmaster's RPC cancellation landed: items 1 (updatedAt trigger) + 2 (matrix fail-closed) independently verified sound - no live-writer behavior change, ST001/CT00x errcodes don't collide with any of 98 plpgsql fns in the schema. pmmaster cancelled section 3 (createSupportTicketAsClient) in favor of wiring the existing support-core.createTicket gate instead - flagged to db-enamel-cc that the file needs re-cutting since ARMs 2/5/6/8/10 test the now-cancelled function and would fail the whole acceptance block as-is.
-
db-lane half SHIPPED: enamel_059_supporttickets_updatedat_and_matrix_failclosed.sql, applied + registered (version 20260912005753, md5 f9ac7911a2f03c15582becd5a5de8a11) and pushed b01cb44. Two items: (1) trg_supportTickets_setUpdatedAt, unconditional BEFORE UPDATE on enamel's own fn_setSupportTicketsUpdatedAt, closing the same hole enamel_045 closed on users; (2) fn_supportTicketsStatusMatrix now FAILS CLOSED on a categoryValue with no supportTicketCategoryStatuses rows, SQLSTATE ST001, which audit-terra-cc confirmed collides with no existing errcode across all 98 plpgsql functions on this ref. No live writer changes behaviour - all six live supportTicketCategory values have matrix rows. Verified live post-apply: three triggers present, ST001 in the function body, zero rows in supportTickets, zero acceptance probes survived. Four controls forced RED first, each firing its own arm: trigger renamed (ARM 1), trigger fn no-op'd (ARM 5), matrix restored to its exact pre-059 fail-open body (ARM 2), pair check disabled (ARM 3). createSupportTicketAsClient was drafted in this file, dry-run clean with its must-fail fixture forced red (live client asserting mars -> REFUSED), then CANCELLED per pmmaster - recorded in the migration header as a cancelled design so a later reader does not read the absence as unfinished work. Remaining ENAMEL-47 work is entirely app-side and coder02-enamel-cc's.
-
audit-terra-cc: Class-A diff review PASS on coder02's portal restoration (actions.ts, page.tsx, portal-form.tsx, comment-only diffs in auth.ts/nav.ts/support-core.ts). Traced the full authorization chain: vocabulary check (isPortalPenAppKey/isPortalPenCategory) is a UX filter only, actual authorization is core.createTicket's in-statement vUserGroups EXISTS on the same appKey - a tampered hidden-input appKey still fails closed with zero rows. No bypass found. Clear to push.
-
V1 CUT COMPLETE AND PUSHED 2026-09-12: 01c0845, app v0.11.0, coder02-enamel-cc. Composition of the cut as shipped, stated this way because three of the five items were ALREADY BUILT and only became visible during this work - store DONE (db-enamel-cc, enamel_059, applied+registered 20260912005753, pushed b01cb44); write path DONE via the pre-existing gated support-core.createTicket rather than a new RPC; read path DONE and PRE-EXISTING (resolveScopedSupportTicketsClientQueue); portal route DONE and RESTORED, NOT NEW (built 2026-07-31 c3e741c, wrongly retired 2026-09-02 6749b68 as 'replaced by soportePreguntas', which is the opposite authorship direction - real cause was zero rows ever); RPC CANCELLED and folded into the existing gate; venus and terra linking pre-existing. ACCEPTANCE, and this is the part that makes the fixture a control rather than a green line: coder02-enamel-cc temporarily stripped the appKey correlation from createTicket's vUserGroups EXISTS clause and re-ran the suite. THE SAME ASSERTION FLIPPED TO FAILING, which proves the test is coupled to the real gate rather than passing for an adjacent reason. Restored, reconfirmed 237/237 plus tsc clean, then pushed. That is the method to reuse: do not force a fixture red by feeding it a bad input, force it red BY REMOVING THE CONTROL - a bad input can be refused by an earlier gate, and then green means only that the outermost gate works. My original fixture spec had exactly that defect: I said assert mars or pluto, and coder02 measured that both fail the STATIC PORTAL_PEN_APP_KEYS gate first, so the membership predicate would never have been evaluated. The real forceable case is ENAMEL - user 6ebaffae holds client in terra and venus only, and enamel clears the static gate, so membership is the only thing that can refuse it. STATUS IS PUSHED, NOT DONE. Definition of done here is code pushed AND WORKING, and the working half is a live authenticated round-trip that has not happened yet - it is a post-deploy check by ruling, deliberately not a pre-push gate, because a refusal produces no write and the negative arm therefore needs no write role. Do not close this item, and do not close EVO-83, until that round-trip is reported.
-
audit-terra-cc verdict on record for pmmaster (3-part): cleared the diff, coverage stated (did not run tests/tsc/live write myself); endorsed the force-red-by-removing-the-control method as sound but did not witness the run; independently diffed the restored portal files against 6749b68^ (pre-retirement, correct baseline - not the 07-31 original, which was 90+ lines smaller) and confirmed byte-identical, zero drift. Noted single-live-auditor-across-fleet context per pmmaster's request.
-
LIVE must-fail fixture on the shipped createTicket gate, run by db-enamel-cc from the enamel query endpoint, one transaction, aborted by an ACC99 raise so nothing committed. Statement text extracted from app/src/lib/support-core.ts:241-259 by sed, not retyped; only the six bind params were substituted with literals. Subject/user: 6ebaffae-a195-459e-afe8-b89c3fd73fc0, the only user holding group=client outside enamel (terra + venus, zero client rows on enamel, asserted live in the same run). ARM A, statement verbatim, appKey enamel: 0 rows = REFUSED. ARM B, same statement, same user, appKey terra: 1 row = ACCEPTED, so the appKey alone flips the verdict and the refusal is not an artifact of the user or the category. ARM C, forcing: vUserGroups EXISTS removed, lookupOptions EXISTS left intact, appKey enamel: 1 row = ACCEPTED, so ARM A's zero is attributable to the membership predicate and not to the category predicate. Post-rollback: supportTickets 0 rows, 0 probe survivors, supportTicketCounters unchanged (no terra row created; fn_assignTicketNumber upserts a counters row rather than advancing a sequence, so the rollback is complete). ARM B is the FIRST live exercise of this insert path ever (0 rows in supportTickets before and after): statusValue default 'open' pairs with changeRequest in supportTicketCategoryStatuses, so tg_supportTickets_statusMatrix accepts it and tg_supportTickets_assignNumber issues a number. That removes the failure mode the post-deploy round-trip was carrying. COVERAGE: this proves the shipped SQL gate, both predicates, against live triggers and live matrix. It does NOT exercise the TS pre-validation, session-to-actorId resolution, the portal isPortalPenCategory closure, or the node-postgres queryOne path - that gap is the distance between this and an app-entrypoint arm.
-
AUDIT VERDICT ON THE RECORD, audit-terra-cc, 2026-09-12, for push 01c0845. CLEARED. Read in full: actions.ts, page.tsx, portal-form.tsx, and the auth.ts/nav.ts/support-core.ts diffs, confirmed comment-only via git diff with no behaviour change to core.createTicket. app-keys.ts: exports grepped, not fully read. Auth chain traced LIVE: the vocabulary check (isPortalPenAppKey / isPortalPenCategory) is UX-ONLY, and the real gate is core.createTicket's in-statement vUserGroups EXISTS correlated on the same appKey - tampering the hidden appKey input still fails closed. DID NOT EXERCISE, stated by the auditor unprompted: the test suite (no local run), tsc, and any live end-to-end write (no write-capable role). TWO DISTINCTIONS THIS VERDICT MAKES THAT ARE WORTH MORE THAN THE CLEARANCE ITSELF. (1) THE AUDITOR ENDORSED THE ACCEPTANCE METHOD BUT DID NOT WITNESS THE RUN. It endorses removing the control and watching the SAME assertion flip as strictly stronger than a bad-input probe - precisely because of tonight's own mars/pluto lesson, where a bad appKey dies at the earlier static PORTAL_PEN_APP_KEYS gate without ever touching the predicate under test - but it did not see the red/green transition, since it holds no write role and the output was not pasted. Its own requested wording, and the wording anyone quoting this must use: 'auditor endorsed the method, did not witness the run.' An endorsement of a method reads as a confirmation of its execution to every later reader, and nothing corrects that unless the auditor writes the line themselves. (2) THE RESTORE BASELINE IN MY EARLIER NOTE WAS WRONG AND THE AUDITOR CORRECTED IT. I recorded the portal as restored verbatim from c3e741c, the 2026-07-31 creation commit. THE FILES GREW BETWEEN CREATION AND RETIREMENT - 131 to 219 lines, 102 to 123, and similar - so c3e741c is the wrong baseline and a diff against it would have shown spurious differences. Checked independently against 6749b68^, the commit immediately BEFORE the retirement: all four files BYTE-IDENTICAL, zero diff. THE RULE: the baseline for verifying a restore is the commit immediately before the DELETION, never the commit that created the file. The failure this guards against is worse than a bad verification - restoring FROM the creation commit silently reverts every change made in between, and the commit message still truthfully says 'restored'. COVERAGE CONTEXT THE AUDITOR REQUIRED BE RECORDED: mars has no live auditor, and aro:terra's own room lists audit-terra-ca as absent. This review ran under a SINGLE live audit lane across the entire fleet tonight, not two-auditor coverage. A single-auditor review is a different artifact from a two-auditor one and must not read as routine.
-
coder-enamel-cc
-
PICKED UP 2026-10-04 by coder-enamel-cc on pmmaster's reassignment. Measured the current state before touching anything; the app-side cut is intact and the ONE open item is still the live authenticated round-trip, which no agent can perform. VERIFIED PRESENT (live, today): /portal deployed and gating - unauthenticated GET https://enamel-evolutiva.pensanta.com/portal returns 307 to /login; the four restored files are in the tree (page.tsx, actions.ts, portal-form.tsx, __tests__/portal.test.ts); category closure IS wired, not dormant - isPortalPenCategory is called at actions.ts:39 and PORTAL_PEN_CATEGORIES drives the form picker at portal-form.tsx:98, so the 'dormant control described as a live one' comment defect from the 00:54 ruling is closed. App at 0.14.4, gate green 274/274 on the most recent push. VERIFIED ABSENT: supportTickets holds 0 rows lifetime, 0 for appKey enamel, and appEvents carries 0 events for any portal/createTicket action. So the round-trip has still never happened - the 2026-09-12 'PUSHED, NOT DONE' condition is unchanged 22 days later. WHY IT CANNOT BE CLOSED BY ANY AGENT, measured not assumed: vUserGroups holds exactly TWO live client rows, both user 6ebaffae-a195-459e-afe8-b89c3fd73fc0 (bound), on terra and venus. That is the only identity the portal gate will accept, and it is a real person - signing in as them is not available to me and should not be. Three non-paths, each measured: (1) Elazar's own account 8157954d holds admin on enamel+terra and ZERO client rows, so the portal predicate REFUSES him - the gate is client membership, not admin, which is exactly what the 'the Director is simply an admin' bar in this item's description protects; (2) a synthetic client cannot substitute, because vUserGroups filters isSynthetic IS FALSE on both joined relations by design; (3) the bearer API route POST /api/soporte/tickets carries the same gate but its pinned actor is Elazar's non-client row, so it would be refused on the client predicate even if SUPPORT_API_KEY were set - ENAMEL-47 is therefore NOT unblocked by TERRA-268, and nobody should set that key expecting it to be. SO THE REMAINING WORK IS A HUMAN ACTION, not code, and it is one of exactly two: (a) user 6ebaffae files ONE ticket from the portal against terra or venus, which closes this on the spec as written; or (b) a live non-synthetic client membership is granted to an identity someone can sign in as, which is a permission decision and not mine. Routed to pmmaster-evolutiva-cc for (a) vs (b). Blocking rather than leaving it inProgress, because the item is not waiting on my lane.
-
Open item is the live authenticated round-trip, which requires a human with a live non-synthetic client membership. The only such identity is real user 6ebaffae (terra+venus); Elazar's own account holds no client row and is refused by the same gate. Routed to pmmaster: either the real client files one ticket, or a client grant is issued to a signable identity. No app-side work outstanding.