@opengeni/db 0.28.9 → 0.36.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/attached-browser-devices.d.ts +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +299 -0
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +137 -0
- package/dist/browser-sessions.d.ts +354 -0
- package/dist/browser-state-artifacts.d.ts +91 -0
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +194 -0
- package/dist/chunk-5KTIEDX7.js +13200 -0
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-CQZ4QSP5.js +995 -0
- package/dist/chunk-CQZ4QSP5.js.map +1 -0
- package/dist/chunk-F32YT7MP.js +117 -0
- package/dist/chunk-F32YT7MP.js.map +1 -0
- package/dist/chunk-JC5QPLUM.js +666 -0
- package/dist/chunk-JC5QPLUM.js.map +1 -0
- package/dist/chunk-P3RE4D2B.js +3535 -0
- package/dist/chunk-P3RE4D2B.js.map +1 -0
- package/dist/chunk-TAUTWOS5.js +259 -0
- package/dist/chunk-TAUTWOS5.js.map +1 -0
- package/dist/chunk-VKLB2ZGI.js +2389 -0
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +57 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +1122 -126
- package/dist/index.js +42329 -18407
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +73 -10
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +14 -0
- package/dist/interaction-schema.d.ts +6036 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +3 -3
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +19 -6
- package/dist/schema.d.ts +22654 -9359
- package/dist/schema.js +209 -3
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +33 -7
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +1 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/transcription-recordings.d.ts +2 -2
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +225 -0
- package/dist/video-generation.js +68 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +26 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +4373 -0
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +1385 -0
- package/src/browser-sessions.ts +2294 -0
- package/src/browser-state-artifacts.ts +449 -0
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1571 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +468 -24
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/generated-images.ts +740 -0
- package/src/index.ts +9963 -1465
- package/src/insights.ts +251 -23
- package/src/integration-bindings.ts +455 -0
- package/src/integration-facets.ts +862 -0
- package/src/interaction-revisions.ts +70 -0
- package/src/interaction-schema.ts +1606 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +886 -0
- package/src/persistence-errors.ts +4 -7
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +534 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +721 -5
- package/src/schema.ts +6270 -3159
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +130 -42
- package/src/session-queue-commands.ts +265 -15
- package/src/session-realtime-context.ts +2 -2
- package/src/session-realtime-ledger.ts +3 -3
- package/src/session-realtime.ts +7 -7
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +18 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/slack-user-link-access.ts +702 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +1530 -0
- package/src/workspace-artifacts.ts +1 -1
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-P6CUHXQZ.js +0 -7248
- package/dist/chunk-P6CUHXQZ.js.map +0 -1
- package/dist/chunk-VHBFHMPC.js +0 -1219
- package/dist/chunk-VHBFHMPC.js.map +0 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
CanonicalHumanIdentityMutationResponse,
|
|
4
|
+
CanonicalHumanIdentityProjection,
|
|
5
|
+
type CanonicalHumanIdentityMutationOutcome,
|
|
6
|
+
type CanonicalHumanIdentityMutationResponse as CanonicalHumanIdentityMutationResponseType,
|
|
7
|
+
type CanonicalHumanIdentityProjection as CanonicalHumanIdentityProjectionType,
|
|
8
|
+
} from "@opengeni/contracts/canonical-human-identities";
|
|
9
|
+
import { sql } from "drizzle-orm";
|
|
10
|
+
import type { Database } from "./database";
|
|
11
|
+
import { rawRows } from "./database";
|
|
12
|
+
import { nestedPostgresSqlState } from "./persistence-errors";
|
|
13
|
+
|
|
14
|
+
export type CanonicalHumanSessionAuthority = {
|
|
15
|
+
identityId: string;
|
|
16
|
+
identityRevision: number;
|
|
17
|
+
authRevision: number;
|
|
18
|
+
identityStatus: "active" | "recovery_required" | "disputed" | "disabled";
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class CanonicalHumanIdentityConflictError extends Error {
|
|
22
|
+
readonly name = "CanonicalHumanIdentityConflictError";
|
|
23
|
+
readonly code = "CANONICAL_HUMAN_IDENTITY_CONFLICT";
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super("The canonical human identity changed in another request");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class CanonicalHumanIdentityOperationReuseError extends Error {
|
|
31
|
+
readonly name = "CanonicalHumanIdentityOperationReuseError";
|
|
32
|
+
readonly code = "CANONICAL_HUMAN_IDENTITY_OPERATION_REUSED";
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
super("The canonical human identity operation id was already used for another request");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class CanonicalHumanIdentityNotFoundError extends Error {
|
|
40
|
+
readonly name = "CanonicalHumanIdentityNotFoundError";
|
|
41
|
+
|
|
42
|
+
constructor(message = "Canonical human identity was not found") {
|
|
43
|
+
super(message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class CanonicalHumanIdentityAuthorityError extends Error {
|
|
48
|
+
readonly name = "CanonicalHumanIdentityAuthorityError";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function deterministicOperationId(seed: string): string {
|
|
52
|
+
const bytes = createHash("sha256").update(seed, "utf8").digest().subarray(0, 16);
|
|
53
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x50;
|
|
54
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
55
|
+
const hex = bytes.toString("hex");
|
|
56
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function projectionValue(value: unknown): unknown {
|
|
60
|
+
if (typeof value === "string") return JSON.parse(value);
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function mapSqlError(error: unknown): never {
|
|
65
|
+
const state = nestedPostgresSqlState(error);
|
|
66
|
+
if (state === "40001") throw new CanonicalHumanIdentityConflictError();
|
|
67
|
+
if (state === "23505") throw new CanonicalHumanIdentityOperationReuseError();
|
|
68
|
+
if (state === "P0002") throw new CanonicalHumanIdentityNotFoundError();
|
|
69
|
+
if (state === "42501") {
|
|
70
|
+
throw new CanonicalHumanIdentityAuthorityError(
|
|
71
|
+
error instanceof Error ? error.message : "Canonical human identity authority denied",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function ensureCanonicalHumanIdentity(
|
|
78
|
+
db: Database,
|
|
79
|
+
input: { authUserId: string; displayName: string },
|
|
80
|
+
): Promise<CanonicalHumanSessionAuthority> {
|
|
81
|
+
try {
|
|
82
|
+
const [row] = await rawRows<{
|
|
83
|
+
identityId: string;
|
|
84
|
+
identityRevision: number | string;
|
|
85
|
+
authRevision: number | string;
|
|
86
|
+
identityStatus: CanonicalHumanSessionAuthority["identityStatus"];
|
|
87
|
+
}>(
|
|
88
|
+
db,
|
|
89
|
+
sql`
|
|
90
|
+
select
|
|
91
|
+
identity_id as "identityId",
|
|
92
|
+
identity_revision as "identityRevision",
|
|
93
|
+
auth_revision as "authRevision",
|
|
94
|
+
identity_status as "identityStatus"
|
|
95
|
+
from ensure_canonical_human_identity(${input.authUserId}, ${input.displayName})
|
|
96
|
+
`,
|
|
97
|
+
);
|
|
98
|
+
if (!row) throw new CanonicalHumanIdentityNotFoundError();
|
|
99
|
+
return {
|
|
100
|
+
identityId: row.identityId,
|
|
101
|
+
identityRevision: Number(row.identityRevision),
|
|
102
|
+
authRevision: Number(row.authRevision),
|
|
103
|
+
identityStatus: row.identityStatus,
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
mapSqlError(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function ensureCanonicalHumanIdentityForAuthUser(
|
|
111
|
+
db: Database,
|
|
112
|
+
authUserId: string,
|
|
113
|
+
): Promise<CanonicalHumanSessionAuthority> {
|
|
114
|
+
const [user] = await rawRows<{ displayName: string }>(
|
|
115
|
+
db,
|
|
116
|
+
sql`
|
|
117
|
+
select coalesce(nullif(btrim(name), ''), email) as "displayName"
|
|
118
|
+
from auth_users
|
|
119
|
+
where id = ${authUserId}
|
|
120
|
+
limit 1
|
|
121
|
+
`,
|
|
122
|
+
);
|
|
123
|
+
if (!user) throw new CanonicalHumanIdentityNotFoundError("Authentication user was not found");
|
|
124
|
+
return await ensureCanonicalHumanIdentity(db, {
|
|
125
|
+
authUserId,
|
|
126
|
+
displayName: user.displayName,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function synchronizeCanonicalHumanLoginBindings(
|
|
131
|
+
db: Database,
|
|
132
|
+
authUserId: string,
|
|
133
|
+
): Promise<CanonicalHumanSessionAuthority> {
|
|
134
|
+
let authority = await ensureCanonicalHumanIdentityForAuthUser(db, authUserId);
|
|
135
|
+
const accounts = await rawRows<{
|
|
136
|
+
id: string;
|
|
137
|
+
providerId: string;
|
|
138
|
+
providerAccountId: string;
|
|
139
|
+
}>(
|
|
140
|
+
db,
|
|
141
|
+
sql`
|
|
142
|
+
select
|
|
143
|
+
id,
|
|
144
|
+
lower(provider_id) as "providerId",
|
|
145
|
+
account_id as "providerAccountId"
|
|
146
|
+
from auth_identities
|
|
147
|
+
where user_id = ${authUserId}
|
|
148
|
+
order by created_at, id
|
|
149
|
+
`,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
for (const account of accounts) {
|
|
153
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
154
|
+
const projection = await getCanonicalHumanIdentityProjection(db, authUserId);
|
|
155
|
+
if (projection.activeIdentity.status !== "active") {
|
|
156
|
+
return {
|
|
157
|
+
identityId: projection.activeIdentity.id,
|
|
158
|
+
identityRevision: projection.activeIdentity.identityRevision,
|
|
159
|
+
authRevision: projection.activeIdentity.authRevision,
|
|
160
|
+
identityStatus: projection.activeIdentity.status,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const existing = projection.loginBindings.find(
|
|
164
|
+
(binding) =>
|
|
165
|
+
binding.providerId === account.providerId &&
|
|
166
|
+
binding.providerAccountId === account.providerAccountId,
|
|
167
|
+
);
|
|
168
|
+
if (existing?.status === "active") {
|
|
169
|
+
authority = {
|
|
170
|
+
identityId: projection.activeIdentity.id,
|
|
171
|
+
identityRevision: projection.activeIdentity.identityRevision,
|
|
172
|
+
authRevision: projection.activeIdentity.authRevision,
|
|
173
|
+
identityStatus: projection.activeIdentity.status,
|
|
174
|
+
};
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const result = await applyCanonicalHumanIdentityOperation(db, {
|
|
179
|
+
operationId: deterministicOperationId(
|
|
180
|
+
`canonical-human-account-link\n${account.id}\n${projection.activeIdentity.identityRevision}`,
|
|
181
|
+
),
|
|
182
|
+
authUserId,
|
|
183
|
+
expectedIdentityRevision: projection.activeIdentity.identityRevision,
|
|
184
|
+
operationType: "link",
|
|
185
|
+
providerId: account.providerId,
|
|
186
|
+
providerAccountId: account.providerAccountId,
|
|
187
|
+
reason: "Synchronize verified authentication account",
|
|
188
|
+
});
|
|
189
|
+
authority = {
|
|
190
|
+
identityId: result.identity.activeIdentity.id,
|
|
191
|
+
identityRevision: result.identity.activeIdentity.identityRevision,
|
|
192
|
+
authRevision: result.identity.activeIdentity.authRevision,
|
|
193
|
+
identityStatus: result.identity.activeIdentity.status,
|
|
194
|
+
};
|
|
195
|
+
break;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (error instanceof CanonicalHumanIdentityConflictError && attempt < 3) continue;
|
|
198
|
+
if (error instanceof CanonicalHumanIdentityAuthorityError) {
|
|
199
|
+
const currentProjection = await getCanonicalHumanIdentityProjection(db, authUserId);
|
|
200
|
+
if (currentProjection.activeIdentity.status !== "active") {
|
|
201
|
+
return {
|
|
202
|
+
identityId: currentProjection.activeIdentity.id,
|
|
203
|
+
identityRevision: currentProjection.activeIdentity.identityRevision,
|
|
204
|
+
authRevision: currentProjection.activeIdentity.authRevision,
|
|
205
|
+
identityStatus: currentProjection.activeIdentity.status,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return authority;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function validateCanonicalHumanSession(
|
|
217
|
+
db: Database,
|
|
218
|
+
input: { authSessionId: string; authUserId: string; allowRecovery?: boolean },
|
|
219
|
+
): Promise<boolean> {
|
|
220
|
+
try {
|
|
221
|
+
const [row] = await rawRows<{ valid: boolean }>(
|
|
222
|
+
db,
|
|
223
|
+
sql`
|
|
224
|
+
select validate_canonical_human_session(
|
|
225
|
+
${input.authSessionId},
|
|
226
|
+
${input.authUserId},
|
|
227
|
+
${input.allowRecovery ?? false}
|
|
228
|
+
) as valid
|
|
229
|
+
`,
|
|
230
|
+
);
|
|
231
|
+
return row?.valid === true;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
mapSqlError(error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function getCanonicalHumanIdentityProjection(
|
|
238
|
+
db: Database,
|
|
239
|
+
authUserId: string,
|
|
240
|
+
): Promise<CanonicalHumanIdentityProjectionType> {
|
|
241
|
+
try {
|
|
242
|
+
const [row] = await rawRows<{ projection: unknown }>(
|
|
243
|
+
db,
|
|
244
|
+
sql`
|
|
245
|
+
select get_canonical_human_identity_projection(${authUserId}) as projection
|
|
246
|
+
`,
|
|
247
|
+
);
|
|
248
|
+
if (row?.projection === null || row?.projection === undefined) {
|
|
249
|
+
throw new CanonicalHumanIdentityNotFoundError();
|
|
250
|
+
}
|
|
251
|
+
return CanonicalHumanIdentityProjection.parse(projectionValue(row.projection));
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error instanceof CanonicalHumanIdentityNotFoundError) throw error;
|
|
254
|
+
mapSqlError(error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export type ApplyCanonicalHumanIdentityOperationInput = {
|
|
259
|
+
operationId: string;
|
|
260
|
+
authUserId: string;
|
|
261
|
+
expectedIdentityRevision: number;
|
|
262
|
+
operationType: "link" | "unlink" | "begin_recovery" | "recover";
|
|
263
|
+
bindingId?: string | null;
|
|
264
|
+
providerId?: string | null;
|
|
265
|
+
providerAccountId?: string | null;
|
|
266
|
+
reason: string;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export async function applyCanonicalHumanIdentityOperation(
|
|
270
|
+
db: Database,
|
|
271
|
+
input: ApplyCanonicalHumanIdentityOperationInput,
|
|
272
|
+
): Promise<CanonicalHumanIdentityMutationResponseType> {
|
|
273
|
+
try {
|
|
274
|
+
const [row] = await rawRows<{
|
|
275
|
+
outcome: CanonicalHumanIdentityMutationOutcome;
|
|
276
|
+
identityId: string;
|
|
277
|
+
identityRevision: number | string;
|
|
278
|
+
authRevision: number | string;
|
|
279
|
+
}>(
|
|
280
|
+
db,
|
|
281
|
+
sql`
|
|
282
|
+
select
|
|
283
|
+
outcome,
|
|
284
|
+
identity_id as "identityId",
|
|
285
|
+
identity_revision as "identityRevision",
|
|
286
|
+
auth_revision as "authRevision"
|
|
287
|
+
from apply_canonical_human_identity_operation(
|
|
288
|
+
${input.operationId},
|
|
289
|
+
${input.authUserId},
|
|
290
|
+
${input.expectedIdentityRevision},
|
|
291
|
+
${input.operationType},
|
|
292
|
+
${input.bindingId ?? null},
|
|
293
|
+
${input.providerId ?? null},
|
|
294
|
+
${input.providerAccountId ?? null},
|
|
295
|
+
${input.reason}
|
|
296
|
+
)
|
|
297
|
+
`,
|
|
298
|
+
);
|
|
299
|
+
if (!row) throw new CanonicalHumanIdentityNotFoundError();
|
|
300
|
+
const identity = await getCanonicalHumanIdentityProjection(db, input.authUserId);
|
|
301
|
+
return CanonicalHumanIdentityMutationResponse.parse({
|
|
302
|
+
outcome: row.outcome,
|
|
303
|
+
operationId: input.operationId,
|
|
304
|
+
identity,
|
|
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
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { and, eq, inArray, sql, type SQLWrapper } from "drizzle-orm";
|
|
2
|
+
|
|
3
|
+
import type { Database } from "./database";
|
|
4
|
+
import * as schema from "./schema";
|
|
5
|
+
|
|
6
|
+
export type CapabilityComponentOwnerIdentity = {
|
|
7
|
+
kind: "direct" | "plugin" | "pack" | "migration";
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class CapabilityComponentVersionConflictError extends Error {
|
|
12
|
+
readonly name = "CapabilityComponentVersionConflictError";
|
|
13
|
+
|
|
14
|
+
constructor(readonly pluginKey: string) {
|
|
15
|
+
super("Capability component version is pinned by another owner");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* An owner row is runtime-effective only while its lifecycle-owning Plugin or
|
|
21
|
+
* v2 Pack installation is active. Direct, migration, and legacy textual Pack
|
|
22
|
+
* ownership is intrinsic so the rolling migration does not hide pre-v2 shared
|
|
23
|
+
* components before those Packs are reinstalled into the normalized ledger.
|
|
24
|
+
*/
|
|
25
|
+
export function effectiveCapabilityOwnerSql(
|
|
26
|
+
ownerKind: SQLWrapper,
|
|
27
|
+
ownerId: SQLWrapper,
|
|
28
|
+
): ReturnType<typeof sql> {
|
|
29
|
+
return sql`(
|
|
30
|
+
${ownerKind} not in ('plugin', 'pack')
|
|
31
|
+
or (
|
|
32
|
+
${ownerKind} = 'plugin'
|
|
33
|
+
and exists (
|
|
34
|
+
select 1 from ${schema.capabilityPluginInstallations} owning_plugin
|
|
35
|
+
where owning_plugin.id::text = ${ownerId}
|
|
36
|
+
and owning_plugin.status = 'active'
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
or (
|
|
40
|
+
${ownerKind} = 'pack'
|
|
41
|
+
and (
|
|
42
|
+
${ownerId} !~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
|
43
|
+
or exists (
|
|
44
|
+
select 1 from ${schema.packInstallations} owning_pack
|
|
45
|
+
where owning_pack.id::text = ${ownerId}
|
|
46
|
+
and owning_pack.status = 'active'
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
)`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Serialize one workspace-local component identity across direct, Plugin, and Pack installers. */
|
|
54
|
+
export async function lockCapabilityComponentIdentity(
|
|
55
|
+
db: Database,
|
|
56
|
+
workspaceId: string,
|
|
57
|
+
pluginKey: string,
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
await db.execute(
|
|
60
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`capability-component:${workspaceId}:${pluginKey}`}, 0))`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A child installation selects exactly one immutable Plugin version. Changing
|
|
66
|
+
* that version is safe only when every retained facet owner is the caller.
|
|
67
|
+
*/
|
|
68
|
+
export async function assertCapabilityComponentVersionCanChange(
|
|
69
|
+
db: Database,
|
|
70
|
+
input: {
|
|
71
|
+
workspaceId: string;
|
|
72
|
+
pluginKey: string;
|
|
73
|
+
pluginInstallationId: string;
|
|
74
|
+
owner: CapabilityComponentOwnerIdentity;
|
|
75
|
+
},
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const owners = await db
|
|
78
|
+
.select({
|
|
79
|
+
kind: schema.capabilityComponentOwners.ownerKind,
|
|
80
|
+
id: schema.capabilityComponentOwners.ownerId,
|
|
81
|
+
})
|
|
82
|
+
.from(schema.capabilityComponentOwners)
|
|
83
|
+
.innerJoin(
|
|
84
|
+
schema.capabilityFacetInstallations,
|
|
85
|
+
eq(
|
|
86
|
+
schema.capabilityFacetInstallations.id,
|
|
87
|
+
schema.capabilityComponentOwners.facetInstallationId,
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
.where(
|
|
91
|
+
and(
|
|
92
|
+
eq(schema.capabilityComponentOwners.workspaceId, input.workspaceId),
|
|
93
|
+
eq(schema.capabilityFacetInstallations.pluginInstallationId, input.pluginInstallationId),
|
|
94
|
+
effectiveCapabilityOwnerSql(
|
|
95
|
+
schema.capabilityComponentOwners.ownerKind,
|
|
96
|
+
schema.capabilityComponentOwners.ownerId,
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
if (owners.some((owner) => owner.kind !== input.owner.kind || owner.id !== input.owner.id)) {
|
|
101
|
+
throw new CapabilityComponentVersionConflictError(input.pluginKey);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Delete physically ownerless facet installations and disable now-empty child
|
|
107
|
+
* components. Inactive lifecycle owners still preserve storage: a Pack may
|
|
108
|
+
* have claimed a component while its installation is pending or repairing,
|
|
109
|
+
* even though that owner is intentionally hidden from runtime reads.
|
|
110
|
+
*/
|
|
111
|
+
export async function cleanupOrphanedCapabilityComponents(
|
|
112
|
+
db: Database,
|
|
113
|
+
workspaceId: string,
|
|
114
|
+
facetInstallationIds: string[],
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const uniqueIds = [...new Set(facetInstallationIds)];
|
|
117
|
+
if (uniqueIds.length === 0) return;
|
|
118
|
+
const orphanRows = await db
|
|
119
|
+
.select({
|
|
120
|
+
facetInstallationId: schema.capabilityFacetInstallations.id,
|
|
121
|
+
pluginInstallationId: schema.capabilityFacetInstallations.pluginInstallationId,
|
|
122
|
+
})
|
|
123
|
+
.from(schema.capabilityFacetInstallations)
|
|
124
|
+
.where(
|
|
125
|
+
and(
|
|
126
|
+
inArray(schema.capabilityFacetInstallations.id, uniqueIds),
|
|
127
|
+
sql`not exists (
|
|
128
|
+
select 1 from ${schema.capabilityComponentOwners} owner
|
|
129
|
+
where owner.facet_installation_id = ${schema.capabilityFacetInstallations.id}
|
|
130
|
+
)`,
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
if (orphanRows.length === 0) return;
|
|
134
|
+
await db.delete(schema.capabilityFacetInstallations).where(
|
|
135
|
+
inArray(
|
|
136
|
+
schema.capabilityFacetInstallations.id,
|
|
137
|
+
orphanRows.map((row) => row.facetInstallationId),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
const childInstallations = [...new Set(orphanRows.map((row) => row.pluginInstallationId))];
|
|
141
|
+
for (const childInstallationId of childInstallations) {
|
|
142
|
+
const [remaining] = await db
|
|
143
|
+
.select({ id: schema.capabilityFacetInstallations.id })
|
|
144
|
+
.from(schema.capabilityFacetInstallations)
|
|
145
|
+
.where(eq(schema.capabilityFacetInstallations.pluginInstallationId, childInstallationId))
|
|
146
|
+
.limit(1);
|
|
147
|
+
if (remaining) continue;
|
|
148
|
+
await db
|
|
149
|
+
.update(schema.capabilityPluginInstallations)
|
|
150
|
+
.set({
|
|
151
|
+
status: "disabled",
|
|
152
|
+
version: sql`${schema.capabilityPluginInstallations.version} + 1`,
|
|
153
|
+
updatedAt: new Date(),
|
|
154
|
+
})
|
|
155
|
+
.where(eq(schema.capabilityPluginInstallations.id, childInstallationId));
|
|
156
|
+
await db
|
|
157
|
+
.update(schema.capabilityInstallations)
|
|
158
|
+
.set({ status: "disabled", updatedAt: new Date() })
|
|
159
|
+
.where(
|
|
160
|
+
and(
|
|
161
|
+
eq(schema.capabilityInstallations.workspaceId, workspaceId),
|
|
162
|
+
sql`${schema.capabilityInstallations.metadata} ->> 'pluginInstallationId' = ${childInstallationId}`,
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|