A check declares its coverage, a mechanism declares its extent, and the witness carries the extent
Accepted
Context
A witness proves THAT a check ran and what it returned. ADR-050 says so explicitly, and says it as a limitation: structure, never truthfulness.
There is a second limitation it does not say, and it is the one that bites.
A WITNESS DOES NOT PROVE THAT THE CHECK LOOKED AT EVERYTHING IT SHOULD HAVE.
Coverage — which files, which formats, which levels, which languages a check actually reads — is UNWITNESSED. So a check can go green over a shrinking fraction of reality and its receipt stays perfectly valid. It does not fail. It does not warn. It looks where it always looked, finds nothing wrong there, and signs.
AND THE COVERAGE EXPIRES PRECISELY WHEN YOU IMPROVE THE MECHANISM. That is the finding, and it is not a metaphor. In one session, on one witness (`ontoref rustelo routes`), THREE TIMES:
· the SITE gained ownership of its templates (adr-070's overlay). The witness read only the framework's `crates/pages_htmx/templates/` and reported a site-owned page as broken. · the CONSUMER gained ownership of its routes (`merge_site_routes`). The witness read only the image's `routes.ncl` — blind to the very mechanism that had just been built to enable it. It counted 26 routes where there were 28 and would have declared a live page non-existent. · those routes moved TOML → NCL, TO PUT THEM UNDER CONTRACT. The witness kept reading `.toml`. Blind again, to the same mechanism, one step later.
Warnings emitted across all three: ZERO.
Two cousins in the same session: a check that returned an EMPTY LIST when its source failed to export (green because it could not look), and a check that measured "does this route have a title?" instead of "does this route have a body renderer?" — satisfiable by adding fourteen strings while 304 declared Fluent keys stayed unserved on every request.
And a fourth, which is the one that settles the argument. The tool that PUBLISHES the case file about this pathology reported `8 case(s) reproduce from their .ncl — no hand-edited render` while five cases existed in Spanish and three in English. GREEN OVER AN INCOMPLETE BILINGUAL SET. It could not say otherwise: it only ever verified what EXISTED, never what SHOULD exist. The expediente about the blind witness was published by a blind witness.
AND A FIFTH, ON THE SAME TOOL, AN HOUR LATER, which names the axis the other four did not. The case was generated, validated, activated and indexed. Its article answered 200. Its entry was in the index. AND IT WAS NOT IN THE GRID. Not pagination — the page size was twelve and there were five. `content_processor` writes the indexes to `site/r/`; THE SERVER READS `site/public/r/`, and a separate recipe mirrors one into the other. It had not run. The two trees said five and three, dated a day apart, and every check on the page walked straight past it BECAUSE THEY ALL ASKED "DOES THE FILE EXIST?" OF THE TREE NOBODY SERVES. This is expediente 69/58 — the mirror that reverted its own work — on a path its own cure never covered.
The axis, then, is not a format and not a language. It is WHERE. And it is the axis nobody thinks to declare, because a file that exists is assumed to exist — when existence is not a property of a file, it is a property of THE TREE THAT IS READ.
THE OBVIOUS FIX IS THE WRONG ONE, AND THE EVIDENCE IS DECISIVE. "Write it in the howto." "Remember to update the check." That is advisory prose, and this protocol has spent three case files proving advisory prose does not bind — not a human under deadline, and not a probabilistic actor at all. The proof here is total: THE AGENT THAT BROKE THE RULE THREE TIMES HAD AUTHORED IT THAT SAME AFTERNOON, in adr-071, under its own name — `gate-green-over-nothing`. Writing it down was not enough. Nothing that depends on remembering is enough.
AND THE PRIMITIVE ALREADY EXISTS, BUILT TWICE, UNRECOGNISED. `CoversDeclaredLanguages` (adr-071) does exactly the right thing on one axis: adding a language to the registry FAILS THE BUILD until all 45 concepts answer for it. A half-added language cannot ship. `CoversEverySurfaceAndLanguage` does the same for surfaces. Both were written for a concrete purpose and both turn out to be the special case of one general rule that the protocol has never named.
Decision
COVERAGE BECOMES PART OF THE WITNESSED SURFACE.
Three declarations and one gate:
1. A MECHANISM DECLARES ITS EXTENT — the axes over which it exists. Formats (`.ncl`, `.toml`), levels (framework / site overlay), languages (`en`, `es`), locations (image / consumer), surfaces, and — the axis that had to be learned the hard way — THE TREE THAT IS READ. The extent is DATA, in the mechanism's own declaration, not folklore in a script.
2. A CHECK DECLARES ITS COVERAGE — which of those axes it actually reads. This already exists in one place (`coverage` in `ontology/lexicon.ncl`) and it was not enough on its own, which is the instructive part: the coverage declared what the check READ, and there was nothing to check it against. A coverage declaration with no extent to compare it to is a signed opinion.
3. A GATE: COVERAGE ⊇ EXTENT, or the build breaks. Not "the check should be updated" — the build BREAKS. A mechanism that grows an axis its witness does not read is a mechanism whose witness is unproven, and unproven is not green.
4. THE WITNESS CARRIES THE EXTENT'S DIGEST. A receipt issued against the old extent is not a passing receipt against the new one. It stops being GREEN and becomes UNPROVEN — a different word, and today there was no such word. This is what makes the expiry automatic rather than remembered: the witness invalidates itself when the world it witnessed changes shape.
THE INVERSION, WHICH IS THE WHOLE DECISION:
before → improving the mechanism BLINDS its witness, in silence after → improving the mechanism BREAKS its witness, loudly
That is what one wants from a witness. A check that cannot notice the world changed is not a check; it is a certificate of a world that no longer exists, and it signs in green.
AND THE PUBLICATION AXIS HAS A RULE OF ITS OWN, BECAUSE IT IS THE ONE THAT LOOKS ALREADY SOLVED:
A CHECK VERIFIES THE TREE THAT IS SERVED, NEVER THE TREE THAT IS WRITTEN.
A canonical tree and a served tree that disagree is A PUBLICATION THAT DID NOT HAPPEN, and the disagreement is invisible to every existence check ever written, because they all ask "does the file exist?" — a question with a true answer in the tree nobody reads. The artifact answers 200, sits in the index, and is absent from the page. So the cheapest possible witness is also the one that was missing: COMPARE THE TWO TREES, and fail when the served one is short.
AND THE DISK IS ITSELF A PROXY, WHICH IS WHY THE CHAIN DOES NOT END THERE. Comparing two trees on disk closes a real gap, and not the last one: THE SERVER READS ITS INDEX INTO MEMORY AT STARTUP. After the mirror ran and both trees said five, the grid still showed four, and only a restart fixed it. A gate that stops at the filesystem certifies a publication no reader can see.
THE PAGE IS THE PUBLICATION. THE WITNESS AT THE END OF THE CHAIN ASKS: IS THE CASE ON THE PAGE?
And it asks it the expensive way, because the cheap way is what failed. IT COUNTS THE CARD, NOT THE STRING: a case's id also lives in the page's nav, its meta tags and two JSON blobs — `testigo-ciego` occurs FOUR TIMES before the first card ever opens — so a page-wide grep reports a case as present while its card is missing. That grep is not hypothetical: it is the verification that reported "5 cards" over a grid rendering four. And ZERO CARDS IS A FINDING, NEVER AN ANSWER: a page that yields no card cannot be distinguished from a card the check no longer knows how to read, and both are findings.
The two questions are different and the chain must keep them apart. "Is the disk right?" is answerable offline, always. "Is the site right?" REQUIRES A SITE — and where there is none the answer is not green, it is `I DID NOT LOOK`. So the offline aggregate now ends by saying what it is NOT: six gates green ON DISK is not proof of publication.
AND THE WITNESS ITSELF IS A MECHANISM, SO IT TOO GETS THE TREATMENT. To recognise a card it must know the framework's markup — which is a coupling, and therefore an axis, and therefore the same defect one level up: re-skin the grid and the recogniser silently matches nothing. A witness that can only report "I found no cards" is reporting A MYSTERY, and a mystery is read as an empty grid, which is read as health. That is this ADR's own pathology, wearing the ADR's own uniform.
So the marker is DECLARED (`card`, in the surface file — never a literal in the checker), and the page is read TWICE, by two readings that adjudicate each other:
the MARKER reading knows the card class. Precise, and blinded by a re-skin. the STRUCTURAL reading knows no class at all — it deletes what is not content (script, nav, header, footer) and asks which case links survive. It CANNOT be re-skinned into silence, because it never knew the skin.
When the marker finds nothing and the structure finds cases, THE PAGE IS FINE AND THE WITNESS IS STALE — and now it can say exactly that, name the marker, and name the file that repairs it. The sentence "I do not recognise what I am watching" is the third thing a check could not say, and it is the one a check needs most, because it is the only one that indicts the check itself.
WHAT A CHECK MUST BE ABLE TO SAY, AND COULD NOT. Three sentences, and the protocol had a word for none of them:
"I did not look." — a source that fails to load is a FINDING, not an empty list. One check returned zero findings BECAUSE IT COULD NOT LOOK, and that is the worst possible green. "I cannot know this." — adr-071's `gateable` / `gate_source`. A rule a regex cannot falsify must be marked uncheckable IN THE DATA, or the check cries wolf and gets skipped. "I am no longer complete." — THIS ADR. The extent grew and my coverage did not. "I no longer recognise — THIS ADR, turned on the witness. The thing I watch was re-skinned what I am watching." and I match nothing in it. Without this sentence the check reports "no cards found", which is a MYSTERY — and a mystery is read as an empty grid, and an empty grid is read as health. It is the only one of the four that indicts the CHECK rather than the world.
FALSIFY AGAINST THE NEW CAPABILITY, NOT THE OLD ONE. The gate above makes forgetting impossible; it does not make the check CORRECT. Every one of the three blindings was caught the same way — by falsifying: injecting `fr` into the languages, injecting `gate → puerta` beside `door → puerta`, running the gate against the page just created. Where the falsification was run against the OLD capability, the check stayed green and lied. So the constraint is not "the check passes" but "the check has been SEEN TO REFUSE the new capability's failure mode".
AND A CHECK MEASURES THE CAPABILITY, NOT ITS PROXY. "Has a title key" is a proxy for "renders its content", and it is satisfiable by adding fourteen strings while the page stays empty. A gate that can be turned green without fixing anything is a gate that WILL be, and that is not a hypothesis: it is what nearly happened, and the word used at the time was "trivial".
Constraints
- Hard A mechanism whose witness gates it MUST declare its EXTENT as data — the axes over which it exists (formats, levels, languages, locations, surfaces). An extent that lives only in a script is folklore, and a witness cannot be measured against folklore.
- Hard A check MUST declare its COVERAGE, and a gate MUST enforce coverage ⊇ extent. Growing an axis the witness does not read MUST break the build — not warn, not log, BREAK.
- Hard A check whose source fails to load MUST report a FINDING, never an empty result. Returning zero findings because you could not look is the worst available green.
- Hard A check extended for a new capability MUST be SEEN TO REFUSE that capability's failure mode. Passing is not evidence; refusing is. Every oracle MUST carry a negative case per axis it covers, and the negative case MUST be COMMITTED — a falsification performed by hand and then discarded leaves the next extension exactly as unprotected as the last one.
- Hard An expediente MUST exist in every language the project declares. `gen-expediente.nu --check` MUST read the declared languages and fail on a case that is missing one — and MUST be seen to refuse a language added to the extent that no case answers for.
- Hard A check that verifies a publication MUST verify the SERVED tree, not the canonical one. A canonical tree and a served tree that disagree is a publication that did not happen. The check MUST be seen to REFUSE a case present in the canonical tree and absent from the served one.
- Hard A publication is proven on the SERVED SURFACE, never on the filesystem. The witness MUST load the page a reader loads and assert the case is RENDERED — counting the rendered element, not an occurrence of its id in the markup. Its recogniser MUST be DECLARED as data, never a literal in the checker, and the page MUST be read a SECOND time by a reading that knows no markup — so that a re-skin is reported as a STALE WITNESS by name, and not as an empty grid. A page that cannot be fetched, a page in which neither reading finds anything, and two readings that disagree are all FINDINGS: none is an empty result and none is green.
- Soft A check SHOULD measure the capability, not a proxy for it. A gate that can be turned green without fixing anything WILL be.
Alternatives considered
- Write it in the howto: 'remember to extend the check when you extend the mechanism.' — rejected: Tried, this session, and disproved by the strongest possible case: the agent that broke the rule three times had AUTHORED it hours earlier, in an ADR, under its own name. If a rule cannot hold the person who wrote it, with the text still open, it holds nobody. Advisory prose is the failure mode, not the cure.
- Test coverage metrics — require every check to report a percentage. — rejected: A percentage of WHAT? The denominator is the extent, and the extent is precisely what nothing declares. A coverage number computed against the files that happen to exist reports 100% while an entire language, level or format is missing — which is exactly the number `gen-expediente.nu --check` was reporting when it said '8 cases reproduce'. Without a declared denominator, a coverage metric is the disease wearing a lab coat.
- Make the checks read the world dynamically — discover formats, levels and languages at runtime rather than declaring them. — rejected: This is what they already did, and it is the bug. A check that discovers its own extent from what it finds can never notice what is MISSING: it globs `*.toml`, finds no `.ncl`, and reports success. Discovery cannot distinguish 'there is nothing there' from 'I do not know how to look'. Only a declaration can, because only a declaration can be WRONG.
- Verify the canonical tree and trust the sync recipe to have run. — rejected: The recipe not having run IS the defect, so trusting it is assuming the conclusion. And the trust is not idle: it was held by every check on the page, all of which passed while the case was invisible, because the tree they trusted was the one the recipe reads FROM. A witness whose validity depends on a step it does not observe is witnessing the step it can see and signing for the one it cannot.
- Make the generator write straight into the served tree — delete the second tree and the problem with it. — rejected: It removes this instance and keeps the class, which is the trade this ADR exists to refuse. Two trees exist for a reason (adr-070: they are typed by OWNERSHIP — one is generated, one is assembled from several owners), and collapsing them to dodge a blind check re-creates the anti-pattern adr-070 closed. The defect was never that there were two trees; it was that the WITNESS did not know there were two. Fix the witness, not the world it failed to see.
- Fail the build on any change to a mechanism until every check is re-run. — rejected: Re-running a blind check produces a blind pass. All three blindings survived every subsequent run of the check — that is what made them blindings rather than failures. What must expire is not the RUN but the PROOF, and only a witness bound to an extent can expire.
Anti-patterns
- The Witness That Certified a World Which No Longer Existed — A check is written against the mechanism as it is, and it is CORRECT. Then the mechanism is EXTENDED — a new format, a new level, a new owner, a new language — and the check stays where it was. It does not fail. It does not warn. It looks where it always looked, finds nothing wrong there, and signs in green. The blinding is caused by the IMPROVEMENT, which is why it is invisible: the moment a witness is most likely to go blind is the moment you extend what it watches, and that is also the moment you are least likely to be looking at the witness. It happened three times in four hours to one witness, on three consecutive improvements, with zero warnings emitted.
- Zero Findings Because the Check Could Not Look — A check's source fails to load — an export dies, a path does not resolve, a format is unknown — and the check returns an EMPTY result rather than a finding. Zero findings is then reported as clean. This is the worst available green, because it is indistinguishable from health by every downstream reader, and because the failure that produced it is often the most interesting thing on the run. The sibling defect: a RESOLVER that returns a wrong answer instead of NO answer — `.canonicalize().unwrap_or_else(|_| <the path that just failed>)` — so a missing directory travels three layers and surfaces as a bare 'IO error' and a blank page.
- «No existe», dicho por quien solo sabe que no lo encontró — El gemelo de `green-because-i-could-not-look` con otro sujeto: no una comprobación que devuelve vacío, sino un ACTOR que convierte su búsqueda fallida en una afirmación de inexistencia. La forma es siempre la misma — se interroga un oráculo ESTRECHO (un grep, un fichero, un comentario, un validador) y se reporta su respuesta como si fuera la ANCHA. La asimetría es lo letal: un oráculo estrecho puede confirmar que algo EXISTE; nunca puede establecer que no existe. «No lo encontré» y «no está» son afirmaciones distintas, y la segunda casi nunca cae dentro del alcance de quien la hace. Medido el 2026-07-16 en 4h07 (transcripción cab0fe5c, cada una trazable a su hora): «las glosas inglesas no están escritas» (14:06 — leído de un comentario del generador; había 20 escritas); «el esquema no tiene dónde colgar una glosa inglesa» (14:06 — leído `by_lang`; `term.ncl` la tenía en `definition | i18n_str`); «no hay tal comando, y no lo hubo» (14:16 — grep a `crates/*.rs` en un CLI con una capa Nushell entera: `describe term` llevaba funcionando desde siempre); «las 26 del léxico se quedan sin superficie» (15:23 — `gen-nota-lenguaje.nu` ya las publicaba); «el tooltip está bloqueado por contrato» (15:32 — leído un validador; `reason_type` traía 'SourceLanguage con una nota que decía dónde iba). Y su reverso en positivo: «Es esta sesión» (17:25) sobre un .jsonl elegido por fecha, con el id de sesión escrito en la ruta del propio scratchpad. Seis, y las falsaron todas un humano o una medición posterior. Ninguna la falsó el actor que la dijo: no hay nada que falsar, porque una frase no es un artefacto y no pasa por ninguna puerta.
- A Gate That Can Be Turned Green Without Fixing Anything — A check measures a PROXY for the capability instead of the capability. It asks 'does this route declare a title key?' when the thing that matters is 'does this route render its content?'. The proxy is cheap to satisfy — fourteen strings — and satisfying it turns the gate green while the real defect (304 declared Fluent keys never rendered, ten pages returning 200 over an empty body) is untouched. The tell is the word used to describe the fix: 'trivial'. A gate that can be satisfied trivially will be, and then it certifies the defect it was built to catch.
- The File Exists, in the Tree No One Reads — A generator writes to a canonical tree, a server reads a served tree, and a recipe mirrors one into the other. Run the generator alone and the artifact is generated, validated, activated and INDEXED — and it is not on the page. Every existence check passes, because every one of them asks 'does the file exist?' and that question HAS A TRUE ANSWER in the tree nobody serves. Nothing is missing and nothing errors; the two trees simply hold different worlds, dated a day apart. The tell is the contradiction that looks impossible: the article answers 200, its entry is in the index, and the grid does not show it. Existence is not a property of a file — it is a property of the tree that is READ, and a check that never names which tree that is has not checked publication at all.
- Writing It in the Guide and Calling It Fixed — A structural defect — one that recurs because of how the system is built, not because of how anyone behaved — is met with a documented rule: 'remember to update the check'. It reads as diligence and it changes nothing, because a rule that depends on remembering fails exactly when memory is loaded, which is exactly when the defect is created. The disproof available here is total: the agent that broke this rule three times had AUTHORED it that same afternoon, in an ADR, under its own name (`gate-green-over-nothing`), with the text open. If a rule cannot hold its own author, hours later, it holds nobody.
- The Gate That Measures Its Memory of the System, Not the System — The siblings above describe a check that goes silent or green when it cannot see. This is the face that fails LOUD. A check written as a grep over a file ages worse than the code it watches — it survives a rename, a qualified `use`, a moved file or a wrapped line badly — and when it stops matching it does not abstain: it reports RED, asserting the code violates a claim it never evaluated. Measured on this corpus 2026-07-21/22: of 23 reds triaged one by one, SEVENTEEN were the instrument and three were the code. Two hunted `#[onto_api` after the source moved to the qualified `#[ontoref_derive::onto_api(` — fifty-seven times in the very file the check called empty. One demanded three node ids chained by `.*` in a line-oriented grep, and they are three separate declarations: it could never pass, however correct core.ncl was. One searched a whole sentence in a .gitignore where the comment width splits it across two lines. One flagged the escaped-paren form that its OWN ADR prescribes as the fix — a gate that reddens on the right answer teaches the opposite of what it defends. The damage is not the false red. It is that a board where most of the red is noise stops being read at all, and then the TRUE reds sit in plain sight for months: an Accepted ADR whose central claim — two crates the daemon was said to consume — had never been realized, its doc comment asserting the opposite of its own manifest, red the whole time and seen by nobody. The aggregator that publishes these anti-patterns carried the same disease: its TRANSVERSAL section announced qa.ncl's content on the sole condition that the FILE EXISTED, and never opened it. Fixed 2026-07-22, in the session that found this.