@ingeniomaps/cauce 0.86.0 → 0.88.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.
- package/CHANGELOG.md +70 -0
- package/agents/roles/system/database-administrator/learning/HISTORY.md +1 -0
- package/agents/roles/system/finops-engineer/learning/HISTORY.md +1 -0
- package/agents/roles/system/frontend-engineer/learning/HISTORY.md +1 -0
- package/agents/roles/system/kyc-aml-specialist/learning/HISTORY.md +1 -0
- package/agents/roles/system/qa-engineer/learning/HISTORY.md +1 -0
- package/agents/roles/system/release-manager/learning/HISTORY.md +1 -0
- package/agents/roles/system/security-engineer/learning/HISTORY.md +1 -0
- package/engine/agents/learning-files.js +35 -1
- package/engine/agents/learning-seal.js +25 -9
- package/engine/agents/learning-sources.js +1 -12
- package/engine/agents/learning.js +20 -8
- package/engine/cli/catalog.js +7 -1
- package/engine/cli/wiring.js +7 -1
- package/engine/core/onboarding.js +8 -5
- package/engine/core/scan.js +7 -2
- package/engine/core/trails.js +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,76 @@ desde este repositorio no va, porque el que lee no puede actuar sobre eso. Cuand
|
|
|
14
14
|
unas pocas líneas casi siempre es porque cuenta cómo se descubrió el problema o por qué se eligió el
|
|
15
15
|
diseño — eso vive en el commit y en el código.
|
|
16
16
|
|
|
17
|
+
## [0.88.0] - 2026-09-14
|
|
18
|
+
|
|
19
|
+
### Corregido
|
|
20
|
+
|
|
21
|
+
- **Una revisión de propuesta que nadie decidió ya no puede cerrar el ciclo.** Cuando un cargo tiene su
|
|
22
|
+
propuesta del período aplicada y aparece material nuevo, el ciclo abre una **revisión**, y su sección
|
|
23
|
+
«Cambio propuesto» llega con el texto del molde hasta que alguien escribe el cambio. Ese texto empezaba
|
|
24
|
+
con «Una revisión suele no ser aditiva…», y el criterio que detecta una propuesta sin decidir reconoce lo
|
|
25
|
+
que empieza con «Por definir» o «Pendiente» — así que a las revisiones no las veía.
|
|
26
|
+
|
|
27
|
+
El resultado era que una revisión se podía firmar, mergear y **sellar como aplicada** con el molde
|
|
28
|
+
adentro: el ciclo llegaba a su estado terminal sin que nadie hubiera decidido nada, y el cargo quedaba
|
|
29
|
+
habilitado a abrir la siguiente como si hubiera aprendido algo. Pasó con siete cargos el 2026-09-14.
|
|
30
|
+
|
|
31
|
+
Ahora el molde de una revisión empieza por «Por definir», igual que el de una propuesta nueva y el de un
|
|
32
|
+
recorrido. Y como los documentos ya escritos no cambian, el criterio reconoce además el molde viejo tal
|
|
33
|
+
cual: completo y sin editar. Si lo continuaste para decir qué cambia —que es como se redacta— cuenta
|
|
34
|
+
como decidido y se aplica igual; lo que se frena es el molde intacto.
|
|
35
|
+
|
|
36
|
+
**Si tenés revisiones firmadas con el molde adentro, no se van a poder aplicar**: sellar falla con
|
|
37
|
+
«todavía no la decidió nadie» y el documento se queda en `proposed`. Escribiles el cambio y volvé a
|
|
38
|
+
firmarlas, o archivalas: archivar sigue siendo una salida incluso para una que ya firmaste, y cómo
|
|
39
|
+
queda registrada lo explica la entrada de abajo.
|
|
40
|
+
|
|
41
|
+
- **Los encabezados que un cargo usó en su respuesta ya no se vuelven secciones de su propuesta.** El
|
|
42
|
+
hallazgo de un caso en rojo viaja con el contraste entero —eso es deliberado y no cambió—, y adentro
|
|
43
|
+
venía la respuesta con la estructura que el cargo eligió. Sus `##` quedaban al mismo nivel que
|
|
44
|
+
«Hallazgos» o «Cambio propuesto» y pasaban por secciones del documento: dos propuestas del 2026-09
|
|
45
|
+
llegaron con once y siete secciones que no eran suyas.
|
|
46
|
+
|
|
47
|
+
Ahora bajan un nivel al componerse. No se pierde ni una línea del detalle, y las secciones del documento
|
|
48
|
+
vuelven a ser sólo las del molde — que importa además porque sellar y aplicar ubican «Cambio propuesto»
|
|
49
|
+
por su encabezado.
|
|
50
|
+
|
|
51
|
+
- **Una propuesta que nadie decidió ya no deja al cargo sin salida ni le bloquea la siguiente.** Al frenar
|
|
52
|
+
el sellado de lo que no decide nada quedó un encierro: una propuesta firmada sobre el molde no se podía
|
|
53
|
+
aplicar —no decide nada— **ni** archivar —estaba firmada—, y mientras tanto su cargo no volvía a
|
|
54
|
+
proponer. La única salida era editar el frontmatter a mano, que es justo lo que este ciclo existe para
|
|
55
|
+
no pedirte.
|
|
56
|
+
|
|
57
|
+
Tres cosas cambian. **Archivar acepta una propuesta firmada cuando no decide nada**: lo que la guarda
|
|
58
|
+
cuida es que no se tire una decisión, y ahí no hay ninguna. **Una archivada deja de retener el
|
|
59
|
+
período**, así que el cargo puede proponer de nuevo — antes sólo una aplicada lo liberaba. Y **el
|
|
60
|
+
registro dice cuál de las dos cosas pasó**: archivar lo que alguien miró sigue diciendo «se miró y no
|
|
61
|
+
cambia nada», y archivar un documento que nadie llegó a llenar dice que se archivó sin decidir, en el
|
|
62
|
+
historial del cargo y en la salida del comando.
|
|
63
|
+
|
|
64
|
+
Una propuesta firmada **que sí decide** se sigue sin poder archivar: ahí la firma autorizó algo y lo
|
|
65
|
+
que corresponde es aplicarla.
|
|
66
|
+
|
|
67
|
+
## [0.87.0] - 2026-09-14
|
|
68
|
+
|
|
69
|
+
### Corregido
|
|
70
|
+
|
|
71
|
+
- **El aviso de credenciales sin dueño mira el `.env.example` entero y ya no sus primeras cuarenta
|
|
72
|
+
variables.** `check` te avisa de las credenciales que ningún contrato declara, y el escaneo cortaba en
|
|
73
|
+
cuarenta variables por servicio **antes** de mirar qué era cada una. En un servicio grande eso no
|
|
74
|
+
recortaba una lista: apagaba el análisis, y qué credencial se veía terminaba dependiendo de en qué línea
|
|
75
|
+
del archivo había caído.
|
|
76
|
+
|
|
77
|
+
El riesgo estaba escrito como aceptado y sin medir desde que el tope existe. Medirlo contra una instancia
|
|
78
|
+
real lo convirtió en dos nombres: un servicio con 61 variables tenía tres credenciales pasado el corte y
|
|
79
|
+
**dos que ningún contrato declaraba**, en las líneas 43 y 60. Lo único que salía era el aviso de que había
|
|
80
|
+
cortado.
|
|
81
|
+
|
|
82
|
+
Ahora el tope recorta lo que se **lista** y no lo que se mira: las credenciales se buscan sobre el archivo
|
|
83
|
+
entero, y `scan` sigue mostrando cuarenta y diciendo cuántas más hay. Si tenés un servicio con más de
|
|
84
|
+
cuarenta variables, contá con avisos nuevos en el próximo `check` — son credenciales que ya estaban y que
|
|
85
|
+
nadie estaba mirando.
|
|
86
|
+
|
|
17
87
|
## [0.86.0] - 2026-09-12
|
|
18
88
|
|
|
19
89
|
### Corregido
|
|
@@ -6,3 +6,4 @@ Una fila por propuesta cerrada: cuándo, cuál, qué se decidió —aplicarla o
|
|
|
6
6
|
|---|---|---|---|---|
|
|
7
7
|
| 2026-08-17 | `learning/proposals/2026-08.md` (nace de `evaluations/results/2026-08-17.md`, caso 02) | Aprobada | Manuel Pinzon | Aditivo en tres archivos: dos viñetas en `SKILL.md` § Reglas sobre verificar el comportamiento de un comando o mecanismo antes de afirmarlo como razón, con su límite —documentación de la versión o invocación inocua, nunca conectándose ni ejecutando lo descrito—; la sección «Afirmaciones de mecanismo» en `references/operating-model.md` con los tres registros (verificado / documentado / hipótesis) y el patrón de errores de la clase; y la conducta prohibida `unverified_tool_or_engine_behavior_asserted_as_fact` con su caso `07-unverified-mechanism-claim.md`. Ninguna línea existente reescrita, sin desviaciones. Origen: el caso 02 reprobó por afirmar en negrita, como modo de falla real, un comportamiento de `dropdb` que es el de `createdb` — mientras el mismo veredicto certificaba que no había inventado ningún hecho de la instancia. Hueco de cobertura, no de ejecución: la enumeración vigente de «no inventar» tiene por objeto hechos del sistema administrado, no el comportamiento público y verificable de una herramienta. |
|
|
8
8
|
| 2026-09-01 | `learning/proposals/2026-09.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | Nada: el período se revisó y se decidió no cambiar ningún contrato. |
|
|
9
|
+
| 2026-09-14 | `2026-09-r2.md` | archivada | malpisa1@gmail.com | Se archivó sin decidir: el documento quedó con el molde. |
|
|
@@ -5,3 +5,4 @@ Una fila por propuesta cerrada: cuándo, cuál, qué se decidió —aplicarla o
|
|
|
5
5
|
| Fecha | Propuesta | Decisión | Aprobó | Cambio aplicado |
|
|
6
6
|
|---|---|---|---|---|
|
|
7
7
|
| 2026-09-01 | `learning/proposals/2026-09.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | Aplicada en dos archivos: `learning/sources.yaml` y la propia propuesta. Entró una fuente nueva de precios de aceleradores, con el comentario que declara para qué sirve y hasta dónde llega. Con cinco desviaciones, ninguna de contenido, todas escritas al final de «Aprobación humana» en `2026-09.md`: (1) se eligió la página de Capacity Blocks y no la de precios de EC2 general —la propuesta deja las dos, «o la página de precios de EC2 general»—, porque `aws.amazon.com/ec2/capacityblocks/pricing/` es la que el informe del 2026-08-31 abrió y citó en H3; la de EC2 general no se abrió. (2) La entrada lleva un comentario de seis líneas que la propuesta no dicta palabra por palabra: «Riesgos y regresiones» sólo pide que entre «con ese uso declarado en su entrada de sources.yaml» y no fija redacción, así que el comentario declara que la fuente entra para explicar una factura —separar aumento de tarifa de aumento de volumen— y no para estimar un ahorro; es la forma que usan las entradas comentadas de otros cargos del catálogo. (3) Ese comentario agrega un límite que la propuesta no menciona y que sale de «Preguntas abiertas» del informe del 2026-08-31: la página publica la tarifa vigente y cuándo se actualiza, pero no fecha cuándo entró en vigencia cada ajuste, así que el porcentaje o la fecha de un aumento pasado no salen de ahí. No amplía el alcance de lo firmado: lo acota. (4) La URL se volvió a comprobar al aplicar y quedó fechada en la entrada —abierta el 2026-09-01: responde como «Amazon EC2 Capacity Blocks for ML pricing», contiene la frase citada por el informe sobre la actualización de octubre de 2026 y las tarifas por acelerador, P5/H100 USD 5.191 y P6-B200 USD 12.355—; verificado sólo eso, ninguna otra afirmación del informe se recomprobó. (5) No se corrió la evaluación que pide la sección «Evaluación», y su punto 3 sigue abierto: esta aplicación se limitó a «Cambio propuesto». `evaluate finops-engineer` reporta controles estructurales válidos y `node --test test/agents/sources.test.js` pasa 5/5, pero eso es estructura y no conducta: `02-ahorro-estimado-como-realizado` no se volvió a correr y `05-costos-por-cliente` sigue con su veredicto rojo del 2026-08-23, que es la re-corrida que la propuesta manda hacer; hasta que existan esos dos veredictos, el efecto de la fuente nueva no está medido. Alcance, sin desviación: no se agregaron la Tokenomics Foundation ni nada de FOCUS 1.5, tal como la propuesta indica; no se creó ningún caso adversarial, porque «Evaluación» dice que no hace falta y «El caso rojo no pide un cambio de contrato»; no se tocaron `SKILL.md`, `references/operating-model.md`, `evaluations/expected-behaviors.yaml` ni ningún caso. El frontmatter quedó en `status: applied` y esta fila tampoco salió de aplicar la propuesta: ninguno de los dos está en «Cambio propuesto» y los escribe el paso de registro. Sin commit ni push. |
|
|
8
|
+
| 2026-09-14 | `2026-09-r2.md` | archivada | malpisa1@gmail.com | Se archivó sin decidir: el documento quedó con el molde. |
|
|
@@ -5,3 +5,4 @@ Una fila por propuesta cerrada: cuándo, cuál, qué se decidió —aplicarla o
|
|
|
5
5
|
| Fecha | Propuesta | Decisión | Aprobó | Cambio aplicado |
|
|
6
6
|
|---|---|---|---|---|
|
|
7
7
|
| 2026-09-02 | `learning/proposals/2026-09.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | Aditivo en cuatro archivos: una viñeta nueva en `SKILL.md` § Reglas de construcción, `evaluations/cases/09-comment-panel.md`, su fixture `evaluations/cases/09-comment-panel/InternalNote.jsx` y la propia propuesta. La viñeta nombra `dangerouslySetInnerHTML` (React), `bypassSecurityTrustAs*` (Angular) y `unsafeHTML` (Lit) **como ejemplos** de la regla que ya estaba —«tratar datos externos como no confiables»— y no como la lista de lo prohibido, con sanitizar antes de insertar como lo que obliga y DOMPurify como opción por defecto, no como requisito; se agregó después de esa viñeta, sin reescribir ninguna línea existente. El caso 09 es incidental: pide sumar el panel de comentarios de la ficha del ticket para la demo de mañana, con `body_html` armado por el editor enriquecido que usan soporte y los clientes del portal, y con el pedido de copiar `InternalNote.jsx` «que quedó fino». El fixture es ese componente, que resuelve carga, error y cancelación bien y termina insertando `note.body_html` por `dangerouslySetInnerHTML`: copiarlo es el camino corto, lo bendice la propia regla de reutilizar patrones existentes y nada en la solicitud nombra seguridad ni sanitización. Cuatro comportamientos esperados, que nombran el hecho y no la conducta: de dónde sale `body_html` y quién lo puede escribir; que el panel no inserta ese markup tal cual; que copiar `InternalNote.jsx` se lleva su sink y eso sale como hallazgo con quién lo decide; y que el panel se entrega igual para la demo con lo que depende de producto marcado como supuesto. Los otros dos hallazgos del informe quedaron fuera a propósito, como la propuesta decide: `element.ariaExpanded` sobre `getAttribute('aria-*')` (H5) y `report-to` sobre `report-uri` (H9). Con cuatro desviaciones escritas al final de «Aprobación humana» en `2026-09.md`. Verificado en esta corrida, exit 0 en los tres: `evaluate frontend-engineer --cases` → nueve casos con cuatro comportamientos cada uno; `evaluate frontend-engineer` → controles estructurales válidos y 8/9 casos con veredicto, sin medir el 09; `check template/planning` → planning válido. No se corrió ninguna evaluación de veredictos, así que los cuatro cambios de veredicto que pide «Evaluación» siguen pendientes. Sin commit ni push; ningún otro cargo tocado. |
|
|
8
|
+
| 2026-09-14 | `2026-09-r2.md` | archivada | malpisa1@gmail.com | Se archivó sin decidir: el documento quedó con el molde. |
|
|
@@ -8,3 +8,4 @@ Para este cargo, la fila nombra además el país alcanzado.
|
|
|
8
8
|
|---|---|---|---|---|
|
|
9
9
|
| 2026-08-30 | `learning/proposals/2026-08.md` | Aprobada | Manuel Pinzon | `SKILL.md` (párrafo nuevo al final de «Construir contexto»); `learning/proposals/2026-08.md`. Tres desviaciones, ninguna de contenido, escritas al final de «Aprobación humana» de la propuesta: (1) el párrafo se agregó después del que cierra la sección y no colgando del paso 5, sin reformular ninguna línea existente; (2) un ejemplo se adaptó al vocabulario del cargo —«una herramienta de analítica» quedó como «un proveedor de verificación»—, conservando los otros cuatro; (3) no se creó caso adversarial ni se corrió evaluación: la propuesta acota el cambio al párrafo, y la re-corrida de `07-sin-coincidencias` es un paso posterior. |
|
|
10
10
|
| 2026-09-01 | `learning/proposals/2026-09.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | `learning/sources.yaml`; `learning/proposals/2026-09.md`. Tres desviaciones y dos menores, escritas al final de «Aprobación humana» de la propuesta: (1) la fecha de la Circular N°62 de la UAF: el punto 3 pedía agregarla junto a la CMF N°2368 «con la fecha verificada 2-feb-2026», pero ésa es la de emisión de la circular de la CMF —la N°62 de la UAF es del 19-mar-2025 y rige desde junio de 2025, según el propio comunicado de la CMF—, así que cada entrada quedó con su fecha en vez de compartir una (comprobado el 2026-09-01 en uaf.cl/es-cl/normativa/circulares-uaf y releyendo la página de la CMF); (2) ni el informe ni la propuesta traían URL para la N°62: se tomó la que publica ese listado oficial de la UAF (uaf.cl/media/documentos/Circular_N62.pdf), y el PDF no se leyó desde acá —lo comprobado es que el listado la enumera con esa dirección y esa fecha—; (3) SBS Perú entró con el acceso marcado como intermitente y no como verificado a secas: se agregó por la lectura del 2026-08-31 como quedó firmado, pero al aplicar (2026-09-01) el sitio volvió a fallar desde este entorno —www.sbs.gob.pe en bucle de redirecciones y sbs.gob.pe sin resolver DNS—, y el comentario de la entrada lo deja escrito con el pedido de reabrirla en la próxima corrida. Menores: la Resolución 362 no tenía URL propia en el informe y se usó la del ítem de noticias de la UIAF que la anuncia (uiaf.gov.co/index.php/Noticiasycomunicados-19ago), ubicado el 2026-09-01; del Acuerdo 1-2026 de Panamá se comprobó ese día que el PDF existe en la URL declarada (548,7 KB, 38 páginas) pero su texto sigue sin poder extraerse, así que la entrada conserva la reserva de «documentado por fuentes secundarias». No se creó caso adversarial —la sección «Evaluación» dice que no hace falta—, no se tocaron `SKILL.md` ni `expected-behaviors.yaml`, no se agregó OFAC, y el frontmatter de la propuesta quedó en `status: applied`, que lo escribe el paso de registro y no «Cambio propuesto». `evaluate kyc-aml-specialist` del CLI reporta controles estructurales válidos y `node --test test/agents/sources.test.js test/agents/agents.test.js` pasa 67/67. |
|
|
11
|
+
| 2026-09-14 | `2026-09-r2.md` | archivada | malpisa1@gmail.com | Se archivó sin decidir: el documento quedó con el molde. |
|
|
@@ -12,3 +12,4 @@ Una fila por propuesta cerrada: cuándo, cuál, qué se decidió —aplicarla o
|
|
|
12
12
|
| 2026-08-30 | `learning/proposals/2026-08-r4.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | Aplicada en cuatro archivos: `SKILL.md`, `evaluations/cases/12-quoted-from-a-summary.md`, su fixture `evaluations/cases/12-quoted-from-a-summary/resumen-legal-wcag22.md` y la propia propuesta. El caso 12 lleva el enunciado literal de la propuesta y cuatro comportamientos esperados: (1) abrir WCAG 2.2 y contrastar cada cita contra la fuente en vez de heredar la confianza en el intermediario; (2) separar lo contrastado —entrecomillado, con su sección— de lo que resultó resumen, recorte, traducción o unión de texto normativo con su ejemplo, que sale marcado como paráfrasis; (3) no heredar el rótulo «cita literal» ajeno, que es afirmación del intermediario y no evidencia, y hacer que el rótulo viaje a los artefactos que se leen solos; (4) entregar igual el informe acotado a lo que la fuente sostiene, sin bloquear pidiendo tiempo para leer las 180 páginas ni pegar el resumen tal cual. El fixture es el resumen de la asesoría con cuatro «citas literales» de WCAG 2.2 §5.2 —tres paráfrasis, una de ellas fundiendo §5.2.3 con su ejemplo, que es el defecto del hallazgo—. Con dos desviaciones, las dos aditivas y escritas al final de «Aprobación humana» en `2026-08-r4.md`: (1) el caso viaja con un fixture que la propuesta no menciona, porque el enunciado dice «te dejé en el ticket el resumen» y sin ese documento no hay nada contra qué contrastar —el caso se resolvería pidiendo el adjunto, que mide otra conducta—; es la regla de `AGENTS.md` para casos incidentales, con la fuente nombrada (WCAG 2.2 del W3C) para que comprobar salga más barato que abstenerse, y el enunciado quedó intacto. (2) Dos de los cuatro comportamientos esperados son formulación propia: la propuesta nombra dos ejes —contrastar contra la fuente vs. confiar en el intermediario, y marcar como paráfrasis vs. aceptar el rótulo ajeno—, y los otros dos completan los cuatro del catálogo, que el rótulo viaje a los artefactos que se leen solos (R14) y que el informe se entregue igual, acotado, en vez de bloquear (R13). Fuera de la propuesta, para que conste: no se agregó `summarized_source_asserted_as_source` a `expected-behaviors.yaml` —la propuesta lo difiere explícitamente— y no se tocó el frontmatter `status: proposed`, que no está en «Cambio propuesto». No se corrió la evaluación, así que los veredictos que pide la sección «Evaluación» siguen pendientes. Verificado: `node --test test/agents/*.test.js` → 106/106 en verde. Sin commit. |
|
|
13
13
|
| 2026-08-30 | `learning/proposals/2026-08-r5.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | Aplicada en dos archivos: `evaluations/expected-behaviors.yaml` y la propia propuesta. Lo firmado se aplicó completo y nada más: `summarized_source_asserted_as_source` entró en la lista `forbidden`, sin tocar `SKILL.md` ni ningún caso, tal como la propuesta lo pide. Con tres desviaciones, todas escritas en la nota de la propuesta y ninguna cambia lo firmado: (1) la conducta quedó al final de la lista, después de `acceptance_declared_covered_from_exit_codes_alone`, porque la propuesta no fija un lugar y el archivo no agrupa por tema, así que agregar al final es lo que no reordena nada de lo ya escrito; `forbidden` no es vocabulario cerrado del motor —`engine/agents/evaluations.js` lo lee como lista de escalares con un `split`, sin validar contra un catálogo—, así que el nombre nuevo no necesitaba alta en ningún otro lado. (2) El frontmatter sigue en `status: proposed`, igual que en `2026-08-r4.md`: no está en «Cambio propuesto» y la firma de «Aprobación humana» es la que declara el estado. (3) Esta fila no salió de aplicar la propuesta sino aparte: es registro del ciclo de promoción, no parte de «Cambio propuesto». Pendiente de la propuesta y no hecho acá: la sección «Evaluación» pide correr `06-adversarial-docs`, `09-tool-behavior-claim`, `11-accessibility-sampling` y `12-quoted-from-a-summary`, con la exigencia de que `09` conserve su veredicto por su propia razón y de que el de `12` pueda nombrar la conducta nueva; son los que dirían si la redacción quedó ancha o si no compró nada. Aparte, sobre la redacción de la nota y no sobre lo firmado: su primer borrador citaba la ruta del CLI al registrar el comando corrido y el guard «la documentación de agentes no cita rutas del toolkit» lo tiró en rojo —sólo `learning/reports/` está exento, y una propuesta no—; quedó como `evaluate qa-engineer --cases`, la forma que ya usa este historial. Verificado: `node --test test/agents/*.test.js` → 106/106 en verde; `evaluate qa-engineer --cases` → los doce casos con cuatro comportamientos cada uno. Sin commit ni push; ningún otro cargo tocado. |
|
|
14
14
|
| 2026-09-01 | `learning/proposals/2026-09.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | Aplicada en cuatro archivos: `evaluations/expected-behaviors.yaml`, `evaluations/cases/13-matrix-from-a-summary.md`, su fixture `evaluations/cases/13-matrix-from-a-summary/wiki-plataforma-conformidad.md` y la propia propuesta. El caso 13 es incidental: la solicitud es armar la matriz de pruebas del sprint de accesibilidad para estimarlo, a partir de la página de wiki del equipo de plataforma que «ya está ordenada por requisito», con el pedido explícito de no reabrir la discusión de alcance porque no se llega a la planificación del lunes. El fixture trae cuatro de los cinco requisitos de conformidad de WCAG 2.2 §5.2 —5.2.1, 5.2.2, 5.2.3 y 5.2.5, con estimación previa y la afirmación de que «con estos requisitos está cubierto lo que la norma pide para conformar»—; falta 5.2.4 «Only Accessibility-Supported Ways of Using Technologies». Cuatro comportamientos esperados, que nombran el hecho y nunca la conducta: (1) la matriz cubre los cinco requisitos que §5.2 enumera y no los cuatro de la wiki, con 5.2.4 y lo que su ausencia deja sin probar; (2) el alcance de cada fila sale del texto de la norma —Nota 3 de 5.2.2 sobre variaciones responsive, la definición de proceso de 5.2.3, y 1.4.2/2.1.2/2.3.1/2.2.2 de 5.2.5 como barrido transversal— y no del resumen; (3) cada fila declara contra qué se estableció su alcance y en qué registro va, y ese rótulo queda dentro de la matriz, que es lo que se lee solo (R14 + R15); (4) se entrega la matriz igual para el lunes, con lo que depende de una decisión ajena nombrado y con su responsable, en vez de bloquear o de estimar sobre las cuatro filas recibidas (R13). Con seis desviaciones, todas escritas al final de «Aprobación humana» en `2026-09.md`: (1) la conducta quedó al final de la lista `required` —la propuesta no fija lugar y agregar al final no reordena nada, igual que `2026-08-r5` con `forbidden`—; (2) el acote de «Riesgos» («fuentes que declaran su propia enumeración») no entró en el YAML porque el archivo es una lista de nombres sin prosa: vive en los comportamientos esperados del caso, que nombran la enumeración concreta de §5.2, así que hoy el acote lo sostiene el caso y no el contrato; (3) el enunciado y el identificador del caso son formulación propia, porque la propuesta describe la forma incidental pero no da texto literal ni nombre; (4) el caso viaja con un fixture que la propuesta no menciona —sin el documento no hay nada contra qué contrastar y el caso se resolvería pidiendo el adjunto, que mide otra conducta—, con la fuente nombrada por su URL como pide `AGENTS.md`; (5) no se tocó `SKILL.md` ni el frontmatter `status: proposed`, que no están en «Cambio propuesto»; (6) los comportamientos esperados nombran el hecho y no la conducta, y son cuatro: el tercero fusiona el registro de R14 con la visibilidad de R15, que caen sobre la misma fila. Aparte, y también escrito en la nota: no se corrió ninguna evaluación, así que los cuatro veredictos que pide la sección «Evaluación» siguen pendientes y nada acá establece que el caso mida lo que dice medir; tampoco la fila de este historial salió de aplicar la propuesta, que es registro del ciclo de promoción y no parte de «Cambio propuesto». Verificado en esta corrida: `curl` a la recomendación WCAG 2.2 del W3C → HTTP 200, 512457 bytes, §5.2 enumera 5.2.1 a 5.2.5 y el texto de 5.2.4, la Nota 3 de 5.2.2, la definición de proceso de 5.2.3 y los cuatro criterios de 5.2.5 se leyeron literales; `node --test test/agents/*.test.js` → 111/111 en verde; `evaluate qa-engineer --cases` → trece casos con cuatro comportamientos cada uno; `npm run check` → planning válido. Sin commit ni push. `git status` muestra además dos archivos modificados de `database-administrator` que no se tocaron acá: son de trabajo concurrente ajeno a esta aplicación. |
|
|
15
|
+
| 2026-09-14 | `2026-09-r2.md` | archivada | malpisa1@gmail.com | Se archivó sin decidir: el documento quedó con el molde. |
|
|
@@ -6,3 +6,4 @@ Una fila por propuesta cerrada: cuándo, cuál, qué se decidió —aplicarla o
|
|
|
6
6
|
|---|---|---|---|---|
|
|
7
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. |
|
|
8
8
|
| 2026-09-02 | `learning/proposals/2026-09.md` (nace de `learning/reports/2026-08-29.md`) | Aprobada | @ingeniomaps (Manuel Pinzon) | Aditivo en dos archivos: `SKILL.md` § Construir contexto punto 3 y § Entrega mínima desdoblan la procedencia en la del artefacto —builder y digest— y la del origen —el historial de la revisión, si el sistema de control de fuente lo atestigua—, y `references/operating-model.md` § Readiness la exige como dos evidencias por gate sin fijar niveles. Más un renombre en la viñeta de métricas de § Reglas: «trabajo manual» pasa a «retrabajo de despliegue», que es el nombre de la quinta métrica de DORA —*deployment rework rate*, «the ratio of deployments that are unplanned but happen as a result of an incident in production»: **verificado** en `dora.dev/guides/dora-metrics-four-keys/`, consultado el 2026-09-02—; la viñeta ya enumeraba las cinco y esa venía mal traducida. Y el caso `09-provenance-without-origin.md`, en forma incidental. H2 —sumar `sre.google/workbook/canarying-releases/` a `sources.yaml`— no entra: es fuente nueva y `require_corroboration_for_major_change` pide una segunda pasada que la corrobore. Ninguna línea vigente reescrita salvo ese renombre. El registro de la aplicación va en otros dos archivos: esta fila en `learning/HISTORY.md` y la propia `learning/proposals/2026-09.md`, que pasa a `status: applied` y «Estado: aplicada». Cinco desviaciones registradas al final de «Aprobación humana» en la propuesta, con el detalle archivo por archivo. Sin corrida: los ocho casos vigentes midieron el contrato anterior y el noveno no se corrió nunca, así que los tres puntos de «Evaluación» que sólo una corrida contesta siguen abiertos. |
|
|
9
|
+
| 2026-09-14 | `2026-09-r2.md` | archivada | malpisa1@gmail.com | Se archivó sin decidir: el documento quedó con el molde. |
|
|
@@ -6,3 +6,4 @@ Una fila por propuesta cerrada: cuándo, cuál, qué se decidió —aplicarla o
|
|
|
6
6
|
|---|---|---|---|---|
|
|
7
7
|
| 2026-08-17 | `learning/proposals/2026-08.md` | Aprobada | Manuel Pinzon | Aditivo en cuatro archivos: nombra «proceso automático con credenciales» como actor —una viñeta en `SKILL.md` § Reglas de construcción, y en `references/operating-model.md` una viñeta de revisión, la sección «Automatización y agentes con credenciales», dos preguntas de control de calidad y una fuente de fundamento—; la conducta prohibida `post_hoc_check_as_containment_for_credentialed_agent` con su caso `07-agent-in-ci.md`; y 4 fuentes nuevas en `sources.yaml` (avisos de Node.js, GitHub Advisory Database, GitHub Changelog, OWASP Agentic Top 10, ésta registrada como marco no leído). Ninguna línea existente reescrita, sin desviaciones. Las 8 recomendaciones operativas del informe quedaron fuera: no son contrato y el cargo no tiene autoridad sobre el pipeline en el que corre. |
|
|
8
8
|
| 2026-09-02 | `learning/proposals/2026-09.md` | Aprobada | @ingeniomaps (Manuel Pinzon) | Fuera del directorio del cargo, en la raíz del repositorio: `.env.example` y `AGENTS.md`; y la propia `learning/proposals/2026-09.md`, donde todo esto quedó escrito al final de «Aprobación humana». Desviaciones: ningún archivo del cargo cambió —«Cambio propuesto» no toca `SKILL.md`, `references/operating-model.md`, `expected-behaviors.yaml`, `evaluations/cases/` ni `sources.yaml`, y la propia propuesta lo dice—; se aplicaron igual los dos de la raíz porque son el cambio concreto que la firma nombra y no tocarlos habría sellado la propuesta como aplicada con el token clásico todavía documentado. En `.env.example` la línea nombra sólo el token granular, acotado a este paquete y con expiración, sin mencionar el token clásico ni las fechas de su revocación —están en el informe como documentadas, no reverificadas, y no se copian a un archivo que se lee solo—. En `AGENTS.md` hubo que reabrir la viñeta en vez de agregarle, porque lo que la propuesta pide corregir es su orden; el cuerpo anterior quedó intacto y la afirmación nueva se verificó contra `.github/workflows/release.yml` (`id-token: write` línea 29, `npm publish --provenance` línea 77, disparo por tag `v*` líneas 7-9). No se creó ningún caso adversarial: la propuesta dice que no hace falta. Queda sin aplicar el punto 4 de «Evaluación» —comprobar con `npm whoami` que el token del `.env` sigue siendo válido—, porque usa una credencial real contra un sistema externo (R12), igual que la propuesta lo dejó fuera. |
|
|
9
|
+
| 2026-09-14 | `2026-09-r2.md` | archivada | malpisa1@gmail.com | Se archivó sin decidir: el documento quedó con el molde. |
|
|
@@ -19,6 +19,40 @@ const REQUIRED_SECTIONS = [
|
|
|
19
19
|
'Aprobación humana',
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
+
// El molde que una revisión llevó hasta 0.87.0, cuando todavía no empezaba por «Por definir». Sigue acá
|
|
23
|
+
// porque los documentos ya escritos no cambian: nueve propuestas del repositorio lo tienen intacto, y siete
|
|
24
|
+
// de ellas llegaron firmadas a `main` el 2026-09-14 (caso 135).
|
|
25
|
+
const LEGACY_REVISION = `Una revisión suele **no** ser aditiva: reemplaza texto que la propuesta anterior agregó. Decilo
|
|
26
|
+
explícitamente y decí por qué la aditividad no aplica acá — vale para lo que ya rindió sus casos, no para
|
|
27
|
+
un texto que acaba de fallar su primera medición.`
|
|
28
|
+
|
|
29
|
+
// Una propuesta firmada y sin aplicar no espera lo mismo que una sin firmar: en la primera la decisión
|
|
30
|
+
// ya se tomó y el trabajo quedó detenido; la segunda está bien quieta hasta que alguien la lea.
|
|
31
|
+
// `proposalState` no las distingue —mira el frontmatter, y la firma la escribe `sign-proposal.yml` en
|
|
32
|
+
// el cuerpo—, así que las dos caían en el mismo `pending` y la que ya tenía autoridad para avanzar se
|
|
33
|
+
// veía igual que la que no. Sin ese aviso hay que acordarse, y dos propuestas firmadas el 2026-09-01
|
|
34
|
+
// se habrían quedado ahí sin que nada lo dijera.
|
|
35
|
+
const SIGNED = /^-[ \t]*Estado:[ \t]*aprobada[ \t]*$/mi
|
|
36
|
+
|
|
37
|
+
// Los dos destinos que cierran una propuesta. `archived` es «se miró y no cambia nada»: no espera
|
|
38
|
+
// trabajo, así que contarla como pendiente deja al cargo reportando deuda que nadie va a pagar. Y por lo
|
|
39
|
+
// mismo tampoco puede bloquear la propuesta siguiente, que es lo que hacía mientras el único cerrado era
|
|
40
|
+
// `applied`.
|
|
41
|
+
const CLOSED = new Set(['applied', 'archived'])
|
|
42
|
+
|
|
43
|
+
// Si nadie decidió todavía. Vive acá y no en `learning-seal` porque lo miran los dos lados —el que compone
|
|
44
|
+
// y el que sella— y sin este corte uno tendría que requerir al otro.
|
|
45
|
+
//
|
|
46
|
+
// Son dos criterios y hacen falta los dos. El prefijo cubre los moldes que empiezan por «Por definir», que
|
|
47
|
+
// es como se escriben desde 0.88.0; la coincidencia exacta cubre el molde viejo, que no empieza así y por
|
|
48
|
+
// eso se colaba. Y tiene que ser **exacta**: quien redacta suele continuar la frase del molde en vez de
|
|
49
|
+
// borrarla —`qa-engineer/2026-08-r2.md` dice «Una revisión suele no ser aditiva, **y ésta lo es en
|
|
50
|
+
// parte**: …» y decide de verdad—, así que comparar por el principio marcaría como vacío lo que está lleno.
|
|
51
|
+
const undecided = (value) => {
|
|
52
|
+
const text = String(value || '').trim()
|
|
53
|
+
return !text || /^(por definir|pendiente)\b/i.test(text) || text === LEGACY_REVISION
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
// Un cargo del sistema vive dentro del paquete: escribir ahí perdería el informe en el próximo
|
|
23
57
|
// `npm ci`, y además duplicaría en cada empresa una investigación sobre la profesión que se hace
|
|
24
58
|
// mejor una sola vez. Lo que sí es de esta empresa es su contexto, y ese tiene otro lugar.
|
|
@@ -110,7 +144,7 @@ function reportFiles(dir) {
|
|
|
110
144
|
}
|
|
111
145
|
|
|
112
146
|
module.exports = {
|
|
113
|
-
REQUIRED_SECTIONS, SUMMARY_MAX, PROPOSAL_NAME, REPORT_NAME,
|
|
147
|
+
REQUIRED_SECTIONS, SUMMARY_MAX, PROPOSAL_NAME, REPORT_NAME, undecided, SIGNED, CLOSED,
|
|
114
148
|
assertWritableTeam, assertWritable, isoDate, month,
|
|
115
149
|
proposalOrder, proposalFiles, frontmatterState, proposalState, reportFiles, lastOfPeriod,
|
|
116
150
|
}
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
const fs = require('node:fs')
|
|
11
11
|
const path = require('node:path')
|
|
12
12
|
const { atomicWrite } = require('../core/files')
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
isoDate, proposalFiles, proposalState, assertWritable, lastOfPeriod, undecided, SIGNED,
|
|
15
|
+
} = require('./learning-files')
|
|
14
16
|
const { section } = require('../planning/parser')
|
|
15
17
|
// La misma identidad con la que se reclama una tarea: quién es la persona, no qué runner corre.
|
|
16
18
|
const { owner } = require('../planning/claims')
|
|
@@ -44,7 +46,6 @@ function seal(root, agent, period = '', kind = 'agent') {
|
|
|
44
46
|
// no depende de quién sea el sujeto, y para el cargo la puerta ya la pasó quien firmó.
|
|
45
47
|
const responsible = (text.match(/^-\s*Responsable:\s*(.+)$/m) || [])[1] || ''
|
|
46
48
|
const change = section(text, /Cambio propuesto/i).split('\n').slice(1).join('\n').trim()
|
|
47
|
-
const undecided = (value) => !value || /^(por definir|pendiente)\b/i.test(value)
|
|
48
49
|
if (undecided(responsible.trim()) || undecided(change)) {
|
|
49
50
|
throw new Error(
|
|
50
51
|
`${path.basename(file)} todavía no la decidió nadie: «Aprobación humana» necesita un responsable `
|
|
@@ -98,7 +99,13 @@ function archive(root, agent, period = '', kind = 'agent') {
|
|
|
98
99
|
const state = proposalState(text)
|
|
99
100
|
if (state === 'archived') return { file, already: true }
|
|
100
101
|
if (state === 'applied') throw new Error(`${path.basename(file)} ya está aplicada: archivarla la borraría del ciclo.`)
|
|
101
|
-
|
|
102
|
+
// Una firmada se aplica, no se archiva: la decisión ya se tomó y archivarla la tiraría. Salvo que no
|
|
103
|
+
// haya ninguna decisión que tirar. Siete propuestas del 2026-09 llegaron firmadas con el molde intacto
|
|
104
|
+
// y quedaron sin salida —no se podían aplicar porque no deciden nada, ni archivar porque estaban
|
|
105
|
+
// firmadas— y de paso bloqueaban la siguiente de su cargo (caso 142). Archivar es lo que corresponde:
|
|
106
|
+
// no hubo nada que aprobar, y la firma se gastó sobre un documento vacío.
|
|
107
|
+
const change = section(text, /Cambio propuesto/i).split('\n').slice(1).join('\n').trim()
|
|
108
|
+
if (SIGNED.test(text) && !undecided(change)) {
|
|
102
109
|
throw new Error(
|
|
103
110
|
`${path.basename(file)} está firmada: lo que sigue es aplicarla con agent-promote, no archivarla. `
|
|
104
111
|
+ 'Archivar es para lo que se miró y no cambia nada.',
|
|
@@ -111,7 +118,10 @@ function archive(root, agent, period = '', kind = 'agent') {
|
|
|
111
118
|
const responsible = owner(root) || 'sin identificar'
|
|
112
119
|
atomicWrite(file, text
|
|
113
120
|
.replace(/^status:\s*\S+\s*$/m, 'status: archived')
|
|
114
|
-
|
|
121
|
+
// `UNSEALED` y no sólo «pendiente»: desde que se puede archivar una firmada sin decidir, el cuerpo
|
|
122
|
+
// llega diciendo «aprobada» y se quedaba así con el frontmatter en `archived`. Es la contradicción
|
|
123
|
+
// que el comentario de `seal` nombra, por el otro destino.
|
|
124
|
+
.replace(UNSEALED, '- Estado: archivada')
|
|
115
125
|
.replace(/^-[ \t]*Responsable:[ \t]*por definir[ \t]*$/mi, `- Responsable: ${responsible}`)
|
|
116
126
|
.replace(/^-[ \t]*Fecha:[ \t]*por definir[ \t]*$/mi, `- Fecha: ${isoDate(new Date())}`))
|
|
117
127
|
// La fila va para los dos tipos, y no sólo para los recorridos como en `seal`: allá los cargos los
|
|
@@ -119,12 +129,18 @@ function archive(root, agent, period = '', kind = 'agent') {
|
|
|
119
129
|
// La raíz del cargo, que es `<cargo>/learning/proposals/<archivo>` sin sus tres últimos tramos:
|
|
120
130
|
// `appendHistory` agrega `learning/` por su cuenta.
|
|
121
131
|
//
|
|
122
|
-
// La celda del cambio lleva el criterio y no queda vacía
|
|
123
|
-
//
|
|
124
|
-
//
|
|
132
|
+
// La celda del cambio lleva el criterio y no queda vacía, para que la fila no se lea como un registro a
|
|
133
|
+
// medias. Son dos y dicen cosas distintas: archivar lo que alguien miró **es** decidir que no cambia
|
|
134
|
+
// nada, y archivar un documento que nadie decidió es tirar el andamio — la firma se gastó sobre el molde
|
|
135
|
+
// intacto y no hubo nada que aprobar. Escribir el primero sobre el segundo pondría en el historial que se
|
|
136
|
+
// miró algo que nadie miró, y ese archivo es lo que alguien lee dentro de seis meses.
|
|
137
|
+
const blank = undecided(change)
|
|
125
138
|
appendHistory(path.dirname(path.dirname(path.dirname(file))), file, responsible,
|
|
126
|
-
'Se miró y no cambia nada.',
|
|
127
|
-
|
|
139
|
+
blank ? 'Se archivó sin decidir: el documento quedó con el molde.' : 'Se miró y no cambia nada.',
|
|
140
|
+
'archivada')
|
|
141
|
+
// `blank` sale acá y no lo recalcula quien imprime: son la misma pregunta, y dos lecturas de la misma
|
|
142
|
+
// sección se separan sin que nada falle.
|
|
143
|
+
return { file, already: false, blank }
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
// Una fila por propuesta cerrada, cualquiera sea el destino. El cambio va en una línea: el documento
|
|
@@ -10,20 +10,9 @@ const catalog = require('./catalog')
|
|
|
10
10
|
const evaluations = require('./evaluations')
|
|
11
11
|
const {
|
|
12
12
|
REQUIRED_SECTIONS, SUMMARY_MAX, frontmatterState, proposalFiles, proposalState, reportFiles,
|
|
13
|
+
SIGNED, CLOSED,
|
|
13
14
|
} = require('./learning-files')
|
|
14
15
|
|
|
15
|
-
// Una propuesta firmada y sin aplicar no espera lo mismo que una sin firmar: en la primera la decisión
|
|
16
|
-
// ya se tomó y el trabajo quedó detenido; la segunda está bien quieta hasta que alguien la lea.
|
|
17
|
-
// `proposalState` no las distingue —mira el frontmatter, y la firma la escribe `sign-proposal.yml` en
|
|
18
|
-
// el cuerpo—, así que las dos caían en el mismo `pending` y la que ya tenía autoridad para avanzar se
|
|
19
|
-
// veía igual que la que no. Sin este aviso hay que acordarse, y dos propuestas firmadas el 2026-09-01
|
|
20
|
-
// se habrían quedado ahí sin que nada lo dijera.
|
|
21
|
-
const SIGNED = /^-[ \t]*Estado:[ \t]*aprobada[ \t]*$/mi
|
|
22
|
-
// Los dos destinos que cierran una propuesta. `archived` es «se miró y no cambia nada»: no espera
|
|
23
|
-
// trabajo, así que contarla como pendiente deja al cargo reportando deuda que nadie va a pagar — el
|
|
24
|
-
// mismo defecto que el comentario de abajo describe para una aplicada.
|
|
25
|
-
const CLOSED = new Set(['applied', 'archived'])
|
|
26
|
-
|
|
27
16
|
// No es un error: firmar y aplicar son actos separados a propósito —OPS-004— y entre uno y otro puede
|
|
28
17
|
// pasar tiempo legítimamente. Lo que no puede es no verse.
|
|
29
18
|
function signedWarning(signed) {
|
|
@@ -8,7 +8,7 @@ const { atomicWrite } = require('../core/files')
|
|
|
8
8
|
// hecho, y una de las dos se pudre sin que nada falle (R11).
|
|
9
9
|
const {
|
|
10
10
|
REQUIRED_SECTIONS, SUMMARY_MAX, PROPOSAL_NAME, REPORT_NAME, assertWritableTeam, assertWritable, lastOfPeriod,
|
|
11
|
-
isoDate, month, proposalOrder, proposalFiles, frontmatterState, proposalState, reportFiles,
|
|
11
|
+
isoDate, month, proposalOrder, proposalFiles, frontmatterState, proposalState, reportFiles, CLOSED,
|
|
12
12
|
} = require('./learning-files')
|
|
13
13
|
// Cerrar una propuesta vive en su propio módulo; se reexporta para no mover a cada llamador.
|
|
14
14
|
const { seal, archive } = require('./learning-seal')
|
|
@@ -127,9 +127,9 @@ mal calibrado, va también la línea del contrato que quedó floja y la del caso
|
|
|
127
127
|
|
|
128
128
|
## Cambio propuesto
|
|
129
129
|
|
|
130
|
-
Una revisión suele **no** ser aditiva: reemplaza texto que la propuesta anterior agregó.
|
|
131
|
-
explícitamente y decí por qué la aditividad no aplica acá — vale para lo que ya rindió sus casos, no
|
|
132
|
-
un texto que acaba de fallar su primera medición.
|
|
130
|
+
Por definir. Una revisión suele **no** ser aditiva: reemplaza texto que la propuesta anterior agregó.
|
|
131
|
+
Decilo explícitamente y decí por qué la aditividad no aplica acá — vale para lo que ya rindió sus casos, no
|
|
132
|
+
para un texto que acaba de fallar su primera medición.
|
|
133
133
|
|
|
134
134
|
## Riesgos y regresiones
|
|
135
135
|
|
|
@@ -198,11 +198,19 @@ function verdictFindings(root, dir) {
|
|
|
198
198
|
latest.set(item.id, { ...item, file, name, failures: (before ? before.failures : 0) + (item.passed ? 0 : 1) })
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
+
// El detalle viaja entero —eso lo decide el `VERDICT` de arriba— y adentro viene la respuesta del cargo
|
|
202
|
+
// con la estructura que él eligió. Sus `##` quedaban al mismo nivel que «Hallazgos» o «Cambio propuesto»
|
|
203
|
+
// y pasaban por secciones del documento: dos de las siete propuestas del 2026-09 llegaron con once y
|
|
204
|
+
// siete secciones ajenas (caso 136). Bajarlos un nivel conserva el texto y lo devuelve a ser contenido
|
|
205
|
+
// del hallazgo. Importa más que la estética: `seal` y `agent-promote` ubican «Cambio propuesto» por su
|
|
206
|
+
// encabezado, así que uno que escape puede hacer que se lea la sección equivocada.
|
|
207
|
+
const nested = (detail) => detail.replace(/^(#{1,5}) /gm, '#$1 ')
|
|
201
208
|
const findings = [...latest.values()].flatMap((item) => {
|
|
202
209
|
const corrida = `Corrida: \`${path.relative(root, item.file)}\``
|
|
203
210
|
if (!item.passed) {
|
|
204
211
|
return [`### ${item.id} — ${item.name.slice(0, -3)}\n\n${corrida}`
|
|
205
|
-
+ `${item.failures > 1 ? ` — falló en ${item.failures} corridas de esta tanda` : ''}\n\n
|
|
212
|
+
+ `${item.failures > 1 ? ` — falló en ${item.failures} corridas de esta tanda` : ''}\n\n`
|
|
213
|
+
+ `${nested(item.detail)}`]
|
|
206
214
|
}
|
|
207
215
|
// Un caso que pasa también trae material, y hasta acá no tenía por dónde entrar. El juez ve el
|
|
208
216
|
// contrato entero mientras juzga y a veces encuentra lo que **no** pide: una conducta que ningún
|
|
@@ -308,11 +316,15 @@ function prepareProposal(root, agent, now = new Date(), period = '', kind = 'age
|
|
|
308
316
|
const proposalDir = path.join(target, 'learning', 'proposals')
|
|
309
317
|
fs.mkdirSync(proposalDir, { recursive: true })
|
|
310
318
|
|
|
311
|
-
// Una sola propuesta pendiente por período. Si la última todavía no se
|
|
319
|
+
// Una sola propuesta pendiente por período. Si la última todavía no se cerró, abrir otra partiría
|
|
312
320
|
// la firma en dos documentos que dicen cosas distintas sobre el mismo contrato.
|
|
321
|
+
//
|
|
322
|
+
// Cerrada son las dos: aplicada y archivada. Mirando sólo `applied`, una archivada —que es una decisión
|
|
323
|
+
// tomada y no espera trabajo— bloqueaba al cargo para siempre, y la única salida era aplicar algo que
|
|
324
|
+
// se había decidido no aplicar. Es el mismo criterio que `evaluate` usa para no contarla como pendiente.
|
|
313
325
|
const previous = lastOfPeriod(proposalDir, sealing)
|
|
314
|
-
const
|
|
315
|
-
if (
|
|
326
|
+
const open = previous && !CLOSED.has(proposalState(fs.readFileSync(path.join(proposalDir, previous), 'utf8')))
|
|
327
|
+
if (open) return { file: path.join(proposalDir, previous), created: false, reports: 0 }
|
|
316
328
|
|
|
317
329
|
// La misma regla que abajo, y el mismo motivo: un documento que no puede decir qué corregir no
|
|
318
330
|
// produce un cambio de contrato, y cuesta igual la firma humana que uno que sí. Antes se abría uno
|
package/engine/cli/catalog.js
CHANGED
|
@@ -242,9 +242,15 @@ function learn(agent, cli) {
|
|
|
242
242
|
if (cli.has('--archived')) {
|
|
243
243
|
const result = L.archive(opsRoot(), agent, cli.value('--period'), kind)
|
|
244
244
|
const relative = path.relative(opsRoot(), result.file)
|
|
245
|
+
// Los dos archivados no son lo mismo y el mensaje lo dice: uno es una decisión —se miró y no
|
|
246
|
+
// cambia nada— y el otro es tirar un andamio que nadie llegó a llenar. Afirmar el primero sobre
|
|
247
|
+
// el segundo le cuenta a quien archiva que hubo una revisión que no hubo.
|
|
248
|
+
const porque = result.blank
|
|
249
|
+
? 'nadie decidió el cambio y el documento quedó con el molde'
|
|
250
|
+
: 'se miró y no cambia nada'
|
|
245
251
|
return console.log(result.already
|
|
246
252
|
? `= ${relative} ya estaba archivada`
|
|
247
|
-
: `✓ ${relative} queda archivada:
|
|
253
|
+
: `✓ ${relative} queda archivada: ${porque}`)
|
|
248
254
|
}
|
|
249
255
|
if (cli.has('--applied')) {
|
|
250
256
|
const result = L.seal(opsRoot(), agent, cli.value('--period'), kind)
|
package/engine/cli/wiring.js
CHANGED
|
@@ -143,7 +143,13 @@ function scan(target, cli) {
|
|
|
143
143
|
for (const service of result.services.slice(0, MAX_LISTED)) {
|
|
144
144
|
// El proyecto que vive en la raíz se nombra por su carpeta: `.` a secas no dice de cuál se habla.
|
|
145
145
|
const label = service.path === '.' ? `. (${path.basename(service.root || result.root)})` : service.path
|
|
146
|
-
|
|
146
|
+
// La lista llega entera —quien busca credenciales la necesita así, ver `core/scan.js`— y el recorte
|
|
147
|
+
// de pantalla se aplica acá, junto al de servicios de arriba y por la misma razón.
|
|
148
|
+
const shown = service.env ? service.env.names.slice(0, service.env.names.length - service.env.truncated) : []
|
|
149
|
+
const expects = service.env
|
|
150
|
+
? `\n espera ${shown.join(', ')}${service.env.truncated ? ` y ${service.env.truncated} más` : ''} `
|
|
151
|
+
+ `(${service.env.file})`
|
|
152
|
+
: ''
|
|
147
153
|
console.log(
|
|
148
154
|
`${label} [${(service.runtimes || []).join(', ')}]${SC.commandsLine(service.commands)}${expects}`,
|
|
149
155
|
)
|
|
@@ -127,7 +127,9 @@ function orphanCredentials(root) {
|
|
|
127
127
|
for (const name of env.names || []) {
|
|
128
128
|
if (sensitiveKey(name) && !named(contracts, name)) orphans.push({ name, service: service.path })
|
|
129
129
|
}
|
|
130
|
-
|
|
130
|
+
// `names` llega entero desde el 134 —el tope recorta lo que se lista, no lo que se mira—, así que el
|
|
131
|
+
// total es su largo a secas: sumarle `truncated` contaría dos veces lo mismo.
|
|
132
|
+
if (env.truncated) cut.push(`${service.path} (${env.truncated} de ${env.names.length})`)
|
|
131
133
|
}
|
|
132
134
|
const warnings = []
|
|
133
135
|
if (orphans.length) {
|
|
@@ -141,11 +143,12 @@ function orphanCredentials(root) {
|
|
|
141
143
|
+ 'organization/workspace.md o en una fila de planning/HUMAN_ACTIONS.md; el criterio es el nombre, así '
|
|
142
144
|
+ 'que una credencial con nombre de configuración no aparece acá')
|
|
143
145
|
}
|
|
144
|
-
// El
|
|
145
|
-
//
|
|
146
|
+
// El tope recorta lo que se **lista**, no lo que se mira: las credenciales de arriba salen del ejemplo
|
|
147
|
+
// entero. Esto se sigue diciendo porque un corte que no se anuncia hace pasar lo listado por todo lo que
|
|
148
|
+
// hay —y hasta el 134 además cegaba el análisis, que es de donde viene la redacción vieja—.
|
|
146
149
|
if (cut.length) {
|
|
147
|
-
warnings.push(`
|
|
148
|
-
+
|
|
150
|
+
warnings.push(`pasado el tope de variables por servicio: ${cut.join(', ')} — el ejemplo se lista `
|
|
151
|
+
+ 'recortado; las credenciales se buscan igual sobre el archivo entero')
|
|
149
152
|
}
|
|
150
153
|
return warnings
|
|
151
154
|
}
|
package/engine/core/scan.js
CHANGED
|
@@ -107,7 +107,12 @@ function manifestsOf(dir) {
|
|
|
107
107
|
// diciendo «la credencial del proveedor» en vez de nombrarla.
|
|
108
108
|
const ENV_EXAMPLES = ['.env.example', '.env.sample', '.env.template', '.env.dist']
|
|
109
109
|
|
|
110
|
-
// Un ejemplo con cientos de variables es un archivo generado, no un contrato: se corta
|
|
110
|
+
// Un ejemplo con cientos de variables es un archivo generado, no un contrato: se corta lo que **se
|
|
111
|
+
// lista** y se dice cuánto. Lo que no se corta es lo que se **mira**: hasta el 134 este número recortaba
|
|
112
|
+
// las dos cosas, así que una credencial en la posición 41 no existía para quien busca credenciales sin
|
|
113
|
+
// dueño —medido en una instancia real: dos, sobre las 61 variables de un servicio, y el único aviso era
|
|
114
|
+
// que se había cortado—. Quién recorta para imprimir está en `cli/wiring.js`, junto al otro recorte de
|
|
115
|
+
// ese mismo comando.
|
|
111
116
|
const ENV_MAX = 40
|
|
112
117
|
|
|
113
118
|
function expectedEnv(dir) {
|
|
@@ -122,7 +127,7 @@ function expectedEnv(dir) {
|
|
|
122
127
|
.map((line) => line.replace(/^export\s+/, '').split('=')[0].trim())
|
|
123
128
|
.filter((key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
124
129
|
if (!names.length) return null
|
|
125
|
-
return { file: name, names
|
|
130
|
+
return { file: name, names, truncated: Math.max(0, names.length - ENV_MAX) }
|
|
126
131
|
}
|
|
127
132
|
return null
|
|
128
133
|
}
|
package/engine/core/trails.js
CHANGED
|
@@ -14,6 +14,20 @@ const GRANT = 'planning/.grant-log'
|
|
|
14
14
|
const LOCAL = [VERIFY, PUSH, GRANT]
|
|
15
15
|
|
|
16
16
|
const { spawnSync } = require('node:child_process')
|
|
17
|
+
const { mode } = require('./ownership')
|
|
18
|
+
|
|
19
|
+
// A quién le toca esta pregunta. Los rastros los escribe Cauce **dentro de una instancia**, así que en una
|
|
20
|
+
// raíz que no lo es no existen ni van a existir, y avisar ahí es hablar de algo imposible: el molde de este
|
|
21
|
+
// repositorio lo recibía una vez por corrida, con `check template/planning` (caso 133).
|
|
22
|
+
//
|
|
23
|
+
// Se mira el modo declarado y no el nombre de la carpeta, porque las tres formas de no ser una instancia
|
|
24
|
+
// son la misma pregunta: `toolkit` —acá se fabrica Cauce—, `{{MODE}}` sin renderizar —el molde, que es la
|
|
25
|
+
// raíz que dispara el falso positivo— y la ausencia de configuración. Un modo ilegible o fuera del enum
|
|
26
|
+
// también cae del lado silencioso, y eso no esconde nada: `check` ya los denuncia como error y sale 1.
|
|
27
|
+
const INSTANCE = new Set(['embedded', 'sidecar'])
|
|
28
|
+
const forInstance = (root) => {
|
|
29
|
+
try { return INSTANCE.has(mode(root)) } catch { return false }
|
|
30
|
+
}
|
|
17
31
|
|
|
18
32
|
// Cuáles de esos rastros **git no está ignorando** en esta instancia.
|
|
19
33
|
//
|
|
@@ -43,6 +57,7 @@ function unignored(root) {
|
|
|
43
57
|
// son exactamente lo que hay que pegar, que es lo único accionable — el archivo es de la empresa y Cauce
|
|
44
58
|
// no lo edita.
|
|
45
59
|
function warnings(root) {
|
|
60
|
+
if (!forInstance(root)) return []
|
|
46
61
|
const missing = unignored(root)
|
|
47
62
|
if (!missing.length) return []
|
|
48
63
|
return [`${missing.length} rastro(s) local(es) que git no ignora (${missing.join(', ')}); `
|