gate(checklist): bind the platform-checklist corpus to the ONE symbol resolver (#16898) - #18100
Conversation
… resolver (#16898) `scripts/check-platform-checklist.mjs` resolved symbol anchors with a rule of its own -- mask comments, then ask whether the bare token appears anywhere in the file -- in the place `scripts/symbol-anchors.mjs` states the #13556 ruling it implements: "One resolver." The private rule was the LOOSER of the two, so it resolved anchors the shared resolver refuses and the checklist reported coverage it did not have; a second resolver that is greener than the shared one is never the one anybody points at. `absentAnchorSegments` now delegates every segment to `symbol-anchors.mjs#symbolResolutionClass` and holds no matcher at all. The per-segment split stays -- that is this corpus's anchor grammar, not a rule about what "present" means -- and a self-test assertion reads this file's own source to pin the body against re-forking. Withdrawing the permissive match is the deliverable, not a regression: 56 of 633 anchor occurrences stopped resolving (577 resolve -- 516 declaration, 61 literal). All 56 are carried in `SHARED_RESOLVER_RESIDUAL`, one closed row per (family file, anchor), each with the shape that used to resolve it and a verdict: 47 bad-citation, 8 accept-set. Every row must fire (a stale row reds), an unlisted failure is an ordinary red, and a grow-never ceiling sits beside it -- so the ledger is a closed list that shrinks by repair, never a rule. No floor is lowered and the maintainer-only baseline is untouched. `scripts/symbol-anchors.mjs` is untouched too: widening the shared core to keep this ledger green would export the looser reading to the ADR, `scripts/**`, spec-docblock and system-context corpora. Two self-test assertions that pinned the permissive behaviour are RE-JUDGED in place with the reason recorded, never deleted: P3 (an inline object-literal member) and P5 (a `.json` value). The directions they really covered are kept as P3b and P5b on shapes the shared rule accepts. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
|
PM 复核:收下,已 undraft + 武装。 三个 open question 逐条判,其中第二条本席不采纳你的建议,并写明为什么。 本席自己验过的(⛔ 不是核对报告)
⇒ 确实改走了共享解析器,⛔ 共享核心未放宽、⛔ 地板一格未降。
&& !/new RegExp|RegExp\(|\.test\(|maskComments|indexOf\(/.test(ownResolverBody)⇒ 它断言的是解析器函数体内没有匹配器,而你那句话正是按这个作用域说的。本席拿一个全文件计数去对一句作用域限定的陈述 —— 本班第 11 次「计数不是阅读」,又是本席的探针。 ⭐⭐ 账本双向精确,而那条自检把理由写在了行上
⇒ ⭐ 这一句就是「为什么 55 行残留是安全的」的全部论证。 没有那个上限,这个账本本身就是那条宽松规则,只不过换成数据拼写。加上「未列失败 ⇒ ABSENT SYMBOL 红」「陈旧行 ⇒ STALE RESIDUAL ROW 红」,行只能靠修复离开。 而四条消融腿把三条边逐个证了,其中 Leg A 最见功力:把解析器体改回宽松匹配后,正面控制 P3 P5 N1 N2 N3 N5 全挂,而 N4 保持沉默 —— 因为宽松规则也接受声明与整 token。⇒ ⭐ 你不只报了哪些对照开火,还报了哪一条对照在这次分叉下无能为力,并说清为什么。 一个「全绿」的对照集合里混着一条永远不会响的,和没有那条一样危险。 判决Q1 —— 判 A:规则级绑定就位,注册缺口另立卡裁决要的那件昂贵的事(「the expensive, subtle part is not the sweep, it is the RESOLUTION RULE … two copies drift, and they drift SILENTLY」)已经满足。剩下的是语法缺口,不是漂移:共享 ⇒ ⛔ 不在本卡放宽共享核心(那会把缺陷出口到另外四个语料)。⭐ 而必须立卡的理由是你给的那条: Q2 —— ⛔ 不采纳你的 A,判一张卡,人口在卡上声明,切片由承接者选已立 #18104。你建议「一区一卡,共 12 张」;本席判一张,理由写在卡里也写在这儿:预先切成十二张近似卡,会把「谁拥有这件事」这个问题本身切碎,还要走十二次分诊 —— 而真正需要存在的是一个被声明的人口 + 一个所有者(本席在 #17797 上立的正是这条:先声明人口,再切)。⇒ 卡里明写允许按区文件分次落地,每片是 ⭐ 而你反对 C 的那句本席原样引进了卡里:「a residual with no owner is how a closed ledger turns back into an allow-list.」 Q3 —— 判 A,⛔ 不是 B;而你反对 B 的理由本身就是裁决
你写的:把 authority 行窄读成「只管降低地板」从而够到它 —— 「is the move the line exists to stop.」 ⇒ ⭐ 这句话本身就是本席的裁断。⛔ 本卡不动它,⛔ #18104 的承接者也不动它;它作为维护者独有的一行记进了 #18104,以免只活在某个会话的记忆里。 你的两条 out-of-scope,判如所报
Generated by Claude Code |
立卡已办 —— 注册缺口卡 #18107本席在 Q1 判 A 时承诺「另立注册缺口卡,以 ⭐ 立卡过程里多测出来一条,⛔ 不在本 PR 的范围内、也⛔ 不要求本 PR 动它 —— 但它把「注册缺口」从制度问题变成了已经发生的漂移:
局部集是共享集的真子集,而本门对集合外的扩展名不是跳过、是
⇒ 本 PR 的评审结论不变:已武装,等队列。 Generated by Claude Code |
Clause-②: no
Closes #16898
scripts/symbol-anchors.mjsstates the #13556 ruling it implements in its own header, verbatim: "One resolver." The platform-checklist corpus resolved symbol anchors with a rule of its own — mask comments, then ask whether the bare token appears anywhere in the file — in exactly the place that ruling says there is to be one implementation. It was the looser of the two, which is the dangerous direction: a second resolver that is greener than the shared one is never the resolver anybody points at, so the drift is only ever found by census.What changed
check-platform-checklist.mjs#absentAnchorSegmentsnow hands every segment toscripts/symbol-anchors.mjs#symbolResolutionClassand holds no matcher at all — no regex, nomaskComments, nothing. The per-segment split of a dotted anchor stays, because that is this corpus's anchor grammar (the shared#symbolcarries no dot), not a rule about what "present" means. A self-test assertion (N5) reads this file's own source to pin the body against re-forking.⛔
scripts/symbol-anchors.mjsis untouched. Widening the shared core to keep this ledger green would export the looser reading to the ADR,scripts/**, spec-docblock and system-context corpora — the exact failure the ruling exists to prevent.⛔ No floor is lowered, and the maintainer-only
scripts/checklist-symbol-anchor-baseline.jsonis not edited at all.The measurement — anchors ceasing to resolve IS the deliverable
declaration, 61literal(family file, anchor)pairs, 54 distinct anchors)That 56 is coverage this checklist was reporting and did not have. It is carried in
SHARED_RESOLVER_RESIDUAL, one row per(family file, anchor), each with the shape that used to resolve it and a verdict — not absorbed by a rule and not absorbed by a lowered floor.The residual is a closed ledger, not an allow-list with a shape:
STALE RESIDUAL ROWred that names it and asks for its deletion, so the ledger cannot outlive the defect it records;ABSENT SYMBOLred — new bad citations cannot join silently;The floor population is now
resolved + residualand the gate's own output prints both halves, so the coverage claim is legible rather than folded:(578/634 rather than 577/633 because the README rewrite below adds one anchor, which resolves.)
Every broken anchor, classified
Acceptance 2.
bad-citation= the cited file does not declare the symbol; the repair is in the ledger (re-point the anchor, or drop to a bare citation).accept-set= the anchor names something real the shared accept set does not reach ⇒ a different card againstscripts/symbol-anchors.mjs, and ⛔ nothing here widens it.verdict:
bad-citation— 47 rowsstring-substring(29) — symbol survives only INSIDE a longer string tokenexamples/app-showcase/src/data/seed/index.ts#Specimenareas/records-forms.jsonpackages/mcp/src/plugin.ts#OS_MCP_SERVER_ENABLEDareas/ai.jsonpackages/objectql/src/overlay-precedence.test.ts#not_overridableareas/studio-authoring.jsonpackages/platform-objects/src/identity/sys-oauth-application.object.ts#OAuthareas/identity-auth.jsonpackages/plugins/plugin-auth/src/auth-plugin.ts#Providersareas/platform-core.jsonpackages/plugins/plugin-auth/src/auth-route-ledger.ts#bootstrapStatusareas/identity-auth.jsonpackages/plugins/plugin-auth/src/auth-route-ledger.ts#linkSocialareas/identity-auth.jsonpackages/plugins/plugin-auth/src/auth-route-ledger.ts#revokeOthersareas/identity-auth.jsonpackages/plugins/plugin-auth/src/auth-route-ledger.ts#sendVerificationEmailareas/identity-auth.jsonpackages/plugins/plugin-auth/src/auth-route-ledger.ts#setActiveareas/identity-auth.jsonpackages/plugins/plugin-auth/src/auth-route-ledger.ts#updateUserareas/identity-auth.jsonpackages/qa/dogfood/test/membership-role-vocabulary.dogfood.test.ts#PermissionSetareas/identity-auth.jsonpackages/qa/dogfood/test/package-first-authoring.dogfood.test.ts#writable_package_requiredareas/platform-core.jsonpackages/rest/src/meta-write-actor-identity.test.ts#Actorareas/studio-authoring.jsonpackages/rest/src/rest-route-ledger.ts#describeDelegableScopeareas/identity-auth.jsonpackages/rest/src/rest-route-ledger.ts#getHistoryareas/studio-authoring.jsonpackages/rest/src/rest-route-ledger.ts#jobIdareas/records-forms.jsonpackages/rest/src/rest-route-ledger.ts#RESTareas/api-backend.jsonpackages/rest/src/rest-route-ledger.ts#RESTareas/studio-authoring.jsonpackages/rest/src/rest-route-ledger.ts#saveItemareas/access-security.jsonpackages/rest/src/rest-route-ledger.ts#shareIdareas/access-security.jsonpackages/runtime/src/route-ledger.ts#commitIdareas/platform-core.jsonpackages/runtime/src/route-ledger.ts#getLegalNextStatesareas/api-backend.jsonpackages/runtime/src/route-ledger.ts#getRuntimeStatusareas/automation.jsonpackages/runtime/src/route-ledger.ts#getScreenareas/automation.jsonpackages/runtime/src/route-ledger.ts#runIdareas/automation.jsonpackages/spec/src/data/object.zod.ts#FEEDS_DISABLEDareas/records-forms.jsonpackages/spec/src/data/object.zod.ts#queryareas/records-forms.jsonpackages/triggers/trigger-api/src/trigger-api-route-ledger.ts#flowNameareas/api-backend.jsonimport-only(9) — cited file IMPORTS the symbol; the declaration is elsewhereexamples/app-showcase/objectstack.config.ts#ConnectorRestPluginareas/automation.jsonexamples/app-showcase/src/security/seed-approval-demo.ts#AUDITOR_DEMO_USERareas/approvals.jsonexamples/app-showcase/src/security/seed-approval-demo.ts#PHONE_DEMO_USERareas/identity-auth.jsonpackages/cli/src/commands/compile.ts#emitJsonareas/cli.jsonpackages/cli/src/commands/doctor-deprecation-hint-commands.test.ts#Doctorareas/cli.jsonpackages/cli/src/utils/format.exit-code.test.ts#emitJsonareas/cli.jsonpackages/platform-objects/src/identity/sys-member.object.ts#BUILTIN_MEMBERSHIP_ROLE_OPTIONSareas/identity-auth.jsonpackages/runtime/src/domains/ai.ts#capabilityUnavailableareas/ai.jsonpackages/services/service-messaging/src/messaging-service.ts#PreferenceResolverareas/integration-system.jsonjson-value-not-key(3) — JSON target carries it as a VALUE; the shared rule reads KEYSpackages/create-objectstack/src/templates/blank/package.json#objectstackareas/cli.jsonpackages/spec/liveness/email_template.json#requireVarsareas/integration-system.jsonpackages/spec/liveness/field.json#liveareas/attachments-storage.jsonregex-literal(1) — symbol survives only inside a regex literalpackages/verify/src/verify.ts#VALIDATION_FAILEDareas/cli.jsondetector-artifact(1) — not a citation at all: THIS gate's detector truncated an item-id at its first hyphendocs/qa/platform-checklist/areas/access-security.json#accessareas/identity-auth.jsonmember-access(3) — symbol survives only asx.symbolon another objectpackages/objectql/src/engine.ts#objectExtensionsareas/platform-core.jsonpackages/plugins/plugin-security/src/security-plugin.ts#__referentialFieldClearareas/identity-auth.jsonpackages/runtime/src/domains/notifications.ts#markReadareas/platform-core.jsonlocal-binding(1) — a parameter name (plus a hyphenated string sharing the token)packages/lint/src/validate-action-locations.ts#actionareas/records-forms.jsonverdict:
accept-set— 8 rowsinline-key(6) — a real object-literal key, written INLINE rather than at the start of a lineexamples/app-showcase/objectstack.config.ts#declarativeStdioareas/integration-system.jsonexamples/app-showcase/src/data/objects/business-unit.object.ts#allowCreateareas/records-forms.jsonexamples/app-showcase/src/data/seed/index.ts#sales_regionareas/dashboards.jsonexamples/app-showcase/src/data/seed/index.ts#signed_onareas/dashboards.jsonexamples/app-showcase/src/system/datasources/showcase-external.datasource.ts#onMismatchareas/integration-system.jsonexamples/app-showcase/src/ui/actions/index.ts#maxSizeareas/records-forms.jsondotted-string-head(2) — a DATA identifier that is the HEAD SEGMENT of a dotted string tokenpackages/spec/src/kernel/public-auth-features.ts#sys_invitationareas/identity-auth.jsonpackages/spec/src/kernel/public-auth-features.ts#sys_userareas/identity-auth.json⭐ The single sharpest row is the
detector-artifact:docs/qa/platform-checklist/areas/access-security.json#access. There is no such anchor — this gate's own detector truncated an item-id reference (…json#access-security.scope-depth-asymmetry) at its first hyphen, and the permissive rule then "resolved" the phantom#accessagainst the spellingaccess-securitysitting in the file. A looser second resolver does not merely miss rot; it manufactures green for citations that were never written.Does the third corpus diverge? — No. (Acceptance 3, with the reading)
The card counted three registered corpora when it was filed. On
maintoday there are four, and the fourth arrived after the card (#17241,6aa1d09043). All four were read:scripts/check-adr-symbol-anchors.mjsdefineCorpus+sweepCorpusscripts/check-scripts-symbol-anchors.mjsdefineCorpus+sweepCorpusscripts/check-spec-docblock-symbol-anchors.mjsdefineCorpus+sweepCorpusscripts/check-system-context-census.mjsdefineCorpus+sweepCorpussymbolResolutionClassdirectly for its ownNON_READ_ANCHORSledger rows, i.e. delegation, not a forkMechanical readings behind that table:
grep -ln "defineCorpus({" scripts/*.mjs⇒ the four gates above plusscripts/symbol-anchors.mjs(the definition).grep -ln "symbolResolutionClass" scripts/*.mjs⇒check-system-context-census.mjs,isystem-census.mjs,symbol-anchors.mjs— all consumers, no re-implementation.function …Symbol…/…Resolve…/absent…/declares…definitions beyond theirasserthelpers).⇒ The shape of the fix is unchanged. This is "one corpus went its own way", not "the shared resolver was never really adopted" — the only divergence in the tree was
check-platform-checklist.mjs, and this PR closes it.defineCorpusregistration — its anchors live in JSON string values rather than markdown code spans, and its#symbolmay be dotted where the shared grammar's may not. Full registration would need the shared core widened on both counts, which is precisely what this card forbids doing here. Recorded below as an unsettled question for the PM.Positive control (Acceptance 4)
⛔ Without it, "bound correctly" and "everything broke" are indistinguishable. Three independent readings, all on this branch:
declaration, 61 asliteral. A binding that simply refused everything would have reported 0.N4asserts a declaration (export function declaredHere) and a complete quoted token ('whole_token') both still resolve, in the same fixture whereN1/N2/N3assert that a call site, a local parameter and a substring of a longer string token do not.P3,P5,N1,N2,N3,N5— and leavesN4silent, because the permissive rule also accepts a declaration and a whole token. That is exactly whyN4alone cannot detect a fork and why it has to sit besideN1–N3rather than instead of them.Ablation — four legs, each with an on-disk mutation proof and a hash-settled restore
⭐ ⛔ No restore relies on
trap(measured to silently not fire in this container, #17875). Every restore isgit checkout HEAD -- PATH— never bare — settled bygit hash-objectequality against theHEADblob and an emptygit diff HEAD, never by an exit code. Every mutation is proved to have reached disk by a before/aftergrep -con the exact anchored text and a blob-hash change, before any verdict is read.HEADblob forscripts/check-platform-checklist.mjs:7be4b5ddef7f08f3fc02cc1ffe86626f80ace9c4.1 → 0, injected text×1, blob7be4b5d… → b1a9d29…P3 P5 N1 N2 N3 N5—N4silent…/plugin.ts#OS_MCP_SERVER_ENABLED)1 → 0, blob changedABSENT SYMBOLnaming that anchor — an unlisted failure still reds×1, blob changedD3) — the ledger cannot grow1 → 0, new×1, blob changedSTALE RESIDUAL ROW+ 1 ×ABSENT SYMBOL— a row that stops firing is a redAll four restores printed
RESTORE: SETTLED — blob == 7be4b5ddef7f08f3fc02cc1ffe86626f80ace9c4 AND git diff HEAD empty. Tree verified clean afterwards; ⛔ no ablation artefact is committed.Tests pinning the permissive behaviour — re-judged in place, ⛔ never deleted
Two self-test assertions asserted a resolution only the permissive rule could produce. Both are kept, with the verdict inverted and the reason recorded in a comment block above them, because deleting them would leave the tree unable to say the narrowing ever happened. Each keeps the direction it really covered, on a shape the shared rule does accept:
P3RESOLVED.moderesolves —{ mode: 'strict' }written INLINEP3b, on a member declared at the start of a lineP5.jsonvalue resolves, because JSON was scanned unmasked.jsonvalue is ABSENT; the shared rule reads JSON keysP5b, which resolves a key while a//-carrying string value sits beside itP3's shape is not incidental — six of the eightaccept-setresidual rows are that same inline-object-literal-key shape, soP3now pins the very narrowness the follow-up card is about.New assertions:
P3b,P5b,N1–N5(the binding and its positive control),D1–D4(the residual ledger, both directions). TheBATTERY_SYMBOL_ANCHORSfloor is ratcheted29 → 40; the file's self-test total is176 → 187.Reverse-read: which existing sentence does this make false?
Sentences carrying a rev or a date are history and were left alone; what rots is a bare present-tense claim. Both directions were checked.
Made false — fixed in this PR (1):
docs/qa/platform-checklist/README.md, the author-facing statement of the rule. Three claims went false at once: "A symbol inside a string literal counts as present" (now: a complete quoted token only), "This is presence-in-code, not a declaration analysis" (it now is one, plus the literal class), and the framing thatcheck:platform-checklistdecides the question at all. Rewritten to state that the rule belongs to the shared module, to name what an author actually gets (comments stripped, declaration, complete quoted token — and nothing else), and to say that an anchor the core refuses is either a bad citation or a card against that module.Made false — reported, ⛔ NOT edited here (1):
scripts/checklist-symbol-anchor-baseline.json, its$comment: "Each entry is the count of anchors that RESOLVED in that family file." An entry is nowresolved + residual. ⛔ Not edited: [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 states that file is maintainer-only by its own$authorityline, and the safe reading of "maintainer-only" is the whole file, not just the numbers. No floor is changed either way. The gate's own--anchor-censusoutput now says the per-file counts are the FLOOR population and not a coverage figure, so the honest reading is available from the gate even while the stale sentence stands. ⇒ a one-line maintainer edit, not this PR's to make.Reverse direction — a sentence that was false and is now closer to true, ⛔ NOT edited (1):
scripts/symbol-anchors.mjs, its ruling block: "platform-checklist step (2): re-author the 1,647 source citations as symbol anchors and add a resolver that reds on an absent symbol #13788 … reuses THIS module by registering a corpus." That was simply false before this PR — the checklist reused nothing. It is now half true: the module's resolution rule is reused; adefineCorpusregistration still does not exist, for the grammar reasons above. Left for the maintainer along with the follow-up card, since qualifying a quoted ruling is their call and the remaining gap is exactly that card's subject.Zeros, reported as zeros: no ADR sentence, no
content/docs/**page and no other gate header states the checklist's resolution rule —grepoverdocs/qa/platform-checklist,scripts/checklist-symbol-anchor-baseline.jsonand.github/workflowsreturned the three items above and nothing else. No test outsidecheck-platform-checklist.mjsasserts on this rule.Verification
node scripts/check-platform-checklist.mjs⇒ exit 0, line quoted above.node scripts/check-platform-checklist.mjs --self-test⇒ exit 0, 187 assertions.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, change set taken from git, not hand-listed): 34 families derived, 34 run, 0 NOT-MEASURED, 0 UNRUN, reconciled with recorded exit codes via--ran. All 34 exit 0 — includingcheck:scripts-symbol-anchors(this file's own comment prose is in that corpus),check:comment-mask-corpus,check:nul-bytes,check:self-test-wiredandcheck:declaration-mirrors. One family neededturbo run build --filter=@objectstack/formula --filter=@objectstack/lintfirst and reported exit 3 (PREREQUISITE NOT MET, nothing measured) until it had it; it is green on the built tree.grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over both changed files ⇒ no hits.Changeset — measured, then judged:
skip-changesetNothing published moves. Measured rather than asserted: 82
package.jsonfiles enumerated, 70 publishable (non-private), and no publishable package'sfiles[]reachesscripts/ordocs/qa/. Positive control on the same enumeration: 70 of those 70files[]blocks do mentiondist, so the scan was reading real arrays and not an empty set. ⇒ theskip-changesetlabel is applied to this PR (a label, not a body sentence).Follow-ups this PR deliberately does not do
bad-citationrows. [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 says in as many words that how the findings land "is a sizing question this card does not answer", and floors have almost no headroom, so dropping a#symbolis not available and each row needs a re-point with judgement. Rows leave the ledger one repair at a time.accept-setrows — an inline object-literal key (6) and the head segment of a dotted string token such assys_userin'sys_user.actions.invite_user'(2). That is a card againstscripts/symbol-anchors.mjs, weighed against all five corpora, ⛔ never a local widening.defineCorpus, which the ruling actually asks for and which needs the shared grammar to reach dotted symbols and non-code-span anchors.symbol-anchorscannot resolve anoverridemember — a true symbol anchor to one is reportedunresolved-symbol, and the remedy is a weaker anchor #16821's accept set is out of scope per triage ("correctly fenced") and was not touched.Generated by Claude Code