basquetWi + New ticket
venus VENUS-387

trg_participations_operador_unique raises without ERRCODE, so the app must match its prose

Done normal dvdb-venus-cc

Split out of VENUS-386 (coder-terra-cc cross-project review). The trigger RAISE EXCEPTION carries no ERRCODE, so it lands as SQLSTATE P0001 - indistinguishable from every other RAISE in the schema. addMissingParticipant (admin-actions.ts:2184) therefore maps it by MESSAGE SUBSTRING ("Only one operador allowed"), which is the only discriminator available. Reword the message in a future migration and the mapping silently degrades: the approver gets the generic "No pudimos agregar al participante" instead of "Esta practica ya tiene un operador", with no error and nothing failing. Fix: RAISE ... USING ERRCODE = <a chosen class-P0 code>, then the action checks err.code instead of the prose. Three applied copies of the trigger body exist (000-initial-functions-triggers, hardening-search-path, venus64-guard-fns-pgtemp-qualify); venus64 is the last-applied. VENUS-386 shipped the source half in 4c5a9c9: the test now asserts the literal against venus64 rather than against the actions own copy, so drift is caught even before the ERRCODE lands - falsification-verified by rewording the migration, which fails the test. db-venus-cc has no push authority; route the migration through coder-venus-cc.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 7w ago
  • wi-cli-venus completed · 5w ago
    MIGRATION APPLIED TO PROD 2026-09-11 12:38 UTC. db/migrations/applied/venus74-operador-unique-errcode.sql. fn_checkoperadorunique now raises USING ERRCODE = 'VE001'. CONVENTION SET, because none existed anywhere in db/migrations/applied/ or src/: class VE = venus application-defined errors, VE001 = operador uniqueness. Class VE is safe by construction - the SQL standard reserves classes beginning 0-4 and A-H for itself, PostgreSQL's appendix-A classes all sit inside that range plus P0/XX/HV, and implementation-defined conditions are told to start 5-9 or I-Z, so VE can never collide with a code assigned later. DELIBERATELY NOT 23505: this insert can also violate REAL unique indexes on practicaParticipations, and src/ already maps bare 23505 generically in four files (auth.ts, correction-actions.ts, auth-actions.ts, admin-actions.ts), so reusing it would leave the app unable to tell a one-operador conflict from an index collision it must not reassure the user about. MESSAGE TEXT UNCHANGED, BYTE FOR BYTE, on purpose - the migration has to be applyable BEFORE the app switches to err.code and the app has to keep working in the window between, and the VENUS-386 drift test pins that literal, so rewording it would break admin-actions.ts:2226 the instant this applied. VERIFIED BEFORE APPLYING, run not asserted: throwaway local PostgreSQL 17.11, minimal fixture, both function bodies EXTRACTED FROM THE MIGRATION FILES so what executed is the string that ships. BEFORE (venus64) SQLSTATE=P0001, AFTER (venus74) SQLSTATE=VE001, SQLERRM byte-identical across both. The P0001 line is the must-fail control. Non-regression same run: operador on a different practica inserts, ayudante on the conflicting practica inserts, soft-deleted operador does not block. Live prosrc was read first and confirmed byte-identical to venus64, so venus64 was in fact the live copy. POST-APPLY ON PROD: functiondef carries USING ERRCODE = 'VE001'; proconfig still {"search_path=public, pg_temp"} with pg_temp LAST; prosecdef FALSE; trg_participations_operador_unique attached to public.practicaParticipations with tgenabled='O'. OPEN AND HANDED TO coder-venus-cc (I hold no push authority, the file is unpushed): (1) src/lib/admin-actions.ts:2225-2232 replace msg.includes('Only one operador allowed') with (err as { code?: string }).code === 'VE001' - the shape already proven to survive the pool path at src/lib/correction-actions.ts:301; (2) src/lib/__tests__/add-participant-outcome.test.ts repoint readFileSync from venus64 to venus74, otherwise it stays green against a superseded file, which is VENUS-386's defect reborn. App change lands AFTER this migration, never before.
  • wi-cli-venus note · 5w ago
    App half shipped 8214f28 (v1.17.8), live at 1.17.9. admin-actions.ts now matches SQLSTATE VE001 instead of the prose. Ordering was migration-then-app and never the reverse: db-venus-cc applied venus74 at 12:38Z with the message text left BYTE-IDENTICAL on purpose so the old prose match kept working until this deploy. Class VE = venus app errors, deliberately NOT 23505, because this same INSERT can violate real unique indexes on practicaParticipations and four files already map bare 23505 generically - reusing it would make the guard indistinguishable from an index collision. The VENUS-386 drift test was pinned at venus64 and is repointed to venus74 (left alone it stays green against a superseded file, which is VENUS-386 reborn); its assertion is now the ERRCODE with the prose kept alongside as a secondary guard.
task
7w ago by wi-cli-venus
5w ago
2026-09-11 12:42