Skip to content

feat(guard): un except que escribe un código constante debe guardar la causa - #245

Merged
wolverin0 merged 1 commit into
mainfrom
feat/lint-swallowed-cause
Aug 30, 2026
Merged

feat(guard): un except que escribe un código constante debe guardar la causa#245
wolverin0 merged 1 commit into
mainfrom
feat/lint-swallowed-cause

Conversation

@wolverin0

Copy link
Copy Markdown
Owner

Tercera aparición del mismo defecto

Y las tres costaron días:

# Dónde Qué se perdió
1 profile/engine.py Registraba AntigravityError —un nombre de proveedor— cuando el fallo real era ProfileValidationError: profile candidates must appear exactly once. Diez días apuntando a una caída de proveedor que no existía.
2 graph_observation_engine.py except Exception: sin capturar, error_code="synthesis_failed" fijo. Cinco intentos, cinco veces la misma palabra, cero información.
3 Migración 0022 Su propio docstring: "el único rastro de por qué era un sha256 de los códigos de diagnóstico — la razón se destruía al escribir".

Una corrección que recurre tres veces tiene que volverse algo que falle solo, no otra línea de prosa.

El guard

scripts/check_swallowed_cause.py camina el AST: si dentro de un handler se pasa un string literal a un parámetro que nombra la causa (error_code, error, reason, outcome), el handler tiene que ligar la excepción y usarla. Escape declarado: # swallow-ok: <motivo>.

Calibración — lo que decide si un guard sobrevive: 4 hallazgos en todo el repo, los 4 genuinos. Incluida discovery_failed, hermana de la que ya conocíamos y que nadie había visto. Un guard que inunda entrena a apaciguarlo, y termina desactivado.

Arreglado de raíz

fail_job no tenía dónde poner el detalle — esa era la falta estructural. Ahora acepta detail y lo escribe en diagnostic_codes, la columna de texto legible que la migración 0022 agregó exactamente para esto, truncada a 500 chars (esta tabla no es un log).

  • Los dos handlers de PPR-7 pasan f"{type(exc).__name__}: {exc}".
  • Los dos de los hooks usan el escape con motivo: son quiet-by-contract y capture_usage.outcome es vocabulario de contabilidad ('ok'/'error'), no un campo de diagnóstico. Guardar la causa ahí pide una columna en otro subsistema. Deuda anotada, no escondida.

Barra roja contra historia real

No casos sintéticos — el guard corrido sobre la versión de main:

graph_observation_engine.py@main:221: error_code='discovery_failed'
graph_observation_engine.py@main:278: error_code='synthesis_failed'

Sobre la versión arreglada: 0.

Tests

11 del propio guard: 3 de alarma y 7 de SILENCIO — porque lo que lo hace sostenible es no ladrar ante código que ya cumple (liga y usa la excepción, re-lanza, valor derivado, palabras parecidas como status/code que quedan fuera a propósito). Más test_el_repo_pasa_el_guard, que lo corre contra el repo real: si se pone rojo, o hay un handler nuevo que tira la causa o el guard se volvió ancho, y las dos merecen mirarse.

185/185 en observaciones y dreaming, ruff limpio.

@wolverin0
wolverin0 force-pushed the feat/lint-swallowed-cause branch from c99acf3 to 9417c63 Compare August 30, 2026 17:55
…a causa

Tercera aparicion del mismo defecto en este repo, y las tres costaron dias:

1. `profile/engine.py` registraba `AntigravityError` —un nombre de proveedor—
   cuando el fallo real era `ProfileValidationError: profile candidates must
   appear exactly once`. Diez dias apuntando a una caida de proveedor que no
   existia; se vio recien al agregar un campo `detail`.
2. `graph_observation_engine.py` tenia `except Exception:` sin capturar y
   escribia `error_code="synthesis_failed"` fijo. Cinco intentos dejaron cinco
   veces la misma palabra y cero informacion.
3. La migracion 0022 documenta lo mismo para `outcome`: "el unico rastro de por
   que era un sha256 de los codigos de diagnostico — la razon se destruia al
   escribir".

Una correccion que recurre tres veces tiene que volverse algo que falle solo, no
otra linea de prosa. `scripts/check_swallowed_cause.py` camina el AST: si dentro
de un handler se pasa un STRING LITERAL a un parametro que nombra la causa
(error_code, error, reason, outcome), el handler tiene que ligar la excepcion y
usarla. Escape declarado: `# swallow-ok: <motivo>`.

Calibracion, que es lo que decide si un guard sobrevive: 4 hallazgos en todo el
repo, los 4 genuinos — incluida `discovery_failed`, hermana de la que ya
conociamos y que nadie habia visto. Un guard que inunda entrena a apaciguarlo.

Arreglado de raiz: `fail_job` no tenia DONDE poner el detalle. Ahora acepta
`detail` y lo escribe en `diagnostic_codes`, la columna de texto legible que la
migracion 0022 agrego exactamente para esto, truncada a 500 chars.

Los dos handlers de PPR-7 pasan `f"{type(exc).__name__}: {exc}"`. Los dos de los
hooks usan el escape con motivo: son quiet-by-contract y `capture_usage.outcome`
es vocabulario de contabilidad ('ok'/'error'), no un campo de diagnostico —
guardar la causa ahi pide una columna en otro subsistema. Deuda anotada, no
escondida.

BARRA ROJA CONTRA HISTORIA REAL, no casos sinteticos: el guard corrido sobre
`graph_observation_engine.py` en main detecta las 2 violaciones; sobre la version
arreglada, 0.

11 tests del propio guard — 3 de alarma y 7 de SILENCIO, porque lo que lo hace
sostenible es no ladrar ante codigo que ya cumple — mas uno que lo corre contra
el repo real. 185/185 en observaciones y dreaming, ruff limpio.
@wolverin0
wolverin0 force-pushed the feat/lint-swallowed-cause branch from 9417c63 to 26fe0b7 Compare August 30, 2026 18:44
@wolverin0
wolverin0 merged commit f74297f into main Aug 30, 2026
15 checks passed
@wolverin0
wolverin0 deleted the feat/lint-swallowed-cause branch August 30, 2026 19:34
wolverin0 added a commit that referenced this pull request Aug 30, 2026


15339 -> 15421 simbolos, 39065 -> 39209 relaciones. Regenerado con
--embeddings para no perder los 12.074 existentes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant