Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions scripts/repo-standard-conformance.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,13 @@ export function classifyRepo({ repo, archived = false, defaultBranch = "main", f
gaps.push(`rules-unreadable:${rules.reason}`);
} else {
const req = rules.contexts;
const gated = req.length > 0;
const hasTest = req.includes("standard / test");
const hasClaim = req.includes("pr-claim / pr-claim");
// `gated` means a CI context is required. The claim is NOT one: `required-baseline`
// requires `pr-claim / pr-claim` on every default branch, so counting it made
// `gated` 90 of 90 by construction on the first run (#391). The claim is the
// passkey's rung — the human touch #913 keeps outside the CI gate on purpose.
const gated = req.some((c) => c !== "pr-claim / pr-claim");
row.dark_factory = { required_checks: req, rulesets: rules.rulesets, arming_lane: arming, legacy_arming: legacy, gate_ready: hasTest && hasClaim && arming === "auto-merge.yml" };
// Green gates nothing: a caller whose contexts nothing on the default branch
// requires is the fail-open case #913 names — the check runs and decides nothing.
Expand Down Expand Up @@ -363,14 +367,14 @@ export function summarize(rows) {
with_findings: 0,
findings: 0,
gaps: 0,
// the dark factory: any required check at all · the org arming lane present · both plus the standard required
// the dark factory: a CI context required (the claim alone does not count) · the org arming lane present · both plus the standard required
gated: 0,
arming_lane: 0,
gate_ready: 0,
};
for (const r of rows) {
if (r.dark_factory) {
if (r.dark_factory.required_checks?.length) t.gated++;
if (r.dark_factory.required_checks?.some((c) => c !== "pr-claim / pr-claim")) t.gated++;
if (r.dark_factory.arming_lane) t.arming_lane++;
if (r.dark_factory.gate_ready === true) t.gate_ready++;
}
Expand Down
16 changes: 14 additions & 2 deletions scripts/repo-standard-conformance.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,16 @@ test("classifyRepo: dark factory — gated but unarmed is a finding; a caller no
const bare = classifyRepo({ repo: "k", files: [], root: [], rules: { read: true, contexts: [], rulesets: [] } });
assert.deepEqual(bare.findings, ["caller-absent"]);

// THE CLAIM ALONE IS NOT A GATE (#391). required-baseline requires pr-claim on every default
// branch, so a caller whose only required context is the claim is still the fail-open case,
// and a bare repo with only the claim is neither gated nor unarmed-while-gated.
const claimOnly = { read: true, contexts: ["pr-claim / pr-claim"], rulesets: [21805316] };
const claimGated = classifyRepo({ repo: "k", files: [wf(".github/workflows/standard.yml", KEYCARD)], root: RUST_ROOT, run: run("success"), rules: claimOnly });
assert.deepEqual(claimGated.findings, ["gate-absent"], "the claim is the human touch, not CI — nothing requires the standard");
assert.deepEqual(claimGated.dark_factory.required_checks, ["pr-claim / pr-claim"], "the claim is still LISTED; it just does not count as a gate");
const bareClaim = classifyRepo({ repo: "k", files: [], root: [], rules: claimOnly });
assert.deepEqual(bareClaim.findings, ["caller-absent"], "no caller and no CI gate: not arming-lane-absent — there is no green to wait on");

// Only the legacy lane: still unarmed — its precondition is false by construction (.github-private#929).
const legacy = classifyRepo({ repo: "k", files: [wf(".github/workflows/standard.yml", KEYCARD), wf(".github/workflows/dependabot-auto-merge.yml", "name: dependabot-auto-merge\non: pull_request\n")], root: RUST_ROOT, run: run("success"), rules: RULES_37 });
assert.deepEqual(legacy.findings, ["arming-lane-absent"]);
Expand Down Expand Up @@ -433,12 +443,14 @@ test("summarize + renderSummary: the dark-factory totals count what was measured
classifyRepo({ repo: "ready", files: [wf(".github/workflows/standard.yml", KEYCARD), wf(".github/workflows/auto-merge.yml", AUTO_MERGE)], root: RUST_ROOT, run: run("success"), rules: RULES_37 }),
classifyRepo({ repo: "gated", files: [wf(".github/workflows/standard.yml", KEYCARD)], root: RUST_ROOT, run: run("success"), rules: RULES_37 }),
classifyRepo({ repo: "unmeasured", files: [wf(".github/workflows/standard.yml", KEYCARD)], root: RUST_ROOT, run: run("success") }),
// Only the org baseline's claim is required: counted in the row, not as gated (#391).
classifyRepo({ repo: "claim-only", files: [], root: [], rules: { read: true, contexts: ["pr-claim / pr-claim"], rulesets: [21805316] } }),
];
const t = summarize(rows);
assert.equal(t.gated, 2);
assert.equal(t.gated, 2, "the claim-only repo is not gated");
assert.equal(t.arming_lane, 1);
assert.equal(t.gate_ready, 1);
const snap = buildSnapshot({ now: "2026-09-08T12:00:00Z", rows, denominator: { public_repos: 3, enumerated: 3, verified: true, archived: 0, rows: 3 }, fleet: { unavailable: "x" }, standard: { head_sha: null, selftest: { state: "none" } }, strict: false });
const snap = buildSnapshot({ now: "2026-09-08T12:00:00Z", rows, denominator: { public_repos: 4, enumerated: 4, verified: true, archived: 0, rows: 4 }, fleet: { unavailable: "x" }, standard: { head_sha: null, selftest: { state: "none" } }, strict: false });
assert.match(renderSummary(snap), /\| gated \/ arming lane \/ dark-factory ready \| 2 \/ 1 \/ 1 \|/);
});

Expand Down
Loading