Skip to content

Commit 9602f30

Browse files
test(metadata): #19246 does not reproduce — a packages[] artifact's top-level-only docs DO register (#19375)
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

File tree

‎packages/metadata/src/plugin-artifact-packages-attribution.test.ts‎

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@
4444
* branch did not move, and this literal is what would go red if it ever did.
4545
* It was recorded on both legs of the ablation in the PR body, from the same
4646
* fixture, and compared byte for byte.
47+
*
48+
* ---------------------------------------------------------------------------
49+
* The second describe block (#19246) — the same door, a different reading
50+
* ---------------------------------------------------------------------------
51+
* #19246 was filed on a STATIC reading, twice and independently: that a
52+
* multi-package artifact's top-level-only `docs` register nowhere at boot.
53+
* Both readings were correct about the two places they looked —
54+
* `resolveArtifactPackageOrder` does walk package bodies only, and
55+
* `AppPlugin` does not register `collections.docs` — and both concluded
56+
* "nowhere" without reaching this door, which is the registrar. Booting the
57+
* #18431 e2e fixture and asking `GET /api/v1/meta/doc` (the live surface)
58+
* showed the doc PRESENT, beside a per-package doc as the lit control. The
59+
* block at the foot of this file is that measurement made permanent, so the
60+
* next reader of those two files finds a pin instead of a third reading.
4761
*/
4862

4963
import { describe, it, expect, vi } from 'vitest';
@@ -318,3 +332,119 @@ describe('#14599 artifact door — a `packages[]` artifact is registered per pac
318332
expect(warnings.filter((w: string) => w.includes('top-level metadata item'))).toEqual([]);
319333
});
320334
});
335+
336+
/**
337+
* The doc `os build` writes out of a flat `src/docs/` — judged by
338+
* `stack.manifest.namespace` and placed at the artifact TOP LEVEL, on no
339+
* package body at all (#18431 ruling, clause 1).
340+
*
341+
* The `content` carries a marker for the same reason the e2e fixture does: a
342+
* count is satisfiable by an echo of the other doc, and pedigree is not.
343+
*/
344+
const FLAT_DOC = { name: 'pkgdocs_index', label: 'Index', content: '# Index\n\nMARKER-flat-doc\n' };
345+
346+
/** The doc `os build` writes out of `src/orders/docs/` — the OWNING body only. */
347+
const PACKAGE_DOC = {
348+
name: 'ord_playbook',
349+
label: 'Orders Playbook',
350+
content: '# Orders Playbook\n\nMARKER-package-doc\n',
351+
};
352+
353+
/**
354+
* The #18431 docs placement, laid over the real composed artifact.
355+
*
356+
* ⚠️ The docs half is positioned by hand, and that is deliberate — it is the
357+
* ONE key on which `composeStacks(…, { manifest: 'preserve' })` is not the
358+
* producer. `os build`'s docs collector places docs AFTER composition: a
359+
* package-directory doc goes on its owning body and ⛔ never to the top level,
360+
* while the stack's own flat `src/docs/` keeps the top level. So composing a
361+
* `docs`-carrying stack would produce the additive both-places shape, which is
362+
* precisely NOT the shape under test. The placement below was read off a real
363+
* `os build` of the #18431 e2e fixture (two packages, `src/docs/` +
364+
* `src/orders/docs/`): top level `['pkgdocs_index']`, orders body
365+
* `['ord_playbook']`, core body none.
366+
*/
367+
const docsArtifact = () => {
368+
const artifact = twoPackageArtifact();
369+
artifact.docs = [{ ...FLAT_DOC }];
370+
const orders = artifact.packages.find((e: any) => e.manifest.id === ORDERS_ID);
371+
orders.manifest.docs = [{ ...PACKAGE_DOC }];
372+
return artifact;
373+
};
374+
375+
describe('#19246 artifact door — a `packages[]` artifact\'s TOP-LEVEL-only docs register', () => {
376+
it('the fixture carries the card shape (premise guard)', () => {
377+
const artifact = docsArtifact();
378+
379+
// The subject: a top-level doc that NO package body declares. This is
380+
// the emitter's intended output for a multi-package project with a flat
381+
// `src/docs/`, not an accident of `packages` being a `concat` key — so
382+
// unlike the orphan `object` case above, this shape is on every such
383+
// artifact the compiler produces.
384+
expect(artifact.docs.map((d: any) => d.name)).toEqual(['pkgdocs_index']);
385+
const onBodies = artifact.packages.flatMap((e: any) => (e.manifest.docs ?? []).map((d: any) => d.name));
386+
expect(onBodies).not.toContain('pkgdocs_index');
387+
388+
// The control: a doc that reaches the registry through the package
389+
// walk, and is NOT at the top level.
390+
expect(onBodies).toEqual(['ord_playbook']);
391+
expect(JSON.stringify(artifact.docs)).not.toContain('MARKER-package-doc');
392+
});
393+
394+
it('registers BOTH — the body doc via the package walk, the top-level-only doc via the residual sweep', async () => {
395+
const { plugin } = await load(docsArtifact());
396+
397+
// ⭐ The lit control, asserted FIRST and in the same load: a `doc` that
398+
// does register. Without it an absent subject below would be equally
399+
// consistent with a door that registers no `doc` at all, a fixture that
400+
// never parsed, or a harness that recorded nothing.
401+
expect(await stampOf(plugin, 'doc', 'ord_playbook'))
402+
.toEqual({ packageId: ORDERS_ID, packageVersion: '2.4.0' });
403+
404+
// The subject. The static reading this pin exists to contradict was
405+
// that `resolveArtifactPackageOrder` walks bodies only (true) and that
406+
// `AppPlugin` registers no `collections.docs` (also true) — and
407+
// concluded "nowhere", having never reached THIS door's residual sweep,
408+
// which is the registrar.
409+
expect(await stampOf(plugin, 'doc', 'pkgdocs_index'))
410+
.toEqual({ packageId: CORE_ID, packageVersion: '1.0.0' });
411+
412+
// Pedigree, not counts: each registered doc carries the bytes of the
413+
// file it came from, so neither assertion above can be satisfied by an
414+
// echo of the other.
415+
expect(((await plugin.manager.get('doc', 'pkgdocs_index')) as any).content)
416+
.toContain('MARKER-flat-doc');
417+
expect(((await plugin.manager.get('doc', 'ord_playbook')) as any).content)
418+
.toContain('MARKER-package-doc');
419+
});
420+
421+
it('the absence arm: with the top-level doc gone the control still registers', async () => {
422+
// The negative leg of the same instrument. A pin that asserts presence
423+
// is worth only as much as its ability to report absence, and these two
424+
// cases differ by exactly the top-level `docs` array.
425+
const artifact = docsArtifact();
426+
delete artifact.docs;
427+
428+
const { plugin, ctx } = await load(artifact);
429+
430+
expect(await stampOf(plugin, 'doc', 'pkgdocs_index')).toBeUndefined();
431+
expect(await stampOf(plugin, 'doc', 'ord_playbook'))
432+
.toEqual({ packageId: ORDERS_ID, packageVersion: '2.4.0' });
433+
434+
// …and with nothing unowned at the top level, the sweep says nothing.
435+
const warnings = ctx.logger.warn.mock.calls.map((c: unknown[]) => String(c[0]));
436+
expect(warnings.filter((w: string) => w.includes('top-level metadata item'))).toEqual([]);
437+
});
438+
439+
it('says out loud that the artifact\'s two halves disagree', async () => {
440+
const { ctx } = await load(docsArtifact());
441+
442+
// The registration is silent-by-default's opposite: the door warns, and
443+
// the remedy it names is one an author can actually perform — measured
444+
// on the same fixture, moving `src/docs/pkgdocs_index.md` to
445+
// `src/core/docs/` puts the doc on the core body and the top-level
446+
// `docs` key disappears from the artifact.
447+
const warnings = ctx.logger.warn.mock.calls.map((c: unknown[]) => String(c[0]));
448+
expect(warnings.some((w: string) => w.includes('top-level metadata item'))).toBe(true);
449+
});
450+
});

0 commit comments

Comments
 (0)