Skip to content

fix(pm): the relay reads no runner 403 as the egress proxy's, and a refused advisory row names its cause - #29505

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-rv61tq
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-rv61tq

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Requested by Irene · project thread

os-dev · domain:skills#1 dispatch · session_01J8VVAoLff4MC52HYARNBKP

Part of #29402. The card's third bullet (the seat records the measured live 403 reason and its seat-side rule in references/rest-channel.md) stays open for the seat after a live reading.

Clause-②: no
Metadata-protocol: none

维护者速读

  • 中继运行器上没有出口代理,所以 write-pace 在那里不再把 GitHub 的 403 当成代理拒绝;403 改走平台退避路径(规则 ③),不再退回名额。
  • 代理识别只认正向标记:代理自己的提示语,或指向代理自身文档站点的 documentation_url。"不是 github.com" 不再算数。
  • 安全公告行被拒时,行内打印平台的 message 和 documentation_url(各限 200 字符、压成一行),再接原有的处置建议,所以交给维护者的读数里有原因。
  • ⛔ 载荷不进日志:若平台消息或链接复现了载荷或预检读到的公告正文中的任意 16 字符片段,就不打印,并在行内注明。标记测试仍为零泄漏。
  • 实际原因(如每日报告上限)要等一次真跑读到后再写进 rest-channel.md,本 PR 不含。

What changes

  • scripts/pm/write-pace.mjs
    • New inActionsRunner(env) (GITHUB_ACTIONS === 'true') and proxyAnswered(body, env): never true on an Actions runner, isProxyRefusal(body) elsewhere. stopSignalFrom (now taking env, default process.env) and noteResponse (its deps.env) act on proxyAnswered, so a GitHub 403 inside the relay runner is judged by rule ③ and never gets the proxy's slot give-back and "No stop marker written" line.
    • isProxyRefusal stays pure (body only, so label-write.mjs and fleet-token.mjs behave the same in a seat container) but needs a positive mark: the proxy's own message, or a documentation_url on the proxy's docs hosts (PROXY_DOC_HOSTS: anthropic.com, claude.com, claude.ai). A URL that is merely not on github.com no longer counts. GitHub's own refusals can point at a short link or a changelog post on another host, which is the likely shape of the misreading the card measured.
  • scripts/pm/fleet-write/execute.mjs
    • New advisoryRefusalCause(answer, known): the refusal's message and documentation_url, from its own answer only (never errors, never the rest of the body). The message is flattened to one table-safe line and capped at ADVISORY_REFUSAL_MESSAGE_CAP (200, cut with a note). The URL is printed only when https-shaped and within ADVISORY_REFUSAL_URL_CAP (200). No message gives "cause unread".
    • known is advisoryProseOf of the request body and, for the PATCH rows, of the advisory the preflight read. Ids, states, logins and URL-shaped words are left out. A message or URL (percent-decoded too) that reproduces any 16-character run of that prose is withheld, and the row says so. The message is judged raw as well as flattened, against the known prose as given and flattened the same way, so a quoted run that straddles a pipe, a backtick, a quote or a line break is still caught.
    • Status 0 (the fetch threw) never reads as the platform's reason: the row says the request did not reach the platform, and the transport's text is dropped if it quotes the payload.
    • confirmAdvisory and advisoryPreflight print that cause between the status and ADVISORY_ACCESS_REMEDY, so "put this reading to the maintainer" now hands over a reading that names the platform's reason. This replaces the six-string ADVISORY_PLATFORM_MESSAGES filter on these two paths. The header says the same.

Why

Card #29402 measured three defects on the live relay. After the first advisory_report, every later report answered 403. write-pace printed that the egress proxy had refused it, which cannot be true on a runner, so no stop marker was written and the slot was given back. The row printed no message because the platform's was not on the six-string list. So the "put this reading to the maintainer" advice went out with no cause in it, and nobody could tell a reporter limit from a missing permission.

Verification

At 3f3345a, in objectstack-ai/objectstack (the contract review's S2 and S3 are the second commit; 478c92a is the first):

  • pnpm check:pm-write-pace: 118 cases pass across 12 batteries (rule ③ battery 14 → 19 cases). It also passes under GITHUB_ACTIONS=true, the shape CI runs it in.
  • pnpm check:pm-fleet-write-execute: 242 cases pass across 21 batteries (advisory battery 27 → 32 cases). The marker sweep now covers 46 runs, up from 41, and finds the marker in no log line, row, summary, annotation or pace-log line.
  • New cases cover:
    • a runner 403 that takes rule ③ even with a proxy-shaped body;
    • a plain runner 403 that keeps its slot spent and prints no proxy line;
    • GitHub short-link, changelog and look-alike-host URLs that are not read as the proxy's;
    • an unlisted 403 message and its short link printed in the row, the summary and the log;
    • a message quoting the payload, and one quoting the preflight's advisory, withheld;
    • a percent-encoded quoting URL dropped while the message is kept;
    • no message;
    • the 200-character cap, and newline, pipe, backtick and quote flattening;
    • an ill-shaped URL not printed;
    • a quoted payload run straddling a pipe, withheld only because the raw message is judged too;
    • status 0 reading as a transport failure, never as the platform's reason.
  • Ablations, each made on the committed tree and restored with git checkout HEAD --, with git status clean afterwards:
    • At 478c92a: quotesKnown returning false turns 5 of 240 execute cases red, the marker sweep among them, and proxyAnswered ignoring the runner turns 3 of 118 write-pace cases red.
    • At 3f3345a: dropping the raw-message check turns 2 of 242 red (the straddle case and the marker sweep), and dropping the status-0 branch turns 1 of 242 red. After each, git hash-object of the file equals the head blob.
  • node scripts/pm/dispatch-gates.mjs --commands derives 36 commands for the two paths. At 478c92a all 36 exited 0, each exit captured before any pipe, and --ran reconciles 36 of 36. At 3f3345a, which changes execute.mjs alone, check:pm-fleet-write-execute, check:pm-write-pace, check:nul-bytes, check:scripts-symbol-anchors, check:doc-authoring, check-self-test-wired and eslint over the file were run again, all exit 0. Also clean: pnpm check:doc-authoring, eslint over both files, and the self-tests of label-write, fleet-token, post-stamped, issue-create, close-cards, issue-transfer, sweep-closed-cards, sweep-stale-finding, fleet-write/dispatch and fleet-write/validate.
  • No changeset: only scripts/pm changes, which publishes nothing, so the PR carries skip-changeset.

Acceptance notes

  • Not measured live. The platform's actual 403 body for a second private report is still unread, because the relay used to hide it. The first refused report after this lands will print it. That reading, with its seat-side rule, goes into references/rest-channel.md as the card's third bullet asks. It is left out of this PR because the reason has not been measured.
  • Whether that 403 writes a stop marker depends on what it carries. Rule ③ fires on a 429, a ratelimit verdict, a body naming the secondary limit, a retry-after or x-ratelimit-remaining: 0. A bare 403 carrying none of them writes none, by rule ③'s own judgment ("a plain 403 does NOT"). The runner's pace log is fresh on every run anyway, so stopping repeat sends from seats rests on the row's cause and the remedy's ⛔ do-not-re-send.
  • Doc drift outside this PR's surface:
    • ops.mjs's header still says a refusal quotes the platform's message only when it is one ADVISORY_PLATFORM_MESSAGES names.
    • ADVISORY_ACCESS_REMEDY still says what the App may do "is decided by the first live run".
    • ADVISORY_PLATFORM_MESSAGES and advisoryPlatformText now have no production caller; validate.mjs's self-test is the only one left.
    • label-write.mjs line 420 still describes the proxy mark as "a documentation_url that is not GitHub's".
    • The surface was held to the two files, so all of these are left for the seat's follow-up.
  • execute.mjs is also being changed on pm/fleet-write: an advisory_read relay op that returns an advisory only as ciphertext, encrypted to a one-time key the seat generates per read #29420's branch. Whichever lands second merges main (no rebase), and the seat re-reviews the delta.
  • PR feat(pm/fleet-write): three security-advisory relay rows, payload read from the event file #29222's ablation B ("a refused answer quotes any platform message → marker test red") described the guard this PR replaces. The new guard is the prose-quote check, and its own ablation is above.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (Not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; eslint over the changed files passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. Nothing publishes, so no changeset; both declaration lines are filled in.
  • 6. packages/spec not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English, except the 维护者速读 section, which the lane asks for in Chinese.
  • The Before Submitting list in CONTRIBUTING.md is done. (Left unchecked: the workspace-wide pnpm lint and pnpm test were not run; the scripts touched have no package tests, and eslint over the changed files was run.)

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAoLff4MC52HYARNBKP


Generated by Claude Code

…efused advisory row names its cause

write-pace: on an Actions runner no egress proxy stands in the path, so
`proxyAnswered` never reads an answer there as the proxy's; a GitHub 403 in the
relay takes rule ③ (the platform back-off path) instead of giving the slot back.
`isProxyRefusal` now needs a positive proxy mark: its message, or a
documentation_url on the proxy's own docs hosts, never merely "not github.com".

fleet-write/execute: a refused advisory row prints its cause — the platform's
message and documentation_url from the refusal's own answer, capped and kept to
one table-safe line, never its errors or body — before the remedy, so the
reading a seat puts to the maintainer carries the reason. A message or URL that
reproduces prose of the payload or of the advisory the preflight read is
withheld, and the row says so.

Claude-Session: https://claude.ai/code/session_01J8VVAoLff4MC52HYARNBKP
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 2, 2026
@github-actions github-actions Bot added the size/m label Nov 2, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract-review-claim: session_01J8VVAnMUWA9rufXzFaEh4J · head 478c92a · 2026-11-02T16:51Z

The seat review runs at CONTRACT_REVIEW_TIER in an isolated subagent (the seat session is not at that tier).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 478c92a00016062c7e4cbaad7b15001606bdf9cc
Local-runs: probe — on a detached worktree of the head (merge-base with origin/main 04e0c2c; neither touched file moved on main since): node scripts/pm/write-pace.mjs --self-test (118 cases, 12 batteries, exit 0; again under GITHUB_ACTIONS=true, 118/12, exit 0), node scripts/pm/fleet-write/execute.mjs --self-test (240 cases, 21 batteries, exit 0, marker found in no emitted line), node scripts/pm/dispatch-gates.mjs --commands over the two paths (36 commands derived), of which 16 were run as a representative set (listed in ①.9), plus the seat-side transports' self-tests (label-write 136/11, fleet-token 75/8, post-stamped 753/26, issue-create 56/8, all exit 0); ten reviewer-authored ablations, each applied to the committed tree, measured, and restored with git checkout HEAD -- to the head blob (git hash-object equal to the PR's file sha, git status --porcelain empty); the worktree was removed after the runs and the main working tree was not touched
Scope: scripts/pm/write-pace.mjs, scripts/pm/fleet-write/execute.mjs (the PR's two files and their self-tests) + read, not reviewed: scripts/pm/fleet-write/ops.mjs (the shape constants and ADVISORY_ACCESS_REMEDY the rows cite), scripts/pm/label-write.mjs and scripts/pm/fleet-token.mjs (the seat-side callers of isProxyRefusal), scripts/pm/fleet-write/validate.mjs (the remaining caller of the orphaned advisoryPlatformText), .github/workflows/fleet-write.yml (the executor step's env: names), card #29402 with comments 6579282773, 6580176042 (triage), 6582331169 (claim) and 6582879449 (dev report, read as input)

