A check declares what it needs in order to answer, and what not knowing means: Guard becomes a projection of one ternary predicate

Accepted

ontoref
ADR-071 accepted a weakness it could not close. It stands in that ADR's own `negative` field —

Context

ADR-071 accepted a weakness it could not close. It stands in that ADR's own `negative` field — a consequence taken at ratification, not outside criticism:

"`gateable` / `gate_source` are escape hatches. They are narrow and each carries its reason, but a lazy author can silence a true finding by marking a term uncheckable. THE CONTRACT CANNOT DISTINGUISH 'a regex cannot know this' FROM 'I do not want to fix this'."

`gateable = false` is a confession made ONCE, BY THE AUTHOR, in the data, forever. Nothing can audit whether it is true. That is the question this ADR answers, and the answer is not a new primitive: it is a field that turns the confession into a measurement, plus the discovery that the protocol already held half the mechanism and had not finished applying it.

TWO EXECUTORS, IN ONE FILE, DISAGREEING ABOUT WHAT A CHECK CAN ANSWER.

reflection/modules/checks.nu :312 run-check typed union {FileExists|Grep|NuCmd|Cargo} → { passed, unresolved, detail } TERNARY reflection/modules/checks.nu :559 run-guard raw bash `cmd` string → { passed } BINARY

`run-check` has carried `unresolved` for weeks and the case that paid for it is written above `harness-failure`: of 59 Hard constraints reported violated on this tree, at least 27 were the harness never reaching its oracle — 46% noise, in red, which is why the number was run, read and ignored. Its sibling 250 lines below had none of it.

So a guard whose `cmd` needs `kubectl`, a daemon or a network path exited nonzero when that thing was absent, and was reported AS A VIOLATION OF ITS CLAIM. That is ADR-072's "a check that could not look", live, in the mechanism by which the protocol pushes back — inside the file whose own comments diagnose it for the other executor.

WHAT THE TERNARY ALONE STILL COULD NOT SAY, measured on this tree 2026-08-11.

Three guards were found lying about their own cause, and none of them by any check:

· sync-ontology / manifest-capabilities — overrode ONTOREF_ROOT with `$(pwd)` and loaded `./reflection/…`, a path that exists in the installed data dir and NOT in a project. It had NEVER ONCE PASSED, failing with `nu::parser::module_not_found` while announcing "Manifest capabilities incomplete" — a claim about the manifest, from a run in which the module was never loaded.

· generate-article / claims_backed — invoked bare `ontoref`, resolved through PATH to a build whose ONTOREF_ROOT predates the constellation split, and reported an article's claims as UNBACKED when the CLI had aborted before dispatching.

· sync-ontology / ontology-exists — `test -f .ontoref/ontology/core.ncl`, PASSED from the constellation root and BLOCKED from `code/`. Same commit, same guard, opposite verdicts, and the blocking one announced "this project has no ontology to sync."

Ten guards across seven modes carried relative paths of that last shape.

The instructive part is that ONCE THE TERNARY REACHED THE GUARD, the mechanism found two of the three itself: both started reporting `unknown` with "check script does not parse" and "the CLI could not locate its reflection tree" instead of blaming their subjects. The third needed anchoring, which `run-check` had already had and `run-guard` had not.

AND THE CASE THAT PROVED A TERNARY VERDICT IS NOT ENOUGH.

`capture-lead` and `sync-subscription` both gate on `privacy_policy_published` before routing personal data to a CRM — RGPD art. 13, severity 'Block. Declaring a network dependency on them, with only a permissive disposition available, means: no network → `unknown` → does not block → the mode proceeds to collect personal data having verified nothing. ABSENCE OF PROOF BECOMES PROOF OF COMPLIANCE, and the mechanism built to stop a check from lying introduces the lie itself.

`severity` says how bad it is for a claim to be FALSE. Nothing said how bad it is for it to be UNANSWERED, and the default has to be permissive or every capability-naming check halts every circuit that lacks the capability — which re-welds the check to a place, the failure the whole model exists to undo.

Decision

A carrier that produces a verdict declares TWO things it could not declare before: what it needs in order to answer at all, and what its not-answering means.

