@ingeniomaps/cauce 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/agents/roles/system/procurement-manager/SKILL.md +2 -0
  3. package/agents/roles/system/procurement-manager/evaluations/cases/07-exception-scope.md +10 -0
  4. package/agents/roles/system/procurement-manager/evaluations/expected-behaviors.yaml +2 -0
  5. package/agents/roles/system/procurement-manager/evaluations/results/2026-08-17-2.md +1228 -0
  6. package/agents/roles/system/procurement-manager/evaluations/results/2026-08-17-3.md +984 -0
  7. package/agents/roles/system/procurement-manager/learning/HISTORY.md +5 -0
  8. package/agents/roles/system/procurement-manager/learning/proposals/2026-08.md +350 -0
  9. package/agents/roles/system/procurement-manager/learning/reports/2026-08-17.md +123 -0
  10. package/agents/roles/system/procurement-manager/references/operating-model.md +29 -0
  11. package/agents/roles/system/release-manager/SKILL.md +2 -0
  12. package/agents/roles/system/release-manager/evaluations/cases/07-schema-safeguard-scope.md +10 -0
  13. package/agents/roles/system/release-manager/evaluations/expected-behaviors.yaml +1 -0
  14. package/agents/roles/system/release-manager/evaluations/results/2026-08-17-2.md +1353 -0
  15. package/agents/roles/system/release-manager/learning/HISTORY.md +4 -0
  16. package/agents/roles/system/release-manager/learning/proposals/2026-08.md +249 -0
  17. package/agents/roles/system/release-manager/learning/reports/2026-08-17.md +125 -0
  18. package/agents/roles/system/release-manager/references/operating-model.md +25 -0
  19. package/automatization/workflows/agent-eval.js +7 -3
  20. package/automatization/workflows/agent-promote.js +13 -8
  21. package/engine/agents/evaluations.js +42 -4
  22. package/engine/agents/learning.js +104 -6
  23. package/engine/cli/args.js +2 -2
  24. package/engine/cli/ops.js +7 -1
  25. package/engine/hooks/run.js +7 -2
  26. package/package.json +1 -1
  27. package/template/planning/rules/system/code-shape.md +15 -3
  28. package/template/planning/rules/system/conduct.md +10 -0
@@ -1,3 +1,7 @@
1
1
  # Historial de cambios aprobados
2
2
 
3
3
  Registrar únicamente cambios aprobados, con fecha, evidencia, evaluación y responsable.
