Oroboro Labs
field notes

The gate that fired anyway

2026-09-05 · field note #108 · E-124

The effect classifier described in the previous note was still a manual step — a discipline of the operator, not a wall. So the next move was obvious: wire it into the acceptance runner itself, before the Start. Destructive verdict means the task is never fired on purpose; acceptance waits for the job's own scheduled window. The runner even got its own escape hatch, a documented override flag, for the rare legitimate case. Then came the first kill-criteria test: register a throwaway task whose script body is a single Remove-Item against a phantom file, and check that the wall holds.

It didn't. The dummy destructive task was registered, classified — and fired. LastTaskResult 0x0, verdict accepted. The wall had watched a destructive task go through and called it fine.

An unparseable verdict is not permission

The failure was one line long. The runner captured the classifier's output with 2>&1, which in PowerShell returns an array of lines, not a string. Matching a regex against an array filters the array; it never populates $Matches. So the extraction of the verdict found nothing, the classification variable kept its default — INDETERMINADA — and the one branch the runner actually implemented, destructive blocks, simply didn't apply. Unknown sailed through as go.

The default was the defect. Every gate has a fall-through case — the classifier hangs, the output is in an unexpected shape, the regex drifts after an edit. What the task does in that moment is the entire security posture of the gate. The fix was two changes: join the captured output into a string so the regex sees what it was written for, and give unknown its own blocking exit code, so an unparseable verdict can never be mistaken for an absence of danger. The override flag still exists, but now it has to be said out loud, and it's stamped into the receipt when used.

Red first, then green

Re-running the red test after the fix: the destructive dummy is registered, the classifier reads the script body, the verdict comes back destructive, verb Remove-Item, the runner writes a receipt with the classification and the classifier's own receipt path, refuses to call Start, exits 3. A read-only dummy — a task that counts scheduled tasks and prints a line — goes the other way: classified, fired once, 0x0, accepted, exit 0. Both throwaway tasks were then unregistered; the count of leftovers is zero, and that count is in a receipt too, because a test that cleans up after itself silently is a test nobody can audit.

The quiet lesson is about promotion. The classifier worked as a manual step, and wiring it in was supposed to add a wall, not a habit. But the integration layer — the seams where two tools exchange a string — is where working components meet: an array where a string was expected, and a default that chose action over restraint. Every gate you promote into a pipeline deserves the red test before the green one, because the red test is the one that tells you whether you built a wall or a recommendation.

Prova: radares\e124-aceite-20260905-*.txt (4 recibos atômicos) · verificação: radares\e124-verificacao-j149.txt · runner: nave\e121-registra-tarefa.ps1

Read before or after: The classifier that hadn't read the script ; and The task that trusted the PATH.

Oroboro Labs is a one-person automation workshop publishing its own field notes. Numbers here come from receipts on disk, named above.

← The classifier that hadn't read the script Work with the workshop