runward

RW™ · V0.26.0

Two rules violated, the gate green.

A field report exposed a limit the gate had never declared: a green row does not travel forward in time. It declares it now — and a command finally answers "which rules govern this file?", showing the pattern that retained each one.

A field report, from a mission built with runward: two written craft rules, violated in one afternoon, on code the agent had just written itself. runward check --strict green from start to finish. One HIGH rule on background-job guardrails, one CRITICAL on the secrets boundary.

The gate behaved exactly as it declared. The problem is that the declaration was incomplete.

What a green row never proved

Since ADR-0040, every verdict travels with its declared blind zone: a green row proves a decision was traced to evidence that resolves, never that the evidence implements the rule. The gate reads bytes at rest; it executes nothing and judges no semantics. That was written down, and it is a credibility asset, not a gap to hide.

What was not written down: a green row does not travel forward in time. The operator's judgment was about the code that existed when the row was written. Every run re-verifies that cited pointers still resolve and that sealed evidence has not drifted — but nothing re-judges code added later under an already-accounted-for rule. By ADR-0040's own standard, "every gate names what it cannot verify", that was an undeclared blind zone. It is declared now.

The question you could not ask

The neutral baseline written at init prescribes confronting the craft rules "at the point of action, not from memory". Nothing made that sentence mechanisable: rules filtered by phase, and the govern phase alone returns twelve. At the point of action, the instruction resolved to "re-read all 64 and guess".

runward rules --for <paths> answers, and renders the pattern that retained each rule — on the git check-ignore -v model, twenty years old and built for exactly this:

async-job-guardrails      HIGH      appliesTo=**/cron/**    src/cron/runner.ts
config-secrets-boundary   CRITICAL  appliesTo=**/config/**  src/config/egress-key.ts

Matching is on a territory the rule declares, never on its tags: tags are thematic, and inferring a path from them would be a heuristic with no auditable reason to show. No filesystem access, no git, no model — text compared to text, so a file need not exist yet. And the paths come from the caller: runward never computes your change set, the composition stays yours.

Silence is not a declaration

First version: a rule either declared a territory, or said nothing. That was the flaw. A rule nobody had ruled on and a rule that deliberately governs no class of files read exactly alike — the very confusion ADR-0040 exists to refuse, reintroduced by the mechanism meant to apply it.

So there are three states, counted apart: a declared territory · a declared absence, with its reason · not ruled on yet. An editorial pass ruled on all 64 rules: 14 declare a territory, 50 openly declare they have none, and none stays silent. Each absence's reason is readable through runward explain, and a test now refuses a rule that declares neither.

Five rules were deliberately refused an anchor rather than inventing a territory to reach a tidy zero. A connection-pooling rule would have dictated your tree. A contract-governance rule would have under-declared: a DTO change would have read as out of scope. A wrong pattern is worse than no pattern, because it is displayed as an auditable fact.

What the command does not do

It turns "re-read 64 rules and guess" into "read three". It will never verify that the code respects the rule: that judgment stays yours, at the gate, and it is a deliberate property. Nor is it exhaustive by construction — fifty rules declare no territory and will therefore never match. The command says so on every run: not matched does not mean not applicable.

What the framework can do is ask the question at the right moment. Answering it stays human.


Update: npm i -g runward@0.26.0 · inside a mission: runward update

ADR-0041 · ADR-0042 · The changelog · All news

← All news