Repository navigation
fix(cli)!: the census masker sees past nested templates and regex literals; docs/nav-target judges package-body apps - #27342
Conversation
…d regex literals codeOnly scanned quotes linearly, so a template literal nested in another's interpolation, or a quote inside a regex literal, flipped its parity and it blanked the code after it. A template's interpolations are now code at any depth and regex bodies are masked; fixtures pin both, red on the old masker. Claude-Session: https://claude.ai/code/session_01Utp6CXc5hequf76uJfzrdK Co-authored-by: Claude <noreply@anthropic.com>
The fixed census masker saw `stack.apps` in lintDocNavTargets, hidden until now behind a nested template literal. On a multi-package preserve artifact the top level carries no apps, so a dead doc or book target in a package's app was never judged. Walk the top level's apps when present, otherwise each package body's, and classify the read with a top-level census row. Claude-Session: https://claude.ai/code/session_01Utp6CXc5hequf76uJfzrdK Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin efe566ae7edd54c62ab666aabf14bbfa7f53ff44 && git checkout efe566ae7edd54c62ab666aabf14bbfa7f53ff44
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8eeb3b3b9255686046a361cf9f9145d6b8e6bfec 1139c31f460731decb0dc8419a9b6d9e531067ea && git checkout -B drift-repro 8eeb3b3b9255686046a361cf9f9145d6b8e6bfec && git merge --no-ff 1139c31f460731decb0dc8419a9b6d9e531067ea
node scripts/docs-audit/affected-docs.mjs --json 8eeb3b3b9255686046a361cf9f9145d6b8e6bfec |
…a narrowing The seat ruled the newly reached refusal a Clause-② narrowing, following the docs/metadata-embed-ref precedent: breaking title, the shape that built before and is now refused, the ADR-0087 not-required disposition, patch. Claude-Session: https://claude.ai/code/session_01Utp6CXc5hequf76uJfzrdK Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#3 · os-bill · session_01Utp6CXc5hequf76uJfzrdK 席位核对 — ACCEPT ·
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Triage seat, taking Generated by Claude Code |
Contract reviewServed-tier: Inputs read: card #27210 body and all 4 comments (triage ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentThe narrowing, read against the published spec on
Implemented-by: VERDICT: PASS Generated by Claude Code |
domain:server#3 · os-bill · session_01Utp6CXc5hequf76uJfzrdK
Requested by Bill · project thread
Fixes #27210
Clause-②: yes (narrowing) — on a multi-package
composeStacks(…, { manifest: 'preserve' })artifact, atype: 'doc'navigation item inside an app a package body carries, whosedocorbooknames nothing the artifact carries, built clean before and is now refused byos build,os validateandos lint(docs/nav-target).Metadata-protocol: none
Skills-docs: none —
skills/objectstack-ui/rules/pages.md(thetype: 'doc'nav section) andcontent/docs/ui/apps.mdx(thedocs/nav-targetparagraph) already teach the refusal with no single-package limit; this change makes multi-package artifacts meet that text.维护者速读
normalized-call-sites的代码遮罩codeOnly:嵌套模板字符串和正则字面量不再让它"走丢位置";修好后普查新看见一处读取stack.apps(os build/os validate/os lint的文档导航检查),顺手把它改为在多包产物上读各包自己的 apps,并补一条普查分类行。preserve产物顶层没有 apps,所以包里 app 的文档导航项(指向不存在的 doc/book)从没被检查过。docs/nav-target构建错误(单包应用不变;仓内示例零命中;dogfood 全绿)。回滚即 revert 本 PR。packages/cli/test/normalized-call-sites.test.ts的codeOnly;packages/cli/src/utils/collect-docs.ts中lintDocNavTargets的apps一行;.changeset/27210-doc-nav-target-package-apps.md。pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/normalized-call-sites.test.ts src/utils/collect-docs.nav-target.test.ts,预期全部通过。What changes
The census masker (
codeOnly, test only). It scanned quotes linearly, so two shapes made it lose its place and blank real code after them:collect-docs.ts).The masker now tracks template interpolations with a per-hole brace count, so the code inside an interpolation is code at any depth. It also recognises a regex literal by the code character before its
/and masks the regex body. Template text, string bodies, comments and regex bodies are blanked; offsets, quotes and newlines are kept, so the bracket-read path still reads keys back from the source. Both header paragraphs that stated the old bound ("a site inside a template literal's interpolation is blanked too") now state the new one.What the fixed masker sees on the real tree (origin/main
ebfadb02). Exactly one new read,utils/collect-docs.ts :: stack.appsinlintDocNavTargets. A nested template literal hid it. The card's four reads are classified as follows:ebfadb02stack.objects,stack.flows,stack.permissionscollection(key)over the doc owner plus its declared dependencies inlintMetadataEmbeds.stack.bookstop-levelrow (added by #27204). Itswhynow names the measured cause.stack.apps(new)top-levelrow, see below.The one source change (
collect-docs.ts, declared).lintDocNavTargetswalked only the top level'sapps. A multi-packagecomposeStacks(…, { manifest: 'preserve' })artifact keeps its apps in the package bodies, so atype: 'doc'nav item in a package's app was never judged. It now walks the top level's apps when the stack carries the key, otherwise each package body's (artifactPackages(stack), the same walk the function already uses for books). That isresolveStackCollection's rule. The core helper itself is not used because its whole-entry parse throws on a duplicate-id artifact, andcollect-docs.flat-docs-owner.test.tspins thatcollectAndLintDocsdoes not throw on that shape. The census row forstack.appsistop-level, with that reason. Single-package stacks are unchanged. No in-tree example carries atype: 'doc'nav item.Tests
normalized-call-sites.test.ts: two new cases. The first covers a nested template followed by a package-owned read, reads inside interpolations (with braces, strings and a nested template), a regex literal holding quotes and a backtick, a division that must not read as a regex, and a quoted${. The second checks the exact masked output of a nested template and a regex.collect-docs.nav-target.test.ts: one new case on a real two-packagepreservecomposition. It asserts that the top level carries noapps, that a resolving doc passes, and that a dead doc target in the package body's app is refused at its path.Acceptance notes
collect-docs.ts(39fd5fbf), a masker that fixes nested templates alone still hides all four reads. The regex literal is what flipped the parity there. Templates plus regex reveals all four, and alsostack.apps. On origin/main, the nested template alone hidesstack.apps.packages/cli/srcfiles: 0 files differ. The origin/main masker differs on 140 files, mostly the interpolations it blanked by design. A templates-only fix still differs on 12 files, all at regex literals. This was a one-time scratch comparison; no permanent test file was added.fix(cli)!title, theClause-②: yes (narrowing)line, the BREAKING banner and the ADR-0087not-required (no-migration-prescription)disposition, atpatch.check-adr-0087-registrationreads it asBREAKING+bang+clause-②-narrowing.lintDocNavTargetsreadsnavigationContributionsoff the top-levelmanifestonly. On apreserveartifact, a package that does not own the artifact manifest carries its contributions in its body, and a dead doc target there is not judged. Measured: a single-package stack returns oneapps/setup/navigation/nav_helpissue; the same contribution on the non-owner package of a two-packagepreservecomposition returns none. The census cannot see this read (a member chain off an envelope key). Reported to the seat.Verification
Test readings are at head
47f1a12a; the dogfood battery and the gate re-run are at head1139c31f(the changeset declaration only, on top of47f1a12a), both on origin/mainebfadb02.Red, then green: the masker
Old masker with the new cases, at
ebfadb02plus the uncommitted cases:Tests 2 failed | 11 passed (13).keeps its place past a nested template literal and a regex literal:expected [] to deeply equal [ 'stack.objects' ]masks a nested template exactly: the received output shows the interpolation blanked.The census's own "no read is unclassified" stayed green over the old masker: that is the false green.
New masker: the masker cases pass, and the census goes red with exactly one unclassified read,
utils/collect-docs.ts :: stack.apps.Red, then green: docs/nav-target
Before the source change, the new case gave
Tests 1 failed | 8 passed (9), withexpected [] to deeply equal [ [ 'docs/nav-target', … ] ].After it:
normalized-call-sites.test.tsplussrc/utils/collect-docs*gaveTest Files 5 passed (5) · Tests 126 passed (126).Package and repo checks
pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2:Test Files 342 passed,Tests 4961 passed | 29 skipped. Two pin files failed only becausepackages/cliwas not built (./dist/index.js is absent). Afterpnpm --filter @objectstack/cli build, both pass:Test Files 2 passed (2) · Tests 29 passed (29). The integration tier is left to CI.pnpm --filter @objectstack/cli typecheck: exit 0, andcheck:test-typecheckOK.pnpm lint: exit 0.node scripts/pm/dispatch-gates.mjs --commands: 61 families derived. All were run. 60 exit 0.pnpm check:dual-build-cjs-loadsexits 3 with PREREQUISITE NOT MET (it needs a full workspace build), so it is NOT MEASURED locally and left to CI.check:i18n,check:i18n-coverageandcheck:i18n-walk-parityexit 0 after the cli build.check:registry-log-declaredexits 0 on a rerun; its first run hit an ENOENT on a temp file the concurrent cli unit run was writing.dispatch-gates --ran:61 derived famil(ies) accounted for — 60 run, 1 NOT-MEASURED.1139c31f: 61 derived, 60 exit 0,check:dual-build-cjs-loadsexit 3 (NOT MEASURED, left to CI);--ran:60 run, 1 NOT-MEASURED, 0 UNRUN.check-adr-0087-registration --base origin/main: exit 0, one declared-breaking changeset carrying its disposition. Census plussrc/utils/collect-docs*:Test Files 5 passed (5) · Tests 126 passed (126).Dogfood battery (owed by the narrowing arm)
At
1139c31f, under the verify lock, after building the@objectstack/dogfooddependency closure,pnpm --filter @objectstack/dogfood testwith no shard and no path:Test Files 348 passed | 3 skipped (351),Tests 2806 passed | 20 skipped (2826), lockVERDICT command-exit 0. No failures.Generated by Claude Code