Skip to content

fix(pm): post-stamped names the platform's known read-back normalisations and locates a real mutation - #18298

Merged
claude[bot] merged 1 commit into
mainfrom
claude/issue-18296-post-stamped-read-back-normalisations
Sep 15, 2026
Merged

claude[bot] merged 1 commit into
mainfrom
claude/issue-18296-post-stamped-read-back-normalisations

Conversation

@claude

@claude claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #18296

The defect

readBackVerdict compared stored === sent, and GitHub stores no body byte-for-byte. Measured over one shift: every --body refresh read back one byte short (the platform strips the trailing newline) and every comment 58 bytes long (the platform appends its footer block). So the warning meant for the sanitizer — "the platform MUTATED the body" — fired on nearly every write, and the one real mutation of that shift, the garbled token on a provenance line (carded as #18284), printed the same sentence as all the noise around it.

What changed — scripts/pm/post-stamped.mjs only

  • PLATFORM_COMMENT_FOOTER: the platform's comment footer as the exact 58 bytes, declared once. Not a regex and not a trim: the question a read-back asks is whether the difference is exactly a normalisation somebody measured, and a pattern matching "a footer, roughly" also forgives a footer the sanitizer has chewed.
  • classifyReadBack({ sent, stored, mode }) (exported, pure): identical · trailing-newline-stripped (stored equals sent minus its trailing newlines) · footer-appended (that body plus exactly the footer constant, with or without the strip) · mutated for everything else, carrying the first differing BYTE offset and up to 60 bytes of context from each body, control characters escaped the way the refusals escape a span.
  • The footer class is COMMENT MODE ONLY. Whether the platform synthesises a footer for a footer-less issue body is unmeasured in this repository — every reading behind the body cell was taken on a draft that already ended in the footer block — so a footer on a body read-back stays mutated with its offset until somebody measures it. readBackVerdict takes the mode, and defaults to the strict body reading, so the lenient rule has to be asked for by name.
  • Transcript lines: read-back: clean — the platform stripped the trailing newline (sent N, stored M) / read-back: clean — the platform appended its footer (sent N, stored M) — no warning sign, mutated: false. The MUTATED line keeps its warning and its sanitizer sentence and adds first difference at byte K: sent ... | stored ....
  • --json carries read_back: { class, first_difference_byte } beside the existing body_mutated.
  • 21 new self-test cases in the read-back battery (12 to 33 cases; its floor moves 11 to 33), including the body-mode footer control, a real mutation underneath an appended footer (offset inside the body, not at the tail), a multi-byte character ahead of the difference, and the strip-plus-footer comment case.

Nothing else moved: the stamp lines, the refusals, the unread-knock check and the write path are untouched.

The design choice: an exact declared set, not a tolerant comparison

Trimming both sides, or matching the footer with a pattern, buys the same quiet by making the tool agree with whatever it is shown — and what it would then agree with is a chewed footer or a whitespace edit nobody made. Declaring the measured set keeps the tool loud everywhere else and keeps the unmeasured body-footer cell honest, which is the contract-first shape: the leniency is declared, named in the output, and removable.

Reverse verification (ablation from the committed state)

Base blob 0e6d55abf5a895c285bdb20879fb2024915543cb restored to disk and confirmed by git hash-object; reading taken; HEAD blob f5fe528a9f2b5c0752efcb71f22226634e1b0393 restored and git diff HEAD empty with a clean git status --porcelain.

probe unpatched (origin/main 99d20cf) patched
sent 'x\n', stored 'x', body mutated=true, "the platform MUTATED the body" mutated=false, trailing-newline-stripped, "clean — the platform stripped the trailing newline (sent 2, stored 1)"
sent 'x', stored 'x' + footer, comment mutated=true, same sentence mutated=false, footer-appended, "clean — the platform appended its footer (sent 1, stored 59)"
sent 'a [b] c', stored 'a b c' mutated=true, same sentence, no offset mutated=true, offset at byte 2 with sent ...[b] c beside stored ...b c

All three printed one indistinguishable warning before; two are now named clean and the third is located.

The smoke test is the act: this card's terminal report is posted with the tool itself, and its own read-back line is quoted in that report.

Gates

node scripts/pm/dispatch-gates.mjs --commands derived 32 commands; all 32 ran, reconciled with --ran. node scripts/pm/post-stamped.mjs --self-test 212 to 233 cases across 10 batteries. ESLint on the file clean, node scripts/check-self-test-wired.mjs clean.

Acceptance notes

  • Observation, not filed: writeArtefact re-reads the artefact with a second GET in both modes, so a comment write costs two requests where the POST response already carries the stored body. Harmless, and a read-back that goes back to the platform is arguably the stronger proof.
  • Observation, not filed: the register's PR-body footer cells stay unmeasured from here — this card's surface is the tool, and a body write to measure them is outside both the file surface and the writes budget.

Generated by Claude Code

…ions and locates a real mutation

`readBackVerdict` compared `stored === sent`, and GitHub stores no body
byte-for-byte: every body refresh reads back one byte short (the trailing
newline is stripped) and every comment 58 bytes long (the platform appends its
footer block). The MUTATED warning therefore fired on nearly every write, and
the one real mutation of a shift — a token the sanitizer ate out of a PR's
provenance line — printed the same sentence as the noise.

The stored body is now judged against a declared set of MEASURED
normalisations, and the line names the one it saw: `identical`,
`trailing-newline-stripped`, `footer-appended` (comment mode only — whether the
platform synthesises a footer for a footer-less issue body is unmeasured, so
that shape stays mutated), and `mutated` for everything else, which keeps the
warning and adds the first differing BYTE offset with up to 60 bytes of context
from each body, control characters escaped.

- `PLATFORM_COMMENT_FOOTER`: the exact 58 bytes, declared once — not a regex
  and not a trim, so a footer the sanitizer has chewed is not forgiven.
- `classifyReadBack` / `firstDifferingByte`: exported and pure, so the
  self-test and a probe drive the same rule the transcript line prints.
- `readBackVerdict` takes the mode; it defaults to the strict `body` reading, so
  the comment-only footer rule must be asked for by name.
- `--json` carries the class and the offset beside `body_mutated`.
- 21 self-test cases in the read-back battery (12 -> 33, floor raised to 33),
  including the body-mode footer control and a mutation underneath an appended
  footer.

Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr
Co-authored-by: Claude <noreply@anthropic.com>
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 15, 2026
@claude

claude Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e09776c4fafb1e769888276fdf2097caf2762403

Reviewing seat: domain:skills, in-seat review at the contract-review tier — get_session read at 2026-09-15T07:15Z: configured_model = session_context.model = last_served_model, the constant's tier, no fallback notice this round. Card #18296, claim 5679922591, report 5680221224 (posted through the tool itself — the act was the smoke test, and the tool's own line on that write read clean — the platform appended its footer (sent 17454, stored 17512)); Clause-②: no on the claim; default-tier build, seat review at tier.

