@happyvertical/smrt-users 0.51.39 → 0.53.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.
@@ -84,6 +84,10 @@ export declare class RolePermissionCollection extends SmrtCollection<RolePermiss
84
84
  * Defaults to the system owner/admin/member/viewer matrix. Re-running is
85
85
  * idempotent: existing mappings are reported as `unchanged`, new catalog
86
86
  * slugs are added, and stale mappings are only removed when `prune` is true.
87
+ *
88
+ * Grants and revocations are planned from one read per role, then committed
89
+ * in batches rather than one durable commit per row (#3323). Inside a
90
+ * caller transaction, pass that transaction's handle.
87
91
  */
88
92
  seedRolePermissions(matrix?: RolePermissionPatternMatrix, options?: SeedRolePermissionsOptions): Promise<SeedRolePermissionsResult>;
89
93
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"RolePermissionCollection.d.ts","sourceRoot":"","sources":["../../src/collections/RolePermissionCollection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAI7E,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAC/C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAClC,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC7C;AAED,eAAO,MAAM,gCAAgC,EAAE,MAAM,CACnD,eAAe,EACf,SAAS,MAAM,EAAE,CAMlB,CAAC;AA4GF;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,cAAc,CAAC,cAAc,CAAC;IAC1E,MAAM,CAAC,QAAQ,CAAC,UAAU,wBAAkB;IAE5C;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAM3D;;OAEG;IACG,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAMvE;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ3E;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC;IAa1B;;;;;;;;;;;;;;OAcG;YACW,eAAe;IAO7B;;OAEG;IACG,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC;IAanB;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAWzD;;;;;;OAMG;IACG,mBAAmB,CACvB,MAAM,GAAE,2BAA8D,EACtE,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,yBAAyB,CAAC;IAmHrC;;;;;OAKG;IACG,yCAAyC,CAC7C,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,OAAO,CAAM,GACtD,OAAO,CAAC,yBAAyB,CAAC;CAWtC"}
1
+ {"version":3,"file":"RolePermissionCollection.d.ts","sourceRoot":"","sources":["../../src/collections/RolePermissionCollection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAK7E,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAC/C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAClC,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC7C;AAED,eAAO,MAAM,gCAAgC,EAAE,MAAM,CACnD,eAAe,EACf,SAAS,MAAM,EAAE,CAMlB,CAAC;AA4GF;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,cAAc,CAAC,cAAc,CAAC;IAC1E,MAAM,CAAC,QAAQ,CAAC,UAAU,wBAAkB;IAE5C;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAM3D;;OAEG;IACG,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAMvE;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ3E;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC;IAa1B;;;;;;;;;;;;;;OAcG;YACW,eAAe;IAO7B;;OAEG;IACG,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC;IAanB;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAWzD;;;;;;;;;;OAUG;IACG,mBAAmB,CACvB,MAAM,GAAE,2BAA8D,EACtE,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,yBAAyB,CAAC;IA0IrC;;;;;OAKG;IACG,yCAAyC,CAC7C,OAAO,GAAE,IAAI,CAAC,0BAA0B,EAAE,OAAO,CAAM,GACtD,OAAO,CAAC,yBAAyB,CAAC;CAWtC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Batched commits for permission-catalog and role-permission seeding.
3
+ * @packageDocumentation
4
+ */
5
+ import type { DatabaseInterface } from '@happyvertical/sql';
6
+ /**
7
+ * Rows a seeding pass writes per transaction (#3323).
8
+ *
9
+ * A cold seed writes every catalog permission and every default grant, each
10
+ * with a change-feed append: thousands of rows on a real registry. When every
11
+ * row autocommits, each is a separate durable commit. On a file-backed SQLite
12
+ * database with `synchronous = FULL` (the local runtime profile), that is an
13
+ * `fsync` per row, and disk flushes, not queries, set the bootstrap's wall
14
+ * clock. Batching cuts the commits by this factor.
15
+ *
16
+ * The batch is bounded, not the whole seed, because a transaction scope in
17
+ * `@happyvertical/sql` tracks every promise created while it is open and
18
+ * keeps each statement's rejection observation until the transaction ends,
19
+ * so its per-statement cost grows with the statements already issued. On the
20
+ * local owner claim, one catalog-wide transaction took ~17s against ~4.5s for
21
+ * batches of 25-100 rows (and ~30s for per-row commits on a real disk).
22
+ */
23
+ export declare const SEEDING_BATCH_SIZE = 50;
24
+ /**
25
+ * Write `items` in transactions of at most {@link SEEDING_BATCH_SIZE} rows.
26
+ *
27
+ * Seeding is idempotent and additive, so committing per batch keeps the
28
+ * semantics of the previous per-row commits: an interrupted pass leaves only
29
+ * complete rows behind, and re-running converges. `write` receives the handle
30
+ * bound to its batch's transaction and must issue every write through it.
31
+ *
32
+ * Nesting follows `@happyvertical/sql`'s rules. Given a transaction handle,
33
+ * SQLite and PostgreSQL re-enter it under a SAVEPOINT, and adapters without
34
+ * savepoints (DuckDB/JSON) reuse the caller's transaction directly. A handle
35
+ * with no `transaction()` keeps per-statement commits. A caller already inside
36
+ * a transaction must pass that transaction's handle: on a single-connection
37
+ * adapter the root handle waits for the open transaction instead of joining
38
+ * it.
39
+ *
40
+ * @internal
41
+ */
42
+ export declare function writeInSeedingBatches<T>(db: DatabaseInterface, items: readonly T[], write: (database: DatabaseInterface, batch: readonly T[]) => Promise<void>): Promise<void>;
43
+ //# sourceMappingURL=seeding-transaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seeding-transaction.d.ts","sourceRoot":"","sources":["../../src/collections/seeding-transaction.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,EAAE,EAAE,iBAAiB,EACrB,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GACzE,OAAO,CAAC,IAAI,CAAC,CAcf"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as deriveOperationPermissionSlug, A as DEFAULT_ANCESTOR_READ_MAX_DEPTH, B as TenantPermissionOverride, C as OidcLoginService, Ct as UsersCliAuthRequestCollection, D as resolveOidcProviderConfig, Dt as generateSessionId, E as getUsersOidcConfig, Et as Session, F as UserCollection, G as applyTenantHierarchyUpdates, H as Tenant, J as recordTenantHierarchyChanges, K as assertTenantTableName, L as UserEmailKeyBackfillError, M as isAncestorReadableSlug, N as normalizeAncestorReadPolicy, Ot as UsersCliAuthRequest, P as OidcProvisioningError, Q as deriveOperationPermissionCollectionName, R as backfillUserEmailKeys, S as OidcLoginError, St as normalizeEmail, T as encodeOidcTransaction, Tt as DEFAULT_SESSION_TTL, U as MAX_TENANT_HIERARCHY_DEPTH, V as TenantCollection, W as TenantHierarchyError, X as RolePermissionCollection, Y as DEFAULT_ROLE_PERMISSION_PATTERNS, Z as PermissionCatalogService, _ as readMobileBearerToken, _t as Group, a as TerminalAuthRateLimitError, at as Role, b as PermissionResolver, bt as User, c as assertOperationPermission, ct as MembershipOverrideCollection, d as getCurrentSessionPermissionContext, dt as Membership, et as normalizeOperationPermissionAction, f as getRequestScopedDatabase, ft as GroupRoleCollection, g as MobileAuthService, gt as GroupCollection, h as MobileAuthError, ht as GroupMember, i as TerminalAuthError, it as RoleCollection, j as getConfiguredAncestorReadPolicy, k as ANCESTOR_READ_ACTION, l as checkOperationPermission, lt as MembershipOverride, m as withSessionPermissionContext, mt as GroupMemberCollection, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nt as syncPermissionCatalog, o as TerminalAuthService, ot as PermissionCollection, p as withPrincipalPermissionContext, pt as GroupRole, q as planTenantHierarchy, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rt as RolePermission, s as OperationPermissionError, st as Permission, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tt as registerPermissionDefinitions, u as hasOperationPermission, ut as MembershipCollection, v as validateMobileRedirectUri, vt as UsersCliAuthApproveLimitCollection, w as decodeOidcTransaction, wt as SessionCollection, x as withoutListBounds, xt as isValidEmail, y as SessionService, yt as UsersCliAuthApproveLimit, z as TenantPermissionOverrideCollection } from "./chunks/TerminalAuthService-BDKUbg4N.js";
1
+ import { $ as deriveOperationPermissionSlug, A as DEFAULT_ANCESTOR_READ_MAX_DEPTH, B as TenantPermissionOverride, C as OidcLoginService, Ct as UsersCliAuthRequestCollection, D as resolveOidcProviderConfig, Dt as generateSessionId, E as getUsersOidcConfig, Et as Session, F as UserCollection, G as applyTenantHierarchyUpdates, H as Tenant, J as recordTenantHierarchyChanges, K as assertTenantTableName, L as UserEmailKeyBackfillError, M as isAncestorReadableSlug, N as normalizeAncestorReadPolicy, Ot as UsersCliAuthRequest, P as OidcProvisioningError, Q as deriveOperationPermissionCollectionName, R as backfillUserEmailKeys, S as OidcLoginError, St as normalizeEmail, T as encodeOidcTransaction, Tt as DEFAULT_SESSION_TTL, U as MAX_TENANT_HIERARCHY_DEPTH, V as TenantCollection, W as TenantHierarchyError, X as RolePermissionCollection, Y as DEFAULT_ROLE_PERMISSION_PATTERNS, Z as PermissionCatalogService, _ as readMobileBearerToken, _t as Group, a as TerminalAuthRateLimitError, at as Role, b as PermissionResolver, bt as User, c as assertOperationPermission, ct as MembershipOverrideCollection, d as getCurrentSessionPermissionContext, dt as Membership, et as normalizeOperationPermissionAction, f as getRequestScopedDatabase, ft as GroupRoleCollection, g as MobileAuthService, gt as GroupCollection, h as MobileAuthError, ht as GroupMember, i as TerminalAuthError, it as RoleCollection, j as getConfiguredAncestorReadPolicy, k as ANCESTOR_READ_ACTION, l as checkOperationPermission, lt as MembershipOverride, m as withSessionPermissionContext, mt as GroupMemberCollection, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nt as syncPermissionCatalog, o as TerminalAuthService, ot as PermissionCollection, p as withPrincipalPermissionContext, pt as GroupRole, q as planTenantHierarchy, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rt as RolePermission, s as OperationPermissionError, st as Permission, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tt as registerPermissionDefinitions, u as hasOperationPermission, ut as MembershipCollection, v as validateMobileRedirectUri, vt as UsersCliAuthApproveLimitCollection, w as decodeOidcTransaction, wt as SessionCollection, x as withoutListBounds, xt as isValidEmail, y as SessionService, yt as UsersCliAuthApproveLimit, z as TenantPermissionOverrideCollection } from "./chunks/TerminalAuthService-D_UjTiR6.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, field, findManifestEntryByQualifiedName, foreignKey, isUniqueViolationError, registerRetentionTask, smrt, unregisterRetentionTask } from "@happyvertical/smrt-core";
4
4
  import { PROFILE_EMAIL_KEY_BACKFILL_NAME, Person, ProfileCollection, ProfileTypeCollection, normalizeIdentityEmail } from "@happyvertical/smrt-profiles";
@@ -1194,7 +1194,7 @@ var MagicLinkService = class MagicLinkService {
1194
1194
  * The caller is responsible for emailing the token to the user.
1195
1195
  */
1196
1196
  async generate(email) {
1197
- const { SignJWT } = await import("./chunks/TerminalAuthService-BDKUbg4N.js").then((n) => n.O);
1197
+ const { SignJWT } = await import("./chunks/TerminalAuthService-D_UjTiR6.js").then((n) => n.O);
1198
1198
  const key = await this.getSigningKey();
1199
1199
  const nonce = crypto.randomUUID();
1200
1200
  const normalizedEmail = normalizeEmail(email);
@@ -1223,7 +1223,7 @@ var MagicLinkService = class MagicLinkService {
1223
1223
  * @throws {MagicLinkError} If the token is invalid, expired, or already used
1224
1224
  */
1225
1225
  async verify(token) {
1226
- const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-BDKUbg4N.js").then((n) => n.O);
1226
+ const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-D_UjTiR6.js").then((n) => n.O);
1227
1227
  const key = await this.getSigningKey();
1228
1228
  let payload;
1229
1229
  try {
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-users",
5
- "packageVersion": "0.51.39",
5
+ "packageVersion": "0.53.0",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-users:AccessRequestCollection": {
8
8
  "name": "accessrequestcollection",
@@ -509,12 +509,14 @@
509
509
  },
510
510
  "indexes": [
511
511
  {
512
- "name": "users_cli_auth_requests_slug_context_idx",
512
+ "name": "users_cli_auth_requests_tenant_id_slug_idx",
513
513
  "columns": [
514
+ "tenant_id",
514
515
  "slug",
515
516
  "context"
516
517
  ],
517
- "unique": true
518
+ "unique": true,
519
+ "nullsNotDistinct": true
518
520
  },
519
521
  {
520
522
  "name": "users_cli_auth_requests_expires_at_idx",
@@ -534,12 +536,6 @@
534
536
  "user_id"
535
537
  ]
536
538
  },
537
- {
538
- "name": "users_cli_auth_requests_tenant_id_idx",
539
- "columns": [
540
- "tenant_id"
541
- ]
542
- },
543
539
  {
544
540
  "name": "users_cli_auth_requests_session_id_idx",
545
541
  "columns": [
@@ -655,8 +651,9 @@
655
651
  },
656
652
  "indexes": [
657
653
  {
658
- "name": "groups_slug_context_idx",
654
+ "name": "groups_tenant_id_slug_idx",
659
655
  "columns": [
656
+ "tenant_id",
660
657
  "slug",
661
658
  "context"
662
659
  ],
@@ -667,12 +664,6 @@
667
664
  "columns": [
668
665
  "created_at"
669
666
  ]
670
- },
671
- {
672
- "name": "groups_tenant_id_idx",
673
- "columns": [
674
- "tenant_id"
675
- ]
676
667
  }
677
668
  ],
678
669
  "version": "1824c19b"
@@ -1444,8 +1435,9 @@
1444
1435
  },
1445
1436
  "indexes": [
1446
1437
  {
1447
- "name": "memberships_slug_context_idx",
1438
+ "name": "memberships_tenant_id_slug_idx",
1448
1439
  "columns": [
1440
+ "tenant_id",
1449
1441
  "slug",
1450
1442
  "context"
1451
1443
  ],
@@ -2254,24 +2246,20 @@
2254
2246
  },
2255
2247
  "indexes": [
2256
2248
  {
2257
- "name": "roles_slug_context_idx",
2249
+ "name": "roles_tenant_id_slug_idx",
2258
2250
  "columns": [
2251
+ "tenant_id",
2259
2252
  "slug",
2260
2253
  "context"
2261
2254
  ],
2262
- "unique": true
2255
+ "unique": true,
2256
+ "nullsNotDistinct": true
2263
2257
  },
2264
2258
  {
2265
2259
  "name": "roles_created_at_idx",
2266
2260
  "columns": [
2267
2261
  "created_at"
2268
2262
  ]
2269
- },
2270
- {
2271
- "name": "roles_tenant_id_idx",
2272
- "columns": [
2273
- "tenant_id"
2274
- ]
2275
2263
  }
2276
2264
  ],
2277
2265
  "version": "47369d9d"
@@ -2824,12 +2812,14 @@
2824
2812
  },
2825
2813
  "indexes": [
2826
2814
  {
2827
- "name": "sessions_slug_context_idx",
2815
+ "name": "sessions_tenant_id_slug_idx",
2828
2816
  "columns": [
2817
+ "tenant_id",
2829
2818
  "slug",
2830
2819
  "context"
2831
2820
  ],
2832
- "unique": true
2821
+ "unique": true,
2822
+ "nullsNotDistinct": true
2833
2823
  },
2834
2824
  {
2835
2825
  "name": "sessions_expires_at_idx",
@@ -2848,12 +2838,6 @@
2848
2838
  "columns": [
2849
2839
  "user_id"
2850
2840
  ]
2851
- },
2852
- {
2853
- "name": "sessions_tenant_id_idx",
2854
- "columns": [
2855
- "tenant_id"
2856
- ]
2857
2841
  }
2858
2842
  ],
2859
2843
  "version": "dc55d351"
@@ -3755,8 +3739,9 @@
3755
3739
  },
3756
3740
  "indexes": [
3757
3741
  {
3758
- "name": "tenant_permission_overrides_slug_context_idx",
3742
+ "name": "tenant_permission_overrides_tenant_id_slug_idx",
3759
3743
  "columns": [
3744
+ "tenant_id",
3760
3745
  "slug",
3761
3746
  "context"
3762
3747
  ],
@@ -3768,12 +3753,6 @@
3768
3753
  "created_at"
3769
3754
  ]
3770
3755
  },
3771
- {
3772
- "name": "tenant_permission_overrides_tenant_id_idx",
3773
- "columns": [
3774
- "tenant_id"
3775
- ]
3776
- },
3777
3756
  {
3778
3757
  "name": "tenant_permission_overrides_permission_id_idx",
3779
3758
  "columns": [
@@ -4518,7 +4497,12 @@
4518
4497
  },
4519
4498
  "mcp": {
4520
4499
  "include": []
4521
- }
4500
+ },
4501
+ "conflictColumns": [
4502
+ "tenant_id",
4503
+ "slug",
4504
+ "context"
4505
+ ]
4522
4506
  },
4523
4507
  "extends": "SmrtObject",
4524
4508
  "exportName": "UsersCliAuthRequest",
@@ -4628,12 +4612,14 @@
4628
4612
  },
4629
4613
  "indexes": [
4630
4614
  {
4631
- "name": "users_cli_auth_requests_slug_context_idx",
4615
+ "name": "users_cli_auth_requests_tenant_id_slug_idx",
4632
4616
  "columns": [
4617
+ "tenant_id",
4633
4618
  "slug",
4634
4619
  "context"
4635
4620
  ],
4636
- "unique": true
4621
+ "unique": true,
4622
+ "nullsNotDistinct": true
4637
4623
  },
4638
4624
  {
4639
4625
  "name": "users_cli_auth_requests_expires_at_idx",
@@ -4653,12 +4639,6 @@
4653
4639
  "user_id"
4654
4640
  ]
4655
4641
  },
4656
- {
4657
- "name": "users_cli_auth_requests_tenant_id_idx",
4658
- "columns": [
4659
- "tenant_id"
4660
- ]
4661
- },
4662
4642
  {
4663
4643
  "name": "users_cli_auth_requests_session_id_idx",
4664
4644
  "columns": [
@@ -4711,7 +4691,12 @@
4711
4691
  "get"
4712
4692
  ]
4713
4693
  },
4714
- "cli": true
4694
+ "cli": true,
4695
+ "conflictColumns": [
4696
+ "tenant_id",
4697
+ "slug",
4698
+ "context"
4699
+ ]
4715
4700
  },
4716
4701
  "extends": "SmrtObject",
4717
4702
  "exportName": "Group",
@@ -4779,8 +4764,9 @@
4779
4764
  },
4780
4765
  "indexes": [
4781
4766
  {
4782
- "name": "groups_slug_context_idx",
4767
+ "name": "groups_tenant_id_slug_idx",
4783
4768
  "columns": [
4769
+ "tenant_id",
4784
4770
  "slug",
4785
4771
  "context"
4786
4772
  ],
@@ -4791,12 +4777,6 @@
4791
4777
  "columns": [
4792
4778
  "created_at"
4793
4779
  ]
4794
- },
4795
- {
4796
- "name": "groups_tenant_id_idx",
4797
- "columns": [
4798
- "tenant_id"
4799
- ]
4800
4780
  }
4801
4781
  ],
4802
4782
  "version": "1824c19b"
@@ -5322,6 +5302,11 @@
5322
5302
  "userId"
5323
5303
  ]
5324
5304
  }