Reviewer: an isolated contract reviewer at CONTRACT_REVIEW_TIER, read-only on GitHub, fed the card body and comments, the PR body, the net diff against the merge-base, the head's check-runs, contract-review.md and review-checklist.md. The dev report was read as input, not as conclusions; every count below is this review's own measurement. This record carries no finding, no mechanism, no advisory id and no advisory title; the self-test fixtures use made-up ids and a generic marker sentence, and none is repeated here. No environment variable value is printed anywhere in this record.

① Derived judgments

The card's three defects and the PR's claims, one at a time:

  1. File surface. The PR is one commit (478c92a, author objectstack-fleet[bot], head repo = base repo, draft, base main); git diff 04e0c2c0 478c92a0 --stat, the REST file listing and changed_files: 2 agree on exactly scripts/pm/write-pace.mjs (+59/−10, blob 07d17a67f3) and scripts/pm/fleet-write/execute.mjs (+129/−22, blob ae829bc186). The claim's file surface was the two files and their self-tests (the self-tests live inside the files); no other path is touched. Neither file moved on origin/main since the merge-base. Correct, and on surface.
  2. Defect ① — a GitHub 403 inside the relay runner read as the egress proxy's. stopSignalFrom and noteResponse now act on proxyAnswered(body, env), which is !inActionsRunner(env) && isProxyRefusal(body), with inActionsRunner reading env.GITHUB_ACTIONS === 'true'. In noteResponse the env is deps.env ?? process.env; on the live path main() calls executeFleetWrite with no pace deps, so rest() passes paceDeps = {} and noteResponse reads process.env, which the Actions runner populates. So on the runner every answer is judged by rule ③: a 403 with a back-off signal writes the marker, a bare 403 keeps its slot spent, writes no marker, releases the lease and prints no proxy line; the slot give-back and the "No stop marker written" line are unreachable there. Measured: the self-test's runner harness case (plain 403, then a proxy-shaped 403 carrying a ratelimit verdict) passes, and ablation W1 (proxyAnswered ignoring the runner) reds 3 of 118, W3 (stopSignalFrom back to body-only) reds 2, W4 (noteResponse back to body-only) reds 1, each naming the runner cases. Fixed, and covered.
  3. Defect ① second half — isProxyRefusal needs a positive mark. The body-only predicate now returns true on the proxy's own message or on a documentation_url matching PROXY_DOC_HOSTS (anthropic.com, claude.com, claude.ai and subdomains, anchored on the host); "not on github.com" no longer counts. The measured proxy shape (message naming the proxy, docs URL on the proxy's own host) still reads true, so the seat-side path is unchanged for the real proxy. Ablation W2 (the old off-github test restored) reds 2 of 118 (the short-link/changelog/look-alike-host case and the proxyAnswered case). Correct. PROXY_DOC_HOSTS is declared after isProxyRefusal, which only reads it at call time; no temporal-dead-zone risk.
  4. Seat-side proxy path still works (outside the runner). label-write.mjs classifyHttp keeps isProxyRefusal(body) → prerequisite, and fleet-token.mjs keeps isProxyRefusal(minted) for its sentence; both are pure body reads and both are true on the measured proxy shape. In a seat container (GITHUB_ACTIONS unset) noteResponse still gives the slot back on a proxy refusal (the existing proxy-refusal.jsonl harness case, now with env: {}, passes). The one behaviour change seat-side is deliberate: a GitHub 403 whose docs URL is off github.com is now classified by status (refusal, or ratelimit with a zero header) rather than prerequisite. label-write 136/11 and fleet-token 75/8 self-tests are green on the head. Correct.
  5. Defect ② — the refusal prints the platform's message and documentation_url, never the payload. advisoryRefusalCause(answer, known) reads answer.detail (which rest() derives from json.message alone, token-scrubbed) or json.message, flattens control characters, whitespace, |, backtick and ", caps at 200 with a note, and prints documentation_url only when https-shaped (REFUSAL_URL_SHAPE, no quotes, spaces, pipes or angle brackets) and at most 200 characters; the errors array and the rest of the body are never read. The known set is advisoryProseOf of the request body and, for the PATCH rows, of the advisory the preflight read (pre.known); ids, states, logins and URL-shaped words are excluded, strings under 8 characters too. A message, or a URL (raw or percent-decoded), reproducing any 16-character run of a known string is withheld and the row says so. Reached from confirmAdvisory (report, draft, PATCH) and from advisoryPreflight's refused GET; advisoryPlatformText is no longer imported by execute.mjs. Measured: ablation E1 (quotesKnown → false) reds 5 of 240 including the marker sweep; E2 (cause hidden) reds 8; E3 (no cap) reds 1; E4 (URL printed unshaped, including a javascript: URL and an over-long one) reds 1; E5 (pre.known dropped) reds 2 including the marker sweep; E6 (preflight refusal without cause) reds 1. The marker sweep covers 45 runs (41 before) and finds the marker in no log line, row, summary, annotation or pace-log line; the positive controls still hold. Fixed, and covered on every path.
  6. Leak surface, read against the code rather than the tests. The only new text any advisory row emits is advisoryRefusalCause's return, composed of fixed prose, the flattened/capped message and a shape-checked documentation_url. message is the platform's own field; the errors array (the one field GitHub does fill with submitted values on a 422) is never touched; the request body and the preflight's advisory reach the function only as the known blocklist. detail is token-scrubbed in rest() before it reaches the cause; the URL is not scrubbed, but it is platform-authored, shape-checked and never carries the bearer token. Annotations are emitted only for landed report/draft rows (::notice with the id), never for a failed row. No path prints a summary, description, vulnerability, credit or any secret into a log line, row, summary or annotation. Two robustness gaps, neither a leak today, are should-fixes S2 and S3 below.
  7. Defect ③ — the row's advice names the cause. Every refused advisory row now reads FAILED: HTTP <status>; cause, as the platform gives it: "<message>" (documentation: <url>); <ADVISORY_ACCESS_REMEDY>, or the withheld/unread variants, and the same text reaches the step summary and the log (the unlisted-message case asserts all three). The six-string filter that hid the measured 403's message is gone from these two paths. Fixed.
  8. The PR body against the head. 118/12 with rule ③ 14 → 19 (diff: the battery row and five new cases); 240/21 with the advisory battery 27 → 30 (diff) and the sweep 41 → 45 (diff); both ablation readings (5 of 240 for quotesKnown, 3 of 118 for proxyAnswered) reproduce exactly; 36 derived commands (measured 36); skip-changeset is right (scripts/pm publishes nothing; Check Changeset success); Clause-②: no and Metadata-protocol: none are right (no published schema, no authorable surface). Every body claim checked holds. One body matter is S1.
  9. Gates. Of the 36 derived commands, 16 were run here: check:pm-fleet-write-validate (275/15), check:pm-fleet-write-dispatch (dispatch and rerun-failed, exit 0), check:scripts-symbol-anchors (4155 anchors resolve, so removing the advisoryPlatformText import broke no anchor), check:nul-bytes, check:entry-guard, check:parse-guard, check-self-test-wired (run and --self-test), check:pm-dispatch-gates (254 s), check:watch-hint-literal, check:agent-test-spelling, check:ratchet-remedy-authority, check:gitlink-declared, check-declaration-mirrors, all exit 0. Two did not exit 0 for reasons outside the diff: check:doc-authoring (rc 3, pnpm refusing to run without node_modules in the throwaway worktree) and check-self-test-workflow-commands (rc 1, release-pending-publish.mjs cannot find @changesets/cli without node_modules; the finding names neither file). CI's Lint · docs & code guards, Lint · ESLint, Lint · scoped · tooling self-tests and Lint · PM dispatch-gates are success on the head, which covers both. ESLint was not run locally for the same reason; CI's is the reading.
  10. Nothing new on any surface: no gate, label, workflow, CODEOWNERS, package or governed-register path. The fixtures name no real advisory: the ids are made-up shapes, the marker is a generic sentence, the unlisted message is invented, and the CWE values are category ids, not advisories. The card, the PR body and the dev report carry no finding and no mechanism.

