Repository navigation
Commit 9602f30
Fixes #19246
## Verdict: the card does **not** reproduce
#19246 was filed NOT MEASURED, on a static reading taken twice
independently (review flag D of `5747199582`, and the #18431 dev): that
a multi-package artifact's **top-level-only** `docs` register
**nowhere** at boot. This round booted it. They register.
Both static readings were correct about the two places they looked, and
wrong about the conclusion drawn from them:
- `resolveArtifactPackageOrder`
(`packages/core/src/artifact-packages.ts`) does return package
**bodies** only when `packages` is present — confirmed, unchanged.
- `packages/runtime/src/app-plugin.ts` does not register
`collections.docs` — confirmed, its only `docs` mention is the
`APP_CATEGORY_KEYS` presence list.
Neither file is the registrar. The registrar is
**`MetadataPlugin._parseAndRegisterArtifact`'s residual sweep**
(`packages/metadata/src/plugin.ts`), which runs after the package walk
and registers every top-level `(type, name)` slot that no package body
claimed, stamped with the artifact's own manifest identity. Neither
reading reached it.
## The measurement
**Fixture** — the card's own e2e multi fixture
(`packages/cli/test/build-package-docs-attachment.e2e.test.ts`,
replicated outside that claimed directory), two packages with different
namespaces, a flat `src/docs/pkgdocs_index.md` and a package-directory
`src/orders/docs/ord_playbook.md`, each carrying a distinct marker
string written into exactly one file on disk.
**`os build`** emitted the shape under test:
```
top-level docs: ['pkgdocs_index'] # THE SUBJECT
packages[com.example.pkgdocs.core]: []
packages[com.example.pkgdocs.orders]: ['ord_playbook'] # THE LIT CONTROL
```
**Boot** — `os dev --fresh -p PORT` on that artifact, real process, real
kernel (29 plugins), better-sqlite3 driver, seeded dev admin.
**Query** — `GET /api/v1/meta/doc` as an **authenticated** caller (both
docs' effective audience is `org`, so an anonymous caller is filtered to
`[]` by design — that empty answer is a fake zero, not a reading):
```
{"type":"doc","items":[
{"name":"ord_playbook","label":"Orders Playbook","_packageId":"com.example.pkgdocs.orders",...},
{"name":"pkgdocs_index","label":"Index","_packageId":"com.example.pkgdocs.core","_packageVersion":"1.0.0",...},
{"name":"setup_overview","label":"Setup overview","_packageId":"com.objectstack.setup",...}
]}
```
With `?include=content`, pedigree holds in both directions:
`pkgdocs_index` carries `MARKER-flat-doc-18431` (written only into
`src/docs/pkgdocs_index.md`) and `ord_playbook` carries
`MARKER-package-doc-18431` (written only into
`src/orders/docs/ord_playbook.md`). Neither item is an echo of the
other, and a third doc from the platform's own `com.objectstack.setup`
package rode along as an unplanned second control.
**Instrument absence arm** — the same fixture rebuilt with `src/docs/`
deleted, booted on a second port and queried identically, returns
`ord_playbook` and `setup_overview` and **not** `pkgdocs_index`. The
query can report a missing doc, so the presence above is a reading
rather than a shape that always answers yes.
**The boot says it out loud.** The residual sweep is not silent — it
warns, naming the count, the owner it fell back to and a remedy:
> `[MetadataPlugin] artifact 'ARTIFACT_PATH' carries 1 top-level
metadata item(s) that none of its 2 package bodies declare. They were
registered under the artifact's own manifest id
('com.example.pkgdocs.core') because no package in the artifact claims
them, so every door will report that id as their owner. Rebuild the
artifact so each collection it ships is carried by the package that owns
it.`
That remedy was checked rather than assumed: moving
`src/docs/pkgdocs_index.md` to `src/core/docs/` and rebuilding puts the
doc on the core body (`2 collected (2 from 2 package directories)`) and
the top-level `docs` key disappears from the artifact. The warning is
actionable.
## What this PR lands
One regression pin, in
`packages/metadata/src/plugin-artifact-packages-attribution.test.ts` —
the existing home for this door's attribution pins, whose `load()`
harness drives the real `_parseAndRegisterArtifact`. Four cases: a
premise guard on the fixture shape, the measurement (lit control
asserted first, in the same load, with marker pedigree on both), the
absence arm, and the warning.
No production code changes. **`packages/cli/src/utils/collect-docs.ts`
was not touched** — the fence held, and nothing the measurement showed
needed it.
The fixture's docs half is positioned by hand and the test header says
why: `composeStacks(…, { manifest: 'preserve' })` is not the producer
for this one key — `os build`'s docs collector places docs after
composition, per #18431's ruling — so composing a `docs`-carrying stack
would produce the additive both-places shape, which is precisely not the
shape under test. The placement is the one read off the real build
above.
## Reverse verification
Both legs run from the committed state, with the mutation proved on disk
by anchor/injection counts and blob hash, and restored byte-identically
(`git hash-object` back to the HEAD blob, `git diff HEAD` empty).
| leg | mutation | result |
|:---|:---|:---|
| baseline | none | 11 passed / 11 |
| narrow ablation | the residual sweep's guard `if (carriesPackages) {`
forced false, package walk untouched | 3 failed / 11 — `registers BOTH
…`, `says out loud …`, and the pre-existing `keeps a top-level item that
NO package body declares` |
The narrow leg is the discriminating one. The new `registers BOTH` case
goes red while the **absence arm stays green** (with the sweep off, the
doc-absent world is unchanged) and the control `ord_playbook` keeps
registering through the package walk — so the pin fails on the subject,
not on the harness. A blunter first leg (replacing the call itself,
which throws) took 8 of 11 down and is recorded here only as the reason
the narrow one was run.
## Local verification
- `pnpm --filter @objectstack/metadata exec vitest run --maxWorkers=2
src/plugin-artifact-packages-attribution.test.ts` — **11 passed (11)**,
at `2cc69a66`.
- Gate families derived with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` against this tree (1
changed path, +130/-0) and run: **50 of 51 green**.
- `pnpm check:dual-build-cjs-loads` answered `PREREQUISITE NOT MET —
this gate reads built output, and some package has no dist/` (13
packages unbuilt in this worktree). **NOT MEASURED**, not a failure and
not a pass; it needs a whole-repo `pnpm build`, and this diff is one
test file that changes no `dist/`.
- Whole-farm scans (`pnpm lint` and the CI-shell job steps
`dispatch-gates` lists as unmeasurable locally) are CI's run, not this
branch's.
## Changeset
`skip-changeset`. Measured, not assumed: `@objectstack/metadata` ships
`files: ["dist","README.md","CHANGELOG.md"]`; a symbol unique to the new
test (`pkgdocs_index`) has **0** hits across those paths, while the
positive control — a string from `plugin.ts` that does ship — has **4**.
A `src/*.test.ts` file publishes nothing.
Clause-②: no
## Acceptance notes
- **The #18431 shape makes the residual-sweep warning routine, by
design.** Every multi-package project that keeps a flat `src/docs/`
boots with that warning, because #18431 deliberately keeps those docs at
the top level while the compiler puts package-directory docs on bodies.
The warning is correct and its remedy works (measured above), but the
platform's own emitter is what produces the shape it warns about. Noted,
not filed: the behaviour is right, the docs register, and the next
reader now finds a pin. The card itself names who meets it — the
clause-5 acceptance for #18431 on hotcrm, which keeps a flat `src/docs/`
today; that acceptance will see the warning and should not read it as a
defect.
- The card's closing note is accurate and unaddressed here: the option-B
reader ledger excludes `docs` as "filesystem input"
(`packages/runtime/src/option-b-reader-probe.ts`), which is why this
path carried no pin. Whether that exclusion should move is that ledger's
card, not this one. `#18431 is not addressed here`.
## 维护者速读(草稿)
**改了什么** — 只加了一个测试。没有改任何生产代码。
**为什么改** — #19246 是一张「没人实测过」的卡:两位评审各自静态读代码,都得出「多包制品的顶层 docs
在启动时哪里都没注册」。这一轮真的把它跑起来了:用卡里点名的 e2e fixture 编译出制品、启动真实服务、带登录态查
`/api/v1/meta/doc`,顶层那篇 doc **在**,并且同一次查询里那篇「一定会注册」的分包 doc
作为对照也在。两位评审看的两个文件都没错,只是注册它的既不是那两个文件,而是 `packages/metadata` 里的 residual
sweep。所以卡的结论被推翻,这个 PR 把这次测量钉成回归测试,免得下一个人得出第三次静态结论。
**风险与代价(含回滚)** — 风险接近零:新增的是测试文件,不进任何 npm 包(已实测 `files[]` 里零命中)。回滚就是
revert 这一个提交。代价是往一个已有测试文件里加了约 130 行。
**席位意见** — (留空)
**你要做的** — 确认一件事就够了:#19246 应当以「不复现」关闭,而不是继续当 bug 排期。另外 #18431 在 hotcrm
上的 clause-5 验收会看到一条启动 warning(顶层 docs 无包认领),那是设计如此、且有可执行的补救,不是缺陷。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QCdUBjM47SxioST9z5Zwdf)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 61dd96f commit 9602f30
1 file changed
Lines changed: 130 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
47 | 61 | | |
48 | 62 | | |
49 | 63 | | |
| |||
318 | 332 | | |
319 | 333 | | |
320 | 334 | | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
0 commit comments