Repository navigation
feat(plugin-hono-server,plugin-sharing): /auth/me/permissions answers hasAnyQueue from the ownership-queue service - #28654
Conversation
… hasAnyQueue from the ownership-queue service The console's list toolbar needs to know, once per session, whether the caller's organization has at least one ownership queue (declared and not switched off in the activation ledger, or a sys_queue row). A plain member may not read sys_queue, so the server answers with one boolean and discloses nothing else. - OwnershipQueueService.hasAnyQueue(organizationId) reuses the registry-first read and the ledger filter queuesOf already uses. - /auth/me/permissions carries hasAnyQueue on every authenticated body; no service, no method, or a throw answers false. Co-Authored-By: Claude <noreply@anthropic.com>
…ue through the real HTTP door Co-Authored-By: Claude <noreply@anthropic.com>
…AnyQueue Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…caller's limit Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 18 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cc412d1070f92bcf6e6df22526839b68382fd116 && git checkout cc412d1070f92bcf6e6df22526839b68382fd116
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dd6349ecfa00cbd3eda805aed9f0f3958d5f1c65 3563cd56e990a0e8bb99fb8e72072f7c9121c5ec && git checkout -B drift-repro dd6349ecfa00cbd3eda805aed9f0f3958d5f1c65 && git merge --no-ff 3563cd56e990a0e8bb99fb8e72072f7c9121c5ec
node scripts/docs-audit/affected-docs.mjs --json dd6349ecfa00cbd3eda805aed9f0f3958d5f1c65
|
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH 席位核对 · ACCEPT · PR #28654 · 2026-11-01T12:27ZHead-sha: 维护者速读(席位终稿)
核对六项
|
Requested by Leehom · project thread
Part of #28508 (the server half; the strict spec response schema for the carrier is the other half, see Acceptance notes)
Clause-②: yes
Metadata-protocol: none
维护者速读
GET /auth/me/permissions的每个已登录响应多一个布尔hasAnyQueue,表示当前用户所在组织是否至少有一个归属队列(声明的、未被启用台账关掉的队列,或本组织的sys_queue行)。答案由 plugin-sharing 的归属队列服务新方法hasAnyQueue(organizationId)在系统上下文里算出。sys_queue是 403,自己判断不了;现在服务端只回答这一个布尔,不给名字、不给数量。sys_queue/sys_queue_member的读权限(测试钉住 403)。服务未注册、无此方法或抛错都答false,且不影响响应其余部分。回滚即 revert 本 PR。packages/plugins/plugin-hono-server/src/current-user-endpoints.ts(organizationHasAnyQueue与三个响应分支)、packages/plugins/plugin-sharing/src/ownership-queue-service.ts(hasAnyQueue)、packages/verify/src/me-permissions-has-any-queue.test.ts(真实 HTTP 门五个用例)。pnpm --filter @objectstack/verify exec vitest run src/me-permissions-has-any-queue.test.ts,预期 5 passed:只有sys_queue行 → true,只有声明队列 → true,声明队列被关 → false,都没有 → false,服务未注册 → false,且每例成员读/data/sys_queue仍是 403。CONTRACT_REVIEW_TIER完成后入队)。What changes
OwnershipQueueService.hasAnyQueue(organizationId)(@objectstack/plugin-sharing).trueexactly when some queue name would resolve in that organization: a declared queue the activation ledger leaves on, or asys_queuerow of the organization that does not sit under a declared queue's name. It reusesactiveDeclaredQueues()andauthoredQueueNames(), the readsqueuesOfalready makes, so the registry-first rule and the ledger filter stay in one place; there is no second resolver. It is a method on the class only: the spec contractIOwnershipQueueServiceis not edited in this PR (see Acceptance notes).GET /auth/me/permissionscarrieshasAnyQueue: boolean(@objectstack/plugin-hono-server) on every authenticated body:mePermissionsBody, now seven required grant keys, so tsc refuses a caller that drops it);false, since a fault vouches for nothing it did not finish.{ "authenticated": false }only, and the service is not asked.organizationHasAnyQueue): the kernel locator underOWNERSHIP_QUEUE_SERVICE, for the caller'stenantId(the organizationqueuesOfis asked for on the sitting-record path). No service, a service without the method, or a throw answersfalse; a throw logs onewarnline and the rest of the body still answers in full. The function never throws./auth/me/permissions, the card's candidate: the console'sMePermissionsProvideralready fetches it once per session (DEFAULT_ENDPOINT = '/api/v1/auth/me/permissions'), so the toolbar needs no extra request on list mount. No route is added.false(no ownership-queue service in those mounts):current-user-endpoints-outer-catch-closed.test.ts(closed body and the degraded byte-for-byte body) andcurrent-user-endpoints-execution-context-envelope.test.ts..changeset/28508-me-permissions-has-any-queue.md:patchfor@objectstack/plugin-sharingand@objectstack/plugin-hono-server(Metadata-protocolnonetakespatch, AGENTS.md Post-Task Checklist step 3).@objectstack/verifygains a test file only, which itsfiles[](dist) does not ship.The field name for #28428's consumer and for the spec half:
hasAnyQueue, typeboolean, on the authenticated/auth/me/permissionsbody.Tests
plugin-sharing/src/ownership-queue.test.ts, new describe, 6 cases + 1 line in the no-engine case): row only ⇒ true for its own organization and false for another; declared and on ⇒ true; declared and switched off with no row ⇒ false (andresolveQueueagrees); a row under a switched-off declared name ⇒ false (it resolves nowhere); neither ⇒ false; a failedsys_queueread ⇒ false, the same posture asresolveQueue.plugin-hono-server/src/current-user-endpoints-has-any-queue.test.ts, 9 cases): the service'strue/falsecarried as is, asked once with the caller's organization; no service ⇒ false with no warn; a service without the method ⇒ false; a throwing service ⇒ false, one warn, body otherwise full; degraded branch carries it; closed body carriesfalse; anonymous body unchanged and service not asked; the only queue-named key on the wire is the boolean.verify/src/me-permissions-has-any-queue.test.ts, 5 cases, twobootStackboots, a fresh sign-up as the plain member): the five Acceptance item 2 cases, each also asserting the member'sGET /data/sys_queueis 403 withcode: PERMISSION_DENIED. The switched-off case goes through the real activation door (POST /security/_activation/queue/:nameas the admin); the absent-service case removesownership-queuefrom the kernel's service maps.Red first, green after, ablations (all through rebuilt dist/)
ownership-queue.test.ts7 failed / 34 passed;current-user-endpoints-has-any-queue.test.ts8 failed / 1 passed (the anonymous case, unchanged by design). HTTP door withorigin/main's two source files checked out and both packages rebuilt (ablation-dist-preflight --absent hasAnyQueueon plugin-hono-server: absent from all 6 built files): 5 failed (5). Restored withgit checkout HEAD --,git diff HEADempty, blob = HEAD, rebuilt, marker present again indist/index.jsanddist/index.mjs.ownership-queue.test.ts41/41,current-user-endpoints-has-any-queue.test.ts9/9, door test 5/5.ablation-replace.mjs, anchor hit 1, markerablation28508-always-falsepresent in plugin-sharingdist/index.js+dist/index.mjs): service cases "row only" and "declared on" red; door cases 1 and 2 red, 3 passed. Restored: blob == HEAD,git diff HEADempty; rebuilt, marker absent.activeDeclaredQueues()→declaredItems(), markerablation28508-no-ledgerin dist): service cases "switched off" and "row under a switched-off name" red; door case 3 red, 4 passed. Restored the same way.sys_queueread, markerablation28508-member-reads-queuein plugin-security dist): door 5 failed (5), eachexpected 200 to be 403. Restored the same way; plugin-security rebuilt, marker absent.Acceptance notes
origin/main004871f7:/auth/me/permissionshas no spec response schema. Nothing inpackages/specdeclares its body; the console types it with a hand-writtenMePermissionsResponseinterface (ui/packages/permissions/src/MePermissionsProvider.tsx) and fetches it raw;packages/clienthas no method for it. The nearest schema,GetEffectivePermissionsResponseSchema(packages/spec/src/api/protocol.zod.ts), declares onlyobjectsandsystemPermissions, is a non-strictz.object, and backs the optionalgetEffectivePermissionsprotocol method that nothing implements. So "declared strictly, unknown sibling still refused" means a new strict schema for the whole body (three shapes: anonymous, degraded, full/closed). That half is with the maintainer through the seat; this PR leavespackages/specuntouched and saysPart of.hasAnyQueuejoinsIOwnershipQueueServicewith the spec half. Until then the route reads it through a local optional type (AnyQueueReader), the same optional shape the contract already uses fordeclaredQueueNamed.tenantId. Withnull(no active organization), the service counts everysys_queuerow, which is whatqueuesOfalready does fornull.Verification
All readings on head
3563cd56(the final commit), after a whole-workspace build (turbo run build --concurrency=2, 118 tasks, VERDICT command-exit 0).pnpm --filter @objectstack/plugin-hono-server test: Test Files 37 passed (37), Tests 466 passed (466).pnpm --filter @objectstack/plugin-sharing test: Test Files 58 passed (58), Tests 1302 passed (1302). Door test: Tests 5 passed (5). VERDICT command-exit 0.pnpm --filter @objectstack/plugin-sharing --filter @objectstack/plugin-hono-server --filter @objectstack/verify typecheck(each runscheck:test-typechecktoo): VERDICT command-exit 0.eslint --no-inline-configoverplugin-sharing/src,plugin-hono-server/srcand the new verify test: exit 0. Repo-widepnpm lintis CI's./auth/me/permissionsthrough a real handler:@objectstack/runtimerequest-execution-context.conformance.test.ts+in-process-session-renewal.pin.test.ts(24/24),@objectstack/http-conformancehono-dispatcher-result-response.conformance.test.ts(3/3),@objectstack/verifyparent-write-reach.test.ts+ the door test (15/15). The other test files that name the route either mock their own body or assert one key (toMatchObject); the only exact-body pins were the three in plugin-hono-server, updated above. The console'sMePermissionsProviderreads the body through a hand-written interface and ignores unknown keys.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 64 commands, each run with its exit code captured before any pipe; all 64 exit 0.--ranreconciliation: "64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED". Three needed their prerequisite first (teardown self-test: one fetch of its pinned commit;check:dual-build-cjs-loadsandcheck:i18n: the workspace build) and one was a real red fixed in3563cd56(check:objectql-double-limit: the new route test'sfinddouble now applies the caller'slimit).test:repoand the full Test Core shards are CI's (this diff changes norepo-project test). No full dogfood battery is owed: no!in the title and no(narrowing)arm.Gate record (64 lines, COMMAND :: exit N)
Generated by Claude Code