① Derived judgments

  • Accept set / public surface: none moves — scripts/pm/post-stamped.mjs (+240/−11) against merge-base 99d20cfd; nothing published; skip-changeset read back (size/m). Non-governed ⇒ in-seat landing.
  • The change: the read-back verdict judges the stored body against a DECLARED set of measured normalisations instead of stored === sent — identical / trailing-newline-stripped / footer-appended (comment mode only, over one exact 58-byte PLATFORM_COMMENT_FOOTER constant, with or without the strip) / mutated, the last keeping the whole warning and adding the first differing BYTE offset with a window from each body, control characters escaped by the one helper the refusals already use. classifyReadBack and firstDifferingByte are exported and pure; mode defaults to body, the strict side, and the call site passes the act. The seat took the pure-core reading on both trees with nine inputs: on origin/main every non-identical input is MUTATED with no offset (the filed noise — the strip, the appended footer, a real mutation and a mutation under a footer all print one sentence); on the head the strip and the comment footer are clean and NAMED, the footer in BODY mode stays MUTATED (offset 1 — the control for the unmeasured cell), a real mutation names byte 2, a mutation under an appended footer names byte 2 (inside the body, not the tail), a multi-byte prefix gives byte 10 = Buffer.byteLength('维护者 '), a truncation names the seam, an unreadable body stays UNVERIFIED with no offset. The stamp lines, the refusals, the unread-knock check and the write path are untouched (diff read in full). Self-test 212 / 10 on main → 233 / 10 on the head (the read-back battery's floor 11 → 33, exact by the dev's 34-probe).
  • Gates 32 / 32 / 0 / 0 by --ran; the battery 1730 cases pass, 442.9 s detached, exit captured; --pair 18298 exit 0. Seat re-run on a detached worktree of the head: self-test 233, ESLint 0/0, check-self-test-wired OK, --pair 18298 0, the battery 1730 cases pass, 451 s detached, exit 0 captured. Reverse verification by the dev: ablation from the committed state with blob hashes on both legs (HEAD f5fe528a, base 0e6d55ab, restored, git diff HEAD and git status --porcelain empty), plus two live-platform readings — POST /pulls stored 5281 for 5282 sent (the strip class on the create channel), and the report comment itself (+58, classified clean by the tool that posted it).