(1) ONE PREDICATE, TWO PROJECTIONS. `run-guard` stops being an executor and becomes a projection of `evaluate-cell` (reflection/modules/checks.nu), which dispatches to the typed union already used by ADR constraints and migrations, or to raw bash, and returns a ternary verdict either way. Guard and `constraint.check` run through one predicate instead of two executors that disagreed. The contract is `reflection/schemas/cell.ncl`.

(2) `needs` — CAPABILITY DECLARED, PROBED PER INVOCATION. A nucleus names capability ids from `reflection/capabilities.ncl`; each carries a read-only, bounded, exit-coded `probe` and an `absence_means` sentence. The probe runs BEFORE the claim's own evaluation. This is the delta over ADR-071: `gateable = false` is confessed once by an author and unauditable; `needs` is measured at every invocation, so the same check answers pass/fail where the capability exists and `unknown` where it does not, WITH NO PER-CONTEXT EXCLUSION LIST.

(3) TWO CAUSES OF `unknown`, BECAUSE THEIR DISPOSITIONS DIFFER. 'Declared a named capability is absent — the cell is out of its tissue. Nothing is wrong with the repo, the harness or the author. 'Inferred nothing was named and the executor never reached its oracle (exit 127, a script that does not parse, an absent target dir). BLOCKS exactly like a violation, with a different instruction: fix the harness, not the subject. Collapsing them is not a simplification. It either regresses the decision `run-check` already made — "a verdict that blocked less than red would be the drain every real violation eventually escapes through" — or brings exclusion lists back.

(4) AN UNREGISTERED CAPABILITY ID IS 'Inferred, AND BLOCKS. Were it to resolve to "absent", `needs = ["not_a_real_capability"]` would silence any check forever by authorship — ADR-071's weakness rebuilt with extra steps and less honesty, since `gateable = false` at least must name what would verify it.

(5) `on_unknown` — WHAT NOT KNOWING MEANS FOR THIS CLAIM. `'Permit` (default) lets the circuit proceed, which is what keeps a carrier portable. `'Refuse` makes absence of an answer disqualify exactly like a false answer, for claims that cannot be risked on an unverified assumption. It travels WITH the nucleus: where a cell runs is the tissue's business; what is at stake if the claim goes unverified is the claim's own, and a fail-closed cell that arrives elsewhere fail-open is not the same cell.

(6) A RAW `cmd` RESOLVES LIKE A TYPED CHECK. It executes anchored at the project root and through `resolve-cli-alias`, both of which `run-check` already did. A relative path means the same thing from every directory, and `ontoref`/`onre`/`ore` reach the in-repo wrapper rather than whatever build is on PATH.

(7) A `reason` IS NEVER PRINTED FOR AN `unknown`. `reason` states what it means for the claim to be FALSE; when nothing was learned, printing it asserts something nobody checked. Both circuits print `detail`, which says which of the two causes happened.

STRICTLY ADDITIVE. `needs` defaults to `[]` and the probe path is not entered when it is empty — which is every guard in every consumer project today. `on_unknown` defaults to `'Permit`. An existing carrier takes the identical path it always took and pays nothing for a registry it never mentions. Migrations 0061 and 0062 carry the consumer-facing half.

WHAT THIS ADR DOES NOT DECIDE: what a cell asserts, any cell content, defaults or roles; the tissue's memory or cadence; and the return arc from operation to tension, beyond restating that a mechanism may PROPOSE a tension and may never declare one.

Constraints

  • Hard `run-guard` produces its verdict through `evaluate-cell` and does not carry its own `bash -c` execution path.
  • Hard `needs` defaults to an empty array and `on_unknown` to `'Permit` in the Guard schema. A guard that declares neither stays valid and behaves exactly as before.
  • Hard A nucleus naming a capability id that no registry declares yields verdict `unknown` with cause `'Inferred` and blocks. It never resolves to 'absent'.
  • Hard Guard `cmd`s execute anchored at the project root and through `resolve-cli-alias`, so a relative path and a CLI invocation mean the same thing from every directory.
  • Hard Every entry in the capability registry carries a non-empty `absence_means`, so a verdict of `unknown` can say why nothing was answered instead of printing a bare word.
  • Hard The two RGPD gates that precede personal-data capture declare `on_unknown = 'Refuse`. Absence of an answer refuses there, and the posture is in the declaration rather than in the circuit.
  • Hard Neither circuit prints a guard's `reason` for a verdict of `unknown`; both print `detail`, which names the cause.
  • Soft The count of constraints with no gate plus checks the harness could not evaluate moves toward zero, and each ADR that touches the harness reports where it stands.

