@opengeni/db 3.4.0-canary.2 → 3.5.2-canary.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/canonical-human-identities.d.ts +20 -0
- package/dist/canonical-human-identities.js +7 -3
- package/dist/{chunk-C2AFP7SB.js → chunk-6NYKC5DD.js} +6507 -6040
- package/dist/chunk-6NYKC5DD.js.map +1 -0
- package/dist/{chunk-O6FTELKH.js → chunk-FJHEWYV5.js} +3 -3
- package/dist/{chunk-ZWRIPCTR.js → chunk-NRGUX3M5.js} +2 -2
- package/dist/{chunk-TRBOWUG3.js → chunk-ORKVXHL2.js} +2 -2
- package/dist/{chunk-PYMY3AJW.js → chunk-PERPE2UY.js} +172 -7
- package/dist/chunk-PERPE2UY.js.map +1 -0
- package/dist/chunk-TRCYF26N.js +299 -0
- package/dist/chunk-TRCYF26N.js.map +1 -0
- package/dist/{chunk-V3TRUXFJ.js → chunk-U7NKCX5E.js} +72 -19
- package/dist/chunk-U7NKCX5E.js.map +1 -0
- package/dist/{chunk-H7MXYG7R.js → chunk-X6T2P3L3.js} +2 -2
- package/dist/{chunk-HY3F5Q4E.js → chunk-Y44FKTYU.js} +3 -3
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +1698 -995
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-set-schema.d.ts +1399 -0
- package/dist/managed-auth-session-sets.d.ts +145 -0
- package/dist/managed-auth-session-sets.js +50 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/organization-recovery-schema.d.ts +2144 -0
- package/dist/organization-recovery.d.ts +186 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +5 -3
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +37 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-control.d.ts +5 -3
- package/dist/session-queue-commands.d.ts +2 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/drizzle/0362_managed_auth_session_sets.sql +2347 -0
- package/drizzle/0363_organization_recovery_custody.sql +2418 -0
- package/drizzle/0364_workspace_learning_policy_snapshot_lock_order.sql +221 -0
- package/package.json +10 -6
- package/src/canonical-human-identities.ts +105 -27
- package/src/index.ts +420 -103
- package/src/managed-auth-session-set-schema.ts +176 -0
- package/src/managed-auth-session-sets.ts +430 -0
- package/src/organization-recovery-schema.ts +277 -0
- package/src/organization-recovery.ts +424 -0
- package/src/provision-roles.ts +49 -0
- package/src/runtime-posture.ts +132 -6
- package/src/schema.ts +2 -0
- package/src/session-control.ts +255 -16
- package/src/session-queue-commands.ts +10 -1
- package/src/session-tool-call-settlement.ts +6 -1
- package/dist/chunk-C2AFP7SB.js.map +0 -1
- package/dist/chunk-PYMY3AJW.js.map +0 -1
- package/dist/chunk-V3TRUXFJ.js.map +0 -1
- /package/dist/{chunk-O6FTELKH.js.map → chunk-FJHEWYV5.js.map} +0 -0
- /package/dist/{chunk-ZWRIPCTR.js.map → chunk-NRGUX3M5.js.map} +0 -0
- /package/dist/{chunk-TRBOWUG3.js.map → chunk-ORKVXHL2.js.map} +0 -0
- /package/dist/{chunk-H7MXYG7R.js.map → chunk-X6T2P3L3.js.map} +0 -0
- /package/dist/{chunk-HY3F5Q4E.js.map → chunk-Y44FKTYU.js.map} +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Migration 0364: keep accepted-attempt learning-policy snapshots on the
|
|
3
|
+
-- canonical workspace -> session -> turn -> attempt lock order.
|
|
4
|
+
--
|
|
5
|
+
-- The original snapshot function locked attempt, turn, and session rows in one
|
|
6
|
+
-- joined SELECT ... FOR SHARE statement. PostgreSQL may acquire row locks in
|
|
7
|
+
-- plan order rather than SQL join order. An attempt-id lookup therefore took a
|
|
8
|
+
-- turn SHARE lock before reaching the session row, while an ordinary lifecycle
|
|
9
|
+
-- writer already held the session and waited for the turn. The two transactions
|
|
10
|
+
-- formed a session/turn cycle and PostgreSQL aborted the snapshot with 40P01.
|
|
11
|
+
--
|
|
12
|
+
-- Acquire each row in the same explicit order as the session lifecycle:
|
|
13
|
+
-- workspaces KEY SHARE, sessions SHARE, session_turns SHARE, then
|
|
14
|
+
-- session_turn_attempts SHARE. A fresh statement revalidates the complete
|
|
15
|
+
-- authority tuple and interruption state after all locks are held, preserving
|
|
16
|
+
-- the accepted-attempt invariant without a planner-dependent lock order.
|
|
17
|
+
|
|
18
|
+
SET LOCAL lock_timeout = '5s';
|
|
19
|
+
SET LOCAL statement_timeout = '5min';
|
|
20
|
+
|
|
21
|
+
DO $snapshot_lock_order$
|
|
22
|
+
DECLARE target_schema text := current_schema();
|
|
23
|
+
BEGIN
|
|
24
|
+
EXECUTE format($ddl$
|
|
25
|
+
CREATE OR REPLACE FUNCTION %I.workspace_learning_policy_get_or_create_snapshot(
|
|
26
|
+
p_account_id uuid,
|
|
27
|
+
p_workspace_id uuid,
|
|
28
|
+
p_session_id uuid,
|
|
29
|
+
p_turn_id uuid,
|
|
30
|
+
p_attempt_id uuid,
|
|
31
|
+
p_execution_generation integer
|
|
32
|
+
) RETURNS TABLE (snapshot_id uuid)
|
|
33
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
|
|
34
|
+
AS $body$
|
|
35
|
+
DECLARE
|
|
36
|
+
accepted_at timestamptz;
|
|
37
|
+
canonical jsonb;
|
|
38
|
+
existing_snapshot record;
|
|
39
|
+
snapshot_revision_id uuid;
|
|
40
|
+
snapshot_revision bigint;
|
|
41
|
+
snapshot_policy_hash text;
|
|
42
|
+
snapshot_activation_version bigint;
|
|
43
|
+
snapshot_activated_at timestamptz;
|
|
44
|
+
snapshot_workspace_mode text;
|
|
45
|
+
snapshot_source_overrides jsonb;
|
|
46
|
+
BEGIN
|
|
47
|
+
IF NULLIF(current_setting('opengeni.account_id', true), '')::uuid IS DISTINCT FROM p_account_id
|
|
48
|
+
OR NULLIF(current_setting('opengeni.workspace_id', true), '')::uuid IS DISTINCT FROM p_workspace_id
|
|
49
|
+
THEN
|
|
50
|
+
RAISE EXCEPTION 'learning-policy snapshot requires exact workspace authority'
|
|
51
|
+
USING ERRCODE = '42501';
|
|
52
|
+
END IF;
|
|
53
|
+
|
|
54
|
+
SELECT * INTO existing_snapshot
|
|
55
|
+
FROM "workspace_learning_policy_snapshots" snapshot
|
|
56
|
+
WHERE snapshot."account_id" = p_account_id
|
|
57
|
+
AND snapshot."workspace_id" = p_workspace_id
|
|
58
|
+
AND snapshot."attempt_id" = p_attempt_id;
|
|
59
|
+
IF FOUND THEN
|
|
60
|
+
IF existing_snapshot."session_id" IS DISTINCT FROM p_session_id
|
|
61
|
+
OR existing_snapshot."turn_id" IS DISTINCT FROM p_turn_id
|
|
62
|
+
OR existing_snapshot."execution_generation" IS DISTINCT FROM p_execution_generation
|
|
63
|
+
THEN
|
|
64
|
+
RAISE EXCEPTION 'learning-policy snapshot attempt identity conflicted'
|
|
65
|
+
USING ERRCODE = '23514';
|
|
66
|
+
END IF;
|
|
67
|
+
snapshot_id := existing_snapshot."id";
|
|
68
|
+
RETURN NEXT;
|
|
69
|
+
RETURN;
|
|
70
|
+
END IF;
|
|
71
|
+
|
|
72
|
+
PERFORM 1
|
|
73
|
+
FROM "workspaces" workspace
|
|
74
|
+
WHERE workspace."id" = p_workspace_id
|
|
75
|
+
AND workspace."account_id" = p_account_id
|
|
76
|
+
FOR KEY SHARE OF workspace;
|
|
77
|
+
IF NOT FOUND THEN
|
|
78
|
+
RAISE EXCEPTION 'learning-policy snapshot requires an exact active attempt'
|
|
79
|
+
USING ERRCODE = '23514';
|
|
80
|
+
END IF;
|
|
81
|
+
|
|
82
|
+
PERFORM 1
|
|
83
|
+
FROM "sessions" session
|
|
84
|
+
WHERE session."account_id" = p_account_id
|
|
85
|
+
AND session."workspace_id" = p_workspace_id
|
|
86
|
+
AND session."id" = p_session_id
|
|
87
|
+
AND session."active_turn_id" = p_turn_id
|
|
88
|
+
FOR SHARE OF session;
|
|
89
|
+
IF NOT FOUND THEN
|
|
90
|
+
RAISE EXCEPTION 'learning-policy snapshot requires an exact active attempt'
|
|
91
|
+
USING ERRCODE = '23514';
|
|
92
|
+
END IF;
|
|
93
|
+
|
|
94
|
+
SELECT turn."created_at" INTO accepted_at
|
|
95
|
+
FROM "session_turns" turn
|
|
96
|
+
WHERE turn."account_id" = p_account_id
|
|
97
|
+
AND turn."workspace_id" = p_workspace_id
|
|
98
|
+
AND turn."session_id" = p_session_id
|
|
99
|
+
AND turn."id" = p_turn_id
|
|
100
|
+
AND turn."execution_generation" = p_execution_generation
|
|
101
|
+
AND turn."active_attempt_id" = p_attempt_id
|
|
102
|
+
AND turn."status" IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
|
|
103
|
+
FOR SHARE OF turn;
|
|
104
|
+
IF NOT FOUND THEN
|
|
105
|
+
RAISE EXCEPTION 'learning-policy snapshot requires an exact active attempt'
|
|
106
|
+
USING ERRCODE = '23514';
|
|
107
|
+
END IF;
|
|
108
|
+
|
|
109
|
+
PERFORM 1
|
|
110
|
+
FROM "session_turn_attempts" attempt
|
|
111
|
+
WHERE attempt."id" = p_attempt_id
|
|
112
|
+
AND attempt."account_id" = p_account_id
|
|
113
|
+
AND attempt."workspace_id" = p_workspace_id
|
|
114
|
+
AND attempt."session_id" = p_session_id
|
|
115
|
+
AND attempt."turn_id" = p_turn_id
|
|
116
|
+
AND attempt."execution_generation" = p_execution_generation
|
|
117
|
+
AND attempt."state" IN ('claimed', 'running')
|
|
118
|
+
FOR SHARE OF attempt;
|
|
119
|
+
IF NOT FOUND THEN
|
|
120
|
+
RAISE EXCEPTION 'learning-policy snapshot requires an exact active attempt'
|
|
121
|
+
USING ERRCODE = '23514';
|
|
122
|
+
END IF;
|
|
123
|
+
|
|
124
|
+
-- This attempt SHARE lock is also the interruption-creation fence. Every
|
|
125
|
+
-- supported interruption writer takes the exact attempt FOR UPDATE before
|
|
126
|
+
-- inserting session_attempt_interruptions. Row locks live until transaction
|
|
127
|
+
-- end, so no such writer can cross the revalidation -> insert -> commit
|
|
128
|
+
-- interval below.
|
|
129
|
+
-- A lock acquisition may wait behind Pause, Steer, replacement, or
|
|
130
|
+
-- settlement. Re-evaluate the complete tuple in a fresh READ COMMITTED
|
|
131
|
+
-- statement after the canonical rows are locked so a pre-wait snapshot
|
|
132
|
+
-- cannot miss the change that just released the lock.
|
|
133
|
+
PERFORM 1
|
|
134
|
+
FROM "sessions" session
|
|
135
|
+
JOIN "session_turns" turn
|
|
136
|
+
ON turn."account_id" = session."account_id"
|
|
137
|
+
AND turn."workspace_id" = session."workspace_id"
|
|
138
|
+
AND turn."session_id" = session."id"
|
|
139
|
+
JOIN "session_turn_attempts" attempt
|
|
140
|
+
ON attempt."account_id" = turn."account_id"
|
|
141
|
+
AND attempt."workspace_id" = turn."workspace_id"
|
|
142
|
+
AND attempt."session_id" = turn."session_id"
|
|
143
|
+
AND attempt."turn_id" = turn."id"
|
|
144
|
+
WHERE session."account_id" = p_account_id
|
|
145
|
+
AND session."workspace_id" = p_workspace_id
|
|
146
|
+
AND session."id" = p_session_id
|
|
147
|
+
AND session."active_turn_id" = p_turn_id
|
|
148
|
+
AND turn."id" = p_turn_id
|
|
149
|
+
AND turn."execution_generation" = p_execution_generation
|
|
150
|
+
AND turn."active_attempt_id" = p_attempt_id
|
|
151
|
+
AND turn."status" IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
|
|
152
|
+
AND attempt."id" = p_attempt_id
|
|
153
|
+
AND attempt."execution_generation" = p_execution_generation
|
|
154
|
+
AND attempt."state" IN ('claimed', 'running')
|
|
155
|
+
AND NOT EXISTS (
|
|
156
|
+
SELECT 1
|
|
157
|
+
FROM "session_attempt_interruptions" interruption
|
|
158
|
+
WHERE interruption."workspace_id" = p_workspace_id
|
|
159
|
+
AND interruption."attempt_id" = p_attempt_id
|
|
160
|
+
AND interruption."state" IN ('pending', 'delivered', 'acknowledged')
|
|
161
|
+
);
|
|
162
|
+
IF NOT FOUND THEN
|
|
163
|
+
RAISE EXCEPTION 'learning-policy snapshot requires an exact active attempt'
|
|
164
|
+
USING ERRCODE = '23514';
|
|
165
|
+
END IF;
|
|
166
|
+
|
|
167
|
+
canonical := workspace_learning_policy_canonical_at(
|
|
168
|
+
p_account_id,
|
|
169
|
+
p_workspace_id,
|
|
170
|
+
accepted_at
|
|
171
|
+
);
|
|
172
|
+
snapshot_revision_id := (canonical->>'revisionId')::uuid;
|
|
173
|
+
snapshot_revision := (canonical->>'revision')::bigint;
|
|
174
|
+
snapshot_policy_hash := canonical->>'policyHash';
|
|
175
|
+
snapshot_activation_version := (canonical->>'activationVersion')::bigint;
|
|
176
|
+
snapshot_activated_at := (canonical->>'activatedAt')::timestamptz;
|
|
177
|
+
snapshot_workspace_mode := canonical->>'workspaceMode';
|
|
178
|
+
snapshot_source_overrides := canonical->'sourceOverrides';
|
|
179
|
+
|
|
180
|
+
INSERT INTO "workspace_learning_policy_snapshots" (
|
|
181
|
+
"account_id", "workspace_id", "session_id", "turn_id", "attempt_id",
|
|
182
|
+
"execution_generation", "revision_id", "revision", "policy_hash",
|
|
183
|
+
"activation_version", "activated_at", "workspace_mode", "source_overrides",
|
|
184
|
+
"snapshot_hash"
|
|
185
|
+
) VALUES (
|
|
186
|
+
p_account_id, p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
|
|
187
|
+
p_execution_generation, snapshot_revision_id, snapshot_revision, snapshot_policy_hash,
|
|
188
|
+
snapshot_activation_version, snapshot_activated_at, snapshot_workspace_mode,
|
|
189
|
+
snapshot_source_overrides,
|
|
190
|
+
workspace_learning_policy_snapshot_hash(
|
|
191
|
+
snapshot_revision_id,
|
|
192
|
+
snapshot_revision,
|
|
193
|
+
snapshot_policy_hash,
|
|
194
|
+
snapshot_activation_version,
|
|
195
|
+
snapshot_activated_at,
|
|
196
|
+
snapshot_workspace_mode,
|
|
197
|
+
snapshot_source_overrides
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
ON CONFLICT ("account_id", "workspace_id", "attempt_id") DO NOTHING
|
|
201
|
+
RETURNING "id" INTO snapshot_id;
|
|
202
|
+
|
|
203
|
+
IF snapshot_id IS NULL THEN
|
|
204
|
+
SELECT snapshot."id" INTO snapshot_id
|
|
205
|
+
FROM "workspace_learning_policy_snapshots" snapshot
|
|
206
|
+
WHERE snapshot."account_id" = p_account_id
|
|
207
|
+
AND snapshot."workspace_id" = p_workspace_id
|
|
208
|
+
AND snapshot."attempt_id" = p_attempt_id
|
|
209
|
+
AND snapshot."session_id" = p_session_id
|
|
210
|
+
AND snapshot."turn_id" = p_turn_id
|
|
211
|
+
AND snapshot."execution_generation" = p_execution_generation;
|
|
212
|
+
IF NOT FOUND THEN
|
|
213
|
+
RAISE EXCEPTION 'learning-policy snapshot concurrent identity conflicted'
|
|
214
|
+
USING ERRCODE = '23514';
|
|
215
|
+
END IF;
|
|
216
|
+
END IF;
|
|
217
|
+
RETURN NEXT;
|
|
218
|
+
END
|
|
219
|
+
$body$
|
|
220
|
+
$ddl$, target_schema, target_schema);
|
|
221
|
+
END $snapshot_lock_order$;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.2-canary.0",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
"types": "./dist/canonical-human-identities.d.ts",
|
|
52
52
|
"import": "./dist/canonical-human-identities.js"
|
|
53
53
|
},
|
|
54
|
+
"./managed-auth-session-sets": {
|
|
55
|
+
"types": "./dist/managed-auth-session-sets.d.ts",
|
|
56
|
+
"import": "./dist/managed-auth-session-sets.js"
|
|
57
|
+
},
|
|
54
58
|
"./session-tenancy": {
|
|
55
59
|
"types": "./dist/session-tenancy.d.ts",
|
|
56
60
|
"import": "./dist/session-tenancy.js"
|
|
@@ -74,11 +78,11 @@
|
|
|
74
78
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
75
79
|
},
|
|
76
80
|
"dependencies": {
|
|
77
|
-
"@opengeni/codemode": "^0.4.
|
|
78
|
-
"@opengeni/codex": "^0.2.19-canary.
|
|
79
|
-
"@opengeni/config": "^0.
|
|
80
|
-
"@opengeni/contracts": "^2.
|
|
81
|
-
"@opengeni/network": "^0.2.3-canary.
|
|
81
|
+
"@opengeni/codemode": "^0.4.16-canary.0",
|
|
82
|
+
"@opengeni/codex": "^0.2.19-canary.4",
|
|
83
|
+
"@opengeni/config": "^0.21.0-canary.0",
|
|
84
|
+
"@opengeni/contracts": "^2.6.0-canary.0",
|
|
85
|
+
"@opengeni/network": "^0.2.3-canary.4",
|
|
82
86
|
"drizzle-orm": "^0.45.2",
|
|
83
87
|
"postgres": "^3.4.7"
|
|
84
88
|
},
|
|
@@ -18,6 +18,37 @@ export type CanonicalHumanSessionAuthority = {
|
|
|
18
18
|
identityStatus: "active" | "recovery_required" | "disputed" | "disabled";
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type CanonicalHumanExactLoginBinding = {
|
|
22
|
+
id: string;
|
|
23
|
+
identityId: string;
|
|
24
|
+
revision: number;
|
|
25
|
+
status: "active" | "recovery_pending" | "stale" | "disputed" | "revoked";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export async function getCanonicalHumanExactLoginBindingForAuthUser(
|
|
29
|
+
db: Database,
|
|
30
|
+
input: { authUserId: string; providerId: string },
|
|
31
|
+
): Promise<CanonicalHumanExactLoginBinding> {
|
|
32
|
+
const [row] = await rawRows<{
|
|
33
|
+
result:
|
|
34
|
+
| (Omit<CanonicalHumanExactLoginBinding, "revision"> & { revision: number | string })
|
|
35
|
+
| null;
|
|
36
|
+
}>(
|
|
37
|
+
db,
|
|
38
|
+
sql`
|
|
39
|
+
select get_canonical_human_exact_login_binding(
|
|
40
|
+
${input.authUserId}, ${input.providerId}
|
|
41
|
+
) as result
|
|
42
|
+
`,
|
|
43
|
+
);
|
|
44
|
+
if (!row?.result) {
|
|
45
|
+
throw new CanonicalHumanIdentityAuthorityError(
|
|
46
|
+
"Authentication did not prove exactly one canonical login binding",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return { ...row.result, revision: Number(row.result.revision) };
|
|
50
|
+
}
|
|
51
|
+
|
|
21
52
|
export class CanonicalHumanIdentityConflictError extends Error {
|
|
22
53
|
readonly name = "CanonicalHumanIdentityConflictError";
|
|
23
54
|
readonly code = "CANONICAL_HUMAN_IDENTITY_CONFLICT";
|
|
@@ -36,6 +67,15 @@ export class CanonicalHumanIdentityOperationReuseError extends Error {
|
|
|
36
67
|
}
|
|
37
68
|
}
|
|
38
69
|
|
|
70
|
+
export class CanonicalHumanIdentityMutationInFlightError extends Error {
|
|
71
|
+
readonly name = "CanonicalHumanIdentityMutationInFlightError";
|
|
72
|
+
readonly code = "CANONICAL_HUMAN_IDENTITY_MUTATION_IN_FLIGHT";
|
|
73
|
+
|
|
74
|
+
constructor() {
|
|
75
|
+
super("Another request is still using this browser actor");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
39
79
|
export class CanonicalHumanIdentityNotFoundError extends Error {
|
|
40
80
|
readonly name = "CanonicalHumanIdentityNotFoundError";
|
|
41
81
|
|
|
@@ -65,6 +105,7 @@ function mapSqlError(error: unknown): never {
|
|
|
65
105
|
const state = nestedPostgresSqlState(error);
|
|
66
106
|
if (state === "40001") throw new CanonicalHumanIdentityConflictError();
|
|
67
107
|
if (state === "23505") throw new CanonicalHumanIdentityOperationReuseError();
|
|
108
|
+
if (state === "55P03") throw new CanonicalHumanIdentityMutationInFlightError();
|
|
68
109
|
if (state === "P0002") throw new CanonicalHumanIdentityNotFoundError();
|
|
69
110
|
if (state === "42501") {
|
|
70
111
|
throw new CanonicalHumanIdentityAuthorityError(
|
|
@@ -111,6 +152,17 @@ export async function ensureCanonicalHumanIdentityForAuthUser(
|
|
|
111
152
|
db: Database,
|
|
112
153
|
authUserId: string,
|
|
113
154
|
): Promise<CanonicalHumanSessionAuthority> {
|
|
155
|
+
try {
|
|
156
|
+
const existing = await getCanonicalHumanIdentityProjection(db, authUserId);
|
|
157
|
+
return {
|
|
158
|
+
identityId: existing.activeIdentity.id,
|
|
159
|
+
identityRevision: existing.activeIdentity.identityRevision,
|
|
160
|
+
authRevision: existing.activeIdentity.authRevision,
|
|
161
|
+
identityStatus: existing.activeIdentity.status,
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (!(error instanceof CanonicalHumanIdentityNotFoundError)) throw error;
|
|
165
|
+
}
|
|
114
166
|
const [user] = await rawRows<{ displayName: string }>(
|
|
115
167
|
db,
|
|
116
168
|
sql`
|
|
@@ -264,6 +316,11 @@ export type ApplyCanonicalHumanIdentityOperationInput = {
|
|
|
264
316
|
providerId?: string | null;
|
|
265
317
|
providerAccountId?: string | null;
|
|
266
318
|
reason: string;
|
|
319
|
+
actorFence?: {
|
|
320
|
+
authorityHash: string;
|
|
321
|
+
actorEpoch: string;
|
|
322
|
+
requestId: string;
|
|
323
|
+
};
|
|
267
324
|
};
|
|
268
325
|
|
|
269
326
|
export async function applyCanonicalHumanIdentityOperation(
|
|
@@ -271,14 +328,46 @@ export async function applyCanonicalHumanIdentityOperation(
|
|
|
271
328
|
input: ApplyCanonicalHumanIdentityOperationInput,
|
|
272
329
|
): Promise<CanonicalHumanIdentityMutationResponseType> {
|
|
273
330
|
try {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
331
|
+
if (input.actorFence) {
|
|
332
|
+
return await db.transaction(async (tx) => {
|
|
333
|
+
const txDb = tx as unknown as Database;
|
|
334
|
+
await rawRows(
|
|
335
|
+
txDb,
|
|
336
|
+
sql`select managed_auth_actor_mutation_fence(
|
|
337
|
+
${input.actorFence!.authorityHash}, ${input.actorFence!.actorEpoch}::bigint,
|
|
338
|
+
${input.actorFence!.requestId}::uuid
|
|
339
|
+
)`,
|
|
340
|
+
);
|
|
341
|
+
return await applyCanonicalHumanIdentityOperationInner(txDb, input);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return await applyCanonicalHumanIdentityOperationInner(db, input);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (
|
|
347
|
+
error instanceof CanonicalHumanIdentityConflictError ||
|
|
348
|
+
error instanceof CanonicalHumanIdentityOperationReuseError ||
|
|
349
|
+
error instanceof CanonicalHumanIdentityMutationInFlightError ||
|
|
350
|
+
error instanceof CanonicalHumanIdentityNotFoundError ||
|
|
351
|
+
error instanceof CanonicalHumanIdentityAuthorityError
|
|
352
|
+
) {
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
mapSqlError(error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function applyCanonicalHumanIdentityOperationInner(
|
|
360
|
+
db: Database,
|
|
361
|
+
input: ApplyCanonicalHumanIdentityOperationInput,
|
|
362
|
+
): Promise<CanonicalHumanIdentityMutationResponseType> {
|
|
363
|
+
const [row] = await rawRows<{
|
|
364
|
+
outcome: CanonicalHumanIdentityMutationOutcome;
|
|
365
|
+
identityId: string;
|
|
366
|
+
identityRevision: number | string;
|
|
367
|
+
authRevision: number | string;
|
|
368
|
+
}>(
|
|
369
|
+
db,
|
|
370
|
+
sql`
|
|
282
371
|
select
|
|
283
372
|
outcome,
|
|
284
373
|
identity_id as "identityId",
|
|
@@ -295,23 +384,12 @@ export async function applyCanonicalHumanIdentityOperation(
|
|
|
295
384
|
${input.reason}
|
|
296
385
|
)
|
|
297
386
|
`,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
} catch (error) {
|
|
307
|
-
if (
|
|
308
|
-
error instanceof CanonicalHumanIdentityConflictError ||
|
|
309
|
-
error instanceof CanonicalHumanIdentityOperationReuseError ||
|
|
310
|
-
error instanceof CanonicalHumanIdentityNotFoundError ||
|
|
311
|
-
error instanceof CanonicalHumanIdentityAuthorityError
|
|
312
|
-
) {
|
|
313
|
-
throw error;
|
|
314
|
-
}
|
|
315
|
-
mapSqlError(error);
|
|
316
|
-
}
|
|
387
|
+
);
|
|
388
|
+
if (!row) throw new CanonicalHumanIdentityNotFoundError();
|
|
389
|
+
const identity = await getCanonicalHumanIdentityProjection(db, input.authUserId);
|
|
390
|
+
return CanonicalHumanIdentityMutationResponse.parse({
|
|
391
|
+
outcome: row.outcome,
|
|
392
|
+
operationId: input.operationId,
|
|
393
|
+
identity,
|
|
394
|
+
});
|
|
317
395
|
}
|