instar 1.3.977 → 1.3.978
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/StageTransitionValidator.d.ts +9 -0
- package/dist/core/StageTransitionValidator.d.ts.map +1 -1
- package/dist/core/StageTransitionValidator.js +55 -2
- package/dist/core/StageTransitionValidator.js.map +1 -1
- package/dist/core/StandardsEnforcementAuditor.d.ts +64 -3
- package/dist/core/StandardsEnforcementAuditor.d.ts.map +1 -1
- package/dist/core/StandardsEnforcementAuditor.js +58 -1
- package/dist/core/StandardsEnforcementAuditor.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +5 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +46 -46
- package/upgrades/1.3.978.md +88 -0
- package/upgrades/side-effects/assessment-confidence-unverified.md +125 -0
- package/upgrades/side-effects/ci-green-latest-run.md +119 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Side-Effects Review — CI greenness must use the LATEST run per check
|
|
2
|
+
|
|
3
|
+
## Summary of the change
|
|
4
|
+
|
|
5
|
+
`StageTransitionValidator.ciIsGreen` iterated EVERY entry in GitHub's `statusCheckRollup` and refused
|
|
6
|
+
on any non-success conclusion. GitHub returns every run of every check, **including superseded ones**,
|
|
7
|
+
so a check that failed and was then re-run to green appears twice and the failed entry never
|
|
8
|
+
disappears.
|
|
9
|
+
|
|
10
|
+
Verified on the live PR #1641 (merged legitimately at 02:07:44Z):
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
rollup entries: 31 | names appearing more than once: eli16 (2), UX impact declaration (2),
|
|
14
|
+
UX assertions (messaging E2E) (2), UX merge safety (2)
|
|
15
|
+
eli16 COMPLETED FAILURE completedAt 2026-07-26T01:44:01Z
|
|
16
|
+
eli16 COMPLETED SUCCESS completedAt 2026-07-26T01:45:32Z
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Branch protection merged the PR because it evaluates each check's CURRENT state. The tracker refused
|
|
20
|
+
it with `CI_NOT_GREEN` because it evaluated the stale run too — so **no correct merge whose CI had
|
|
21
|
+
ever been red could ever be recorded.**
|
|
22
|
+
|
|
23
|
+
Fix: `latestRunPerCheck` collapses the rollup to the newest run per check name (by `completedAt`,
|
|
24
|
+
falling back to `startedAt`) before `ciIsGreen` evaluates it. `GhPrView.statusCheckRollup`'s type
|
|
25
|
+
gains the `name`/`context`/`startedAt`/`completedAt` fields the dedupe needs; the route's existing
|
|
26
|
+
`gh pr view --json …statusCheckRollup` already returns them, so no query change.
|
|
27
|
+
|
|
28
|
+
## Decision-point inventory
|
|
29
|
+
|
|
30
|
+
- `ciIsGreen` feeds the `building → merged` GATE. This change makes the gate ACCEPT a case it
|
|
31
|
+
previously refused, so it is the direction that needs the most scrutiny — see §1/§2.
|
|
32
|
+
- No other decision point, hook, sentinel, reaper, scheduler, migration or config surface is touched.
|
|
33
|
+
- `latestRunPerCheck` is a pure function over the rollup array.
|
|
34
|
+
|
|
35
|
+
## 1. Over-block
|
|
36
|
+
|
|
37
|
+
Strictly reduced, and that is the intent: a merged PR with a superseded failure is now recordable.
|
|
38
|
+
Nothing that used to pass now fails.
|
|
39
|
+
|
|
40
|
+
The one preserved refusal worth naming: a **latest** run that is still in flight (`status` not
|
|
41
|
+
`COMPLETED`) is still not green, asserted in its own test, so the dedupe cannot be used to skip a
|
|
42
|
+
pending check by pairing it with an older success.
|
|
43
|
+
|
|
44
|
+
## 2. Under-block — the part that matters
|
|
45
|
+
|
|
46
|
+
A dedupe is a potential laundering mechanism, so three properties are enforced and each is tested:
|
|
47
|
+
|
|
48
|
+
1. **Latest is authoritative in BOTH directions.** SUCCESS followed by a later FAILURE is NOT green.
|
|
49
|
+
Without this, "keep the latest" would let anyone bury a red by arranging run order.
|
|
50
|
+
2. **Unorderable runs FAIL CLOSED.** Equal or missing timestamps ⇒ the FAILING run wins. An undatable
|
|
51
|
+
success must never mask a red. (Three variants tested: no timestamps either way round, and equal
|
|
52
|
+
timestamps.)
|
|
53
|
+
3. **Unnamed entries are keyed individually.** Bare status contexts with neither `name` nor `context`
|
|
54
|
+
are never collapsed into each other, so a failing one cannot be replaced by a passing one.
|
|
55
|
+
|
|
56
|
+
Residual, stated: the dedupe trusts GitHub's timestamps. A platform that reported them wrongly could
|
|
57
|
+
mis-order runs — but the fail-closed rule bounds the damage to the unorderable case, and the
|
|
58
|
+
alternative (ignoring the rollup entirely, or trusting merge status alone) is strictly weaker.
|
|
59
|
+
|
|
60
|
+
## 3. Blast radius
|
|
61
|
+
|
|
62
|
+
One function plus one type widening in `src/core/StageTransitionValidator.ts`. `ciIsGreen` is private
|
|
63
|
+
to that module (`grep` for callers: only the `to === 'merged'` branch). The route passes the rollup
|
|
64
|
+
through unchanged. No response shape changes; no new code.
|
|
65
|
+
|
|
66
|
+
## 4. Rollback plan
|
|
67
|
+
|
|
68
|
+
Single-commit revert; no state, config, migration or artifact. Reverting restores the previous
|
|
69
|
+
(over-refusing) behaviour immediately.
|
|
70
|
+
|
|
71
|
+
## 5. Test coverage
|
|
72
|
+
|
|
73
|
+
`tests/unit/StageTransitionValidator.test.ts` (46 pass), five new cases: the live #1641 shape
|
|
74
|
+
(superseded FAILURE + later SUCCESS ⇒ green); the reverse (SUCCESS + later FAILURE ⇒ not green);
|
|
75
|
+
unorderable-runs-fail-closed across three variants; unnamed entries not collapsed; and an in-flight
|
|
76
|
+
latest run still not green.
|
|
77
|
+
|
|
78
|
+
No integration/e2e tier added: this is a pure predicate inside an existing gate whose route path is
|
|
79
|
+
already covered, and the decisive evidence is the end-to-end check below.
|
|
80
|
+
|
|
81
|
+
## 6. End-to-end evidence, gathered BEFORE the fix and after
|
|
82
|
+
|
|
83
|
+
The validator's real logic was run against the two real merged PRs with the route's exact helper
|
|
84
|
+
wiring:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
before: item 1 (PR 1641) -> CI_NOT_GREEN
|
|
88
|
+
item 2 (PR 1644) -> MERGE_BASE_UNVERIFIABLE (stale local ref; resolved by git fetch)
|
|
89
|
+
after: item 1 (PR 1641) -> WOULD SUCCEED
|
|
90
|
+
item 2 (PR 1644) -> WOULD SUCCEED
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Note the second line: the `MERGE_BASE_UNVERIFIABLE` code shipped hours earlier in #1643 did its job
|
|
94
|
+
here on a real case — an unknown local SHA reported as *unverifiable* rather than as a false "not an
|
|
95
|
+
ancestor".
|
|
96
|
+
|
|
97
|
+
## 7. Third instance in one code path, in one evening
|
|
98
|
+
|
|
99
|
+
The completion-recording path has now yielded three independent defects tonight, each the same
|
|
100
|
+
underlying error:
|
|
101
|
+
|
|
102
|
+
| | defect | what it reported |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| #1640 | the `gh` binary was unreachable | a flat refusal |
|
|
105
|
+
| #1643 | a SourceTreeGuard refusal was caught and returned as `false` | "the merge is not on main" |
|
|
106
|
+
| this | a superseded check run read as the check's current state | "CI is not green" |
|
|
107
|
+
|
|
108
|
+
All three presented identically from outside — an opaque refusal — and each masked the next. That is
|
|
109
|
+
worth recording as a property of the path rather than three coincidences: **a code path whose failures
|
|
110
|
+
all render as one indistinguishable refusal will hide its own defects in series.** The general fix is
|
|
111
|
+
the one #1643 started: distinguish "could not determine" from "determined, and the answer is no".
|
|
112
|
+
|
|
113
|
+
## 8. How it was found
|
|
114
|
+
|
|
115
|
+
By refusing to assert. I was about to report that recording would work once the server updated, and
|
|
116
|
+
instead ran the validator's own logic against the real PRs. It returned two different refusals. That
|
|
117
|
+
cost two minutes and avoided discovering both blockers serially, each appearing to be a fresh
|
|
118
|
+
mystery. Habit worth keeping: **before claiming something will work once a dependency lands, execute
|
|
119
|
+
its logic now and read what it returns.**
|