② Semver level

None. No published package, no packages/spec/src/**, no TypeScript; scripts/pm tooling only. skip-changeset is the right declaration and Check Changeset is success on the head.

③ Boundary flags

  • Dev open_questions: none.
  • Dev out-of-scope note (a): ops.mjs's header (lines 433 to 434) still says the platform's message is printed only when ADVISORY_PLATFORM_MESSAGES names it, and ADVISORY_ACCESS_REMEDY still says what the App may do "is decided by the first live run" (that run has happened: one 201 then 403s). After this PR ADVISORY_PLATFORM_MESSAGES and advisoryPlatformText have no production caller (validate.mjs's self-test alone). label-write.mjs line 420 likewise still describes the proxy mark as "a documentation_url that is not GitHub's". Judgment: follow-ups, not blockers. The file that does the printing (execute.mjs) carries the correct header; the drift is in neighbouring files the claim held outside the surface, and it describes old behaviour rather than enabling a wrong one. Folded into S1's follow-up card.
  • Dev out-of-scope note (b): a bare 403 on the runner writes no stop marker under rule ③. Judgment: follow-up, not a blocker. Rule ③'s own text says a plain 403 writes none; the card's "takes the platform back-off path" is met in that rule ③ now judges the answer. The runner's pace log is per-run, so a marker there would not stop a second seat anyway; stopping repeat sends is seat-side, and belongs with the rest-channel.md rule the card's third bullet asks for after the live reading (S1). Whether rule ③ should read a documented daily-limit message as a back-off signal is a design question for after that reading, since the body is still unread.
  • execute.mjs is also edited on pm/fleet-write: an advisory_read relay op that returns an advisory only as ciphertext, encrypted to a one-time key the seat generates per read #29420's branch (not yet pushed; no origin head names 29420). The dev's note (whichever lands second merges main, the seat re-reviews the delta) is the right handling.
  • Independence: PR author objectstack-fleet[bot], commit author Claude; no off-relay author note is owed.

④ Design judgment

Per hits file, against the card's scenario (a second advisory_report of the day refused by GitHub inside the relay runner, read by a seat that must decide whether to stop):

  • scripts/pm/write-pace.mjs: splitting "could a proxy be in the path" (proxyAnswered, env) from "does this body read as the proxy's" (isProxyRefusal, pure) is the right cut: the body predicate stays shared with the seat-side transports unchanged in signature, the env predicate is the one thing that differs between a runner and a seat, and GITHUB_ACTIONS is the documented default variable for exactly that question. Requiring a positive mark closes the misreading the card measured without loosening the proxy case. The env threading (stopSignalFrom field, noteResponse deps.env) is minimal and defaulted. Holds.
  • scripts/pm/fleet-write/execute.mjs: printing the platform's message and docs URL under caps, shape checks and a prose-quote blocklist is a sound replacement for a six-string allowlist: the allowlist's failure mode was the card's defect ② (an unlisted message hides the cause), while the blocklist's failure mode is a withheld cause with a line saying so. Excluding ids, logins, states and URL-shaped words from known is reasoned correctly (an advisory's own url shares its path with GitHub's docs). Keeping errors out entirely is right: it is the one field that echoes submitted values. The two should-fixes below tighten the guard's robustness; the design holds.

Must-fix: none.

Should-fix:

  • S1 (PR body, seat's call before landing, no new head): the body says Fixes #29402, but the card's third Expected bullet (the seat reads the actual reason once and records it with its seat-side rule in references/rest-channel.md) is deliberately left out of this PR, and the dev report carries it as "noted, not filed". Under review-checklist's Fixes/Part-of rule, either switch to Part of #29402 and arrange the close in a comment, or file the follow-up card and name it in the body. The same card should carry the out-of-surface drift from ③: ops.mjs header lines 433 to 434 and ADVISORY_ACCESS_REMEDY's "first live run", the now-callerless ADVISORY_PLATFORM_MESSAGES/advisoryPlatformText, label-write.mjs line 420, and the bare-403 seat-side stop rule.
  • S2 (execute.mjs, one line): quotesKnown(oneLine, known) compares the flattened message with unflattened known prose, so a quoted fragment that straddles a |, backtick or " in the known text matches only if one side alone spans 16 characters. Add quotesKnown(raw, known) beside it (or flatten known the same way). Not a leak today: GitHub's refusal message does not echo submitted prose, the errors array that does is never printed, and the marker sweep is green.
  • S3 (execute.mjs, cosmetic): on status: 0 (fetch threw) rest() puts the transport error's text in detail, and the row then says "cause, as the platform gives it: ..." for text the platform never sent. Branch on status === 0 to say the request did not reach the platform.

Check-runs at record time (2026-11-02T17:10Z): 46 check-runs on 478c92a, all completed, 26 success, 20 skipped, 0 failure, 0 in progress. Success: Lint & Repo Gates, TypeScript Type Check, Test Core, Governed Surface Queue Guard, Test UI, Lint · ESLint, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · tooling self-tests, Lint · PM dispatch-gates, Lint · CI release & engine gates, Type Check · workspace/source gates/debt ledger/consumer gates, PR-body guards, Check Changeset, Check PR Size, Dogfood Regression Gate, Plan Test Core shards, Auto Label, filter. Skipped by path filter: Build Core, Lint UI, Type Check UI, Build & E2E UI, Live E2E UI and the opt-in lanes. Combined status pending (statuses API; no check-run is pending). mergeable_state blocked (draft, Tier S record pending).

Implemented-by: claude/project-thread-rv61tq
Reviewed-by: session_01J8VVAnMUWA9rufXzFaEh4J

VERDICT: PASS with should-fixes (S1 PR-body Fixes/Part-of and the follow-up card; S2 quote check against the raw message; S3 status-0 wording)


Generated by Claude Code

… and a transport failure never reads as the platform's reason

The prose-quote check now judges the raw message as well as the flattened one,
against the known prose as given and flattened the same way, so a quoted run
that straddles a pipe, a backtick, a quote or a line break is withheld. On
status 0 (the fetch threw) the row says the request did not reach the
platform instead of "cause, as the platform gives it".

Claude-Session: https://claude.ai/code/session_01J8VVAoLff4MC52HYARNBKP
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3f3345a87563e768c016b7d3eab0827e630bd0bb
Local-runs: probe — on a detached worktree of the head (merge-base with origin/main still 04e0c2c; origin/main has moved to c88467d and neither touched file moved on main since the merge-base): node scripts/pm/fleet-write/execute.mjs --self-test (242 cases, 21 batteries, exit 0, marker found in no emitted line), node scripts/pm/write-pace.mjs --self-test (118 cases, 12 batteries, exit 0); seven reviewer-authored ablations of execute.mjs (A1 to A6 and cr17's E1 re-run), each applied to the committed tree, measured, and restored with git checkout HEAD -- to the head blob (git hash-object equal to the PR's file sha c1c5e176, git status --porcelain empty), plus two probe runs of advisoryRefusalCause with constructed inputs under A2 and A3 (①.4); node scripts/pm/dispatch-gates.mjs --commands over execute.mjs (35 commands derived), of which 27 were run here (①.7), including check:pm-dispatch-gates (252 s) and the self-tests of fleet-write/validate (275/15), fleet-write/dispatch (298/26), label-write (136/11) and fleet-token (75/8), all exit 0; the worktree was removed after the runs and the main working tree was not touched
Scope: delta review of 3f3345a over 478c92a (the head cr17 passed, PR comment 6583357121): scripts/pm/fleet-write/execute.mjs alone (+21/−4, the only file the delta touches) + read, not reviewed: scripts/pm/fleet-write/ops.mjs (advisoryReportReadBack's why on a status-0 answer), the unchanged scripts/pm/write-pace.mjs (blob 07d17a67, identical to 478c92a), the current PR body (read against the head's counts), cr17's record as the baseline

Reviewer: an isolated contract reviewer at CONTRACT_REVIEW_TIER, read-only on GitHub, fed cr17's PASS on 478c92a, the one-commit delta, the current PR body, the head's check-runs, contract-review.md and review-checklist.md. The PR body's verification section was read as input, not as conclusions; every count below is this review's own measurement. This record judges only what moved since 478c92a; cr17's judgments stand where the code is unchanged. This record carries no finding, no mechanism, no advisory id and no advisory title; the self-test fixtures use a generic marker sentence and made-up ids, and none is repeated here. No environment variable value is printed anywhere in this record.

① Derived judgments

The delta, one claim at a time:

  1. File surface. The PR has exactly two commits (478c92a, then 3f3345a, both author Claude via objectstack-fleet[bot], head repo = base repo, draft, base main); git diff 478c92a0 3f3345a8 --stat is scripts/pm/fleet-write/execute.mjs alone, +21/−4; the REST file listing (changed_files: 2) gives execute.mjs blob c1c5e176 and write-pace.mjs blob 07d17a67, and the head's write-pace.mjs blob equals 478c92a's, so cr17's ①.2 to ①.4 (the whole write-pace side) apply to this head unchanged. Against the merge-base the PR is still the two files, now +205/−32. Neither file moved on origin/main since 04e0c2c. Correct.
  2. What the delta is. One new pure helper flattenRefusalText (the same control-character, whitespace, pipe, backtick and quote rewrite the old inline expression did, lifted out so the known prose can be flattened by the same rule); in advisoryRefusalCause, a status === 0 branch before the documentation_url read; the quote check widened from quotesKnown(oneLine, known) to quotesKnown(raw, known) || quotesKnown(oneLine, [...known, ...known.map(flattenRefusalText)]); two new self-test cases (the pipe-straddle row and the three status-0 readings); the advisory battery's ledger row 30 → 32 and the marker sweep's expected run count 45 → 46. Nothing else moves: the message source (detail, else json.message), the caps, REFUSAL_URL_SHAPE, the errors array never read, the callers in confirmAdvisory and advisoryPreflight, and the header are as cr17 read them.
  3. S2 (cr17): the quote check straddling a flattened character. Fixed in substance: a payload run that straddles a pipe (|…| in the summary, quoted raw by the platform) is now withheld, where the old check printed it rewritten with slashes. Measured: ablation A1 (the line reverted whole to quotesKnown(oneLine, known)) reds 2 of 242, the straddle case and the marker sweep, which reproduces the body's reading exactly; cr17's E1 (quotesKnown → false) now reds 7 of 242 (5 at the old head: the two new cases join). The marker sweep covers 46 runs (45 before) and finds the marker in no log line, row, summary, annotation or pace-log line; the positive controls still hold. Fixed, and covered as a whole.
  4. S2, the two halves read separately. The new check is a disjunction, and each half has a message shape only it catches: the raw half alone catches a message quoting a raw 16-character window whose whitespace flattens to fewer than 16 characters (probe under A3, raw half dropped: that message is printed); the flattened-known half alone catches a message that carries the payload fragment already rewritten, slash for pipe (probe under A2, flattened-known half dropped: that message is printed). The committed straddle case, though, is caught by either half: A2 and A3 each leave all 242 cases green. So the self-test's comment ("only the raw message reproduces it") and the body's "withheld only because the raw message is judged too" are imprecise — the flattened-known side reproduces it as well — and neither half has a case that reds when it alone is dropped. Not a leak: at the head both shapes are withheld (probed), and the sweep is green. Should-fix S1 below, test precision only.
  5. S3 (cr17): status 0. Fixed: rest()'s thrown fetch returns { status: 0, json: null, detail: scrub(e.message, [token]) }, and the new branch fires on answer?.status === 0 before anything else, returning "cause: the request did not reach the platform — the transport failed[: ""], so the platform gave no reason"; the transport text is the token-scrubbed detail, flattened, capped at 200, and dropped altogether when quotesKnown(raw, known) or empty. The branch is reachable on the live path: both confirmAdvisory (refused = !(2xx)) and advisoryPreflight treat status 0 as refused and call the cause with the right known. Measured: A4 (branch disabled) reds 1 of 242 (the status-0 case, all three readings wrong); A5 (the branch keeps a transport text that quotes the payload) reds 1 of 242 (the second reading prints the marker). A6 (the branch's cap removed) stays green: the cap inside the status-0 branch has no case; a transport error's text is short in practice and the row is still bounded by the flattening, so this is cosmetic, folded into S1. Fixed, and covered.
  6. Leak surface of the new code. The only new text any row can carry is the status-0 sentence, whose variable part is the transport error's message — token-scrubbed in rest() before it reaches the function, never the request body, dropped when it quotes any known prose — and the quote check now has one more way to say "not printed", never one more way to print. flattenRefusalText is applied to known only as a blocklist; its output is never emitted. The delta's added lines name summary only inside the self-test fixture. No path prints a summary, description, vulnerability, credit, token or environment value into a log line, row, summary or annotation; the header's description of the printed set still holds (it does not mention the status-0 sentence, which carries nothing of the platform's; no edit owed).
  7. Gates at this head. Of the 35 commands dispatch-gates.mjs --commands derives for execute.mjs, 27 were run here, all exit 0: check:pm-fleet-write-execute, check:pm-write-pace, check:pm-fleet-write-validate, check:pm-fleet-write-dispatch, check:scripts-symbol-anchors (4155 anchors resolve), check:nul-bytes, check:entry-guard, check:parse-guard, check-self-test-wired (run and --self-test), check:watch-hint-literal, check:agent-test-spelling, check:gitlink-declared, check-declaration-mirrors (run and --self-test), check:ratchet-remedy-authority, check-whole-set-label-write (run and --self-test), check:pm-dispatch-gates (--self-test and run, 252 s), plus label-write and fleet-token. Three could not run in the bare worktree for the same reason cr17 recorded (no node_modules: check-closing-keyword-parity and check-comment-mask-corpus print PREREQUISITE NOT MET, rc 3, naming yaml and @typescript-eslint/parser; ESLint not run); CI's Lint · docs & code guards, Lint · ESLint, Lint · scoped · tooling self-tests, Lint · PM dispatch-gates and Lint & Repo Gates are success on the head, which covers them.
  8. The PR body against the head. First line Part of #29402. with no verb beside the number (cr17's S1, disposed; the PR-body guards' Part-of check is success on the head); no closing keyword anywhere near pm/fleet-write: an advisory_read relay op that returns an advisory only as ciphertext, encrypted to a one-time key the seat generates per read #29420 or feat(pm/fleet-write): three security-advisory relay rows, payload read from the event file #29222. 118/12 and the rule ③ battery 14 → 19 (unchanged file); 242/21 with the advisory battery 27 → 32 (ledger row and run agree) and the sweep 46 (code and run agree); the two head-ablation readings (2 of 242 for the quote check reverted whole, 1 of 242 for the status-0 branch) reproduce exactly; the 478c92a readings are cr17's and unchanged; 36 derived commands for the two paths, 35 for execute.mjs alone (measured); skip-changeset is right and the label is on the PR; Clause-②: no and Metadata-protocol: none are right. One wording is imprecise (①.4): "a quoted payload run straddling a pipe, withheld only because the raw message is judged too" — it is withheld by either half. Folded into S1.
  9. No new regression in what cr17 judged. The write-pace side is blob-identical; on the execute side the message source, the errors exclusion, the URL shape and caps, the pre.known threading, the annotation surface (landed rows only) and the row text for a non-zero refusal are untouched, and the self-test's existing cases (unlisted message in row/summary/log, quoting message and quoting URL withheld, percent-decoded URL, no message, cap and flattening, ill-shaped URL) are green. The one row that reads differently is the status-0 row, which now says the request did not reach the platform; its prefix is still FAILED: HTTP 0; from ops.mjs's read-back why (out of surface, pre-existing; the cause sentence that follows corrects the reading). Nothing new on any gate, label, workflow, CODEOWNERS, package or governed-register path; the fixtures name no real advisory.

② Semver level

None, unchanged from cr17. No published package, no packages/spec/src/**, no TypeScript; scripts/pm tooling only. skip-changeset is the right declaration; Check Changeset is skipped on this head (the label), which is the ordinary reading for it.

③ Boundary flags

  • cr17's S1 (PR body): disposed by the body's Part of #29402; the follow-up card cr17 asked to carry the out-of-surface drift (ops.mjs header lines 433 to 434 and ADVISORY_ACCESS_REMEDY's "first live run", the callerless ADVISORY_PLATFORM_MESSAGES/advisoryPlatformText, label-write.mjs line 420, the bare-403 seat-side stop rule, and now the HTTP 0 prefix of ①.9) is not yet filed: the body lists the drift under Acceptance notes as "left for the seat's follow-up" without a number. Under review-checklist's Part-of rule the card does not auto-close and pm:dispatched is hand-removed; filing the card and arranging the close are the seat's at ACCEPT, not the dev's. No flag.
  • cr17's S2 and S3: addressed in this head (①.3 to ①.5), with the precision gap of ①.4 as the one remaining should-fix.
  • cr17's ③ out-of-scope notes (a) and (b) and the pm/fleet-write: an advisory_read relay op that returns an advisory only as ciphertext, encrypted to a one-time key the seat generates per read #29420 overlap are unchanged by this delta; not re-raised. The dev's "whichever lands second merges main, the seat re-reviews the delta" still stands.
  • Dev open_questions: none.
  • Independence: PR author objectstack-fleet[bot], commit author Claude; no off-relay author note is owed.

④ Design judgment

Per hits file, against the card's scenario (a refused advisory row read by a seat that must decide whether to stop):

  • scripts/pm/fleet-write/execute.mjs: judging the message both raw and flattened, against the known prose both as given and flattened by the same rule, is the right closure of the gap cr17 named — the blocklist now sees the text in every form either side of the table-safe rewrite can take, and its only failure mode is still a withheld cause with a line saying so. Lifting the rewrite into one pure helper keeps the two sides identical by construction. Branching on status === 0 before the platform fields are read is the correct cut: rest() is the one place that produces that status, its detail there is the transport's, and the row no longer attributes it to the platform while still handing the seat the (scrubbed, quote-checked, capped) transport text it would need. The design cr17 judged holds; this delta tightens it.

Must-fix: none.

Should-fix:

  • S1 (execute.mjs self-test and PR body, test precision; no leak, seat's call whether it needs a new head): the straddle case is caught by either half of the new check, so dropping the raw half alone (A3) or the flattened-known half alone (A2) leaves 242 of 242 green, and the status-0 branch's cap (A6) has no case either. Add one case per half — a message carrying the payload run already rewritten, slash for pipe (reds when the flattened-known half is dropped), and a message quoting a raw 16-character window whose whitespace flattens under 16 characters (reds when the raw half is dropped) — and, if wanted, a status-0 transport text over 200 characters; then correct the test comment ("only the raw message reproduces it") and the body's "withheld only because the raw message is judged too" to say either half catches it. Both shapes are withheld at the head (probed), so this is coverage and wording, not behaviour.

Check-runs at record time (2026-11-02T17:28Z): 44 check-runs on 3f3345a, 43 completed (24 success, 19 skipped, 0 failure), 1 in_progress (Test Core 1/8). Success: Lint & Repo Gates, TypeScript Type Check, Lint · ESLint, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · tooling self-tests, Lint · scoped · slot lookup & corpus, Lint · scoped · query-options erasure, Lint · PM dispatch-gates, Lint · CI, release & engine gates, Type Check · workspace/source gates/debt ledger/consumer gates, Governed Surface Queue Guard, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Test Core (2/8), Test UI, Dogfood Regression Gate, Check PR Size, Auto Label, filter. Skipped by path filter or label: Check Changeset, Build Core, Build Docs, Build UI Deps, Lint UI, Type Check UI, Test UI shards, Build & E2E UI, Live E2E UI, Dogfood Verify CLI, Dogfood shards, UI Published Artifacts, Temporal Conformance, Schedule gate and the two opt-in lanes. Combined status pending (statuses API, 0 statuses; no check-run failed). mergeable_state blocked (draft). Labels size/m, skip-changeset.

Implemented-by: claude/project-thread-rv61tq
Reviewed-by: session_01J8VVAnMUWA9rufXzFaEh4J

VERDICT: PASS with should-fix (S1 test precision: one case per half of the quote check, the status-0 cap, and the "only the raw message" wording)


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J

维护者速读

  • 改了什么: 修 fleet-write: after the first live advisory_report, every later report answers HTTP 403, write-pace files it as an egress-proxy refusal (no back-off), and the relay hides GitHub's message, so the cause cannot be read #29402 的三个中继工具缺陷。① write-pace 在 Actions 运行器里不再把 GitHub 的 403 当成出口代理的拒绝,改按规则 ③ 判定(不退还配额,有退避信号才写停止标记)。② 安全公告行被拒时,打印平台给的 message 和 documentation_url(截断),绝不打印载荷;若平台消息引用了提交的正文,就整句隐去。③ 拒绝行的建议写明原因;请求根本没到平台(status 0)时也照实说。
  • 为什么改: 现场运行测得第一次上报后每次都 403,中继却报"代理拒绝"且不给原因,无法分辨是限额还是权限。
  • 风险与回滚: 只改两个 pm 工具脚本,不在受治理面上;可整 PR revert。
  • 两分钟验证: node scripts/pm/fleet-write/execute.mjs --self-test,预期 242 例全过,标记扫描 0 泄漏。
  • 需要你做的: 无。Tier S,本席落地。
  • 席位意见: ACCEPT。复核 PASS(6583357121),三处建议改动已在新 head 落实,增量复核 PASS(6583760182)。

席位核对 — ACCEPT · domain:skills#1 · head 3f3345a87563e768c016b7d3eab0827e630bd0bb

  1. 形态: draft → main,Part of #29402(卡上第三项"席位实测 403 原因并写进 rest-channel.md"留给本席在现场读数之后做);skip-changeset;作者 objectstack-fleet[bot];改动正好是认领时的 2 个文件(6582331169)。
  2. 前提与自测: origin/main 上 write-pace 不分运行器一律按代理拒绝处理,已被复核证实。新 head 上 write-pace 118/12、execute 242/21 全过,标记扫描 46 次 0 泄漏;复核方自做的消融逐个变红。合并基 04e0c2c0 之后 main 没动过这两个文件。
  3. check-runs(钉 head 3f3345a8): success 26 / skipped 19 / 无红无在跑;check-governed-merges --pr 29505:非受治理面,+205/−32,低于 3000 行人合线。
  4. 接受的偏差: 增量复核 S1,测试精度:新引用检查是"原文"与"压平后的已知正文"两半的析取,现有用例两半各自都能抓住,所以单删一半不变红;status 0 的 200 字截断也没有用例。没有泄漏路径,不值一个新 head。连同开发报告的区外漂移(ops.mjs 头注释和 ADVISORY_ACCESS_REMEDY 措辞、已无调用者的 ADVISORY_PLATFORM_MESSAGES/advisoryPlatformText、label-write.mjs 第 420 行措辞、裸 403 的席位侧停止规则),记在 fleet-write: after the first live advisory_report, every later report answers HTTP 403, write-pace files it as an egress-proxy refusal (no back-off), and the relay hides GitHub's message, so the cause cannot be read #29402 上,由本席在卡的第三项里一并处理或转入 pm/fleet-write: three text drifts left by PR #29436 (advisory_create) — security-findings.md names an advisory_report fallback the mint cannot deliver, execute.mjs cites a renamed ops.mjs section, fleet-write.yml says THREE consumers #29448。
  5. 落地条款: Tier S。CI 全绿后本席 pr_ready + automerge_enable。

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 17:31
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 41491992528 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (1/8) — 失败步骤: Run this shard's tests

    @objectstack/spec:test:      ✓ inverts the card's measured control: the three bogus keys now FAIL parse instead of adding zero warnings  355ms
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    @objectstack/spec:test:  FAIL   local  scripts/dropped-refinements.test.ts > the committed ledger > names at least one site per entry, and its header totals match its body
      ↳ 失败原因: @objectstack/spec:test: AssertionError: expected 993 to be 998 // Object.is equality
    @objectstack/spec:test:repo:  FAIL   repo  scripts/strictness-ledger-doc.test.ts > the ledger and the generated counts agree > is checked in current — every shard on disk equals a fresh render, and no
      ↳ 失败原因: @objectstack/spec:test:repo: AssertionError: expected Map{ …(14) } to deeply equal Map{ …(14) }
    @objectstack/spec:test:repo:  FAIL   repo  scripts/strictness-ledger-doc.test.ts > the ledger and the generated counts agree > the read-time totals equal the sums of the shards' own rows
      ↳ 失败原因: @objectstack/spec:test:repo: AssertionError: expected 'untriaged: 1297 object site(s) across…' to be 'untriaged: 1296 object site(s) across…' // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 10 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 41495587695 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (1/8) — 失败步骤: Run this shard's tests

    @objectstack/spec:test:      ✓ inverts the card's measured control: the three bogus keys now FAIL parse instead of adding zero warnings  356ms
      ↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行 —— 点进 job 看)
    @objectstack/spec:test:  FAIL   local  scripts/dropped-refinements.test.ts > the committed ledger > names at least one site per entry, and its header totals match its body
      ↳ 失败原因: @objectstack/spec:test: AssertionError: expected 993 to be 998 // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 19 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

huangyiirene pushed a commit that referenced this pull request Nov 2, 2026
Conflicts in scripts/pm/fleet-write/execute.mjs, resolved as follows:
- the ops import list takes both sides: advisory_read's names and #29505's
  ADVISORY_STATE_SHAPE;
- the battery map keeps #29505's advisory rows count and advisory_read's row;
- advisoryPlatformText is imported again. #29505 moved the other advisory
  rows onto advisoryRefusalCause and dropped the import, but advisory_read's
  refused arm still quotes only the messages ADVISORY_PLATFORM_MESSAGES names
  (review S2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAng9QxNxoEzyWkqbW6
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

Development

Successfully merging this pull request may close these issues.

1 participant