Skip to content

Commit 4b1f735

Browse files
committed
test(cli): read what SHIPS for the retired os create example, and read roster rows
`create-example-retired-docs-parity` pinned the retirement across four `content/docs` pages and could not see `packages/cli/README.md` — the carrier that actually ships, named verbatim by this package's own `files: ["dist", "README.md", "CHANGELOG.md"]`. Adding the README to the existing map was measured and does not work: on a clean tree it fails only the `os create plugin` FENCE control, and with the retired command written into the README's roster TABLE ROW it fails that same one test and no other. Both retirement assertions passed over a README that was carrying the retired command, so widening the population alone would have made the suite CLAIM a carrier it does not read. The reader is therefore widened as well: an OFFER is a fenced line or a markdown table row, applied to both populations. `content/docs/deployment/cli.mdx` was measured to carry four `os create` table rows of its own, so fence-only reading left the same blind spot on a page already claimed as guarded. The shipped population is derived from `files` rather than restated, with a tripwire on its non-markdown entries. The reader stops short of running prose, and `CHANGELOG.md` — which ships and records `os create example` in two historical entries — pins that boundary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
1 parent f36eef5 commit 4b1f735

1 file changed

Lines changed: 177 additions & 15 deletions

File tree

packages/cli/test/create-example-retired-docs-parity.test.ts

