@stratum-hq/lib 1.5.0 → 1.7.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.
Files changed (49) hide show
  1. package/README.md +22 -20
  2. package/dist/crypto.d.ts +9 -6
  3. package/dist/crypto.d.ts.map +1 -1
  4. package/dist/crypto.js +26 -18
  5. package/dist/crypto.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/migrate-schemas.js +1 -1
  9. package/dist/migrate-schemas.js.map +1 -1
  10. package/dist/migrations/001_init.sql +3 -3
  11. package/dist/migrations/031_subtree_read_scope.sql +307 -0
  12. package/dist/scoped-job.d.ts +9 -1
  13. package/dist/scoped-job.d.ts.map +1 -1
  14. package/dist/scoped-job.js +3 -2
  15. package/dist/scoped-job.js.map +1 -1
  16. package/dist/services/abac-service.js +1 -1
  17. package/dist/services/abac-service.js.map +1 -1
  18. package/dist/services/api-key-service.d.ts +1 -1
  19. package/dist/services/api-key-service.d.ts.map +1 -1
  20. package/dist/services/config-service.js +5 -5
  21. package/dist/services/config-service.js.map +1 -1
  22. package/dist/services/event-service.js +2 -2
  23. package/dist/services/event-service.js.map +1 -1
  24. package/dist/services/key-rotation-service.d.ts +18 -4
  25. package/dist/services/key-rotation-service.d.ts.map +1 -1
  26. package/dist/services/key-rotation-service.js +30 -10
  27. package/dist/services/key-rotation-service.js.map +1 -1
  28. package/dist/services/permission-service.js +1 -1
  29. package/dist/services/permission-service.js.map +1 -1
  30. package/dist/services/rate-limit-service.d.ts +3 -3
  31. package/dist/services/region-service.js +1 -1
  32. package/dist/services/region-service.js.map +1 -1
  33. package/dist/services/retention-service.d.ts +2 -2
  34. package/dist/services/retention-service.js +2 -2
  35. package/dist/services/role-service.d.ts +1 -1
  36. package/dist/services/role-service.js +1 -1
  37. package/dist/services/tenant-service.d.ts +2 -2
  38. package/dist/services/tenant-service.js +4 -4
  39. package/dist/services/tenant-service.js.map +1 -1
  40. package/dist/stratum.d.ts +5 -5
  41. package/dist/stratum.d.ts.map +1 -1
  42. package/dist/stratum.js +9 -9
  43. package/dist/stratum.js.map +1 -1
  44. package/dist/telemetry.d.ts +2 -2
  45. package/dist/telemetry.js +4 -4
  46. package/dist/telemetry.js.map +1 -1
  47. package/dist/url-redaction.js +1 -1
  48. package/dist/url-redaction.js.map +1 -1
  49. package/package.json +20 -12
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @stratum-hq/lib
2
2
 
