Filling guide
This guide is the single source of truth for filling the artifact that
disensor new creates under .residue/. It ships inside the package:
disensor guide prints it for any coding agent, and disensor init installs
it as a Claude Code skill.
The validator (disensor validate) enforces much of what is described here,
but not all of it: where a validator rule exists, this guide names it, and what
is not named is the method’s obligation that nobody checks for you. The CI gate adds
its own checks, G1 to G9: some read the artifact you are filling (its level,
its confinement, whether its reviewed commit belongs to the pull request) and
some read the pull request as a whole (coverage, integration witness,
append-only evidence). They are documented in the reference, not here.
Filling the artifact correctly the first time is cheaper than iterating against
their errors.
The round, and then the declaration
Section titled “The round, and then the declaration”disensor prompt --gate <plan|diff|architecture>prints the adversarial brief. Hand it, together with the material under review, to a reviewer from ANOTHER model family. A free tier is enough. Same family as the generator is a degraded mode: it is declarable, with its independence recorded and its own residue item, but it is not the same thing and the record has to say so.- Verify every finding against the actual code before accepting it. The reviewer is decorrelated, not right, and an unverified finding is not a finding.
disensor new --gate <plan|diff|architecture> --level <A|B|C>creates the template, prefilled with what git knows (repository, commits, timestamp, uuid).- Fill in every
FILL_INmarker and the findings of the round. The template does not validate while markers remain: that is intentional. disensor validate .residue/<id>.json. Fix until it prints VALID.- Commit the artifact alone:
docs(residue): declare event <short-id>. Never mixed with code changes.
Declare what happened, not what should have happened. An event without findings and with an express declaration of absence is valid data, not a failure.
The three gates
Section titled “The three gates”event.gate says what was submitted to review, and it changes what the rules
demand afterwards. Each one has its own packaged brief.
plan: the plan before implementing. The cheapest moment to be wrong. Anincorporatedfinding here may close withfix_verificationof typepending_in_diff_gate, because the fix has not been written yet.diff: the change before merging. This is the one the CI gate demands for code, and the only one whereincorporatedrequires the fix to have passed its own verification (diff_gateorspecific_test, rule R7). Applying the fix is not closing the finding; verifying it is.architecture: a design decision or a comparison of alternatives, when the question is not whether the code is right but whether the shape is. Same contract as the others; what changes is the brief and the horizon of the findings.
A repository declares in its configuration which gate it accepts for which
paths. By default everything demands diff.
Actors
Section titled “Actors”generator: the assistant that produced the plan or diff.familyis its model family (anthropic, openai, google, meta, mistral, other).reviewers[]: the attacking assistants. Each needsreviewer_id(r1, r2…),family,model,confinementandindependence.reviewers[].independence:cross_familywhen the reviewer comes from another model family, which is what the method expects; below that,same_family_distinct_modelorsame_model_fresh_context. Rule R4 no longer demands a different family unconditionally: it demands that what you declare match the families you declared, socross_familywith two reviewers of the same family is rejected. A degraded independence also requiresfallback_reason(why the round settled for less) and a residue item of classreviewer_correlationnaming that reviewer: the errors a model shares with itself were not covered by the round, and that is residue. Level A does not admit it at all.reviewers[].hardening:verifiedif the reviewer ran through an adapter whose neutralisation of project instructions was tested against a hostile repository,unverifiedotherwise.unverifieddoes not block, but it requires areviewer_hardening_gapitem: the material under review can address the reviewer before your brief does.reviewers[].prompt_hash: hash of the adversarial brief given to the reviewer. If you used the packaged brief, it isdisensor prompt --gate <plan|diff|architecture> --hash, and anyone can recompute that value from the same version to check what you actually asked for. If you wrote or edited your own brief, hash the file you really used withdisensor hash <brief-file>. Either way, paste the fullsha256:....confinement.mode: how it was guaranteed that the reviewer only reads (permissions, sandbox, read_only_by_instruction, no_confinement). Declare the real mode; the gate makes gaps visible instead of hiding them.confinement.verified: true ONLY if you rangit statusafter the reviewer’s run and the tree was clean. Otherwise leave false.human_arbiter.present: must be true; an event without a human arbiter does not comply with the protocol (R0).
Delegation inside an actor is invisible to this contract, on purpose. A
generator or a reviewer may fan out into agents, subagents, scripts or any
other internal tooling: the artifact declares principals, not processes, and
the principal answers for the delegated output as if it were its own work.
No rule inspects how an actor produced what it signed, and none should; the
confinement verification over the working tree already covers whatever the
actor’s internal processes did there, and extensions is the place to
volunteer internal delegation when disclosing it matters. One honest nuance
comes with this: R4 decorrelates the declared principals. An actor that
internally leans on the same family as its counterpart keeps the declaration
formally true while weakening the statistical decorrelation, and the machine
cannot see that. It belongs to the protocol’s honest limit: the gate reads
declarations; human sampling reads reality.
Findings
Section titled “Findings”One entry per point the reviewer raised. Fields: id (h1, h2…), origin
(the reviewer_id that produced it), severity (critical, major, minor,
info), title, description, location (full profile only), and:
verification.against: what the generator checked the finding against before accepting or refuting it:repository(code, config, contracts),execution(running tests or the program),external_source(literature, third-party specifications, advisories, external documentation), ornone. Do not take the reviewer’s word: verify, then decide. Pick the class the verification actually had. If none of them is true of what you did, that is a defect of this vocabulary and it should be reported, not approximated.final_state, the terminal outcome. Decision table:incorporated: the finding changed the plan or the code. In the diff gate you MUST addfix_verificationwith typediff_gateorspecific_test(R7);pending_in_diff_gateis not legal there. R7 fires only in the diff gate, so plan and architecture both accept it. If the reviewer’s remedy was wrong and you fixed it, recordremedy_adjustment.debt_recorded: valid, deferred; requiresdebt_id(schema).owner_decision: valid, the owner changed scope, behavior or accepted risk; requiresrisk_record(schema).refuted_verifiable: false positive with proof. This is the state that closes a finding without touching the code, so it is the one that deserves the most resistance from you. It requiresevidencecarrying material content (text,linkorhash; neither the empty object nor a blank string counts, andtextneeds at least 10 characters) and averification.againstother thannone: refuting something without having checked anything is a contradiction, not a refutation. Note what the evidence does and does not establish. “The test passed” can be fully verifiable while “therefore the defect does not exist” is not deduced from it; when that is the case, say so inverification.detail.refuted_interpretive: false positive by judgment; it MUST also appear as a residue item (R1) withrequires_human_attention: true(R8).escalated_open: no decision yet; it MUST also appear as a residue item (R1).
Residue
Section titled “Residue”The heart of the declaration: what the cycle could not close by itself.
Either items or the express absence, never an empty field.
items[]:id(r1, r2…),class,finding_refwhen it comes from a finding,requires_human_attention.escalation_without_decision: from everyescalated_openfinding.principal_refutation: from every refuted finding; addrefutation_type(verifiableorinterpretive; interpretive forcesrequires_human_attention: true).execution_gap: behavior execution could not arbitrate; addgap_reason. In Level A an execution gap blocks the merge until a technical lead accepts it in writing (lead_acceptance, R5).
- Absence:
"declared_absence": trueplusdeclaration, minimum 30 characters of concrete text. Generic markers (none, n/a, all resolved, ninguno, todo resuelto…) are rejected by R2. Its list is closed and covers English and Spanish: an equivalent marker in another language gets through.
Metrics
Section titled “Metrics”counts must add up exactly against the findings list (R6): each
valid.* and false_positives.* bucket equals the number of findings in
that state, escalated_open likewise, total_findings equals the list
length. Count, do not estimate.
The rules catch less than that sentence suggests. R6 compares the counts against
the list whenever the list is there, empty included, and in the full profile R10
also demands the list and rejects an empty one whose total_findings says
otherwise. What neither of them can check is whether each finding carries the
right terminal state: a finding recorded as incorporated when it was really
refuted keeps every count coherent. Getting the states right is on you; the
validator catches mismatches, not misclassification.
Minimized profile
Section titled “Minimized profile”R9 strips the free text it covers: no titles, descriptions or locations in
findings; no descriptions in items; evidence only as hash; and it rejects a
repository that starts with http. Note the literal: that check does not
catch HTTPS://, ssh://, git:// or git@host:repo, all of which are clear
locators that pass today.
It narrows the leak channel; it does not close it. R9 does not reach every
string in the artifact: residue.declaration, event.pr, verification.detail,
human_arbiter.id and lead_acceptance, among others, still admit free prose.
Treat minimized as a reduction of surface, not as a guarantee that nothing
leaves the environment.
extensions is not exempt. In the full profile it takes anything; in the
minimized one every value must be opaque (a sha256: hash, a number, a
boolean, null, or containers of those) and every key must be identifier-shaped:
a name, not a message. The extension space is deliberately not interpreted
by the rules, which is exactly why free text parked there leaves the
environment as readily as anywhere else the profile does not reach.
Quick map of validator labels
Section titled “Quick map of validator labels”R0 human arbiter absent; R1 residue/finding coherence; R2 generic or
template markers; R3 abbreviated path over protected cases; R4 reviewer
shares the generator’s family; R5 Level A execution gap without lead
acceptance; R6 counts that do not add up; R7 incorporated without verified
fix in the diff gate; R8 interpretive refutation without human attention;
R9 text leaks in the minimized profile; R10 full profile without findings;
schema shape errors (missing required fields, wrong enums, bad patterns).