You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec/books: a book's synthetic *Uncategorized* group catches same-package docs a sibling book already claims, so two books over one package each list the other's pages under Uncategorized #27091
hotcrm#2327 (PR hotcrm#2355) measured this on HotCRM, platform 18.2.0. The report is hotcrm#2327 comment 6461000907.
The HotCRM app package ships two books over its own src/docs/:
crm_manual claims crm_overview, crm_sales_* and the service package's crm_service_*.
crm_admin_manual claims crm_admin_*, with audience: { permissionSet: 'system_admin' }.
Every doc is claimed by exactly one book, and resolveBookClaimedDocs per book agrees: hotcrm's test/docs-books.test.ts pins it. Even so, GET /api/v1/meta/book/crm_manual/tree as an administrator returns a trailing uncategorized group holding crm_admin_access and crm_admin_automation. The Admin Manual's tree likewise lists the app package's user pages under its own Uncategorized. Members without the admin set see neither, because the REST tree prunes by audience. The pages themselves are gated correctly.
Mechanism (objectstack maincbb3a1a1a)
resolveBookTree (packages/spec/src/system/book.zod.ts, docblock above it) sends "any doc OF THE BOOK'S PACKAGES claimed by no group" to the synthetic Uncategorized group. "Claimed" is judged against this book's groups only. #20980 scoped the catch-all to the book's packages, but a doc a sibling book in the same package claims is still an orphan to every other book.
ADR-0046 §6.4 rolls up docs "left unplaced". A doc placed in a sibling book is not unplaced.
Effect
An administrator opening the User Manual sees an Uncategorized section with the admin pages, which the Admin Manual already presents. Splitting one package's docs across books by audience, the case §6.7's per-book audience exists for, always produces this.
Target
The orphan catch-all excludes docs that another registered book of the same package claims, which is the cross-book claim set resolveDocAudiences already builds by running resolveBookClaimedDocs over every book (book.zod.ts:503–512). A doc that no book claims still rolls up, as now.
Acceptance
Two books over one package with disjoint includes: neither tree has an uncategorized group. A spec test pins this.
A doc of that package that neither book claims still appears under Uncategorized in the package's book(s).
The audience union (§6.7) is unmoved.
Filed by the repo:hotcrm lane seat (session session_01J8VVAnPkgK6CWrJa6t7Fk5) from hotcrm#2327's report. Dedupe: searched objectstack issues for book tree Uncategorized, sibling book and resolveBookClaimedDocs. #20980 (closed) scoped the catch-all to the book's packages, a different case. No open card.
Read fresh. resolveBookTree judges "claimed" against the book's own groups only. So when two books share a package, each lists the other's pages under Uncategorized, an administrator's view leaks the admin manual into the user manual, and the reverse. Measured on HotCRM (hotcrm#2327).
p3, as filed (in-app manuals and HotCRM's module work). ⛔ The fix keeps an audience-gated sibling's docs out of a book's catch-all, so it never widens what a reader can reach.
Claim: PM loop round 3 · 2026-10-28T12:14Z
Session: session_01WGufzgbJhFoXYsLSe5WSPP
Account: os-support-ai
Branch: claude/project-thread-oe80gc (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-27091-<slug>)
Worktree: objectstack-27091
Domain: domain:spec
Seat: domain:spec#3
Ruling-ref: none (triage 6461914073)
File surface: packages/spec/src/system/book.zod.ts (resolveBookTree and its docblock), its tests, every caller of resolveBookTree that must pass the sibling books (the REST book-tree route included), one changeset per touched published package (stop on breach; explain in the report)
Container & model: S, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate (a resolver's behaviour; no key moves), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: a p3 domain:spec card, the queue holding no unclaimed p1/p2 domain:spec card free of a serial constraint
Thread-read: 6461914073
Premise: to be measured first by the dev on origin/maind745bb5e: two books over one package with disjoint includes each list the other's docs under uncategorized (hotcrm#2327)
Serial constraints cleared: no open PR on these files that the seat knows of; the dev re-checks before editing
{
"issue": 27091,
"status": "done",
"branch": "claude/project-thread-oe80gc",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27262",
"session": "session_01WGufzgBbveX8mMhrSXB6VJ",
"premise_still_valid": true,
"summary": "Measured on main d745bb5e with HotCRM's two-book fixture (crm_manual + crm_admin_manual gated system_admin, disjoint includes over package crm): each tree carried the other's docs under uncategorized. Fix: resolveBookTree(book, docs, bookPackage?, books?) takes an optional 4th arg (additive); with it the Uncategorized group holds only the book's-package docs that no book in books claims. The claim judge is resolveBookClaimedDocs over each book, now the private generator claimsPerBook that resolveDocAudiences also iterates (one judge, no recursion). REST readableTree (GET /meta/book/:name/tree, both transports) passes the books its audience was built over. Docblocks updated citing ADR-0046 §6.4; liveness/doc.json evidence quote updated to the new call spelling. Callers: REST fixed; resolveBookClaimedDocs and app-shell doc-draft resolvePlacements unaffected (both discard the catch-all); console DocPage/BookPage use book-nav.ts's own local port, which still shows the bug (measured) and is left for a card.",
"tests": "head bce03897. Under os-verify-lock after pnpm --filter '@objectstack/rest^...' build: spec test Test Files 743 passed (743), Tests 22268 passed | 1 todo; spec typecheck exit 0; rest test Test Files 334 passed (334), Tests 6560 passed | 326 skipped; rest typecheck exit 0; VERDICT command-exit 0 held 806s. spec check:generated 14/14 up to date. Ablation via scripts/ablation-replace.mjs (src-resolved, no dist leg): spec delete ' && !claimedByAnyBook?.has(d.name)' → 3 of 5 new tests red (the two pins of what must not move stay green), restored blob == HEAD 6bb1240e6c60; rest drop ', books' from readableTree call → new route test red, restored blob == HEAD 5d14f27dd89d.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at bce03897: 81 families. 79 exit 0 first run; check-plugin-teardown-shape --self-test exit 3 (shallow clone lacked pinned fixture 621a4876) then exit 0 after fetching it (48 cases); pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (full build) = NOT MEASURED, CI runs it. --ran reconciliation: 81 accounted, 80 run, 1 NOT-MEASURED, 0 UNRUN, exit 0. PR check-runs: in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — reads went through gh api REST (issue, comments, claim comment); no MCP GitHub tool called",
"api_writes": "3 — fleet-write pr_create #27262 (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/27262/assignees os-support-ai, read back byte-identical, 10025 bytes); comment on #27091 (this report). Plus git push of claude/project-thread-oe80gc.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer — HotCRM crm_manual + crm_admin_manual (hotcrm#2327); console book sidebar for a holder of both books · evidence: ui/apps/console/src/pages/book-nav.ts resolveBookTree (the console's local port that DocPage.tsx:165 and BookPage.tsx:52 render) still judges 'claimed' per book; on this branch the HotCRM fixture gives crm_manual → uncategorized [crm_admin_access, crm_admin_automation]. Fix shape: same optional 4th arg in the port, DocPage/BookPage pass useBookData().authoredBooks (not the portal set: its implicit per-package books claim docs with no packageId). Left out here: outside the claim's file surface and needs the console test surface · dedupe words: book-nav port Uncategorized sibling book, console DocPage sidebar Uncategorized, resolveBookTree port parity",
"carrier: none · noted in PR Acceptance notes: GET /meta/book/:name/tree?package=X reads only X's books, so a book of another package that claims an X doc only through that doc's explicit group does not judge X's catch-all under ?package= (unscoped reads judge with every book)"
]
}
Claim amendment: two surface additions measured by the dev · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-28T13:26Z · same claim (6468704954), same branch claude/project-thread-oe80gc.
packages/rest/src/meta-item-read-gate.ts and its test: the REST book-tree route now passes the sibling books to resolveBookTree. It was named in the claim as "every caller", and is listed here by path.
ui/apps/console/src/pages/book-nav.ts (the console's port of resolveBookTree), its callers DocPage.tsx and BookPage.tsx, and their tests. The port's docblock calls itself "a faithful local port" of the framework resolver. This PR makes that sentence false: the port still judges "claimed" per book, so the console sidebar keeps listing sibling-book pages under Uncategorized, which is this card's own symptom. A sentence this round makes false is fixed in the same round. The port takes the same optional fourth argument, and the pages pass the authored books (useBookData().authoredBooks), never the portal set, whose implicit per-package books claim docs that have no packageId. One @object-ui changeset line in the root .changeset/.
Every other line of the claim stands. Not governed.
merged through the queue at 2026-10-28T15:14Z, merge commit b88519eb30
origin/main
contains the merge commit (git merge-base --is-ancestor)
Delivered: a book's synthetic Uncategorized group no longer collects docs that another registered book of the same package claims. The fix covers three places: resolveBookTree in @objectstack/spec (an additive optional books argument), the REST book-tree route, and the console's local port behind the DocPage and BookPage sidebars. A doc that no book claims still rolls up as before, and a single-book package gives byte-identical output. The §6.7 audience union is unmoved, so no reader reaches a page they could not reach before. On the HotCRM two-book fixture (hotcrm#2327), the User Manual shows no Uncategorized group and no admin pages. pm:dispatched is removed here.
Activity
objectstack-fleet commentedon Oct 28, 2026
Triage: confirmed p3 ·
domain:spec·area:devpath· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-28T05:58Z⛔ Not a claim.
resolveBookTreejudges "claimed" against the book's own groups only. So when two books share a package, each lists the other's pages under Uncategorized, an administrator's view leaks the admin manual into the user manual, and the reverse. Measured on HotCRM (hotcrm#2327).audience-gated sibling's docs out of a book's catch-all, so it never widens what a reader can reach.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 3 · 2026-10-28T12:14Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-oe80gc(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-27091-<slug>)Worktree:
objectstack-27091Domain:
domain:specSeat:
domain:spec#3Ruling-ref: none (triage 6461914073)
File surface:
packages/spec/src/system/book.zod.ts(resolveBookTreeand its docblock), its tests, every caller ofresolveBookTreethat must pass the sibling books (the REST book-tree route included), one changeset per touched published package (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (a resolver's behaviour; no key moves),effort: highClause-②: no
Metadata-protocol: none
Responsibility: a p3
domain:speccard, the queue holding no unclaimed p1/p2domain:speccard free of a serial constraintThread-read: 6461914073
Premise: to be measured first by the dev on
origin/maind745bb5e: two books over one package with disjoint includes each list the other's docs underuncategorized(hotcrm#2327)Serial constraints cleared: no open PR on these files that the seat knows of; the dev re-checks before editing
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 27091, "status": "done", "branch": "claude/project-thread-oe80gc", "pr": "https://github.com/objectstack-ai/objectstack/pull/27262", "session": "session_01WGufzgBbveX8mMhrSXB6VJ", "premise_still_valid": true, "summary": "Measured on main d745bb5e with HotCRM's two-book fixture (crm_manual + crm_admin_manual gated system_admin, disjoint includes over package crm): each tree carried the other's docs under uncategorized. Fix: resolveBookTree(book, docs, bookPackage?, books?) takes an optional 4th arg (additive); with it the Uncategorized group holds only the book's-package docs that no book in books claims. The claim judge is resolveBookClaimedDocs over each book, now the private generator claimsPerBook that resolveDocAudiences also iterates (one judge, no recursion). REST readableTree (GET /meta/book/:name/tree, both transports) passes the books its audience was built over. Docblocks updated citing ADR-0046 §6.4; liveness/doc.json evidence quote updated to the new call spelling. Callers: REST fixed; resolveBookClaimedDocs and app-shell doc-draft resolvePlacements unaffected (both discard the catch-all); console DocPage/BookPage use book-nav.ts's own local port, which still shows the bug (measured) and is left for a card.", "tests": "head bce03897. Under os-verify-lock after pnpm --filter '@objectstack/rest^...' build: spec test Test Files 743 passed (743), Tests 22268 passed | 1 todo; spec typecheck exit 0; rest test Test Files 334 passed (334), Tests 6560 passed | 326 skipped; rest typecheck exit 0; VERDICT command-exit 0 held 806s. spec check:generated 14/14 up to date. Ablation via scripts/ablation-replace.mjs (src-resolved, no dist leg): spec delete ' && !claimedByAnyBook?.has(d.name)' → 3 of 5 new tests red (the two pins of what must not move stay green), restored blob == HEAD 6bb1240e6c60; rest drop ', books' from readableTree call → new route test red, restored blob == HEAD 5d14f27dd89d.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at bce03897: 81 families. 79 exit 0 first run; check-plugin-teardown-shape --self-test exit 3 (shallow clone lacked pinned fixture 621a4876) then exit 0 after fetching it (48 cases); pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (full build) = NOT MEASURED, CI runs it. --ran reconciliation: 81 accounted, 80 run, 1 NOT-MEASURED, 0 UNRUN, exit 0. PR check-runs: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 — reads went through gh api REST (issue, comments, claim comment); no MCP GitHub tool called", "api_writes": "3 — fleet-write pr_create #27262 (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/27262/assignees os-support-ai, read back byte-identical, 10025 bytes); comment on #27091 (this report). Plus git push of claude/project-thread-oe80gc.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: named producer — HotCRM crm_manual + crm_admin_manual (hotcrm#2327); console book sidebar for a holder of both books · evidence: ui/apps/console/src/pages/book-nav.ts resolveBookTree (the console's local port that DocPage.tsx:165 and BookPage.tsx:52 render) still judges 'claimed' per book; on this branch the HotCRM fixture gives crm_manual → uncategorized [crm_admin_access, crm_admin_automation]. Fix shape: same optional 4th arg in the port, DocPage/BookPage pass useBookData().authoredBooks (not the portal set: its implicit per-package books claim docs with no packageId). Left out here: outside the claim's file surface and needs the console test surface · dedupe words: book-nav port Uncategorized sibling book, console DocPage sidebar Uncategorized, resolveBookTree port parity", "carrier: none · noted in PR Acceptance notes: GET /meta/book/:name/tree?package=X reads only X's books, so a book of another package that claims an X doc only through that doc's explicit group does not judge X's catch-all under ?package= (unscoped reads judge with every book)" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim amendment: two surface additions measured by the dev ·
domain:specseat 3 · sessionsession_01WGufzgbJhFoXYsLSe5WSPP· 2026-10-28T13:26Z · same claim (6468704954), same branchclaude/project-thread-oe80gc.packages/rest/src/meta-item-read-gate.tsand its test: the REST book-tree route now passes the sibling books toresolveBookTree. It was named in the claim as "every caller", and is listed here by path.ui/apps/console/src/pages/book-nav.ts(the console's port ofresolveBookTree), its callersDocPage.tsxandBookPage.tsx, and their tests. The port's docblock calls itself "a faithful local port" of the framework resolver. This PR makes that sentence false: the port still judges "claimed" per book, so the console sidebar keeps listing sibling-book pages under Uncategorized, which is this card's own symptom. A sentence this round makes false is fixed in the same round. The port takes the same optional fourth argument, and the pages pass the authored books (useBookData().authoredBooks), never the portal set, whose implicit per-package books claim docs that have nopackageId. One@object-uichangeset line in the root.changeset/.Every other line of the claim stands. Not governed.
objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27262 comment 6472271450
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-28T15:15Zb88519eb30origin/maingit merge-base --is-ancestor)Delivered: a book's synthetic Uncategorized group no longer collects docs that another registered book of the same package claims. The fix covers three places:
resolveBookTreein@objectstack/spec(an additive optionalbooksargument), the REST book-tree route, and the console's local port behind the DocPage and BookPage sidebars. A doc that no book claims still rolls up as before, and a single-book package gives byte-identical output. The §6.7 audience union is unmoved, so no reader reaches a page they could not reach before. On the HotCRM two-book fixture (hotcrm#2327), the User Manual shows no Uncategorized group and no admin pages.pm:dispatchedis removed here.