3
- Framework-agnostic library for embedding [Stratum](https://github.com/stratum-hq/Stratum) directly in your Node.js app. Talks straight to PostgreSQL with no HTTP server in between — maximum performance for tenant operations.
3
+ Multi-tenancy for Node.js and PostgreSQL: tenant hierarchy, config inheritance, row-level security, ABAC, audit log, and GDPR tooling. This is the framework-agnostic core of [Stratum](https://github.com/stratum-hq/Stratum). Embed it directly in your Node.js app. It talks straight to PostgreSQL with no HTTP server in between.
4
+
5
+ Read the documentation at [docs.stratum-hq.org/packages/lib](https://docs.stratum-hq.org/packages/lib/).
4
6
 
5
7
  ## Installation
6
8
 
@@ -26,7 +28,7 @@ const customer = await stratum.createTenant({
26
28
  isolation_strategy: "SHARED_RLS",
27
29
  });
28
30
 
29
- // Config flows root → leaf — children inherit automatically
31
+ // Config flows root → leaf; children inherit automatically
30
32
  await stratum.setConfig(msp.id, "max_seats", { value: 500, locked: true });
31
33
  const config = await stratum.resolveConfig(customer.id);
32
34
  // → { max_seats: { value: 500, inherited: true, locked: true } }
@@ -34,19 +36,19 @@ const config = await stratum.resolveConfig(customer.id);
34
36
  const permissions = await stratum.resolvePermissions(customer.id);
35
37
  ```
36
38
 
37
- The `pool` is **borrowed, not owned** — Stratum never creates or closes it. With `autoMigrate: true`, `initialize()` runs the schema migrations on first start; leave it off and manage migrations yourself via `migrate`.
39
+ The `pool` is **borrowed, not owned**: Stratum never creates or closes it. With `autoMigrate: true`, `initialize()` runs the schema migrations on first start; leave it off and manage migrations yourself via `migrate`.
38
40
 
39
41
  ## API Summary
40
42
 
41
43
  The `Stratum` instance covers the full tenant lifecycle:
42
44
 
43
- - **Tenants** — `createTenant`, `getTenant`, `listTenants`, `updateTenant`, `moveTenant`, `getAncestors`, `getDescendants`, `batchCreateTenants`
44
- - **Config** — `resolveConfig`, `setConfig`, `deleteConfig`, `batchSetConfig`, `diffConfig`
45
- - **Permissions & ABAC** — `resolvePermissions`, `createPermission`, `createAbacPolicy`, `evaluateAbac`
46
- - **API keys & roles** — `createApiKey`, `validateApiKey`, `rotateApiKey`, `createRole`, `assignRoleToKey`
47
- - **Webhooks & audit** — `createWebhook`, `testWebhook`, `queryAuditLogs`, `listFailedDeliveries`
48
- - **GDPR & regions** — `exportTenantData`, `purgeTenant`, `grantConsent`, `createRegion`, `migrateRegion`
49
- - **Usage metering** — `recordUsage`, `aggregateUsage` (see [docs/usage-metering.md](../../docs/usage-metering.md))
45
+ - **Tenants**: `createTenant`, `getTenant`, `listTenants`, `updateTenant`, `moveTenant`, `getAncestors`, `getDescendants`, `batchCreateTenants`
46
+ - **Config**: `resolveConfig`, `setConfig`, `deleteConfig`, `batchSetConfig`, `diffConfig`
47
+ - **Permissions & ABAC**: `resolvePermissions`, `createPermission`, `createAbacPolicy`, `evaluateAbac`
48
+ - **API keys & roles**: `createApiKey`, `validateApiKey`, `rotateApiKey`, `createRole`, `assignRoleToKey`
49
+ - **Webhooks & audit**: `createWebhook`, `testWebhook`, `queryAuditLogs`, `listFailedDeliveries`
50
+ - **GDPR & regions**: `exportTenantData`, `purgeTenant`, `grantConsent`, `createRegion`, `migrateRegion`
51
+ - **Usage metering**: `recordUsage`, `aggregateUsage` (see [docs/usage-metering.md](../../docs/usage-metering.md))
50
52
 
51
53
  Low-level pool helpers are also exported:
52
54
 
@@ -64,11 +66,11 @@ await withTransaction(pool, async (client) => {
64
66
  establishes the tenant context in two layers for the whole duration of the job
65
67
  and tears both down when the job settles:
66
68
 
67
- - **AsyncLocalStorage** — the tenant is placed in the SDK's ALS store, so code
69
+ - **AsyncLocalStorage**: the tenant is placed in the SDK's ALS store, so code
68
70
  inside the job reads it through `Stratum.currentTenantId()` /
69
71
  `Stratum.currentTenantContext()` exactly as a request handler would. Each job
70
72
  gets its own store, so concurrent jobs cannot observe each other's tenant.
71
- - **Postgres row-level security** — the job runs through the data-plane
73
+ - **Postgres row-level security**: the job runs through the data-plane
72
74
  `withTenantContext` (`@stratum-hq/db-adapters`), which opens a transaction and
73
75
  issues `SET LOCAL app.current_tenant_id`. Every query the job makes on the
74
76
  provided client is confined to that tenant by RLS, so it cannot read or write
@@ -82,7 +84,7 @@ import { runScopedJob } from "@stratum-hq/lib";
82
84
  // `pool` must connect as a NON-superuser, NON-BYPASSRLS role, or RLS is a no-op.
83
85
  await runScopedJob(pool, tenantId, async (client) => {
84
86
  Stratum.currentTenantId(); // === tenantId
85
- // Confined to `tenantId` by RLS — no app-layer WHERE filter needed.
87
+ // Confined to `tenantId` by RLS; no app-layer WHERE filter needed.
86
88
  await client.query("SELECT * FROM invoices WHERE status = 'pending'");
87
89
  });
88
90
  ```
@@ -96,7 +98,7 @@ ADR 0001 (`docs/adr/0001-postgres-rls-defense-in-depth.md`).
96
98
 
97
99
  ## Rate Limiting
98
100
 
99
- `RateLimiter` is a standalone, storage-agnostic per-tenant rate-limiting primitive. It is not part of the `Stratum` facade (which is bound to a single `pg.Pool`) — the point is that its storage is pluggable. It ships with a process-local in-memory store and a documented `RateLimitStore` contract you can implement over Redis, Postgres, or anything else.
101
+ `RateLimiter` is a standalone, storage-agnostic per-tenant rate-limiting primitive. It is not part of the `Stratum` facade (which is bound to a single `pg.Pool`). The point is that its storage is pluggable. It ships with a process-local in-memory store and a documented `RateLimitStore` contract you can implement over Redis, Postgres, or anything else.
100
102
 
101
103
  > This is distinct from the HTTP-layer rate limiting in `@stratum-hq/control-plane`. Use this to embed per-tenant limits directly in an application.
102
104
 
@@ -115,7 +117,7 @@ const limiter = new RateLimiter({
115
117
  // one tenant's usage never affects another's.
116
118
  const res = await limiter.checkLimit(tenantId, "api");
117
119
  if (!res.allowed) {
118
- throw new Error(`rate limited — retry in ${res.retryAfter}s`);
120
+ throw new Error(`rate limited, retry in ${res.retryAfter}s`);
119
121
  }
120
122
  // res: { allowed, limit, remaining, resetAt, retryAfter }
121
123
  ```
@@ -130,7 +132,7 @@ For each tenant the limit is resolved in order:
130
132
  2. the static `limits[tenantId]` override,
131
133
  3. `defaultLimit`.
132
134
 
133
- `resolveLimit` is the seam for **config inheritance** — back it with `stratum.resolveConfig` to drive limits from the tenant config tree:
135
+ `resolveLimit` is the seam for **config inheritance**: back it with `stratum.resolveConfig` to drive limits from the tenant config tree:
134
136
 
135
137
  ```typescript
136
138
  const limiter = new RateLimiter({
@@ -162,9 +164,9 @@ interface RateLimitStore {
162
164
 
163
165
  Implementation notes:
164
166
 
165
- - **Redis** — `INCR key`, then on a reply of `1`, `PEXPIRE key windowMs`; derive `resetAt` from `now + PTTL`. A short Lua script keeps it atomic and returns count + TTL in one round trip.
166
- - **Postgres** — `INSERT ... ON CONFLICT (key) DO UPDATE` returning the new count, resetting the row when `reset_at` has passed.
167
- - **In-memory** — `MemoryRateLimitStore`; single-process only, holds one entry per distinct `tenantId:key` pair. For high key cardinality or multi-process deployments, use a store with native TTL eviction.
167
+ - **Redis**: `INCR key`, then on a reply of `1`, `PEXPIRE key windowMs`; derive `resetAt` from `now + PTTL`. A short Lua script keeps it atomic and returns count + TTL in one round trip.
168
+ - **Postgres**: `INSERT ... ON CONFLICT (key) DO UPDATE` returning the new count, resetting the row when `reset_at` has passed.
169
+ - **In-memory**: `MemoryRateLimitStore`; single-process only, holds one entry per distinct `tenantId:key` pair. For high key cardinality or multi-process deployments, use a store with native TTL eviction.
168
170
 
169
171
  ## Error Handling
170
172
 
@@ -177,7 +179,7 @@ try {
177
179
  await stratum.setConfig(childId, "max_seats", { value: 999 });
178
180
  } catch (err) {
179
181
  if (err instanceof ConfigLockedError) {
180
- // A parent locked this key — child cannot override
182
+ // A parent locked this key, so the child cannot override
181
183
  }
182
184
  }
183
185
  ```
package/dist/crypto.d.ts CHANGED
@@ -1,13 +1,16 @@
1
1
  /** Encrypts a value. Returns versioned format: v1:iv:authTag:ciphertext (all hex) */
2
2
  export declare function encrypt(plaintext: string): string;
3
3
  /** Decrypts a versioned encrypted value. Supports v1 format and legacy (no version prefix).
4
- * Falls back to STRATUM_ENCRYPTION_KEY_PREVIOUS if primary key decryption fails,
5
- * enabling dual-key reads during rolling deployments. */
4
+ * If the current key and salt fail, it tries the previous pair: STRATUM_ENCRYPTION_KEY_PREVIOUS
5
+ * and STRATUM_HKDF_SALT_PREVIOUS. A previous variable that is unset uses the current value.
6
+ * This keeps values readable during a rolling deployment and until a rotation finishes. */
6
7
  export declare function decrypt(encrypted: string): string;
7
- /** Encrypts a value with the key derived from `keyMaterial`.
8
+ /** Encrypts a value with the key derived from `keyMaterial` and `saltHex`.
9
+ * If `saltHex` is not given, the configured salt applies.
8
10
  * Key rotation uses it because it must not read or change process.env. */
9
- export declare function encryptWithKeyMaterial(plaintext: string, keyMaterial: string): string;
10
- /** Returns the plaintext, or null when the value does not decrypt with the key derived from `keyMaterial`.
11
+ export declare function encryptWithKeyMaterial(plaintext: string, keyMaterial: string, saltHex?: string): string;
12
+ /** Returns the plaintext, or null when the value does not decrypt with the key derived from `keyMaterial` and `saltHex`.
13
+ * If `saltHex` is not given, the configured salt applies.
11
14
  * Key rotation uses the null result to try the next key instead of failing. */
12
- export declare function decryptWithKeyMaterial(encrypted: string, keyMaterial: string): string | null;
15
+ export declare function decryptWithKeyMaterial(encrypted: string, keyMaterial: string, saltHex?: string): string | null;
13
16
  //# sourceMappingURL=crypto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAoFA,qFAAqF;AACrF,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;yDAEyD;AACzD,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAcjD;AAED;0EAC0E;AAC1E,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAErF;AAED;+EAC+E;AAC/E,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5F"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAyFA,qFAAqF;AACrF,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;2FAG2F;AAC3F,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAejD;AAED;;0EAE0E;AAC1E,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvG;AAED;;+EAE+E;AAC/E,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM9G"}
package/dist/crypto.js CHANGED
@@ -35,23 +35,27 @@ const HKDF_SALT = (() => {
35
35
  }
36
36
  return Buffer.from(NON_PRODUCTION_DEFAULT_SALT, "utf8");
37
37
  })();
38
- function hkdfDeriveKey(keyMaterial, info = "stratum-aes-key") {
39
- return Buffer.from(node_crypto_1.default.hkdfSync("sha256", Buffer.from(keyMaterial, "utf8"), HKDF_SALT, info, 32));
38
+ function hkdfDeriveKey(keyMaterial, salt = HKDF_SALT, info = "stratum-aes-key") {
39
+ return Buffer.from(node_crypto_1.default.hkdfSync("sha256", Buffer.from(keyMaterial, "utf8"), salt, info, 32));
40
40
  }
41
- function getEncryptionKey() {
41
+ function getEncryptionKeyMaterial() {
42
42
  const envKey = process.env.STRATUM_ENCRYPTION_KEY ?? process.env.WEBHOOK_ENCRYPTION_KEY;
43
43
  if (envKey) {
44
- return hkdfDeriveKey(envKey);
44
+ return envKey;
45
45
  }
46
46
  const strictEnv = nodeEnvRequiringKeyMaterial();
47
47
  if (strictEnv) {
48
48
  throw new Error(`STRATUM_ENCRYPTION_KEY must be set in ${strictEnv}`);
49
49
  }
50
50
  // Development and test only fallback
51
- return hkdfDeriveKey("stratum-dev-key");
51
+ return "stratum-dev-key";
52
+ }
53
+ function getEncryptionKey() {
54
+ return hkdfDeriveKey(getEncryptionKeyMaterial());
52
55
  }
53
- function deriveKey(keyMaterial) {
54
- return hkdfDeriveKey(keyMaterial, "stratum-aes-key");
56
+ // A hex salt argument follows the STRATUM_HKDF_SALT format. No argument means the configured salt.
57
+ function deriveKey(keyMaterial, saltHex) {
58
+ return hkdfDeriveKey(keyMaterial, saltHex ? Buffer.from(saltHex, "hex") : HKDF_SALT);
55
59
  }
56
60
  function encryptWithKey(plaintext, key) {
57
61
  const iv = node_crypto_1.default.randomBytes(IV_LENGTH);
@@ -86,35 +90,39 @@ function encrypt(plaintext) {
86
90
  return encryptWithKey(plaintext, getEncryptionKey());
87
91
  }
88
92
  /** Decrypts a versioned encrypted value. Supports v1 format and legacy (no version prefix).
89
- * Falls back to STRATUM_ENCRYPTION_KEY_PREVIOUS if primary key decryption fails,
90
- * enabling dual-key reads during rolling deployments. */
93
+ * If the current key and salt fail, it tries the previous pair: STRATUM_ENCRYPTION_KEY_PREVIOUS
94
+ * and STRATUM_HKDF_SALT_PREVIOUS. A previous variable that is unset uses the current value.
95
+ * This keeps values readable during a rolling deployment and until a rotation finishes. */
91
96
  function decrypt(encrypted) {
92
97
  try {
93
98
  return decryptWithKey(encrypted, getEncryptionKey());
94
99
  }
95
100
  catch (err) {
96
101
  const previousKey = process.env.STRATUM_ENCRYPTION_KEY_PREVIOUS;
97
- if (previousKey) {
102
+ const previousSalt = process.env.STRATUM_HKDF_SALT_PREVIOUS;
103
+ if (previousKey || previousSalt) {
98
104
  try {
99
- return decryptWithKey(encrypted, hkdfDeriveKey(previousKey));
105
+ return decryptWithKey(encrypted, deriveKey(previousKey || getEncryptionKeyMaterial(), previousSalt));
100
106
  }
101
107
  catch {
102
- // Both keys failed — throw the original error
108
+ // Both pairs failed: throw the original error
103
109
  }
104
110
  }
105
111
  throw err;
106
112
  }
107
113
  }
108
- /** Encrypts a value with the key derived from `keyMaterial`.
114
+ /** Encrypts a value with the key derived from `keyMaterial` and `saltHex`.
115
+ * If `saltHex` is not given, the configured salt applies.
109
116
  * Key rotation uses it because it must not read or change process.env. */
110
- function encryptWithKeyMaterial(plaintext, keyMaterial) {
111
- return encryptWithKey(plaintext, deriveKey(keyMaterial));
117
+ function encryptWithKeyMaterial(plaintext, keyMaterial, saltHex) {
118
+ return encryptWithKey(plaintext, deriveKey(keyMaterial, saltHex));
112
119
  }
113
- /** Returns the plaintext, or null when the value does not decrypt with the key derived from `keyMaterial`.
120
+ /** Returns the plaintext, or null when the value does not decrypt with the key derived from `keyMaterial` and `saltHex`.
121
+ * If `saltHex` is not given, the configured salt applies.
114
122
  * Key rotation uses the null result to try the next key instead of failing. */
115
- function decryptWithKeyMaterial(encrypted, keyMaterial) {
123
+ function decryptWithKeyMaterial(encrypted, keyMaterial, saltHex) {
116
124
  try {
117
- return decryptWithKey(encrypted, deriveKey(keyMaterial));
125
+ return decryptWithKey(encrypted, deriveKey(keyMaterial, saltHex));
118
126
  }
119
127
  catch {
120
128
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export { RateLimiter, MemoryRateLimitStore } from "./services/rate-limit-service
12
12
  export type { RateLimit, RateLimitResult, RateLimitState, RateLimitStore, RateLimitResolver, RateLimiterOptions, } from "./services/rate-limit-service.js";
13
13
  export type { ApiKeyRecord, CreatedApiKey, ValidatedApiKey, CreateApiKeyOptions } from "./services/api-key-service.js";
14
14
  export type { BatchCreateResult } from "./services/tenant-service.js";
15
- export type { KeyRotationResult, KeyRotationUnreadableRow } from "./services/key-rotation-service.js";
15
+ export type { KeyRotationResult, KeyRotationSalts, KeyRotationUnreadableRow } from "./services/key-rotation-service.js";
16
16
  export type { DeliveryStats } from "./services/event-service.js";
17
17
  export type { Role, CreateRoleInput, UpdateRoleInput } from "./services/role-service.js";
18
18
  export type { AuditContext, AuditEntry, AuditLogQuery, RecordAuditEventInput } from "@stratum-hq/core";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iCAAiC,EACjC,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKrE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,EAC1B,uBAAuB,EACvB,iCAAiC,EACjC,qBAAqB,EACrB,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACrF,YAAY,EACV,SAAS,EACT,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACvH,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AACtG,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGzF,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAGvG,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iCAAiC,EACjC,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKrE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,EAC1B,uBAAuB,EACvB,iCAAiC,EACjC,qBAAqB,EACrB,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACrF,YAAY,EACV,SAAS,EACT,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACvH,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AACxH,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAGzF,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAGvG,YAAY,EACV,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -126,7 +126,7 @@ function hashSchemaLock(schema) {
126
126
  return (hash ^ 0x5354524d) | 0; // XOR with "STRM"
127
127
  }
128
128
  function quoteIdent(name) {
129
- // Simple identifier quoting — disallow anything that could break out
129
+ // Simple identifier quoting: disallow anything that could break out
130
130
  if (!/^[a-z_][a-z0-9_]*$/i.test(name)) {
131
131
  throw new Error(`Invalid schema name: ${name}`);
132
132
  }
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-schemas.js","sourceRoot":"","sources":["../src/migrate-schemas.ts"],"names":[],"mappings":";;;;;AAuBA,8CAgEC;AAvFD,sDAAyB;AACzB,0DAA6B;AAE7B,uDAA+C;AAc/C;;;;;GAKG;AACI,KAAK,UAAU,iBAAiB,CACrC,OAA8B;IAE9B,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAElE,uEAAuE;IACvE,2EAA2E;IAC3E,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAA,4BAAU,EAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CACjD,MAAM,CAAC,KAAK,CACV,gHAAgH,CACjH,CACF,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,MAAM,GAAyB,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAEnE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,4BAA4B;IAC5B,MAAM,aAAa,GAAG,mBAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC5D,IAAI,CAAC,iBAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,gCAAgC,aAAa,sDAAsD,CACpG,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,iBAAE;SACb,WAAW,CAAC,aAAa,CAAC;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;SACjC,IAAI,EAAE,CAAC;IAEV,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,iBAAE,CAAC,YAAY,CAAC,mBAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC;KAC9D,CAAC,CAAC,CAAC;IAEJ,kDAAkD;IAClD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,CAC7E,CAAC;QAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBACnC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;gBAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;oBACjB,MAAM;oBACN,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;iBAC3D,CAAC,CAAC;YACL,CAAC;YACD,SAAS,EAAE,CAAC;YACZ,UAAU,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,IAAa,EACb,MAAc,EACd,UAA2C,EAC3C,UAAoB;IAEpB,0EAA0E;IAC1E,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAElC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAE5B,sCAAsC;YACtC,MAAM,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;YAElE,yEAAyE;YACzE,uEAAuE;YACvE,6BAA6B;YAC7B,MAAM,MAAM,CAAC,KAAK,CAAC,2BAA2B,MAAM,UAAU,CAAC,CAAC;YAEhE,0EAA0E;YAC1E,qEAAqE;YACrE,yEAAyE;YACzE,MAAM,MAAM,CAAC,KAAK,CAAC;qCACY,MAAM;;;;;OAKpC,CAAC,CAAC;YAEH,2BAA2B;YAC3B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CACjC,iBAAiB,MAAM,8BAA8B,EACrD,CAAC,SAAS,CAAC,IAAI,CAAC,CACjB,CAAC;YACF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC7B,SAAS;YACX,CAAC;YAED,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAC7D,CAAC;YAED,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,MAAM,CAAC,KAAK,CAAC,eAAe,MAAM,iCAAiC,EAAE;gBACzE,SAAS,CAAC,IAAI;aACf,CAAC,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACjC,CAAC;YAAC,MAAM,CAAC;gBACP,6BAA6B;YAC/B,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,MAAc;IACpC,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,iEAAiE;IACjE,OAAO,CAAC,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB;AACpD,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,qEAAqE;IACrE,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,4EAA4E;IAC5E,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,IAAI,IAAI,GAAG,CAAC;AACrB,CAAC"}
1
+ {"version":3,"file":"migrate-schemas.js","sourceRoot":"","sources":["../src/migrate-schemas.ts"],"names":[],"mappings":"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"}
@@ -2,13 +2,13 @@
2
2
  DO $$ BEGIN
3
3
  CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
4
4
  EXCEPTION WHEN insufficient_privilege THEN
5
- RAISE WARNING 'Cannot create uuid-ossp extension — insufficient privileges. Ask a database administrator to run: CREATE EXTENSION IF NOT EXISTS "uuid-ossp";';
5
+ RAISE WARNING 'Cannot create uuid-ossp extension: insufficient privileges. Ask a database administrator to run: CREATE EXTENSION IF NOT EXISTS "uuid-ossp";';
6
6
  END $$;
7
7
 
8
8
  DO $$ BEGIN
9
9
  CREATE EXTENSION IF NOT EXISTS "ltree";
10
10
  EXCEPTION WHEN insufficient_privilege THEN
11
- RAISE WARNING 'Cannot create ltree extension — insufficient privileges. Ask a database administrator to run: CREATE EXTENSION IF NOT EXISTS "ltree";';
11
+ RAISE WARNING 'Cannot create ltree extension: insufficient privileges. Ask a database administrator to run: CREATE EXTENSION IF NOT EXISTS "ltree";';
12
12
  END $$;
13
13
 
14
14
  -- Warn if application role has BYPASSRLS privilege.
@@ -127,7 +127,7 @@ CREATE TRIGGER update_config_entries_updated_at
127
127
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
128
128
 
129
129
  -- Config entries uses ON DELETE CASCADE (unlike permission_policies which uses RESTRICT)
130
- -- because config values are safe to delete with a tenant — they carry no semantic guarantees
130
+ -- because config values are safe to delete with a tenant; they carry no semantic guarantees
131
131
  -- like PERMANENT revocation mode. If soft-delete is used (v1 default), CASCADE never fires.
132
132
 
133
133
  -- API keys table (for control plane auth)
@@ -0,0 +1,307 @@
1
+ -- Migration 031: Opt-in subtree read scope for row-level security.
2
+ --
3
+ -- A session scoped to tenant T can opt in to read the rows of T and of every
4
+ -- descendant of T. The default stays exact-tenant isolation (019, 020).
5
+ --
6
+ -- Context model, in addition to 019:
7
+ -- * Subtree scope: SET LOCAL app.tenant_scope = 'subtree' (transaction
8
+ -- scoped). Only the exact value 'subtree' widens reads. The library sets it
9
+ -- only through its context helpers (db-adapters setTenantContext and
10
+ -- withTenantContext with { scope: 'subtree' }).
11
+ --
12
+ -- Reads only. Each table below gets a second permissive policy, tenant_subtree_read,
13
+ -- FOR SELECT. PostgreSQL ORs it with tenant_isolation for reads. INSERT,
14
+ -- UPDATE and DELETE must also pass the policies for their own command, and
15
+ -- tenant_isolation is the only one. Writes therefore stay limited to the exact
16
+ -- tenant: an insert for a descendant fails WITH CHECK, and UPDATE and DELETE
17
+ -- do not reach a descendant's rows. SELECT ... FOR UPDATE and FOR SHARE also
18
+ -- need the UPDATE policy, so in subtree scope they return the exact tenant's
19
+ -- rows only. The 019 and 020 policies do not change.
20
+ --
21
+ -- Tables with a subtree read policy:
22
+ -- tenants (on id), config_entries (rows with sensitive = false only),
23
+ -- permission_policies, audit_logs, webhook_events, consent_records,
24
+ -- abac_policies, roles, usage_events, and, through their parent row,
25
+ -- webhook_deliveries (via webhook_events) and principal_roles (via roles).
26
+ --
27
+ -- Credential-bearing rows stay exact-tenant. api_keys and webhooks get no
28
+ -- subtree policy, and the config_entries policy admits no row with
29
+ -- sensitive = true. A key row holds a key hash, a webhook row holds a signing
30
+ -- secret hash and a URL that can carry credentials, and a sensitive config
31
+ -- row holds a secret value. Their 019 policies stay the only ones that admit
32
+ -- a tenant session, so only the exact tenant reads them. webhook_deliveries
33
+ -- is scoped through webhook_events, not webhooks, so it gives no subtree path
34
+ -- to a webhook row.
35
+ --
36
+ -- Tenant status does not affect the scope. Suspended, archived and pending
37
+ -- descendants are in the subtree, as exact-tenant RLS ignores status too.
38
+ -- Applications filter by status when they need to.
39
+ --
40
+ -- The bypass (app.bypass_rls = 'on') does not change: it sees every row.
41
+ --
42
+ -- The scope follows the tree as it is when the statement runs: a move through
43
+ -- moveTenant changes the subtree at once. The tree columns of tenants change
44
+ -- only under the bypass (see guard_tenant_tree_columns below), so a tenant
45
+ -- session cannot change its own position in the tree.
46
+ --
47
+ -- Cost: the function runs once per policy reference in a statement, and its
48
+ -- result grows with the size of the subtree. Each table then matches tenant_id
49
+ -- against that array, so it needs an index on tenant_id.
50
+
51
+ -- ---------------------------------------------------------------------------
52
+ -- stratum_subtree_tenant_ids(): the current tenant and all its descendants.
53
+ --
54
+ -- The subtree is selected by the ID-based ancestry_path, not by the
55
+ -- slug-derived ancestry_ltree, for the reason getDescendants gives in
56
+ -- tenant-service.ts: an isolation boundary must not follow slugs. A
57
+ -- descendant's ancestry_path is the subtree path itself or starts with it
58
+ -- plus '/'. The ~>=~ / ~<~ range lets idx_tenant_ancestry_path_prefix (028)
59
+ -- serve the lookup with a value known only at run time ('0' is the character
60
+ -- after '/'). The LIKE then drops what the range admits but is not a path
61
+ -- segment boundary.
62
+ --
63
+ -- The policies call the function in a scalar subquery, so PostgreSQL runs it
64
+ -- once per policy reference in a statement as an InitPlan and then matches
65
+ -- tenant_id against the array with the table's own index on tenant_id.
66
+ --
67
+ -- The function must read the whole subtree while the caller sees only its own
68
+ -- tenants row (019). It does what 029 does: SECURITY DEFINER would not help,
69
+ -- because FORCE ROW LEVEL SECURITY applies to the owner too, so a SET clause
70
+ -- turns on the 019 bypass for the function's own duration. A SET clause is
71
+ -- restored when the function exits, on error too. The second SET clause clears
72
+ -- app.tenant_scope inside the function, so the tenant_subtree_read policy on
73
+ -- tenants cannot call the function again while it runs.
74
+ --
75
+ -- The function returns only ids in the subtree of app.current_tenant_id. A
76
+ -- session that can call it can already set both settings, so it gives no new
77
+ -- access. With no current tenant it returns an empty array.
78
+ --
79
+ -- search_path is pinned below to pg_catalog, the schema this migration runs
80
+ -- in (where 001 created tenants), and pg_temp last, so that a table a session
81
+ -- creates cannot stand in for tenants.
82
+ -- ---------------------------------------------------------------------------
83
+ CREATE OR REPLACE FUNCTION stratum_subtree_tenant_ids()
84
+ RETURNS uuid[]
85
+ LANGUAGE sql
86
+ STABLE
87
+ SET app.bypass_rls = 'on'
88
+ SET app.tenant_scope = ''
89
+ AS $$
90
+ WITH cur AS (
91
+ SELECT t.id, rtrim(t.ancestry_path, '/') || '/' || t.id::text AS sub
92
+ FROM tenants t
93
+ WHERE t.id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid
94
+ )
95
+ SELECT coalesce(array_agg(s.id), '{}'::uuid[])
96
+ FROM (
97
+ SELECT cur.id FROM cur
98
+ UNION ALL
99
+ SELECT d.id
100
+ FROM cur
101
+ JOIN tenants d
102
+ ON d.ancestry_path ~>=~ cur.sub
103
+ AND d.ancestry_path ~<~ (cur.sub || '0')
104
+ AND (d.ancestry_path = cur.sub OR d.ancestry_path LIKE cur.sub || '/%')
105
+ ) s
106
+ $$;
107
+
108
+ DO $pin$ BEGIN
109
+ EXECUTE format(
110
+ 'ALTER FUNCTION stratum_subtree_tenant_ids() SET search_path = pg_catalog, %I, pg_temp',
111
+ current_schema()
112
+ );
113
+ END $pin$;
114
+
115
+ -- Reusable predicate (inlined per policy below, as in 019):
116
+ -- current_setting('app.tenant_scope', true) = 'subtree'
117
+ -- AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
118
+ -- The scope check comes first. With any other scope the AND stops there, so
119
+ -- the default path never runs the function.
120
+
121
+ -- ---------------------------------------------------------------------------
122
+ -- Direct tenant_id tables.
123
+ -- ---------------------------------------------------------------------------
124
+
125
+ -- config_entries: non-sensitive rows only. A sensitive row holds a secret
126
+ -- value, so only the exact tenant reads it (019).
127
+ DROP POLICY IF EXISTS tenant_subtree_read ON config_entries;
128
+ CREATE POLICY tenant_subtree_read ON config_entries FOR SELECT
129
+ USING (
130
+ current_setting('app.tenant_scope', true) = 'subtree'
131
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
132
+ AND NOT sensitive
133
+ );
134
+
135
+ DROP POLICY IF EXISTS tenant_subtree_read ON permission_policies;
136
+ CREATE POLICY tenant_subtree_read ON permission_policies FOR SELECT
137
+ USING (
138
+ current_setting('app.tenant_scope', true) = 'subtree'
139
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
140
+ );
141
+
142
+ DROP POLICY IF EXISTS tenant_subtree_read ON audit_logs;
143
+ CREATE POLICY tenant_subtree_read ON audit_logs FOR SELECT
144
+ USING (
145
+ current_setting('app.tenant_scope', true) = 'subtree'
146
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
147
+ );
148
+
149
+ DROP POLICY IF EXISTS tenant_subtree_read ON webhook_events;
150
+ CREATE POLICY tenant_subtree_read ON webhook_events FOR SELECT
151
+ USING (
152
+ current_setting('app.tenant_scope', true) = 'subtree'
153
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
154
+ );
155
+
156
+ -- webhooks: no subtree policy. A webhook row holds credential material, so
157
+ -- only the exact tenant reads it (019).
158
+ DROP POLICY IF EXISTS tenant_subtree_read ON webhooks;
159
+
160
+ DROP POLICY IF EXISTS tenant_subtree_read ON consent_records;
161
+ CREATE POLICY tenant_subtree_read ON consent_records FOR SELECT
162
+ USING (
163
+ current_setting('app.tenant_scope', true) = 'subtree'
164
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
165
+ );
166
+
167
+ DROP POLICY IF EXISTS tenant_subtree_read ON abac_policies;
168
+ CREATE POLICY tenant_subtree_read ON abac_policies FOR SELECT
169
+ USING (
170
+ current_setting('app.tenant_scope', true) = 'subtree'
171
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
172
+ );
173
+
174
+ DROP POLICY IF EXISTS tenant_subtree_read ON roles;
175
+ CREATE POLICY tenant_subtree_read ON roles FOR SELECT
176
+ USING (
177
+ current_setting('app.tenant_scope', true) = 'subtree'
178
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
179
+ );
180
+
181
+ DROP POLICY IF EXISTS tenant_subtree_read ON usage_events;
182
+ CREATE POLICY tenant_subtree_read ON usage_events FOR SELECT
183
+ USING (
184
+ current_setting('app.tenant_scope', true) = 'subtree'
185
+ AND tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
186
+ );
187
+
188
+ -- ---------------------------------------------------------------------------
189
+ -- tenants registry: the subtree's own registry rows, on id.
190
+ -- ---------------------------------------------------------------------------
191
+ DROP POLICY IF EXISTS tenant_subtree_read ON tenants;
192
+ CREATE POLICY tenant_subtree_read ON tenants FOR SELECT
193
+ USING (
194
+ current_setting('app.tenant_scope', true) = 'subtree'
195
+ AND id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
196
+ );
197
+
198
+ -- ---------------------------------------------------------------------------
199
+ -- Indirect tables: scoped through their tenant-bearing parent, as in 019.
200
+ -- ---------------------------------------------------------------------------
201
+
202
+ DROP POLICY IF EXISTS tenant_subtree_read ON webhook_deliveries;
203
+ CREATE POLICY tenant_subtree_read ON webhook_deliveries FOR SELECT
204
+ USING (
205
+ current_setting('app.tenant_scope', true) = 'subtree'
206
+ AND EXISTS (
207
+ SELECT 1 FROM webhook_events we
208
+ WHERE we.id = webhook_deliveries.event_id
209
+ AND we.tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
210
+ )
211
+ );
212
+
213
+ DROP POLICY IF EXISTS tenant_subtree_read ON principal_roles;
214
+ CREATE POLICY tenant_subtree_read ON principal_roles FOR SELECT
215
+ USING (
216
+ current_setting('app.tenant_scope', true) = 'subtree'
217
+ AND EXISTS (
218
+ SELECT 1 FROM roles r
219
+ WHERE r.id = principal_roles.role_id
220
+ AND r.tenant_id = ANY ((SELECT stratum_subtree_tenant_ids())::uuid[])
221
+ )
222
+ );
223
+
224
+ -- ---------------------------------------------------------------------------
225
+ -- Tree columns change only under the bypass.
226
+ --
227
+ -- A tenant session may update its own tenants row (019), and the subtree read
228
+ -- above follows parent_id and ancestry_path. This BEFORE trigger refuses any
229
+ -- change to parent_id, ancestry_path, depth or ancestry_ltree unless
230
+ -- app.bypass_rls = 'on'. The library changes these columns only in
231
+ -- createTenant, moveTenant and the other tree operations, which all run under
232
+ -- the bypass (pool-helpers.ts), and so do the migrations. A role that skips
233
+ -- RLS must also set the bypass to change them.
234
+ --
235
+ -- A slug rename stays allowed: the trigger is named to run before
236
+ -- maintain_tenant_ancestry_ltree (BEFORE triggers run in name order), so it
237
+ -- compares the ancestry_ltree the statement wrote, not the one that trigger
238
+ -- recomputes from the new slug.
239
+ -- ---------------------------------------------------------------------------
240
+ CREATE OR REPLACE FUNCTION refuse_tenant_tree_column_change()
241
+ RETURNS TRIGGER
242
+ AS $$
243
+ BEGIN
244
+ IF current_setting('app.bypass_rls', true) IS DISTINCT FROM 'on' AND (
245
+ NEW.parent_id IS DISTINCT FROM OLD.parent_id
246
+ OR NEW.ancestry_path IS DISTINCT FROM OLD.ancestry_path
247
+ OR NEW.depth IS DISTINCT FROM OLD.depth
248
+ OR NEW.ancestry_ltree IS DISTINCT FROM OLD.ancestry_ltree
249
+ ) THEN
250
+ RAISE EXCEPTION 'tenant % tree columns (parent_id, ancestry_path, depth, ancestry_ltree) change only under app.bypass_rls', OLD.id
251
+ USING ERRCODE = 'insufficient_privilege';
252
+ END IF;
253
+ RETURN NEW;
254
+ END;
255
+ $$ language 'plpgsql';
256
+
257
+ DO $pin$ BEGIN
258
+ EXECUTE format(
259
+ 'ALTER FUNCTION refuse_tenant_tree_column_change() SET search_path = pg_catalog, %I, pg_temp',
260
+ current_schema()
261
+ );
262
+ END $pin$;
263
+
264
+ DROP TRIGGER IF EXISTS guard_tenant_tree_columns ON tenants;
265
+ CREATE TRIGGER guard_tenant_tree_columns
266
+ BEFORE UPDATE OF parent_id, ancestry_path, depth, ancestry_ltree ON tenants
267
+ FOR EACH ROW
268
+ EXECUTE FUNCTION refuse_tenant_tree_column_change();
269
+
270
+ -- ---------------------------------------------------------------------------
271
+ -- refuse_tenant_parent_cycle() (029), with pg_temp last on its search_path.
272
+ --
273
+ -- The body is the one 029 created, unchanged. 029 pinned search_path to
274
+ -- pg_catalog and the migration schema; this adds pg_temp at the end, so the
275
+ -- cycle walk always reads the tenants table of that schema.
276
+ -- ---------------------------------------------------------------------------
277
+ CREATE OR REPLACE FUNCTION refuse_tenant_parent_cycle()
278
+ RETURNS TRIGGER
279
+ SET app.bypass_rls = 'on'
280
+ AS $$
281
+ BEGIN
282
+ IF NEW.parent_id = NEW.id OR EXISTS (
283
+ WITH RECURSIVE up(id, parent_id, seen) AS (
284
+ SELECT t.id, t.parent_id, ARRAY[t.id]
285
+ FROM tenants t
286
+ WHERE t.id = NEW.parent_id
287
+ UNION ALL
288
+ SELECT t.id, t.parent_id, up.seen || t.id
289
+ FROM tenants t
290
+ JOIN up ON t.id = up.parent_id
291
+ WHERE t.id <> ALL (up.seen)
292
+ )
293
+ SELECT 1 FROM up WHERE up.id = NEW.id
294
+ ) THEN
295
+ RAISE EXCEPTION 'tenant % cannot be its own ancestor (parent_id cycle)', NEW.id
296
+ USING ERRCODE = 'check_violation';
297
+ END IF;
298
+ RETURN NEW;
299
+ END;
300
+ $$ language 'plpgsql';
301
+
302
+ DO $pin$ BEGIN
303
+ EXECUTE format(
304
+ 'ALTER FUNCTION refuse_tenant_parent_cycle() SET search_path = pg_catalog, %I, pg_temp',
305
+ current_schema()
306
+ );
307
+ END $pin$;