La accion del servidor acepta cualquier tipo de documento activo del catalogo aunque el formulario ofrezca solo dos: validacion de presencia sin guarda de vocabulario, y la llamada posicional de nueve parametros que ENAMEL-34 va a romper
EL FORMULARIO OFRECE DOS TIPOS DE DOCUMENTO Y LA ACCION DEL SERVIDOR ACEPTA LOS CINCO. El select de la pantalla se arma con resolveScopedVocabulary('terra','documentoType'), que hoy devuelve DNI y pasaporte. La validacion del lado del servidor, en src/lib/practica-actions.ts:322, es unicamente de presencia: comprueba que el campo no venga vacio y nada mas. Un select en el navegador no limita lo que llega por la red, asi que cualquier usuario ya autenticado con uno de los roles habilitados puede mandar cedula, libreta civica o libreta de enrolamiento y el paciente se crea con ese valor. POR QUE IMPORTA MAS DE LO QUE PARECE: el tipo de documento es la mitad del par que identifica a un paciente en el indice unico ux_pacientes_documento_live. Un valor que el producto ya no ofrece no genera un error visible - genera un paciente en un espacio de nombres que despues nadie va a buscar, y la historia clinica de esa persona queda partida en dos filas sin nada que las reconcilie. Elazar saco libreta civica del producto el 2026-09-10 y el 2026-09-11 sigue siendo escribible por esta via. DOS PIEZAS, Y LA SEGUNDA ESPERA A ENAMEL-34. PRIMERA, y se hace ahora: una guarda de vocabulario en la accion del servidor, con el mismo patron que ya usa POSTOP_VOCAB_CHECK_SQL en practica-postop-core.ts - un EXISTS contra resolveScopedVocabulary leido en la misma transaccion y en la misma conexion, no una lista copiada en TypeScript. Cierra el camino de terra sin tocar ningun objeto compartido, sin migracion y sin decision de producto. SEGUNDA, cuando db-enamel-cc publique la nueva firma: practica-actions.ts:508 llama hoy a resolveOrCreatePaciente con nueve parametros posicionales y de forma directa, sin pasar por createTerraPractica. Esa llamada se rompe en el instante en que cambia la aridad, y un deploy de Vercel no es atomico respecto de una migracion. El acuerdo con db-enamel-cc es en dos fases: primero convive la firma nueva con la vieja y este lado migra la llamada y despliega, despues se borra la vieja. Esta fila es la duena de ese movimiento del lado de terra y es la que le da a la fase 2 un responsable con nombre. QUE NO ES ALCANCE DE ESTA FILA: arreglar resolveOrCreatePaciente, que es de enamel y va por ENAMEL-34; tocar createTerraPractica, que es una funcion SQL de db-terra-cc y que la aplicacion de terra no llama nunca; desactivar opciones del catalogo compartido, que es la palanca equivocada porque resolveScopedVocabulary es abierta por defecto y enamel, que no tiene fila de scope, hoy recibe las cinco; y decidir si cedula o libreta de enrolamiento entran al scope de terra, que sigue en TERRA-217. COMO SE CIERRA: la guarda desplegada y verificada contra la version viva, y la llamada movida a la firma nueva con el numero de version informado a db-enamel-cc para que pueda ejecutar la fase 2.
Activity
-
BLAST RADIUS MEASURED BEFORE THE SIGNATURE CHANGE, AND THE PHASE-1 SHAPE IS AGREED. Marker: T221-radius-mtwy6. THE AUDIT (audit-terra-cc, marker BOUNDARY-CALLSITES-mtwy4, coverage: command grep -r over src/**/*.ts,*.tsx and db/migrations/applied,dryrun/*.sql, invocation lines only, definitions and prose excluded; NOT searched: agents evidence SQL, enamel's own repo). resolveOrCreatePaciente has exactly ONE terra app-code call site and it is the one this row owns: practica-actions.ts:508, positional, arity 9. It is also called from inside createTerraPractica's body (terra_6:339, terra_12B:248, terra_42:106), which is enamel-owned code calling enamel-owned code - terra's migration file is the deployment vehicle, not a separate terra call site. createTerraPractica itself has ZERO app-code calls; the only invocations are the terra_152 and terra_164 dry-run harnesses at arity 24. THREE ENAMEL BOUNDARY FUNCTIONS HAVE ZERO INVOCATIONS IN TERRA: getTerraPracticaFormCatalogs, resolveTerraSessionUser and lookupTerraPaciente. The last one is not even defined in terra's migrations - it exists here only as a name in the terra_13:495 assertion comment. Granted surface nobody calls. Not this row's scope and not terra's to drop, since the functions are enamel-owned; recorded because a signature audit that reports only the callers hides the opposite finding, and because a REVOKE is cheaper to justify while someone is already looking at this family. resolveScopedVocabulary is the one with real blast radius if it ever changes shape: 10 app call sites (panel/page.tsx x5, practica-postop-core.ts, informe-query.ts, vocabulary-drift.ts, practica-actions.ts:809, lookups.ts:408) plus nine terra-owned vocab-guard trigger functions in applied migrations, plus about 35 more in dry-run harnesses. EVERY call in the sweep is positional arity 2 and there are ZERO named-parameter calls to any of the seven functions anywhere in the repo. That is the fact that makes an arity change expensive here and it is worth knowing before anyone proposes one. PHASE-1 SIGNATURE, RECEIVED FROM db-enamel-cc AND ACCEPTED: the replacement is a NEW NAME, not an overload - public.resolveOrCreatePacienteForApp(appKeyArg text, documentoTypeArg text, documentoNumberArg text, nombreArg text, apellidoArg text, emailArg text default null, telefonoArg text default null, fechaNacimientoArg date default null, isSyntheticArg boolean default false, syntheticSessionIdArg text default null). Return shape unchanged, same four outcomes. Only the validation predicate changes: the global lookupOptions EXISTS becomes an EXISTS through resolveScopedVocabulary(appKeyArg,'documentoType'). Terra passes 'terra'. TWO REASONS THE NEW NAME IS BETTER THAN THE OVERLOAD, BOTH LOAD-BEARING AND NEITHER STYLISTIC. appKey must LEAD because arguments 5-9 of the current function carry defaults and PostgreSQL forbids a mandatory parameter after a defaulted one, so every positional index shifts by one no matter what. And node-postgres transmits parameters with unspecified types, so two same-named candidates give the server nothing to discriminate on and it raises 42725 function is not unique - the practica path would break during exactly the interval the two-phase sequence exists to keep safe. A distinct name also turns phase 2 into a plain drop instead of the step everyone forgets. WHAT THIS ROW STILL OWES: move practica-actions.ts:508 to the new name in one commit, ship it, and report the deployed version to db-enamel-cc so phase 2 fires on a fact rather than a schedule. Phase 2 is drop function public.resolveOrCreatePaciente(text,text,text,text,text,text,date,boolean,text). I will not write against the new name until the migration is applied - a call to a function that does not exist yet is not a guard, it is an outage.
-
GUARD SHIPPED. Marker: T221-guard-mtwy8. Version 0.31.55, push exit 0, two files: src/lib/practica-actions.ts and src/lib/practica-documento-type-guard.test.ts. WHAT LANDED: a positive match against resolveScopedVocabulary(APP_KEY,'documentoType') on the checked-out client, inside the transaction, immediately before the resolveOrCreatePaciente call, refusing with a Spanish message in the sibling register. Same idiom as the rolEnPractica check that already lives forty lines below in the same function - that one was written for exactly this hazard and documentoType was simply never given one. No NOT IN: resolveScopedVocabulary is enamel-owned, its return nullability is not auditable from this repo, and NOT IN fails OPEN on a single NULL. THE TEST READS THE SOURCE and pins three things rather than the string: that the accepted set comes from the resolver, that the guard runs BEFORE the registry write, and that no documento value appears as a literal anywhere in the action. Its first version failed on its own terms - it banned the literal in COMMENTS too, and the file documents in prose that the type "is carried, never assumed to be 'dni'". A source-reading assertion that cannot tell code from commentary bans the sentence explaining the rule it enforces. Comments are stripped before the literal check now. COVERAGE, STATED HONESTLY: tsc clean, 524 tests green. NOT executed against the live database - I hold no credential - so what is verified is the query's SHAPE and its POSITION, not an observed rejection. Whoever next has a live session on terra can close that gap in one statement. WHAT THIS CLOSES AND WHAT IT DOES NOT. Closes: terra's server action, which was the only terra path reaching resolveOrCreatePaciente and was presence-validated only, so any authenticated PRACTICA_ROLES user could post an unoffered value over the wire regardless of what the browser select offered. Does NOT close: the function itself, which still validates against lookupOptions for every other caller - that is ENAMEL-34. INCIDENT WORTH RECORDING BECAUSE IT ALMOST ATE THIS WORK. Both files vanished from disk between the first push attempt and the second - reverted to HEAD, the new test file gone entirely, no error and exit 0 on my side. Cause: a concurrent agent's gitpush hit a push rejection, its gitmerge fallback ran git stash to sync, and did not pop it back; one working tree, several agents, so the stash held my changes and a third agent's as well. I could not run git stash list to recover, since git-workflow.md bans manual git for me including read-only, so I re-authored both files from context and pushed. That worked, and it has a cost the next person needs to know: the stash still exists and my re-authored versions are now at HEAD, so popping it conflicts on exactly these two paths and the natural-looking resolution reverts the guard. coder03-terra-cc holds that stash and has been told to resolve those two paths to HEAD. REMAINING ON THIS ROW: move practica-actions.ts to resolveOrCreatePacienteForApp when db-enamel-cc's phase-1 migration is applied, ship, and report the deployed version so phase 2 fires on a fact.
-
El guard esta desplegado; queda mover la llamada a resolveOrCreatePacienteForApp, que no existe hasta que db-enamel-cc aplique la migracion de la fase 1 de ENAMEL-34
-
coder-terra-cc
-
cleared: El guard esta desplegado; queda mover la llamada a resolveOrCreatePacienteForApp, que no existe hasta que db-enamel-cc aplique la migracion de la fase 1 de ENAMEL-34
-
Remaining half shipped: practica-actions.ts:583 calls resolveOrCreatePacienteForApp (10-arg, appKey 'terra') since 1425140 'refactor(ENAMEL-36): mover los dos llamados al resolvedor con appKey' (2026-09-11), which is an ancestor of origin/main. ENAMEL-34 is done (both phases live 2026-09-12). The vocabulary guard deployed earlier. Closed by coder-terra-cc on reassignment triage; no new code.