Skip to content

policy-sign does not cover the risk-tier data: risk-rules.json and its test can change without a human signature - #3092

Open
HugoXK wants to merge 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-policy-sign-covers-tier-data
Open

HugoXK wants to merge 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-policy-sign-covers-tier-data

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3091

Owner: Dev-1
Reviewer: Birfy
Written-by: Dev-1
Bot: FlyBot Dev-1
Claim-Id: da93569a1a5948fbb832b05330c7d0cb
Tier: T2 (policy/ is not a T3 path, so both files fall to the unknown-path default; policy-sign applies because this PR edits a gateFile, not because of the tier)
Base: 3e901fa
Verified commit: 9475ca45976ae6ac0ec72b7f5e781df1931cf1c4
Tree: 421f0618619752e4e021e42edee456a7dde35450
Pushed head: e19cb05

What was wrong

policy/gates.json listed the tier checker .github/scripts/risk-gate.mjs, but not the data it reads and not the test that pins where each pattern applies. So .github/agent/risk-rules.json — the file that decides what counts as T3 for every later pull request in this repository — could be rewritten by a pull request that carries no human signature, and .github/scripts/risk-gate.test.mjs could lose the pin that keeps each pattern on the right side of the test boundary.

#3089 is the live example: it moved new Function\( and \beval\( between the two lists, which changes the tier every future pull request is given, and it landed on 3e901fab at 2026-11-15T00:47:11Z with policy-sign green, because gateFiles() returned [] for its two files. The change was right; the signature surface simply did not cover it.

The change

Two entries in policy/gates.json gateFiles:

  • .github/agent/risk-rules.json
  • .github/scripts/risk-gate.test.mjs

Nothing else: policy/ is already a directory entry (so policy/gates.json cannot change unsigned — this change included), no other file class is added, policy-sign.mjs, risk-gate.mjs and risk-rules.json themselves are untouched, and the list is not reordered.

Red first, at 3e901fab, twice

Four cases added to policy/gates.test.mjs with policy/gates.json untouched: node --test policy/gates.test.mjs → 7 tests, 4 pass, 3 fail, exit 1, both runs. The three failures are the acceptance cases, the fourth (the controls) passes on purpose:

not ok 4 - #3091: the risk-tier data is a gate file
  error: |-
    Expected values to be strictly deep-equal:
    + actual - expected

    + []
    - [
    -   '.github/agent/risk-rules.json'
    - ]

not ok 5 - #3091: the test that pins where each tier pattern applies is a gate file
  ... [] vs [ '.github/scripts/risk-gate.test.mjs' ]

not ok 6 - #3091: a change to the tier data with no signature line is refused, and one that names a comment is accepted
  error: |-
    Expected values to be strictly equal:

    true !== false

red-1.log sha256 4a63c0e1f7034120, red-2.log sha256 9bf83f2867e48f77.

The cases ask the checker itself — gateFiles() and judge() imported from .github/scripts/policy-sign.mjs — rather than re-reading policy.gateFiles in the test: a second reading could agree with the list while disagreeing with the gate the workflow runs. Case 6 drives judge() to end to end: the tier-data change with a body carrying no Policy-sign: line is refused with a reason naming risk-rules.json and the missing line, and the same change with Policy-sign: 4242 naming a signable comment is accepted.

Green after, twice

Same command with the two entries added → 7 tests, 7 pass, 0 fail, exit 0, both runs (green-final-1.log sha256 31a8be21908962b9, green-final-2.log sha256 902de10ef78277e4).

The control case stayed green through both runs: .github/scripts/risk-gate.mjs is still a gate change, and an ordinary client file (plugins/social/client.js) is not.

What this does to the queue

This pull request changes policy/gates.json, which is itself a gate file, so its own policy-sign will be red until a person writes Policy-sign: <comment id> into this body, naming a comment by someone other than the author that also names this tip. That is the intended shape — the owner keeps the veto on widening the signature surface — and a bot account cannot sign, so this body carries no such line. enqueue-guard will be red for the usual no Verifier report names the tip.

Gates, at 9475ca45 (tree 421f0618)

