@happyvertical/smrt-users 0.55.24 → 0.56.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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as PermissionResolver, $t as GroupCollection, A as withSessionPermissionContext, At as recordTenantHierarchyChanges, B as resolveOidcProviderConfig, Bt as RoleCollection, C as OperationPermissionError, Ct as TenantCollection, D as getCurrentSessionPermissionContext, Dt as applyTenantHierarchyUpdates, E as hasOperationPermission, Et as TenantHierarchyError, F as OidcLoginError, Ft as deriveOperationPermissionSlug, G as DeviceCredentialForbiddenError, Gt as UsersPasswordCredential, H as DEFAULT_DEVICE_PERSON_IDLE_SECONDS, Ht as UsersPinCredential, I as OidcLoginService, It as normalizeOperationPermissionAction, J as PIN_LOGIN_KIND, Jt as MembershipCollection, K as DeviceCredentialPolicyError, Kt as MembershipOverrideCollection, L as decodeOidcTransaction, Lt as registerPermissionDefinitions, M as MobileAuthService, Mt as RolePermissionCollection, N as readMobileBearerToken, Nt as PermissionCatalogService, O as getRequestScopedDatabase, Ot as assertTenantTableName, P as validateMobileRedirectUri, Pt as deriveOperationPermissionCollectionName, Q as SessionService, Qt as GroupMember, R as encodeOidcTransaction, Rt as syncPermissionCatalog, S as outOfBandProofSessionData, St as TenantPermissionOverride, T as checkOperationPermission, Tt as MAX_TENANT_HIERARCHY_DEPTH, U as DEFAULT_PIN_MANAGE_PERMISSION, Ut as PermissionCollection, Vt as UsersPinCredentialCollection, W as DeviceCredentialError, Wt as UsersPasswordCredentialCollection, X as PinVerifier, Xt as GroupRole, Y as PIN_PERMISSION_DEFINITIONS, Yt as GroupRoleCollection, Z as ensurePinPermissionsRegistered, Zt as GroupMemberCollection, _ as PasswordCredentialError, _n as Membership, _t as UserCollection, a as TerminalAuthError, an as UsersCliAuthRequestCollection, at as DEFAULT_SOURCE_LOCKOUT_AUDIT_SECONDS, b as PasswordPolicyError, bt as backfillUserEmailKeys, c as DEFAULT_PASSWORD_DENYLIST, cn as User, ct as LoginAttemptLimiter, d as DEFAULT_PASSWORD_MIN_LENGTH, dn as DEFAULT_SESSION_TTL, dt as ANCESTOR_READ_ACTION, en as Group, et as DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, f as DEFAULT_PASSWORD_RECOVERY_MAX_AGE_SECONDS, fn as SESSION_DATA_KEYS, ft as DEFAULT_ANCESTOR_READ_MAX_DEPTH, g as PASSWORD_RECOVERY_MAX_AGE_CEILING_SECONDS, gn as Permission, gt as OidcProvisioningError, h as PASSWORD_PERMISSION_DEFINITIONS, hn as Role, ht as normalizeAncestorReadPolicy, i as TERMINAL_APPROVE_LOGIN_KIND, in as UsersLoginAttempt, it as DEFAULT_LOGIN_MAX_ATTEMPTS, j as MobileAuthError, jt as DEFAULT_ROLE_PERMISSION_PATTERNS, k as withPrincipalPermissionContext, kt as planTenantHierarchy, l as DEFAULT_PASSWORD_MANAGE_PERMISSION, ln as isValidEmail, lt as LoginRateLimitError, m as PASSWORD_MAX_LENGTH_CEILING, mn as generateSessionId, mt as isAncestorReadableSlug, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nn as UsersLoginAuditEvent, nt as DEFAULT_LOGIN_LOCKOUT_FACTOR, o as TerminalAuthRateLimitError, on as SessionCollection, ot as DurableLoginAuditSink, p as PASSWORD_LOGIN_KIND, pn as Session, pt as getConfiguredAncestorReadPolicy, q as DeviceCredentialService, qt as MembershipOverride, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rn as UsersLoginAttemptCollection, rt as DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, s as TerminalAuthService, sn as UsersCliAuthRequest, st as InvalidCredentialsError, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tn as UsersLoginAuditEventCollection, tt as DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, u as DEFAULT_PASSWORD_MAX_LENGTH, un as normalizeEmail, ut as withoutListBounds, v as PasswordCredentialForbiddenError, w as assertOperationPermission, wt as Tenant, x as ensurePasswordPermissionsRegistered, xt as TenantPermissionOverrideCollection, y as PasswordCredentialService, yt as UserEmailKeyBackfillError, z as getUsersOidcConfig, zt as RolePermission } from "./chunks/TerminalAuthService-B0icE2mR.js";
1
+ import { $ as PermissionResolver, $t as GroupCollection, A as withSessionPermissionContext, At as recordTenantHierarchyChanges, B as resolveOidcProviderConfig, Bt as RoleCollection, C as OperationPermissionError, Ct as TenantCollection, D as getCurrentSessionPermissionContext, Dt as applyTenantHierarchyUpdates, E as hasOperationPermission, Et as TenantHierarchyError, F as OidcLoginError, Ft as deriveOperationPermissionSlug, G as DeviceCredentialForbiddenError, Gt as UsersPasswordCredential, H as DEFAULT_DEVICE_PERSON_IDLE_SECONDS, Ht as UsersPinCredential, I as OidcLoginService, It as normalizeOperationPermissionAction, J as PIN_LOGIN_KIND, Jt as MembershipCollection, K as DeviceCredentialPolicyError, Kt as MembershipOverrideCollection, L as decodeOidcTransaction, Lt as registerPermissionDefinitions, M as MobileAuthService, Mt as RolePermissionCollection, N as readMobileBearerToken, Nt as PermissionCatalogService, O as getRequestScopedDatabase, Ot as assertTenantTableName, P as validateMobileRedirectUri, Pt as deriveOperationPermissionCollectionName, Q as SessionService, Qt as GroupMember, R as encodeOidcTransaction, Rt as syncPermissionCatalog, S as outOfBandProofSessionData, St as TenantPermissionOverride, T as checkOperationPermission, Tt as MAX_TENANT_HIERARCHY_DEPTH, U as DEFAULT_PIN_MANAGE_PERMISSION, Ut as PermissionCollection, Vt as UsersPinCredentialCollection, W as DeviceCredentialError, Wt as UsersPasswordCredentialCollection, X as PinVerifier, Xt as GroupRole, Y as PIN_PERMISSION_DEFINITIONS, Yt as GroupRoleCollection, Z as ensurePinPermissionsRegistered, Zt as GroupMemberCollection, _ as PasswordCredentialError, _n as Membership, _t as UserCollection, a as TerminalAuthError, an as UsersCliAuthRequestCollection, at as DEFAULT_SOURCE_LOCKOUT_AUDIT_SECONDS, b as PasswordPolicyError, bt as backfillUserEmailKeys, c as DEFAULT_PASSWORD_DENYLIST, cn as User, ct as LoginAttemptLimiter, d as DEFAULT_PASSWORD_MIN_LENGTH, dn as DEFAULT_SESSION_TTL, dt as ANCESTOR_READ_ACTION, en as Group, et as DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, f as DEFAULT_PASSWORD_RECOVERY_MAX_AGE_SECONDS, fn as SESSION_DATA_KEYS, ft as DEFAULT_ANCESTOR_READ_MAX_DEPTH, g as PASSWORD_RECOVERY_MAX_AGE_CEILING_SECONDS, gn as Permission, gt as OidcProvisioningError, h as PASSWORD_PERMISSION_DEFINITIONS, hn as Role, ht as normalizeAncestorReadPolicy, i as TERMINAL_APPROVE_LOGIN_KIND, in as UsersLoginAttempt, it as DEFAULT_LOGIN_MAX_ATTEMPTS, j as MobileAuthError, jt as DEFAULT_ROLE_PERMISSION_PATTERNS, k as withPrincipalPermissionContext, kt as planTenantHierarchy, l as DEFAULT_PASSWORD_MANAGE_PERMISSION, ln as isValidEmail, lt as LoginRateLimitError, m as PASSWORD_MAX_LENGTH_CEILING, mn as generateSessionId, mt as isAncestorReadableSlug, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nn as UsersLoginAuditEvent, nt as DEFAULT_LOGIN_LOCKOUT_FACTOR, o as TerminalAuthRateLimitError, on as SessionCollection, ot as DurableLoginAuditSink, p as PASSWORD_LOGIN_KIND, pn as Session, pt as getConfiguredAncestorReadPolicy, q as DeviceCredentialService, qt as MembershipOverride, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rn as UsersLoginAttemptCollection, rt as DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, s as TerminalAuthService, sn as UsersCliAuthRequest, st as InvalidCredentialsError, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tn as UsersLoginAuditEventCollection, tt as DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, u as DEFAULT_PASSWORD_MAX_LENGTH, un as normalizeEmail, ut as withoutListBounds, v as PasswordCredentialForbiddenError, w as assertOperationPermission, wt as Tenant, x as ensurePasswordPermissionsRegistered, xt as TenantPermissionOverrideCollection, y as PasswordCredentialService, yt as UserEmailKeyBackfillError, z as getUsersOidcConfig, zt as RolePermission } from "./chunks/TerminalAuthService-BQjH5_bB.js";
2
2
  import { a as MembershipStatus, c as TenantPermissionEffect, i as DEFAULT_TENANT_POLICY, l as TenantStatus, n as DEFAULT_ROLES, o as OverrideEffect, r as DEFAULT_ROLE_SLUGS, s as SessionStatus, t as AccessRequestStatus, u as UserStatus } from "./chunks/types-DFqeph0R.js";
3
3
  import { ObjectRegistry, SmrtCollection, SmrtObject, SmrtRecipe, bumpChangeFeed, ensureChangeFeedTable, field, findManifestEntryByQualifiedName, foreignKey, isEmbeddedDatabase, isUniqueViolationError, registerRetentionTask, smrt, unregisterRetentionTask, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
4
4
  import { PROFILE_EMAIL_KEY_BACKFILL_NAME, Person, ProfileCollection, ProfileTypeCollection, normalizeIdentityEmail } from "@happyvertical/smrt-profiles";
@@ -1948,7 +1948,7 @@ var MagicLinkService = class MagicLinkService {
1948
1948
  * The caller is responsible for emailing the token to the user.
1949
1949
  */
1950
1950
  async generate(email) {
1951
- const { SignJWT } = await import("./chunks/TerminalAuthService-B0icE2mR.js").then((n) => n.V);
1951
+ const { SignJWT } = await import("./chunks/TerminalAuthService-BQjH5_bB.js").then((n) => n.V);
1952
1952
  const key = await this.getSigningKey();
1953
1953
  const nonce = crypto.randomUUID();
1954
1954
  const normalizedEmail = normalizeEmail(email);
@@ -1977,7 +1977,7 @@ var MagicLinkService = class MagicLinkService {
1977
1977
  * @throws {MagicLinkError} If the token is invalid, expired, or already used
1978
1978
  */
1979
1979
  async verify(token) {
1980
- const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-B0icE2mR.js").then((n) => n.V);
1980
+ const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-BQjH5_bB.js").then((n) => n.V);
1981
1981
  const key = await this.getSigningKey();
1982
1982
  let payload;
1983
1983
  try {
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-users",
5
- "packageVersion": "0.55.24",
5
+ "packageVersion": "0.56.0",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-users:AccessRequestCollection": {
8
8
  "name": "accessrequestcollection",
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionCatalogService.d.ts","sourceRoot":"","sources":["../../src/services/PermissionCatalogService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PermissionCatalogService.d.ts","sourceRoot":"","sources":["../../src/services/PermissionCatalogService.ts"],"names":[],"mappings":"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"}
@@ -3,14 +3,14 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-users",
6
- "packageVersion": "0.55.24",
6
+ "packageVersion": "0.56.0",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "eb560dd6dbf66f7ea3b65a9bb1515ef7e6b667711a0946d18c689b6da1e0eaf4",
11
- "packageJson": "79a32250dd1b9d1bca08db77a8eb4c5aaf0800dadb82d86698743d53e07cf222",
10
+ "manifest": "c8fc0427cb898349fd4716a71c5e91d07230ba21fcb91f8b889ab88bf043d2a5",
11
+ "packageJson": "7e315019dcf37e84f1ad0aef50122f292495038f8410bedff95f549d30aa6935",
12
12
  "agents": "c4ab6a1aae24c0fb687d4d02ef25dcb5e6942ad7e8318e9d0dd8d03d5750b788",
13
- "moduleDoc:agents/permissions.md": "58629e6c33cd7610c5923443b3d88b8938c6ab38ff8c961749c45df8211b3b3f",
13
+ "moduleDoc:agents/permissions.md": "02b2f03f0e057721369d9c693a624a6fdb3df4935401531a4373ff18229a64be",
14
14
  "moduleDoc:agents/oidc-provisioning.md": "b73f3fdaf61136720132a614c7e14237b7bd35f7bf5d43eabb29f565485803b9",
15
15
  "moduleDoc:agents/mobile-auth.md": "25e85e83f43e3a46383a92fa809f3a003b4545c3026e78dbe1bccbbc35161e18",
16
16
  "moduleDoc:agents/terminal-auth-testing.md": "8a84c16a19e381427aed81d3b1882344c427fe22a70e87f5ac2db836b1a6822d",
@@ -10841,7 +10841,7 @@
10841
10841
  {
10842
10842
  "path": "agents/permissions.md",
10843
10843
  "module": "permissions",
10844
- "content": "# Permissions and tenant inheritance\n\nRead for `src/services/PermissionResolver.ts`, operation guards, role seeding,\nRLS, and tenant-hierarchy changes. Integration examples live in README's\nmanifest-derived permission catalog and PostgreSQL RLS sections.\n\n## Membership selection\n\n1. A direct target-tenant membership pins resolution: active resolves normally;\n pending/suspended returns empty. Never union direct and ancestor roles.\n2. Otherwise walk hierarchyPath nearest-first for the nearest active ancestor\n membership whose role has inheritsToDescendants. Skip inactive/unflagged\n ancestors; they neither grant nor block. No qualifying ancestor means empty.\n3. Run later layers against the target tenant. Only target-tenant groups apply;\n membership overrides travel with the chosen ancestor membership.\n inheritedFromTenantId reports that ancestor, null for direct resolution.\n\nVerify hierarchyPath link-by-link against loaded parentTenantId rows before\ntrusting it: excess depth, self-reference, duplicates or inconsistent paths\nfail closed. Bound traversal by MAX_TENANT_HIERARCHY_DEPTH. Tenant status is not\nconsulted, matching direct resolution. Long-lived user/tenant caches must\ninvalidate ancestor membership changes and inheritsToDescendants flips;\nrequest-scoped caches do not survive those requests.\n\n`loadSessionContext().tenantAuthorization` is authoritative for required-tenant\nconsumers; membership null can mean inherited authority.\n\n4. If no membership resolved at all, apply the opt-in ancestor-read policy\n (smrt#2939): the user's ACTIVE memberships on VERIFIED DESCENDANTS of the\n target, whose role slug is declared in `permissions.ancestorRead.roles` and\n within `maxDepth` hops, contribute `<collection>.read` for declared\n collections, intersected with BOTH that role's own catalog grants (so a\n descendant-side tenant GRANT, group role, or membership GRANT cannot widen\n it) AND the principal's effective permissions in that descendant (so its\n DENY cascade and membership DENY apply). Only a SYSTEM\n role (`tenantId` null, `isSystem: true`) matches a declared slug: slugs are\n not unique across a hierarchy, so a tenant-scoped custom role of the same\n name must not opt its tenant in. Off by default;\n read-only; never lateral; never reached when a direct or inherited\n membership already decided. The target tenant's DENY still subtracts.\n `ancestorReadFromTenantIds` reports the contributing descendants. The grant\n carries no membership, so `membershipId`/`tenantAuthorization.membershipId`\n stay null and a consumer requiring a non-empty membership still refuses.\n\n## Permission precedence\n\nApply these layers in order, later layers overriding earlier ones:\n\n1. Ancestor TenantPermissionOverride cascade (GRANT adds, DENY removes).\n2. Selected membership role permissions.\n3. Target-tenant group-role permissions.\n4. Tenant cascade's net DENY removes role/group grants.\n5. Membership GRANT may re-add a tenant-denied slug.\n6. Membership DENY removes last and always wins.\n\nThe tenant block is the cascade's net result, not the union of historical DENYs:\na child's more-specific GRANT can override a parent DENY. Use a direct child\nmembership or tenant DENY to attenuate inherited role authority.\n`getGroupIdsForTenant(userId, tenantId)` is required; getGroupIds is cross-tenant.\n\n## Hierarchies and seeding\n\n- `Tenant.save()` derives hierarchyPath/hierarchyLevel from the REAL\n parentTenantId chain on every save (never from an ancestor's stored path),\n refuses missing parents, cycles and over-depth before writing, and\n re-materializes descendants by raw column update (so STI subclass columns\n are never clobbered). createChild/moveToParent/makeRoot are thin wrappers.\n `materializeTenantHierarchy(db, { dryRun })` / `smrt\n db:materialize-tenant-hierarchy` backfills legacy rows: idempotent, one\n transaction, refuses a broken chain without writing (#3036). A NEW or\n CHANGED parent link is authority-bearing, so `Tenant.save()` first loads the\n parent through TenantCollection (the caller's tenancy scope) and refuses an\n invisible one on every path — helpers, `create({ parentTenantId })`, or a\n plain assignment + save; system context bypasses as usual, and an unchanged\n link is never re-checked. getTree(rootId?)\n returns UI structure. Maximum depth is 10.\n- Tenant override cascade requires parent cascadePermissions and child\n inheritPermissions. These flags do not gate the independent, per-role\n inheritsToDescendants membership flow.\n- `permissions.ancestorRead` in the `users` package config declares upward,\n read-only visibility: `{ roles, collections, maxDepth? }`. Both lists are\n required and non-empty and `maxDepth >= 1`, else the policy is OFF — there is\n no partially valid declaration. `isAncestorReadableSlug()` is the single place\n the read-only bound is enforced; only a two-segment `<collection>.<action>`\n slug whose action normalizes to `read` on a declared collection passes.\n `PermissionResolver.create(options, { ancestorReadPolicy })` binds one\n resolver; `null` forces it off. It grants the OPERATION at the ancestor, never\n row visibility — sibling rows stay scoped by the tenancy interceptor and RLS.\n- seedSystemRoles({ inheritsToDescendants: ['owner', 'admin'] }) flags listed\n slugs additively, never unflags omitted ones, and rejects unknown slugs.\n Default seeds are exact-tenant.\n- RolePermissionCollection.seedRolePermissions() or\n seedSystemRoles({ seedPermissions: true }) maps owner/admin to all catalog\n permissions, member to ordinary-resource read/create, viewer to read. Member\n create excludes identity/RBAC/security resources and their joins/overrides.\n Seeding is additive/idempotent; removal requires prune: true. Catalog sync\n and grants plan from one read, then commit in `SEEDING_BATCH_SIZE` batches\n (#3323): never one durable commit per row (an fsync each on local SQLite),\n and never one catalog-wide transaction (`@happyvertical/sql` transaction\n scopes cost superlinearly per statement). Pass a caller transaction's handle.\n- When a package adds built-in self-personalization permissions after role\n creation, explicitly call seedDefaultRolePersonalizationPermissions(). It\n upgrades owner/admin/member/viewer idempotently and never grants custom roles.\n The built-in self-personalization slugs are `fields.policy.personalize`,\n `overviews.personalize` and `shell.personalize`, each seeded only once its\n package registered it.\n\n## Guards and RLS\n\nPermissionCatalogService derives collection.action slugs from manifests,\nincluding custom actions; list/get map to read. Guards derive a class's slug\nthrough the same resolution (explicit `collection`, manifest entry, then its\nSTI base's), so an STI subtype resolves its STI base's collection\n(`Network extends Tenant` -> `tenants.update`) in every build, including a\nbundle decorated before its manifest loads (#3125). Hand-written mutations in form\nhandlers, endpoints, CLI and jobs use assertOperationPermission(). It requires\ncatalog presence then resolves permissions, throwing fail-closed by default.\nUse onDeny: 'return', checkOperationPermission or hasOperationPermission only\nwhen the caller handles a structured/boolean denial.\n\nGenerated SvelteKit write routes (POST/PUT/PATCH/DELETE CRUD and mutating\ncustom actions) enforce the same `collection.action` slug (custom actions use\n`collection.<method>`; actions hosted on a collection class use the item collection, which the catalog now emits) after authentication: the slug must be in the request's\nsession permission snapshot (`locals.permissions`/`permissionSet`/\n`smrtPermissions` or `locals.tenantContext.permissions`), otherwise 403. Only\n`locals.tenantContext.superAdminBypass` skips it; `api.public: true` stays open.\n\nResource-anchored guards receive the resource tenant ID. When it differs from\nthe session tenant, omit the session membership so the resolver selects the\nappropriate authority; mismatched supplied membership/tenant fails closed.\nInherited root-admin authority can then authorize descendant resources without\napplication-side membership fan-out.\n\nResolution cost must not scale with catalog size in objects (#3047): role →\npermission ids and ids → slugs are `list({ select })` projection reads (the\ncollections' beforeList interceptors still run; no RolePermission/Permission\nis hydrated), so an owner mapped to the whole catalog costs two queries and\nno per-permission objects. Keep new resolver reads projections when only ids\nor slugs are needed, and do not add public collection methods for them — each\nbecomes a custom-action slug in the manifest-derived catalog. The resolver's\ncollections never inherit `defaultListLimit`/`maxListLimit` from the caller's\noptions, and id-list reads chunk at `IN_LIST_CHUNK_SIZE` with an explicit\nlimit: a truncated authorization read would narrow the result silently.\n`issue-3047-permission-resolution-cost-postgres.test.ts` pins the hydration\nbudget and a golden equivalence matrix recorded on the hydrating resolver.\n\nThe same rule holds for every framework-internal authorization read (#3048):\ncollections that back a decision — the resolver's, the ResourceGrant exact-tuple\nread in `checkResourceOperationPermission()` (DENY wins only if every row is\nread), and TenantService's owned-tenant quota count — are created through\n`withoutListBounds()` (`src/services/authorization-read-options.ts`), which\nstrips only the caller's list bounds. Use it for any new decision read;\n`issue-3048-authorization-list-bounds-postgres.test.ts` pins it.\n\nPermissionResolver runs every resolution inside smrt-tenancy's system context\n(#3036). Its reads are deliberately cross-tenant and framework-owned — the\nancestor TenantPermissionOverride batch, ancestor memberships for\ninheritsToDescendants, descendant memberships for ancestor-read — and are keyed\nby the explicit (userId, tenantId); with those classes registered tenant-scoped\n(`autoFilter`) the consumer's filter otherwise threw TenantIsolationError or\nsilently narrowed them. It returns slugs and ids only, never rows, and the\ncaller's own reads stay filtered. Do not move a resolver read back under the\nambient tenant; the Postgres regression suite\n`issue-3036-hierarchy-resolution-postgres.test.ts` pins this. Because nothing\nbackstops those reads any more, the tenant-override cascade verifies its chain\ntoo: it uses hierarchyPath only when it agrees link-by-link with\nparentTenantId, otherwise walks the real parent links (so a forged path cannot\ncascade an unrelated tenant in, and a never-materialized row still gets its real\nancestors' DENYs — and GRANTs), and throws TenantHierarchyError on a broken\nreal chain: a per-request failure for that tenant's whole subtree, which\n`smrt db:materialize-tenant-hierarchy --dry-run` reports ahead of time (see\nREADME \"Upgrading an existing fleet\").\n`getTenantInheritanceChain()` returns Tenant rows, so it is NOT run in system\ncontext; it reports the same verified chain the cascade applies when the\ncaller can read every ancestor. Otherwise it never reports PARENT_NOT_FOUND\nfor a row the caller simply cannot see: an interceptor refusal surfaces as\nTenantIsolationError, and a silently filtered row (e.g. RLS) ends the chain at\nthe nearest readable ancestor. Descendant path rewrites are raw column updates recorded through\n`bumpChangeFeed`; `updated_at` is intentionally not bumped (derived columns;\nevery save recomputes them). They are not atomic with the moving row's save —\nan interrupted subtree fails closed until re-saved or backfilled.\n\nSystem context and super-admin bypass are honored. Pass\nallowSuperAdminBypass: false for money or separation-of-duties operations that\nrequire an explicit grant.\n\ngeneratePostgresPermissionSql/applyPostgresPermissionPolicies enforce generated\nRLS using smrt.permissions and smrt.tenant_id, installed with set_config by\nwithSessionPermissionContext. This bounds REST, MCP and in-process database\naccess once principal context is set. Child-tenant sessions receive resolved\ninherited permissions automatically, but RLS row filtering remains bound to the\nsession tenant: a root session's app-level guard authorization does not itself\npermit child rows through RLS. Preserve this distinction.\n\nRolePermission's natural key is `(role_id, permission_id)` (#3329); repeated or\nconcurrent inserts converge without changing the grant ID. Existing deployments\nmust run `deduplicateRolePermissions()` with stopped writers before ordinary\nschema migration; see README's \"Upgrade duplicate role grants\". It keeps the\noldest row and installs uniqueness atomically. Runtime seeding never performs\nthis schema repair.\n\nMembershipOverride `(membership_id, permission_id)`, TenantPermissionOverride\n`(tenant_id, permission_id)` and GroupMember `(group_id, user_id)` are natural\nkeys too (#3375): `setOverride()` upserts (last effect wins), removal deletes by\nthe pair, and their FKs declare `onDelete: 'NO ACTION'` so the natural key does\nnot turn them into CASCADE. Existing deployments run\n`deduplicateAuthorityPairs()` in the same stopped-writer window; it keeps the\neffective override (DENY > GRANT > INHERIT) so authority never widens.\n"
10844
+ "content": "# Permissions and tenant inheritance\n\nRead for `src/services/PermissionResolver.ts`, operation guards, role seeding,\nRLS, and tenant-hierarchy changes. Integration examples live in README's\nmanifest-derived permission catalog and PostgreSQL RLS sections.\n\n## Membership selection\n\n1. A direct target-tenant membership pins resolution: active resolves normally;\n pending/suspended returns empty. Never union direct and ancestor roles.\n2. Otherwise walk hierarchyPath nearest-first for the nearest active ancestor\n membership whose role has inheritsToDescendants. Skip inactive/unflagged\n ancestors; they neither grant nor block. No qualifying ancestor means empty.\n3. Run later layers against the target tenant. Only target-tenant groups apply;\n membership overrides travel with the chosen ancestor membership.\n inheritedFromTenantId reports that ancestor, null for direct resolution.\n\nVerify hierarchyPath link-by-link against loaded parentTenantId rows before\ntrusting it: excess depth, self-reference, duplicates or inconsistent paths\nfail closed. Bound traversal by MAX_TENANT_HIERARCHY_DEPTH. Tenant status is not\nconsulted, matching direct resolution. Long-lived user/tenant caches must\ninvalidate ancestor membership changes and inheritsToDescendants flips;\nrequest-scoped caches do not survive those requests.\n\n`loadSessionContext().tenantAuthorization` is authoritative for required-tenant\nconsumers; membership null can mean inherited authority.\n\n4. If no membership resolved at all, apply the opt-in ancestor-read policy\n (smrt#2939): the user's ACTIVE memberships on VERIFIED DESCENDANTS of the\n target, whose role slug is declared in `permissions.ancestorRead.roles` and\n within `maxDepth` hops, contribute `<collection>.read` for declared\n collections, intersected with BOTH that role's own catalog grants (so a\n descendant-side tenant GRANT, group role, or membership GRANT cannot widen\n it) AND the principal's effective permissions in that descendant (so its\n DENY cascade and membership DENY apply). Only a SYSTEM\n role (`tenantId` null, `isSystem: true`) matches a declared slug: slugs are\n not unique across a hierarchy, so a tenant-scoped custom role of the same\n name must not opt its tenant in. Off by default;\n read-only; never lateral; never reached when a direct or inherited\n membership already decided. The target tenant's DENY still subtracts.\n `ancestorReadFromTenantIds` reports the contributing descendants. The grant\n carries no membership, so `membershipId`/`tenantAuthorization.membershipId`\n stay null and a consumer requiring a non-empty membership still refuses.\n\n## Permission precedence\n\nApply these layers in order, later layers overriding earlier ones:\n\n1. Ancestor TenantPermissionOverride cascade (GRANT adds, DENY removes).\n2. Selected membership role permissions.\n3. Target-tenant group-role permissions.\n4. Tenant cascade's net DENY removes role/group grants.\n5. Membership GRANT may re-add a tenant-denied slug.\n6. Membership DENY removes last and always wins.\n\nThe tenant block is the cascade's net result, not the union of historical DENYs:\na child's more-specific GRANT can override a parent DENY. Use a direct child\nmembership or tenant DENY to attenuate inherited role authority.\n`getGroupIdsForTenant(userId, tenantId)` is required; getGroupIds is cross-tenant.\n\n## Hierarchies and seeding\n\n- `Tenant.save()` derives hierarchyPath/hierarchyLevel from the REAL\n parentTenantId chain on every save (never from an ancestor's stored path),\n refuses missing parents, cycles and over-depth before writing, and\n re-materializes descendants by raw column update (so STI subclass columns\n are never clobbered). createChild/moveToParent/makeRoot are thin wrappers.\n `materializeTenantHierarchy(db, { dryRun })` / `smrt\n db:materialize-tenant-hierarchy` backfills legacy rows: idempotent, one\n transaction, refuses a broken chain without writing (#3036). A NEW or\n CHANGED parent link is authority-bearing, so `Tenant.save()` first loads the\n parent through TenantCollection (the caller's tenancy scope) and refuses an\n invisible one on every path — helpers, `create({ parentTenantId })`, or a\n plain assignment + save; system context bypasses as usual, and an unchanged\n link is never re-checked. getTree(rootId?)\n returns UI structure. Maximum depth is 10.\n- Tenant override cascade requires parent cascadePermissions and child\n inheritPermissions. These flags do not gate the independent, per-role\n inheritsToDescendants membership flow.\n- `permissions.ancestorRead` in the `users` package config declares upward,\n read-only visibility: `{ roles, collections, maxDepth? }`. Both lists are\n required and non-empty and `maxDepth >= 1`, else the policy is OFF — there is\n no partially valid declaration. `isAncestorReadableSlug()` is the single place\n the read-only bound is enforced; only a two-segment `<collection>.<action>`\n slug whose action normalizes to `read` on a declared collection passes.\n `PermissionResolver.create(options, { ancestorReadPolicy })` binds one\n resolver; `null` forces it off. It grants the OPERATION at the ancestor, never\n row visibility — sibling rows stay scoped by the tenancy interceptor and RLS.\n- seedSystemRoles({ inheritsToDescendants: ['owner', 'admin'] }) flags listed\n slugs additively, never unflags omitted ones, and rejects unknown slugs.\n Default seeds are exact-tenant.\n- RolePermissionCollection.seedRolePermissions() or\n seedSystemRoles({ seedPermissions: true }) maps owner/admin to all catalog\n permissions, member to ordinary-resource read/create, viewer to read. Member\n create excludes identity/RBAC/security resources and their joins/overrides.\n Seeding is additive/idempotent; removal requires prune: true. Catalog sync\n and grants plan from one read, then commit in `SEEDING_BATCH_SIZE` batches\n (#3323): never one durable commit per row (an fsync each on local SQLite),\n and never one catalog-wide transaction (`@happyvertical/sql` transaction\n scopes cost superlinearly per statement). Pass a caller transaction's handle.\n- When a package adds built-in self-personalization permissions after role\n creation, explicitly call seedDefaultRolePersonalizationPermissions(). It\n upgrades owner/admin/member/viewer idempotently and never grants custom roles.\n The built-in self-personalization slugs are `fields.policy.personalize`,\n `overviews.personalize` and `shell.personalize`, each seeded only once its\n package registered it.\n\n## Guards and RLS\n\nPermissionCatalogService derives collection.action slugs from manifests,\nincluding custom actions; list/get map to read. Guards derive a class's slug\nthrough the same resolution (explicit `collection`, manifest entry, then its\nSTI base's), so an STI subtype resolves its STI base's collection\n(`Network extends Tenant` -> `tenants.update`) in every build, including a\nbundle decorated before its manifest loads (#3125). Hand-written mutations in form\nhandlers, endpoints, CLI and jobs use assertOperationPermission(). It requires\ncatalog presence then resolves permissions, throwing fail-closed by default.\nUse onDeny: 'return', checkOperationPermission or hasOperationPermission only\nwhen the caller handles a structured/boolean denial.\n\nGenerated SvelteKit write routes (POST/PUT/PATCH/DELETE CRUD and mutating\ncustom actions) enforce the same `collection.action` slug (custom actions use\n`collection.<method>`; actions hosted on a collection class use the item collection, which the catalog now emits) after authentication: the slug must be in the request's\nsession permission snapshot (`locals.permissions`/`permissionSet`/\n`smrtPermissions` or `locals.tenantContext.permissions`), otherwise 403. Only\n`locals.tenantContext.superAdminBypass` skips it; `api.public: true` stays open.\n\nResource-anchored guards receive the resource tenant ID. When it differs from\nthe session tenant, omit the session membership so the resolver selects the\nappropriate authority; mismatched supplied membership/tenant fails closed.\nInherited root-admin authority can then authorize descendant resources without\napplication-side membership fan-out.\n\nResolution cost must not scale with catalog size in objects (#3047): role →\npermission ids and ids → slugs are `list({ select })` projection reads (the\ncollections' beforeList interceptors still run; no RolePermission/Permission\nis hydrated), so an owner mapped to the whole catalog costs two queries and\nno per-permission objects. Keep new resolver reads projections when only ids\nor slugs are needed, and do not add public collection methods for them — each\nbecomes a custom-action slug in the manifest-derived catalog. The resolver's\ncollections never inherit `defaultListLimit`/`maxListLimit` from the caller's\noptions, and id-list reads chunk at `IN_LIST_CHUNK_SIZE` with an explicit\nlimit: a truncated authorization read would narrow the result silently.\n`issue-3047-permission-resolution-cost-postgres.test.ts` pins the hydration\nbudget and a golden equivalence matrix recorded on the hydrating resolver.\n\nThe same rule holds for every framework-internal authorization read (#3048):\ncollections that back a decision — the resolver's, the ResourceGrant exact-tuple\nread in `checkResourceOperationPermission()` (DENY wins only if every row is\nread), and TenantService's owned-tenant quota count — are created through\n`withoutListBounds()` (`src/services/authorization-read-options.ts`), which\nstrips only the caller's list bounds. Use it for any new decision read;\n`issue-3048-authorization-list-bounds-postgres.test.ts` pins it.\n\nPermissionResolver runs every resolution inside smrt-tenancy's system context\n(#3036). Its reads are deliberately cross-tenant and framework-owned — the\nancestor TenantPermissionOverride batch, ancestor memberships for\ninheritsToDescendants, descendant memberships for ancestor-read — and are keyed\nby the explicit (userId, tenantId); with those classes registered tenant-scoped\n(`autoFilter`) the consumer's filter otherwise threw TenantIsolationError or\nsilently narrowed them. It returns slugs and ids only, never rows, and the\ncaller's own reads stay filtered. Do not move a resolver read back under the\nambient tenant; the Postgres regression suite\n`issue-3036-hierarchy-resolution-postgres.test.ts` pins this. Because nothing\nbackstops those reads any more, the tenant-override cascade verifies its chain\ntoo: it uses hierarchyPath only when it agrees link-by-link with\nparentTenantId, otherwise walks the real parent links (so a forged path cannot\ncascade an unrelated tenant in, and a never-materialized row still gets its real\nancestors' DENYs — and GRANTs), and throws TenantHierarchyError on a broken\nreal chain: a per-request failure for that tenant's whole subtree, which\n`smrt db:materialize-tenant-hierarchy --dry-run` reports ahead of time (see\nREADME \"Upgrading an existing fleet\").\n`getTenantInheritanceChain()` returns Tenant rows, so it is NOT run in system\ncontext; it reports the same verified chain the cascade applies when the\ncaller can read every ancestor. Otherwise it never reports PARENT_NOT_FOUND\nfor a row the caller simply cannot see: an interceptor refusal surfaces as\nTenantIsolationError, and a silently filtered row (e.g. RLS) ends the chain at\nthe nearest readable ancestor. Descendant path rewrites are raw column updates recorded through\n`bumpChangeFeed`; `updated_at` is intentionally not bumped (derived columns;\nevery save recomputes them). They are not atomic with the moving row's save —\nan interrupted subtree fails closed until re-saved or backfilled.\n\nSystem context and super-admin bypass are honored. Pass\nallowSuperAdminBypass: false for money or separation-of-duties operations that\nrequire an explicit grant.\n\ngeneratePostgresPermissionSql/applyPostgresPermissionPolicies enforce generated\nRLS using smrt.permissions and smrt.tenant_id, installed with set_config by\nwithSessionPermissionContext. This bounds REST, MCP and in-process database\naccess once principal context is set. Child-tenant sessions receive resolved\ninherited permissions automatically, but RLS row filtering remains bound to the\nsession tenant: a root session's app-level guard authorization does not itself\npermit child rows through RLS. Preserve this distinction.\n\nRolePermission's natural key is `(role_id, permission_id)` (#3329); repeated or\nconcurrent inserts converge without changing the grant ID. Existing deployments\nmust run `deduplicateRolePermissions()` with stopped writers before ordinary\nschema migration; see README's \"Upgrade duplicate role grants\". It keeps the\noldest row and installs uniqueness atomically. Runtime seeding never performs\nthis schema repair.\n\nMembershipOverride `(membership_id, permission_id)`, TenantPermissionOverride\n`(tenant_id, permission_id)` and GroupMember `(group_id, user_id)` are natural\nkeys too (#3375): `setOverride()` upserts (last effect wins), removal deletes by\nthe pair, and their FKs declare `onDelete: 'NO ACTION'` so the natural key does\nnot turn them into CASCADE. Existing deployments run\n`deduplicateAuthorityPairs()` in the same stopped-writer window; it keeps the\neffective override (DENY > GRANT > INHERIT) so authority never widens.\n\nCustom methods can declare `operationPermissions` aliases to the same\ncollection's CRUD grants (#3878); `deriveOperationPermissionSlug()` resolves\nthem for class and catalog-slug callers. Catalog generation merges optional\n`databaseActions` into the target grant's PostgreSQL bindings. CRUD grants\ncannot themselves be aliased. See core's [generator contract](../../core/agents/generators.md#custom-action-permission-aliases-3878).\n"
10845
10845
  },
10846
10846
  {
10847
10847
  "path": "agents/oidc-provisioning.md",
package/dist/sveltekit.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as withSessionPermissionContext, B as resolveOidcProviderConfig, C as OperationPermissionError, F as OidcLoginError, G as DeviceCredentialForbiddenError, I as OidcLoginService, K as DeviceCredentialPolicyError, L as decodeOidcTransaction, M as MobileAuthService, N as readMobileBearerToken, P as validateMobileRedirectUri, Q as SessionService, R as encodeOidcTransaction, W as DeviceCredentialError, _ as PasswordCredentialError, a as TerminalAuthError, b as PasswordPolicyError, j as MobileAuthError, lt as LoginRateLimitError, o as TerminalAuthRateLimitError, q as DeviceCredentialService, s as TerminalAuthService, v as PasswordCredentialForbiddenError, y as PasswordCredentialService, z as getUsersOidcConfig } from "./chunks/TerminalAuthService-B0icE2mR.js";
1
+ import { A as withSessionPermissionContext, B as resolveOidcProviderConfig, C as OperationPermissionError, F as OidcLoginError, G as DeviceCredentialForbiddenError, I as OidcLoginService, K as DeviceCredentialPolicyError, L as decodeOidcTransaction, M as MobileAuthService, N as readMobileBearerToken, P as validateMobileRedirectUri, Q as SessionService, R as encodeOidcTransaction, W as DeviceCredentialError, _ as PasswordCredentialError, a as TerminalAuthError, b as PasswordPolicyError, j as MobileAuthError, lt as LoginRateLimitError, o as TerminalAuthRateLimitError, q as DeviceCredentialService, s as TerminalAuthService, v as PasswordCredentialForbiddenError, y as PasswordCredentialService, z as getUsersOidcConfig } from "./chunks/TerminalAuthService-BQjH5_bB.js";
2
2
  import { DiscoveryArtifactValidationError, SMRT_APP_RESULT_CONTRACT, SMRT_APP_RESULT_SCHEMA, SMRT_APP_RESULT_VERSION, SMRT_DISCOVERY_CONFORMANCE_ARTIFACT_SCHEMA, SMRT_DISCOVERY_CONFORMANCE_SCHEMA, SMRT_DISCOVERY_CONFORMANCE_VERSION, SMRT_MCP_RESULT_METADATA_KEY, canonicalizeDiscoveryArtifact, createDiscoveryConformanceArtifact, deriveCommandRequirements, validateDiscoveryConformanceArtifact } from "./app-contract.js";
3
3
  import { ObjectRegistry, createClassNamePredicate, resolveCustomActionMetadata, resolveEffectiveActionMetadata } from "@happyvertical/smrt-core";
4
4
  import { createLogger } from "@happyvertical/logger";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-users",
3
- "version": "0.55.24",
3
+ "version": "0.56.0",
4
4
  "smrtJsdoc": "strict",
5
5
  "description": "Multi-tenant user management for the SMRT framework - users, tenants, roles, permissions, groups",
6
6
  "type": "module",
@@ -49,18 +49,18 @@
49
49
  "dependencies": {
50
50
  "@happyvertical/auth": "^0.103.6",
51
51
  "@happyvertical/logger": "^0.103.6",
52
- "@happyvertical/smrt-config": "0.55.24",
53
- "@happyvertical/smrt-core": "0.55.24",
54
- "@happyvertical/smrt-mobile-contract": "0.55.24",
55
- "@happyvertical/smrt-profiles": "0.55.24",
56
- "@happyvertical/smrt-tenancy": "0.55.24",
57
- "@happyvertical/smrt-types": "0.55.24",
58
- "@happyvertical/smrt-ui": "0.55.24",
52
+ "@happyvertical/smrt-config": "0.56.0",
53
+ "@happyvertical/smrt-core": "0.56.0",
54
+ "@happyvertical/smrt-mobile-contract": "0.56.0",
55
+ "@happyvertical/smrt-profiles": "0.56.0",
56
+ "@happyvertical/smrt-tenancy": "0.56.0",
57
+ "@happyvertical/smrt-types": "0.56.0",
58
+ "@happyvertical/smrt-ui": "0.56.0",
59
59
  "@happyvertical/sql": "^0.103.6",
60
60
  "jose": "^6.2.12"
61
61
  },
62
62
  "devDependencies": {
63
- "@happyvertical/smrt-vitest": "0.55.24",
63
+ "@happyvertical/smrt-vitest": "0.56.0",
64
64
  "@sveltejs/package": "^2.5.8",
65
65
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
66
66
  "@types/node": "24.13.6",
@@ -98,7 +98,7 @@
98
98
  "clean": "rm -rf dist",
99
99
  "dev": "npm run build:watch",
100
100
  "test": "vitest run",
101
- "test:postgres": "node ../../scripts/run-with-ci-postgres.mjs -- pnpm exec vitest run src/__tests__/permission-postgres-rls.test.ts src/__tests__/principal-permission-context-postgres.test.ts src/__tests__/oidc-provisioning-postgres.test.ts src/__tests__/legacy-user-profile-backfill-postgres.test.ts src/__tests__/terminal-auth-postgres.test.ts src/__tests__/device-credential-postgres.test.ts src/__tests__/password-credential-postgres.test.ts src/__tests__/resource-grant-postgres.test.ts src/__tests__/issue-3036-hierarchy-resolution-postgres.test.ts src/__tests__/issue-3047-permission-resolution-cost-postgres.test.ts src/__tests__/issue-3048-authorization-list-bounds-postgres.test.ts src/__tests__/issue-3329-concurrent-seeding-postgres.test.ts src/__tests__/issue-3329-grant-dedup.test.ts src/__tests__/issue-3375-authority-pair-dedup.test.ts src/__tests__/issue-3375-authority-pair-identity.test.ts src/__tests__/oauth-authorization.test.ts",
101
+ "test:postgres": "node ../../scripts/run-with-ci-postgres.mjs -- pnpm exec vitest run src/__tests__/permission-action-alias-postgres.test.ts src/__tests__/permission-postgres-rls.test.ts src/__tests__/principal-permission-context-postgres.test.ts src/__tests__/oidc-provisioning-postgres.test.ts src/__tests__/legacy-user-profile-backfill-postgres.test.ts src/__tests__/terminal-auth-postgres.test.ts src/__tests__/device-credential-postgres.test.ts src/__tests__/password-credential-postgres.test.ts src/__tests__/resource-grant-postgres.test.ts src/__tests__/issue-3036-hierarchy-resolution-postgres.test.ts src/__tests__/issue-3047-permission-resolution-cost-postgres.test.ts src/__tests__/issue-3048-authorization-list-bounds-postgres.test.ts src/__tests__/issue-3329-concurrent-seeding-postgres.test.ts src/__tests__/issue-3329-grant-dedup.test.ts src/__tests__/issue-3375-authority-pair-dedup.test.ts src/__tests__/issue-3375-authority-pair-identity.test.ts src/__tests__/oauth-authorization.test.ts",
102
102
  "typecheck": "tsc --noEmit && node ../../scripts/svelte-check-a11y.mjs --tsconfig ./tsconfig.svelte.json",
103
103
  "check": "pnpm exec svelte-check --tsconfig ./tsconfig.svelte.json",
104
104
  "verify:pack": "node ../../scripts/verify-package-types-exports.js ."