@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,893 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES } from "@opengeni/contracts/editable-artifacts";
|
|
3
|
+
import { sql, type SQL } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { rawRows, type Database } from "./database";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Database boundary for the global artifact materializer.
|
|
9
|
+
*
|
|
10
|
+
* The worker connection is deliberately not tenant-scoped: it may claim jobs
|
|
11
|
+
* from every workspace. It receives no table privileges. Every mutation below
|
|
12
|
+
* goes through a narrowly granted SECURITY DEFINER function which owns the
|
|
13
|
+
* global SKIP LOCKED scan and the exact owner + attempt-count lease fence.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const EDITABLE_ARTIFACT_MATERIALIZATION_MAX_CLAIM = 1_000;
|
|
17
|
+
export const EDITABLE_ARTIFACT_MATERIALIZATION_MAX_LEASE_MS = 86_400_000;
|
|
18
|
+
export const EDITABLE_ARTIFACT_MATERIALIZATION_MAX_ATTEMPTS = 1_000;
|
|
19
|
+
export const EDITABLE_ARTIFACT_MATERIALIZATION_MAX_SOURCE_BYTES = 512 * 1024 * 1024;
|
|
20
|
+
export const EDITABLE_ARTIFACT_MATERIALIZATION_MAX_OPTIONS_BYTES = 256 * 1024;
|
|
21
|
+
|
|
22
|
+
export type EditableArtifactMaterializationFormat =
|
|
23
|
+
| "xlsx"
|
|
24
|
+
| "pptx"
|
|
25
|
+
| "docx"
|
|
26
|
+
| "pdf"
|
|
27
|
+
| "png"
|
|
28
|
+
| "webp";
|
|
29
|
+
|
|
30
|
+
export type EditableArtifactMaterializationMimeType =
|
|
31
|
+
| "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
32
|
+
| "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
33
|
+
| "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
34
|
+
| "application/pdf"
|
|
35
|
+
| "image/png"
|
|
36
|
+
| "image/webp";
|
|
37
|
+
|
|
38
|
+
export type EditableArtifactMaterializationScope = Readonly<{
|
|
39
|
+
accountId: string;
|
|
40
|
+
workspaceId: string;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
export type EditableArtifactMaterializationModality = "spreadsheet" | "presentation" | "document";
|
|
44
|
+
|
|
45
|
+
export type ClaimedEditableArtifactMaterialization = Readonly<{
|
|
46
|
+
scope: EditableArtifactMaterializationScope;
|
|
47
|
+
artifactId: string;
|
|
48
|
+
jobId: string;
|
|
49
|
+
versionId: string | null;
|
|
50
|
+
modality: EditableArtifactMaterializationModality;
|
|
51
|
+
inputSnapshotId: string;
|
|
52
|
+
targetHeadSequence: number;
|
|
53
|
+
stateHash: string;
|
|
54
|
+
sourceObjectReference: string;
|
|
55
|
+
sourceByteSize: number;
|
|
56
|
+
sourceContentHash: string;
|
|
57
|
+
sourceMimeType: string;
|
|
58
|
+
modelSchemaVersion: number;
|
|
59
|
+
operationProtocolVersion: number;
|
|
60
|
+
snapshotProtocolVersion: number;
|
|
61
|
+
format: EditableArtifactMaterializationFormat;
|
|
62
|
+
codecId: string;
|
|
63
|
+
normalizedOptions: string;
|
|
64
|
+
optionsHash: string;
|
|
65
|
+
codecVersion: string;
|
|
66
|
+
kernelVersion: string;
|
|
67
|
+
fontRegistryHash: string;
|
|
68
|
+
policyHash: string;
|
|
69
|
+
attemptCount: number;
|
|
70
|
+
leaseOwner: string;
|
|
71
|
+
leaseExpiresAt: string;
|
|
72
|
+
}>;
|
|
73
|
+
|
|
74
|
+
/** @deprecated Use the self-contained pinned source manifest name. */
|
|
75
|
+
export type ClaimedEditableArtifactMaterializationJob = ClaimedEditableArtifactMaterialization;
|
|
76
|
+
|
|
77
|
+
export type ClaimEditableArtifactMaterializationsRequest = Readonly<{
|
|
78
|
+
owner: string;
|
|
79
|
+
leaseDurationMs: number;
|
|
80
|
+
limit: number;
|
|
81
|
+
}>;
|
|
82
|
+
|
|
83
|
+
export type EditableArtifactMaterializationLease = Readonly<{
|
|
84
|
+
scope: EditableArtifactMaterializationScope;
|
|
85
|
+
artifactId: string;
|
|
86
|
+
jobId: string;
|
|
87
|
+
owner: string;
|
|
88
|
+
attemptCount: number;
|
|
89
|
+
}>;
|
|
90
|
+
|
|
91
|
+
export type RenewEditableArtifactMaterializationRequest = EditableArtifactMaterializationLease &
|
|
92
|
+
Readonly<{
|
|
93
|
+
leaseDurationMs: number;
|
|
94
|
+
}>;
|
|
95
|
+
|
|
96
|
+
export type SucceedEditableArtifactMaterializationRequest = EditableArtifactMaterializationLease &
|
|
97
|
+
Readonly<{
|
|
98
|
+
resultId: string;
|
|
99
|
+
blobRefId: string;
|
|
100
|
+
/** Immutable, content-addressed object-store reference written before settlement. */
|
|
101
|
+
objectReference: string;
|
|
102
|
+
byteSize: number;
|
|
103
|
+
contentHash: string;
|
|
104
|
+
mimeType: EditableArtifactMaterializationMimeType;
|
|
105
|
+
verifiedAt: string;
|
|
106
|
+
}>;
|
|
107
|
+
|
|
108
|
+
export type PersistedEditableArtifactMaterializationResult = Readonly<{
|
|
109
|
+
scope: EditableArtifactMaterializationScope;
|
|
110
|
+
artifactId: string;
|
|
111
|
+
jobId: string;
|
|
112
|
+
resultId: string;
|
|
113
|
+
blobRefId: string;
|
|
114
|
+
objectReference: string;
|
|
115
|
+
byteSize: number;
|
|
116
|
+
contentHash: string;
|
|
117
|
+
mimeType: EditableArtifactMaterializationMimeType;
|
|
118
|
+
verifiedAt: string;
|
|
119
|
+
createdAt: string;
|
|
120
|
+
replayed: boolean;
|
|
121
|
+
}>;
|
|
122
|
+
|
|
123
|
+
export type FailEditableArtifactMaterializationRequest = EditableArtifactMaterializationLease &
|
|
124
|
+
Readonly<{
|
|
125
|
+
/** Bounded machine code only; never place provider diagnostics here. */
|
|
126
|
+
errorCode: string;
|
|
127
|
+
}>;
|
|
128
|
+
|
|
129
|
+
export type FailEditableArtifactMaterializationResult = Readonly<{
|
|
130
|
+
replayed: boolean;
|
|
131
|
+
}>;
|
|
132
|
+
|
|
133
|
+
export interface EditableArtifactMaterializationRepository {
|
|
134
|
+
claim(
|
|
135
|
+
request: ClaimEditableArtifactMaterializationsRequest,
|
|
136
|
+
): Promise<readonly ClaimedEditableArtifactMaterialization[]>;
|
|
137
|
+
renew(request: RenewEditableArtifactMaterializationRequest): Promise<string>;
|
|
138
|
+
succeed(
|
|
139
|
+
request: SucceedEditableArtifactMaterializationRequest,
|
|
140
|
+
): Promise<PersistedEditableArtifactMaterializationResult>;
|
|
141
|
+
fail(
|
|
142
|
+
request: FailEditableArtifactMaterializationRequest,
|
|
143
|
+
): Promise<FailEditableArtifactMaterializationResult>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export class EditableArtifactMaterializationPersistenceError extends Error {
|
|
147
|
+
constructor(
|
|
148
|
+
readonly code: "lease_fenced" | "database_contract_violation",
|
|
149
|
+
message: string,
|
|
150
|
+
options?: ErrorOptions,
|
|
151
|
+
) {
|
|
152
|
+
super(message, options);
|
|
153
|
+
this.name = "EditableArtifactMaterializationPersistenceError";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export type PostgresEditableArtifactMaterializationRepositoryOptions = Readonly<{
|
|
158
|
+
/** Unquoted PostgreSQL schema. Unset resolves through current_schema() in SQL. */
|
|
159
|
+
dataSchema?: string;
|
|
160
|
+
}>;
|
|
161
|
+
|
|
162
|
+
type MaterializerDatabase = Pick<Database, "execute">;
|
|
163
|
+
|
|
164
|
+
type ClaimRow = Record<string, unknown> & {
|
|
165
|
+
account_id: unknown;
|
|
166
|
+
workspace_id: unknown;
|
|
167
|
+
artifact_id: unknown;
|
|
168
|
+
job_id: unknown;
|
|
169
|
+
version_id: unknown;
|
|
170
|
+
modality: unknown;
|
|
171
|
+
input_snapshot_id: unknown;
|
|
172
|
+
target_head_sequence: unknown;
|
|
173
|
+
state_hash: unknown;
|
|
174
|
+
source_object_reference: unknown;
|
|
175
|
+
source_byte_size: unknown;
|
|
176
|
+
source_content_hash: unknown;
|
|
177
|
+
source_mime_type: unknown;
|
|
178
|
+
model_schema_version: unknown;
|
|
179
|
+
operation_protocol_version: unknown;
|
|
180
|
+
snapshot_protocol_version: unknown;
|
|
181
|
+
format: unknown;
|
|
182
|
+
codec_id: unknown;
|
|
183
|
+
normalized_options: unknown;
|
|
184
|
+
options_hash: unknown;
|
|
185
|
+
codec_version: unknown;
|
|
186
|
+
kernel_version: unknown;
|
|
187
|
+
font_registry_hash: unknown;
|
|
188
|
+
policy_hash: unknown;
|
|
189
|
+
attempt_count: unknown;
|
|
190
|
+
lease_owner: unknown;
|
|
191
|
+
lease_expires_at: unknown;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
type RenewalRow = Record<string, unknown> & {
|
|
195
|
+
account_id: unknown;
|
|
196
|
+
workspace_id: unknown;
|
|
197
|
+
artifact_id: unknown;
|
|
198
|
+
job_id: unknown;
|
|
199
|
+
lease_owner: unknown;
|
|
200
|
+
attempt_count: unknown;
|
|
201
|
+
lease_expires_at: unknown;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
type SuccessRow = Record<string, unknown> & {
|
|
205
|
+
outcome: unknown;
|
|
206
|
+
account_id: unknown;
|
|
207
|
+
workspace_id: unknown;
|
|
208
|
+
artifact_id: unknown;
|
|
209
|
+
job_id: unknown;
|
|
210
|
+
result_id: unknown;
|
|
211
|
+
blob_ref_id: unknown;
|
|
212
|
+
object_reference: unknown;
|
|
213
|
+
byte_size: unknown;
|
|
214
|
+
content_hash: unknown;
|
|
215
|
+
mime_type: unknown;
|
|
216
|
+
verified_at: unknown;
|
|
217
|
+
created_at: unknown;
|
|
218
|
+
settled_by_owner: unknown;
|
|
219
|
+
attempt_count: unknown;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
type FailureRow = Record<string, unknown> & {
|
|
223
|
+
outcome: unknown;
|
|
224
|
+
account_id: unknown;
|
|
225
|
+
workspace_id: unknown;
|
|
226
|
+
artifact_id: unknown;
|
|
227
|
+
job_id: unknown;
|
|
228
|
+
settled_by_owner: unknown;
|
|
229
|
+
attempt_count: unknown;
|
|
230
|
+
error_code: unknown;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export class PostgresEditableArtifactMaterializationRepository implements EditableArtifactMaterializationRepository {
|
|
234
|
+
private readonly dataSchema: string | null;
|
|
235
|
+
|
|
236
|
+
constructor(
|
|
237
|
+
private readonly db: MaterializerDatabase,
|
|
238
|
+
options: PostgresEditableArtifactMaterializationRepositoryOptions = {},
|
|
239
|
+
) {
|
|
240
|
+
this.dataSchema =
|
|
241
|
+
options.dataSchema === undefined ? null : validateDataSchema(options.dataSchema);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async claim(
|
|
245
|
+
request: ClaimEditableArtifactMaterializationsRequest,
|
|
246
|
+
): Promise<readonly ClaimedEditableArtifactMaterializationJob[]> {
|
|
247
|
+
const owner = validateOwner(request.owner);
|
|
248
|
+
const leaseDurationMs = validateInteger(
|
|
249
|
+
request.leaseDurationMs,
|
|
250
|
+
"materialization lease duration",
|
|
251
|
+
1,
|
|
252
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_LEASE_MS,
|
|
253
|
+
);
|
|
254
|
+
const limit = validateInteger(
|
|
255
|
+
request.limit,
|
|
256
|
+
"materialization claim limit",
|
|
257
|
+
1,
|
|
258
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_CLAIM,
|
|
259
|
+
);
|
|
260
|
+
const rows = await rawRows<ClaimRow>(
|
|
261
|
+
this.db,
|
|
262
|
+
sql`select * from opengeni_private.claim_editable_artifact_materializations(
|
|
263
|
+
${owner}, ${leaseDurationMs}, ${limit}, ${this.schemaArgument()}
|
|
264
|
+
)`,
|
|
265
|
+
);
|
|
266
|
+
if (rows.length > limit) {
|
|
267
|
+
throw contractViolation("Materialization claim returned more rows than requested");
|
|
268
|
+
}
|
|
269
|
+
const jobs = rows.map((row) => claimedJobFromRow(row, owner));
|
|
270
|
+
const identities = new Set<string>();
|
|
271
|
+
for (const job of jobs) {
|
|
272
|
+
const identity = `${job.scope.accountId}\0${job.scope.workspaceId}\0${job.artifactId}\0${job.jobId}`;
|
|
273
|
+
if (identities.has(identity)) {
|
|
274
|
+
throw contractViolation("Materialization claim returned a duplicate job identity");
|
|
275
|
+
}
|
|
276
|
+
identities.add(identity);
|
|
277
|
+
}
|
|
278
|
+
return Object.freeze(jobs);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async renew(request: RenewEditableArtifactMaterializationRequest): Promise<string> {
|
|
282
|
+
const lease = validateLease(request);
|
|
283
|
+
const leaseDurationMs = validateInteger(
|
|
284
|
+
request.leaseDurationMs,
|
|
285
|
+
"materialization lease duration",
|
|
286
|
+
1,
|
|
287
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_LEASE_MS,
|
|
288
|
+
);
|
|
289
|
+
const rows = await rawRows<RenewalRow>(
|
|
290
|
+
this.db,
|
|
291
|
+
sql`select * from opengeni_private.renew_editable_artifact_materialization(
|
|
292
|
+
${lease.scope.accountId}::uuid,
|
|
293
|
+
${lease.scope.workspaceId}::uuid,
|
|
294
|
+
${lease.artifactId},
|
|
295
|
+
${lease.jobId},
|
|
296
|
+
${lease.owner},
|
|
297
|
+
${lease.attemptCount},
|
|
298
|
+
${leaseDurationMs},
|
|
299
|
+
${this.schemaArgument()}
|
|
300
|
+
)`,
|
|
301
|
+
);
|
|
302
|
+
if (rows.length === 0) throw leaseFenced();
|
|
303
|
+
if (rows.length !== 1) {
|
|
304
|
+
throw contractViolation("Materialization renewal returned an invalid row count");
|
|
305
|
+
}
|
|
306
|
+
assertReturnedLeaseFence(rows[0]!, lease, "lease_owner", "renewal");
|
|
307
|
+
return databaseTimestamp(rows[0]!.lease_expires_at, "renewed lease expiration");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async succeed(
|
|
311
|
+
request: SucceedEditableArtifactMaterializationRequest,
|
|
312
|
+
): Promise<PersistedEditableArtifactMaterializationResult> {
|
|
313
|
+
const lease = validateLease(request);
|
|
314
|
+
const resultId = validateStableId(request.resultId, "materialization result id");
|
|
315
|
+
const blobRefId = validateStableId(request.blobRefId, "materialization blob id");
|
|
316
|
+
const objectReference = validateBoundedText(
|
|
317
|
+
request.objectReference,
|
|
318
|
+
"materialization object reference",
|
|
319
|
+
1_024,
|
|
320
|
+
);
|
|
321
|
+
const byteSize = validateInteger(
|
|
322
|
+
request.byteSize,
|
|
323
|
+
"materialization byte size",
|
|
324
|
+
1,
|
|
325
|
+
Number.MAX_SAFE_INTEGER,
|
|
326
|
+
);
|
|
327
|
+
const contentHash = validateHash(request.contentHash, "materialization content hash");
|
|
328
|
+
const mimeType = validateMimeType(request.mimeType);
|
|
329
|
+
const verifiedAt = validateTimestamp(request.verifiedAt, "materialization verification time");
|
|
330
|
+
const rows = await rawRows<SuccessRow>(
|
|
331
|
+
this.db,
|
|
332
|
+
sql`select * from opengeni_private.succeed_editable_artifact_materialization(
|
|
333
|
+
${lease.scope.accountId}::uuid,
|
|
334
|
+
${lease.scope.workspaceId}::uuid,
|
|
335
|
+
${lease.artifactId},
|
|
336
|
+
${lease.jobId},
|
|
337
|
+
${lease.owner},
|
|
338
|
+
${lease.attemptCount},
|
|
339
|
+
${resultId},
|
|
340
|
+
${blobRefId},
|
|
341
|
+
${objectReference},
|
|
342
|
+
${byteSize},
|
|
343
|
+
${contentHash},
|
|
344
|
+
${mimeType},
|
|
345
|
+
${verifiedAt}::timestamptz,
|
|
346
|
+
${this.schemaArgument()}
|
|
347
|
+
)`,
|
|
348
|
+
);
|
|
349
|
+
if (rows.length === 0) throw leaseFenced();
|
|
350
|
+
if (rows.length !== 1) {
|
|
351
|
+
throw contractViolation("Materialization success returned an invalid row count");
|
|
352
|
+
}
|
|
353
|
+
return resultFromRow(rows[0]!, {
|
|
354
|
+
lease,
|
|
355
|
+
resultId,
|
|
356
|
+
blobRefId,
|
|
357
|
+
objectReference,
|
|
358
|
+
byteSize,
|
|
359
|
+
contentHash,
|
|
360
|
+
mimeType,
|
|
361
|
+
verifiedAt,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async fail(
|
|
366
|
+
request: FailEditableArtifactMaterializationRequest,
|
|
367
|
+
): Promise<FailEditableArtifactMaterializationResult> {
|
|
368
|
+
const lease = validateLease(request);
|
|
369
|
+
const errorCode = validateErrorCode(request.errorCode);
|
|
370
|
+
const rows = await rawRows<FailureRow>(
|
|
371
|
+
this.db,
|
|
372
|
+
sql`select * from opengeni_private.fail_editable_artifact_materialization(
|
|
373
|
+
${lease.scope.accountId}::uuid,
|
|
374
|
+
${lease.scope.workspaceId}::uuid,
|
|
375
|
+
${lease.artifactId},
|
|
376
|
+
${lease.jobId},
|
|
377
|
+
${lease.owner},
|
|
378
|
+
${lease.attemptCount},
|
|
379
|
+
${errorCode},
|
|
380
|
+
${this.schemaArgument()}
|
|
381
|
+
)`,
|
|
382
|
+
);
|
|
383
|
+
if (rows.length !== 1) {
|
|
384
|
+
throw contractViolation("Materialization failure returned an invalid row count");
|
|
385
|
+
}
|
|
386
|
+
const outcome = rows[0]!.outcome;
|
|
387
|
+
if (outcome === "fenced") throw leaseFenced();
|
|
388
|
+
if (outcome !== "failed" && outcome !== "replayed") {
|
|
389
|
+
throw contractViolation("Materialization failure returned an invalid outcome");
|
|
390
|
+
}
|
|
391
|
+
assertReturnedLeaseFence(rows[0]!, lease, "settled_by_owner", "failure");
|
|
392
|
+
if (rows[0]!.error_code !== errorCode) {
|
|
393
|
+
throw contractViolation("Materialization failure returned a different error code");
|
|
394
|
+
}
|
|
395
|
+
return Object.freeze({ replayed: outcome === "replayed" });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private schemaArgument(): SQL {
|
|
399
|
+
return this.dataSchema === null
|
|
400
|
+
? sql`pg_catalog.current_schema()`
|
|
401
|
+
: sql`${this.dataSchema}::name`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function claimedJobFromRow(
|
|
406
|
+
row: ClaimRow,
|
|
407
|
+
expectedOwner: string,
|
|
408
|
+
): ClaimedEditableArtifactMaterialization {
|
|
409
|
+
const leaseOwner = validateDatabaseOwner(row.lease_owner);
|
|
410
|
+
if (leaseOwner !== expectedOwner) {
|
|
411
|
+
throw contractViolation("Claimed materialization lease owner differs from the claimant");
|
|
412
|
+
}
|
|
413
|
+
const leaseExpiresAt = databaseTimestamp(
|
|
414
|
+
row.lease_expires_at,
|
|
415
|
+
"materialization lease expiration",
|
|
416
|
+
);
|
|
417
|
+
const modality = validateDatabaseModality(row.modality);
|
|
418
|
+
const format = validateDatabaseFormat(row.format);
|
|
419
|
+
validateFormatForModality(modality, format);
|
|
420
|
+
const optionsHash = validateDatabaseHash(row.options_hash, "materialization options hash");
|
|
421
|
+
const sourceMimeType = validateDatabaseText(
|
|
422
|
+
row.source_mime_type,
|
|
423
|
+
"materialization source MIME type",
|
|
424
|
+
256,
|
|
425
|
+
);
|
|
426
|
+
if (sourceMimeType !== "application/vnd.opengeni.editable-artifact-snapshot") {
|
|
427
|
+
throw contractViolation("Materialization source is not an editable artifact snapshot");
|
|
428
|
+
}
|
|
429
|
+
return Object.freeze({
|
|
430
|
+
scope: validateDatabaseScope(row.account_id, row.workspace_id),
|
|
431
|
+
artifactId: validateDatabaseStableId(row.artifact_id, "artifact id"),
|
|
432
|
+
jobId: validateDatabaseStableId(row.job_id, "materialization job id"),
|
|
433
|
+
versionId:
|
|
434
|
+
row.version_id === null
|
|
435
|
+
? null
|
|
436
|
+
: validateDatabaseStableId(row.version_id, "artifact version id"),
|
|
437
|
+
modality,
|
|
438
|
+
inputSnapshotId: validateDatabaseStableId(
|
|
439
|
+
row.input_snapshot_id,
|
|
440
|
+
"materialization input snapshot id",
|
|
441
|
+
),
|
|
442
|
+
targetHeadSequence: databaseInteger(
|
|
443
|
+
row.target_head_sequence,
|
|
444
|
+
"materialization target head sequence",
|
|
445
|
+
0,
|
|
446
|
+
Number.MAX_SAFE_INTEGER,
|
|
447
|
+
),
|
|
448
|
+
stateHash: validateDatabaseHash(row.state_hash, "materialization state hash"),
|
|
449
|
+
sourceObjectReference: validateDatabaseText(
|
|
450
|
+
row.source_object_reference,
|
|
451
|
+
"materialization source object reference",
|
|
452
|
+
1_024,
|
|
453
|
+
),
|
|
454
|
+
sourceByteSize: databaseInteger(
|
|
455
|
+
row.source_byte_size,
|
|
456
|
+
"materialization source byte size",
|
|
457
|
+
1,
|
|
458
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_SOURCE_BYTES,
|
|
459
|
+
),
|
|
460
|
+
sourceContentHash: validateDatabaseHash(
|
|
461
|
+
row.source_content_hash,
|
|
462
|
+
"materialization source content hash",
|
|
463
|
+
),
|
|
464
|
+
sourceMimeType,
|
|
465
|
+
modelSchemaVersion: databaseInteger(
|
|
466
|
+
row.model_schema_version,
|
|
467
|
+
"materialization source model schema version",
|
|
468
|
+
1,
|
|
469
|
+
2_147_483_647,
|
|
470
|
+
),
|
|
471
|
+
operationProtocolVersion: databaseInteger(
|
|
472
|
+
row.operation_protocol_version,
|
|
473
|
+
"materialization source operation protocol version",
|
|
474
|
+
1,
|
|
475
|
+
2_147_483_647,
|
|
476
|
+
),
|
|
477
|
+
snapshotProtocolVersion: databaseInteger(
|
|
478
|
+
row.snapshot_protocol_version,
|
|
479
|
+
"materialization source snapshot protocol version",
|
|
480
|
+
1,
|
|
481
|
+
2_147_483_647,
|
|
482
|
+
),
|
|
483
|
+
format,
|
|
484
|
+
codecId: validateDatabaseText(row.codec_id, "codec id", 128),
|
|
485
|
+
normalizedOptions: validateDatabaseNormalizedOptions(row.normalized_options, optionsHash),
|
|
486
|
+
optionsHash,
|
|
487
|
+
codecVersion: validateDatabaseText(row.codec_version, "codec version", 128),
|
|
488
|
+
kernelVersion: validateDatabaseText(
|
|
489
|
+
row.kernel_version,
|
|
490
|
+
"kernel version",
|
|
491
|
+
EDITABLE_ARTIFACT_KERNEL_VERSION_MAX_BYTES,
|
|
492
|
+
),
|
|
493
|
+
fontRegistryHash: validateDatabaseHash(row.font_registry_hash, "font registry hash"),
|
|
494
|
+
policyHash: validateDatabaseHash(row.policy_hash, "materialization policy hash"),
|
|
495
|
+
attemptCount: databaseInteger(
|
|
496
|
+
row.attempt_count,
|
|
497
|
+
"materialization attempt count",
|
|
498
|
+
1,
|
|
499
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_ATTEMPTS,
|
|
500
|
+
),
|
|
501
|
+
leaseOwner,
|
|
502
|
+
leaseExpiresAt,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function resultFromRow(
|
|
507
|
+
row: SuccessRow,
|
|
508
|
+
expected: Readonly<{
|
|
509
|
+
lease: EditableArtifactMaterializationLease;
|
|
510
|
+
resultId: string;
|
|
511
|
+
blobRefId: string;
|
|
512
|
+
objectReference: string;
|
|
513
|
+
byteSize: number;
|
|
514
|
+
contentHash: string;
|
|
515
|
+
mimeType: EditableArtifactMaterializationMimeType;
|
|
516
|
+
verifiedAt: string;
|
|
517
|
+
}>,
|
|
518
|
+
): PersistedEditableArtifactMaterializationResult {
|
|
519
|
+
if (row.outcome !== "committed" && row.outcome !== "replayed") {
|
|
520
|
+
throw contractViolation("Materialization success returned an invalid outcome");
|
|
521
|
+
}
|
|
522
|
+
assertReturnedLeaseFence(row, expected.lease, "settled_by_owner", "success");
|
|
523
|
+
const scope = validateDatabaseScope(row.account_id, row.workspace_id);
|
|
524
|
+
const artifactId = validateDatabaseStableId(row.artifact_id, "artifact id");
|
|
525
|
+
const jobId = validateDatabaseStableId(row.job_id, "materialization job id");
|
|
526
|
+
const resultId = validateDatabaseStableId(row.result_id, "materialization result id");
|
|
527
|
+
const blobRefId = validateDatabaseStableId(row.blob_ref_id, "materialization blob id");
|
|
528
|
+
const objectReference = validateDatabaseText(
|
|
529
|
+
row.object_reference,
|
|
530
|
+
"materialization object reference",
|
|
531
|
+
1_024,
|
|
532
|
+
);
|
|
533
|
+
const byteSize = databaseInteger(
|
|
534
|
+
row.byte_size,
|
|
535
|
+
"materialization byte size",
|
|
536
|
+
1,
|
|
537
|
+
Number.MAX_SAFE_INTEGER,
|
|
538
|
+
);
|
|
539
|
+
const contentHash = validateDatabaseHash(row.content_hash, "materialization content hash");
|
|
540
|
+
const mimeType = validateDatabaseMimeType(row.mime_type);
|
|
541
|
+
const verifiedAt = databaseTimestamp(row.verified_at, "materialization verification time");
|
|
542
|
+
const createdAt = databaseTimestamp(row.created_at, "materialization result creation time");
|
|
543
|
+
if (
|
|
544
|
+
scope.accountId !== expected.lease.scope.accountId ||
|
|
545
|
+
scope.workspaceId !== expected.lease.scope.workspaceId ||
|
|
546
|
+
artifactId !== expected.lease.artifactId ||
|
|
547
|
+
jobId !== expected.lease.jobId ||
|
|
548
|
+
resultId !== expected.resultId ||
|
|
549
|
+
blobRefId !== expected.blobRefId ||
|
|
550
|
+
objectReference !== expected.objectReference ||
|
|
551
|
+
byteSize !== expected.byteSize ||
|
|
552
|
+
contentHash !== expected.contentHash ||
|
|
553
|
+
mimeType !== expected.mimeType ||
|
|
554
|
+
verifiedAt !== expected.verifiedAt
|
|
555
|
+
) {
|
|
556
|
+
throw contractViolation("Materialization result differs from the exact settlement facts");
|
|
557
|
+
}
|
|
558
|
+
if (Date.parse(verifiedAt) > Date.parse(createdAt)) {
|
|
559
|
+
throw contractViolation("Materialization result predates its verification");
|
|
560
|
+
}
|
|
561
|
+
return Object.freeze({
|
|
562
|
+
scope,
|
|
563
|
+
artifactId,
|
|
564
|
+
jobId,
|
|
565
|
+
resultId,
|
|
566
|
+
blobRefId,
|
|
567
|
+
objectReference,
|
|
568
|
+
byteSize,
|
|
569
|
+
contentHash,
|
|
570
|
+
mimeType,
|
|
571
|
+
verifiedAt,
|
|
572
|
+
createdAt,
|
|
573
|
+
replayed: row.outcome === "replayed",
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function validateLease(
|
|
578
|
+
input: EditableArtifactMaterializationLease,
|
|
579
|
+
): EditableArtifactMaterializationLease {
|
|
580
|
+
return Object.freeze({
|
|
581
|
+
scope: validateScope(input.scope),
|
|
582
|
+
artifactId: validateStableId(input.artifactId, "artifact id"),
|
|
583
|
+
jobId: validateStableId(input.jobId, "materialization job id"),
|
|
584
|
+
owner: validateOwner(input.owner),
|
|
585
|
+
attemptCount: validateInteger(
|
|
586
|
+
input.attemptCount,
|
|
587
|
+
"materialization attempt count",
|
|
588
|
+
1,
|
|
589
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_ATTEMPTS,
|
|
590
|
+
),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function assertReturnedLeaseFence(
|
|
595
|
+
row: Record<string, unknown>,
|
|
596
|
+
expected: EditableArtifactMaterializationLease,
|
|
597
|
+
ownerColumn: "lease_owner" | "settled_by_owner",
|
|
598
|
+
operation: string,
|
|
599
|
+
): void {
|
|
600
|
+
const scope = validateDatabaseScope(row.account_id, row.workspace_id);
|
|
601
|
+
const artifactId = validateDatabaseStableId(row.artifact_id, "artifact id");
|
|
602
|
+
const jobId = validateDatabaseStableId(row.job_id, "materialization job id");
|
|
603
|
+
const owner = validateDatabaseOwner(row[ownerColumn]);
|
|
604
|
+
const attemptCount = databaseInteger(
|
|
605
|
+
row.attempt_count,
|
|
606
|
+
"materialization attempt count",
|
|
607
|
+
1,
|
|
608
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_ATTEMPTS,
|
|
609
|
+
);
|
|
610
|
+
if (
|
|
611
|
+
scope.accountId !== expected.scope.accountId ||
|
|
612
|
+
scope.workspaceId !== expected.scope.workspaceId ||
|
|
613
|
+
artifactId !== expected.artifactId ||
|
|
614
|
+
jobId !== expected.jobId ||
|
|
615
|
+
owner !== expected.owner ||
|
|
616
|
+
attemptCount !== expected.attemptCount
|
|
617
|
+
) {
|
|
618
|
+
throw contractViolation(`Materialization ${operation} returned a different lease fence`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function validateScope(
|
|
623
|
+
scope: EditableArtifactMaterializationScope,
|
|
624
|
+
): EditableArtifactMaterializationScope {
|
|
625
|
+
if (!scope || typeof scope !== "object") throw new TypeError("Materialization scope is invalid");
|
|
626
|
+
return Object.freeze({
|
|
627
|
+
accountId: validateUuid(scope.accountId, "account id"),
|
|
628
|
+
workspaceId: validateUuid(scope.workspaceId, "workspace id"),
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function validateDatabaseScope(
|
|
633
|
+
accountId: unknown,
|
|
634
|
+
workspaceId: unknown,
|
|
635
|
+
): EditableArtifactMaterializationScope {
|
|
636
|
+
try {
|
|
637
|
+
return Object.freeze({
|
|
638
|
+
accountId: validateUuid(accountId, "account id"),
|
|
639
|
+
workspaceId: validateUuid(workspaceId, "workspace id"),
|
|
640
|
+
});
|
|
641
|
+
} catch (error) {
|
|
642
|
+
throw contractViolation("Materialization row contains an invalid tenant scope", error);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function validateUuid(value: unknown, label: string): string {
|
|
647
|
+
if (
|
|
648
|
+
typeof value !== "string" ||
|
|
649
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value)
|
|
650
|
+
) {
|
|
651
|
+
throw new TypeError(`${label} must be a canonical lowercase UUID`);
|
|
652
|
+
}
|
|
653
|
+
return value;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function validateStableId(value: unknown, label: string): string {
|
|
657
|
+
if (typeof value !== "string" || !/^[0-9a-f]{32}$/.test(value) || /^0+$/.test(value)) {
|
|
658
|
+
throw new TypeError(`${label} must be fixed-width lowercase nonzero hexadecimal text`);
|
|
659
|
+
}
|
|
660
|
+
return value;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function validateHash(value: unknown, label: string): string {
|
|
664
|
+
if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value)) {
|
|
665
|
+
throw new TypeError(`${label} must be a canonical SHA-256 digest`);
|
|
666
|
+
}
|
|
667
|
+
return value;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function validateOwner(value: unknown): string {
|
|
671
|
+
return validateBoundedText(value, "materialization lease owner", 256);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function validateErrorCode(value: unknown): string {
|
|
675
|
+
if (typeof value !== "string" || !/^[a-z][a-z0-9_.-]{0,127}$/.test(value)) {
|
|
676
|
+
throw new TypeError("Materialization error code must be a bounded machine code");
|
|
677
|
+
}
|
|
678
|
+
return value;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function validateDataSchema(value: unknown): string {
|
|
682
|
+
if (
|
|
683
|
+
typeof value !== "string" ||
|
|
684
|
+
!/^[a-z_][a-z0-9_$]{0,62}$/.test(value) ||
|
|
685
|
+
utf8Length(value) > 63
|
|
686
|
+
) {
|
|
687
|
+
throw new TypeError("Materialization data schema must be an unquoted PostgreSQL identifier");
|
|
688
|
+
}
|
|
689
|
+
return value;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function validateBoundedText(value: unknown, label: string, maxBytes: number): string {
|
|
693
|
+
if (
|
|
694
|
+
typeof value !== "string" ||
|
|
695
|
+
value.length === 0 ||
|
|
696
|
+
value !== value.trim() ||
|
|
697
|
+
value.includes("\0") ||
|
|
698
|
+
utf8Length(value) > maxBytes
|
|
699
|
+
) {
|
|
700
|
+
throw new TypeError(`${label} must be non-empty, trimmed, and at most ${maxBytes} UTF-8 bytes`);
|
|
701
|
+
}
|
|
702
|
+
return value;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function validateInteger(value: unknown, label: string, minimum: number, maximum: number): number {
|
|
706
|
+
if (!Number.isSafeInteger(value) || (value as number) < minimum || (value as number) > maximum) {
|
|
707
|
+
throw new TypeError(`${label} must be an integer between ${minimum} and ${maximum}`);
|
|
708
|
+
}
|
|
709
|
+
return value as number;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function databaseInteger(value: unknown, label: string, minimum: number, maximum: number): number {
|
|
713
|
+
const parsed =
|
|
714
|
+
typeof value === "bigint"
|
|
715
|
+
? Number(value)
|
|
716
|
+
: typeof value === "string" && /^[0-9]+$/.test(value)
|
|
717
|
+
? Number(value)
|
|
718
|
+
: value;
|
|
719
|
+
try {
|
|
720
|
+
return validateInteger(parsed, label, minimum, maximum);
|
|
721
|
+
} catch (error) {
|
|
722
|
+
throw contractViolation(`Materialization row contains an invalid ${label}`, error);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function validateTimestamp(value: unknown, label: string): string {
|
|
727
|
+
const date = value instanceof Date ? value : typeof value === "string" ? new Date(value) : null;
|
|
728
|
+
if (!date || !Number.isFinite(date.getTime())) {
|
|
729
|
+
throw new TypeError(`${label} must be a valid timestamp`);
|
|
730
|
+
}
|
|
731
|
+
return date.toISOString();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function databaseTimestamp(value: unknown, label: string): string {
|
|
735
|
+
try {
|
|
736
|
+
return validateTimestamp(value, label);
|
|
737
|
+
} catch (error) {
|
|
738
|
+
throw contractViolation(`Materialization row contains an invalid ${label}`, error);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const formats = new Set<EditableArtifactMaterializationFormat>([
|
|
743
|
+
"xlsx",
|
|
744
|
+
"pptx",
|
|
745
|
+
"docx",
|
|
746
|
+
"pdf",
|
|
747
|
+
"png",
|
|
748
|
+
"webp",
|
|
749
|
+
]);
|
|
750
|
+
|
|
751
|
+
const modalities = new Set<EditableArtifactMaterializationModality>([
|
|
752
|
+
"spreadsheet",
|
|
753
|
+
"presentation",
|
|
754
|
+
"document",
|
|
755
|
+
]);
|
|
756
|
+
|
|
757
|
+
function validateDatabaseModality(value: unknown): EditableArtifactMaterializationModality {
|
|
758
|
+
if (
|
|
759
|
+
typeof value !== "string" ||
|
|
760
|
+
!modalities.has(value as EditableArtifactMaterializationModality)
|
|
761
|
+
) {
|
|
762
|
+
throw contractViolation("Materialization row contains an invalid modality");
|
|
763
|
+
}
|
|
764
|
+
return value as EditableArtifactMaterializationModality;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const mimeTypes = new Set<EditableArtifactMaterializationMimeType>([
|
|
768
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
769
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
770
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
771
|
+
"application/pdf",
|
|
772
|
+
"image/png",
|
|
773
|
+
"image/webp",
|
|
774
|
+
]);
|
|
775
|
+
|
|
776
|
+
function validateDatabaseFormat(value: unknown): EditableArtifactMaterializationFormat {
|
|
777
|
+
if (typeof value !== "string" || !formats.has(value as EditableArtifactMaterializationFormat)) {
|
|
778
|
+
throw contractViolation("Materialization row contains an invalid format");
|
|
779
|
+
}
|
|
780
|
+
return value as EditableArtifactMaterializationFormat;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function validateFormatForModality(
|
|
784
|
+
modality: EditableArtifactMaterializationModality,
|
|
785
|
+
format: EditableArtifactMaterializationFormat,
|
|
786
|
+
): void {
|
|
787
|
+
const valid =
|
|
788
|
+
(modality === "spreadsheet" && ["xlsx", "pdf", "png", "webp"].includes(format)) ||
|
|
789
|
+
(modality === "presentation" && ["pptx", "pdf", "png", "webp"].includes(format)) ||
|
|
790
|
+
(modality === "document" && ["docx", "pdf", "png", "webp"].includes(format));
|
|
791
|
+
if (!valid) {
|
|
792
|
+
throw contractViolation("Materialization row contains a format incompatible with its modality");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function validateDatabaseNormalizedOptions(value: unknown, expectedHash: string): string {
|
|
797
|
+
let normalized: string;
|
|
798
|
+
try {
|
|
799
|
+
normalized = validateBoundedText(
|
|
800
|
+
value,
|
|
801
|
+
"normalized materialization options",
|
|
802
|
+
EDITABLE_ARTIFACT_MATERIALIZATION_MAX_OPTIONS_BYTES,
|
|
803
|
+
);
|
|
804
|
+
const parsed = JSON.parse(normalized) as unknown;
|
|
805
|
+
if (
|
|
806
|
+
parsed === null ||
|
|
807
|
+
typeof parsed !== "object" ||
|
|
808
|
+
Array.isArray(parsed) ||
|
|
809
|
+
JSON.stringify(parsed) !== normalized
|
|
810
|
+
) {
|
|
811
|
+
throw new TypeError("normalized materialization options are not a canonical JSON object");
|
|
812
|
+
}
|
|
813
|
+
} catch (error) {
|
|
814
|
+
throw contractViolation("Materialization row contains invalid normalized options", error);
|
|
815
|
+
}
|
|
816
|
+
const actualHash = `sha256:${createHash("sha256").update(normalized, "utf8").digest("hex")}`;
|
|
817
|
+
if (actualHash !== expectedHash) {
|
|
818
|
+
throw contractViolation("Materialization options hash does not bind its exact canonical bytes");
|
|
819
|
+
}
|
|
820
|
+
return normalized;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function validateMimeType(value: unknown): EditableArtifactMaterializationMimeType {
|
|
824
|
+
if (
|
|
825
|
+
typeof value !== "string" ||
|
|
826
|
+
!mimeTypes.has(value as EditableArtifactMaterializationMimeType)
|
|
827
|
+
) {
|
|
828
|
+
throw new TypeError("Materialization MIME type is unsupported");
|
|
829
|
+
}
|
|
830
|
+
return value as EditableArtifactMaterializationMimeType;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function validateDatabaseMimeType(value: unknown): EditableArtifactMaterializationMimeType {
|
|
834
|
+
try {
|
|
835
|
+
return validateMimeType(value);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
throw contractViolation("Materialization row contains an invalid MIME type", error);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function validateDatabaseOwner(value: unknown): string {
|
|
842
|
+
try {
|
|
843
|
+
return validateOwner(value);
|
|
844
|
+
} catch (error) {
|
|
845
|
+
throw contractViolation("Materialization row contains an invalid lease owner", error);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function validateDatabaseStableId(value: unknown, label: string): string {
|
|
850
|
+
try {
|
|
851
|
+
return validateStableId(value, label);
|
|
852
|
+
} catch (error) {
|
|
853
|
+
throw contractViolation(`Materialization row contains an invalid ${label}`, error);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function validateDatabaseHash(value: unknown, label: string): string {
|
|
858
|
+
try {
|
|
859
|
+
return validateHash(value, label);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
throw contractViolation(`Materialization row contains an invalid ${label}`, error);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function validateDatabaseText(value: unknown, label: string, maxBytes: number): string {
|
|
866
|
+
try {
|
|
867
|
+
return validateBoundedText(value, label, maxBytes);
|
|
868
|
+
} catch (error) {
|
|
869
|
+
throw contractViolation(`Materialization row contains an invalid ${label}`, error);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function utf8Length(value: string): number {
|
|
874
|
+
return new TextEncoder().encode(value).byteLength;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function leaseFenced(): EditableArtifactMaterializationPersistenceError {
|
|
878
|
+
return new EditableArtifactMaterializationPersistenceError(
|
|
879
|
+
"lease_fenced",
|
|
880
|
+
"Editable artifact materialization lease is no longer authoritative",
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function contractViolation(
|
|
885
|
+
message: string,
|
|
886
|
+
cause?: unknown,
|
|
887
|
+
): EditableArtifactMaterializationPersistenceError {
|
|
888
|
+
return new EditableArtifactMaterializationPersistenceError(
|
|
889
|
+
"database_contract_violation",
|
|
890
|
+
message,
|
|
891
|
+
cause === undefined ? undefined : { cause },
|
|
892
|
+
);
|
|
893
|
+
}
|