Repository navigation
[Decision] 17 of 39 bare-root-worklist verdicts carry REFUSE-UNSPELLABLE whose definitional basis is now false — declare, re-name, or re-price? #12369
Description
Raised by the domain:devx @ objectstack seat (#6023) from the #12328 dispatch, which stopped without editing anything under its own falsification clause. ⛔ This seat picks among none of these options — execution seats never adjudicate.
Split out of #12328 because that card scopes 4 rows and the measured class is 17.
What was measured
scripts/pm/bare-root-worklist.mjs defines the verdict at :109-111:
REFUSE-UNSPELLABLE— the population is a file-KIND or FILENAME filter inside the root. The subtree idiom can only say "all of it", at the precision quoted.
So the verdict asserts no precise spelling exists. #12300 (783111d252, +186/−6, all in dispatch-gates.mjs) added if (globInNonFinalSegment(hint)) return triggerCovers(hint, inputPath); ahead of the collapse. ⭐ That retirement is general, not specific to the 4 rows whose prose happens to cite the collapse. Only 5 rows cite it in prose; the definitional basis moved under 17.
Each of the 17 now has a measured, 100%-precise live spelling — driven through the real hintCovers/trackedFiles, never collapseHint alone, and cross-checked against the tree's corpus:
| spelling | covers / corpus | rows held |
|---|---|---|
packages/**/*.test.ts |
2736 / 2736 | 1 |
packages/**/package.json · apps/**/package.json · examples/**/package.json |
74/74 · 1/1 · 4/4 | 9 |
packages/**/scripts/i18n-extract.config.ts |
9 / 9 | 3 |
skills/*/SKILL.md |
11 / 11 | 1 |
skills/*/references/** |
12 / 12 | 1 |
examples/*/objectstack.config.ts |
3 / 3 | 1 |
skills/*/SKILL.md (+skills/README.md) |
11 of 12 with one hint | 1 |
Verified independently by this seat at origin/main: skills/*/references/ = 12 tracked files; the TRIAGE map holds 39 verdicts, 26 of them REFUSE-UNSPELLABLE — so a 17-row subset is arithmetically consistent.
** in a non-final segment matches one or more segments, never zero. That is #12329, already open.
四维分析
① 实际业务需求. 下游拉力是实测的,但只覆盖 2 个门禁:#12310 点名 check:skill-refs,#12322 点名 check:objectql-double-limit。其余 15 行(package.json 三连、i18n 三连等)没有任何地方要求它们被命名。把 17 行全部声明出去,是在需求之前扩张能力面。
② 平台长远合理性. 这一维直接排除选项 C。让一个叫「unspellable」的裁决去描述一个 idiom 现在能精确拼出的population,等于把裁决名字变成谎言 —— 「拼不出来」退化成「我们选择不拼」。这正是本仓在消费侧一贯拒绝的容忍,只是上移了一层。台账的全部价值就是每一行都诚实。
③ 避免 AI 写代码犯错. 只有 B 的机械 pin 能关掉这个类。本次缺陷的实质是:一个裁决悄悄地不再描述这棵树,而 --self-test 全程绿着。#12347(可达性半边)与 #12330(单行机制)是两个先例 —— 都用 pin 而不是重写散文。A 单独做只修好 17 行、一个 pin 都不留,下一次推导变更会把同一个洞重新打开。
④ 创业阶段不扩散需求. 一个 PR 里跨 12 个门禁脚本写 17 处声明,正是要避免的无焦点扩张。packages/**/*.test.ts 一行就给推导加了 2736 个文件 × 门禁。
⭐ 四维并不同向到同一个选项 —— ② 排除 C,③ 指向 B,①④ 指向「只在有拉力处声明」。这正是必须人工决裁的形状。
Options
- A. 为全部 17 行在各自门禁的 population 常量旁声明精确拼法(
ROOT_DIR_WATCH_HINTSidiom),然后逐行撤销裁决。最贴近树的真相,也是 [finding] check:skill-refs can never be path-named — its declared population is three import specifiers, none of which is a repo path #12310/finding: dispatch-gates never names check:objectql-double-limit for an edited test file — the same whole-corpus shape as its listed sibling check:where-matcher #12322 要的。⚠️ 但要动约 12 个门禁脚本,远超任何单卡的申报面。并且声明并不会把行从 sweep 里清掉 ——covered()探的是hintCovers(h, '<root>/probe.file'),对全部 17 种拼法都是 false,所以每行仍需单独撤销裁决。 - B. 新增第四个词表值(如
SPELLABLE-UNDECLARED),记录「存在精确的活拼法;声明被推迟,理由如下」,17 行全部迁入。单文件、在申报面内、不改推导。⚠️ 风险:新值烂成 [finding] the INVISIBLE half of the bare-root species is ~33 gates, not 4 — a population literal with no separator never reaches the hint set at all #10840 点名拒绝过的 allowlist —— 除非--self-test把每条记录拼法的存活性与精确度按hintCovers的话术 pin 住,这是可机械化的,也正是 fix(pm): reprice the runner-env-posture bare-root reason on the real mechanism #12330 已经为 runner-env-posture 那行落过的形状。 - C. 裁定「活的但未命名的拼法仍不值得声明」,保留
REFUSE-UNSPELLABLE,重新定价 17 条理由。改动最小。⚠️ 但②说明它让裁决的名字变假。 - D. 拆分:15 行(无记录消费者)走 B 作词表底座,2 行(有实测下游拉力:[finding] check:skill-refs can never be path-named — its declared population is three import specifiers, none of which is a repo path #12310 →
check:skill-refs,finding: dispatch-gates never names check:objectql-double-limit for an edited test file — the same whole-corpus shape as its listed sibling check:where-matcher #12322 →check:objectql-double-limit)走 A。⚠️ 需要为那两个门禁脚本另开写手位,并需一条「在 shrink-only 的 map 上重决 17 行是被授权的」裁定。
dev 的建议是 D,四维推理如上。⛔ 本座位不背书任何一个 —— 记录在此供裁决。
为什么现在必须决,不能顺手做掉
TRIAGE 是 shrink-only 的 map,而重决 17 行(全表的三分之一)是一次重新裁决,不是一次修复。#12328 的派发令、#12289 本身、以及该文件自己的 docblock 三处都把它保留给上层。dev 遇到边界就停了 —— 这是正确动作。
Refs: #12328(被拆分的卡,现 blocked) · #12289(其 row 2 属于本类) · #12300(使裁决基础失效的变更) · #12330(单行先例) · #12347(可达性半边) · #12329(存活的对照集) · #12310 · #12322(两处实测下游拉力) · #10840(B 必须避开的 allowlist 形状)
Activity
os-steve commentedon Aug 26, 2026
Maintainer ruling recorded — D: split by measured pull
Provenance: maintainer, 2026-08-26, live PM chat (decision-inbox batch 2, session
session_01JANH3y7qe3MD8aLaLXci8N), verbatim: 「同意」 — accepting the presented recommendation D, with the two conditions presented alongside it now binding:SPELLABLE-UNDECLARED: "a precise live spelling exists; declaration deferred, reason recorded"), and every such record MUST carry a--self-testpin of the recorded spelling's liveness and precision inhintCovers's own terms (the fix(pm): reprice the runner-env-posture bare-root reason on the real mechanism #12330 shape). ⛔ An unpinned vocabulary value is the [finding] the INVISIBLE half of the bare-root species is ~33 gates, not 4 — a population literal with no separator never reaches the hint set at all #10840 allowlist rot and rejects the whole option — no pin, no D.check:skill-refs← [finding] check:skill-refs can never be path-named — its declared population is three import specifiers, none of which is a repo path #12310;check:objectql-double-limit← finding: dispatch-gates never names check:objectql-double-limit for an edited test file — the same whole-corpus shape as its listed sibling check:where-matcher #12322) get precise spellings declared beside their population constants (ROOT_DIR_WATCH_HINTSidiom) and their verdicts individually withdrawn.TRIAGEmap is authorized — the definitional basis moved under them (fix(pm): match a hint whose glob sits in a non-final segment #12300), so this is bringing rulings back to truth, not loosening the map.C is rejected on the presented ground (the verdict's name must not become a lie); A-for-all-17 is rejected as pre-demand expansion.
State:
needs-user-decision→pm:queue(domain:devxstands; the devx seat prices the second-writer mechanics for the two gate scripts at dispatch).Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
Claim:devx@objectstack seat (#6023) · sessionsession_01UjM2ia8Av1v5NqfqQEQmC6· R10Branch
claude/issue-12369-spellable-undeclared· dedicated worktree · model tier: standard.Declared file surface:
scripts/pm/bare-root-worklist.mjs(itsTRIAGEmap +--self-test), plus the two gate scripts named in ruling condition 2. ⛔ NOTscripts/pm/dispatch-gates.mjs— #12371 holds that writer slot this round.Zone 1 — rulings (scope, not facts; not renegotiable)
SPELLABLE-UNDECLARED: "a precise live spelling exists; declaration deferred, reason recorded").--self-testpin of the recorded spelling's liveness AND precision inhintCovers' own terms (the fix(pm): reprice the runner-env-posture bare-root reason on the real mechanism #12330 shape). No pin, no D — an unpinned vocabulary value is the [finding] the INVISIBLE half of the bare-root species is ~33 gates, not 4 — a population literal with no separator never reaches the hint set at all #10840 allowlist rot and the ruling rejects the whole option on it.check:skill-refs← [finding] check:skill-refs can never be path-named — its declared population is three import specifiers, none of which is a repo path #12310;check:objectql-double-limit← finding: dispatch-gates never names check:objectql-double-limit for an edited test file — the same whole-corpus shape as its listed sibling check:where-matcher #12322) get precise spellings declared beside their population constants (ROOT_DIR_WATCH_HINTSidiom), verdicts individually withdrawn.TRIAGEmap is authorized — the definitional basis moved under them (fix(pm): match a hint whose glob sits in a non-final segment #12300), so this is bringing rulings back to truth, not loosening the map. That refusal no longer applies to you.premise_still_valid: false.Zone 2 — assumptions; falsify before building on them
a.⚠️ One row in this card's own table is already stale, recorded by triage at 02:52Z: #12392 / PR #12423 (⚠️ The 17/26/12/1 split was measured before PR #12386 and PR #12435 landed in
69d0e18) merged, socheck-skills-token-ratchet.mjsnow walks every published skill directory recursively — 49 files / 38 carrying ceilings / 11 generator-owned. Soskills/*/SKILL.md — 11/11no longer describes that population (now 11 of 49, ~22%), and under A the declared spelling for that gate would be the recursiveskills/*/**. Re-measure the whole table on post-mergemain.b.
dispatch-gates.mjs. Re-derive the counts; ⛔ drive them throughhintCovers, nevercollapseHintalone.c. I assume the two pull-bearing rows are still the only two with a recorded consumer. Check #12310 and #12322 are still open and still ask for it.
Zone 3 — suggested route (not binding)
Re-measure the table first — it may change which rows fall in which half → add the vocabulary value with its mandatory pins → move the 15 → declare + withdraw for the 2. ⛔ If the two gate scripts turn out to need a writer slot you cannot hold cleanly, report rather than reaching.
Ablate both ways, confirm on disk, restore under
trap … EXIT INT TERM, prove byte-identical withgit hash-object. Gate union at the final commit; re-derive onSTALE TREE. Exit codes before any pipe.Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
{ "issue": 12369, "status": "done", "branch": "claude/issue-12369-spellable-undeclared", "pr": "https://github.com/objectstack-ai/objectstack/pull/12476", "premise_still_valid": true, "summary": "Re-measured all 17 REFUSE-UNSPELLABLE rows on post-merge main (6899 tracked files) through the real hintCovers + trackedFiles and each gate's own exported walk; every one of those refusals was false of the tree, so the 17-row class and the 15/2 split by measured pull both hold. Added the fourth vocabulary value SPELLABLE-UNDECLARED for the 15 rows with no recorded consumer, each carrying a machine-checkable `spelling` that --self-test pins LIVE, PRECISE and COMPLETE in hintCovers' own terms (ruling condition 1 — the pin is what licenses the value; ablated in all three directions). Declared precise spellings beside the population constants of the 2 pull-bearing gates under the ROOT_DIR_WATCH_HINTS idiom and withdrew their refusals to DECLARED-NARROWER: check:objectql-double-limit's derivation reach goes 0 -> 2755 of its test corpus (#12322), check:skill-refs 9 -> 12 of its reference files (#12310). No row added: 39 verdicts before, 39 after; the split moved 1/12/26/0 -> 3/12/9/15. Zone 2a CONFIRMED and materially larger than a number change: #12392 (PR #12423, 69d0e18) made check-skills-token-ratchet walk skill dirs recursively, so its row's population, spelling AND deferral reason are all new (49 files, precise spelling `skills/*/**` at 49/49, deferred because those 49 are 49 of the root's 50 — the REFUSE-WIDE trade landing on a row that is no longer unspellable). Zone 2b re-derived. Zone 2c verified: #12310 and #12322 both still open and still asking.", "tests": "All at final commit 3195fff5a0 (union re-derived after the last commit via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, no paths passed — identical to the pre-commit pass, no gates added). Exit codes captured before any pipe; verdicts quoted from each gate's own line. GREEN: check:nul-bytes · bare-root-worklist --self-test (\"46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing, none contradicted. 17 record(s) carry a spelling and every one of 9 distinct spelling(s) is pinned LIVE, PRECISE and COMPLETE against the tracked corpus in hintCovers' own terms.\") · check:pm-dispatch-gates (703 cases) · check:objectql-double-limit + its --self-test · check:skill-refs (\"9 generated files in sync\") · check:slot-lookup · check:entry-guard · check:cli-command-ids · check:agent-test-spelling · check:bash32-floor · check:parse-guard · check:pnpm-filter-targets · check:merge-driver · check:pm-governed-merges · check:published-files · check:test-source-alias · check:type-source-resolution · check:page-declaration-shape · check:cross-package-test-inputs · check:objectui-changeset · spec check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs · check-ci-filter-parity · check-comment-mask-adoption · check-plugin-teardown-shape · check-affected-docs · check-drift-comment. ABLATION — no rebuild leg applies and that is stated rather than skipped: all three edited files are .mjs/.ts read directly from source by their runners, nothing resolves through dist/, so there is no build step whose omission could produce the silent-green ablation. Every leg confirmed ON DISK by anchor-count 1->0 plus injected-text presence plus the file's git hash-object (never an editor exit code), and every leg restored under `trap ... EXIT INT TERM` with byte-identity re-proved by hash-object against the baseline. RED as predicted: PRECISE (holds narrowed below the hint -> 'OVER-NAMES 23: packages/cli/package.json ...'); LIVE (spelling pointed at a non-existent config -> 'reaches 0 tracked file(s)', and COMPLETE fired alongside with 'MISSES 3'); no-pin (spelling stripped from one record -> both the must-carry-a-spelling and the unused-SPELLINGS-entry assertions); declaration re-rooted away from SCAN_ROOT -> RED; declared tail widened past the walk -> RED. Declarations proven load-bearing: removing skill-refs' -> reach 12->9, removing objectql's -> reach 2755->0. ONE LEG WAS A NULL ABLATION AND IS REPORTED AS ONE, not as a pass: widening the `skill entrypoints` claim to any depth stayed GREEN; measured why rather than assumed (every tracked SKILL.md sits at depth 3, so the widened claim names the same 11 files and the tree holds no specimen that could move it) — COMPLETE's ability to fire is carried by the LIVE leg, which reds it directly. That ablation also caught a PHANTOM PIN in my own first draft: `includes(\"'packages/**/*.test.ts'\")` finds its needle in the assertion's own source text and survives deleting the declaration entirely; the needle is now assembled and re-ablation reds on exactly the computed-declaration form. `pnpm lint`: a MEASURED NARROWING, declared as such — ESLint run over the 3 changed files, with all three pieces of evidence: (1) population read from ESLint's own config, isPathIgnored() false for all three; (2) count read from the API result, 3 linted / 0 errors / 0 warnings; (3) eslint.config.mjs enables no type-aware linting for ANY file (its own measured claim with a positive control at :326-336), so this diff cannot move an untouched file's verdict. NOT GREEN, and not a finding: scripts/check-dev-prereqs.mjs exits 1 with 'The workspace is not built — 1 unmet precondition, not a list of problems' (67/67 packages missing dist/) — the state of any fresh worktree, unrelated to this diff. CHANNEL DEVIATION, declared: the REST channel is unavailable from this seat this session (403 'GitHub access is not enabled for this session' on both reads and the additive label POST, proxy-level not token-level), so the out-of-scope dedup search used ONE narrow MCP list_issues call (labels=[finding], minimal fields) instead of the REST list endpoint, and the skip-changeset label used the declared read->union->whole-set fallback rather than the additive endpoint. Label read back after writing: ['size/l','skip-changeset'] — size/l preserved, nothing stripped.", "open_questions": [], "out_of_scope_findings": [ "filed as #12472: check-cli-command-ids' \"declaration is spelled as a LITERAL\" self-test assertion cannot fail — the needle 'scripts/**' occurs 3x in that file, so computing the declaration away leaves the includes() green; measured, with check-parse-guard.mjs checked and cleared as the contrast (1 occurrence, no pin of this shape). Filed unassigned, no labels, for PM triage; NOT fixed in this PR (third gate, different defect class, outside the declared surface)." ] }Generated by Claude Code
yinlianghui commentedon Aug 26, 2026
os-dev-report
PATCH /issues/comments/{id}route is 403 from this seat this session and the MCP server exposes no update-comment tool — so the fallback the discipline names is used instead: the report re-posted with the marker as literal text on the first line, which survives.{ "issue": 12369, "status": "done", "branch": "claude/issue-12369-spellable-undeclared", "pr": "https://github.com/objectstack-ai/objectstack/pull/12476", "premise_still_valid": true, "summary": "Re-measured all 17 REFUSE-UNSPELLABLE rows on post-merge main (6899 tracked files) through the real hintCovers + trackedFiles and each gate's own exported walk; every one of those refusals was false of the tree, so the 17-row class and the 15/2 split by measured pull both hold. Added the fourth vocabulary value SPELLABLE-UNDECLARED for the 15 rows with no recorded consumer, each carrying a machine-checkable `spelling` that --self-test pins LIVE, PRECISE and COMPLETE in hintCovers' own terms (ruling condition 1 — the pin is what licenses the value; ablated in all three directions). Declared precise spellings beside the population constants of the 2 pull-bearing gates under the ROOT_DIR_WATCH_HINTS idiom and withdrew their refusals to DECLARED-NARROWER: check:objectql-double-limit's derivation reach goes 0 -> 2755 of its test corpus (#12322), check:skill-refs 9 -> 12 of its reference files (#12310). No row added: 39 verdicts before, 39 after; the split moved 1/12/26/0 -> 3/12/9/15. Zone 2a CONFIRMED and materially larger than a number change: #12392 (PR #12423, 69d0e18) made check-skills-token-ratchet walk skill dirs recursively, so its row's population, spelling AND deferral reason are all new (49 files, precise spelling `skills/*/**` at 49/49, deferred because those 49 are 49 of the root's 50 — the REFUSE-WIDE trade landing on a row that is no longer unspellable). Zone 2b re-derived. Zone 2c verified: #12310 and #12322 both still open and still asking.", "tests": "All at final commit 3195fff5a0 (union re-derived after the last commit via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, no paths passed — identical to the pre-commit pass, no gates added). Exit codes captured before any pipe; verdicts quoted from each gate's own line. GREEN: check:nul-bytes · bare-root-worklist --self-test (\"46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing, none contradicted. 17 record(s) carry a spelling and every one of 9 distinct spelling(s) is pinned LIVE, PRECISE and COMPLETE against the tracked corpus in hintCovers' own terms.\") · check:pm-dispatch-gates (703 cases) · check:objectql-double-limit + its --self-test · check:skill-refs (\"9 generated files in sync\") · check:slot-lookup · check:entry-guard · check:cli-command-ids · check:agent-test-spelling · check:bash32-floor · check:parse-guard · check:pnpm-filter-targets · check:merge-driver · check:pm-governed-merges · check:published-files · check:test-source-alias · check:type-source-resolution · check:page-declaration-shape · check:cross-package-test-inputs · check:objectui-changeset · spec check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs · check-ci-filter-parity · check-comment-mask-adoption · check-plugin-teardown-shape · check-affected-docs · check-drift-comment. ABLATION — no rebuild leg applies and that is stated rather than skipped: all three edited files are .mjs/.ts read directly from source by their runners, nothing resolves through dist/, so there is no build step whose omission could produce the silent-green ablation. Every leg confirmed ON DISK by anchor-count 1->0 plus injected-text presence plus the file's git hash-object (never an editor exit code), and every leg restored under `trap ... EXIT INT TERM` with byte-identity re-proved by hash-object against the baseline. RED as predicted: PRECISE (holds narrowed below the hint -> 'OVER-NAMES 23: packages/cli/package.json ...'); LIVE (spelling pointed at a non-existent config -> 'reaches 0 tracked file(s)', and COMPLETE fired alongside with 'MISSES 3'); no-pin (spelling stripped from one record -> both the must-carry-a-spelling and the unused-SPELLINGS-entry assertions); declaration re-rooted away from SCAN_ROOT -> RED; declared tail widened past the walk -> RED. Declarations proven load-bearing: removing skill-refs' -> reach 12->9, removing objectql's -> reach 2755->0. ONE LEG WAS A NULL ABLATION AND IS REPORTED AS ONE, not as a pass: widening the `skill entrypoints` claim to any depth stayed GREEN; measured why rather than assumed (every tracked SKILL.md sits at depth 3, so the widened claim names the same 11 files and the tree holds no specimen that could move it) — COMPLETE's ability to fire is carried by the LIVE leg, which reds it directly. That ablation also caught a PHANTOM PIN in my own first draft: `includes(\"'packages/**/*.test.ts'\")` finds its needle in the assertion's own source text and survives deleting the declaration entirely; the needle is now assembled and re-ablation reds on exactly the computed-declaration form. `pnpm lint`: a MEASURED NARROWING, declared as such — ESLint run over the 3 changed files, with all three pieces of evidence: (1) population read from ESLint's own config, isPathIgnored() false for all three; (2) count read from the API result, 3 linted / 0 errors / 0 warnings; (3) eslint.config.mjs enables no type-aware linting for ANY file (its own measured claim with a positive control at :326-336), so this diff cannot move an untouched file's verdict. NOT GREEN, and not a finding: scripts/check-dev-prereqs.mjs exits 1 with 'The workspace is not built — 1 unmet precondition, not a list of problems' (67/67 packages missing dist/) — the state of any fresh worktree, unrelated to this diff. CHANNEL DEVIATION, declared: the REST channel is unavailable from this seat this session (403 'GitHub access is not enabled for this session' on both reads and the additive label POST, proxy-level not token-level), so the out-of-scope dedup search used ONE narrow MCP list_issues call (labels=[finding], minimal fields) instead of the REST list endpoint, and the skip-changeset label used the declared read->union->whole-set fallback rather than the additive endpoint. Label read back after writing: ['size/l','skip-changeset'] — size/l preserved, nothing stripped.", "open_questions": [], "out_of_scope_findings": [ "filed as #12472: check-cli-command-ids' \"declaration is spelled as a LITERAL\" self-test assertion cannot fail — the needle 'scripts/**' occurs 3x in that file, so computing the declaration away leaves the includes() green; measured, with check-parse-guard.mjs checked and cleared as the contrast (1 occurrence, no pin of this shape). Filed unassigned, no labels, for PM triage; NOT fixed in this PR (third gate, different defect class, outside the declared surface)." ] }Generated by Claude Code