② Semver level

Not applicable — nothing published moves; skip-changeset measured.

③ Boundary flags

open_questions: none. Assumptions: the comment-mode reading TRUE with a refinement the seat accepts (a comment whose sent body already carries the footer reads back as the strip, not the append — the classifier accepts both, which is exactly the measured pair); the body-mode strip NOT independently re-measured (no issue-body write in the budget) and confirmed on the adjacent create channel — accepted, the seat's own 29 readings stand; the footer-less issue-body cell UNMEASURED ⇒ comment-only, pinned by a control case — the right side of the unmeasured line. Six declared deviations, each answered: (1) control-escape spelling reused from offendingSpan rather than JSON.stringify — accepted, one spelling in the file and \xNN is not JSON. (2) one window per body opening AT the offset — accepted, the prefix is identical by construction. (3) real byte counts in the clean lines — accepted. (4) read_back added beside the existing body_mutated in --json — accepted, the dispatch allowed it. (5) mode defaulting to the strict side — accepted, pinned by a case. (6) a lookalike bar in one quoted line of the posted report — cosmetic, not re-posted. Two seat flags, neither blocking: the strip class accepts ANY count of trailing newlines (/\n+$/) where only one was measured — a declared transform whose residue is whitespace-only, so it cannot hide a sanitizer edit, but it is one unmeasured shape under a measured name; and byteWindowFrom spells U+FFFD as the literal character where offendingSpan spells it as the six-character backslash escape — cosmetic, same behaviour. Out of scope (noted by the dev, not filed): the PR-create channel appending nothing when the sent tail already carries the block (a second observation of the register's fourth shape), the second GET after a comment POST, and the unmeasured issue-body footer cell — the first is a register row for this seat to carry when the region is next open. Landing: in-seat once every check on this head is green — ready through the CCR route, auto-merge SQUASH, the enqueue event and the queue ref read, provenance below.

Implemented-by: claude/issue-18296-post-stamped-read-back-normalisations
Reviewed-by: session_01HZfg2AwVX191qCizp88gQr

VERDICT: PASS

Edit by the reviewing seat: the second seat flag above first read 「spells <the replacement character>」 — the six-character escape the seat typed was decoded into the literal U+FFFD by the seat's own command layer before the write, so the platform stored exactly what was sent; reworded, nothing else changed.


Generated by Claude Code

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Landing provenance — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T12:52Z. Non-governed landing (scripts/pm/**): review of record 5680375613 PASS on head e09776c4fafb1e769888276fdf2097caf2762403, ACCEPT 5680376047 on #18296; every check on that head read green at 2026-09-15T12:51Z (24 success / 11 skipped / running none / failed none). The seat flipped the PR ready through POST …/pulls/18298/ccr/ready_for_review and armed auto-merge SQUASH through PUT …/pulls/18298/ccr/auto_merge; the added_to_merge_queue event reads 2026-09-15T12:51Z; the queue ref refs/heads/gh-readonly-queue/main/pr-18298-* read 1 fifteen seconds after the enqueue. Landing is confirmed by the three readings (merged_at + git log origin/main carrying (#18298) single-parent + the queue ref gone) at the next wake; then #18296's residue is stripped and the landing record posted. The record on this PR was posted by origin/main's tool and read back 「MUTATED」 at +58 — the last such line this seat expects to see: once this head is on origin/main, the same write reads 「clean — the platform appended its footer」.


Generated by Claude Code

Merged via the queue into main with commit 827cacb Sep 15, 2026
37 checks passed
@claude
claude Bot deleted the claude/issue-18296-post-stamped-read-back-normalisations branch September 15, 2026 13:13
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
… skip is not a failure, and a check reports the skips outside the declared roster (objectstack-ai#18357)

Fixes objectstack-ai#18308

## What

`.claude/skills/pm-dispatch/SKILL.md` :629 read 「入队资格 = PR 上每一个 check
全绿,⛔ 不是 required 子集;required 集是队列强制的地板。」 — a bar no landing on this
repository can satisfy, because `skipped` is the ordinary conclusion of
a path-filtered job and the charter defined no state for it. Measured
over the ten most recent landed heads (below), every head carried 8–19
skipped check-runs beside its successes; the seat's landings read
「success = green, skipped = not failed」 by an unstated convention.

Two changes, both inside the claimed file surface:

1. **SKILL.md :629, re-keyed in place** (112 B → 119 B, ≤120 B; 812 →
812 lines; no issue number):

> `- 入队资格 = 每个 check 为 success 或预期 skip(名单:check-expected-skips.mjs),⛔
不是 required 子集。`

The bar now names the two states a check may be in — `success`, or an
expected skip — and points at the one machine-readable roster of
expected skips. The third clause of the old line (「required 集是队列强制的地板」)
did not fit the byte ceiling and is carried by AGENTS.md §7 (「the queue
enforces only the required set」); the ⛔ clause is kept verbatim.

2. **`scripts/pm/check-expected-skips.mjs`** (new; `package.json` gains
`check:pm-expected-skips` = its `--self-test`): given `--pr N` or
`--head SHA` (or a pre-fetched payload via `--check-runs-json FILE|-`),
it reads the head's check-runs and judges every `skipped` run against a
roster declared once, in the file, as data with a one-line reason per
row. Exit register: **0** every skip is in the roster · **4** a skip is
outside it (each named and classified: a filter miss, or a dependency
skip when the same check suite holds a failed run; a raw `matrix`
template in the name is read as "skipped before matrix expansion, i.e. a
job-level gate — never a workflow-level `paths:` filter, which creates
no check-run at all") · **3** NOT MEASURED (unresolvable sha, 404,
network, no check-runs on the head, or a check-run still running — the
skip set is not final). Report-only; the self-test pins structurally
that the file carries no `method:` key and imports no writer.

The roster is **tied to the workflows, not remembered**: `--self-test`
parses each row's workflow with the `yaml` package and asserts the job
exists, carries the row's name, carries an `if:`, that the `if:` spells
the declared gate (`needs.filter.outputs.X != 'false'`, the
`github.event.action` exclusion, or the label literal), and — for ci.yml
rows — that the `filter` job's output keeps its `|| 'true'` widening,
which is what makes "the merge-queue build runs it" true. The audit is
driven red in the self-test on a deleted, renamed, un-gated and re-gated
job, a lost widening and an unreadable workflow.

## The roster (11 names), measured over ten landed heads

| name | workflow › job | mechanism | over the ten heads |
|---|---|---|---|
| `Build Core` | ci.yml › build-core | `filter` output `core` said
false; REQUIRED context, judged on the queue build | skipped 10/10 |
| `Temporal Conformance (live PG + MySQL)` | ci.yml ›
temporal-conformance | same, REQUIRED context | skipped 10/10 |
| `Dogfood Regression Gate (${{ matrix.shard }}/3)` | ci.yml › dogfood |
same; raw matrix template = pre-expansion name (the aggregate `Dogfood
Regression Gate` runs `if: always()`, never skips) | skipped 10/10 |
| `Dogfood Verify CLI` | ci.yml › dogfood-verify | same | skipped 10/10
|
| `Test Core (${{ matrix.shard }}/6)` | ci.yml › test | `core` OR
`crosspkg` both false (scripts/** is in `crosspkg`, so scripts/pm heads
RUN it) | skipped 3/10 — only the .md-only heads |
| `Build Docs` | ci.yml › build-docs | `filter` output `docs` | skipped
10/10 |
| `Console Pin Gate` | ci.yml › console-pin | `filter` output `console`
| skipped 10/10 |
| `Check PR Size` | pr-automation.yml › pr-size | `if:` excludes
`labeled` / `unlabeled` / `edited` events; each event is its own run on
the same head | skipped 9/10, success beside it 10/10 |
| `Auto Label` | pr-automation.yml › auto-label | same | skipped 9/10,
success beside it 10/10 |
| `Check Changeset` | pr-automation.yml › changeset-check | `if:` skips
a PR carrying `skip-changeset` | skipped 10/10 (every head carried the
label), success beside it 9/10 (the run before the label) |
| `Packed-tarball smoke (opt-in)` | pack-smoke-optin.yml › pack-smoke |
opt-in by `needs:pack-smoke` | skipped 10/10 |

Never skipped on any of the ten heads (and carrying no `if:`): `Lint &
Repo Gates`, the four `Type Check ·` lanes, `TypeScript Type Check`,
`Test Core` and `Dogfood Regression Gate` (the aggregates), `Governed
Surface Queue Guard`, `filter`, the four claim/keyword guards, `Check
Documentation Links`, `Close issues referenced in other repositories`.
Workflows with a workflow-level `paths:` filter
(`half-state-patrol.yml`, `board-snapshot.yml`) produce no check-run at
all on a non-matching head — they are absent on 6 of the ten heads,
never `skipped` — which is the measured basis for the "a skipped
check-run is never a `paths:` filter" reading.

## Reverse verification (all at `7a1f99ea`)

| leg | result |
|---|---|
| `--head` on the ten landed heads objectstack-ai#18298 · objectstack-ai#18307 · objectstack-ai#18311 · objectstack-ai#18315 ·
objectstack-ai#18316 · objectstack-ai#18322 · objectstack-ai#18326 · objectstack-ai#18327 · objectstack-ai#18328 · objectstack-ai#18332 | **exit 0 on every
one**; accepted skips per head: 11 · 12 · 11 · 19 · 12 · 8 · 11 · 18 ·
18 · 11, every name in the roster; e.g. objectstack-ai#18322 (the 8-skip head): `Build
Core`, `Build Docs`, `Check Changeset`, `Console Pin Gate`, `Dogfood
Regression Gate (…/3)`, `Dogfood Verify CLI`, `Packed-tarball smoke
(opt-in)`, `Temporal Conformance` |
| constructed fixture: the real objectstack-ai#18322 payload with `Lint & Repo Gates`
mutated to `skipped` | **exit 4**, naming `Lint & Repo Gates (check
suite 94780297729)` and classifying it `filter-miss` |
| garbage sha `--head deadbeef…deadbeef` | **exit 3** — `NOT MEASURED —
HTTP 422 — the API cannot resolve that sha` |
| `--pr 18315` (the head is looked up through the proxy) | exit 0, `19
skipped check-run(s), every one in the roster`; `--pr 18308` (an issue
number, not a PR) → exit 3 (HTTP 404) |
| `--self-test` | 99 cases pass, offline (the roster's truth on the live
workflows and its audit driven red six ways; the judge on the measured
39-run objectstack-ai#18315 head and on fixtures for 0 / 4 / 3; read classification;
argv; the real CLI on payload files incl. `--json`; the structural pins)
|
| SKILL.md ratchet | `wc -l` 812 → 812; :629 112 B → 119 B;
`check-skill-line-ratchet: SKILL.md is 812 lines (ceiling 812; headroom
0)` |

## Gates (local, at `7a1f99ea`)

`node scripts/pm/dispatch-gates.mjs --commands
.claude/skills/pm-dispatch/SKILL.md scripts/pm/check-expected-skips.mjs
package.json` derived 45 commands; all 45 were run with the exit
captured by redirect, and `--ran` reconciles: `✓ dispatch-gates --ran:
45 derived famil(ies) accounted for — 40 run, 5 NOT-MEASURED (5 DERIVED
from a recorded exit 3)`. The five NOT MEASURED are the `dist/`-reading
families on an unbuilt tree (`check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`,
`check:sourcemap-no-sources-content`, `@objectstack/lint
check:doc-formula-expressions` — each prints `PREREQUISITE NOT MET`);
this diff touches no package, so no build closure is owed locally and CI
runs them built. The `pnpm check:pm-dispatch-gates` battery was not
derived, so it was not run.

Named gates, verdict lines quoted: `check-skill-line-ratchet: SKILL.md
is 812 lines (ceiling 812; headroom 0)` · `check-skill-id-lint: 27
file(s) clean` · `check-skill-frame-sync: the one declared copy of the
decision frame is internally coherent` · `check-self-test-wired: every
one of the 212 script(s) CI runs that ship a --self-test has that
self-test run by CI` (the new script is not in that population — see
Acceptance notes) · `check-nul-bytes: OK (scanned 8707 text file(s))` ·
`check-governed-prose: 2 instruction surface(s) name all 5 registered
governed surfaces` · ESLint (`--no-inline-config`) on the new file: exit
0 · `check-governed-merges.mjs --test
.claude/skills/pm-dispatch/SKILL.md`: **GOVERNED** (`.claude/**` ×1),
exit 3 as designed. `check-clause2-carriers.mjs --pair` is run once this
PR exists and its reading goes in the report comment.

## The one design choice, on the four axes: a roster declared in the
check vs. deriving expectedness live from the workflows' `paths` filters

- **实际业务需求** — the measured need is name-level: 31 landings this shift
and the ten heads above were judged by "is this skipped name one that
always skips?", and zero of them needed a diff-level answer. The
diff-level question ("should `Build Core` have run on THIS diff?") is
already answered for the required family by the platform: on
`merge_group` ci.yml's `filter` widens every output to `'true'` (the `||
'true'` half of the filter contract, now pinned by this check's
self-test), so the family runs on the merged tree before `main` moves. A
live derivation would answer a question nobody measured a need for, at
the cost below.
- **项目长远合理性** — a roster is a declaration that can rot; a live
derivation is a second evaluator of the platform's own semantics
(dorny/paths-filter's picomatch dialect, GitHub's expression language,
matrix name templates, per-event runs) that can drift from the real
evaluator. Both are drift; the roster's drift is made LOUD here (every
row is pinned to its live job, name, `if:` and gate spelling — a rename
or re-gate reddens CI), while an evaluator's drift is silent by
construction (a wrong glob yields a confident "expected").
Contract-first: the workflow file is the contract, and the roster is a
checked reading of it, not a copy of its path lists.
- **防 AI 写代码犯错** — the roster makes the wrong move structurally hard: a
new gated job's first skip is exit 4 until someone adds a row WITH its
mechanism, and a row that names a job the tree does not gate is red. A
live evaluator is where an AI would quietly mis-implement glob semantics
and produce the false green this tree refuses everywhere else (the
"could not read" ≠ "clean" class). The declared-vs-delivered line is
kept: the check advertises the name question only, and says so in its
header and report.
- **创业阶段不扩散需求** — the roster is ~11 rows of data and one audit; live
derivation is a YAML-expression evaluator with parity tests against
GitHub. No pull exists for the latter; if a rostered required job is
ever found skipped on a diff inside its filter, that measurement is the
card that would justify it.

**Recommendation: the roster in the check (implemented).** Should the
seat prefer live derivation, nothing here blocks it — the roster rows
already carry `workflow`, `job` and the gate's outputs, which is the
input a derivation would start from.

## Acceptance notes

- **Self-test wiring.** `check:pm-expected-skips` exists in
`package.json` (mirroring the report-only siblings), but no workflow
names it and lint.yml was outside this card's file surface, so
`check-self-test-wired` (correctly) does not count it and CI does not
run its 99 cases. The completion is one lint.yml step beside the other
`check:pm-*` steps (`run: pnpm check:pm-expected-skips`); left to the
seat — 承接者:the skills seat, on this PR or a sibling. Noted, not filed.
- **:629's floor clause dropped for the byte ceiling** (「required
集是队列强制的地板」); AGENTS.md §7 carries the fact. Noted, not filed.
- **Exit 4 judges skips only.** Other conclusions on the head
(`failure`, `cancelled`, `neutral`, …) are printed loudly under `other
conclusions` and do not move this check's exit; the bar's success half
is read from the same listing. A malformed `--head` (non-hex) is a usage
error (exit 2), a well-formed sha the API cannot resolve is exit 3.
Noted, not filed.
- **The card's five-name family was a subset.** The measured recurring
family is eleven names (six ci.yml `filter`-gated jobs the card did not
list, including two REQUIRED contexts); the card's citation of a
"platform-readings discipline (a skip is not a pass)" has no verbatim
carrier — the nearest lines are AGENTS.md §7 (「Green means the
gate-carrying jobs' conclusion is success」) and
`references/review-checklist.md:43`. Recorded in the report, no card.
- objectstack-ai#18349 is not addressed here; it holds :513 / :523 of the same file
(region-level parallel). `origin/main` did not move under this branch
after cut (`ceb6b5fb`).

## 维护者速读(草稿)

**改了什么**:入队资格这一行改成「每个 check 为 success 或预期 skip」,并新增一个只读的检查脚本
`scripts/pm/check-expected-skips.mjs`:给它一个 PR 号或提交 SHA,它读出该提交上所有
check,凡是 `skipped` 的都对照脚本内声明的「预期 skip 名单」(11 个名字,每个带一句为什么会 skip
的机制),名单外的 skip 会被点名并退出码 4;读不到就退出码 3,绝不当作通过。

**为什么改**:原来的「每一个 check 全绿」在本仓库任何一个 PR 上都做不到——路径过滤的 job 本来就以 `skipped`
结束,实测最近十次落地每次都有 8–19 个 skip。席位一直靠「记得哪些通常会 skip」在判断,而真正要分辨的是「预期
skip」与「本该跑却没跑」。现在名单是机器可读的,并且自测会把名单逐条对照真实 workflow 文件校验(job
存在、名字一致、带条件、条件拼写一致),名单不会悄悄过期。

**风险与代价(含回滚)**:规则层只改一行(≤120 B、行数 812 不变);脚本只读不写、不接入任何门禁,CI
不因它变红。名单是名字层面的判断,不回答「这个 diff 是否本该触发某个
job」——必查项由合并队列在合并树上全量重跑兜底,这一点写在脚本头部。回滚 = revert 本 PR。

**席位意见**:(留空,席位定稿)

**你要做的**:本 PR 触及 `.claude/**`(规则层),需要你的 APPROVED;之后由席位落地。是否把该脚本的自测接进
lint.yml(一行 `pnpm check:pm-expected-skips`)由席位决定,本 PR 未动 lint.yml。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant