basquetWi + New ticket
terra TERRA-15

deletedAt on the four terra clinical detail tables (ruled 2026-07-31, not delivered by terra_13)

Done high dtdb-terra-cc

PM ruling 2026-07-31 (TERRA-10 description): the four terra clinical detail tables get a deletedAt column, AND IT LANDS BEFORE ANY FORBID-HARD-DELETE GUARD - because a delete ban on a table with no soft-delete column strands the workflow (a wrongly-recorded complication must be retractable but never erasable). Migration terra_13 shipped NEITHER: no deletedAt, and no delete bans on those four tables. Instead it gave them AFTER DELETE archive capture. Consequence, both halves: the ruling's HAZARD is absent (no guard exists, so nothing can be stranded, and deletion is now recorded rather than silent) but the ruling's GOAL is UNMET (a detail row can still be hard-deleted, merely visibly). Surfaced by db-terra-cc at apply time, self-reported: they reached archive-capture by the same reasoning as the ruling but stopping one step short of the ordered column, and the substitution never came back for a decision. Filed as its OWN WI, deliberately not as a fourth item on TERRA-10's tail, so the requirement cannot leave with TERRA-10's close. ORDERING CONSTRAINT SURVIVES: deletedAt lands before any guard on these tables.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Questions

No questions.

Activity

  • wi-cli-venus created · 2w ago
  • wi-cli-venus note · 2w ago
    STANDING CONSTRAINT, recorded here because WI TERRA-16 was cancelled and a constraint must not die with its row: the four vTerra*Activas views created by this migration MUST be the read path for ANY future read of terraPracticasPiezasDentarias / terraPracticasRegionesAnatomicas / terraPracticasComplicaciones / terraPracticasAntecedentes. This is a constraint on whoever first builds a retraction or detail-display path, NOT a migration of existing call sites - enumeration measured 2026-08-01 across the whole tree: ZERO existing readers (the three are INSERT-only at src/lib/practica-actions.ts:338/345/352; terraPracticasAntecedentes has zero occurrences in src/; the lookups.ts hit is a comment; grep -rnE 'FROM.*terraPracticas(PiezasDentarias|RegionesAnatomicas|Complicaciones|Antecedentes)' src/ returns no matches). An empty result WITH its command attached, not a blank line. SECOND STANDING CONSTRAINT: when a detail-row retraction path is built, its server action MUST set app.user_id + app.archive_reason - PM-ruled 2026-08-01, Ley 26.529 attribution; terra's archive idiom fails LOUD without them and an unattributed clinical retraction is the failure being prevented. Do not add a default that lets a retraction through unattributed.
  • wi-cli-venus note · 2w ago
    SCOPE WIDENED BY PM RULING 2026-08-01, surfaced by db-terra-cc at design time before any DDL was written. All four detail tables have NATURAL composite PKs including the value column and no surrogate id: terraPracticasPiezasDentarias (practicaId, codigoFdi); terraPracticasRegionesAnatomicas (practicaId, regionAnatomicaValue); terraPracticasComplicaciones (practicaId, fase, value); terraPracticasAntecedentes (practicaId, antecedenteId). deletedAt ALONE over a natural PK is a DEFECT, not a partial implementation: a retracted row keeps its PK slot, so re-adding the same value - the correct fix for a mis-click - violates the primary key and surfaces as a raw constraint violation on an ordinary clinical action. Strictly worse than today, where the row is deletable and re-insertable. Same trap as the fleet canonicalizeEmail rule: the unique must be PARTIAL over live rows or soft-delete breaks. RULED ABSORB, one transaction, no split - a split leaves a window in exactly that broken state. Shape: (1) id uuid PRIMARY KEY DEFAULT gen_random_uuid() on each of the four; (2) drop natural PK, replace with partial UNIQUE INDEX on the same columns WHERE deletedAt IS NULL; (3) deletedAt + four vTerra*Activas views + forbid-hard-delete guards + archive-context requirement. Any later existence read must reproduce the index predicate exactly, every term. ALSO: fn_terraDetailArchive currently records rowId = OLD.practicaId because no row identity existed - re-key it on the surrogate id (practicaId identifies the PARENT, not the row, which defeats the archive on the tables it was added for). CREATE OR REPLACE restates SET search_path = public, pg_temp VERBATIM and asserts proconfig in the same file. Safe now and only now: no inbound FKs, three INSERTs, zero reads - record row counts per table before the swap, since a swap over zero rows and over live clinical rows are different operations.
  • wi-cli-venus completed · 2w ago
    terra_14 applied + pushed 663df9f/0.15.3: PK swap + soft-delete on 4 detail tables
task
2w ago by wi-cli-venus
2w ago
2026-08-01 11:59