Repository navigation
feat(judge): refuse the invocation a protected span appears in, and cap what crosses - #264
Conversation
…ap what crosses Refs: CLOUD-135
CLOUD-135 Define the judge's payload-privacy boundary (what may be sent to a model)
Why Batten's output law reduces content to pointers (CLOUD-92); the judge (CLOUD-56) is the one component built to send repo content to a model. Ungoverned, that is an egress path inside the tool whose purpose is keeping content out of model context. The bound is set by what the egress buys: the judge's verdict is advisory-only and structurally unable to block (decision, 2026-08-07 evidence base), so no payload is justified by enforcement power — the boundary defaults to refuse, and every byte that crosses must be explicitly admitted, capped, and recorded. This issue closes the previously open positions (classes, redaction, local models, protected-content default) as decisions carried by a computable mechanism: a payload-assembly module that is the only way a judge payload can exist. Decisions
Rejected alternatives
Definition of done
Acceptance (fixture-level, each falsifiable)
Refinement — Ready (three admissible payload classes by type; protected membership refuses whole; caps tighten-only; stdin channel; pointer-only byte-stable record) Refinement gate: Definition of Ready & Done. This body carries only specializations.
Stated assumptions
|
|
|
/fast-forward |



Closes the DoD bullets the 2026-08-11 conformance audit demoted CLOUD-135 from
Done for. PR #247 landed the config types,
Attribution, and the pure builder;bullets 1, 3, 4 and 5 were not landed, and one landed behaviour contradicts the
issue's own recorded decision.
The landed module withheld protected spans individually, behind a config key:
CLOUD-135's Rejected alternatives says, verbatim:
over_protected = "raw"is that key. Decision 2 says instead that a protectedmember refuses the whole invocation as a usage error (exit 1), naming the
rule id and the count. This PR implements the decision and removes the key.
There is a second reason beyond conformance: per-span withholding quietly changes
what the judge is judging. The row named a set of files; silently sending a subset
means the verdict is about content the config never described. Refusal is the only
posture that keeps both the verdict honest and the bytes home.
Consumer #1 declares no
[judge]table, so no committed config changes. A configstill carrying the key now gets exit 1 from
deny_unknown_fieldsrather thanparsing green — asserted, because an author who wrote
over_protected = "raw"should hear that it no longer does anything rather than keep believing protected
content crosses.
lint.rs'sjudge-over-protected-unstatedsmell goes with it: a smell over aquestion the engine now answers structurally could never fire.
What else landed
The cap (bullet 3).
max_payload_bytes, default 16 KiB, refuses whole —never truncates. A truncated payload would have the judge return a verdict about
a prefix while the record claims it judged the row.
effective_capistighten-only, and the direction is worth stating because it reads backwards: for
a budget, smaller is stricter, so §8's "may not weaken" means "may not raise".
resolvedoes not layer[judge]at all today — strictly stronger thantighten-only — so the clamp is the semantics waiting for the day it does.
The rule class (bullet 1). The DoD names three classes: rule, content,
pointer. Only content and pointer existed.
RuleTextis its own type becauseit is the one class that is not repo content — the config author's own committed
words — so it carries no egress question and always crosses. Separating it is what
makes "the constructor admits exactly three classes" checkable by reading the
signature.
The invocation record (bullet 5).
InvocationRecord— rule id, byte count,SHA-256, matched-file count, disposition — byte-stable and pointer-only, asserted
to carry none of the payload's bytes.
Assembledhands back the serialized bytesalongside it so the record cannot describe different bytes than the ones sent.
Ordering is load-bearing.
assembledecides protection before any byteenters a payload value, and checks the cap on the assembled bytes so it bounds
what would actually cross.
Not in scope
The stdin channel (decision 4) is CLOUD-56's wiring — this module reaches nothing
and hands back bytes. §7 already assigns the binary-level assertions there.
Tests
Twelve module tests: one protected span refuses the whole invocation and the
diagnostic carries the rule and count but neither the bytes nor the path; a span
with no provenance refuses too; a clean payload byte-scans positive for the
criteria and the matched bytes and negative for three planted sentinels; the raw
opt-in admits exactly the named class; nothing crosses by default while the rule
class still does; the cap boundary is inclusive and one byte over refuses whole;
the clamp is tighten-only in both directions; the record carries no payload bytes;
two assemblies are byte-identical.
Refs: CLOUD-135