@ingeniomaps/cauce 0.63.0 → 0.64.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 +23 -0
- package/engine/cli/planning.js +9 -0
- package/engine/hooks/approval.js +31 -0
- package/engine/hooks/shell.js +15 -7
- package/package.json +1 -1
- package/template/AGENTS.md +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,29 @@ 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.64.0] - 2026-09-06
|
|
18
|
+
|
|
19
|
+
### Agregado
|
|
20
|
+
|
|
21
|
+
- **Las salidas de los guards están documentadas, y con su alcance real.** Cinco guards se pueden abrir
|
|
22
|
+
y ninguna de las cinco llaves estaba escrita en un archivo que alguien fuera a abrir: el bloqueo te
|
|
23
|
+
nombraba una variable y no había dónde leer dónde va. `AGENTS.md` ahora las lista en «Qué se puede
|
|
24
|
+
editar y qué no», con lo incómodo dicho: el guard lee la variable de **su propio proceso**, así que
|
|
25
|
+
escribirla delante del comando no llega, y la forma que sí funciona —exportarla en el entorno desde
|
|
26
|
+
el que arranca tu runner— deja el guard apagado hasta que cierres la sesión. La única con alcance de
|
|
27
|
+
operación es la aprobación de gobernanza.
|
|
28
|
+
|
|
29
|
+
- **Un commit de gobernanza se aprueba por operación, no por sesión.** El guard ofrecía como salida
|
|
30
|
+
`OPS_GOVERNANCE_OVERRIDE=1`, que se lee del entorno del proceso: prendida antes de lanzar tu runner
|
|
31
|
+
deja el guard apagado hasta que la sesión cierre. Eso convierte «aprobado este commit» en «apagado
|
|
32
|
+
hasta que me vaya», y el `Makefile` de este proyecto ya decía cuál es el alcance correcto — «la
|
|
33
|
+
autorización de R10 es por operación y humana». **Qué cambia para vos**: escribís las rutas
|
|
34
|
+
autorizadas en `planning/.governance-approval`, una por línea, y el commit pasa. Vale para ese
|
|
35
|
+
conjunto y para ningún otro: si después sumás un archivo, ese archivo no está aprobado. No se consume
|
|
36
|
+
ni se borra sola —así un commit frenado por otra razón no te obliga a rehacerla—, así que `check` te
|
|
37
|
+
avisa mientras exista para que la borres. La variable sigue funcionando y el mensaje del guard ahora
|
|
38
|
+
dice por qué no es la vía recomendada.
|
|
39
|
+
|
|
17
40
|
## [0.63.0] - 2026-09-06
|
|
18
41
|
|
|
19
42
|
### Corregido
|
package/engine/cli/planning.js
CHANGED
|
@@ -11,6 +11,7 @@ const PC = require('../planning/contracts')
|
|
|
11
11
|
const SZ = require('../planning/sizing')
|
|
12
12
|
const ST = require('../planning/state')
|
|
13
13
|
const AD = require('../planning/adoption')
|
|
14
|
+
const AP = require('../hooks/approval')
|
|
14
15
|
const I = require('../integrations/registry')
|
|
15
16
|
const O = require('../core/ownership')
|
|
16
17
|
const OB = require('../core/onboarding')
|
|
@@ -85,6 +86,14 @@ function check(dir, cli) {
|
|
|
85
86
|
epics, milestones, done, wip, roles, humanActions: P.readHumanActions(root), adopted: new Set(adopted),
|
|
86
87
|
}))
|
|
87
88
|
warnings.push(...AD.report({ done, epics, adopted }))
|
|
89
|
+
// Una aprobación de gobernanza vale para el conjunto que nombra, así que olvidada sigue autorizando
|
|
90
|
+
// esas mismas rutas la próxima vez que alguien las stagee. No caduca sola: lo que la cierra es que se
|
|
91
|
+
// vea en cada corrida y alguien la borre.
|
|
92
|
+
const aprobadas = AP.read(path.resolve(root, '..'))
|
|
93
|
+
if (aprobadas.length) {
|
|
94
|
+
warnings.push(`planning/${AP.APPROVAL}: ${aprobadas.length} ruta(s) de gobernanza aprobadas y sin `
|
|
95
|
+
+ 'borrar; el archivo sigue autorizándolas')
|
|
96
|
+
}
|
|
88
97
|
|
|
89
98
|
const integration = I.validate(path.resolve(root, '..'))
|
|
90
99
|
errors.push(...integration.errors)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// La aprobación de un commit de gobernanza: una lista de rutas que una persona escribió a mano para
|
|
4
|
+
// autorizar exactamente ese cambio. Existe porque la vía que había era una variable de entorno, y una
|
|
5
|
+
// variable es por sesión: prendida antes de lanzar el runner deja el guard apagado hasta que la sesión
|
|
6
|
+
// cierre. El `Makefile` de este repositorio ya dice cuál es el alcance correcto —«la autorización de R10
|
|
7
|
+
// es por operación y humana»—, y una llave que dura toda la sesión no lo es.
|
|
8
|
+
//
|
|
9
|
+
// **Se coteja, no se consume.** Borrar el archivo al leerlo daría el mismo alcance y traería dos cosas
|
|
10
|
+
// que no queremos: hoy ningún guard escribe en el repositorio, y `governance` corre antes que `verify`,
|
|
11
|
+
// así que un commit frenado por otra razón se habría llevado puesta la aprobación y habría que
|
|
12
|
+
// rehacerla. Cotejando, la aprobación vale para el conjunto que nombra y para ningún otro: en cuanto
|
|
13
|
+
// cambia lo que está en el índice deja de servir, que es «por operación» sin fecha ni contador.
|
|
14
|
+
//
|
|
15
|
+
// Queda a la vista porque `check` avisa mientras exista. Sin eso, un archivo olvidado sigue autorizando
|
|
16
|
+
// esas mismas rutas la próxima vez que alguien las stagee, que es la puerta abierta que esto evitaba.
|
|
17
|
+
|
|
18
|
+
const path = require('node:path')
|
|
19
|
+
const fs = require('node:fs')
|
|
20
|
+
|
|
21
|
+
const APPROVAL = '.governance-approval'
|
|
22
|
+
|
|
23
|
+
// Una ruta por línea, `#` para lo demás. El archivo ausente y el vacío son lo mismo: no hay nada
|
|
24
|
+
// aprobado, que es el estado normal.
|
|
25
|
+
function read(root) {
|
|
26
|
+
let text = ''
|
|
27
|
+
try { text = fs.readFileSync(path.join(root, 'planning', APPROVAL), 'utf8') } catch { return [] }
|
|
28
|
+
return text.split('\n').map((line) => line.replace(/#.*$/, '').trim()).filter(Boolean)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { APPROVAL, read }
|
package/engine/hooks/shell.js
CHANGED
|
@@ -10,8 +10,9 @@ const path = require('node:path')
|
|
|
10
10
|
const { spawnSync } = require('node:child_process')
|
|
11
11
|
const {
|
|
12
12
|
commandOf, cwdOf, block, gitDirectory, isCommit, stagedFiles, pushAllowed,
|
|
13
|
-
writableRoots, outsideRoots, DECLARE_IT, unquoted,
|
|
13
|
+
writableRoots, outsideRoots, DECLARE_IT, unquoted, findOpsRoot,
|
|
14
14
|
} = require('./input')
|
|
15
|
+
const AP = require('./approval')
|
|
15
16
|
|
|
16
17
|
// Dónde empieza y dónde termina una palabra dentro de un comando. Tres reglas de la tabla de abajo lo
|
|
17
18
|
// decidían por su cuenta admitiendo sólo un espacio, el principio o el fin, y en un shell una palabra
|
|
@@ -250,12 +251,19 @@ function governance(input) {
|
|
|
250
251
|
String.raw`|evaluations\/(?:cases\/|expected-behaviors\.yaml)|learning\/proposals\/))`,
|
|
251
252
|
)
|
|
252
253
|
const governed = stagedFiles(dir).filter((file) => governedPattern.test(file))
|
|
253
|
-
if (governed.length)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
if (!governed.length) return
|
|
255
|
+
// La aprobación vale para lo que nombra y para nada más: lo que quede sin cubrir es lo que se
|
|
256
|
+
// reporta. Así una aprobación vieja no autoriza el archivo que se sumó después, que es la diferencia
|
|
257
|
+
// entre una llave por operación y una puerta que quedó abierta.
|
|
258
|
+
const root = findOpsRoot(process.env.OPS_ROOT || process.env.CLAUDE_PROJECT_DIR || cwdOf(input))
|
|
259
|
+
const aprobados = new Set(root ? AP.read(root) : [])
|
|
260
|
+
const pendientes = governed.filter((file) => !aprobados.has(file))
|
|
261
|
+
if (!pendientes.length) return
|
|
262
|
+
const files = pendientes.map((file) => ` - ${file}`).join('\n')
|
|
263
|
+
block(`El commit toca gobernanza protegida:\n${files}\n`
|
|
264
|
+
+ `Aprobalo escribiendo esas rutas en planning/${AP.APPROVAL}, una por línea: vale para ese `
|
|
265
|
+
+ 'conjunto y deja de valer en cuanto cambie. La variable OPS_GOVERNANCE_OVERRIDE=1 sigue '
|
|
266
|
+
+ 'existiendo y apaga el guard para toda la sesión, que es por lo que no es la vía recomendada.')
|
|
259
267
|
}
|
|
260
268
|
|
|
261
269
|
function run(program, args, cwd) {
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -98,6 +98,34 @@ siquiera están acá — los lee el motor desde el paquete.
|
|
|
98
98
|
Un guard existente **no se edita**: `upgrade` detecta el cambio y se detiene antes de pisarlo, y con
|
|
99
99
|
`--force` deja registrado qué descartó.
|
|
100
100
|
|
|
101
|
+
### Cuando un guard te frena con razón
|
|
102
|
+
|
|
103
|
+
Algunos bloqueos tienen salida, y conviene saber cuál antes de necesitarla — el momento en que un guard
|
|
104
|
+
te frena es el peor para elegir bien.
|
|
105
|
+
|
|
106
|
+
**Un commit que toca gobernanza** —reglas, ADRs, el contrato de un cargo o lo que lo mide— se autoriza
|
|
107
|
+
escribiendo las rutas en `planning/.governance-approval`, una por línea, con `#` para lo que no sea una
|
|
108
|
+
ruta. Vale para ese conjunto y para ningún otro: si después sumás un archivo, ese archivo no está
|
|
109
|
+
aprobado y el guard lo nombra. No se borra sola —así un commit frenado por otra cosa no te obliga a
|
|
110
|
+
rehacerla—, así que `check` te avisa mientras exista, y borrarla es parte de terminar.
|
|
111
|
+
|
|
112
|
+
**Las demás salidas son variables de entorno**, y hay que decir su alcance
|
|
113
|
+
porque no es el que uno espera: el guard la lee de **su propio proceso**, no del comando. Escribirla
|
|
114
|
+
delante —`VAR=1 git commit`— no llega. La forma que sí funciona es exportarla en el entorno desde el que
|
|
115
|
+
arranca tu runner, y eso deja el guard apagado **hasta que cierres la sesión**, no para un comando.
|
|
116
|
+
|
|
117
|
+
| variable | qué abre |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `OPS_GOVERNANCE_OVERRIDE=1` | lo mismo que la aprobación de arriba, pero para toda la sesión |
|
|
120
|
+
| `OPS_MIGRATIONS_OVERRIDE=1` | escribir SQL destructivo en una migración |
|
|
121
|
+
| `OPS_TEST_EVIDENCE_OVERRIDE=1` | borrar o apagar una prueba |
|
|
122
|
+
| `OPS_DEPENDENCIES_OVERRIDE=1` | tocar manifiestos y lockfiles, publicar o instalar global |
|
|
123
|
+
| `OPS_SKIP_VERIFY=1` | saltear los gates del stack antes de un commit |
|
|
124
|
+
|
|
125
|
+
Son de sesión y no de operación, que es exactamente lo que la aprobación de gobernanza vino a corregir.
|
|
126
|
+
Mientras sigan así, lo que corresponde es prenderlas para lo que hacía falta y apagarlas después — y que
|
|
127
|
+
la razón quede escrita donde alguien la lea, no sólo en la memoria de quien la prendió.
|
|
128
|
+
|
|
101
129
|
## Cómo leer el estado
|
|
102
130
|
|
|
103
131
|
Antes de abrir un archivo de `planning/`, preguntarle al CLI: es determinista, no gasta contexto y no
|