Repository navigation
chore(pm): delete report-only check-widening-tells.mjs and its wiring (ruling 208) - #22002
Conversation
…ring Removes scripts/pm/check-widening-tells.mjs, its check:pm-widening-tells package script and its lint.yml self-test step, and re-points every live mention: the measurement-claim triage EXCLUDED row and scope docblock, two source comments that cited the file as an importer, the stale step reference in a neighbouring lint.yml comment, the two pm-dispatch instrument-discipline lines that named it, and the census rows (kept, marked deleted). Nothing replaces it; no gate is added. Claude-Session: https://claude.ai/code/session_0181E4ZeZmWyknawnauxD2CE Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Face reviewed: governed rule text — ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
维护者速读(终稿)改了什么:删掉 PM 内部那个只报告、不挡任何 PR 的「放宽 tell」仪器 为什么改:裁决 208 定了只报告的仪器不配 dev、在途工作只有删除;您在分诊席决策批 2 第 2 项答「同意」删它。它在普查窗口里零拦截,却每次 CI 都跑自测、隔三岔五要人修(最近一次 10-01)。 风险与代价(含回滚):Clause-② 方向的判断回到达档复核席手里(原本也是席位裁,脚本只印读数)。卡上一条前提是假的——仓里没有任何文件从它 import 席位意见:席位达档复核 PASS(记录在本 PR 评论 ⬆);改动是一次机械删除加七处改引用,范围与卡面一致,无扩写。建议批准。 你要做的:一个动作——在本 PR 上点 Approve。改动 6580 行超过 5000 行人工合并线,规则要求授权账户 APPROVED;批准后由席位在 OSV 红清除、CI 全绿时入队落地,不需要您合并。 |
|
Landing status — Tier H by size; the one red check is not this PR's ·
|
|
CI note — one re-run of the cancelled type-check lane ·
|
Contract reviewServed-tier: Successor record to 6019414425 (head ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…to a checkout budget plus a body budget (objectstack-ai#22033) Fixes objectstack-ai#22020 Clause-②: no ## Which route The card's done-when offers two routes. This PR takes the first one, in the split form triage asked for (`6022336293`). Every full-history checkout under a fixed job wall gets its own step-level `timeout-minutes: 20`. Each job wall becomes that checkout budget plus the job's re-measured body budget. No gate, command, fetch depth, job id or check name changes. The seven required contexts are untouched, and the merge-base anchor of the authorable-surface deletion gate keeps its full history (`fetch-depth: 0` is unchanged on every job). | job | file | wall before | checkout step | body budget | wall after | |---|---|---|---|---|---| | Type Check · source gates (the card) | `lint.yml` | 10 | 20 | 10 | **30** | | Type Check · workspace (adjacent) | `lint.yml` | 30 | 20 | 35 | **55** | | Type Check · debt ledger (adjacent) | `lint.yml` | 15 | 20 | 15 | **35** | | Type Check · consumer gates (adjacent) | `lint.yml` | 20 | 20 | 25 | **45** | | Governed Surface Queue Guard (adjacent) | `governed-surface-guard.yml` | 10 | 20 | 10 | **30** | **In-scope adjacent fixes.** All four have the same defect class and get the same mechanical treatment: - The other three Type Check lanes, added by the PM after merge-group run 37509811445. In that run, job 112427734781 ("Type Check · consumer gates") spent 11m05s in the checkout and was cancelled at its 20-minute wall. The aggregate failed and PR objectstack-ai#22016 was ejected from the queue. - The Governed Surface Queue Guard, which the claim named (run 37508992265, job 112424902156). The `lint` job is not touched. Open PR objectstack-ai#22002 edits its region. ## Measured **Window.** I read the 300 most recent completed runs of `Lint & Type Check` (2026-10-05T15:32Z to 2026-10-06T18:41Z) and of `Governed Surface Guard` (2026-10-05T14:57Z to 2026-10-06T18:59Z). Every attempt was included (`GET /actions/runs/{id}/jobs?filter=all`), using step timestamps. The controls come from the 300 most recent `CI` runs (2026-10-05T15:15Z to 2026-10-06T18:22Z). **Checkout per job.** "Wall hits" counts the runs that the old wall cancelled behind a slow checkout: | job | n | p50 | p90 | p99 | max | wall hits | |---|---|---|---|---|---|---| | Type Check · source gates | 293 | 31 s | 122 s | 599 s | 600 s (cut by the wall) | 8 | | Type Check · workspace | 296 | 31 s | 100 s | 395 s | 421 s | 0 | | Type Check · debt ledger | 293 | 31 s | 123 s | 599 s | 774 s | 2 | | Type Check · consumer gates | 293 | 31 s | 151 s | 574 s | 735 s | 2 (both `merge_group`) | | Governed Surface Queue Guard | 292 | 31 s | 156 s | 595 s | 599 s (cut by the wall) | 4 | **Pooled.** Adding the same fetch from `ci.yml`'s Test Core shards gives n = 3,459: p50 31 s, p90 119 s, p99 553 s, max 895 s. Another 19 samples were cut off by a wall and are lower bounds only. **Control.** The shallow (`fetch-depth` 1) checkouts in `ci.yml` over the same hours (n = 2,743) read p50 15 s, p99 63 s, max 107 s. None went over 180 s. So the slow tail belongs to the full-history fetch, not to the runner pool. **Body budgets.** The body is everything after the checkout, on successful runs: | job | max | 2 × max | budget | |---|---|---|---| | source gates | 3.2 min | 6.4 | 10 (the floor) | | workspace | 17.9 min | 35.8 | 35 | | debt ledger | 7.4 min | 14.8 | 15 | | consumer gates | 11.5 min | 23 | 25 | | guard | 1.1 min | 2.2 | 10 (the floor) | The rule is the `lint` job's: 2× the measured max, on a 5-minute grain, never below 10. **Checkout budget.** 20 minutes is 1.3× the pooled 895 s max. The rule's 2× would give 30, but the queue's window binds first: the workspace lane's 35 plus 30 is 65, past the 55 cap the `lint` job argues, while 35 + 20 = 55. It is one fetch, so every job gets the same budget. ## What the required aggregate reads **Before.** The lane's job wall stopped any checkout that left the body too little time. That covers slow checkouts that would have finished, such as the 555 s and 588 s cases, as well as stuck ones. The lane read `cancelled` and every gate was skipped. `TypeScript Type Check` printed "concluded `cancelled` -- expected `success`" and failed the PR. **After:** - **A checkout that ends inside 20 minutes:** nothing happens. The body keeps its full budget and the lane goes green. In this window, that covers every checkout that completed. - **A checkout still running at 20 minutes:** the step fails with "The action 'Checkout repository' has timed out after 20 minutes." Later steps are skipped, the lane reads `failure` and the aggregate goes red. No layout can keep a truly hung checkout green, because the aggregate correctly refuses a lane that ran no gate. This layout names the step that hung, and it fires only beyond 1.3× anything measured. - **A later step that hangs:** the job wall cancels the job, the lane reads `cancelled`, and the aggregate goes red, as before but at the new wall. **Source check (premise 2).** In actions/runner `main` at `67f01c27`, `StepsRunner.RunStepAsync` evaluates every step's `timeout-minutes`, `uses:` steps included. When it expires, the runner sets `TaskResult.Failed` with that message. A job-level cancellation sets `TaskResult.Canceled` instead. No workflow in this repo had a step-level timeout before this PR. ## Deviation from triage's direction Triage wrote: "The gate steps keep the stall guard's intent through their own step-level timeouts." I did not add those. The PM thread has the reasoning: - Actions has no timeout over a group of steps. Covering the gates would take one step-level timeout per step, about 65 of them across the four lanes, each sized from a single 27-hour window. - Several of those steps are bimodal, for example a turbo cache hit in 4 s against a miss in 5 minutes. Timeouts sized that tightly would be a new source of random reds, which is this card's own defect class. The cost: after a fast checkout, a hung gate now runs up to the new wall (30, 35, 45 or 55 minutes) instead of the old one. Every new wall stays at or under the 55 cap, inside the queue's 60-minute window. ## This PR's own lane run Read from the jobs API by the seat (step timestamps), run `37519814891` (`Lint & Type Check`, head `f5060b251`) and run `37519814823` (`Governed Surface Guard`): | job | conclusion | checkout | job duration | new wall | |---|---|---|---|---| | Type Check · source gates | success | 34 s | 197 s (3.3 of 30 min) | 30 | | Type Check · debt ledger | success | 147 s | 217 s | 35 | | Type Check · workspace | success | 242 s | 280 s | 55 | | Type Check · consumer gates | success | 33 s | 298 s | 45 | | TypeScript Type Check (aggregate) | success | — | 3 s | — | | Governed Surface Queue Guard | success | 38 s | 66 s | 30 | _Section filled in by the `domain:devx` seat 2 PM from the run above, because the body is written before its own run exists._ ## Local gates (head `f5060b251`) `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 44 commands from the actual diff. Exit codes were written to disk before reading: - 42 exit 0. This includes `check:required-contexts`, `check:stall-guard-budget`, `check:stall-guard-headroom`, `check:workflow-status-functions`, `check:workflow-step-name-quoting`, `check:aggregator-roster` (+ `--self-test`) and `check:step-collectors` (+ `--self-test`). The full reconciliation is in the report. - I also ran the guard's own `check-governed-queue-guard.mjs --self-test`: 296 cases passed, including the `fetch-depth: 0` pin. - `check:type-check-debt` ran its `--self-test` half (exit 0). Its `--re-measure` half needs the workspace build the debt lane runs first. It is declared to this PR's CI rather than measured here, because the diff touches no package and no ledger. ## Acceptance notes - **Mechanism, measured locally and not changed here.** `fetch-depth: 0` makes actions/checkout v7 fetch `+refs/heads/*` and `+refs/tags/*`: 1,236 branch heads and about 7,950 tags today. From this container: | fetch | time | pack | |---|---|---| | `main`'s full history alone | 29 s and 32 s | 397 MB | | the all-refs refspec CI uses | 473 s | 598 MB | | all heads without tags | 686 s | 597 MB | | depth 1 plus unshallowing `main` | 120 s and 169 s | not recorded | The last variant kept the anchor exact on PR objectstack-ai#22002's merge ref and on PR objectstack-ai#6356's old head. The cost is the ref set, not `main`'s depth. Fetching less could make every one of these jobs faster, but the CI tail of any narrower fetch is unmeasured (n = 0), and its semantics need a per-gate audit across all five jobs. It is left as a question for the PM in the report. - **`ci.yml` Test Core shards**, same fetch, 45-minute wall: one shard was cancelled at the wall behind a 508 s checkout in the window (run 37338337323, `Test Core (2/6)`). This is outside this PR's file surface, and those steps run under the stall guard, whose budget gate reads that wall. The report names it for the seat. - **`Lint & Repo Gates`** is not exposed in the window: body max 34.5 min plus checkout max 730 s is 46.7, under its 55. --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ Co-authored-by: Claude <noreply@anthropic.com>
…OSV scan red (objectstack-ai#22016) Fixes objectstack-ai#22013 Clause-②: no ## What this does `Validate Package Dependencies` runs OSV-Scanner against `pnpm-lock.yaml`. Two advisories published on 2026-10-06 match `main`'s lockfile, so every PR that touches a `package.json` inherits a red that is not its own (PR objectstack-ai#22002, run 37483796939, is the first measured), and the next scheduled scan will be red too. Both advisories name a fixed version, so this PR takes both fixes. There is no exemption: `osv-scanner.toml` is untouched, because it is for advisories with no fixed release. PR objectstack-ai#22002 is not touched here. Its `update-branch` after this lands is the PM's pointer to its holder. ## OSV reading: before and after Measured locally with OSV-Scanner **v2.3.8**, the version `validate-deps.yml` pins, in offline mode (`--offline-vulnerabilities --download-offline-databases`). The OSV npm database was downloaded on 2026-10-06 at 16:36 UTC, after both advisories were published (13:40 and 13:43 UTC). `main` at `803764a3` (exit 1). These are the two rows the card names: ```text | https://osv.dev/GHSA-wq5f-xc86-pv6w | 8.9 | npm | sharp | 0.35.4 | 0.35.5 | pnpm-lock.yaml | | https://osv.dev/GHSA-pqg4-j6r4-53mv | 9.2 | npm | shell-quote | 1.10.0 | 1.11.0 | pnpm-lock.yaml | ``` This PR at `d263b701` (exit 0): `No issues found`. One vulnerability is filtered, the standing `sprintf-js` exemption, unchanged. The scanner names nothing beyond these two advisories, so no third one has appeared since the card was filed. ## Changes ### `sharp`: the override target lifts from `^0.35.4` to `^0.35.5` - **Advisory:** GHSA-wq5f-xc86-pv6w (8.9 high). It is a memory bug in the librsvg that sharp's prebuilt binaries bundle. The range is introduced 0, fixed 0.35.5, read from the scanner's offline database. - **Edit:** the target only, `'sharp@>=0.34.0 <0.36.0': '^0.35.5'`. The selector already sits at the 0.x caret boundary, so it does not change. This is the same shape as the objectstack-ai#16999 lift on this entry. - **Dedupe, not a forced upgrade:** `next@16.3.6` declares the optional `sharp: ^0.35.4`, which already admits 0.35.5. 0.35.4 was the single resolved copy. - **What moves with it:** sharp pins its prebuilt `@img/sharp-*` binaries exactly, so they move to 0.35.5, and the libvips binaries move to 1.3.4. - **Measured:** the installed sharp 0.35.5 loads on linux-x64. It reports `rsvg 2.63.2`, the librsvg release the advisory names as fixed, and it renders a PNG. ### `shell-quote`: a new override, `'shell-quote@>=1.8.4 <2.0.0': '^1.11.0'` - **Advisory:** GHSA-pqg4-j6r4-53mv (9.2 critical). In `quote()`, a line terminator in a string after a `{ comment }` token ends the comment, and the rest of that string runs as shell input. The range is introduced 1.8.4, fixed 1.11.0. - **The one path:** `launch-editor@2.14.1`, then `@changesets/cli@3.0.3`, then the root `package.json`'s `devDependencies`. launch-editor declares `shell-quote: ^1.8.4`, which admits the fix, so this is a dedupe onto the patched line. The copy sat on 1.10.0 through lockfile inertia. - **Selector shape:** the floor is the advisory's 1.8.4, and the bound sits at the 2.0.0 major boundary, per the block header's rule. The bound is never `<1.11.0`. - **Resolution:** `^1.11.0` floats to **1.12.0**, the newest 1.x. launch-editor calls shell-quote in one place, `parse()` on the editor command (`guess.js`). 1.11.0 and 1.12.0 only teach `parse()` ANSI-C quoting and more operators. Seven editor command strings (`code --wait`, a quoted macOS path with `-w`, `emacsclient -t -a ''` and others) parse identically under 1.10.0 and 1.12.0. The vulnerable `quote()` is never called on this path. The fix is taken anyway, because the gate reads the lockfile, not the call graph. - **Note:** the entry carries a note in the block's style, at the foot of `overrides:`. ### Why an override and not a `@changesets/cli` bump No release on that path forces the fix (`npm view`, 2026-10-06): - `@changesets/cli` 3.0.3 is npm `latest`, which is the version the root already declares. Every 3.0.x declares `launch-editor: ^2.14.1`. - launch-editor's latest, 2.14.2 (published today), declares `shell-quote: ^1.10.0`, which still admits the flagged 1.10.0. So the bump route does not exist, and a launch-editor bump would still leave the floor to lockfile inertia. On the four axes: - **Real need:** the measured need is a patched resolution and a green gate. Only the override delivers both, because no upstream release declares a range above 1.10.0. - **Long-term soundness:** the entry follows the block header's selector rule (bound at the major boundary), so a later advisory is a target-only lift. It turns into a dedupe floor once launch-editor declares `^1.11.0`. Its cost is one more ledgered entry. - **Making AI mistakes harder:** a declared floor is audited by `check:override-consistency`, and no re-lock can land below it. A bare re-lock with no floor, like objectstack-ai#21951's `proxy-addr` step, leaves nothing to stop a later resolution from drifting back. - **Startup scope:** this is the smallest change. It moves no devDependency and adds no gate. ## Lockfile diff `pnpm-lock.yaml` is **+126/−125**, re-locked by `pnpm install`, never by hand. Every changed line falls into one of four kinds: - a `sharp`, `@img/sharp-*` or `shell-quote` package or snapshot key; - a dependency edge onto one of those packages; - the integrity line under one of those keys; - one of the two `overrides:` header lines, which are the sharp target and the new shell-quote entry. Nothing else moves. This was measured by attributing every changed line: after dropping the lines that name sharp or shell-quote, and the integrity lines under those keys, zero lines remain. `pnpm-workspace.yaml` is **+39/−1**: the sharp target, its dated note, and the shell-quote entry with its note. `pnpm why`, before and after: - `sharp` 0.35.4 becomes 0.35.5, one version in both cases. The tree shape is byte-identical with the version masked: through `next@16.3.6` under `@objectstack/docs` (and the fumadocs packages), and through better-auth's `next` peer under `@objectstack/plugin-auth`. - `shell-quote` 1.10.0 becomes 1.12.0, one version in both cases: `launch-editor@2.14.1`, then `@changesets/cli@3.0.3`, then the root's devDependencies. ## Changeset `skip-changeset`. The diff touches `pnpm-lock.yaml` and `pnpm-workspace.yaml`, both repo-root configuration, and neither is in any package's `files[]`. sharp resolves under two importers: - `@objectstack/docs`, which is private. - `@objectstack/plugin-auth`, which is published. Its `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and its `package.json` does not change. It declares no sharp range at all. sharp reaches it only through better-auth 1.7.3's optional `next` peer, which this workspace auto-installs (`auto-install-peers=true`), and next's own optional `sharp` dependency. Overrides do not reach downstream installs, so nothing published changes. shell-quote is dev-only, under the private root. ## Local verification, at `d263b701` - `pnpm install --frozen-lockfile --prefer-offline`: exit 0. - The gates come from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, derived from the change set at this head: 22 commands. The dispatch named 25 because it included `package.json`, which this diff does not touch. The `--ran` reconciliation is filled in below. `--ran` reconciliation, with exit codes recorded before any pipe: **22 derived, 18 run, 4 NOT-MEASURED, 0 UNRUN**. | Gate | Exit | |---|---| | `node scripts/check-changeset-fixed.mjs` | 0 | | `node scripts/check-closing-keyword-parity.mjs` (+ `--self-test`) | 0 / 0 | | `node scripts/check-comment-mask-corpus.mjs` | 0 | | `node scripts/check-dts-emitted.mjs --self-test` | 0 | | `node scripts/check-osv-exemptions.mjs` (+ `--self-test`) | 0 / 0 | | `node scripts/check-prerelease-pin-watch.mjs --self-test` ; `--verbose` | 0 / 0 | | `pnpm --filter @objectstack/spec run check:llms-txt` | 0 | | `pnpm check:driver-memory-census` · `check:gitlink-declared` · `check:nul-bytes` · `check:override-consistency` · `check:refd-timer-probe` · `check:vendor-export-contract-resolve` · `check:watch-hint-literal` · `check:workspace-manifest-cycles` | 0 each | | `pnpm check:dts-closure` · `check:dual-build-cjs-loads` · `check:lean-entry-closure` · `check:sourcemap-no-sources-content` | **3, PREREQUISITE NOT MET** | - **`check:override-consistency`:** the census now counts 38 overrides, up from 37. shell-quote appears in neither report: it has a consumer, and its bound clears the target floor. - **NOT MEASURED (four gates):** they read every package's built `dist/`. This diff touches no package source, so the local scope builds no package, and the build these four need is the full `pnpm build`. CI's `Build Core` and `Lint & Repo Gates` measure them on the built tree. This narrowing is declared here, and none of the four is counted as a pass. - **Not run locally, CI's:** the path-scheduled jobs that `dispatch-gates` lists (`Test Core`, `Temporal Conformance`, `Dogfood Regression Gate`, `Dogfood Verify CLI`, `Build Core`, `Build Docs`) and the type-check lanes. ## Acceptance notes - `pnpm install` prints `ioredis-mock 8.13.1: unmet peer ioredis@^5: found 6.0.0`. That warning is already on `main` and is not from this diff. - The sharp selector's floor is 0.34.0, while the new advisory's range starts at 0. No sharp copy below 0.34 resolves anywhere, and the card rules the selector untouched, so the floor stays where it is. --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ Co-authored-by: Claude <noreply@anthropic.com>
…rge base; main keeps the absolute daily scan; job timeout 15 min (objectstack-ai#22138) Fixes objectstack-ai#22082 Clause-②: no ## What this does Implements ruling A as recorded on the card (comment 6049727506). On `pull_request`, the OSV verdict of `validate-deps.yml` is now base-relative. On `schedule` and `workflow_dispatch`, `main` keeps the absolute verdict from the same step as before. That step's only change is `if: github.event_name != 'pull_request'` (its version comment is also corrected, see below). The job gains `timeout-minutes: 15`, sized from the measured p50 of 1.3 min and max of 1.9 min. - **Lockfile unchanged.** The new stage step compares the pull request's test merge with its merge base. If neither `pnpm-lock.yaml` nor `osv-scanner.toml` differs, the step prints the notice "lockfile unchanged — inherits main's verdict" and runs no scan. The ledger counts as part of the input because a PR that deletes an exemption changes the verdict without touching the lockfile (replay R3 below). - **Otherwise, both lockfiles are scanned.** The same pinned action scans the PR's lockfile and the merge base's. Each is judged under its own `osv-scanner.toml`, so the merge base's verdict is what `main`'s scan gives and the PR's verdict is what the old absolute scan gave. `scripts/osv-base-relative.mjs` then sorts the advisories: - An advisory matched by both is **inherited**. It gets a `notice` annotation that names it, the package versions each side matched, and the open finding card that names it, when one exists. It does not fail the step. - An advisory matched only by the PR's lockfile is **introduced**. It gets an `error` annotation and fails the step, as before. - **The merge base.** `actions/checkout` puts the PR's test merge (`refs/pull/N/merge`) at depth 1. Its first parent is the base-branch commit that GitHub merged the PR into, which makes it the merge base of the scanned tree. The step reads that parent from the commit object and fetches it with one depth-1 `git fetch` (never `fetch-depth: 0`). If HEAD is not a two-parent merge, the step fails with an error annotation. - **Fail-closed.** A side is read only when its result file parses and its step outcome matches its finding count (success means zero findings, failure means some). In every other case the step fails and names that side. This adds no new gate, changes nothing that `main`'s scan judges, and adds no exemption. `osv-scanner.toml`, `scripts/check-osv-exemptions.mjs` and the other gate scripts are untouched, and they still run first and unchanged. ## Why a helper script, and why not upstream's `osv-reporter-action` The upstream reporter was read at `google/osv-scanner` v2.5.0 (`cmd/osv-reporter/main.go`, `internal/ci/vulnerability_result_diff.go`). Three things rule it out: - **It fails open.** When the new-side result file is missing or does not parse, it logs a warning, treats the side as having no findings and exits 0. Upstream's own reusable PR workflow runs both scans with `continue-on-error: true`, so a scan error on the PR side passes the gate. - **It needs both sides at the same source path.** The diff keys on `source.path`, so the base lockfile would have to be swapped into the workspace and restored mid-job. - **It cannot name the inherited advisories**, and the ruling requires that notice. The helper is one definition of introduced versus inherited, with the scan-error guard built in. Its `--self-test` (23 cases, floor pinned) runs as the first line of the stage step, so `check:self-test-wired` sees it. The helper is in the `paths:` filter, so a PR that edits it runs this workflow. ## Measured mechanism (osv-scanner 2.5.0, the binary in the pinned action's image) - **The pin.** `f4cfcc01edc9c8b756a9b873b7a623ca674da51e` is an untagged upstream commit between the v2.5.0 and v2.5.1 tags. Its `action.yml` runs `ghcr.io/google/osv-scanner-action:v2.5.0`, and run 37483796939's job steps include "Pull ghcr.io/google/osv-scanner-action:v2.5.0". The `# v2.3.8` comment had been stale since Dependabot's sha bump (objectstack-ai#9209). It is corrected, and all three `uses:` lines stay on that one sha. - **The image's binary matches the release binary.** It is built from the same `cmd/osv-scanner` with the same flags (goreleaser builds `osv-scanner-action`). The release binary `osv-scanner_linux_amd64` matched its published SHA256SUMS (`edcfc41d…`). The action's entrypoint is upstream's `exit_code_redirect.sh`, which rewrites exit 128 (no packages) to 0. - **The ledger is found only in the lockfile's own directory.** With a ledger one directory up, an ignored advisory was still reported. The workflow therefore passes `--config` explicitly on both sides. This settles dispatch assumption ②: without `--config`, a base lockfile staged at another path is scanned with no ledger at all. - **Exit codes, with `--config` and `--output-file`:** | case | exit | result file | |:--|:--|:--| | no findings | 0 | written, `results: []` | | findings | 1 | written | | expired `ignoreUntil` | 1 | written; the advisory is reported again (the ledger's expiry convention holds on both sides) | | unknown key / broken TOML / missing `--config` file | 127 | **none** | | missing lockfile | 127 | **none** | | no packages | 128, rewritten to 0 by the action | **none** | Because the outcome alone cannot tell findings from errors, the helper's file-plus-outcome agreement check is what keeps the verdict closed. ## The pin The body is written once, when the PR is created and before any run exists, so the run ids are posted in the `os-dev-report` comment on objectstack-ai#22082, keyed by the head shas named here. **Real runs** (seat amendment, from the dev's report on objectstack-ai#22082). All are `validate-deps.yml` on `pull_request`, against the merge base `ef1fcb26a24f` (the `main` tip): | Head | Run (job) | Result | |---|---|---| | `ce598b1ab7` | `37719042481` (`113122177520`) | success, with the "lockfile unchanged" notice | | `501569be49` (probe: `minimist` 1.2.5) | `37719190017` (`113122655549`) | **failure**, one error: `GHSA-xvch-5gv4-984h` (minimist@1.2.5) | | `3576cdc334` (probe reverted) | `37719368398` (`113123226526`) | success, with the "lockfile unchanged" notice | | `5473cad514` (probe: `minimist` 1.2.8, no advisory, lockfile changed) | `37719558844` (`113123838149`) | success, with **six inherited notices** for `next@16.3.6` | | `cc98faf47a` (final; same tree as `ce598b1ab7`) | `37719733046` (`113124391631`) | success, with the "lockfile unchanged" notice | `main` at `ef1fcb26a24f` carries those six advisories (filed as objectstack-ai#22148). So: - the inherited pin is a **real run**: `37719558844`; - the `scripts`-only pin is `37719368398` / `37719733046`, against that advisory-carrying `main`; - the replays R1–R3 below are supplementary. ### "A lockfile that introduces a new advisory fails": real run - **Head `501569be49`** is a TEMPORARY probe commit. It adds root devDependency `minimist` 1.2.5 (GHSA-xvch-5gv4-984h, fixed in 1.2.6) through `pnpm add -D -w`. The PR squashes, and `3576cdc334` reverts the probe. - **Expected run result:** the stage step reports `compare=true`, both scans run, and the judge fails with exactly one `error` annotation, for GHSA-xvch-5gv4-984h (minimist@1.2.5). - **Pre-check run locally**, with the same steps against main `8fc50b7647` and the 2026-10-06 database snapshot described under R1–R3: ``` ::error title=OSV advisory introduced by this pull request::GHSA-xvch-5gv4-984h (minimist@1.2.5) is matched by this pull request's lockfile and not by the merge base's (8fc50b7). ... OSV base-relative verdict against the merge base (8fc50b7): 1 introduced, 0 inherited, 0 resolved. judge exit=1 ``` ### "Lockfile unchanged": real runs - The heads are `ce598b1ab7` (the implementation, at PR open) and `3576cdc334` (after the probe's revert). The two trees are identical: both are `d477febb20`. - This PR changes neither `pnpm-lock.yaml` nor `osv-scanner.toml`, and `.github/workflows/validate-deps.yml` is in the `paths:` filter, so the workflow runs on it. - **Expected:** a pass with the notice "lockfile unchanged — inherits main's verdict", and the three scan and judge steps skipped. - This branch runs no scan, so `main`'s advisory state cannot reach it. That is why a `scripts`-only PR against a `main` that carries an advisory passes here. R1 replays exactly that case. ### "Inherited passes with a notice": real run `37719558844` (above); the replays below are supplementary `main` is clean today: scheduled run 37565270565 succeeded. A real PR run here cannot have a merge base that carries an advisory unless `main` is touched or a throwaway branch is made, and both are out of bounds. Following the dispatch, the step logic was replayed on real historical pairs instead. The replay worked like this: - It built the test merge GitHub would build (`git merge-tree --write-tree BASE PRHEAD`, then `git commit-tree -p BASE -p PRHEAD`) in an isolated clone. - It ran the stage step's `run:` text verbatim, as extracted from this branch's workflow. The only change was the helper's path, because the historical trees carry no copy of the helper. - It ran each scan step's `scan-args` verbatim through upstream's own `exit_code_redirect.sh` and the release binary, with outcome = exit 0 ? success : failure. - It ran the judge step's `run:` text verbatim. **The gap, named:** this container's egress policy refuses `api.osv.dev`. The scans therefore ran `--offline` against a local snapshot of the OSV npm database. That snapshot is an `all.zip` of 230,017 records, with max `modified` 2026-10-06T16:30:04Z and sha256 `0bd50081c140…`. It was found already on this container's disk, and its provenance is otherwise unknown. Its GHSA-wq5f-xc86-pv6w (sharp, published 13:43Z) and GHSA-pqg4-j6r4-53mv (shell-quote, published 13:40Z) records are those finding card objectstack-ai#22013 measured. The anchor lookup used the real repo-scoped listing. **R1: PR objectstack-ai#22002 as run 37483796939 saw it (the card's specimen: a `scripts`-only change).** Base is `f0022c46c1`, `main`'s tip when the run was created (15:00:57Z). PR head is `95c510eb8a`. ``` osv-base-relative self-test: 23 case(s), all held (floor 23) ::notice title=OSV-Scanner verdict (base-relative)::lockfile unchanged — inherits main's verdict. pnpm-lock.yaml and osv-scanner.toml are identical to the merge base f0022c4, so this pull request introduces no advisory; main's daily scheduled scan judges that lockfile. GITHUB_OUTPUT: base=f0022c46c10a142f5fb29e6891a4fc06ec05a296 / compare=false -- for contrast, the old absolute step on the same test merge's lockfile: | https://osv.dev/GHSA-wq5f-xc86-pv6w | 8.9 | npm | sharp | 0.35.4 | 0.35.5 | pnpm-lock.yaml | | https://osv.dev/GHSA-pqg4-j6r4-53mv | 9.2 | npm | shell-quote | 1.10.0 | 1.11.0 | pnpm-lock.yaml | Exit code: 1 ``` **R2: PR objectstack-ai#21951 (a real lockfile-changing PR against a `main` that carries advisories).** Base is `4c49150e0c`, PR head is `539b0752cd`. The constructed merge's `pnpm-lock.yaml`, `osv-scanner.toml` and `pnpm-workspace.yaml` blobs equal those of the PR's real squash commit `787104baa9`. ``` GITHUB_OUTPUT: base=4c49150e0cb27a493390c4ac24e0b93e13efe662 / compare=true osv-scan-base: found 1372 packages; GHSA-hp3w-g68c-fv3c filtered (the base's ledger); Exit code: 1 -> outcome failure osv-scan-head: found 1371 packages; GHSA-hp3w-g68c-fv3c filtered (the PR's ledger); Exit code: 1 -> outcome failure ::notice title=OSV advisory inherited from the merge base::GHSA-pqg4-j6r4-53mv (here: shell-quote@1.10.0; at the merge base: shell-quote@1.10.0) is matched by the merge base's lockfile (4c49150) too, so it is main's to fix and does not fail this pull request; no open issue names it yet; main's daily scheduled scan is the signal that files its finding card. ::notice title=OSV advisory inherited from the merge base::GHSA-wq5f-xc86-pv6w (here: sharp@0.35.4; at the merge base: sharp@0.35.4) is matched by the merge base's lockfile (4c49150) too, so it is main's to fix and does not fail this pull request; no open issue names it yet; main's daily scheduled scan is the signal that files its finding card. OSV base-relative verdict against the merge base (4c49150): 0 introduced, 2 inherited, 3 resolved. resolved GHSA-238p-pmpm-9mq7 katex@0.16.47 resolved GHSA-68fv-2mgg-jv7q source-map-js@1.2.1 resolved GHSA-jqcg-44mw-7w3h proxy-addr@2.0.7 judge exit=0 -- the old absolute step on the same lockfile: sharp + shell-quote, Exit code: 1 ``` The anchor clause reads "no open issue names it yet" because objectstack-ai#22013 is closed today. A live probe of the same listing found the open issues that name a given advisory id. It also turned up a seat board post quoting one, which is why board posts (`pm:seat`) are skipped; the self-test pins both the anchor case and the board-post skip. **R3: a synthetic ledger-only PR** that deletes the sprintf-js exemption from `787104baa9`'s `osv-scanner.toml` and touches no lockfile: ``` GITHUB_OUTPUT: base=787104baa9ecca77ff13a8e83a4b7349728feadc / compare=true (no short-circuit: the ledger differs) ::error title=OSV advisory introduced by this pull request::GHSA-hp3w-g68c-fv3c (sprintf-js@1.1.3) is matched by this pull request's lockfile and not by the merge base's (787104b). ... OSV base-relative verdict against the merge base (787104b): 1 introduced, 2 inherited, 0 resolved. judge exit=1 ``` ## Helper ablations (each run with `scripts/ablation-replace.mjs`, which restores the file and confirms the restored blob equals HEAD) - **A1: `compare` ignores the base** (`if (base.has(id)) inherited.push` becomes `if (false) inherited.push`). The self-test fails: 25 failed expectations over 23 cases, exit 1. - **A2: a missing result file read as "no findings"** (upstream's fail-open). The self-test fails: 4 failed expectations, exit 1. - **Restore:** the blob is back to `f35d31ee1072` = HEAD, and `git diff HEAD` is empty. ## Local verification These gates ran at `bf15ed3831`, plus `ce598b1ab7` for the entry-guard fix. The final tree `d477febb20` is identical at `ce598b1ab7` and `3576cdc334`. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands` derived 54 commands over the actual diff, and all 54 exit 0. - **One red caught and fixed:** `check:entry-guard` first went red because the helper exported bindings and ran on import. The fix puts the dispatch behind `isEntrypoint`, and the gate is now green. - **`pnpm check:pm-dispatch-gates`:** 1976 cases pass, in 820.5s. - **`--ran` reconciliation:** 54 derived, 54 run, 0 NOT-MEASURED. - **eslint on the new file:** `npx eslint --no-inline-config --format json scripts/osv-base-relative.mjs` reported 1 file, 0 errors and 0 warnings. - The file is in eslint's population: a JSON result came back for it, not an "ignored" warning. - The config enables no type-aware linting (`eslint.config.mjs` has no `parserOptions.project`), so this diff cannot change the verdict on any file it does not touch. - The whole-repo `pnpm lint` belongs to CI. ## Acceptance notes - **The unit is the advisory id,** following the ruling's wording ("an advisory matched by both is reported as inherited"). Suppose a PR adds a second vulnerable version of a package for an advisory `main` already carries. That advisory reads as inherited, and the notice lists both sides' package versions so the addition is visible. `main`'s daily scan still judges it. Upstream's reporter counts occurrences instead. - **Prose that drifts with this change, in files outside this PR's allowed surface.** Both are out of bounds for this PR, and this PR has no carrier for them: - The `osv-scanner.toml` header says the workflow "blocks on any advisory at any severity". That is still true of `main`'s scan, but a PR now answers only for what it introduces. - The `osv-scanner.toml` and `scripts/check-osv-exemptions.mjs` headers cite measurements "against v2.3.8". The pinned image has been v2.5.0 since the Dependabot bump. - **Remaining inherited-red path, by design:** an exemption that expires on `main` still reddens every dependency-touching PR through the unchanged "Verify OSV exemptions carry an expiry and a reason" step, which the ruling keeps as is. - **The job keeps `issues: write`,** which no step in it uses. The judge's anchor lookup only reads. Permissions are unchanged here. --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21959
Clause-②: no
Deletes the report-only widening-tell instrument
scripts/pm/check-widening-tells.mjs(6,528 lines) with all of its wiring, under ruling 208 (report-only instruments get no dev; their only in-flight work is deletion) and the maintainer's answer to decision batch 2 item 2, verbatim 「同意」. ⛔ Nothing replaces it, no gate is added, no workflow step is added.What changed — the full list (12 hunks in 8 files; nothing outside them)
Line numbers are at base
6befe19c(origin/mainwhen the worktree was cut).scripts/pm/check-widening-tells.mjspackage.json:85check:pm-widening-tellsscript.github/workflows/lint.yml:1324-1336.github/workflows/lint.yml:1693scripts/pm/measurement-claim-triage.mjs:57-62scripts/pm/dispatch-gates.mjsscripts/pm/measurement-claim-triage.mjs:122-125EXCLUDEDrow for the instrumentscripts/pm/check-half-states.mjs:1336-1339SCHEMA_PROPERTY_FORMSscripts/pm/check-prior-rulings.mjs:215-217.claude/skills/pm-dispatch/references/instrument-discipline.md:7scripts/pm/check-widening-tells.mjs印 file:line,归达档复核裁。.claude/skills/pm-dispatch/references/instrument-discipline.md:6docs/audits/gate-census-2026-09.md:246retireretire· deleted 2026-10 (ruling 208; 「同意」) — row keptdocs/audits/gate-census-2026-09.md:353instrument-discipline.mdstays at 12 lines (both edits are in place).Bounded in-place fixes beyond the dispatch's list (rows 4 and 10). Both are the same defect class as the card (a live sentence naming the deleted instrument), mechanical, in a file this PR already edits, and in the same gate family. Row 4: after row 3 the comment pointed at a step that no longer exists. Row 10: line 6 listed 放宽 tell as an existing intent-judging instrument. Without this edit it would contradict the rewritten line 7 on the line below.
Premise checks (the card body is a lead, not a spec)
check-half-states.mjsandcheck-prior-rulings.mjsimportSCHEMA_PROPERTY_FORMSfrom it": false at base. Probegit grep -n -E "(import|from|require|import\()[^;]*check-widening-tells" 6befe19creturns exactly 1 hit, and that hit is a comment:check-prior-rulings.mjs:215. The control is the same shape oncheck-half-states. It returns 33 hits, real imports among them,check-prior-rulings.mjs:218and the instrument's own:1484included. Outside the instrument,SCHEMA_PROPERTY_FORMSappears only in thecheck-half-states.mjs:1336docblock. The import direction is the reverse of the card's reading. The instrument imported fromcheck-half-states.mjs,dispatch-gates.mjsandregen-artifacts.mjs, and nothing imported from it. The "move the constant" item therefore became rows 7–8, which rewrite the two comments. No code moved.git grep -n -E "widening-tells|SCHEMA_PROPERTY_FORMS"over the whole tree at HEAD returns 4 lines, and none is in a CHANGELOG. No CHANGELOG ever named it. The 4 lines are: census:246and:353(marked deleted, kept per dispatch), census:398(the historical drift paragraph, deliberately untouched) andscripts/pm/dispatch-gates.mjs:3895. That file is frozen by ruling 208: its own--self-testis green with the instrument gone (below), so the word stays.Verification (HEAD
95c510eb, worktreeobjectstack-issue-21959)node scripts/pm/dispatch-gates.mjs --self-test→✓ dispatch-gates self-test: 1976 cases pass.EXIT=0 (nohup+tail --pid)pnpm check:pm-dispatch-gates→✓ check:pm-dispatch-gates --self-test: the exit contract holds in all three directions.·✓ dispatch-gates self-test: 1976 cases pass.·the battery took 842.7s on this box.EXIT=0. It ran undernohup+tail --pid. A first attempt inside my sequential runner hit that runner's own 540 s per-command timeout, so it was re-run on its own, and that run is the one recorded here.node scripts/pm/check-half-states.mjs --self-test→✓ check-half-states self-test: 4912 cases pass.EXIT=0node scripts/pm/check-prior-rulings.mjs --self-test→✓ check-prior-rulings self-test: 155 cases passEXIT=0node scripts/check-self-test-wired.mjsEXIT=0 andnode scripts/check-self-test-workflow-commands.mjsEXIT=0. The three wiring pieces went out together.node scripts/check-scripts-symbol-anchors.mjs→3722 anchors across 281 scripts resolveEXIT=0pnpm check:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-governed-prose,check:pm-governed-merges,check:pm-expected-skips,check:doc-authoring,check:nul-bytes,check:issue-citations→ all EXIT=0node scripts/pm/measurement-claim-triage.mjs --self-test→ EXIT=1 identically at base6befe19cand at HEAD. The failure in both isUNTRIAGED scripts/check-dts-references.mjs:74, which predates this PR and is unrelated to it. The report run's only difference base→HEAD is the droppedNOT SWEPT … check-widening-tells.mjsline; the population is unchanged (88 claim(s) over 49 file(s)). The tool is not wired into CI.dispatch-gates --commands(no paths; change set from the merge base) derived 82 commands. I ran all 82, plus the six the dispatch named on top: 80 EXIT=0. NOT MEASURED (exit 3,PREREQUISITE NOT MET, nodist/in a fresh worktree):check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:sourcemap-no-sources-content,@objectstack/lint check:doc-formula-expressions. Declared narrowing: these read built workspace packages, this diff touches no workspace package, and CI runs them.--ranreconciliation:✓ dispatch-gates --ran: 82 derived famil(ies) accounted for — 77 run, 5 NOT-MEASURED (5 DERIVED from a recorded exit 3).0 UNRUN.pnpm lintbelongs to CI).eslint --no-inline-config --format jsonon the 3 edited.mjsfiles: 3 files linted, 0 errors, 0 warnings, and none was reported ignored, so all 3 sit inside the config's population. Invariance:eslint.config.mjsnever enables type-aware linting (noparserOptions.project, no typed rules). Nothing imported the deleted file. So this diff cannot move the verdict on any untouched file.Landing
.claude/skills/pm-dispatch/references/instrument-discipline.md). The PR stays draft until the seat's contract-tier review.skip-changeset: nothing here ships in any package'sfiles[](scripts/pm/**,.github/**,.claude/**,docs/audits/**, rootpackage.jsonscripts).Acceptance notes (observed, not filed)
scripts/pm/dispatch-gates.mjs:3895still listscheck-widening-tellsamong "five" live prose mentions; four remain. The file is frozen and its self-test is green, so the mention was left. Carrier: none.measurement-claim-triage.mjs --self-testhas been red onmainsince before this PR (scripts/check-dts-references.mjs:74untriaged). The tool is report-only and unwired. Carrier: none.instrument-discipline.mdhas no row incheck-skill-line-ratchet.mjsCEILINGS, so no gate holds its line count. It is kept at 12 regardless. Carrier: none..claude/skills/pm-dispatch/references/triage-duties.md:64still names 放宽 tell among report-only instruments whose fix cards are closed on first touch. The rule stays true for any stray card about the deleted file, so it was left. Carrier: none.维护者速读(草稿)
改了什么:删掉一个只报告、不挡任何 PR 的 PM 内部检查脚本(放宽 tell 仪器,6528 行),连同它的
package.json脚本行和 CI 里跑它自测的一步;再把仓里所有还点名它的地方改成不再指向一个不存在的文件(两处脚本注释、一处 CI 注释、一份工具的排除表、PM 技能的仪器纪律两行、门禁普查表两行标「已删」)。为什么改:裁决 208 定了只报告的仪器在途工作只有删除;维护者在决策批 2 第 2 项答「同意」删它。它在普查窗口里零拦截,却每次 CI 都要跑自测、隔三岔五还要派人修。
风险与代价(含回滚):不替换、不新增门禁;Clause-② 方向的判断回到达档复核席位手里(原本也是席位裁,脚本只是印读数)。相关自测与门禁本地均绿。回滚 = revert 本 PR 一次即恢复脚本与 CI 步。
席位意见:
你要做的:本 PR 触受管面(Tier S)且改动行数超过 5000(几乎全是删一个文件),按规则需要一次授权 APPROVED 审阅后由席位落地;无需其它动作。
Synced: the parked red cleared (landing-operations §C, released)
This PR was parked as a draft behind PR #22016 (the OSV fix for #22013) with an expected-red list for
Validate Package Dependencies. The release condition was met: PR #22016 merged intomainas289ff6d4. The seat then ran the oneupdate-branchthat list named: headfab444b4is the merge ofmain289ff6d4into the reviewed head95c510eb, with no file authored by anyone; the net diff againstmainis unchanged (8 files, +17 / −6563, every added and removed line byte-identical to the reviewed diff).fab444b4: 37 check-runs completed, 0 failures, 0 cancelled; the 4 skips are all in the expected-skips roster (check-expected-skips --pr 22002exit 0).Validate Package Dependenciesis green on the fixed lockfile.95c510eb).check-governed-merges --pr 22002exit 3), so this PR stays a draft until an authorized APPROVED fromos-zhuangorhotlong; the seat then clearsneeds-user-decision, flips ready and arms auto-merge, and the PR lands through the queue.Section written by the
domain:skillsseat 2 PM (session_0181E4ZeZmWyknawnauxD2CE); everything above it is the dev's.Generated by Claude Code