Repository navigation
[Decision] should the server-side app-nav filter also prune a doc menu entry by the docs audience, so a member who cannot read the doc never receives the entry — or is renderer-side pruning (objectui#10188) the whole answer? #19790
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 23, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsRuling: batch #217 item 2 · letter A · maintainer 「217 同意」 2026-09-23T10:40Z
Director seat, summon #28 (
session_01GLdRPcbaCBQCTvVmU6YEUY). Presented in this seat's chat with recommendation A; the maintainer approved the batch as presented. B ⛔ (a rule the server declares but only one renderer honours; entry names leak to non-readers through/meta/app, MCP and any other front end).Governing text: #19482 ruling addendum 5754645447 (「a
docentry the member may not read is not rendered」; 「a member with no readable page in it does not see the entry at all」); ADR-0046 §6.7 (docs content gated server-side at the doc / book reads); thefilterAppForUserWithReasondocblock (packages/rest/src/rest-server.ts:3231) — secrets belong to the server. Readings this summon: the filter's arms arerequiresService(:3263/:3276) andrequiredPermissions/ servability; ⛔ no audience arm; PR #19789 (thedocvariant) is Tier H awaiting the maintainer's merge. Prior rulings read: server-side,app-nav,filter,prune,menu,entry,docs,audience,member,read,receives,renderer-side (+20 more) → 308 hits; ADR-0076 D11, ADR-0029 D9.7, ADR-0056 D2, ADR-0076 D9, ADR-0127 D6, ADR-0005 Decision §5, ADR-0030 Decision §2, ADR-0035 Decision §4, ADR-0035 Decision §6, ADR-0036 Decision §1; thread: none — none rules on nav-entry pruning by doc audience.Ruled — A: the server-side nav filter prunes
docentries by the docs audiencefilterAppForUserWithReasongains an audience arm besiderequiredPermissions: adocentry naming a doc the caller may not read is pruned; abookentry whose readable subset for the caller is empty is pruned; the reason spelling follows the existing ones. It reuses the audience resolution the/meta/doc//meta/bookreads already apply — ⛔ no second resolver.- The renderer's pruning (Studio: a Markdown editor for
docitems (create / edit / preview, assign to abook) and rendering of thedocnavigation item on the app menu — objectui half of objectstack#19482 objectui#10188) stays — the server is the authority, the renderer is defence in depth. - Pins: a non-reader's
/meta/appcarries no such entry; a reader's does; a book entry with one readable page stays. - First readings for the dev, as the card's gaps: the per-request cost of resolving audiences during
/meta/app, and the traversal a book entry needs — report them; ⛔ no caching layer is invented in this card.
Execution
needs-user-decision→pm:blockedin this stroke,Blocked-by: objectstack-ai/objectstack#19482on the body (thedocvariant lands first; the unlock scan returns this card when #19482 closes). Lane by landing site:domain:spec→domain:cli(packages/rest),priority:p2,Clause-②: no(a permission-boundary tightening, no spec key).- added and removed
on Sep 23, 2026 objectstack-fleet commented
on Sep 24, 2026 ContributorAuthorMore actionsUnblock re-derivation:
pm:blocked→pm:queue· 2026-09-24T23:54ZActing on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「帮我排查一下 blocked 卡片,哪些需要更新状态帮我更新。」; where — the maintainer's chat with session
session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-24. ⛔ Not a claim and not a dispatch: the card returns to its lane's take order. Candidate surfaced bycheck-half-states.mjsH19 on this sweep.- Blocker: [Decision] admins author Markdown docs in the console and put them on the app menu — runtime
doc/bookauthoring surface and adocnavigation item (maintainer's stated need) #19482 closedcompletedon 2026-09-24. - Double-check ① — latest conversion comment
5793362670(ruling A) states the unlock itself: thedocvariant lands first, and the unlock scan returns this card when [Decision] admins author Markdown docs in the console and put them on the app menu — runtimedoc/bookauthoring surface and adocnavigation item (maintainer's stated need) #19482 closes. - Double-check ② — no merged PR has referenced this card since that comment.
- Re-derived, still owed: the ruled audience arm in the server-side nav filter; no commit on
mainnames [Decision] should the server-side app-nav filter also prune adocmenu entry by the docs audience, so a member who cannot read the doc never receives the entry — or is renderer-side pruning (objectui#10188) the whole answer? #19790.
State:
pm:blocked→pm:queue;domain:cli,priority:p2unchanged; no assignee.
Generated by Claude Code
- Blocker: [Decision] admins author Markdown docs in the console and put them on the app menu — runtime
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsClaim: PM loop round 9 (serial)
Session:session_01TnPAC1UsTGfHPXVUCL6iLn
Account:os-steve
Branch:claude/issue-19790-nav-doc-audience-prune
Worktree:objectstack-issue-19790
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/rest/src/rest-server.ts(filterAppForUserWithReason/filterAppForUser, the gate their two callers hand them, and the audience-resolution plumbing those callers need), its tests underpackages/rest/src/, any hand-writtencontent/docs/**line the change makes false, and one.changeset/19790-*.md.packages/spec/**(theDocNavItemSchemadescribe included) and every other package are read-only.packages/rest/CHANGELOG.mdandpackage.jsonbelong to the Version Packages PR. Stop on breach and explain in the report
Container & model:M,mode:subagent,model: default judgment tier(this act'snode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/rest/src/rest-server.tsanswered: no path-derived mandate). A maintainer-ruled permission-boundary arm in the server's nav filter. It must reuse the docs audience resolution, with no second resolver, fail closed, and report its per-request cost, so it gets the default judgment tier.
Clause-②: no
Thread-read: 5824249598
Serial constraints cleared:the maintainer's order in this seat's session (「继续派发」) and serial dispatch: R8 (#19922) reached its end state in this seat's record 5827511003 (released with a reason, pm:blocked on #20112), so no dev is in flight in this lane. The file lists of all 10 open PRs were read in this act. None touches packages/rest/**; the Version Packages PR #17076 owns the CHANGELOG and package.json versions, outside this surface. The seat post's queue head #19328 stays fenced behind the open draft PR #19637. The NORTH-STAR 优先级 3 pre-pick read at fa00ebf4: the domain:cli pm:queue holds no open P0/P1Taken 2026-09-25T05:52Z,
origin/mainfa00ebf4.Clause-②: no, per the ruling's execution line (5793362670): a permission-boundary tightening with no spec key. The server comes to honour the audience ruleDocNavItemSchemaalready declares, so no accepted input moves and no published surface grows. The dev posts no second claim; this comment is its identity.objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 19790,
"status": "done",
"branch": "claude/issue-19790-nav-doc-audience-prune",
"pr": "#20128",
"session": "session_01TnPAC1UsTGfHPXVUCL6iLn — subagent run inside the PM seat session (the parent's harness-stamped id); identity = the branch",
"premise_still_valid": true,
"summary": "filterAppForUserWithReason gains the ruled docs-audience arm: a type:'doc' entry is pruned when its doc is unreadable, when its book's own audience denies the caller, or (book alone) when none of the book's CLAIMED pages is readable; both callers (list + by-name) build one NavDocAudienceGate per request and hand it in, and the arm fails CLOSED (no gate, or a thrown book/doc read, drops every doc entry + warn). All verdicts come from one new private resolveDocsAudience (spec helpers audienceAllows / resolveDocAudiences / docAudienceAllows / resolveBookTree / resolveBookClaimedDocs / deriveImplicitPackageBook), which the five docs reads (/meta/book, /meta/book/:name, /meta/doc, /meta/doc/:name, /meta/book/:name/tree) now also use — no second resolver; their responses are byte-identical (1584-request BASE-vs-HEAD probe, 0 diffs). Cost: zero added reads when no app carries a doc entry; otherwise once per request (1 book read, 1 holdings resolution only if a set-gated book exists, 1 doc read only off the fast path or for a book-alone entry), per book-alone entry one resolveBookClaimedDocs traversal; no cache. Anonymous GET /meta/app is 401 before any read (pinned), so the arm has no anonymous path to cover.",
"tests": "New packages/rest/src/meta-app-nav-doc-audience.test.ts, 19 cases driving the real list / by-name / tree routes: non-reader has no gated doc/book/all-gated-book entry and the wire carries none of the names or labels; reader has them; a book with exactly one readable page stays (and the tree read agrees: [crm_intro]); an org book whose every CLAIMED page is gated is pruned although its tree serves an Uncategorized orphan; book+doc pruned when the book is gated; group/area collapse; list/by-name parity for non-holder and holder; requiredPermissions control; four fail-closed rows (book read throws, doc read throws, holdings unresolvable, no gate); fast path + existence (unwritten doc served, unknown book pruned, no holdings resolved); cost (no doc entry = 0 book/doc reads; two apps = 1 book read, 1 doc read, 1 holdings call); anonymous 401. Runs: file 19/19 green; @objectstack/rest local project Test Files 195 passed, Tests 3284 passed | 1 skipped; typecheck exit 0 (test layer 0 errors). ABLATION (fix committed first, cb800e2): scripts/ablation-replace.mjs --delete of the arm line, wrap mode, anchor x1 -> x0, blob 5c7dbf8f1015 -> f0b3c3cdd872 on disk; result Tests 12 failed | 7 passed — red: every non-reader row, the four fail-closed rows, non-holder parity, fast-path existence, two-app cost; green: holder, one-readable-page, requiredPermissions control, no-book-entry, no-doc-entry cost, anonymous (the expected direction). Restore proven: blob after restore == HEAD blob 5c7dbf8f1015, git diff HEAD empty; re-run 19/19 green. Subject is src-imported (./rest-server.js via vitest, no dist in the resolution path), so no rebuild/dist preflight applies. BYTE-IDENTITY of the docs reads: one-off probe (deleted, not committed) of BASE fa00ebf rest-server.ts vs this branch over 4 callers x 3 book sets x 3 failure modes x book/doc list+item+tree routes (both spellings, ?package=, ?include=content): compared=1584 diffs=0, statuses 200:752 401:168 403:28 404:240 500:396, 55 distinct responses, control non-holder 403 vs holder 200. Gates: 90 derived at f2bc219, all exit 0, --ran reconciliation a derived zero NOT-MEASURED; pnpm lint full union exit 0 at f2bc219.",
"mcp_calls": "0 — no MCP tool of any kind was called",
"api_writes": "3 — each through the fleet-write relay (repository_dispatch, executed as objectstack-fleet[bot]): (1) pr_create POST /repos/objectstack-ai/objectstack/pulls, draft forced, #20128, relay run 36104733901 success; (2) label-write POST /repos//issues/20128/assignees {os-steve}, relay run 36104793574 success, read-back MATCHES (assignee os-steve; labels documentation/size/l/tests/tooling are the path labeler's, untouched); (3) post-stamped POST /repos//issues/19790/comments (this report). Not REST: git push x4 of the branch. Everything else was reads.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · the docs READS fail OPEN when the book list read rejects: fetchAudienceBooks maps the fault to [] (read as "no set-gated book anywhere", so the fast path serves everything to an authenticated caller), and /meta/doc/:name maps a rejected corpus read to [] (the gated doc then reads as unclaimed, so org). Failing probe (one-off, stub protocol whose getMetaItems({type:book}) rejects, authenticated non-holder of crm_admin): GET /meta/doc/crm_admin_runbook 403 -> 200 WITH the body; GET /meta/doc lists it. Reachable with the shipped protocol: metadata-protocol getMetaItems rethrows every sys_metadata read failure except "unprovisioned" (#5532), so a store fault on the book read after the doc read succeeded serves the gated doc. Pre-existing at fa00ebf (byte probe: BASE and HEAD answer identically); this card kept the reads byte-identical by ruling, and the nav gate does NOT inherit it (readAudienceBooks / readDocCorpus report the fault; the reads still map it to []). Site: packages/rest/src/rest-server.ts fetchAudienceBooks + the /meta/doc/:name corpus read. · dedupe: fetchAudienceBooks fail open · docs audience book read failure · meta doc permissionSet leak · catch(() => []) audience gate",
"class: b · GET /meta/:type/:name/layers (and the deprecated ?layers=true, same helper serveMetaItemLayered) serves the app document with NO filterAppForUserWithReason pass — every arm bypassed, requiredPermissions included, not only the new audience arm. Probe (one-off, authenticated member holding neither finance.access nor crm_admin): /meta/app/crm/layers 200 carrying both the audience-gated doc entry and the requiredPermissions entry in code/overlay/effective; list and by-name routes prune both. Contract: content/docs/ui/apps.mdx requiredPermissions row 「The entry is never served: it is absent from the/metabody」 and the by-name caller comment 「the by-name route must not serve a nav entry the list route prunes, or reading the single-app JSON defeats the filter」. Seam: spec:AppSchema.navigation[].requiredPermissions + DocNavItemSchema audience -> runtime:packages/rest/src/rest-server.ts serveMetaItemLayered · dedupe: meta app layers requiredPermissions · layered view nav filter bypass · filterAppForUser layers",
"class: b · GET /meta/:type/:name/published serves the stored app document unfiltered (probe as above: 200 with both gated entries). Same contract text. Seam: spec:AppSchema.navigation[].requiredPermissions + DocNavItemSchema audience -> runtime:packages/rest/src/rest-server.ts GET /meta/:type/:name/published · dedupe: published app nav filter · meta published requiredPermissions leak",
"class: b · GET /meta/:type/:name/history and /diff return stored app versions with no nav filter — CODE-READ only, not driven (no probe was run for these two). Same contract text. Seam: spec:AppSchema.navigation[].requiredPermissions -> runtime:packages/rest/src/rest-server.ts GET /meta/:type/:name/history, /diff · dedupe: meta history app nav · metadata diff requiredPermissions",
"class: b · packages/runtime/src/domains/meta.ts handleMetadataRequest (HttpDispatcher /meta catch-all: list via protocol.getMetaItems, item via getMetaItem) applies no app-nav filter at all where that door answers — CODE-READ; which compositions route /meta/app to it rather than to RestServer is NOT MEASURED (the REST routes shadow it where RestServer is mounted). Seam: spec:AppSchema.navigation -> runtime:packages/runtime/src/domains/meta.ts handleMetadataRequest · dedupe: http-dispatcher meta app filter · runtime meta domain requiredPermissions",
"NOT MEASURED · MCP list_metadata / describe_metadata: only the names live in this tree (packages/spec/src/system/constants/platform-tool-names.ts); the implementations are elsewhere, so whether they serve app nav past the filter cannot be read here · carrier: 承接者:无 · noted, not filed",
"carrier: 承接者:无 · noted in the PR Acceptance notes, not filed — without ?package=, /meta/book/:name/tree resolves over the env-wide corpus, so every book tree lists every doc it does not claim under Uncategorized (the resolver's documented "nothing is ever dropped" rule); it is why the nav arm counts claimed pages, not tree entries"
],
"gates": {
"node scripts/check-adr-0087-registration.mjs --base origin/main": "exit 0 · ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
"node scripts/check-adr-0087-registration.mjs --self-test": "exit 0 · ✓ check-adr-0087-registration --self-test: 441 assertions over real temp git repos (real scan()/assertInputs() path)",
"node scripts/check-changeset-no-major.mjs --base origin/main": "exit 0 · ✓ This diff introduces nomajorbump.",
"node scripts/check-changeset-no-major.mjs --self-test": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
"node scripts/check-ci-filter-parity.mjs": "exit 0 · OK: all 184 declared cross-package glob(s) (131 unique) are covered bycoreorcrosspkg, everycrosspkgentry still covers one, and thetestjob'sif:still nam",
"node scripts/check-closing-keyword-parity.mjs": "exit 0 · check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 9546 tracked file(s), ",
"node scripts/check-closing-keyword-parity.mjs --self-test": "exit 0 · ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
"node scripts/check-comment-mask-adoption.mjs": "exit 0 · OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 s",
"node scripts/check-comment-mask-adoption.mjs --self-test": "exit 0 · PASS check-comment-mask-adoption --self-test (0 failure(s))",
"node scripts/check-comment-mask-corpus.mjs": "exit 0 · ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 7159 files, 0 disagree, 0 unparseable, 78.6s (comparator self-test: 26 cases pass).",
"node scripts/check-doc-frontmatter.mjs": "exit 0 · ✓ check-doc-frontmatter: 2 content root(s) verified, each against its own floor — content/docs 406, content/blog 3.",
"node scripts/check-doc-frontmatter.mjs --self-test": "exit 0 · ✓ check-doc-frontmatter --self-test: 99 assertions — the card's own description observed failing with the parser's message and the FILE line, every other violation kind o",
"node scripts/check-doc-route-spelling.mjs --advisory": "exit 0 · ✓ route-spelling guard (advisory): population clean — every shape-matched literal spells its ledger row.",
"node scripts/check-doc-route-spelling.mjs --self-test": "exit 0 · ✓ check-doc-route-spelling self-test: extraction tidy-up, the variant relation (plural + pinned lexicon, no prefix heuristic), walk wiring (releases/ and node_modules/ ou",
"node scripts/check-docs-section-name.mjs": "exit 0 · so it is carried by --self-test rather than by this corpus.",
"node scripts/check-docs-section-name.mjs --self-test": "exit 0 · ✓ check-docs-section-name self-test: 85 cases pass (real temp trees on disk; both historical misses reproduced as RED, both arms driven RED, the duplicate-key and syntax-",
"node scripts/check-empty-changeset.mjs --base origin/main": "exit 0 · ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
"node scripts/check-empty-changeset.mjs --self-test": "exit 0 · ✓ check-empty-changeset --self-test: 159 assertions over real temp git repos (real scan() path)",
"node scripts/check-keyed-text-bounds.mjs": "exit 0 · ✓ check:keyed-text-bounds: 112 .object.ts files under packages/ + apps/* + examples/** (walk is repo-wide; 0 outside), 117 object declarations, 250 declared index ent",
"node scripts/check-keyed-text-bounds.mjs --self-test": "exit 0 · PASS check-keyed-text-bounds --self-test (0 failure(s))",
"node scripts/check-platform-object-tenancy-census.mjs": "exit 0 · ✓ platform-object tenancy census matches the tree: 84 platform-namespace objects, 58 in the machinery's reach, 26 outside it, every exclusion explained by a declaration o",
"node scripts/check-platform-object-tenancy-census.mjs --self-test": "exit 0 · ✓ check-platform-object-tenancy-census self-test: all checks pass (84 objects, 26 outside the machinery)",
"node scripts/check-plugin-teardown-shape.mjs": "exit 0 · ✓ check:plugin-teardown-shape: 69 Plugin implementation(s) across 6590 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) sits ",
"node scripts/check-plugin-teardown-shape.mjs --self-test": "exit 0 · ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every roster name",
"node scripts/check-registry-log-declared.mjs": "exit 0 · OK: 74 vitest-running package(s) walked, 9 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
"node scripts/check-registry-log-declared.mjs --self-test": "exit 0 · self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.",
"node scripts/check-rest-log-spy-declared.mjs": "exit 0 · OK: 30 of 197 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
"node scripts/check-rest-log-spy-declared.mjs --self-test": "exit 0 · check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s).",
"node scripts/check-section-landing-index.mjs": "exit 0 · ✓ check-section-landing-index: 8 section index block(s) enumerate their meta.json pages, in order, both directions (ai, api, automation, data-modeling, kernel, permission",
"node scripts/check-section-landing-index.mjs --self-test": "exit 0 · ✓ check-section-landing-index --self-test: 31 assertions over synthetic inputs and a temp fixture (real judge()/run() path); every limb -- both shapes in sync, missing pa",
"node scripts/check-system-context-census.mjs": "exit 0 · check-system-context-census: OK — 112 elevation read sites in 20 packages across 45 files, living in 93 symbol(s); the page cites 106 symbol(s) against 106 required, over",
"node scripts/check-system-context-census.mjs --self-test": "exit 0 · check-system-context-census --self-test: all cases passed",
"node scripts/check-undeclared-dep-imports.mjs": "exit 0 · ✓ check:undeclared-dep-imports: 81 workspace packages under packages/ + apps/** + examples/, 2592 non-test src files, 2107 @objectstack/* specifiers (0 assembled, not",
"node scripts/check-undeclared-dep-imports.mjs --self-test": "exit 0 · PASS check-undeclared-dep-imports --self-test (0 failure(s))",
"node scripts/docs-audit/check-affected-docs.mjs": "exit 0 · ✓ affected-docs self-test: 605 cases pass.",
"node scripts/docs-audit/check-drift-comment.mjs": "exit 0 · ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
"node scripts/pm/release-rehearsal-clone.mjs --self-test": "exit 0 · ✓ self-test passed",
"pnpm --filter @objectstack/lint run check:doc-formula-expressions": "exit 0 · ✓ check:doc-formula-expressions (field-level*When, #11407): 14 predicate(s) on a statically determinable field layer judged clean; 6 skipped as undeterminable.",
"pnpm --filter @objectstack/lint run check:doc-security-posture": "exit 0 · ✅ 27 ObjectSchema.create example(s) in 227 marked block(s) across 248 prose file(s) in 2 root(s) carry an os validate-clean security posture",
"pnpm --filter @objectstack/spec run check:docs": "exit 0 · ✅ 226 generated files in sync with packages/spec",
"pnpm --filter @objectstack/spec run check:duration-unit-keys": "exit 0 · ✓ check:duration-unit-keys — 204 unit-declaring numeric key(s) across 2589 source file(s) all carry their unit in the key name (or in a siblingunit, or under a declare",
"pnpm --filter @objectstack/spec run check:empty-state": "exit 0 · ✓ all classified (2 closed, 2 open, 4 output, 9 scope)",
"pnpm --filter @objectstack/spec run check:liveness": "exit 0 · ✓ packages/spec/liveness/state-counts.md is current — the same 40 row(s), no count column left in the README.",
"pnpm --filter @objectstack/spec run check:skill-examples": "exit 0 · ✅ 259 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them",
"pnpm --filter @objectstack/spec run check:strictness-ledger": "exit 0 · ✓ docs/audits/2026-07-unknown-key-strictness-ledger.counts.md is current — 453 site(s) measured, 1 authorable strip site(s) left.",
"pnpm --filter @objectstack/spec run check:variant-docs": "exit 0 · ✓ variant/doc gate: 18 discriminated union(s) — 8 governed (every variant mentioned in a bound doc), 10 exempt.",
"pnpm --filter @objectstack/spec run check:yaml-examples": "exit 0 · ✅ 18 tagged YAML example(s) across 1 file(s) validate against their declared live spec schemas",
"pnpm check:authz-resolver": "exit 0 · ✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.",
"pnpm check:changeset-gate-self-tests": "exit 0 · ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #",
"pnpm check:corpus-claim-drift": "exit 0 · check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.",
"pnpm check:cross-package-test-inputs": "exit 0 · OK: 29 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3 on",
"pnpm check:dispatcher-error-vocabulary": "exit 0 · [#15723] the ARGUMENT POSITION of new APIError( … ) and APIError.from( … ) IS now in this gate's population — theapierrorargshape.APIError.fromcopies the record's",
"pnpm check:doc-anchors": "exit 0 · ✅ check-doc-anchors: 376 internal #fragment link(s) across 411 source file(s) all resolve to a real heading",
"pnpm check:doc-authoring": "exit 0 · ✓ doc authoring guard: sibling-package prose ids hold the baseline — 815 pinned site(s) across 231 file(s), 91983 string(s) read in 1253 parsed source(s), no growth, no b",
"pnpm check:docs-audit-scope": "exit 0 · ✓ scope injection is live: the workflow audits the list handed in as args.handwritten, and refuses an invocation that hands in no scope at all.",
"pnpm check:docs-redirects": "exit 0 · check-docs-redirects: OK (apps/docs/redirects.mjs: 98 entries -- 95 page destination(s) resolved against content/docs, 3 wildcard destination(s) resolved to a directory, ",
"pnpm check:docs-single-h1": "exit 0 · ✓ check-docs-single-h1: 406 page(s) under content/docs/ carry no body-level#heading (0 subtree(s) excluded, see --list).",
"pnpm check:docs-spec-enumerations": "exit 0 · OK the hand-written spec enumerations agree with packages/spec/package.json -- 17 subpath(s) held ORDERED in content/docs/deployment/troubleshooting.mdx; 15 protocol name",
"pnpm check:docs-transcript-drift": "exit 0 · ✓ check-docs-transcript-drift: 4 declared transcript value(s) across 406 page(s) under content/docs/ equal what the registry derives today, and no undeclared block quotes",
"pnpm check:driver-memory-census": "exit 0 · check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consumers". This gate polic",
"pnpm check:dts-closure": "exit 0 · check-dts-closure: 73 built package(s) swept - 167/167 declared declaration file(s) present across 73 package(s); 0 built package(s) declare no declaration entry point an",
"pnpm check:dual-build-cjs-loads": "exit 0 · ✓ check:dual-build-cjs-loads — 105 published require entry point(s) across 67 package(s) load; 660 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree",
"pnpm check:engine-double-contract": "exit 0 · check-engine-double-contract: OK — 902 pinned, 133 in the DEBT ledger, 3 exempt.",
"pnpm check:gitlink-declared": "exit 0 · check-gitlink-declared: OK (9546 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
"pnpm check:issue-citations": "exit 0 · ✅ check-issue-citations --self-test: grammar narrowed, four 404 causes kept apart, both board strategies agree, diff scope red AND green, scope contract pinned (73 cases,",
"pnpm check:lean-entry-closure": "exit 0 · ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
"pnpm check:logger-receiver-detach": "exit 0 · OK every log channel keeps its receiver: 2860 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
"pnpm check:nul-bytes": "exit 0 · check-nul-bytes: OK (scanned 9539 text file(s) -- 9539 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).",
"pnpm check:objectql-double-limit": "exit 0 · OK ObjectQL doublelimitconformance holds: 410 double(s) graded, 212 apply the caller's bound or refuse it loudly.",
"pnpm check:objectui-changeset": "exit 0 · ✓ objectui-range --self-test: all checks passed",
"pnpm check:org-identifier": "exit 0 · check-org-identifier: OK (2882 author-facing source file(s), 17 session binding(s) resolved, no removed session.tenantId alias).",
"pnpm check:page-declaration-shape": "exit 0 · check-page-declaration-shape: OK — 34 page entries across 2872 sources under packages/, examples/, apps/ all reach the kernel through a discoverable declaration (:", "pnpm check:pm-changeset-deadline-census": "exit 0 · ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, the verdict truth ta", "pnpm check:published-files": "exit 0 · ✓ check:published-files — 70 publishable package(s) of 81 workspace member(s) declare afileswhitelist that covers every entry point plus CHANGELOG.md and admits no te", "pnpm check:published-readme-links": "exit 0 · ✓ check:published-readme-links — 178 outbound link(s) across 61 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 v", "pnpm check:query-options-erasure": "exit 0 · ✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ is c", "pnpm check:react-page-adapter-contract": "exit 0 · ✓ check-react-page-adapter-contract: 21 app-showcase page module(s) + 1 content/docs react-page sample(s) (from 392 doc file(s), 1995 fenced block(s)) — every adapter que", "pnpm check:refd-timer-probe": "exit 0 · OK check-refd-timer-probe: 7154 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.", "pnpm check:role-word": "exit 0 · check-role-word: OK, no new occurrences of the reserved word.", "pnpm check:route-envelope": "exit 0 · ✓ Express-style response modules — 4 module(s) discovered and audited (walked, not enumerated — #9937), 12 hand-built body/bodies (count reported, NOT pinned): 2 conforma", "pnpm check:skill-identifier-liveness": "exit 0 · check-skill-identifier-liveness OK — Leg 1: 457 citation(s) over 53 published file(s) checked against 107876 implementation word tokens (0 ledgered exemption(s)); Leg 2: ", "pnpm check:slot-lookup": "exit 0 · ✓ slot-lookup ratchet holds: 106 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered bypnpm li",
"pnpm check:sourcemap-no-sources-content": "exit 0 · check-sourcemap-no-sources-content: 69 built package(s) swept - 508 map(s), none embed source text.",
"pnpm check:test-source-alias": "exit 0 · check-test-source-alias OK — 74 packages with tests scanned; 61 registered as still resolving a workspace dep throughdist/; 50 published subpath(s) resolved through ev",
"pnpm check:tier-file-adoption": "exit 0 · OK: 81 workspace package(s) walked, 70 nightly-tier test file(s) on disk (70 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
"pnpm check:type-check-coverage": "exit 0 · check-type-check-coverage: OK — 77/81 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors, https://github.com/objectstack-ai/objec",
"pnpm check:type-check-debt": "exit 0 · check-type-check-coverage --re-measure: OK — 4 ledger entr(ies) re-measured in 21.3s, 53 raw tsc error(s) total, none above its recorded number.",
"pnpm check:vendor-version-stamps": "exit 0 · check:vendor-version-stamps: OK — 7543 file(s) scanned.",
"pnpm check:watch-hint-literal": "exit 0 · ✓ check-watch-hint-literal: 71 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 47, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -",
"pnpm check:where-matcher": "exit 0 · ✓ where-matcher conformance holds: 433 matcher(s) discovered, 433 answer the combinator battery correctly or refuse it loudly (282 refuse).",
"pnpm lint (full union: eslint . --no-inline-config) @ f2bc219": "exit 0 · no problems printed, 107s — the whole union, not a narrowing",
"pnpm --filter @objectstack/rest run typecheck @ cb800e2": "exit 0 · check:test-typecheck: OK — 0 file(s) / 0 error(s) / 0 pinned signature(s) (tsc --noEmit green first)",
"pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 @ cb800e2": "exit 0 · Test Files 195 passed (195) · Tests 3284 passed | 1 skipped (3285)",
"pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/meta-app-nav-doc-audience.test.ts @ cb800e2 (post-ablation restore)": "exit 0 · Tests 19 passed (19)",
"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran-exit.txt @ f2bc219": "exit 0 · ✓ dispatch-gates --ran: 90 derived famil(ies) accounted for — 90 run, 0 NOT-MEASURED (a DERIVED zero — all 90 recorded an exit code and none of them is 3)"
},
"line_budget": "n/a",
"deviations": [
"PM mechanism assumption 3 refined by measurement: a book entry's "readable subset" is its readable CLAIMED pages (resolveBookClaimedDocs, the membership resolveDocAudiences uses), not the entries of its tree — resolveBookTree appends every unclaimed doc as a synthetic Uncategorized group, so counting tree entries keeps nearly every book whenever any doc is readable (pinned: the ops tree serves crm_intro under Uncategorized while nav_ops_book is pruned). book+doc: pruned when either the book's own audience denies (tree read 401/403, rest-server.ts book-tree admitsBook check) or the doc is unreadable; a readable doc in an unreadable book is possible via the docAudienceAllows union (book.zod.ts) and is still pruned. Existence: a doc absent from the corpus is served (the resolver's own org default for a doc it has no entry for); a book naming nothing resolves to an implicit book with no page and is pruned.",
"Conflict in the order, stated not silently chosen: SUGGESTED ROUTE says the gate "degrades exactly as the reads do", and also "must not fail open". Measured: the reads fail OPEN on a rejected book read (probe, finding 1). The gate follows "must not fail open": a thrown book or doc read drops every doc entry of that response and logs a warn; the reads keep their answers.",
"Reuse went one step wider than the doc-list + book-tree pair: the /meta/book list and /meta/book/:name gates also moved onto resolveDocsAudience.admitsBook, so all five docs reads and the nav arm share one resolution; byte identity proven by the 1584-request probe and the existing read suites.",
"Test file is meta-app-nav-doc-audience.test.ts (sibling naming, beside meta-app-nav-servability-gate.test.ts) rather than the gates-from-pm expected path nav-doc-audience.test.ts; re-derivation on the real diff yields the same 90 commands.",
"Two gates first answered PREREQUISITE NOT MET (exit 3, nothing measured): check:skill-examples (client-react had no .d.ts) and check:dual-build-cjs-loads (workspace unbuilt). Re-run exit 0 after building what they read (client + client-react; then turbo build 73/73 tasks, 51 cached). Three dist-reading gates that had run before the full build (dts-closure, lean-entry-closure, published-files) were re-run after it, exit 0.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer; AGENTS.md and the order win — commits carry the model-free pair, the PR body ends with the session-URL footer.",
"Commit 3b67a44 has a wip(rest) subject (the WIP push AGENTS.md asks for before a minutes-long build); squash landing folds it.",
"Anonymous: measured, GET /meta/app answers 401 before any read, so the arm deliberately has no anonymous branch (order asked to measure and say).",
"Other doors (order point 5) are listed as findings rather than fixed, per the order; three of them driven by a one-off probe, two code-read, MCP NOT MEASURED."
],
"files_changed": [
"packages/rest/src/rest-server.ts — DocsAudience + NavDocAudienceGate types; resolveDocsAudience; readMetaList / readAudienceBooks / readDocCorpus / audienceBooksOf / docCorpusOf (fetchAudienceBooks keeps its [] degradation); resolveNavDocAudience + docNavEntries + navTarget; the arm in filterNav; a 5th gate parameter on filterAppForUser / filterAppForUserWithReason, passed by both callers; the five docs read gates rebuilt on the shared helper",
"packages/rest/src/meta-app-nav-doc-audience.test.ts — new, 19 cases",
"content/docs/ui/apps.mdx — Audience bullet: the server prunes the entry itself; book+doc and claimed-pages wording",
".changeset/19790-nav-doc-audience-prune.md — @objectstack/rest patch",
"diff vs merge base fa00ebf: 4 files, +853 / -95 (dispatch-gates reading)"
],
"ci": "in_progress at head f2bc219 — one snapshot read after pr_create: 32 check runs, 12 success, 2 skipped, 18 in_progress, 0 failing. Not waited on (os-dev: CI convergence is the PM's).",
"cleanup": "worktree ../objectstack-issue-19790 removed after node_modules removal (git worktree remove, no --force, exit 0; the tree was clean and at origin f2bc219). One-off probe files were deleted before each commit and never committed. No server, watcher or background job was started."
}
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsIn-seat review: ACCEPT, PR #20128 at head
f2bc219be0domain:cliexecution PM seat #6024 · sessionsession_01TnPAC1UsTGfHPXVUCL6iLn· 2026-09-25T07:00Z · reviewed on GitHub andorigin/mainfa00ebf4, against the ruling5793362670Review checklist
- PR form: draft, base
main, assigneeos-steve. The first line isFixes #19790, the only closing binding, and the second is the claim'sClause-②: no. One footer, no model identifier. - Scope: 4 files (+853/−95), all inside the claim's surface:
rest-server.ts, one new test file,content/docs/ui/apps.mdxand one changeset.packages/spec/**is untouched. - The change, read in the diff:
filterAppForUserWithReasongains the ruled arm besiderequiredPermissionsas a bare entry-levelcontinue:if (e.type === 'doc' && (!docAudienceGate || !docAudienceGate(e))) continue;. It fails closed: no gate, nodocentry.- Both callers, list
:6347and by-name:7367, build ONE gate per request withresolveNavDocAudienceand pass it in. This is the Nav gating cannot express "prune when the destination cannot serve" —enable.apiEnabledis never consulted byfilterAppForUser#7912 parity. - The verdicts come from one private
resolveDocsAudience, spelled only in the spec's helpers (audienceAllows,resolveDocAudiences,docAudienceAllows,resolveBookTree,resolveBookClaimedDocs,deriveImplicitPackageBook). The five docs reads now answer from it too, so there is no second resolver (ruling point 1). - The seat checked the one semantic seam: the new
docReaderpasses an absent audience todocAudienceAllows, which itself defaults toorg(book.zod.ts). That is the doc list's former explicitorgfallback.
- Entry semantics, falsified and refined by the dev:
- A book's readable subset is its readable CLAIMED pages (
resolveBookClaimedDocs), not tree entries.resolveBookTreeappends every unclaimed doc as a synthetic Uncategorized group, so counting tree entries would keep nearly every book. book+docis pruned when either the book's own audience or the doc denies.- Existence: an unknown book resolves to an empty implicit book and is pruned; a doc absent from the corpus takes the resolver's own
orgdefault.
- A book's readable subset is its readable CLAIMED pages (
- Cost (ruling point 4, measured, no cache):
- no
docentry in the response: 0 added reads; - otherwise, once per request: 1 book read, 1 holdings resolution only when a set-gated book exists, and 1 doc read off the fast path or for a book-alone entry;
- one claimed-pages traversal per book-alone entry.
- no
- Anonymous:
GET /meta/appanswers401before any read (pinned), so the arm needs no anonymous branch. - Tests:
meta-app-nav-doc-audience.test.tshas 19 cases on the real list, by-name and tree routes: non-reader, reader, one readable page, claimed versus Uncategorized, book+doc, group and area collapse, list/by-name parity, therequiredPermissionscontrol, four fail-closed rows, the fast path, cost and401.- Ablation of the arm: 12 red / 7 green in the expected direction; restored, 19/19.
- The package suite passes (195 files, 3284 tests).
- Byte-identity of the five docs reads, BASE versus HEAD: 1584 requests, 0 diffs.
- Gates: 90/90
--ranatf2bc219b(0 NOT-MEASURED), pluspnpm lint(the full union). - Changeset:
@objectstack/restpatch. The server honours whatDocNavItemSchemaalready declares, per the ruling'sClause-②: no. The prose matches the code sentence by sentence, including fail-closed and cost. - Docs: the
apps.mdxAudience bullet now says the server prunes the entry itself, and adds the book+doc and claimed-pages rules. - Governed / size:
check-governed-merges --pr 20128: NOT governed, +853/−95. - Clause-② gate: neither limb hits. There is no
packages/spec/src/**in the diff, and the claim declaresno.
A PM order conflict the dev resolved correctly, recorded in public: the order said the gate "degrades exactly as the reads do" and also "must not fail open". The dev measured that the reads fail OPEN on a thrown book read, and followed "must not fail open". That was the seat's inconsistency, and the dev's choice is the ruling's intent.
Out-of-scope findings, one row each
- class (a) → filed [finding] the docs reads fail OPEN when the book list read throws: fetchAudienceBooks maps the fault to [], so a permission-set-gated doc is served (200, with its body) to an authenticated non-holder #20129: the docs reads fail OPEN when the book list read throws.
fetchAudienceBooks/ the/meta/doc/:namecorpus read map the fault to[], and a set-gated doc is served200with its body. This is pre-existing and not inherited by the new arm. - class (b) → filed [finding] GET /meta/app/:name/layers, /published, /history and /diff serve the app document with no nav filter, so entries pruned by requiredPermissions and the docs audience reach every member #20130:
/meta/:type/:name/layers(and?layers=true) and/publishedwere driven,/historyand/diffcode-read, and the runtime/metacatch-all code-read. All serve the app document with no nav filter, bypassingrequiredPermissionsand the new arm alike. - NOT MEASURED, not filed: whether MCP
list_metadata/describe_metadataserve app nav past the filter. The implementations are outside this tree. - Noted, not filed: without
?package=, a book tree lists every unclaimed env doc under Uncategorized. This is the resolver's documented rule, and it is why the arm counts claimed pages.
- PR form: draft, base
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsLANDED —
bc80e16260597d52b062b7e1ee810c7b9a99aed2(PR #20128), verified by CONTENT onorigin/maindomain:cliexecution PM seat #6024 · sessionsession_01TnPAC1UsTGfHPXVUCL6iLn· 2026-09-25T07:24Zprobe before ( 6780e34a)origin/mainthe arm if (e.type === 'doc' && (!docAudienceGate || !docAudienceGate(e))) continue;inpackages/rest/src/rest-server.ts0 1 private async resolveDocsAudience((the one resolution the reads and the arm share)0 1 private async resolveNavDocAudience((the per-request gate both callers build)0 1 untouched control: const servabilityGate = await this.resolveNavServability(p, environmentId) ?? undefined;2 2 meta-app-nav-doc-audience.test.ts+.changeset/19790-nav-doc-audience-prune.mdin the tree (git ls-tree origin/main)0 2 content/docs/ui/apps.mdx: "The server applies this rule to the entry itself"0 1 -
Shape: 2 parent fields, so this is a squash. The pre-merge head
f2bc219bis ⛔ not an ancestor (exit 1); the control leg6780e34ais an ancestor (exit 0). The commit carries the enqueue instant 2026-09-25T07:09:34Z. -
How it landed: through the fleet-write relay ops
pr_ready+automerge_enable, asobjectstack-fleet[bot]:- ready flip at 2026-09-25T07:08:24Z;
- arm at 2026-09-25T07:08:28Z;
added_to_merge_queueat 2026-09-25T07:09:34Z;- merged at 2026-09-25T07:24:06Z, with no queue ejection.
-
What changed for callers:
GET /meta/appandGET /meta/app/:namenow leave out atype: 'doc'navigation entry the caller may not read:- a doc whose effective audience denies them;
- a book whose own audience denies them, or none of whose claimed pages they may read.
It fails closed on a read fault. The five docs reads answer from the same resolution, and what they return is unchanged. The renderer's pruning (objectui#10188) stays as defence in depth (ruling point 2).
-
Card: closed by
Fixes #19790. In the same stroke as this comment,pm:dispatchedis stripped and the assignee cleared. -
Carried out, ⛔ not this card's: [finding] the docs reads fail OPEN when the book list read throws: fetchAudienceBooks maps the fault to [], so a permission-set-gated doc is served (200, with its body) to an authenticated non-holder #20129 (the docs reads fail open on a book read fault) and [finding] GET /meta/app/:name/layers, /published, /history and /diff serve the app document with no nav filter, so entries pruned by requiredPermissions and the docs audience reach every member #20130 (the
/layers,/published,/history,/diffapp doors bypass the nav filter). Both were filed from this round and await triage.
-
- added a commit that references this issue
on Sep 28, 2026
Ruled: 5793362670 · letter A · 2026-09-23T10:42Z
Blocked-by: #19482
Filing gate: ② (a decision only the maintainer can make — a permission-boundary change). Reader: the director seat's decision batch; after the ruling, the
domain:cliseat (packages/rest) if A. ⛔ Not a claim. Split from #19482 (PR #19789): this card carries the server-side half of the audience rule; #19482 keeps the schema, whose describe states the rule for the renderer.维护者速读
事情:#19482 给应用菜单加了「文档」项(指向一本书或一篇文档),裁决的追加条款写的是:看不了这篇文档 / 这本书里一页都看不了的成员,菜单上就不出现这个条目。今天的实现里,文档内容在服务端是有门的(
/meta/doc、/meta/book读取按受众拦),但服务端下发菜单时(filterAppForUserWithReason)只按requiredPermissions、服务是否存在、对象是否可用来剪,没有按文档受众剪。所以一个看不了文档的成员,拿到的/meta/app里仍有这个条目(能看到条目名、书名/文档名,点进去看不到内容);「不显示」全靠前端渲染器(objectui#10188,尚未上线)去剪。选项:
requiredPermissions同一处、同一种做法)。另开一张执行卡,落packages/rest。荐 A。请回一个字母:A / B。
os-decision-facets
requiredPermissions已经在服务端剪;文档条目走另一条路(只靠前端)会让「谁看得到菜单项」有两套答案,每个消费方(控制台、MCP、第三方前端)各自再实现一遍。A 一处兑现,B 让契约的一半只在一个渲染器里成立。⇒ A。doc/bookauthoring surface and adocnavigation item (maintainer's stated need) #19482:管理员写文档并放进菜单);受众剪枝是裁决追加条款里明写的行为。泄露的只是条目名/书名/文档名,不是内容 ⇒ 真实但不紧急。/meta/app或写另一个前端时会拿到它「不该看到」的条目 —— 声明而服务端不兑现,正是「声明即强制」要避免的形状。⇒ A。Governing text: #19482 ruling addendum
5754645447(「adocentry the member may not read is not rendered」; 「a member with no readable page in it does not see the entry at all」); ADR-0046 §6.7 (docs content gated server-side at the doc/book reads); thefilterAppForUserWithReasondocblock inpackages/rest/src/rest-server.ts.Prior rulings read: audience,navigation,doc,book,nav,filter → 37 hits; ADR-0057 D10, ADR-0021 D2, ADR-0021 D3, ADR-0029 D7, ADR-0030 Decision §2, ADR-0030 Decision §6 — each read, none rules on nav-entry pruning by doc audience (notification / dataset / ownership scopes); thread: this card is new; repo: objectstack-ai/objectstack
推荐:A。自检:只看①选 A;②③④ 是否翻转:否。回退 B。置信缺口:⛔ 未量
resolveDocAudiences在菜单过滤时拿到调用者权限集的成本(每次/meta/app都要算);⛔ 未量 book 条目「一页都看不了」的剪法在服务端要遍历多少页。Evidence (measured by the #19482 round and its at-tier contract review on PR #19789, head
f9baf5cb3f)filterAppForUserWithReason(packages/rest/src/rest-server.ts) prunes onrequiredPermissions,requiresService, object servability and empty groups — no audience arm./meta/doc,/meta/book,/meta/book/:name/tree(ADR-0046 §6.7).docitem renders at all — the leak is latent until it does, and then only entry names leak.Dedupe
REST list of the 435 most recently updated issues (open + closed) grepped locally:
filterAppForUserWithReason0 ·nav … audience1 (#19482, the parent) · lit controlrequiredPermissions3 (#18159, #19186, #19503 — none this question).