Skip to content

fix(scaffold): state the measured pnpm build-approval boundary, and gate the two scaffold paths against each other - #11090

Merged
os-elon merged 3 commits into
mainfrom
claude/issue-10499-scaffold-workspace-consistency
Aug 22, 2026
Merged

fix(scaffold): state the measured pnpm build-approval boundary, and gate the two scaffold paths against each other#11090
os-elon merged 3 commits into
mainfrom
claude/issue-10499-scaffold-workspace-consistency

Conversation

@os-elon

@os-elonos-elon commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Fixes#10499
Fixes#10498

Two scaffold paths render a pnpm-workspace.yaml into a new user's project, and
on main they ship contradictory prose about the same rule. This PR closes
the instance and the class in one change: the template moves to the measured
pnpm boundary, and a cross-file gate now fails when the two producers disagree.

The divergence, measured at head 03bdd148ee

allowBuilds flooronlyBuiltDependencies band
packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml:15-17pnpm >= 10.3110.0–10.30
packages/cli/src/commands/init.ts:244-248pnpm >= 10.2610.0–10.25

#10498 measured the true floor at 10.26.0 with per-version clean installs on
isolated stores. #10405 corrected the CLI renderer; the template was never
updated. A user on pnpm 10.28 was told by the file inside their own project that
their pnpm cannot read the key it is in fact reading — while the sibling
scaffold path told them the opposite.

⛔ Repaired by moving the template to the measured numbers.
packages/cli/src/commands/init.ts is read as evidence and is not modified by
this PR
(verified: it does not appear in git diff --name-only origin/main...HEAD).

The gate — and why it is not vacuous

packages/cli/test/scaffold-workspace-consistency.test.ts compares the
rendered outputs of both producers: renderPnpmWorkspaceYaml() is called
for the CLI path, and the template file is read for the other. Neither file's
expected content is restated in the test — every expected value comes from the
other producer, so the test measures the two against each other rather than
against a transcription.

Two limbs:

  1. The granted build set under both keys must be identical across the two
    producers, with a non-empty guard on each side (an empty grant would compare
    equal while approving nothing).
  2. The pnpm versions each file names for each key must be identical, again
    with a non-empty guard (prose naming no version would compare equal while
    telling the reader nothing). Dotted versions only: 10.26 and 10.0 are
    boundary claims, while the bare majors both files use in passing ("pnpm 11
    reads ONLY this one") are prose, and so is the 1 in "exits 1".

Wording is deliberately not compared — the two files explain the rule in
their own words and are meant to.

Landed RED first, against the live defect

The gate was committed and run before the comment fix, on the divergence
sitting on main:

FAIL test/scaffold-workspace-consistency.test.ts > the two scaffold paths render
the same pnpm build approvals (#10499) > states the same pnpm version boundary for each key
AssertionError: the two scaffold paths tell a user different things about which pnpm reads
`allowBuilds`: objectstack init names [10.26] and npx create-objectstack names [10.31]. Both
files ship into a user's own project, so one of them is telling that user their pnpm cannot
read a key it is reading. The measured boundary is the one to move TO — never move a correct
file to match a wrong one.: expected [ '10.26' ] to deeply equal [ '10.31' ]
Test Files 1 failed (1)
Tests 1 failed | 1 passed (2)

Limb 1 passed there (both already granted better-sqlite3, esbuild), limb 2
failed — the boundary limb is the one carrying the invariant. After the comment
fix: Test Files 1 passed (1) · Tests 2 passed (2).

Reverse verification

Fix committed first, so both legs restore to a byte-identical tree.

LegPredictedObserved
Re-introduce 10.31 / 10.0–10.30 in the templatenew gate RED on the allowBuilds limbRED — objectstack init names [10.26] and npx create-objectstack names [10.31], 1 failed | 1 passed
Same mutated tree, create-objectstack's own ratchetsGREEN — package-local, cannot see the sibling producerGREEN — 30 passed (30)
Restore the template from the commit (git checkout HEAD -- the template path)GREEN, byte-identicalGREEN — 2 passed (2), git diff HEAD empty

The second row is the thesis of #10499 stated as a measurement: the existing
ratchet stays green through the exact divergence this PR's gate catches.

No rebuild between legs, and none was needed — both sides resolve to
source: renderPnpmWorkspaceYaml is imported relatively from
../src/commands/init.js (no alias redirects it to dist/), and the template
is read from src/templates/. Proof rather than assertion: the verdict flipped
RED → GREEN across a source-only edit with no build in between.

Declared repairs beyond the two named files

Both are the same defect class as #10498, mechanically pinned by the measured
table, in files already on the claimed surface:

  • template-consistency.test.ts test title and failure message. Beyond the
    header comment [finding] The blank template's pnpm-workspace.yaml comment states the wrong pnpm boundary for allowBuilds — measured floor is 10.26.0, not 10.31 #10498 names, the same wrong boundary sat in
    it('… for pnpm 10.0–10.30') and in pnpm < 10.31 does not understand allowBuilds. Correcting the header while leaving those two would have left
    the wrong numbers in the file being corrected. No assertion semantics change
    (a title and an expect message), and nothing references either string —
    checked repo-wide before editing.
  • Three repairs the gates themselves demanded, all on the new test file, in
    the third commit: the cross-package read had to go on one line (the detector
    reconstructs resolve(HERE, …) only unsplit — it flagged my declared glob as
    held by nothing); the gate script's own path had to be declared because the
    test's header quotes it (this package's check-nul-bytes.mjs entry settles
    that trade); and the relative import needed its .js extension, because
    packages/cli/test is a hidden typecheck layer held by a shrink-only ledger
    where an extensionless NodeNext import adds a TS2835 plus its TS7006 cascade.

peerDependencyRules is untouched — #10931 owns that skew and is still open
on this same file. The gate deliberately carries no peer limb, so it neither
duplicates nor pre-empts that card.

Verification, at head 69c17a2347

Gate set derived with node scripts/pm/dispatch-gates.mjs and no path
arguments, re-derived after the final commit (same 20 families). All green;
each gate's own verdict line:

check:cross-package-test-inputs OK: 13 package(s) read outside themselves, all declared,
and turbo.json hashes every declared glob. (104 self-tests pass)
check:nul-bytes ✓ 75 assertions over a temp git repo (real scan() path)
check:ci-filter-parity OK: all 85 declared cross-package glob(s) (73 unique) are covered
by `core` or `crosspkg` …
check:where-matcher ✓ 277 matcher(s) discovered, 277 answer the combinator battery
correctly or refuse it loudly (167 refuse).
check:test-source-alias OK — 72 packages with tests scanned; 61 registered …
check:empty-changeset ✓ No empty-frontmatter changeset introduced by this diff
(1 declaring changeset(s) added).
check:type-check-coverage ✓ 47 semantic + 59 observation + 29 re-measure + 28 built-closure
+ 19 auto-lowering case(s) hold.

plus check:entry-guard, check:parse-guard, check:slot-lookup,
check:template-version-sync, check:type-source-resolution,
check:changeset-gate-self-tests, check:objectui-changeset,
check:query-options-erasure, check:engine-double-contract,
check-adr-0087-registration, check-changeset-no-major,
check-plugin-teardown-shape, docs-audit/check-affected-docs — all EXIT=0,
captured before any pipe.

Suites: @objectstack/cli — the new gate plus test/init.test.ts, the sibling
ratchet on the same renderer: 2 passed (2) files · 51 passed (51).
create-objectstack — full suite: 13 passed (13) files · 140 passed (140).

Two declared narrowings, both because CI runs the farm regardless:

  • check:type-check-debt --re-measure was not run whole-workspace (it needs
    the full ./packages/* build and re-runs tsc for every ledger entry). Its
    question for this diff is narrower and was answered directly: the only way
    this change can move that ratchet is the new file in packages/cli/test,
    which is inside TEST_DEBT['@objectstack/cli'] (146, shrink-only). Measured
    with a temp project over packages/cli/src + this one test file, the same
    shape measureTestDebt() builds: tsc exit 0, zero errors, so the count
    cannot rise. Nothing else in the diff is TypeScript that any ledger entry
    reads.
  • @objectstack/cli's full 137-file suite was not run locally (~500s, and 56%
    of it is subprocess spawns unrelated to this diff, which is additive there).

Clause-② path limb

git diff --name-only origin/main...HEADno path under packages/spec/src/, as expected:

.changeset/scaffold-pnpm-workspace-boundary-consistency.md
packages/cli/test/scaffold-workspace-consistency.test.ts
packages/create-objectstack/src/template-consistency.test.ts
packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml
scripts/check-cross-package-test-inputs.mjs
turbo.json

⚠️ Reported three-dot. The literal two-dot git diff --name-only origin/main
returns 27 paths here, 21 of them sibling PRs that landed on origin/main after
this branch's merge base (03bdd148ee) — files this branch never touched. None
of my six overlap anything that landed since, so there is no conflict risk.

Changeset

create-objectstack: patch. The corrected comment is user-visible — it is
delivered verbatim into every scaffolded project — while nothing executable
moves: the rendered onlyBuiltDependencies / allowBuilds values are
byte-identical, and no assertion changed. The packages/cli side of this PR is
a new test plus input declarations, which ship nothing, so it takes no bump.

Left open

packages/create-objectstack/CHANGELOG.md (lines 643-644 and 743-744) repeats
the wrong 10.31 / 10.0–10.30 boundary in two historical release entries.
Deliberately untouched — a changelog is an append-only record of what shipped,
and rewriting past entries falsifies it; this PR's own changeset carries the
corrected numbers forward. Recorded rather than filed: the GitHub API was rate
limited at report time, so the required search-before-filing pass could not run,
and filing blind risks a duplicate.

CI is not awaited — every conclusion above is local. The lint farm, the full
@objectstack/cli suite and check:type-check-debt --re-measure are still in
flight on CI at report time.


Generated by Claude Code

os-elonand others added 3 commits August 22, 2026 15:59
… approvals (#10499)
Two producers render a `pnpm-workspace.yaml` into a new user's project —
`renderPnpmWorkspaceYaml()` in packages/cli and create-objectstack's literal
blank template — and both ratchets are package-local, so neither can fail for
the other file's regression. That is how one scaffold path went on shipping the
pre-`allowBuilds` shape for months, and how the two currently ship
contradictory pnpm boundaries for the same key.
The gate compares the RENDERED outputs of both producers: the packages each key
actually grants a build to, and the pnpm versions each file actually names for
each key. Neither file's expected content is restated in the test — every
expected value comes from the other producer.
Verified RED against the live divergence on main before the comment fix:
`objectstack init names [10.26] and npx create-objectstack names [10.31]`.
The template read escapes packages/cli, so it is declared in
check-cross-package-test-inputs.mjs and in turbo.json's `@objectstack/cli#test`
inputs — without that, a template-only diff would replay a cached green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
…ilds (#10498)
The blank template's `pnpm-workspace.yaml` is copied verbatim into every
scaffolded project, and its comment told the reader `allowBuilds` needs
pnpm >= 10.31 while `onlyBuiltDependencies` covers 10.0-10.30. Measured with
one clean install per pnpm version, each with its own store, the floor is
10.26.0: 10.25.0 ignores `allowBuilds`, 10.26.0 honours it. So a user on
pnpm 10.28 was told by the file inside their own project that their pnpm
cannot read the key it is in fact reading.
Both load-bearing claims in that comment were and remain correct — both keys
are needed, and pnpm 11 reads only `allowBuilds`. Only the boundary moves.
The sibling scaffold path (`renderPnpmWorkspaceYaml` in packages/cli) already
carried the measured numbers, so this is what closes the divergence the new
consistency gate catches. Comment-only: no assertion, no behaviour and no
rendered setting changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
…eset
Three repairs the gates themselves found, all on the new consistency test:
* `check:cross-package-test-inputs` rejected the declared template glob as
held by nothing — the read was spelled `resolve(\n HERE,\n '…')`, and the
detector reconstructs that expression only on one line. Put back on one line,
with a comment saying why it must stay there. The reverse-staleness limb
catching this is the gate working: an unrecognised read means no declaration,
silently.
* The same gate then demanded a declaration for
`scripts/check-cross-package-test-inputs.mjs` itself, which the test's header
quotes while explaining where its read is declared. Declared, following this
package's own `check-nul-bytes.mjs` precedent: the literal collector takes
quoted paths without parsing, and declaring a rarely-touched file is the
trade that entry already settles.
* The relative import was extensionless. `packages/cli` is
`moduleResolution: NodeNext` and `packages/cli/test` is a hidden typecheck
layer (tsconfig `include` is `src` only) held by a shrink-only ledger, so an
extensionless import adds a TS2835 plus its TS7006 cascade and reddens
check:type-check-debt for everyone. Now `../src/commands/init.js`; measured
at 0 errors over `src` + this file.
Changeset is a `create-objectstack` patch: the corrected comment ships inside
every scaffolded project, while nothing executable moves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml) — pages documenting those are invisible to this run

Coarse fallback — 1 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ab47f6974a275586355f06abd83a0975b8637d12packageMentionDocs.

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.68s
    

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

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

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

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

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

Merged via the queue into main with commit 6d441e4Aug 22, 2026
37 checks passed
@os-elon
os-elon deleted the claude/issue-10499-scaffold-workspace-consistency branch August 22, 2026 18:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

1 participant

@os-elon