|
89 | 89 | // |
90 | 90 | // A battery of assertions over a script that is already correct is green on day |
91 | 91 | // one and green forever, including the day someone deletes the thing it |
92 | | -// guards. `--self-test` mutates the extracted source -- drop the `setFailed`, |
93 | | -// stop collecting failed keys, `break` out of the loop instead of isolating, |
94 | | -// disable the same-repo skip, narrow the keyword set, collapse the |
95 | | -// already-closed branch, strip the backlink marker, drop the triage guard, |
96 | | -// post blind when the comment listing is refused, stop recording which half |
97 | | -// was lost -- and requires the battery to go RED for each, naming the scenario |
98 | | -// it expects. Each mutation also asserts its own anchor was PRESENT before |
99 | | -// substituting: a mutation that silently matched nothing would leave the |
100 | | -// battery green and read exactly like a passing self-test. |
| 92 | +// guards. `--self-test` mutates the extracted source and requires the battery |
| 93 | +// to go RED for each mutation, naming the scenario it expects. |
| 94 | +// |
| 95 | +// What the mutations cover is written here as CLASSES, and their number is not |
| 96 | +// written here at all (#9917). The list this paragraph used to carry named ten |
| 97 | +// of them and was never updated again; the same habit in lint.yml's step |
| 98 | +// comment was counting seven when there were fifteen. A prose class survives |
| 99 | +// the next mutation being added, a hand-count does not, and `--self-test` |
| 100 | +// prints its own total on every run. The classes: a verdict DOWNGRADED (red |
| 101 | +// becomes a warning, or an info line nothing annotates), the loop's |
| 102 | +// BOOKKEEPING deleted (which keys failed, which half was lost, which target |
| 103 | +// was refused -- each one lets a run report green about work it did not do), |
| 104 | +// ISOLATION abandoned mid-loop, the TARGET PARSE narrowed (the keyword set, |
| 105 | +// the optional colon, the same-repo skip), a GUARD removed (already-closed, |
| 106 | +// triage, pull-request), and IDEMPOTENCY eroded (the backlink marker dropped, |
| 107 | +// or a blind post where a skip belongs). |
| 108 | +// |
| 109 | +// Each mutation also asserts its own anchor was PRESENT before substituting: a |
| 110 | +// mutation that silently matched nothing would leave the battery green and |
| 111 | +// read exactly like a passing self-test. |
101 | 112 | // |
102 | 113 | // One scenario (L11) is driven TWICE, the second run's world built out of the |
103 | 114 | // first run's calls. Re-run idempotency is a property of the PAIR, and a |
@@ -200,9 +211,14 @@ function makeDoubles({ body, token, issues = {}, prCommentError = null, summaryE |
200 | 211 | calls.get.push(`${owner}/${repo}#${n}`); |
201 | 212 | const t = target(owner, repo, n); |
202 | 213 | if (t.getError) throw t.getError; |
203 | | - // `state_reason` is nullable on a real closed issue -- objectui#4478 |
204 | | - // answers `null` from this very endpoint -- so the default models that |
205 | | - // rather than inventing a value the API does not promise. |
| 214 | + // `state_reason` is nullable BY CONTRACT on this endpoint -- the API |
| 215 | + // states a reason when it has one and promises nothing otherwise -- so |
| 216 | + // the default models `null` rather than inventing a value the API does |
| 217 | + // not promise. Modelled, not sampled, and that distinction is load |
| 218 | + // bearing here: the citation that stood on this line named a foreign |
| 219 | + // number as a closed issue answering `null` and it was a pull request |
| 220 | + // (#9917). L9 carries the reason a sampled specimen is the wrong kind of |
| 221 | + // evidence for THIS default in particular. |
206 | 222 | // |
207 | 223 | // `pull_request` is how the same endpoint says the number is a PULL |
208 | 224 | // REQUEST, and it is modelled as ABSENCE rather than as `undefined` |
@@ -667,9 +683,25 @@ export const SCENARIOS = [ |
667 | 683 | scenario: () => ({ |
668 | 684 | body: MIXED_BODY, |
669 | 685 | token: 'pat', |
670 | | - // Measured, not invented: objectstack-ai/objectui#4478 is closed and |
671 | | - // answers `state_reason: null` from `issues.get`. A fix that keys on the |
672 | | - // reason has to say what null means, and this pins the answer. |
| 686 | + // MODELLED FROM THE CONTRACT, deliberately not sampled. `issues.get` |
| 687 | + // declares `state_reason` nullable and promises no reason on a closed |
| 688 | + // issue, so a fix that keys on the reason has to say what null means, and |
| 689 | + // this pins the answer -- the shipped script's own reading of it, "no |
| 690 | + // objection recorded", is stated on the already-closed branch of |
| 691 | + // .github/workflows/cross-repo-issue-closer.yml, which is the subject |
| 692 | + // this scenario drives. |
| 693 | + // |
| 694 | + // A SAMPLED citation stood here and was withdrawn (#9917): it named |
| 695 | + // objectui#4478 as a closed issue answering `null`, and that number is a |
| 696 | + // PULL REQUEST. Read that as the trap it is rather than as one bad |
| 697 | + // lookup -- the objects that most dependably answer `state: 'closed'` |
| 698 | + // with `state_reason: null` ARE pull requests, so hunting a specimen for |
| 699 | + // this fixture walks straight into L13's subject, and the file then |
| 700 | + // offers ONE observation as evidence for two opposite scenarios. L9 and |
| 701 | + // L13 are kept on separate footings on purpose: L9 models the contract |
| 702 | + // and cites nothing, L13 cites a measured pull request |
| 703 | + // (objectstack-ai/objectstack#9143). Do not "improve" this by finding a |
| 704 | + // real issue to name. |
673 | 705 | issues: { [CLOSED_TARGET]: { state: 'closed', stateReason: null, comments: [] } }, |
674 | 706 | }), |
675 | 707 | check: (r, t) => [ |
@@ -845,10 +877,13 @@ export const SCENARIOS = [ |
845 | 877 | token: 'pat', |
846 | 878 | // A merged pull request answers `state: 'closed'` with |
847 | 879 | // `state_reason: null` from the issues endpoint -- measured on |
848 | | - // objectstack-ai/objectstack#9143. That is the very fixture L9 uses to |
849 | | - // prove a closed ISSUE gets its backlink, which makes this scenario the |
850 | | - // ORDERING test: a `pull_request` guard placed after the state branch |
851 | | - // would sail past it and comment on somebody else's pull request. |
| 880 | + // objectstack-ai/objectstack#9143. That is the very fixture SHAPE L9 uses |
| 881 | + // to prove a closed ISSUE gets its backlink -- the shape, not the |
| 882 | + // observation: L9 models its null from the contract and cites no object, |
| 883 | + // precisely so that this measured pull request stays evidence for THIS |
| 884 | + // scenario only (#9917). The shared shape is what makes this the ORDERING |
| 885 | + // test: a `pull_request` guard placed after the state branch would sail |
| 886 | + // past it and comment on somebody else's pull request. |
852 | 887 | issues: { |
853 | 888 | [PR_TARGET]: { |
854 | 889 | state: 'closed', |
|
0 commit comments