Repository navigation
Commit 94608a7
Fixes #20980
Clause-②: no
## What changed
`resolveBookTree` (`packages/spec/src/system/book.zod.ts`) put every
unclaimed doc it was handed into the synthetic *Uncategorized* group.
`GET /api/v1/meta/book/:name/tree` resolves over every doc in the
environment, so a book's tree listed every other package's ungrouped
docs there. ADR-0046 §6.4 says the unplaced docs are the package's, and
the portal (`scopeDocsToBook`) already answers per package.
- **The fix (landing site: the resolver, as triage directed in
`5923314533`).** The orphan pass now keeps an unclaimed doc only when it
belongs to one of the book's packages: `bookPackage` plus every group's
`package`.
- **One reading of a doc's package.** "Belongs" is asked as
`matchesInclude(d, '*', p)`: the implicit package book's own catch-all
rule (`deriveImplicitPackageBook`), through the same scope test
`include` uses. A doc's package is its stamped `packageId`, and a doc
with none is in every scope. There is no second notion and no package
lookup.
- **No package declared anywhere:** unchanged. Every unclaimed doc is an
orphan.
- **Unchanged by construction:** derived membership, `pages` overrides
and explicit `group` placement. A doc of another package whose `group`
names one of the book's groups still joins it. `resolveBookClaimedDocs`
already skipped the *Uncategorized* group, so the claim set,
`resolveDocAudiences` and every doc's effective audience are the same as
before.
## Mechanism hypotheses, measured at `2f2fa11d75` (base) and re-read at
the merged head
- **H1 — what a doc carries.** Both REST corpora project `packageId:
d._packageId`: the tree read at
`packages/rest/src/meta-item-read-gate.ts:2766` and `docCorpusOf` at
`:457`. `packages/cli` `collect-docs` docs carry no `packageId`, and its
one `resolveBookTree` call passes no `bookPackage` and names no group
`package`, so that call is unscoped, as before. `packages/client` has no
call, only a mock comment at `client.test.ts:290`. No caller declares a
scope over docs it cannot place. The stop condition does not hold.
- **H2 — every caller.** `readableTree` (`:608`) and `readablePages`
(`:624`) pass `book._packageId`. The undeclared-name fallback
`deriveImplicitPackageBook(name, name)` (`:604`) carries no
`_packageId`, but its one group names `package: name`, so the implicit
book is scoped too. That book used to list every other package's docs in
*Uncategorized*. `resolveDocAudiences` passes `book.packageId` per book.
`rest-route-ledger.ts:228` and `spec/src/api/index.ts` only name the
symbols. The only verdict that moves is the tree's *Uncategorized*
group. The fix site is the resolver, not the REST caller.
- **H3 — the door.** I measured once through the REST tests' harness,
with a scratch test that was never committed. It booted `RestServer`
with a mocked protocol and read `GET /api/v1/meta/book/help_center/tree`
as an `org` member. The book belongs to `crm` and has one group `start`
with `include: 'crm_intro'`. The corpus was `crm_intro` and `crm_stray`
(crm), `ops_keys` (ops), and `ops_placed` (ops, `group: 'start'`).
- before, spec dist at the base: `200
{"start":["crm_intro","ops_placed"],"uncategorized":["ops_keys","crm_stray"]}`
- after, spec dist rebuilt with the fix: `200
{"start":["crm_intro","ops_placed"],"uncategorized":["crm_stray"]}`
- So another package's ungrouped doc is present before and absent after.
The book's own ungrouped doc is present both times (the lit control).
The cross-package explicit placement is unchanged.
## Files
- `packages/spec/src/system/book.zod.ts`: the orphan pass and the
resolver docblock.
- `packages/spec/src/system/book.test.ts`: seven pins in a new block,
and one existing pin re-judged. The pins are:
- another package's ungrouped doc is out, and the book's own is in;
- control: a cross-package `group` placement still joins;
- a group `package` counts as one of the book's packages;
- the implicit package book catches no foreign doc;
- no package anywhere keeps the old answer;
- an unstamped doc is in every scope;
- the claim set is unchanged.
- The re-judged pin is `include scoped by package ignores docs from
other packages`. Its last line asserted that another package's doc
*falls through* to *Uncategorized*, which is the defect. It now asserts
that no *Uncategorized* group is appended.
- `packages/rest/src/meta-app-nav-doc-audience.test.ts`: a file-surface
addition beyond the claim, named in the report. Its assertion pinned the
defect: the implicit `ops` book's tree served `crm_intro`, a `crm` doc,
in *Uncategorized*. With the fix that tree serves no group, because its
two pages are gated for a non-holder. The nav assertion the test is
named for is unchanged.
- `content/docs/ui/doc-pages.mdx`: step 4 of the membership rules said
"anything claimed by nobody", which this change makes false. It now
states the book's-packages rule.
- `.changeset/20980-book-tree-orphans-scoped.md`: `patch` for
`@objectstack/spec`. No consumer package's published files change: the
rest edit is a test file.
## Tests (real runs, foreground, through `os-verify-lock.sh`)
- `@objectstack/spec`: `vitest run src/system/` gave 48 files, 1706
passed, at merged head `407071df8` (spec source is unchanged since). The
verbose `book.test.ts` run gave 40 passed. `typecheck`, including
`check:test-typecheck` over `tsconfig.test.json`, whose `include` is
`src/**/*` so it covers `book.test.ts`, exited 0. `check:generated`: all
15 generated artifacts up to date.
- `@objectstack/rest` (`--project local`, full) at `07a04a571`, before
the rest test edit: 250 of 251 files passed, 1 failed, 4973 passed, 114
skipped. The one failure was `meta-app-nav-doc-audience.test.ts:243`:
expected `['uncategorized']`, received `[]`. That is the pin of the
defect. After the edit, that file passed 19/19. At `407071df8`,
`meta-app-nav-doc-audience` + `meta-doc-audience-read-fault` +
`rest.test.ts` gave 3 files, 246 passed. `typecheck` exited 0.
- `@objectstack/cli` (`--project unit`): 238 of 240 files passed, 3372
passed. The other 2 files (`published-subpath-console.pin`,
`published-subpath-hook-body.pin`) refused with PREREQUISITE NOT MET
because `packages/cli` was not built. After `pnpm --filter
@objectstack/cli build` both passed, 29 tests. `collect-docs.test.ts` is
among the passes. The `integration` layer was not run here; CI runs it.
- `@objectstack/client`: 50 files, 641 passed.
- `@objectstack/runtime` `meta-list-projection-parity.test.ts`
(`--project repo`), an extra consumer of the tree route: 682 passed,
every TREE cell included.
- Filter direction: the dependency closures were built upstream-first
with the suffix form (`pnpm --filter '@objectstack/rest^...' --filter
'@objectstack/cli^...' --filter '@objectstack/client^...' build`). After
the fix only `@objectstack/spec` was rebuilt. The consumer tests read
the rebuilt spec `dist`, and the dist was confirmed to carry the fix
before they were read.
## Reverse verification (one-off, fix committed first)
`node scripts/ablation-replace.mjs` replaced the orphan scope filter
(`ownPackages.length === 0 || ownPackages.some(...)`) with an
always-true filter, then ran `book.test.ts`. The expected direction was
red, and it went red.
- The anchor hit 1 to 0. The blob went from `a3e0522bd71f` to
`62c6324257f8`.
- Result: 5 failed, 35 passed. The failures were the four new scoped
pins and the re-judged `include scoped by package` pin. The pins whose
behaviour does not move stayed green: no package declared, unstamped
doc, and claim set.
- The tool's restore leg: blob after restore `a3e0522bd71f`, equal to
HEAD, and `git diff HEAD` empty.
- Spec tests read `./book.zod` from source, so this ablation needed no
`dist` rebuild.
## Gates
- I derived `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from this branch's own diff and ran its
union at `da260e723`, the last commit. 104 of 105 commands exited 0.
- `--ran` reconciliation: 105 derived, 104 run, 0 NOT-MEASURED, 1 UNRUN.
- **NOT MEASURED: `pnpm check:type-check-debt`.** Reason: its
`--re-measure` first refreshes the closure of every ledgered package
with the whole-workspace turbo build, which does not fit under the
foreground cap. A first attempt was killed by the runner's time cap
mid-refresh. The structural half, `pnpm check:type-check-coverage`,
exited 0. Invariance: no exported type moved (`check:api-surface` exit
0). The edits are a function body and TSDoc, so no ledgered package's
tsc count can move from this diff. CI runs this gate after its own
closure build.
- `pnpm check:dual-build-cjs-loads` first refused (exit 3, nine packages
had no `dist`). After those nine were built it exited 0.
- ESLint, narrowed. Population: the three touched TS files, each inside
`eslint.config.mjs` (`--print-config` resolves for all three). The
`.mdx` and the changeset get "File ignored because no matching
configuration", so they are outside the population. Count: `--format
json` over the three files gave 3 files, 0 errors, 0 warnings.
Invariance: `eslint.config.mjs` enables no type-aware linting (its own
note at `:328`: no `parserOptions.project`), so this diff cannot move
the verdict on any untouched file.
## Acceptance notes (not filed)
- `packages/rest/src/meta-item-read-gate.ts:1108` docblock:
"`resolveBookTree` appends every doc the book does NOT claim as a
synthetic *Uncategorized* group, so over an env-wide corpus nearly every
book's tree holds some readable doc". After this change that group holds
only the book's own packages' unclaimed docs. The rule it justifies
still holds, since orphans are not pages, but the premise is now
narrower. This is a comment, outside this claim's surface
(`domain:cli`). Carrier: the next PR that touches that file.
- `meta-app-nav-doc-audience.test.ts` no longer witnesses, at the REST
door, a tree whose only readable entries are orphans. An implicit book
now has no foreign orphan to serve. The orphans-are-not-claims half
stays pinned in spec: `a doc claimed by no book defaults to org —
orphans do NOT ride along with public books`.
- objectui's `scopeDocsToBook` pre-filter also removes another package's
doc that a `pages` override pins by name. The server's resolver still
labels such a doc. This is read-only inference from objectui `main`'s
`book-nav.ts`, not measured. Carrier: objectui, when its pre-filter is
retired in favour of the resolver's own scoping (the card's "after that"
step).
---
_Generated by [Claude
Code](https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent b3d7a70 commit 94608a7
5 files changed
Lines changed: 123 additions & 11 deletions
File tree
- .changeset
- content/docs/ui
- packages
- rest/src
- spec/src/system
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
180 | 180 | | |
181 | 181 | | |
182 | 182 | | |
183 | | - | |
184 | | - | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
185 | 188 | | |
186 | 189 | | |
187 | 190 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
237 | 237 | | |
238 | 238 | | |
239 | 239 | | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | | - | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
244 | 245 | | |
245 | 246 | | |
246 | 247 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
186 | 262 | | |
187 | 263 | | |
188 | 264 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
295 | 295 | | |
296 | 296 | | |
297 | 297 | | |
298 | | - | |
299 | | - | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
300 | 308 | | |
301 | 309 | | |
302 | 310 | | |
| |||
373 | 381 | | |
374 | 382 | | |
375 | 383 | | |
376 | | - | |
377 | | - | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
378 | 398 | | |
379 | 399 | | |
380 | 400 | | |
| |||
0 commit comments