@objectstack/core 16.0.0 → 16.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1781,6 +1781,56 @@ interface ResolveAuthzInput {
1781
1781
  * never throws. Anonymous requests yield `{ positions: [], permissions: [], ... }`.
1782
1782
  */
1783
1783
  declare function resolveAuthzContext(input: ResolveAuthzInput): Promise<ResolvedAuthzContext>;
1784
+ /** The authorization grants a KNOWN user holds — a subset of {@link ResolvedAuthzContext}. */
1785
+ interface UserAuthzGrants {
1786
+ positions: string[];
1787
+ permissions: string[];
1788
+ systemPermissions: string[];
1789
+ /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */
1790
+ org_user_ids: string[];
1791
+ tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;
1792
+ posture?: AuthzPosture;
1793
+ /** The user's unique email (`sys_user`), for `current_user.email` owner RLS. */
1794
+ email?: string;
1795
+ }
1796
+ interface ResolveUserAuthzGrantsOptions {
1797
+ /** Active org/tenant id — scopes org-bound grants (a null-org row is global). */
1798
+ tenantId?: string;
1799
+ /** Clock injection for grant validity windows (tests). */
1800
+ nowMs?: number;
1801
+ /**
1802
+ * Permission names the CALLER already resolved (e.g. API-key scopes) to seed
1803
+ * `permissions` BEFORE permission-set names are appended, so a mixed
1804
+ * API-key+session principal keeps every scope and the ordering is preserved.
1805
+ * Copied, never mutated.
1806
+ */
1807
+ seedPermissions?: string[];
1808
+ /** A caller-supplied email (e.g. from the session) that wins over the `sys_user` read. */
1809
+ seedEmail?: string;
1810
+ }
1811
+ /**
1812
+ * resolveUserAuthzGrants — the userId-driven core of {@link resolveAuthzContext}.
1813
+ *
1814
+ * Given a KNOWN user id, aggregate the authorization grants that user holds:
1815
+ * org-admin positions (`sys_member`), platform-RBAC positions
1816
+ * (`sys_user_position`), user- and position-bound permission sets
1817
+ * (`sys_user_permission_set` / `sys_position_permission_set` →
1818
+ * `sys_permission_set`), the derived `platform_admin` built-in + posture rung,
1819
+ * fellow-org peers for identity-table RLS, and the env-side `ai_seat`.
1820
+ *
1821
+ * Factored out of `resolveAuthzContext` so a surface that already knows WHO the
1822
+ * principal is — with no HTTP request to resolve it from — can build the SAME
1823
+ * envelope through the ONE resolver, instead of re-reading `sys_member` /
1824
+ * `sys_user_position` / `sys_*_permission_set` itself. The motivating consumer
1825
+ * is a `runAs:'user'` automation run resolving the triggering user's grants
1826
+ * (#3356): the record-change hook session carries only a `userId`, so the
1827
+ * automation engine calls this to run the flow's data ops exactly as that user
1828
+ * — not the bare member/everyone fallback the missing grants used to leave it.
1829
+ *
1830
+ * Fail-closed like its parent: every read is defensive, a missing engine/table
1831
+ * yields an empty-but-valid envelope, and it never throws.
1832
+ */
1833
+ declare function resolveUserAuthzGrants(ql: any, userId: string, opts?: ResolveUserAuthzGrantsOptions): Promise<UserAuthzGrants>;
1784
1834
  interface ResolveLocalizationInput {
1785
1835
  ql: any;
1786
1836
  /** Settings service exposing `get(namespace, key, { tenantId, userId })`. */
@@ -2637,4 +2687,4 @@ declare class NamespaceResolver {
2637
2687
  private suggestAlternative;
2638
2688
  }
2639
2689
 
2640
- export { ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, type AnonymousDenyInput, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, postureVisibleRows, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, shouldDenyAnonymous, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, zonedDateStartToUtcMs };
2690
+ export { ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, type AnonymousDenyInput, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolveUserAuthzGrantsOptions, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type UserAuthzGrants, type VersionCompatibility, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, postureVisibleRows, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, resolveUserAuthzGrants, safeExit, shouldDenyAnonymous, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, zonedDateStartToUtcMs };
package/dist/index.d.ts CHANGED
@@ -1781,6 +1781,56 @@ interface ResolveAuthzInput {
1781
1781
  * never throws. Anonymous requests yield `{ positions: [], permissions: [], ... }`.
1782
1782
  */
1783
1783
  declare function resolveAuthzContext(input: ResolveAuthzInput): Promise<ResolvedAuthzContext>;
1784
+ /** The authorization grants a KNOWN user holds — a subset of {@link ResolvedAuthzContext}. */
1785
+ interface UserAuthzGrants {
1786
+ positions: string[];
1787
+ permissions: string[];
1788
+ systemPermissions: string[];
1789
+ /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */
1790
+ org_user_ids: string[];
1791
+ tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;
1792
+ posture?: AuthzPosture;
1793
+ /** The user's unique email (`sys_user`), for `current_user.email` owner RLS. */
1794
+ email?: string;
1795
+ }
1796
+ interface ResolveUserAuthzGrantsOptions {
1797
+ /** Active org/tenant id — scopes org-bound grants (a null-org row is global). */
1798
+ tenantId?: string;
1799
+ /** Clock injection for grant validity windows (tests). */
1800
+ nowMs?: number;
1801
+ /**
1802
+ * Permission names the CALLER already resolved (e.g. API-key scopes) to seed
1803
+ * `permissions` BEFORE permission-set names are appended, so a mixed
1804
+ * API-key+session principal keeps every scope and the ordering is preserved.
1805
+ * Copied, never mutated.
1806
+ */
1807
+ seedPermissions?: string[];
1808
+ /** A caller-supplied email (e.g. from the session) that wins over the `sys_user` read. */
1809
+ seedEmail?: string;
1810
+ }
1811
+ /**
1812
+ * resolveUserAuthzGrants — the userId-driven core of {@link resolveAuthzContext}.
1813
+ *
1814
+ * Given a KNOWN user id, aggregate the authorization grants that user holds:
1815
+ * org-admin positions (`sys_member`), platform-RBAC positions
1816
+ * (`sys_user_position`), user- and position-bound permission sets
1817
+ * (`sys_user_permission_set` / `sys_position_permission_set` →
1818
+ * `sys_permission_set`), the derived `platform_admin` built-in + posture rung,
1819
+ * fellow-org peers for identity-table RLS, and the env-side `ai_seat`.
1820
+ *
1821
+ * Factored out of `resolveAuthzContext` so a surface that already knows WHO the
1822
+ * principal is — with no HTTP request to resolve it from — can build the SAME
1823
+ * envelope through the ONE resolver, instead of re-reading `sys_member` /
1824
+ * `sys_user_position` / `sys_*_permission_set` itself. The motivating consumer
1825
+ * is a `runAs:'user'` automation run resolving the triggering user's grants
1826
+ * (#3356): the record-change hook session carries only a `userId`, so the
1827
+ * automation engine calls this to run the flow's data ops exactly as that user
1828
+ * — not the bare member/everyone fallback the missing grants used to leave it.
1829
+ *
1830
+ * Fail-closed like its parent: every read is defensive, a missing engine/table
1831
+ * yields an empty-but-valid envelope, and it never throws.
1832
+ */
1833
+ declare function resolveUserAuthzGrants(ql: any, userId: string, opts?: ResolveUserAuthzGrantsOptions): Promise<UserAuthzGrants>;
1784
1834
  interface ResolveLocalizationInput {
1785
1835
  ql: any;
1786
1836
  /** Settings service exposing `get(namespace, key, { tenantId, userId })`. */
@@ -2637,4 +2687,4 @@ declare class NamespaceResolver {
2637
2687
  private suggestAlternative;
2638
2688
  }
2639
2689
 
2640
- export { ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, type AnonymousDenyInput, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, postureVisibleRows, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, safeExit, shouldDenyAnonymous, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, zonedDateStartToUtcMs };
2690
+ export { ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, type AnonymousDenyInput, type ApiKeyPrincipal, ApiRegistry, type ApiRegistryPluginConfig, type AuthGate, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolveUserAuthzGrantsOptions, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type UserAuthzGrants, type VersionCompatibility, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, evaluateAuthGate, extractApiKey, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, parseScopes, parseSignature, postureVisibleRows, readAuthoredTranslationLayer, resolveApiKeyPrincipal, resolveAuthzContext, resolveLocale, resolveLocalizationContext, resolveUserAuthzGrants, safeExit, shouldDenyAnonymous, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, zonedDateStartToUtcMs };
package/dist/index.js CHANGED
@@ -4346,6 +4346,31 @@ async function resolveAuthzContext(input) {
4346
4346
  ctx.userId = userId;
4347
4347
  if (tenantId) ctx.tenantId = tenantId;
4348
4348
  if (!ql || typeof ql.find !== "function") return ctx;
4349
+ const grants = await resolveUserAuthzGrants(ql, userId, {
4350
+ tenantId,
4351
+ nowMs: input.nowMs,
4352
+ seedPermissions: ctx.permissions,
4353
+ seedEmail: ctx.email
4354
+ });
4355
+ ctx.positions = grants.positions;
4356
+ ctx.permissions = grants.permissions;
4357
+ ctx.systemPermissions = grants.systemPermissions;
4358
+ ctx.org_user_ids = grants.org_user_ids;
4359
+ if (grants.tabPermissions) ctx.tabPermissions = grants.tabPermissions;
4360
+ if (grants.posture) ctx.posture = grants.posture;
4361
+ if (grants.email && !ctx.email) ctx.email = grants.email;
4362
+ return ctx;
4363
+ }
4364
+ async function resolveUserAuthzGrants(ql, userId, opts = {}) {
4365
+ const { tenantId } = opts;
4366
+ const grants = {
4367
+ positions: [],
4368
+ permissions: Array.isArray(opts.seedPermissions) ? [...opts.seedPermissions] : [],
4369
+ systemPermissions: [],
4370
+ org_user_ids: [userId]
4371
+ };
4372
+ if (opts.seedEmail) grants.email = opts.seedEmail;
4373
+ if (!ql || typeof ql.find !== "function") return grants;
4349
4374
  let userRowLoaded = false;
4350
4375
  let userRow;
4351
4376
  const getUserRow = async () => {
@@ -4356,9 +4381,9 @@ async function resolveAuthzContext(input) {
4356
4381
  }
4357
4382
  return userRow;
4358
4383
  };
4359
- if (!ctx.email) {
4384
+ if (!grants.email) {
4360
4385
  const u = await getUserRow();
4361
- if (u?.email) ctx.email = String(u.email);
4386
+ if (u?.email) grants.email = String(u.email);
4362
4387
  }
4363
4388
  const memberWhere = tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId };
4364
4389
  const members = await tryFind(ql, "sys_member", memberWhere, 50);
@@ -4366,18 +4391,18 @@ async function resolveAuthzContext(input) {
4366
4391
  if (m.role && typeof m.role === "string") {
4367
4392
  for (const raw of m.role.split(",").map((s) => s.trim()).filter(Boolean)) {
4368
4393
  const r = mapMembershipRole(raw);
4369
- if (!ctx.positions.includes(r)) ctx.positions.push(r);
4394
+ if (!grants.positions.includes(r)) grants.positions.push(r);
4370
4395
  }
4371
4396
  }
4372
4397
  }
4373
- const nowMs = input.nowMs ?? Date.now();
4398
+ const nowMs = opts.nowMs ?? Date.now();
4374
4399
  const userPositionRows = await tryFind(ql, "sys_user_position", { user_id: userId }, 200);
4375
4400
  for (const ur of userPositionRows) {
4376
4401
  const org = ur.organization_id ?? null;
4377
4402
  if (org && tenantId && org !== tenantId) continue;
4378
4403
  if (!isGrantActive(ur, nowMs)) continue;
4379
4404
  const r = ur.position;
4380
- if (typeof r === "string" && r && !ctx.positions.includes(r)) ctx.positions.push(r);
4405
+ if (typeof r === "string" && r && !grants.positions.includes(r)) grants.positions.push(r);
4381
4406
  }
4382
4407
  if (tenantId) {
4383
4408
  const orgMembers = await tryFind(ql, "sys_member", { organization_id: tenantId }, 1e3);
@@ -4385,9 +4410,7 @@ async function resolveAuthzContext(input) {
4385
4410
  orgMembers.map((m) => m.user_id ?? m.userId).filter((v) => typeof v === "string" && v.length > 0)
4386
4411
  );
4387
4412
  ids.add(userId);
4388
- ctx.org_user_ids = Array.from(ids);
4389
- } else {
4390
- ctx.org_user_ids = [userId];
4413
+ grants.org_user_ids = Array.from(ids);
4391
4414
  }
4392
4415
  const upsRowsAll = await tryFind(ql, "sys_user_permission_set", { user_id: userId }, 100);
4393
4416
  const upsRows = upsRowsAll.filter((r) => isGrantActive(r, nowMs));
@@ -4401,9 +4424,9 @@ async function resolveAuthzContext(input) {
4401
4424
  upsRows.filter((r) => (r.organization_id ?? r.organizationId ?? null) === null).map((r) => r.permission_set_id ?? r.permissionSetId).filter(Boolean)
4402
4425
  );
4403
4426
  let hasPlatformAdminGrant = false;
4404
- if (!ctx.positions.includes("everyone")) ctx.positions.push("everyone");
4405
- if (ctx.positions.length > 0) {
4406
- const positionRows = await tryFind(ql, "sys_position", { name: { $in: ctx.positions } }, 100);
4427
+ if (!grants.positions.includes("everyone")) grants.positions.push("everyone");
4428
+ if (grants.positions.length > 0) {
4429
+ const positionRows = await tryFind(ql, "sys_position", { name: { $in: grants.positions } }, 100);
4407
4430
  const positionIds = positionRows.map((r) => r.id).filter(Boolean);
4408
4431
  if (positionIds.length > 0) {
4409
4432
  const rpsRows = await tryFind(ql, "sys_position_permission_set", { position_id: { $in: positionIds } }, 500);
@@ -4418,12 +4441,12 @@ async function resolveAuthzContext(input) {
4418
4441
  const tabRank = { hidden: 0, default_off: 1, default_on: 2, visible: 3 };
4419
4442
  const mergedTabs = {};
4420
4443
  for (const ps of psRows) {
4421
- if (ps.name && !ctx.permissions.includes(ps.name)) ctx.permissions.push(ps.name);
4444
+ if (ps.name && !grants.permissions.includes(ps.name)) grants.permissions.push(ps.name);
4422
4445
  if (ps.name === ADMIN_FULL_ACCESS && unscopedUserPsIds.has(ps.id)) hasPlatformAdminGrant = true;
4423
4446
  const sysPerms = typeof ps.system_permissions === "string" ? safeJsonParse2(ps.system_permissions, []) : ps.system_permissions ?? ps.systemPermissions;
4424
4447
  if (Array.isArray(sysPerms)) {
4425
4448
  for (const p of sysPerms) {
4426
- if (typeof p === "string" && !ctx.systemPermissions.includes(p)) ctx.systemPermissions.push(p);
4449
+ if (typeof p === "string" && !grants.systemPermissions.includes(p)) grants.systemPermissions.push(p);
4427
4450
  }
4428
4451
  }
4429
4452
  const tabs = typeof ps.tab_permissions === "string" ? safeJsonParse2(ps.tab_permissions, {}) : ps.tab_permissions ?? ps.tabPermissions;
@@ -4437,20 +4460,20 @@ async function resolveAuthzContext(input) {
4437
4460
  }
4438
4461
  }
4439
4462
  }
4440
- if (Object.keys(mergedTabs).length > 0) ctx.tabPermissions = mergedTabs;
4463
+ if (Object.keys(mergedTabs).length > 0) grants.tabPermissions = mergedTabs;
4441
4464
  }
4442
- if (hasPlatformAdminGrant && !ctx.positions.includes(BUILTIN_IDENTITY_PLATFORM_ADMIN)) {
4443
- ctx.positions.unshift(BUILTIN_IDENTITY_PLATFORM_ADMIN);
4465
+ if (hasPlatformAdminGrant && !grants.positions.includes(BUILTIN_IDENTITY_PLATFORM_ADMIN)) {
4466
+ grants.positions.unshift(BUILTIN_IDENTITY_PLATFORM_ADMIN);
4444
4467
  }
4445
- ctx.posture = derivePosture({
4468
+ grants.posture = derivePosture({
4446
4469
  isPlatformAdmin: hasPlatformAdminGrant,
4447
- isTenantAdmin: ctx.permissions.includes(ORGANIZATION_ADMIN)
4470
+ isTenantAdmin: grants.permissions.includes(ORGANIZATION_ADMIN)
4448
4471
  });
4449
- if (!ctx.permissions.includes("ai_seat")) {
4472
+ if (!grants.permissions.includes("ai_seat")) {
4450
4473
  const aiAccess = (await getUserRow())?.ai_access;
4451
- if (aiAccess === true || aiAccess === 1 || aiAccess === "1") ctx.permissions.push("ai_seat");
4474
+ if (aiAccess === true || aiAccess === 1 || aiAccess === "1") grants.permissions.push("ai_seat");
4452
4475
  }
4453
- return ctx;
4476
+ return grants;
4454
4477
  }
4455
4478
  function isValidTimeZone(tz) {
4456
4479
  try {
@@ -5774,6 +5797,7 @@ export {
5774
5797
  resolveAuthzContext,
5775
5798
  resolveLocale,
5776
5799
  resolveLocalizationContext,
5800
+ resolveUserAuthzGrants,
5777
5801
  safeExit,
5778
5802
  shouldDenyAnonymous,
5779
5803
  signPayload,