@happyvertical/smrt-users 0.51.14 → 0.51.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -1
- package/README.md +66 -3
- package/dist/chunks/{TerminalAuthService-DgAU4Fz1.js → TerminalAuthService-BBxkYCOG.js} +353 -120
- package/dist/chunks/TerminalAuthService-BBxkYCOG.js.map +1 -0
- package/dist/chunks/types-DFqeph0R.js +39 -0
- package/dist/chunks/types-DFqeph0R.js.map +1 -0
- package/dist/collections/TenantCollection.d.ts +19 -19
- package/dist/collections/TenantCollection.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -5
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +15 -15
- package/dist/migrations/materializeTenantHierarchy.d.ts +52 -0
- package/dist/migrations/materializeTenantHierarchy.d.ts.map +1 -0
- package/dist/models/Tenant.d.ts +40 -8
- package/dist/models/Tenant.d.ts.map +1 -1
- package/dist/models/tenant-hierarchy.d.ts +105 -0
- package/dist/models/tenant-hierarchy.d.ts.map +1 -0
- package/dist/services/PermissionResolver.d.ts +22 -0
- package/dist/services/PermissionResolver.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +24 -17
- package/dist/sveltekit.js +1 -1
- package/package.json +10 -10
- package/dist/chunks/TerminalAuthService-DgAU4Fz1.js.map +0 -1
package/dist/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": "1.0.0",
|
|
3
3
|
"timestamp": 0,
|
|
4
4
|
"packageName": "@happyvertical/smrt-users",
|
|
5
|
-
"packageVersion": "0.51.
|
|
5
|
+
"packageVersion": "0.51.16",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-users:AccessRequestCollection": {
|
|
8
8
|
"name": "accessrequestcollection",
|
|
@@ -3122,20 +3122,6 @@
|
|
|
3122
3122
|
"returnType": "any",
|
|
3123
3123
|
"isStatic": false,
|
|
3124
3124
|
"isPublic": true
|
|
3125
|
-
},
|
|
3126
|
-
"create": {
|
|
3127
|
-
"name": "create",
|
|
3128
|
-
"async": true,
|
|
3129
|
-
"parameters": [
|
|
3130
|
-
{
|
|
3131
|
-
"name": "options",
|
|
3132
|
-
"type": "SmrtCreateInput<Tenant>",
|
|
3133
|
-
"optional": false
|
|
3134
|
-
}
|
|
3135
|
-
],
|
|
3136
|
-
"returnType": "Promise<Tenant>",
|
|
3137
|
-
"isStatic": false,
|
|
3138
|
-
"isPublic": true
|
|
3139
3125
|
}
|
|
3140
3126
|
},
|
|
3141
3127
|
"decoratorConfig": {},
|
|
@@ -6798,6 +6784,20 @@
|
|
|
6798
6784
|
"isStatic": false,
|
|
6799
6785
|
"isPublic": true
|
|
6800
6786
|
},
|
|
6787
|
+
"save": {
|
|
6788
|
+
"name": "save",
|
|
6789
|
+
"async": true,
|
|
6790
|
+
"parameters": [
|
|
6791
|
+
{
|
|
6792
|
+
"name": "options",
|
|
6793
|
+
"type": "SmrtSaveOptions",
|
|
6794
|
+
"optional": true
|
|
6795
|
+
}
|
|
6796
|
+
],
|
|
6797
|
+
"returnType": "Promise<this>",
|
|
6798
|
+
"isStatic": false,
|
|
6799
|
+
"isPublic": true
|
|
6800
|
+
},
|
|
6801
6801
|
"getAncestorIds": {
|
|
6802
6802
|
"name": "getAncestorIds",
|
|
6803
6803
|
"async": false,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getDatabase } from '@happyvertical/sql';
|
|
2
|
+
import { TenantHierarchyPlan, TenantHierarchyProblem } from '../models/tenant-hierarchy.js';
|
|
3
|
+
type DatabaseInterface = Awaited<ReturnType<typeof getDatabase>>;
|
|
4
|
+
/** Options for {@link materializeTenantHierarchy}. */
|
|
5
|
+
export interface MaterializeTenantHierarchyOptions {
|
|
6
|
+
/** Report what would change without writing. Default `false`. */
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
/** The Tenant STI table. Default `'tenants'`. */
|
|
9
|
+
tableName?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Result of {@link materializeTenantHierarchy}. */
|
|
12
|
+
export interface MaterializeTenantHierarchyResult extends TenantHierarchyPlan {
|
|
13
|
+
/** `true` when the planned changes were written. */
|
|
14
|
+
applied: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Raised before any row is changed when a tenant's parent chain is broken
|
|
18
|
+
* (missing parent, cycle, or too deep), so an operator can repair
|
|
19
|
+
* `parent_tenant_id` explicitly instead of the backfill guessing at an
|
|
20
|
+
* authorization source.
|
|
21
|
+
*/
|
|
22
|
+
export declare class TenantHierarchyMaterializationError extends Error {
|
|
23
|
+
readonly problems: TenantHierarchyProblem[];
|
|
24
|
+
constructor(problems: TenantHierarchyProblem[]);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Backfill `hierarchy_path` / `hierarchy_level` for every tenant from its
|
|
28
|
+
* `parent_tenant_id` chain (smrt#3036).
|
|
29
|
+
*
|
|
30
|
+
* `Tenant.save()` keeps these current from now on; this repairs rows written
|
|
31
|
+
* before the framework maintained them — typically every row created through
|
|
32
|
+
* a path other than `TenantCollection.createChild()`, which left an empty path
|
|
33
|
+
* and level 0 even with a correct parent. Until repaired, both
|
|
34
|
+
* `inheritsToDescendants` and the declared ancestor-read policy fail closed on
|
|
35
|
+
* those tenants.
|
|
36
|
+
*
|
|
37
|
+
* Idempotent: it writes only rows that differ, so a second run reports zero
|
|
38
|
+
* changes. It refuses (writing nothing) when any tenant's chain is broken;
|
|
39
|
+
* `dryRun` reports the same plan, problems included, without throwing.
|
|
40
|
+
* Runs in one transaction and refuses an adapter without `transaction()`
|
|
41
|
+
* (dry runs excepted). Touches only the two
|
|
42
|
+
* derived columns (not `updated_at`) and records one table-level change-feed
|
|
43
|
+
* entry after commit.
|
|
44
|
+
*
|
|
45
|
+
* Exposed as `smrt db:materialize-tenant-hierarchy [--dry-run]`.
|
|
46
|
+
*
|
|
47
|
+
* @throws {TenantHierarchyMaterializationError} when not a dry run and any
|
|
48
|
+
* tenant's parent chain is broken.
|
|
49
|
+
*/
|
|
50
|
+
export declare function materializeTenantHierarchy(db: DatabaseInterface, options?: MaterializeTenantHierarchyOptions): Promise<MaterializeTenantHierarchyResult>;
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=materializeTenantHierarchy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materializeTenantHierarchy.d.ts","sourceRoot":"","sources":["../../src/migrations/materializeTenantHierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAML,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAE5B,MAAM,+BAA+B,CAAC;AAEvC,KAAK,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC;AAEjE,sDAAsD;AACtD,MAAM,WAAW,iCAAiC;IAChD,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,gCAAiC,SAAQ,mBAAmB;IAC3E,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,mCAAoC,SAAQ,KAAK;IAChD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE;gBAAlC,QAAQ,EAAE,sBAAsB,EAAE;CAOxD;AAoCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,iBAAiB,EACrB,OAAO,GAAE,iCAAsC,GAC9C,OAAO,CAAC,gCAAgC,CAAC,CAiC3C"}
|
package/dist/models/Tenant.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SmrtObject, SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
1
|
+
import { SmrtObject, SmrtObjectOptions, SmrtSaveOptions } from '@happyvertical/smrt-core';
|
|
2
2
|
import { Tenant as TenantContract } from '@happyvertical/smrt-types';
|
|
3
3
|
import { TenantStatus } from '../types/index.js';
|
|
4
4
|
/**
|
|
@@ -14,11 +14,7 @@ export interface TenantOptions extends SmrtObjectOptions {
|
|
|
14
14
|
cascadePermissions?: boolean;
|
|
15
15
|
inheritPermissions?: boolean;
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
* Maximum allowed depth for tenant hierarchy.
|
|
19
|
-
* Prevents excessively deep trees that could cause performance issues.
|
|
20
|
-
*/
|
|
21
|
-
export declare const MAX_TENANT_HIERARCHY_DEPTH = 10;
|
|
17
|
+
export { MAX_TENANT_HIERARCHY_DEPTH } from './tenant-hierarchy.js';
|
|
22
18
|
/**
|
|
23
19
|
* Tenant represents an organizational boundary in the multi-tenant system.
|
|
24
20
|
*
|
|
@@ -88,14 +84,18 @@ export declare class Tenant extends SmrtObject implements TenantContract {
|
|
|
88
84
|
parentTenantId?: string | null;
|
|
89
85
|
/**
|
|
90
86
|
* Depth in the hierarchy tree (0 = root, 1 = first level child, etc.)
|
|
91
|
-
*
|
|
87
|
+
* Derived from `parentTenantId` and maintained by {@link Tenant.save};
|
|
88
|
+
* any value assigned directly is recomputed on save.
|
|
92
89
|
*/
|
|
93
90
|
hierarchyLevel: number;
|
|
94
91
|
/**
|
|
95
92
|
* Materialized path for efficient tree traversal.
|
|
96
93
|
* Format: "ancestor-id/parent-id" (path to parent; does not include this tenant's id)
|
|
97
94
|
* Empty string for root tenants.
|
|
98
|
-
*
|
|
95
|
+
* Derived from `parentTenantId` and maintained by {@link Tenant.save};
|
|
96
|
+
* any value assigned directly is recomputed on save. Rows written before
|
|
97
|
+
* the framework maintained it are backfilled with
|
|
98
|
+
* `smrt db:materialize-tenant-hierarchy` (smrt#3036).
|
|
99
99
|
*/
|
|
100
100
|
hierarchyPath: string;
|
|
101
101
|
/**
|
|
@@ -141,6 +141,38 @@ export declare class Tenant extends SmrtObject implements TenantContract {
|
|
|
141
141
|
* for accurate permission calculation.
|
|
142
142
|
*/
|
|
143
143
|
acceptsInheritance(): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Persist the tenant, keeping the derived hierarchy fields true (smrt#3036).
|
|
146
|
+
*
|
|
147
|
+
* `hierarchyPath` / `hierarchyLevel` are recomputed from the real
|
|
148
|
+
* `parentTenantId` chain on EVERY save — whichever collection, subclass, or
|
|
149
|
+
* code path produced the object — so a consumer never hand-maintains them.
|
|
150
|
+
* When they change (a reparent, or healing a stale row), every descendant is
|
|
151
|
+
* re-materialized too. Both columns are an authorization source for
|
|
152
|
+
* `inheritsToDescendants` and the declared ancestor-read policy.
|
|
153
|
+
*
|
|
154
|
+
* @throws {TenantHierarchyError} before anything is written, when the new
|
|
155
|
+
* parent is missing, would create a cycle, or would put this tenant or one
|
|
156
|
+
* of its descendants at or below `MAX_TENANT_HIERARCHY_DEPTH`.
|
|
157
|
+
*/
|
|
158
|
+
save(options?: SmrtSaveOptions): Promise<this>;
|
|
159
|
+
/**
|
|
160
|
+
* Refuse to link this tenant under a parent the caller cannot see.
|
|
161
|
+
*
|
|
162
|
+
* A parent link is authority-bearing: ancestor tenant overrides cascade down
|
|
163
|
+
* it and `inheritsToDescendants` roles flow along it. The chain itself is
|
|
164
|
+
* resolved with raw reads (so paths can be derived for any row), so this is
|
|
165
|
+
* the check that keeps a NEW or CHANGED link subject to the caller's own
|
|
166
|
+
* tenancy scope, whichever path made it — `createChild`, `moveToParent`,
|
|
167
|
+
* `create({ parentTenantId })`, or a plain `parentTenantId` assignment and
|
|
168
|
+
* `save()`. The parent is loaded through `TenantCollection`, i.e. through
|
|
169
|
+
* the tenancy interceptor: a refused or filtered read fails the save before
|
|
170
|
+
* anything is written. System context (migrations, backfills, platform
|
|
171
|
+
* administration) bypasses the interceptor as everywhere else. An unchanged
|
|
172
|
+
* link is not re-checked, so a tenant can still save itself under its own
|
|
173
|
+
* scope when its parent is not visible there.
|
|
174
|
+
*/
|
|
175
|
+
private assertNewParentVisible;
|
|
144
176
|
/**
|
|
145
177
|
* Get ancestor IDs from the hierarchy path.
|
|
146
178
|
* Returns an array of tenant IDs from root to immediate parent.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tenant.d.ts","sourceRoot":"","sources":["../../src/models/Tenant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"Tenant.d.ts","sourceRoot":"","sources":["../../src/models/Tenant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAUjD;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBASa,MAAO,SAAQ,UAAW,YAAW,cAAc;IAC9D;;OAEG;IACH,IAAI,EAAE,MAAM,CAAM;IAElB;;OAEG;IAEH,MAAM,EAAE,YAAY,CAAuB;IAE3C;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAIzB;;;OAGG;IAEH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAK;IAE3B;;;;;;;;OAQG;IACH,aAAa,EAAE,MAAM,CAAM;IAI3B;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAQ;IAEnC;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAQ;gBAEvB,OAAO,GAAE,aAAkB;IAkBvC;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;;;;OAKG;IACH,oBAAoB,IAAI,OAAO;IAI/B;;;;;;;;;OASG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;;;;;;;;;;;;OAaG;IACY,IAAI,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuCjE;;;;;;;;;;;;;;;OAeG;YACW,sBAAsB;IAoCpC;;;;OAIG;IACH,cAAc,IAAI,MAAM,EAAE;CAI3B"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { bumpChangeFeed } from '@happyvertical/smrt-core';
|
|
2
|
+
/**
|
|
3
|
+
* Maximum allowed depth for tenant hierarchy.
|
|
4
|
+
* Prevents excessively deep trees that could cause performance issues.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MAX_TENANT_HIERARCHY_DEPTH = 10;
|
|
7
|
+
/** Reasons a tenant hierarchy cannot be (re)materialized. */
|
|
8
|
+
export type TenantHierarchyErrorCode = 'CIRCULAR_REFERENCE' | 'MAX_DEPTH_EXCEEDED' | 'PARENT_NOT_FOUND' | 'INVALID_OPERATION';
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when tenant hierarchy operations fail
|
|
11
|
+
*/
|
|
12
|
+
export declare class TenantHierarchyError extends Error {
|
|
13
|
+
readonly code: TenantHierarchyErrorCode;
|
|
14
|
+
constructor(message: string, code: TenantHierarchyErrorCode);
|
|
15
|
+
}
|
|
16
|
+
/** The derived hierarchy columns of one tenant row. */
|
|
17
|
+
export interface TenantHierarchyFields {
|
|
18
|
+
hierarchyPath: string;
|
|
19
|
+
hierarchyLevel: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The minimal database surface these helpers need — satisfied by every
|
|
23
|
+
* `@happyvertical/sql` adapter and transaction handle.
|
|
24
|
+
*/
|
|
25
|
+
export interface TenantHierarchyDatabase {
|
|
26
|
+
query: (sql: string, ...vars: unknown[]) => Promise<{
|
|
27
|
+
rows: Array<Record<string, unknown>>;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
/** Quote-free guard: the table name is interpolated, so it must be a plain identifier. */
|
|
31
|
+
export declare function assertTenantTableName(tableName: string): string;
|
|
32
|
+
/** The path a child of `parent` must carry. */
|
|
33
|
+
export declare function childHierarchyFields(parentId: string, parent: TenantHierarchyFields): TenantHierarchyFields;
|
|
34
|
+
/**
|
|
35
|
+
* Compute a tenant's hierarchy fields from the REAL `parentTenantId` chain,
|
|
36
|
+
* never from any ancestor's materialized path (which may itself be stale).
|
|
37
|
+
*
|
|
38
|
+
* @throws {TenantHierarchyError} `PARENT_NOT_FOUND` when a link is missing,
|
|
39
|
+
* `CIRCULAR_REFERENCE` when the chain loops or reaches `tenantId`,
|
|
40
|
+
* `MAX_DEPTH_EXCEEDED` when the tenant would sit at or below
|
|
41
|
+
* {@link MAX_TENANT_HIERARCHY_DEPTH}.
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeTenantHierarchyFields(db: TenantHierarchyDatabase, tableName: string, tenantId: string | null | undefined, parentTenantId: string | null | undefined): Promise<TenantHierarchyFields>;
|
|
44
|
+
/** One pending descendant rewrite. */
|
|
45
|
+
export interface TenantHierarchyUpdate extends TenantHierarchyFields {
|
|
46
|
+
id: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Plan the descendant rewrites implied by `rootId` now carrying `root`.
|
|
50
|
+
* Read-only; walks `parent_tenant_id` links breadth-first, so it works even
|
|
51
|
+
* when descendants were never materialized.
|
|
52
|
+
*
|
|
53
|
+
* @throws {TenantHierarchyError} `MAX_DEPTH_EXCEEDED` when a descendant would
|
|
54
|
+
* end up too deep, `CIRCULAR_REFERENCE` when the subtree loops.
|
|
55
|
+
*/
|
|
56
|
+
export declare function planDescendantHierarchy(db: TenantHierarchyDatabase, tableName: string, rootId: string, root: TenantHierarchyFields): Promise<TenantHierarchyUpdate[]>;
|
|
57
|
+
/** Write planned hierarchy fields. Touches only the two derived columns. */
|
|
58
|
+
export declare function applyTenantHierarchyUpdates(db: TenantHierarchyDatabase, tableName: string, updates: readonly TenantHierarchyUpdate[]): Promise<void>;
|
|
59
|
+
/** A stored tenant row, as the backfill planner sees it. */
|
|
60
|
+
export interface TenantHierarchyRow {
|
|
61
|
+
id: string;
|
|
62
|
+
parentTenantId: string | null;
|
|
63
|
+
hierarchyPath: string | null;
|
|
64
|
+
hierarchyLevel: number | null;
|
|
65
|
+
}
|
|
66
|
+
/** A row the planner refuses to materialize, and why. */
|
|
67
|
+
export interface TenantHierarchyProblem {
|
|
68
|
+
id: string;
|
|
69
|
+
code: Exclude<TenantHierarchyErrorCode, 'INVALID_OPERATION'>;
|
|
70
|
+
message: string;
|
|
71
|
+
}
|
|
72
|
+
/** A single planned change: what is stored, and what it must become. */
|
|
73
|
+
export interface TenantHierarchyChange extends TenantHierarchyUpdate {
|
|
74
|
+
previousPath: string | null;
|
|
75
|
+
previousLevel: number | null;
|
|
76
|
+
}
|
|
77
|
+
/** Result of {@link planTenantHierarchy}. */
|
|
78
|
+
export interface TenantHierarchyPlan {
|
|
79
|
+
/** Rows examined. */
|
|
80
|
+
total: number;
|
|
81
|
+
/** Rows whose stored fields differ from the parent chain. */
|
|
82
|
+
changes: TenantHierarchyChange[];
|
|
83
|
+
/** Rows whose parent chain is broken; nothing is written while any exist. */
|
|
84
|
+
problems: TenantHierarchyProblem[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Pure planner: compute every tenant's hierarchy fields from the
|
|
88
|
+
* `parentTenantId` graph of `rows` and report what differs. Rows whose chain
|
|
89
|
+
* is broken (missing parent, cycle, too deep) are reported as problems rather
|
|
90
|
+
* than guessed at, because the result is an authorization source.
|
|
91
|
+
*/
|
|
92
|
+
export declare function planTenantHierarchy(rows: readonly TenantHierarchyRow[]): TenantHierarchyPlan;
|
|
93
|
+
/**
|
|
94
|
+
* Record raw hierarchy rewrites in the change feed through core's documented
|
|
95
|
+
* out-of-band escape hatch, so feed consumers observe that a row's ancestry
|
|
96
|
+
* changed. `rowIds` omitted records one table-level change.
|
|
97
|
+
*
|
|
98
|
+
* Mirrors the framework writer's failure policy: the write already succeeded
|
|
99
|
+
* and must not be un-succeeded by feed bookkeeping, so failures are logged and
|
|
100
|
+
* swallowed. `updated_at` is deliberately NOT bumped — the two columns are
|
|
101
|
+
* derived, and every `Tenant.save()` recomputes them, so a concurrent holder
|
|
102
|
+
* of a stale instance heals rather than conflicts.
|
|
103
|
+
*/
|
|
104
|
+
export declare function recordTenantHierarchyChanges(db: Parameters<typeof bumpChangeFeed>[0], tableName: string, rowIds?: readonly string[]): Promise<void>;
|
|
105
|
+
//# sourceMappingURL=tenant-hierarchy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-hierarchy.d.ts","sourceRoot":"","sources":["../../src/models/tenant-hierarchy.ts"],"names":[],"mappings":"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"}
|
|
@@ -196,6 +196,27 @@ export declare class PermissionResolver {
|
|
|
196
196
|
* 4. Return the final effective permission set
|
|
197
197
|
*/
|
|
198
198
|
resolveTenantPermissions(tenantId: string): Promise<TenantPermissionInheritanceResult>;
|
|
199
|
+
private resolveTenantPermissionsInternal;
|
|
200
|
+
/**
|
|
201
|
+
* The tenant's ancestors, root first, proven against real `parentTenantId`
|
|
202
|
+
* links (smrt#3036).
|
|
203
|
+
*
|
|
204
|
+
* The materialized `hierarchyPath` is used when it agrees link-by-link with
|
|
205
|
+
* the parent chain. When it does not — a legacy row with a correct parent
|
|
206
|
+
* and an empty path, or a stale or forged path — the chain is rebuilt by
|
|
207
|
+
* walking `parentTenantId`, so a path can never make an unrelated tenant's
|
|
208
|
+
* overrides cascade in, and a never-materialized row still receives its
|
|
209
|
+
* real ancestors' DENYs. A parent chain that is itself broken (missing
|
|
210
|
+
* parent, cycle, or deeper than `MAX_TENANT_HIERARCHY_DEPTH`) throws
|
|
211
|
+
* {@link TenantHierarchyError}: the resolution fails closed rather than
|
|
212
|
+
* guessing which overrides apply.
|
|
213
|
+
*
|
|
214
|
+
* `unreadableAncestor: 'truncate'` is for callers outside system context
|
|
215
|
+
* (the display chain), where an ancestor the caller cannot read is
|
|
216
|
+
* indistinguishable from a missing one: the chain then ends at the nearest
|
|
217
|
+
* readable ancestor instead of throwing `PARENT_NOT_FOUND`.
|
|
218
|
+
*/
|
|
219
|
+
private loadVerifiedAncestorChain;
|
|
199
220
|
/**
|
|
200
221
|
* Get the inheritance chain for a tenant (for debugging/display purposes)
|
|
201
222
|
*/
|
|
@@ -245,6 +266,7 @@ export declare class PermissionResolver {
|
|
|
245
266
|
* 6. Subtract membership DENY overrides (absolute precedence)
|
|
246
267
|
*/
|
|
247
268
|
resolvePermissions(userId: string, tenantId: string, options?: PermissionResolutionOptions): Promise<PermissionResolutionResult>;
|
|
269
|
+
private resolvePermissionsInternal;
|
|
248
270
|
/**
|
|
249
271
|
* The permission slugs a role grants through the role-permission catalog,
|
|
250
272
|
* excluding every per-tenant, per-group, and per-membership override.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PermissionResolver.d.ts","sourceRoot":"","sources":["../../src/services/PermissionResolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"PermissionResolver.d.ts","sourceRoot":"","sources":["../../src/services/PermissionResolver.ts"],"names":[],"mappings":"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"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -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.
|
|
6
|
+
"packageVersion": "0.51.16",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
13
|
-
"moduleDoc:agents/permissions.md": "
|
|
10
|
+
"manifest": "b6882674446eda5d6f38704b8029ac78a24cd2daadaf0f35a62629c56a103a2e",
|
|
11
|
+
"packageJson": "1cc836cd453f30426095c1d379325bffef78c8c0470f7de232f613d6e119af0f",
|
|
12
|
+
"agents": "d7f1bb57f7bbbc1b750ab87c33592a53622236d17f49f8c04b15c4745b5c2ab2",
|
|
13
|
+
"moduleDoc:agents/permissions.md": "9d061473b4da77aa853a34d7adf1e4cfbaf5984a3ba3f7408df91db75e9e0c73",
|
|
14
14
|
"moduleDoc:agents/oidc-provisioning.md": "5efa607ff23b9a693391e0c02670324a8cea4114d4968c9718f10a763da919c6",
|
|
15
15
|
"moduleDoc:agents/mobile-auth.md": "25e85e83f43e3a46383a92fa809f3a003b4545c3026e78dbe1bccbbc35161e18",
|
|
16
16
|
"moduleDoc:agents/retention.md": "425c0a34ed33969badfef699751bfa670bdf618eda65e079e00815620aabed68"
|
|
@@ -2669,7 +2669,6 @@
|
|
|
2669
2669
|
"fields": [],
|
|
2670
2670
|
"relationships": [],
|
|
2671
2671
|
"methods": [
|
|
2672
|
-
"create",
|
|
2673
2672
|
"createChild",
|
|
2674
2673
|
"findActive",
|
|
2675
2674
|
"findBySlug",
|
|
@@ -2689,14 +2688,6 @@
|
|
|
2689
2688
|
"validateHierarchy"
|
|
2690
2689
|
],
|
|
2691
2690
|
"methodSignatures": [
|
|
2692
|
-
{
|
|
2693
|
-
"name": "create",
|
|
2694
|
-
"async": true,
|
|
2695
|
-
"params": [
|
|
2696
|
-
"options: SmrtCreateInput<Tenant>"
|
|
2697
|
-
],
|
|
2698
|
-
"returns": "Promise<Tenant>"
|
|
2699
|
-
},
|
|
2700
2691
|
{
|
|
2701
2692
|
"name": "createChild",
|
|
2702
2693
|
"async": true,
|
|
@@ -6169,7 +6160,8 @@
|
|
|
6169
6160
|
"getAncestorIds",
|
|
6170
6161
|
"isActive",
|
|
6171
6162
|
"isRoot",
|
|
6172
|
-
"isSuspended"
|
|
6163
|
+
"isSuspended",
|
|
6164
|
+
"save"
|
|
6173
6165
|
],
|
|
6174
6166
|
"methodSignatures": [
|
|
6175
6167
|
{
|
|
@@ -6195,6 +6187,14 @@
|
|
|
6195
6187
|
{
|
|
6196
6188
|
"name": "isSuspended",
|
|
6197
6189
|
"returns": "boolean"
|
|
6190
|
+
},
|
|
6191
|
+
{
|
|
6192
|
+
"name": "save",
|
|
6193
|
+
"async": true,
|
|
6194
|
+
"params": [
|
|
6195
|
+
"options?: SmrtSaveOptions"
|
|
6196
|
+
],
|
|
6197
|
+
"returns": "Promise<this>"
|
|
6198
6198
|
}
|
|
6199
6199
|
],
|
|
6200
6200
|
"tableStrategy": "sti",
|
|
@@ -6336,6 +6336,13 @@
|
|
|
6336
6336
|
"code": "not-included",
|
|
6337
6337
|
"reason": "not listed in api.include",
|
|
6338
6338
|
"objectName": "@happyvertical/smrt-users:Tenant"
|
|
6339
|
+
},
|
|
6340
|
+
{
|
|
6341
|
+
"kind": "api",
|
|
6342
|
+
"operation": "save",
|
|
6343
|
+
"code": "lifecycle-method",
|
|
6344
|
+
"reason": "`save` is a framework lifecycle method, not a distinct operation",
|
|
6345
|
+
"objectName": "@happyvertical/smrt-users:Tenant"
|
|
6339
6346
|
}
|
|
6340
6347
|
],
|
|
6341
6348
|
"relationshipFeatures": [
|
|
@@ -10548,12 +10555,12 @@
|
|
|
10548
10555
|
"polymorphicAssociations": 0,
|
|
10549
10556
|
"uuidColumns": 90
|
|
10550
10557
|
},
|
|
10551
|
-
"agentDoc": "# @happyvertical/smrt-users\n\nMulti-tenant identity, RBAC, hierarchical tenants, sessions, and SvelteKit auth.\n\n## Modules\n\nRead only the module relevant to the change; protocol and provisioning details\nare not prerequisites for unrelated user-package work.\n\n| Module | Scope | Module doc |\n|---|---|---|\n| `src/services/PermissionResolver.ts` | permission precedence, inherited memberships, guards, RLS, role seeding | [agents/permissions.md](agents/permissions.md) |\n| `src/services/OidcLoginService.ts`, collections and OIDC handlers | identity reconciliation, transaction boundaries, migration readiness | [agents/oidc-provisioning.md](agents/oidc-provisioning.md) |\n| `src/services/MobileAuthService.ts` | mobile handshake, bearer sessions, bootstrap extension boundary | [agents/mobile-auth.md](agents/mobile-auth.md) |\n| `src/retention.ts` | expired session/token/CLI-auth reaping and retention sweep wiring | [agents/retention.md](agents/retention.md) |\n\n## Models and authority\n\n- Users are global; tenant access comes through Membership (unique user/tenant).\n User email is normalized and globally unique through readonly nullable\n `emailKey`. `profileId` is a unique cross-package reference: at most one User\n owns each non-null Profile.\n- Tenant uses STI and a materialized hierarchy, maximum depth 10.\n `
|
|
10558
|
+
"agentDoc": "# @happyvertical/smrt-users\n\nMulti-tenant identity, RBAC, hierarchical tenants, sessions, and SvelteKit auth.\n\n## Modules\n\nRead only the module relevant to the change; protocol and provisioning details\nare not prerequisites for unrelated user-package work.\n\n| Module | Scope | Module doc |\n|---|---|---|\n| `src/services/PermissionResolver.ts` | permission precedence, inherited memberships, guards, RLS, role seeding | [agents/permissions.md](agents/permissions.md) |\n| `src/services/OidcLoginService.ts`, collections and OIDC handlers | identity reconciliation, transaction boundaries, migration readiness | [agents/oidc-provisioning.md](agents/oidc-provisioning.md) |\n| `src/services/MobileAuthService.ts` | mobile handshake, bearer sessions, bootstrap extension boundary | [agents/mobile-auth.md](agents/mobile-auth.md) |\n| `src/retention.ts` | expired session/token/CLI-auth reaping and retention sweep wiring | [agents/retention.md](agents/retention.md) |\n\n## Models and authority\n\n- Users are global; tenant access comes through Membership (unique user/tenant).\n User email is normalized and globally unique through readonly nullable\n `emailKey`. `profileId` is a unique cross-package reference: at most one User\n owns each non-null Profile.\n- Tenant uses STI and a materialized hierarchy, maximum depth 10.\n `hierarchyPath`/`hierarchyLevel` are derived from `parentTenantId` by\n `Tenant.save()` on every save (any caller-supplied value is recomputed) and\n descendants are re-materialized when they change; never hand-maintain them.\n Legacy rows are backfilled with `smrt db:materialize-tenant-hierarchy`\n (`materializeTenantHierarchy()`). Tests that need a corrupt path must write it\n with raw SQL (#3036).\n- Role with `tenantId = null` is available to all tenants; `isSystem` prevents\n deletion. `inheritsToDescendants` is opt-in. Seed owner/admin/member/viewer\n through `RoleCollection.seedSystemRoles()` at application initialization.\n- Group roles apply only in their own tenant. Use `getGroupIdsForTenant()`,\n never cross-tenant `getGroupIds()`, for authorization.\n- Upward visibility is opt-in and read-only: `permissions.ancestorRead`\n ({ roles, collections, maxDepth? }) lets a DESCENDANT membership contribute\n declared `<collection>.read` at an ancestor, only when no membership resolved\n there, intersected with BOTH the role's own catalog grants and the\n principal's effective permissions in that descendant, and only for SYSTEM\n roles (a tenant-scoped role sharing a declared slug is ignored). Off by default, never\n write, never lateral, and never row visibility — sibling rows stay scoped by\n tenancy/RLS. Read the permissions module before changing it.\n- Membership DENY always wins. Direct inactive membership blocks inherited\n authority; a direct active membership pins resolution instead of unioning it\n with ancestors. Read the permissions module before changing these rules.\n- AccessRequest has no generated API/MCP/CLI operations; access it through\n `AccessRequestService`. Its JSON field is `requestContext`, not reserved\n slug-scoping `context`.\n\n## Security boundaries\n\n- Generated REST/MCP operations on identity/RBAC models are list/get only.\n Route authentication does not authorize authority mutations, and these models\n are not tenant-scoped. Use permission-gated services or explicitly checked\n consumer handlers. CLI remains a local-operator surface. Preserve the\n registry assertions in `security-audit-1400.test.ts`.\n- Resource guards take the resource tenant, not the session tenant. Omit a\n session membership when targeting a different tenant; mismatches fail closed.\n `loadSessionContext().tenantAuthorization` must be checked by required-tenant\n consumers: null membership can represent inherited authority.\n- Sessions use secure UUIDs; TTL is seconds (default seven days). Access marks\n expired sessions EXPIRED. Magic-link tokens are single use.\n- Tenant switching verifies active membership before writing and rotates the\n session ID for non-null targets, revoking the old session. Persist the returned\n `SwitchTenantResult.sessionId`; `switchSessionTenant()` updates the cookie and\n preserves its security settings. Failed switches mutate nothing; null clears\n do not rotate. `SessionCollection.setSessionTenant()` is unguarded and must\n never receive an untrusted tenant ID.\n- OIDC provisioning is atomic and fail-closed. Preserve exact issuer/subject\n identifiers, claim-source email verification, unique global Person ownership,\n and transaction-bound reconciliation. Read the OIDC module and canonical\n scenario matrix before changing any provisioning path.\n\n## Entry points and validation\n\n`src/sveltekit/` owns `createSessionHandler`, `createSessionCookie`,\n`destroySessionCookie`, and `switchSessionTenant`. Use README integration examples\nrather than copying application setup into these instructions.\n\nFrom the repository root:\n\n```bash\npnpm --filter @happyvertical/smrt-users test\npnpm --filter @happyvertical/smrt-users typecheck\npnpm --filter @happyvertical/smrt-users test:postgres\n```\n\nStart with the relevant test file via `test -- src/__tests__/<file>.test.ts`.\nRun `test:postgres` for RLS, principal context, OIDC, or terminal-auth database\nchanges. `typecheck` includes Svelte accessibility checks; plain `tsc` is\ninsufficient. Follow root knowledge freshness checks before shipping.\n",
|
|
10552
10559
|
"moduleDocs": [
|
|
10553
10560
|
{
|
|
10554
10561
|
"path": "agents/permissions.md",
|
|
10555
10562
|
"module": "permissions",
|
|
10556
|
-
"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- createChild calculates paths/depth; moveToParent updates all descendants;\n getTree(rootId?) 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. 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\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"
|
|
10563
|
+
"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. 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\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"
|
|
10557
10564
|
},
|
|
10558
10565
|
{
|
|
10559
10566
|
"path": "agents/oidc-provisioning.md",
|
package/dist/sveltekit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as decodeOidcTransaction, E as resolveOidcProviderConfig, S as OidcLoginService, T as getUsersOidcConfig, _ 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 encodeOidcTransaction, x as OidcLoginError, y as SessionService } from "./chunks/TerminalAuthService-
|
|
1
|
+
import { C as decodeOidcTransaction, E as resolveOidcProviderConfig, S as OidcLoginService, T as getUsersOidcConfig, _ 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 encodeOidcTransaction, x as OidcLoginError, y as SessionService } from "./chunks/TerminalAuthService-BBxkYCOG.js";
|
|
2
2
|
import { DiscoveryArtifactValidationError, SMRT_APP_RESULT_CONTRACT, SMRT_APP_RESULT_SCHEMA, SMRT_APP_RESULT_VERSION, SMRT_DISCOVERY_CONFORMANCE_ARTIFACT_SCHEMA, SMRT_DISCOVERY_CONFORMANCE_SCHEMA, SMRT_DISCOVERY_CONFORMANCE_VERSION, SMRT_MCP_RESULT_METADATA_KEY, canonicalizeDiscoveryArtifact, createDiscoveryConformanceArtifact, deriveCommandRequirements, validateDiscoveryConformanceArtifact } from "./app-contract.js";
|
|
3
3
|
import { ObjectRegistry, createClassNamePredicate, resolveCustomActionMetadata, resolveEffectiveActionMetadata } from "@happyvertical/smrt-core";
|
|
4
4
|
import { createLogger } from "@happyvertical/logger";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-users",
|
|
3
|
-
"version": "0.51.
|
|
3
|
+
"version": "0.51.16",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Multi-tenant user management for the SMRT framework - users, tenants, roles, permissions, groups",
|
|
6
6
|
"type": "module",
|
|
@@ -47,18 +47,18 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@happyvertical/logger": "^0.89.12",
|
|
50
|
-
"@happyvertical/smrt-config": "0.51.
|
|
51
|
-
"@happyvertical/smrt-core": "0.51.
|
|
52
|
-
"@happyvertical/smrt-mobile-contract": "0.51.
|
|
53
|
-
"@happyvertical/smrt-profiles": "0.51.
|
|
54
|
-
"@happyvertical/smrt-tenancy": "0.51.
|
|
55
|
-
"@happyvertical/smrt-types": "0.51.
|
|
56
|
-
"@happyvertical/smrt-ui": "0.51.
|
|
50
|
+
"@happyvertical/smrt-config": "0.51.16",
|
|
51
|
+
"@happyvertical/smrt-core": "0.51.16",
|
|
52
|
+
"@happyvertical/smrt-mobile-contract": "0.51.16",
|
|
53
|
+
"@happyvertical/smrt-profiles": "0.51.16",
|
|
54
|
+
"@happyvertical/smrt-tenancy": "0.51.16",
|
|
55
|
+
"@happyvertical/smrt-types": "0.51.16",
|
|
56
|
+
"@happyvertical/smrt-ui": "0.51.16",
|
|
57
57
|
"@happyvertical/sql": "^0.89.12",
|
|
58
58
|
"jose": "^6.2.12"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@happyvertical/smrt-vitest": "0.51.
|
|
61
|
+
"@happyvertical/smrt-vitest": "0.51.16",
|
|
62
62
|
"@sveltejs/package": "^2.5.8",
|
|
63
63
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
64
64
|
"@types/node": "24.13.6",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"clean": "rm -rf dist",
|
|
97
97
|
"dev": "npm run build:watch",
|
|
98
98
|
"test": "vitest run",
|
|
99
|
-
"test:postgres": "node ../../scripts/run-with-ci-postgres.mjs -- pnpm exec vitest run src/__tests__/permission-postgres-rls.test.ts src/__tests__/principal-permission-context-postgres.test.ts src/__tests__/oidc-provisioning-postgres.test.ts src/__tests__/legacy-user-profile-backfill-postgres.test.ts src/__tests__/terminal-auth-postgres.test.ts src/__tests__/resource-grant-postgres.test.ts",
|
|
99
|
+
"test:postgres": "node ../../scripts/run-with-ci-postgres.mjs -- pnpm exec vitest run src/__tests__/permission-postgres-rls.test.ts src/__tests__/principal-permission-context-postgres.test.ts src/__tests__/oidc-provisioning-postgres.test.ts src/__tests__/legacy-user-profile-backfill-postgres.test.ts src/__tests__/terminal-auth-postgres.test.ts src/__tests__/resource-grant-postgres.test.ts src/__tests__/issue-3036-hierarchy-resolution-postgres.test.ts",
|
|
100
100
|
"typecheck": "tsc --noEmit && node ../../scripts/svelte-check-a11y.mjs --tsconfig ./tsconfig.svelte.json",
|
|
101
101
|
"check": "pnpm exec svelte-check --tsconfig ./tsconfig.svelte.json",
|
|
102
102
|
"verify:pack": "node ../../scripts/verify-package-types-exports.js ."
|