Skip to content

Commit 5c7caff

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-19036-queue-guard-size-limb
2 parents 28faa51 + c229223 commit 5c7caff

27 files changed

Lines changed: 1401 additions & 102 deletions

‎.claude/skills/checklist-author/SKILL.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ metadata:
4141
3. **本技能的简报是假设,源码才是真相** —— 每个测试项按 README.md「Item anatomy」
4242
的契约写;缺 fixture 记 `blocked`/`knownGaps`,永不伪造覆盖。
4343

44+
## 缺项写成 `planned` 项,不写成注释
45+
46+
sweep 发现「定义要求、平台还没有」的能力,写成该区的 `status: "planned"` 项(title ·
47+
priority · personas,`since: null` 或目标 release,⛔ 无 steps);注释没有 id,派发时指
48+
不到。它是 `coverage.json` 的合法映射目标但⛔ 不算覆盖:只映射到它的种类仍报 UNMAPPED。
49+
50+
**`planned → active` 只由一次判 pass 的运行记录兑现** —— steps/acceptance 随实现 PR 落
51+
地,跑过才翻状态(bump `revision`、追 `history`);⛔ 代码落地就翻 = 账本开始虚报覆盖。
52+
4453
## 规模指引
4554

4655
一轮全量 sweep ≈ 5 个 hunter + 8 个 writer agent。范围化的问题(「X 有测试吗?」)

‎.claude/skills/checklist-test/SKILL.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ node scripts/checklist-select.mjs <selector> --json
5555
`blocked` 时才传 `--include-blocked`。**把解析器报出的 `revision` 钉进运行记录**:
5656
判定只对它运行时所对的那个 revision 有效。
5757

58+
**`planned` 项任何选择器都不返回,也没有开关让它返回**:能力不存在,无 steps 可驱动、
59+
无 oracle 可查,解析器单独列出命中项。运行记录逐项记 `planned`,⛔ 永不 pass / fail /
60+
blocked / not-run,⛔ 不进标题计数;与 blocked 的分界是 fixture 债 vs 平台债。
61+
5862
## 1. 规划这一轮 —— 只构建需要的,钉住的先跑
5963

6064
读命中项的 `surface`:

‎docs/qa/platform-checklist/README.md‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ next-sequential numbers do.
4646
"id": "approvals.per-group-signoff", // "<area>.<slug>" — immutable, globally unique, never reused
4747
"title": "Per-group sign-off (会签) needs one approval from EACH group",
4848
"since": "v16", // release that introduced the capability
49-
"status": "active", // active | draft | retired
49+
"status": "active", // active | draft | planned | retired (see "Implementation status")
5050
"revision": 1, // bumps on any semantic edit
5151
"priority": "P1", // P0 = release-gating smoke · P1 = core · P2 = extended
5252
"surface": "browser", // browser | api | cli | build | mixed (the 15.1 plan's 🖥/🔌 lanes)
@@ -297,6 +297,67 @@ Three things worth knowing before you meet it:
297297
showcase-side fixture gaps #3358 uncovered (#3408, #3409, #3415) each cost a sweep to
298298
rediscover; recording the gap on the item is what stops that.
299299

300+
## Implementation status — `planned` is how the ledger holds a capability gap
301+
302+
The ledger used to record only capabilities that already work, so a missing piece of a
303+
listed capability had nowhere to live: the backlog sweep had nothing to point a `Path:`
304+
at, and the platform's implementation status lived in nobody's head. The North Star's
305+
definition line (「做出来的是什么」…「缺任何一样就不是这个应用」) makes such a piece a
306+
**requirement**, so it belongs on the ledger — as a fourth status, not as a second
307+
document. A separate feature list drifts against this one the moment either is edited,
308+
and nothing can then say which of the two is wrong: there is no ratchet between two
309+
hand-maintained documents, only a pair of disagreeing readings. Hence one ledger, one id
310+
space, one more status — and a reading entry that carries numbers only because a command
311+
produces them.
312+
313+
**`status: "planned"`** — the definition requires this capability and the platform does
314+
not yet implement or verify it. What a planned item carries, and what it deliberately
315+
does not:
316+
317+
| field | on a planned item |
318+
|:---|:---|
319+
| `id` · `title` · `priority` · `surface` · `revision` · `history` | as for any item — an id is picked once and is immutable, so it can be pointed at from the day the gap is found |
320+
| `personas` | **required** — who the capability is for is knowable the day the gap is found, and is what makes the gap readable to the next sweep |
321+
| `since` | `null` (no target release chosen) **or a TARGET release**. ⛔ Only the SHAPE is checked: this ledger holds no release timeline, so a target naming a release that already shipped is an authoring error no gate here can see |
322+
| `steps` | **none.** There is nothing to drive. Steps arrive in the PR that implements the capability, in the same edit that promotes the item |
323+
| `acceptance` | not required — no oracle can be consulted yet. Clauses drafted early are still validated |
324+
325+
Three consequences, all mechanical:
326+
327+
- **A planned item never runs.** `scripts/checklist-select.mjs` resolves it in no
328+
selector's runnable set, and there is no flag that makes it one. The selector reports
329+
it separately and the run record carries the verdict `planned` — ⛔ never `pass`,
330+
`fail` or `blocked` ([RUNNER.md](./RUNNER.md) "Verdicts"). A blocked item is a real
331+
test the environment cannot run today; a planned item has nothing to run at all.
332+
- **A planned item is not coverage.** It is a legal `coverage.json` map target — that is
333+
where a capability-gap card points — and it contributes zero: a kind whose *only*
334+
items are planned is reported **UNMAPPED**, exactly as if the entry were empty.
335+
Otherwise `planned` would be the cheapest way to green an untested kind, and the
336+
ratchet would measure intentions instead of tests.
337+
- **Promotion `planned → active` is earned, not declared.** It takes a run record in
338+
which the item **passed**: steps and acceptance arrive with the implementation, the
339+
item is run, and only then does the status flip (bumping `revision` and appending to
340+
`history` like any other semantic edit). ⛔ Flipping the status because the code
341+
landed is how a ledger starts reporting coverage it does not have.
342+
343+
**Reading the status** — one command, and the human entry point it publishes:
344+
345+
```bash
346+
pnpm gen:checklist-status # per-area active/planned counts + every planned id
347+
pnpm gen:checklist-status --out <dir> # also render the wiki pages into <dir>
348+
```
349+
350+
The wiki index **[Platform-Checklist](https://github.com/objectstack-ai/objectstack/wiki/Platform-Checklist)**
351+
is the 「平台功能清单 + 实现状态」 reading entry: one row per area with its active and
352+
planned counts, linking to one `Checklist-<area>` page per area that lists every item
353+
(`id · title · priority · status · personas`) with the planned ones in their own section
354+
first. It is regenerated on a schedule by
355+
[`.github/workflows/checklist-status.yml`](../../../.github/workflows/checklist-status.yml)
356+
and is never committed here — ⛔ there is no `STATUS.md` in this tree and no gate paired
357+
with one, by maintainer ruling: staleness is tolerated, and a generated page in the tree
358+
is a third artifact to keep fresh whose stale copy reads exactly as authoritative as a
359+
current one.
360+
300361
## Capability coverage — every capability the platform has gets tested
301362

302363
`coverage.json` makes "凡是有的能力, 都要测试" mechanical instead of aspirational. The

‎docs/qa/platform-checklist/RUNNER.md‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,28 @@ Per **item**, derived — never hand-assigned:
3535
- `partial` — some passed, none failed (the "proved half, left it unticked" state from
3636
#3358, now first-class instead of a prose apology);
3737
- `fail` — any clause failed;
38-
- `blocked` / `not-run` — nothing consulted.
38+
- `blocked` / `not-run` — nothing consulted;
39+
- `planned` — the item's `status` is `planned`: the definition requires the capability
40+
and the platform does not verify it yet. Read off the item, never derived from a run.
3941

4042
**No verdict without evidence.** A clause with no captured artifact is `not-run`, not
4143
`pass`. Evidence means: the API/network trace, the screenshot, the log excerpt, or the
4244
test-run output the clause's `evidence` field names.
4345

46+
**A `planned` item is never driven, and never scores.** `scripts/checklist-select.mjs`
47+
returns it in no selector's runnable set — it prints the matches under a separate
48+
PLANNED heading and hands the runner only what can run. Carry each one into the run
49+
record as `planned`, with no clause table: there are no `steps` to follow and no
50+
`acceptance` to consult, so ⛔ it is never `pass`, never `fail`, never `blocked` and
51+
never `not-run`. The distinction that matters is against `blocked`: a blocked item is a
52+
**real test** the environment cannot run today, so its gap is a fixture debt and the
53+
right answer is to provision it; a planned item has **nothing to run at all**, so its
54+
gap is a platform debt and the right answer is to build the capability. Scoring one as
55+
the other sends the next sweep to fix the wrong thing. A planned item earns `active`
56+
only through a run record in which it passed — see the README's "Implementation status".
57+
The completion criterion is unchanged by them: an area is run when every item the
58+
selector handed you has a verdict; planned ids are listed, not chased.
59+
4460
## The accuracy rules
4561

4662
1. **Oracle hierarchy** — server truth (`api`, `network`, `build`, `test`) outranks
@@ -429,6 +445,13 @@ QA run · <selector> (<judged>/<total>) · <framework-sha8> · <YYYY-MM-DD> · <
429445
**An omitted bucket means "not declared", never zero** — the roll-up renders the two
430446
differently, so write `0 FAIL` when you mean zero.
431447

448+
**⛔ `planned` items appear nowhere in this title** — not inside `<total>`, and not as a
449+
sixth bucket. The counts vocabulary is closed at the five verdicts above, and a planned
450+
item was never a judgeable unit of this run: putting one in `<total>` deflates the
451+
coverage claim the record is read on, and inventing a `PLANNED` segment is a deviation
452+
the roll-up refuses to parse. They are named in the body, under the scope section that
453+
already records the selector.
454+
432455
**Retired phrasings — ⛔ none of these may be written again:** `(FULL area)`, `(N items)`,
433456
`(N of M items)`, `(N/M items consulted)`, omitting the parenthetical entirely, and the
434457
trailing `(11 not-run)` spelling of NOT-RUN. `(FULL area)` is why the ruling exists: it

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"check:scripts-symbol-anchors": "node scripts/symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs --self-test && node scripts/check-scripts-symbol-anchors.mjs",
108108
"check:spec-docblock-symbol-anchors": "node scripts/symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs --self-test && node scripts/check-spec-docblock-symbol-anchors.mjs",
109109
"check:platform-checklist": "node scripts/checklist-select.mjs --self-test && node scripts/check-platform-checklist.mjs --self-test && node scripts/check-platform-checklist.mjs",
110+
"gen:checklist-status": "node scripts/gen-checklist-status.mjs --self-test && node scripts/gen-checklist-status.mjs",
110111
"check:org-identifier": "node scripts/check-org-identifier.mjs --self-test && node scripts/check-org-identifier.mjs",
111112
"check:runner-env-posture": "node scripts/check-runner-env-posture.mjs --self-test && node scripts/check-runner-env-posture.mjs",
112113
"check:cli-test-child-env": "node scripts/check-cli-test-child-env.mjs --self-test && node scripts/check-cli-test-child-env.mjs",

‎packages/spec/scripts/build-schemas-check-mode.test.ts‎

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import os from 'node:os';
5454
import path from 'node:path';
5555
import { fileURLToPath } from 'node:url';
5656

57+
import { gitFreeEnv } from '../../../scripts/git-env.mjs';
5758
import { schemaStamp } from '../../../scripts/check-regen-pending.mjs';
5859
import { RENAMED_DEFS } from './lib/renamed-defs';
5960
import { CONVERSIONS_BY_MAJOR } from '../src/conversions/registry';
@@ -232,25 +233,23 @@ let surfaceBaseDescription: string;
232233
// are already serial — vitest runs a file's tests one at a time, and every repo
233234
// here is an `fs.mkdtemp`, so no concurrently running test FILE can name one.
234235

235-
/** `GIT_*` variables that would point a fixture's git at a different repository
236-
* (or a different index/object store) than the directory it was handed. */
237-
const LEAKED_GIT_ENV = [
238-
'GIT_DIR',
239-
'GIT_WORK_TREE',
240-
'GIT_COMMON_DIR',
241-
'GIT_INDEX_FILE',
242-
'GIT_OBJECT_DIRECTORY',
243-
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
244-
'GIT_NAMESPACE',
245-
'GIT_CEILING_DIRECTORIES',
246-
'GIT_TEMPLATE_DIR',
247-
'GIT_CONFIG',
248-
] as const;
236+
/* #16644 -- a hand-maintained allowlist of ten GIT_* location variables used to stand
237+
* here. It is retired in favour of the blanket strip in `scripts/git-env.mjs`, and the
238+
* constant is not re-spelled anywhere in this file so that a census of the retired shape
239+
* does not match this paragraph.
240+
*
241+
* The reason the allowlist goes rather than gets one more entry: it had to be kept level
242+
* with git's own list of location variables, and its failure mode is that THE KEY IT
243+
* MISSES IS THE KEY THAT BITES. `gitFreeEnv()` removes every GIT_-prefixed key instead,
244+
* which is the shape #16624 landed and #16753 applied. ⛔ One spelling in the repo, not
245+
* two -- no "either is fine" transition state.
246+
*
247+
* Every git this file spawns is LOCAL-ONLY: it operates on the `fs.mkdtemp` fixture named
248+
* by its `cwd`, so the blanket strip takes no transport configuration away from it. */
249249

250250
/** The environment every fixture git — and every generator run inside one — gets. */
251251
const HERMETIC_ENV: NodeJS.ProcessEnv = (() => {
252-
const env = { ...process.env };
253-
for (const key of LEAKED_GIT_ENV) delete env[key];
252+
const env = gitFreeEnv();
254253
// git's own documented "read no config file" spellings. `/dev/null` parses as
255254
// an empty config, which is what makes `init.templateDir`, `core.hooksPath`
256255
// and any ambient `[gc]` block unable to reach a fixture.

‎packages/spec/scripts/sharded-artifacts.test.ts‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import fs from 'node:fs';
3939
import os from 'node:os';
4040
import path from 'node:path';
4141

42+
import { gitFreeEnv } from '../../../scripts/git-env.mjs';
43+
4244
import {
4345
API_SURFACE_DIR_NAME,
4446
AUTHORABLE_SURFACE_DIR_NAME,
@@ -411,25 +413,23 @@ describe('sharded artifacts — entry-point shard naming (#5837)', () => {
411413
// Nothing about what these cases assert changes: the fixtures are built from the
412414
// same writes and read by the same `readShardedKeysAtRev`, only insulated.
413415

414-
/** `GIT_*` variables that would point a fixture's git at a different repository
415-
* (or a different index/object store) than the directory it was handed. */
416-
const LEAKED_GIT_ENV = [
417-
'GIT_DIR',
418-
'GIT_WORK_TREE',
419-
'GIT_COMMON_DIR',
420-
'GIT_INDEX_FILE',
421-
'GIT_OBJECT_DIRECTORY',
422-
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
423-
'GIT_NAMESPACE',
424-
'GIT_CEILING_DIRECTORIES',
425-
'GIT_TEMPLATE_DIR',
426-
'GIT_CONFIG',
427-
] as const;
416+
/* #16644 -- a hand-maintained allowlist of ten GIT_* location variables used to stand
417+
* here. It is retired in favour of the blanket strip in `scripts/git-env.mjs`, and the
418+
* constant is not re-spelled anywhere in this file so that a census of the retired shape
419+
* does not match this paragraph.
420+
*
421+
* The reason the allowlist goes rather than gets one more entry: it had to be kept level
422+
* with git's own list of location variables, and its failure mode is that THE KEY IT
423+
* MISSES IS THE KEY THAT BITES. `gitFreeEnv()` removes every GIT_-prefixed key instead,
424+
* which is the shape #16624 landed and #16753 applied. ⛔ One spelling in the repo, not
425+
* two -- no "either is fine" transition state.
426+
*
427+
* Every git this file spawns is LOCAL-ONLY: it operates on the `fs.mkdtemp` fixture named
428+
* by its `cwd`, so the blanket strip takes no transport configuration away from it. */
428429

429430
/** The environment every fixture git in this file gets. */
430431
const HERMETIC_ENV: NodeJS.ProcessEnv = (() => {
431-
const env = { ...process.env };
432-
for (const key of LEAKED_GIT_ENV) delete env[key];
432+
const env = gitFreeEnv();
433433
// git's own documented "read no config file" spellings. `/dev/null` parses as
434434
// an empty config, which is what makes `init.templateDir`, `core.hooksPath`
435435
// and any ambient `[gc]` block unable to reach a fixture.

‎packages/spec/vitest.repo-tests.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"scripts/references-banner.test.ts",
2121
"scripts/root-index.test.ts",
2222
"scripts/schema-tree-freshness.test.ts",
23+
"scripts/sharded-artifacts.test.ts",
2324
"scripts/solution-blueprint-header-row.test.ts",
2425
"scripts/strictness-ledger-doc.test.ts",
2526
"scripts/strictness-ledger.test.ts",

‎scripts/ablation-dist-preflight.mjs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ import { execFileSync } from 'node:child_process';
211211
import { tmpdir } from 'node:os';
212212
import { fileURLToPath } from 'node:url';
213213
import process from 'node:process';
214+
import { gitFreeEnv } from './git-env.mjs';
214215
import { isEntrypoint } from './invoked-as.mjs';
215216
import { WORKSPACE_FILE, parseWorkspaceGlobs, workspacePackageDirs } from './workspace-enumerator.mjs';
216217

@@ -450,6 +451,11 @@ function readTreeStatus(repoRoot) {
450451
try {
451452
const out = execFileSync('git', ['status', '--porcelain', '-z'], {
452453
cwd: repoRoot,
454+
// The tree under test is the one `repoRoot` names and nothing else (#16644).
455+
// An inherited GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE outranks `cwd`, so under a
456+
// hook this would certify SOME OTHER tree as restored -- the one direction this
457+
// preflight exists to make impossible.
458+
env: gitFreeEnv(),
453459
encoding: 'utf8',
454460
maxBuffer: 64 * 1024 * 1024,
455461
stdio: ['ignore', 'pipe', 'pipe'],
@@ -475,6 +481,8 @@ function markerPresence(repoRoot, entries, marker) {
475481
try {
476482
head = execFileSync('git', ['show', `HEAD:${e.path}`], {
477483
cwd: repoRoot,
484+
env: gitFreeEnv(), // #16644: the HEAD blob of THIS tree, never a hook's
485+
478486
maxBuffer: 64 * 1024 * 1024,
479487
stdio: ['ignore', 'pipe', 'ignore'],
480488
});
@@ -714,7 +722,9 @@ function selfTest() {
714722
// verdict mean anything. This leg replays the measured incident end to end.
715723
const repo = mkdtempSync(join(tmpdir(), 'ablation-preflight-git-'));
716724
try {
717-
const git = (...args) => execFileSync('git', args, { cwd: repo, stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
725+
// #16644: a throwaway corpus, so every child is spawned with GIT_* stripped --
726+
// `git init` here under an inherited GIT_DIR writes core.bare into the SHARED config.
727+
const git = (...args) => execFileSync('git', args, { cwd: repo, env: gitFreeEnv(), stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' });
718728
const srcPath = join(repo, 'source.ts');
719729
const genPath = join(repo, 'generated-baseline.json');
720730
const MARK = 'OS_ABLATION_LEAK_MARK';

‎scripts/check-adr-0087-registration.mjs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'nod
365365
import { tmpdir } from 'node:os';
366366
import { dirname, join, resolve } from 'node:path';
367367
import { fileURLToPath } from 'node:url';
368+
import { gitFreeEnv } from './git-env.mjs';
368369
import { isEntrypoint } from './invoked-as.mjs';
369370
import { maskComments, maskCommentsAndLiterals } from './js-comment-mask.mjs';
370371
// #16421 — the DIRECTION ARM, read through the fleet's one declaration reader.
@@ -2002,11 +2003,16 @@ export function projectedMigrationIds(specChangesJson) {
20022003
// ---------------------------------------------------------------------------
20032004

20042005
function git(args, cwd) {
2006+
// `env: gitFreeEnv()` (#16644): every caller of this helper -- the real checkout on a
2007+
// gate run, a mkdtemp fixture in the self-test -- names its repository by `cwd`. An
2008+
// inherited GIT_DIR outranks `cwd`, so without the strip the fixture legs read and
2009+
// write THE REAL REPOSITORY under a hook, silently and with `ok` printed throughout.
2010+
//
20052011
// stderr is PIPED, not inherited: `showOrNull` probes paths that legitimately do
20062012
// not exist at a rev (a ledger file added mid-history, a changeset deleted), and
20072013
// git's "fatal: path ... does not exist" would otherwise print as though the gate
20082014
// had failed while it is in fact answering the question it asked.
2009-
return execFileSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] });
2015+
return execFileSync('git', args, { cwd, env: gitFreeEnv(), encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] });
20102016
}
20112017

20122018
/** File contents at a rev, or `null` when the path does not exist there. */
@@ -2035,7 +2041,7 @@ function showManyOrNull(rev, paths, cwd) {
20352041
let out;
20362042
try {
20372043
out = execFileSync('git', ['cat-file', '--batch'], {
2038-
cwd, input, maxBuffer: 512 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'],
2044+
cwd, env: gitFreeEnv(), input, maxBuffer: 512 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'],
20392045
});
20402046
} catch { return found; }
20412047

0 commit comments

Comments
 (0)