Skip to content

feat(client,runtime): auth.mePermissions() reads GET /auth/me/permissions as MePermissionsResponse - #28766

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-9mtsex
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-9mtsex

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #28709

Clause-②: yes (widening) — one new SDK method, client.auth.mePermissions(), over the existing GET /auth/me/permissions. No wire change, no new route, and no packages/spec/** file is touched.

Metadata-protocol: none — nothing a metadata author writes is read differently; the method reads a response body already declared in @objectstack/spec/api by PR #28704.

维护者速读

What changes

  • packages/client/src/index.ts: auth.mePermissions() resolves MePermissionsResponse; it GETs ${baseUrl}${getRoute('auth')}/me/permissions through the shared fetch wrapper. The type is imported from @objectstack/spec/api, not restated. Its JSDoc says how it differs from auth.me(): a signed-out caller resolves { authenticated: false } (the route answers that at 200) rather than rejecting.
  • packages/runtime/src/route-ledger.ts: a GET /auth/me/permissions row (domain: '/auth', disposition: 'sdk', client: 'auth.mePermissions', responseSchema: 'MePermissionsResponseSchema'). Exact rows sort ahead of the * /auth/** family in client-url-conformance, so the method is credited to this row and not to the wildcard. The note states what serves it: plugin-hono-server's registerCurrentUserEndpoints with rawApp.get(), ahead of plugin-auth's catch-all, not dispatch() (the /auth domain only relays to the auth service, which does not serve this path). It names the suite that gives the responseSchema its conformance coverage: current-user-endpoints-has-any-queue.test.ts drives this handler and parses every body it provokes through the strict schema. The census sentence was regenerated (check-route-ledger-census.mjs --fix: 115 → 116).
  • packages/qa/dogfood/test/route-ledger-live-mount-parity.dogfood.test.ts: GET /api/v1/auth/me/permissions joins UNEXERCISED_BY_THIS_BOOT, with its reason. A raw-app mount never passes through the IHttpServer port, so without the pin direction 1 ("every ledgered route is reachably mounted") would red on this row. Dogfood does not run in PR CI, so a missing pin would only show up on main.
  • .changeset/28709-client-auth-me-permissions.md: @objectstack/client patch with both declaration lines. Metadata-protocol: none grades every entry patch (check-changeset-no-major). route-ledger.ts ships in no package: ROUTE_LEDGER / LEGACY_CHAIN_PREFIXES get 0 hits in packages/runtime/dist (control createDispatcherPlugin hits dist/index.js), and dogfood is private.

Tests

  • New packages/client/src/auth-me-permissions.test.ts (3 cases) runs over the real handler. plugin-hono-server's registerCurrentUserEndpoints is mounted on new HonoHttpServer(0).getRawApp(), the app HonoServerPlugin mounts it on, and the client's fetch hands each request to that app.
    • Signed in: exactly one GET http://localhost:3000/api/v1/auth/me/permissions. The resolved value parses through MePermissionsResponseSchema with no issues, and narrows on authenticated to the caller's own userId, tenantId, hasAnyQueue and permissionSets.
    • Anonymous: resolves { authenticated: false } and does not throw.
    • The ledger row names auth.mePermissions and the schema these cases parse with.
  • Ablation (scripts/ablation-replace.mjs, anchor hit once, blob 3dc5d07f → 9b262a18): the row's route is renamed to GET /auth/me/permissions-ablated, and client-url-conformance.test.ts reds with 1 failed / 2 passed: auth.mePermissions → GET /api/v1/auth/me/permissions (via * /auth/**): expected 1 to be +0. That is the failure this card was filed for, and the row is what closes it. Restore: blob equals HEAD's (3dc5d07f) and git diff HEAD is empty. The ledger is imported as relative source, so no build sits on the path.

Acceptance notes

  • Which ledger. The order's hypothesis was the runtime ledger. Measured: that file's SCOPE header describes dispatcher routes, and the dispatcher does not serve this path. But the file already holds rows for doors a plugin serves from the raw app, and its own conformance header names raw-app doors as rows it guards by census. plugin-auth's ledger cannot take the row: both halves are held to better-auth's live table and to auth-plugin.ts's own mounts. A new plugin-hono-server ledger would be a sixth member of the client union with its own conformance gate. It would also have to give dispositions to /auth/me/localization and /me/apps, which no ledger carries today and this card does not ask about. So the row went into the runtime ledger, and its note says plainly that the door is a raw-app mount.
  • /auth/me/localization and /me/apps (same registrar) stay in no ledger and have no client method. Not filed: no consumer asks for an SDK method for them.
  • No console migration onto the method (order ruling); that is a follow-up of its own.
  • Prior art: ff0d7c02 (written and then reverted on Session payload: the console learns once per session whether the caller's organization has at least one ownership queue (declared or sys_queue), with no widening of member read on sys_queue #28508's branch) was reused for the method and its JSDoc. Its test used a mocked fetch; this one runs over the real handler instead.

Verification

All at head 7ecb0017 (base 7a48bf73, origin/main when the branch was cut).

Package suites and typecheck (under the verify lock)
  • pnpm --filter @objectstack/client test: Test Files 54 passed (54), Tests 693 passed (693).
  • pnpm --filter @objectstack/client typecheck (tsc --noEmit plus check:test-typecheck over tsconfig.test.json, which covers the new test): exit 0.
  • pnpm --filter @objectstack/runtime exec vitest run src/route-ledger.conformance.test.ts: 8 passed (8). pnpm --filter @objectstack/runtime typecheck: exit 0.
  • Targeted: auth-me-permissions, client-url-conformance, route-ledger-coverage, route-ledger-response-schema, auth-route-ledger-coverage: 5 files, 16 passed (16).
  • Dogfood (does not run in PR CI, so measured here after pnpm --workspace-concurrency=2 --filter '@objectstack/dogfood^...' build): route-ledger-live-mount-parity.dogfood.test.ts 8 passed (8). With the new pin's key changed (ablation, blob 43b809da → d1fbf06f): 2 failed / 6 passed. Direction 1 says runtime/route-ledger.ts: GET /auth/me/permissions — LEDGERED BUT NOT MOUNTED and the stale-pin check names the changed key. Restore: blob equals HEAD's, git diff HEAD empty.
  • pnpm exec eslint --no-inline-config over the four touched source files: exit 0.
Derived gates (dispatch-gates --commands, 66 families)
  • 65 run with exit 0, 1 NOT MEASURED. dispatch-gates.mjs --ran: "66 derived famil(ies) accounted for — 65 run, 1 NOT-MEASURED".
  • NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 (PREREQUISITE NOT MET: packages outside this closure have no dist). CI runs it.
  • Three first readings were re-run once their prerequisite was in place. check-plugin-teardown-shape.mjs --self-test first exited 3 on the shallow clone; after git fetch --depth=1 origin 621a4876 it exited 0 (48 cases). spec check:skill-examples first exited 3 until @objectstack/client-react was built, then 0 (279 prose examples, 23 client SDK blocks). check:dts-closure first exited 1 because it ran while a closure build was still writing, then 0 (241/241 declaration files across 77 packages).
  • check:route-ledger-census: exit 0 after --fix (115 → 116).

CI check-runs: read by the seat on this head.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5mKQh9Ew1XG2GQk2UN


Generated by Claude Code

…ions as MePermissionsResponse, on its own route-ledger row

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Nov 1, 2026
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 cross-cutting symbol(s) contributed no route anchor: responseSchema (6 routes)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 39 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 706ac52d570096a47b64283e970bc1a8e569bc91 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7ad3251320b60c9a9120ccfaa09c9da8688bad2b — the merge of head 7ecb00173b85c50366402b29fe9ac10d78561bc8 into base 706ac52d570096a47b64283e970bc1a8e569bc91, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7ad3251320b60c9a9120ccfaa09c9da8688bad2b && git checkout 7ad3251320b60c9a9120ccfaa09c9da8688bad2b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 706ac52d570096a47b64283e970bc1a8e569bc91 7ecb00173b85c50366402b29fe9ac10d78561bc8 && git checkout -B drift-repro 706ac52d570096a47b64283e970bc1a8e569bc91 && git merge --no-ff 7ecb00173b85c50366402b29fe9ac10d78561bc8

node scripts/docs-audit/affected-docs.mjs --json 706ac52d570096a47b64283e970bc1a8e569bc91

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 7ecb00173b85c50366402b29fe9ac10d78561bc8
Local-runs: none

Inputs read: card #28709 (body, triage, unblock note, claim, the dev's os-dev-report), PR #28766 (body, 5-file list), the net diff against merge-base 7a48bf73 (+179/-1), and the check-runs on this head. origin/main is 4 commits past the merge-base, and none of them touches the five files in this diff.

① Derived judgments

  • Public surface: client.auth.mePermissions() (packages/client/src/index.ts). RIGHT. This is one new member on the published ObjectStackClient.auth namespace. It returns Promise of MePermissionsResponse, imported from @objectstack/spec/api (on main since PR feat(spec): a strict response contract for GET /auth/me/permissions, and hasAnyQueue on IOwnershipQueueService #28704, 4d278c95) and not restated. It builds ${baseUrl}${getRoute('auth')}/me/permissions through the shared this.fetch, the same way its sibling auth.me() does. It reads res.json() raw rather than through unwrapResponse, which is correct: the handler writes the whole body with no { success, data } envelope, and the new test parses the resolved value through the strict schema over the REAL registerCurrentUserEndpoints handler. The JSDoc states the one behaviour a caller must know, which is that an anonymous caller resolves { authenticated: false } at 200 and nothing is thrown, unlike auth.me(). It also says that systemPermissions / tabPermissions being absent means unknown, not none. No wire change, no new route, and no packages/spec/** file. This matches the card's Wanted and the triage Direction exactly. The test's dependencies already exist: @objectstack/plugin-hono-server and @objectstack/runtime are client devDependencies, registerCurrentUserEndpoints and HonoHttpServer are both re-exported from the plugin index, and the relative ../../runtime/src/route-ledger import is the same one client-url-conformance and route-ledger-coverage already use.

  • Accept set: runtime ROUTE_LEDGER row GET /auth/me/permissions (domain: '/auth', sdk, client: 'auth.mePermissions', responseSchema: 'MePermissionsResponseSchema'). RIGHT in effect, and the placement is acceptable with a stated residue. The row does what the card filed it for. Exact rows sort ahead of the * /auth/** family, so client-url-conformance credits the method to this row and not to the wildcard. The dev's ablation (route renamed, giving (via * /auth/**): expected 1 to be +0) shows the row is load-bearing. The census moves 115 to 116, one row for one row added. On placement: the file's SCOPE header says the ledger covers dispatcher routes plus what dispatcher-plugin.ts mounts, and this path is neither. dispatch() does not serve it: its /auth domain relays to the auth service, which disclaims it, as the hono adapter's own comment confirms (/auth/me/permissions is the canonical not-a-better-auth-endpoint example). So this is the first row in the file with no dispatcher door at all. The existing raw-app precedents (/approvals/act, /webhooks/.../redeliver, the automation inbound hook) are two-door rows, where the dispatcher answers too. The dev states this plainly in the report deviation, in the PR's Acceptance notes and in the row note, so it is not hidden. The alternatives are worse:

    • plugin-auth's ledger is held to better-auth's live table and auth-plugin.ts mounts, so the row would red there.
    • a new plugin-hono-server ledger would add another member to the client's ledger union, with its own conformance gate, for one row.

    The file's census header and the conformance test header already name "a door a plugin serves from the raw app" as a row the census alone guards. So the row sits inside the guard regime the file declares, even though it sits outside the SCOPE sentence. domain: '/auth' is the registry prefix that the client's getRoute('auth') and the domain-coverage arm key on, so it is the right value, and the note says it relays rather than serves. The residue is the should-fix S1 below.

  • Dogfood pin: UNEXERCISED_BY_THIS_BOOT gains GET /api/v1/auth/me/permissions. RIGHT, and required by the row above. A rawApp.get() mount never passes through the IHttpServer port, so direction 1 of the live-mount parity gate would otherwise report the new row LEDGERED BUT NOT MOUNTED on main. Dogfood does not run in PR CI. The reason string names the test that does cover the handler (current-user-endpoints-has-any-queue.test.ts). The dev's dogfood run (8/8) and the pin-key ablation (2 failed / 6 passed) are the dev's own measurements, and no check-run on this head re-derives them.

  • Not published: packages/runtime/src/route-ledger.ts is runtime-internal. It is not re-exported from src/index.ts, and files is dist, README and CHANGELOG. packages/qa/dogfood is private: true. So no runtime or dogfood changeset is owed.

② Semver level

  • Changeset .changeset/28709-client-auth-me-permissions.md: "@objectstack/client": patch. This matches what the diff publishes, which is one widening of @objectstack/client and nothing else from any released package.
  • Is minor owed for a new exported method? No, under this repo's rule. AGENTS.md § Post-Task Checklist step 3 sets the level by the Metadata-protocol kind alone: a kind (added/narrowed/semantic) takes at least minor, none takes patch, and "a non-protocol change, widening or breaking, ships patch". A new SDK method changes nothing a metadata author can write, so Metadata-protocol: none is the right kind and patch is the owed level. A minor here would actually contradict the rule (and check-changeset-no-major grades none entries patch). Check Changeset is success on this head.
  • Clause-②: yes (widening), line-initial in BOTH the PR body and the changeset body. Correct: a new published method is a widening, with no narrowing arm, no ! and no BREAKING banner owed. Metadata-protocol: none is line-initial in both, and correct.
  • The changeset text is CHANGELOG-ready. It gives Before/Now, names the type and the discriminator, and states the difference from auth.me().

③ Boundary flags

  • open_questions: none (an empty array in the os-dev-report). Nothing to answer.
  • Deviation 1, ledger placement: the order assumed the runtime ledger owns the plugin-hono-server endpoints, and that is only half true. Answered: accepted under ① (the claim's File surface allows the runtime ledger, the census already guards raw-app doors, and both alternatives are worse), with S1 as the residue.
  • Deviation 2, pnpm --concurrency forwarded to the cli build script: a local tooling slip, corrected with --workspace-concurrency. No effect on the diff. Answered, nothing owed.
  • Out-of-scope finding: /auth/me/localization and /me/apps (same registrar) are in no ledger and have no client method. Noted, not filed, because nothing pulls for them. Answered: agreed. The card asks for one method, and an SDK method with no consumer is a published surface that is costly to withdraw. If a consumer asks, those two routes reopen the same placement question this row raises, and S1's header sentence will answer it ahead of time.
  • Order rulings held: no console migration (the MePermissionsProvider raw fetch stays, as a follow-up of its own), no wire change, no new route.
  • Gates, read off the check-runs on this head (60 runs): 15 success, 9 skipped, 36 in progress, 0 failure. The successes include Check Changeset, PR-body guards, Governed Surface Queue Guard, Lint · ESLint, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Type Check · source gates, Dogfood Regression Gate, Spec property liveness and Check PR Size. Still running: Build Core, Test Core 1–8 of 8, Type Check · workspace / consumer gates / debt ledger, the Lint · repo & tooling / docs & code guards / CI-release-engine / scoped families, and the UI build, type and test shards. This verdict is on the contract. Landing still waits for those runs to finish green, and any red there voids it.

Blocking: none.

Should-fix (non-blocking):

  • S1. packages/runtime/src/route-ledger.ts SCOPE header still says the ledger holds dispatcher routes plus dispatcher-plugin.ts mounts. This row is the first one with no dispatcher door. Add one sentence there naming single-door raw-app rows (this one, mounted by plugin-hono-server's registerCurrentUserEndpoints) as in scope, and guarded per row by the census plus the dogfood pin. The next reader then won't learn the scope from a row note, and won't "fix" the row by deleting it. A follow-up is fine.

Implemented-by: claude/project-thread-9mtsex
Reviewed-by: session_01FEkQ5mKQh9Ew1XG2GQk2UN-review-28766

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

席位核对 · ACCEPT · PR #28766 · 2026-11-01T18:36Z

Head-sha: 7ecb00173b85c50366402b29fe9ac10d78561bc8

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Fixes #28709,合并关卡正确,与派单 pm-orders/server/order-28709.md 一致。文件 5 个,在认领 6561419182 申报面内。
  2. 前提实测:新测试跑在真实 handler 上(registerCurrentUserEndpoints 挂在 HonoHttpServer raw app),应答过严格 schema;消融把台账行改名后 client-url-conformance 变红(方法落到 * /auth/** 通配行),还原后 HEAD 一致。dogfood 挂载一致性 8/8 本地实跑(PR CI 不跑 dogfood)。
  3. 门禁:本 head check-runs 55 success、9 skipped(路径未命中)、0 失败或待跑。dev 本地 66 条派生门禁中 65 条 exit 0,check:dual-build-cjs-loads 本地 NOT MEASURED(缺全量 dist),由 CI Build Core(success)读取。
  4. 接受的偏差:台账行放在运行时台账,虽然 dispatch() 不服务此路径(由 plugin-hono-server raw app 提供)。plugin-auth 台账绑 better-auth 路由表会红;另起第六本台账为一行不值。复核认可此放置,并留一条 should-fix 不挡落地:route-ledger.ts 的 SCOPE 头应补一句说明这类无 dispatcher 门的 raw-app 行在范围内(由普查与 dogfood 钉住)。
  5. 范围外去向:/auth/me/localization、/me/apps 无 SDK 方法、无台账行,无人提需求,不立卡(复核同意)。控制台迁移到新方法按派单另起。Metadata-protocol: none,Skills-docs: none。
  6. 落地条款:Clause-②: yes (widening),非 spec 路径 ⇒ TIER_DEFAULT 复核 PASS(6562036697,Head-sha 与现 head 一致)。semver:@objectstack/client patch,与 changeset 一致(非协议扩大取 patch,Check Changeset 本 head success)。check-governed-merges --pr 28766:NOT governed,180 行。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 18:37
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 18:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 8afaf12 Nov 1, 2026
65 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-9mtsex branch November 1, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

client: GET /auth/me/permissions has no route-ledger row, so packages/client cannot offer a typed method for the now-declared MePermissionsResponse

1 participant