@fuzdev/fuz_app 0.111.0 → 0.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +15 -11
- package/dist/actions/action_rpc.d.ts +1 -1
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +4 -6
- package/dist/actions/peer_ping.d.ts +8 -0
- package/dist/actions/peer_ping.d.ts.map +1 -1
- package/dist/actions/peer_ping.js +8 -0
- package/dist/actions/peer_request.d.ts +1 -1
- package/dist/actions/peer_request.js +1 -1
- package/dist/actions/perform_action.d.ts +16 -9
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +30 -21
- package/dist/actions/register_action_ws.d.ts +1 -1
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +88 -21
- package/dist/auth/CLAUDE.md +37 -7
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +10 -1
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +36 -16
- package/dist/auth/account_schema.d.ts +20 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +22 -1
- package/dist/auth/actor_search_actions.d.ts +3 -3
- package/dist/auth/actor_search_actions.d.ts.map +1 -1
- package/dist/auth/actor_search_actions.js +10 -6
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +11 -5
- package/dist/auth/app_settings_queries.d.ts.map +1 -1
- package/dist/auth/app_settings_queries.js +6 -4
- package/dist/auth/audit_log_queries.d.ts +6 -0
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +11 -5
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +3 -8
- package/dist/auth/cell_audit_actions.d.ts.map +1 -1
- package/dist/auth/cell_audit_actions.js +1 -1
- package/dist/auth/cell_field_actions.js +1 -1
- package/dist/auth/cell_grant_actions.js +1 -1
- package/dist/auth/cell_item_actions.js +1 -1
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +10 -8
- package/dist/auth/role_grant_offer_ddl.d.ts +11 -0
- package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_ddl.js +20 -2
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +29 -15
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +21 -7
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +9 -4
- package/dist/db/CLAUDE.md +21 -2
- package/dist/db/cell_audit_queries.js +2 -2
- package/dist/db/cell_field_queries.d.ts +1 -1
- package/dist/db/cell_field_queries.d.ts.map +1 -1
- package/dist/db/cell_field_queries.js +8 -6
- package/dist/db/cell_grant_queries.d.ts +1 -1
- package/dist/db/cell_grant_queries.d.ts.map +1 -1
- package/dist/db/cell_grant_queries.js +12 -10
- package/dist/db/cell_item_queries.d.ts +1 -1
- package/dist/db/cell_item_queries.d.ts.map +1 -1
- package/dist/db/cell_item_queries.js +9 -7
- package/dist/db/cell_queries.d.ts +3 -3
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +6 -4
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +9 -3
- package/dist/db/sql_columns.d.ts +80 -3
- package/dist/db/sql_columns.d.ts.map +1 -1
- package/dist/db/sql_columns.js +91 -4
- package/dist/http/CLAUDE.md +3 -1
- package/dist/http/jsonrpc_helpers.d.ts +13 -0
- package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
- package/dist/http/jsonrpc_helpers.js +13 -0
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +7 -2
- package/dist/testing/CLAUDE.md +53 -27
- package/dist/testing/admin_integration.d.ts +0 -3
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +0 -1
- package/dist/testing/adversarial_404.d.ts.map +1 -1
- package/dist/testing/adversarial_404.js +4 -1
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +5 -4
- package/dist/testing/app_server.d.ts +4 -3
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +11 -5
- package/dist/testing/attack_surface.d.ts +16 -0
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +36 -13
- package/dist/testing/audit_completeness.d.ts +0 -3
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +0 -1
- package/dist/testing/auth_apps.d.ts +9 -2
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +9 -2
- package/dist/testing/cross_backend/account_lifecycle.d.ts +5 -4
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +7 -0
- package/dist/testing/cross_backend/action_manifest.d.ts +19 -3
- package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -1
- package/dist/testing/cross_backend/action_manifest.js +10 -4
- package/dist/testing/cross_backend/action_manifest_parity.d.ts +9 -3
- package/dist/testing/cross_backend/action_manifest_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/action_manifest_parity.js +6 -0
- package/dist/testing/cross_backend/actor_lookup.d.ts +3 -2
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.d.ts +4 -2
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +1 -1
- package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +10 -0
- package/dist/testing/cross_backend/cell_gated_create.d.ts +3 -3
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.d.ts +2 -2
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts +17 -2
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -1
- package/dist/testing/cross_backend/make_cross_backend_project.js +23 -3
- package/dist/testing/cross_backend/origin.d.ts +4 -2
- package/dist/testing/cross_backend/origin.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin.js +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +11 -7
- package/dist/testing/cross_backend/query_shape.d.ts +6 -5
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
- package/dist/testing/cross_backend/ready.d.ts +2 -2
- package/dist/testing/cross_backend/ready.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_enumeration.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_enumeration.js +7 -1
- package/dist/testing/cross_backend/setup.d.ts +26 -6
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.d.ts +5 -1
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +1 -5
- package/dist/testing/cross_backend/testing_backdoor.js +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_lifetime.js +6 -0
- package/dist/testing/cross_backend/token_scope_surface.d.ts +2 -2
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/wire_shapes.d.ts +22 -0
- package/dist/testing/cross_backend/wire_shapes.d.ts.map +1 -0
- package/dist/testing/cross_backend/wire_shapes.js +41 -0
- package/dist/testing/data_exposure.d.ts +0 -3
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +0 -1
- package/dist/testing/db.d.ts +44 -7
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +62 -27
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +15 -2
- package/dist/testing/round_trip.d.ts +0 -3
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +0 -4
- package/dist/testing/rpc_attack_surface.js +2 -2
- package/dist/testing/rpc_round_trip.d.ts +0 -3
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +0 -1
- package/dist/testing/schema_generators.d.ts +15 -0
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +45 -11
- package/dist/testing/standard.d.ts +5 -1
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +2 -7
- package/dist/timestamp.d.ts +58 -0
- package/dist/timestamp.d.ts.map +1 -0
- package/dist/timestamp.js +57 -0
- package/dist/ui/RoleGrantOfferHistory.svelte +3 -1
- package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
- package/dist/ui/role_grant_offers_state.svelte.js +6 -2
- package/package.json +1 -1
|
@@ -54,11 +54,6 @@ import { filter_visible_target_ids } from './cell_relation_visibility.js';
|
|
|
54
54
|
import { to_grant_json } from './cell_grant_actions.js';
|
|
55
55
|
import { to_field_json } from './cell_field_actions.js';
|
|
56
56
|
import { to_item_json } from './cell_item_actions.js';
|
|
57
|
-
const to_iso_nullable = (value) => {
|
|
58
|
-
if (value === null)
|
|
59
|
-
return null;
|
|
60
|
-
return typeof value === 'string' ? value : value.toISOString();
|
|
61
|
-
};
|
|
62
57
|
/**
|
|
63
58
|
* Translate the `idx_cell_path_unique` violation into a clean `conflict`
|
|
64
59
|
* (409) reason. `path` is the only unique constraint a cell write can hit
|
|
@@ -92,9 +87,9 @@ export const to_cell_json = (row) => ({
|
|
|
92
87
|
moderation: row.moderation,
|
|
93
88
|
created_by: row.created_by,
|
|
94
89
|
updated_by: row.updated_by,
|
|
95
|
-
created_at:
|
|
96
|
-
updated_at:
|
|
97
|
-
deleted_at:
|
|
90
|
+
created_at: row.created_at,
|
|
91
|
+
updated_at: row.updated_at,
|
|
92
|
+
deleted_at: row.deleted_at,
|
|
98
93
|
grant_count: row.grant_count
|
|
99
94
|
});
|
|
100
95
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_audit_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_audit_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_audit_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_audit_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AA+B/F,eAAO,MAAM,yBAAyB,QAAO,KAAK,CAAC,SAAS,CA0B3D,CAAC"}
|
|
@@ -32,7 +32,7 @@ const to_cell_audit_event_json = (row) => ({
|
|
|
32
32
|
event_type: row.event_type,
|
|
33
33
|
outcome: row.outcome,
|
|
34
34
|
actor_id: row.actor_id,
|
|
35
|
-
created_at:
|
|
35
|
+
created_at: row.created_at
|
|
36
36
|
});
|
|
37
37
|
export const create_cell_audit_actions = () => {
|
|
38
38
|
const handler = async (input, ctx) => {
|
|
@@ -36,7 +36,7 @@ export const to_field_json = (row) => ({
|
|
|
36
36
|
source_id: row.source_id,
|
|
37
37
|
name: row.name,
|
|
38
38
|
target_id: row.target_id,
|
|
39
|
-
created_at:
|
|
39
|
+
created_at: row.created_at
|
|
40
40
|
});
|
|
41
41
|
/** Create the three `cell_field_*` RPC actions. */
|
|
42
42
|
export const create_cell_field_actions = (deps) => {
|
|
@@ -44,7 +44,7 @@ export const to_grant_json = (row) => ({
|
|
|
44
44
|
role: row.role,
|
|
45
45
|
scope_id: row.scope_id,
|
|
46
46
|
granted_by: row.granted_by,
|
|
47
|
-
created_at:
|
|
47
|
+
created_at: row.created_at
|
|
48
48
|
});
|
|
49
49
|
/**
|
|
50
50
|
* Build the audit-metadata principal envelope from a `cell_grant` row.
|
|
@@ -44,7 +44,7 @@ export const to_item_json = (row) => ({
|
|
|
44
44
|
// on ingress).
|
|
45
45
|
position: row.position,
|
|
46
46
|
child_id: row.child_id,
|
|
47
|
-
created_at:
|
|
47
|
+
created_at: row.created_at
|
|
48
48
|
});
|
|
49
49
|
const position_taken_error = () => jsonrpc_errors.invalid_params('cell_item position taken', {
|
|
50
50
|
reason: ERROR_CELL_ITEM_POSITION_TAKEN
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,8FAQqB,CAAC;
|
|
1
|
+
{"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,8FAQqB,CAAC;AAKjD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,4CAA4C,GACxD,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAgB5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAIlF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAUxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,CAMjB,CAAC"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { assert_row } from '../db/assert_row.js';
|
|
10
|
-
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
10
|
+
import { columns_sql, iso8601_timestamp_expr, qualify_columns } from '../db/sql_columns.js';
|
|
11
11
|
/**
|
|
12
12
|
* The full `invite` column set, named explicitly so a row read fails loud on
|
|
13
13
|
* schema drift — invite rows ride the `invite_create` / `invite_list` RPC
|
|
@@ -26,6 +26,8 @@ export const INVITE_COLUMNS = [
|
|
|
26
26
|
'created_at',
|
|
27
27
|
'created_by'
|
|
28
28
|
];
|
|
29
|
+
/** The `invite` timestamp override, by row qualifier (`''` bare, `i` for the username JOIN). */
|
|
30
|
+
const invite_expr = iso8601_timestamp_expr(INVITE_COLUMNS, ['claimed_at', 'created_at']);
|
|
29
31
|
/**
|
|
30
32
|
* Create a new invite.
|
|
31
33
|
*
|
|
@@ -37,20 +39,20 @@ export const INVITE_COLUMNS = [
|
|
|
37
39
|
export const query_create_invite = async (deps, input) => {
|
|
38
40
|
const row = await deps.db.query_one(`INSERT INTO invite (email, username, created_by)
|
|
39
41
|
VALUES ($1, $2, $3)
|
|
40
|
-
RETURNING ${columns_sql(INVITE_COLUMNS)}`, [input.email ?? null, input.username ?? null, input.created_by]);
|
|
42
|
+
RETURNING ${columns_sql(INVITE_COLUMNS, invite_expr(''))}`, [input.email ?? null, input.username ?? null, input.created_by]);
|
|
41
43
|
return assert_row(row, 'INSERT INTO invite');
|
|
42
44
|
};
|
|
43
45
|
/**
|
|
44
46
|
* Find an unclaimed invite by email (case-insensitive).
|
|
45
47
|
*/
|
|
46
48
|
export const query_invite_find_unclaimed_by_email = async (deps, email) => {
|
|
47
|
-
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite WHERE LOWER(email) = LOWER($1) AND claimed_at IS NULL`, [email]);
|
|
49
|
+
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS, invite_expr(''))} FROM invite WHERE LOWER(email) = LOWER($1) AND claimed_at IS NULL`, [email]);
|
|
48
50
|
};
|
|
49
51
|
/**
|
|
50
52
|
* Find an unclaimed invite by username (case-insensitive).
|
|
51
53
|
*/
|
|
52
54
|
export const query_invite_find_unclaimed_by_username = async (deps, username) => {
|
|
53
|
-
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite WHERE LOWER(username) = LOWER($1) AND claimed_at IS NULL`, [username]);
|
|
55
|
+
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS, invite_expr(''))} FROM invite WHERE LOWER(username) = LOWER($1) AND claimed_at IS NULL`, [username]);
|
|
54
56
|
};
|
|
55
57
|
/**
|
|
56
58
|
* Find an unclaimed invite matching email and/or username, taking a
|
|
@@ -75,7 +77,7 @@ export const query_invite_find_unclaimed_by_username = async (deps, username) =>
|
|
|
75
77
|
* @returns the matching invite (locked), or `undefined`
|
|
76
78
|
*/
|
|
77
79
|
export const query_invite_find_unclaimed_match_for_update = async (deps, email, username) => {
|
|
78
|
-
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite WHERE claimed_at IS NULL AND (
|
|
80
|
+
return deps.db.query_one(`SELECT ${columns_sql(INVITE_COLUMNS, invite_expr(''))} FROM invite WHERE claimed_at IS NULL AND (
|
|
79
81
|
(email IS NOT NULL AND username IS NULL
|
|
80
82
|
AND $1::text IS NOT NULL AND LOWER(email) = LOWER($1::text))
|
|
81
83
|
OR
|
|
@@ -85,7 +87,7 @@ export const query_invite_find_unclaimed_match_for_update = async (deps, email,
|
|
|
85
87
|
(email IS NOT NULL AND username IS NOT NULL
|
|
86
88
|
AND $1::text IS NOT NULL AND LOWER(email) = LOWER($1::text)
|
|
87
89
|
AND LOWER(username) = LOWER($2))
|
|
88
|
-
) ORDER BY created_at ASC, id ASC LIMIT 1
|
|
90
|
+
) ORDER BY invite.created_at ASC, invite.id ASC LIMIT 1
|
|
89
91
|
FOR UPDATE`, [email, username]);
|
|
90
92
|
};
|
|
91
93
|
/**
|
|
@@ -118,7 +120,7 @@ export const query_invite_claim_unscoped = async (deps, invite_id, account_id) =
|
|
|
118
120
|
* List all invites, newest first.
|
|
119
121
|
*/
|
|
120
122
|
export const query_invite_list_all = async (deps) => {
|
|
121
|
-
return deps.db.query(`SELECT ${columns_sql(INVITE_COLUMNS)} FROM invite ORDER BY created_at DESC`);
|
|
123
|
+
return deps.db.query(`SELECT ${columns_sql(INVITE_COLUMNS, invite_expr(''))} FROM invite ORDER BY invite.created_at DESC`);
|
|
122
124
|
};
|
|
123
125
|
/**
|
|
124
126
|
* List all invites with resolved creator/claimer usernames, newest first.
|
|
@@ -127,7 +129,7 @@ export const query_invite_list_all = async (deps) => {
|
|
|
127
129
|
* @returns invites with `created_by_username` and `claimed_by_username`
|
|
128
130
|
*/
|
|
129
131
|
export const query_invite_list_all_with_usernames = async (deps) => {
|
|
130
|
-
return deps.db.query(`SELECT ${qualify_columns(INVITE_COLUMNS, 'i')},
|
|
132
|
+
return deps.db.query(`SELECT ${qualify_columns(INVITE_COLUMNS, 'i', invite_expr('i'))},
|
|
131
133
|
act.name AS created_by_username,
|
|
132
134
|
a.username AS claimed_by_username
|
|
133
135
|
FROM invite i
|
|
@@ -34,6 +34,17 @@ export declare const ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN = "GLOBAL";
|
|
|
34
34
|
* `db/sql_columns.ts`.
|
|
35
35
|
*/
|
|
36
36
|
export declare const ROLE_GRANT_OFFER_COLUMNS: readonly ["id", "from_actor_id", "to_account_id", "to_actor_id", "role", "scope_kind", "scope_id", "message", "created_at", "expires_at", "accepted_at", "declined_at", "decline_reason", "retracted_at", "superseded_at", "resulting_role_grant_id"];
|
|
37
|
+
/**
|
|
38
|
+
* The `role_grant_offer` timestamp override, by row qualifier (`''` bare,
|
|
39
|
+
* `o` for the history JOIN, `u` for the `updated` CTE's outer select). Lives
|
|
40
|
+
* beside the const for the same reason the const does — both query modules
|
|
41
|
+
* that project this table need it.
|
|
42
|
+
*
|
|
43
|
+
* A cascade's inner `RETURNING` deliberately projects the *raw* columns: the
|
|
44
|
+
* outer select over the `updated` CTE applies this, and `to_char` over an
|
|
45
|
+
* already-formatted text column would fail.
|
|
46
|
+
*/
|
|
47
|
+
export declare const role_grant_offer_expr: (alias: string) => import("../db/sql_columns.js").ColumnExpr;
|
|
37
48
|
/**
|
|
38
49
|
* Outer `SELECT` over an `updated` CTE of `role_grant_offer` rows, joining
|
|
39
50
|
* the grantor's `account_id` on as `from_account_id` — the `SupersededOffer` /
|
|
@@ -1 +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;;;;;;;;;;;;;;;GAeG;AAKH,mHAAmH;AACnH,eAAO,MAAM,oCAAoC,yCAAyC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,WAAW,CAAC;AAEjE;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,uPAiBmB,CAAC;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,
|
|
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;;;;;;;;;;;;;;;GAeG;AAKH,mHAAmH;AACnH,eAAO,MAAM,oCAAoC,yCAAyC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,WAAW,CAAC;AAEjE;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,uPAiBmB,CAAC;AAEzD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,8DAOhC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,oCAAoC,QAMI,CAAC;AAEtD,eAAO,MAAM,uBAAuB,qzCAkClC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC,2XAYpB,CAAC;AAE/B,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,kOAMX,CAAC"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
|
-
import { qualify_columns } from '../db/sql_columns.js';
|
|
17
|
+
import { iso8601_timestamp_expr, qualify_columns } from '../db/sql_columns.js';
|
|
18
18
|
/** Sentinel UUID used inside the partial unique indexes to collapse `scope_id IS NULL` into a comparable value. */
|
|
19
19
|
export const ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
20
20
|
/**
|
|
@@ -52,6 +52,24 @@ export const ROLE_GRANT_OFFER_COLUMNS = [
|
|
|
52
52
|
'superseded_at',
|
|
53
53
|
'resulting_role_grant_id'
|
|
54
54
|
];
|
|
55
|
+
/**
|
|
56
|
+
* The `role_grant_offer` timestamp override, by row qualifier (`''` bare,
|
|
57
|
+
* `o` for the history JOIN, `u` for the `updated` CTE's outer select). Lives
|
|
58
|
+
* beside the const for the same reason the const does — both query modules
|
|
59
|
+
* that project this table need it.
|
|
60
|
+
*
|
|
61
|
+
* A cascade's inner `RETURNING` deliberately projects the *raw* columns: the
|
|
62
|
+
* outer select over the `updated` CTE applies this, and `to_char` over an
|
|
63
|
+
* already-formatted text column would fail.
|
|
64
|
+
*/
|
|
65
|
+
export const role_grant_offer_expr = iso8601_timestamp_expr(ROLE_GRANT_OFFER_COLUMNS, [
|
|
66
|
+
'created_at',
|
|
67
|
+
'expires_at',
|
|
68
|
+
'accepted_at',
|
|
69
|
+
'declined_at',
|
|
70
|
+
'retracted_at',
|
|
71
|
+
'superseded_at'
|
|
72
|
+
]);
|
|
55
73
|
/**
|
|
56
74
|
* Outer `SELECT` over an `updated` CTE of `role_grant_offer` rows, joining
|
|
57
75
|
* the grantor's `account_id` on as `from_account_id` — the `SupersededOffer` /
|
|
@@ -60,7 +78,7 @@ export const ROLE_GRANT_OFFER_COLUMNS = [
|
|
|
60
78
|
* `auth/role_grant_offer_queries.ts`). The CTE must be named `updated` and
|
|
61
79
|
* `RETURNING` the full offer row.
|
|
62
80
|
*/
|
|
63
|
-
export const ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT = `SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'u')}, grantor.account_id AS from_account_id
|
|
81
|
+
export const ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT = `SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'u', role_grant_offer_expr('u'))}, grantor.account_id AS from_account_id
|
|
64
82
|
FROM updated u
|
|
65
83
|
JOIN actor grantor ON grantor.id = u.from_actor_id`;
|
|
66
84
|
export const ROLE_GRANT_OFFER_SCHEMA = `
|
|
@@ -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":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
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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"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { assert_row } from '../db/assert_row.js';
|
|
15
15
|
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
16
|
-
import { ROLE_GRANT_OFFER_COLUMNS, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT } from './role_grant_offer_ddl.js';
|
|
16
|
+
import { ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT } from './role_grant_offer_ddl.js';
|
|
17
17
|
import { query_create_role_grant, query_role_grant_by_id } from './role_grant_queries.js';
|
|
18
18
|
import { query_audit_log } from './audit_log_queries.js';
|
|
19
19
|
/**
|
|
@@ -154,7 +154,7 @@ export const query_role_grant_offer_create = async (deps, input) => {
|
|
|
154
154
|
to_actor_id = EXCLUDED.to_actor_id,
|
|
155
155
|
message = EXCLUDED.message,
|
|
156
156
|
expires_at = EXCLUDED.expires_at
|
|
157
|
-
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}`, [
|
|
157
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))}`, [
|
|
158
158
|
input.from_actor_id,
|
|
159
159
|
input.to_account_id,
|
|
160
160
|
input.to_actor_id ?? null,
|
|
@@ -193,6 +193,7 @@ export const query_role_grant_offer_decline = async (deps, offer_id, to_account_
|
|
|
193
193
|
AND declined_at IS NULL
|
|
194
194
|
AND retracted_at IS NULL
|
|
195
195
|
AND superseded_at IS NULL
|
|
196
|
+
-- raw columns; the outer grantor select formats them
|
|
196
197
|
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
197
198
|
)
|
|
198
199
|
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [offer_id, to_account_id, reason ?? null]);
|
|
@@ -220,7 +221,7 @@ export const query_role_grant_offer_retract = async (deps, offer_id, from_actor_
|
|
|
220
221
|
AND declined_at IS NULL
|
|
221
222
|
AND retracted_at IS NULL
|
|
222
223
|
AND superseded_at IS NULL
|
|
223
|
-
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}`, [offer_id, from_actor_id]);
|
|
224
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))}`, [offer_id, from_actor_id]);
|
|
224
225
|
if (updated)
|
|
225
226
|
return updated;
|
|
226
227
|
return resolve_terminal_or_missing(deps, offer_id, { from_actor_id });
|
|
@@ -238,7 +239,7 @@ const resolve_terminal_or_missing = async (deps, offer_id, scope) => {
|
|
|
238
239
|
conditions.push(`from_actor_id = $${idx++}`);
|
|
239
240
|
params.push(scope.from_actor_id);
|
|
240
241
|
}
|
|
241
|
-
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer WHERE ${conditions.join(' AND ')}`, params);
|
|
242
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer WHERE ${conditions.join(' AND ')}`, params);
|
|
242
243
|
if (!row)
|
|
243
244
|
return null;
|
|
244
245
|
if (row.accepted_at || row.declined_at || row.retracted_at || row.superseded_at) {
|
|
@@ -254,14 +255,14 @@ const resolve_terminal_or_missing = async (deps, offer_id, scope) => {
|
|
|
254
255
|
* sweep (`query_role_grant_offer_sweep_expired`) handles audit tombstoning.
|
|
255
256
|
*/
|
|
256
257
|
export const query_role_grant_offer_list = async (deps, to_account_id) => {
|
|
257
|
-
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer
|
|
258
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer
|
|
258
259
|
WHERE to_account_id = $1
|
|
259
260
|
AND accepted_at IS NULL
|
|
260
261
|
AND declined_at IS NULL
|
|
261
262
|
AND retracted_at IS NULL
|
|
262
263
|
AND superseded_at IS NULL
|
|
263
264
|
AND expires_at > NOW()
|
|
264
|
-
ORDER BY expires_at ASC`, [to_account_id]);
|
|
265
|
+
ORDER BY role_grant_offer.expires_at ASC`, [to_account_id]);
|
|
265
266
|
};
|
|
266
267
|
/**
|
|
267
268
|
* List every offer involving an account (either direction), newest first.
|
|
@@ -269,7 +270,7 @@ export const query_role_grant_offer_list = async (deps, to_account_id) => {
|
|
|
269
270
|
* Includes terminal offers — used by the grantor-side admin / history view.
|
|
270
271
|
*/
|
|
271
272
|
export const query_role_grant_offer_history_for_account = async (deps, account_id, limit = 100, offset = 0) => {
|
|
272
|
-
return deps.db.query(`SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')} FROM role_grant_offer o
|
|
273
|
+
return deps.db.query(`SELECT ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o', role_grant_offer_expr('o'))} FROM role_grant_offer o
|
|
273
274
|
LEFT JOIN actor a ON a.id = o.from_actor_id
|
|
274
275
|
WHERE o.to_account_id = $1 OR a.account_id = $1
|
|
275
276
|
ORDER BY o.created_at DESC
|
|
@@ -280,7 +281,7 @@ export const query_role_grant_offer_history_for_account = async (deps, account_i
|
|
|
280
281
|
* expired (server-side filter), or missing.
|
|
281
282
|
*/
|
|
282
283
|
export const query_role_grant_offer_find_pending = async (deps, offer_id) => {
|
|
283
|
-
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer
|
|
284
|
+
const row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer
|
|
284
285
|
WHERE id = $1
|
|
285
286
|
AND accepted_at IS NULL
|
|
286
287
|
AND declined_at IS NULL
|
|
@@ -298,13 +299,13 @@ export const query_role_grant_offer_find_pending = async (deps, offer_id) => {
|
|
|
298
299
|
* already exists for the offer id).
|
|
299
300
|
*/
|
|
300
301
|
export const query_role_grant_offer_sweep_expired = async (deps) => {
|
|
301
|
-
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)} FROM role_grant_offer
|
|
302
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))} FROM role_grant_offer
|
|
302
303
|
WHERE accepted_at IS NULL
|
|
303
304
|
AND declined_at IS NULL
|
|
304
305
|
AND retracted_at IS NULL
|
|
305
306
|
AND superseded_at IS NULL
|
|
306
307
|
AND expires_at <= NOW()
|
|
307
|
-
ORDER BY expires_at ASC`);
|
|
308
|
+
ORDER BY role_grant_offer.expires_at ASC`);
|
|
308
309
|
};
|
|
309
310
|
/**
|
|
310
311
|
* Accept an offer atomically: mark accepted, insert the role_grant, stamp
|
|
@@ -346,12 +347,23 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
346
347
|
// We defer writing `accepted_at` until the role_grant row exists — the
|
|
347
348
|
// `role_grant_offer_role_grant_iff_accepted` CHECK constraint demands both be set
|
|
348
349
|
// (or neither) at row-visibility time.
|
|
349
|
-
|
|
350
|
+
//
|
|
351
|
+
// Trailing `expires_at > NOW() AS still_valid` rides on the FOR UPDATE row
|
|
352
|
+
// so the expiry branch below compares the **raw** column on Postgres' clock
|
|
353
|
+
// — the projected `expires_at` is floored to the second, so comparing it in
|
|
354
|
+
// JS would report expiry up to a second early. Twin of the Rust
|
|
355
|
+
// `query_accept_offer`'s `o.expires_at > NOW() AS still_valid`.
|
|
356
|
+
const locked_row = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))},
|
|
357
|
+
role_grant_offer.expires_at > NOW() AS still_valid
|
|
358
|
+
FROM role_grant_offer
|
|
350
359
|
WHERE id = $1 AND to_account_id = $2
|
|
351
360
|
FOR UPDATE`, [offer_id, to_account_id]);
|
|
352
|
-
if (!
|
|
361
|
+
if (!locked_row) {
|
|
353
362
|
throw new RoleGrantOfferNotFoundError(offer_id);
|
|
354
363
|
}
|
|
364
|
+
// `still_valid` is the comparison result, not a column of the offer — split
|
|
365
|
+
// it off so `locked` stays exactly `ROLE_GRANT_OFFER_COLUMNS`.
|
|
366
|
+
const { still_valid, ...locked } = locked_row;
|
|
355
367
|
if (locked.accepted_at) {
|
|
356
368
|
// Race winner already committed; return the pre-existing role_grant.
|
|
357
369
|
// `role_grant_offer_role_grant_iff_accepted` CHECK guarantees resulting_role_grant_id is non-null.
|
|
@@ -377,8 +389,9 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
377
389
|
}
|
|
378
390
|
// Expiry check AFTER the accepted-path: a validly-accepted offer past its
|
|
379
391
|
// expires_at still returns the role_grant idempotently. Only pending offers
|
|
380
|
-
// past expiry reach this branch.
|
|
381
|
-
|
|
392
|
+
// past expiry reach this branch. `still_valid` rode on the FOR UPDATE row
|
|
393
|
+
// above, so the comparison stays on the raw column and Postgres' clock.
|
|
394
|
+
if (!still_valid) {
|
|
382
395
|
throw new RoleGrantOfferExpiredError(offer_id);
|
|
383
396
|
}
|
|
384
397
|
// Actor-targeted offer gate. When the offer is account-grain
|
|
@@ -424,7 +437,7 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
424
437
|
const offer_accepted = await deps.db.query_one(`UPDATE role_grant_offer
|
|
425
438
|
SET accepted_at = NOW(), resulting_role_grant_id = $2
|
|
426
439
|
WHERE id = $1
|
|
427
|
-
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}`, [locked.id, role_grant.id]);
|
|
440
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS, role_grant_offer_expr(''))}`, [locked.id, role_grant.id]);
|
|
428
441
|
const offer = assert_row(offer_accepted, 'mark offer accepted');
|
|
429
442
|
// Supersede sibling pending offers for the same (to_account, role, scope).
|
|
430
443
|
// Forecloses the "accept this other sibling later to get the role back
|
|
@@ -442,6 +455,7 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
442
455
|
AND declined_at IS NULL
|
|
443
456
|
AND retracted_at IS NULL
|
|
444
457
|
AND superseded_at IS NULL
|
|
458
|
+
-- raw columns; the outer grantor select formats them
|
|
445
459
|
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
446
460
|
)
|
|
447
461
|
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [to_account_id, offer.role, offer.scope_id, offer.id]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAS3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,0KAaoB,CAAC;
|
|
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 { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
10
|
+
import { columns_sql, iso8601_timestamp_expr, qualify_columns } from '../db/sql_columns.js';
|
|
11
11
|
import { ROLE_GRANT_OFFER_COLUMNS, ROLE_GRANT_OFFER_SCOPE_KIND_GLOBAL_TOKEN, ROLE_GRANT_OFFER_SCOPE_SENTINEL_UUID, ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT } from './role_grant_offer_ddl.js';
|
|
12
12
|
/**
|
|
13
13
|
* The full `role_grant` column set, named explicitly so a row read fails loud
|
|
@@ -31,6 +31,17 @@ export const ROLE_GRANT_COLUMNS = [
|
|
|
31
31
|
'granted_by',
|
|
32
32
|
'source_offer_id'
|
|
33
33
|
];
|
|
34
|
+
/**
|
|
35
|
+
* The `role_grant` timestamp override; every read here is on the bare table.
|
|
36
|
+
* `created_at` / `expires_at` ride `RoleGrantSummaryJson` (twin of the
|
|
37
|
+
* spine's `role_grant_summary_columns`); `revoked_at` is what
|
|
38
|
+
* `is_role_grant_active` reads.
|
|
39
|
+
*/
|
|
40
|
+
const role_grant_expr = iso8601_timestamp_expr(ROLE_GRANT_COLUMNS, [
|
|
41
|
+
'created_at',
|
|
42
|
+
'expires_at',
|
|
43
|
+
'revoked_at'
|
|
44
|
+
])('');
|
|
34
45
|
/**
|
|
35
46
|
* Grant a role_grant to an actor.
|
|
36
47
|
* Idempotent — if an active role_grant already exists for this actor, role, and
|
|
@@ -62,7 +73,7 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
62
73
|
)
|
|
63
74
|
WHERE revoked_at IS NULL
|
|
64
75
|
DO NOTHING
|
|
65
|
-
RETURNING ${columns_sql(ROLE_GRANT_COLUMNS)}`, [
|
|
76
|
+
RETURNING ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)}`, [
|
|
66
77
|
input.actor_id,
|
|
67
78
|
input.role,
|
|
68
79
|
input.scope_kind ?? null,
|
|
@@ -74,7 +85,7 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
74
85
|
if (inserted)
|
|
75
86
|
return inserted;
|
|
76
87
|
// Active role_grant already exists — return it (idempotent grant).
|
|
77
|
-
const existing = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant
|
|
88
|
+
const existing = await deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant
|
|
78
89
|
WHERE actor_id = $1
|
|
79
90
|
AND role = $2
|
|
80
91
|
AND scope_kind IS NOT DISTINCT FROM $3
|
|
@@ -90,7 +101,7 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
90
101
|
* (a row the offer's own IDOR guard has already vouched for). Authority
|
|
91
102
|
* checks stay at the call site.
|
|
92
103
|
*/
|
|
93
|
-
export const query_role_grant_by_id = async (deps, id) => deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant WHERE id = $1`, [id]);
|
|
104
|
+
export const query_role_grant_by_id = async (deps, id) => deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant WHERE id = $1`, [id]);
|
|
94
105
|
/**
|
|
95
106
|
* Look up the role of an active role_grant (constrained to a specific
|
|
96
107
|
* actor) plus the actor's `account_id`.
|
|
@@ -168,6 +179,7 @@ export const query_revoke_role_grant = async (deps, role_grant_id, actor_id, rev
|
|
|
168
179
|
AND o.declined_at IS NULL
|
|
169
180
|
AND o.retracted_at IS NULL
|
|
170
181
|
AND o.superseded_at IS NULL
|
|
182
|
+
-- raw columns; the outer grantor select formats them
|
|
171
183
|
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
172
184
|
)
|
|
173
185
|
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [actor_id, revoked.role, revoked.scope_id]);
|
|
@@ -183,11 +195,11 @@ export const query_revoke_role_grant = async (deps, role_grant_id, actor_id, rev
|
|
|
183
195
|
* Find all active (non-revoked, non-expired) role_grants for an actor.
|
|
184
196
|
*/
|
|
185
197
|
export const query_role_grant_find_active_for_actor = async (deps, actor_id) => {
|
|
186
|
-
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant
|
|
198
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant
|
|
187
199
|
WHERE actor_id = $1
|
|
188
200
|
AND revoked_at IS NULL
|
|
189
201
|
AND (expires_at IS NULL OR expires_at > NOW())
|
|
190
|
-
ORDER BY created_at`, [actor_id]);
|
|
202
|
+
ORDER BY role_grant.created_at`, [actor_id]);
|
|
191
203
|
};
|
|
192
204
|
/**
|
|
193
205
|
* Check if an actor has an active role_grant for a given role.
|
|
@@ -314,7 +326,7 @@ export const query_count_active_accounts_with_global_role = async (deps, role) =
|
|
|
314
326
|
* List all role_grants for an actor (including revoked/expired).
|
|
315
327
|
*/
|
|
316
328
|
export const query_role_grant_list_for_actor = async (deps, actor_id) => {
|
|
317
|
-
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS)} FROM role_grant WHERE actor_id = $1 ORDER BY created_at DESC`, [actor_id]);
|
|
329
|
+
return deps.db.query(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant WHERE actor_id = $1 ORDER BY role_grant.created_at DESC`, [actor_id]);
|
|
318
330
|
};
|
|
319
331
|
/**
|
|
320
332
|
* Find the account ID of an account that holds an active role_grant for a given role.
|
|
@@ -396,6 +408,7 @@ export const query_role_grant_revoke_for_scope = async (deps, scope_id, revoked_
|
|
|
396
408
|
AND o.declined_at IS NULL
|
|
397
409
|
AND o.retracted_at IS NULL
|
|
398
410
|
AND o.superseded_at IS NULL
|
|
411
|
+
-- raw columns; the outer grantor select formats them
|
|
399
412
|
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
400
413
|
)
|
|
401
414
|
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [scope_id]);
|
|
@@ -448,6 +461,7 @@ export const query_role_grant_revoke_role = async (deps, actor_id, role, revoked
|
|
|
448
461
|
AND o.declined_at IS NULL
|
|
449
462
|
AND o.retracted_at IS NULL
|
|
450
463
|
AND o.superseded_at IS NULL
|
|
464
|
+
-- raw columns; the outer grantor select formats them
|
|
451
465
|
RETURNING ${qualify_columns(ROLE_GRANT_OFFER_COLUMNS, 'o')}
|
|
452
466
|
)
|
|
453
467
|
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [actor_id, role]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;
|
|
1
|
+
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,CAKnF,CAAC"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { hash_blake3 } from '@fuzdev/fuz_util/hash_blake3.ts';
|
|
10
10
|
import { generate_random_base64url } from '../crypto.js';
|
|
11
|
-
import { columns_sql, qualify_columns } from '../db/sql_columns.js';
|
|
11
|
+
import { columns_sql, iso8601_timestamp_expr, qualify_columns } from '../db/sql_columns.js';
|
|
12
12
|
/**
|
|
13
13
|
* Session lifetime in milliseconds (30 days).
|
|
14
14
|
*
|
|
@@ -70,6 +70,11 @@ export const AUTH_SESSION_COLUMNS = [
|
|
|
70
70
|
'created_at',
|
|
71
71
|
'expires_at'
|
|
72
72
|
];
|
|
73
|
+
/** The `auth_session` timestamp override, by row qualifier (`''` bare, `s` for the admin listing's JOIN). */
|
|
74
|
+
const auth_session_expr = iso8601_timestamp_expr(AUTH_SESSION_COLUMNS, [
|
|
75
|
+
'created_at',
|
|
76
|
+
'expires_at'
|
|
77
|
+
]);
|
|
73
78
|
/**
|
|
74
79
|
* Get a session if it exists, is not expired, and has not been revoked.
|
|
75
80
|
*
|
|
@@ -77,7 +82,7 @@ export const AUTH_SESSION_COLUMNS = [
|
|
|
77
82
|
* @param token_hash - blake3 hash of the session token
|
|
78
83
|
*/
|
|
79
84
|
export const query_session_get_valid = async (deps, token_hash) => {
|
|
80
|
-
return deps.db.query_one(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS)} FROM auth_session WHERE id = $1 AND expires_at > NOW()`, [token_hash]);
|
|
85
|
+
return deps.db.query_one(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS, auth_session_expr(''))} FROM auth_session WHERE id = $1 AND expires_at > NOW()`, [token_hash]);
|
|
81
86
|
};
|
|
82
87
|
/**
|
|
83
88
|
* Revoke (delete) a session by its token hash, with no account scoping.
|
|
@@ -123,7 +128,7 @@ export const query_session_revoke_all_for_account = async (deps, account_id) =>
|
|
|
123
128
|
* List sessions for an account, newest first.
|
|
124
129
|
*/
|
|
125
130
|
export const query_session_list_for_account = async (deps, account_id, limit = 50) => {
|
|
126
|
-
return deps.db.query(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS)} FROM auth_session WHERE account_id = $1 ORDER BY created_at DESC LIMIT $2`, [account_id, limit]);
|
|
131
|
+
return deps.db.query(`SELECT ${columns_sql(AUTH_SESSION_COLUMNS, auth_session_expr(''))} FROM auth_session WHERE account_id = $1 ORDER BY auth_session.created_at DESC LIMIT $2`, [account_id, limit]);
|
|
127
132
|
};
|
|
128
133
|
/**
|
|
129
134
|
* Enforce a per-account session limit by evicting the oldest sessions.
|
|
@@ -187,7 +192,7 @@ export const query_session_enforce_limit = async (deps, account_id, max_sessions
|
|
|
187
192
|
* @returns active sessions joined with account usernames, newest first
|
|
188
193
|
*/
|
|
189
194
|
export const query_session_list_all_active = async (deps, limit = 200) => {
|
|
190
|
-
return deps.db.query(`SELECT ${qualify_columns(AUTH_SESSION_COLUMNS, 's')}, a.username
|
|
195
|
+
return deps.db.query(`SELECT ${qualify_columns(AUTH_SESSION_COLUMNS, 's', auth_session_expr('s'))}, a.username
|
|
191
196
|
FROM auth_session s
|
|
192
197
|
JOIN account a ON a.id = s.account_id
|
|
193
198
|
WHERE s.expires_at > NOW()
|
package/dist/db/CLAUDE.md
CHANGED
|
@@ -30,7 +30,26 @@ the code.
|
|
|
30
30
|
- `sql_columns.ts` — `columns_sql` / `qualify_columns` / `omit_columns`,
|
|
31
31
|
the derivation helpers every `*_COLUMNS` projection const's variants
|
|
32
32
|
(alias-qualified JOIN reads, the client-safe `minus token_hash` listing)
|
|
33
|
-
are built from, so the drift guard on the base const covers them.
|
|
33
|
+
are built from, so the drift guard on the base const covers them. Both
|
|
34
|
+
renderers take an optional `ColumnExpr` — a per-column expression
|
|
35
|
+
override, twin of the third argument to `fuz_db::qualify_columns` — and
|
|
36
|
+
alias an overridden column back to its own name (TS reads rows by name;
|
|
37
|
+
the Rust twin decodes positionally and needs no output name). The one
|
|
38
|
+
override in this repo is `iso8601_timestamp_column(alias, column)`, twin
|
|
39
|
+
of `fuz_db::iso8601_timestamp_column` down to the format literal
|
|
40
|
+
(`to_char(… AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`): every
|
|
41
|
+
timestamp that reaches the wire projects through it, so both spines
|
|
42
|
+
serialize the same instant to the same 20-character second-precision UTC
|
|
43
|
+
string. Each query module builds its override once with
|
|
44
|
+
`iso8601_timestamp_expr(TABLE_COLUMNS, [...timestamp columns])` — typed
|
|
45
|
+
against the const, checked at load — and passes `expr(alias)` at each
|
|
46
|
+
read. `timestamp.ts` (`to_iso8601_seconds` / `ISO8601_SECONDS`) is that
|
|
47
|
+
shape for stamps minted in TS rather than read from a row. A cascade's
|
|
48
|
+
inner `RETURNING` inside a `WITH updated AS (…)` stays raw — the outer
|
|
49
|
+
select formats, and `to_char` over already-formatted text would fail.
|
|
50
|
+
`fact`'s store rows are the deliberate exception: `PgFactStore` feeds
|
|
51
|
+
`FactMeta.created_at`, a `Date` in the `@fuzdev/fuz_util` `FactStore`
|
|
52
|
+
contract, and nothing serializes it.
|
|
34
53
|
- `status.ts` — CLI DB status utility.
|
|
35
54
|
- `schema_ready.ts` — `/ready` deploy-gate core: `query_public_columns`
|
|
36
55
|
(keeps `schema_version`, unlike `query_schema_snapshot`), pure
|
|
@@ -108,7 +127,7 @@ child_id`, fractional-index keyed): `query_cell_item_insert` (throws
|
|
|
108
127
|
(the meta read derives `minus bytes`). The derived `grant_count` stays an
|
|
109
128
|
appended expression in the exported `cell_row_projection(alias)` rather than a
|
|
110
129
|
`CELL_COLUMNS` entry, so the const is exactly the table's column set. Column
|
|
111
|
-
order twins the Rust `fuz_cell` `*_COLUMNS` consts. Every const is pinned to
|
|
130
|
+
order twins the Rust `fuz_cell` / `fuz_fact` `*_COLUMNS` consts. Every const is pinned to
|
|
112
131
|
the live schema — and every public table forced to carry a const or a named
|
|
113
132
|
exemption — by `src/test/db/column_projections.db.test.ts`; the cell
|
|
114
133
|
`*_queries.db.test.ts` files pin the aliased JOIN reads and `grant_count`.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module
|
|
14
14
|
*/
|
|
15
|
-
import { AUDIT_LOG_COLUMNS } from '../auth/audit_log_queries.js';
|
|
15
|
+
import { AUDIT_LOG_COLUMNS, audit_log_expr } from '../auth/audit_log_queries.js';
|
|
16
16
|
import { columns_sql } from './sql_columns.js';
|
|
17
17
|
/**
|
|
18
18
|
* Metadata-jsonb keys cell-domain events use to name the cell. This
|
|
@@ -53,5 +53,5 @@ export const query_audit_log_list_by_cell = async (deps, cell_id, options) => {
|
|
|
53
53
|
const where = `WHERE ${conditions.join(' AND ')}`;
|
|
54
54
|
const limit_placeholder = `$${i++}`;
|
|
55
55
|
params.push(options.limit);
|
|
56
|
-
return deps.db.query(`SELECT ${columns_sql(AUDIT_LOG_COLUMNS)} FROM audit_log ${where} ORDER BY seq DESC LIMIT ${limit_placeholder}`, params);
|
|
56
|
+
return deps.db.query(`SELECT ${columns_sql(AUDIT_LOG_COLUMNS, audit_log_expr(''))} FROM audit_log ${where} ORDER BY seq DESC LIMIT ${limit_placeholder}`, params);
|
|
57
57
|
};
|