@ingeniomaps/cauce 0.65.0 → 0.66.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 CHANGED
@@ -14,6 +14,23 @@ 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.66.0] - 2026-09-07
18
+
19
+ ### Corregido
20
+
21
+ - **`shell-boundary` ignoraba el `cd` del propio comando.** Resolvía las rutas relativas contra el
22
+ directorio que le entrega tu runner, no contra el que el comando elige antes de escribir, así que
23
+ juzgaba una ruta que nadie iba a escribir. Fallaba para los dos lados: `cd <fuera de tus raíces> &&
24
+ echo x > nota.md` pasaba sin decir nada y el archivo se escribía afuera; y con el runner abierto en
25
+ otro directorio, un `cd` a un lugar legítimo se frenaba nombrando una ruta que no estaba en el
26
+ comando.
27
+
28
+ Ahora cada escritura se juzga contra el `cd` que la precede. **Lo que te pide algo**: un `cd` cuyo
29
+ destino no se puede saber acá —`cd $TRABAJO`, `cd -`— deja sin juzgar a toda ruta relativa que venga
30
+ después, y eso se bloquea pidiendo la ruta absoluta o el `cd` en un comando aparte. Es deliberado y
31
+ es el criterio que ya regía para el índice: un guard que no puede verificar no autoriza. Una ruta
32
+ absoluta no depende del `cd` y se sigue juzgando igual.
33
+
17
34
  ## [0.65.0] - 2026-09-07
18
35
 
19
36
  ### Cambiado
@@ -264,11 +264,47 @@ function writeTargets(command) {
264
264
  // el guard frena `> /dev/null 2>&1`, y lo primero que hace quien lo sufre es apagarlo entero.
265
265
  const NEUTRAL = [/^\/dev\/(?:null|stdout|stderr|tty|fd\/)/, new RegExp(`^${os.tmpdir()}(?:/|$)`)]
266
266
 
267
+ // A dónde deja parado un `cd`. `null` significa que no se sabe, que no es lo mismo que la raíz: un
268
+ // destino con variable o un `cd -` dependen de un estado que este proceso no tiene.
269
+ function cdTarget(argument, base) {
270
+ if (argument === undefined) return os.homedir()
271
+ if (argument === '-' || /[$`\u0000]/.test(argument)) return null
272
+ if (/^~(?=$|\/)/.test(argument)) return argument.replace(/^~/, os.homedir())
273
+ return path.resolve(base, argument)
274
+ }
275
+
276
+ // Las escrituras de un comando, cada una con el directorio contra el que hay que resolverla. El `cd`
277
+ // del propio comando cambia eso para todo lo que viene después y es lo primero que el shell ejecuta;
278
+ // sin mirarlo, el guard juzgaba una ruta que nadie iba a escribir, y fallaba en los dos sentidos.
279
+ //
280
+ // Se recorre por tramos y se lleva la cuenta, en vez de mirar sólo el primero como hace `gitDirectory`.
281
+ // Ahí alcanza porque un comando elige un repositorio; acá cada escritura puede caer bajo un `cd`
282
+ // distinto, y juzgar la primera contra el último sería cambiar un error de lugar en vez de arreglarlo.
283
+ //
284
+ // Los tramos salen del texto ya sin comillas, así que un `;` adentro de una cadena no parte nada.
285
+ function writesWithBase(command, cwd) {
286
+ const found = []
287
+ let base = cwd
288
+ for (const segment of unquoted(command).split(/[;&|\n]+/)) {
289
+ const cd = segment.match(/^\s*cd(?:\s+(\S+))?\s*$/)
290
+ if (cd) { base = base === null ? null : cdTarget(cd[1], base); continue }
291
+ for (const raw of writeTargets(segment)) found.push({ raw, base })
292
+ }
293
+ return found
294
+ }
295
+
267
296
  function shellBoundary(input) {
268
297
  const allowed = writableRoots(input)
269
298
  if (!allowed) return
270
- for (const raw of writeTargets(commandOf(input))) {
271
- const file = path.resolve(cwdOf(input), raw)
299
+ for (const { raw, base } of writesWithBase(commandOf(input), cwdOf(input))) {
300
+ // Una ruta absoluta no depende del `cd`, así que un destino que no se sabe no la vuelve injuzgable.
301
+ // Al revés sí: sin saber desde dónde se resuelve, una relativa no se puede verificar, y un guard que
302
+ // no puede verificar no autoriza —el criterio que fijó el 031 para el índice—.
303
+ if (!path.isAbsolute(raw) && base === null) {
304
+ block(`el comando hace \`cd\` a un destino que no se puede resolver acá, así que no hay contra qué `
305
+ + `resolver ${raw}. Escribí la ruta absoluta, o hacé el \`cd\` en un comando aparte.`)
306
+ }
307
+ const file = path.resolve(base, raw)
272
308
  if (NEUTRAL.some((pattern) => pattern.test(file))) continue
273
309
  if (outsideRoots(file, allowed)) {
274
310
  block(`el comando escribe en ${file}, fuera de las raíces declaradas en ops.config.json. ${DECLARE_IT}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.65.0",
3
+ "version": "0.66.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",