5305
+ ],
5306
+ "conflictColumns": [
5307
+ "tenant_id",
5308
+ "slug",
5309
+ "context"
5325
5310
  ]
5326
5311
  },
5327
5312
  "extends": "SmrtObject",
@@ -5417,8 +5402,9 @@
5417
5402
  },
5418
5403
  "indexes": [
5419
5404
  {
5420
- "name": "memberships_slug_context_idx",
5405
+ "name": "memberships_tenant_id_slug_idx",
5421
5406
  "columns": [
5407
+ "tenant_id",
5422
5408
  "slug",
5423
5409
  "context"
5424
5410
  ],
@@ -6104,7 +6090,12 @@
6104
6090
  },
6105
6091
  "cli": {
6106
6092
  "skipApiCheck": true
6107
- }
6093
+ },
6094
+ "conflictColumns": [
6095
+ "tenant_id",
6096
+ "slug",
6097
+ "context"
6098
+ ]
6108
6099
  },
6109
6100
  "extends": "SmrtObject",
6110
6101
  "exportName": "Role",
@@ -6177,24 +6168,20 @@
6177
6168
  },
6178
6169
  "indexes": [
6179
6170
  {
6180
- "name": "roles_slug_context_idx",
6171
+ "name": "roles_tenant_id_slug_idx",
6181
6172
  "columns": [
6173
+ "tenant_id",
6182
6174
  "slug",
6183
6175
  "context"
6184
6176
  ],
6185
- "unique": true
6177
+ "unique": true,
6178
+ "nullsNotDistinct": true
6186
6179
  },
6187
6180
  {
6188
6181
  "name": "roles_created_at_idx",
6189
6182
  "columns": [
6190
6183
  "created_at"
6191
6184
  ]
6192
- },
6193
- {
6194
- "name": "roles_tenant_id_idx",
6195
- "columns": [
6196
- "tenant_id"
6197
- ]
6198
6185
  }
6199
6186
  ],
6200
6187
  "version": "47369d9d"
@@ -6553,7 +6540,12 @@
6553
6540
  },
6554
6541
  "cli": {
6555
6542
  "skipApiCheck": true
6556
- }
6543
+ },
6544
+ "conflictColumns": [
6545
+ "tenant_id",
6546
+ "slug",
6547
+ "context"
6548
+ ]
6557
6549
  },
6558
6550
  "extends": "SmrtObject",
6559
6551
  "exportName": "Session",
@@ -6647,12 +6639,14 @@
6647
6639
  },
6648
6640
  "indexes": [
6649
6641
  {
6650
- "name": "sessions_slug_context_idx",
6642
+ "name": "sessions_tenant_id_slug_idx",
6651
6643
  "columns": [
6644
+ "tenant_id",
6652
6645
  "slug",
6653
6646
  "context"
6654
6647
  ],
6655
- "unique": true
6648
+ "unique": true,
6649
+ "nullsNotDistinct": true
6656
6650
  },
6657
6651
  {
6658
6652
  "name": "sessions_expires_at_idx",
@@ -6671,12 +6665,6 @@
6671
6665
  "columns": [
6672
6666
  "user_id"
6673
6667
  ]
6674
- },
6675
- {
6676
- "name": "sessions_tenant_id_idx",
6677
- "columns": [
6678
- "tenant_id"
6679
- ]
6680
6668
  }
6681
6669
  ],
6682
6670
  "version": "dc55d351"
@@ -7331,7 +7319,12 @@
7331
7319
  },
7332
7320
  "cli": {
7333
7321
  "skipApiCheck": true
7334
- }
7322
+ },
7323
+ "conflictColumns": [
7324
+ "tenant_id",
7325
+ "slug",
7326
+ "context"
7327
+ ]
7335
7328
  },
7336
7329
  "extends": "SmrtObject",
7337
7330
  "exportName": "TenantPermissionOverride",
@@ -7409,8 +7402,9 @@
7409
7402
  },
7410
7403
  "indexes": [
7411
7404
  {
7412
- "name": "tenant_permission_overrides_slug_context_idx",
7405
+ "name": "tenant_permission_overrides_tenant_id_slug_idx",
7413
7406
  "columns": [
7407
+ "tenant_id",
7414
7408
  "slug",
7415
7409
  "context"
7416
7410
  ],
@@ -7422,12 +7416,6 @@
7422
7416
  "created_at"
7423
7417
  ]
7424
7418
  },
7425
- {
7426
- "name": "tenant_permission_overrides_tenant_id_idx",
7427
- "columns": [
7428
- "tenant_id"
7429
- ]
7430
- },
7431
7419
  {
7432
7420
  "name": "tenant_permission_overrides_permission_id_idx",
7433
7421
  "columns": [
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionCatalogService.d.ts","sourceRoot":"","sources":["../../src/services/PermissionCatalogService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAQlC,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,wBAAwB,GAChC,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAC;AAEb,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,SAAS,CAAC,wBAAwB,CAAC,GAAG,wBAAwB,CAAC;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,yBAAyB,EAAE,CAAC;KACxC,CAAC;IACF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,uBAAuB,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,EAAE,oBAAoB,EAAE,CAAC;IAC1C,mBAAmB,EAAE,oBAAoB,EAAE,CAAC;IAC5C,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,kBAAkB,EAAE,oBAAoB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,iBAAiB,CAAC;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAY,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;QAChC,QAAQ,CAAC,EAAE;YACT,QAAQ,CAAC,EAAE,yBAAyB,EAAE,CAAC;YACvC,OAAO,CAAC,EAAE,OAAO,CAAC;SACnB,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,CAAC,EAAE,MAAM,eAAe,CAAC;CACtC;AAED,MAAM,MAAM,kCAAkC,GAC1C,MAAM,GACN,eAAe,GACf,cAAc,GACd,MAAM,CAAC;AAEX,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,kCAAkC,EAClC,GAAG,CAAC,MAAM,EAAE,oBAAoB,EAAE,CAAC,GACnC,SAAS,CAAC;IAEd,IAAI,wCAAwC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClE;AAsED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAkBzE;AAmCD,wBAAgB,uCAAuC,CACrD,KAAK,EAAE,kCAAkC,GACxC,MAAM,CAiCR;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,kCAAkC,EAC9C,MAAM,EAAE,MAAM,GACb,MAAM,CAQR;AAkdD,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,oBAAoB,EAAE,GAClC,MAAM,IAAI,CASZ;AAED,qBAAa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,gBAAqB;IAE3D,cAAc,IAAI,WAAW;IAI7B,+BAA+B,IAAI,oBAAoB,EAAE;IAIzD,8BAA8B,IAAI,oBAAoB,EAAE;IAIxD,UAAU,IAAI,iBAAiB;IA0B/B,6BAA6B,CAC3B,UAAU,EAAE,kCAAkC,EAC9C,MAAM,EAAE,MAAM,GACb,MAAM;IAIT,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAMlC,qBAAqB,IAAI,OAAO,CAAC,2BAA2B,CAAC;IA0EnE,OAAO,CAAC,gCAAgC;IAgJxC,MAAM,CAAC,MAAM,CAAC,OAAO,GAAE,gBAAqB,GAAG,wBAAwB;CAGxE;AAED,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,2BAA2B,CAAC,CAEtC"}
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.51.39",
6
+ "packageVersion": "0.53.0",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "19ba052694414c0a46e25dc2500c99a39886ac821a02b5ceb7ad5e900a528f1c",
11
- "packageJson": "1a986f013f79d7f2bd0da8ea4d29d258a57489c8240695bf676b3b37bded4a13",
10
+ "manifest": "8a2a473838ea0348c03da150f0833df98f6549a15bc1edbb87f5953b92f60495",
11
+ "packageJson": "6c2f7c78278561277194b9e2f934ddca20ea3821f6c4cec7eadc0ab8f9ce2414",
12
12
  "agents": "c1bd1dcae5f1662f31d01d0cff19cb3f774715eb71ecfd249eb429e5f64a3eb7",
13
- "moduleDoc:agents/permissions.md": "b5be3f5b84b409cb6e62080e2d7a75b67e0763266c11054045c5c401b84f8dd6",
13
+ "moduleDoc:agents/permissions.md": "6797e8c1cac7f1af34611b7b0a7a53e61806cab250ea1afb1a820356b7d2b233",
14
14
  "moduleDoc:agents/oidc-provisioning.md": "5efa607ff23b9a693391e0c02670324a8cea4114d4968c9718f10a763da919c6",
15
15
  "moduleDoc:agents/mobile-auth.md": "25e85e83f43e3a46383a92fa809f3a003b4545c3026e78dbe1bccbbc35161e18",
16
16
  "moduleDoc:agents/terminal-auth-testing.md": "8a84c16a19e381427aed81d3b1882344c427fe22a70e87f5ac2db836b1a6822d",
@@ -41,7 +41,7 @@
41
41
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
42
42
  "@types/node": "24.13.6",
43
43
  "fast-glob": "^3.3.3",
44
- "svelte": "^5.57.1",
44
+ "svelte": "^5.57.0",
45
45
  "svelte-check": "^4.7.6",
46
46
  "typescript": "6.0.3",
47
47
  "vite": "8.1.4",
@@ -4313,6 +4313,11 @@
4313
4313
  }
4314
4314
  ],
4315
4315
  "methods": [],
4316
+ "conflictColumns": [
4317
+ "tenant_id",
4318
+ "slug",
4319
+ "context"
4320
+ ],
4316
4321
  "surfaces": [],
4317
4322
  "relationshipFeatures": [
4318
4323
  "foreignKey",
@@ -4361,6 +4366,11 @@
4361
4366
  }
4362
4367
  ],
4363
4368
  "methods": [],
4369
+ "conflictColumns": [
4370
+ "tenant_id",
4371
+ "slug",
4372
+ "context"
4373
+ ],
4364
4374
  "surfaces": [
4365
4375
  {
4366
4376
  "kind": "api",
@@ -4832,6 +4842,11 @@
4832
4842
  "returns": "boolean"
4833
4843
  }
4834
4844
  ],
4845
+ "conflictColumns": [
4846
+ "tenant_id",
4847
+ "slug",
4848
+ "context"
4849
+ ],
4835
4850
  "surfaces": [
4836
4851
  {
4837
4852
  "kind": "api",
@@ -5497,6 +5512,11 @@
5497
5512
  "returns": "boolean"
5498
5513
  }
5499
5514
  ],
5515
+ "conflictColumns": [
5516
+ "tenant_id",
5517
+ "slug",
5518
+ "context"
5519
+ ],
5500
5520
  "surfaces": [
5501
5521
  {
5502
5522
  "kind": "api",
@@ -5870,6 +5890,11 @@
5870
5890
  "returns": "void"
5871
5891
  }
5872
5892
  ],
5893
+ "conflictColumns": [
5894
+ "tenant_id",
5895
+ "slug",
5896
+ "context"
5897
+ ],
5873
5898
  "surfaces": [
5874
5899
  {
5875
5900
  "kind": "api",
@@ -6712,6 +6737,11 @@
6712
6737
  "returns": "boolean"
6713
6738
  }
6714
6739
  ],
6740
+ "conflictColumns": [
6741
+ "tenant_id",
6742
+ "slug",
6743
+ "context"
6744
+ ],
6715
6745
  "surfaces": [
6716
6746
  {
6717
6747
  "kind": "api",
@@ -10561,7 +10591,7 @@
10561
10591
  {
10562
10592
  "path": "agents/permissions.md",
10563
10593
  "module": "permissions",
10564
- "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.\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\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"
10594
+ "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\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"
10565
10595
  },
10566
10596
  {
10567
10597
  "path": "agents/oidc-provisioning.md",
package/dist/sveltekit.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as OidcLoginService, D as resolveOidcProviderConfig, E as getUsersOidcConfig, S as OidcLoginError, T as encodeOidcTransaction, _ as readMobileBearerToken, a as TerminalAuthRateLimitError, g as MobileAuthService, h as MobileAuthError, i as TerminalAuthError, m as withSessionPermissionContext, o as TerminalAuthService, s as OperationPermissionError, v as validateMobileRedirectUri, w as decodeOidcTransaction, y as SessionService } from "./chunks/TerminalAuthService-BDKUbg4N.js";
1
+ import { C as OidcLoginService, D as resolveOidcProviderConfig, E as getUsersOidcConfig, S as OidcLoginError, T as encodeOidcTransaction, _ as readMobileBearerToken, a as TerminalAuthRateLimitError, g as MobileAuthService, h as MobileAuthError, i as TerminalAuthError, m as withSessionPermissionContext, o as TerminalAuthService, s as OperationPermissionError, v as validateMobileRedirectUri, w as decodeOidcTransaction, y as SessionService } from "./chunks/TerminalAuthService-D_UjTiR6.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";