Lines changed: 177 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* DOCS PARITY (#16483) — no public page still offers `os create example` to run.
4+
* DOCS PARITY (#16483, #16690) — no public page and no SHIPPED file still
5+
* offers `os create example` to run.
56
*
67
* ## The half no process can check
78
*
89
* `create-example-retired.e2e.test.ts` drives the real CLI and proves the
910
* refusal: non-zero, and the message names `os init`. That says nothing about
10-
* the four public pages which present `os create` as a user-facing command. A
11+
* the public pages which present `os create` as a user-facing command. A
1112
* reader who follows a page rather than a terminal is exactly the reader the
1213
* #15531 ruling is protecting, and a page that still prints
1314
* `os create example my-app` in a copyable block hands them a command that now
@@ -18,8 +19,8 @@
1819
* That one is `*.e2e.test.*`, a NAME-decided tier (`scripts/nightly-tiers.mjs`,
1920
* #16455): those files are excluded from the per-PR and merge-queue population
2021
* and run in the nightly job. That is the right lane for four cold `tsx` spawns
21-
* and the wrong lane for four `readFileSync` calls — a docs-only PR that
22-
* reintroduced the retired command would otherwise be caught the following
22+
* and the wrong lane for a handful of `readFileSync` calls — a docs-only PR
23+
* that reintroduced the retired command would otherwise be caught the following
2324
* night instead of on the PR. This file spawns nothing, so it stays queue-tier
2425
* and reddens where the edit is made.
2526
*
@@ -29,19 +30,80 @@
2930
* a reader arriving from a search engine or an older tutorial needs. What the
3031
* ruling fixed is two narrower things, and those are what is asserted:
3132
*
32-
* a. no FENCED CODE BLOCK on any public page carries it, so there is nothing
33-
* left to copy into a terminal;
33+
* a. nothing a reader can COPY carries it, so there is nothing left to paste
34+
* into a terminal;
3435
* b. a page that mentions it in prose also names `os init`, so the mention is
3536
* a signpost rather than a leftover.
3637
*
3738
* ⛔ Never satisfy a red here by deleting a page's `os create` section. The
38-
* positive control below fails when a page stops showing `os create plugin` in
39-
* a fence, which is what makes the two assertions capable of failing at all.
39+
* positive controls below fail when a carrier stops showing a SURVIVING
40+
* `os create`, which is what makes the assertions capable of failing at all.
4041
*
4142
* All four pages are already declared as cross-package inputs of
4243
* `@objectstack/cli` (`scripts/cross-package-test-inputs.mjs`, mirrored into
4344
* `turbo.json`), so a docs-only edit reaches this suite instead of replaying a
44-
* cached green.
45+
* cached green. The shipped carriers below live inside this package, so
46+
* `$TURBO_DEFAULT$` already hashes them and they need no declaration.
47+
*
48+
* ## The carrier the docs population could not see (#16690)
49+
*
50+
* The four pages above are the WEBSITE. `packages/cli/README.md` is not one of
51+
* them, and it is the carrier that actually ships: this package's own `files`
52+
* names it — `["dist", "README.md", "CHANGELOG.md"]` — so it goes out in every
53+
* tarball as the npm front page. Of the five carriers of the retired command,
54+
* the four that were guarded were documentation pages and the one that was not
55+
* is the one in the tarball.
56+
*
57+
* ## Widening the population without widening the READER is worse than the gap
58+
*
59+
* Adding the README to `DOC_SITES` and changing nothing else was MEASURED, and
60+
* it does not work. On a clean tree that naive addition fails exactly one test
61+
* — the `os create plugin` FENCE control, because the README shows that
62+
* command in prose and in a table, never in a fence. With `os create example`
63+
* written into the README's command roster TABLE ROW it fails THAT SAME ONE
64+
* TEST AND NO OTHER: both retirement assertions passed over a README that was
65+
* carrying the retired command. The two failure signatures are identical, so
66+
* the red says nothing about the retirement — an author would silence it by
67+
* adding a fenced `os create plugin` to the README and land a fully green
68+
* suite that had just been shown a retired roster row.
69+
*
70+
* ⇒ A roster row is not a fence and the fence reader cannot see it. So the
71+
* reader is widened here too, and the same widening is applied to the four
72+
* pages: `content/docs/deployment/cli.mdx` was measured to carry four
73+
* `os create` TABLE ROWS of its own, so fence-only reading left the same
74+
* blind spot on a page the suite already claimed to guard.
75+
*
76+
* ## What counts as an OFFER, and why it stops short of prose
77+
*
78+
* `offeredLines` reads two shapes, applied to both populations:
79+
*
80+
* - lines inside a fenced code block — what a reader copies to a terminal;
81+
* - markdown table rows — a command roster IS a list of runnable commands,
82+
* and a table is the shape both the README and `cli.mdx` present them in.
83+
*
84+
* ⛔ It deliberately does NOT read inline code in running prose, and that
85+
* boundary is measured rather than assumed: `packages/cli/CHANGELOG.md` ships
86+
* too, and it carries `os create example` in two historical entries (it has no
87+
* fences at all, and none of its 348 table rows names `os create`). A
88+
* changelog's job is to RECORD that a command existed and was retired;
89+
* reddening on it would demand rewriting history to keep a gate green. The
90+
* boundary test below pins that, so the next author who widens the reader sees
91+
* which line they are about to cross.
92+
*
93+
* ⚠️ NOT COVERED, stated plainly so this suite is not read as claiming it: an
94+
* inline `os create example` written into README PROSE is invisible here.
95+
* Closing that needs a rule separating an offer from a mention inside running
96+
* text, and the CHANGELOG is the proof that the rule is not "it is in
97+
* backticks".
98+
*
99+
* ## The shipped population is DERIVED, never restated
100+
*
101+
* `SHIPPED_MARKDOWN` is read out of `packages/cli/package.json`'s `files`. A
102+
* second hand-written shipping list is the drift this card exists to fix, so
103+
* the only hand-written thing is a TRIPWIRE on the entries that are NOT
104+
* markdown — `dist` today, measured to ship 0 `.md` files out of 500. A new
105+
* entry there may be a prose carrier this suite cannot see, and it must fail
106+
* until someone re-derives the population rather than extending a list.
45107
*/
46108

47109
import { describe, it, expect } from 'vitest';
@@ -59,6 +121,8 @@ const CLI_DOCS = resolve(HERE, '../../..', 'content/docs/deployment/cli.mdx');
59121
const PLUGINS_INDEX = resolve(HERE, '../../..', 'content/docs/plugins/index.mdx');
60122
const KERNEL_INDEX = resolve(HERE, '../../..', 'content/docs/protocol/kernel/index.mdx');
61123
const PLUGIN_SPEC = resolve(HERE, '../../..', 'content/docs/protocol/kernel/plugin-spec.mdx');
124+
const CLI_PACKAGE_ROOT = resolve(HERE, '..');
125+
const CLI_PACKAGE_JSON = resolve(HERE, '..', 'package.json');
62126

63127
/** The four public pages that present `os create` as a user-facing command. */
64128
const DOC_SITES: Record<string, string> = {
@@ -68,14 +132,47 @@ const DOC_SITES: Record<string, string> = {
68132
'content/docs/protocol/kernel/plugin-spec.mdx': PLUGIN_SPEC,
69133
};
70134

135+
/**
136+
* This package's OWN shipping declaration. ⛔ Never restate it here: `files` is
137+
* the one list that cannot drift out of step with what `npm pack` emits, and
138+
* `packages/cli/package.json` is read for it and never written by this suite.
139+
*/
140+
const SHIPPED: readonly string[] = (
141+
JSON.parse(readFileSync(CLI_PACKAGE_JSON, 'utf8')) as { files: string[] }
142+
).files;
143+
144+
/** The shipped carriers that can hold prose, derived from the line above. */
145+
const SHIPPED_MARKDOWN = SHIPPED.filter((entry) => entry.endsWith('.md'));
146+
147+
const shippedText = (rel: string): string => readFileSync(resolve(CLI_PACKAGE_ROOT, rel), 'utf8');
148+
71149
/** Every fenced code block on a page — what a reader copies into a terminal. */
72-
function fences(mdx: string): string[] {
73-
return [...mdx.matchAll(/^```[^\n]*\n([\s\S]*?)^```/gm)].map((m) => m[1]);
150+
function fences(md: string): string[] {
151+
return [...md.matchAll(/^```[^\n]*\n([\s\S]*?)^```/gm)].map((m) => m[1]);
152+
}
153+
154+
/** Every markdown table row — the shape a COMMAND ROSTER is written in. */
155+
function tableRows(md: string): string[] {
156+
return md.split('\n').filter((line) => /^\s*\|/.test(line));
157+
}
158+
159+
/**
160+
* What a carrier OFFERS a reader to run. Fences and roster rows both present a
161+
* command as runnable; running prose does not, and the CHANGELOG boundary test
162+
* below is what holds that line in place.
163+
*/
164+
function offeredLines(md: string): string[] {
165+
return [...fences(md).flatMap((block) => block.split('\n')), ...tableRows(md)];
74166
}
75167

76168
/** The retired spelling, DERIVED from the registry rather than written twice. */
77169
const RETIRED = Object.keys(RETIRED_TEMPLATES).map((key) => `os create ${key}`);
78170

171+
const offersRetired = (md: string): string[] =>
172+
offeredLines(md)
173+
.filter((line) => RETIRED.some((cmd) => line.includes(cmd)))
174+
.map((line) => line.trim());
175+
79176
describe('[#16483] the docs and the retirement registry agree', () => {
80177
it('has something retired to look for, and a survivor to control against', () => {
81178
// Both halves, so neither loop below can pass over an empty population.
@@ -94,11 +191,8 @@ describe('[#16483] the docs and the retirement registry agree', () => {
94191
});
95192

96193
it.each(Object.keys(DOC_SITES))('%s offers nothing to COPY that now refuses', (site) => {
97-
const copyable = fences(readFileSync(DOC_SITES[site], 'utf8'))
98-
.flatMap((f) => f.split('\n'))
99-
.filter((line) => RETIRED.some((cmd) => line.includes(cmd)));
100194
expect(
101-
copyable.map((line) => line.trim()),
195+
offersRetired(readFileSync(DOC_SITES[site], 'utf8')),
102196
`${site} still shows a retired command as runnable — it exits non-zero now`,
103197
).toEqual([]);
104198
});
@@ -112,3 +206,71 @@ describe('[#16483] the docs and the retirement registry agree', () => {
112206
).toContain('os init');
113207
});
114208
});
209+
210+
describe('[#16690] what SHIPS carries the retirement too', () => {
211+
it('derives a non-empty carrier population from the package `files` array', () => {
212+
expect(
213+
SHIPPED_MARKDOWN.length,
214+
'`files` names no markdown, so every assertion below passes over an empty '
215+
+ 'population — re-derive the population, do not delete these tests',
216+
).toBeGreaterThan(0);
217+
for (const rel of SHIPPED_MARKDOWN) {
218+
expect(() => shippedText(rel), `\`files\` names ${rel}, which is not on disk`).not.toThrow();
219+
}
220+
});
221+
222+
it('has no shipped entry that could carry prose unread (tripwire)', () => {
223+
// ⛔ NOT a second shipping list — the population above is derived. This is a
224+
// tripwire: `dist` was measured to ship 0 `.md` files out of 500, so it
225+
// carries no prose. A new entry here may, and must fail until someone
226+
// re-derives the population rather than widening this line.
227+
expect(
228+
SHIPPED.filter((entry) => !entry.endsWith('.md')),
229+
'a new non-markdown entry ships in the tarball — if it can carry a runnable '
230+
+ 'command, it belongs in the suite population before this line changes',
231+
).toEqual(['dist']);
232+
});
233+
234+
it.each(SHIPPED_MARKDOWN)('%s offers nothing to COPY that now refuses', (rel) => {
235+
expect(
236+
offersRetired(shippedText(rel)),
237+
`${rel} ships in the npm tarball and still offers a retired command as `
238+
+ 'runnable — it exits non-zero now',
239+
).toEqual([]);
240+
});
241+
242+
it('reads a roster TABLE ROW, not only a fence (control)', () => {
243+
// ⭐ The control that makes the assertion above capable of failing at all.
244+
// The README presents `os create` in a table cell and never in a fence, so
245+
// a fence-only reader finds nothing here and every assertion goes vacuous.
246+
const rosterRows = SHIPPED_MARKDOWN.flatMap((rel) => tableRows(shippedText(rel))).filter(
247+
(line) => line.includes('os create'),
248+
);
249+
expect(
250+
rosterRows.length,
251+
'no shipped markdown shows `os create` in a table row — the reader has '
252+
+ 'nothing to prove it can see a roster cell, so the retirement '
253+
+ 'assertion above can no longer fail',
254+
).toBeGreaterThan(0);
255+
});
256+
257+
it('records a retired command outside an offer position without reddening (boundary)', () => {
258+
// The CHANGELOG names `os create example` in historical entries. That is a
259+
// RECORD, not an offer, and it must stay green: a reader widened to all
260+
// inline code would demand rewriting shipped history to keep a gate green.
261+
const recorded = SHIPPED_MARKDOWN.flatMap((rel) => {
262+
const text = shippedText(rel);
263+
const offered = new Set(offeredLines(text));
264+
return text
265+
.split('\n')
266+
.filter((line) => RETIRED.some((cmd) => line.includes(cmd)) && !offered.has(line));
267+
});
268+
expect(
269+
recorded.length,
270+
'no shipped file mentions a retired command outside an offer position, so '
271+
+ 'the offer/record boundary this reader depends on is no longer '
272+
+ 'exercised — re-establish it or retire this control deliberately',
273+
).toBeGreaterThan(0);
274+
expect(offersRetired(recorded.join('\n'))).toEqual([]);
275+
});
276+
});

0 commit comments

Comments
 (0)