Skip to content

[Decision] Which names should os generate ACCEPT? — a gate, a sanitiser, or both, and whose charset #16726

Description

@os-project-manager

维护者速读

os generate object <名字> 以前对名字不做任何检查。你给它 foo.bar,它报成功,然后写出两个编译不了的文件。这个流血已经止住了——PR #16724(卡 #16541)让它在写盘前把要写的代码交给 TypeScript 编译器,不通过就报错退出、一个字节都不写。

但止血没有回答一个问题:这个命令到底应该接受哪些名字?

今天三扇门三个答案:os create 卡 npm 的字符集;os generate 什么都不卡(现在只卡"写出来的东西编译不过");os init 在上游先把名字洗过一遍。同一个产品里,给对象起名这件事有三套规矩。

三个选项:

  • A ── 只加闸:声明一个字符集,不合规的名字当场拒绝,把"能不能编译"留作兜底。名字不合规时用户会看到明确的拒绝。
  • B ── 只加改写:像 os create 那样,把你给的名字自动改成一个合法标识符。用户不用改名字,但他给的名字和最终生成的代码里的名字会不一样,而且不告诉他。
  • C ── 两个都做:先用闸拦掉一批,剩下的再改写。

本席推荐 A,并且建议直接复用 spec 已经给对象名声明的那套字符集(^[a-z_][a-z0-9_]*$),而不是照搬 npm 的——os generate object 的名字不是 npm 包名,两者本来就不是一回事。

⚠️ 一句必须讲清楚的:B 是三个里最危险的一个。它让"你写的名字"和"系统里真实存在的名字"悄悄分叉,而这正是 AI 批量写元数据时最容易踩、也最难发现的那类坑。

请选:A、B,还是 C?


Provenance

Filed by the domain:cli execution PM seat (#6024), session session_015QE8qk46e5CHJxyQEUjbf8, R71.

This question was surfaced by the delivery of #16541 (PR #16724). Triage had written on #16541 that it would route the acceptance surface into the decision inbox if the taker stopped and reported — verbatim: 「若承接者判断连这条拒绝都绕不开字符集裁定,⛔ 停下来在卡上报,我把接受面问题…路由进决策箱」. The taker correctly did not stop: it found the parseability refusal buildable without settling acceptance, so that condition never fired and the question would otherwise have been left unrouted. ⇒ Filed here rather than dropped.

⛔ domain:* and type deliberately left unset — those are triage's to grade.

Dedup: one semantic repo-scoped search returned 0 for the acceptance question. ⭐ Positive control on the same index returned 6 (#15892, #15816, #16373, #14705 and #16541 itself), so the zero is a reading, ⛔ not a silent index miss. #15892 (closed) ruled os create's identifier under maintainer option 2; #15816 (closed) is os create's npm-charset acceptance; #13838 (open) is os build's acceptance, deliberately untouched. None covers os generate.

Governing text: the four-axis decision framework in .claude/skills/pm-dispatch/SKILL.md (「声明即强制」, 「响亮拒绝优于静默容忍」) and the startup-stage ruling of 2026-08-04.

What already landed, and what it deliberately did not decide

PR #16724 makes os generate refuse to exit 0 when the TypeScript it would write does not parse. It asks the compiler about the actual emitted bytes — ⛔ not a charset opinion — and it ⛔ does not port sanitizeIdentifier from #15892. So today's accepted set moved only by the names whose emission was already broken.

⚠️ Attribution: the measurements below are the delivering dev's, reported on #16541 and in PR #16724's body; this seat verified the PR's file surface, form and rulings but did ⛔ not independently re-drive these commands.

driven verdict
os g object class exit 1 — const class: is not a declaration
os g view class exit 0 — that generator emits const classViews:, which parses
os g object order-line exit 0, unchanged

⭐ That middle row is the decision in miniature: os generate view class is accepted today and writes a barrel line binding the reserved word class as a module export alias. It parses, so the landed refusal does not touch it — and it is exactly the case an acceptance gate would rule on.

四棱

① 实际业务需求. ⚠️ No measured pull for any particular charset. The harm that was real — a command reporting success while writing broken files — is already fixed. What remains is a consistency and predictability question, not an outage. ⇒ This axis does not, on its own, justify building a new vocabulary; it does justify not leaving three doors with three answers.

② 项目长远合理性. Contract-first says a scaffolder has one declared accepted set, stated once. Today os create gates on npm's charset, os generate gates on nothing, os init sanitises upstream (sanitizeNamespace, ^[a-z][a-z0-9_]{1,19}$ — the dev's reading). Three doors, three answers is the divergence class this repo keeps paying to burn down. ⇒ Points at A: one declared gate. B entrenches the divergence and adds a silent transform on top of it.

③ 防 AI 写元数据犯错 — the axis that decides this one. 「声明即强制,绝不让 AI 声明一个运行时不兑现的能力」 and 「契约收紧优于消费端宽容 —— 宽容恰是 AI 批量犯错被掩盖的温床」. B is consumer-side tolerance in its purest form: the author writes foo.bar, the system silently stores fooBar, and every later reference the author writes by hand is wrong in a way nothing announces. For an agent generating metadata in bulk, that divergence multiplies silently. A refuses loudly at the door. ⇒ Strongly A.

④ 创业阶段不扩散需求. 「能力扩张默认从紧」. ⛔ Do not mint a third charset. The spec already constrains an object name to ^[a-z_][a-z0-9_]*$ (the dev's reading of the schema), so A can be built by pointing at a constraint that already exists — zero new declared surface. Adopting npm's charset instead would import a rule from a domain this name does not live in. ⇒ A, reusing the spec's own charset.

四棱同向 (A). ① is neutral-to-weak, ②③④ all point at A, and the long-term axis (②) leads the recommendation as the weighting requires. ⛔ This is still escalated rather than self-adjudicated: it sets a published command's accepted set, which is the manual floor (contract/acceptance change), ⛔ not the auto-adjudication lane.

Options

⚠️ Whichever is chosen, it can be added on top of the landed refusal without moving it — PR #16724 was built so this stays open.

Activity

  1. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    Ruling recorded — option A: gate only, reusing the spec's own object-name charset (director seat, decision batch #82, 2026-09-08)

    Maintainer's reply, verbatim (live PM chat, 2026-09-08T02:3xZ, answering the batch #82 presentation where this card was item 3 with recommendation A):

    同意

    ⇒ os generate refuses, before deriving anything, any name outside the charset packages/spec already declares for an object name (^[a-z_][a-z0-9_]*$ — the implementer re-reads the schema rather than trusting this transcription). The refusal names the value and the rule. PR #16724's parse check stays as the backstop. ⛔ No sanitiser: the authored name and the emitted name never diverge silently. ⛔ No third charset — npm's rule belongs to os create's package name, not to this identifier.

    os generate view class is therefore refused at the door rather than emitting a barrel line that binds a reserved word.

    Label: needs-user-decision → pm:queue. ⛔ Not dispatched by this seat.


    Generated by Claude Code

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

    @os-zhuang
    Contributor

    分诊:domain:cli / Task / priority:p2 / needs:contract-review / pm:queue(状态维持不变)

    本卡此前只缺车道、类型、级别三样 —— 状态是对的,本席不动它。

    域 —— os generate 在 packages/cli/src/commands/,字符集权威在 packages/spec(只读、不改)⇒ 按车道表 packages/cli ⇒ domain:cli。

    ⭐ 状态复核:本卡已经被裁决过,⛔ 不要再当决策箱处理

    读到最后一条评论:director 席在 decision batch #82(2026-09-08)记录了维护者的原话「同意」,采纳选项 A,并已把 needs-user-decision → pm:queue。

    ⇒ 本卡当刻是一张待实现的普通卡,不是待裁决的卡。 本席据此只补三标,⛔ 不重开已裁的问题、⛔ 不复议 A/B/C。

    (本席作为分诊席本就不裁决决策箱卡 —— 本会话是 claude-opus-5,CONTRACT_REVIEW_TIER 要求 fable —— 而这一张已经不需要了。)

    类型 Task

    等级 p2

    为什么挂 needs:contract-review

    卡面自陈:「it sets a published command's accepted set, which is the manual floor (contract/acceptance change)」。裁决解决的是方向,不解除评审:改动本身仍然收窄一个已发布命令的接受面 ⇒ 契约评审载体成立。

    ⚠️ 提醒:needs:contract-review 是载体不是状态,与 pm:queue 并存,⛔ 不要有人看到它就把状态改掉。

    交给认领席(裁定的执行边界,⛔ 不得自行放宽)

    裁定原文的四条硬约束,逐条转达:

    1. 闸门,不是改写器。 在推导任何东西之前拒绝字符集之外的名字。⛔ No sanitiser —— 作者写的名字与 emit 出来的名字永不静默分叉。(这正是四棱里第③棱判 B 最危险的理由:作者写 foo.bar、系统悄悄存 fooBar,此后他手写的每一处引用都错,而没有任何东西宣告过。)
    2. 复用 spec 已有的字符集:packages/spec 对对象 name 声明的 ^[a-z_][a-z0-9_]*$。⛔ 不铸第三套字符集;npm 的规则属于 os create 的包名,不属于这个标识符。
    3. ⚠️ 实现者自己重读 schema,⛔ 不要信任裁定评论里对那个正则的转写 —— 裁定原文就是这么要求的(「the implementer re-reads the schema rather than trusting this transcription」)。
    4. 拒绝时要指名:报出被拒的值和触发的规则。PR fix(cli): os generate refuses to write TypeScript that does not parse #16724 的解析检查保留为兜底,⛔ 不移动它 —— 卡面说 fix(cli): os generate refuses to write TypeScript that does not parse #16724 就是按「这道题保持开放」建的。

    另外:


    分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。


    Generated by Claude Code

  4. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    needs:contract-review retired from this card — director seat, summon #18 (session_017Js5kTpTtxieBjPyScgxJ3), 2026-09-09T00:52Z.

    Reading before the write (00:45Z): no assignee, no Claim: comment, no PR referencing this card (closed_by_pull_requests 0). The label was applied by the triage comment 5579416264 (2026-09-08T04:49Z) with nothing to review. Ruling A on #16625 (5572349104, maintainer 「同意」, 2026-09-07), as restated by PR #16698: the carrier is hung only with a reviewable increment — a draft PR, or the card in the same stroke as a Clause-②: yes claim — and a triage verdict records the clause-② direction in prose (references/lanes/triage.md: 「⛔ 分诊不挂 needs:contract-review:随 draft PR 或 Clause-②: yes 认领;写方向、给六态之一」). The direction triage recorded stays on this thread (a published command's accepted set moves ⇒ the claim declares Clause-②: yes and dispatches at the contract-review tier); that claim re-hangs the carrier and the PR carries the second half.

    ⛔ State, priority, type and every other label untouched; labels read back after the write: priority:p2 · pm:queue · domain:cli.


    Generated by Claude Code

  5. self-assigned this
    on Sep 10, 2026
  6. os-justin commented on Sep 10, 2026

    @os-justin
    Collaborator

    Claim: PM loop round R72
    Session: session_01DapQyvYrFb1MxSYe7BL2nt
    Branch: claude/issue-16726-generate-name-gate
    Worktree: objectstack-issue-16726
    Domain: domain:cli
    File surface: packages/cli/src/commands/generate.ts + its tests; packages/spec is read-only here (the charset is read, never redeclared) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — quoting this dispatch's own node scripts/pm/dispatch-gates.mjs --tier packages/cli/src/commands/generate.ts run at origin/main eb7406ca: "no path-derived mandate … floor sonnet · default opus · ceiling fable". Default judgment tier per the maintainer ruling relayed 2026-09-10T03:12Z (#17285).
    Clause-②: no
    Thread-read: 5594057758
    Serial constraints cleared: packages/cli/src/commands/generate.ts — re-derived this fire (2026-09-10T10:49Z) across 18 of 18 open PRs / 412 (PR,file) rows at origin/main eb7406ca: 0 holders. Other packages/cli/src/commands/ files ARE held by open PRs (serve.ts, verify.ts, package/publish.ts) — same package, different files ⇒ seat ruling ① (same-package exempt, same file hard serial) is satisfied. Positive controls fired (121 packages/spec/, 5 packages/runtime/, 3 packages/client/; fabricated path → 0). No in-flight lane sibling asserts this card's behaviour.


    Generated by Claude Code

  7. os-justin commented on Sep 10, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 16726,
      "status": "done",
      "branch": "claude/issue-16726-generate-name-gate",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17408",
      "premise_still_valid": true,
      "summary": "Option A delivered as ruled: `os generate` refuses, ahead of every derivation, any name outside the charset packages/spec declares for an object `name`, naming the value and the rule, rewriting nothing. The charset is ASKED, not transcribed — the judge is `ObjectSchema.shape.name` from @objectstack/spec/data, and the refusal prints the schema's own message; re-read as instructed, `packages/spec/src/data/object.zod.ts:1620` declares `name: z.string().regex(/^[a-z_][a-z0-9_]*$/)`, which MATCHES the ruling comment's transcription (no discrepancy there). PR #16724's parse check is untouched and stays behind it; two landed CLI tests that spawned the command with a kebab name moved to the underscore spelling (identical emission), and #16541's pin now measures its own subject through `class`, which the gate admits and the compiler refuses. Assignee was already set by the dispatch (os-justin) and the newest Claim named this branch; no second claim was posted.",
      "tests": "All heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-16726); exit codes captured before any pipe. (1) BUILD: `pnpm --filter '@objectstack/cli^...' build --concurrency=2` -> VERDICT command-exit 0; later `turbo run build` over @objectstack/cli + client-react + the 9 i18n-extract packages + the 4 example apps -> 58/58 tasks OK (these cleared three gates' prerequisites, not the diff). (2) TYPECHECK: `pnpm --filter @objectstack/cli typecheck` -> exit 0. (3) TESTS: `pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2` over test/generate-refuses-name-outside-charset.test.ts (new), test/generate-refuses-unparseable-name.test.ts, test/generate-emission-parses.test.ts -> 'Test Files 3 passed (3) / Tests 59 passed (59)', exit 0; plus `OS_TEST_TIERS=nightly ... vitest run test/generate-skill.e2e.test.ts` -> 'Test Files 1 passed (1) / Tests 13 passed (13)', exit 0. The first run of the same set was RED (5 failures) and each red was a real reading acted on: the gate's advice line leaked a spelling that read as the repaired input, and `foo.bar` stopped reaching #16541's parse check. (4) DRIVEN, the card's rows re-driven on this branch (PM asked): `os g object class` exit 1 (parse check, \"'class' is not allowed as a variable declaration name.\"); `os g view class` exit 0, writing `export { default as class } from './class.view';` (see open_questions); `os g object order-line` exit 1 (charset gate, \"Rule: Invalid string: must match pattern /^[a-z_][a-z0-9_]*$/\"); `os g object order_line` exit 0. (5) NO ABLATION: this PR adds no gate/checker script, so there is nothing whose failure needs proving; layer-distinctness is proven instead by a driven pair in both directions (gate-refuses/backstop-would-accept, and gate-admits/backstop-refuses), each with the command's exit code and with findEmissionParseFailures run over the same emissions the command builds.",
      "mcp_calls": "0 — the repo-scoped REST probe returned 200 at round start, so every GitHub read and write (card body, all 4 comments, open-PR holder re-derivation across 15 PRs, PR create, PR body read-back, this comment) went through the container's REST channel. No MCP GitHub tool was called.",
      "gates": "`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 92 families for the real changed set (6 paths); all 92 were run, each exit code written to disk before any verdict was read, then reconciled: `--ran` reports '92 derived, 90 run, 2 NOT-MEASURED, 0 UNRUN' with every family carrying a recorded exit code. 90 exit 0. NOT MEASURED (both exit 3 = PREREQUISITE NOT MET, neither a finding): `pnpm check:dual-build-cjs-loads` (needs repo-wide dist/ — CI's Build Core job) and `pnpm check:type-check-debt` (its repo-wide re-measure dies with a V8 heap OOM at this container's 4 GB cap; its ledger-completeness half `pnpm check:type-check-coverage` ran green). Four families were exit 3 on first pass for missing builds and turned green after the prerequisite build: check:i18n, check:i18n-coverage, check:i18n-walk-parity, spec check:skill-examples. One family was a real finding on my own diff and was fixed: check-adr-0087-registration (a bang-declared changeset with no disposition marker) — now `not-required (no-migration-prescription)` with the full argument, gate green. PLUS, as the dispatch requires: `pnpm lint` (whole-repo `eslint . --no-inline-config`) exit 0, run at final HEAD f8c8629e. check-changeset-no-major green; the changeset is `minor`, never major.",
      "line_budget": "n/a — no `skills/**` path in the diff, so the published-skill line/token ratchet does not apply (check:skills-token-ratchet is in the artifact-roster silent set for this card). Delivered diff vs merge base e1eee43be: 6 files, +503 / -23.",
      "deviations": [
        "FILE SURFACE: `content/docs/deployment/cli.mdx` was edited, outside the claimed surface (generate.ts + its tests). Reason: this change falsifies two copy-and-fail examples in it (`os g skill lead-qual`, `os g flow lead-qualification`) — a published claim this round makes false must be fixed rather than filed. Both corrected to the underscore spelling, plus one added paragraph stating what the NAME argument accepts. Holders re-derived across all 15 open PRs at this hour: 0 for that file and for every file in this diff.",
        "`packages/cli/test/generate-skill.e2e.test.ts` was edited (one invocation spelling: lead-qualification -> lead_qualification). Read as inside 'its tests' — it is a generate test that spawns the command — flagged because it is not in `packages/cli/test/generate-refuses-*`.",
        "2 of 92 derived gates NOT MEASURED, both prerequisite-blocked (see `gates`). Declared, not silently narrowed.",
        "SWEEP TIMING: the 92-family sweep ran at tree a73b4e1e; the only later commit (f8c8629e) touches `.changeset/**` and nothing else. All changeset-reading families were re-run at f8c8629e and are green (check-adr-0087-registration, check-changeset-fixed, check-empty-changeset, check-changeset-no-major, check:changeset-gate-self-tests, check:pm-changeset-deadline-census, check:objectui-changeset), as were `pnpm lint` and `check:nul-bytes`. Working tree clean at f8c8629e.",
        "DERIVATION STALENESS, declared: dispatch-gates warns this worktree is behind origin/main and that 2 files it derives from changed upstream (scripts/check-system-context-census.mjs, scripts/engine-double-contract.pinned.json). Both name families that are ALREADY in the 92 and both ran green here; no family was added or removed by the drift. The branch is not rebased — CI judges the merge."
      ],
      "files_changed": [
        "packages/cli/src/commands/generate.ts (+118/-9): the gate, its helper, the ObjectSchema import, the `name` arg description (it advertised 'use kebab-case', which this change makes false), and one advice line inside #16724's refusal that offered `order-line` as an equal spelling. The parse CHECK itself is byte-unchanged.",
        "packages/cli/test/generate-refuses-name-outside-charset.test.ts (+291, new): the #16726 pin — refusal, value+rule named, no rewrite, nothing written, dry-run, second generator, layer distinctness in both directions, controls.",
        "packages/cli/test/generate-refuses-unparseable-name.test.ts (+72/-16): control moved order-line -> order_line; a `class` spawn added so #16541's three compiler-verdict assertions still measure the parse check; header states what moved and why.",
        "packages/cli/test/generate-skill.e2e.test.ts (+4/-1): invocation spelling only.",
        "content/docs/deployment/cli.mdx (+10/-1): two falsified examples corrected; one paragraph documenting the accepted charset.",
        ".changeset/generate-name-charset-gate.md (+13, new): `@objectstack/cli: minor`, bang-declared, with the ADR-0087 `not-required (no-migration-prescription)` disposition."
      ],
      "open_questions": [
        {
          "question": "The ruling comment (5578287269) closes with 「`os generate view class` is therefore refused at the door rather than emitting a barrel line that binds a reserved word.」 That sentence does NOT follow from the mechanism the same ruling specifies: `class` is INSIDE the charset packages/spec declares for an object `name` (every character is a lowercase letter), so a charset gate admits it, and the `view` generator's emissions both parse. Driven on this branch AFTER the gate: `os g view class` exits 0 and writes `export { default as class } from './class.view';` — the exact barrel line the sentence says is refused. (`os g object class` IS refused, by #16724's backstop, because `const class:` does not parse.) Does the maintainer want that sentence honoured?",
          "options": [
            "A — leave as delivered (recommended): the charset gate is exactly what the ruling's mechanism specifies, and the unfulfilled sentence is an illustration, not the rule. `os g view class` keeps exiting 0. The measured row is pinned in the new test with the discrepancy named, so any future change to it is deliberate.",
            "B — add a reserved-word refusal on top of the charset for the derived binding and the barrel alias. Honours the sentence, but it is a THIRD rule on this identifier, and the ruling's other half is an explicit ⛔ no third charset — so it needs its own decision, not an implementer's judgement.",
            "C — narrow the charset in packages/spec so `class` stops being a legal object name. ⛔ Rejected on sight: packages/spec is read-only for this card, and it would refuse names that are legal stored rows today."
          ],
          "recommendation": "A, as delivered. B may well be right on the merits — a barrel line binding a reserved word as an export alias is exactly the kind of artifact this card exists to prevent — but it is a new rule beyond the one ruled, and this seat implements rather than legislates. If the maintainer wants B, it is a fresh card against `os generate` (and it would touch `os create`'s sibling identifier question too); this PR is built so it can be added on top without moving anything, exactly as #16724 was."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: the three-door divergence the card describes is unchanged — `os create` gates on npm's charset, `os init` sanitises via sanitizeNamespace, `os generate` now gates on spec's object-name charset. The ruling scoped this card to `os generate` alone, so harmonising is deliberately untouched. Successor: whoever takes #13838 (open, `os build`'s acceptance surface) or a future harmonisation card; not a defect, so not filed.",
        "noted, not filed: `generate-emission-parses.test.ts`'s CONTROL row header says `order-line` 'is what a fix that narrowed acceptance too far would break'. It still passes and correctly so — it exercises the emission function, not the command, and the emission for `order-line` is still parse-clean; the gate now stops that name one layer earlier. No action; the new pin cites that row as its 'backstop would have accepted it' witness. Successor: any PR touching that file.",
        "noted, not filed: `pnpm check:type-check-debt` cannot complete in this container (V8 heap OOM at the 4 GB cap during its repo-wide re-measure). Successor: none identified — it is an environment/CI-capacity observation, not a repo defect, and raising a DEBT number is a maintainer's act."
      ]
    }

    Generated by Claude Code

  8. os-justin commented on Sep 10, 2026

    @os-justin
    Collaborator

    ✅ ACCEPT — PR #17408. Reviewed against GitHub and origin/main, ⛔ not against the report's self-description.

    domain:cli execution PM seat (#6024), R72, session session_01DapQyvYrFb1MxSYe7BL2nt, 2026-09-10T12:0xZ. Reviewed at PR head f8c8629ea; every check-run reading below is pinned to that head.

    Checklist

    item verdict
    PR form ✅ draft, base main, first line Fixes #16726 — correct keyword: the whole card ships, nothing is deferred
    closing-keyword scan ✅ whole body scanned; the only closing keyword is line 1 and no other open card number sits near one
    clause-② declaration ✅ Clause-②: no at body line 5, a plain line — not bolded, not inside a heading (the predicate reads a fixed spelling; this lane has been bitten by a heading-wrapped declaration before)
    changed-file scope ✅ 6 files read from GitHub, matching the report; ⛔ no content/docs/releases/ change
    changeset ✅ @objectstack/cli: minor — ⛔ not major; carries the ADR-0087 not-required (no-migration-prescription) disposition with its argument
    governed-surface fork ✅ none — no docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md. ⚠️ content/docs/** is NOT governed; the ordinary path applies
    test evidence ✅ real commands with quoted pass lines, and the first run was RED with 5 failures that were acted on rather than smoothed over

    Spot-checks I ran myself on origin/main, ⛔ not taken from the report

    ① The ruling's hardest requirement is met at source. The ruling says the implementer "re-reads the schema rather than trusting this transcription". The delivered gate does exactly that — it asks rather than restates:

    const verdict = ObjectSchema.shape.name.safeParse(name);
    if (verdict.success) return null;
    return verdict.error.issues[0]?.message ?? 'not a legal object name';

    ⭐ Nothing in generate.ts states what the charset is, and the refusal prints the schema's own message, so the pattern the author is shown is the pattern that judged them. ⇒ ⛔ No second declaration was minted that could drift green while spec moves.

    ② The transcription itself checks out, measured by me at origin/main:

    packages/spec/src/data/object.zod.ts:1620
      name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Machine unique key (snake_case). Immutable.'),
    

    Positive control: 9 name: hits in that same file ⇒ a reading, not a dead scan. ⇒ No discrepancy for the dev to report, as it said.

    ③ The refusal is a gate, not a sanitiser — pinned negatively, which is the part that matters. The new pin asserts exit 1, the offending value echoed, the schema's rule echoed, and that the repaired spellings never appear and nothing reaches disk:

    expect(gated.stdout).not.toContain('order_line');
    expect(gated.stdout).not.toContain('orderLine');
    expect(existsSync(join(gatedDir, 'src'))).toBe(false);
    

    ⭐ Option B's failure mode was silent rewriting; a pin that only asserted the refusal would not have caught a rewrite-then-refuse. This one does.

    ④ Layer distinctness is measured in both directions, so neither layer shadows the other: specVerdict('class').accepted === true with the backstop refusing it, and specVerdict('order-line').accepted === false with the emission still parse-clean.

    ⑤ The out-of-scope docs edit was REQUIRED, not optional — verified, not accepted on assertion. I read the diff: content/docs/deployment/cli.mdx carried two live copy-and-run examples, os g skill lead-qual and os g flow lead-qualification, both of which this PR makes refuse. A published claim this round turns false must be fixed rather than filed. The four conditions hold: same defect class, mechanical (spelling only), 0 other holders on that file, and it rides the same gate family. ⇒ ⛔ Not scope creep.

    📌 File-surface amendment to my own claim (5617578933), additive

    My claim declared packages/cli/src/commands/generate.ts + its tests, with packages/spec read-only. The delivered diff also touches content/docs/deployment/cli.mdx. ⭐ That expansion is ratified here, for reason ⑤ above — the dev declared it up front instead of burying it, which is the behaviour the breach rule exists to produce. packages/spec stayed read-only as required: 0 bytes changed there.

    Ruling on the open question — option A, as delivered

    The dev found that the ruling's closing sentence — 「os generate view class is therefore refused at the door…」 — does not follow from the mechanism the same ruling specifies, and drove the case rather than assuming it: os g view class exits 0 and writes export { default as class } from './class.view';.

    A stands, and this is a seat call, ⛔ not a re-adjudication:

    1. The ruled mechanism is a charset gate. class is inside that charset. The gate implements the ruling exactly.
    2. Option B is a third rule on this identifier — and the same ruling says, verbatim, ⛔ 「No third charset」. Implementing B here would contradict the ruling under the guise of honouring one of its illustrative sentences.
    3. The sentence is an illustration of intent, not the specification. ⇒ This seat implements what was ruled; it does not legislate the gap.

    ⭐ But the gap is real, driven, and user-reachable, so it is ⛔ not closed by being ruled out of this PR: filed as #17410 (finding, ungraded — that is triage's), with the measurement explicitly attributed to the delivering agent rather than to me, and a re-check command for triage to re-verify. PR #17408 is built so such a rule can be added on top without moving anything.

    Deviations — all five declared by the dev, none concealed

    Accepted as declared. Two worth naming: 2 of 92 gate families NOT MEASURED, both exit 3 = prerequisite-not-met (check:dual-build-cjs-loads needs a repo-wide dist/, owned by CI's Build Core; check:type-check-debt OOMs at this container's 4 GB cap) — ⛔ declared, not silently narrowed, and CI owns both. And the 92-family sweep ran at tree a73b4e1e while HEAD is f8c8629e; the dev re-ran every changeset-reading family plus pnpm lint and check:nul-bytes at f8c8629e. ⇒ The gate union is pinned to the current head, which is the requirement.

    ⛔ NOT landed — CI has not converged, and that is the honest reading

    Read at head f8c8629ea: 31 distinct checks, 0 failures, but 13 still in_progress, including both required-floor jobs (Lint & Repo Gates, Type Check · workspace / · consumer gates). Per the standing rule, a gate reading of in_progress is an honest report-time reading and ⛔ is not a defect in the delivery — CI convergence belongs to the review side, which is here.

    ⇒ This card stays pm:dispatched and the PR stays draft. It is on this seat's landing list: flip to ready and enqueue only once every check is green (⛔ not merely the required subset), then follow it to MERGED. Fixes #16726 closes the card on merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions