Skip to content

feat(create-objectstack): the blank starter ships a test story on @objectstack/verify; two test gates read the tsconfig-exclude payload boundary - #22960

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-15952-scaffold-half
Oct 12, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-15952-scaffold-half

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #15952
Clause-②: no

The scaffold half of #15952: the blank starter of create-objectstack ships a test story. The docs half landed as f868e688ff. This PR carries the card's ask items 2 and 3 and the acceptance line "a freshly scaffolded blank app runs pnpm test green with the one example test", plus the seat's ruling A on open question 1 (claim comment 6117707121).

What changes

The blank template (packages/create-objectstack/src/templates/blank/), ported from claude/issue-15952-scaffold-held at 812d8dbf94 without its superseded plugin-spec.mdx:

  • note.test.ts: ONE starter test. It boots the project's own stack with bootStack from @objectstack/verify and asserts one business fact through the in-process handle: a note without a title is refused with VALIDATION_FAILED naming title, and the same note with a title is saved.
  • vitest.config.ts: a 60 s hook timeout for the boot, console intercept off, and OS_REGISTRY_LOG: 'warn' for the scaffolded project's own boots.
  • package.json: a test script (vitest run), plus two devDependencies:
    • @objectstack/verify at ^17.0.0, the range template-consistency.test.ts demands (the current major), restamped at release by sync-template-versions.mjs;
    • vitest at ~4.0.0, which is SCAFFOLD_VITEST_RANGE, generated (below).
  • tsconfig.json includes *.ts, so pnpm typecheck checks the test and the config.
  • README.md lists pnpm test.

The 8 ported files are byte-identical to the held branch except two lines: the vitest range (^4.0.0 to ~4.0.0, the terms in 6117112916) and one changeset sentence.

packages/create-objectstack/vitest.config.ts: only the exclude of src/templates/**. The payload test is never run by this package's own suite.

.github/workflows/scaffold-e2e.yml: job 1 gets one npm test step after validate and build. The scheduled registry canary runs npm test only when the published scaffolder declares a test script.

scripts/sync-scaffold-emission-policy.mjs: one POLICY_STAMPS row, devDependencies.vitest stamped from SCAFFOLD_VITEST_RANGE. The self-test fixtures carry the third constant. The write run already finds the template clean, because the committed value is the generated value.

Open question 1, ruling A: the payload boundary in two gates. check-registry-log-declared.mjs and check-test-source-alias.mjs now read the boundary check-undeclared-dep-imports.mjs already reads. A directory the package's own tsconfig.json excludes, as a literal entry inside src/, is payload and not a test source.

  • Shared helper: both gates import tsconfigExcludedDirs from check-undeclared-dep-imports.mjs, where it is already exported. That is one definition with no move and no copy, and the same gate-to-gate import dispatch-gates.mjs already uses for isTestPath.
  • Glob entries are not payload: most packages here exclude **/*.test.ts in their tsconfig, and that glob is not payload. On this tree the boundary honours exactly one directory, packages/create-objectstack/src/templates. The registry-log green line now prints it.
  • What moves in the alias gate: only the walk's ROOTS. A real test that imports a payload file still walks into it.
  • Self-tests: one case each, each with a control that carries a glob exclusion.
  • Refused options stay refused: no OS_REGISTRY_LOG or alias lines in create-objectstack's vitest config (option B), and no payload rename (option C).

Cross-lane: the three scripts/ files are domain:spec, declared on #6017 as 6117713390.

Changeset: create-objectstack minor, carrying Clause-②: no. No exported symbol moves.

Measured

All readings below are at c8cfe2e8c2 unless named otherwise.