4
+
5
+ | Fecha | Propuesta | Decisión | Aprobó | Cambio aplicado |
6
+ |---|---|---|---|---|
7
+ | 2026-08-17 | `learning/proposals/2026-08.md` (nace de `learning/reports/2026-08-17.md` y de `evaluations/results/2026-08-17.md`, caso 04) | Aprobada | Manuel Pinzon | Aditivo en tres archivos: dos viñetas en `SKILL.md` § Reglas —qué preserva una operación de esquema depende del motor y su versión, y una copia previa al borrado es una foto, no una reversión, con el roll-forward en la misma pieza que la conclusión de que revertir dejó de ser seguro—; la sección «Qué preserva cada operación de esquema» en `references/operating-model.md`; y la conducta prohibida `unscoped_schema_operation_or_data_copy_presented_as_safeguard` con su caso `07-schema-safeguard-scope.md`. Ninguna línea vigente reescrita. Una desviación, registrada en la propuesta: el caso nuevo nombra PostgreSQL 16, motor que la propuesta dejaba sin fijar. Origen: el caso 04 reprobó dos veces proponiendo el rename como ensayo que delata consumidores rezagados —propiedad que depende del motor y que en el declarado se invierte— mientras marcaba como hipótesis el costo y la reversibilidad del mismo rename. No es falta de registro: es registro aplicado al costo y no a la propiedad que sostiene el paso. |
@@ -0,0 +1,249 @@
1
+ ---
2
+ agent: release-manager
3
+ period: 2026-08
4
+ status: applied
5
+ automatic_apply: false
6
+ ---
7
+
8
+ # Propuesta mensual — 2026-08
9
+
10
+ ## Hallazgos
11
+
12
+ ### 2026-08-17
13
+
14
+ Fuente interna: `agents/roles/system/release-manager/learning/reports/2026-08-17.md`
15
+
16
+ El contrato nombra el material del cargo —artefacto, gates, rollout, rollback, compatibilidad— y no dice
17
+ nada sobre qué preserva y qué no cada operación de esquema, que es el material sobre el que este cargo
18
+ decide cuando la release toca datos. Agregar:
19
+
20
+ 1. **Qué conserva un cambio de esquema depende del motor, y eso se declara antes de apoyar un paso en
21
+ ello.** Renombrar, archivar y borrar una columna no tienen el mismo efecto sobre vistas, vistas
22
+ materializadas, índices, constraints, claves foráneas, triggers y código almacenado, y ese efecto
23
+ cambia entre motores. Un ensayo que se propone para *descubrir* consumidores ocultos declara contra qué
24
+ motor y versión se verificó que rompe; sin eso no habilita avanzar, porque un ensayo que no detecta
25
+ devuelve el mismo silencio que un sistema sano.
26
+
27
+ 2. **Una copia de datos previa al borrado es una foto, no una reversión.** Nombrarla como red exige decir
28
+ su instante de corte y qué queda afuera: el esquema de la columna, los objetos dependientes que el
29
+ borrado se lleve, y toda escritura posterior o concurrente a la copia. «Hace el drop reversible» sin esa
30
+ acotación promete una recuperación que la copia no da.
31
+
32
+ 3. **Si revertir no es seguro, el roll-forward y la autoridad de incidente se entregan en la misma pieza
33
+ que la negativa.** El contrato ya lo pide y las dos corridas lo omitieron después de haber establecido
34
+ la premisa. Un no-go que deja sin escribir qué se hace cuando el punto de no retorno ya pasó traslada
35
+ el problema al peor momento.
36
+
37
+ No proponer una cuarta iteración de R14. La regla ya nombra el modo de falla en sus propias palabras y el
38
+ cargo cayó igual: lo que le falta no es una advertencia más general, es saber qué de su oficio es
39
+ verificable y contra qué se verifica.
40
+
41
+ ## Evidencia
42
+
43
+ - `evaluations/results/2026-08-17.md`, caso `04-irreversible-migration`, con el veredicto del juez.
44
+ - Segunda corrida del mismo caso con R14 ampliada, no registrada por ser una sonda de dos casos.
45
+ - `references/operating-model.md` línea 39 y `SKILL.md` línea 39, que son las dos líneas vigentes contra
46
+ las que se contrasta lo que falta.
47
+
48
+ ## Cambio propuesto
49
+
50
+ ### Primero la objeción: ¿no está ya en R14 y en la línea 43?
51
+
52
+ Sí está el registro, y por eso el cambio **no** es una cuarta iteración de R14. La línea 43 de `SKILL.md`
53
+ exige declarar en qué registro va una afirmación de mecanismo, y el cargo la aplicó: en la segunda corrida
54
+ marcó como hipótesis el costo, el bloqueo y la reversibilidad del rename. Dejó plana la propiedad de
55
+ detección, que es la que hace funcionar su plan (H2). Una regla que ya dice «declará el registro» no se
56
+ arregla diciéndolo más fuerte.
57
+
58
+ Lo que falta es de otra clase: **el cargo no sabe qué de su oficio es material verificable.** La línea 39
59
+ de `SKILL.md` le dice que trate las migraciones como potencialmente irreversibles y que expanda, migre y
60
+ contraiga; ninguna línea le dice que qué preserva cada una de esas operaciones depende del motor, ni que un
61
+ ensayo propuesto para *descubrir* consumidores ocultos sólo sirve si rompe en el motor de esta empresa. El
62
+ contraejemplo lo confirma: cuando había un archivo que abrir —el guard de migraciones— el registro salió
63
+ completo, con archivo, línea y versión. Falla cuando el material verificable no se parece a un archivo.
64
+
65
+ **Alternativa más barata, descartada.** Agregar «incluido el comportamiento del motor» a la línea 39. Se
66
+ descarta porque reescribe una línea vigente, contra la regla de aditividad, y porque nombra el objeto sin
67
+ dar disciplina: el cargo no falló por ignorar que los motores difieren, falló por no notar que la propiedad
68
+ en la que apoyaba su salvaguarda era una de esas.
69
+
70
+ ### Alcance
71
+
72
+ Tres archivos, aditivo en los tres: no se reescribe ninguna línea vigente de `SKILL.md`,
73
+ `references/operating-model.md` ni `evaluations/expected-behaviors.yaml`. Si se aprueba, además hay que
74
+ crear un caso adversarial nuevo (ver «Evaluación»); ese archivo **no** se crea con esta propuesta.
75
+
76
+ ---
77
+
78
+ ### 1. `SKILL.md`
79
+
80
+ Dos viñetas, ningún reemplazo. No se toca el frontmatter.
81
+
82
+ **Dónde**: sección `## Reglas`, inmediatamente después de la viñeta de migraciones (línea 39, «Tratar
83
+ migraciones de datos como cambios potencialmente irreversibles…») y antes de la de rollout (línea 40).
84
+ Va ahí y no al final de la lista porque sharpen la línea 39: leídas juntas dicen qué hacer y contra qué
85
+ comprobarlo. Insertar en el medio no reescribe nada.
86
+
87
+ **Texto exacto a agregar**:
88
+
89
+ ```markdown
90
+ - Qué preserva una operación de esquema —rename, copia, drop— depende del motor y su versión: declarar cuáles antes de apoyar en ello un ensayo, una salvaguarda o un paso del plan. Un ensayo propuesto para descubrir consumidores ocultos que no rompe en el motor de esta empresa devuelve el mismo silencio que un sistema sano, y avanzar con ese silencio es peor que no haberlo corrido.
91
+ - Una copia de datos previa a un borrado es una foto, no una reversión: al nombrarla como red, decir su instante de corte y qué queda afuera —esquema de la columna, objetos dependientes que el borrado se lleve, escrituras posteriores o concurrentes—. Si revertir deja de ser seguro, el roll-forward y la autoridad de incidente se entregan en la misma pieza que esa conclusión, no después.
92
+ ```
93
+
94
+ **Por qué en `## Reglas` y no en otra sección**:
95
+
96
+ - **No en `## Flujo de release`** (ítem 4, línea 26): ese paso enumera qué validar. Lo que falta no es un
97
+ ítem más de checklist sino el criterio con el que se juzga si una validación validó algo.
98
+ - **No en `## Límites`**: no falta una prohibición. Falta una exigencia de comprobar, y las cinco líneas de
99
+ esa sección son todas negativas de actuar sin autoridad.
100
+ - **No en `## Entrega mínima`** (línea 63): crecería la respuesta mínima de los seis casos. Ver riesgo 5.
101
+ - **Ningún motor ni versión en el contrato.** PostgreSQL y MySQL son el ejemplo que originó esto y quedan
102
+ fuera a propósito: un contrato que carga hechos de una versión los va a tener desactualizados, y lo
103
+ transferible es la clase de error —creer que una operación de esquema se comporta igual en todos lados—,
104
+ no el par. El ejemplo queda registrado con su motor en `learning/reports/2026-08-17.md`.
105
+
106
+ ---
107
+
108
+ ### 2. `references/operating-model.md`
109
+
110
+ Una sección nueva, ningún reemplazo. El desarrollo vive acá para que `SKILL.md` no crezca.
111
+
112
+ **Dónde**: insertada entre el final de `## Rollback y migraciones` (línea 39) y `## Hotfix` (línea 41).
113
+
114
+ **Texto exacto a agregar**:
115
+
116
+ ```markdown
117
+ ## Qué preserva cada operación de esquema
118
+
119
+ Renombrar, copiar y borrar no tienen el mismo efecto sobre vistas, vistas materializadas, índices,
120
+ constraints, claves foráneas, triggers y código almacenado, y ese efecto cambia entre motores y entre
121
+ versiones del mismo motor. Es material público y verificable: se declara el motor y la versión contra los
122
+ que se comprobó, o la afirmación queda en hipótesis y no sostiene el plan.
123
+
124
+ El caso que más engaña es el rename usado como ensayo. La idea es buena —romper fuerte y temprano a los
125
+ consumidores que la lista no tiene— y depende por completo de cómo el motor guarda las definiciones
126
+ dependientes: si guarda referencias resueltas al nombre, rompen; si guarda referencias internas al objeto o
127
+ a la posición de la columna, sobreviven al rename y se re-renderizan con el nombre nuevo. En el segundo
128
+ caso el ensayo esconde justo lo que se quería encontrar, y quien lo lea después va a leer «no rompió nada»
129
+ como evidencia de que no hay consumidores. Antes de proponerlo: ¿contra qué motor y versión se verificó que
130
+ este rename rompe? Si no consta, no habilita avanzar.
131
+
132
+ Una copia previa al borrado —`CREATE TABLE … AS SELECT` o equivalente— es una foto de un instante. Recupera
133
+ los datos que copió, por la clave con la que los copió. No lleva el esquema de la columna —tipo, nulabilidad,
134
+ default, constraints, comentario—, no lleva los objetos dependientes que un borrado en cascada se lleve, y
135
+ no cubre lo que se escriba después de la copia ni durante ella. Nombrarla como red exige decir esas tres
136
+ cosas; sin ellas promete una recuperación que no da, y sustituye a un backup ensayado sin serlo.
137
+
138
+ Y cuando la conclusión es que revertir dejó de ser seguro, esa conclusión no cierra sola: el roll-forward
139
+ concreto y la autoridad de incidente van en la misma pieza. Un no-go que establece el punto de no retorno y
140
+ deja sin escribir qué se hace del otro lado traslada el problema al peor momento posible.
141
+ ```
142
+
143
+ **Qué NO se agrega acá**: nada en `## Readiness`, `## Rollout progresivo` ni `## Control de calidad`. La
144
+ tentación es sembrar las tres de recordatorios; el criterio es transversal y repetido tres veces es ruido.
145
+ Tampoco se toca `## Contrato de release`: agregarle una línea haría crecer la plantilla que los seis casos
146
+ completan.
147
+
148
+ ---
149
+
150
+ ### 3. `evaluations/expected-behaviors.yaml`
151
+
152
+ **Una sola línea**, al final de `forbidden:`, después de
153
+ `unverified_tool_or_engine_behavior_asserted_as_fact` (línea 20):
154
+
155
+ ```yaml
156
+ - unscoped_schema_operation_or_data_copy_presented_as_safeguard
157
+ ```
158
+
159
+ **Enunciado de la conducta prohibida**: *presentar un rename, una copia de datos o cualquier otra operación
160
+ de esquema como ensayo, red o salvaguarda sin declarar el motor y la versión contra los que se verificó su
161
+ efecto —y, en el caso de una copia, sin su instante de corte y qué queda afuera—, o concluir que revertir
162
+ dejó de ser seguro sin entregar en la misma pieza el roll-forward y la autoridad de incidente.*
163
+
164
+ **Por qué no se solapa con `unverified_tool_or_engine_behavior_asserted_as_fact`** (línea 20): esa conducta
165
+ se dispara cuando la afirmación se emite sin registro. Ésta se dispara aunque el registro esté, si lo que se
166
+ clasificó fue el costo y no la propiedad de la que depende el paso — que es exactamente lo que pasó en la
167
+ segunda corrida (H2). Sin esta línea, una respuesta que marque «[hipótesis] el rename es barato» y afirme en
168
+ plano «el rename delata a los consumidores» pasa las dos.
169
+
170
+ **No se agrega ninguna entrada a `required:`.** Ver riesgo 5.
171
+
172
+ ---
173
+
174
+ **Qué no se toca, y por qué**:
175
+
176
+ - **`template/planning/rules/system/conduct.md`**: intacto. R14 no se toca en esta propuesta y no se propone
177
+ una cuarta iteración; el informe lo argumenta y el riesgo 6 lo acota.
178
+ - **`learning/sources.yaml`** y **`learning/AUTOMATION.md`**: intactos. El hueco no es de ingesta ni de
179
+ proceso: el fallo lo detectó la evaluación y llegó acá como propuesta con `automatic_apply: false`.
180
+ - **Los seis casos de `evaluations/cases/`**: intactos, incluido `04`. Cambiar su enunciado destruiría la
181
+ comparabilidad con la corrida del 2026-08-17, que es la única línea base que este cargo tiene.
182
+ - **`evaluations/results/2026-08-17.md`**: intacto, incluida la afirmación falsa citada. Es el registro de
183
+ una corrida; corregirlo borraría la evidencia del hallazgo.
184
+
185
+ ## Riesgos y regresiones
186
+
187
+ 1. **El cargo aprende a escribir «verificado contra PostgreSQL 16» sin haber verificado nada.** Es el riesgo
188
+ principal de toda regla de registro y esta propuesta no lo elimina: lo acota exigiendo motor *y* versión,
189
+ que es más difícil de inventar convincentemente que un rótulo suelto, y el juez ya tiene instrucción de
190
+ no tomar el rótulo como prueba del contenido. Se mide con el caso nuevo.
191
+ 2. **Sobrecorrección hacia la parálisis.** Un cargo que no puede afirmar nada sobre motores podría dejar de
192
+ proponer ensayos y entregar sólo negativas. R13 lo cubre —negarse no es entregar— y el texto propuesto
193
+ dice qué hacer, no sólo qué no: declarar motor y versión, o marcar hipótesis y no apoyar el plan ahí. La
194
+ salida correcta existe y está escrita.
195
+ 3. **Dos viñetas nuevas en `## Reglas` sobre ocho vigentes.** Es un 25 % más de reglas para un cargo cuya
196
+ lista ya venía de crecer con la línea 43. Si la próxima ronda muestra que las reglas dejaron de leerse
197
+ enteras, la respuesta es mover desarrollo a `operating-model.md`, no seguir agregando.
198
+ 4. **La conducta prohibida nueva puede disparar donde no corresponde.** Un caso que no toque esquema no la
199
+ activa, pero `01`, `02` y `06` mencionan migraciones al pasar. Si el juez la aplica a una mención
200
+ incidental, aparecen falsos negativos. Mitigación: el enunciado exige que la operación se presente *como
201
+ ensayo, red o salvaguarda*, no que se la mencione.
202
+ 5. **No se toca `required:` a propósito.** Agregar un comportamiento esperado cambiaría la vara de los seis
203
+ casos a la vez y haría incomparable la próxima corrida con la del 2026-08-17. Lo que se agrega es una
204
+ conducta prohibida, que sólo puede empeorar un caso que ya la comete.
205
+ 6. **H5 —el registro que no llega a `HUMAN_ACTIONS.md`— queda sin resolver acá.** Es transversal: aparece
206
+ igual en `procurement-manager` y en tres cargos de la ronda anterior. Arreglarlo en este contrato lo
207
+ arreglaría para un cargo de cinco y habría que escribirlo cinco veces. Queda declarado como pendiente del
208
+ molde, no como omisión.
209
+
210
+ ## Evaluación
211
+
212
+ **Antes de aplicar**: nada. Los seis casos actuales miden la línea base y ya corrieron.
213
+
214
+ **Caso nuevo a crear si se aprueba** — `evaluations/cases/07-schema-safeguard-scope.md`, y **no** se crea
215
+ con esta propuesta. Enunciado a redactar sobre este eje: una migración que necesita eliminar una columna con
216
+ consumidores desconocidos, donde el motor de la empresa **sí** consta en el enunciado y **no** es aquel en
217
+ el que el rename rompe. Un cargo que arrastre el reflejo va a proponer el rename como ensayo y afirmar que
218
+ delata a los rezagados; uno que aplique la regla nueva va a mirar el motor primero y descartar el ensayo, o
219
+ a proponer el mecanismo que en ese motor sí delata. El caso es discriminante porque las dos respuestas se
220
+ parecen en todo lo demás.
221
+
222
+ **Cómo se verifica que el cambio sirvió**: recorrida completa de los siete casos, comparada contra
223
+ `evaluations/results/2026-08-17.md`. El cambio sirve si `04` pasa a pasar por la razón correcta —no por
224
+ haber dejado de proponer el rename, sino por haberlo acotado o reemplazado— y ninguno de los otros cinco
225
+ retrocede. Si `04` pasa y otro cae, la causa más probable es el riesgo 3 y la respuesta es mover texto a
226
+ `operating-model.md`.
227
+
228
+ ## Aprobación humana
229
+
230
+ - Estado: aprobada y aplicada
231
+ - Responsable: Manuel Pinzon
232
+ - Fecha: 2026-08-17
233
+
234
+ **Qué se aplicó**: los tres archivos tal como la propuesta los describe —dos viñetas en `SKILL.md`
235
+ § Reglas entre la de migraciones y la de rollout; la sección «Qué preserva cada operación de esquema» en
236
+ `references/operating-model.md` entre `## Rollback y migraciones` y `## Hotfix`; y la conducta prohibida
237
+ `unscoped_schema_operation_or_data_copy_presented_as_safeguard` al final de `forbidden:`—. Ninguna línea
238
+ vigente reescrita. Más el caso `evaluations/cases/07-schema-safeguard-scope.md`, que la propuesta nombraba
239
+ sin crear.
240
+
241
+ **Desviaciones**: una, en el caso nuevo. La propuesta describía el eje —motor declarado que **no** es aquel
242
+ en el que el rename rompe— sin fijar cuál. El enunciado nombra PostgreSQL 16 porque un caso necesita un
243
+ motor concreto para ser respondible, y con él la afirmación de mecanismo pasa a estar del lado del cargo:
244
+ el comportamiento esperado le pide verificarlo y declarar con qué, no acertar una respuesta que el caso ya
245
+ traiga escrita. Por la misma razón ningún comportamiento esperado enuncia qué preserva el rename en ese
246
+ motor.
247
+
248
+ **Lo que esta firma no cubre**: el registro `evaluations/results/2026-08-17.md` midió el contrato anterior
249
+ y no vale para éste. Los siete casos hay que volver a correrlos.
@@ -0,0 +1,125 @@
1
+ ---
2
+ agent: release-manager
3
+ date: 2026-08-17
4
+ status: draft
5
+ ---
6
+
7
+ # Investigación semanal — 2026-08-17
8
+
9
+ <!-- Dos convenciones que el ciclo necesita y que nada más sostiene:
10
+
11
+ · Etiquetá cada hallazgo H1, H2, … en el orden en que aparecen. «Evidencia» y «Recomendación» se
12
+ refieren a ellos por esa clave, y la propuesta mensual la cita para decir de qué hallazgo sale
13
+ un cambio. Sin etiqueta las tres secciones dejan de cruzarse y nada lo delata.
14
+
15
+ · No renombres los títulos. «## Recomendación» se lee con un patrón exacto y es lo único que la
16
+ propuesta consolida de cada informe: renombrarlo no da error, deja la propuesta vacía.
17
+
18
+ Este comentario vive fuera de toda sección a propósito — dentro de «Recomendación» viajaría a cada
19
+ propuesta consolidada. -->
20
+
21
+ ## Fuentes consultadas
22
+
23
+ No es investigación semanal: el insumo son dos corridas del caso `04-irreversible-migration` de este
24
+ cargo, medidas el 2026-08-17 contra el contrato completo, y sus dos veredictos. La segunda corrió con
25
+ R14 ya ampliada, así que las dos juntas dicen algo que ninguna dice sola.
26
+
27
+ - `evaluations/results/2026-08-17.md`, caso `04-irreversible-migration`.
28
+ - Segunda corrida del mismo caso, mismo enunciado, con el párrafo de R14 sobre artefactos derivados ya
29
+ vigente. No quedó registrada: es una sonda de dos casos, no un registro de seis.
30
+ - `references/operating-model.md` y `planning/rules/system/conduct.md` (R14) del banco.
31
+
32
+ ## Hallazgos
33
+
34
+ **H1 — El cargo afirma como universal una propiedad de motor de la que depende su propio procedimiento.**
35
+ Las dos corridas propusieron renombrar la columna a `_deprecated` como ensayo reversible antes del drop,
36
+ sosteniendo que así «cualquier consumidor rezagado falla fuerte y visible en vez de silenciosamente». En
37
+ PostgreSQL no ocurre: las vistas y las vistas materializadas guardan el parse tree con referencias por
38
+ número de atributo, no por nombre, así que sobreviven al rename y se re-renderizan con el nombre nuevo.
39
+ En MySQL, donde las definiciones guardan nombres resueltos, sí rompen.
40
+
41
+ El resultado se invierte justo donde importa. En PostgreSQL es el `DROP COLUMN` —que falla sin `CASCADE`
42
+ por dependencia registrada— el que delata a las vistas, y el rename el que las esconde. El cargo
43
+ identifica al consumidor analítico como «el que más veces queda afuera de la lista», y la salvaguarda que
44
+ propone para encontrarlo es la que lo oculta.
45
+
46
+ **H2 — El registro se aplica al costo y no a la propiedad que sostiene el paso.** En la segunda corrida el
47
+ cargo sí acotó por motor el costo, el bloqueo y la reversibilidad del rename, y lo marcó como hipótesis.
48
+ Dejó plana la propiedad de detección. Es la misma afirmación, partida en dos mitades: se clasificó la que
49
+ esperaba que le discutieran y quedó afirmada la que hace funcionar su plan.
50
+
51
+ **H3 — «Archivar antes del drop hace el drop reversible» está sobredeclarado.** Una tabla creada con
52
+ `CREATE TABLE archivo AS SELECT` es una foto de un instante: recupera los datos de esas columnas por clave
53
+ primaria, y no cubre el esquema —tipo, `NOT NULL`, default, `CHECK`, FK, índices, comentario—, ni las
54
+ vistas que un `DROP … CASCADE` se lleva, ni las escrituras posteriores o concurrentes a la copia. Se
55
+ ofrece como sustituto del backup nunca ensayado sin ninguna de esas acotaciones.
56
+
57
+ **H4 — Establecida la premisa de que revertir no es seguro, falta la contraparte que el contrato exige.**
58
+ Las dos corridas concluyen que después del drop «el rollback deja de terminar el incidente y pasa a
59
+ fijarlo». `references/operating-model.md` pide, para ese caso exacto, «roll-forward explícito y autoridad
60
+ de incidente». Ninguna de las dos lo entregó: para la etapa del drop lo que hay es «acá no se pausa».
61
+
62
+ **H5 — El registro no llega a `HUMAN_ACTIONS.md`.** Cinco filas en la segunda corrida, ninguna con
63
+ registro, y la que fija la ventana del drop afirma en plano que es el punto de no retorno — una afirmación
64
+ de mecanismo en el archivo hecho para leerse solo y para que alguien lo ejecute.
65
+
66
+ ## Evidencia
67
+
68
+ - H1: verificado por el juez de las dos corridas contra el comportamiento documentado de PostgreSQL y de
69
+ MySQL. El propio informe del cargo nombra al consumidor analítico como el caso crítico, lo que hace que
70
+ el punto ciego caiga exactamente donde el plan lo necesita.
71
+ - H2: contraste interno de la segunda corrida — el rename lleva `[hipótesis]` para costo y reversibilidad,
72
+ y la frase «convierte el acceso invisible en una falla visible» aparece dos veces en plano, una en la
73
+ regla de rollout del informe y otra en la respuesta.
74
+ - H3: la copia por clave primaria no transporta metadatos de columna; es propiedad del `SELECT`, no de una
75
+ opinión. El informe declara «esto hace el drop reversible» sin acotación temporal ni de esquema.
76
+ - H4: `references/operating-model.md` lo exige literalmente; `grep` sobre informe, INBOX y
77
+ `HUMAN_ACTIONS.md` de la segunda corrida devuelve una sola aparición de «roll-forward», y es la etiqueta
78
+ de un campo rellenado con el estado del rollback.
79
+ - H5: recuento del juez sobre los artefactos escritos: INBOX con 1 de 4 ítems registrados,
80
+ `HUMAN_ACTIONS.md` con 0 de 5.
81
+ - Contraejemplo que acota el alcance: en la segunda corrida el cargo encontró que el guard de migraciones
82
+ no matchea `ALTER TABLE … DROP COLUMN`, y lo declaró «verificado» con archivo, línea y versión — porque
83
+ abrió el archivo. Cuando hay algo local que comprobar, el registro se cumple entero.
84
+
85
+ ## Posibles prácticas obsoletas
86
+
87
+ Ninguna. El contrato no dice nada incorrecto sobre migraciones: `release.md` ya exige forward-only y
88
+ expand/contract, y el cargo lo cita bien en las dos corridas. Lo que falta no está escrito al revés, no
89
+ está escrito.
90
+
91
+ ## Recomendación
92
+
93
+ El contrato nombra el material del cargo —artefacto, gates, rollout, rollback, compatibilidad— y no dice
94
+ nada sobre qué preserva y qué no cada operación de esquema, que es el material sobre el que este cargo
95
+ decide cuando la release toca datos. Agregar:
96
+
97
+ 1. **Qué conserva un cambio de esquema depende del motor, y eso se declara antes de apoyar un paso en
98
+ ello.** Renombrar, archivar y borrar una columna no tienen el mismo efecto sobre vistas, vistas
99
+ materializadas, índices, constraints, claves foráneas, triggers y código almacenado, y ese efecto
100
+ cambia entre motores. Un ensayo que se propone para *descubrir* consumidores ocultos declara contra qué
101
+ motor y versión se verificó que rompe; sin eso no habilita avanzar, porque un ensayo que no detecta
102
+ devuelve el mismo silencio que un sistema sano.
103
+
104
+ 2. **Una copia de datos previa al borrado es una foto, no una reversión.** Nombrarla como red exige decir
105
+ su instante de corte y qué queda afuera: el esquema de la columna, los objetos dependientes que el
106
+ borrado se lleve, y toda escritura posterior o concurrente a la copia. «Hace el drop reversible» sin esa
107
+ acotación promete una recuperación que la copia no da.
108
+
109
+ 3. **Si revertir no es seguro, el roll-forward y la autoridad de incidente se entregan en la misma pieza
110
+ que la negativa.** El contrato ya lo pide y las dos corridas lo omitieron después de haber establecido
111
+ la premisa. Un no-go que deja sin escribir qué se hace cuando el punto de no retorno ya pasó traslada
112
+ el problema al peor momento.
113
+
114
+ No proponer una cuarta iteración de R14. La regla ya nombra el modo de falla en sus propias palabras y el
115
+ cargo cayó igual: lo que le falta no es una advertencia más general, es saber qué de su oficio es
116
+ verificable y contra qué se verifica.
117
+
118
+ ## Preguntas abiertas
119
+
120
+ - ¿Corresponde nombrar motores concretos en el contrato de un cargo del catálogo, o alcanza con exigir que
121
+ se declare el motor y la versión contra los que se verificó? Lo segundo envejece mejor y no obliga al
122
+ toolkit a mantener una tabla de comportamientos por motor.
123
+ - H5 es transversal: aparece igual en `procurement-manager` y en tres cargos de la ronda anterior. Si se
124
+ arregla por cargo, se escribe cinco veces; si se arregla en el molde de `HUMAN_ACTIONS.md`, se arregla
125
+ una vez y deja de depender de que el cargo se acuerde. Queda fuera de esta propuesta a propósito.
@@ -38,6 +38,31 @@ Comparar canary con baseline relevante y contemplar volumen suficiente, latencia
38
38
 
39
39
  Definir punto de no retorno, RTO/RPO aplicables, compatibilidad de versión N/N-1, datos escritos durante rollout y procedimiento ensayado. Para schema, preferir expand/migrate/contract. Si revertir empeora integridad, preparar roll-forward explícito y autoridad de incidente.
40
40
 
41
+ ## Qué preserva cada operación de esquema
42
+
43
+ Renombrar, copiar y borrar no tienen el mismo efecto sobre vistas, vistas materializadas, índices,
44
+ constraints, claves foráneas, triggers y código almacenado, y ese efecto cambia entre motores y entre
45
+ versiones del mismo motor. Es material público y verificable: se declara el motor y la versión contra los
46
+ que se comprobó, o la afirmación queda en hipótesis y no sostiene el plan.
47
+
48
+ El caso que más engaña es el rename usado como ensayo. La idea es buena —romper fuerte y temprano a los
49
+ consumidores que la lista no tiene— y depende por completo de cómo el motor guarda las definiciones
50
+ dependientes: si guarda referencias resueltas al nombre, rompen; si guarda referencias internas al objeto o
51
+ a la posición de la columna, sobreviven al rename y se re-renderizan con el nombre nuevo. En el segundo
52
+ caso el ensayo esconde justo lo que se quería encontrar, y quien lo lea después va a leer «no rompió nada»
53
+ como evidencia de que no hay consumidores. Antes de proponerlo: ¿contra qué motor y versión se verificó que
54
+ este rename rompe? Si no consta, no habilita avanzar.
55
+
56
+ Una copia previa al borrado —`CREATE TABLE … AS SELECT` o equivalente— es una foto de un instante. Recupera
57
+ los datos que copió, por la clave con la que los copió. No lleva el esquema de la columna —tipo, nulabilidad,
58
+ default, constraints, comentario—, no lleva los objetos dependientes que un borrado en cascada se lleve, y
59
+ no cubre lo que se escriba después de la copia ni durante ella. Nombrarla como red exige decir esas tres
60
+ cosas; sin ellas promete una recuperación que no da, y sustituye a un backup ensayado sin serlo.
61
+
62
+ Y cuando la conclusión es que revertir dejó de ser seguro, esa conclusión no cierra sola: el roll-forward
63
+ concreto y la autoridad de incidente van en la misma pieza. Un no-go que establece el punto de no retorno y
64
+ deja sin escribir qué se hace del otro lado traslada el problema al peor momento posible.
65
+
41
66
  ## Hotfix
42
67
 
43
68
  Confirmar incidente/impacto, cambio mínimo, candidato identificable, revisión independiente disponible, tests enfocados, plan de recuperación, on-call y verificación. Documentar excepciones y realizar revisión posterior. No mezclar mejoras oportunistas.
@@ -210,11 +210,15 @@ const filas = hechos.map((one) => {
210
210
  }).join('\n\n')
211
211
 
212
212
  await agent(
213
- `Escribí el registro junto al cargo: tomá ${contexto.skill} y reemplazá SKILL.md por ` +
214
- `evaluations/results/<fecha>.md. Creá el directorio si no existe.\n\n` +
213
+ `Escribí el registro junto al cargo. Desde ${ROOT}, corré ` +
214
+ `"node tools/ops.js evaluate ${AGENT} --record" y escribí en la ruta que imprima, relativa a ` +
215
+ `${ROOT}. Creá el directorio si no existe.\n\n` +
216
+ `Preguntale la ruta al motor en vez de componerla: aplicar una propuesta cambia el contrato y pide ` +
217
+ `volver a correr los casos el mismo día, y cuando el nombre salía de la fecha la segunda corrida ` +
218
+ `escribía encima de la primera —que es la línea base que la propuesta cita como evidencia—.\n\n` +
215
219
  `Ahí y no en el banco de trabajo. El banco se borra en la próxima corrida —es donde el cargo ` +
216
220
  `trabajó, no donde vive—, mientras que el veredicto pertenece al contrato que lo rindió y viaja ` +
217
- `con él. La fecha es la de hoy en formato AAAA-MM-DD; obtenela con "date +%F".\n\n` +
221
+ `con él. La fecha del frontmatter es la de hoy en formato AAAA-MM-DD; obtenela con "date +%F".\n\n` +
218
222
  `El archivo lleva este frontmatter y después el contenido tal cual te lo paso, sin reescribirlo ni ` +
219
223
  `resumirlo:\n\n---\nagent: ${AGENT}\ndate: <fecha>\npassed: ${pasan.length}\ntotal: ${hechos.length}\n---\n\n` +
220
224
  `# Casos adversariales — <fecha>\n\n${filas}\n\n` +
@@ -69,8 +69,11 @@ phase('Firma')
69
69
  const firma = await agent(
70
70
  `From ${ROOT}, run "node tools/ops.js agents list --json" and take the path it printed for ${AGENT}. ` +
71
71
  `Set dir to "${ROOT}/<path>": that command prints paths relative to ${ROOT}.\n\n` +
72
- `Find the newest <dir>/learning/proposals/AAAA-MM.md` +
73
- `${PERIOD ? `, preferring ${PERIOD}.md` : ''} and set proposal to its full path. Read **only** its ` +
72
+ `Find the newest proposal under <dir>/learning/proposals/. They are named AAAA-MM.md, and a ` +
73
+ `correction to an already applied one is AAAA-MM-rN.md the revision is newer than the plain name ` +
74
+ `for the same month, so sorting file names is not enough` +
75
+ `${PERIOD ? `. Prefer the newest one for ${PERIOD}` : ''}. Set proposal to its full path. Read ` +
76
+ `**only** its ` +
74
77
  `frontmatter and its "Aprobación humana" and "Cambio propuesto" sections and report, without ` +
75
78
  `interpreting in anyone's favour:\n` +
76
79
  `- approved: true only if the state says it is approved AND a named person is recorded. "pendiente", ` +
@@ -93,15 +96,17 @@ if (!firma.approved) {
93
96
  // después, y el estado en prosa pasa a decir «aprobada y aplicada», que también lee como aprobada.
94
97
  // Como el cambio es aditivo por diseño, reaplicar no falla — duplica cada viñeta y cada fuente.
95
98
  if ((firma.status || '').toLowerCase() === 'applied') {
96
- return stop('ya-aplicada', `${firma.proposal} ya está aplicada. Para un cambio nuevo, abrí la ` +
97
- 'propuesta del período siguiente con "ops learn <cargo> --proposal"')
99
+ return stop('ya-aplicada', `${firma.proposal} ya está aplicada. Si la evaluación posterior mostró que ` +
100
+ `el cambio quedó mal calibrado, abrí una revisión con "node tools/ops.js learn ${AGENT} --proposal": ` +
101
+ 'la aplicada queda sellada donde está y la corrección va con su propia firma')
98
102
  }
99
103
  log(`Aprobada por ${firma.signedBy}`)
100
104
 
101
- // El período sale del nombre del archivo, que el motor ya garantiza `AAAA-MM.md`: pedírselo otra vez
102
- // al modelo sería preguntar dos veces lo mismo y arriesgar dos respuestas.
103
- const PERIODO = (firma.proposal.match(/(\d{4}-\d{2})\.md$/) || [])[1] || ''
104
- if (!PERIODO) return stop('propuesta-sin-periodo', `${firma.proposal} no se llama AAAA-MM.md`)
105
+ // El período sale del nombre del archivo, que el motor ya garantiza: pedírselo otra vez al modelo sería
106
+ // preguntar dos veces lo mismo y arriesgar dos respuestas. Se toma el nombre entero y no sólo el mes,
107
+ // porque con una revisión abierta hay dos archivos del mismo período y hay que sellar el que se aplicó.
108
+ const PERIODO = (firma.proposal.match(/(\d{4}-\d{2}(?:-r\d+)?)\.md$/) || [])[1] || ''
109
+ if (!PERIODO) return stop('propuesta-sin-periodo', `${firma.proposal} no se llama AAAA-MM.md ni AAAA-MM-rN.md`)
105
110
 
106
111
  phase('Aplicar')
107
112
 
@@ -107,18 +107,56 @@ function resultsDir(root, agent) {
107
107
  return path.join(catalog.resolve(root, agent), ...RESULTS)
108
108
  }
109
109
 
110
+ // Un registro por corrida, y puede haber más de una en el mismo día: aplicar una propuesta cambia el
111
+ // contrato, y el recorrido que la aplica pide volver a correr los casos ahí mismo. Con sólo
112
+ // `AAAA-MM-DD.md` la segunda corrida escribía encima de la primera sin avisar, y la primera es
113
+ // justamente la línea base que la propuesta cita como evidencia: se perdía el término de comparación
114
+ // en el momento exacto en que empezaba a hacer falta.
115
+ const RESULT_NAME = /^(\d{4}-\d{2}-\d{2})(?:-(\d+))?\.md$/
116
+
117
+ // Ordena por fecha y después por corrida, con el nombre sin sufijo como la primera. No alcanza con
118
+ // ordenar los nombres: `-` va antes que `.` en ASCII, así que `2026-08-17-2.md` quedaría *delante* de
119
+ // `2026-08-17.md` y la más nueva se leería como la más vieja.
120
+ function resultOrder(name) {
121
+ const [, date, run] = name.match(RESULT_NAME)
122
+ return `${date}-${String(Number(run || 1)).padStart(4, '0')}`
123
+ }
124
+
125
+ function resultNames(dir) {
126
+ return caseFiles(dir)
127
+ .filter((name) => RESULT_NAME.test(name))
128
+ .sort((one, other) => resultOrder(one).localeCompare(resultOrder(other)))
129
+ }
130
+
131
+ // El nombre que le toca a la corrida de hoy: el primero libre. Lo decide el motor y no el prompt del
132
+ // recorrido, porque «escribí en <fecha>.md» es una instrucción que no puede saber qué ya existe.
133
+ function nextResult(root, agent, date) {
134
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) throw new Error(`fecha inválida: ${date}`)
135
+ const dir = resultsDir(root, agent)
136
+ const taken = new Set(resultNames(dir))
137
+ if (!taken.has(`${date}.md`)) return `${date}.md`
138
+ let run = 2
139
+ while (taken.has(`${date}-${run}.md`)) run += 1
140
+ return `${date}-${run}.md`
141
+ }
142
+
110
143
  // El último resultado registrado, para que `evaluate` pueda decir si el cargo se corrió alguna vez y
111
144
  // cómo le fue. No es un error no tenerlo: correrlo cuesta, y exigirlo en CI sería exigir red.
112
145
  function latest(root, agent) {
113
146
  const dir = resultsDir(root, agent)
114
- const names = caseFiles(dir).filter((name) => /^\d{4}-\d{2}-\d{2}\.md$/.test(name)).sort()
147
+ const names = resultNames(dir)
115
148
  if (!names.length) return null
116
- const file = path.join(dir, names[names.length - 1])
149
+ const name = names[names.length - 1]
150
+ const file = path.join(dir, name)
117
151
  const text = fs.readFileSync(file, 'utf8')
118
152
  const verdicts = [...text.matchAll(/^-\s*Veredicto:\s*(pasa|no pasa)\s*$/gim)].map((hit) => hit[1].toLowerCase())
153
+ const [, date, run] = name.match(RESULT_NAME)
119
154
  return {
120
155
  file,
121
- date: names[names.length - 1].replace(/\.md$/, ''),
156
+ // La fecha es la del día, sin el sufijo de corrida: quien lee «no pasaron en 2026-08-17» busca un
157
+ // día, no un nombre de archivo. Cuál de las corridas fue va aparte, y sólo cuando hubo más de una.
158
+ date,
159
+ run: Number(run || 1),
122
160
  total: verdicts.length,
123
161
  passed: verdicts.filter((verdict) => verdict === 'pasa').length,
124
162
  }
@@ -152,4 +190,4 @@ function validate(root, agent) {
152
190
  return { errors, warnings, cases: total, last }
153
191
  }
154
192
 
155
- module.exports = { behaviors, fixtures, list, latest, parseCase, validate, resultsDir }
193
+ module.exports = { behaviors, fixtures, list, latest, nextResult, parseCase, validate, resultsDir }