AICENT.COM
← Back to Blog

Refusal is an answer — the grant-reference family enters the lab mainline

2026-11-04 · RTTP · IQA
Sources cited in this piece · the text the vectors are anchored to, quoted sentence by sentence: draft-li-rttp-iqa-addressing-02 (full text) · series page and revision history: datatracker · the family page, sealed: iqa.org/conformance/aicent-grant-ref/ (four files, per-file SHA-256) · canonical source: Aicent-Stack/conformance · candidates/aicent-grant-ref · merged into the lab mainline: Agent-Authority-Conformance lab, PR #210 (merged 3 November 2026) · the sibling record: lab PR #212 · the wall-side narrative: No. 23, the reason makes a refusal auditable · Sister pillars: RTTP (rttp://, CRI 27) · IQA (iqa://, CRI 28)

On the evening of 3 November the Agent-Authority-Conformance laboratory merged both of our vector families into its mainline, thirty-three minutes apart: aicent-grant-ref first, as PR #210 at 17:47 UTC with merge commit 0da1e29, then its sibling aicent-lease-boundary as PR #212 at 18:20 UTC as 29423aa, rebased onto the first merge so the shared candidates index would stay clean. The argument that made this family worth building — the fortnight the walls spent on refusal semantics — is already on record in No. 23; nobody needs it repeated here. What this piece records instead is the artifact itself: what a case is made of, how the expectations were checked against the source, and what an implementer can do with the four files tonight.

the anatomy of one case · complete, self-contained, runnabletwenty-five cases · each gives the facts on record, one response an implementation could produce, and the rule that decidesfacts at answer timethe evidence on file · the observationthe revocation record · the timesone responsea reason from the closed set — or nullthe five grant-reference membersexpected verdictconformant · nonconformant · ambiguous+ which rule decides, section by section11 conformantthe response satisfies the text12 nonconformantthe rule says no, and names the section2 ambiguousscored in named partsreading rules: an outcome is read as the record it presents · an absent key is not an explicit null · outcome field names are the candidate's own representation, since the source names values and members, not a JSON bindingevery expectation anchored to §8.1 and §8.2 of draft-li-rttp-iqa-addressing-02 · retrieved TXT pinned at sha256 02491c5a · checked against the source sentence by sentence
Figure 1: the unit the family ships. A case is not a fragment — it is a complete answer-time scenario with its verdict and its deciding rule, readable without any other file.

What one case is

Each of the twenty-five cases is a complete answer-time scenario: the facts on record when the answer is produced, one response an implementation could produce against those facts, and the expected verdict with the rule that decides it — the rule, and the section of the source the rule rests on. Eleven cases are conformant, twelve are nonconformant, and two are open readings scored in parts. The reading rules are stated in the vectors themselves: an outcome is read as the record it presents, not as intentions behind it; an absent key is not the same as an explicit null; and the outcome field names are this candidate's own representation, because the source names values and structure members, not a JSON binding. That last rule matters more than it looks — it keeps the family from silently pretending the draft specified a wire format it never specified.

The priority order is the definition

The closed vocabulary's five values were covered on the wall side in No. 23; what the family adds is the discovery that testing the order is its own engineering problem. R-ORDER holds that the reason is the first value in the list whose condition applies — and the family proves that claim is testable by making the combinations explicit: mismatch over expiry (GR-P01/P02), expiry over revocation (GR-P03/P04), mismatch over revocation (GR-P05), all three conditions stacked at once (GR-P06), and operation_not_permitted standing alone with nothing above it (GR-P08). One case, GR-P09, tests the boundary of the whole scheme: when no condition applies at all, the reason must be null — emitting a refusal value without a condition is nonconformant. An order that only ever appears in prose can be honored in the breach; an order that has a case for every stacking pattern cannot.

The null rule, conservatively encoded

R-NULLOBS is the family's strictest rule and the one most likely to be implemented wrong. observation_as_of_ms is null whenever the observation the answer relied on is absent or not fresh — even if a later successful observation exists (GR-N01/N02/N03). The diagnostic-read clause is encoded conservatively: the renewal reading fires only when a diagnostic read is actually in play and the response pushes expires_at_ms past the grant's own recorded expiry (GR-N05/N06). R-NOTDET holds the never-assent line: an unreachable source, or an observation that predates the request, ends in authority_not_determined and is never read as assent — and GR-N04 shows a permit computed from a diagnostic read failing for exactly that reason. The section's own sentence carries the argument: the reason is what makes a refusal auditable, and a record that backfills a stale observation with a fresh one is not auditable — it is repaired, which is a different thing.

Closed means closed

R-CLOSED holds the set's edges: no affirmative “granted” value exists (GR-C01), a response without its reason is not conformant (GR-C02), there is no sixth value (GR-C03), and null reason over the set is the only representation of permitted (GR-C04). R-GRANTREF holds the receipt together: every response, permit or refusal, carries the five members — grant, evidence_id, issued_at_ms, expires_at_ms, observation_as_of_ms — and GR-G02 to GR-G04 each drop exactly one member, so the checker can name which one went missing. The symmetry is the point: a refusal with a reason and no receipt is an opinion; a refusal with a reason and the full receipt is a record a third party can re-derive.

the mutation battery · the checker checks itselfmutate.py weakens or distorts exactly one rule of check.py at a time — and fails unless some case catches each changeR-ORDER weakeneda wrong order slips a reason past a lazy checkerR-NOTDET invertedunreachable starts reading as assent — caught at onceR-NULLOBS relaxeda backfilled observation stops being a violationR-CLOSED wideneda sixth value — or an affirmative granted — slips inR-GRANTREF droppeda missing member stops being scored at alltwo sentinelsalways-nonconformant · always-conformant — both must be caught21/21 mutants killed · exit 0a checker that cannot tell a correct implementation from a broken one fails loudly instead of pretending to score
Figure 2: the battery that proves the vectors can score. Every rule of the checker is broken on purpose, one at a time, and the family fails its own build unless a case catches each break.

The battery that checks the checker

A vector family with a hand-built checker carries an obvious risk: the checker itself could be the bug. mutate.py answers it mechanically. It weakens or distorts one rule of check.py at a time — the priority order, the never-assent mapping, the null rule, the closed set, the five-member receipt — and it includes two sentinels: a version of every rule that always answers nonconformant and one that always answers conformant. The battery fails unless some case catches each change. At the submitted revision the runs printed 25/25 agree and 21/21 mutants killed, each with exit 0. The family's own documents state what these numbers mean and, just as carefully, what they do not: they show the checker agrees with the expected results and that every rule has a case depending on it in both directions — they say nothing about any implementation, and they do not show that the expected results are the right reading of the source.

Two open readings, scored in parts

The two ambiguous cases are the family's most honest feature. GR-A01 scores a correct value computed over an operator label — §8.1 says a refusal computed over labels, opinions, or unrecorded context alone is not auditable, but it does not state what verdict the response itself earns, so the case refuses to manufacture one. GR-A02 scores a revocation arriving one millisecond after the relied-on observation — the section calls that a boundary no protocol erases and names the record the response owes, but which answer the boundary requires is settled, if at all, by evaluation rules the draft references and the family does not cover. Both cases are scored in named parts via expect_parts, so a case can show which part of an outcome holds and which does not. An open reading is recorded as an open reading, not flattened into pass or fail — the same discipline the series has argued for on the walls, applied to our own artifacts.

What the family does not claim

The CANDIDATE's limitations section is worth reading as part of the design. The five members are tested for presence only — value formats, identifier syntax, and the relationship between expires_at_ms and the lease rules of §6 are not covered. The source is one document at one revision; a revised draft needs a new revision of the family. The checker encodes one reading of the source, and a correction in a later revision overrides it. None of this is hedging for its own sake: a family that claimed more than its twenty-five cases could support would be exactly the kind of overclaiming artifact the closed vocabulary exists to prevent.

What an implementer can do tonight

The family is four files — CANDIDATE.md, vectors.json, check.py, mutate.py — downloadable from the sealed family page with per-file SHA-256, and the runs need nothing but Python 3 with the standard library: python3 -I check.py vectors.json, then python3 -I mutate.py. To test an implementation, hand it each case's input, replace the outcome with the response it produced, and run the checker — twenty-five verdicts in seconds, each one naming the rule and the section behind it. The sibling family extends this to the lease: the never-assent mapping these cases establish is aicent-lease-boundary's given, and what the modes add on top is that family's subject, recorded in its companion piece.

One line: the walls argued about refusal semantics for a fortnight, and the answer that did not have to win the argument — twenty-five cases, 21/21 mutants killed, merged into the lab mainline as 0da1e29 — is now downloadable, runnable, and auditable tonight.

SEALED rttp://e8122ff5.blog.aicent · iqa://e8122ff53afc75334be0ff1206c6f72f.forge.iqa · seal record