npm install matrix, full scaffolded manifest. The repo-built bin ran final-notes --skip-install --skip-skills, and the emitted package.json holds @objectstack/* ^17.7.0 and vitest ~4.0.0. Each row used Node v22.22.0, a fresh copy of the scaffold, a fresh --cache directory, and npm install --no-fund --no-audit.

npm exit vitest vite @objectstack/verify first error
10.9.4 (Node 22's bundled npm) 0 (562 packages) 4.0.18 7.3.7 17.7.0 none
10.9.9 (newest 10.x) 0 (562 packages) 4.0.18 7.3.7 17.7.0 none
11.21.0 (newest 11.x) 0 (562 packages) 4.0.18 7.3.7 17.7.0 none
10.9.9, CONTROL: the same manifest with vitest at ^4.0.0 1 n/a n/a n/a Cannot read properties of null (reading 'edgesOut')

The gates before and after the predicate.

gate at ba4a876ccc (port only) at c8cfe2e8c2
check-registry-log-declared exit 1: "1 engine-booting suite(s) declare no recognised registry log level" (packages/create-objectstack/vitest.config.ts) exit 0: "73 vitest-running package(s) walked, 10 selected", plus the payload line naming packages/create-objectstack/src/templates
check-test-source-alias exit 1: packages/create-objectstack "tests import 5 workspace package(s) that resolve to dist/ with no source alias" exit 0: "73 packages with tests scanned; 60 registered"

Ablations. Each mutation went through scripts/ablation-replace.mjs: the anchor hit 1 to 0, the blob changed, and the restore read blob == HEAD with git diff HEAD empty.

  1. The registry-log payload filter was removed. The self-test went red on the new row with "expected selected [packages/b, packages/c], got [packages/a, packages/b, packages/c]", and the real gate went red on create-objectstack.
  2. The alias-gate payload filter was removed. The self-test went red with "a starter test under a tsconfig-excluded dir was walked as a test of the package that ships it", and the real gate went red on create-objectstack.
  3. The glob guard in tsconfigExcludedDirs was removed. The registry-log self-test went red on its payload list, which read packages/c/src/**/*.test.ts. The alias self-test stayed GREEN. A glob string used as a literal path prefix matches no file, so that mutation cannot drop a test. The controls guard against a glob-MATCHING predicate, and this mutation does not produce one.
  4. The template range was drifted to ^4.0.0. --check exited 1 with "devDependencies.vitest is "^4.0.0" but the policy declares "~4.0.0"", and the write run restamped it to ~4.0.0.
  5. The create-objectstack vitest exclude was deleted. vitest list --filesOnly went from 18 files to 19, the extra one being src/templates/blank/note.test.ts.
  6. The starter test, on a fresh scaffold copy outside the repo, with a sha256 restore check:
    • baseline green;
    • required: true deleted from note.object.ts: pnpm test exit 1, "AssertionError: promise resolved ... instead of rejecting";
    • restored (sha256 equal): green.

Tests, through os-verify-lock:

  • pnpm --filter create-objectstack test && typecheck: VERDICT command-exit 0, "Test Files 18 passed (18), Tests 265 passed (265)", tsc exit 0.
  • cli unit pins (scaffold-ci-script-parity, create-objectstack-wiring-parity, starter-field-consumers): exit 0, "Test Files 3 passed (3), Tests 58 passed (58)".
  • OS_TEST_TIERS=nightly scaffold-emission-policy.e2e.test.ts: exit 0, "Test Files 1 passed (1), Tests 10 passed (10)".
  • The cli integration layer is declared to CI.

The Scaffold E2E steps, run locally on a fresh scaffold:

  • npm 10.9.9 install, then npm run validate ("Validation passed"), npm run build ("Build complete") and npm test ("Test Files 1 passed (1)"): VERDICT command-exit 0.
  • A pnpm install copy: pnpm test green, and pnpm typecheck exit 0. tsc --listFiles lists note.test.ts and vitest.config.ts.

What only CI can show: the GitHub runner's npm for node-version: '22', the starter boot inside the 60 s hook timeout on a shared runner, and the boot, health and Docker steps after the new test step. The registry canary is schedule-only, and its npm test arm stays dormant until a published scaffolder declares a test script.

Gates: dispatch-gates --commands with no paths derived 113 commands on c8cfe2e8c2, the same list the claim's 11 paths gave. All 113 ran, and --ran reports "113 derived famil(ies) accounted for — 113 run, 0 NOT-MEASURED (a DERIVED zero)". 112 exited 0 on the first pass. check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, 9 packages without dist/) and exited 0 after a turbo build (73 tasks, 72 cached).

Lint (proven narrowing):

  • Population: the eslint config lints **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}, so this diff's lintable files are 6: the 3 scripts, the 2 template TS files and the create-objectstack vitest config.
  • Run: pnpm exec eslint --no-inline-config --format json read 6 files, with 0 errors and 0 warnings.
  • Invariance: the config enables no type-aware linting (no parserOptions.project), so this diff cannot move a verdict on any untouched file.

Acceptance notes

These are observations only. None is filed.

  • On a passing starter run (published 17.7.0), the boot logs [sql-driver] DATABASE_ERROR ... no such table: _objectstack_sequences. The refused insert the test provokes also logs a WARN with a full stack trace. Both appear on a green npm test. They were not investigated. Carrier: none.
  • The census paragraph in check-registry-log-declared.mjs's header still reads "72 walked, 8 selected". The live green line reads 73 and 10 (packages/cli and packages/plugins/organizations joined before this PR). The header itself says the green line is the census of record. Carrier: none.

Generated by Claude Code

…jectstack/verify

The blank template gains a `test` script, @objectstack/verify and vitest
devDependencies, a vitest.config.ts and ONE note.test.ts that boots the
project's own stack with bootStack and asserts one business fact through
the in-process handle. The Scaffold E2E workflow runs it after install.

Ported from the held scaffold branch (812d8db) without its superseded
docs page; the template's vitest range is ~4.0.0, the shared scaffold
policy's value, instead of the held branch's ^4.0.0.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
check-registry-log-declared and check-test-source-alias walked every
test file under a package directory, so the blank template's starter
test, payload create-objectstack copies into a scaffolded project and
never runs, reddened both: one demanded OS_REGISTRY_LOG in a config that
boots nothing, the other six source aliases for imports no run loads.

Both now read the boundary check-undeclared-dep-imports already reads,
imported from there (tsconfigExcludedDirs): a directory the package's own
tsconfig.json excludes, as a literal entry inside src/, is payload and
not a test source. A glob such as **/*.test.ts is not payload. One
self-test case each, with a control for the glob shape. The registry-log
green line names the honoured payload directories.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…'s vitest range

POLICY_STAMPS gains a devDependencies.vitest row generated from
SCAFFOLD_VITEST_RANGE, so the blank starter's vitest range is written
by create-objectstack's build from the constant the CLI scaffolders
import, and check:scaffold-emission-policy reddens if they drift apart.
The self-test fixtures carry the third constant and assert it is stamped.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…usion

`**/*.test.ts` alone is dropped by tsconfigExcludedDirs's inside-src/
filter before its glob filter is consulted, so the control could not see
that guard. `src/**/*.test.ts` reaches it.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 4 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/blank/README.md, packages/create-objectstack/src/templates/blank/tsconfig.json, packages/create-objectstack/src/templates/blank/vitest.config.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)).

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/blank/README.md, packages/create-objectstack/src/templates/blank/tsconfig.json, packages/create-objectstack/src/templates/blank/vitest.config.ts, …) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 11 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 da9c2bf2f25d61a9fec44afc300b657ae10399bc → packageMentionDocs.

…and @objectstack/verify

The blank starter now ships note.test.ts, vitest.config.ts and
@objectstack/verify as a devDependency, so the "What was generated" tree
gains the two root files and the package table, which calls itself the
entire framework surface a project installs, gains the verify row.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

  • Temporal Conformance (live PG + MySQL) — 失败步骤: Run driver-sql suite against both live servers

    × WAL (the file-backed default): every free page leaves the database, and its bytes leave the -wal sidecar too, while the driver is open 5222ms
    FAIL  src/sql-driver-sqlite-reclaim-space.test.ts > SqlDriver.reclaimSpace() on better-sqlite3 returns the whole freelist > WAL (the file-backed default): every free page leaves the database, and its 
      ↳ 失败原因: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(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,按失败测试文件聚合):

  • src/sql-driver-sqlite-reclaim-space.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Queue ejection triage: re-queued once. A timeout in a driver-sql test this diff cannot reach, on a base where the same check is green

domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-12T06:44Z. Triage comment read: 6119166204.

Signature: queue run 38212559019, merge group on base 246edf5b39, job Temporal Conformance (live PG + MySQL), step Run driver-sql suite against both live servers.

  • First error: FAIL src/sql-driver-sqlite-reclaim-space.test.ts > SqlDriver.reclaimSpace() on better-sqlite3 returns the whole freelist > WAL (the file-backed default) … 5222ms.
  • The cause: Error: Test timed out in 5000ms.
  • The triage workflow found no cross-PR aggregate: this PR is the only one to hit the file in its 24h window (a lower bound, as it notes).

The three facts for one re-queue:

  1. Import closure disjoint from the diff: this PR changes the create-objectstack blank template, its vitest.config.ts, three scripts/ gates, scaffold-e2e.yml, one docs page and a changeset. Nothing under packages/drivers/driver-sql imports any of them.
  2. The queue base's same check was green: base 246edf5b39 (PR docs(releases): the versioning policy states the 17.x launch-window rule and the v18 prerelease regime #22963's landing) carries Temporal Conformance (live PG + MySQL) → success (started 06:14:41Z).
  3. The first error is a timeout, not an assertion: a 5000 ms test budget on a file-backed WAL reclaim, which is load-sensitive.

Action: auto-merge is re-enabled once, on the same head 604bd2e22f. A second red with this signature on this PR ⇒ ⛔ no further re-queue. It is then anchored as its own card for the driver-sql lane, and this PR waits on it.


Generated by Claude Code

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 12, 2026
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 12, 2026
Merged via the queue into main with commit 19805d8 Oct 12, 2026
40 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-15952-scaffold-half branch October 12, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants