Repository navigation
Commit 614fdec
fix(pm): ci-failure reads an aggregator's members from its own run's rows, so a later cancelled same-name row in another run no longer hides the red member (#23337)
Fixes #23305
Clause-②: no (internal PM tooling)
## What changed
`scripts/pm/ci-failure.mjs` only, on the card's predicted landing point
(`walkInto` and its `memberLanes` call). No `.github/workflows/**`.
`latestPerName` is untouched, and the sha-level failing list is still
read from its `kept` set.
- **`checkRunListing(checkRuns)`**, new, next to `latestPerName`. It is
the walk's one reading of a sha's check-runs: `{ all, kept, superseded
}`. `kept` and `superseded` are exactly what `latestPerName(all)`
returns.
- **`memberLanes(aggregator, listing, { runId })`** now takes that whole
listing instead of a flat row list:
- It reads the members from `listing.all`, restricted to the
aggregator's own run.
- Inside that run, the newest row per name still wins (`latestPerName`
over the run's rows). A job re-run within the run is still read at its
latest attempt.
- `listing.kept` is used for one thing only: telling whether a member's
row is the one the report lists above.
- **`walkInto`** builds the listing once and passes it to `memberLanes`.
The failing list, `pending` and `total` are still read from `kept`.
- **`renderRosterVerdict`**: a red member can be superseded on the sha
by a later same-name row from another run. Its row is then not in the
failing list, so "its own row is above" would point the reader at a row
the report never prints. My change makes that branch reachable. That
line now names the member's check-run, the row that superseded it, and
its job URL. Every other lane line is unchanged.
**Why the listing goes into `memberLanes`, instead of only swapping
`kept` for `all` at the call site.** The walk only does network work,
and the offline self-test never runs it. A fix made only at the call
site could be reverted with the self-test still green. I measured
exactly that, as ablation A4 below. Now the row set is chosen inside the
pure function, so reading the wrong set turns the self-test red (A1).
Passing the walk's `kept` array back in is a loud `TypeError`, not a
silent "NONE".
## Root cause, measured
On `main` at `cc14657ede`, `node scripts/pm/ci-failure.mjs --run
38411694784` exits 2 and prints `NONE of its members was found among
this sha's check-runs` for `Lint & Repo Gates`. That reproduces the
card.
I ran a read-only probe over the sha's 106 check-runs (`GET
/commits/042ef51530/check-runs`, 2 pages). It confirms the mechanism the
card predicted, and the effect is wider than one row:
- All 8 `Lint ·` member names have 3 rows each.
- For **all 8**, `latestPerName` keeps the row from run `38411617233`.
Those rows are cancelled and started at 14:15:56Z. It does not keep the
rows from the asked run `38411694784`, which started at 14:14:50Z.
- The kept `Lint & Repo Gates` row is the asked run's (`115353240189`).
- So the run-id filter dropped every kept member row, and `matched` came
back 0.
The red member's three rows, `Lint · PM dispatch-gates`:
- `115338637229`: success, run `38409754946`.
- **`115345404875`: failure, run `38411694784`.**
- `115345950501`: cancelled, run `38411617233`. This is the one `kept`
holds.
## Before and after, `node scripts/pm/ci-failure.mjs --run 38411694784`
- Before: `main` `cc14657ede`. After: this branch at `e29bf95658`.
- Exit 2 / UNDETERMINED both times.
- The sha-level reading is byte-identical: `49 check-run(s) after
grouping by name (106 rows on the sha, 57 superseded)`, the same single
failing row, and the same verdict line.
- `diff` of the two outputs touches only the roster block. Before:
```text
NONE of its members was found among this sha's check-runs (renamed, or absent from
this tree?), so no row here carries the answer. Open the job URL above.
```
After:
```text
Member(s) that did not succeed in this walk:
✗ "Lint · PM dispatch-gates" (lint-pm-dispatch-gates) — failure; check-run 115345404875, NOT listed above:
a later same-name row from run 38411617233 (check-run 115345950501, cancelled) supersedes it on this sha.
Its own annotations carry the answer: https://github.com/objectstack-ai/objectstack/actions/runs/38411694784/job/115345404875
```
## Self-test
`node scripts/pm/ci-failure.mjs --self-test` exits 0 at `e29bf95658`.
The battery `roster verdict: the substitute names the member lanes` goes
from 13 to 17 cases. Its pin moves 13 → 17 and equals the count
registered: I set the pin to 18 for one run, and it printed `registered
17 case(s), below its pinned floor of 18`.
The fixture models the measured shape. Run 7 is the aggregator's run and
run 9 is another run on the same sha:
- `Fixture · lane A` is red in run 7, and a later cancelled row from run
9 supersedes it.
- `Fixture shard (1/3)` failed in run 7, was re-run green inside run 7,
and has a later red row in run 9.
The four new cases:
1. **The trap is in the fixture.** The newest-per-name set holds run 9's
rows for both names.
2. **The fix.** `memberLanes` finds the red member: 4 matched, and the
one that did not succeed is check-run 101, superseded by 201.
3. **The control.** Shard 1/3's newest row in the aggregator's own run
is green, so it reads as succeeded. Its older red row in that run and
run 9's later red row do not count.
4. **The render.** The superseded red member is named by check-run and
URL, and no line says "its own row is above".
The existing cases now pass `checkRunListing(...)`, and their expected
values are unchanged.
## Ablations
These were one-shot runs, not kept as tests. Each one ran after the fix
commit, through `node scripts/ablation-replace.mjs` in wrap mode. For
each leg the anchor went 1 → 0 and the blob changed. Each restore read
blob == HEAD `07b4465c88c4` with an empty `git diff HEAD`. `git status
--porcelain` was empty after every leg. I wrote down the expected
direction before each run.
| leg | mutation | expected | observed |
|---|---|---|---|
| A1 | members read from `listing.kept` instead of `listing.all` (the
original defect, moved into the tested function) | case 2 and the render
case red | exactly those 2 red |
| A2 | in-run grouping dropped (every row of the run matched) | case 2
and the control red | exactly those 2 red |
| A3 | the superseded render branch disabled | the render case red |
exactly 1 red |
| A4 | the walk's call site handed `kept` again | offline self-test
green, because the walk is not exercised offline; the live run loud |
self-test exit 0. Live, 2nd attempt: `Cannot read properties of
undefined (reading 'filter')`, caught by the mid-walk net, exit 2,
"Nothing was judged". |
The first A4 live attempt was **NOT MEASURED**. The jobs API answered
`HTTP 502`, so no failed step was known and `memberLanes` was never
called. I declared it and ran it once more.
## Gates
All at `e29bf95658`.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 30 commands. All 30 exited 0,
including `node scripts/pm/ci-failure.mjs --self-test` and `pnpm
check:pm-dispatch-gates` (`fast 1839 case(s) in 370.5s`).
- `--ran` reconciliation, with each line carrying its exit code: `30
derived famil(ies) accounted for — 30 run, 0 NOT-MEASURED (a DERIVED
zero — all 30 recorded an exit code and none of them is 3)`.
- Outside the derived set: `scripts/pm/check-governed-merges.mjs` reads
this file's source in its self-test (the proxy re-arm guard-spelling
mirror). `pnpm check:pm-governed-merges` exit 0, 500 assertions.
- No new gate.
**Lint, as a proven narrowing.** The full `pnpm lint` run is CI's.
- Population: the one touched file. `eslint.config.mjs` lints it, and it
is not ignored.
- Count: `eslint --no-inline-config --format json
scripts/pm/ci-failure.mjs` reports 1 file, 0 errors, 0 warnings.
- Invariance: the resolved config for the file has no
`parserOptions.project` and no `projectService`, so there is no
type-aware linting. This diff cannot move any untouched file's verdict.
**Changeset: none, and this PR takes `skip-changeset`.**
- The root manifest is `private: true`.
- None of the 71 non-private `package.json` files contains
`scripts/pm/ci-failure.mjs` or lists it in `files[]`.
- `git grep` for `checkRunListing` or `memberLanes` under `packages/**`
exits 1 with 0 hits.
- Positive control: the same `git grep` method finds `export const
FieldSchema` in `packages/spec/src/data/field.zod.ts`, which
`@objectstack/spec`'s `files[]` ships (`src/**/*.zod.ts`). Exit 0.
- `scripts/pm/**` is not a governed surface: it has no row in
`GOVERNED_SURFACES`.
## Acceptance notes
- **Not changed: three general sentences are still false for this run.**
- The `ROSTER JOB` banner says "The answer is in those members' rows,
listed above".
- The roster job-log line says "the member jobs listed above, whose own
rows already carry their assertions".
- The substitute line says "the member row(s) named above carry the
answer, each with its own substitute".
- On run `38411694784` no member row is listed above, and the new lane
line says so. These sentences are outside the card's surface. They are
reported to the PM seat as a finding.
- **The verdict stays UNDETERMINED / exit 2.** The superseded red
member's annotations are not fetched, because only `kept` rows are
walked. That is the card's constraint on `latestPerName`'s role. The
lane line gives the job URL instead.
- **The walk's wiring stays outside the offline self-test**, as it was
for the earlier roster and Lane-verdict changes to this file. With this
change, a revert at the call site fails loudly.
- **Seen only on the ablated tree:** the mid-walk net describes a
non-transport exception in transport words ("the transport failed after
106 check-run(s) had been listed"). Noted, not filed.
---
_Generated by [Claude
Code](https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4185638 commit 614fdec
1 file changed
Lines changed: 142 additions & 18 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
357 | | - | |
| 357 | + | |
358 | 358 | | |
359 | 359 | | |
360 | 360 | | |
| |||
538 | 538 | | |
539 | 539 | | |
540 | 540 | | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
541 | 559 | | |
542 | 560 | | |
543 | 561 | | |
| |||
1063 | 1081 | | |
1064 | 1082 | | |
1065 | 1083 | | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
1066 | 1098 | | |
1067 | | - | |
| 1099 | + | |
1068 | 1100 | | |
1069 | | - | |
1070 | | - | |
| 1101 | + | |
1071 | 1102 | | |
1072 | | - | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
1073 | 1109 | | |
1074 | 1110 | | |
1075 | | - | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
1076 | 1123 | | |
1077 | 1124 | | |
1078 | 1125 | | |
| |||
1423 | 1470 | | |
1424 | 1471 | | |
1425 | 1472 | | |
1426 | | - | |
1427 | | - | |
1428 | | - | |
1429 | | - | |
1430 | | - | |
1431 | | - | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
| 1482 | + | |
| 1483 | + | |
| 1484 | + | |
| 1485 | + | |
| 1486 | + | |
| 1487 | + | |
| 1488 | + | |
| 1489 | + | |
| 1490 | + | |
| 1491 | + | |
| 1492 | + | |
| 1493 | + | |
| 1494 | + | |
1432 | 1495 | | |
1433 | 1496 | | |
1434 | 1497 | | |
| |||
1856 | 1919 | | |
1857 | 1920 | | |
1858 | 1921 | | |
1859 | | - | |
1860 | | - | |
| 1922 | + | |
| 1923 | + | |
| 1924 | + | |
| 1925 | + | |
1861 | 1926 | | |
1862 | 1927 | | |
1863 | 1928 | | |
| |||
1927 | 1992 | | |
1928 | 1993 | | |
1929 | 1994 | | |
1930 | | - | |
1931 | | - | |
| 1995 | + | |
| 1996 | + | |
| 1997 | + | |
| 1998 | + | |
1932 | 1999 | | |
1933 | 2000 | | |
1934 | 2001 | | |
| |||
2682 | 2749 | | |
2683 | 2750 | | |
2684 | 2751 | | |
2685 | | - | |
| 2752 | + | |
2686 | 2753 | | |
2687 | 2754 | | |
2688 | 2755 | | |
| |||
2737 | 2804 | | |
2738 | 2805 | | |
2739 | 2806 | | |
| 2807 | + | |
| 2808 | + | |
| 2809 | + | |
| 2810 | + | |
| 2811 | + | |
| 2812 | + | |
| 2813 | + | |
| 2814 | + | |
| 2815 | + | |
| 2816 | + | |
| 2817 | + | |
| 2818 | + | |
| 2819 | + | |
| 2820 | + | |
| 2821 | + | |
| 2822 | + | |
| 2823 | + | |
| 2824 | + | |
| 2825 | + | |
| 2826 | + | |
| 2827 | + | |
| 2828 | + | |
| 2829 | + | |
| 2830 | + | |
| 2831 | + | |
| 2832 | + | |
| 2833 | + | |
| 2834 | + | |
| 2835 | + | |
| 2836 | + | |
| 2837 | + | |
| 2838 | + | |
| 2839 | + | |
| 2840 | + | |
| 2841 | + | |
| 2842 | + | |
| 2843 | + | |
| 2844 | + | |
| 2845 | + | |
| 2846 | + | |
| 2847 | + | |
| 2848 | + | |
| 2849 | + | |
| 2850 | + | |
| 2851 | + | |
| 2852 | + | |
| 2853 | + | |
| 2854 | + | |
| 2855 | + | |
| 2856 | + | |
| 2857 | + | |
| 2858 | + | |
| 2859 | + | |
| 2860 | + | |
| 2861 | + | |
| 2862 | + | |
| 2863 | + | |
2740 | 2864 | | |
2741 | 2865 | | |
2742 | 2866 | | |
| |||
2771 | 2895 | | |
2772 | 2896 | | |
2773 | 2897 | | |
2774 | | - | |
| 2898 | + | |
2775 | 2899 | | |
2776 | 2900 | | |
2777 | 2901 | | |
| |||
0 commit comments