refresh: ask who authorised a run, not whether it is CI
Both overrides stand in for a person deciding something, and the gate that enforced it asked the coarser question. `isCi()` got the case that matters wrong: a `workflow_dispatch` sets CI=true, so somebody clicking "Run workflow" was refused exactly as the cron was, which made both flags unreachable from the workflow at all. A dispatch is a person, and a better-evidenced one than a local shell — GitHub records which one, and the run log keeps it. `runAttendance` draws the line where it belongs: a local shell is a person, a dispatch is a person and names the actor, and a schedule — or any other runner event, erring that way on purpose — is not. The run now prints which override was used and who authorised it, because a local shell leaves no trace anybody else can read. The schedule stays refused, and for the robots override that is not ceremony. From an address that gets a challenge we never receive robots.txt at all, so a cron standing on the recorded permission has no way to notice the host withdrawing it, and that permission has no expiry. The challenge-or-refusal narrowing does not close this: a plain 403 still stops us, but a robots.txt edited to disallow us would be invisible, because a challenge arrives instead of a file. A person re-reading it is the only thing that ever re-validates it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d0b0f2e00c
commit
2fb944135d
@@ -8,6 +8,7 @@ import {
|
||||
DEFAULT_HOST_GAP_MS,
|
||||
outputs,
|
||||
parseArgs,
|
||||
runAttendance,
|
||||
runRefresh,
|
||||
stepSummary,
|
||||
type RefreshOptions,
|
||||
@@ -1076,3 +1077,65 @@ describe("flags", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The override gates ask who authorised the run, not whether a runner is
|
||||
* present. That distinction is the whole reason the workflow can offer these
|
||||
* toggles at all: a `workflow_dispatch` sets `CI=true`, so the old `isCi()`
|
||||
* check refused a human clicking "Run workflow" exactly as it refused the cron.
|
||||
*
|
||||
* What must not regress is the other half — a *schedule* must never be able to
|
||||
* assert either override, because an override on every cycle is the new default
|
||||
* with extra steps, and for the robots one a cron cannot re-read the permission
|
||||
* it would be standing on.
|
||||
*/
|
||||
describe("runAttendance", () => {
|
||||
test("a local shell is a person", () => {
|
||||
const a = runAttendance({});
|
||||
expect(a.attended).toBe(true);
|
||||
expect(a.attended && a.by).toBe("a local shell");
|
||||
});
|
||||
|
||||
test("a scheduled run is not, and says so", () => {
|
||||
const a = runAttendance({ CI: "true", GITHUB_EVENT_NAME: "schedule" });
|
||||
expect(a.attended).toBe(false);
|
||||
expect(!a.attended && a.why).toBe("schedule");
|
||||
});
|
||||
|
||||
test("a manual dispatch is a person, and names the actor", () => {
|
||||
const a = runAttendance({
|
||||
CI: "true",
|
||||
GITHUB_ACTIONS: "true",
|
||||
GITHUB_EVENT_NAME: "workflow_dispatch",
|
||||
GITHUB_ACTOR: "StereotypicalCat",
|
||||
});
|
||||
expect(a.attended).toBe(true);
|
||||
expect(a.attended && a.by).toBe("a manual dispatch by StereotypicalCat");
|
||||
});
|
||||
|
||||
test("a dispatch with no actor recorded is still a person", () => {
|
||||
const a = runAttendance({ CI: "true", GITHUB_EVENT_NAME: "workflow_dispatch" });
|
||||
expect(a.attended).toBe(true);
|
||||
expect(a.attended && a.by).toBe("a manual dispatch");
|
||||
});
|
||||
|
||||
test("any other runner event counts as unattended", () => {
|
||||
// Erring towards "no person" — a push- or repository_dispatch-triggered run
|
||||
// gets no overrides either, so a schedule cannot be dressed up as one.
|
||||
for (const event of ["push", "repository_dispatch", "pull_request", ""]) {
|
||||
const a = runAttendance({ CI: "true", GITHUB_EVENT_NAME: event });
|
||||
expect(`${event}: ${a.attended}`).toBe(`${event}: false`);
|
||||
}
|
||||
});
|
||||
|
||||
test("GITHUB_ACTIONS alone is enough to count as a runner", () => {
|
||||
const a = runAttendance({ GITHUB_ACTIONS: "true", GITHUB_EVENT_NAME: "schedule" });
|
||||
expect(a.attended).toBe(false);
|
||||
});
|
||||
|
||||
test("an empty CI variable is not a runner", () => {
|
||||
// `CI=` is how a shell unsets it in practice; treating it as a runner would
|
||||
// refuse a person their own flags.
|
||||
expect(runAttendance({ CI: "" }).attended).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user