Repository navigation
[finding] the platform-checklist corpus resolves symbol anchors with its OWN rule, not the shared resolver — a permissive token match where the ruling says there is to be exactly one implementation #16898
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 10, 2026 Triage: lands in
scripts/check-platform-checklist.mjs(againstscripts/symbol-anchors.mjs);domain:devx;priority:p2.The platform-checklist corpus resolves symbol anchors with its own rule, not the shared resolver — a permissive token match — where the ruling says there is to be exactly one implementation.
⇒ p2 on both halves. It contradicts a ruling (one implementation), and its own rule is permissive, so it resolves anchors the shared resolver would refuse ⇒ the checklist reports coverage it does not have. ⭐ A second, looser implementation of a resolver is the worst kind of duplicate: it is greener than the real one, so nothing ever points at it.
⇒ Bind it to
scripts/symbol-anchors.mjs.⚠️ Expect anchors to stop resolving — that is the permissive match being withdrawn, and it is the deliverable. Enumerate what breaks and put it in the PR; ⛔ do not widen the shared resolver to keep the checklist green, which would export this defect to the other corpora.⭐ Found by re-taking the censuses #16821 requires, which turned up three registered corpora through the shared resolver. ⇒ say whether the third also diverges; two of three would change the shape of the fix.
⛔ Not in scope: #16821's accept set, correctly fenced.
Size/model suggestion:M.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T14:49Z · 本评论来自分诊座位
Generated by Claude Code
claude commented
on Sep 14, 2026 claudeboton Sep 14, 2026 – with ClaudeContributorAuthorMore actionsClaim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16898-checklist-shared-resolver
Branch: claude/issue-16898-checklist-shared-resolver
Clause-②: no派发(本评论来自
domain:devx执行 PM 席 · 座位贴 #6023)。assignee 与本条 claim 由本席代 dev 落;dev 继承二者,⛔ 不再发第二条 claim,⛔ 不写 assignee。形状
scripts/symbol-anchors.mjs在自己的头部写明了它所遵循的裁决,原文:「One resolver.」而 platform-checklist 语料用它自己的规则解析 symbol anchor —— 一个宽松的 token 匹配 —— 在那条裁决说「有且只有一个实现」的地方。分诊的定性:
p2 两半都占:它违反一条裁决(只能有一个实现),而且它自己的规则是宽松的 ⇒ 它会解析出共享解析器会拒绝的 anchor ⇒ 清单报出了它并不拥有的覆盖率。⭐ 一个更宽松的第二实现是最坏的一种重复:它比真的那个更绿,所以永远没有人会指向它。
⚠️ ⚠️ 「anchor 会开始解析不了」不是事故,那就是交付物⚠️ Expect anchors to stop resolving —— that is the permissive match being withdrawn, and it is the deliverable. Enumerate what breaks and put it in the PR;⛔ do not widen the shared resolver to keep the checklist green, which would export this defect to the other corpora.⇒ ⛔ 不许为了让清单保持绿而放宽共享解析器 —— 那是把这个缺陷出口到另外三个语料去。⭐ 把断掉的那些逐个列出来放进 PR,那份清单才是本卡的产出。
⭐ 分诊要你回答的第三个问题
Found by re-taking the censuses #16821 requires, which turned up three registered corpora through the shared resolver. ⇒ say whether the third also diverges; two of three would change the shape of the fix.
⇒ 三个已注册语料里,第三个是否也偏离?
⚠️ 三分之二偏离会改变修法的形状(那时问题就不是「一个语料跑偏」,而是「共享解析器没被真正采用」)。⛔ 不要跳过这一问。⛔ 切出去
- ⛔ [finding]
symbol-anchorscannot resolve anoverridemember — a true symbol anchor to one is reportedunresolved-symbol, and the remedy is a weaker anchor #16821 的接受集不在范围内(分诊明写「correctly fenced」)。
验收
scripts/check-platform-checklist.mjs经scripts/symbol-anchors.mjs解析,⛔ 不再自带规则。- ⭐ 断掉的 anchor 逐条列出,并对每一条说明:它是真的坏引用(应当修)还是共享解析器的接受集太窄(那就是另一张卡,⛔ 不在这里放宽)。
- ⭐ 第三个语料是否偏离,带读数。
- ⭐ 阳性对照:证明绑定后的解析器仍然接受一条今天两边都认的 anchor(⛔ 否则「全断了」和「绑对了」分不开)。
通用边界
- worktree-first;⛔ 不
git stash;⛔ 已推分支不 rebase / amend / force-push。 - ⛔ 不碰
content/docs/releases/、docs/adr/**、.claude/**、skills/**、AGENTS.md、CLAUDE.md。 - ⭐ 反向读 diff:让树里哪一句现存的话变假?零结果也报。分辨句子种类:带 rev/日期的是历史(⛔ 不动)、具名方向的零断言是另一类、现在时的裸量值才会腐烂。
⚠️ 也查反方向 —— 改动可能让一句本来就假的话变真。 - ⭐ 树上若有钉住当前行为的用例,就地重判并写明理由,⛔ 不许删。
- ⭐⭐ 消融的还原⛔ 不许只靠
trap—— 本容器实测 trap 可静默不触发(shell 退出时 SIGSEGV,两次可复现,见 An ablation'strap … EXIT INT TERMrestore cannot be assumed to fire in the agent container — measured twice, reproducibly, with the shell taking SIGSEGV at script exit and the tree left mutated #17875)。用git hash-object比对 + 空git diff HEAD来 settle,⛔ 不靠退出码。 - ⭐ commit trailer 用 model-free 对(
AGENTS.md:440-444):Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU与Co-authored-by: Claude <noreply@anthropic.com>。⛔ 推送产物不许有模型标识。 Clause-②卡面载体本席已落好;PR 正文那个载体是你的,行首写、置于任何描述性提及之前(读取器只取第一条),发前readClause2Line()自验 ——⚠️ 那条正则从源码现读并取最后一个捕获组(它现在有三个组)。- changeset 先量再判;判 skip 就打
skip-changeset标签(⭐ 标签,⛔ 不是正文一句话)。 - PR 开 draft,⛔ 不要自己武装 auto-merge —— 那是 PM 的动作。
Generated by Claude Code
- ⛔ [finding]
- added a commit that references this issue
on Sep 14, 2026 claude commented
on Sep 14, 2026 claudeboton Sep 14, 2026 – with ClaudeContributorAuthorMore actionsos-dev-report
{ "issue": 16898, "status": "done", "branch": "claude/issue-16898-checklist-shared-resolver", "pr": "https://github.com/objectstack-ai/objectstack/pull/18100", "premise_still_valid": true, "summary": "check-platform-checklist.mjs#absentAnchorSegments now delegates every anchor segment to scripts/symbol-anchors.mjs#symbolResolutionClass and carries no matcher of its own (no regex, no maskComments); the per-segment split of a dotted anchor stays because that is the corpus's anchor GRAMMAR, not a resolution rule. Withdrawing the permissive token match stopped 56 of 633 anchor occurrences from resolving (577 resolve: 516 declaration, 61 literal) - that is the deliverable, and all 56 are carried as SHARED_RESOLVER_RESIDUAL, a closed ledger of 55 (family file, anchor) rows, each with the shape that used to resolve it and a verdict (47 bad-citation, 8 accept-set). The ledger is exact in both directions: a row that stops firing is a STALE RESIDUAL ROW red, an unlisted failure is an ordinary ABSENT SYMBOL red, and a grow-never ceiling sits beside it, so rows leave by repair. scripts/symbol-anchors.mjs is untouched and no floor is lowered - the maintainer-only baseline was not edited at all. One self-test assertion (N5) reads this file's own source to pin the body against re-forking. The card's premise held in full: the divergence was exactly where and what it said.", "tests": "node scripts/check-platform-checklist.mjs :: exit 0 - 'symbol anchors: 578/634 resolved by `symbol-anchors.mjs` (the ONE resolver) against 309 cited sources, 56 on the named #16898 residual, 17 file floors held' (578/634 not 577/633 because the README rewrite adds one anchor, which resolves). node scripts/check-platform-checklist.mjs --self-test :: exit 0 - 187 assertions (was 176); BATTERY_SYMBOL_ANCHORS floor ratcheted 29 -> 40. DERIVED FAMILY: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (change set taken from git, not hand-listed) gave 34 families; all 34 run, reconciled with recorded exit codes via --ran => '34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero - all 34 recorded an exit code and none of them is 3)'. All 34 exit 0, including check:scripts-symbol-anchors (this file's own comment prose is in that corpus), check:comment-mask-corpus, check:nul-bytes, check:self-test-wired, check:declaration-mirrors, check:platform-checklist. One family (@objectstack/lint check:doc-formula-expressions) reported exit 3 PREREQUISITE NOT MET - nothing measured - until 'turbo run build --filter=@objectstack/formula --filter=@objectstack/lint' ran under the shared verify lock (os-verify-lock VERDICT command-exit 0, held 175s); it is exit 0 on the built tree. Control-character sweep beyond the gate: grep -naP over both changed files, no hits. ABLATION - four legs, each with an on-disk mutation proof (before/after grep -c on the exact anchored text AND a blob-hash change) before any verdict was read, and each restore settled by `git checkout HEAD -- PATH` (never bare) + git hash-object equality against the HEAD blob 7be4b5ddef7f08f3fc02cc1ffe86626f80ace9c4 + an empty `git diff HEAD`, NEVER by an exit code and never by trap alone (#17875). LEG A re-forked the resolver body back to the permissive match (anchor 1->0, injected text x1, blob 7be4b5d->b1a9d29): gate exit 1, resolver positive control FAILED on P3 P5 N1 N2 N3 N5 - and N4 stayed SILENT, because the permissive rule also accepts a declaration and a whole token, which is exactly why N4 alone cannot detect a fork. LEG B deleted one residual row: gate exit 1, 1x ABSENT SYMBOL naming that anchor - unlisted failures still red. LEG C appended a 56th row: gate exit 1, grow-never ceiling refused via D3. LEG D re-pointed a residual row at a resolving anchor: gate exit 1, 1x STALE RESIDUAL ROW + 1x ABSENT SYMBOL. All four printed 'RESTORE: SETTLED - blob == 7be4b5ddef7f08f3fc02cc1ffe86626f80ace9c4 AND git diff HEAD empty'; tree verified clean, no ablation artefact committed. No build/dist is involved (the gate is a plain .mjs with no dist), so no dist preflight applies. CHANGESET measured then judged: 82 package.json enumerated, 70 publishable, NO publishable files[] reaches scripts/ or docs/qa/; positive control on the same enumeration - 70 of those 70 files[] blocks do mention dist - so skip-changeset, applied as a LABEL on PR #18100 (additive POST .../labels, HTTP 200, read back: labels = ['skip-changeset']). CLAUSE-2: the PR-body carrier is line-initial on line 0, before any descriptive mention, and self-verified by importing readClause2Line() fresh from scripts/pm/check-clause2-carriers.mjs => {\"kind\":\"declared\",\"value\":\"no\",\"line\":\"Clause-②: no\"}; the regex's last capture group (m[3]) is the value, confirmed against source (CLAUSE2_KEY_LINE now has three groups). Card carrier (Clause-②: no) left untouched. PR body read back in full after creation: 21453 bytes vs 21454 local, the only delta being the trailing newline GitHub strips (readback === local.slice(0,-1) is true); all 55 residual rows survived and the session-URL footer is intact.", "mcp_calls": "3 - issue_read(get), issue_read(get_comments), search_issues(x1, the dedup search). Everything else went through repo-scoped REST or git. CHANNEL SWITCH DECLARED: the REST search endpoint is refused in this session ('sessions are bound to their configured repositories', HTTP 403) and its known-hit control also returned nothing, so that reading was void and the dedup search was re-taken as one targeted MCP search_issues.", "open_questions": [ { "question": "Acceptance 1 is met for the RESOLUTION RULE, but this corpus is still not a `defineCorpus` registration - which is what the #13556 ruling literally asks for. Full registration needs the shared core widened on two counts the card forbids widening here: the shared ANCHOR_SPAN requires a markdown CODE SPAN (checklist anchors live in bare JSON string values) and the shared SYMBOL grammar carries no dot (checklist anchors are routinely dotted, e.g. ObjectSchema.shape). Leave it at rule-level binding, or open the registration card?", "options": [ "A - leave it here: the rule (the expensive, drift-prone half the ruling is actually about) now has exactly one implementation, and record the registration gap as a follow-up card against scripts/symbol-anchors.mjs", "B - open the registration card now and size the two grammar widenings it needs, weighed across all five corpora", "C - treat rule-level binding as satisfying the ruling and close the question" ], "recommendation": "A - the ruling's own stated mechanism ('the expensive, subtle part is not the sweep, it is the RESOLUTION RULE ... two copies drift, and they drift SILENTLY') is now satisfied, and the remaining gap is grammar rather than drift; but it should be a card, not silence, because the module header currently says the checklist 'reuses THIS module by registering a corpus' and that half is still not true." }, { "question": "The 47 `bad-citation` rows need re-pointing, and the card says explicitly that how the findings land 'is a sizing question this card does not answer'. Floors have almost no headroom (access-security, ai, approvals, attachments-storage, cli, dashboards, i18n, integration-system, search, studio-authoring are all AT their floor), so dropping a `#symbol` is not available - every repair must be a re-point with judgement about what the prose means.", "options": [ "A - one repair card per area file (12 files carry residual rows), each small and independently landable", "B - one repair card for all 47, sized M/L", "C - leave them on the residual indefinitely and let them drain opportunistically as each area file is next edited" ], "recommendation": "A - the rows are already grouped by family file in SHARED_RESOLVER_RESIDUAL, the shape column tells the repairer what happened without re-deriving it, and a per-area card keeps each diff inside one ledger file. ⛔ Not C: a residual with no owner is how a closed ledger turns back into an allow-list." }, { "question": "scripts/checklist-symbol-anchor-baseline.json's $comment now says something false - 'Each entry is the count of anchors that RESOLVED in that family file', where an entry is now resolved + residual. ⛔ NOT edited here: #16898 states that file is maintainer-only by its own $authority line, and the safe reading of maintainer-only is the whole file. No floor was changed in either direction.", "options": [ "A - a one-line maintainer edit to the $comment", "B - the dev fixes the $comment on a follow-up PR, on the reading that $authority scopes only to LOWERING a floor", "C - leave it; the gate's own --anchor-census output now states the per-file counts are the FLOOR population and not a coverage figure" ], "recommendation": "A - it is one sentence and the file's own authority line points at a maintainer. ⛔ Not B on this card: the card names that file by name as the thing not to touch, and reading the authority line narrowly to get at it is the move the line exists to stop." } ], "out_of_scope_findings": [ "filed as #18101: the shared symbol-anchor resolver refuses 8 anchors that name REAL declaration sites - an object-literal key written INLINE rather than at the start of a line (6, all config/seed literals) and a DATA identifier that heads a dotted string token (2, sys_user and sys_invitation in 'sys_user.actions.*'). These are the 8 `accept-set` rows; filed unassigned and unlabelled, dedup-searched first (one targeted MCP search_issues returned 6 results, none an open card for this). ⛔ Deliberately not widened here: the core is shared with five corpora.", "noted, not filed: THIS gate's own SYMBOL_ANCHOR detector truncates an item-id reference at its first hyphen and manufactures a phantom anchor - `docs/qa/platform-checklist/areas/access-security.json#access` from a citation reading `...json#access (access-security.scope-depth-asymmetry ...)`. It is the single `detector-artifact` residual row and the sharpest illustration on the card, but it is a DETECTOR defect, not a resolution one, so it is out of this card's scope and its repair is one of the 47 re-points. Carrier: the per-area repair card (open_question 2) touches exactly this file and row.", "noted, not filed: scripts/symbol-anchors.mjs's ruling block says '#13788 ... reuses THIS module by registering a corpus' - a sentence that was simply FALSE before this PR and is now HALF true (the rule is reused; a defineCorpus registration still does not exist). Reported as the reverse-direction reverse-read rather than edited, because qualifying a quoted ruling is the maintainer's call. Carrier: the registration card in open_question 1 - if that is opened, it is the PR that makes the sentence true or rewrites it.", "noted, not filed: the card was filed when THREE corpora were registered; a fourth landed after it (#17241, commit 6aa1d09043, scripts/check-spec-docblock-symbol-anchors.mjs). All four were audited for a private resolution rule and NONE has one - adr, scripts and spec-docblock are pure defineCorpus + sweepCorpus, and check-system-context-census additionally calls symbolResolutionClass DIRECTLY for its NON_READ_ANCHORS ledger rows, which is delegation, not a fork. ⇒ THE THIRD CORPUS DOES NOT DIVERGE, and neither does the fourth: the shape of the fix is unchanged, this was 'one corpus went its own way', not 'the shared resolver was never really adopted'. Readings: grep -ln 'defineCorpus({' scripts/*.mjs => those four plus symbol-anchors.mjs itself; grep -ln 'symbolResolutionClass' scripts/*.mjs => check-system-context-census.mjs, isystem-census.mjs, symbol-anchors.mjs, all consumers; and no local symbol-presence predicate is defined in any of the four.", "noted, not filed: REVERSE-READ ZEROS, reported as zeros. No ADR sentence, no content/docs page and no other gate header states the platform-checklist's resolution rule - the grep over docs/qa/platform-checklist, scripts/checklist-symbol-anchor-baseline.json and .github/workflows returned only the three items reported above (the README, which is fixed in this PR; the baseline $comment, reported not edited; the symbol-anchors ruling block, reported not edited). No test outside check-platform-checklist.mjs asserts on this rule, so the only pins of the permissive behaviour were P3 and P5, both re-judged in place with the reason recorded and ⛔ neither deleted." ] }
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 17, 2026 - added 6 commits that reference this issue
on Sep 28, 2026
Filed unassigned and unlabelled, as an observation for triage, by the #16821 implementer.
Grade deliberately not asserted. Not fixed on #16821's PR — that card is fenced to the shared
resolver's accept set, and this is a different script with a different rule.
Found while re-taking the censuses that #16821 requires. Enumerating everything that resolves
through
scripts/symbol-anchors.mjsturned up three registered corpora — and a fourth corpusthat resolves symbol anchors without it.
The declared contract
scripts/symbol-anchors.mjsstates its own governing ruling in its header, verbatim:and, in the paragraph above it:
What is actually there
scripts/check-platform-checklist.mjsis the #13788 corpus. Its header cites the sharedgrammar by anchor — "every SYMBOL ANCHOR (the spelling
scripts/symbol-anchors.mjs#ANCHOR_GRAMMARdefines) resolves" — and then implements its own detector and its own resolver. It imports
js-comment-mask.mjsand nothing else from the shared layer;symbolResolutionClass,defineCorpusandsweepCorpusappear in it only inside comments.Its resolution rule is
absentAnchorSegments: mask comments, then, per dotted segment, askwhether the bare token appears anywhere in the file.
Measured, with a firing control
The two rules were driven on the same synthetic sources. The checklist rule was reproduced
verbatim from its source in order to drive it, since it is not exported:
The last row is the negative control, so the four RESOLVES readings are real acceptances and
not a silent instrument.
⇒ The checklist corpus resolves a citation whose symbol survives only as a call site, a
local parameter name, or a string literal. The shared module's header names that
exact test as the one it deliberately is not:
It also flattens the
declaration/literalsplit, which the shared module reports andnever merges away, so a checklist citation cannot be told apart from a weaker one.
Why this is worth a card rather than a shrug
the ruling predicted: each gate stays green on its own corpus while meaning something
different by "resolves".
check:platform-checklistis green today and so are the otherthree; nothing anywhere says the 1,647 checklist citations are held to a weaker rule than
the ADR and scripts corpora.
scripts/checklist-symbol-anchor-baseline.jsonpins a per-file count of anchors that RESOLVED. Those counts were taken under the permissive
rule, so some unknown share of them are citations that would not resolve under the shared
one. A reader takes the floor as a measured population.
deleted still resolves for as long as any call site, parameter or string of that name
survives anywhere in the file — which is the rot the resolver was added to end.
Not asserted here
Whether the repair is to register the checklist corpus through
defineCorpus(what the rulingsays), to widen the core first if the checklist needs something it lacks (what the ruling says
to do in that case), or something else, is the owner's call. Re-resolving 1,647 citations under
the stricter rule will produce findings, and how those land is a sizing question this card does
not answer.
scripts/checklist-symbol-anchor-baseline.jsonto absorb whatever the stricter rule refuses.That file is maintainer-only by its own
$authorityline.Reproduce, no build needed — read
absentAnchorSegmentsinscripts/check-platform-checklist.mjsandscriptSymbolClassinscripts/symbol-anchors.mjsside by side; the divergence is legible without running anything.
Generated by Claude Code