Repository navigation
Commit ebed73b
fix(cli)!: docs/nav-target judges each package body's navigationContributions on a multi-package artifact (#27394)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuVpEmthvkRbuHGjC6oSQZvd)_
Fixes #27343
Clause-②: no (narrowing) — on a multi-package `composeStacks(…, {
manifest: 'preserve' })` artifact, a `type: 'doc'` item in a package
body's `navigationContributions`, whose `doc` or `book` names nothing
the artifact carries, built clean before and is now refused by `os
build`, `os validate` and `os lint` (`docs/nav-target`).
Metadata-protocol: narrowed
Skills-docs: none — `skills/objectstack-ui/rules/pages.md` (the `type:
'doc'` nav section) and `content/docs/ui/apps.mdx` (the
`docs/nav-target` paragraph) already teach the refusal with no
single-package limit; this change makes contributions on multi-package
artifacts meet that text.
## 维护者速读
- **改了什么**:`os build`/`os validate`/`os lint`
的文档导航检查(`docs/nav-target`)在多包产物上改为逐包读取各包自己的 `navigationContributions`(只改
`packages/cli/src/utils/collect-docs.ts` 的 `lintDocNavTargets` 一处读取)。
- **为什么改**:多包 `preserve` 产物的顶层 manifest
只是"最后一个包"(主包)的副本,其他包贡献到别的应用里的文档导航项从没被检查过,指向不存在文档的条目会静默通过构建、到运行时才坏。
-
**风险与回滚**:一次收窄(BREAKING,`minor`):多包产物里"非主包贡献的导航项指向不存在文档"从静默通过变为构建错误;单包应用不变,主包的贡献只判一次不重复报。回滚即
revert 本 PR。
- **先看**:`packages/cli/src/utils/collect-docs.ts` 中 `lintDocNavTargets`
的 `contributions`
一行;`packages/cli/src/utils/collect-docs.nav-target.test.ts`
新用例;`.changeset/27343-doc-nav-target-package-contributions.md`。
- **两分钟验证**:`pnpm --filter @objectstack/cli exec vitest run
--maxWorkers=2 src/utils/collect-docs.nav-target.test.ts`,预期 10 个用例全部通过。
- **需要你做的**:无,席位落地。
## What changes
`lintDocNavTargets` read `navigationContributions` off the artifact's
top-level manifest only. Measured on a real two-package `preserve`
composition (`composeStacks([svc, crm], { manifest: 'preserve' })`):
| Who contributes | Top-level `manifest.navigationContributions` |
`packages[i].manifest.navigationContributions` | Issues before |
|:--|:--|:--|:--|
| first package (`svc`, non-owner) | absent | `packages[0]` only | 0 |
| last package (`crm`, owner) | present (the owner's copy) |
`packages[1]` | 1 |
The contributions now come from each package body when the stack carries
`packages[]`, otherwise from the top-level manifest. The walk is the
same `artifactPackages(stack)` the function already uses for books and
apps; no second walker.
**Why not "top level when present, else bodies" (the card's wording).**
Falsified by the table: when the owner contributes, the top level
carries its copy, so that rule would still skip every other package's
contributions. Reading the top level as well as the bodies would judge
the owner's twice. The rule here is the one `collectNavGroupInputs`
(`packages/cli/src/utils/nav-contribution-groups.ts`) already applies to
the same key for the same reason.
## Tests
One new case in `collect-docs.nav-target.test.ts`, on the real composer:
- the top-level manifest carries no `navigationContributions` when only
the non-owner contributes;
- a resolving doc passes;
- a dead doc in the non-owner's contribution is refused at
`apps/setup/navigation/nav_svc_help`;
- when both packages contribute a dead doc, exactly two issues come
back, one per package (the owner's top-level copy is not judged a second
time).
## Acceptance notes
- **Narrowed population.** A hand-written artifact that carries
`packages[]` and puts contributions on its top-level manifest that no
body carries is no longer judged there. The runtime registers each
`packages[]` entry's own manifest (ADR-0130 D4), so such a top-level
copy is not what gets registered; `collectNavGroupInputs` makes the same
call.
- **Declaration lines** follow the dispatch: `Clause-②: no (narrowing)`
and `Metadata-protocol: narrowed`, so the changeset is `minor` with a
BREAKING banner and the ADR-0087 `not-required
(no-migration-prescription)` disposition. The claim comment carries the
bare `Clause-②: no`; the narrowing arm is from triage and the dispatch.
- No in-tree example's `navigationContributions` carries a `type: 'doc'`
item (the dogfood battery below is green).
## Verification
Final head `6c5250cbd` (origin/main `28872e68d` merged in). The unit
tier, typecheck and the first gate run are at `260fe77e0`, before that
merge; the merge touches none of the three changed files. The dogfood
battery, the gate re-run and the target test re-run are at `6c5250cbd`.
<details><summary>Red, then green, then ablation</summary>
- Before the source change, the new case: `Tests 1 failed | 9 passed
(10)`, `AssertionError: expected [] to deeply equal [ [
'docs/nav-target', …(1) ] ]`.
- After it: `Tests 10 passed (10)`.
- Ablation at `91e14b22` (fix committed first), through
`scripts/ablation-replace.mjs`: anchor `const contributions =
(bodies.length > 0` replaced with `const contributions = (false` (the
old top-level-only read). Anchor x1 to x0, replacement x0 to x1, blob
`f64208d1cd06` to `edd969ecd1bb`. Run: the new case fails with the same
`expected [] …` assertion, `Tests 1 failed | 9 passed (10)`. Restore:
blob equals HEAD (`f64208d1cd06`), `git diff HEAD` empty. The test
imports `./collect-docs.js` from source, so no `dist/` rebuild is in the
path.
</details>
<details><summary>Package checks</summary>
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2` (under the verify lock, `VERDICT command-exit 0`): `Test
Files 346 passed (346)`, `Tests 5033 passed (5033)`. The integration
tier is left to CI.
- `pnpm --filter @objectstack/cli build && pnpm --filter
@objectstack/cli typecheck`: `VERDICT command-exit 0`;
`check:test-typecheck: OK`.
- eslint with the repo config over `packages/cli` (`eslint packages/cli
--no-inline-config`): exit 0.
</details>
<details><summary>Derived gates</summary>
- First run at `260fe77e0`: `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` derived 60 families on 3
changed paths (51 changed lines). All 60 ran, exit codes captured before
any pipe; 57 exit 0. Three exit 3 on a prerequisite:
`check-plugin-teardown-shape --self-test` (its pinned fixture commit was
not in the shallow clone; exit 0, `48 cases pass` after fetching that
commit), `check:i18n-coverage` (`@objectstack/connector-slack` had no
build; exit 0 after building it) and `check:dual-build-cjs-loads` (needs
a full workspace build).
- Re-run at `6c5250cbd` after the dogfood closure build: the same 60
families (derivation unchanged), all 60 exit 0,
`check:dual-build-cjs-loads` included. `check-adr-0087-registration
--base origin/main` exit 0: the changeset reads
`BREAKING+bang+clause-②-narrowing+metadata-protocol-narrowed`,
`not-required (no-migration-prescription)`. `check:i18n-coverage`: `OK
(14 config(s), 527 baselined untranslated string(s), none new)`.
- `dispatch-gates --ran` at `6c5250cbd`: `60 derived famil(ies)
accounted for — 60 run, 0 NOT-MEASURED (a DERIVED zero — all 60 recorded
an exit code and none of them is 3)`.
- Target test re-run at `6c5250cbd`: `Tests 10 passed (10)`.
</details>
<details><summary>Dogfood battery (owed by the narrowing arm)</summary>
At `6c5250cbd`, under the verify lock, after building the
`@objectstack/dogfood` dependency closure, `pnpm --filter
@objectstack/dogfood test` with no shard and no path: `Test Files 348
passed | 3 skipped (351)`, `Tests 2806 passed | 20 skipped (2826)`, lock
`VERDICT command-exit 0`. No failures.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgXcN9HaZYSEUdJWEjw)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgXcN9HaZYSEUdJWEjw
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9f76037 commit ebed73b
3 files changed
Lines changed: 49 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
107 | 126 | | |
108 | 127 | | |
109 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1110 | 1110 | | |
1111 | 1111 | | |
1112 | 1112 | | |
1113 | | - | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
1114 | 1116 | | |
1115 | 1117 | | |
1116 | 1118 | | |
| |||
1194 | 1196 | | |
1195 | 1197 | | |
1196 | 1198 | | |
1197 | | - | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
| 1207 | + | |
1198 | 1208 | | |
1199 | 1209 | | |
1200 | 1210 | | |
| |||
0 commit comments