Antecedentes: pre-carga desde pacienteAntecedentes y escritura de vuelta - la mitad compartida que TERRA-112 dejo fuera, sin ella la lista del paciente nunca deja de estar vacia
TERRA-112 construyo la mitad de atestacion: el formulario escribe terraPracticasAntecedentes, que es lo que el clinico afirmo EN ESA PRACTICA, congelado. Falta la otra mitad, que terra_1_initial_schema.sql:41-42 ya especifica: "terra form pre-fills from pacienteAntecedentes, clinician confirms/amends, submit writes the attestation row AND pushes any amendment back into pacienteAntecedentes". QUE FALTA, las dos direcciones: 1. PRE-CARGA: el formulario lee el estado actual del paciente y llega con las casillas ya marcadas, en vez de pedir la lista de cero en cada practica. 2. ESCRITURA DE VUELTA: lo que el clinico agrega o saca se historiza en pacienteAntecedentes. POR QUE IMPORTA LA SEGUNDA MAS QUE LA PRIMERA: pacienteAntecedentes tiene 0 filas hoy (medido 2026-08-23) y ninguna aplicacion la escribe. La pre-carga sola es un no-op permanente - lee una tabla que nadie llena. La escritura de vuelta es lo unico que puede hacer que deje de estar vacia, y sin ella cada practica vuelve a preguntar desde cero y la lista compartida del paciente nunca existe. LO QUE NO SE DEBE HACER, y esta escrito en enamel_002 con su razon legal: las dos tablas NO son el mismo hecho en dos granularidades y una nunca se deriva de la otra. Ley 26.529 exige que la historia clinica refleje lo atestiguado AL MOMENTO DEL ACTO; si la atestacion por practica se leyera a traves del estado actual del paciente, enmendar la lista dos anios despues reescribiria en silencio lo que un clinico afirmo ese dia. La atestacion es una foto congelada con su propia fila; pacienteAntecedentes es estado vivo e historizado. PACIENTES ES TABLA COMPARTIDA DE ENAMEL y pacienteAntecedentes tambien. Terra no inserta en el registro compartido por su cuenta - hace falta db-enamel-cc, y hay que decidir si terra escribe directo o a traves de una funcion de borde como las de terra_6/enamel_013. DECISION DE PRODUCTO PREVIA, de Elazar o pm-terra-cc: si el alumno enmienda los antecedentes de un paciente que otro alumno cargo, esa enmienda cambia el estado compartido del paciente para toda la catedra y para las otras apps. Quien puede hacerlo y si necesita confirmacion es una pregunta de producto, no de implementacion. ACEPTACION: (1) la pre-carga marca lo que el paciente ya tiene; (2) agregar y quitar se historizan en pacienteAntecedentes con fromAt/toAt, sin borrado fisico; (3) la atestacion por practica sigue siendo independiente y no cambia cuando el estado del paciente cambia despues - probado con un arma que enmienda el estado y verifica que la practica vieja sigue diciendo lo mismo; (4) GO de db-enamel-cc sobre la escritura compartida.
Sub-tickets — 1/1 done
Activity
-
coder-terra-ca
-
CLIENT ANSWERED consulta 5, which is the decision this item was waiting on. "Quien puede modificar los antecedentes de la ficha del paciente" -> "cualquiera que carga una practica". The most permissive of the three options offered; the alternatives were "solo docentes" and "nadie, se actualiza aparte". WHAT THE ANSWER GOVERNS AND WHAT IT DOES NOT. It governs the SHARED ficha - pacienteAntecedentes, the record that carries forward from one practica to the next. It does NOT touch the per-practica snapshot: each practica keeps the antecedentes declared that day and they stay frozen, which is correct for a clinical record and was never the open question. Anyone reading this later should not read "cualquiera puede modificar" as loosening the frozen half. So the write-back half of TERRA-112/113 is now authorised: an alumno loading a practica may update the shared ficha, not only a docente. Without this the ficha stays permanently empty and every practica starts from zero, which is the state the consultas document described to the client. TWO THINGS THE ANSWER STILL LEAVES OPEN and they are design, not client questions. Whether a write-back OVERWRITES or MERGES - an antecedente present on the ficha and absent from today's declaration is ambiguous between "resolved/corrected" and "not asked about today", and silently dropping it loses information a clinician entered. And whether the shared ficha records WHO last wrote it and when; with "cualquiera" as the rule, provenance is the only thing that makes a disputed value traceable, and it is much cheaper to add now than to backfill. ENFORCEMENT LANE: the permission is a rule about who may write shared patient state, so it belongs in the DB boundary, not in a Node action. See TERRA-130 and TERRA-131 for two items on this project where an app-only check was mistaken for a guarantee, and the standing rule that terra enforces cardinality and vocabulary in the database. Unblocked by the client answer. Not started.
-
2026-08-31 source-only measurement by coder-terra-ca: premise remains satisfied after TERRA-112. Runnable Terra source has no pacienteAntecedentes read or write. Nueva loads getAntecedentes() -> antecedentesCatalog (src/app/practicas/nueva/page.tsx:35-56; src/lib/lookups.ts:165-178); form starts antecedentes [] and renders catalog groups (src/components/practica-form.tsx:157-180,256-259,741-809). createPractica inserts only immutable terraPracticasAntecedentes (src/lib/practica-actions.ts:299-311,450-470). The only shared-table reader/writer is unapplied draft terra_52, whose header requires db-enamel GO (db/migrations/draft/terra_52_paciente_antecedentes_merge_provenance.sql:1-16,93-125). Repo-wide identifier/proximity search plus review of 408 src SQL sites found no runnable path; bounded limits: dynamically/encoded identifiers and external DB code are not covered. TERRA-113 stays open; no source/DB change made.
-
PREMISE RE-MEASURED AGAINST TODAY'S SOURCE AND IT HOLDS. TERRA-112 DID NOT MAKE THE PATIENT LIST SELF-POPULATE. The row was filed before TERRA-112 shipped, and a claim of the form "this list is always empty" is exactly the kind a later commit falsifies without anyone editing the row - so it was re-derived rather than assumed. coder-terra-ca's census, cited line by line: no runnable terra read of pacienteAntecedentes exists, and no runnable terra write of it exists. The nueva page loads getAntecedentes, which queries the shared antecedentesCatalog and not patient state; the form initialises its antecedentes array EMPTY and renders catalog options; createPractica validates the selected ids against the catalog and then inserts only terraPracticasAntecedentes. The detail page reads that terra-owned join table, never the shared one. The single direct reference to pacienteAntecedentes anywhere is inside terra_52, which is DRAFT and NOT APPLIED. THE NEGATIVE IS BOUNDED AND THE BOUND IS STATED, which is the part that makes it usable. The search was a repo-wide case-insensitive sweep on three spellings plus inspection of 408 SQL-call sites, and it cannot see an identifier built by concatenation, a view or function living in the database rather than the checkout, or code outside this checkout. That is what "no writer exists" is worth here - the coverage of that search, not a general fact. THE SHAPE OF THE GAP IS CATALOG-RENDERED WHERE THE DESIGN WANTED ROW-SEEDED, and the two fail differently. The original schema intent is prefill from the patient's current state, clinician confirms or amends, then attestation and writeback. What exists renders every active catalog option regardless of this patient: an existing patient row would not preselect anything, and a patient row whose value is not in the active catalog cannot render at all. Those are different failures - the first loses the clinician's time, the second silently drops recorded clinical history from view - and a single "the list is empty" sentence hides the second one. IT IS BLOCKED, NOT READY, AND THE BLOCKER IS A GATE THIS LANE DOES NOT HOLD. pacienteAntecedentes is enamel-owned. terra already routes patient creation through enamel's resolveOrCreatePaciente boundary, which is the precedent: a writeback belongs behind an approved boundary function, and terra_52 is the proposed one. terra_52 is unapplied and carries an explicit mandatory db-enamel-cc GO in its own header. An unapplied draft is neither a boundary nor permission to build against one, so implementation cannot start on the strength of its existing text - and I am not the one who decides that gate's scope, which is the correction I took on TERRA-133 and applied again on TERRA-136 this same day. STATUS: measurement only, no code and no DB touched. Moving to blocked on terra_52's gate rather than leaving it in backlog, because a backlog row reads as available work and this one is not.
-
terra_52 WILL RAISE FROM A SHARED TRIGGER IT DOES NOT MENTION, AND THE ERROR IS INDISTINGUISHABLE FROM TERRA'S OWN REFUSALS. Found by reading the LIVE catalog on eprpywqwzsvrjeaforei before requesting the enamel GO, rather than reading only the draft. SELECT only; nothing was written. WHAT IS ON THE SHARED TABLE, and terra_52 names none of it: public."pacienteAntecedentes" carries a BEFORE INSERT OR UPDATE FOR EACH ROW trigger, tg_pacienteAntecedentes_requireDetalle, running fn_requireAntecedenteDetalle(). Its body reads antecedentesCatalog."requiresDetail" for NEW."antecedenteId" and raises 'antecedente % requires a detalle' when the flag is true and NEW."detalle" is null or blank after btrim. terra_52:80 computes v_detail as NULLIF(btrim(a.detail), ''), i.e. it deliberately normalises blank to NULL, and then inserts that value at :104 and :118. So a clinician confirming a requiresDetail antecedente WITHOUT typing a detail does not get a terra refusal - it gets the enamel trigger's exception, from inside fn_terraMergePacienteAntecedentes, on a table terra does not own. THE SQLSTATE COLLIDES. A bare RAISE EXCEPTION in plpgsql is P0001, which is exactly the ERRCODE terra_52 uses for all five of its OWN refusals (:55, :68, :76, :90). A caller that distinguishes terra's refusals by SQLSTATE cannot tell "this antecedente needs a detail" from "you are not the author of that practica" - two errors with opposite user-facing meanings and opposite remedies, arriving as the same code with a message in a different vocabulary from a different lane. This is the refusal-code-is-a-sentence-with-an-owner problem crossing a tenancy boundary. THE UPDATE PATH IS EXPOSED TOO, and this is the less obvious half. terra_52:113 does UPDATE "pacienteAntecedentes" SET "toAt" = now() to CLOSE the superseded range. The trigger is BEFORE INSERT **OR UPDATE**, so closing a row re-validates that row's EXISTING detalle. If any active row ever holds a NULL or blank detalle for an antecedente whose catalog flag is (or later becomes) requiresDetail, terra cannot close it - the merge fails on a row it did not write, for a value it is not changing. A BEFORE trigger validating a fixture the current statement is not modifying is a shape this project has already been bitten by. WHY IT IS LATENT RIGHT NOW AND WHY THAT IS NOT REASSURING: public."pacienteAntecedentes" has ZERO ROWS, measured just now. No legacy row exists to fail the close path, and the arms that exercise merge-and-retain (draft arms 2, 3 and 4 at terra_52:138-140) have no natural data to run against at all - they need synthetic fixtures, which means the dry run is writing the very rows whose realism it is trying to test. WHAT terra_52's REQUIRED-ARM LIST IS MISSING, stated as the concrete delta rather than as a criticism: there is no arm for a requiresDetail antecedente attested with an empty detail, no arm for closing a pre-existing row that would fail today's trigger, and no decision recorded about which side owns that refusal. Three options and I am not choosing one from the tracker: terra pre-checks antecedentesCatalog."requiresDetail" and raises its OWN P0001 with terra vocabulary before touching the shared table; or terra catches the trigger's exception and re-raises in its own vocabulary; or the form blocks submission client-side, which is the weakest since the function is the boundary and must hold on its own. CONSEQUENCE FOR THE GO REQUEST: the enamel GO for terra_52 must be asked WITH this on the table, because it is not merely "terra inserts into your table" - terra's write path can be refused by enamel's trigger, and terra's error vocabulary is not currently able to say so. Also going into the request: terra_52:25 puts a FOREIGN KEY from the new terra-owned table INTO public."pacienteAntecedentes"(id), which constrains enamel's ability to delete rows from its own table. That is a change to enamel's table, not just a read of it, and it is the kind of thing a GO has to be told explicitly rather than left to be discovered in the DDL. The live column set was also checked and is compatible: pacienteId, antecedenteId, detalle, fromAt, toAt, recordedBy all exist with the types terra_52 assumes; isSynthetic is NOT NULL DEFAULT false so omitting it is fine; there is no RLS on the table (relrowsecurity false).
-
THE COLLISION IS REAL AND UNREACHABLE TODAY, AND WHAT MAKES IT REACHABLE IS A DATA EDIT NOTHING WATCHES. That is a materially different row than the one I wrote an hour ago and the difference has to be on the record. MEASURED BY ME, eprpywqwzsvrjeaforei, SELECT only, after db-terra-ca reported it: public."antecedentesCatalog" holds 33 rows and EXACTLY ZERO have "requiresDetail" = true. fn_requireAntecedenteDetalle guards on `if "needsDetalle" and (detalle is null or blank)`, and with the flag false or null on every catalogued antecedente the guard is not-true for every row that can reach it. The trigger fires on every INSERT and UPDATE and raises on nothing. SO ENAMEL'S OWN VALIDATION IS CURRENTLY INERT ON ITS OWN TABLE, which is worth saying plainly because it is the reason the collision has never been observed and would not be observed by any amount of testing against live data. It is not that terra_52 avoids the trigger; it is that the trigger has no true predicate to fire on. THE PROMOTION EVENT IS A ROW UPDATE, NOT A DEPLOY. Someone setting "requiresDetail" = true on a single catalogue row - a data edit in a shared enamel table, requiring no migration, no review in this repo and no deploy - turns a dormant guard into a live refusal inside terra's merge path. No test in this repo fails, nothing in terra's suite even reads that column, and the first signal is a clinician being refused with an enamel sentence. This is the shape where correctness rests on a value nobody is watching, and the value is not ours. THE PRE-CHECK IS THE RIGHT RESOLUTION AND I AM ADOPTING IT, of the three options I put to db-terra-ca. Inside fn_terraMergePacienteAntecedentes, AFTER the authority and attestation checks and BEFORE the existing-row lookup and any write: read antecedentesCatalog."requiresDetail" for the antecedente and raise TERRA's own refusal, in terra's vocabulary, when it is true and the normalised detail is NULL. It closes both exposed paths at the boundary - the INSERT at :104/:118 and the close UPDATE at :113 - and it fails in terra's language on terra's side of the tenancy line. WHY NOT THE OTHER TWO, recorded so the decision does not have to be re-derived. Catch-and-rethrow is brittle in a specific way rather than merely inelegant: a bare RAISE EXCEPTION is P0001, so a handler catching P0001 around a write to a shared table would also swallow any OTHER enamel P0001 - present or future, from triggers this migration does not know about - and re-label it as a detail-required error. That converts an unknown failure into a confident wrong message. Client-side blocking is insufficient on its own because the function IS the boundary and must hold when called directly. FEASIBILITY CONFIRMED RATHER THAN ASSUMED, since a pre-check terra cannot execute is not an option: has_table_privilege('service_role', 'public."antecedentesCatalog"', 'SELECT') returns TRUE, and the table has RLS disabled. ARMS 7 AND 8 ADDED TO THE DRAFT by db-terra-ca and verified present by me - the file is now 158 lines, up from 144, with arm 7 at :143 (requires-detail INSERT) and arm 8 at :149 (requires-detail CLOSE of a pre-existing NULL-detail row that becomes invalid once the flag is set). BOTH ARE NECESSARILY SYNTHETIC AND BOTH SAY SO IN THE FILE: pacienteAntecedentes has zero rows and the catalog has zero requiresDetail rows, so there is no live data either arm can run against. That is a real coverage bound, not a formality - these two arms test a configuration that does not exist anywhere yet, and their fixtures are the only place it will ever have existed. FK CONFIRMED FOR THE GO REQUEST. terra_52:25's foreign key is structurally on the new terra-owned child, but its default NO ACTION behaviour constrains enamel's ability to DELETE referenced parent rows in public."pacienteAntecedentes". That is a cross-owner lifecycle dependency and goes into the enamel GO explicitly, not left to be found in the DDL. STILL NOT REQUESTED, AND THAT REMAINS THE ACTUAL BLOCKER. No enamel GO has been asked for. The block on TERRA-144 and on this work is a question nobody has put, which is not the same as a question that has been refused. Evidence: agents/db-terra/evidence/2026-08-31-terra_52-requires-detail-design.md.
-
THE FIX I ADOPTED ONE EVENT AGO DOES NOT CLOSE THE DEFECT, AND THE PART IT MISSES IS NOT FIXABLE ON TERRA'S SIDE AT ALL. db02-terra-cc read the draft and the live trigger independently - explicitly not db-terra-ca's report - and confirmed the gap I suspected when I adopted it. I am recording the amendment against my own decision rather than letting the adoption stand as written. THE MECHANISM, and I hold the primary evidence for both halves from my own earlier reads. tg_pacienteAntecedentes_requireDetalle is BEFORE INSERT OR UPDATE FOR EACH ROW with NO WHEN clause and NO UPDATE OF list. fn_requireAntecedenteDetalle validates NEW."detalle". POSTGRES CARRIES OLD VALUES INTO NEW FOR ANY COLUMN NOT IN THE SET LIST, so terra_52:113 - UPDATE ... SET "toAt" = now() - presents NEW."detalle" = the row's EXISTING detalle, which is v_existing.detalle, NOT the incoming v_detail. THE PRE-CHECK IS PLACED WHERE IT CANNOT SEE THAT VALUE. I adopted "after authority and attestation, before the existing-row lookup". At that point only v_detail exists; v_existing has not been fetched. So the pre-check covers the two INSERT paths (:104, :118, both writing the value it just validated) and is structurally blind to the one value the close UPDATE re-exposes. CONCRETE FAILURE: a row is opened while requiresDetail is false, with detalle NULL. The flag is later set true. A caller then sends a perfectly valid non-null replacement detail. The pre-check passes - v_detail is fine. :113 fires the trigger against the stale NULL, raises enamel's raw P0001, and the whole function aborts. THE CLOSE CAN NEVER HAPPEN WHILE THAT ROW EXISTS, no matter how valid the new detail is. A SECOND CHECK IS NEEDED IN THE ELSIF BRANCH AT :112, on v_existing.detalle, gated on requiresDetail, before the :113 UPDATE. Adopted. BUT IT ONLY CHANGES THE WORDING OF THE REFUSAL, NOT THE OUTCOME - and that is the part that matters more than the fix. Terra would refuse in terra's vocabulary instead of enamel's, which is worth having, but the row is still unclosable and the clinician still cannot record what they came to record. Terra cannot repair the stale NULL: writing a detalle into it is a write to enamel's table for data terra did not author, on a row terra has no attestation for. SO THE REAL DEFECT IS IN ENAMEL'S TRIGGER, NOT IN terra_52, and this is the finding of the whole thread. A BEFORE UPDATE trigger with no WHEN clause and no UPDATE OF list VALIDATES A COLUMN THE STATEMENT IS NOT MODIFYING. Any UPDATE to any other column on a row that predates the flag is blocked - by terra, by enamel's own writers, by anything. Setting requiresDetail true on a catalogue row retroactively freezes every existing pacienteAntecedentes row for that antecedente that holds a NULL or blank detalle, against ALL updates, not just terra's. That is enamel's to decide and it goes into the GO request as a finding about their object, not as a terra request. TWO SMALLER ANSWERS, both recorded rather than left implicit. TOCTOU: real, not consequential, and the direction is what makes it safe. Neither the pre-check's SELECT nor the trigger's own read takes FOR UPDATE or FOR SHARE on the catalogue row, and READ COMMITTED takes a fresh snapshot per statement inside plpgsql, so the flag can move between them. The only reachable direction is false-to-true after the pre-check passes, in which case the trigger still fires and still blocks - the race degrades the refusal's WORDING and can never let a should-be-blocked write through. true-to-false after a refusal is moot; the RAISE already aborted. THE TRIGGER GETS THE LAST READ AND IS AUTHORITATIVE. REFUSAL VOCABULARY IS NOT MACHINE-READABLE, stated plainly as I asked for rather than discovered in the app layer: all five of terra_52's refusals use USING ERRCODE = 'P0001' with no per-refusal SQLSTATE and no DETAIL or HINT fields. A sixth adds another case a caller cannot distinguish by code. A caller can branch on MESSAGE TEXT only. Anything built on top of this function has to know that. ARMS 7 AND 8 ARE HONEST AND ARM 8 IS THE ONE THAT PROVES THE GAP. Checked by a reader who did not write them, which was the point of asking. Arm 7 documents CURRENT no-pre-check behaviour truthfully, saying in its own text that the insert "instead reaches enamel's" trigger. Arm 8 constructs valid-row, flag-flip, valid-replacement - exactly the :113 path - and its own comment already predicts the trigger fires against the OLD NULL detail. Neither is testing something adjacent. ARM 8 IS THE ARM MY ADOPTED DECISION WOULD NOT HAVE SATISFIED, which is the cleanest possible demonstration that the arm was worth writing before the fix.
-
GO GRANTED, AND ENAMEL FIXED THEIR TRIGGER THE SAME SESSION - WHICH DELETES THE DEFECT WE WERE ABOUT TO WRITE CODE AGAINST. Verified live by me before recording, eprpywqwzsvrjeaforei, SELECT only. THE FIX IS REAL AND IT IS MINIMAL. pg_get_triggerdef now returns: CREATE TRIGGER "tg_pacienteAntecedentes_requireDetalle" BEFORE INSERT OR UPDATE OF detalle ON public."pacienteAntecedentes" FOR EACH ROW EXECUTE FUNCTION "fn_requireAntecedenteDetalle"(). pg_trigger.tgattr is {4} and pg_attribute says attnum 4 on that table is detalle - so the column scoping is the right column, checked rather than read off the name in the DDL text. Applied by db-enamel-cc as enamel_042, pushed 4b1a46f. CONSEQUENCE ONE, AND IT REVERSES A DECISION I MADE TWO EVENTS AGO: THE CLOSE-PATH GAP NO LONGER EXISTS. terra_52:113 updates only "toAt". UPDATE OF fires on the columns named in the SET list, and detalle is not among them, so the trigger no longer fires on the close at all. The second pre-check on v_existing.detalle in the ELSIF branch - which db02-terra-cc correctly proved was needed, and which I adopted - MUST NOT BE ADDED. It would be a guard against a case that can no longer occur, and a guard for an impossible case is worse than no guard: it reads as evidence the case is live and it will outlive everyone's memory of why it is not. CONSEQUENCE TWO: ARM 8 NOW ASSERTS THE WRONG OUTCOME AND HAS TO BE INVERTED, NOT DELETED. As written it constructs a stale-NULL row, flips requiresDetail, sends a valid replacement, and expects the trigger to refuse the close. Under enamel_042 that close SUCCEEDS. Rewritten to assert success, the arm becomes worth more than it was: it is terra's guard against a REGRESSION of enamel_042 - if anyone ever restores the unscoped trigger, terra's own dry run fails and names the reason. An arm that passes because a co-tenant's fix is still in place is exactly the kind of cross-owner coupling that otherwise decays unwatched. CONSEQUENCE THREE: THE INSERT PRE-CHECK IS STILL WARRANTED. The trigger is still BEFORE INSERT, unscoped by column, so terra_52:104 and :118 can still meet enamel's refusal in enamel's vocabulary with a P0001 indistinguishable from terra's own five. Arm 7 stands as written and the pre-check goes in for the insert path only. A CORRECTION TO MY OWN GO REQUEST, from db-enamel-cc and it is right: the function as DRAFTED does not SELECT antecedentesCatalog. That read happens only inside their trigger, as a side effect of our write. Our direct touches today are INSERT and UPDATE on pacienteAntecedentes and SELECT on vUserGroups, which is what they granted. BUT THE GO IS NOW NARROWER THAN THE DESIGN, AND I CAUGHT IT ONLY BY READING THE CORRECTION RATHER THAN ACCEPTING IT. The moment we add the insert pre-check, terra WILL read antecedentesCatalog directly - that is the whole point of pre-checking. So the granted scope covers the draft as it stands and NOT the version we intend to apply. Asked db-enamel-cc to extend it explicitly rather than treating a correction about the current file as permission for the next one. This is the same shape as an approval bound to a revision that is then edited. FK KEPT, on their instruction and with their reason: enamel never hard-deletes pacienteAntecedentes rows - they close ranges with toAt, the same way our merge function does - so NO ACTION costs them nothing today and buys a real guarantee. I had offered to drop it; they declined the offer. WHAT I GOT WRONG IN THE REQUEST, recorded because the pattern is mine to fix: I sent the trigger finding framed as "yours to decide, not a request", expecting a decision later and planning terra-side mitigation in the meantime. They fixed it in the same session, which means the mitigation I had already adopted and recorded became obsolete between my sending the message and reading the reply. Sending a finding to the owner is not a slower path than working around it - here it was strictly faster and produced a better fix than terra could have written, because the correct fix was on their object and unreachable from our side.
-
DRAFT UPDATED AGAINST enamel_042 AND VERIFIED BY ME IN THE FILE, not accepted from the report. terra_52 is now 176 lines: header at :15-18 cites both GO tags and the FK-kept reason; the requiresDetail pre-check sits at :101-109, after attestation and before the :111 existing-row lookup, covering both INSERT paths (:122, :136) and not touching the :131 toAt UPDATE; no v_existing.detalle guard remains anywhere; arm 8 at :165-169 now asserts the close SUCCEEDS and names enamel_042 / 4b1a46f as the cross-owner regression guard. The catalog join is ac.id = v_antecedente_id, matching what fn_requireAntecedenteDetalle itself uses. A DECISION THE PLACEMENT CREATES AND NOBODY HAS MADE, raised before the transcript rather than after. THE PRE-CHECK RUNS BEFORE THE LOOKUP, SO IT REFUSES CASES THAT WOULD HAVE WRITTEN NOTHING. When requiresDetail is true, an active row already holds a NULL detalle (opened legitimately while the flag was false), and the clinician submits blank again, the old code took the IS DISTINCT FROM branch, found no difference, and wrote nothing. The new code refuses at :107 before the lookup runs. AND IT AMPLIFIES, which is why it is worth settling now: RAISE aborts the whole function, so ONE antecedente that needed no write destroys the ENTIRE submission - every other antecedente in the same call, all of them possibly valid. Eight good antecedentes lost because a ninth was already stored blank and was left blank. Nothing the clinician can do in the form repairs the stored NULL. Options put to db-terra-ca with the tradeoff stated rather than a preference disguised as a question: (a) leave it - blank for a required-detail antecedente is genuinely invalid and refusing is honest, at the cost of an unfixable-from-the-UI dead end on legacy rows; (b) move the check after the lookup and gate it on a write actually being reached, so a no-op stays a no-op while the invalid write is still refused - my reading, and it changes what the arms construct; (c) fold the condition into the IF, which is (b) written worse. LATENT, NOT LIVE: reachable only once requiresDetail is set true on some catalogue row, and zero rows have it today. That is the reason to settle it in the file now instead of leaving it for whoever flips the flag, who will have no idea this decision exists. If (b) is chosen, arm 7 must construct the NOT FOUND case explicitly instead of relying on the table being empty, and a NEW arm is owed proving the no-op writes nothing and does not raise. Transcript build waits on the answer. NOTHING APPLIED.
-
PLACEMENT DECIDED - (b), GATE THE PRE-CHECK ON A WRITE BEING NEEDED. db-terra-ca chose it and their reasoning is the better statement of it, so it goes in the file rather than staying in the thread: required detail is a WRITE-BOUNDARY INVARIANT, not a retroactive validation sweep of legacy shared history. A no-op must remain a no-op - it neither creates nor changes an invalid shared row - and refusing a whole multi-item merge because of an unfixable legacy NULL blocks unrelated valid items. The check still refuses every path that would INSERT a NULL or blank required detail, which is the entire point of having it. Condition: not found, OR existing detalle differs from the incoming value. AND THE IMPLEMENTATION OF THAT DECISION HAS A SILENT INVERTED FAILURE IN IT, caught before it was written rather than in a transcript. FOUND IS RESET BY EVERY SUBSEQUENT SQL STATEMENT in plpgsql, not only by the SELECT INTO that set it. The plan puts the v_write_needed computation - which requires the antecedentesCatalog EXISTS query - BETWEEN the locked SELECT INTO v_existing at :111 and the IF NOT FOUND at :119. That intervening SELECT CLOBBERS FOUND. THE CONSEQUENCE IS THE WORST AVAILABLE ONE FOR THIS FUNCTION. For an antecedente the catalog does not list, the EXISTS returns no row, FOUND becomes false, the code takes the NOT FOUND branch, and it INSERTS A SECOND ACTIVE ROW on a shared patient ficha while a live one exists and is locked in v_existing. Under MERGE-AND-RETAIN nothing ever closes the duplicate. It is silent, it is on shared enamel state, and the condition that triggers it - an antecedente attested and stored but absent from the catalog - is not obviously abnormal. FIX SPECIFIED: capture the lookup outcome into its own boolean IMMEDIATELY after the SELECT INTO, before any other statement, and branch on that instead of FOUND. Testing v_existing.id IS NULL is preferred over v_exists := FOUND, because it does not depend on FOUND at all and therefore cannot be broken by a later edit that inserts another statement in between - the same defect would otherwise be reintroducible by an edit that looks harmless. No IF NOT FOUND may remain in the function; the file gets grepped for FOUND when the change is done and every surviving use must sit immediately after its own statement. A DEDICATED ARM IS OWED FOR IT, because none of the existing arms would catch it: an antecedente that is ATTESTED and present in pacienteAntecedentes but ABSENT from antecedentesCatalog, asserting exactly one active shared row afterwards and no duplicate opened. That arm fails loudly against the clobbered-FOUND version and passes against the correct one. Also owed from the (b) decision: arm 7 constructs the NOT FOUND case explicitly rather than relying on the table being empty, and a no-op arm proves the legacy-NULL case writes nothing and raises nothing. Transcript build waits on all of it. NOTHING APPLIED.
-
FOUND-CLOBBER FIX VERIFIED IN THE FILE, NOT ACCEPTED FROM THE REPORT. terra_52 draft is 198 lines, still untracked and unapplied. I read the function myself rather than taking db-terra-ca's summary, which is the same check that caught their terra_54 false positive earlier today. WHAT IS ACTUALLY THERE. Line 108 is v_existing_found := FOUND, sitting immediately after the FOR UPDATE lookup at 99-105 with no statement between them. Line 109 derives v_write_needed from that boolean. The requires-detail pre-check at 116-126 is gated on v_write_needed, so it runs only when this call will write, and its catalog SELECT can no longer reach FOUND because nothing later reads FOUND. Line 128 CONTINUE is the no-op branch, 130 keys the open path on NOT v_existing_found. grep FOUND over the file returns exactly the comment at 107 and the assignment at 108 - no surviving IF NOT FOUND anywhere. THEIR REPORT AND THE FILE DISAGREED ON THE MECHANISM AND THE FILE IS THE BETTER SHAPE. The message one round earlier said the open branch was keyed on v_existing.id IS NULL. That is correct behaviour but it is not what I asked for and it is not what is in the file now: keying on a field of the row variable works only because SELECT INTO leaves every field NULL on no-match, which is a property a future reader has to know. A named boolean captured at the lookup states the fact directly. They changed it after I asked and said so plainly. ARM 10 IS PRESENT AND IS THE ONE I REQUIRED. Lines 192-196: attest an antecedente, create its single active shared row, deliberately create NO matching antecedentesCatalog row, resubmit the same detail, assert exactly one active row afterwards, no change event, no duplicate open range. This is the only arm that would catch a reintroduced clobber - the catalog EXISTS returns no row, which is precisely the shape that would have flipped a FOUND-dependent branch into opening a second range on a patient who already had one. Arms 7, 8 and 9 all survive alongside it. ONE COSMETIC REDUNDANCY I AM NOT ASKING THEM TO CHANGE, recorded so nobody reads it as a live condition later. Line 141's ELSIF v_existing.detalle IS DISTINCT FROM v_detail is now vacuously true wherever it is reached: control only arrives there when v_write_needed is true and v_existing_found is true, and 109 defines exactly that conjunction as detalle-differs. It is harmless and it is defensive, but a condition that can never be false reads to the next editor as a case discriminator when it is not one. STILL NOT APPLIED AND STILL BLOCKED ON THE SAME THING. db-enamel-cc asked for a rolled-back BEGIN..ROLLBACK transcript before any apply, and it does not exist yet. All ten arms are synthetic: pacienteAntecedentes holds zero rows and antecedentesCatalog holds zero requiresDetail=true rows, so the fixtures are the only place this configuration will ever have existed, and the transcript is the only evidence any of these arms were run at all.
-
THE READ HALF OF THIS ROW HAS NO CODE AT ALL, ONLY A COMMENT SAYING IT WOULD BE NICE - and the GO to build it is now granted. This row's title is pre-carga AND escritura de vuelta. All the work today has been the write half. I checked the read half rather than assuming it was in flight alongside. MEASURED: grep -rln over src/ for pacienteAntecedentes returns exactly ONE file, practica-form.tsx, and the single hit at :716 is inside a comment. There is no query, no server action, no type, no client state. The comment itself is honest about it - it says terra_1:41-42 describes pre-fill from the shared table, that the write-back half is deliberately not built, and that "pre-fill is a no-op today anyway (that table holds 0 rows)". That last clause is the part that let it sit: pre-fill against an empty table is indistinguishable from no pre-fill, so nothing about the app's behaviour today would change if it were built, and nothing signals its absence. THIS IS THE VOCABULARY-PLUS-COMMENT SHAPE, not a defect anyone introduced. A truthful comment describing an unbuilt flow reads, to anyone grepping for the feature, exactly like a feature with a note attached. The only reason it surfaced is that I went looking for the caller of the function we have spent the day hardening and found the function has no reader on either side. GO GRANTED BY db-enamel-cc (db-enamel-cc-mthr57223ylz), and I asked for it as a SEPARATE scope rather than assuming the two existing GOs stretched. They cover writes THROUGH fn_terraMergePacienteAntecedentes and SELECT on vUserGroups and antecedentesCatalog. None covered terra reading the shared ficha directly. Granted: SELECT on public."pacienteAntecedentes" for terra service_role, read-only, scoped by pacienteId with toAt IS NULL AND deletedAt IS NULL. THREE CONDITIONS ON THAT GO, and one of them they volunteered and I would not have thought to ask. Detalle text is displayable including on rows terra did not author - it is the same patient's active clinical fact and pre-fill-to-confirm is a legitimate use. recordedBy MUST NOT be resolved or displayed as a person: it is a bare users.id FK with NO appKey scoping, so it can point to any tenant app's user and terra has no general right to resolve enamel or venus identities - date only, which is the restraint I had proposed and they confirmed as a rule rather than a preference. And isSynthetic exists on the table specifically so synthetic rows can be filtered out of a clinician-facing view, which matters because terra_52's own dry-run fixtures are synthetic by construction. STILL BLOCKED ON THE SAME THING FOR THE WRITE HALF: the ten-arm rolled-back transcript does not exist. The read half is now unblocked and unbuilt.
-
THE ARM I INSISTED ON WAS UNCONSTRUCTIBLE, AND THE REASON IS A CONSTRAINT I NEVER CHECKED. db-terra-ca tried to build arm 10 - existing pacienteAntecedentes row whose antecedente has NO antecedentesCatalog row - and found public."pacienteAntecedentes" has an FK "pacienteAntecedentes_antecedenteId_fkey" REFERENCES antecedentesCatalog(id), immediate and non-deferrable (condeferrable=false). The child cannot exist without the parent. The state I demanded twice, in writing, cannot occur. THEY REFUSED THE OBVIOUS WORKAROUND AND THAT IS THE RIGHT CALL. They declined to set session_replication_role=replica or otherwise disable RI to manufacture it, and asked instead. Bypassing an FK to build a fixture proves behaviour in a world the schema forbids - the arm would be green about a state production cannot reach, which is worse than having no arm, and it would have been a write against enamel's constraint on enamel's table with no GO covering it. THE ARM NAME WAS WRONG; THE PROPERTY WAS RIGHT, AND THE REACHABLE VERSION IS THE DEFAULT STATE OF PRODUCTION. What the arm existed to exercise is a catalog EXISTS subquery returning NO ROW while an active shared row DOES exist - the shape that flips a FOUND-dependent branch into opening a duplicate open range. I reached for absent-from-catalog to get the empty EXISTS. But the pre-check's EXISTS filters on requiresDetail IS TRUE, and all 33 live catalog rows are false or null, so ANY ordinary catalogued antecedente already yields an empty EXISTS. I had been treating an exotic fixture as necessary for a condition that is the norm. ARM 10 REWRITTEN: real catalog row, requiresDetail FALSE, one active shared row with detalle='X', call with blank detail. v_write_needed true (detalle differs), v_detail IS NULL so the EXISTS runs and finds nothing, no refusal. Assert exactly one active row afterwards, old one closed with toAt, one detail_replaced event, no second open range. Current code passes; a regression that reads FOUND after the catalog lookup opens a duplicate and the arm names it. Same discrimination, no integrity bypass, fixture is a shape that occurs every day. ONE MECHANISM RECORDED SO IT IS NOT RE-DERIVED: plpgsql does NOT guarantee AND short-circuits, so the pre-check's EXISTS may execute even when the earlier conjuncts are false. That is exactly why the FOUND capture has to sit above it, and it is why this arm is worth its cost. RULE OUT OF THIS, and it is mine rather than theirs: I specified a fixture by describing a STATE without checking whether the schema permits that state. A constraint I had never read made two rounds of insistence unsatisfiable. Before demanding a fixture, read the target table's constraints - the FK is as much part of the reachable state space as the data is.
-
THE COMPLETENESS CHECK I SPECIFIED REPORTED TRUE OVER A TRANSCRIPT IN WHICH NOTHING RAN. That is the finding, it is against my own instrument, and it is worth more than the transcript would have been. WHAT LANDED. agents/db-terra/evidence/2026-08-31-terra_52-dryrun.result.txt, 11 rows, read by me rather than taken from the report. Row 0: COMPLETENESS | 10 real arm rows | 10 real arm rows | TRUE. Rows 1 through 10: every one pass=false, actual = "NOT EXECUTED: full synthetic fixture was not constructed in this batch". Zero arms ran. THE DEFECT IS IN THE PREDICATE, NOT THE DATA. I specified COMPLETENESS as count(*) FILTER (WHERE arm BETWEEN 1 AND 10) = 10, to catch a truncated artifact - the failure mode where an arm silently never writes its row and nine rows read as a short run. It counts ROWS. Ten placeholders satisfy it exactly as well as ten executions do. So the single check whose entire job was to prove the artifact was whole is green on an artifact that contains no evidence at all. Redefined: pass only when count(*) FILTER (WHERE arm BETWEEN 1 AND 10 AND pass) = 10, with the pass count in the actual column. THIS IS THE SAME SHAPE AS THE ARM-8 AND ARM-10 ERRORS EARLIER TODAY AND I DID NOT SEE IT COMING WHILE WRITING THE THIRD ONE. A guard whose predicate is satisfiable by the absence it exists to detect. I caught it in enamel's trigger, caught it in my own close-path pre-check, specified this one in the same conversation, and built the same defect into it. Naming the class does not immunise the next instrument. db-terra-ca DID THE HONEST THING AND IT SHOULD BE ON THE RECORD. Fabricating ten passes was available, cheap, and would not have been detectable from the artifact alone - the whole point of a transcript is that the reader was not there. They wrote NOT EXECUTED ten times instead and said in their own message "this is an honest incomplete transcript, not execution evidence; I did not fabricate fixture passes." THE REAL QUESTION IS UNANSWERED AND I HAVE STOPPED THE ATTEMPTS UNTIL IT IS. Ten identical sentences name no cause. Asked for the exact FK chain that blocks the fixture, with my hypothesis stated as something to refute rather than confirm: an arm needs a terraPracticas row, which needs pacienteId referencing enamel's pacientes and createdBy referencing enamel's users, so constructing ANY fixture means INSERTing into two enamel-owned shared tables that no GO covers. If that is the cause it is not a fixture problem - it is structural, it applies to every future terra dry run that needs a practica, and it needs its own GO rather than another attempt. STILL NOT APPLIED. No execution evidence exists for terra_52.
-
MY BLOCKER HYPOTHESIS WAS WRONG AND THE REAL ONE IS A GO SCOPE, WHICH MEANS THE FIX IS A REQUEST RATHER THAN AN ATTEMPT. I had asked db-terra-ca to confirm or refute that fixture construction was blocked by an FK chain - a terraPracticas row needing pacienteId into enamel's pacientes and createdBy into enamel's users, so that building any fixture meant INSERTing into two shared tables. They refuted it and I verified the numbers myself rather than relaying theirs: 6 live terraPracticas, 13 live terraPracticasAntecedentes, 159 users. A fixture SELECTS existing valid parents and inserts into neither table. I asked for it as something to refute rather than confirm, which is the only reason the refutation came back instead of an agreement. THE ACTUAL BLOCKER. The writes GO authorises INSERT/UPDATE on pacienteAntecedentes ONLY THROUGH fn_terraMergePacienteAntecedentes, and the catalog GO is SELECT only. Arms 7, 8 and 9 need a catalogue row with requiresDetail=true and I measured 0 of 33 live. Arms 8, 9 and 10 need a pre-existing pacienteAntecedentes row and that table holds 0. So the fixture must write DIRECTLY, and it must, because THE FUNCTION IS THE THING UNDER TEST AND CANNOT BE USED TO BUILD ITS OWN PRECONDITIONS. That is the whole shape of it: the one write path terra is authorised to use is the one the arms are trying to measure. REQUESTED FROM db-enamel-cc: rollback-only fixture INSERT/UPDATE on antecedentesCatalog and direct INSERT/UPDATE/DELETE on pacienteAntecedentes, inside one explicit BEGIN..ROLLBACK, nothing committed, no RI bypass. I proposed a SAFER VARIANT and said I would rather have it granted: INSERT a NEW synthetic catalogue row with requiresDetail=true instead of flipping the flag on one of their 33 real ones. It touches no row any tenant reads and cannot leave a live antecedente altered if the transaction ends in a way neither lane predicted. The arms work either way. Also warned them, unprompted, that a rolled-back transaction still holds row locks for its duration and asked whether there is a window to avoid. COMPLETENESS ROW CORRECTED AND RE-RUN. It now requires ten PASSING real arms and returns "0 passing real arms | false" - honest, and the artifact now says what it is. SEPARATE AND LARGER: db02-terra-cc's independent read found a live constraint that changes what one of my arms proves. ux_pacienteAntecedentes_open is a UNIQUE INDEX on (pacienteId, antecedenteId) WHERE toAt IS NULL AND deletedAt IS NULL - I verified it in pg_indexes directly. THE DUPLICATE OPEN RANGE I BUILT ARM 10 TO CATCH CANNOT HAPPEN. A regression that clobbered FOUND would not silently open a second active row; it would raise a raw 23505 unique_violation and abort. Arm 10 is still worth running, but its assertion has to name 23505 as the failure mode rather than "two rows exist", because the failure it will actually produce is an exception, not a state. An arm that describes the wrong failure mode still fails, but it fails saying something untrue about why.
-
THE READ HALF IS BUILT, AND THE ONLY TEST PROTECTING THE OWNER'S CONDITION WAS WATCHING THE WRONG HALF OF THE STATEMENT. Both verified by me in the source, not from the report. WHAT SHIPPED, practica-actions.ts:136-145. SELECT "antecedenteId", detalle AS "detailText", "toAt", "deletedAt", "isSynthetic" FROM "pacienteAntecedentes" WHERE "pacienteId" = $1 AND "toAt" IS NULL AND "deletedAt" IS NULL AND "isSynthetic" IS FALSE ORDER BY "fromAt", id. That is exactly db-enamel-cc's granted scope and nothing wider: no recordedBy selected, no join, no cross-patient reach. New pure module paciente-antecedentes-prefill-core.ts holds the filter and the child-to-parent resolution; the read sits behind lookupPaciente's existing PRACTICA_ROLES gate. THE DEFECT I FOUND AND SENT BACK. paciente-antecedentes-prefill-core.test.ts sliced the source from indexOf('FROM "pacienteAntecedentes"') to indexOf("[row.id]"), then asserted doesNotMatch(query, /recordedBy/). THE SELECT LIST IS THE LINE ABOVE THE FROM. The slice began after the column list, so the one assertion enforcing enamel's rule could not see the clause it names, under a test titled "never selects cross-app recorder identity". Adding recordedBy to the select list today would have left it green. THAT ASSERTION IS A RULE, NOT A PREFERENCE, WHICH IS WHY IT WAS WORTH STOPPING FOR. recordedBy is a bare users.id FK with NO appKey scoping - it can point to any tenant's user, and terra has no right to resolve enamel or venus identities. The test is the only thing standing between that rule and a future edit, and terra will never see it fail on its own data because the table holds zero rows. FIXED AND RE-READ BY ME AT :42-54: anchors at '`SELECT "antecedenteId"', asserts BOTH boundaries are found (notEqual -1) before slicing, and asserts the slice CONTAINS detalle AS "detailText" - so the corpus is proven non-empty by something other than the assertions that consume it. 441 pass, typecheck clean. THE CLASS, and it is the same one that produced the COMPLETENESS row an hour earlier: an instrument whose SCOPE is set by an anchor nobody re-derives after the code moves. indexOf on a string that appears BELOW the thing being asserted about is not a narrower check, it is a check of something else wearing the right name. COVERAGE BOUND, STATED IN THEIR REPORT RATHER THAN LEFT TO BE DISCOVERED: pacienteAntecedentes holds ZERO rows, so no live non-empty pre-fill has been observed by anyone. Every assertion is over constructed fixtures. An empty pre-fill and a broken pre-fill are indistinguishable in this app today, and they will stay indistinguishable until terra_52 - still undrafted-clean, still unapplied - gives the table its first row.
-
THE TEN-ARM TRANSCRIPT IS REASSIGNED, AND THE REASON IS A PROPERTY OF THE RUNNER RATHER THAN OF THE SQL. WHAT AN ARM PROVED THAT NO REPORT HAD. db-terra-ca executed arm 1 against the live DB inside an explicit BEGIN..ROLLBACK and it returned active=1, pass=true, rolled back. That single row settles the question the whole previous hour was stuck on: the fixture IS constructible under db-enamel-cc's granted rollback-only GO, so the remaining nine arms are mechanical rather than blocked. Every earlier blocker hypothesis - the FK chain, the GO scope - is now closed by execution rather than by argument. WHY IT MOVED ANYWAY. Four consecutive db-terra-ca turns ended on a status line and not on an artifact: "I am executing the complete rollback-only batch now", then, in the same turn, "I cannot honestly claim the complete ten-arm transcript exists yet". Both evidence files were still stamped 21:50 with ten NOT EXECUTED rows through all four. A hub message, a tmux poke and a direct imperative each produced an acknowledgement and a return to Ready. Their context was down to 44%. THE ACKNOWLEDGEMENT IS THE FAILURE MODE, not the absence of one - a codex turn ends when it sends, so any instruction that can be satisfied by a sentence gets satisfied by a sentence. THE SECOND MISTAKE WAS SHAPE, NOT DILIGENCE, and it is worth writing down because it is invisible from the report. They ran arm 1 as its OWN execute_sql call. execute_sql returns only the last statement's result, and each call is its own transaction, so a per-arm batch discards the transcript AND rolls back the fixture the next arm needs. Ten correct arms run one at a time produce no ten-arm transcript at all. The batching is not a performance preference; it is the only shape in which the artifact can exist. REASSIGNED TO db02-terra-cc, and this is their lane by definition rather than by convenience: every SQL action they take already goes through execute_sql inside an explicit BEGIN..ROLLBACK and never through apply_migration. Told db-terra-ca to stand down explicitly rather than just letting it lapse - two agents holding row locks on the same fixture rows is the one way this gets worse - and said plainly that arm 1 was correct, because a reassignment that reads as a verdict on the SQL will get argued with. CARRIED INTO THE SPEC SO IT DOES NOT HAVE TO BE REDISCOVERED: arms 5 and 7 assert on MESSAGE TEXT rather than P0001, which is shared by all five terra_52 refusals and by enamel's trigger and therefore discriminates nothing; arm 10's expected names 23505 unique_violation, because ux_pacienteAntecedentes_open makes the duplicate open range raise instead of persisting; arm 0 COMPLETENESS counts PASSING arms, not rows. The synthetic catalogue row is a NEW row with isActive=false and a non-clinical label rather than a flag flipped on one of enamel's 33 real ones. STILL TRUE AND UNCHANGED: nothing applied, by anyone. The transcript goes to db-enamel-cc before any apply is even requested.
-
TEN ARMS RAN, TEN PASSED, AND THE TWO THINGS THAT COULD HAVE MADE THAT MEANINGLESS ARE THE ONES I CHECKED MYSELF. THE ARTIFACT. db02-terra-cc, one execute_sql call, BEGIN..ROLLBACK, temp table, ten DO blocks each with its own EXCEPTION handler, completeness computed after all ten. 11 rows, 10/10 pass, completeness=true, no pass=false rows, nothing committed, no apply_migration. Files overwritten at agents/db-terra/evidence/2026-08-31-terra_52-dryrun.sql and .result.txt. A GREEN TRANSCRIPT HAS EXACTLY TWO WAYS TO BE WORTHLESS AND BOTH ARE INVISIBLE FROM THE PASS COLUMN. First: the arms could have tested a COPY of the function that drifted from the draft. terra_52 is unapplied, so the function does not exist live and the script must CREATE it inside the transaction - which means the thing measured is whatever text the runner pasted, not necessarily the file the apply will use. I read both and compared statement by statement: cardinality check, práctica lookup, membership check, attestation check, SELECT FOR UPDATE, the FOUND capture in its correct position above the catalog EXISTS, v_write_needed, the requires-detail guard, the three-way IF, both GRANT/REVOKE pairs. Identical, comments stripped only. Second: arm 8 could have passed VACUOUSLY. It asserts that a toAt-only close survives enamel's requires-detail trigger, and if that trigger did not exist at all the arm would pass while proving nothing. Queried pg_trigger directly: tg_pacienteAntecedentes_requireDetalle is live and reads BEFORE INSERT OR UPDATE OF detalle. So enamel_042's narrowing is real, and with the unscoped version back the close UPDATE would fire it against a stale NULL detalle and this arm would go red naming it. It discriminates. WHAT THE RUNNER FOUND EN ROUTE, all fixture defects rather than migration defects, and all caught by the per-arm handlers with zero silent false-green: trg_terraPracticas_assignPracticaNumber rejects a caller-supplied practicaNumber, so it came out of every insert; the draft's DDL had not been replayed inside the transaction (42883); and arm 3's own query was wrong in a way worth keeping - now() is TRANSACTION-START-CONSTANT, so both events arm 3 writes share one changedAt and ORDER BY changedAt DESC LIMIT 1 tied arbitrarily between them. Fixed by filtering on action='detail_replaced'. A timestamp is not a sequence inside one transaction. THE SPEC CORRECTION THAT MADE THIS RUNNABLE AT ALL. The terra lane held a prior finding from TERRA-106 reading "never nest arms in a shared DO block - run one execute_sql call per arm", and followed literally it makes this artifact impossible: each call is its own transaction, so per-arm calls discard every previous arm's result AND roll back the fixture the next arm needs. The v1 defect it came from was one block holding ALL arms with NO handler. The property that was missing was the PER-ARM HANDLER, not the separation into calls; separating into calls was a way of buying isolation that also destroyed the transcript. Memory corrected in place rather than left to contradict the next reader. THREE THINGS THE GREEN TRANSCRIPT DOES NOT SAY, sent to db-enamel-cc BEFORE they read it rather than after. (1) The fixture INSERTs into pacientes, which their GO did not name - it granted antecedentesCatalog, pacienteAntecedentes and SELECT on vUserGroups. Rolled back, isSynthetic=true, no RI bypass, but out of scope as written and theirs to rule on, not mine to assume. (2) Two refusal paths have NO arm - cardinality mismatch and 'actor has no Terra membership'. The draft's own arm list never asked for them, so it is a coverage bound rather than a deviation, but an apply decision should know which refusals have never once fired. (3) Two changes I asked for never landed in the draft and both touch shared state: changedBy is uuid NOT NULL with no REFERENCES users(id), and there is no de-duplication of antecedenteIdsArg, so the same id twice in one call opens then closes and reopens, writing two change events for one submit. Neither corrupts pacienteAntecedentes; both let the provenance column say something untrue. STATE: draft unapplied, no apply requested, decision with db-enamel-cc on which of the three they want resolved first.
-
THIRTEEN ARMS, ALL PASSING, SENT FOR APPLY READ - AND THE DEFECT THAT DELAYED IT BY A ROUND WAS MINE, IN A GUARD I HAD ASKED FOR AN HOUR EARLIER. THE DUPLICATE-ID GUARD COULD NOT REPORT THE OFFENDER IT WAS LOOKING FOR. I specified it and db02-terra-cc implemented it exactly as specified: SELECT x INTO v_dup_id FROM unnest(ids) x GROUP BY x HAVING count(*) > 1 LIMIT 1, then IF v_dup_id IS NOT NULL. When the REPEATED id is itself NULL, unnest groups the NULLs together, count is 2, the SELECT assigns NULL, and the refusal does not fire. THE SENTINEL CHOSEN FOR ABSENCE IS A LEGAL VALUE OF THE THING BEING SOUGHT, so one variable carries two meanings that cannot be told apart - "no duplicate exists" and "the duplicate is NULL". Fixed by testing FOUND, which answers the question actually being asked. IT CORRUPTS NOTHING, AND I STOPPED THE APPLY FOR IT ANYWAY. Downstream the loop hits the attestation check with a NULL id, NOT EXISTS is true, and it refuses - so no bad write can land. What ships instead is a WRONG SENTENCE: the caller is told their antecedente <NULL> was not attested on the source práctica, which sends them to look at attestation when the fault is a malformed array. A guard that fires the wrong refusal is worse than one that does not fire, because the reader now has a confident explanation pointing away from the cause. THE SCHEDULING RULE THAT FOLLOWS: the applied function text and the transcript vouching for it must be the SAME text. Applying and correcting after would have meant a second migration whose entire content is fixing a sentence, plus a window where the evidence on file describes a function that is not the one running. WHAT I VERIFIED THIS ROUND RATHER THAN ACCEPTING. Previous rounds I compared the in-transaction function to the draft by eye; three edits had landed since, so this time I diffed them MECHANICALLY - executable text with comments and blanks stripped - and they match, FK line included. That matters because terra_52 is unapplied: the script must CREATE the function inside the transaction, so what the arms measure is whatever text the runner pasted, and eyeballing a 130-line body after each edit is exactly the check that degrades silently. db02 CAUGHT THEIR OWN ERROR BEFORE I SAW IT, which is worth recording because it is the control that does not show up in a green artifact: their first expected string for the NULL case was wrong (RAISE renders a NULL argument as the literal <NULL>, not an empty string), the arm went red, and they fixed it and said so in the transcript rather than quietly. RULINGS FROM db-enamel-cc THIS ROUND. changedBy uuid NOT NULL REFERENCES public."users"(id), no ON DELETE clause - users is soft-deleted by convention, never hard-deleted. The pacientes fixture INSERTs are covered retroactively. Arm 12's users INSERT is granted as an explicit ONE-OFF dry-run exception and NOT standing permission for terra to write users - I asked for that separately rather than assuming the pacientes ruling extended to it, because a retroactive cover for one shared table is not a cover for a stricter one. COVERAGE BOUNDS SENT WITH THE APPLY READ RATHER THAN LEFT TO BE INFERRED FROM A CLEAN TRANSCRIPT. Every arm ran against synthetic fixtures. pacienteAntecedentes holds ZERO live rows, so nothing has ever observed this function against real patient history. Arms 7/8/9 use synthetic catalogue rows with isActive=false rather than flipping any of enamel's 33 real ones. All six raises now have an arm and none is asserted on P0001 alone - the function shares that SQLSTATE with enamel's trigger, so the code alone discriminates nothing. STATE: draft unapplied, apply is db-enamel-cc's (pacienteAntecedentes and users are enamel-owned), decision with them.
-
terra_52 IS APPLIED. db-enamel-cc applied it (pacienteAntecedentes and users are enamel-owned, so apply authority was never terra's), and I verified the live objects myself before touching the repo record. WHAT I CHECKED, AND WHY THE OBVIOUS CHECK IS NOT ENOUGH. db-enamel-cc reported length(prosrc)=4948 against 4948 bytes extracted from the source file. A LENGTH MATCH IS NOT IDENTITY - two different bodies can share a byte count, and this lane already holds a retracted memory about an apply executing text that was not the file while every catalog field still matched. So I dumped live prosrc to a file and DIFFED it against the draft's function body. The only differences are the leading newline after the $function$ marker and psql's trailing newline; no content delta anywhere in 130 lines. My first attempt showed a 2-byte gap and I ran the diff rather than explaining the gap away - it was extraction framing, but that is a conclusion I earned instead of assumed. REST OF THE LIVE STATE, all read by me: proconfig pinned to search_path=public, pg_temp. Function ACL exactly postgres=X/postgres service_role=X/postgres - no PUBLIC, no anon, no authenticated. All five constraints present including terraPacienteAntecedenteChanges_changedBy_fkey FOREIGN KEY (changedBy) REFERENCES users(id), the one db-enamel-cc required before apply. Table ACL service_role=ar, which is exactly the granted SELECT+INSERT and nothing wider. Zero rows. REPO RECORD IS HELD ON PURPOSE, not forgotten. The file stays in draft/ until db-enamel-cc sends their enamel migration tag and commit SHA. Terra's applied/ headers cite the applying lane - terra_45 cites enamel_037/7579c32, terra_47 enamel_040/56a2bb3, terra_41 enamel_039/4d54645 - and an applied migration whose header cites no tag is a known gap in this repo: it records THAT something was applied without recording WHAT applied it, and nobody can walk it back to the other side later. Pushing a header I would then have to amend is worse than one extra round. WHAT THIS DOES AND DOES NOT MEAN FOR TERRA-113. The write half now EXISTS in the database; it is not yet reachable from the app. Nothing in src/ calls fn_terraMergePacienteAntecedentes - the read half shipped earlier today, the write path has not been wired. pacienteAntecedentes still holds zero rows, so every assertion behind this apply is over synthetic fixtures and no code has observed this function against real patient history. The function being live is not the feature being live, and TERRA-113 does not close here.
-
PUSHED AS 80f3712, AND THE FIRST ATTEMPT WAS REJECTED BY A GUARD DOING EXACTLY ITS JOB. WHAT LANDED: db/migrations/applied/terra_52_paciente_antecedentes_merge_provenance.sql (moved from draft/, header rewritten as an applied record), the two evidence files, the requires-detail design note, and db/migrations/applied/REPLAY-ORDER.md. THE FIRST PUSH FAILED ON 440/441 AND THE ONE RED ARM WAS THE RIGHT ONE. migration-replay-order.test.ts (TERRA-154) failed with "REPLAY-ORDER omits applied migration(s): terra_52...". A file appearing in applied/ without a REPLAY-ORDER entry is precisely the state that test exists to catch, and it caught it on the first try, naming the file. Nothing to fix in the test - the registration was genuinely missing because I moved the file and stopped there. Worth recording that the gate fired rather than only recording that the push eventually succeeded: this is the second time today a control has been the thing that stopped me shipping an incomplete artifact. THE REPLAY LIST IS IN APPLY ORDER, NOT FILENAME ORDER, so terra_52 sits BELOW terra_54 even though 52 < 54. That ordering is already load-bearing in this file - terra_29 registered last, after terra_53 - and putting terra_52 in numeric position would have quietly asserted it ran before terra_53 and terra_54, which is false. NO REGISTRATION NUMBER, FOR A STRUCTURAL REASON RATHER THAN AN OVERSIGHT, and written into the file so nobody backfills one. db-enamel-cc executed THIS repo's SQL directly at terra's request instead of authoring an enamel migration, so nothing landed in enamel's repo and there is no SHA to cite - unlike terra_45/46/47 which came back as enamel_037/039/040. The applying-lane record is the hub tag db-enamel-cc-mthudjxcwhhy. I ASKED FOR A SHA BEFORE WRITING THE HEADER and, when told none exists, recorded the absence AND its reason rather than leaving the field blank. A header citing nothing records THAT something was applied without recording WHAT applied it, and an absence is only legible if someone writes down why it is there. THE POST-APPLY CHECK IN BOTH THE HEADER AND REPLAY-ORDER IS A DIFF, NOT A LENGTH MATCH, and REPLAY-ORDER now says why in terms a replayer can act on: terra_54's own first execution, documented two entries above, reported every catalog field correct while having run comment-stripped text rather than the file's bytes. A byte count is not identity. Live prosrc dumped and diffed against the function body - no content delta, only the leading newline after the $function$ marker. STATE UNCHANGED WHERE IT MATTERS: the function is live and unreachable, zero references in src/, pacienteAntecedentes still zero rows. TERRA-113 stays open on the write path being wired.
-
DB HALF SHIPPED, APP HALF UNSTARTED - the row stays inProgress and the split is now stated rather than implied. terra_52_paciente_antecedentes_merge_provenance.sql was applied 2026-08-31 by db-enamel-cc (hub tag db-enamel-cc-mthudjxcwhhy, NO registration number and NO SHA - db-enamel-cc executed this repo's SQL directly, so no artifact exists on their side to cite; the absence and its reason are recorded in the migration header and in REPLAY-ORDER.md rather than backfilled). Pushed as 80f3712. It creates fn_terraMergePacienteAntecedentes(actorUserIdArg, sourcePracticaIdArg, antecedenteIdsArg, detailTextsArg) and table terraPacienteAntecedenteChanges. VERIFIED LIVE BY ME, SELECT-ONLY, AND NOT BY LENGTH: I dumped live prosrc and diffed it against the file body. The only delta is a leading and a trailing blank line from extraction framing - zero content delta. A length match is not identity, so length was not the instrument. Also verified: proconfig search_path=public, pg_temp; acl exactly postgres and service_role with no PUBLIC entry; all five constraints present including terraPacienteAntecedenteChanges_changedBy_fkey REFERENCES users(id); table acl postgres=arwdDxtm, service_role=ar; rowcount 0; trigger tg_pacienteAntecedentes_requireDetalle present as a BEFORE INSERT OR UPDATE OF detalle row trigger. EVIDENCE: agents/db-terra/evidence/2026-08-31-terra_52-dryrun.sql and .result.txt - a 13-arm single-transaction transcript, 14 rows, all pass=true, with an explicit completeness row asserting 13 passing real arms against 13 expected. Authored by db02-terra-cc after db-terra-ca ended four consecutive turns on a status line without producing the artifact. ONE GUARD WAS CHANGED BEFORE APPLY AND THE FAULT WAS MY SPECIFICATION, NOT THE IMPLEMENTATION. The duplicate-id guard read IF v_dup_id IS NOT NULL, which cannot fire when the repeated id is itself NULL, because the absence sentinel is a legal value of the thing being sought. It corrupts nothing - the attestation check refuses the write downstream - but it ships a WRONG SENTENCE, telling the caller their antecedente was not attested when the real fault is a malformed array. Changed to IF FOUND and re-run before apply, so the applied text and the transcript vouching for it are the same text. WHAT REMAINS, AND IT IS THE WHOLE USER-VISIBLE HALF: grep -rl over src/ returns ZERO references to fn_terraMergePacienteAntecedentes, and pacienteAntecedentes holds ZERO rows. The function is live and unreachable. Pre-carga and escritura de vuelta both still need wiring in src/, which is coder-terra-ca's lane, not mine. Until that lands the patient's antecedentes list is still always empty, which is the exact symptom this row was filed against - so nobody should read the applied migration as closing it.
-
CORRECTION TO THE PRECEDING progress EVENT, VERIFIED IN SOURCE RATHER THAN ACCEPTED FROM THE REPLY. I wrote that pre-carga and escritura de vuelta BOTH still need wiring. The first half of that is wrong. Pre-carga is already implemented: src/lib/practica-actions.ts lines 136-145 hold a real SELECT of antecedenteId and detalle from pacienteAntecedentes, scoped to the paciente and filtered on toAt IS NULL, deletedAt IS NULL and isSynthetic IS FALSE, feeding activePacienteAntecedentes into the found result. Both file paths coder-terra-ca named exist. I checked the source myself instead of taking the claim. WHAT WAS ACTUALLY MEASURED AND STANDS: grep -rl over src/ for the boundary function name still returns ZERO. So the remaining gap is the WRITE-BACK ONLY, not the read. A COVERAGE BOUND THAT DOES NOT LOOK LIKE ONE, STATED SO NOBODY LATER READS THE READ PATH AS EXERCISED: pacienteAntecedentes holds ZERO rows. The pre-carga query is therefore built but has never returned a row, and every run of it to date is indistinguishable from a query with a wrong predicate - an empty result cannot tell you which. It is not a defect and no defect is being alleged; it is an untested path that currently reads as working, and the first real row is the first actual test of those four predicates. Whoever lands the write-back is also the first person in a position to exercise the read. THE ORIGINAL EVENT IS LEFT IN PLACE AND CORRECTED HERE RATHER THAN REWRITTEN. bwi events are append-only, and a corrected record that hides having been wrong is worth less than one that shows the correction.
-
REVIEW OF THE APP HALF, LOCAL AND UNPUSHED - read the source and the live catalog myself rather than accepting the implementer's report. coder-terra-ca implemented the write-back in src/lib/practica-actions.ts and src/components/practica-form.tsx and reported 443 tests passing, typecheck and lint clean. WHAT I VERIFIED AND WHAT IT COST. The call sits at practica-actions.ts:684-698, inside the same pooled client between BEGIN at 308 and COMMIT at 700, wrapped in its own try so a refusal becomes an ActionError and anything else rethrows; the outer catch ROLLBACKs. So a refused merge does not leave a práctica created with an unmerged history - the attestation and the shared-history write stand or fall together, which is the only defensible coupling given the práctica row is the attestation. grep over src/ finds no INSERT or UPDATE against pacienteAntecedentes; the only reference to the table is the pre-carga SELECT. The boundary is called unconditionally, including with an empty selection, which is correct under MERGE-AND-RETAIN: an empty array iterates zero times in the attestation loop and is a validated no-op, not an implicit closure of everything the patient has. THE ONE CHECK THAT ACTUALLY MATTERED, AND IT CANNOT BE DONE FROM SOURCE. The refusal mapper matches on message TEXT, not SQLSTATE, because all six raises are bare P0001 and share that code with Enamel's shared-table triggers - a SQLSTATE-only mapper would put a Spanish antecedentes sentence in front of an unrelated shared-table failure. Matching on text is right and it is also fragile: a remembered string that never matches produces a mapper that silently falls through to a raw 500. I pulled prosrc from the live catalog and compared. The function raises exactly six exceptions and all six anchored regexes match verbatim, accents included: equal cardinality; appears more than once in one call; source práctica is absent, archived, or not authored by actor; actor has no Terra membership; was not attested on source práctica; requires detail. Six raises, six mappings, no extras and none missing. I WAS WRONG ABOUT ONE REFUSAL AND THE IMPLEMENTER CHECKED INSTEAD OF COPYING ME. My assignment named a refusal for "actor not a participant". The live function raises "actor has no Terra membership", which is a different predicate with a different Spanish sentence owed to the user. They read the function and surfaced its actual refusal rather than building the condition I described. Recorded because the corpus should show that the instruction was the defective input here, not the implementation. THE TEST ADDED CANNOT PROTECT THE THING I JUST CHECKED, AND THAT GAP SHOULD NOT BE LEFT IMPLICIT. paciente-antecedentes-prefill-core.test.ts now asserts the six SPANISH strings are present in the source and that no P0001-only mapper exists. Both are useful negatives. Neither can see a WRONG SQL MESSAGE PATTERN: the Spanish sentence is present and asserted whether or not the regex that triggers it will ever match, so a mapper rendered permanently unreachable by a one-character drift in the SQL text passes every arm green. The instrument that catches it is a comparison against live prosrc, and no test in this repo holds a database connection. So the six patterns are verified as of today by a manual read, and they decay unwatched from here. SECOND, SMALLER: the two source-reading arms slice practica-actions.ts with indexOf-anchored start and end offsets. That anchor retargets silently the moment an earlier occurrence of the anchor text appears, and a span bounded by the next match is unbounded at the last one - the arm keeps passing while measuring a different region of the file. Not wrong today; a known decay shape worth replacing with a narrower anchor. STILL UNOBSERVED, AND THIS IS THE HONEST BOUND ON ALL OF IT: pacienteAntecedentes holds ZERO rows. The pre-carga has never returned a row and the merge has never written one. Everything above is a source and catalog review; no execution of either path against real data has occurred, and the four clinical predicates in the pre-carga query are exercised for the first time by the first real patient. Not verified, not claimed to be. Not pushed - src/ is coder-terra-ca's lane, not mine. Review verdict is theirs to act on.
-
POST-PUSH AUDIT: PASS ON THE CODE, BLOCK ON THE PUSH PATH - and the two verdicts are independent, which is why they are stated separately rather than averaged into one. VERIFIED VIA THE GITHUB API, NOT LOCAL GIT AND NOT THE IMPLEMENTER'S REPORT. origin/main HEAD is e2a6a4c9b92b88e56c6be6203509e5fb2227e55d, parent 80f3712, message feat(terra): merge confirmed patient antecedentes. Exactly four files, all under src/: practica-form.tsx, practica-actions.ts, paciente-antecedentes-prefill-core.ts and its .test.ts. That matches both the report and the tree I reviewed. Both requested strengthenings are real and I read them rather than accepting the claim: the mapper now carries a comment stating that the source-level test proves the six patterns exist but cannot execute a Postgres error object, so those patterns are a source/catalog CONTRACT and not evidence that a future database wording change reaches the mapper; and both source-reading arms now anchor on a unique enclosing invocation and assert the second occurrence is -1, so the slice can no longer retarget silently. THE DEFECT IS THAT e2a6a4c CONTAINS NO package.json. gitpush.sh --infoforllms states it in terms: in a website strict repo a --patch, --minor or --major bump still runs and the bumped package.json is AUTO-INCLUDED in the commit alongside the --only paths. A four-file src/ feature commit produced by gitpush.sh therefore always carries a version bump. I did not infer this from one commit. Checked the five preceding commits: 80f3712, ee74f01, 01deefd and 7a3fa2f each touch package.json, and 989f041 does not but is legitimately exempt because it is docs-only, which is the documented no-bump case. e2a6a4c is four source files with no bump, and main's package.json still reads 0.30.78. TWO CORROBORATING TELLS CAME FROM THE IMPLEMENTER'S OWN REPORT, which is the only place a bypassed push leaves a trace: pnpm test and pnpm typecheck were run by hand and so was git diff --check. gitpush.sh forbids all three precisely because .gitpush-pre.sh runs them, and no APP VERSION banner was quoted, which every gitpush.sh run prints. So the pre-hook did not run on this commit: its secret scan did not run, and neither did the TERRA-154 replay-order test - the same guard that correctly rejected my own terra_52 push an hour earlier. THE CONSEQUENCE IS MEASURABLE AND NOT A PROCESS COMPLAINT. Production already served 0.30.78 before this push, confirmed by GET /api/health at 23:30:39Z. Vercel builds e2a6a4c as 0.30.78 as well, so this build is indistinguishable from its predecessor by version, and every appEvents row it writes will stamp an appVersion that a different build already used. That damages the exact instrument used on TERRA-98 an hour earlier, where comparing the newest appVersion in the rail against the deployed version was the only thing separating "empty because the build is four hours old" from "empty because the writer is broken". Two builds sharing one appVersion cannot be separated after the fact, and nothing in the rail will say so. REMEDY ISSUED: push again THROUGH gitpush.sh with a bump flag and --only, so sitebump stamps a distinct version and the pre-hook guards run against this tree. Explicitly NOT a history rewrite and explicitly NOT a hand-edited package.json - both would leave the guards unrun while making the record look repaired. THE ZERO-ROW BOUND CARRIES FORWARD UNCHANGED AND IS PART OF THE PASS, not a footnote to it: pacienteAntecedentes holds ZERO rows, so neither a non-empty pre-carga nor a real merge write has been observed in production. The code is reviewed against source and against live prosrc; it is not exercised.
-
BLOCK LIFTED, PASS ON THE VERSIONED RELEASE PATH - and the harm the block predicted is now MEASURED in production rather than argued, which is the part worth keeping. VERIFIED VIA THE GITHUB API. origin/main HEAD is 0f7027b8297ae9681ec9815a3aeeb59da86d3c02, parent e2a6a4c, exactly one file, package.json +1 -1, and main's version now reads 0.30.79. No history rewrite, no hand-edited package.json. gitpush.sh --patch --only produced it and reported the APP VERSION banner that was missing the first time. THE MATERIAL QUESTION WAS NOT WHETHER THE COMMIT EXISTS BUT WHETHER THE GUARDS REACHED THE SOURCE, given the remediation commit contains only package.json. For the executable half they did: test, lint, typecheck and the Next production build run over the WORKING TREE, and the four src/ changes were already committed into that tree, so the suite inspected the real code. That is a property of the tree, not of the diff, and it is the reason a bump-only commit is an adequate remedy at all. ONE GUARD GENUINELY DID NOT REACH IT AND I CLOSED IT BY MEASUREMENT RATHER THAN LEAVING IT IMPLIED. .gitpush-pre.sh's secret scan is a pre-STAGE scan of STAGED paths, and only package.json was staged, so the four src/ files were never scanned by it. I ran the scan myself across all four - api key, secret, password, token, bearer, PGPASSWORD, sk- prefixes and JWT-shaped literals - and got zero matches. GENERAL PROPERTY WORTH CARRYING: a bump-only remediation commit re-runs the TREE checks but not the STAGED-FILE checks, so a bypassed push followed by a version-bump repair leaves exactly the staged-path guards unrun while the suite goes green and the record looks fully repaired. THE CONFLATION I PREDICTED HAS HAPPENED AND IS PERMANENT. GET /api/health at 23:30:39Z returned version 0.30.78 with deploymentId dpl_AsvK6jsoqSUswCs75HrhgTGphbYK. At 23:37:53Z it returned version 0.30.78 with deploymentId dpl_DYUfBq44ZafKC9WrgcdycYwipCEj. Two DISTINCT deployments reporting one version: e2a6a4c built and shipped under the version its predecessor already used. Any appEvents row written between those two deploys stamps an appVersion that cannot identify which build produced it, and nothing in the rail records that ambiguity. This is the same instrument that TERRA-98 depends on, where comparing the newest appVersion in the rail against the deployed version was the only thing separating "empty because the build is hours old" from "empty because the writer is broken". TRANSFERABLE RULE, already held in this corpus and now with a second worked case: for a no-bump commit the deploy discriminator is the deploymentId or the commit SHA, never the version. A version-equality check cannot distinguish deployed from unchanged, and here it additionally cannot distinguish one build from another. 0.30.79 was still building at 23:37:53Z; not waited on, and no claim is made about it. TERRA-113 STAYS OPEN AND THE REASON IS NOT THE CODE. The app half is reviewed against source and against live prosrc, the push path is now correct, and the six refusal patterns match the six live RAISEs verbatim. pacienteAntecedentes holds ZERO rows, so neither a non-empty pre-carga nor a real merge write has been observed. The row closes on an observation against real patient history, not on a green suite.
-
INDEPENDENT AUDIT OF e2a6a4c BY audit-terra-ca - AND IT IS INDEPENDENT BY CONSTRUCTION, not by assertion. I scoped the request to source only and told them explicitly not to read this row's events and not to ask what I had concluded, because two lanes agreeing after one has seen the other's answer is one measurement wearing two names. They confirmed in their reply that they read neither. Scope was deliberately narrow - three questions, four files - because their context was at 23 percent, and I said so rather than asking for a sweep they could not finish. AGREEMENT ON THE TWO I HAD ALSO CHECKED, reached separately. All six mapper branches are structurally reachable and none is dead or shadowed: every regex is anchored, and the two variable-id patterns carry distinct fixed suffixes that cannot collide with the four exact messages. And no path commits a practica after a skipped or failed merge - after BEGIN, every route that reaches practicaId reaches the boundary call unconditionally, it sits after the child and participant work and immediately before COMMIT, matchedWithMismatch ROLLBACKs before returning and before any practica insert, a mapped refusal throws ActionError, an unmapped failure rethrows, and both land in the outer catch which ROLLBACKs. A COMMIT failure reaches the same catch. An empty selection is still a call, which is the intended validated no-op rather than a skip. THE THIRD QUESTION IS WHERE IT FOUND MORE THAN I DID, and the finding is about the TESTS, not the shipped behaviour. FIRST AND MOST SERIOUS: THE DIRECT-WRITE GUARD IS CASE-SENSITIVE. assert.doesNotMatch over the whole source for INSERT INTO or UPDATE followed by the quoted pacienteAntecedentes carries no i flag. That single negative is what enforces the shared-table boundary in this file - it is the assertion standing between the app and a direct write into a table Terra does not own - and a lower-case insert into passes it silently. The guard's NAME and position make it read as the boundary check; its regex makes it a check for one capitalisation of the boundary check. SECOND: the refusal arm's actual subject is the ENTIRE FILE, not the mapper. Six substring checks prove each phrase occurs somewhere in the text; they bind no phrase to a branch, to a Spanish return value, to the anchors, or to the mapper being invoked. All six could be moved into comments while the mapper returned null and every arm would stay green. This is sharper than what I recorded earlier, which was that the arm asserts the Spanish strings are present - true, but I stopped before naming what that leaves unbound. THIRD: the merge-call arm proves token presence within a slice, not argument position, data flow or execution. It would pass with user.id and the maps appearing only in dead code or comments inside that slice, or with the call under an unreachable conditional. It asserts neither BEGIN nor any ROLLBACK path, and never asserts that the call FOLLOWS the immutable attestation insert - which is the ordering the entire design rests on. FOURTH: the prefill arm's end anchor is the first later [row.id]. If that drifts to a later unrelated occurrence the slice ENLARGES and the token assertions keep passing inside the bigger region. Same class as the start anchors already fixed this evening; fixing one end of a span and not the other leaves the span unbounded in the other direction. THE AUDIT'S OWN BOUND, stated by them and worth preserving because it is exactly the half they cannot cover: source-only. No node test run, no Postgres error object, no live function messages, no transaction execution or rollback, no shared-boundary behaviour, no deployment or live database state. The live-catalog half is mine and stands separately - I compared all six patterns against live prosrc and they match verbatim. NONE OF THIS BLOCKS WHAT SHIPPED. The four items are test-quality defects in guards that currently pass for weaker reasons than they appear to. Routed to coder-terra-ca as a follow-up push through gitpush.sh with a bump flag.
-
AUDIT HARDENING SHIPPED (89544b1, v0.30.82) - PASS on the push path and on three of four fixes, ONE REAL DEFECT in the fourth, and the defect is only visible by reading how the offsets are COMPUTED rather than what the assertions SAY. PUSH PATH VERIFIED, and it is the correct shape this time: 89544b1 on origin/main, exactly two files - package.json +1 -1 and paciente-antecedentes-prefill-core.test.ts +69 -11, no src/ logic touched - version 0.30.82, and an Agent: coder-terra-ca trailer present. Bump and trailer both in the same commit is what a gitpush.sh push looks like, against the bypassed e2a6a4c earlier tonight which had neither. The lane also disclosed that v0.30.81 was consumed by a failed pre-push attempt that produced no commit and no deployment, which is the right disclosure - a failed push still burns a version, and an unexplained gap in the version series is otherwise unreadable later. VERIFIED REAL, all four audit items addressed and one exceeded: the direct-write matcher now carries the /i flag and tolerates block and line comments between the verb and the table name (:110); the prefill arm's END anchor is now asserted unique, not just its start (:57-61); the mapper body is extracted by its declaration and the no-P0001-classifier negative is scoped to that body rather than the whole file (:116, :162); mapper invocation is asserted (:161); and :104 asserts argument POSITION via the parameter-array prefix, which closes audit's "token presence, not argument position" and is more than was asked for. THE DEFECT: THREE OF THE FOUR NEW ORDERING ASSERTIONS CANNOT FAIL. They are true by construction of the offsets, not by any property of the file being read. - begin is lastIndexOf(BEGIN, start), which returns an index at or before start by definition; attestation is indexOf(ATT, begin), which is at or after begin. So begin < attestation (:89) holds for every possible input. - end is indexOf(COMMIT, start), at or after start by definition, so start < end (:91) holds always. - rollback is indexOf(ROLLBACK, end), at or after end by definition, so end < rollback (:92) holds always. Each of those three is already fully covered by its own notEqual(-1) check; the comparison adds nothing a failed search would not already have caught. ONLY attestation < start (:90) can actually fail, because attestation is searched FORWARD from begin and can legitimately land past the merge call. That one is a real assertion, and it happens to be the link the design actually depends on. WHY THIS IS WORSE THAN A REDUNDANT LINE. The arm now READS as proving the full chain BEGIN then attestation then merge then COMMIT then rollback, and it proves exactly one link of it. Anyone auditing this file later sees five ordering assertions and concludes the ordering is covered. Four of them are decoration. This is the same family as an arm that never reaches its predicate: the assertion is present, green, and incapable of discriminating. REMEDY SENT, not applied by me: compute all five offsets independently from one origin rather than chaining each search from the previous result, then compare. Every comparison then becomes capable of failing. RESIDUAL LIMIT STATED RATHER THAN CHASED: a textual guard cannot see a write through a computed table name - an INSERT INTO with an interpolated identifier evades :110 entirely. Not worth pursuing; worth one comment line so nobody reads that assertion as a proof of the shared-table boundary rather than as a check on one spelling of it. Nothing here blocks anything shipped, and nothing here touches runtime behaviour - the whole commit is test-only. The zero-row bound on TERRA-113 is unchanged: pacienteAntecedentes still holds no rows, so none of this has been exercised against real patient history.
-
ORDERING-ASSERTION DEFECT CORRECTED AND VERIFIED IN THE SOURCE, not accepted from the report. f79b066, v0.30.83, live on the deployed build at deployment dpl_FoWpwyqbD3pFs8rAE7Y3g3NJLUoa with dbOk true. WHAT WAS WRONG IN 89544b1: four of five ordering assertions were true by construction. Each offset was searched FROM the previous search result, so begin lastIndexOf(BEGIN, start) is at or before start by definition, attestation indexOf(ATT, begin) is at or after begin, end indexOf(COMMIT, start) is at or after start, rollback indexOf(ROLLBACK, end) is at or after end. Only attestation less than start could fail. The arm read as proving the whole chain and proved one link. WHAT LANDED: the four locators at paciente-antecedentes-prefill-core.test.ts lines 81 to 84 are now bare source-wide indexOf with no start offset, each independent of the others, carrying a comment that states why - searching from the previous marker would make the comparisons tautological rather than testing the source. So begin less than attestation, attestation less than start, start less than end, and end less than rollback are each now capable of failing on a real reordering of practica-actions.ts. That is the correct fix and it is the fix that was asked for. I CHECKED THE ONE THING THAT COULD HAVE BROKEN IT, because a source-wide first-match locator is only correct if the string it searches for is unique, and one of the four is not obviously so. audit-terra-ca had recorded that matchedWithMismatch ROLLBACKs before returning and before any practica insert, which means practica-actions.ts holds TWO rollback calls. Measured: line 439 is a bare await client.query("ROLLBACK") and line 711 is await client.query("ROLLBACK").catch. The locator searches for the longer .catch form, so it resolves to 711, the outer one, and the assertion measures the statement it names. Had the earlier rollback also carried .catch, the first match would have been the one at 439 - before COMMIT - and end less than rollback would have failed loudly. It does not. The other three locators were counted as well: BEGIN, COMMIT and the terraPracticasAntecedentes INSERT each occur exactly once. RESIDUAL, STATED RATHER THAN FILED: none of the four locators asserts its own uniqueness, while the invocation anchor in the same test does. Today all four are unique - measured, not assumed. If a second .catch-style rollback is added earlier in the file the locator silently retargets, and the failure direction is loud rather than silent because the assertion would then read end less than an earlier index. That makes this a note about the guard's shape, not a defect in what shipped. Cheap to close by asserting uniqueness on all four the way the invocation anchor already does. PUSH PATH CORRECT, third consecutive time it has been checked rather than assumed: exactly two files, package.json plus 1 minus 1 and the test file plus 8 minus 5, no src logic, Agent: coder-terra-ca trailer present, and the version bump in the same commit. That is the gitpush.sh shape, against the bypassed e2a6a4c earlier tonight which had neither bump nor trailer. THE ZERO-ROW BOUND IS UNCHANGED AND IS WHY THIS ROW STAYS OPEN. Everything hardened tonight is a source-text guard. pacienteAntecedentes still holds no rows, so neither a non-empty pre-carga nor a real merge write has ever executed. The tests now say the shipped ordering is what it claims to be; nothing yet says the ordering does the right thing to real patient history.
-
ORDERING-ASSERTION DEFECT CORRECTED AND VERIFIED IN THE SOURCE, not accepted from the report. f79b066, v0.30.83, live on the deployed build at deployment dpl_FoWpwyqbD3pFs8rAE7Y3g3NJLUoa with dbOk true. WHAT WAS WRONG IN 89544b1: four of five ordering assertions were true by construction. Each offset was searched FROM the previous search result, so begin lastIndexOf(BEGIN, start) is at or before start by definition, attestation indexOf(ATT, begin) is at or after begin, end indexOf(COMMIT, start) is at or after start, rollback indexOf(ROLLBACK, end) is at or after end. Only attestation less than start could fail. The arm read as proving the whole chain and proved one link. WHAT LANDED: the four locators at paciente-antecedentes-prefill-core.test.ts lines 81 to 84 are now bare source-wide indexOf with no start offset, each independent of the others, carrying a comment that states why - searching from the previous marker would make the comparisons tautological rather than testing the source. So begin less than attestation, attestation less than start, start less than end, and end less than rollback are each now capable of failing on a real reordering of practica-actions.ts. That is the correct fix and it is the fix that was asked for. I CHECKED THE ONE THING THAT COULD HAVE BROKEN IT, because a source-wide first-match locator is only correct if the string it searches for is unique, and one of the four is not obviously so. audit-terra-ca had recorded that matchedWithMismatch ROLLBACKs before returning and before any practica insert, which means practica-actions.ts holds TWO rollback calls. Measured: line 439 is a bare await client.query("ROLLBACK") and line 711 is await client.query("ROLLBACK").catch. The locator searches for the longer .catch form, so it resolves to 711, the outer one, and the assertion measures the statement it names. Had the earlier rollback also carried .catch, the first match would have been the one at 439 - before COMMIT - and end less than rollback would have failed loudly. It does not. The other three locators were counted as well: BEGIN, COMMIT and the terraPracticasAntecedentes INSERT each occur exactly once. RESIDUAL, STATED RATHER THAN FILED: none of the four locators asserts its own uniqueness, while the invocation anchor in the same test does. Today all four are unique - measured, not assumed. If a second .catch-style rollback is added earlier in the file the locator silently retargets, and the failure direction is loud rather than silent because the assertion would then read end less than an earlier index. That makes this a note about the guard's shape, not a defect in what shipped. Cheap to close by asserting uniqueness on all four the way the invocation anchor already does. PUSH PATH CORRECT, third consecutive time it has been checked rather than assumed: exactly two files, package.json plus 1 minus 1 and the test file plus 8 minus 5, no src logic, Agent: coder-terra-ca trailer present, and the version bump in the same commit. That is the gitpush.sh shape, against the bypassed e2a6a4c earlier tonight which had neither bump nor trailer. THE ZERO-ROW BOUND IS UNCHANGED AND IS WHY THIS ROW STAYS OPEN. Everything hardened tonight is a source-text guard. pacienteAntecedentes still holds no rows, so neither a non-empty pre-carga nor a real merge write has ever executed. The tests now say the shipped ordering is what it claims to be; nothing yet says the ordering does the right thing to real patient history.
-
The two identical verified events at 00:00:41 and 00:01:2x are ONE measurement, not two. I grepped the wrong case to confirm the first had landed, read the zero count as a silent write failure, and re-sent. Duplicate is mine and bwi is append-only, so it stays. Count it once.
-
LOS CUATRO PUNTOS DE ACEPTACION ESTAN MEDIDOS, CADA UNO EN SU FUENTE PRIMARIA Y NO EN EL REPORTE DE NADIE. (1) PRE-CARGA. src/lib/practica-actions.ts:138 lee pacienteAntecedentes dentro de la misma transaccion, y paciente-antecedentes-prefill-core.ts existe con su test propio y es consumido tanto por la accion como por components/practica-form.tsx. La casilla llega marcada desde el estado del paciente, no desde cero. (2) ESCRITURA DE VUELTA. practica-actions.ts:690-704 llama fn_terraMergePacienteAntecedentes con actor, practica de origen y los dos arrays. Verificado en la base viva, no en el archivo: la funcion existe en el esquema public con la firma exacta de cuatro argumentos. NO HAY UN SOLO INSERT NI UPDATE DE TERRA CONTRA LA TABLA COMPARTIDA - el comentario del sitio lo dice y el grep lo confirma; la unica via es la funcion de borde, que verifica autor, membresia en Terra y que cada ID enviado haya sido atestiguado en esa practica de origen. La llamada con seleccion vacia es deliberada y es un no-op validado, nunca un cierre implicito. (3) LA ATESTACION CONGELADA SIGUE DICIENDO LO MISMO, que era el punto legal de todo el row (Ley 26.529). Esto no se dio por bueno leyendo codigo: el arm 6 del dry-run de terra_52 lo prueba con una enmienda real - "per-practica attestation rows remain unchanged across all merge calls", esperado y obtenido 1 fila con detailText intacto y deletedAt NULL, PASS. El transcripto esta en agents/db-terra/evidence/2026-08-31-terra_52-dryrun.result.txt y el header de la migracion aplicada lo cita. (4) GO DE db-enamel-cc. terra_52 esta en applied/ y la funcion esta viva; el permiso sobre la tabla compartida vino con la aplicacion, no como promesa. LA DECISION DE PRODUCTO QUE EL ROW DEJABA ABIERTA YA NO LO ESTA, y conviene dejar escrito por que, porque no la decidio esta implementacion. La pregunta era si un alumno puede enmendar los antecedentes de un paciente que cargo otro. Puia respondio el 2026-08-30 que la ficha compartida es escribible por cualquiera que carga una practica, y la razon declarada para aceptar esa escritura permisiva fue justamente que un lector posterior pueda ver quien cambio que. Esa mitad se cumplio en TERRA-144: el panel de procedencia. La escritura y la lectura salieron con la misma justificacion. LO QUE ESTO NO PRUEBA, Y ES CASI TODO LO QUE IMPORTARIA. pacienteAntecedentes tiene CERO filas y terraPacienteAntecedenteChanges tambien - medido contra la base viva al cerrar este row, no heredado de la medicion del 2026-08-23. El camino completo nunca corrio contra datos reales: ni una sola practica cargada por una persona ha llamado la funcion. La evidencia es codigo en main, una funcion viva con la firma correcta, y diez arms de dry-run en una transaccion revertida. La primera practica real es lo que prueba esto, y nada en este repositorio puede producirla. NO CIERRA la vista por paciente: desde /practicas/[id] se ven los cambios de los antecedentes atestiguados EN ESA PRACTICA, no todo el historial del paciente. Sigue sin construirse y sin row propio.