TZ=UTC npm run verify → exit 0: core 3248, social 684, tree 149, timeline 172, workflow 535, project 147, gate/policy 685, interop 37, live-e2e 34 — 0 fail everywhere (sha256 of the log 744fb5e903ffdb03).

Also run on this tree: cd core && ARCH_DOCS_BASE=3e901fab… node scripts/check-arch-docs.mjs — arch-docs: OK (2 changed file(s)); node .github/scripts/changelog.mjs check — 1 fragment well formed; node .github/scripts/testing-layers.mjs --check — every test file and every workflow classified; node .github/scripts/check-mojibake.mjs --check — 0 occurrences of U+FFFD in 1705 text files.

Files

2 files, +39/−0:

  • policy/gates.json (+2) — the two gateFiles entries
  • policy/gates.test.mjs (+37) — the four cases

Checkpoint

{
  "pr": 3092,
  "claim": "Dev-1",
  "issue": 3091,
  "summary": "policy/gates.json listed the tier checker but not the risk-tier data it reads, so a pull request could rewrite the file that decides what counts as T3 for every later pull request with no human signature. risk-rules.json and risk-gate.test.mjs are now gate files; policy/ stays a directory entry and nothing else is added. The four cases ask the checker itself through gateFiles() and judge(), so they cannot agree with the list while disagreeing with the workflow. One case is a control on purpose: the tier checker is still a gate file and an ordinary client file is not.",
  "files": [
    "policy/gates.json",
    "policy/gates.test.mjs"
  ],
  "checks": { "ran": "TZ=UTC npm run verify; node --test policy/gates.test.mjs (red x2 before, green x2 after); arch-docs, changelog, testing-layers, check-mojibake", "result": "green", "at": "e19cb051888267b5e91f00ffcab13250a1df862a" },
  "next": "a human Policy-sign on this body after a Verdict; the signature line is not mine to write"
}

…iles

risk-gate.mjs was a gate file while the rules it reads and the test that pins where each pattern applies were not, so a pull request could rewrite the tier every later pull request gets with no human signature. policy/ stays a directory entry; the two files are added individually.
@HugoXK

HugoXK commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@Birfy verdict request — tip e19cb051888267b5e91f00ffcab13250a1df862a (T2) Fixes #3091.

What it changes: policy/gates.json (+2) adds two gateFiles entries, .github/agent/risk-rules.json and .github/scripts/risk-gate.test.mjs; policy/gates.test.mjs (+37) adds four cases that ask the checker itself — gateFiles() and judge() imported from .github/scripts/policy-sign.mjs, not a second copy of the list.

Why: #3089 landed on 3e901fab with policy-sign green while it moved new Function\( and \beval\( between the two test-boundary lists — so the file that decides what counts as T3 for every later pull request could change with no human signature. The two entries are the whole change: policy/ stays a directory entry, no other file class is added, risk-gate.mjs / risk-rules.json / policy-sign.mjs are untouched, and the list is not reordered.

Red first ×2 at 3e901fab: node --test policy/gates.test.mjs → 7 tests, 4 pass / 3 fail (two empty gateFiles() results, and judge() returning ok: true for an unsigned tier-data change). Green ×2 with the two entries: 7/7, 0 fail. TZ=UTC npm run verify exit 0 at 9475ca45 / tree 421f0618 (gate/policy 685/685).

Checks at this tip: policy-sign red is expected — this PR edits policy/gates.json itself, so it needs a person's Policy-sign: naming this tip, and that line is not mine to write. enqueue-guard red is the missing Verdict this comment asks for.

Correction to this comment's first line: the computed tier is T2, not T3 — policy/ is not a T3 path and both files fall to the unknown-path default (risk-tier: T2; basis: policy/gates.json=T2, policy/gates.test.mjs=T2). The request itself stands unchanged: the policy-sign requirement comes from gateFiles, not from the tier.

@github-actions github-actions Bot added the risk:T2 Risk tier T2 (host/plugin code or an unnamed path), set by the controller from the diff label Nov 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:T2 Risk tier T2 (host/plugin code or an unnamed path), set by the controller from the diff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

policy-sign does not cover the risk-tier data: risk-rules.json and its test can change without a human signature

1 participant