Skip to content

skills(objectstack-ui): state the one-app-per-package cap in App Navigation (GOVERNED — token ceiling raised to the landed count under a maintainer ruling) - #17310

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-16565-one-app-rule-ui-skill
Sep 13, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/issue-16565-one-app-rule-ui-skill

Conversation

@baozhoutao

@baozhoutao baozhoutao commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Item 2 of #16565. This PR does not close that card — item 1/3 ship in the companion PR #17309 and item 4 remains open. No closing keyword is used here.

⛔ GOVERNED SURFACE — do not merge, queue, arm auto-merge, or approve. skills/** is on the GOVERNED_SURFACES register. Landing this is a GOVERNED_APPROVERS act by hand. It is deliberately kept out of #17309's diff: a mixed diff is governed whole, judged on the file list and not on proportion.

Authorization for the ratchet raise

check-skills-token-ratchet is shrink-only, and its header sets exactly one evidence bar for the other direction: "The other direction lands only in a PR whose body quotes a maintainer ruling authorizing it." That ruling was given by the maintainer in PM chat on 2026-09-13, authorizing this PR specifically. Reproduced verbatim and untranslated, as the Communication rule requires:

「17310 允许增加」

This supersedes the earlier revision of this PR, whose title and body said the change was "over the token ratchet by 59" and "needs a maintainer decision", and which deliberately left the ceiling untouched. That decision has now been made. The ceiling is raised in this revision, the PR is no longer red by design, and nothing was re-wrapped, reflowed or trimmed to buy tokens — the raise is paid for with the ruling, which is what the ruling is for.

The re-measurement — the raise is pinned AT the landed count, not at the number CI reported

The 3874 in the earlier revision was measured on dc077e17, before origin/main was merged in. It is stale, and copying it forward would have granted 18 tokens of headroom this file has not earned. origin/main deleted the page row from this same file's view-type table while this PR sat open:

-| `page` | Mounts a published Page (`pageName`); no rows of its own |

So the arithmetic, re-derived on the merged head 4f22acc38d, in bytes because bytes are what this gate's ceil(utf8 bytes / 4) convention divides:

merge base b90aff81f   skills/objectstack-ui/SKILL.md   15260 bytes -> 3815 tokens   ceiling 3815   headroom 0
this PR's addition                                       +234 bytes -> 3874 tokens   (+59)  <- the pre-merge CI number
origin/main's `page`-row deletion                         -70 bytes
LANDED, merged head                                      15424 bytes -> 3856 tokens   (+41)

The ceiling moves 3815 to 3856, by 41 rather than 59, and is pinned at the measurement — zero headroom, so the next token added to this file is paid for by deleting one from it, exactly as before. Independently reproducible without running the script, which is the point of the convention:

$ wc -c < skills/objectstack-ui/SKILL.md
15424
$ node -e "console.log(Math.ceil(15424/4))"
3856

Exactly one ceiling entry moves. No other row in CEILINGS is touched, and the raise carries its ruling in a comment on the row itself, in the shape the surrounding rows already use.

维护者速读(草稿)

改了什么 —— 两处。① skills/objectstack-ui/SKILL.md 的 App Navigation 一节加三行(与上一版完全相同,未改一字):一个 type: 'app' 包只暴露一个 App,多受众是它内部用 requiredPermissions 门控的分组,并把 ADR-0019 写成指名到 app-as-consumer-unit 记录。② scripts/check-skills-token-ratchet.mjs 里 skills/objectstack-ui/SKILL.md 这一行的上限由 3815 抬到 3856,并在该行上方按本文件既有格式记下裁决原话与算术。

为什么改 —— ① 的理由没变:这是 AI 作者真正会读的地方。发布包里的实测:一个 app 的规则出现 0 次(所有拼写),而 defineStack 出现 54 次、App.create 3 次 —— 零是读数,不是空语料。② 是因为你已经就这 41 个 token 给了裁决(见上方 Authorization 节),按门禁自己的规则,这正是抬上限唯一合法的落地方式。

风险与代价(含回滚) —— 代价是 41 个 token,不是上一版说的 59:origin/main 在本 PR 挂起期间删掉了同文件里的 page 视图类型行,少了 70 字节。抬到的 3856 是合入后实测值,余量仍为 0 —— 下一个作者往这个文件加一个 token,仍然只能靠在同文件删一个来付账,棘轮没有被放松,只是按裁决上移了一格。回滚成本仍为零:两个文件、一个常量、无代码、无生成物。⛔ 本 PR 没有跳过、禁用或隔离任何测试或门禁。

席位意见 —— (留空,待席位定稿)

你要做的 —— 人工合并(治理面,skills/**)。本 PR 仍不因合并关闭 #16565。

The measurement behind the teaching, unchanged

Taken on origin/main at b90aff81f2, over the whole published bundle. Every zero carries a same-corpus positive control from the same pass.

grep -ric over skills/          (BEFORE)
  'at most one app'                    0
  'exactly one app'                    0
  'only one app'                       0
  'one app per package'                0
  'single app'                         0
  'app-as-consumer-unit'               0
  positive control  defineStack       54
  positive control  App.create         3
  positive control  ADR-0019           3   <- and all 3 mean the APPROVALS record:
                                             objectstack-automation/SKILL.md x2,
                                             objectstack-platform/SKILL.md x1
  negative control  'zzz-nonsense-zzz'  0

So the rule is absent in every spelling from a bundle that names defineStack 54 times, and the number an author would follow resolves, in this bundle's own usage, to the wrong record.

Changeset: still the label, not a file

Not a changeset — the skip-changeset label, already applied. Measured rather than assumed: no package manifest's files[] contains skills, skills/ is not a workspace member, and the root manifest is private. The second file this revision touches, scripts/check-skills-token-ratchet.mjs, is a repo gate script that ships in nothing. pr-automation.yml spells this exact case out, including that an empty-frontmatter changeset is rejected and that the label is what "names no package" is for.

Verification, re-run on the merged head

Gate family derived from this diff by the repo's own deriver, not recalled:

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack   ->  44 commands
44 run: 43 exit 0 on the first pass; 1 exit 3 (PREREQUISITE NOT MET, not a finding)
  `pnpm --filter @objectstack/lint run check:doc-formula-expressions` exit 3 -> built
  `@objectstack/formula` + `@objectstack/lint` as its own fix line prescribes -> re-run exit 0
=> 44 of 44 exit 0. Exit codes captured with redirection BEFORE any pipe.

The gate this PR is about, and its self-test, on the merged head:

$ node scripts/check-skills-token-ratchet.mjs ; echo EXIT=$?
✓ check-skills-token-ratchet: skills/objectstack-ui/SKILL.md is 3856 tokens (ceiling 3856; headroom 0).
   skills/objectstack-ui/SKILL.md                               3856 /   3856   (+0)
EXIT=0

$ node scripts/check-skills-token-ratchet.mjs --self-test ; echo EXIT=$?
✓ check-skills-token-ratchet self-test: 65 cases pass.
EXIT=0

check-ratchet-remedy-authority was run deliberately, because this gate's own source carries a placement warning about prose near CEILINGS flipping its classification from marked to excluded. The new comment sits inside the map, far from the declaration, and the classification holds:

$ pnpm check:ratchet-remedy-authority ; echo EXIT=$?
OK  check-ratchet-remedy-authority: 245 scripts swept; 15 mark the expanding remedy ⛔ MAINTAINER-ONLY,
    6 turn it down outright, 224 hand out no ratchet-expanding remedy.
EXIT=0

pnpm lint is a whole-repo scan CI owns; the local reading is a declared narrowing, with the three facts that make it a measurement rather than a skipped step: (a) the linted population is read from eslint.config.mjs itself — files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] — which does not include .md, and eslint confirms it in its own words for SKILL.md ("File ignored because no matching configuration was supplied"); (b) the file count is read from --format json: 2 paths handed in, 1 in population, 0 errors, 0 warnings on it; (c) the invariance claim — this repo runs one eslint.config.mjs which never enables type-aware linting for any file (no parserOptions.project, no typed rules; the config says so at its QUERY_OPTIONS_TEST_GLOBS comment, with its own positive control), so a comment block added to one .mjs cannot move the verdict on any untouched file.

Control-character self-scan on both touched files, outside check:nul-bytes: grep -naP over the C0 set plus DEL, zero hits.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NFSv55L8jzmE9yvi9UwZug


Generated by Claude Code

…gation

The App Navigation section tells an author to `register under
defineStack({ apps: [...] })` and stops there. Nothing in the published
bundle says the array holds one entry: measured across `skills/`, the
one-app rule appears 0 times in any spelling, while `defineStack` appears
54 times and `App.create` 3 — the zeros are readings, not an empty corpus.
An agent authoring `*.app.ts` from this skill therefore has no reason not
to write one app per audience, and finds out at `defineStack` load time,
after the design is written.

All three `ADR-0019` citations in the published bundle point at
`0019-approval-as-flow-node`, so this one names its record explicitly.

⚠️ This overruns the published-skills token ratchet by 59 tokens
(3874 against a ceiling of 3815, headroom measured at 0 before writing).
The ceiling is deliberately NOT touched and nothing else is trimmed to
pay for it — both are maintainer decisions. See the PR body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 10, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 10, 2026
…landed count

The one-app-per-package cap added to `skills/objectstack-ui/SKILL.md` sits
41 tokens over the shrink-only ratchet. The maintainer ruled on 2026-09-13
that this PR may grow the file; the ruling is quoted verbatim beside the row
and in the PR body, which is the evidence bar the gate's header sets.

Re-measured on the merged head rather than copied from the pre-merge CI run:
`origin/main` deleted the `page` view-type row from the same file after that
run, so the landed count is 3856 (15424 bytes), not the 3874 CI reported.
The row is pinned AT that measurement, so it carries zero headroom.

No other ceiling moves, and no text was re-wrapped or reflowed to buy tokens.

Claude-Session: https://claude.ai/code/session_01NFSv55L8jzmE9yvi9UwZug
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s and removed size/xs labels Sep 13, 2026
@claude claude Bot changed the title skills(objectstack-ui): state the one-app-per-package cap in App Navigation (GOVERNED — over the token ratchet by 59, needs a maintainer decision) skills(objectstack-ui): state the one-app-per-package cap in App Navigation (GOVERNED — token ceiling raised to the landed count under a maintainer ruling) Sep 13, 2026
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 13, 2026

Copy link
Copy Markdown
Collaborator

维护者速读

改了什么 —— 两个文件,共 +22 / −1。

① skills/objectstack-ui/SKILL.md 的 App Navigation 一节 +4 行:一个 type: 'app' 包只暴露一个 App,defineStack 在加载时拒绝第二个;更多受众是那一个 app 内部用 requiredPermissions 门控的分组。并把依据指名到 ADR-0019 的 app-as-consumer-unit 记录。

② scripts/check-skills-token-ratchet.mjs:skills/objectstack-ui/SKILL.md 这一行的上限 3815 → 3856,行上方按本文件既有格式记下您的裁决原话与算术。

为什么改 —— ① 是因为这条规则在已发布技能包里一次都没出现过。实测(origin/main b90aff81f2,全包扫描,每个零都带同语料阳性对照):'at most one app' / 'exactly one app' / 'only one app' / 'one app per package' / 'single app' 全部 0 次,而 defineStack 54 次、App.create 3 次 —— 零是读数不是空语料。更糟的是 ADR-0019 在这个包里出现 3 次,三次全指 APPROVALS 记录,所以作者照着编号查会查到另一份文档。AI 作者读的就是这个包,规则不在里面,它就会写出第二个 App,然后在 defineStack 加载时才撞上限——本该在写的时候就知道。

② 是因为您已就这 41 个 token 给了裁决,而按门禁自己 header 的规定,「正文引用维护者裁决」是抬上限唯一合法的落地方式。

风险与代价(含回滚) —— 代价是 41 个 token,不是最初报的 59。origin/main 在本 PR 挂起的三天里从同一个文件的视图类型表删掉了 page 那一行(−70 字节),所以落地值是 3856。抬到的是合入后实测值,余量为 0 —— 下一个往这文件加 token 的作者,仍然只能靠在同文件删一个来付账。棘轮没有被放松,只是按您的裁决上移了一格。⛔ 没有 re-wrap、没有重排、没有为筹 token 删改任何内容;⛔ 没有跳过、禁用或隔离任何测试或门禁。回滚成本接近零:两个文件、一个常量、无代码、无生成物,git revert 即可。

席位意见 —— 建议合并。三条理由,都是本席自己对着 diff 和树核验过的,不取报告自述:

  1. 抬上限抬对了地方。本席原本给 dev 的数字是 3874(照抄 CI 那次的读数)。它没有照抄,重测后发现 main 已经移动了该文件,钉在 3856。照本席的数字抬会白送 18 个 token 的余量——这是本轮唯一一处本席给错、施工方纠正的地方,值得记下。
  2. 裁决记在了两个地方:PR 正文(门禁 header 要求的位置)和被抬的那一行旁边(便于下一个读代码的人当场看到依据),两处均为逐字未译。
  3. 教学本身值得这 41 个 token:它防的是一类 AI 会反复犯、且只在运行时才报错的元数据错误。

⚠️ 一处如实申报:dev 未在本地跑完整的 Lint & Repo Gates(133 条),理由是 AGENTS.md 禁止在共享容器上枚举该工作流;它改用仓库自己的机械派生器(44 条全绿)加一份申报过的 eslint 收窄(含总体、计数、不变性三项事实)。本席认为可接受——因为真正的那 133 条随后在 CI 上跑了,且 13:19:11Z 全绿,该收窄已被实跑覆盖。

你要做的 —— 人工合并(受管面 skills/**,队列不接)。⛔ 本 PR 合并后仍不关闭 #16565(item 2 of 4,无 closing keyword)。


CI 读数,取于 2026-09-13T13:26Z,head 4f22acc38d:38 个 check,success 或 skipped,无一 failure。含原先那条红 Type Check · source gates(现 success)、Lint & Repo Gates(13:19:11Z success)、聚合 TypeScript Type Check(success)、Governed Surface Queue Guard(success)。

⛔ 本席未入队、未挂 auto-merge、未提交任何批准 review。


Generated by Claude Code

Merged via the queue into main with commit 4daa69d Sep 13, 2026
39 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-16565-one-app-rule-ui-skill branch September 13, 2026 14:08
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…rding it (objectstack-ai#17766)

Fixes objectstack-ai#17149

A `Claim:` comment that parses to ZERO branches is now reported as a
MALFORMED claim instead of being discarded. The deliverable is triage
5620657752's, verbatim: 「⛔ do not fix the third spelling. **A parser
that silently yields zero must say so** — an unparsed claim is an
*unclassified* result, ⛔ never a *no*.」 Option 1 on the card (widening
`claimedBranches` for the inline spelling) is **not** done here, and the
branch reader's accept set is byte-identical — pinned as a case, because
that is what keeps the NEXT unrecognised spelling loud on its first
occurrence.

## The mechanism

`governingClaim` picked the newest claim comment from which at least one
branch parses, and `if (branches.length === 0) return;` threw the rest
away. A discard is indistinguishable from an absence, so governance fell
back to an older claim and said nothing.

`claimGovernance(commentRows)` is now the two-part reading:

- `governing` — what `governingClaim` has always returned.
`governingClaim` is a one-line wrapper over it, so the ~18 readers that
legitimately need only branches are untouched (proved below: the
self-test's 3616 pre-existing cases pass unchanged on the refactor
alone).
- `malformed` — the NEWEST claim-shaped comment when it parses to zero
branches, carrying its comment id, its timestamp, and the `created_at`
of whatever governance fell back to.

Recency is written once and both halves call it, so the two readings can
never disagree about which comment is current.

## Every reader says so

| reader | before | after |
|---|---|---|
| H20 / H27 dispatch liveness | probed the superseded branch, or
(nothing parses) went silent as "no claim" | **H60**, a new `state`-band
row, fires for every open `pm:dispatched` card, naming the comment id,
what governance did instead, and the remedy |
| `check-clause2-carriers` declaration limb | read the superseded
claim's `Clause-②` line as if current | `cardDeclaration` returns
`claim-branch-unparsed` **before** any line is read, from any comment
(including the objectstack-ai#17366 correction path) |
| `--pair N` | exit 4 with a verdict taken from the wrong comment |
**exit 2 (UNJUDGED)**, with the whole reading printed |
| the fold/lane rosters (H37/H38) | built on `governing` | unchanged —
they need branches only, and H60 is the row that says the roster may
rest on a superseded claim |

**The exit code is the file's own, not a new one.** Its table already
reads 2 as UNJUDGED — "an unread carrier is NOT a bare carrier and an
unread thread is NOT an absent declaration (objectstack-ai#4690)" — and an
unresolvable carrier is exactly that. Rendering it as 4 would make an
unclassified result an adverse verdict, which is the reading the card
refuses by name. Rendering it as 0-with-a-message is what the whole file
exists against.

## The three questions the card asked

### 1. Where does the guard belong — carriers, half-states, or both?

**Both, from one source.** The state is produced once in
`check-half-states.mjs` (which owns `CLAIM_COMMENT_MARKER` and
`claimedBranches`) and consumed in both files. A second detector in the
carriers file is the drift that file's own docblocks refuse by name
("imported rather than restated ... so the two readers cannot drift").

### 2. Which end?

Option 2 only. See the four-axis reading below.

### 3. Is the fleet's claim template the cause? — MEASURED on the live
board

Read 2026-09-12T03:4xZ, REST, repo-scoped, over every open
`pm:dispatched` card:

- **28** open `pm:dispatched` cards.
- **5** of them (18%) have a newest claim-shaped comment that parses to
ZERO branches: **objectstack-ai#16310, objectstack-ai#16268, objectstack-ai#16251, objectstack-ai#16175, objectstack-ai#15234**.
- 22 parse; 1 (objectstack-ai#13597) carries no claim-shaped comment at all.

Four of the five claims were posted by ONE session
(`session_012GKcPZbMoGq7WPzKLfRBTU`, the `domain:devx` execution PM
seat) within **three seconds** of each other, 02:53:05Z–02:53:08Z, all
in the same shape:

```
Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16310-orphan-locale-key-gateable
Clause-②: yes
```

That is not a run of typos; it is a template emitting a carrier no
reader accepts. **objectstack-ai#16175 is the silent-fallback shape, live today**: its
newest claim (5642984850, 2026-09-12) parses to zero, so governance
falls back to its 2026-09-06 claim (5557414924) — which names a
**different** branch, `claude/issue-16175-regen-sibling-stale-rules`
against the current `claude/issue-16175-staleness-mtime-false-refusal`.
Every downstream reader is probing the wrong ref, and the two `Clause-②`
values happen to AGREE, which is the objectstack-ai#16589 near-miss recorded on objectstack-ai#16322
reproducing itself.

⛔ No `.claude/**` edit is made from this card, per the dispatch. The
template finding is handed to the skills-lane seat; after this PR lands,
those five cards are visible rather than silent, which is the point.

## Four-axis reading of option 1 vs option 2

- **实际业务需求** — measured, not speculative: 5 live carriers today, plus
the objectstack-ai#16322 cost (two rounds, a director re-review, a re-issued claim)
and its near-miss sibling. Option 1 addresses the one spelling in front
of us; option 2 addresses the population that produces them.
- **项目长远合理性** — option 1 is the treadmill the card names and objectstack-ai#16170
already bought once; each widening buys one spelling and leaves the next
silent. Contract-first says the defect is at the reader's CONTRACT (a
two-valued answer where three states exist), not in its accept set.
- **防 AI 写代码犯错** — decisive here. Option 1 is consumer-side tolerance —
the exact shape the axis forbids, and its failure mode is the silent
one. Option 2 is a loud refusal at read time: a claim written in a shape
the protocol does not accept is refused with the remedy named, and the
seat cannot declare a claim the tooling does not honour.
- **创业阶段不扩散需求** — option 2 adds no capability surface: one new reading
of data already in hand, no new request, no new exit code, no new label
written. Option 1 would grow the accept set permanently for a spelling
the standing rule already forbids.

⇒ Option 2, on all four. Option 1 is deliberately NOT also done: doing
both would let the accept set absorb the measured spelling and leave the
new state unexercised on the live board, which is the one way to ship
this fix and still not know whether it works.

## Verification

All exit codes captured before any pipe (`cmd > log 2>&1; EXIT=$?`).

| command | verdict line | exit |
|---|---|---|
| `pnpm check:pm-half-states` | `✓ check-half-states self-test: 3656
cases pass.` (was 3616) | 0 |
| `pnpm check:pm-clause2-carriers` | `✓ check-clause2-carriers
self-test: 493 cases pass (...)` (was 465) | 0 |
| `pnpm check:pm-dispatch-gates` | `✓ dispatch-gates self-test: 1678
cases pass.` | 0 |
| the derived union, 41 commands | `✓ dispatch-gates --ran: 41 derived
famil(ies) accounted for — 41 run, 0 NOT-MEASURED (a DERIVED zero — all
41 recorded an exit code and none of them is 3).` | all 0 |

The union was re-derived and re-run **after** the final commit, on head
`b2b55c2b4` (`git rev-parse --short HEAD`), with `--repo
objectstack-ai/objectstack` asserted. The first derivation printed a
STALE TREE clause naming `check-skill-line-ratchet.mjs` and
`check-widening-tells.mjs` (PR objectstack-ai#17760 had landed); `origin/main` was
merged in, the list re-derived byte-identically at 41, and every command
re-run on the merged head. Reconciliation was fed `command :: exit N`
lines so the zero is derived, not claimed.

⚠️ NOT MEASURED, and named rather than implied: the 47 artifact-roster
families, the 11 declared-wide-population families, the 4 families
taking a value from the workflow, and the 1 path-scheduled CI job are
each outside the derived total — CI's, not this run's.

### Ablation — the new state can actually fail

Committed first, then mutated on disk, then restored; the mutation and
the restore are both proved by `git hash-object` against the HEAD blob
rather than by an exit code.

- Mutation: the pre-fix silent discard restored (`malformed` never
populated). Anchor occurrences 1 → 0, file hash `f9f869cd` → `013b485b`.
- Result: **20 of 3656** half-states cases and **14** carriers cases
turn RED, every one of them in the new batteries. Direction: turns red,
as pre-registered.
- Restore: `git checkout HEAD -- scripts/pm/check-half-states.mjs`; `git
diff HEAD` empty, hash back to `f9f869cd`. The script carries `trap ...
EXIT INT TERM`.

## Live verification — the fix, run against the real board

Both sweeps were run on the merged head, 2026-09-12T04:0xZ.

**`check-half-states.mjs` (sweep, exit 0)** emits exactly **5** H60 rows
— the same five cards the independent REST census found, arrived at
through the fix's own code path: objectstack-ai#15234, objectstack-ai#16175, objectstack-ai#16251, objectstack-ai#16268, objectstack-ai#16310.
Both sentence variants fire live: objectstack-ai#15234 gets the "NOTHING governs this
card" reading, objectstack-ai#16175 gets "governance SILENTLY FELL BACK to an OLDER
claim (2026-09-06...)".

**`check-clause2-carriers.mjs` (sweep, exit 2)** reports **5 of 18
card/PR pairs UNJUDGED** — and two of them (objectstack-ai#15627 via PR objectstack-ai#17776, objectstack-ai#16565
via PR objectstack-ai#17310) are cards the `pm:dispatched` census could not see,
because they are not in that population. Before this change the sweep
exited 0 on all of them.

**The before/after, measured rather than reasoned** — the base tree at
`813f8e9f` materialised with `git archive` and its `cardDeclaration` run
against the same live comment rows:

| card | BEFORE | AFTER |
|---|---|---|
| objectstack-ai#16565 | `declared` `no` (`"Clause-②: no"`) | `claim-branch-unparsed`
|
| objectstack-ai#16175 | `declared` `no` (`"Clause-②: no"`) | `claim-branch-unparsed`
|
| objectstack-ai#15627 | `missing` | `claim-branch-unparsed` |

Two of the three read as a confident `Clause-②: no` taken off a comment
whose governance was never established — the objectstack-ai#16589 near-miss shape,
twice, on today's board. The third read as `missing`, which sends the
seat looking for a declaration line that is already written. Neither was
a reading anybody could have found without opening the card by hand.

## Fixtures

The measured bodies are quoted, never paraphrased, so a future widening
cannot make these batteries pass by accident:

- objectstack-ai#16322's two claims (5593513389 `Clause-②: no`, 5594909614 `Clause-②:
yes`) — the inline spelling, and the two values DISAGREE, which is why
reading the wrong one was a wrong answer and not merely an unlucky one.
⚠️ Re-measured: the OLDER claim is branchless too, so on that card
nothing parsed at all and `cardDeclaration`'s `pool = claimRows`
fallback read the FIRST claim in thread order — a second silent fallback
the card did not name, and the same fix reaches it.
- The live 2026-09-12 specimen on objectstack-ai#16175, with its different-branch
fallback.
- objectstack-ai#16170's bulleted directive still parsing (its pin stays green,
untouched at the H20 branch battery).
- Controls: a well-formed newest claim still governs; an older
branchless claim beside a well-formed newest one raises nothing; no
claim comment at all still reads `absent`; an unreadable thread still
reads `unreadable`.
- The negative pins that keep this a STATE: the inline spelling still
parses to ZERO, and `CLAIM_COMMENT_MARKER` still matches it.

## Acceptance notes

- `skip-changeset` applies: `scripts/pm/**` ships in no package
(fast-track path, no measurement owed).
- `Clause-②: no`, as the claim declares — nothing published moves.
- Scope held: no H22 docblock prose (objectstack-ai#17626 owns it), no widening tells
(objectstack-ai#17618), no `.md`, no `.claude/**`.
- H60 takes the `state` band, beside H34 — H34 reads a claim whose
SEPARATOR the marker refuses, this one reads a claim whose BRANCH line
the directive reader refuses; both are a live card contradicting itself,
repaired on the board. ⛔ Not `stall`: the row does not claim the card is
stopped, and the dev may be working perfectly well — what is broken is
the READING.
- ⛔ No age gate on H60, unlike H20's 60 minutes: a branchless claim is
wrong at the instant it is posted and no later sweep frees it, because
the protocol forbids a second `Claim:`. Pinned in both directions.
- noted, not filed: `cardDeclaration`'s `pool = claimRows` path (used
when nothing on the thread parses) picks the FIRST claim comment in
thread order rather than the newest. This PR makes that path unreachable
for the branchless case, so the residue is a recency question on a path
no live shape now reaches. Successor: whoever next touches
`cardDeclaration` — objectstack-ai#17098 is open against that function's
neighbourhood.

---

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK

---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants