Skip to content

The two surfaces that merely restate the gate set should name pnpm verify instead of enumerating it — a restatement of a definition cannot be allowed to drift - #81

Open
zhuangjianguo wants to merge 2 commits into
mainfrom
claude/issue-78-restatements-name-verify
Open

zhuangjianguo wants to merge 2 commits into
mainfrom
claude/issue-78-restatements-name-verify

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes #78

⚠️ This PR touches a governed surface (AGENTS.md, CLAUDE.md) and is left OPEN as a draft for the maintainer to merge. Per AGENTS.md → Delivery process → Governed surface, the seat reviews it, records an ACCEPT on the issue, and stops there — it does not merge and does not flip it out of draft. Same path PR #77 and PR #40 took. docs/backlog/README.md is ungoverned, but it travels with the governed files in one PR, so the whole PR waits. See 维护者速读 at the bottom.

⛔ Hardening, not a repair

Every statement of the gate set in the tree was already correct before this PR. Nothing was broken and nothing misled anyone. This PR buys exactly one thing: the surfaces that merely point at the bar can no longer go stale from the bar. package.json:21 is the definition; pnpm verify cannot drift because it is the list.

What changed

Three cells, plus one paragraph re-flow. Seven insertions, eight deletions, markdown only.

surface what it is change
AGENTS.md 130 — delivery-process table, Gates row restatement: names the set only to say "this is the bar" → `pnpm verify`. — one line, cell shape and the row's trailing period kept
docs/backlog/README.md 29–32 — the acceptance floor every dispatched card inherits restatement: same, for every card → `pnpm verify` green — paragraph re-flowed (see below)
CLAUDE.md 8–9 — the inlined ⛔ gate rule restatement, and not named by the card — see The grep → `pnpm verify` must pass — separate commit, independently revertable

Why docs/backlog/README.md needed a re-flow, not a one-line edit

The floor wrapped mid-command across lines 29–30. Substituting pnpm verify shortens line 29 by 54 characters, which would have left a ragged wrap reading as damage, so the whole paragraph was re-flowed at the file's existing width. It occupies the same four lines (29–32) as before, and the re-flow is confined to that one paragraph — nothing outside it moved.

Why CLAUDE.md needed must all pass → must pass

all bound to a plural list of commands. Over a single script name it is ungrammatical. This is required by the substitution, not an extra edit — the same class of check the card asked for on the acceptance floor. Both new readings are complete sentences:

  • `pnpm verify` must pass before a change is reported as done. — and the following sentence's "the gates" still refers correctly to what verify runs.
  • Every card inherits the same acceptance floor: `pnpm verify` green, gate output pasted in the PR, … — the same shape as the precedent already in the tree at docs/backlog/14-release.md:14.

🔎 The grep — and a surface the card does not name

Four spellings, whole tree, tracked files. Run because the card told me not to trust its list of two, and it was right to.

git grep -n 'pnpm validate'
git grep -nE 'pnpm (validate|lint|typecheck|lint:i18n-gate)'
git grep -n 'lint:i18n-gate'
git grep -nEi 'all (three|four)|(three|four) (green|gates|commands)|三(条|个)|四(条|个)'
git grep -nE 'validate.{0,3}/.{0,3}lint|lint.{0,3}/.{0,3}typecheck'

The composed spelling existed in exactly four places on 14c899f. One is the definition. Three — not two — were restatements:

AGENTS.md:130                 | Gates | `pnpm validate && pnpm lint && pnpm typecheck && pnpm lint:i18n-gate`. |
CLAUDE.md:8                   `pnpm validate && pnpm lint && pnpm typecheck && pnpm lint:i18n-gate` must all pass before a change
docs/backlog/README.md:29     Every card inherits the same acceptance floor: `pnpm validate && pnpm lint && pnpm typecheck &&
package.json:21               "verify": "pnpm validate && pnpm lint && pnpm typecheck && pnpm lint:i18n-gate"   ← THE DEFINITION

CLAUDE.md:8 is a restatement by the card's own criterion — it names the set only to say "this is the bar" and carries zero per-command information; its second sentence ("Metadata mistakes fail silently at runtime; the gates are the only place they surface early") is a rationale for the set, not information about any one gate. It is on no exclusion list in the card, and it is not a demonstration or a historical record.

It is also provably drift-capable rather than a statement that has never moved — which is the distinction that keeps DESIGN.md:354 out. git diff 14c899f^1 14c899f -- CLAUDE.md shows it said three commands until 4560611 rewrote it. It is one of the surfaces that went stale when CI gained a fourth gate.

I moved it, in its own commit (84d7642) so it can be dropped with a single revert by anyone who reads the card's "no other file" as binding over the card's own criterion. Commit 3217a91 is complete and self-sufficient without it. My reasoning for moving rather than filing: it is mechanically identical to the other two, it is the most-read file in the repository (auto-loaded into every Claude Code session, so a stale copy there misinforms the widest audience), no open PR or issue holds that file (zero open PRs at the time of writing), and it adds no process cost at all — CLAUDE.md is governed, and this PR was already routed to the maintainer for AGENTS.md. Filing it instead would produce a second governed-surface card to do one line that is already in front of the maintainer's eyes.

Every other hit, and why it stays

hit verdict why
README.md 47–50 fenced block demonstration — leave shows a newcomer the four things and what each catches; collapsing deletes the point of the block
AGENTS.md 29–34 fenced block demonstration — leave same for an agent, and its per-gate comments are what explain that lint is not a second spelling of validate. Its surrounding prose was deliberately rewritten count-free by PR #77 and is untouched here
DESIGN.md:354 M1 acceptance cell historical record — leave at three, ⛔ untouched adjudicated on #73: it records what a delivered milestone was accepted against, not what a contributor runs today. Also the only slash-form spelling in the tree, and it did not drift — PR #77 deliberately did not touch it
package.json:21 the definition — leave this is the thing the other three now point at
.github/workflows/ci.yml 58/65/67/75 executable enforcement, not a restatement — leave separate CI steps give separate red X's; collapsing would cost per-gate failure granularity
docs/backlog/02-contract-domain.md 43, 45 · 03-post-signature-domain.md 26 · 11-i18n.md 8 per-command information — leave each names one gate and states its expected output ("pnpm validate reports 9 objects", "pnpm lint shows no title-unresolvable"). Not statements of the set
objectstack.config.ts 109/148 · scripts/check-lint-i18n-gate.mjs · 11 files under src/** incidental single-command mentions — leave design notes about one gate's behaviour; none states the set
docs/backlog/14-release.md:14 already correct — leave already says `pnpm verify` green; this is the precedent this PR generalises
DESIGN.md 270, 333 already name-based — leave "走同一条 verify 链" / "保留 verify 链" — reference the chain by name, no enumeration
count-words all three / all four / 三条 / 四条, 30+ hits none is about the gate set — leave all concern dashboards, datasets, AI fields, design principles, lookup columns. PR #77's count-free rewrite held: no gate-count word survives anywhere in prose
pnpm-lock.yaml @objectstack/verify unrelated a package name

Repo-wide after this PR, the composed spelling survives only at its definition:

$ git grep -n 'pnpm validate && pnpm lint'
package.json:21:    "verify": "pnpm validate && pnpm lint && pnpm typecheck && pnpm lint:i18n-gate"

⚠️ A statement in the card is false, and it matters

The card says: "Five places in the repository restated that list, and all five drifted at once when CI gained a fourth gate — fixed in one pass by #73 / PR #77", and its table names those five as AGENTS.md Gates cell · docs/backlog/README.md · README.md fenced · AGENTS.md fenced · DESIGN.md:354.

The count is right; the membership is wrong in two directions. git diff 14c899f^1 14c899f --stat shows PR #77 touched exactly four files — AGENTS.md, CLAUDE.md, README.md, docs/backlog/README.md — and not DESIGN.md:

So the card swapped CLAUDE.md:8 out for DESIGN.md:354. The consequence is not cosmetic: it meant CLAUDE.md:8 was never classified restatement-vs-demonstration by anybody. PR #77's dev enumerated it in their table but silently omitted it from the collapse/keep split in their judgement-call section — which is the recommendation this card adopted as option A. It fell through the seam between the two cards.

For the record, the five that actually drifted and were fixed by PR #77 are: README.md fenced · AGENTS.md fenced block and prose · AGENTS.md:130 Gates cell · CLAUDE.md:8 · docs/backlog/README.md:29. DESIGN.md:354 is a sixth, independent statement that never drifted.

The judgement call: should either cell carry more than the bare name?

No count — directed, and I agree without reservation: a count is the precise mechanism that put five copies wrong in a single edit.

No pointer either. This was left to me, and my reading is that a pointer is a net loss on all three axes that matter here:

  1. It reintroduces a drift-capable coordinate. The card's thesis is that a pointer to a definition should be the definition's name. Naming package.json adds a second fact that can go stale — trivially as package.json:21 (a line number that is already wrong the moment a script is added above it), and non-trivially if the repo ever gains a workspace or a different runner. Replacing "which four commands" with "which file" shrinks the drift surface without closing it. pnpm verify closes it.
  2. pnpm run already resolves it. There is exactly one place a pnpm script can be defined. A reader who wants the expansion types pnpm run, and gets the live value rather than a documented one. A pointer restates a guarantee the tool already makes.
  3. In AGENTS.md specifically, the pointer would aim somewhere worse than what is already on the page. The four gates with their explanations sit in the fenced block at lines 29–34, a hundred lines above the table. Sending a reader to package.json sends them to the terser of the two copies in arm's reach.

So: bare pnpm verify in both cells, plus the period the AGENTS.md row already carried. The one thing a reader loses is seeing the four names at that spot; both files show them elsewhere — AGENTS.md in its own fenced block, and the backlog's readers are dispatched agents who read AGENTS.md.

Gate output — all four exit 0, captured before any pipe

Run on 84d7642 in a dedicated worktree. Each gate written to its own log, $? read directly into a variable on the next statement, never through a pipe:

GATE validate       EXIT=0
GATE lint           EXIT=0
GATE typecheck      EXIT=0
GATE lint:i18n-gate EXIT=0
SUM_NONZERO=0

One green tail per gate:

validate        ✓ Validation passed (2549ms)
                  Data: 11 Objects  170 Fields

lint            21 warning(s), 5 suggestion(s) (1499ms)
                platform built-ins: 773 i18n issue(s) hidden — rerun with --include-platform to audit them

typecheck       > hotclm@0.1.0 typecheck /home/user/hotclm-issue-78
                > tsc --noEmit                    (no diagnostics, exit 0)

lint:i18n-gate  ✓ i18n gate
                  LOCALES  : "en", "zh-CN" checked (required: en, zh-CN)
                  REACH    : 12 surface classes walked — object=34 · field=273 · option=188 · section=35 ·
                             view=37 · action=55 · app=2 · navigation=28 · dashboard=6 · widget=38 ·
                             dataset=70 · page=2
                  COVERAGE : 0 missing keys across 2 locale(s)

The validate and lint warnings are pre-existing on main and are not caused by this diff, which is markdown only. Independent corroboration rather than an assertion: PR #77 reported lint as 21 warning(s), 5 suggestion(s) on 4560611 and validate as ✓ Validation passed. Both readings are byte-identical here. The 27 validate carrier-only field notices and the four approval-approvers-may-resolve-empty suggestions concern src/objects/ and src/flows/, neither of which this PR touches.

Demonstration: no gate reads any file this PR touches

Asserting it is not evidence, so here is the measurement, matching the bar PR #77 set. All three documents were reduced to garbage at once, all four gates were run against the corrupted tree, and the tree was restored under trap … EXIT INT TERM using absolute paths from git rev-parse --show-toplevel, then verified by state — never by an exit code.

### 1. restore targets: HEAD blob hashes at 84d7642
   AGENTS.md                HEAD=7814aa72e968bf822a1e7d292e9349c1b166b639  bytes=11760  marker=0
   CLAUDE.md                HEAD=3dfb25b6cc2d40d52f93fbef74da7f43713e67e5  bytes=925    marker=0
   docs/backlog/README.md   HEAD=5c7fea1d3454722d535a2f197831910653c1e67d  bytes=2691   marker=0

### 2. mutate all three at once — proved on disk, not by an editor's exit code
   AGENTS.md                marker 0->2   bytes 11760->70   lines=2
   CLAUDE.md                marker 0->2   bytes 925->70     lines=2
   docs/backlog/README.md   marker 0->2   bytes 2691->70    lines=2
   git diff --stat HEAD:  3 files changed, 6 insertions(+), 222 deletions(-)

### 3. all four gates against the corrupted tree — $? captured before any pipe
   GATE validate       EXIT=0
   GATE lint           EXIT=0
   GATE typecheck      EXIT=0
   GATE lint:i18n-gate EXIT=0
   ANY_GATE_NONZERO=0
   VERDICT: every gate still exits 0 with all three documents destroyed => no gate reads them

### 4. restore — git checkout HEAD -- (absolute path), never a bare checkout -- which reads the index
### 5. verify restore BY STATE
   MATCH   AGENTS.md                7814aa72e968bf822a1e7d292e9349c1b166b639
   MATCH   CLAUDE.md                3dfb25b6cc2d40d52f93fbef74da7f43713e67e5
   MATCH   docs/backlog/README.md   5c7fea1d3454722d535a2f197831910653c1e67d
   git status --porcelain: []
   RESTORE VERIFIED BY STATE

Each mutation was proved to have landed by a marker count on disk (0 → 2 per file) and a byte-count change, because an editing tool's exit code is 0 on a zero-hit substitution. The restore is proved by comparing git hash-object against git rev-parse HEAD:PATH for each file plus an empty git status --porcelain; an empty hash would have been read as failure, not as "nothing to compare".

A second, independent line of evidence agrees with the ablation: tsconfig.json:14 is "include": ["objectstack.config.ts", "src/**/*"], and validate / lint walk the metadata graph from objectstack.config.ts. No markdown is in any gate's input set. The ablation is the measurement; the tsconfig line is the explanation.

Edits verified on disk by before/after spelling counts

Counted on disk, per file, never by an editor's exit code:

file composed spelling pnpm verify
AGENTS.md 1 → 0 0 → 1
CLAUDE.md 1 → 0 0 → 1
docs/backlog/README.md 1 → 0 0 → 1
README.md 0 → 0 0 → 0
DESIGN.md 0 → 0 0 → 0
package.json 1 → 1 (the definition, untouched) 0 → 0

The substitution script asserted each replacement at count exactly 1 and would have aborted with no partial credit on any other number; the table above is the independent on-disk confirmation.

Scope

Documentation only: AGENTS.md, CLAUDE.md, docs/backlog/README.md. No README.md, no DESIGN.md, no package.json, no .github/, no src/, no LICENSE, no CHANGELOG.md, no docs/design/**. What the gates are and the order they run in are unchanged. Zero open PRs at the time of writing, so no file-surface conflict with anything in flight. No changeset (this repo has no changeset gate). No browser run owed — no surface a human touches changed. No control characters introduced (grep -naP over the three changed files, clean).

Acceptance notes

  • Every quoted line was re-read on 14c899f before editing, not copied from the card's table.
  • The card's "five places … all five drifted" claim is factually wrong in its membership — see the dedicated section above. This is reported, not worked around.
  • Both re-flowed paragraphs were checked for width against the file's existing wrap (docs/backlog/README.md prose ≤ 103, CLAUDE.md prose ≤ 99); the new lines are 94–97 and 57–95 respectively.
  • Noted, not filed: DESIGN.md:354 still spells three gates. Adjudicated on Four surfaces state the gate set as three commands; CI now enforces four — one PR must move all four, and three of them are governed #73, deliberately untouched, and correct as a historical record. Nothing to do. Succeeding party: none — no PR or person should touch this line without a needs-user-decision card.
  • Noted, not filed: .github/workflows/ci.yml could in principle become a single pnpm verify step. It should not — four separate steps give four separate failure signals in the CI UI, and the workflow is the enforcement rather than a restatement of it. Succeeding party: none.

维护者速读(草稿)

由 dev 起草,席位定稿为 issue 上的 ACCEPT 评论。

改了什么:仓库里「只是指向门槛」的三处文字,从逐条枚举四条 gate 改成直接写 pnpm verify——AGENTS.md 交付流程表的 Gates 格、docs/backlog/README.md 每张卡继承的验收底线、以及 CLAUDE.md 里那条内联的 ⛔ 门槛规则。package.json 的 verify 是定义,保持原样;README.md 与 AGENTS.md 的两个代码块继续逐条列出(它们的职责是演示,每条后面的注释才是它们值得读的原因),DESIGN.md 354 的 M1 验收格继续写三条(#73 已裁决,属历史记录)。

为什么改(是加固,不是缺陷):改之前这些话全部是对的,没有任何人会被误导。买到的只有一件事:指针不可能再和定义脱节。pnpm verify 不会过期,因为它就是那张清单。

⚠️ 卡片里有一句是错的,需要你知道:卡片说「五处都同时漂移、被 PR #77 一次修好」,并把 DESIGN.md:354 算作其中一处。实测 PR #77 只碰了四个文件,没有 DESIGN.md;真正同时漂移的第五处是 CLAUDE.md:8(它在 #77 之前写着三条),而卡片的表格里根本没有它。后果不是笔误:CLAUDE.md:8 因此从来没被任何人判定过属于「restatement 还是 demonstration」,它掉进了两张卡之间的缝里。按卡片自己的判据它是 restatement,所以我把它一起改了,单独一个 commit(84d7642),你若不同意,一次 revert 即可去掉,前一个 commit 独立完整。

风险与代价:零。纯 Markdown,7 加 8 删。没有任何 gate 读这三个文件——不是断言,是量出来的:三个文件同时毁成两行乱码,四条 gate 依然全部 exit 0,随后在 trap 下按 git hash-object 与 HEAD blob 逐个比对还原并验证(见 Demonstration)。validate 与 lint 的告警在 main 上本就存在,与本 PR 无关(lint 的「21 warning(s), 5 suggestion(s)」与 PR #77 在 4560611 上的读数逐字一致)。回滚就是 revert,无迁移、无数据、无依赖。

席位意见:(留给席位填写)

你要做的:① 看一眼三处新写法的语气是否合你意——尤其 CLAUDE.md 那句为了语法把「must all pass」改成了「must pass」(all 对单个脚本名不成立);② 决定是否保留第二个 commit(CLAUDE.md);③ 合并。本 PR 因含受管面而留给你合并,席位只记 ACCEPT、不合并、不撤草稿。


🤖 Generated with Claude Code

https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR


Generated by Claude Code

`package.json`'s `verify` is the definition of the gate set. AGENTS.md's
delivery-process Gates cell and the backlog's acceptance floor restated that
list verbatim while carrying no per-command information: they exist to point at
the bar, and a pointer to a definition should be the definition's name. Both
now say `pnpm verify`, which cannot go stale because it *is* the list.

This is hardening, not a repair. Every statement of the gate set in the tree was
already correct; what changes is that two of them can no longer drift from it.

The two fenced blocks in README.md and AGENTS.md keep enumerating, deliberately:
their job is to show a reader what runs and what each gate catches, and those
per-gate comments are the reason the blocks are worth reading. DESIGN.md's M1
acceptance cell stays at three commands, adjudicated on #73 as a record of what
a delivered milestone was accepted against rather than an instruction.

Neither cell gains a count or a pointer. A count is the precise mechanism that
put five copies wrong in one edit, and naming `package.json` would add a second
drift-capable coordinate for a fact `pnpm run` already resolves on its own.

The backlog paragraph is re-flowed because the substitution shortened its first
line mid-command; the re-flow is confined to that one paragraph.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
Found by the enumeration grep this card mandates, and absent from the card's own
table of surfaces. CLAUDE.md's rule restated the composed spelling verbatim and
carried no per-command information, so it is a restatement by exactly the
criterion that moved the other two.

It is also provably drift-capable rather than a statement that has never moved:
it said three commands until 4560611 rewrote it, so it is one of the surfaces
that went stale when CI gained a fourth gate.

"must all pass" becomes "must pass": `all` bound to a plural list of commands and
does not read over a single script name.

Kept as its own commit so it can be dropped with one revert by anyone who reads
the card's "no other file" as binding over the card's own criterion. The
preceding commit is complete and self-sufficient without this one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR

Copy link
Copy Markdown
Collaborator Author

维护者速读(席位定稿)

ACCEPT 已记在 #78。本 PR 含受管面,席位不合并,留给你。已从草稿翻出为 ready-for-review——草稿状态你点不了合并(这是 PR #77 的教训)。PR 正文开头那句「the seat … does not flip it out of draft」是 dev 转述我派发卡时写岔了:不撤草稿的是 dev,撤草稿的正是席位。以本条为准。

改了什么:仓库里「只是指向门槛」的三处文字,从逐条枚举四条 gate 改成直接写 pnpm verify——AGENTS.md 交付流程表的 Gates 格、docs/backlog/README.md 每张卡继承的验收底线、CLAUDE.md 里那条内联的 ⛔ 门槛规则。package.json:21 的 verify 是定义,原样不动;README.md 和 AGENTS.md 的两个代码块继续逐条列出(它们的职责是演示,每条后面的注释才是它们值得读的地方);DESIGN.md:354 的 M1 验收格继续写三条(#73 已裁决,属历史记录)。改完之后,那串组合写法全仓库只剩定义一处。

为什么改(加固,不是缺陷):改之前这些话全是对的,没有任何人会被误导。买到的只有一件事——指针不可能再和定义脱节。pnpm verify 不会过期,因为它就是那张清单。

⚠️ 我的卡片写错了一处,需要你知道:卡片说「五处同时漂移、被 PR #77 一次修好」,并把 DESIGN.md:354 算进这五处。实测 PR #77 只动了四个文件,没有 DESIGN.md;真正同时漂移的第五处是 CLAUDE.md:8(#77 之前它写着三条),而它根本不在我卡片的表里。数字对,成员错。后果不是笔误:CLAUDE.md:8 因此从没被任何人判定过属于 restatement 还是 demonstration,它掉进了两张卡之间的缝里。dev 按卡片自己的判据判定它是 restatement 并一并改了——判断正确,我已在 #78 上把这条错误记在席位账上。

要你定夺的一件事:是否保留第二个 commit 84d7642(只动 CLAUDE.md)。席位意见是保留——卡片错的是枚举、对的是判据,而 CLAUDE.md 是全仓库被读得最多的文件(每个 Claude Code 会话自动加载),一份过期的副本误导面最广;且它本就是受管面,本 PR 已因 AGENTS.md 送到你面前,不增加任何流程成本。你若不同意,git revert 84d7642 即可,第一个 commit 3217a91 独立完整(已验证:它只碰 AGENTS.md 与 docs/backlog/README.md)。

顺带一提:CLAUDE.md 那句为了语法把「must all pass」改成了「must pass」——all 对单个脚本名不成立。这是替换要求的,不是额外改动。

风险与代价:零。纯 Markdown,7 加 8 删,3 个文件。没有任何 gate 读这三个文件——不是断言,是量出来的:三个文件同时毁成两行乱码,四条 gate 依然全部 exit 0,随后在 trap 下按 git hash-object 与 HEAD blob 逐个比对还原并按状态验证。validate 与 lint 的告警在 main 上本就存在,与本 PR 无关(lint 的「21 warning(s), 5 suggestion(s)」与 PR #77 在 4560611 上的读数逐字一致)。回滚就是 revert,无迁移、无数据、无依赖。

你要做的:① 看一眼三处新写法的语气合不合你意;② 决定留不留 84d7642;③ 合并。


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 12, 2026 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants