@fuzdev/fuz_app 0.57.2 → 0.58.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.
@@ -76,15 +76,20 @@ Design notes:
76
76
 
77
77
  ## Schemas, types, and DDL
78
78
 
79
+ Convention — `*_schema.ts` is Zod-only; `*_ddl.ts` holds DDL constants and
80
+ index strings. Mixed modules split into a `_schema` + `_ddl` pair.
81
+
79
82
  | Module | What's inside |
80
83
  | ----------------------------------- | ----------------------------------------------------------------------------------------- |
81
84
  | `account_schema.ts` | Runtime types + client-safe Zod schemas for identity entities |
82
85
  | `role_schema.ts` | Role vocabulary and extensibility |
83
- | `ddl.ts` | Raw `CREATE TABLE` / index / seed SQL strings |
86
+ | `auth_ddl.ts` | Raw `CREATE TABLE` / index / seed SQL strings for the core identity tables |
84
87
  | `invite_schema.ts` | `Invite`, `InviteJson`, `InviteWithUsernamesJson`, `CreateInviteInput` |
85
88
  | `app_settings_schema.ts` | `AppSettings`, `AppSettingsJson`, `AppSettingsWithUsernameJson`, `UpdateAppSettingsInput` |
86
- | `audit_log_schema.ts` | Event-type enum, per-type metadata schemas, table DDL |
87
- | `role_grant_offer_schema.ts` | Role grant offer DDL, types, and client-safe schemas |
89
+ | `audit_log_schema.ts` | Event-type enum, per-type metadata schemas, client-safe Zod |
90
+ | `audit_log_ddl.ts` | `audit_log` table DDL + index strings |
91
+ | `role_grant_offer_schema.ts` | Role grant offer types and client-safe Zod |
92
+ | `role_grant_offer_ddl.ts` | `role_grant_offer` table DDL, indexes, and the index-side sentinel constants |
88
93
  | `role_grant_offer_notifications.ts` | WS notification specs for the consentful-role-grant lifecycle |
89
94
 
90
95
  ### Identity entities (`account_schema.ts`)
@@ -237,7 +242,7 @@ against the corresponding open registries at construction time.
237
242
  filter helpers used by `admin_actions` and
238
243
  `self_service_role_actions` to derive their default eligibility.
239
244
 
240
- ### Raw DDL (`ddl.ts`)
245
+ ### Raw DDL (`auth_ddl.ts`)
241
246
 
242
247
  Separated from runtime types to isolate DDL concerns. Consumed by
243
248
  `migrations.ts`:
@@ -262,7 +267,7 @@ Separated from runtime types to isolate DDL concerns. Consumed by
262
267
  - `APP_SETTINGS_SCHEMA`, `APP_SETTINGS_SEED` — single-row via
263
268
  `CHECK (id = 1)` constraint; seed is `ON CONFLICT DO NOTHING`.
264
269
 
265
- ### Audit log (`audit_log_schema.ts`)
270
+ ### Audit log (`audit_log_schema.ts` + `audit_log_ddl.ts`)
266
271
 
267
272
  #### Audit event types
268
273
 
@@ -396,7 +401,7 @@ Zod enum; `AuditOutcome` is `'success' | 'failure'`.
396
401
  accidental mutation (bugs, test cross-contamination, cast escapes)
397
402
  into loud TypeErrors — not a security boundary.
398
403
 
399
- ### Role grant offer (`role_grant_offer_schema.ts`)
404
+ ### Role grant offer (`role_grant_offer_schema.ts` + `role_grant_offer_ddl.ts`)
400
405
 
401
406
  The consentful-role-grants surface. Key constants:
402
407
 
@@ -11,7 +11,7 @@
11
11
  * `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
12
12
  * `acting?: ActingActor`).
13
13
  *
14
- * DDL lives in `auth/ddl.ts`; role system in `auth/role_schema.ts`.
14
+ * DDL lives in `auth/auth_ddl.ts`; role system in `auth/role_schema.ts`.
15
15
  * See docs/identity.md for design rationale.
16
16
  *
17
17
  * @module
@@ -11,7 +11,7 @@
11
11
  * `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
12
12
  * `acting?: ActingActor`).
13
13
  *
14
- * DDL lives in `auth/ddl.ts`; role system in `auth/role_schema.ts`.
14
+ * DDL lives in `auth/auth_ddl.ts`; role system in `auth/role_schema.ts`.
15
15
  * See docs/identity.md for design rationale.
16
16
  *
17
17
  * @module
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Audit log DDL — `CREATE TABLE` + index statements for the `audit_log` table.
3
+ *
4
+ * Consumed by `auth/migrations.ts`. Separated from `auth/audit_log_schema.ts`
5
+ * so the schema module stays Zod-only (paired with `auth/auth_ddl.ts` and
6
+ * `auth/role_grant_offer_ddl.ts`).
7
+ *
8
+ * Multi-actor invariants the envelope columns assume:
9
+ *
10
+ * - `actor_id` + `account_id`, when both populated, refer to the same
11
+ * account (derivable via `actor.account_id`). Denormalized for indexed
12
+ * audit queries; do not let them disagree.
13
+ * - `target_actor_id` + `target_account_id`, same rule when both populated.
14
+ * - `target_account_id` is the SSE/WS socket-close key — sessions stay
15
+ * account-grain after multi-actor lands, so this column carries the
16
+ * routing identity even on actor-bound events.
17
+ * - `target_actor_id` is populated iff the event subject is actor-bound
18
+ * (see `AuditLogEvent.target_actor_id` doc-comment for the rule).
19
+ *
20
+ * @module
21
+ */
22
+ export declare const AUDIT_LOG_SCHEMA = "\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq SERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB\n)";
23
+ export declare const AUDIT_LOG_INDEXES: string[];
24
+ //# sourceMappingURL=audit_log_ddl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit_log_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,eAAO,MAAM,gBAAgB,ihBAa3B,CAAC;AAEH,eAAO,MAAM,iBAAiB,UAM7B,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Audit log DDL — `CREATE TABLE` + index statements for the `audit_log` table.
3
+ *
4
+ * Consumed by `auth/migrations.ts`. Separated from `auth/audit_log_schema.ts`
5
+ * so the schema module stays Zod-only (paired with `auth/auth_ddl.ts` and
6
+ * `auth/role_grant_offer_ddl.ts`).
7
+ *
8
+ * Multi-actor invariants the envelope columns assume:
9
+ *
10
+ * - `actor_id` + `account_id`, when both populated, refer to the same
11
+ * account (derivable via `actor.account_id`). Denormalized for indexed
12
+ * audit queries; do not let them disagree.
13
+ * - `target_actor_id` + `target_account_id`, same rule when both populated.
14
+ * - `target_account_id` is the SSE/WS socket-close key — sessions stay
15
+ * account-grain after multi-actor lands, so this column carries the
16
+ * routing identity even on actor-bound events.
17
+ * - `target_actor_id` is populated iff the event subject is actor-bound
18
+ * (see `AuditLogEvent.target_actor_id` doc-comment for the rule).
19
+ *
20
+ * @module
21
+ */
22
+ export const AUDIT_LOG_SCHEMA = `
23
+ CREATE TABLE IF NOT EXISTS audit_log (
24
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
25
+ seq SERIAL NOT NULL,
26
+ event_type TEXT NOT NULL,
27
+ outcome TEXT NOT NULL DEFAULT 'success',
28
+ actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,
29
+ account_id UUID REFERENCES account(id) ON DELETE SET NULL,
30
+ target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,
31
+ target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,
32
+ ip TEXT,
33
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
34
+ metadata JSONB
35
+ )`;
36
+ export const AUDIT_LOG_INDEXES = [
37
+ `CREATE INDEX IF NOT EXISTS idx_audit_log_seq ON audit_log(seq DESC)`,
38
+ `CREATE INDEX IF NOT EXISTS idx_audit_log_account ON audit_log(account_id)`,
39
+ `CREATE INDEX IF NOT EXISTS idx_audit_log_event_type ON audit_log(event_type)`,
40
+ `CREATE INDEX IF NOT EXISTS idx_audit_log_target_account ON audit_log(target_account_id)`,
41
+ `CREATE INDEX IF NOT EXISTS idx_audit_log_target_actor ON audit_log(target_actor_id)`,
42
+ ];
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Audit log database schema and types.
2
+ * Audit log types and client-safe Zod schemas.
3
3
  *
4
4
  * Records auth mutations (login, logout, grant, revoke, etc.) for
5
5
  * security monitoring and operational visibility.
6
6
  *
7
+ * Table DDL and indexes live in `auth/audit_log_ddl.ts`.
8
+ *
7
9
  * @module
8
10
  */
9
11
  import { z } from 'zod';
@@ -411,6 +413,4 @@ export declare const AdminSessionJson: z.ZodObject<{
411
413
  username: z.ZodString;
412
414
  }, z.core.$strict>;
413
415
  export type AdminSessionJson = z.infer<typeof AdminSessionJson>;
414
- export declare const AUDIT_LOG_SCHEMA = "\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq SERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB\n)";
415
- export declare const AUDIT_LOG_INDEXES: string[];
416
416
  //# sourceMappingURL=audit_log_schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAO5C;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,8aAsBnB,CAAC;AAEZ,wCAAwC;AACxC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;EAA4B,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,QAA+B,CAAC;AAExE,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB,aAE7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,2CAA2C;AAC3C,eAAO,MAAM,YAAY;;;EAAiC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6MW,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,MAAM,gBAAgB,GAAG;KAC7B,CAAC,IAAI,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CAClE,CAAC;AAEF,oGAAoG;AACpG,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,kBAAkB,CAAC;IAC/B,OAAO,EAAE,YAAY,CAAC;IACtB;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzC;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,cAAc,EAC1D,OAAO,aAAa,GAAG;IAAC,UAAU,EAAE,CAAC,CAAA;CAAC,KACpC,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAExB,CAAC;AAEF,6CAA6C;AAC7C,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM,GAAG,cAAc;IAC/D,UAAU,EAAE,CAAC,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,iBAAiB,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAChC,eAAe,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC9B,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,SAAS,cAAc,GAChC,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GACtD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,cAAc;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;CAC/D;AAED,4FAA4F;AAC5F,eAAO,MAAM,wBAAwB,EAAE,cAGrC,CAAC;AAEH,6CAA6C;AAC7C,MAAM,WAAW,2BAA2B;IAC3C;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,GAAI,UAAU,2BAA2B,KAAG,cA2B/E,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;kBAY5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,+DAA+D;AAC/D,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;kBAGzC,CAAC;AACH,MAAM,MAAM,8BAA8B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAE5F,wEAAwE;AACxE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;kBAGpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,iEAAiE;AACjE,eAAO,MAAM,gBAAgB;;;;;;;kBAE3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Audit log database schema and types.
2
+ * Audit log types and client-safe Zod schemas.
3
3
  *
4
4
  * Records auth mutations (login, logout, grant, revoke, etc.) for
5
5
  * security monitoring and operational visibility.
6
6
  *
7
+ * Table DDL and indexes live in `auth/audit_log_ddl.ts`.
8
+ *
7
9
  * @module
8
10
  */
9
11
  import { z } from 'zod';
@@ -345,36 +347,3 @@ export const RoleGrantHistoryEventJson = AuditLogEventJson.extend({
345
347
  export const AdminSessionJson = AuthSessionJson.extend({
346
348
  username: z.string(),
347
349
  });
348
- // Schema DDL
349
- //
350
- // Multi-actor invariants the envelope columns assume:
351
- // - `actor_id` + `account_id`, when both populated, refer to the same
352
- // account (derivable via `actor.account_id`). Denormalized for
353
- // indexed audit queries; do not let them disagree.
354
- // - `target_actor_id` + `target_account_id`, same rule when both populated.
355
- // - `target_account_id` is the SSE/WS socket-close key — sessions stay
356
- // account-grain after multi-actor lands, so this column carries
357
- // the routing identity even on actor-bound events.
358
- // - `target_actor_id` is populated iff the event subject is actor-bound
359
- // (see `AuditLogEvent.target_actor_id` doc-comment for the rule).
360
- export const AUDIT_LOG_SCHEMA = `
361
- CREATE TABLE IF NOT EXISTS audit_log (
362
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
363
- seq SERIAL NOT NULL,
364
- event_type TEXT NOT NULL,
365
- outcome TEXT NOT NULL DEFAULT 'success',
366
- actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,
367
- account_id UUID REFERENCES account(id) ON DELETE SET NULL,
368
- target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,
369
- target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,
370
- ip TEXT,
371
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
372
- metadata JSONB
373
- )`;
374
- export const AUDIT_LOG_INDEXES = [
375
- `CREATE INDEX IF NOT EXISTS idx_audit_log_seq ON audit_log(seq DESC)`,
376
- `CREATE INDEX IF NOT EXISTS idx_audit_log_account ON audit_log(account_id)`,
377
- `CREATE INDEX IF NOT EXISTS idx_audit_log_event_type ON audit_log(event_type)`,
378
- `CREATE INDEX IF NOT EXISTS idx_audit_log_target_account ON audit_log(target_account_id)`,
379
- `CREATE INDEX IF NOT EXISTS idx_audit_log_target_actor ON audit_log(target_actor_id)`,
380
- ];
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Auth table DDL — CREATE TABLE, index, and seed statements.
2
+ * Identity-table DDL — `CREATE TABLE`, index, and seed statements for the
3
+ * core auth tables (account, actor, role_grant, auth_session, api_token,
4
+ * bootstrap_lock, invite, app_settings).
3
5
  *
4
- * Consumed by `auth/migrations.ts`. Separated from `auth/account_schema.ts`
5
- * to isolate DDL concerns from runtime types.
6
+ * Consumed by `auth/migrations.ts`. Paired with `auth/audit_log_ddl.ts`
7
+ * (audit table) and `auth/role_grant_offer_ddl.ts` (offer table) — DDL lives
8
+ * in `*_ddl.ts`, Zod schemas in `*_schema.ts`.
6
9
  *
7
10
  * @module
8
11
  */
@@ -24,4 +27,4 @@ export declare const INVITE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS invite (\n i
24
27
  export declare const INVITE_INDEXES: string[];
25
28
  export declare const APP_SETTINGS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS app_settings (\n id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),\n open_signup BOOLEAN NOT NULL DEFAULT false,\n updated_at TIMESTAMPTZ,\n updated_by UUID\n)";
26
29
  export declare const APP_SETTINGS_SEED = "\nINSERT INTO app_settings (id) VALUES (1) ON CONFLICT DO NOTHING";
27
- //# sourceMappingURL=ddl.d.ts.map
30
+ //# sourceMappingURL=auth_ddl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/auth_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,cAAc,8WAWzB,CAAC;AAEH,eAAO,MAAM,YAAY,mUAQvB,CAAC;AAEH,eAAO,MAAM,WAAW,wEAC0C,CAAC;AAEnE,eAAO,MAAM,iBAAiB,2ZAU5B,CAAC;AAEH,eAAO,MAAM,kBAAkB,UAI9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,0RAO9B,CAAC;AAEH,eAAO,MAAM,oBAAoB,UAGhC,CAAC;AAEF,eAAO,MAAM,gBAAgB,iUAU3B,CAAC;AAEH,eAAO,MAAM,mBAAmB,4GACsE,CAAC;AAEvG,eAAO,MAAM,yBAAyB,6FACiD,CAAC;AAExF,eAAO,MAAM,eAAe,gFAC8C,CAAC;AAE3E,eAAO,MAAM,qBAAqB,wJAIhC,CAAC;AAEH,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,yHAGP,CAAC;AAE1B,eAAO,MAAM,aAAa,6ZAUxB,CAAC;AAEH,eAAO,MAAM,cAAc,UAI1B,CAAC;AAEF,eAAO,MAAM,mBAAmB,oMAM9B,CAAC;AAEH,eAAO,MAAM,iBAAiB,sEACkC,CAAC"}
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Auth table DDL — CREATE TABLE, index, and seed statements.
2
+ * Identity-table DDL — `CREATE TABLE`, index, and seed statements for the
3
+ * core auth tables (account, actor, role_grant, auth_session, api_token,
4
+ * bootstrap_lock, invite, app_settings).
3
5
  *
4
- * Consumed by `auth/migrations.ts`. Separated from `auth/account_schema.ts`
5
- * to isolate DDL concerns from runtime types.
6
+ * Consumed by `auth/migrations.ts`. Paired with `auth/audit_log_ddl.ts`
7
+ * (audit table) and `auth/role_grant_offer_ddl.ts` (offer table) — DDL lives
8
+ * in `*_ddl.ts`, Zod schemas in `*_schema.ts`.
6
9
  *
7
10
  * @module
8
11
  */
@@ -36,9 +36,9 @@
36
36
  *
37
37
  * @module
38
38
  */
39
- import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX, ROLE_GRANT_SCHEMA, ROLE_GRANT_INDEXES, AUTH_SESSION_SCHEMA, AUTH_SESSION_INDEXES, API_TOKEN_SCHEMA, API_TOKEN_INDEX, BOOTSTRAP_LOCK_SCHEMA, BOOTSTRAP_LOCK_SEED, INVITE_SCHEMA, INVITE_INDEXES, APP_SETTINGS_SCHEMA, APP_SETTINGS_SEED, } from './ddl.js';
40
- import { AUDIT_LOG_SCHEMA, AUDIT_LOG_INDEXES } from './audit_log_schema.js';
41
- import { ROLE_GRANT_OFFER_SCHEMA, ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX, ROLE_GRANT_OFFER_INBOX_INDEX, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, } from './role_grant_offer_schema.js';
39
+ import { ACCOUNT_SCHEMA, ACCOUNT_EMAIL_INDEX, ACCOUNT_USERNAME_CI_INDEX, ACTOR_SCHEMA, ACTOR_INDEX, ROLE_GRANT_SCHEMA, ROLE_GRANT_INDEXES, AUTH_SESSION_SCHEMA, AUTH_SESSION_INDEXES, API_TOKEN_SCHEMA, API_TOKEN_INDEX, BOOTSTRAP_LOCK_SCHEMA, BOOTSTRAP_LOCK_SEED, INVITE_SCHEMA, INVITE_INDEXES, APP_SETTINGS_SCHEMA, APP_SETTINGS_SEED, } from './auth_ddl.js';
40
+ import { AUDIT_LOG_SCHEMA, AUDIT_LOG_INDEXES } from './audit_log_ddl.js';
41
+ import { ROLE_GRANT_OFFER_SCHEMA, ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX, ROLE_GRANT_OFFER_INBOX_INDEX, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, } from './role_grant_offer_ddl.js';
42
42
  /** Namespace identifier for fuz_app auth migrations. */
43
43
  export const AUTH_MIGRATION_NAMESPACE = 'fuz_auth';
44
44
  /**
@@ -105,7 +105,7 @@ export const AUTH_MIGRATIONS = [
105
105
  },
106
106
  // v1: consentful role_grants — role_grant_offer table + scoped role_grants
107
107
  {
108
- name: 'permit_offer_and_scoped_permits',
108
+ name: 'role_grant_offer_and_scoped_role_grants',
109
109
  up: async (db) => {
110
110
  await db.query(ROLE_GRANT_OFFER_SCHEMA);
111
111
  await db.query(ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Role grant offer DDL — `CREATE TABLE` + index statements and the index-side
3
+ * sentinel constants the queries / migrations interpolate.
4
+ *
5
+ * Separated from `auth/role_grant_offer_schema.ts` so the schema module stays
6
+ * Zod-only (paired with `auth/auth_ddl.ts` and `auth/audit_log_ddl.ts`).
7
+ *
8
+ * An offer is a pending grant awaiting recipient consent. Lifecycle states
9
+ * are mutually exclusive via a CHECK constraint (`role_grant_offer_single_terminal`):
10
+ * at most one of `accepted_at` / `declined_at` / `retracted_at` may be set.
11
+ * On accept, the offer's `resulting_role_grant_id` links to the role_grant row
12
+ * produced by `query_accept_offer`.
13
+ *
14
+ * @module
15
+ */
16
+ /** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
17
+ export declare const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = "00000000-0000-0000-0000-000000000000";
18
+ /**
19
+ * Index-side token for the global case in the partial unique index. Uppercase
20
+ * so it cannot collide with consumer-declared `ScopeKindName` values (which
21
+ * are lowercase by regex). Never appears as a column value — column-level
22
+ * `scope_kind = NULL` and `scope_id = NULL` together encode the global case.
23
+ */
24
+ export declare const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = "GLOBAL";
25
+ export declare const ROLE_GRANT_OFFER_SCHEMA = "\nCREATE TABLE IF NOT EXISTS role_grant_offer (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n to_actor_id UUID NULL REFERENCES actor(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n scope_kind TEXT NULL,\n scope_id UUID NULL,\n message TEXT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ NOT NULL,\n accepted_at TIMESTAMPTZ NULL,\n declined_at TIMESTAMPTZ NULL,\n decline_reason TEXT NULL,\n retracted_at TIMESTAMPTZ NULL,\n superseded_at TIMESTAMPTZ NULL,\n resulting_role_grant_id UUID NULL REFERENCES role_grant(id) ON DELETE SET NULL,\n CONSTRAINT role_grant_offer_single_terminal CHECK (\n (accepted_at IS NOT NULL)::int\n + (declined_at IS NOT NULL)::int\n + (retracted_at IS NOT NULL)::int\n + (superseded_at IS NOT NULL)::int\n <= 1\n ),\n CONSTRAINT role_grant_offer_role_grant_iff_accepted CHECK (\n (accepted_at IS NOT NULL) = (resulting_role_grant_id IS NOT NULL)\n ),\n CONSTRAINT role_grant_offer_reason_iff_declined CHECK (\n decline_reason IS NULL OR declined_at IS NOT NULL\n ),\n CONSTRAINT role_grant_offer_scope_kind_paired CHECK (\n (scope_kind IS NULL) = (scope_id IS NULL)\n )\n)";
26
+ /**
27
+ * At most one pending offer per (to_account, role, scope_kind, scope, from_actor).
28
+ *
29
+ * Including `from_actor_id` in the tuple lets multiple grantors coexist —
30
+ * teacher A and teacher B can each have a pending `classroom_student` offer
31
+ * for the same student and scope. A same-grantor re-offer upserts the
32
+ * existing pending row. `COALESCE` collapses `NULL` scopes into the
33
+ * sentinel values so Postgres's NULL-in-unique-index quirk does not allow
34
+ * duplicate global pending offers; the `scope_kind` / `scope_id` pair is
35
+ * always either both null (global) or both non-null (scoped) per the
36
+ * `role_grant_offer_scope_kind_paired` CHECK, so the two COALESCE expressions
37
+ * always agree. The ON CONFLICT target in `query_role_grant_offer_create` must
38
+ * match this expression literally.
39
+ */
40
+ export declare const ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX = "\nCREATE UNIQUE INDEX IF NOT EXISTS role_grant_offer_pending_unique\n ON role_grant_offer (\n to_account_id,\n role,\n COALESCE(scope_kind, 'GLOBAL'),\n COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'::uuid),\n from_actor_id\n )\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL";
41
+ /** Inbox lookup — pending offers for an account, ordered by soonest expiry. */
42
+ export declare const ROLE_GRANT_OFFER_INBOX_INDEX = "\nCREATE INDEX IF NOT EXISTS role_grant_offer_inbox\n ON role_grant_offer (to_account_id, expires_at)\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL";
43
+ //# sourceMappingURL=role_grant_offer_ddl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role_grant_offer_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,mHAAmH;AACnH,eAAO,MAAM,oCAAoC,yCAAyC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,WAAW,CAAC;AAEjE,eAAO,MAAM,uBAAuB,qzCAkClC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC,2XAYpB,CAAC;AAE/B,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,kOAMX,CAAC"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Role grant offer DDL — `CREATE TABLE` + index statements and the index-side
3
+ * sentinel constants the queries / migrations interpolate.
4
+ *
5
+ * Separated from `auth/role_grant_offer_schema.ts` so the schema module stays
6
+ * Zod-only (paired with `auth/auth_ddl.ts` and `auth/audit_log_ddl.ts`).
7
+ *
8
+ * An offer is a pending grant awaiting recipient consent. Lifecycle states
9
+ * are mutually exclusive via a CHECK constraint (`role_grant_offer_single_terminal`):
10
+ * at most one of `accepted_at` / `declined_at` / `retracted_at` may be set.
11
+ * On accept, the offer's `resulting_role_grant_id` links to the role_grant row
12
+ * produced by `query_accept_offer`.
13
+ *
14
+ * @module
15
+ */
16
+ /** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
17
+ export const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = '00000000-0000-0000-0000-000000000000';
18
+ /**
19
+ * Index-side token for the global case in the partial unique index. Uppercase
20
+ * so it cannot collide with consumer-declared `ScopeKindName` values (which
21
+ * are lowercase by regex). Never appears as a column value — column-level
22
+ * `scope_kind = NULL` and `scope_id = NULL` together encode the global case.
23
+ */
24
+ export const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = 'GLOBAL';
25
+ export const ROLE_GRANT_OFFER_SCHEMA = `
26
+ CREATE TABLE IF NOT EXISTS role_grant_offer (
27
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
28
+ from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,
29
+ to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,
30
+ to_actor_id UUID NULL REFERENCES actor(id) ON DELETE CASCADE,
31
+ role TEXT NOT NULL,
32
+ scope_kind TEXT NULL,
33
+ scope_id UUID NULL,
34
+ message TEXT NULL,
35
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
36
+ expires_at TIMESTAMPTZ NOT NULL,
37
+ accepted_at TIMESTAMPTZ NULL,
38
+ declined_at TIMESTAMPTZ NULL,
39
+ decline_reason TEXT NULL,
40
+ retracted_at TIMESTAMPTZ NULL,
41
+ superseded_at TIMESTAMPTZ NULL,
42
+ resulting_role_grant_id UUID NULL REFERENCES role_grant(id) ON DELETE SET NULL,
43
+ CONSTRAINT role_grant_offer_single_terminal CHECK (
44
+ (accepted_at IS NOT NULL)::int
45
+ + (declined_at IS NOT NULL)::int
46
+ + (retracted_at IS NOT NULL)::int
47
+ + (superseded_at IS NOT NULL)::int
48
+ <= 1
49
+ ),
50
+ CONSTRAINT role_grant_offer_role_grant_iff_accepted CHECK (
51
+ (accepted_at IS NOT NULL) = (resulting_role_grant_id IS NOT NULL)
52
+ ),
53
+ CONSTRAINT role_grant_offer_reason_iff_declined CHECK (
54
+ decline_reason IS NULL OR declined_at IS NOT NULL
55
+ ),
56
+ CONSTRAINT role_grant_offer_scope_kind_paired CHECK (
57
+ (scope_kind IS NULL) = (scope_id IS NULL)
58
+ )
59
+ )`;
60
+ /**
61
+ * At most one pending offer per (to_account, role, scope_kind, scope, from_actor).
62
+ *
63
+ * Including `from_actor_id` in the tuple lets multiple grantors coexist —
64
+ * teacher A and teacher B can each have a pending `classroom_student` offer
65
+ * for the same student and scope. A same-grantor re-offer upserts the
66
+ * existing pending row. `COALESCE` collapses `NULL` scopes into the
67
+ * sentinel values so Postgres's NULL-in-unique-index quirk does not allow
68
+ * duplicate global pending offers; the `scope_kind` / `scope_id` pair is
69
+ * always either both null (global) or both non-null (scoped) per the
70
+ * `role_grant_offer_scope_kind_paired` CHECK, so the two COALESCE expressions
71
+ * always agree. The ON CONFLICT target in `query_role_grant_offer_create` must
72
+ * match this expression literally.
73
+ */
74
+ export const ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX = `
75
+ CREATE UNIQUE INDEX IF NOT EXISTS role_grant_offer_pending_unique
76
+ ON role_grant_offer (
77
+ to_account_id,
78
+ role,
79
+ COALESCE(scope_kind, '${ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN}'),
80
+ COALESCE(scope_id, '${ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID}'::uuid),
81
+ from_actor_id
82
+ )
83
+ WHERE accepted_at IS NULL
84
+ AND declined_at IS NULL
85
+ AND retracted_at IS NULL
86
+ AND superseded_at IS NULL`;
87
+ /** Inbox lookup — pending offers for an account, ordered by soonest expiry. */
88
+ export const ROLE_GRANT_OFFER_INBOX_INDEX = `
89
+ CREATE INDEX IF NOT EXISTS role_grant_offer_inbox
90
+ ON role_grant_offer (to_account_id, expires_at)
91
+ WHERE accepted_at IS NULL
92
+ AND declined_at IS NULL
93
+ AND retracted_at IS NULL
94
+ AND superseded_at IS NULL`;
95
+ // **Deferred**: a `role_grant_offer_to_actor` partial index belongs here once
96
+ // an actor-side inbox query (`query_role_grant_offer_list_for_actor`) lands —
97
+ // no current consumer filters on `to_actor_id`, and adding the index
98
+ // before the query is paying write-amp for nothing. Land the index in
99
+ // the same slice as the query.
@@ -14,7 +14,7 @@
14
14
  import type { Uuid } from '@fuzdev/fuz_util/id.js';
15
15
  import type { QueryDeps } from '../db/query_deps.js';
16
16
  import type { RoleGrant } from './account_schema.js';
17
- import { type CreateRoleGrantOfferInput, type RoleGrantOffer, type SupersededOffer } from './role_grant_offer_schema.js';
17
+ import type { CreateRoleGrantOfferInput, RoleGrantOffer, SupersededOffer } from './role_grant_offer_schema.js';
18
18
  import type { AuditLogEvent } from './audit_log_schema.js';
19
19
  /**
20
20
  * Error thrown by offer-lifecycle queries when the offer is in a non-pending
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAKnD,OAAO,KAAK,EACX,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEzD;;;;;GAKG;AACH,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;gBACxC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;;CAKvD;AAED;;;;;;;GAOG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,uCAAwC,SAAQ,KAAK;;CAKjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,OAAO,yBAAyB,KAC9B,OAAO,CAAC,cAAc,CAuDxB,CAAC;AAEF,uGAAuG;AACvG,MAAM,WAAW,aAAc,SAAQ,cAAc;IACpD;;;;;OAKG;IACH,eAAe,EAAE,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,eAAe,MAAM,EACrB,QAAQ,MAAM,GAAG,IAAI,KACnB,OAAO,CAAC,aAAa,GAAG,IAAI,CAoB9B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,eAAe,MAAM,KACnB,OAAO,CAAC,cAAc,GAAG,IAAI,CAe/B,CAAC;AA8BF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,eAAe,MAAM,KACnB,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAY/B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0CAA0C,GACtD,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAW,EACX,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAS/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,cAAc,GAAG,IAAI,CAY/B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAU/B,CAAC;AAEF,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,IAAI,CAAC;IACf,mGAAmG;IACnG,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,IAAI,CAAC;IACf,gDAAgD;IAChD,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnB;AAED,6HAA6H;AAC7H,MAAM,WAAW,iBAAiB;IACjC,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,cAAc,CAAC;IACtB,oJAAoJ;IACpJ,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAC1C,uMAAuM;IACvM,YAAY,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,OAAO,gBAAgB,KACrB,OAAO,CAAC,iBAAiB,CAoO3B,CAAC"}
@@ -12,7 +12,7 @@
12
12
  * @module
13
13
  */
14
14
  import { assert_row } from '../db/assert_row.js';
15
- import { ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, } from './role_grant_offer_schema.js';
15
+ import { ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, } from './role_grant_offer_ddl.js';
16
16
  import { query_audit_log } from './audit_log_queries.js';
17
17
  /**
18
18
  * Error thrown by offer-lifecycle queries when the offer is in a non-pending
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Role grant offer DDL, types, and client-safe schemas.
2
+ * Role grant offer types and client-safe schemas.
3
3
  *
4
4
  * An offer is a pending grant awaiting recipient consent. Lifecycle states
5
5
  * are mutually exclusive via a CHECK constraint (`role_grant_offer_single_terminal`):
@@ -7,41 +7,16 @@
7
7
  * On accept, the offer's `resulting_role_grant_id` links to the role_grant row
8
8
  * produced by `query_accept_offer`.
9
9
  *
10
+ * Table DDL and index-side sentinel constants live in `auth/role_grant_offer_ddl.ts`.
11
+ *
10
12
  * @module
11
13
  */
12
14
  import { z } from 'zod';
13
15
  import { Uuid } from '@fuzdev/fuz_util/id.js';
14
- /** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
15
- export declare const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = "00000000-0000-0000-0000-000000000000";
16
16
  /** Maximum length of the optional message attached to an offer. */
17
17
  export declare const ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX = 500;
18
18
  /** Default TTL for a newly created offer — 30 days. Matches GitHub org-invite expiry. */
19
19
  export declare const ROLE_GRANT_OFFER_DEFAULT_TTL_MS: number;
20
- export declare const ROLE_GRANT_OFFER_SCHEMA = "\nCREATE TABLE IF NOT EXISTS role_grant_offer (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,\n to_actor_id UUID NULL REFERENCES actor(id) ON DELETE CASCADE,\n role TEXT NOT NULL,\n scope_kind TEXT NULL,\n scope_id UUID NULL,\n message TEXT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ NOT NULL,\n accepted_at TIMESTAMPTZ NULL,\n declined_at TIMESTAMPTZ NULL,\n decline_reason TEXT NULL,\n retracted_at TIMESTAMPTZ NULL,\n superseded_at TIMESTAMPTZ NULL,\n resulting_role_grant_id UUID NULL REFERENCES role_grant(id) ON DELETE SET NULL,\n CONSTRAINT role_grant_offer_single_terminal CHECK (\n (accepted_at IS NOT NULL)::int\n + (declined_at IS NOT NULL)::int\n + (retracted_at IS NOT NULL)::int\n + (superseded_at IS NOT NULL)::int\n <= 1\n ),\n CONSTRAINT role_grant_offer_role_grant_iff_accepted CHECK (\n (accepted_at IS NOT NULL) = (resulting_role_grant_id IS NOT NULL)\n ),\n CONSTRAINT role_grant_offer_reason_iff_declined CHECK (\n decline_reason IS NULL OR declined_at IS NOT NULL\n ),\n CONSTRAINT role_grant_offer_scope_kind_paired CHECK (\n (scope_kind IS NULL) = (scope_id IS NULL)\n )\n)";
21
- /**
22
- * Index-side token for the global case in the partial unique index. Uppercase
23
- * so it cannot collide with consumer-declared `ScopeKindName` values (which
24
- * are lowercase by regex). Never appears as a column value — column-level
25
- * `scope_kind = NULL` and `scope_id = NULL` together encode the global case.
26
- */
27
- export declare const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = "GLOBAL";
28
- /**
29
- * At most one pending offer per (to_account, role, scope_kind, scope, from_actor).
30
- *
31
- * Including `from_actor_id` in the tuple lets multiple grantors coexist —
32
- * teacher A and teacher B can each have a pending `classroom_student` offer
33
- * for the same student and scope. A same-grantor re-offer upserts the
34
- * existing pending row. `COALESCE` collapses `NULL` scopes into the
35
- * sentinel values so Postgres's NULL-in-unique-index quirk does not allow
36
- * duplicate global pending offers; the `scope_kind` / `scope_id` pair is
37
- * always either both null (global) or both non-null (scoped) per the
38
- * `role_grant_offer_scope_kind_paired` CHECK, so the two COALESCE expressions
39
- * always agree. The ON CONFLICT target in `query_role_grant_offer_create` must
40
- * match this expression literally.
41
- */
42
- export declare const ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX = "\nCREATE UNIQUE INDEX IF NOT EXISTS role_grant_offer_pending_unique\n ON role_grant_offer (\n to_account_id,\n role,\n COALESCE(scope_kind, 'GLOBAL'),\n COALESCE(scope_id, '00000000-0000-0000-0000-000000000000'::uuid),\n from_actor_id\n )\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL";
43
- /** Inbox lookup — pending offers for an account, ordered by soonest expiry. */
44
- export declare const ROLE_GRANT_OFFER_INBOX_INDEX = "\nCREATE INDEX IF NOT EXISTS role_grant_offer_inbox\n ON role_grant_offer (to_account_id, expires_at)\n WHERE accepted_at IS NULL\n AND declined_at IS NULL\n AND retracted_at IS NULL\n AND superseded_at IS NULL";
45
20
  /** Role grant offer row as returned by the database. */
46
21
  export interface RoleGrantOffer {
47
22
  id: Uuid;
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAI5C,mHAAmH;AACnH,eAAO,MAAM,oCAAoC,yCAAyC,CAAC;AAE3F,mEAAmE;AACnE,eAAO,MAAM,mCAAmC,MAAM,CAAC;AAEvD,yFAAyF;AACzF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,eAAO,MAAM,uBAAuB,qzCAkClC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,WAAW,CAAC;AAEjE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC,2XAYpB,CAAC;AAE/B,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,kOAMX,CAAC;AAQ/B,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,EAAE,IAAI,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACtD,eAAe,EAAE,IAAI,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;kBAoD0D,CAAC;AAC1F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,GAAI,OAAO,cAAc,KAAG,kBAiB/D,CAAC"}
1
+ {"version":3,"file":"role_grant_offer_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAI5C,mEAAmE;AACnE,eAAO,MAAM,mCAAmC,MAAM,CAAC;AAEvD,yFAAyF;AACzF,eAAO,MAAM,+BAA+B,QAA2B,CAAC;AAExE,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,EAAE,IAAI,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACtD,eAAe,EAAE,IAAI,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,IAAI,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;kBAoD0D,CAAC;AAC1F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,GAAI,OAAO,cAAc,KAAG,kBAiB/D,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Role grant offer DDL, types, and client-safe schemas.
2
+ * Role grant offer types and client-safe schemas.
3
3
  *
4
4
  * An offer is a pending grant awaiting recipient consent. Lifecycle states
5
5
  * are mutually exclusive via a CHECK constraint (`role_grant_offer_single_terminal`):
@@ -7,94 +7,17 @@
7
7
  * On accept, the offer's `resulting_role_grant_id` links to the role_grant row
8
8
  * produced by `query_accept_offer`.
9
9
  *
10
+ * Table DDL and index-side sentinel constants live in `auth/role_grant_offer_ddl.ts`.
11
+ *
10
12
  * @module
11
13
  */
12
14
  import { z } from 'zod';
13
15
  import { Uuid } from '@fuzdev/fuz_util/id.js';
14
16
  import { RoleName } from './role_schema.js';
15
- /** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
16
- export const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = '00000000-0000-0000-0000-000000000000';
17
17
  /** Maximum length of the optional message attached to an offer. */
18
18
  export const ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX = 500;
19
19
  /** Default TTL for a newly created offer — 30 days. Matches GitHub org-invite expiry. */
20
20
  export const ROLE_GRANT_OFFER_DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1000;
21
- export const ROLE_GRANT_OFFER_SCHEMA = `
22
- CREATE TABLE IF NOT EXISTS role_grant_offer (
23
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
24
- from_actor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,
25
- to_account_id UUID NOT NULL REFERENCES account(id) ON DELETE CASCADE,
26
- to_actor_id UUID NULL REFERENCES actor(id) ON DELETE CASCADE,
27
- role TEXT NOT NULL,
28
- scope_kind TEXT NULL,
29
- scope_id UUID NULL,
30
- message TEXT NULL,
31
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
32
- expires_at TIMESTAMPTZ NOT NULL,
33
- accepted_at TIMESTAMPTZ NULL,
34
- declined_at TIMESTAMPTZ NULL,
35
- decline_reason TEXT NULL,
36
- retracted_at TIMESTAMPTZ NULL,
37
- superseded_at TIMESTAMPTZ NULL,
38
- resulting_role_grant_id UUID NULL REFERENCES role_grant(id) ON DELETE SET NULL,
39
- CONSTRAINT role_grant_offer_single_terminal CHECK (
40
- (accepted_at IS NOT NULL)::int
41
- + (declined_at IS NOT NULL)::int
42
- + (retracted_at IS NOT NULL)::int
43
- + (superseded_at IS NOT NULL)::int
44
- <= 1
45
- ),
46
- CONSTRAINT role_grant_offer_role_grant_iff_accepted CHECK (
47
- (accepted_at IS NOT NULL) = (resulting_role_grant_id IS NOT NULL)
48
- ),
49
- CONSTRAINT role_grant_offer_reason_iff_declined CHECK (
50
- decline_reason IS NULL OR declined_at IS NOT NULL
51
- ),
52
- CONSTRAINT role_grant_offer_scope_kind_paired CHECK (
53
- (scope_kind IS NULL) = (scope_id IS NULL)
54
- )
55
- )`;
56
- /**
57
- * Index-side token for the global case in the partial unique index. Uppercase
58
- * so it cannot collide with consumer-declared `ScopeKindName` values (which
59
- * are lowercase by regex). Never appears as a column value — column-level
60
- * `scope_kind = NULL` and `scope_id = NULL` together encode the global case.
61
- */
62
- export const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = 'GLOBAL';
63
- /**
64
- * At most one pending offer per (to_account, role, scope_kind, scope, from_actor).
65
- *
66
- * Including `from_actor_id` in the tuple lets multiple grantors coexist —
67
- * teacher A and teacher B can each have a pending `classroom_student` offer
68
- * for the same student and scope. A same-grantor re-offer upserts the
69
- * existing pending row. `COALESCE` collapses `NULL` scopes into the
70
- * sentinel values so Postgres's NULL-in-unique-index quirk does not allow
71
- * duplicate global pending offers; the `scope_kind` / `scope_id` pair is
72
- * always either both null (global) or both non-null (scoped) per the
73
- * `role_grant_offer_scope_kind_paired` CHECK, so the two COALESCE expressions
74
- * always agree. The ON CONFLICT target in `query_role_grant_offer_create` must
75
- * match this expression literally.
76
- */
77
- export const ROLE_GRANT_OFFER_PENDING_UNIQUE_INDEX = `
78
- CREATE UNIQUE INDEX IF NOT EXISTS role_grant_offer_pending_unique
79
- ON role_grant_offer (
80
- to_account_id,
81
- role,
82
- COALESCE(scope_kind, '${ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN}'),
83
- COALESCE(scope_id, '${ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID}'::uuid),
84
- from_actor_id
85
- )
86
- WHERE accepted_at IS NULL
87
- AND declined_at IS NULL
88
- AND retracted_at IS NULL
89
- AND superseded_at IS NULL`;
90
- /** Inbox lookup — pending offers for an account, ordered by soonest expiry. */
91
- export const ROLE_GRANT_OFFER_INBOX_INDEX = `
92
- CREATE INDEX IF NOT EXISTS role_grant_offer_inbox
93
- ON role_grant_offer (to_account_id, expires_at)
94
- WHERE accepted_at IS NULL
95
- AND declined_at IS NULL
96
- AND retracted_at IS NULL
97
- AND superseded_at IS NULL`;
98
21
  /** Zod schema for client-safe role_grant offer data. */
99
22
  export const RoleGrantOfferJson = z
100
23
  .strictObject({
@@ -9,7 +9,7 @@
9
9
  import type { Uuid } from '@fuzdev/fuz_util/id.js';
10
10
  import type { QueryDeps } from '../db/query_deps.js';
11
11
  import type { RoleGrant, CreateRoleGrantInput } from './account_schema.js';
12
- import { type SupersededOffer } from './role_grant_offer_schema.js';
12
+ import type { SupersededOffer } from './role_grant_offer_schema.js';
13
13
  /**
14
14
  * Grant a role_grant to an actor.
15
15
  * Idempotent — if an active role_grant already exists for this actor, role, and
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_queries.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_queries.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * @module
8
8
  */
9
9
  import { assert_row } from '../db/assert_row.js';
10
- import { ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, } from './role_grant_offer_schema.js';
10
+ import { ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, } from './role_grant_offer_ddl.js';
11
11
  /**
12
12
  * Grant a role_grant to an actor.
13
13
  * Idempotent — if an active role_grant already exists for this actor, role, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.57.2",
3
+ "version": "0.58.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -1 +0,0 @@
1
- {"version":3,"file":"ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,cAAc,8WAWzB,CAAC;AAEH,eAAO,MAAM,YAAY,mUAQvB,CAAC;AAEH,eAAO,MAAM,WAAW,wEAC0C,CAAC;AAEnE,eAAO,MAAM,iBAAiB,2ZAU5B,CAAC;AAEH,eAAO,MAAM,kBAAkB,UAI9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,0RAO9B,CAAC;AAEH,eAAO,MAAM,oBAAoB,UAGhC,CAAC;AAEF,eAAO,MAAM,gBAAgB,iUAU3B,CAAC;AAEH,eAAO,MAAM,mBAAmB,4GACsE,CAAC;AAEvG,eAAO,MAAM,yBAAyB,6FACiD,CAAC;AAExF,eAAO,MAAM,eAAe,gFAC8C,CAAC;AAE3E,eAAO,MAAM,qBAAqB,wJAIhC,CAAC;AAEH,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,yHAGP,CAAC;AAE1B,eAAO,MAAM,aAAa,6ZAUxB,CAAC;AAEH,eAAO,MAAM,cAAc,UAI1B,CAAC;AAEF,eAAO,MAAM,mBAAmB,oMAM9B,CAAC;AAEH,eAAO,MAAM,iBAAiB,sEACkC,CAAC"}