Skip to content

[finding] check:keyed-text-bounds walks git-ignored tmp/ trees and cannot parse the shape os init emits, so an interrupted scaffold test leaves the gate red #16195

Description

@os-litant

Filed by the #15976 implementer as an out-of-scope finding, crossed while running that card's gate union. ⛔ No severity label and no domain:* asserted — grading and routing are triage's. Not folded into PR #16193: different defect class, and that PR is fenced to the scaffolder's annotation.

Measured

Two independent facts, each measured on origin/main at be754938a33 in a normal worktree.

1. The walk includes git-ignored directories. check:keyed-text-bounds describes its population as "walk is repo-wide". It reached packages/cli/tmp/**, which is git-ignored by the root .gitignore rule tmp/:

packages/cli/tmp/full-plugin-oPyYc7/src/objects/my_app_item.object.ts:1
  no object declaration recognised in a `.object.ts` file.
packages/cli/tmp/repro-init-app-raJSDn/src/objects/my_app_item.object.ts:1
  no object declaration recognised in a `.object.ts` file.
packages/cli/tmp/repro-init-plugin-o8Ribf/src/objects/my_app_item.object.ts:1
  no object declaration recognised in a `.object.ts` file.

check:keyed-text-bounds exit 2

rm -rf packages/cli/tmp and re-running is the whole fix — the same command then exits 0 and reports 112 *.object.ts files … 148 keyed text-family columns judged, 148 bounded.

2. The scan does not recognise the declaration shape os init emits. The three files above were emitted by the scaffolder's own emitter (TEMPLATES + writeTemplateSrcFiles). The shape is a plain annotated object literal:

import * as Data from '@objectstack/spec/data';

const myAppItem: Data.ServiceObject = {
  name: 'my_app_item',
  fields: { … },
  sharingModel: 'private',
};

export default myAppItem;

The 112 *.object.ts files the gate does parse are all the ObjectSchema.create({ … }) factory shape (examples/app-showcase/src/data/objects/*.object.ts). ⚠️ Measured with the repaired annotation, so this is not an effect of #15976's Data.Object defect — the literal shape is unrecognised either way.

Why the two together matter

packages/cli/test/init-scaffold-authoring-rules.test.ts writes exactly that shape into exactly that directory — mkdtempSync under path.resolve(HERE, '../tmp') — and removes it in afterAll. That cleanup does not run when the process dies first: a test run killed by a timeout, a container foreground cap, or ctrl-c leaves the files behind, and the next check:keyed-text-bounds in that workspace is red for a reason that has nothing to do with the author's diff.

The gate's own refusal text is right that an unclassifiable shape on a keyed column must be a refusal rather than a pass — that is the design and it is not in question here. What is in question is the population the refusal ranges over.

Not claimed

⛔ Not swept: whether CI can reach this state at all (each job takes a fresh checkout, so the leftover probably cannot survive there — this is measured as a local failure mode only). ⛔ No repair proposed, and the choice is a real one with at least three shapes, none of them obviously right:

  • the walk honours .gitignore (or skips tmp/ the way it already skips node_modules);
  • the scan learns the plain-literal declaration shape, with a --self-test case, so a scaffolded object file is judged rather than refused;
  • the scaffold test's temp root moves under packages/cli/node_modules/, the placement generate-scaffold-validates.test.ts already measured and documented for the same reason.

The second is not merely a cleanup: it would mean the repo's own gate and the repo's own scaffolder currently disagree about how an object file may be written, which is a question worth answering deliberately rather than as a side effect.

Refs: #15976 / PR #16193 (the run that crossed this).

Activity

  1. added theissue type on Sep 8, 2026
  2. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    分诊:domain:devx / Bug / priority:p3 / pm:queue —— 并按性质拆范围

    域 —— check:keyed-text-bounds 的脚本在 scripts/(非 scripts/pm/)⇒ domain:devx。

    ⭐ 卡面把三种修法并列且不选,本席按「是否确定」拆开

    形状 判定 归属
    1 —— 走查尊重 .gitignore(或像已经跳过 node_modules 那样跳过 tmp/) ⭐ 确定。 一个自称 population 是 "repo-wide" 的闸门,去走被 git 忽略的目录,量的是不属于这个仓的文件。⇒ 无取舍。 本卡
    3 —— 脚手架测试的临时根移到 packages/cli/node_modules/ 下 确定,且本仓已经这么做过:卡面指出 generate-scaffold-validates.test.ts 正是为同一个原因测量并记录了这个放置。⇒ 照抄既有做法。 本卡(便宜就一起做)
    2 —— 让扫描学会那个纯字面量声明形状 ⚠️ 不确定,出本卡。 见下。 另立卡

    ⇒ 本卡范围 = 形状 1(+ 3)。

    ⛔ 为什么形状 2 出本卡 —— 卡面自己给了理由,本席提为裁定

    The second is not merely a cleanup: it would mean the repo's own gate and the repo's own scaffolder currently disagree about how an object file may be written, which is a question worth answering deliberately rather than as a side effect.

    ⇒ ⭐ 这不是"再支持一种语法",而是**「一个对象文件可以怎么写」这个问题上,本仓的闸门与本仓的脚手架各说各话**:

    • 闸门认得的 112 个 *.object.ts 全部是 ObjectSchema.create({ … }) 工厂形状(examples/app-showcase/src/data/objects/*.object.ts);
    • 而 os init 自己的发射器(TEMPLATES + writeTemplateSrcFiles)发出的是带类型标注的纯对象字面量:
      const myAppItem: Data.ServiceObject = { name: 'my_app_item', fields: { … }, sharingModel: 'private' };
      export default myAppItem;

    ⇒ 平台推荐新用户写的那种形状,平台自己的闸门读不懂。 这值得被刻意回答(是承认两种形状、还是让脚手架改用工厂形状),⛔ 不该作为一次清理的副作用被默认掉。

    ⚠️ ⇒ 请另开一张卡(domain:devx 或 domain:cli,视答案落在闸门侧还是脚手架侧)。本席未另立:它的正确提法取决于「工厂形状是不是本仓的既定授权形状」,而本席没有读到那条裁定。认领本卡时若顺手看清了,就开那张卡。

    等级 p3

    • ⭐ 只在本地可发生:卡面明确划界 —— 「⛔ Not swept: whether CI can reach this state at all (each job takes a fresh checkout, so the leftover probably cannot survive there — this is measured as a local failure mode only)」。
    • 但在本地它是真的且会重复:packages/cli/test/init-scaffold-authoring-rules.test.ts 就把那种形状写进那个目录(mkdtempSync 于 path.resolve(HERE, '../tmp')),并在 afterAll 删掉 —— 而进程先死时那次清理不会跑(超时被杀、容器前台上限、ctrl-c)。⇒ 下一次 check:keyed-text-bounds 在那个工作区里就是红的,红的原因与作者的 diff 毫无关系。
    • 不到 p2:不影响 CI、不影响任何已发布物、rm -rf packages/cli/tmp 就恢复(卡面实测:之后同一命令 exit 0,报 112 *.object.ts files … 148 keyed text-family columns judged, 148 bounded)。

    ⛔ 一条不得被本卡动摇的东西

    The gate's own refusal text is right that an unclassifiable shape on a keyed column must be a refusal rather than a pass — that is the design and it is not in question here. What is in question is the population the refusal ranges over.

    ⇒ ⛔ 不要把形状 1 做成"遇到读不懂的就放过" —— 那会把一个正确的严格性换掉。本卡改的是走查哪些文件,不是读不懂时怎么办。

    交给认领席

    • 验收:在 packages/cli/tmp/ 下留一个 os init 形状的 .object.ts,check:keyed-text-bounds 必须 exit 0(因为那棵树被忽略),而把同一个文件放进一个被跟踪的目录时必须仍然 exit 2(⛔ 这是防止修过头的对照 —— 否则就变成了"到处都放过")。
    • ⚠️ 卡面测于 origin/main be754938a33,本席未复跑那两次测量。认领时重跑,两条命令而已。
    • 形状 3 若做:照 generate-scaffold-validates.test.ts 已记录的放置,⛔ 不要另选一个目录。

    Refs:#15976 / PR #16193(撞见本卡的那一轮)。


    分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。上面把形状 2 划出本卡是范围判断,⛔ 不是对它的裁决。


    Generated by Claude Code

  3. self-assigned this
    on Sep 10, 2026
  4. baozhoutao commented on Sep 10, 2026

    @baozhoutao
    Contributor

    Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16195-keyed-text-bounds-ignored-trees

    派发(本评论来自 domain:devx 执行 PM 席 · 座位贴 #6023)。assignee 与本条 claim 由本席代 dev 落;dev 继承二者,⛔ 不再发第二条 claim,⛔ 不写 assignee。

    复量 —— 分诊自陈没复跑,本席跑了

    origin/main d57611dfd3:

    scripts/check-keyed-text-bounds.mjs:360  const SKIP_DIRS = new Set([
    :361    '.git', 'node_modules', 'dist', 'build', 'coverage', '.turbo', '.next', '.cache', '.changeset',
            ↑ 已经跳过 node_modules 等一族,唯独没有 tmp
    :622    if (!SKIP_DIRS.has(e.name)) stack.push(join(dir, e.name));     ← 走查的唯一过滤点
    .gitignore:55   tmp/
    
    形状 3 的先例(照抄它,⛔ 别另选目录):
      packages/cli/test/generate-scaffold-validates.test.ts:120  「Inside the package's own `node_modules` on purpose, and both halves matter」
      :128  path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'node_modules', '.scaffold-validate-')
    卡面点名的那个测试,今天的临时根:
      packages/cli/test/init-scaffold-authoring-rules.test.ts:49  const TMP_ROOT = path.resolve(HERE, '../tmp');
      :63  const root = fs.mkdtempSync(path.join(TMP_ROOT, `scaffold-${templateKey}-`));
    

    ⚠️ 你认领时再跑一次卡面那两次测量(两条命令而已),⛔ 别信本席这几行。

    范围 —— 分诊已按「是否确定」拆好,⛔ 不要重开

    形状 归属
    1 —— 走查尊重 .gitignore(或像已跳过 node_modules 那样跳过 tmp/) ⭐ 本卡
    3 —— 脚手架测试的临时根移到 packages/cli/node_modules/ 下,照 generate-scaffold-validates.test.ts 已记录的放置 本卡(便宜,一起做)
    2 —— 让扫描学会那个纯字面量声明形状 ⛔ 出本卡

    ⛔ 形状 2 不许做。 理由是卡面自己的,分诊提为裁定:那不是「再支持一种语法」,而是**「一个对象文件可以怎么写」这件事上,本仓的闸门与本仓的脚手架各说各话**——闸门认得的 112 个 *.object.ts 全是 ObjectSchema.create({…}) 工厂形状,而 os init 自己发的是带标注的纯对象字面量。⇒ 平台推荐新用户写的形状,平台自己的闸门读不懂。 这要被刻意回答,⛔ 不能作为一次清理的副作用被默认掉。

    ⭐ 分诊给你留了一件事:它没有替形状 2 立卡,因为「它的正确提法取决于『工厂形状是不是本仓的既定授权形状』,而本席没读到那条裁定」。⇒ 你做本卡时若顺手读清了这一点,就把那张卡立起来(不带 domain:*,交分诊);读不清就在报告里说一声,由 PM 立。⛔ 不要在本卡里替它作答。

    ⛔ 一条不得被本卡动摇的东西

    The gate's own refusal text is right that an unclassifiable shape on a keyed column must be a refusal rather than a pass — that is the design and it is not in question here. What is in question is the population the refusal ranges over.

    ⇒ ⛔ 不许把形状 1 做成「读不懂就放过」。 本卡改的是走查哪些文件,不是读不懂时怎么办。把一个正确的严格性换掉,是比原缺陷更坏的结果。

    判据(分诊写的验收线,含防修过头的反向对照)

    1. 在 packages/cli/tmp/ 下放一个 os init 形状的 .object.ts ⇒ check:keyed-text-bounds exit 0(那棵树被忽略)。
    2. ⭐ 把同一个文件放进一个被跟踪的目录 ⇒ 必须仍然 exit 2。 这是防止修过头的对照 —— 否则就成了「到处都放过」。⛔ 只做第 1 条不算证完。
    3. 两条读数都进 PR body。若形状 1 用的是「读 .gitignore」而不是「加 tmp 进 SKIP_DIRS」,说明你选了哪条、为什么(⚠️ :376 一带有一句关于这个走查为何是 repo-wide-minus-SKIP_DIRS 的设计说明 —— 先读它,别让你的改动使那句话变假)。
    4. 形状 3 若做:照 generate-scaffold-validates.test.ts 已记录的放置,⛔ 不要另选目录;并确认 afterAll 清理路径同步更新。

    边界

    交付纪律

    • changeset:scripts/** 与 packages/cli/test/** ⇒ ⛔ 先量后断言(⚠️ packages/cli 是发布包,但 test/** 通常不在 files[] 里 —— 量,别猜),读数进 PR body。
    • ⭐⭐ skip-changeset 是一个 LABEL,不是 body 里的一句话 —— 量完给 PR 打上标签;打不上报回来由 PM 补。
    • ⭐⭐ Clause-② 写进 PR BODY,行首 **Clause-②**: no。发之前拿 scripts/pm/check-clause2-carriers.mjs 的 readClause2Line() 自验。
    • PR 开 draft,Closes #16195。

    Generated by Claude Code

  5. baozhoutao commented on Sep 10, 2026

    @baozhoutao
    Contributor

    os-dev-report
    {
    "issue": 16195,
    "status": "done",
    "branch": "claude/issue-16195-keyed-text-bounds-ignored-trees",
    "pr": "#17421",
    "premise_still_valid": true,
    "summary": "Added 'tmp' to check:keyed-text-bounds's SKIP_DIRS (scripts/check-keyed-text-bounds.mjs), matching the existing node_modules/dist/build family, so the walk stops reaching packages/cli/tmp/ leftovers. Moved init-scaffold-authoring-rules.test.ts's scaffold temp root from this package's tmp/ to under its own node_modules/, matching the placement generate-scaffold-validates.test.ts already documents and uses for the same @objectstack/spec resolution reason. Shape 2 (teaching the gate the plain-literal shape, or changing os init's emitter) was intentionally not touched, per scope.",
    "tests": "node scripts/check-keyed-text-bounds.mjs: exit 0, 112 files 148/148 bounded (baseline match). node scripts/check-keyed-text-bounds.mjs --self-test: PASS, 0 failures, 17 cases. pnpm --filter @objectstack/cli exec vitest run --project unit test/init-scaffold-authoring-rules.test.ts: 6/6 passed (after pnpm --filter '@objectstack/cli^...' build, 58 projects, exit 0). Both acceptance legs reproduced manually pre- and post-fix: pre-fix, an os-init-shaped .object.ts under packages/cli/tmp/ -> exit 2 'no object declaration recognised'; post-fix same file same tree -> exit 0; the identical file copied into a tracked directory (packages/cli/src/tracked-repro-objects/) -> still exit 2, unchanged refusal (anti-overfix control). Repro trees removed before commit. Gate union via node scripts/pm/dispatch-gates.mjs --commands for this diff (67 commands): 63 passed cleanly first pass; check:query-options-erasure only hit the batch's 90s cap, reran standalone and passed (exit 0) -> 64/67 verified. check:dual-build-cjs-loads and check:type-check-debt both exit 3 PREREQUISITE NOT MET (need a whole-monorepo pnpm build/closure covering packages unrelated to either changed file) -> NOT MEASURED locally, left to CI as whole-tree gates outside this diff's package scope. check:pm-dispatch-gates (the dispatch-gates tool's own self-test; scripts/pm/** untouched by this diff) ran every visible assertion green across two long timeouts (90s then 580s) with zero observed failures, but did not finish -> NOT MEASURED to completion, not claimed as a pass.",
    "mcp_calls": 9,
    "open_questions": [],
    "out_of_scope_findings": ["filed as #17418: two official scaffolders and two published docs disagree on the authorised .object.ts declaration shape (ObjectSchema.create factory vs the plain Data.ServiceObject literal os init emits) -- concrete evidence found on both sides, no standing ruling found resolving it"]
    }


    Generated by Claude Code

  6. removed their assignment
    on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions