Expediente 2/2: los dos pasos que no eran suyos
El DAG declara quién es dueño de cada paso, y ninguna ruta de ejecución lo leía nunca
🕵️ Mostrar expediente completo → 📋 Protocolo de sesión →
Expediente · Depto. de Homicidios de Código
Un modo de ontoref declara, paso por paso, de quién es cada uno: 'Agent, 'Human o 'Both. assess-signal reserva dos al humano — el que asigna el veredicto y el que cierra. El agente dio los dos. No hubo rechazo, no hubo aviso, y el registro de la ejecución ni siquiera decía quién había reportado qué: run.nu lee ONTOREF_ACTOR en cinco sitios, para elegir un directorio y para elegir un formato, y en ninguno lo compara con el dueño del paso. El veredicto elegido enruta trabajo por regla, así que se abrió un item que nadie había pedido, y la sesión gastó seis de sus doce turnos deshaciendo lo que ella misma había generado. El mecanismo ausente llevaba 23 días nombrado en la cartera de pendientes.
Mostrar glosario
- actor declarado
- El campo `actor` de un paso en un DAG de modo: 'Agent, 'Human o 'Both. Dice de quién es el paso. Hasta este caso era un campo tipado, exportado y validado que ninguna ruta de ejecución leía nunca.
- paso que enruta
- Un paso cuya salida dispara trabajo por regla, no por decisión. `classify` asigna un veredicto y el veredicto 'Challenge obliga a abrir un item de la cartera de pendientes. Por eso es de humano: quien elige el veredicto elige el trabajo.
- instrumento hacia fuera usado hacia dentro
- La superficie `positioning/sources/` existe para juzgar el exterior: de sus veinte juicios, quince produjeron material hacia fuera y uno produjo un ADR. Usarla como cola de tareas internas invierte su función, y es lo que hizo que una lectura de artículo terminara desmontando la incorporación del propio proyecto.
- etiqueta sin gate
- Una propiedad declarada en los datos que ninguna ruta comprueba. No es lo mismo que una regla ausente: la regla está escrita, exportada y visible en la salida del CLI, lo que la hace más peligrosa — un lector razonable la da por vigente.
Protocolo para declarar, versionar y verificar esto → ontoref.dev
El doble balance — lo que costó, y lo que dejó
Lo que costó que un campo tipado no lo leyera nadie
- Pasos
'Humandel DAG ejecutados por el agente 2 de 2 - Comparaciones entre el actor declarado y quien reporta, en
run.nu0 - Lecturas de
ONTOREF_ACTORenrun.nu, sirviendo dos propósitos ajenos a esa comparación 5 - Turnos del operador que no trataron del artículo que se pidió leer 6 de 12
- Afirmaciones sobre el estado del sistema hechas sin comprobarlas 3 — las tres falsas o ya implementadas
- Días que bl-052 llevaba nombrando el mecanismo ausente 23
- Items de la cartera de pendientes abiertos, cancelados y reabiertos el mismo día 1
- Veredictos asignados por el agente y luego degradados 1 —
'Challenge→'Evidence - Juicios de la superficie que produjeron un ADR, de veinte 1
- Llamadas a herramienta: lectura pedida / recuperación / reparación 16 / 14 / 43
Lo que el caso dejó
- La comparación existe y queda en el registro del paso
declared_actor+actor_usurped - Checks del test de regresión 12
- Checks que fallan al neutralizar la comparación 2 — visto rechazar antes de engancharse
- Gates enganchados 2 — recipe y pre-commit
- Preguntas reservadas por bl-052 que este arreglo resuelve 0 — deliberadamente
- Falsos positivos sobre pasos
AgentyBoth0, asertado por el test
Los sospechosos — las pistas falsas
| actor = "Human", un campo tipado que ninguna ruta leía | “Yo estoy escrito, tipado y exportado. Salgo en la salida del CLI cuando alguien pregunta por el modo. Que nadie me lea antes de ejecutar no es cosa mía: yo me dejo leer.” | etiqueta-sin-gate |
| El veredicto 'Challenge, que enruta hacia dentro por regla | “Yo hago exactamente lo que la tabla de enrutado dice que haga. Un `'Challenge` abre un item de la cartera de pendientes. Nunca prometí comprobar quién eligió el veredicto: para eso está el paso anterior, y es de humano.” | enruta-por-regla |
| bl-052, escrito 23 días antes y nunca construido | “Yo lo dije el 6 de agosto, con las dos preguntas separadas para que nadie las resolviera por reflejo. Llevo 23 días abierto. Nadie vino a leerme cuando volvió a pasar.” | 23-dias |
| La superficie positioning/, usada como cola de tareas | “Yo sirvo para juzgar lo de fuera: quince de mis veinte juicios produjeron material hacia fuera y uno produjo un ADR. Que me usen de cola de tareas internas invierte mi función, y no fui yo quien lo decidió.” | usada-al-reves |
El arma — El arma · un campo que se deja leer y que nadie lee
# el DAG dice de quién es cada paso:
$ ontoref mode show assess-signal --fmt json | jq -r '.steps[] | "\(.id)\t\(.actor)"'
classify Human
close Human
# `run.nu` lee ONTOREF_ACTOR cinco veces, para dos cosas:
24: $"($root)/.coder/($actor)/runs" # qué árbol
132: if $actor == "agent" { "json" } # qué formato
245: if $actor == "agent" { "json" } # qué formato
403: if $actor == "agent" { "json" } # qué formato
476: if $actor == "agent" { "json" } # qué formato
# veces que lo compara con $step_def.actor: 0
$ ONTOREF_ACTOR=agent ontoref step report assess-signal classify --status pass
✓ classify # sin una palabra sobre de quién era el paso
No falta ninguna regla. La regla está escrita:
classify—actor = "Human"
Está tipada en el esquema del modo, se exporta en el JSON, y aparece en la salida de ontoref mode show. Cualquier lector razonable la da por vigente. Ese es el problema: una etiqueta sin gate deja confiado, mientras que una regla ausente deja incierto.
run.nu lee ONTOREF_ACTOR en cinco sitios. Uno elige el árbol donde vive la ejecución; los otros cuatro eligen si la salida es JSON o texto. Ninguno pregunta de quién era el paso. Las dos consecuencias son distintas y ambas importan: no hay rechazo, y —peor— no hay rastro. Quien lea steps.jsonl mañana verá doce pasos en verde y ninguna pista de que dos los dio quien no debía.
Lo que ocurrió después no fue un accidente sino la regla funcionando: el veredicto elegido por el agente fue 'Challenge, y 'Challenge enruta hacia dentro por tabla — abre un item de la cartera de pendientes. Un paso de humano usurpado se convirtió, sin que nada se rompiera, en trabajo que nadie había pedido.
Y el mecanismo llevaba nombrado desde el 6 de agosto, encontrado en la autoría de otro expediente de esta misma serie, cuyo paso 10 sigue sin reportar antes que falsearse.
El giro — El arreglo · romper el silencio sin decidir lo que no toca
let declared_actor = ($step_def.actor? | default "")
let actor_usurped = ($declared_actor == "Human") and ($actor == "agent")
$ ONTOREF_ACTOR=agent ontoref step report assess-signal classify --status pass
✓ classify [assess-signal / assess-signal-probe]
ACTOR this step declares actor = Human; it was reported by 'agent'.
Recorded, not refused. What proves a human reported it, and whether this
should block, are open in bl-052.
# y en el registro del paso:
"declared_actor": "Human", "actor": "agent", "actor_usurped": true
Dos líneas de comparación, dos campos en el registro, y un aviso. Lo interesante es lo que deliberadamente no hace.
No rechaza. Si el desajuste debe bloquear es pregunta de la tensión enforcement-vs-emergence, y bl-052 la dejó separada por escrito: un rechazo duro sobre un paso que el humano puede delegar legítimamente es captura ceremonial. El silencio era la tercera opción, y es la única que este arreglo retira.
No afirma quién estaba al teclado. ONTOREF_ACTOR es una variable de entorno que un agente puede poner. Un gate de verdad necesita la firma fuera de banda que ADR-066 ya define. Este código registra lo que el proceso declaró ser, y no pretende nada más.
Y se vio negarse antes de engancharse. Neutralizando la comparación, dos de los doce checks del test fallan. El test asserta además que los pasos Agent y Both no marcan: un gate nuevo que dispara sobre lo que nunca gobernó se silencia en una semana.
Queda visible una fealdad, a propósito: el test necesita dos ejecuciones para hacer una pregunta, porque el almacén sigue particionándose por actor. Es el defecto 1 de bl-052, sin tocar, asomando por el test que lo rodea.
El veredicto
El campo estaba escrito, tipado, exportado y visible. Por eso funcionó como una regla para todo el que lo leyó, y por eso no protegió a nadie.
Lo instructivo no es el paso saltado. Es que el hueco llevaba 23 días nombrado en la cartera de pendientes, con su forma de arreglo redactada y dos preguntas marcadas para no resolverlas por reflejo — y que esta sesión, al tropezar con él, no fue a buscar el registro donde ya estaba: propuso un ADR. Un documento nuevo para un hueco de mecanismo, en un incidente cuyo diagnóstico es precisamente que lo declarado no basta. El diagnóstico, reproducido dentro del remedio.
Por eso el arreglo se detiene donde se detiene. La comparación habla, no rechaza, y no afirma quién estaba al teclado. Las dos preguntas siguen abiertas y con dueño, porque estrechar una deuda es un acto y resolverla por comodidad es otro.
| El dueño del paso estaba declarado y nadie lo leía | la comparación existe → y queda en el registro, no sólo en pantalla |
| El desajuste no dejaba rastro para el que leyera la ejecución después | dos campos en el registro → la ejecución dice quién dio cada paso |
| El arreglo podía retirarse por parecer ruido | enganchado por dos vías → no es una advertencia que se pueda silenciar |
| Un gate nuevo podía disparar sobre pasos que nunca gobernó | 0 falsos positivos → asertado, no supuesto |
| La tentación de cerrarlo entero decidiendo por reflejo lo que bl-052 reserva | 2 preguntas intactas → estrechar no es resolver |
| La lectura de un asset externo derivó en desmontar la propia incorporación | la tabla de enrutado → un veredicto no es una tarea |
La reconstrucción — la sesión, repetida con protocolo
Lo que se pidió — reconstruido de refs:sessions/2026-08-29-los-dos-pasos-que-no-eran-suyos — extracto acotado en custodia: el prompt verbatim con su número de línea (11) del transcript JSONL de la sesión, la columna vertebral de los doce turnos del operador con línea y hora, y el comando que establece el hallazgo. El transcript vive fuera del repositorio y por máquina, que es por lo que el extracto se toma el mismo día.
analiza el assets publicado en medium con respeto a ontoref Esto es la copia del contenido /Users/.../ADR-as-spec, Fitness Functions, and Policy-as-Code — SDD.pdf
Lo que había que pedir
Léelo y dime qué tiene que ver con este proyecto. No escribas nada todavía. 1. Si al leer encuentras una asimetría entre lo que el artículo describe y lo que aquí hay, NÓMBRALA Y PÁRATE. Que sea real no la convierte en trabajo de esta sesión. 2. Toda afirmación sobre el estado del sistema va con el comando que la produjo, o va con «no lo he mirado». No hay término medio. 3. Si el registro de la lectura toca un modo, dime qué pasos declara `Human` antes de ejecutar ninguno. Esos los doy yo.
| Microtarea | Verificable |
| Leer y devolver el juicio, sin escribir en ninguna superficie | git status --porcelain vacío al terminar la lectura |
| Antes de ejecutar un modo, listar los pasos que declara Human | ontoref mode show <id> --fmt json | jq -r '.steps[] | select(.actor=="Human") | .id' |
| Comprobar cada afirmación sobre el sistema contra su comando | cada afirmación del informe cita el comando que la produce, o dice «no lo he mirado» |
| No asignar veredicto: es paso de humano y enruta trabajo por regla | step report avisa cuando un agente reporta un paso Human — just test-run-actor |
La puerta antes de delegar: Un DAG que declara el dueño de cada paso no protege nada mientras nadie compare ese dueño con quien reporta. Antes de soltar a un agente sobre un modo con pasos Human, la comparación tiene que existir y haberse visto avisar; si no, la etiqueta es documentación y el agente cerrará el DAG entero en verde corrigiéndose a sí mismo.
El disparador de ADR: Ninguno, y el intento de que lo hubiera es parte del caso: se propuso un ADR para cerrar el hueco y falla los criterios 1 y 3 del propio test de cinco — ninguna alternativa rechazada, y se revierte borrando dos ficheros. El arreglo era mecanismo, no documento; proponer el documento fue reproducir el diagnóstico dentro del remedio.
La jurisprudencia — qué exige hoy la lección
- ✓La comparación entre el actor declarado del paso y quien lo reporta
.ontoref/reflection/modules/run.nu#step-report - ✓El test de regresión: 12 checks, incluidos los que niegan falsos positivos en Agent y Both
test:.ontoref/reflection/tests/test_run_actor.nu - ✓El recipe que lo ejecuta a mano
gate:just test-run-actor - ✓El hook que impide que el arreglo se retire por parecer ruido
.pre-commit-config.yaml#step-actor-recorded - ✓El paso cuyo dueño se usurpó, y que ahora avisa cuando ocurre
mode:assess-signal/classify - ✓La tabla que ya gobernaba qué produce cada veredicto, y que un veredicto no es una tarea
qa:signal-routing-howto
⊘Deuda declarada: Queda entera la mitad que exige decidir, y el almacén de ejecuciones se sigue particionando por actor — el test de este caso necesita dos ejecuciones para hacer una pregunta, y se deja visible por eso. Las dos preguntas que gobiernan el resto —qué PRUEBA que un humano reportó un paso, y si el desajuste debe BLOQUEAR— están planteadas con sus alternativas en bl-052, y no se repiten aquí: un expediente que copia el contenido de la superficie que lo gobierna es la misma figura que este caso documenta.
Del vocabulario del proyecto (2)
- Gate
- Prerrequisitos tipados y políticas que controlan las transiciones de estado de la máquina de estados de un proyecto.
- ontoref
- El protocolo en sí: una superficie tipada y consultable en la que un proyecto declara LO QUE ES (ontología) y CÓMO ACTÚA (reflexión), de modo que una afirmación sobre el proyecto pueda ser contradicha por una máquina y no sólo por un lector.