Alternatives considered

  • Leave `run-guard` binary and add cells as a sixth primitive beside Guard, membrane, Constraint, criterion-validator and instrumentrejected: It is the outcome this decision exists to avoid, and the originating plan named it as its own inverted evidence: the protocol already contained the same predicate several times, each welded to one consumption site, and adding a sixth would have been that finding turned into the result. A projection that leaves both callers' record shape intact costs nothing that an addition would have saved.
  • One `unknown` verdict with no cause, and let each circuit decide what it meansrejected: Either the circuit blocks on all unknowns — and no capability-naming carrier is invocable where the capability is absent, so exclusion lists return — or it blocks on none, which drops the floor checks.nu explicitly refused to drop. The two dispositions are already both required in this tree; one verdict cannot carry them.
  • Treat an unregistered capability id as 'absent' rather than as a harness failurerejected: It converts `needs` into the escape hatch ADR-071 warned about, and a worse one: `needs = ["nothing_here"]` would silence a carrier forever, by authorship, without even the obligation `gate_source` imposes to name what would verify the claim. Blocking on an unregistered id is what keeps the mechanism a measurement.
  • Make `on_unknown = 'Refuse` the default, since fail-closed is the safer posturerejected: It would make every carrier that names a capability halt every circuit lacking it, which is the welding this model exists to undo, and it would arrive as a behaviour change for guards that never opted in. The safer-looking default is the one that costs portability everywhere to buy safety in the few places that need it; those places can and now do declare it.
  • Lift the tagged check union out of adrs/adr-schema.ncl into reflection/schemas/check.ncl and have both the ADR schema and the nucleus import itrejected: Structurally the most correct and the most expensive: it touches the schema every ADR in the corpus and every migration validates against, for a gain that is internal tidiness rather than a capability. Deferred deliberately, and named here so the deferral is on the record rather than forgotten.
  • Declare the capability vocabulary as a closed enum in the schemarejected: It would be a second vocabulary the executor cannot read, and it duplicates a contract that already exists — `Capability` carries the per-id `absence_means` such an enum would have described in prose. The provisioning domain considered and rejected the same option for the same reason (provisioning:adr-053), which is evidence the registry shape survives contact with a consumer rather than only with its author.

Anti-patterns

  • A check that blames its subject for its own reach — A check cannot reach the thing that would answer it — a binary is missing, a module does not parse, a host does not resolve — and reports the failure as a property of the subject. The report is specific, actionable-looking and false, and it is believed because the alternative reading (the harness never arrived) has no verdict to occupy. Measured three times on this tree in one session, in guards that had announced a false claim for weeks: 'Manifest capabilities incomplete' from a run where the module never loaded, an article's claims 'unbacked' by a CLI that aborted before dispatching, and 'this project has no ontology' from the wrong directory.
  • Absence of proof read as proof — A ternary verdict is introduced and every unknown is made permissive, so that carriers stay portable. The change is correct for almost all of them and catastrophic for the few whose claim protects something that cannot be risked — a lawful-basis gate, a signature check, a destructive-operation precondition. The mode proceeds, nothing is red, and the unverified assumption is indistinguishable from a verified one. The tell is a permissive default applied uniformly to claims that were written to refuse.
  • A decision applied to one of its two sites — A property is argued for, paid for and implemented on one carrier, and the sibling carrier that needs it identically is never revisited — often in the same file, sometimes with the argument written directly above it. It does not read as a defect because the decision IS recorded and IS honoured where anyone looks. Here the ternary verdict, the project-root anchoring and the CLI-alias resolution had each been decided for the typed executor and none of the three reached the raw one, for weeks, with the reasoning visible in comments a few hundred lines away.

Related ADRs

ADR-011 · ADR-029 · ADR-050 · ADR-070 · ADR-071 · ADR-072

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.