Repository navigation
Permission-posture claims restated across unsynced surfaces — enumeration-based containment cannot close the class #353
Description
Activity
New member of this class: governance-enumeration restatement in the remediation doctrine
Surfaced by the pre-PR adversarial review loop on branch
feat/359-recorded-disposition(implementing #359). Filing here rather than opening a new issue: this is the sameroot_classfamily already tracked by this issue, and the doctrine's own Defer-with-Scope default is to prefer an existing tracked home.Root, sharpened
The generalisation that fits the evidence is not "the enumeration was replicated". It is:
A governance sentence asserted an UNBOUNDED NEGATIVE UNIVERSAL — a claim about the content of every file in the repo, including files not yet authored.
plugin/governance/remediation-doctrine.mdbriefly carried: "The admissible destinations are enumerated ONCE — here — and no other section, agent, or skill restates them."That claim was false at the instant of introduction.
plugin/agents/overlord.md:123, authored on the same branch one commit earlier, contained the exact string the rule quotes as its forbidden example. Its truth could only ever be established by a hand-built inventory taken at authoring time, and it is falsified by the next sentence anyone writes.This also explains why the first structural fix on this surface failed to close the cluster: re-keying the concept from a destination type to a role changed the value being asserted, not the assertion form. The assertion form was the primitive.
Contrast
remediation-doctrine.mdlines 202 and 248, which make the same shape of claim but bounded to two named consumers. A bounded claim is fixture-pinnable; the unbounded one is pinnable by nothing.Linked threads
Cluster members from the review loop:
1ab1893ceb3a45968680c20facecbad99ee1ebfbda5d063ff5f5ead35d833323(high) — the unbounded claim itselfdd21cbd514684ceb465566b52fe72af079cce12f157bc53472e251a5a67e0e02(medium) — the doctrine restating a downstream payload shape, which is the same root one step out49e9482bf92c57156e69023be0b00ee97caadedc637610ccc915c6d56a8adb73— the iteration-1 member
Fixed on that branch
The two live defects and the assertion form itself, by deleting the claim rather than enforcing it. After that change, no sentence in
plugin/governance/asserts a fact about the content of a file it does not name — so there is nothing left to enforce and nothing left that can be false.What that closes, precisely: the doctrine shipping a claim whose truth depends on the content of files it does not name. Previously, a narrowing sentence written in some consumer made the doctrine false (self-refuting governance). Now it makes one consumer wrong — an ordinary content defect that review catches.
Deferred here
The general primitive: how a governance enumeration reaches its consumers without being hand-copied.
A token-denylist-plus-allowlist check was considered and rejected, on this issue's own disqualifying test — does it compare a surface to a SOURCE, or to a LIST? If a LIST, it is another iteration of this class. That mechanism was in fact already built as CHECK 15, hardened across three commits, and reverted at
0fa39cbpending a source-derived successor. Building it again would be the next iteration of the class, not a fix for it.Candidate primitives are unchanged: single-source plus transclusion with a byte-diff gate, or derivation from ground truth.
Site audit — six of eight reported sites are legitimate, not defects
The review loop's site list over-generalised. Audited verdicts, so a future attempt does not "fix" correct prose:
Site Verdict plugin/agents/overlord.md:123defect — bound the role noun to one destination type. Fixed. plugin/agents/overlord.md:121legitimate — a rendered field label, not an enumeration. Fixture-pinned. plugin/agents/cerebrate.md:260-265legitimate — an acting agent that must produce the destination choice; names both, narrows neither. plugin/governance/decision-autonomy.md:44legitimate — disposition 2x2 cell, a different concern; names both. plugin/governance/decision-autonomy.md:69-70legitimate but heavy — a genuine second normative copy of the Recorded Residual mechanics. Not drifted. The strongest DRY smell remaining, and the natural first target if this issue is picked up. CONTEXT.md:179,:218legitimate — glossary. Defining a term requires naming what it enumerates. Not runtime-loaded. docs/adr/0023,docs/adr/0026,CHANGELOG.mdout of scope — historical records. Rewriting them would falsify the record. Bounded impact
Documentation and CI accuracy only. No runtime behavior, no public contract, no data path, no security boundary. Worst case: a runtime-loaded doc restates a rule that later drifts, and review catches it. It cannot fire without a human authoring a new narrowing restatement.
Note on this issue's metadata
The "Linked surfaces" section cites
tools/policy_check.shCHECK 15 andtests/policy/safety-permission-engine-claim-containment.json. Neither exists at HEAD — both were removed by the revert at0fa39cb. Worth correcting so a future reader does not go looking for them.Also noticed, not filed separately
remediation-doctrine.md:202and:248make bounded two-consumer absence claims that no fixture pins. A load-bearing claim without a test is decoration. Deliberately out of scope for the branch above — fold into this issue or split out, whichever suits.
Root cause
Claude Code permission-engine posture — what
allowed-toolsdoes, what a settings allow-rule does, where a local-review provider grant may live — is authored independently on N surfaces and kept correct by restatement. Every containment attempt so far has been an ENUMERATION at a different layer. An enumeration can only be complete with respect to what its author already knew; the next surface, the next paraphrase, or the next unrouted path reopens the class.Four consecutive review iterations on one PR each found a DIFFERENT member. That is the signature of complete-the-known-set, not of a closed guard.
root_class:permission-posture-restated-across-unsynced-surfacesEvidence — four iterations, four members, one branch
3510a2eBash(node /path/to/...)grant into a consumer's COMMITTED settings; a single-quoted heredoc froze the doc placeholder into runtime data98d98db,4c486f5web/functionality.htmlout of sync with each other, and carrying copilot-provider claims not shipped in the releaseset_checkequal mode over 3 named files)0b6b50avalidate.shrouting addedCHECK15_ALLOWLIST)ca7c7b2,02139a2validate.shdid not route; the new AUTHORITY sentence was violated by the very surfaces exempted from the check; the allowlist prefix-matched where its header promised exact; the check advertised FAIL-CLOSED but failed OPEN at EOFmap_pathlegs) and the TRIGGER (one literal token)Corroborating detail: review cycle 4 enumerated three allowlisted surfaces asserting engine behavior. There are four —
plugin/governance/security-policy.md:124also carries the token. Whether:124counts as an "engine behavior assertion" or a structural-containment claim is precisely the judgment an enumerated guard cannot make. The enumeration of the violations was itself incomplete.What a genuinely class-eliminating fix would need
A different primitive than any tried. Every attempt to date compares a surface against a LIST:
safety-local-review-grant-single-home.json)CHECK15_ALLOWLIST)map_pathlegs)CHECK15_TOKEN = 'allowed-tools'), which CHECK 15's own comment concedes is defeated by any paraphraseA closing fix must instead compare a surface against a SOURCE. Two candidate primitives, neither yet attempted:
Distinguishing property to test any future proposal against: does it compare a surface to a SOURCE, or to a LIST? If a LIST, it is another iteration of this class.
Open engine question (blocks part of the cluster)
Whether a skill's
allowed-toolsentry — e.g.Bash(node *)— suppresses the permission prompt for a provider binary invoked via node, including in a subagent context, is NOT established.Recorded as a NON-FINDING in
docs/adr/0010-permission-allowlist-posture.md, which forbids any surface from stating an answer until it is verified empirically against an installed Claude Code binary and the official permissions documentation.Resolving this empirically would remove the standing reason several surfaces keep getting re-edited. It is a prerequisite for candidate primitive 2 on this specific claim, and is independently worth doing.
Linked surfaces
docs/adr/0010-permission-allowlist-posture.md— Findings (authority home + NON-FINDING)docs/adr/0013-decoupled-leaf-pipeline-skills.md:31docs/adr/0018-declarative-workflow-state-machine.md:69plugin/governance/security-policy.md:124,:148README.md,CLAUDE.md,web/functionality.html— the three-surface grant literalplugin/skills/_shared/settings-merge.sh— the permission template and itsNO-PROVIDER-GRANTinvarianttools/policy_check.sh— CHECK 15tools/validate.sh—map_pathtests/policy/safety-local-review-grant-single-home.jsontests/policy/safety-permission-engine-claim-containment.jsonBounded impact — why this is deferred, not fixed now
Blast radius is documentation and CI accuracy only. Across all four iterations:
Worst realistic case today: (a) a doc asserts an unverified engine behavior a contributor acts on and is mildly inconvenienced; (b) a CI guard advertises reach it does not have, so a violation authored in an unrouted doc is caught at push-to-main rather than at PR. Cycle 4 closes (b) for the
.md/.htmlaxis.The fix-now/defer split: cycle 4 landed the four concrete correctness defects because they are bounded, verified green-safe, and each is a few lines. The structural primitive swap is deferred here because it is a design change touching a dozen surfaces plus a build/generation step, it would be the fifth structural attempt inside a patch release, and the impact it mitigates is documentation accuracy — not a defect any consumer can hit.
Residuals carried here, nothing dropped
The cluster members NOT fixed in cycle 4, all recorded above:
allowed-toolspasses green. Accepted P17 non-mechanizable residue.# CHECK15-ALLOWmarker, or outside theCHECK15_ALLOWLISTarray, is invisible to the line-scoped marker-anchored pin.Introduced by PR for
fix/seed-hive-dead-codex-grant-a7f2(plugin 2.40.10).