The repo's rulesets are recorded and held to the Lenses, and code scanning's threshold with them (#62, #95)

Merged
DO
5d ago
+1697-12

The Factory repo's two rulesets are now recorded as data. A check holds them to the Lenses, and a hand-run tool compares them with what GitHub has live. Code scanning's threshold is recorded with them.

What changes

  • config/rulesets.json records two rulesets, as GitHub answered the Maintainer's read of them on 2026-10-21. Each rule is keyed by its type.

    • main (24886320): deletion, non-fast-forward, the pull-request rule, verify pinned to GitHub Actions (15368), the merge queue, and CodeQL's code-scanning rule.
    • factory release tags (25183656): creating, updating, deleting or force-pushing a factory/* tag is refused to everyone but the repository's admin role.
  • schemas/rulesets.schema.json holds as a constant what a ruling fixed:

    • no bypass on main;
    • required_review_thread_resolution: true, applied 2026-10-18;
    • squash merges and the merge queue;
    • a required check pinned to its App;
    • security_alerts_threshold: all (#95, set 2026-10-20);
    • the tag rules and their bypass, which the admin role alone holds until P-30 names the release identity.

    Nine counter-examples in examples/invalid/rulesets.*.json, each wrong in one way.

  • tools/check_rulesets.py is a new check run by make verify. It holds the values written both here and in config/defaults.json:

    • each check main requires is a deterministic Lens's check, pinned to that Lens's app_id;
    • verify is among them;
    • each code-scanning tool is a Lens that no workflow runs.
  • tools/rulesets_drift.sh compares the live rulesets with the file. It is run by hand, never in CI, and calls tools/rulesets_drift.py.

    • It reads through whoever is logged in to gh and mints nothing.
    • It writes each answer outside the repo, refusing a scratch directory that resolves inside it.
    • It redacts each answer with tools/redact_trial.py and deletes the raw one.
    • A ruleset is known by its name and id together, so an org ruleset that shares a recorded name is reported as drift.
    • Run on the final head: no drift.
  • The design names the threshold where it describes the deterministic Lenses (#95's third box). The §9.9 gap row now says what is set and what no trial has seen. It also says the merge queue's group stays open: the code-scanning rule does not hold it, as the oversight session saw on 2026-10-21.

  • Decision rows. One Boundaries row on decision 0014: CodeQL's threshold is stricter than the org's and needs no exception. One on decision 0020: the main ruleset requires no review, as the file records it. No new decision record: no rule changes.

Not done here

  • The live check in CI. CI fetches nothing, so the drift check stays manual until controller/ exists (the 2026-10-19 plan review).
  • Requiring the machine user's approval. This ruleset change stays the Maintainer's, with #89.

Reviews

  • Export verify. make verify passed on a fresh git archive export with PATH=/usr/bin:/bin SIM_SEEDS=40, on both heads.
  • Mutants. No full python3 -m tools.mutants ran locally. All 14 new mutants were applied by hand on scratch copies, and each was killed by its named test.
  • Claude review (fresh context). One major finding and five minor ones, all fixed in the second commit. The major one: two live rulesets that share a name hid one of them from the drift check.
  • Security pass. No blocker and no major finding. Six minor findings, all fixed: the scratch-directory guard now resolves links, the cleanup runs on a signal under dash, the test proves the stand-in gh is the one that answers, and the admin role and the gap row are worded no stronger than the data holds.
  • AGY (Gemini 3.8 Flash High). No findings.
  • Muse. Not run (Maintainer, 2026-10-20: Kernel/hook only).

Closes #62, Closes #95

🤖 Generated with Claude Code