From a570efd9644bbd98f6b5e85c289660057498dd80 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 12:30:51 +0000 Subject: [PATCH 01/11] feat(spec): a strict response contract for GET /auth/me/permissions, and hasAnyQueue on IOwnershipQueueService Co-Authored-By: Claude --- ...rrent-user-endpoints-has-any-queue.test.ts | 12 +- .../src/current-user-endpoints.ts | 24 ++-- .../src/ownership-queue-service.ts | 5 +- packages/spec/src/api/auth.zod.ts | 74 +++++++++++ .../src/api/me-permissions-response.test.ts | 120 ++++++++++++++++++ .../src/contracts/ownership-queue-service.ts | 13 ++ .../src/type-alias-convention.pin.test.ts | 3 + .../src/me-permissions-has-any-queue.test.ts | 8 +- 8 files changed, 242 insertions(+), 17 deletions(-) create mode 100644 packages/spec/src/api/me-permissions-response.test.ts diff --git a/packages/plugins/plugin-hono-server/src/current-user-endpoints-has-any-queue.test.ts b/packages/plugins/plugin-hono-server/src/current-user-endpoints-has-any-queue.test.ts index d6fd0563289..d2f7d982c8a 100644 --- a/packages/plugins/plugin-hono-server/src/current-user-endpoints-has-any-queue.test.ts +++ b/packages/plugins/plugin-hono-server/src/current-user-endpoints-has-any-queue.test.ts @@ -32,6 +32,7 @@ import { describe, it, expect, vi } from 'vitest'; import { Hono } from 'hono'; import { OWNERSHIP_QUEUE_SERVICE } from '@objectstack/spec/contracts'; +import { MePermissionsResponseSchema } from '@objectstack/spec/api'; import { registerCurrentUserEndpoints } from './current-user-endpoints'; const ME_PERMISSIONS = '/api/v1/auth/me/permissions'; @@ -116,10 +117,19 @@ function mount( return { app, warn, hasAnyQueue }; } +/** + * Read the body, and hold it to the declared contract: every body this file + * provokes (full, degraded, closed, anonymous) parses through the STRICT + * `MePermissionsResponseSchema`, so a key the handler sends that the spec does + * not declare reds here. + */ async function read(app: Hono) { const res = await app.request(`http://localhost${ME_PERMISSIONS}`); expect(res.status).toBe(200); - return res.json(); + const body = await res.json(); + const parsed = MePermissionsResponseSchema.safeParse(body); + expect(parsed.success ? [] : parsed.error.issues).toEqual([]); + return body; } describe('[#28508] /auth/me/permissions carries hasAnyQueue from the ownership-queue service', () => { diff --git a/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts b/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts index e95ce1bcb86..39c7a53ee71 100644 --- a/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts +++ b/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts @@ -50,11 +50,13 @@ import { type RequestAuthService, } from '@objectstack/core'; import type { ExecutionContext } from '@objectstack/spec/kernel'; +import type { MePermissionsAnonymousResponse, MePermissionsAuthenticatedResponse } from '@objectstack/spec/api'; import { preferredLocaleFromHeader } from '@objectstack/spec/system'; import { OWNERSHIP_QUEUE_SERVICE, type IMetadataService, type IObjectQLEngine, + type IOwnershipQueueService, type ISecurityService, type Logger, } from '@objectstack/spec/contracts'; @@ -308,15 +310,10 @@ function closedMePermissionsGrants(): MePermissionsGrants { } /** - * [#28508] The ownership-queue service's one "any queue at all?" answer, as - * this handler reads it. Not yet on the spec contract - * (`IOwnershipQueueService`), which gains it with this payload's response - * schema; until then the method is optional here, like the contract's own - * `declaredQueueNamed`. + * [#28508] The slice of the ownership-queue contract this handler reads: its + * one "any queue at all?" answer. */ -interface AnyQueueReader { - hasAnyQueue?: (organizationId: string | null) => Promise; -} +type AnyQueueReader = Pick; /** * [#28508] Does the caller's organization have at least one ownership queue, @@ -344,9 +341,12 @@ async function organizationHasAnyQueue( ): Promise { try { const service = (() => { - try { return ctx.getService(OWNERSHIP_QUEUE_SERVICE) ?? null; } + try { return ctx.getService>(OWNERSHIP_QUEUE_SERVICE) ?? null; } catch { return null; } })(); + // The contract requires the method, but the locator hands back + // whatever a host registered under the name, which may be an + // implementation older than the contract: that one answers `false`. const hasAnyQueue = service?.hasAnyQueue; if (typeof hasAnyQueue !== 'function') return false; return (await hasAnyQueue.call(service, organizationId)) === true; @@ -373,7 +373,7 @@ async function organizationHasAnyQueue( function mePermissionsBody( identity: { userId: string; tenantId: string | null }, grants: MePermissionsGrants, -) { +): MePermissionsAuthenticatedResponse { return { authenticated: true as const, userId: identity.userId, @@ -897,7 +897,7 @@ export function registerCurrentUserEndpoints( rawApp.get(`${prefix}/auth/me/permissions`, withRequestContext(async (c, ctx, resolveCtx) => { const execCtx = await resolveCtx(c); if (!execCtx?.userId) { - return c.json({ authenticated: false }); + return c.json({ authenticated: false } satisfies MePermissionsAnonymousResponse); } // Captured BEFORE the `try`: the closed body the outer `catch` answers is // built from this alone (see {@link mePermissionsBody}). @@ -947,7 +947,7 @@ export function registerCurrentUserEndpoints( objects: {}, fields: {}, hasAnyQueue, - }); + } satisfies MePermissionsAuthenticatedResponse); } // [#7616] The caller's sets, resolved ONCE by the owner of the // rule: positions expanded, the ADR-0090 D5 baseline applied diff --git a/packages/plugins/plugin-sharing/src/ownership-queue-service.ts b/packages/plugins/plugin-sharing/src/ownership-queue-service.ts index c4217df2c78..0cd711c0ef9 100644 --- a/packages/plugins/plugin-sharing/src/ownership-queue-service.ts +++ b/packages/plugins/plugin-sharing/src/ownership-queue-service.ts @@ -215,9 +215,8 @@ export class OwnershipQueueService implements IOwnershipQueueService { * * The session payload's one boolean (`/auth/me/permissions` `hasAnyQueue`): * the console's list toolbar asks it once per session, for a member who may - * not read `sys_queue` themselves. It discloses no name and no count. Not - * yet on the spec contract, which gains it with the payload's response - * schema. + * not read `sys_queue` themselves. It discloses no name and no count. The + * contract states it on {@link IOwnershipQueueService.hasAnyQueue}. */ async hasAnyQueue(organizationId: string | null): Promise { const engine = this.engine(); diff --git a/packages/spec/src/api/auth.zod.ts b/packages/spec/src/api/auth.zod.ts index b06b60a5145..c5684fc3827 100644 --- a/packages/spec/src/api/auth.zod.ts +++ b/packages/spec/src/api/auth.zod.ts @@ -2,6 +2,7 @@ import { z } from 'zod'; import { BaseResponseSchema } from './contract.zod'; +import { EffectiveObjectPermissionSchema } from '../security/permission.zod'; /** * Authentication Service Protocol @@ -131,6 +132,76 @@ export const UserProfileResponseSchema = lazySchema(() => BaseResponseSchema.ext data: SessionUserSchema, })); +// ========================================== +// GET /auth/me/permissions +// ========================================== + +/** + * [#28508] The anonymous answer of `GET /auth/me/permissions`: a caller with no + * session is told only that, at 200, so the console can tell "signed out" from + * a fault. Strict: nothing about any user is disclosed here. + */ +export const MePermissionsAnonymousResponseSchema = lazySchema(() => z.object({ + authenticated: z.literal(false).describe('No session: the caller is anonymous.'), +}).strict()); + +/** + * [#28508] The authenticated answer of `GET /auth/me/permissions`: the caller's + * effective permissions, resolved once by the server so the console never + * re-derives them (`@objectstack/plugin-hono-server`, `current-user-endpoints.ts`). + * + * Three bodies take this one shape: + * + * - **full** — a stack with SecurityPlugin: every key present; + * - **closed** — the server knew the caller but failed to compute the grants: + * every key present and empty, `hasAnyQueue: false`; + * - **degraded** — no SecurityPlugin (or no metadata service): `systemPermissions` + * and `tabPermissions` are ABSENT on purpose. Absent means "unknown", which + * the console fails open on, because there the server enforces nothing + * either; present-and-empty means "none". The two must never be conflated. + * + * Strict: an undeclared key is refused, so a key the server starts sending is + * declared here first (`Clause-②`). + */ +export const MePermissionsAuthenticatedResponseSchema = lazySchema(() => z.object({ + authenticated: z.literal(true).describe('A session was resolved for the caller.'), + userId: z.string().describe('The caller\'s user id.'), + tenantId: z.string().nullable().describe('The caller\'s active organization id, or null when the session has none.'), + positions: z.array(z.string()).describe( + 'The security positions the caller holds (ADR-0068 D3): built-in identity names plus assigned positions, scope-resolved.', + ), + permissionSets: z.array(z.string()).describe('The names of the permission sets resolved for the caller.'), + objects: z.record(z.string(), EffectiveObjectPermissionSchema).describe( + 'The effective object permissions, keyed by object name — the map `current_user.can()` reads on the server.', + ), + fields: z.record(z.string(), z.object({ + readable: z.boolean().describe('The caller may read the field.'), + editable: z.boolean().describe('The caller may edit the field on some row (a row-conditioned entry is reported as its lock).'), + }).strict()).describe('The effective field permissions, keyed `object.field`.'), + systemPermissions: z.array(z.string()).optional().describe( + 'The effective system capabilities (ADR-0066). Absent only on the degraded body, where it means unknown; ' + + 'an empty array means none.', + ), + tabPermissions: z.record(z.string(), z.enum(['visible', 'hidden', 'default_on', 'default_off'])).optional().describe( + 'The effective app tab visibilities, keyed by app name. Absent only on the degraded body.', + ), + hasAnyQueue: z.boolean().describe( + 'Whether the caller\'s organization has at least one ownership queue: a declared queue the activation ledger ' + + 'has not switched off, or a `sys_queue` row of the organization. Resolved server-side through the ' + + 'ownership-queue service, so a member who may not read `sys_queue` still gets the answer; no names and no ' + + 'counts are disclosed. False when no ownership-queue service is registered, and on the closed body.', + ), +}).strict()); + +/** + * [#28508] The response of `GET /auth/me/permissions`, discriminated by + * `authenticated`. The console reads it once per session. + */ +export const MePermissionsResponseSchema = lazySchema(() => z.discriminatedUnion('authenticated', [ + MePermissionsAnonymousResponseSchema, + MePermissionsAuthenticatedResponseSchema, +])); + export type AuthProvider = z.input; export type SessionUser = z.input; /** Post-parse shape of {@link SessionUser} — defaults applied, transforms run (ADR-0122). */ @@ -148,3 +219,6 @@ export type SessionResponseParsed = z.infer; export type UserProfileResponse = z.input; /** Post-parse shape of {@link UserProfileResponse} — defaults applied, transforms run (ADR-0122). */ export type UserProfileResponseParsed = z.infer; +export type MePermissionsAnonymousResponse = z.input; +export type MePermissionsAuthenticatedResponse = z.input; +export type MePermissionsResponse = z.input; diff --git a/packages/spec/src/api/me-permissions-response.test.ts b/packages/spec/src/api/me-permissions-response.test.ts new file mode 100644 index 00000000000..7f2c3f6899e --- /dev/null +++ b/packages/spec/src/api/me-permissions-response.test.ts @@ -0,0 +1,120 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// #28508: `GET /auth/me/permissions` has a declared, STRICT response contract. +// The console reads this body once per session for its whole permission layer, +// and since #28508 for `hasAnyQueue` too. A key the server starts sending is +// declared here first, and an undeclared one is refused rather than dropped. +// +// The handler's own bodies are parsed through this schema in +// plugin-hono-server's `current-user-endpoints-has-any-queue.test.ts`; this +// file pins the contract itself. + +import { describe, it, expect } from 'vitest'; +import { + MePermissionsResponseSchema, + MePermissionsAuthenticatedResponseSchema, + MePermissionsAnonymousResponseSchema, +} from './auth.zod'; + +/** The full body: a stack with SecurityPlugin. */ +const FULL = { + authenticated: true, + userId: 'usr_1', + tenantId: 'org_1', + positions: ['org_member', 'everyone'], + permissionSets: ['member_default'], + objects: { crm_account: { allowRead: true, allowEdit: true, apiOperations: ['get', 'list'] } }, + fields: { 'crm_account.revenue': { readable: true, editable: false } }, + systemPermissions: ['setup.access'], + tabPermissions: { sales: 'visible' }, + hasAnyQueue: true, +} as const; + +/** The closed body: the caller is known, the grants could not be computed. */ +const CLOSED = { + authenticated: true, + userId: 'usr_1', + tenantId: 'org_1', + positions: [], + permissionSets: [], + objects: {}, + fields: {}, + systemPermissions: [], + tabPermissions: {}, + hasAnyQueue: false, +} as const; + +/** The degraded body: no SecurityPlugin; capabilities unknown, so the two keys are absent. */ +const DEGRADED = { + authenticated: true, + userId: 'usr_1', + tenantId: null, + positions: ['everyone'], + permissionSets: [], + objects: {}, + fields: {}, + hasAnyQueue: false, +} as const; + +const issuesOf = (value: unknown) => { + const r = MePermissionsResponseSchema.safeParse(value); + return r.success ? [] : r.error.issues; +}; + +describe('[#28508] MePermissionsResponseSchema — the declared /auth/me/permissions body', () => { + it.each([ + ['anonymous', { authenticated: false }], + ['full', FULL], + ['closed', CLOSED], + ['degraded', DEGRADED], + ] as const)('the %s body parses', (_label, body) => { + expect(issuesOf(body)).toEqual([]); + }); + + it('hasAnyQueue is declared, and is a boolean', () => { + expect(MePermissionsAuthenticatedResponseSchema.shape.hasAnyQueue).toBeDefined(); + expect(MePermissionsResponseSchema.parse(FULL)).toMatchObject({ hasAnyQueue: true }); + expect(issuesOf({ ...FULL, hasAnyQueue: 'yes' }).map((i) => i.path.join('.'))).toEqual(['hasAnyQueue']); + expect(issuesOf({ ...FULL, hasAnyQueue: 2 }).map((i) => i.path.join('.'))).toEqual(['hasAnyQueue']); + }); + + it('hasAnyQueue is required on an authenticated body — every authenticated branch carries it', () => { + const { hasAnyQueue: _dropped, ...withoutIt } = FULL; + expect(issuesOf(withoutIt).map((i) => i.path.join('.'))).toEqual(['hasAnyQueue']); + }); + + it('an unknown sibling is refused on the authenticated body, not dropped', () => { + const issues = issuesOf({ ...FULL, queueNames: ['billing'] }); + expect(issues).toHaveLength(1); + expect(issues[0].code).toBe('unrecognized_keys'); + expect((issues[0] as { keys?: string[] }).keys).toEqual(['queueNames']); + }); + + it('an unknown sibling is refused on the anonymous body: nothing about a user rides on it', () => { + const issues = MePermissionsAnonymousResponseSchema.safeParse({ authenticated: false, hasAnyQueue: true }); + expect(issues.success).toBe(false); + expect(issues.success ? [] : issues.error.issues.map((i) => i.code)).toEqual(['unrecognized_keys']); + expect(issuesOf({ authenticated: false, userId: 'usr_1' }).map((i) => i.code)).toEqual(['unrecognized_keys']); + }); + + it('a field entry is closed too', () => { + const issues = issuesOf({ ...FULL, fields: { 'crm_account.revenue': { readable: true, editable: false, masked: true } } }); + expect(issues.map((i) => i.code)).toEqual(['unrecognized_keys']); + }); + + it('absent systemPermissions is the degraded body\'s "unknown"; present-and-empty is "none" — both parse, and stay distinct', () => { + const degraded = MePermissionsResponseSchema.parse(DEGRADED); + const closed = MePermissionsResponseSchema.parse(CLOSED); + expect('systemPermissions' in degraded).toBe(false); + expect(closed).toMatchObject({ systemPermissions: [] }); + }); + + it('a tab visibility outside the four is refused', () => { + expect(issuesOf({ ...FULL, tabPermissions: { sales: 'shown' } }).map((i) => i.path.join('.'))).toEqual(['tabPermissions.sales']); + }); + + it('the discriminator is the boolean `authenticated`: anything else matches no branch', () => { + expect(issuesOf({ authenticated: 'true' })).not.toEqual([]); + expect(issuesOf({})).not.toEqual([]); + }); +}); diff --git a/packages/spec/src/contracts/ownership-queue-service.ts b/packages/spec/src/contracts/ownership-queue-service.ts index 513167a14fd..3749062710d 100644 --- a/packages/spec/src/contracts/ownership-queue-service.ts +++ b/packages/spec/src/contracts/ownership-queue-service.ts @@ -109,6 +109,19 @@ export interface IOwnershipQueueService { */ queuesOf(userId: string, organizationId: string | null): Promise; + /** + * Does `organizationId` have at least one ownership queue? `true` exactly + * when some name would resolve there through {@link resolveQueue}: a + * declared queue the activation ledger leaves on, or an organization's + * `sys_queue` row that does not sit under a declared queue's name. + * + * The one answer `GET /auth/me/permissions` carries as `hasAnyQueue` + * (`MePermissionsAuthenticatedResponseSchema`, `@objectstack/spec/api`), so + * a member who may not read `sys_queue` can still tell whether a queue + * filter can ever return a row. It discloses no name and no count. + */ + hasAnyQueue(organizationId: string | null): Promise; + /** * The declared queue named `name`, whether or not the activation ledger * switches it off — `null` when no package declares it. Optional: the diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index f3ea947306c..8be62e36d5d 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -429,6 +429,9 @@ export type Iso_api_authEndpoints__EmailPasswordConfigPublicSchema = Assert, z.infer< typeof M13.AuthProvider > >>; export type Iso_api_auth__LoginType = Assert, z.infer< typeof M13.LoginType > >>; +export type Iso_api_auth__MePermissionsAnonymousResponseSchema = Assert, z.infer< typeof M13.MePermissionsAnonymousResponseSchema > >>; +export type Iso_api_auth__MePermissionsAuthenticatedResponseSchema = Assert, z.infer< typeof M13.MePermissionsAuthenticatedResponseSchema > >>; +export type Iso_api_auth__MePermissionsResponseSchema = Assert, z.infer< typeof M13.MePermissionsResponseSchema > >>; export type Iso_api_auth__RefreshTokenRequestSchema = Assert, z.infer< typeof M13.RefreshTokenRequestSchema > >>; export type Iso_api_auth__RegisterRequestSchema = Assert, z.infer< typeof M13.RegisterRequestSchema > >>; export type Iso_api_auth__SessionSchema = Assert, z.infer< typeof M13.SessionSchema > >>; diff --git a/packages/verify/src/me-permissions-has-any-queue.test.ts b/packages/verify/src/me-permissions-has-any-queue.test.ts index e57b53f9f0e..1b4982cd3c0 100644 --- a/packages/verify/src/me-permissions-has-any-queue.test.ts +++ b/packages/verify/src/me-permissions-has-any-queue.test.ts @@ -32,6 +32,7 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { defineStack } from '@objectstack/spec'; import { ObjectSchema, Field } from '@objectstack/spec/data'; import { OWNERSHIP_QUEUE_SERVICE } from '@objectstack/spec/contracts'; +import { MePermissionsResponseSchema } from '@objectstack/spec/api'; import { bootStack, type VerifyStack } from './harness.js'; import { warmUpBoot } from './boot-warm-up.testkit.js'; @@ -78,7 +79,12 @@ const stackWithDeclaredQueue = () => async function hasAnyQueueAs(stack: VerifyStack, token: string): Promise { const res = await stack.apiAs(token, 'GET', '/auth/me/permissions'); expect(res.status).toBe(200); - const body = (await res.json()) as { authenticated?: unknown; hasAnyQueue?: unknown }; + const raw: unknown = await res.json(); + // The real full-stack body holds to the declared STRICT contract: an + // undeclared key the handler sends reds here. + const parsed = MePermissionsResponseSchema.safeParse(raw); + expect(parsed.success ? [] : parsed.error.issues).toEqual([]); + const body = raw as { authenticated?: unknown; hasAnyQueue?: unknown }; expect(body.authenticated).toBe(true); return body.hasAnyQueue; } From ff0d7c02b4a22b573823c9cabce6bde8ad6bd048 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 12:32:18 +0000 Subject: [PATCH 02/11] feat(client): auth.mePermissions() reads GET /auth/me/permissions as its declared contract Co-Authored-By: Claude --- .../client/src/auth-me-permissions.test.ts | 62 +++++++++++++++++++ packages/client/src/index.ts | 21 +++++++ 2 files changed, 83 insertions(+) create mode 100644 packages/client/src/auth-me-permissions.test.ts diff --git a/packages/client/src/auth-me-permissions.test.ts b/packages/client/src/auth-me-permissions.test.ts new file mode 100644 index 00000000000..3cd8fa09f49 --- /dev/null +++ b/packages/client/src/auth-me-permissions.test.ts @@ -0,0 +1,62 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * [#28508] `client.auth.mePermissions()` — the typed read of + * `GET /auth/me/permissions`, whose body `MePermissionsResponseSchema` + * (`@objectstack/spec/api`) declares strictly. The console reads this once per + * session; `hasAnyQueue` is on it for the list toolbar's queue toggle. + */ + +import { describe, it, expect, vi } from 'vitest'; +import { MePermissionsResponseSchema } from '@objectstack/spec/api'; +import { ObjectStackClient } from './index'; + +function createMockClient(body: unknown, status = 200) { + const fetchMock = vi.fn().mockResolvedValue({ + ok: status >= 200 && status < 300, + status, + statusText: status === 200 ? 'OK' : 'Error', + json: async () => body, + headers: new Headers(), + }); + const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000', fetch: fetchMock }); + return { client, fetchMock }; +} + +const AUTHENTICATED = { + authenticated: true, + userId: 'usr_1', + tenantId: 'org_1', + positions: ['org_member', 'everyone'], + permissionSets: ['member_default'], + objects: {}, + fields: {}, + systemPermissions: [], + tabPermissions: {}, + hasAnyQueue: true, +} as const; + +describe('[#28508] client.auth.mePermissions', () => { + it('GETs /api/v1/auth/me/permissions and resolves the body as the route answers it', async () => { + const { client, fetchMock } = createMockClient(AUTHENTICATED); + const answer = await client.auth.mePermissions(); + + const [url, init] = fetchMock.mock.calls[0]; + expect(String(url)).toBe('http://localhost:3000/api/v1/auth/me/permissions'); + expect(init?.method ?? 'GET').toBe('GET'); + expect(answer).toEqual(AUTHENTICATED); + // The fixture is a body the declared contract accepts, so this case + // cannot pass on a shape the server would never send. + expect(MePermissionsResponseSchema.safeParse(answer).success).toBe(true); + // The type narrows on the discriminator. + if (answer.authenticated) expect(answer.hasAnyQueue).toBe(true); + else throw new Error('expected the authenticated branch'); + }); + + it('an anonymous caller resolves { authenticated: false }, not a throw', async () => { + const { client } = createMockClient({ authenticated: false }); + const answer = await client.auth.mePermissions(); + expect(answer).toEqual({ authenticated: false }); + expect(MePermissionsResponseSchema.safeParse(answer).success).toBe(true); + }); +}); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 027ab0283d1..7ddfedc235f 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -31,6 +31,7 @@ import { PublishPackageDraftsResponse, LoginRequest, SessionResponse, + MePermissionsResponse, GetPresignedUrlRequest, PresignedUrlResponse, CompleteUploadRequest, @@ -4991,6 +4992,26 @@ export class ObjectStackClient { return normalizeSessionResponse(await res.json()); }, + /** + * [#28508] The caller's effective permissions: `GET /auth/me/permissions`, + * the one payload the console reads once per session for its whole + * permission layer, declared STRICTLY by `MePermissionsResponseSchema` + * (`@objectstack/spec/api`). + * + * Discriminate on `authenticated`: an anonymous caller resolves + * `{ authenticated: false }` at 200 (the route answers it so a client can + * tell "signed out" from a fault). An authenticated answer carries, among + * the rest, `hasAnyQueue` (does the caller's organization have at least one + * ownership queue), and `systemPermissions` / `tabPermissions` are absent + * only when the server runs without SecurityPlugin, where absent means + * unknown and an empty array means none. + */ + mePermissions: async (): Promise => { + const route = this.getRoute('auth'); + const res = await this.fetch(`${this.baseUrl}${route}/me/permissions`); + return (await res.json()) as MePermissionsResponse; + }, + /** * Register a new user account * Uses better-auth endpoint: POST /sign-up/email From 1a4fa82b8b0b7ac4bbf2304e2830b664058c93b1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 12:37:47 +0000 Subject: [PATCH 03/11] chore(spec): regenerate artifacts for the /auth/me/permissions response contract; changeset Co-Authored-By: Claude --- .../28508-me-permissions-response-contract.md | 22 ++++ content/docs/references/api/auth.mdx | 123 +++++++++++++++++- skills/objectstack-api/references/_index.md | 22 ++++ 3 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 .changeset/28508-me-permissions-response-contract.md diff --git a/.changeset/28508-me-permissions-response-contract.md b/.changeset/28508-me-permissions-response-contract.md new file mode 100644 index 00000000000..9405500c5de --- /dev/null +++ b/.changeset/28508-me-permissions-response-contract.md @@ -0,0 +1,22 @@ +--- +'@objectstack/spec': patch +'@objectstack/client': patch +'@objectstack/plugin-hono-server': patch +'@objectstack/plugin-sharing': patch +--- + +feat(spec,client): a strict response contract for `GET /auth/me/permissions`, and `hasAnyQueue` on the ownership-queue contract + +Clause-②: yes +Metadata-protocol: none + +`GET /auth/me/permissions` is the payload the console reads once per session for its whole permission layer. Until now nothing in `@objectstack/spec` declared its body. It is now declared, strictly: + +- **`MePermissionsResponseSchema`** (`@objectstack/spec/api`), discriminated by `authenticated`: + - `MePermissionsAnonymousResponseSchema`: `{ authenticated: false }` and nothing else. + - `MePermissionsAuthenticatedResponseSchema`: `userId`, `tenantId` (nullable), `positions`, `permissionSets`, `objects` (effective object permissions), `fields` (`{ readable, editable }` per `object.field`), `systemPermissions` and `tabPermissions` (optional: absent only on the body a server without SecurityPlugin answers, where absent means unknown), and `hasAnyQueue: boolean`. + - Both branches are strict: an undeclared key is refused, not dropped. A key the server starts sending is declared here first. + - Types `MePermissionsResponse`, `MePermissionsAnonymousResponse`, `MePermissionsAuthenticatedResponse`. +- **`IOwnershipQueueService.hasAnyQueue(organizationId)`** (`@objectstack/spec/contracts`): does the organization have at least one ownership queue, `true` exactly when some name would resolve through `resolveQueue`. `@objectstack/plugin-sharing`'s `OwnershipQueueService` implements it. +- **`client.auth.mePermissions()`** (`@objectstack/client`): reads the route and resolves `MePermissionsResponse`. An anonymous caller resolves `{ authenticated: false }` at 200, not a throw. +- **`@objectstack/plugin-hono-server`** types each body it builds for the route against the schema's types, and reads `hasAnyQueue` through the contract. The wire is unchanged. diff --git a/content/docs/references/api/auth.mdx b/content/docs/references/api/auth.mdx index fc1ff171330..b50d2e3438c 100644 --- a/content/docs/references/api/auth.mdx +++ b/content/docs/references/api/auth.mdx @@ -18,8 +18,8 @@ and Access Control. ## TypeScript Usage ```typescript -import { AuthProvider, LoginRequestSchema, LoginType, RefreshTokenRequestSchema, RegisterRequestSchema, SessionSchema, SessionResponseSchema, SessionUserSchema, UserProfileResponseSchema } from '@objectstack/spec/api'; -import type { AuthProvider, LoginRequest, LoginType, RefreshTokenRequest, RegisterRequest, Session, SessionResponse, SessionUser, UserProfileResponse } from '@objectstack/spec/api'; +import { AuthProvider, LoginRequestSchema, LoginType, MePermissionsAnonymousResponseSchema, MePermissionsAuthenticatedResponseSchema, MePermissionsResponseSchema, RefreshTokenRequestSchema, RegisterRequestSchema, SessionSchema, SessionResponseSchema, SessionUserSchema, UserProfileResponseSchema } from '@objectstack/spec/api'; +import type { AuthProvider, LoginRequest, LoginType, MePermissionsAnonymousResponse, MePermissionsAuthenticatedResponse, MePermissionsResponse, RefreshTokenRequest, RegisterRequest, Session, SessionResponse, SessionUser, UserProfileResponse } from '@objectstack/spec/api'; // Validate data const result = AuthProvider.parse(data); @@ -68,6 +68,125 @@ const result = AuthProvider.parse(data); * `social` +--- + +## MePermissionsAnonymousResponse + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **authenticated** | `false` | ✅ | No session: the caller is anonymous. | + + +--- + +## MePermissionsAuthenticatedResponse + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **authenticated** | `true` | ✅ | A session was resolved for the caller. | +| **userId** | `string` | ✅ | The caller's user id. | +| **tenantId** | `string \| null` | ✅ | The caller's active organization id, or null when the session has none. | +| **positions** | `string[]` | ✅ | The security positions the caller holds (ADR-0068 D3): built-in identity names plus assigned positions, scope-resolved. | +| **permissionSets** | `string[]` | ✅ | The names of the permission sets resolved for the caller. | +| **objects** | `Record` | ✅ | The effective object permissions, keyed by object name — the map `current_user.can()` reads on the server. | +| **fields** | `Record` | ✅ | The effective field permissions, keyed `object.field`. | +| **systemPermissions** | `string[]` | optional | The effective system capabilities (ADR-0066). Absent only on the degraded body, where it means unknown; an empty array means none. | +| **tabPermissions** | `Record>` | optional | The effective app tab visibilities, keyed by app name. Absent only on the degraded body. | +| **hasAnyQueue** | `boolean` | ✅ | Whether the caller's organization has at least one ownership queue: a declared queue the activation ledger has not switched off, or a `sys_queue` row of the organization. Resolved server-side through the ownership-queue service, so a member who may not read `sys_queue` still gets the answer; no names and no counts are disclosed. False when no ownership-queue service is registered, and on the closed body. | + +### Nested Shape: `MePermissionsAuthenticatedResponse.objects[string]` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **allowCreate** | `boolean` | optional (default: `false`) | Create permission | +| **allowRead** | `boolean` | optional (default: `false`) | Read permission | +| **allowEdit** | `boolean` | optional (default: `false`) | Edit permission | +| **allowDelete** | `boolean` | optional (default: `false`) | Delete permission | +| **allowExport** | `boolean` | optional | User-level export axis over read (opt-in grant). true = export granted (still bounded by read); unset/false = no export. Merged most-permissively like the CRUD bits; NOT implied by viewAllRecords/modifyAllRecords. | +| **allowTransfer** | `boolean` | optional (default: `false`) | [RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id) | +| **allowRestore** | `never` | optional | [REMOVED] `objects..allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. | +| **allowPurge** | `never` | optional | [REMOVED] `objects..allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. | +| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) | +| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. | +| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org | +| **writeScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Write depth: own\|unit\|unit_and_below\|org | +| **apiOperations** | `Enum<'get' \| 'list' \| 'create' \| 'update' \| 'delete' \| 'upsert' \| 'bulk' \| …>[]` | optional | Server-resolved effective API operations for this object. Present only when the object tightens exposure via apiMethods; absent = default-allow. The frontend renders this effective set, never the raw whitelist. Vocabulary is the EFFECTIVE ApiOperation set (six primitives + eight derived verbs), not the authored six-value ApiMethod enum. | + +### Nested Shape: `MePermissionsAuthenticatedResponse.fields[string]` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **readable** | `boolean` | ✅ | The caller may read the field. | +| **editable** | `boolean` | ✅ | The caller may edit the field on some row (a row-conditioned entry is reported as its lock). | + + +--- + +## MePermissionsResponse + +### Union Options + +This schema accepts one of the following structures: + +#### Option 1 + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **authenticated** | `false` | ✅ | No session: the caller is anonymous. | + +--- + +#### Option 2 + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **authenticated** | `true` | ✅ | A session was resolved for the caller. | +| **userId** | `string` | ✅ | The caller's user id. | +| **tenantId** | `string \| null` | ✅ | The caller's active organization id, or null when the session has none. | +| **positions** | `string[]` | ✅ | The security positions the caller holds (ADR-0068 D3): built-in identity names plus assigned positions, scope-resolved. | +| **permissionSets** | `string[]` | ✅ | The names of the permission sets resolved for the caller. | +| **objects** | `Record` | ✅ | The effective object permissions, keyed by object name — the map `current_user.can()` reads on the server. | +| **fields** | `Record` | ✅ | The effective field permissions, keyed `object.field`. | +| **systemPermissions** | `string[]` | optional | The effective system capabilities (ADR-0066). Absent only on the degraded body, where it means unknown; an empty array means none. | +| **tabPermissions** | `Record>` | optional | The effective app tab visibilities, keyed by app name. Absent only on the degraded body. | +| **hasAnyQueue** | `boolean` | ✅ | Whether the caller's organization has at least one ownership queue: a declared queue the activation ledger has not switched off, or a `sys_queue` row of the organization. Resolved server-side through the ownership-queue service, so a member who may not read `sys_queue` still gets the answer; no names and no counts are disclosed. False when no ownership-queue service is registered, and on the closed body. | + +### Nested Shape: `MePermissionsResponse[authenticated=true].objects[string]` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **allowCreate** | `boolean` | optional (default: `false`) | Create permission | +| **allowRead** | `boolean` | optional (default: `false`) | Read permission | +| **allowEdit** | `boolean` | optional (default: `false`) | Edit permission | +| **allowDelete** | `boolean` | optional (default: `false`) | Delete permission | +| **allowExport** | `boolean` | optional | User-level export axis over read (opt-in grant). true = export granted (still bounded by read); unset/false = no export. Merged most-permissively like the CRUD bits; NOT implied by viewAllRecords/modifyAllRecords. | +| **allowTransfer** | `boolean` | optional (default: `false`) | [RBAC-gated; ENFORCED via the insert/update owner_id guard] Change record ownership (assign/reassign/disown owner_id) | +| **allowRestore** | `never` | optional | [REMOVED] `objects..allowRestore` was removed in @objectstack/spec 17 (ADR-0049) — the `restore` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing. Delete the key — a dispatched `restore` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. | +| **allowPurge** | `never` | optional | [REMOVED] `objects..allowPurge` was removed in @objectstack/spec 17 (ADR-0049) — the `purge` ObjectQL operation it claimed to gate has never shipped (roadmap M2), so granting the bit delivered nothing (a compliance/GDPR erase the author believed was permission-locked was not — the operation itself does not exist). Delete the key — a dispatched `purge` stays denied fail-closed by the permission evaluator's destructive-operation backstop, and the bit returns with the M2 lifecycle initiative alongside the operation it gates. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. | +| **viewAllRecords** | `boolean` | optional (default: `false`) | View All Data (Bypass Sharing) | +| **modifyAllRecords** | `boolean` | optional (default: `false`) | Modify All Data (Bypass Sharing) — bypasses sharing rules and ownership on the objects record sharing enforces on; on an object with NO owner field sharing abstains, so the platform created_by write floor still applies. Includes delete: an omitted allowDelete parses to true, and an explicit allowDelete: false beside it is refused. | +| **readScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Read depth: own\|unit\|unit_and_below\|org | +| **writeScope** | `Enum<'own' \| 'own_and_reports' \| 'unit' \| 'unit_and_below' \| 'org'>` | optional | [ADR-0057 D1] Write depth: own\|unit\|unit_and_below\|org | +| **apiOperations** | `Enum<'get' \| 'list' \| 'create' \| 'update' \| 'delete' \| 'upsert' \| 'bulk' \| …>[]` | optional | Server-resolved effective API operations for this object. Present only when the object tightens exposure via apiMethods; absent = default-allow. The frontend renders this effective set, never the raw whitelist. Vocabulary is the EFFECTIVE ApiOperation set (six primitives + eight derived verbs), not the authored six-value ApiMethod enum. | + +### Nested Shape: `MePermissionsResponse[authenticated=true].fields[string]` + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **readable** | `boolean` | ✅ | The caller may read the field. | +| **editable** | `boolean` | ✅ | The caller may edit the field on some row (a row-conditioned entry is reported as its lock). | + +--- + + --- ## RefreshTokenRequest diff --git a/skills/objectstack-api/references/_index.md b/skills/objectstack-api/references/_index.md index f4b8571eaf9..ddeb3b94c98 100644 --- a/skills/objectstack-api/references/_index.md +++ b/skills/objectstack-api/references/_index.md @@ -23,15 +23,37 @@ from `node_modules` — there is no local copy in the skill bundle. - `node_modules/@objectstack/spec/src/api/error-code-ledger.zod.ts` — Error-Code Ledger (ADR-0112 D3). - `node_modules/@objectstack/spec/src/api/realtime-shared.zod.ts` — Realtime Shared Protocol - `node_modules/@objectstack/spec/src/data/data-engine.zod.ts` — Data Engine Protocol +- `node_modules/@objectstack/spec/src/data/date-macros.zod.ts` — Date Macro Tokens — the declarative placeholders the UI substitutes +- `node_modules/@objectstack/spec/src/data/field-value.zod.ts` — Field runtime VALUE-shape contract (ADR-0104 D1). +- `node_modules/@objectstack/spec/src/data/field.zod.ts` — Exports: FieldType, SelectOptionSchema, LocationCoordinatesSchema, CurrencyConfigSchema, UniqueScopeSchema - `node_modules/@objectstack/spec/src/data/filter.zod.ts` — Unified Query DSL Specification +- `node_modules/@objectstack/spec/src/data/hook-body.zod.ts` — Exports: HookBodyCapability, ExpressionBodySchema, ScriptBodySchema, HookBodySchema +- `node_modules/@objectstack/spec/src/data/object.zod.ts` — Exports: ApiMethod, ApiOperationSchema, ObjectCapabilities, IndexSchema, TenancyConfigSchema - `node_modules/@objectstack/spec/src/data/query.zod.ts` — QueryAST — Abstract Syntax Tree for data queries. +- `node_modules/@objectstack/spec/src/data/validation.zod.ts` — ObjectStack Validation Protocol +- `node_modules/@objectstack/spec/src/identity/eval-user.zod.ts` — EvalUser — the one user-context contract (ADR-0068 D1). - `node_modules/@objectstack/spec/src/kernel/execution-context.zod.ts` — Exports: AuthGateCodeSchema, AuthGateScopeSchema, ExecutionContextSchema - `node_modules/@objectstack/spec/src/kernel/metadata-protection.zod.ts` — Metadata Protection Model — Phase 1 (ADR-0010) +- `node_modules/@objectstack/spec/src/kernel/override-declaration.zod.ts` — The override declaration (ADR-0146 D2) - `node_modules/@objectstack/spec/src/security/explain.zod.ts` — [ADR-0090 D6] Access-explanation contract — `explain(principal, object, +- `node_modules/@objectstack/spec/src/security/permission.zod.ts` — Exports: ObjectAccessScopeSchema, ObjectPermissionSchema, EffectiveObjectPermissionSchema, AdminScopeSchema, FieldPermissionSchema +- `node_modules/@objectstack/spec/src/security/rls.zod.ts` — Row-Level Security (RLS) Protocol +- `node_modules/@objectstack/spec/src/shared/conditional-formatting.zod.ts` — The **single declaration** of a conditional formatting rule — - `node_modules/@objectstack/spec/src/shared/duration.zod.ts` — Exports: DurationMs, DurationSeconds - `node_modules/@objectstack/spec/src/shared/epoch.zod.ts` — Exports: EpochMs +- `node_modules/@objectstack/spec/src/shared/expression.zod.ts` — Expression Protocol - `node_modules/@objectstack/spec/src/shared/http.zod.ts` — Shared HTTP Schemas +- `node_modules/@objectstack/spec/src/shared/identifiers.zod.ts` — Exports: SystemIdentifierSchema, SnakeCaseIdentifierSchema, MetadataItemNameSchema +- `node_modules/@objectstack/spec/src/shared/protection.zod.ts` — Package-level metadata protection (ADR-0010 §3.7 — Phase 4.3) - `node_modules/@objectstack/spec/src/shared/suggestions.zod.ts` — "Did you mean?" Suggestion Utilities +- `node_modules/@objectstack/spec/src/shared/value-domain.zod.ts` — Standard value domains: one closed vocabulary and one membership predicate for settings and fields. +- `node_modules/@objectstack/spec/src/ui/action-params.zod.ts` — The action DISPATCH contract: what the platform validates on the way in, and +- `node_modules/@objectstack/spec/src/ui/action.zod.ts` — Exports: ActionParamSchema, ActionType, ActionLocationSchema, ActionAiSchema, ActionSchema +- `node_modules/@objectstack/spec/src/ui/bulk-action.zod.ts` — Bulk Action Schemas +- `node_modules/@objectstack/spec/src/ui/chart.zod.ts` — Unified Chart Type Taxonomy +- `node_modules/@objectstack/spec/src/ui/i18n.zod.ts` — Display-label and ARIA-label primitives shared by every `ui/` shape. +- `node_modules/@objectstack/spec/src/ui/sharing.zod.ts` — Sharing & Embedding Protocol +- `node_modules/@objectstack/spec/src/ui/view.zod.ts` — View protocol schemas — the `view` metadata type and its three persisted body spellings. ## How to read these From aaa135c5cc4d3fbd13c4bce2fff09da67d265d80 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 12:52:04 +0000 Subject: [PATCH 04/11] =?UTF-8?q?revert(client):=20drop=20auth.mePermissio?= =?UTF-8?q?ns()=20=E2=80=94=20/auth/me/permissions=20sits=20in=20no=20rout?= =?UTF-8?q?e=20ledger=20the=20client=20conformance=20test=20reads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client's URL conformance test (client-url-conformance.test.ts) requires every SDK URL to match an exact ledger route; /auth/me/permissions is mounted by plugin-hono-server and appears in none, so the method would be credited only by the * /auth/** wildcard. The typed read is left to the consumer card. Co-Authored-By: Claude --- .../client/src/auth-me-permissions.test.ts | 62 ------------------- packages/client/src/index.ts | 21 ------- 2 files changed, 83 deletions(-) delete mode 100644 packages/client/src/auth-me-permissions.test.ts diff --git a/packages/client/src/auth-me-permissions.test.ts b/packages/client/src/auth-me-permissions.test.ts deleted file mode 100644 index 3cd8fa09f49..00000000000 --- a/packages/client/src/auth-me-permissions.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * [#28508] `client.auth.mePermissions()` — the typed read of - * `GET /auth/me/permissions`, whose body `MePermissionsResponseSchema` - * (`@objectstack/spec/api`) declares strictly. The console reads this once per - * session; `hasAnyQueue` is on it for the list toolbar's queue toggle. - */ - -import { describe, it, expect, vi } from 'vitest'; -import { MePermissionsResponseSchema } from '@objectstack/spec/api'; -import { ObjectStackClient } from './index'; - -function createMockClient(body: unknown, status = 200) { - const fetchMock = vi.fn().mockResolvedValue({ - ok: status >= 200 && status < 300, - status, - statusText: status === 200 ? 'OK' : 'Error', - json: async () => body, - headers: new Headers(), - }); - const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000', fetch: fetchMock }); - return { client, fetchMock }; -} - -const AUTHENTICATED = { - authenticated: true, - userId: 'usr_1', - tenantId: 'org_1', - positions: ['org_member', 'everyone'], - permissionSets: ['member_default'], - objects: {}, - fields: {}, - systemPermissions: [], - tabPermissions: {}, - hasAnyQueue: true, -} as const; - -describe('[#28508] client.auth.mePermissions', () => { - it('GETs /api/v1/auth/me/permissions and resolves the body as the route answers it', async () => { - const { client, fetchMock } = createMockClient(AUTHENTICATED); - const answer = await client.auth.mePermissions(); - - const [url, init] = fetchMock.mock.calls[0]; - expect(String(url)).toBe('http://localhost:3000/api/v1/auth/me/permissions'); - expect(init?.method ?? 'GET').toBe('GET'); - expect(answer).toEqual(AUTHENTICATED); - // The fixture is a body the declared contract accepts, so this case - // cannot pass on a shape the server would never send. - expect(MePermissionsResponseSchema.safeParse(answer).success).toBe(true); - // The type narrows on the discriminator. - if (answer.authenticated) expect(answer.hasAnyQueue).toBe(true); - else throw new Error('expected the authenticated branch'); - }); - - it('an anonymous caller resolves { authenticated: false }, not a throw', async () => { - const { client } = createMockClient({ authenticated: false }); - const answer = await client.auth.mePermissions(); - expect(answer).toEqual({ authenticated: false }); - expect(MePermissionsResponseSchema.safeParse(answer).success).toBe(true); - }); -}); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7ddfedc235f..027ab0283d1 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -31,7 +31,6 @@ import { PublishPackageDraftsResponse, LoginRequest, SessionResponse, - MePermissionsResponse, GetPresignedUrlRequest, PresignedUrlResponse, CompleteUploadRequest, @@ -4992,26 +4991,6 @@ export class ObjectStackClient { return normalizeSessionResponse(await res.json()); }, - /** - * [#28508] The caller's effective permissions: `GET /auth/me/permissions`, - * the one payload the console reads once per session for its whole - * permission layer, declared STRICTLY by `MePermissionsResponseSchema` - * (`@objectstack/spec/api`). - * - * Discriminate on `authenticated`: an anonymous caller resolves - * `{ authenticated: false }` at 200 (the route answers it so a client can - * tell "signed out" from a fault). An authenticated answer carries, among - * the rest, `hasAnyQueue` (does the caller's organization have at least one - * ownership queue), and `systemPermissions` / `tabPermissions` are absent - * only when the server runs without SecurityPlugin, where absent means - * unknown and an empty array means none. - */ - mePermissions: async (): Promise => { - const route = this.getRoute('auth'); - const res = await this.fetch(`${this.baseUrl}${route}/me/permissions`); - return (await res.json()) as MePermissionsResponse; - }, - /** * Register a new user account * Uses better-auth endpoint: POST /sign-up/email From 1262253110e1fa63fd4e25f5c830175027f3d2cf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 12:52:21 +0000 Subject: [PATCH 05/11] test(spec): the isomorphic-pin count gains the three /auth/me/permissions response schemas; changeset drops the client Co-Authored-By: Claude --- .changeset/28508-me-permissions-response-contract.md | 4 +--- packages/spec/src/type-alias-convention.pin.test.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.changeset/28508-me-permissions-response-contract.md b/.changeset/28508-me-permissions-response-contract.md index 9405500c5de..3dbacb254f0 100644 --- a/.changeset/28508-me-permissions-response-contract.md +++ b/.changeset/28508-me-permissions-response-contract.md @@ -1,11 +1,10 @@ --- '@objectstack/spec': patch -'@objectstack/client': patch '@objectstack/plugin-hono-server': patch '@objectstack/plugin-sharing': patch --- -feat(spec,client): a strict response contract for `GET /auth/me/permissions`, and `hasAnyQueue` on the ownership-queue contract +feat(spec): a strict response contract for `GET /auth/me/permissions`, and `hasAnyQueue` on the ownership-queue contract Clause-②: yes Metadata-protocol: none @@ -18,5 +17,4 @@ Metadata-protocol: none - Both branches are strict: an undeclared key is refused, not dropped. A key the server starts sending is declared here first. - Types `MePermissionsResponse`, `MePermissionsAnonymousResponse`, `MePermissionsAuthenticatedResponse`. - **`IOwnershipQueueService.hasAnyQueue(organizationId)`** (`@objectstack/spec/contracts`): does the organization have at least one ownership queue, `true` exactly when some name would resolve through `resolveQueue`. `@objectstack/plugin-sharing`'s `OwnershipQueueService` implements it. -- **`client.auth.mePermissions()`** (`@objectstack/client`): reads the route and resolves `MePermissionsResponse`. An anonymous caller resolves `{ authenticated: false }` at 200, not a throw. - **`@objectstack/plugin-hono-server`** types each body it builds for the route against the schema's types, and reads `hasAnyQueue` through the contract. The wire is unchanged. diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index 8be62e36d5d..1de234178af 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -285,7 +285,7 @@ import type * as M194 from './automation/assignment-rule.zod.js'; import type * as M195 from './security/queue.zod.js'; // --------------------------------------------------------------------------- -// 808 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. +// 811 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. // // That number is machine-checked, not hand-kept. The runtime companion at the // bottom of this file recomputes the pin count from the source and asserts that @@ -1745,7 +1745,7 @@ describe('ADR-0122 type-alias convention', () => { // this title and the section header above the pin list — are now asserted // against the recomputed count below, so neither can go stale without a red // test naming it. - it('still declares all 808 isomorphic pins', () => { + it('still declares all 811 isomorphic pins', () => { // The truth of each pin is proved by tsc, not here — an `Assert>` // that stops holding is a compile error with the alias named. What tsc // cannot notice is a pin that was DELETED: removing the assertion removes @@ -2719,7 +2719,11 @@ describe('ADR-0122 type-alias convention', () => { // shape, no default and no transform // (Iso_shared_connectorAuth__ConnectorInstanceSignedAlipayV3AuthSchema). // +1 added. - expect(pins).toHaveLength(808); + // 808 -> 811 is #28508: the `GET /auth/me/permissions` response contract + // (`api/auth.zod.ts`), strict objects with no default and no transform — + // the anonymous branch, the authenticated branch and their discriminated + // union (Iso_api_auth__MePermissions*ResponseSchema). +3 added. + expect(pins).toHaveLength(811); // The count is stated in PROSE twice as well — this case's title and the // section header above the pin list — and until commit c6b05c76a nothing read either From 3e38f46ff948c3901d3a35513120f7c3dfd17d97 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 12:58:58 +0000 Subject: [PATCH 06/11] chore(spec): regenerate the surface ledgers for the /auth/me/permissions response contract on main 3944d2e1 Co-Authored-By: Claude --- content/docs/references/index.mdx | 10 +++++----- .../api.md | 2 +- packages/spec/api-surface/api.json | 6 ++++++ packages/spec/authorable-surface/api.json | 11 +++++++++++ packages/spec/declaration-map/api.json | 6 ++++++ packages/spec/export-origins/api.json | 6 ++++++ packages/spec/json-schema.manifest/api.json | 3 +++ 7 files changed, 38 insertions(+), 6 deletions(-) diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index d918783a56c..f861a1dc511 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,7 +1,7 @@ --- title: Protocol reference — every schema by module navTitle: Protocol Reference -description: Every schema published by @objectstack/spec — 1584 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1587 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -21,7 +21,7 @@ counts are sums of the rows they head. Regenerate with | Module | Pages | Schemas | Description | | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | -| [API Protocol](/docs/references/api) | 34 | 460 | REST contracts, endpoints, routing, realtime, batch, discovery. | +| [API Protocol](/docs/references/api) | 34 | 463 | REST contracts, endpoints, routing, realtime, batch, discovery. | | [Automation Protocol](/docs/references/automation) | 15 | 77 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Data Protocol](/docs/references/data) | 31 | 174 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | @@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 175 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **205** | **1584** | 14 protocol modules | +| **Total** | **205** | **1587** | 14 protocol modules | --- @@ -63,7 +63,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations. ## API Protocol -**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **34 pages, 460 schemas** +**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **34 pages, 463 schemas** REST contracts, endpoints, routing, realtime, batch, discovery. @@ -71,7 +71,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. | :--- | :--- | | [`analytics.zod.ts`](/docs/references/api/analytics) | `AnalyticsEndpoint`, `AnalyticsMetadataResponse`, `AnalyticsQueryRequest`, `AnalyticsResultResponse`, `AnalyticsSqlResponse`, `DatasetCompareTo`, `DatasetSelection`, `DatasetTotals`, `GetAnalyticsMetaRequest` | | [`app-bundle.zod.ts`](/docs/references/api/app-bundle) | `AppMetadataBundleResponse` | -| [`auth.zod.ts`](/docs/references/api/auth) | `AuthProvider`, `LoginRequest`, `LoginType`, `RefreshTokenRequest`, `RegisterRequest`, `Session`, `SessionResponse`, `SessionUser`, `UserProfileResponse` | +| [`auth.zod.ts`](/docs/references/api/auth) | `AuthProvider`, `LoginRequest`, `LoginType`, `MePermissionsAnonymousResponse`, `MePermissionsAuthenticatedResponse`, `MePermissionsResponse`, `RefreshTokenRequest`, `RegisterRequest`, `Session`, `SessionResponse`, `SessionUser`, `UserProfileResponse` | | [`auth-endpoints.zod.ts`](/docs/references/api/auth-endpoints) | `AuthEndpoint`, `AuthFeaturesConfig`, `AuthProviderInfo`, `DeviceRequestResponse`, `DeviceTokenResponse`, `EmailPasswordConfigPublic`, `GetAuthConfigResponse` | | [`automation-api.zod.ts`](/docs/references/api/automation-api) | `AutomationApiErrorCode`, `AutomationFlowPathParams`, `AutomationRunPathParams`, `CloneFlowRequest`, `CloneFlowResponse`, `CreateFlowRequest`, `CreateFlowResponse`, `DeleteFlowRequest`, `DeleteFlowResponse`, `FlowFailureDetails`, `FlowInnerRefusal`, `GetFlowRequest`, `GetFlowResponse`, `GetRunRequest`, `GetRunResponse`, `ListRunsRequest`, `ListRunsResponse`, `ResumeFailureDetails`, `ToggleFlowRequest`, `ToggleFlowResponse`, `TriggerFlowRequest`, `TriggerFlowResponse`, `UpdateFlowRequest`, `UpdateFlowResponse` | | [`batch.zod.ts`](/docs/references/api/batch) | `BatchConfig`, `BatchOperationResult`, `BatchOperationType`, `BatchOptions`, `BatchRecord`, `BatchUpdateRequest`, `BatchUpdateResponse`, `CrossObjectBatchDroppedFields`, `CrossObjectBatchOperation`, `CrossObjectBatchRequest`, `CrossObjectBatchResponse`, `DeleteManyRequest`, `UpdateManyRecord`, `UpdateManyRequest` | diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md index 6f0f8431d55..026d1041d08 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md @@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate. | Dir | Sites | |---|---| -| `api/` | 474 | +| `api/` | 477 | diff --git a/packages/spec/api-surface/api.json b/packages/spec/api-surface/api.json index 95b10f2bc9f..7fc574e19ff 100644 --- a/packages/spec/api-surface/api.json +++ b/packages/spec/api-surface/api.json @@ -647,6 +647,12 @@ "MarkNotificationsReadRequestSchema (const)", "MarkNotificationsReadResponse (type)", "MarkNotificationsReadResponseSchema (const)", + "MePermissionsAnonymousResponse (type)", + "MePermissionsAnonymousResponseSchema (const)", + "MePermissionsAuthenticatedResponse (type)", + "MePermissionsAuthenticatedResponseSchema (const)", + "MePermissionsResponse (type)", + "MePermissionsResponseSchema (const)", "MetadataAuthoringClient (type)", "MetadataAuthoringClientSchema (const)", "MetadataAuthoringSurface (type)", diff --git a/packages/spec/authorable-surface/api.json b/packages/spec/authorable-surface/api.json index 5fc2984b66e..0053ba102cf 100644 --- a/packages/spec/authorable-surface/api.json +++ b/packages/spec/authorable-surface/api.json @@ -1092,6 +1092,17 @@ "api/MarkNotificationsReadRequest:ids", "api/MarkNotificationsReadResponse:readCount", "api/MarkNotificationsReadResponse:success", + "api/MePermissionsAnonymousResponse:authenticated", + "api/MePermissionsAuthenticatedResponse:authenticated", + "api/MePermissionsAuthenticatedResponse:fields", + "api/MePermissionsAuthenticatedResponse:hasAnyQueue", + "api/MePermissionsAuthenticatedResponse:objects", + "api/MePermissionsAuthenticatedResponse:permissionSets", + "api/MePermissionsAuthenticatedResponse:positions", + "api/MePermissionsAuthenticatedResponse:systemPermissions", + "api/MePermissionsAuthenticatedResponse:tabPermissions", + "api/MePermissionsAuthenticatedResponse:tenantId", + "api/MePermissionsAuthenticatedResponse:userId", "api/MetadataAuthoringClient:id", "api/MetadataAuthoringClient:name", "api/MetadataBulkRegisterRequest:continueOnError", diff --git a/packages/spec/declaration-map/api.json b/packages/spec/declaration-map/api.json index b60c10ce26d..3cb01388007 100644 --- a/packages/spec/declaration-map/api.json +++ b/packages/spec/declaration-map/api.json @@ -475,6 +475,12 @@ "MarkNotificationsReadRequestSchema": "api/MarkNotificationsReadRequest", "MarkNotificationsReadResponse": "api/MarkNotificationsReadResponse", "MarkNotificationsReadResponseSchema": "api/MarkNotificationsReadResponse", + "MePermissionsAnonymousResponse": "api/MePermissionsAnonymousResponse", + "MePermissionsAnonymousResponseSchema": "api/MePermissionsAnonymousResponse", + "MePermissionsAuthenticatedResponse": "api/MePermissionsAuthenticatedResponse", + "MePermissionsAuthenticatedResponseSchema": "api/MePermissionsAuthenticatedResponse", + "MePermissionsResponse": "api/MePermissionsResponse", + "MePermissionsResponseSchema": "api/MePermissionsResponse", "MetadataAuthoringClient": "api/MetadataAuthoringClient", "MetadataAuthoringClientSchema": "api/MetadataAuthoringClient", "MetadataAuthoringSurface": "api/MetadataAuthoringSurface", diff --git a/packages/spec/export-origins/api.json b/packages/spec/export-origins/api.json index 73133f764d9..336781c7b53 100644 --- a/packages/spec/export-origins/api.json +++ b/packages/spec/export-origins/api.json @@ -622,6 +622,12 @@ "MarkNotificationsReadRequestSchema": "src/api/protocol.zod.ts#MarkNotificationsReadRequestSchema (const)", "MarkNotificationsReadResponse": "src/api/protocol.zod.ts#MarkNotificationsReadResponse (type)", "MarkNotificationsReadResponseSchema": "src/api/protocol.zod.ts#MarkNotificationsReadResponseSchema (const)", + "MePermissionsAnonymousResponse": "src/api/auth.zod.ts#MePermissionsAnonymousResponse (type)", + "MePermissionsAnonymousResponseSchema": "src/api/auth.zod.ts#MePermissionsAnonymousResponseSchema (const)", + "MePermissionsAuthenticatedResponse": "src/api/auth.zod.ts#MePermissionsAuthenticatedResponse (type)", + "MePermissionsAuthenticatedResponseSchema": "src/api/auth.zod.ts#MePermissionsAuthenticatedResponseSchema (const)", + "MePermissionsResponse": "src/api/auth.zod.ts#MePermissionsResponse (type)", + "MePermissionsResponseSchema": "src/api/auth.zod.ts#MePermissionsResponseSchema (const)", "MetadataAuthoringClient": "src/api/protocol.zod.ts#MetadataAuthoringClient (type)", "MetadataAuthoringClientSchema": "src/api/protocol.zod.ts#MetadataAuthoringClientSchema (const)", "MetadataAuthoringSurface": "src/api/protocol.zod.ts#MetadataAuthoringSurface (type)", diff --git a/packages/spec/json-schema.manifest/api.json b/packages/spec/json-schema.manifest/api.json index 9a261b3e900..41ae5600d9d 100644 --- a/packages/spec/json-schema.manifest/api.json +++ b/packages/spec/json-schema.manifest/api.json @@ -261,6 +261,9 @@ "api/MarkAllNotificationsReadResponse", "api/MarkNotificationsReadRequest", "api/MarkNotificationsReadResponse", + "api/MePermissionsAnonymousResponse", + "api/MePermissionsAuthenticatedResponse", + "api/MePermissionsResponse", "api/MetadataAuthoringClient", "api/MetadataAuthoringSurface", "api/MetadataBulkRegisterRequest", From 4c474a1ede2adcdb4ac24900d2aea75317ae293c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 13:33:43 +0000 Subject: [PATCH 07/11] chore(spec): declare the /auth/me/permissions authenticated union in the variant-doc ledger (not-authorable) Co-Authored-By: Claude --- packages/spec/variant-docs.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/spec/variant-docs.json b/packages/spec/variant-docs.json index 4ec7c340e61..a8aabe88ccf 100644 --- a/packages/spec/variant-docs.json +++ b/packages/spec/variant-docs.json @@ -188,6 +188,12 @@ "content/docs/automation/flows.mdx" ], "note": "The automation engine's verdict the flow meta read serves beside a stored flow (#23316). Not authored — the engine answers it — but GOVERNED rather than exempt: a reader acts on each state differently, and automation/flows.mdx ('Discovery & Registration') names all three in its state table." + }, + { + "key": "authenticated:false|true", + "label": "/auth/me/permissions response body", + "exempt": "not-authorable", + "reason": "Wire response of GET /auth/me/permissions (#28508): the server answers the anonymous or the authenticated body from the session, and nobody writes either into metadata. content/docs/references/api/auth.mdx is generated from MePermissionsResponseSchema." } ] } From 567e3dc8230bd5af5d160a005efee9cecb831964 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 13:54:56 +0000 Subject: [PATCH 08/11] fix(plugin-hono-server): keep the degraded and anonymous /auth/me/permissions bodies as bare literals the route-envelope census counts The inline `satisfies` hid both from check:route-envelope (found 7, pinned 9). The strict schema parse in the route and door tests still binds them. Co-Authored-By: Claude --- .changeset/28508-me-permissions-response-contract.md | 2 +- .../plugin-hono-server/src/current-user-endpoints.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/28508-me-permissions-response-contract.md b/.changeset/28508-me-permissions-response-contract.md index 3dbacb254f0..8b4029cb4a2 100644 --- a/.changeset/28508-me-permissions-response-contract.md +++ b/.changeset/28508-me-permissions-response-contract.md @@ -17,4 +17,4 @@ Metadata-protocol: none - Both branches are strict: an undeclared key is refused, not dropped. A key the server starts sending is declared here first. - Types `MePermissionsResponse`, `MePermissionsAnonymousResponse`, `MePermissionsAuthenticatedResponse`. - **`IOwnershipQueueService.hasAnyQueue(organizationId)`** (`@objectstack/spec/contracts`): does the organization have at least one ownership queue, `true` exactly when some name would resolve through `resolveQueue`. `@objectstack/plugin-sharing`'s `OwnershipQueueService` implements it. -- **`@objectstack/plugin-hono-server`** types each body it builds for the route against the schema's types, and reads `hasAnyQueue` through the contract. The wire is unchanged. +- **`@objectstack/plugin-hono-server`** types the route's body builder against `MePermissionsAuthenticatedResponse`, and reads `hasAnyQueue` through the contract. The wire is unchanged. diff --git a/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts b/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts index 39c7a53ee71..27850db1cc3 100644 --- a/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts +++ b/packages/plugins/plugin-hono-server/src/current-user-endpoints.ts @@ -50,7 +50,7 @@ import { type RequestAuthService, } from '@objectstack/core'; import type { ExecutionContext } from '@objectstack/spec/kernel'; -import type { MePermissionsAnonymousResponse, MePermissionsAuthenticatedResponse } from '@objectstack/spec/api'; +import type { MePermissionsAuthenticatedResponse } from '@objectstack/spec/api'; import { preferredLocaleFromHeader } from '@objectstack/spec/system'; import { OWNERSHIP_QUEUE_SERVICE, @@ -897,7 +897,7 @@ export function registerCurrentUserEndpoints( rawApp.get(`${prefix}/auth/me/permissions`, withRequestContext(async (c, ctx, resolveCtx) => { const execCtx = await resolveCtx(c); if (!execCtx?.userId) { - return c.json({ authenticated: false } satisfies MePermissionsAnonymousResponse); + return c.json({ authenticated: false }); } // Captured BEFORE the `try`: the closed body the outer `catch` answers is // built from this alone (see {@link mePermissionsBody}). @@ -947,7 +947,7 @@ export function registerCurrentUserEndpoints( objects: {}, fields: {}, hasAnyQueue, - } satisfies MePermissionsAuthenticatedResponse); + }); } // [#7616] The caller's sets, resolved ONCE by the owner of the // rule: positions expanded, the ADR-0090 D5 baseline applied From d86253262001646cf28fbaaa8dd928e1509a727a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 15:27:36 +0000 Subject: [PATCH 09/11] fix(spec): hasAnyQueue is optional on IOwnershipQueueService; the change is a widening that adds to the protocol (spec minor) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contract review on 567e3dc8: a required member is an undeclared break for an out-of-tree implementer, and the route already guards a missing method. The changeset declares Clause-② yes (widening) and Metadata-protocol added, with @objectstack/spec at minor, since the authorable-surface baseline moves. The objectstack-api skill index keeps its pre-#28508 transitive pointers through a TRANSITIVE_ALLOWLIST row instead of gaining 22 through the new permission.zod edge. The schema's doc names the older GetEffectivePermissionsResponseSchema it is not. Co-Authored-By: Claude --- .../28508-me-permissions-response-contract.md | 10 +++++---- packages/spec/scripts/lib/skill-map-guards.ts | 22 +++++++++++++++++++ packages/spec/src/api/auth.zod.ts | 6 +++++ .../src/contracts/ownership-queue-service.ts | 6 ++++- skills/objectstack-api/references/_index.md | 22 ------------------- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/.changeset/28508-me-permissions-response-contract.md b/.changeset/28508-me-permissions-response-contract.md index 8b4029cb4a2..775dbf3a86c 100644 --- a/.changeset/28508-me-permissions-response-contract.md +++ b/.changeset/28508-me-permissions-response-contract.md @@ -1,13 +1,14 @@ --- -'@objectstack/spec': patch +'@objectstack/spec': minor '@objectstack/plugin-hono-server': patch '@objectstack/plugin-sharing': patch +'@object-ui/permissions': patch --- feat(spec): a strict response contract for `GET /auth/me/permissions`, and `hasAnyQueue` on the ownership-queue contract -Clause-②: yes -Metadata-protocol: none +Clause-②: yes (widening) +Metadata-protocol: added `GET /auth/me/permissions` is the payload the console reads once per session for its whole permission layer. Until now nothing in `@objectstack/spec` declared its body. It is now declared, strictly: @@ -16,5 +17,6 @@ Metadata-protocol: none - `MePermissionsAuthenticatedResponseSchema`: `userId`, `tenantId` (nullable), `positions`, `permissionSets`, `objects` (effective object permissions), `fields` (`{ readable, editable }` per `object.field`), `systemPermissions` and `tabPermissions` (optional: absent only on the body a server without SecurityPlugin answers, where absent means unknown), and `hasAnyQueue: boolean`. - Both branches are strict: an undeclared key is refused, not dropped. A key the server starts sending is declared here first. - Types `MePermissionsResponse`, `MePermissionsAnonymousResponse`, `MePermissionsAuthenticatedResponse`. -- **`IOwnershipQueueService.hasAnyQueue(organizationId)`** (`@objectstack/spec/contracts`): does the organization have at least one ownership queue, `true` exactly when some name would resolve through `resolveQueue`. `@objectstack/plugin-sharing`'s `OwnershipQueueService` implements it. +- **`IOwnershipQueueService.hasAnyQueue?(organizationId)`** (`@objectstack/spec/contracts`): does the organization have at least one ownership queue, `true` exactly when some name would resolve through `resolveQueue`. Optional, like `declaredQueueNamed?`, so an implementation written before it still satisfies the contract; the route answers `false` without it. `@objectstack/plugin-sharing`'s `OwnershipQueueService` implements it. - **`@objectstack/plugin-hono-server`** types the route's body builder against `MePermissionsAuthenticatedResponse`, and reads `hasAnyQueue` through the contract. The wire is unchanged. +- **`@object-ui/permissions`**: `MePermissionsResponse` is now the spec's type, re-exported from `@objectstack/spec/api` (with `MePermissionsAuthenticatedResponse` beside it), in place of a hand-written interface that had drifted from the wire. The interface read a `roles` key the server never sends; the server's key is `positions`, so `usePermissions().roles` under `MePermissionsProvider` was always empty and now carries the caller's positions. A fixture typed as the response migrates `roles: [...]` → `positions: [...]` and adds `hasAnyQueue`; the anonymous body is `{ authenticated: false }` and nothing else. diff --git a/packages/spec/scripts/lib/skill-map-guards.ts b/packages/spec/scripts/lib/skill-map-guards.ts index bc5d4a416a7..7d6188cfd43 100644 --- a/packages/spec/scripts/lib/skill-map-guards.ts +++ b/packages/spec/scripts/lib/skill-map-guards.ts @@ -280,6 +280,28 @@ export const TRANSITIVE_ALLOWLIST: Record = { // declaration those keys must match -- lives here. 'data/field.zod.ts', ], + // The closure as it stood before #28508. `api/auth.zod.ts` (core) gained one + // import edge then -- `security/permission.zod.ts`, for the effective object + // permissions `MePermissionsAuthenticatedResponseSchema` carries -- and that + // edge alone dragged 22 pointers into this index (fields, objects, views, + // actions, RLS, expressions) that the objectstack-api SKILL.md does not teach. + // These are the transitive pointers the index already published; a new one + // joins by a row here, not by an import edge. + 'objectstack-api': [ + 'api/contract.zod.ts', + 'api/error-code-ledger.zod.ts', + 'api/realtime-shared.zod.ts', + 'data/data-engine.zod.ts', + 'data/filter.zod.ts', + 'data/query.zod.ts', + 'kernel/execution-context.zod.ts', + 'kernel/metadata-protection.zod.ts', + 'security/explain.zod.ts', + 'shared/duration.zod.ts', + 'shared/epoch.zod.ts', + 'shared/http.zod.ts', + 'shared/suggestions.zod.ts', + ], }; /** Whether a `packages/spec/src`-relative path exists on disk; injected so the guard stays pure. */ diff --git a/packages/spec/src/api/auth.zod.ts b/packages/spec/src/api/auth.zod.ts index c5684fc3827..59a0e9f0d23 100644 --- a/packages/spec/src/api/auth.zod.ts +++ b/packages/spec/src/api/auth.zod.ts @@ -162,6 +162,12 @@ export const MePermissionsAnonymousResponseSchema = lazySchema(() => z.object({ * * Strict: an undeclared key is refused, so a key the server starts sending is * declared here first (`Clause-②`). + * + * Not to be confused with `GetEffectivePermissionsResponseSchema` + * (`api/protocol.zod.ts`), the older, non-strict answer of the optional + * `getEffectivePermissions` protocol method: it declares only `objects` and + * `systemPermissions`, and no route serves it. This schema is the body + * `/auth/me/permissions` actually answers. */ export const MePermissionsAuthenticatedResponseSchema = lazySchema(() => z.object({ authenticated: z.literal(true).describe('A session was resolved for the caller.'), diff --git a/packages/spec/src/contracts/ownership-queue-service.ts b/packages/spec/src/contracts/ownership-queue-service.ts index 3749062710d..9561fa195a8 100644 --- a/packages/spec/src/contracts/ownership-queue-service.ts +++ b/packages/spec/src/contracts/ownership-queue-service.ts @@ -119,8 +119,12 @@ export interface IOwnershipQueueService { * (`MePermissionsAuthenticatedResponseSchema`, `@objectstack/spec/api`), so * a member who may not read `sys_queue` can still tell whether a queue * filter can ever return a row. It discloses no name and no count. + * + * Optional, as {@link declaredQueueNamed} is, so an implementation written + * before it still satisfies the contract: the route answers `false` when the + * registered service does not carry it. */ - hasAnyQueue(organizationId: string | null): Promise; + hasAnyQueue?(organizationId: string | null): Promise; /** * The declared queue named `name`, whether or not the activation ledger diff --git a/skills/objectstack-api/references/_index.md b/skills/objectstack-api/references/_index.md index ddeb3b94c98..f4b8571eaf9 100644 --- a/skills/objectstack-api/references/_index.md +++ b/skills/objectstack-api/references/_index.md @@ -23,37 +23,15 @@ from `node_modules` — there is no local copy in the skill bundle. - `node_modules/@objectstack/spec/src/api/error-code-ledger.zod.ts` — Error-Code Ledger (ADR-0112 D3). - `node_modules/@objectstack/spec/src/api/realtime-shared.zod.ts` — Realtime Shared Protocol - `node_modules/@objectstack/spec/src/data/data-engine.zod.ts` — Data Engine Protocol -- `node_modules/@objectstack/spec/src/data/date-macros.zod.ts` — Date Macro Tokens — the declarative placeholders the UI substitutes -- `node_modules/@objectstack/spec/src/data/field-value.zod.ts` — Field runtime VALUE-shape contract (ADR-0104 D1). -- `node_modules/@objectstack/spec/src/data/field.zod.ts` — Exports: FieldType, SelectOptionSchema, LocationCoordinatesSchema, CurrencyConfigSchema, UniqueScopeSchema - `node_modules/@objectstack/spec/src/data/filter.zod.ts` — Unified Query DSL Specification -- `node_modules/@objectstack/spec/src/data/hook-body.zod.ts` — Exports: HookBodyCapability, ExpressionBodySchema, ScriptBodySchema, HookBodySchema -- `node_modules/@objectstack/spec/src/data/object.zod.ts` — Exports: ApiMethod, ApiOperationSchema, ObjectCapabilities, IndexSchema, TenancyConfigSchema - `node_modules/@objectstack/spec/src/data/query.zod.ts` — QueryAST — Abstract Syntax Tree for data queries. -- `node_modules/@objectstack/spec/src/data/validation.zod.ts` — ObjectStack Validation Protocol -- `node_modules/@objectstack/spec/src/identity/eval-user.zod.ts` — EvalUser — the one user-context contract (ADR-0068 D1). - `node_modules/@objectstack/spec/src/kernel/execution-context.zod.ts` — Exports: AuthGateCodeSchema, AuthGateScopeSchema, ExecutionContextSchema - `node_modules/@objectstack/spec/src/kernel/metadata-protection.zod.ts` — Metadata Protection Model — Phase 1 (ADR-0010) -- `node_modules/@objectstack/spec/src/kernel/override-declaration.zod.ts` — The override declaration (ADR-0146 D2) - `node_modules/@objectstack/spec/src/security/explain.zod.ts` — [ADR-0090 D6] Access-explanation contract — `explain(principal, object, -- `node_modules/@objectstack/spec/src/security/permission.zod.ts` — Exports: ObjectAccessScopeSchema, ObjectPermissionSchema, EffectiveObjectPermissionSchema, AdminScopeSchema, FieldPermissionSchema -- `node_modules/@objectstack/spec/src/security/rls.zod.ts` — Row-Level Security (RLS) Protocol -- `node_modules/@objectstack/spec/src/shared/conditional-formatting.zod.ts` — The **single declaration** of a conditional formatting rule — - `node_modules/@objectstack/spec/src/shared/duration.zod.ts` — Exports: DurationMs, DurationSeconds - `node_modules/@objectstack/spec/src/shared/epoch.zod.ts` — Exports: EpochMs -- `node_modules/@objectstack/spec/src/shared/expression.zod.ts` — Expression Protocol - `node_modules/@objectstack/spec/src/shared/http.zod.ts` — Shared HTTP Schemas -- `node_modules/@objectstack/spec/src/shared/identifiers.zod.ts` — Exports: SystemIdentifierSchema, SnakeCaseIdentifierSchema, MetadataItemNameSchema -- `node_modules/@objectstack/spec/src/shared/protection.zod.ts` — Package-level metadata protection (ADR-0010 §3.7 — Phase 4.3) - `node_modules/@objectstack/spec/src/shared/suggestions.zod.ts` — "Did you mean?" Suggestion Utilities -- `node_modules/@objectstack/spec/src/shared/value-domain.zod.ts` — Standard value domains: one closed vocabulary and one membership predicate for settings and fields. -- `node_modules/@objectstack/spec/src/ui/action-params.zod.ts` — The action DISPATCH contract: what the platform validates on the way in, and -- `node_modules/@objectstack/spec/src/ui/action.zod.ts` — Exports: ActionParamSchema, ActionType, ActionLocationSchema, ActionAiSchema, ActionSchema -- `node_modules/@objectstack/spec/src/ui/bulk-action.zod.ts` — Bulk Action Schemas -- `node_modules/@objectstack/spec/src/ui/chart.zod.ts` — Unified Chart Type Taxonomy -- `node_modules/@objectstack/spec/src/ui/i18n.zod.ts` — Display-label and ARIA-label primitives shared by every `ui/` shape. -- `node_modules/@objectstack/spec/src/ui/sharing.zod.ts` — Sharing & Embedding Protocol -- `node_modules/@objectstack/spec/src/ui/view.zod.ts` — View protocol schemas — the `view` metadata type and its three persisted body spellings. ## How to read these From 0eb64410bfc2f25acf1f9b8985675654342f89ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 15:27:37 +0000 Subject: [PATCH 10/11] feat(ui/permissions): MePermissionsResponse is derived from the spec contract, not hand-written @object-ui/permissions re-exports MePermissionsResponse (and MePermissionsAuthenticatedResponse) from @objectstack/spec/api. The hand-written interface had drifted from the wire: it read a roles key the server never sends, so usePermissions().roles under MePermissionsProvider was always empty; it now carries the server's positions. Only the authenticated body's grants are read. Test fixtures typed as the response move roles to positions and add hasAnyQueue; the anonymous fixture is { authenticated: false }. Co-Authored-By: Claude --- .../App.homePermissions-12700.test.tsx | 9 ++- ui/apps/console/src/components/studioEntry.ts | 3 +- .../consoleFaces.displayLocale-9909.test.tsx | 7 +- ...mezonePrompt.afterBootPaint-12980.test.tsx | 7 +- .../WorkspaceTimezonePrompt.test.tsx | 7 +- ...valsInboxPage.displayLocale-10375.test.tsx | 7 +- ...rovalsInboxPage.queueHiddenAmount.test.tsx | 7 +- ...nboxPage.readerLocaleLabels-26883.test.tsx | 7 +- ...servedPageMemberPermissions-13324.test.tsx | 7 +- ...ataSource.servedPageSummary-12483.test.tsx | 5 +- ...rce.servedPageSummaryAmount-12534.test.tsx | 5 +- .../AppContent.declaredLanding-28450.test.tsx | 9 ++- .../AppContent.hasCapability-13083.test.tsx | 7 +- ...pContent.landingMemberGates-12426.test.tsx | 9 ++- ...ppContent.landingWaitsReads-13097.test.tsx | 3 +- ...ent.noReadableObjectLanding-25338.test.tsx | 9 ++- .../ExcelImportBar.importGrant-12082.test.tsx | 7 +- .../HomePage.aiStudioDisabled.test.tsx | 9 ++- .../HomePage.authoringCapabilityGate.test.tsx | 9 ++- .../HomePage.draftsReadGate-25894.test.tsx | 9 ++- .../HomePage.marketplaceDisabled.test.tsx | 9 ++- .../navEntryDataSource-13029.test.tsx | 7 +- .../navEntryReadReuse-13323.test.tsx | 7 +- ...aredUserFeeds.activityGrant-12081.test.tsx | 9 ++- ...redUserFeeds.activityRoster-12530.test.tsx | 7 +- .../AppHeader.aboutBuild-12970.test.tsx | 7 +- .../AppHeader.appBarLeftGroup-12461.test.tsx | 7 +- .../AppHeader.appIdentity-12201.test.tsx | 7 +- .../AppHeader.centeredSearch-12472.test.tsx | 7 +- .../AppHeader.crumbFold-12702.test.tsx | 7 +- ...Header.dashboardPageNoCrumb-12526.test.tsx | 7 +- ...pHeader.deferredChromeReads-12980.test.tsx | 7 +- ...esignInStudioNeedsAuthoring-10899.test.tsx | 7 +- ...ditAppFollowsPackagePosture-12615.test.tsx | 7 +- .../AppHeader.homeMark-12605.test.tsx | 7 +- .../AppHeader.hostSingleApp-12913.test.tsx | 7 +- .../__tests__/AppHeader.inboxVariant.test.tsx | 7 +- ...pageRouteNoAuthoringHandles-12198.test.tsx | 7 +- .../AppHeader.sidebarTrigger-12429.test.tsx | 7 +- ...rItemMenu.designOpensStudio-12520.test.tsx | 7 +- ...edSidebar.navCanPermissions-12700.test.tsx | 9 ++- ...ifiedSidebar.objectReadGate-12109.test.tsx | 7 +- .../currentUserCan-4421.render.test.tsx | 7 +- ...UserCan-disabledLegs-11242.render.test.tsx | 7 +- ...ntUserCan-failClosed-11212.render.test.tsx | 7 +- .../hasCapability-13006.render.test.tsx | 7 +- ...ectView.listActionsReadGate-12109.test.tsx | 7 +- ...iew.managedByBadgeAuthoring-12194.test.tsx | 7 +- .../ObjectView.setupListBadge-13385.test.tsx | 7 +- .../ObjectView.viewRefusal-12112.test.tsx | 7 +- ...w.authoredActivityThreading-12249.test.tsx | 9 ++- ...lView.feedCommentEditDelete-12268.test.tsx | 9 ++- .../RecordDetailView.feedGrant-12082.test.tsx | 9 ++- ...iew.managedByBadgeAuthoring-12194.test.tsx | 7 +- ...DetailView.replyCountOnRead-12144.test.tsx | 9 ++- ...tailView.setupManagedByChip-13385.test.tsx | 7 +- ...ecordFormPage.hasCapability-13083.test.tsx | 7 +- ...age.managedByBadgeAuthoring-12194.test.tsx | 7 +- .../__tests__/AppReportIndex-13020.test.tsx | 7 +- .../AppReportIndex.groupsViews-27526.test.tsx | 7 +- ...eportIndex.navContributions-13273.test.tsx | 7 +- .../AppReportIndex.navGates-26892.test.tsx | 7 +- .../AppReportIndex.otherApps-27356.test.tsx | 7 +- ...StudioFollowsPackagePosture-12615.test.tsx | 7 +- ....editInStudioNeedsAuthoring-10899.test.tsx | 7 +- ...iew.editInStudioOpensStudio-12540.test.tsx | 7 +- ...hmentsPanel.affordanceGrant-12047.test.tsx | 7 +- ...nav-object-read-gate-12109.render.test.tsx | 7 +- ...esourceEditPage.tabDeepLink-12496.test.tsx | 7 +- .../setupHoldersGrant-12082.test.tsx | 13 ++-- .../trailDoorsGate-12475.test.tsx | 7 +- ...udioDesignSurface.itemTools-28239.test.tsx | 2 +- .../LookupField.createGrant-12082.test.tsx | 7 +- ...dPickerDialog.fieldReadRule-10594.test.tsx | 9 ++- .../permissions/src/MePermissionsProvider.tsx | 75 ++++++------------- ...onsProvider.absentBitDenies-26193.test.tsx | 7 +- ...ionsProvider.capabilitiesReported.test.tsx | 21 ++++-- ...sionsProvider.ownerTransfer-12103.test.tsx | 10 ++- .../__tests__/MePermissionsProvider.test.tsx | 21 ++++-- .../RecordFieldAccessScope-26594.test.tsx | 7 +- ...ieldAccessScope.rowWritable-27138.test.tsx | 7 +- .../__tests__/ServerReadScope-13324.test.tsx | 7 +- .../__tests__/effectiveObjects-4421.test.tsx | 7 +- .../providerCtxIdentity.discarded.test.tsx | 7 +- ui/packages/permissions/src/index.ts | 7 +- ...Calendar.quickCreateHandoff-12291.test.tsx | 7 +- .../ObjectCalendar.writeGrant-12082.test.tsx | 7 +- ...tailSection.fieldWriteGrant-12103.test.tsx | 7 +- ...RecordDetailPanel.editGrant-12082.test.tsx | 7 +- ...istRenderer.refusalAudience-11768.test.tsx | 9 ++- ...tedListRenderer.removeGrant-12082.test.tsx | 9 ++- .../RelatedList.addGrant-12082.test.tsx | 9 ++- .../src/queueClaim-25769.test.tsx | 7 +- ...rd-blocks.recordFieldAccess-27146.test.tsx | 9 ++- ...d-blocks.requiredPermissions-gate.test.tsx | 9 ++- .../record-blocks.rowWritable-27138.test.tsx | 9 ++- ...-actions.requiredPermissions-gate.test.tsx | 9 ++- ...ecordEditable.inflightShare-11699.test.tsx | 7 +- .../useRecordEditable.invalidation.test.tsx | 7 +- .../useRecordEditable.principalScope.test.tsx | 7 +- ...eRecordEditable.recheckOnce-13341.test.tsx | 3 +- .../LineItemsPanel.lineGrant-12082.test.tsx | 7 +- .../MasterDetailForm.lineGrant-12082.test.tsx | 7 +- .../src/affordanceGrantMap-12082.test.tsx | 7 +- .../src/createFormGrant-12082.test.tsx | 7 +- .../src/listNewAbsentCreateBit-26193.test.tsx | 7 +- .../src/recordFieldAccess-26594.test.tsx | 3 +- .../src/ObjectGantt.writeGrant-12082.test.tsx | 7 +- ...ObjectGrid.emptyStateAccess-25898.test.tsx | 7 +- .../__tests__/bulkUpdateGrant-12082.test.tsx | 7 +- .../inlineEditFieldWriteGrant-12103.test.tsx | 7 +- ...ordCrudVerdicts.afterAction-13341.test.tsx | 7 +- ...seRecordCrudVerdicts.invalidation.test.tsx | 7 +- ...RecordCrudVerdicts.principalScope.test.tsx | 7 +- .../src/ObjectKanban.moveGrant-12082.test.tsx | 7 +- .../ListView.emptyStateAccess-25898.test.tsx | 7 +- .../ListView.fieldListRead-11984.test.tsx | 7 +- .../ListView.filterFieldRead-11925.test.tsx | 7 +- .../ListView.sortFieldRead-11943.test.tsx | 7 +- .../ListView.sortRemovableOnly-11943.test.tsx | 7 +- .../ListViewBlock.inertKeys-13074.test.tsx | 9 ++- ...ectGallery.emptyStateAccess-25898.test.tsx | 7 +- ...atasetReportRenderer.export-25873.test.tsx | 7 +- .../ObjectView.createGrant-12082.test.tsx | 7 +- 124 files changed, 552 insertions(+), 442 deletions(-) diff --git a/ui/apps/console/src/__tests__/App.homePermissions-12700.test.tsx b/ui/apps/console/src/__tests__/App.homePermissions-12700.test.tsx index a2b135b3b01..bd31b1c87db 100644 --- a/ui/apps/console/src/__tests__/App.homePermissions-12700.test.tsx +++ b/ui/apps/console/src/__tests__/App.homePermissions-12700.test.tsx @@ -36,7 +36,7 @@ import { useContext, type ReactNode } from 'react'; import { useNavigate } from 'react-router-dom'; import { MetadataCtx, type MetadataContextValue } from '@object-ui/react'; import { __resetVisibilityPredicateWarnings } from '../../../../packages/react/src/utils/visibilityDiagnostic'; -import { usePermissions, type MePermissionsResponse } from '@object-ui/permissions'; +import { usePermissions, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // Not on the package's entry: the context object itself, read by its module id // (`@object-ui/permissions` resolves to `packages/permissions/src`, so this is // the same module instance the provider writes). @@ -80,12 +80,13 @@ const METADATA: MetadataContextValue = { getTypeStatus: () => 'ready', } as unknown as MetadataContextValue; -function permissionsAnswer(): MePermissionsResponse { +function permissionsAnswer(): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: ['studio.access'], objects: { @@ -94,7 +95,7 @@ function permissionsAnswer(): MePermissionsResponse { crm_opportunity: { allowRead: net.opportunityRead }, }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } function respond(input: unknown): Promise { diff --git a/ui/apps/console/src/components/studioEntry.ts b/ui/apps/console/src/components/studioEntry.ts index 781c106bc4c..c59f0f3ddc8 100644 --- a/ui/apps/console/src/components/studioEntry.ts +++ b/ui/apps/console/src/components/studioEntry.ts @@ -191,7 +191,8 @@ export function useStudioEntry(options?: { } const json = (await res.json()) as MePermissionsResponse; if (cancelled) return; - const reported = json?.systemPermissions; + // [objectstack#28508] Only the authenticated body carries systemPermissions. + const reported = json?.authenticated === true ? json.systemPermissions : undefined; setState({ status: 'loaded', systemPermissions: Array.isArray(reported) ? reported : undefined, diff --git a/ui/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx b/ui/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx index 4ab86582b06..9f4dc30cfc7 100644 --- a/ui/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx +++ b/ui/apps/console/src/pages/consoleFaces.displayLocale-9909.test.tsx @@ -34,7 +34,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { LocalizationProvider } from '@object-ui/i18n'; import { isMachineLocale, recordLocaleArgumentsAsync } from '@object-ui/test-support'; @@ -123,12 +123,13 @@ afterEach(() => { vi.unstubAllGlobals(); }); -function permissions(): MePermissionsResponse { +function permissions(): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_1', tenantId: 't_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: {}, fields: {}, diff --git a/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.afterBootPaint-12980.test.tsx b/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.afterBootPaint-12980.test.tsx index 38145348677..9cf1171471b 100644 --- a/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.afterBootPaint-12980.test.tsx +++ b/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.afterBootPaint-12980.test.tsx @@ -14,7 +14,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, cleanup, render, screen } from '@testing-library/react'; import { I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const { AUTH, toastError, toastSuccess } = vi.hoisted(() => ({ AUTH: { user: { id: 'u_admin' }, activeOrganization: { id: 'org_1' } }, @@ -114,11 +114,12 @@ const settingsCalls = (method: string, url = '/api/settings/localization') => fetchMock.mock.calls.filter(([u, init]) => u === url && ((init as RequestInit | undefined)?.method ?? 'GET') === method); function permissionsPayload(systemPermissions: string[] | undefined): MePermissionsResponse { - const base: MePermissionsResponse = { + const base: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_admin', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: {}, fields: {}, diff --git a/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.test.tsx b/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.test.tsx index ea0240d8ca0..fa509024061 100644 --- a/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.test.tsx +++ b/ui/apps/console/src/pages/settings/__tests__/WorkspaceTimezonePrompt.test.tsx @@ -24,7 +24,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const { AUTH, toastError, toastSuccess } = vi.hoisted(() => ({ AUTH: { user: { id: 'u_admin' }, activeOrganization: { id: 'org_1' } }, @@ -130,11 +130,12 @@ const settingsCalls = (method: string, url = '/api/settings/localization') => fetchMock.mock.calls.filter(([u, init]) => u === url && ((init as RequestInit | undefined)?.method ?? 'GET') === method); function permissionsPayload(systemPermissions: string[] | undefined): MePermissionsResponse { - const base: MePermissionsResponse = { + const base: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_admin', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: {}, fields: {}, diff --git a/ui/apps/console/src/pages/system/ApprovalsInboxPage.displayLocale-10375.test.tsx b/ui/apps/console/src/pages/system/ApprovalsInboxPage.displayLocale-10375.test.tsx index 572e5744e19..9a4a8d44a14 100644 --- a/ui/apps/console/src/pages/system/ApprovalsInboxPage.displayLocale-10375.test.tsx +++ b/ui/apps/console/src/pages/system/ApprovalsInboxPage.displayLocale-10375.test.tsx @@ -32,7 +32,7 @@ import '@testing-library/jest-dom/vitest'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render, screen } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { I18nProvider, LocalizationProvider } from '@object-ui/i18n'; import { isMachineLocale, recordLocaleArgumentsAsync } from '@object-ui/test-support'; @@ -99,12 +99,13 @@ import { ApprovalsInboxPage } from './ApprovalsInboxPage'; afterEach(() => cleanup()); -function permissions(): MePermissionsResponse { +function permissions(): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_1', tenantId: 't_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: {}, fields: {}, diff --git a/ui/apps/console/src/pages/system/ApprovalsInboxPage.queueHiddenAmount.test.tsx b/ui/apps/console/src/pages/system/ApprovalsInboxPage.queueHiddenAmount.test.tsx index 28bd4b0bee1..f509f7198bb 100644 --- a/ui/apps/console/src/pages/system/ApprovalsInboxPage.queueHiddenAmount.test.tsx +++ b/ui/apps/console/src/pages/system/ApprovalsInboxPage.queueHiddenAmount.test.tsx @@ -75,7 +75,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vitest'; import { render, screen, cleanup, within, waitFor, fireEvent } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const APP = 'com.objectstack.account'; const PURCHASE = 'showcase_purchase'; @@ -216,12 +216,13 @@ vi.mock('../../services/approvalsApi', async (importOriginal) => ({ // Imported after the mocks so the page picks them up. import { ApprovalsInboxPage } from './ApprovalsInboxPage'; -function permissionsPayload(): MePermissionsResponse { +function permissionsPayload(): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_1', tenantId: 't_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: {}, fields: {}, diff --git a/ui/apps/console/src/pages/system/ApprovalsInboxPage.readerLocaleLabels-26883.test.tsx b/ui/apps/console/src/pages/system/ApprovalsInboxPage.readerLocaleLabels-26883.test.tsx index 41730af18a5..e5f16cb1ac5 100644 --- a/ui/apps/console/src/pages/system/ApprovalsInboxPage.readerLocaleLabels-26883.test.tsx +++ b/ui/apps/console/src/pages/system/ApprovalsInboxPage.readerLocaleLabels-26883.test.tsx @@ -31,7 +31,7 @@ import { useEffect, useState, type ReactNode } from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { I18nProvider } from '@object-ui/i18n'; const { ADAPTER, AUTH } = vi.hoisted(() => ({ @@ -123,12 +123,13 @@ afterEach(() => { vi.unstubAllGlobals(); }); -function permissions(): MePermissionsResponse { +function permissions(): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_1', tenantId: 't_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: {}, fields: {}, diff --git a/ui/apps/console/src/services/approvalRequestsDataSource.servedPageMemberPermissions-13324.test.tsx b/ui/apps/console/src/services/approvalRequestsDataSource.servedPageMemberPermissions-13324.test.tsx index e1df79707e8..0108e4a8881 100644 --- a/ui/apps/console/src/services/approvalRequestsDataSource.servedPageMemberPermissions-13324.test.tsx +++ b/ui/apps/console/src/services/approvalRequestsDataSource.servedPageMemberPermissions-13324.test.tsx @@ -46,7 +46,7 @@ vi.mock('sonner', () => ({ import { AdapterCtx, MetadataCtx, useAdapter } from '@object-ui/react'; import { I18nProvider, type I18nConfig } from '@object-ui/i18n'; -import { MePermissionsProvider, usePermissions, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, usePermissions, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordDetailView } from '@object-ui/app-shell'; import { APPROVAL_ACTION_OBJECT, APPROVAL_REQUEST_OBJECT, routeApprovalRecordReads } from './approvalRequestsDataSource'; import { ApprovalRecordReads } from '../AppContent'; @@ -204,11 +204,12 @@ const SERVED_PAGE = { * approval object, so `checkField` takes its authenticated unknown-object * default (closed) for both. */ -const MEMBER_ANSWER: MePermissionsResponse = { +const MEMBER_ANSWER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member_default'], systemPermissions: [], objects: { diff --git a/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummary-12483.test.tsx b/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummary-12483.test.tsx index 749aeafdff2..9804f43fa28 100644 --- a/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummary-12483.test.tsx +++ b/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummary-12483.test.tsx @@ -302,10 +302,11 @@ function readerWithout(...denied: string[]): MePermissionsResponse { authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { '*': all } as never, - fields: Object.fromEntries(denied.map((f) => [`invoice.${f}`, { readable: false }])), + fields: Object.fromEntries(denied.map((f) => [`invoice.${f}`, { readable: false, editable: false }])), systemPermissions: [], }; } diff --git a/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummaryAmount-12534.test.tsx b/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummaryAmount-12534.test.tsx index 7b54dcd6fd0..fa9baf83486 100644 --- a/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummaryAmount-12534.test.tsx +++ b/ui/apps/console/src/services/approvalRequestsDataSource.servedPageSummaryAmount-12534.test.tsx @@ -382,10 +382,11 @@ function readerWithout(...denied: string[]): MePermissionsResponse { authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { '*': all } as never, - fields: Object.fromEntries(denied.map((f) => [`showcase_expense_report.${f}`, { readable: false }])), + fields: Object.fromEntries(denied.map((f) => [`showcase_expense_report.${f}`, { readable: false, editable: false }])), systemPermissions: [], }; } diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx index 56eea698a88..4a8ca3af58c 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx @@ -28,7 +28,7 @@ import { describe, it, expect, vi, beforeEach, type MockInstance } from 'vitest' import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // AGENTS.md §测试纪律 — the lazy page modules are stubbed so no unbounded // `import()` races a bounded `findBy` window. @@ -129,19 +129,20 @@ function LocationProbe() { } /** The member's `/me/permissions` answer: every object readable unless denied. */ -function permissionsDenying(...unreadable: string[]): MePermissionsResponse { +function permissionsDenying(...unreadable: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { '*': { allowRead: true }, ...Object.fromEntries(unreadable.map((name) => [name, { allowRead: false }])), }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } function renderConsoleAt(initialUrl: string, permissions: Partial>) { diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx index cf38e70ed3c..ca719ee19fa 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx @@ -33,7 +33,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { render, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // The reporter's dedupe `Set` is module state: reset it so every arm's // "nothing reported" is a reading, not a leftover from an earlier arm. import { __resetVisibilityPredicateWarnings } from '../../../../react/src/utils/visibilityDiagnostic'; @@ -130,12 +130,13 @@ vi.mock('@object-ui/react', async (importOriginal) => ({ import { AppContent } from '../AppContent'; -function payload(systemPermissions: string[]): MePermissionsResponse { +function payload(systemPermissions: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['member'], systemPermissions, objects: { org_member: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: false } }, diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx index 8cf32457b8d..4f31e227ac7 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx @@ -34,7 +34,7 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // AGENTS.md §测试纪律 — the lazy page modules are stubbed so no unbounded // `import()` races a bounded `findBy` window. @@ -131,19 +131,20 @@ function LocationProbe() { } /** The member's `/me/permissions` answer: every object readable unless denied. */ -function permissionsDenying(...unreadable: string[]): MePermissionsResponse { +function permissionsDenying(...unreadable: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { '*': { allowRead: true }, ...Object.fromEntries(unreadable.map((name) => [name, { allowRead: false }])), }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } function renderConsoleAt(initialUrl: string, permissions: Partial>) { diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.landingWaitsReads-13097.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.landingWaitsReads-13097.test.tsx index 50e2f77ae88..a02e362edce 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.landingWaitsReads-13097.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.landingWaitsReads-13097.test.tsx @@ -139,7 +139,8 @@ const EVERYTHING_READABLE = { authenticated: true, userId: 'u_1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { '*': { allowRead: true } }, fields: {}, diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx index 8a11293a530..a495fe6026d 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // AGENTS.md §测试纪律 — the lazy page modules are stubbed so no unbounded // `import()` races a bounded `findBy` window. @@ -131,19 +131,20 @@ function LocationProbe() { } /** The member's `/me/permissions` answer: every object readable unless denied. */ -function permissionsDenying(...unreadable: string[]): MePermissionsResponse { +function permissionsDenying(...unreadable: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { '*': { allowRead: true }, ...Object.fromEntries(unreadable.map((name) => [name, { allowRead: false }])), }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } function renderConsoleAt(initialUrl: string, permissions: Partial>) { diff --git a/ui/packages/app-shell/src/console/ai/__tests__/ExcelImportBar.importGrant-12082.test.tsx b/ui/packages/app-shell/src/console/ai/__tests__/ExcelImportBar.importGrant-12082.test.tsx index ee466f5fe44..4b7f1c32d75 100644 --- a/ui/packages/app-shell/src/console/ai/__tests__/ExcelImportBar.importGrant-12082.test.tsx +++ b/ui/packages/app-shell/src/console/ai/__tests__/ExcelImportBar.importGrant-12082.test.tsx @@ -21,7 +21,7 @@ import '@testing-library/jest-dom/vitest'; import * as React from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, cleanup, fireEvent, waitFor, screen, within, act } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const h = vi.hoisted(() => ({ /** The objects the bar lists as import targets. */ @@ -64,11 +64,12 @@ const SHAPES: Array<{ shape: string; bits: Record; create: bool ]; const FULL = SHAPES[3].bits; -const envelope = (objects: Record>): MePermissionsResponse => ({ +const envelope = (objects: Record>): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects: objects as never, fields: {}, diff --git a/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx b/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx index 3046114c64d..a63cc963e95 100644 --- a/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx +++ b/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx @@ -57,7 +57,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { render, screen, cleanup } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateMock = vi.fn(); @@ -248,17 +248,18 @@ async function bootOnNoConfig() { * open and only the DEPLOYMENT flag can move the verdict. Without * `manage_metadata` here, every denial below would pass for the wrong reason. */ -const permissions = (): MePermissionsResponse => +const permissions = (): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u1', tenantId: 'acme', - roles: ['org_owner', 'everyone'], + positions: ['org_owner', 'everyone'], + hasAnyQueue: false, permissionSets: ['organization_admin', 'member_default'], systemPermissions: ['manage_org_users', 'setup.access', 'setup.write', 'manage_metadata'], objects: {}, fields: {}, - }) as MePermissionsResponse; + }) as MePermissionsAuthenticatedResponse; function renderHome() { return render( diff --git a/ui/packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx b/ui/packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx index ac04b32b674..e688c5d4522 100644 --- a/ui/packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx +++ b/ui/packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx @@ -46,7 +46,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { render, screen, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateMock = vi.fn(); @@ -222,17 +222,18 @@ afterEach(() => { * workspace owner. `systemPermissions` is present and NON-empty — it simply * does not contain `manage_metadata`. */ -const eePayload = (systemPermissions?: string[]): MePermissionsResponse => +const eePayload = (systemPermissions?: string[]): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u1', tenantId: 'acme', - roles: ['org_owner', 'everyone'], + positions: ['org_owner', 'everyone'], + hasAnyQueue: false, permissionSets: ['organization_admin', 'member_default'], ...(systemPermissions ? { systemPermissions } : {}), objects: {}, fields: {}, - }) as MePermissionsResponse; + }) as MePermissionsAuthenticatedResponse; const OWNER_CAPS = ['manage_org_users', 'setup.access', 'setup.write']; diff --git a/ui/packages/app-shell/src/console/home/__tests__/HomePage.draftsReadGate-25894.test.tsx b/ui/packages/app-shell/src/console/home/__tests__/HomePage.draftsReadGate-25894.test.tsx index 734bfcdd4d1..c4759373c04 100644 --- a/ui/packages/app-shell/src/console/home/__tests__/HomePage.draftsReadGate-25894.test.tsx +++ b/ui/packages/app-shell/src/console/home/__tests__/HomePage.draftsReadGate-25894.test.tsx @@ -26,7 +26,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { act, render, screen, cleanup } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('react-router-dom', async () => { const { createElement } = await import('react'); @@ -123,17 +123,18 @@ afterEach(() => { vi.unstubAllGlobals(); }); -const payload = (systemPermissions?: string[]): MePermissionsResponse => +const payload = (systemPermissions?: string[]): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u1', tenantId: 'acme', - roles: ['everyone'], + positions: ['everyone'], + hasAnyQueue: false, permissionSets: ['member_default'], ...(systemPermissions ? { systemPermissions } : {}), objects: {}, fields: {}, - }) as MePermissionsResponse; + }) as MePermissionsAuthenticatedResponse; async function renderHome(permissions: MePermissionsResponse) { render( diff --git a/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx b/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx index c10b69a3fc2..5361b5f86e7 100644 --- a/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx +++ b/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx @@ -36,7 +36,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { render, screen, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateMock = vi.fn(); @@ -216,17 +216,18 @@ async function bootOnNoConfig() { } /** An admin who MAY author metadata — so only the marketplace gate is in play. */ -const permissions = (): MePermissionsResponse => +const permissions = (): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u1', tenantId: 'acme', - roles: ['org_owner', 'everyone'], + positions: ['org_owner', 'everyone'], + hasAnyQueue: false, permissionSets: ['organization_admin', 'member_default'], systemPermissions: ['manage_org_users', 'setup.access', 'setup.write', 'manage_metadata'], objects: {}, fields: {}, - }) as MePermissionsResponse; + }) as MePermissionsAuthenticatedResponse; function renderHome() { return render( diff --git a/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx b/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx index a8e7b9aa948..083bf0d116d 100644 --- a/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx +++ b/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx @@ -40,7 +40,7 @@ import '@object-ui/components'; import { SidebarProvider } from '@object-ui/components'; import { NavigationRenderer } from '@object-ui/layout'; import { SchemaRenderer, SchemaRendererContext, MetadataCtx, type ApiFetch } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { createI18n, I18nProvider } from '@object-ui/i18n'; import type { NavigationItem } from '@object-ui/types'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; @@ -52,11 +52,12 @@ import '../../views/nav-menu-renderer'; type Surface = 'sidebar' | 'nav:menu' | 'palette'; const SURFACES: Surface[] = ['sidebar', 'nav:menu', 'palette']; -const ANSWER: MePermissionsResponse = { +const ANSWER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_member', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: [], objects: {}, diff --git a/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx b/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx index 86a8aed5c70..2ed050ad186 100644 --- a/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx +++ b/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx @@ -40,17 +40,18 @@ import { MemoryRouter, Route, Routes, useLocation, useNavigate, type NavigateFun // Module scope, never a `beforeAll` (AGENTS.md §测试纪律, objectui#3010). import { ComponentRegistry } from '@object-ui/core'; import { SchemaRenderer, SchemaRendererContext, MetadataCtx, notifyDataChanged, type ApiFetch } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { NavigationItem } from '@object-ui/types'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; import { ConsoleElementApiOpen } from '../../console/ConsoleElementApiOpen'; import { useNavMemberGates, navEntriesShownToMember } from '../useNavMemberGates'; -const ANSWER: MePermissionsResponse = { +const ANSWER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_member', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: [], objects: {}, diff --git a/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityGrant-12081.test.tsx b/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityGrant-12081.test.tsx index 1ea4ee8682a..6e8b81ea192 100644 --- a/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityGrant-12081.test.tsx +++ b/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityGrant-12081.test.tsx @@ -30,7 +30,7 @@ import '@testing-library/jest-dom/vitest'; import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // The feed reads only the objects an app lists (objectui#12530); `crm_lead` is // on the default roster, so the grant is all this file varies. import { withRoster } from './activityRoster'; @@ -79,11 +79,12 @@ const { useSharedActivityFeed, __resetSharedUserFeeds, __markFirstPainted } = aw const settle = () => act(async () => { await vi.advanceTimersByTimeAsync(0); }); -const payload = (over: Partial): MePermissionsResponse => ({ +const payload = (over: Partial): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: {}, fields: {}, @@ -160,7 +161,7 @@ describe('every uncertainty still reads — a wrong skip is the expensive mistak }); it('the anonymous payload (no resolvable permissions by design) ⇒ reads', async () => { - const { result } = await mountFeed(payload({ authenticated: false, userId: null })); + const { result } = await mountFeed({ authenticated: false }); expect(reads).toEqual(['u1']); expect(result.current.readable).toBe(true); }); diff --git a/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx b/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx index 7cde10f15f1..deb5211b5ae 100644 --- a/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx +++ b/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx @@ -27,7 +27,7 @@ import '@testing-library/jest-dom/vitest'; import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { MetadataCtx, type MetadataContextValue } from '@object-ui/react'; import { appListing, rosterRegistry, withRoster } from './activityRoster'; @@ -158,11 +158,12 @@ describe('the feed reads the objects the user’s apps list, in the query (objec }); it('an object entry the member may not read lends the roster nothing (the sidebar’s own prune)', async () => { - const perms: MePermissionsResponse = { + const perms: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_admin', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { sys_activity: { allowRead: true }, crm_lead: { allowRead: true } }, fields: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx index 0544a15bac6..d1a8a3e7e08 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx @@ -20,7 +20,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, within } from '@testing-library/react'; import React from 'react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal>()), @@ -160,11 +160,12 @@ vi.mock('../../providers/AdapterProvider', () => ({ import { AppHeader, type AppHeaderVariant } from '../AppHeader'; -const MEMBER: MePermissionsResponse = { +const MEMBER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: [], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx index 4bdb00c7933..c3aec362f56 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx @@ -34,7 +34,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, within } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/lead', search: '' })); const nav = vi.hoisted(() => ({ recordTitle: undefined as string | undefined })); @@ -149,11 +149,12 @@ vi.mock('../../providers/AdapterProvider', () => ({ import { AppHeader } from '../AppHeader'; import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.appIdentity-12201.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.appIdentity-12201.test.tsx index 084b2a5e553..0cfed91ec49 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.appIdentity-12201.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.appIdentity-12201.test.tsx @@ -32,7 +32,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('react-router-dom', () => ({ useLocation: () => ({ pathname: '/apps/cloud_control/page/welcome', search: '', hash: '', state: null, key: 't' }), @@ -175,12 +175,13 @@ import { AppHeader } from '../AppHeader'; import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; import { getProductName } from '../../runtime-config'; -function mePermissions(systemPermissions: string[]): MePermissionsResponse { +function mePermissions(systemPermissions: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions, objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx index 8310b2f78e4..145a37429fa 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx @@ -41,7 +41,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/lead', search: '' })); @@ -140,11 +140,12 @@ vi.mock('../../providers/AdapterProvider', () => ({ import { AppHeader, type AppHeaderVariant } from '../AppHeader'; import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx index 539648804ec..f3a6a95dd9a 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, within, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/system/marketplace', search: '' })); @@ -134,11 +134,12 @@ vi.mock('../../providers/AdapterProvider', () => ({ import { AppHeader, AppBarCrumbTrail } from '../AppHeader'; import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx index c2c12f7abad..21fa84f20cb 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx @@ -29,7 +29,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, within } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/dashboard/ops_board' })); const served = vi.hoisted(() => ({ @@ -126,11 +126,12 @@ vi.mock('../../providers/AdapterProvider', () => ({ import { AppHeader } from '../AppHeader'; -const MEMBER: MePermissionsResponse = { +const MEMBER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: [], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx index bfd127ee88d..d1156a9797c 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx @@ -184,7 +184,7 @@ const fakeAdapter = { }; vi.mock('../../providers/AdapterProvider', () => ({ useAdapter: () => fakeAdapter })); -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { AppHeader } from '../AppHeader'; import { __resetSharedUserFeeds, @@ -192,11 +192,12 @@ import { __markFirstPainted, } from '../../hooks/sharedUserFeeds'; -const READER: MePermissionsResponse = { +const READER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: [], objects: { diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.designInStudioNeedsAuthoring-10899.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.designInStudioNeedsAuthoring-10899.test.tsx index 041331d094f..55a292d87b1 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.designInStudioNeedsAuthoring-10899.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.designInStudioNeedsAuthoring-10899.test.tsx @@ -21,7 +21,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('react-router-dom', () => ({ useLocation: () => ({ pathname: '/apps/cloud_control/page/welcome', search: '', hash: '', state: null, key: 't' }), @@ -153,12 +153,13 @@ import { AppHeader } from '../AppHeader'; // reads that trigger as its "the header rendered" control. import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; -function mePermissions(systemPermissions: string[]): MePermissionsResponse { +function mePermissions(systemPermissions: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions, objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.editAppFollowsPackagePosture-12615.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.editAppFollowsPackagePosture-12615.test.tsx index ccf3e490a21..0eb215a1e21 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.editAppFollowsPackagePosture-12615.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.editAppFollowsPackagePosture-12615.test.tsx @@ -33,7 +33,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, cleanup, render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { SidebarMenu, SidebarMenuItem, SidebarProvider } from '@object-ui/components'; import type { NavigationItem } from '@object-ui/types'; @@ -172,11 +172,12 @@ vi.mock('../../providers/AdapterProvider', () => ({ import { AppHeader } from '../AppHeader'; import { SidebarItemMenu, sidebarItemDesignPath } from '../SidebarItemMenu'; -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx index 2e4e8c86515..f2a9e2dec61 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx @@ -29,7 +29,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, cleanup, act } from '@testing-library/react'; import React from 'react'; import { MemoryRouter, Routes, Route, useLocation, useNavigate, useNavigationType } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal>()), @@ -121,11 +121,12 @@ import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; import { GO_HOME_SHORTCUT, useAdvertisedShortcuts, type ShortcutChord } from '../../chrome/advertisedShortcuts'; import { getProductName } from '../../runtime-config'; -const MEMBER: MePermissionsResponse = { +const MEMBER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: [], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx index ee15adc441d..eaf2754f76c 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx @@ -24,7 +24,7 @@ import '@testing-library/jest-dom/vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const served = vi.hoisted(() => ({ apps: [] as Array> })); @@ -115,11 +115,12 @@ import { AppHeader } from '../AppHeader'; import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; import { initRuntimeConfig, resetRuntimeConfigForTesting } from '../../runtime-config'; -const MEMBER: MePermissionsResponse = { +const MEMBER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: [], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.inboxVariant.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.inboxVariant.test.tsx index c30489b1b36..f17ac961c1e 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.inboxVariant.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.inboxVariant.test.tsx @@ -263,7 +263,7 @@ vi.mock('../../providers/AdapterProvider', () => ({ useAdapter: () => fakeAdapter, })); -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { AppHeader } from '../AppHeader'; import { useHomeInbox } from '../../hooks/useHomeInbox'; import { __resetSharedUserFeeds, __markFirstPainted } from '../../hooks/sharedUserFeeds'; @@ -821,11 +821,12 @@ describe('AppHeader — the bell panel renders what the inbox returns, under BOT */ describe('AppHeader — the Activity tab asks only a caller who may read sys_activity (objectui#12081)', () => { /** `/auth/me/permissions` for an authenticated member — `member_default`'s two inbox reads, no `*`. */ - const MEMBER: MePermissionsResponse = { + const MEMBER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['member_default'], objects: { sys_inbox_message: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.pageRouteNoAuthoringHandles-12198.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.pageRouteNoAuthoringHandles-12198.test.tsx index 623545ab836..8bb237cff4a 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.pageRouteNoAuthoringHandles-12198.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.pageRouteNoAuthoringHandles-12198.test.tsx @@ -37,7 +37,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { act, render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // The page's pencil is a button that navigates, so its target is read off the // navigate call it makes (objectui#12540). @@ -198,12 +198,13 @@ import { PageView } from '../../views/PageView'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; /** `/me/permissions` for a persona, with the fields these surfaces do not read left empty. */ -function mePermissions(systemPermissions: string[]): MePermissionsResponse { +function mePermissions(systemPermissions: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions, objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx index 8da3a8011f0..a11e3a8115d 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx @@ -21,7 +21,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('react-router-dom', () => ({ useLocation: () => ({ pathname: '/apps/cloud_control/page/welcome', search: '', hash: '', state: null, key: 't' }), @@ -165,11 +165,12 @@ vi.mock('../../providers/AdapterProvider', () => ({ import { AppHeader } from '../AppHeader'; import { CommandPaletteProvider } from '../../context/CommandPaletteProvider'; -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx b/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx index 9ba2be7b527..db55b3e3ef6 100644 --- a/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx @@ -32,7 +32,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, cleanup, render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { NavigationItem } from '@object-ui/types'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ @@ -159,11 +159,12 @@ import { resolveHref } from '@object-ui/layout'; import { AppHeader } from '../AppHeader'; import { sidebarItemDesignPath, favoriteDesignPath } from '../SidebarItemMenu'; -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.navCanPermissions-12700.test.tsx b/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.navCanPermissions-12700.test.tsx index edf3abc0dee..059b6244e30 100644 --- a/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.navCanPermissions-12700.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.navCanPermissions-12700.test.tsx @@ -25,7 +25,7 @@ import React from 'react'; import { render, screen, waitFor, act } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import type { NavigationItem } from '@object-ui/types'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { __resetVisibilityPredicateWarnings } from '../../../../react/src/utils/visibilityDiagnostic'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ @@ -94,12 +94,13 @@ const NAVIGATION: NavigationItem[] = [ const CRM_APP = { name: 'crm', label: 'CRM', active: true, navigation: NAVIGATION }; -function answerFor(opportunityRead: boolean): MePermissionsResponse { +function answerFor(opportunityRead: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: [], objects: { @@ -107,7 +108,7 @@ function answerFor(opportunityRead: boolean): MePermissionsResponse { crm_opportunity: { allowRead: opportunityRead }, }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } function adapterFor(app: Record) { diff --git a/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.objectReadGate-12109.test.tsx b/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.objectReadGate-12109.test.tsx index fbcfd6dea12..3c2651c3f10 100644 --- a/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.objectReadGate-12109.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/UnifiedSidebar.objectReadGate-12109.test.tsx @@ -25,7 +25,7 @@ import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import type { NavigationArea, NavigationItem } from '@object-ui/types'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal>()), @@ -113,12 +113,13 @@ const NAVIGATION: NavigationItem[] = [ { id: 'nav_help', type: 'url', url: '/help', label: 'Help' }, ]; -function answerFor(opportunityRead: boolean): MePermissionsResponse { +function answerFor(opportunityRead: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: [], objects: { diff --git a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx index b6eb03862b2..705f0c677cc 100644 --- a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx +++ b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx @@ -56,7 +56,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { ComponentRegistry } from '@object-ui/core'; import { ActionProvider, RecordContextProvider } from '@object-ui/react'; -import { MePermissionsProvider, usePermissions, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, usePermissions, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ExpressionProvider } from '../ExpressionProvider'; // Module-scope side-effect imports: the two registry renderers must be // registered before the first render (AGENTS.md §测试纪律 — import phase, not a @@ -88,12 +88,13 @@ const RECORD = { id: 'acc_1', name: 'Northwind', status: 'open' }; type State = 'not-loaded' | 'granted' | 'denied'; -function payload(allowDelete: boolean): MePermissionsResponse { +function payload(allowDelete: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['account_clerk'], // `allowEdit` is granted in BOTH loaded states, so a verb mix-up (reading // edit for delete) would show the action in the denied state. diff --git a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx index 25b457886bc..6bd44171a2e 100644 --- a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx +++ b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx @@ -67,7 +67,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react'; import { ComponentRegistry, type ActionResult } from '@object-ui/core'; import { ActionProvider, RecordContextProvider, useAction } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ExpressionProvider } from '../ExpressionProvider'; // Module-scope side-effect imports: the registry renderers must be registered // before the first render (AGENTS.md §测试纪律 — import phase, not a hook). @@ -101,12 +101,13 @@ const nameOf = (leg: string, state: State | 'unbound') => `void_${leg}_${state.r const USER = { id: 'u1', name: 'Ada', email: 'ada@example.com', role: 'user', positions: ['everyone'] }; const RECORD = { id: 'acc_1', name: 'Northwind', status: 'open' }; -function payload(allowDelete: boolean): MePermissionsResponse { +function payload(allowDelete: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['account_clerk'], // `allowEdit` is granted in BOTH loaded states, so a verb mix-up (reading // edit for delete) would enable the action in the denied state. diff --git a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx index ebcbf17ef22..b2e81ebf8cb 100644 --- a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx +++ b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx @@ -62,7 +62,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react'; import { ComponentRegistry } from '@object-ui/core'; import { ActionProvider, RecordContextProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ExpressionProvider } from '../ExpressionProvider'; // Module-scope side-effect imports: the registry renderers must be registered // before the first render (AGENTS.md §测试纪律 — import phase, not a hook). @@ -94,12 +94,13 @@ const nameOf = (leg: string, state: State | 'unbound') => `void_${leg}_${state.r const USER = { id: 'u1', name: 'Ada', email: 'ada@example.com', role: 'user', positions: ['everyone'] }; const RECORD = { id: 'acc_1', name: 'Northwind', status: 'open' }; -function payload(allowDelete: boolean): MePermissionsResponse { +function payload(allowDelete: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['account_clerk'], // `allowEdit` is granted in BOTH loaded states, so a verb mix-up (reading // edit for delete) would show the action in the denied state. diff --git a/ui/packages/app-shell/src/providers/__tests__/hasCapability-13006.render.test.tsx b/ui/packages/app-shell/src/providers/__tests__/hasCapability-13006.render.test.tsx index 8310cf8f210..fd320e744c8 100644 --- a/ui/packages/app-shell/src/providers/__tests__/hasCapability-13006.render.test.tsx +++ b/ui/packages/app-shell/src/providers/__tests__/hasCapability-13006.render.test.tsx @@ -50,7 +50,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, cleanup, waitFor } from '@testing-library/react'; import { ComponentRegistry, subjectCapabilitiesOf, subjectPermissionsOf } from '@object-ui/core'; import { ActionProvider, SchemaRenderer, useAction, usePredicateScope } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // The reporter's dedupe `Set` is module state: reset it so every arm's // "nothing reported" is a reading, not a leftover from an earlier arm. import { __resetVisibilityPredicateWarnings } from '../../../../react/src/utils/visibilityDiagnostic'; @@ -89,12 +89,13 @@ const PRINCIPALS = { }, } as const; -function payload(userId: string, systemPermissions: string[] | undefined): MePermissionsResponse { +function payload(userId: string, systemPermissions: string[] | undefined): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['member'], ...(systemPermissions !== undefined ? { systemPermissions } : {}), objects: { account: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: false } }, diff --git a/ui/packages/app-shell/src/views/ObjectView.listActionsReadGate-12109.test.tsx b/ui/packages/app-shell/src/views/ObjectView.listActionsReadGate-12109.test.tsx index 15036e0c496..a1d03f5785f 100644 --- a/ui/packages/app-shell/src/views/ObjectView.listActionsReadGate-12109.test.tsx +++ b/ui/packages/app-shell/src/views/ObjectView.listActionsReadGate-12109.test.tsx @@ -31,7 +31,7 @@ import * as React from 'react'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, cleanup, screen, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -101,12 +101,13 @@ const OBJECTS = [ }, ]; -function answerFor(opportunityRead: boolean): MePermissionsResponse { +function answerFor(opportunityRead: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: [], objects: { [OBJECT_NAME]: { allowRead: opportunityRead, allowEdit: true } }, diff --git a/ui/packages/app-shell/src/views/ObjectView.managedByBadgeAuthoring-12194.test.tsx b/ui/packages/app-shell/src/views/ObjectView.managedByBadgeAuthoring-12194.test.tsx index 712483dc4d1..a1780e68075 100644 --- a/ui/packages/app-shell/src/views/ObjectView.managedByBadgeAuthoring-12194.test.tsx +++ b/ui/packages/app-shell/src/views/ObjectView.managedByBadgeAuthoring-12194.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -88,12 +88,13 @@ const OBJECTS = [ ]; /** A `/me/permissions` answer holding exactly `held`. */ -function answer(held: string[]): MePermissionsResponse { +function answer(held: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: held, objects: { '*': { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, diff --git a/ui/packages/app-shell/src/views/ObjectView.setupListBadge-13385.test.tsx b/ui/packages/app-shell/src/views/ObjectView.setupListBadge-13385.test.tsx index a79335966b1..4aaf90b04b4 100644 --- a/ui/packages/app-shell/src/views/ObjectView.setupListBadge-13385.test.tsx +++ b/ui/packages/app-shell/src/views/ObjectView.setupListBadge-13385.test.tsx @@ -27,7 +27,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -84,12 +84,13 @@ const OBJECTS = [ ]; /** A `/me/permissions` answer holding exactly `held`. */ -function answer(held: string[]): MePermissionsResponse { +function answer(held: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: held, objects: { '*': { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, diff --git a/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx b/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx index 877a189ee49..81ae4e7714d 100644 --- a/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx +++ b/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx @@ -34,7 +34,7 @@ import * as React from 'react'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, cleanup, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { AdapterCtx } from '@object-ui/react'; import { createI18n, en, I18nProvider } from '@object-ui/i18n'; @@ -115,11 +115,12 @@ const OBJECTS = [ }, ]; -const PERMISSIONS: MePermissionsResponse = { +const PERMISSIONS: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_author', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['studio_user'], systemPermissions: ['studio.access'], objects: { [OBJECT_NAME]: { allowRead: true, allowEdit: true } }, diff --git a/ui/packages/app-shell/src/views/RecordDetailView.authoredActivityThreading-12249.test.tsx b/ui/packages/app-shell/src/views/RecordDetailView.authoredActivityThreading-12249.test.tsx index 3ab75399773..fb37b49e3c4 100644 --- a/ui/packages/app-shell/src/views/RecordDetailView.authoredActivityThreading-12249.test.tsx +++ b/ui/packages/app-shell/src/views/RecordDetailView.authoredActivityThreading-12249.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, fireEvent, within, act, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { MetadataCtx } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { DETAIL_DEFAULT_TRANSLATIONS } from '@object-ui/plugin-detail'; vi.mock('@object-ui/auth', async (importOriginal) => ({ @@ -184,16 +184,17 @@ const CREATE_ONLY = { allowRead: true, allowCreate: true, allowEdit: false, allo const READ_ONLY = { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }; /** The caller's `/me/permissions` answer: full on the record's object, `bits` on `sys_comment`. */ -function answer(bits: Record): MePermissionsResponse { +function answer(bits: Record): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects: { [OBJECT_NAME]: FULL, [COMMENT]: bits }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } interface MountOptions { diff --git a/ui/packages/app-shell/src/views/RecordDetailView.feedCommentEditDelete-12268.test.tsx b/ui/packages/app-shell/src/views/RecordDetailView.feedCommentEditDelete-12268.test.tsx index e5d66a0212d..f79d5c08ce8 100644 --- a/ui/packages/app-shell/src/views/RecordDetailView.feedCommentEditDelete-12268.test.tsx +++ b/ui/packages/app-shell/src/views/RecordDetailView.feedCommentEditDelete-12268.test.tsx @@ -97,7 +97,7 @@ vi.mock('./MetadataInspector', () => ({ import { toast } from 'sonner'; import { I18nProvider } from '@object-ui/i18n'; import { MetadataCtx } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordDetailView } from './RecordDetailView'; type Pack = { @@ -252,16 +252,17 @@ function metadataFor(pages: unknown[]) { const FULL = { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true }; /** The caller's `/me/permissions` answer: full on the record's object, `bits` on `sys_comment`. */ -function answer(bits: Record = FULL): MePermissionsResponse { +function answer(bits: Record = FULL): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: auth.user.id, tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects: { [OBJECT_NAME]: FULL, [COMMENT]: bits }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } interface MountOptions { diff --git a/ui/packages/app-shell/src/views/RecordDetailView.feedGrant-12082.test.tsx b/ui/packages/app-shell/src/views/RecordDetailView.feedGrant-12082.test.tsx index 5526c191051..13e6373aa47 100644 --- a/ui/packages/app-shell/src/views/RecordDetailView.feedGrant-12082.test.tsx +++ b/ui/packages/app-shell/src/views/RecordDetailView.feedGrant-12082.test.tsx @@ -34,7 +34,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, within, act } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { MetadataCtx } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { DETAIL_DEFAULT_TRANSLATIONS } from '@object-ui/plugin-detail'; import { effectiveOperationsArray, resolveEffectiveApiMethods } from '@objectstack/spec/data'; @@ -121,16 +121,17 @@ const SHAPE = { type Bits = Record; /** The caller's answer: full on the record's own object, `feed` on the feed's objects (absent keys are absent). */ -function answer(feed: { [COMMENT]?: Bits; [REACTION]?: Bits }): MePermissionsResponse { +function answer(feed: { [COMMENT]?: Bits; [REACTION]?: Bits }): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects: { [OBJECT_NAME]: SHAPE.full, ...feed }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } /** diff --git a/ui/packages/app-shell/src/views/RecordDetailView.managedByBadgeAuthoring-12194.test.tsx b/ui/packages/app-shell/src/views/RecordDetailView.managedByBadgeAuthoring-12194.test.tsx index c6328c77ecb..06e5d6c3d49 100644 --- a/ui/packages/app-shell/src/views/RecordDetailView.managedByBadgeAuthoring-12194.test.tsx +++ b/ui/packages/app-shell/src/views/RecordDetailView.managedByBadgeAuthoring-12194.test.tsx @@ -45,7 +45,7 @@ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vite import { render, waitFor, cleanup } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { MetadataCtx } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -88,12 +88,13 @@ const FIELDS = { const RECORD = { id: REC, name: 'Acme Workspace', city: 'London' }; /** A `/me/permissions` answer; `systemPermissions` omitted when `held` is `undefined`. */ -function answer(held: string[] | undefined): MePermissionsResponse { +function answer(held: string[] | undefined): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(held === undefined ? {} : { systemPermissions: held }), objects: { '*': { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, diff --git a/ui/packages/app-shell/src/views/RecordDetailView.replyCountOnRead-12144.test.tsx b/ui/packages/app-shell/src/views/RecordDetailView.replyCountOnRead-12144.test.tsx index db8cf62a584..01deb23f0dd 100644 --- a/ui/packages/app-shell/src/views/RecordDetailView.replyCountOnRead-12144.test.tsx +++ b/ui/packages/app-shell/src/views/RecordDetailView.replyCountOnRead-12144.test.tsx @@ -33,7 +33,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, fireEvent, within, act, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { MetadataCtx } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { DETAIL_DEFAULT_TRANSLATIONS } from '@object-ui/plugin-detail'; vi.mock('@object-ui/auth', async (importOriginal) => ({ @@ -148,16 +148,17 @@ const CREATE_ONLY = { allowRead: true, allowCreate: true, allowEdit: false, allo const READ_ONLY = { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }; /** The caller's `/me/permissions` answer: full on the record's object, `bits` on `sys_comment`. */ -function answer(bits: Record): MePermissionsResponse { +function answer(bits: Record): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects: { [OBJECT_NAME]: FULL, [COMMENT]: bits }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } interface MountOptions { diff --git a/ui/packages/app-shell/src/views/RecordDetailView.setupManagedByChip-13385.test.tsx b/ui/packages/app-shell/src/views/RecordDetailView.setupManagedByChip-13385.test.tsx index 41c3ad0b623..b2c03085217 100644 --- a/ui/packages/app-shell/src/views/RecordDetailView.setupManagedByChip-13385.test.tsx +++ b/ui/packages/app-shell/src/views/RecordDetailView.setupManagedByChip-13385.test.tsx @@ -33,7 +33,7 @@ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vite import { render, waitFor, cleanup } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { MetadataCtx } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -76,12 +76,13 @@ const FIELDS = { const RECORD = { id: REC, name: 'Acme Workspace', city: 'London' }; /** A `/me/permissions` answer; `systemPermissions` omitted when `held` is `undefined`. */ -function answer(held: string[] | undefined): MePermissionsResponse { +function answer(held: string[] | undefined): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(held === undefined ? {} : { systemPermissions: held }), objects: { '*': { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, diff --git a/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx b/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx index 2cbbf0beb9e..2238dfe2591 100644 --- a/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx +++ b/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx @@ -37,7 +37,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // The reporter's dedupe `Set` is module state: reset it so every arm's // "nothing reported" is a reading, not a leftover from an earlier arm. import { __resetVisibilityPredicateWarnings } from '../../../react/src/utils/visibilityDiagnostic'; @@ -92,12 +92,13 @@ const ORG = { const USER = { id: 'u1', name: 'Ada', email: 'ada@example.com', role: 'user', positions: ['everyone'] }; -function payload(systemPermissions: string[]): MePermissionsResponse { +function payload(systemPermissions: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['member'], systemPermissions, objects: { org_member: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: false } }, diff --git a/ui/packages/app-shell/src/views/RecordFormPage.managedByBadgeAuthoring-12194.test.tsx b/ui/packages/app-shell/src/views/RecordFormPage.managedByBadgeAuthoring-12194.test.tsx index 28e5937cbce..c48f650359f 100644 --- a/ui/packages/app-shell/src/views/RecordFormPage.managedByBadgeAuthoring-12194.test.tsx +++ b/ui/packages/app-shell/src/views/RecordFormPage.managedByBadgeAuthoring-12194.test.tsx @@ -33,7 +33,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordFormPage } from './RecordFormPage'; vi.mock('sonner', () => ({ @@ -75,12 +75,13 @@ vi.mock('../providers/AdapterProvider', () => ({ })); /** A `/me/permissions` answer holding exactly `held`. */ -function answer(held: string[]): MePermissionsResponse { +function answer(held: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: held, objects: { '*': { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx index 4fecd53c747..a7b1fa64bc1 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, cleanup } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -81,11 +81,12 @@ const DATASETS = [ { name: 'ds_cases', object: 'crm_case' }, ]; -const ANSWER: MePermissionsResponse = { +const ANSWER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: [], objects: { diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx index bfb43e7d3aa..ea52d389db3 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx @@ -35,7 +35,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, cleanup, fireEvent, within } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -103,11 +103,12 @@ const OBJECTS = [ { name: 'crm_case', label: 'Case', pluralLabel: 'Cases', _packageId: PKG }, ]; -const ANSWER: MePermissionsResponse = { +const ANSWER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['sales'], systemPermissions: [], objects: { diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx index 9dd4fc7a32a..27b8ffc2626 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx @@ -29,7 +29,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { MetadataProvider } from '../../providers/MetadataProvider'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; @@ -90,11 +90,12 @@ const DATASETS = [ { name: 'ds_cases', object: 'crm_case' }, ]; -const ANSWER: MePermissionsResponse = { +const ANSWER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: [], objects: { diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx index 56febeb1a09..a1542317cdc 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx @@ -48,7 +48,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, cleanup, act } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { SchemaRendererContext, type ApiFetch } from '@object-ui/react'; vi.mock('@object-ui/auth', async (importOriginal) => ({ @@ -105,12 +105,13 @@ const gammaEntry = (extra: Entry = {}): Entry => ({ ...extra, }); -function answer(forecastRead: boolean): MePermissionsResponse { +function answer(forecastRead: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: [], objects: { diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx index ada9a8e7c45..63ce584c281 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx @@ -22,7 +22,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, cleanup, within } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), @@ -48,11 +48,12 @@ const DATASETS = [ { name: 'ds_salaries', object: 'hr_salary' }, ]; -const ANSWER: MePermissionsResponse = { +const ANSWER: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u_admin', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['admin'], systemPermissions: [], objects: { diff --git a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx index 8604aa89d8a..e729cd9132c 100644 --- a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx @@ -22,7 +22,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, cleanup, render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateSpy = vi.hoisted(() => vi.fn()); vi.mock('react-router-dom', () => ({ @@ -63,11 +63,12 @@ import { PageView } from '../PageView'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; /** A metadata author: holds `manage_metadata`. */ -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioNeedsAuthoring-10899.test.tsx b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioNeedsAuthoring-10899.test.tsx index 885e2febc04..675d620db2d 100644 --- a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioNeedsAuthoring-10899.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioNeedsAuthoring-10899.test.tsx @@ -25,7 +25,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, cleanup, render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('react-router-dom', () => ({ useParams: () => ({ pageName: 'welcome' }), @@ -78,12 +78,13 @@ const APP = { name: 'cloud_control', label: 'Cloud', _packageId: 'com.objectstac const EXPRESSION_USER = {}; /** `/me/permissions` for a persona, with the fields this surface does not read left empty. */ -function mePermissions(systemPermissions: string[] | undefined): MePermissionsResponse { +function mePermissions(systemPermissions: string[] | undefined): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(systemPermissions ? { systemPermissions } : {}), objects: {}, diff --git a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx index 44d133932ad..fe56a121449 100644 --- a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx @@ -29,7 +29,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, cleanup, render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateSpy = vi.hoisted(() => vi.fn()); vi.mock('react-router-dom', () => ({ @@ -71,11 +71,12 @@ import { ExpressionProvider } from '../../providers/ExpressionProvider'; import { appStudioRoutePath } from '../../utils/appRoute'; /** A metadata author: holds `manage_metadata`. */ -const AUTHOR: MePermissionsResponse = { +const AUTHOR: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions: ['manage_metadata', 'studio.access', 'setup.access'], objects: {}, diff --git a/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx b/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx index 2a0740451d3..c1912134aae 100644 --- a/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx @@ -34,7 +34,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, within, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; /** The fake presigned adapter: `upload` resolving means a `sys_file` was committed. */ const adapterUpload = vi.fn(); @@ -70,12 +70,13 @@ const OWN_ROW = { type Grant = { allowCreate?: boolean; allowDelete?: boolean }; /** A `/me/permissions` answer for an authenticated caller with this `sys_attachment` grant. */ -function payload(grant: Grant): MePermissionsResponse { +function payload(grant: Grant): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: ME, tenantId: 'org-1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['member'], objects: { sys_attachment: { diff --git a/ui/packages/app-shell/src/views/__tests__/nav-object-read-gate-12109.render.test.tsx b/ui/packages/app-shell/src/views/__tests__/nav-object-read-gate-12109.render.test.tsx index 1264d305d74..202f38aacb7 100644 --- a/ui/packages/app-shell/src/views/__tests__/nav-object-read-gate-12109.render.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/nav-object-read-gate-12109.render.test.tsx @@ -24,7 +24,7 @@ import { MemoryRouter, Routes, Route } from 'react-router-dom'; // Module scope, never a `beforeAll` (AGENTS.md §测试纪律, objectui#3010). import '@object-ui/components'; import { SchemaRenderer, MetadataCtx } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import '../nav-menu-renderer'; const NAVIGATION = [ @@ -54,12 +54,13 @@ const AREAS = [ }, ]; -function answerFor(opportunityRead: boolean): MePermissionsResponse { +function answerFor(opportunityRead: boolean): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u_agent', tenantId: null, - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: ['service_agent'], systemPermissions: [], objects: { crm_account: { allowRead: true }, crm_opportunity: { allowRead: opportunityRead } }, diff --git a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tabDeepLink-12496.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tabDeepLink-12496.test.tsx index e8d361ad4a0..74f6052a071 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tabDeepLink-12496.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tabDeepLink-12496.test.tsx @@ -19,7 +19,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, waitFor, within, fireEvent } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const POSITION = { name: 'env_position', label: 'Environment Position' }; @@ -57,12 +57,13 @@ vi.mock('./useMetadata', async (importOriginal) => ({ import { MetadataResourceEditPage } from './ResourceEditPage'; -function mePermissions(systemPermissions: string[]): MePermissionsResponse { +function mePermissions(systemPermissions: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], systemPermissions, objects: {}, diff --git a/ui/packages/app-shell/src/views/metadata-admin/setupHoldersGrant-12082.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/setupHoldersGrant-12082.test.tsx index 1cdb6bcb3b9..b2c1fc79f96 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/setupHoldersGrant-12082.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/setupHoldersGrant-12082.test.tsx @@ -27,7 +27,7 @@ import * as React from 'react'; import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/react', async (importOriginal) => ({ ...(await importOriginal>()), @@ -99,16 +99,17 @@ const SHAPE = { full: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true }, } as const; -function answer(objects: MePermissionsResponse['objects']): MePermissionsResponse { +function answer(objects: MePermissionsAuthenticatedResponse['objects']): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u-admin', tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } /** A super-user wildcard, as the organization administrator set carries one. */ @@ -136,7 +137,7 @@ const ORG_ADMIN = answer({ '*': WILDCARD, [GRANT_OBJECT]: SHAPE.createAndDelete, [HOLDING_OBJECT]: SHAPE.createAndDelete, -} as MePermissionsResponse['objects']); +} as MePermissionsAuthenticatedResponse['objects']); /** * CONTROL: the same wildcard beside explicit entries with neither create nor @@ -146,7 +147,7 @@ const WILDCARD_BESIDE_READ_ONLY = answer({ '*': WILDCARD, [GRANT_OBJECT]: SHAPE.readOnly, [HOLDING_OBJECT]: SHAPE.readOnly, -} as MePermissionsResponse['objects']); +} as MePermissionsAuthenticatedResponse['objects']); const SECTIONS = [ { diff --git a/ui/packages/app-shell/src/views/metadata-admin/trailDoorsGate-12475.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/trailDoorsGate-12475.test.tsx index 4d89b6e55ab..7f5359da988 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/trailDoorsGate-12475.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/trailDoorsGate-12475.test.tsx @@ -20,7 +20,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const POSITION = { name: 'env_position', label: 'Environment Position' }; const SET = { name: 'sales_perms', label: 'Sales', objects: {}, fields: {} }; @@ -62,12 +62,13 @@ import { MetadataResourceEditPage } from './ResourceEditPage'; import { PermissionMatrixEditPage } from './PermissionMatrixEditor'; /** `/me/permissions` for a persona; `undefined` = a backend that reports none. */ -function mePermissions(systemPermissions: string[] | undefined): MePermissionsResponse { +function mePermissions(systemPermissions: string[] | undefined): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 'org_1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(systemPermissions ? { systemPermissions } : {}), objects: {}, diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.itemTools-28239.test.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.itemTools-28239.test.tsx index bca2f4a6517..f3c070b66cf 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.itemTools-28239.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.itemTools-28239.test.tsx @@ -149,7 +149,7 @@ registerMetadataDefaultInspector('page', PageDefaultInspector); vi.stubGlobal('fetch', vi.fn(async () => new Response('null', { status: 404, headers: { 'content-type': 'application/json' } }))); function mePermissions(systemPermissions: string[]): MePermissionsResponse { - return { authenticated: true, userId: 'u1', tenantId: 'org_1', roles: [], permissionSets: [], systemPermissions, objects: {}, fields: {} }; + return { authenticated: true, userId: 'u1', tenantId: 'org_1', hasAnyQueue: false, positions: [], permissionSets: [], systemPermissions, objects: {}, fields: {} }; } const SETUP_ONLY = mePermissions(['manage_org_users', 'setup.access', 'setup.write']); diff --git a/ui/packages/fields/src/widgets/LookupField.createGrant-12082.test.tsx b/ui/packages/fields/src/widgets/LookupField.createGrant-12082.test.tsx index c6841934ebe..6282268fe99 100644 --- a/ui/packages/fields/src/widgets/LookupField.createGrant-12082.test.tsx +++ b/ui/packages/fields/src/widgets/LookupField.createGrant-12082.test.tsx @@ -40,7 +40,7 @@ import { render, screen, fireEvent, waitFor, act, cleanup } from '@testing-libra import '@testing-library/jest-dom'; import React from 'react'; import { describe, it, expect, vi, afterEach } from 'vitest'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { LookupField } from './LookupField'; afterEach(cleanup); @@ -68,11 +68,12 @@ function makeDataSource() { type Grant = { allowCreate: boolean; apiOperations?: string[] }; /** `/me/permissions` with `target` and `source` grants on the two objects. */ -const envelope = (target: Grant, source: Grant = { allowCreate: true }): MePermissionsResponse => ({ +const envelope = (target: Grant, source: Grant = { allowCreate: true }): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-requester', tenantId: null, - roles: ['clm_requester'], + hasAnyQueue: false, + positions: ['clm_requester'], permissionSets: ['clm_requester'], objects: { [TARGET]: { allowRead: true, allowEdit: false, allowDelete: false, ...target }, diff --git a/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx b/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx index c753644f481..eb4edbff2eb 100644 --- a/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx +++ b/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx @@ -48,7 +48,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, waitFor, act, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { SchemaRendererContext } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordPickerDialog } from './RecordPickerDialog'; import { getCellRenderer } from '../index'; @@ -78,15 +78,16 @@ function makeBackend(prefix: string) { * An authenticated `/me/permissions` payload: `account` is readable, and each * named `account` field is not. */ -function policyDenying(...fields: string[]): MePermissionsResponse { +function policyDenying(...fields: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: ['viewer'], + positions: ['viewer'], + hasAnyQueue: false, permissionSets: [], objects: { account: { allowRead: true } }, - fields: Object.fromEntries(fields.map((f) => [`account.${f}`, { readable: false }])), + fields: Object.fromEntries(fields.map((f) => [`account.${f}`, { readable: false, editable: false }])), }; } diff --git a/ui/packages/permissions/src/MePermissionsProvider.tsx b/ui/packages/permissions/src/MePermissionsProvider.tsx index 6cf0d345d4e..ff4638be537 100644 --- a/ui/packages/permissions/src/MePermissionsProvider.tsx +++ b/ui/packages/permissions/src/MePermissionsProvider.tsx @@ -21,52 +21,21 @@ import type { } from '@object-ui/types'; import { objectPermissionGrants, type ObjectPermissionVerbTarget } from '@objectstack/spec/security'; import { SystemFieldName } from '@objectstack/spec/system'; +import type { MePermissionsResponse } from '@objectstack/spec/api'; import { PermCtx, type PermissionContextValue } from './PermissionContext.js'; import { createDiscardProofCache } from './discardProofCache.js'; /** - * Shape of the upstream `/api/v1/auth/me/permissions` response. - * See framework/packages/plugins/plugin-hono-server. + * The `/api/v1/auth/me/permissions` response, DERIVED from the strict contract + * `@objectstack/spec/api` declares for it (objectstack#28508): an anonymous + * `{ authenticated: false }`, or the authenticated body with `positions`, + * `permissionSets`, `objects`, `fields`, the optional `systemPermissions` / + * `tabPermissions`, and `hasAnyQueue`. It replaces a hand-written interface + * that had drifted from the wire: it read a `roles` key the server never + * sends (the server's key is `positions`), so `roles` in this provider's + * context was always empty. */ -export interface MePermissionsResponse { - authenticated: boolean; - userId: string | null; - tenantId: string | null; - roles: string[]; - permissionSets: string[]; - /** [ADR-0066] System capabilities (union of permission-set systemPermissions): manage_users, setup.access, … */ - systemPermissions?: string[]; - /** - * object-level perms: { "*": {...}, "account": {...} }. A row grants only - * what the spec's `objectPermissionGrants` reads from it — a bit the row - * omits is NOT granted, exactly as the server enforces it. - */ - objects: Record; - /** field-level perms keyed `"object.field"` */ - fields: Record; -} +export type { MePermissionsResponse, MePermissionsAuthenticatedResponse } from '@objectstack/spec/api'; export interface MePermissionsProviderProps { /** Absolute or relative URL to the /me/permissions endpoint */ @@ -311,6 +280,8 @@ export function MePermissionsProvider({ ); const dataKey: object = data ?? NO_DATA; + // Only the authenticated body carries grants; the anonymous one is `{ authenticated: false }`. + const authed = data?.authenticated === true ? data : null; const checkField = CHECK_FIELD([dataKey], () => (object: string, field: string, action: 'read' | 'write' | 'create'): boolean => { @@ -319,8 +290,8 @@ export function MePermissionsProvider({ // pass schema.objectName as "Account" / "account" interchangeably. const objKey = (object ?? '').toLowerCase(); const key = `${objKey}.${field}`; - const fieldPerm = data.fields?.[key] ?? data.fields?.[`${object}.${field}`]; - const objPerm = data.objects?.[objKey] ?? data.objects?.[object] ?? data.objects?.['*']; + const fieldPerm = authed?.fields[key] ?? authed?.fields[`${object}.${field}`]; + const objPerm = authed?.objects[objKey] ?? authed?.objects[object] ?? authed?.objects['*']; // [objectui#12103] An update that writes the record's OWNER is a // transfer: the server's security step refuses it unless the caller holds // the transfer grant, whatever the field's own entry says. So `write` for @@ -376,7 +347,7 @@ export function MePermissionsProvider({ if (!data) return { allowed: false, reason: 'permissions-loading' }; // Same key normalization as checkField: the backend keys objects lowercase. const objKey = (object ?? '').toLowerCase(); - const objPerm = data.objects?.[objKey] ?? data.objects?.[object] ?? data.objects?.['*']; + const objPerm = authed?.objects[objKey] ?? authed?.objects[object] ?? authed?.objects['*']; const map: Record = { read: 'allowRead', view: 'allowRead', @@ -409,7 +380,7 @@ export function MePermissionsProvider({ if (!data) return []; const prefix = `${object}.`; const out: FieldLevelPermission[] = []; - for (const [key, value] of Object.entries(data.fields ?? {})) { + for (const [key, value] of Object.entries(authed?.fields ?? {})) { if (!key.startsWith(prefix)) continue; const field = key.slice(prefix.length); out.push({ @@ -428,7 +399,7 @@ export function MePermissionsProvider({ const objKey = (object ?? '').toLowerCase(); // Per-object only — the `*` wildcard carries no apiOperations. Absent → // undefined so consumers fall back to their current (default-allow) path. - const objPerm = data.objects?.[objKey] ?? data.objects?.[object]; + const objPerm = authed?.objects[objKey] ?? authed?.objects[object]; const ops = objPerm?.apiOperations; return Array.isArray(ops) ? ops : undefined; }, @@ -444,18 +415,20 @@ export function MePermissionsProvider({ getFieldPermissions, getRowFilter: NO_ROW_FILTER, getObjectApiOperations, - roles: data?.roles ?? [], + // The server's `positions` (objectstack#28508: the console used to read a + // `roles` key the server never sends, so this was always empty). + roles: authed?.positions ?? [], // [objectui#5683] `null` while unloaded/anonymous — never ''. Consumers // treat null as "unknown" and defer to the server. - userId: data?.userId ?? null, + userId: authed?.userId ?? null, // [objectui#4656] Forward the raw signal — do NOT `?? []` this. A // backend predating ADR-0066 omits `systemPermissions` from the // response entirely, and defaulting that to `[]` here made it // indistinguishable from a genuinely empty grant to every consumer // downstream (this provider's own `hasCapabilities` included). - systemPermissions: data?.systemPermissions, + systemPermissions: authed?.systemPermissions, hasCapabilities: (required: string[]) => { - const perms = data?.systemPermissions; + const perms = authed?.systemPermissions; // Unknown (backend never reported systemPermissions) fails OPEN — see // the doctrine on `PermissionContextValue.hasCapabilities`. if (!Array.isArray(perms)) return true; @@ -465,7 +438,7 @@ export function MePermissionsProvider({ // [objectui#4421] The response's `objects` map, verbatim — the data // `current_user.can(object, verb)` is answered from. Keyed on `dataKey` // like every member here, so it is the SAME object for the same payload. - effectiveObjects: data?.objects, + effectiveObjects: authed?.objects, isLoaded, })); diff --git a/ui/packages/permissions/src/__tests__/MePermissionsProvider.absentBitDenies-26193.test.tsx b/ui/packages/permissions/src/__tests__/MePermissionsProvider.absentBitDenies-26193.test.tsx index be804d667c2..cd537137d02 100644 --- a/ui/packages/permissions/src/__tests__/MePermissionsProvider.absentBitDenies-26193.test.tsx +++ b/ui/packages/permissions/src/__tests__/MePermissionsProvider.absentBitDenies-26193.test.tsx @@ -27,16 +27,17 @@ import { describe, it, expect, afterEach } from 'vitest'; import { render, cleanup } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '../MePermissionsProvider'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; import { usePermissions } from '../usePermissions'; afterEach(cleanup); -const envelope = (objects: MePermissionsResponse['objects']): MePermissionsResponse => ({ +const envelope = (objects: MePermissionsAuthenticatedResponse['objects']): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-auditor', tenantId: null, - roles: ['auditor'], + hasAnyQueue: false, + positions: ['auditor'], permissionSets: ['auditor'], objects, fields: {}, diff --git a/ui/packages/permissions/src/__tests__/MePermissionsProvider.capabilitiesReported.test.tsx b/ui/packages/permissions/src/__tests__/MePermissionsProvider.capabilitiesReported.test.tsx index bea00bdf8c2..88eddd910a6 100644 --- a/ui/packages/permissions/src/__tests__/MePermissionsProvider.capabilitiesReported.test.tsx +++ b/ui/packages/permissions/src/__tests__/MePermissionsProvider.capabilitiesReported.test.tsx @@ -56,7 +56,8 @@ describe('objectui#4656 — absent-vs-empty systemPermissions truth table', () = authenticated: true, userId: 'u1', tenantId: 't1', - roles: ['org_owner'], + hasAnyQueue: false, + positions: ['org_owner'], permissionSets: ['organization_admin'], // No `systemPermissions` key at all — pre-ADR-0066 shape. objects: {}, @@ -81,7 +82,8 @@ describe('objectui#4656 — absent-vs-empty systemPermissions truth table', () = authenticated: true, userId: 'u1', tenantId: 't1', - roles: ['org_owner'], + hasAnyQueue: false, + positions: ['org_owner'], permissionSets: ['organization_admin'], systemPermissions: [], // a REAL answer: holds zero capabilities objects: {}, @@ -105,7 +107,8 @@ describe('objectui#4656 — absent-vs-empty systemPermissions truth table', () = authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], systemPermissions: [], objects: {}, @@ -125,7 +128,8 @@ describe('objectui#4656 — absent-vs-empty systemPermissions truth table', () = authenticated: true, userId: 'u1', tenantId: 't1', - roles: ['org_owner'], + hasAnyQueue: false, + positions: ['org_owner'], permissionSets: ['organization_admin'], systemPermissions: ['manage_org_users', 'setup.access', 'manage_metadata'], objects: {}, @@ -146,7 +150,8 @@ describe('objectui#4656 — absent-vs-empty systemPermissions truth table', () = authenticated: true, userId: 'u1', tenantId: 't1', - roles: ['org_owner'], + hasAnyQueue: false, + positions: ['org_owner'], permissionSets: ['organization_admin'], // The objectstack#8270 EE owner's exact set — non-empty, no manage_metadata. systemPermissions: ['manage_org_users', 'setup.access', 'setup.write'], @@ -203,7 +208,8 @@ describe('objectui#4656 — absent-vs-empty systemPermissions truth table', () = { authenticated: true, userId: 'u1', tenantId: 't1', - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['restricted'], objects: { '*': { allowRead: true, allowEdit: true } }, fields: { @@ -68,7 +69,8 @@ describe('MePermissionsProvider', () => { authenticated: true, userId: 'u1', tenantId: 't1', - roles: ['viewer'], + positions: ['viewer'], + hasAnyQueue: false, permissionSets: ['viewer_readonly'], objects: { '*': { allowRead: true, allowEdit: false } }, fields: {}, @@ -107,7 +109,8 @@ describe('MePermissionsProvider', () => { authenticated: true, userId: 'u1', tenantId: 't1', - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['restricted'], objects: { account: { allowRead: true, allowEdit: true } }, // no '*', nothing for 'project' fields: {}, @@ -151,7 +154,8 @@ describe('MePermissionsProvider', () => { authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], objects: { '*': { allowRead: true, allowEdit: true } }, fields: {}, @@ -178,10 +182,11 @@ describe('MePermissionsProvider', () => { authenticated: true, userId: 'u', tenantId: 't', - roles: [], + positions: [], permissionSets: [], objects: {}, - fields: { 'account.secret': { readable: false } }, + fields: { 'account.secret': { readable: false, editable: false } }, + hasAnyQueue: false, }} > @@ -210,7 +215,7 @@ describe('MePermissionsProvider', () => { { {String(perms.isLoaded)}; } -function response(objects: MePermissionsResponse['objects']): MePermissionsResponse { +function response(objects: MePermissionsAuthenticatedResponse['objects']): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: ['account_clerk'], objects, fields: {}, diff --git a/ui/packages/permissions/src/__tests__/providerCtxIdentity.discarded.test.tsx b/ui/packages/permissions/src/__tests__/providerCtxIdentity.discarded.test.tsx index a085720b12c..0124c0e41f9 100644 --- a/ui/packages/permissions/src/__tests__/providerCtxIdentity.discarded.test.tsx +++ b/ui/packages/permissions/src/__tests__/providerCtxIdentity.discarded.test.tsx @@ -53,7 +53,7 @@ import type { ObjectPermissionConfig, RoleDefinition } from '@object-ui/types'; import { PermCtx, type PermissionContextValue } from '../PermissionContext'; import { usePermissions } from '../usePermissions'; import { PermissionProvider } from '../PermissionProvider'; -import { MePermissionsProvider, type MePermissionsResponse } from '../MePermissionsProvider'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; const memoProxy = vi.hoisted(() => ({ armed: false, epoch: 0 })); @@ -137,11 +137,12 @@ const PERMISSIONS: ObjectPermissionConfig[] = [ ]; const USER_ROLES = ['restricted']; -const ME: MePermissionsResponse = { +const ME: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u-1', tenantId: 't-1', - roles: ['restricted'], + hasAnyQueue: false, + positions: ['restricted'], permissionSets: ['ps-1'], systemPermissions: ['manage_app'], objects: { accounts: { allowRead: true, allowEdit: false, apiOperations: ['find'] } }, diff --git a/ui/packages/permissions/src/index.ts b/ui/packages/permissions/src/index.ts index 47ee7d6f644..c854a2b6894 100644 --- a/ui/packages/permissions/src/index.ts +++ b/ui/packages/permissions/src/index.ts @@ -20,7 +20,12 @@ */ export { PermissionProvider, type PermissionProviderProps } from './PermissionProvider.js'; -export { MePermissionsProvider, type MePermissionsProviderProps, type MePermissionsResponse } from './MePermissionsProvider.js'; +export { + MePermissionsProvider, + type MePermissionsProviderProps, + type MePermissionsResponse, + type MePermissionsAuthenticatedResponse, +} from './MePermissionsProvider.js'; export { ServerReadScope, type ServerReadScopeProps } from './ServerReadScope.js'; export { RecordFieldAccessScope, diff --git a/ui/packages/plugin-calendar/src/ObjectCalendar.quickCreateHandoff-12291.test.tsx b/ui/packages/plugin-calendar/src/ObjectCalendar.quickCreateHandoff-12291.test.tsx index ae8d994f6c1..ac7103b8bec 100644 --- a/ui/packages/plugin-calendar/src/ObjectCalendar.quickCreateHandoff-12291.test.tsx +++ b/ui/packages/plugin-calendar/src/ObjectCalendar.quickCreateHandoff-12291.test.tsx @@ -35,7 +35,7 @@ import React from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react'; const probe = vi.hoisted(() => ({ @@ -227,11 +227,12 @@ describe('objectui#12291 — an object quick-create cannot satisfy hands the day it('a closed `calendarQuickCreate` row withholds the hand-off exactly as it withholds the dialog', async () => { const onEdit = vi.fn(); - const readOnly: MePermissionsResponse = { + const readOnly: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { showcase_task: { allowCreate: false, allowRead: true, allowEdit: false, allowDelete: false } as never }, fields: {}, diff --git a/ui/packages/plugin-calendar/src/ObjectCalendar.writeGrant-12082.test.tsx b/ui/packages/plugin-calendar/src/ObjectCalendar.writeGrant-12082.test.tsx index 7d335aaa623..dfe248423ee 100644 --- a/ui/packages/plugin-calendar/src/ObjectCalendar.writeGrant-12082.test.tsx +++ b/ui/packages/plugin-calendar/src/ObjectCalendar.writeGrant-12082.test.tsx @@ -32,7 +32,7 @@ import React from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const probe = vi.hoisted(() => ({ props: null as null | Record, @@ -78,11 +78,12 @@ const SHAPES: Array<{ shape: string; bits: Record; create: bool { shape: 'full', bits: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, create: true, update: true }, ]; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits as never }, fields: {}, diff --git a/ui/packages/plugin-detail/src/__tests__/DetailSection.fieldWriteGrant-12103.test.tsx b/ui/packages/plugin-detail/src/__tests__/DetailSection.fieldWriteGrant-12103.test.tsx index 1d03940c97d..888fa1c98a6 100644 --- a/ui/packages/plugin-detail/src/__tests__/DetailSection.fieldWriteGrant-12103.test.tsx +++ b/ui/packages/plugin-detail/src/__tests__/DetailSection.fieldWriteGrant-12103.test.tsx @@ -10,7 +10,7 @@ import { describe, it, expect, vi, beforeAll, afterEach } from 'vitest'; import { render, screen, fireEvent, cleanup } from '@testing-library/react'; import React from 'react'; import { InlineEditProvider, useInlineEdit } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { SystemFieldName } from '@objectstack/spec/system'; import type { DetailViewSection } from '@object-ui/types'; import { DetailSection } from '../DetailSection'; @@ -39,12 +39,13 @@ const OBJECT = 'crm_account'; const OWNER = SystemFieldName.OWNER_ID; /** The measured envelope: may edit the object, may not edit annual revenue, may not transfer. */ -function envelope(objectBits: Record = {}): MePermissionsResponse { +function envelope(objectBits: Record = {}): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u-rep', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: ['rep'], objects: { [OBJECT]: { diff --git a/ui/packages/plugin-detail/src/__tests__/RecordDetailPanel.editGrant-12082.test.tsx b/ui/packages/plugin-detail/src/__tests__/RecordDetailPanel.editGrant-12082.test.tsx index d9633019c9d..d484cc3b73d 100644 --- a/ui/packages/plugin-detail/src/__tests__/RecordDetailPanel.editGrant-12082.test.tsx +++ b/ui/packages/plugin-detail/src/__tests__/RecordDetailPanel.editGrant-12082.test.tsx @@ -30,7 +30,7 @@ import React from 'react'; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { cleanup, fireEvent, render, screen } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordDetailPanel } from '../RecordDetailPanel'; const OBJECT = 'crm_opportunity'; @@ -51,11 +51,12 @@ const FULL = SHAPES[3].bits; * the field, so the field question answers yes and only the object-level row * can decide. */ -const envelope = (bits: Record, named = true): MePermissionsResponse => ({ +const envelope = (bits: Record, named = true): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits as never }, fields: named ? { [`${OBJECT}.note`]: { readable: true, editable: true } } : {}, diff --git a/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx b/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx index af96e6fb9a9..7a11064079b 100644 --- a/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx +++ b/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx @@ -52,7 +52,7 @@ import { type RelatedRecordActionsValue, type RelatedRowActionDef, } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // Registers `record:related_list`, the block under test. import '../index'; @@ -114,19 +114,20 @@ const makeDataSource = () => ({ * The child object is readable, so the list's automatic read gate stays open * and every verdict below is the notice's own. */ -function me(systemPermissions: string[] | undefined): MePermissionsResponse { +function me(systemPermissions: string[] | undefined): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(systemPermissions === undefined ? {} : { systemPermissions }), objects: { contact: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }, }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } const doc = (actions: string[]) => ({ diff --git a/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.removeGrant-12082.test.tsx b/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.removeGrant-12082.test.tsx index c7ec0927f7f..927eb1aa471 100644 --- a/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.removeGrant-12082.test.tsx +++ b/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.removeGrant-12082.test.tsx @@ -30,7 +30,7 @@ import { RelatedRecordActionsProvider, type RelatedRecordActionsValue, } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordRelatedListRenderer } from '../renderers/record-related-list'; const h = vi.hoisted(() => ({ captured: null as any })); @@ -52,16 +52,17 @@ const SHAPE = { full: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true }, } as const; -function answer(bits: Record): MePermissionsResponse { +function answer(bits: Record): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects: { [JUNCTION]: bits }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } const ds = { find: vi.fn(async () => []), delete: vi.fn(async () => undefined) }; diff --git a/ui/packages/plugin-detail/src/__tests__/RelatedList.addGrant-12082.test.tsx b/ui/packages/plugin-detail/src/__tests__/RelatedList.addGrant-12082.test.tsx index 2a855b3843e..08d77e36103 100644 --- a/ui/packages/plugin-detail/src/__tests__/RelatedList.addGrant-12082.test.tsx +++ b/ui/packages/plugin-detail/src/__tests__/RelatedList.addGrant-12082.test.tsx @@ -26,7 +26,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, waitFor, cleanup } from '@testing-library/react'; import * as React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RelatedList } from '../RelatedList'; const JUNCTION = 'sys_user_permission_set'; @@ -41,16 +41,17 @@ const SHAPE = { full: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true }, } as const; -function answer(objects: Record>): MePermissionsResponse { +function answer(objects: Record>): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + positions: ['member'], + hasAnyQueue: false, permissionSets: ['member'], objects, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } /** diff --git a/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx b/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx index 2f615bfc7e0..841d8cfc883 100644 --- a/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx +++ b/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { recordSitsInQueue, queueClaimAdmitted, @@ -30,12 +30,13 @@ import { claimQueuedRecord, } from './queueClaim'; -function me(userId: string): MePermissionsResponse { +function me(userId: string): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { ticket: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx index 127b835b000..a419ee16a41 100644 --- a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx +++ b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx @@ -34,7 +34,7 @@ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vite import { render, screen, waitFor, act, cleanup, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { HighlightFieldsProvider, InlineEditProvider, RecordContextProvider, useInlineEdit } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordDetailsRenderer } from '../record-details'; import { RecordHighlightsRenderer } from '../record-highlights'; @@ -55,11 +55,12 @@ const OBJECT_SCHEMA = { }, }; -const PRINCIPAL: MePermissionsResponse = { +const PRINCIPAL: MePermissionsAuthenticatedResponse = { authenticated: true, userId: 'u-1', tenantId: null, - roles: ['finance'], + positions: ['finance'], + hasAnyQueue: false, permissionSets: ['rfa_fixture_member'], objects: { [OBJECT]: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: false } }, // As `/auth/me/permissions` serves them: the conditioned entry as its lock. @@ -67,7 +68,7 @@ const PRINCIPAL: MePermissionsResponse = { [`${OBJECT}.summary`]: { readable: true, editable: false }, [`${OBJECT}.ref_no`]: { readable: true, editable: false }, }, -} as MePermissionsResponse; +} as MePermissionsAuthenticatedResponse; const RECORD = { id: 'c1', name: 'MSA', region: 'EMEA', summary: 'Draft terms', ref_no: 'R-1' }; diff --git a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx index e32925e4e92..10da7365037 100644 --- a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx +++ b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx @@ -47,7 +47,7 @@ import { MePermissionsProvider, PermissionProvider, usePermissions, - type MePermissionsResponse, + type MePermissionsResponse, type MePermissionsAuthenticatedResponse, } from '@object-ui/permissions'; import { RecordDetailsRenderer } from '../record-details'; import { RecordHighlightsRenderer } from '../record-highlights'; @@ -99,12 +99,13 @@ const ds = { find: vi.fn(async () => []) }; * lets the discriminators below fail — a gate still reading the object-action * path lands on a different verdict for at least one of them. */ -function me(systemPermissions: string[] | undefined, objectBits?: Record): MePermissionsResponse { +function me(systemPermissions: string[] | undefined, objectBits?: Record): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(systemPermissions === undefined ? {} : { systemPermissions }), objects: { @@ -116,7 +117,7 @@ function me(systemPermissions: string[] | undefined, objectBits?: Record): MePermissionsResponse { +function me(systemPermissions: string[] | undefined, objectBits?: Record): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(systemPermissions === undefined ? {} : { systemPermissions }), objects: { crm_account: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false, ...objectBits }, }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } function bar(schema: Record, objectName = 'crm_account') { diff --git a/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx b/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx index edb377e7b5c..95db8836f4e 100644 --- a/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx +++ b/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx @@ -32,7 +32,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordEditable, __clearRecordEditableCache } from './useRecordEditable'; interface HeldAsk { @@ -63,12 +63,13 @@ function heldExplain() { return { asks, fetchMock }; } -function me(userId: string): MePermissionsResponse { +function me(userId: string): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { note: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx b/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx index ae6744b5ef2..37f39e991f9 100644 --- a/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx +++ b/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx @@ -37,16 +37,17 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordEditable, __clearRecordEditableCache } from './useRecordEditable'; /** A `/me/permissions` payload for one principal; the object grant is constant. */ -function me(userId: string): MePermissionsResponse { +function me(userId: string): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { note: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx b/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx index a92a0b68600..a2eecd7340f 100644 --- a/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx +++ b/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx @@ -42,18 +42,19 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordEditable, __clearRecordEditableCache } from './useRecordEditable'; /** A `/me/permissions` payload for one principal. The object bits are constant * across principals on purpose: the OBJECT-level grant is not what this file * measures, so it cannot be the reason a verdict changes. */ -function me(userId: string): MePermissionsResponse { +function me(userId: string): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { note: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-detail/src/useRecordEditable.recheckOnce-13341.test.tsx b/ui/packages/plugin-detail/src/useRecordEditable.recheckOnce-13341.test.tsx index 15007a26ca7..b2ccc3445ae 100644 --- a/ui/packages/plugin-detail/src/useRecordEditable.recheckOnce-13341.test.tsx +++ b/ui/packages/plugin-detail/src/useRecordEditable.recheckOnce-13341.test.tsx @@ -64,7 +64,8 @@ const asA = ({ children }: { children: React.ReactNode }) => ( authenticated: true, userId: 'userA', tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { note: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-form/src/LineItemsPanel.lineGrant-12082.test.tsx b/ui/packages/plugin-form/src/LineItemsPanel.lineGrant-12082.test.tsx index 64112edc1e9..dafc9135c26 100644 --- a/ui/packages/plugin-form/src/LineItemsPanel.lineGrant-12082.test.tsx +++ b/ui/packages/plugin-form/src/LineItemsPanel.lineGrant-12082.test.tsx @@ -27,7 +27,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, waitFor, cleanup } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { registerAllFields } from '@object-ui/fields'; import { SchemaRendererProvider } from '@object-ui/react'; import { LineItemsPanel } from './LineItemsPanel'; @@ -60,11 +60,12 @@ const SHAPES: Array<{ shape: string; bits: Record; add: boolean { shape: 'full', bits: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, add: true, remove: true }, ]; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], // The parent is fully granted: only the CHILD's grant may decide. objects: { diff --git a/ui/packages/plugin-form/src/MasterDetailForm.lineGrant-12082.test.tsx b/ui/packages/plugin-form/src/MasterDetailForm.lineGrant-12082.test.tsx index ebe7c788efc..2c58afcbfa4 100644 --- a/ui/packages/plugin-form/src/MasterDetailForm.lineGrant-12082.test.tsx +++ b/ui/packages/plugin-form/src/MasterDetailForm.lineGrant-12082.test.tsx @@ -32,7 +32,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { registerAllFields } from '@object-ui/fields'; import { MasterDetailForm } from './MasterDetailForm'; @@ -51,11 +51,12 @@ const SHAPES: Array<{ shape: string; bits: Record; create: bool { shape: 'full', bits: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, create: true, remove: true }, ]; -const envelope = (child: Record): MePermissionsResponse => ({ +const envelope = (child: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [PARENT]: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, diff --git a/ui/packages/plugin-form/src/affordanceGrantMap-12082.test.tsx b/ui/packages/plugin-form/src/affordanceGrantMap-12082.test.tsx index a8612be09a5..28f9faaf812 100644 --- a/ui/packages/plugin-form/src/affordanceGrantMap-12082.test.tsx +++ b/ui/packages/plugin-form/src/affordanceGrantMap-12082.test.tsx @@ -74,7 +74,7 @@ import { type ConsoleAffordance, type FieldAffordance, } from '@object-ui/core'; -import { MePermissionsProvider, usePermissions, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, usePermissions, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { SystemFieldName } from '@objectstack/spec/system'; import { closedFormAffordance, fieldWriteGate, gateFormFields } from './fieldWriteGate'; import ts from 'typescript'; @@ -182,11 +182,12 @@ const SHAPES: Array<{ shape: string; bits: Record; holds: Reado }, ]; -const envelope = (bits: Record, fields: MePermissionsResponse['fields'] = {}): MePermissionsResponse => ({ +const envelope = (bits: Record, fields: MePermissionsAuthenticatedResponse['fields'] = {}): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits }, fields, diff --git a/ui/packages/plugin-form/src/createFormGrant-12082.test.tsx b/ui/packages/plugin-form/src/createFormGrant-12082.test.tsx index 78478b9ebf6..f944452af90 100644 --- a/ui/packages/plugin-form/src/createFormGrant-12082.test.tsx +++ b/ui/packages/plugin-form/src/createFormGrant-12082.test.tsx @@ -48,7 +48,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { registerAllFields } from '@object-ui/fields'; import { ObjectForm } from './ObjectForm'; import { MasterDetailForm } from './MasterDetailForm'; @@ -66,11 +66,12 @@ const FIELDS = { const RECORD = { id: 'v1', contract: 'C-1', version_no: 1, notes: 'first' }; /** `/me/permissions` for the card's requester: create, never edit. */ -const createOnly = (fields: MePermissionsResponse['fields'] = {}): MePermissionsResponse => ({ +const createOnly = (fields: MePermissionsAuthenticatedResponse['fields'] = {}): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-requester', tenantId: null, - roles: ['clm_requester'], + hasAnyQueue: false, + positions: ['clm_requester'], permissionSets: ['clm_requester'], objects: { [OBJECT]: { allowCreate: true, allowRead: true, allowEdit: false, allowDelete: false }, diff --git a/ui/packages/plugin-form/src/listNewAbsentCreateBit-26193.test.tsx b/ui/packages/plugin-form/src/listNewAbsentCreateBit-26193.test.tsx index 23ddfa4765e..7d7ccbe66c8 100644 --- a/ui/packages/plugin-form/src/listNewAbsentCreateBit-26193.test.tsx +++ b/ui/packages/plugin-form/src/listNewAbsentCreateBit-26193.test.tsx @@ -27,17 +27,18 @@ import { describe, it, expect, afterEach } from 'vitest'; import { render, cleanup } from '@testing-library/react'; import React from 'react'; import { resolveAffordance, type ConsoleAffordance } from '@object-ui/core'; -import { MePermissionsProvider, usePermissions, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, usePermissions, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; afterEach(cleanup); const OBJECT = 'invoice'; -const envelope = (objects: MePermissionsResponse['objects']): MePermissionsResponse => ({ +const envelope = (objects: MePermissionsAuthenticatedResponse['objects']): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-auditor', tenantId: null, - roles: ['auditor'], + hasAnyQueue: false, + positions: ['auditor'], permissionSets: ['auditor'], objects, fields: {}, diff --git a/ui/packages/plugin-form/src/recordFieldAccess-26594.test.tsx b/ui/packages/plugin-form/src/recordFieldAccess-26594.test.tsx index 95b7e51d508..77f84b5055f 100644 --- a/ui/packages/plugin-form/src/recordFieldAccess-26594.test.tsx +++ b/ui/packages/plugin-form/src/recordFieldAccess-26594.test.tsx @@ -50,7 +50,8 @@ const PRINCIPAL: any = { authenticated: true, userId: 'u-1', tenantId: null, - roles: ['finance'], + positions: ['finance'], + hasAnyQueue: false, permissionSets: ['rfa_fixture_member'], objects: { rfa_contract: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: false } }, // As `/auth/me/permissions` serves them: the conditioned entry as its lock. diff --git a/ui/packages/plugin-gantt/src/ObjectGantt.writeGrant-12082.test.tsx b/ui/packages/plugin-gantt/src/ObjectGantt.writeGrant-12082.test.tsx index df21f47b628..fd6e797195e 100644 --- a/ui/packages/plugin-gantt/src/ObjectGantt.writeGrant-12082.test.tsx +++ b/ui/packages/plugin-gantt/src/ObjectGantt.writeGrant-12082.test.tsx @@ -30,7 +30,7 @@ import React from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, cleanup, render, screen, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const probe = vi.hoisted(() => ({ props: null as null | Record, @@ -92,11 +92,12 @@ const SHAPES: Array<{ shape: string; bits: Record; update: bool { shape: 'full', bits: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, update: true, remove: true }, ]; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits as never }, fields: {}, diff --git a/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx b/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx index 53990f9f60e..1c11d1016ee 100644 --- a/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx +++ b/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx @@ -26,7 +26,7 @@ import { describe, it, expect, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import '@testing-library/jest-dom'; import { FilterScopeProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // Registers `object-grid`, the block under test. import { ObjectGridRenderer } from '../index'; @@ -38,11 +38,12 @@ const USER = 'u-rep'; const READ_ONLY = { allowCreate: false, allowRead: true, allowEdit: false, allowDelete: false }; const FULL = { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: USER, tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits }, fields: {}, diff --git a/ui/packages/plugin-grid/src/__tests__/bulkUpdateGrant-12082.test.tsx b/ui/packages/plugin-grid/src/__tests__/bulkUpdateGrant-12082.test.tsx index e514afdb958..cf1d3defbd4 100644 --- a/ui/packages/plugin-grid/src/__tests__/bulkUpdateGrant-12082.test.tsx +++ b/ui/packages/plugin-grid/src/__tests__/bulkUpdateGrant-12082.test.tsx @@ -29,7 +29,7 @@ import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { registerAllFields } from '@object-ui/fields'; import { ActionProvider, SchemaRendererProvider } from '@object-ui/react'; import { ObjectGrid } from '../ObjectGrid'; @@ -57,11 +57,12 @@ const SHAPES: Array<{ shape: string; bits: Record; update: bool { shape: 'full', bits: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, update: true, remove: true }, ]; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits as never }, fields: {}, diff --git a/ui/packages/plugin-grid/src/__tests__/inlineEditFieldWriteGrant-12103.test.tsx b/ui/packages/plugin-grid/src/__tests__/inlineEditFieldWriteGrant-12103.test.tsx index 31304e389f7..61d4cb75a94 100644 --- a/ui/packages/plugin-grid/src/__tests__/inlineEditFieldWriteGrant-12103.test.tsx +++ b/ui/packages/plugin-grid/src/__tests__/inlineEditFieldWriteGrant-12103.test.tsx @@ -27,7 +27,7 @@ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vite import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'; import '@testing-library/jest-dom'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { SystemFieldName } from '@objectstack/spec/system'; import { ObjectGrid } from '../ObjectGrid'; import { __clearRecordCrudVerdictCache } from '../hooks/useRecordCrudVerdicts'; @@ -56,12 +56,13 @@ afterEach(() => { }); /** The measured envelope: may edit the object, may not edit annual revenue, may not transfer. */ -function envelope(objectBits: Record = {}): MePermissionsResponse { +function envelope(objectBits: Record = {}): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u-rep', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: ['rep'], objects: { [OBJECT]: { diff --git a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx index c511f3d03d5..0136f0cf224 100644 --- a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx +++ b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx @@ -39,7 +39,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordCrudVerdicts, __clearRecordCrudVerdictCache, @@ -48,12 +48,13 @@ import { const OBJECT = 'sys_business_unit'; -function me(userId: string): MePermissionsResponse { +function me(userId: string): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { [OBJECT]: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx index 569617f32e0..f3883a18454 100644 --- a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx +++ b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx @@ -29,7 +29,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordCrudVerdicts, __clearRecordCrudVerdictCache, @@ -38,12 +38,13 @@ import { const OBJECT = 'note'; -function me(userId: string): MePermissionsResponse { +function me(userId: string): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { [OBJECT]: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx index 223bcba989a..8f4714c7dc9 100644 --- a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx +++ b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx @@ -33,18 +33,19 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordCrudVerdicts, __clearRecordCrudVerdictCache } from './useRecordCrudVerdicts'; const OBJECT = 'note'; /** A `/me/permissions` payload for one principal; the object grant is constant. */ -function me(userId: string): MePermissionsResponse { +function me(userId: string): MePermissionsAuthenticatedResponse { return { authenticated: true, userId, tenantId: 't1', - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { [OBJECT]: { allowRead: true, allowCreate: true, allowEdit: true, allowDelete: true } }, fields: {}, diff --git a/ui/packages/plugin-kanban/src/ObjectKanban.moveGrant-12082.test.tsx b/ui/packages/plugin-kanban/src/ObjectKanban.moveGrant-12082.test.tsx index 7cdd86a896d..408cd7c2189 100644 --- a/ui/packages/plugin-kanban/src/ObjectKanban.moveGrant-12082.test.tsx +++ b/ui/packages/plugin-kanban/src/ObjectKanban.moveGrant-12082.test.tsx @@ -28,7 +28,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, act, cleanup, waitFor } from '@testing-library/react'; import { registerAllFields } from '@object-ui/fields'; import { toast } from '@object-ui/components'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { DataSource, ObjectKanbanSchema } from '@object-ui/types'; import { ObjectKanban } from './ObjectKanban'; @@ -88,11 +88,12 @@ const SHAPES: Array<{ shape: string; bits: Record; update: bool { shape: 'full', bits: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, update: true }, ]; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits as never }, fields: {}, diff --git a/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx b/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx index a0f10ec8bf8..2cb140b35f5 100644 --- a/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx @@ -38,7 +38,7 @@ import { render, waitFor, cleanup } from '@testing-library/react'; import { ListView } from '../ListView'; import { filterNamesCurrentUser } from '../ListEmptyState'; import { SchemaRendererProvider, FilterScopeProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { ListViewSchema } from '@object-ui/types'; afterEach(cleanup); @@ -59,11 +59,12 @@ function emptyDataSource() { const READ_ONLY = { allowCreate: false, allowRead: true, allowEdit: false, allowDelete: false }; const FULL = { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: USER, tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits }, fields: {}, diff --git a/ui/packages/plugin-list/src/__tests__/ListView.fieldListRead-11984.test.tsx b/ui/packages/plugin-list/src/__tests__/ListView.fieldListRead-11984.test.tsx index 1b6f4b02c05..79bf76c1010 100644 --- a/ui/packages/plugin-list/src/__tests__/ListView.fieldListRead-11984.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListView.fieldListRead-11984.test.tsx @@ -49,7 +49,7 @@ import '@testing-library/jest-dom'; import React from 'react'; import type { DataSource, ListViewSchema } from '@object-ui/types'; import { SchemaRendererProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ListView } from '../ListView'; const OBJECT = 'fls_ticket'; @@ -66,12 +66,13 @@ const COLUMNS = Object.entries(FIELDS).map(([field, def]) => ({ field, label: de const FIELD_LABELS = COLUMNS.map((c) => c.label); const UNREADABLE = /secret/i; -function answer(fields: MePermissionsResponse['fields']): MePermissionsResponse { +function answer(fields: MePermissionsAuthenticatedResponse['fields']): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: ['fls_member'], objects: { [OBJECT]: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false } }, fields, diff --git a/ui/packages/plugin-list/src/__tests__/ListView.filterFieldRead-11925.test.tsx b/ui/packages/plugin-list/src/__tests__/ListView.filterFieldRead-11925.test.tsx index e6e874498d6..d270bfa67ba 100644 --- a/ui/packages/plugin-list/src/__tests__/ListView.filterFieldRead-11925.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListView.filterFieldRead-11925.test.tsx @@ -27,7 +27,7 @@ import '@testing-library/jest-dom'; import React from 'react'; import type { DataSource, ListViewSchema } from '@object-ui/types'; import { SchemaRendererProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; type Candidate = { value: string; label: string; type: string }; @@ -62,12 +62,13 @@ const COLUMNS = ['title', 'status', 'secret_note']; /** Today's list for a user who may read every field: the columns, then the other business field. */ const FULL_READ_LIST = ['title', 'status', 'secret_note', 'priority']; -function answer(fields: MePermissionsResponse['fields']): MePermissionsResponse { +function answer(fields: MePermissionsAuthenticatedResponse['fields']): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: ['fls_member'], objects: { [OBJECT]: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false } }, fields, diff --git a/ui/packages/plugin-list/src/__tests__/ListView.sortFieldRead-11943.test.tsx b/ui/packages/plugin-list/src/__tests__/ListView.sortFieldRead-11943.test.tsx index 9abe76bc04b..ee101563197 100644 --- a/ui/packages/plugin-list/src/__tests__/ListView.sortFieldRead-11943.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListView.sortFieldRead-11943.test.tsx @@ -31,7 +31,7 @@ import '@testing-library/jest-dom'; import React from 'react'; import type { DataSource, ListViewSchema } from '@object-ui/types'; import { SchemaRendererProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ListView } from '../ListView'; const OBJECT = 'fls_ticket'; @@ -49,12 +49,13 @@ const COLUMNS = ['title', 'status', 'secret_note']; /** Today's Sort picker for a user who may read every field. */ const FULL_READ_LABELS = ['Title', 'Status', 'Secret Note', 'Priority']; -function answer(fields: MePermissionsResponse['fields']): MePermissionsResponse { +function answer(fields: MePermissionsAuthenticatedResponse['fields']): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: ['fls_member'], objects: { [OBJECT]: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false } }, fields, diff --git a/ui/packages/plugin-list/src/__tests__/ListView.sortRemovableOnly-11943.test.tsx b/ui/packages/plugin-list/src/__tests__/ListView.sortRemovableOnly-11943.test.tsx index 785c37caf33..6b47de81a80 100644 --- a/ui/packages/plugin-list/src/__tests__/ListView.sortRemovableOnly-11943.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListView.sortRemovableOnly-11943.test.tsx @@ -33,7 +33,7 @@ import { resolveObjectSortability } from '@objectstack/spec/api'; import { attachObjectSortability } from '@object-ui/core'; import type { DataSource, ListViewSchema } from '@object-ui/types'; import { SchemaRendererProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ListView } from '../ListView'; const OBJECT = 'fls_ticket'; @@ -53,12 +53,13 @@ function definition(first: string) { return { name: OBJECT, label: 'Ticket', fields }; } -function answer(fields: MePermissionsResponse['fields']): MePermissionsResponse { +function answer(fields: MePermissionsAuthenticatedResponse['fields']): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: ['fls_member'], objects: { [OBJECT]: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false } }, fields, diff --git a/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx b/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx index f64fef5d5bb..df42ea08d6a 100644 --- a/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx @@ -42,7 +42,7 @@ import { type RelatedRecordActionsValue, type RelatedRecordHandlers, } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ComponentPropsMap } from '@objectstack/spec/ui'; // Registers the REAL `list-view`. import '../index'; @@ -102,19 +102,20 @@ function node(properties: Row = {}): Row { } /** A `/me/permissions` payload; the object may be read, never created. */ -function me(systemPermissions: string[] | undefined): MePermissionsResponse { +function me(systemPermissions: string[] | undefined): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'u1', tenantId: 't1', - roles: [], + positions: [], + hasAnyQueue: false, permissionSets: [], ...(systemPermissions === undefined ? {} : { systemPermissions }), objects: { [OBJECT]: { allowRead: true, allowCreate: false, allowEdit: false, allowDelete: false }, }, fields: {}, - } as MePermissionsResponse; + } as MePermissionsAuthenticatedResponse; } interface MountOptions { diff --git a/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx b/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx index e1584bf2ddb..0dd6bd18390 100644 --- a/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx @@ -25,7 +25,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, cleanup, waitFor } from '@testing-library/react'; import { I18nProvider } from '@object-ui/i18n'; import { SchemaRendererProvider, FilterScopeProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { DataSource, ObjectGallerySchema } from '@object-ui/types'; import { ObjectGallery } from '../ObjectGallery'; @@ -36,11 +36,12 @@ const USER = 'u-rep'; const READ_ONLY = { allowCreate: false, allowRead: true, allowEdit: false, allowDelete: false }; const FULL = { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: USER, tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits }, fields: {}, diff --git a/ui/packages/plugin-report/src/__tests__/DatasetReportRenderer.export-25873.test.tsx b/ui/packages/plugin-report/src/__tests__/DatasetReportRenderer.export-25873.test.tsx index 75cc885be04..df590951347 100644 --- a/ui/packages/plugin-report/src/__tests__/DatasetReportRenderer.export-25873.test.tsx +++ b/ui/packages/plugin-report/src/__tests__/DatasetReportRenderer.export-25873.test.tsx @@ -28,7 +28,7 @@ import { render, screen, fireEvent, waitFor, cleanup, within } from '@testing-li // at once and the parse-back below reads the same module. import ExcelJS from 'exceljs'; import { I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { DatasetReportRenderer } from '../DatasetReportRenderer'; const OBJECT = 'crm_opportunity'; @@ -103,12 +103,13 @@ afterEach(() => { expect(strayUrls).toEqual([]); }); -function permissions(apiOperations?: string[]): MePermissionsResponse { +function permissions(apiOperations?: string[]): MePermissionsAuthenticatedResponse { return { authenticated: true, userId: 'usr_1', tenantId: null, - roles: [], + hasAnyQueue: false, + positions: [], permissionSets: [], objects: { [OBJECT]: { allowRead: true, ...(apiOperations ? { apiOperations } : {}) } }, fields: {}, diff --git a/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx b/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx index 58e533b0211..d1dd48d3381 100644 --- a/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx +++ b/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx @@ -23,7 +23,7 @@ */ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { ObjectViewSchema, DataSource } from '@object-ui/types'; vi.mock('@object-ui/react', async (importOriginal) => { @@ -67,11 +67,12 @@ const SHAPES: Array<{ shape: string; bits: Record; create: bool { shape: 'full', bits: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: true }, create: true }, ]; -const envelope = (bits: Record): MePermissionsResponse => ({ +const envelope = (bits: Record): MePermissionsAuthenticatedResponse => ({ authenticated: true, userId: 'u-pin', tenantId: null, - roles: ['member'], + hasAnyQueue: false, + positions: ['member'], permissionSets: ['member'], objects: { [OBJECT]: bits }, fields: {}, From e51961f0ab8b123546f06a079c94829219dbb216 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 16:38:24 +0000 Subject: [PATCH 11/11] test(ui): drop the MePermissionsResponse imports the fixture migration left unused object-ui/no-unused-imports (Lint UI) flagged 53 test files across 7 packages after their builders moved to MePermissionsAuthenticatedResponse. Co-Authored-By: Claude --- .../console/__tests__/AppContent.declaredLanding-28450.test.tsx | 2 +- .../console/__tests__/AppContent.hasCapability-13083.test.tsx | 2 +- .../__tests__/AppContent.landingMemberGates-12426.test.tsx | 2 +- .../__tests__/AppContent.noReadableObjectLanding-25338.test.tsx | 2 +- .../console/home/__tests__/HomePage.aiStudioDisabled.test.tsx | 2 +- .../home/__tests__/HomePage.marketplaceDisabled.test.tsx | 2 +- .../src/hooks/__tests__/navEntryDataSource-13029.test.tsx | 2 +- .../src/hooks/__tests__/navEntryReadReuse-13323.test.tsx | 2 +- .../__tests__/sharedUserFeeds.activityRoster-12530.test.tsx | 2 +- .../src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx | 2 +- .../layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx | 2 +- .../layout/__tests__/AppHeader.centeredSearch-12472.test.tsx | 2 +- .../src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx | 2 +- .../__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx | 2 +- .../__tests__/AppHeader.deferredChromeReads-12980.test.tsx | 2 +- .../src/layout/__tests__/AppHeader.homeMark-12605.test.tsx | 2 +- .../src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx | 2 +- .../layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx | 2 +- .../__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx | 2 +- .../src/providers/__tests__/currentUserCan-4421.render.test.tsx | 2 +- .../__tests__/currentUserCan-disabledLegs-11242.render.test.tsx | 2 +- .../__tests__/currentUserCan-failClosed-11212.render.test.tsx | 2 +- .../app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx | 2 +- .../src/views/RecordFormPage.hasCapability-13083.test.tsx | 2 +- .../app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx | 2 +- .../views/__tests__/AppReportIndex.groupsViews-27526.test.tsx | 2 +- .../__tests__/AppReportIndex.navContributions-13273.test.tsx | 2 +- .../src/views/__tests__/AppReportIndex.navGates-26892.test.tsx | 2 +- .../src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx | 2 +- .../PageView.editInStudioFollowsPackagePosture-12615.test.tsx | 2 +- .../__tests__/PageView.editInStudioOpensStudio-12540.test.tsx | 2 +- .../RecordAttachmentsPanel.affordanceGrant-12047.test.tsx | 2 +- .../src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx | 2 +- .../src/__tests__/RecordFieldAccessScope-26594.test.tsx | 2 +- .../permissions/src/__tests__/ServerReadScope-13324.test.tsx | 2 +- .../permissions/src/__tests__/effectiveObjects-4421.test.tsx | 2 +- .../RecordRelatedListRenderer.refusalAudience-11768.test.tsx | 2 +- ui/packages/plugin-detail/src/queueClaim-25769.test.tsx | 2 +- .../__tests__/record-blocks.recordFieldAccess-27146.test.tsx | 2 +- .../__tests__/record-blocks.requiredPermissions-gate.test.tsx | 2 +- .../__tests__/record-blocks.rowWritable-27138.test.tsx | 2 +- .../record-quick-actions.requiredPermissions-gate.test.tsx | 2 +- .../src/useRecordEditable.inflightShare-11699.test.tsx | 2 +- .../plugin-detail/src/useRecordEditable.invalidation.test.tsx | 2 +- .../plugin-detail/src/useRecordEditable.principalScope.test.tsx | 2 +- .../src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx | 2 +- .../src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx | 2 +- .../src/hooks/useRecordCrudVerdicts.invalidation.test.tsx | 2 +- .../src/hooks/useRecordCrudVerdicts.principalScope.test.tsx | 2 +- .../src/__tests__/ListView.emptyStateAccess-25898.test.tsx | 2 +- .../src/__tests__/ListViewBlock.inertKeys-13074.test.tsx | 2 +- .../src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx | 2 +- .../src/__tests__/ObjectView.createGrant-12082.test.tsx | 2 +- 53 files changed, 53 insertions(+), 53 deletions(-) diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx index 4a8ca3af58c..62ef3b8e437 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.declaredLanding-28450.test.tsx @@ -28,7 +28,7 @@ import { describe, it, expect, vi, beforeEach, type MockInstance } from 'vitest' import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // AGENTS.md §测试纪律 — the lazy page modules are stubbed so no unbounded // `import()` races a bounded `findBy` window. diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx index ca719ee19fa..75a0baec780 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.hasCapability-13083.test.tsx @@ -33,7 +33,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { render, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // The reporter's dedupe `Set` is module state: reset it so every arm's // "nothing reported" is a reading, not a leftover from an earlier arm. import { __resetVisibilityPredicateWarnings } from '../../../../react/src/utils/visibilityDiagnostic'; diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx index 4f31e227ac7..a8ebabce665 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.landingMemberGates-12426.test.tsx @@ -34,7 +34,7 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // AGENTS.md §测试纪律 — the lazy page modules are stubbed so no unbounded // `import()` races a bounded `findBy` window. diff --git a/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx b/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx index a495fe6026d..605cfca4f1b 100644 --- a/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx +++ b/ui/packages/app-shell/src/console/__tests__/AppContent.noReadableObjectLanding-25338.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter, Routes, Route, useLocation } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // AGENTS.md §测试纪律 — the lazy page modules are stubbed so no unbounded // `import()` races a bounded `findBy` window. diff --git a/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx b/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx index a63cc963e95..f6397d9c377 100644 --- a/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx +++ b/ui/packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx @@ -57,7 +57,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { render, screen, cleanup } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateMock = vi.fn(); diff --git a/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx b/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx index 5361b5f86e7..10f5b4b8152 100644 --- a/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx +++ b/ui/packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx @@ -36,7 +36,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import React from 'react'; import { render, screen, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateMock = vi.fn(); diff --git a/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx b/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx index 083bf0d116d..f64b790222d 100644 --- a/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx +++ b/ui/packages/app-shell/src/hooks/__tests__/navEntryDataSource-13029.test.tsx @@ -40,7 +40,7 @@ import '@object-ui/components'; import { SidebarProvider } from '@object-ui/components'; import { NavigationRenderer } from '@object-ui/layout'; import { SchemaRenderer, SchemaRendererContext, MetadataCtx, type ApiFetch } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { createI18n, I18nProvider } from '@object-ui/i18n'; import type { NavigationItem } from '@object-ui/types'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; diff --git a/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx b/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx index 2ed050ad186..b3638e82af5 100644 --- a/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx +++ b/ui/packages/app-shell/src/hooks/__tests__/navEntryReadReuse-13323.test.tsx @@ -40,7 +40,7 @@ import { MemoryRouter, Route, Routes, useLocation, useNavigate, type NavigateFun // Module scope, never a `beforeAll` (AGENTS.md §测试纪律, objectui#3010). import { ComponentRegistry } from '@object-ui/core'; import { SchemaRenderer, SchemaRendererContext, MetadataCtx, notifyDataChanged, type ApiFetch } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { NavigationItem } from '@object-ui/types'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; import { ConsoleElementApiOpen } from '../../console/ConsoleElementApiOpen'; diff --git a/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx b/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx index deb5211b5ae..983300d19b5 100644 --- a/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx +++ b/ui/packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx @@ -27,7 +27,7 @@ import '@testing-library/jest-dom/vitest'; import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { MetadataCtx, type MetadataContextValue } from '@object-ui/react'; import { appListing, rosterRegistry, withRoster } from './activityRoster'; diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx index d1a8a3e7e08..b3faf8cc56e 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.aboutBuild-12970.test.tsx @@ -20,7 +20,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, within } from '@testing-library/react'; import React from 'react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx index c3aec362f56..42aa9771d52 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.appBarLeftGroup-12461.test.tsx @@ -34,7 +34,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, within } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/lead', search: '' })); const nav = vi.hoisted(() => ({ recordTitle: undefined as string | undefined })); diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx index 145a37429fa..701d37ae5ca 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.centeredSearch-12472.test.tsx @@ -41,7 +41,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/lead', search: '' })); diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx index f3a6a95dd9a..6c36bc4c3af 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.crumbFold-12702.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, within, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/system/marketplace', search: '' })); diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx index 21fa84f20cb..56b774db979 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.dashboardPageNoCrumb-12526.test.tsx @@ -29,7 +29,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, within } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const route = vi.hoisted(() => ({ pathname: '/apps/crm/dashboard/ops_board' })); const served = vi.hoisted(() => ({ diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx index d1156a9797c..b77cd6932dd 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.deferredChromeReads-12980.test.tsx @@ -184,7 +184,7 @@ const fakeAdapter = { }; vi.mock('../../providers/AdapterProvider', () => ({ useAdapter: () => fakeAdapter })); -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { AppHeader } from '../AppHeader'; import { __resetSharedUserFeeds, diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx index f2a9e2dec61..afb0b895d48 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.homeMark-12605.test.tsx @@ -29,7 +29,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, cleanup, act } from '@testing-library/react'; import React from 'react'; import { MemoryRouter, Routes, Route, useLocation, useNavigate, useNavigationType } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx index eaf2754f76c..856946ebcfd 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.hostSingleApp-12913.test.tsx @@ -24,7 +24,7 @@ import '@testing-library/jest-dom/vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const served = vi.hoisted(() => ({ apps: [] as Array> })); diff --git a/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx b/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx index a11e3a8115d..269bac914f4 100644 --- a/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/AppHeader.sidebarTrigger-12429.test.tsx @@ -21,7 +21,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('react-router-dom', () => ({ useLocation: () => ({ pathname: '/apps/cloud_control/page/welcome', search: '', hash: '', state: null, key: 't' }), diff --git a/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx b/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx index db55b3e3ef6..e4679f28ec9 100644 --- a/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx +++ b/ui/packages/app-shell/src/layout/__tests__/SidebarItemMenu.designOpensStudio-12520.test.tsx @@ -32,7 +32,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, cleanup, render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { NavigationItem } from '@object-ui/types'; vi.mock('@object-ui/i18n', async (importOriginal) => ({ diff --git a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx index 705f0c677cc..b52e5996aad 100644 --- a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx +++ b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-4421.render.test.tsx @@ -56,7 +56,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { ComponentRegistry } from '@object-ui/core'; import { ActionProvider, RecordContextProvider } from '@object-ui/react'; -import { MePermissionsProvider, usePermissions, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, usePermissions, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ExpressionProvider } from '../ExpressionProvider'; // Module-scope side-effect imports: the two registry renderers must be // registered before the first render (AGENTS.md §测试纪律 — import phase, not a diff --git a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx index 6bd44171a2e..17e33a4b3e5 100644 --- a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx +++ b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-disabledLegs-11242.render.test.tsx @@ -67,7 +67,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react'; import { ComponentRegistry, type ActionResult } from '@object-ui/core'; import { ActionProvider, RecordContextProvider, useAction } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ExpressionProvider } from '../ExpressionProvider'; // Module-scope side-effect imports: the registry renderers must be registered // before the first render (AGENTS.md §测试纪律 — import phase, not a hook). diff --git a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx index b2e81ebf8cb..2fb31330669 100644 --- a/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx +++ b/ui/packages/app-shell/src/providers/__tests__/currentUserCan-failClosed-11212.render.test.tsx @@ -62,7 +62,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react'; import { ComponentRegistry } from '@object-ui/core'; import { ActionProvider, RecordContextProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ExpressionProvider } from '../ExpressionProvider'; // Module-scope side-effect imports: the registry renderers must be registered // before the first render (AGENTS.md §测试纪律 — import phase, not a hook). diff --git a/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx b/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx index 81ae4e7714d..039a0e3ce64 100644 --- a/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx +++ b/ui/packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx @@ -34,7 +34,7 @@ import * as React from 'react'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, cleanup, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { AdapterCtx } from '@object-ui/react'; import { createI18n, en, I18nProvider } from '@object-ui/i18n'; diff --git a/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx b/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx index 2238dfe2591..15402eb126f 100644 --- a/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx +++ b/ui/packages/app-shell/src/views/RecordFormPage.hasCapability-13083.test.tsx @@ -37,7 +37,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // The reporter's dedupe `Set` is module state: reset it so every arm's // "nothing reported" is a reading, not a leftover from an earlier arm. import { __resetVisibilityPredicateWarnings } from '../../../react/src/utils/visibilityDiagnostic'; diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx index a7b1fa64bc1..16e00419cd7 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex-13020.test.tsx @@ -31,7 +31,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, cleanup } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx index ea52d389db3..e532ee2d99a 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.groupsViews-27526.test.tsx @@ -35,7 +35,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, cleanup, fireEvent, within } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx index 27b8ffc2626..ec56dcc7482 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navContributions-13273.test.tsx @@ -29,7 +29,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { MetadataProvider } from '../../providers/MetadataProvider'; import { ExpressionProvider } from '../../providers/ExpressionProvider'; diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx index a1542317cdc..7fc25256939 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.navGates-26892.test.tsx @@ -48,7 +48,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, cleanup, act } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { SchemaRendererContext, type ApiFetch } from '@object-ui/react'; vi.mock('@object-ui/auth', async (importOriginal) => ({ diff --git a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx index 63ce584c281..2f55dad13b8 100644 --- a/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/AppReportIndex.otherApps-27356.test.tsx @@ -22,7 +22,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, cleanup, within } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createI18n, I18nProvider } from '@object-ui/i18n'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx index e729cd9132c..6b886fbc6db 100644 --- a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioFollowsPackagePosture-12615.test.tsx @@ -22,7 +22,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, cleanup, render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateSpy = vi.hoisted(() => vi.fn()); vi.mock('react-router-dom', () => ({ diff --git a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx index fe56a121449..cd63293389b 100644 --- a/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/PageView.editInStudioOpensStudio-12540.test.tsx @@ -29,7 +29,7 @@ import '@testing-library/jest-dom/vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, cleanup, render, screen, fireEvent } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; const navigateSpy = vi.hoisted(() => vi.fn()); vi.mock('react-router-dom', () => ({ diff --git a/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx b/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx index c1912134aae..4bb9ce62b65 100644 --- a/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx +++ b/ui/packages/app-shell/src/views/__tests__/RecordAttachmentsPanel.affordanceGrant-12047.test.tsx @@ -34,7 +34,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, within, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; /** The fake presigned adapter: `upload` resolving means a `sys_file` was committed. */ const adapterUpload = vi.fn(); diff --git a/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx b/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx index eb4edbff2eb..f334e484528 100644 --- a/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx +++ b/ui/packages/fields/src/widgets/RecordPickerDialog.fieldReadRule-10594.test.tsx @@ -48,7 +48,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, waitFor, act, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { SchemaRendererContext } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordPickerDialog } from './RecordPickerDialog'; import { getCellRenderer } from '../index'; diff --git a/ui/packages/permissions/src/__tests__/RecordFieldAccessScope-26594.test.tsx b/ui/packages/permissions/src/__tests__/RecordFieldAccessScope-26594.test.tsx index 5c990b14dd6..aff59fdcacb 100644 --- a/ui/packages/permissions/src/__tests__/RecordFieldAccessScope-26594.test.tsx +++ b/ui/packages/permissions/src/__tests__/RecordFieldAccessScope-26594.test.tsx @@ -24,7 +24,7 @@ import { describe, it, expect, vi } from 'vitest'; import { render, waitFor, act } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; import { RecordFieldAccessScope } from '../RecordFieldAccessScope'; import { usePermissions } from '../usePermissions'; diff --git a/ui/packages/permissions/src/__tests__/ServerReadScope-13324.test.tsx b/ui/packages/permissions/src/__tests__/ServerReadScope-13324.test.tsx index 9b98db81954..45ce1ec4571 100644 --- a/ui/packages/permissions/src/__tests__/ServerReadScope-13324.test.tsx +++ b/ui/packages/permissions/src/__tests__/ServerReadScope-13324.test.tsx @@ -23,7 +23,7 @@ import { describe, it, expect } from 'vitest'; import { render } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; import { ServerReadScope } from '../ServerReadScope'; import { usePermissions } from '../usePermissions'; diff --git a/ui/packages/permissions/src/__tests__/effectiveObjects-4421.test.tsx b/ui/packages/permissions/src/__tests__/effectiveObjects-4421.test.tsx index 1ae9bef6fed..d8d406cc245 100644 --- a/ui/packages/permissions/src/__tests__/effectiveObjects-4421.test.tsx +++ b/ui/packages/permissions/src/__tests__/effectiveObjects-4421.test.tsx @@ -20,7 +20,7 @@ import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '../MePermissionsProvider'; import { PermissionProvider } from '../PermissionProvider'; import { usePermissions } from '../usePermissions'; diff --git a/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx b/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx index 7a11064079b..10859b1a5f7 100644 --- a/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx +++ b/ui/packages/plugin-detail/src/__tests__/RecordRelatedListRenderer.refusalAudience-11768.test.tsx @@ -52,7 +52,7 @@ import { type RelatedRecordActionsValue, type RelatedRowActionDef, } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // Registers `record:related_list`, the block under test. import '../index'; diff --git a/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx b/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx index 841d8cfc883..6ccdbecb8f1 100644 --- a/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx +++ b/ui/packages/plugin-detail/src/queueClaim-25769.test.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { recordSitsInQueue, queueClaimAdmitted, diff --git a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx index a419ee16a41..1ea1c00ff41 100644 --- a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx +++ b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.recordFieldAccess-27146.test.tsx @@ -34,7 +34,7 @@ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vite import { render, screen, waitFor, act, cleanup, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { HighlightFieldsProvider, InlineEditProvider, RecordContextProvider, useInlineEdit } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordDetailsRenderer } from '../record-details'; import { RecordHighlightsRenderer } from '../record-highlights'; diff --git a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx index 10da7365037..87e07d4ba6e 100644 --- a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx +++ b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.requiredPermissions-gate.test.tsx @@ -47,7 +47,7 @@ import { MePermissionsProvider, PermissionProvider, usePermissions, - type MePermissionsResponse, type MePermissionsAuthenticatedResponse, + type MePermissionsAuthenticatedResponse, } from '@object-ui/permissions'; import { RecordDetailsRenderer } from '../record-details'; import { RecordHighlightsRenderer } from '../record-highlights'; diff --git a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.rowWritable-27138.test.tsx b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.rowWritable-27138.test.tsx index b0a20864c47..62a5710629c 100644 --- a/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.rowWritable-27138.test.tsx +++ b/ui/packages/plugin-detail/src/renderers/__tests__/record-blocks.rowWritable-27138.test.tsx @@ -22,7 +22,7 @@ import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vite import { render, screen, waitFor, act, cleanup, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { HighlightFieldsProvider, InlineEditProvider, RecordContextProvider, useInlineEdit } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordDetailsRenderer } from '../record-details'; import { RecordHighlightsRenderer } from '../record-highlights'; diff --git a/ui/packages/plugin-detail/src/renderers/__tests__/record-quick-actions.requiredPermissions-gate.test.tsx b/ui/packages/plugin-detail/src/renderers/__tests__/record-quick-actions.requiredPermissions-gate.test.tsx index 4512d50fce2..f8d2242a367 100644 --- a/ui/packages/plugin-detail/src/renderers/__tests__/record-quick-actions.requiredPermissions-gate.test.tsx +++ b/ui/packages/plugin-detail/src/renderers/__tests__/record-quick-actions.requiredPermissions-gate.test.tsx @@ -55,7 +55,7 @@ import '@testing-library/jest-dom'; import { ComponentRegistry } from '@object-ui/core'; import { MetadataCtx, RecordContextProvider } from '@object-ui/react'; import type { MetadataContextValue } from '@object-ui/react'; -import { MePermissionsProvider, PermissionProvider, usePermissions, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, PermissionProvider, usePermissions, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { RecordQuickActionsProps } from '@objectstack/spec/ui'; import { RecordQuickActionsRenderer } from '../record-quick-actions'; // Registers `record:quick_actions` and its three sibling record blocks, whose diff --git a/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx b/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx index 95db8836f4e..60eed3ee2d4 100644 --- a/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx +++ b/ui/packages/plugin-detail/src/useRecordEditable.inflightShare-11699.test.tsx @@ -32,7 +32,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordEditable, __clearRecordEditableCache } from './useRecordEditable'; interface HeldAsk { diff --git a/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx b/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx index 37f39e991f9..52a19beffb7 100644 --- a/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx +++ b/ui/packages/plugin-detail/src/useRecordEditable.invalidation.test.tsx @@ -37,7 +37,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordEditable, __clearRecordEditableCache } from './useRecordEditable'; /** A `/me/permissions` payload for one principal; the object grant is constant. */ diff --git a/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx b/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx index a2eecd7340f..2b60e8d1eb9 100644 --- a/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx +++ b/ui/packages/plugin-detail/src/useRecordEditable.principalScope.test.tsx @@ -42,7 +42,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordEditable, __clearRecordEditableCache } from './useRecordEditable'; /** A `/me/permissions` payload for one principal. The object bits are constant diff --git a/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx b/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx index 1c11d1016ee..508f9211073 100644 --- a/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx +++ b/ui/packages/plugin-grid/src/__tests__/ObjectGrid.emptyStateAccess-25898.test.tsx @@ -26,7 +26,7 @@ import { describe, it, expect, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import '@testing-library/jest-dom'; import { FilterScopeProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; // Registers `object-grid`, the block under test. import { ObjectGridRenderer } from '../index'; diff --git a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx index 0136f0cf224..5858e9ae761 100644 --- a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx +++ b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.afterAction-13341.test.tsx @@ -39,7 +39,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordCrudVerdicts, __clearRecordCrudVerdictCache, diff --git a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx index f3883a18454..4738276f199 100644 --- a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx +++ b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.invalidation.test.tsx @@ -29,7 +29,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; import { notifyDataChanged } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordCrudVerdicts, __clearRecordCrudVerdictCache, diff --git a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx index 8f4714c7dc9..4a17b11ebfa 100644 --- a/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx +++ b/ui/packages/plugin-grid/src/hooks/useRecordCrudVerdicts.principalScope.test.tsx @@ -33,7 +33,7 @@ import * as React from 'react'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook, waitFor } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { useRecordCrudVerdicts, __clearRecordCrudVerdictCache } from './useRecordCrudVerdicts'; const OBJECT = 'note'; diff --git a/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx b/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx index 2cb140b35f5..b2981fe45cd 100644 --- a/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListView.emptyStateAccess-25898.test.tsx @@ -38,7 +38,7 @@ import { render, waitFor, cleanup } from '@testing-library/react'; import { ListView } from '../ListView'; import { filterNamesCurrentUser } from '../ListEmptyState'; import { SchemaRendererProvider, FilterScopeProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { ListViewSchema } from '@object-ui/types'; afterEach(cleanup); diff --git a/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx b/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx index df42ea08d6a..9780dbfd2b7 100644 --- a/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ListViewBlock.inertKeys-13074.test.tsx @@ -42,7 +42,7 @@ import { type RelatedRecordActionsValue, type RelatedRecordHandlers, } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import { ComponentPropsMap } from '@objectstack/spec/ui'; // Registers the REAL `list-view`. import '../index'; diff --git a/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx b/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx index 0dd6bd18390..f921cc3b017 100644 --- a/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx +++ b/ui/packages/plugin-list/src/__tests__/ObjectGallery.emptyStateAccess-25898.test.tsx @@ -25,7 +25,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, cleanup, waitFor } from '@testing-library/react'; import { I18nProvider } from '@object-ui/i18n'; import { SchemaRendererProvider, FilterScopeProvider } from '@object-ui/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { DataSource, ObjectGallerySchema } from '@object-ui/types'; import { ObjectGallery } from '../ObjectGallery'; diff --git a/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx b/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx index d1dd48d3381..02237b93e51 100644 --- a/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx +++ b/ui/packages/plugin-view/src/__tests__/ObjectView.createGrant-12082.test.tsx @@ -23,7 +23,7 @@ */ import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; -import { MePermissionsProvider, type MePermissionsResponse, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; +import { MePermissionsProvider, type MePermissionsAuthenticatedResponse } from '@object-ui/permissions'; import type { ObjectViewSchema, DataSource } from '@object-ui/types'; vi.mock('@object-ui/react', async (importOriginal) => {