@opengeni/db 0.28.1 → 0.31.1
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 +91 -0
- package/dist/browser-identities.d.ts +128 -0
- package/dist/browser-sessions.d.ts +272 -0
- package/dist/browser-state-artifacts.d.ts +17 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +192 -0
- package/dist/chunk-F5FT7BEZ.js +3535 -0
- package/dist/chunk-F5FT7BEZ.js.map +1 -0
- package/dist/chunk-ISI2TIUG.js +639 -0
- package/dist/chunk-ISI2TIUG.js.map +1 -0
- package/dist/chunk-MWDVXQOL.js +11639 -0
- package/dist/chunk-MWDVXQOL.js.map +1 -0
- package/dist/chunk-NBE6CHRI.js +955 -0
- package/dist/chunk-NBE6CHRI.js.map +1 -0
- package/dist/{chunk-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -11
- package/dist/chunk-YHZXQ7HP.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 +52 -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/environment-crypto.d.ts +8 -4
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +958 -126
- package/dist/index.js +31204 -12172
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +51 -4
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-features.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +3 -0
- package/dist/interaction-schema.d.ts +5372 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/lossless-columns.d.ts +58 -0
- package/dist/lossless-json.d.ts +39 -0
- package/dist/memory-domain.d.ts +3 -6
- 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 +7 -14
- 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 +18 -6
- package/dist/schema.d.ts +19122 -8731
- package/dist/schema.js +173 -1
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +4 -4
- 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 +8 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/transcription-recordings-schema.d.ts +44 -6
- package/dist/transcription-recordings.d.ts +3 -3
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +216 -0
- package/dist/video-generation.js +66 -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/drizzle/0065_enrollment_credential_generation.sql +10 -0
- package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
- package/drizzle/0176_lossless_canonical_json.sql +975 -0
- package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
- package/drizzle/0178_permissioned_secret_reads.sql +14 -0
- package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
- package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
- package/drizzle/0181_connected_machine_removal.sql +52 -0
- package/drizzle/0182_connected_machine_remove_session_default.sql +77 -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_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/package.json +18 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +1896 -0
- package/src/browser-identities.ts +1168 -0
- package/src/browser-sessions.ts +2005 -0
- package/src/browser-state-artifacts.ts +59 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1663 -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 +434 -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/environment-crypto.ts +22 -9
- package/src/generated-images.ts +740 -0
- package/src/index.ts +10117 -2730
- package/src/insights.ts +160 -13
- package/src/integration-bindings.ts +455 -0
- package/src/integration-features.ts +859 -0
- package/src/interaction-revisions.ts +40 -0
- package/src/interaction-schema.ts +1303 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/lossless-columns.ts +35 -0
- package/src/lossless-json.ts +322 -0
- package/src/memory-domain.ts +5 -44
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +929 -0
- package/src/persistence-errors.ts +26 -34
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +302 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +514 -5
- package/src/schema.ts +5177 -2957
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +133 -81
- package/src/session-queue-commands.ts +511 -247
- package/src/session-realtime-context.ts +20 -7
- package/src/session-realtime-ledger.ts +32 -10
- package/src/session-realtime-mirror.ts +4 -2
- package/src/session-realtime-terminal.ts +89 -21
- package/src/session-realtime.ts +10 -9
- package/src/session-tool-call-settlement.ts +38 -15
- package/src/slack-user-link-access.ts +702 -0
- package/src/transcription-recordings-schema.ts +5 -2
- package/src/transcription-recordings.ts +70 -40
- package/src/video-generation.ts +1452 -0
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/dist/chunk-DBS5HPEW.js.map +0 -1
- package/dist/chunk-HT5T62CC.js +0 -6803
- package/dist/chunk-HT5T62CC.js.map +0 -1
- package/dist/event-payload-sanitizer.d.ts +0 -32
- package/src/event-payload-sanitizer.ts +0 -377
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { rawRows, type Database, withRlsContext } from "./database";
|
|
6
|
+
|
|
7
|
+
export type PersistedEditableArtifactExportScope = Readonly<{
|
|
8
|
+
accountId: string;
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
}>;
|
|
11
|
+
|
|
12
|
+
export type PersistedEditableArtifactExportActor =
|
|
13
|
+
| Readonly<{ kind: "human"; subjectId: string; replicaId: string }>
|
|
14
|
+
| Readonly<{
|
|
15
|
+
kind: "agent";
|
|
16
|
+
subjectId: string;
|
|
17
|
+
replicaId: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
turnId: string;
|
|
20
|
+
attemptId: string;
|
|
21
|
+
generation: number;
|
|
22
|
+
}>
|
|
23
|
+
| Readonly<{
|
|
24
|
+
kind: "service";
|
|
25
|
+
subjectId: string;
|
|
26
|
+
replicaId: string;
|
|
27
|
+
service: string;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export type PersistedEditableArtifactExportModality = "spreadsheet" | "document" | "presentation";
|
|
31
|
+
|
|
32
|
+
export type PersistedEditableArtifactExportFormat =
|
|
33
|
+
| "xlsx"
|
|
34
|
+
| "pptx"
|
|
35
|
+
| "docx"
|
|
36
|
+
| "pdf"
|
|
37
|
+
| "png"
|
|
38
|
+
| "webp";
|
|
39
|
+
|
|
40
|
+
export type PersistedEditableArtifactExportProfile = Readonly<{
|
|
41
|
+
modality: PersistedEditableArtifactExportModality;
|
|
42
|
+
format: PersistedEditableArtifactExportFormat;
|
|
43
|
+
codecId: string;
|
|
44
|
+
codecVersion: string;
|
|
45
|
+
kernelVersion: string;
|
|
46
|
+
fontRegistryHash: string;
|
|
47
|
+
policyHash: string;
|
|
48
|
+
normalizedOptions: string;
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
export type PersistedEditableArtifactPinnedVersion = Readonly<{
|
|
52
|
+
scope: PersistedEditableArtifactExportScope;
|
|
53
|
+
artifactId: string;
|
|
54
|
+
id: string;
|
|
55
|
+
modality: PersistedEditableArtifactExportModality;
|
|
56
|
+
snapshotId: string;
|
|
57
|
+
headSequence: number;
|
|
58
|
+
causalFrontier: readonly Readonly<{ replicaId: string; counter: number }>[] | null;
|
|
59
|
+
nativeRevision: number | null;
|
|
60
|
+
stateHash: string;
|
|
61
|
+
name: string;
|
|
62
|
+
pinned: true;
|
|
63
|
+
createdBySubjectId: string;
|
|
64
|
+
createdAt: string;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type PersistedEditableArtifactMaterializationResult = Readonly<{
|
|
68
|
+
id: string;
|
|
69
|
+
byteSize: number;
|
|
70
|
+
contentHash: string;
|
|
71
|
+
mimeType: string;
|
|
72
|
+
verifiedAt: string;
|
|
73
|
+
createdAt: string;
|
|
74
|
+
}>;
|
|
75
|
+
|
|
76
|
+
export type PersistedEditableArtifactMaterializationJob = Readonly<{
|
|
77
|
+
scope: PersistedEditableArtifactExportScope;
|
|
78
|
+
artifactId: string;
|
|
79
|
+
id: string;
|
|
80
|
+
versionId: string;
|
|
81
|
+
inputSnapshotId: string;
|
|
82
|
+
targetHeadSequence: number;
|
|
83
|
+
stateHash: string;
|
|
84
|
+
format: PersistedEditableArtifactExportFormat;
|
|
85
|
+
state: "pending" | "running" | "succeeded" | "failed";
|
|
86
|
+
attemptCount: number;
|
|
87
|
+
errorCode: string | null;
|
|
88
|
+
createdAt: string;
|
|
89
|
+
startedAt: string | null;
|
|
90
|
+
completedAt: string | null;
|
|
91
|
+
result: PersistedEditableArtifactMaterializationResult | null;
|
|
92
|
+
}>;
|
|
93
|
+
|
|
94
|
+
export type PersistedEditableArtifactExportSnapshot = Readonly<{
|
|
95
|
+
modality: PersistedEditableArtifactExportModality;
|
|
96
|
+
snapshotId: string;
|
|
97
|
+
coveredHeadSequence: number;
|
|
98
|
+
stateHash: string;
|
|
99
|
+
coveredCausalFrontier: readonly Readonly<{ replicaId: string; counter: number }>[] | null;
|
|
100
|
+
nativeRevision: number | null;
|
|
101
|
+
}>;
|
|
102
|
+
|
|
103
|
+
type ExportContext = Readonly<{
|
|
104
|
+
scope: PersistedEditableArtifactExportScope;
|
|
105
|
+
artifactId: string;
|
|
106
|
+
actor: PersistedEditableArtifactExportActor;
|
|
107
|
+
}>;
|
|
108
|
+
|
|
109
|
+
export type PinPersistedEditableArtifactVersionRequest = ExportContext &
|
|
110
|
+
Readonly<{
|
|
111
|
+
expectedAuthorizationRevision: number;
|
|
112
|
+
authorityKey: string;
|
|
113
|
+
receiptId: string;
|
|
114
|
+
versionId: string;
|
|
115
|
+
idempotencyKey: string;
|
|
116
|
+
requestHash: string;
|
|
117
|
+
name: string;
|
|
118
|
+
snapshot: PersistedEditableArtifactExportSnapshot;
|
|
119
|
+
}>;
|
|
120
|
+
|
|
121
|
+
export type EnqueuePersistedEditableArtifactMaterializationRequest = ExportContext &
|
|
122
|
+
Readonly<{
|
|
123
|
+
expectedAuthorizationRevision: number;
|
|
124
|
+
authorityKey: string;
|
|
125
|
+
receiptId: string;
|
|
126
|
+
jobId: string;
|
|
127
|
+
idempotencyKey: string;
|
|
128
|
+
requestHash: string;
|
|
129
|
+
versionId: string;
|
|
130
|
+
profile: PersistedEditableArtifactExportProfile;
|
|
131
|
+
}>;
|
|
132
|
+
|
|
133
|
+
export type PersistedEditableArtifactAuthorizationResult<T> =
|
|
134
|
+
| Readonly<{ kind: "result"; value: T }>
|
|
135
|
+
| Readonly<{ kind: "authorization_stale" }>;
|
|
136
|
+
|
|
137
|
+
export class EditableArtifactDurableExportPersistenceError extends Error {
|
|
138
|
+
constructor(
|
|
139
|
+
readonly code:
|
|
140
|
+
| "not_found"
|
|
141
|
+
| "idempotency_conflict"
|
|
142
|
+
| "snapshot_conflict"
|
|
143
|
+
| "unsupported_format"
|
|
144
|
+
| "database_contract_violation",
|
|
145
|
+
message: string,
|
|
146
|
+
options?: ErrorOptions,
|
|
147
|
+
) {
|
|
148
|
+
super(message, options);
|
|
149
|
+
this.name = "EditableArtifactDurableExportPersistenceError";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type ArtifactRow = Record<string, unknown> & {
|
|
154
|
+
modality: unknown;
|
|
155
|
+
authorization_revision: unknown;
|
|
156
|
+
head_sequence: unknown;
|
|
157
|
+
state_hash: unknown;
|
|
158
|
+
current_snapshot_id: unknown;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
type VersionRow = Record<string, unknown> & {
|
|
162
|
+
account_id: unknown;
|
|
163
|
+
workspace_id: unknown;
|
|
164
|
+
artifact_id: unknown;
|
|
165
|
+
id: unknown;
|
|
166
|
+
modality: unknown;
|
|
167
|
+
snapshot_id: unknown;
|
|
168
|
+
head_sequence: unknown;
|
|
169
|
+
causal_frontier: unknown;
|
|
170
|
+
native_revision: unknown;
|
|
171
|
+
state_hash: unknown;
|
|
172
|
+
name: unknown;
|
|
173
|
+
pinned: unknown;
|
|
174
|
+
created_by_subject_id: unknown;
|
|
175
|
+
created_at: unknown;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
type JobRow = Record<string, unknown> & {
|
|
179
|
+
account_id: unknown;
|
|
180
|
+
workspace_id: unknown;
|
|
181
|
+
artifact_id: unknown;
|
|
182
|
+
id: unknown;
|
|
183
|
+
version_id: unknown;
|
|
184
|
+
input_snapshot_id: unknown;
|
|
185
|
+
target_head_sequence: unknown;
|
|
186
|
+
state_hash: unknown;
|
|
187
|
+
format: unknown;
|
|
188
|
+
state: unknown;
|
|
189
|
+
attempt_count: unknown;
|
|
190
|
+
error_code: unknown;
|
|
191
|
+
created_at: unknown;
|
|
192
|
+
started_at: unknown;
|
|
193
|
+
completed_at: unknown;
|
|
194
|
+
result_id: unknown;
|
|
195
|
+
result_byte_size: unknown;
|
|
196
|
+
result_content_hash: unknown;
|
|
197
|
+
result_mime_type: unknown;
|
|
198
|
+
result_verified_at: unknown;
|
|
199
|
+
result_created_at: unknown;
|
|
200
|
+
object_reference?: unknown;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
type ReceiptRow = Record<string, unknown> & {
|
|
204
|
+
request_hash: unknown;
|
|
205
|
+
resource_id: unknown;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
type AuthorizationRow = Record<string, unknown> & {
|
|
209
|
+
allowed: unknown;
|
|
210
|
+
authorization_revision: unknown;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/** Tenant-scoped public version/export persistence; global claiming remains separate. */
|
|
214
|
+
export class PostgresEditableArtifactDurableExportStore {
|
|
215
|
+
constructor(private readonly db: Database) {}
|
|
216
|
+
|
|
217
|
+
async pinVersion(
|
|
218
|
+
requestInput: PinPersistedEditableArtifactVersionRequest,
|
|
219
|
+
): Promise<
|
|
220
|
+
PersistedEditableArtifactAuthorizationResult<
|
|
221
|
+
Readonly<{ version: PersistedEditableArtifactPinnedVersion; replayed: boolean }>
|
|
222
|
+
>
|
|
223
|
+
> {
|
|
224
|
+
const request = validatePinRequest(requestInput);
|
|
225
|
+
return await withRlsContext(
|
|
226
|
+
this.db,
|
|
227
|
+
request.scope,
|
|
228
|
+
async (tx) => {
|
|
229
|
+
const authorization = await authorize(tx, request);
|
|
230
|
+
if (
|
|
231
|
+
!authorization.allowed ||
|
|
232
|
+
authorization.revision !== request.expectedAuthorizationRevision
|
|
233
|
+
) {
|
|
234
|
+
return Object.freeze({ kind: "authorization_stale" as const });
|
|
235
|
+
}
|
|
236
|
+
await lockIdempotencyRequest(tx, request, "version");
|
|
237
|
+
const replay = await findReceipt(tx, request, "version", request.idempotencyKey);
|
|
238
|
+
if (replay) {
|
|
239
|
+
if (replay.requestHash !== request.requestHash) throw idempotencyConflict();
|
|
240
|
+
const version = await loadVersion(
|
|
241
|
+
tx,
|
|
242
|
+
request.scope,
|
|
243
|
+
request.artifactId,
|
|
244
|
+
replay.resourceId,
|
|
245
|
+
);
|
|
246
|
+
if (!version) throw contractViolation("Version receipt points to a missing version");
|
|
247
|
+
return Object.freeze({
|
|
248
|
+
kind: "result" as const,
|
|
249
|
+
value: Object.freeze({ version, replayed: true }),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const artifact = await lockArtifact(tx, request.scope, request.artifactId);
|
|
254
|
+
if (!artifact) throw notFound();
|
|
255
|
+
if (artifact.authorizationRevision !== request.expectedAuthorizationRevision) {
|
|
256
|
+
return Object.freeze({ kind: "authorization_stale" as const });
|
|
257
|
+
}
|
|
258
|
+
assertSnapshotPinsArtifact(request.snapshot, artifact);
|
|
259
|
+
const inserted = await rawRows<VersionRow>(
|
|
260
|
+
tx,
|
|
261
|
+
sql`insert into editable_artifact_versions (
|
|
262
|
+
account_id, workspace_id, artifact_id, id, snapshot_id, head_sequence,
|
|
263
|
+
causal_frontier, native_revision, state_hash, name, pinned, created_by_subject_id
|
|
264
|
+
) values (
|
|
265
|
+
${request.scope.accountId}::uuid, ${request.scope.workspaceId}::uuid,
|
|
266
|
+
${request.artifactId}, ${request.versionId}, ${request.snapshot.snapshotId},
|
|
267
|
+
${request.snapshot.coveredHeadSequence},
|
|
268
|
+
${request.snapshot.coveredCausalFrontier === null ? null : JSON.stringify(request.snapshot.coveredCausalFrontier)}::jsonb,
|
|
269
|
+
${request.snapshot.nativeRevision}, ${request.snapshot.stateHash}, ${request.name},
|
|
270
|
+
true, ${request.actor.subjectId}
|
|
271
|
+
) returning
|
|
272
|
+
account_id, workspace_id, artifact_id, id,
|
|
273
|
+
${artifact.modality}::text as modality, snapshot_id, head_sequence,
|
|
274
|
+
causal_frontier, native_revision, state_hash, name, pinned,
|
|
275
|
+
created_by_subject_id, created_at`,
|
|
276
|
+
);
|
|
277
|
+
const versionRow = exactRow(inserted, "Version insert");
|
|
278
|
+
await insertReceipt(tx, {
|
|
279
|
+
...request,
|
|
280
|
+
operationKind: "version",
|
|
281
|
+
resourceType: "artifact_version",
|
|
282
|
+
resourceId: request.versionId,
|
|
283
|
+
result: {
|
|
284
|
+
schemaVersion: 1,
|
|
285
|
+
artifactId: request.artifactId,
|
|
286
|
+
versionId: request.versionId,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
return Object.freeze({
|
|
290
|
+
kind: "result" as const,
|
|
291
|
+
value: Object.freeze({ version: versionFromRow(versionRow), replayed: false }),
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
{ isolationLevel: "serializable" },
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async enqueueMaterialization(
|
|
299
|
+
requestInput: EnqueuePersistedEditableArtifactMaterializationRequest,
|
|
300
|
+
): Promise<
|
|
301
|
+
PersistedEditableArtifactAuthorizationResult<
|
|
302
|
+
Readonly<{ job: PersistedEditableArtifactMaterializationJob; replayed: boolean }>
|
|
303
|
+
>
|
|
304
|
+
> {
|
|
305
|
+
const request = validateEnqueueRequest(requestInput);
|
|
306
|
+
return await withRlsContext(
|
|
307
|
+
this.db,
|
|
308
|
+
request.scope,
|
|
309
|
+
async (tx) => {
|
|
310
|
+
const authorization = await authorize(tx, request);
|
|
311
|
+
if (
|
|
312
|
+
!authorization.allowed ||
|
|
313
|
+
authorization.revision !== request.expectedAuthorizationRevision
|
|
314
|
+
) {
|
|
315
|
+
return Object.freeze({ kind: "authorization_stale" as const });
|
|
316
|
+
}
|
|
317
|
+
await lockIdempotencyRequest(tx, request, "materialize");
|
|
318
|
+
const replay = await findReceipt(tx, request, "materialize", request.idempotencyKey);
|
|
319
|
+
if (replay) {
|
|
320
|
+
if (replay.requestHash !== request.requestHash) throw idempotencyConflict();
|
|
321
|
+
const job = await loadJob(
|
|
322
|
+
tx,
|
|
323
|
+
request.scope,
|
|
324
|
+
request.artifactId,
|
|
325
|
+
replay.resourceId,
|
|
326
|
+
false,
|
|
327
|
+
);
|
|
328
|
+
if (!job) throw contractViolation("Materialization receipt points to a missing job");
|
|
329
|
+
return Object.freeze({
|
|
330
|
+
kind: "result" as const,
|
|
331
|
+
value: Object.freeze({ job: job.job, replayed: true }),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const artifact = await lockArtifact(tx, request.scope, request.artifactId);
|
|
336
|
+
if (!artifact) throw notFound();
|
|
337
|
+
if (artifact.authorizationRevision !== request.expectedAuthorizationRevision) {
|
|
338
|
+
return Object.freeze({ kind: "authorization_stale" as const });
|
|
339
|
+
}
|
|
340
|
+
if (
|
|
341
|
+
artifact.modality !== request.profile.modality ||
|
|
342
|
+
!formatMatchesModality(artifact.modality, request.profile.format)
|
|
343
|
+
) {
|
|
344
|
+
throw unsupportedFormat();
|
|
345
|
+
}
|
|
346
|
+
const version = await loadVersion(tx, request.scope, request.artifactId, request.versionId);
|
|
347
|
+
if (!version || version.modality !== artifact.modality) throw notFound();
|
|
348
|
+
const optionsHash = sha256(request.profile.normalizedOptions);
|
|
349
|
+
const inserted = await rawRows<Record<string, unknown> & { id: unknown }>(
|
|
350
|
+
tx,
|
|
351
|
+
sql`insert into editable_artifact_materialization_jobs (
|
|
352
|
+
account_id, workspace_id, artifact_id, id, version_id, input_snapshot_id,
|
|
353
|
+
target_head_sequence, state_hash, format, normalized_options, codec_id,
|
|
354
|
+
codec_version, kernel_version, font_registry_hash, policy_hash
|
|
355
|
+
) values (
|
|
356
|
+
${request.scope.accountId}::uuid, ${request.scope.workspaceId}::uuid,
|
|
357
|
+
${request.artifactId}, ${request.jobId}, ${version.id}, ${version.snapshotId},
|
|
358
|
+
${version.headSequence}, ${version.stateHash}, ${request.profile.format},
|
|
359
|
+
${request.profile.normalizedOptions}, ${request.profile.codecId},
|
|
360
|
+
${request.profile.codecVersion}, ${request.profile.kernelVersion},
|
|
361
|
+
${request.profile.fontRegistryHash}, ${request.profile.policyHash}
|
|
362
|
+
) on conflict (
|
|
363
|
+
account_id, workspace_id, artifact_id, input_snapshot_id, state_hash, format,
|
|
364
|
+
options_hash, codec_id, codec_version, kernel_version, font_registry_hash, policy_hash
|
|
365
|
+
) do nothing returning id`,
|
|
366
|
+
);
|
|
367
|
+
let selectedJobId: string;
|
|
368
|
+
if (inserted.length === 1) {
|
|
369
|
+
selectedJobId = text(inserted[0]!.id, "inserted materialization job id");
|
|
370
|
+
} else if (inserted.length === 0) {
|
|
371
|
+
const cached = await rawRows<Record<string, unknown> & { id: unknown }>(
|
|
372
|
+
tx,
|
|
373
|
+
sql`select id from editable_artifact_materialization_jobs
|
|
374
|
+
where account_id = ${request.scope.accountId}::uuid
|
|
375
|
+
and workspace_id = ${request.scope.workspaceId}::uuid
|
|
376
|
+
and artifact_id = ${request.artifactId}
|
|
377
|
+
and input_snapshot_id = ${version.snapshotId}
|
|
378
|
+
and state_hash = ${version.stateHash}
|
|
379
|
+
and format = ${request.profile.format}
|
|
380
|
+
and options_hash = ${optionsHash}
|
|
381
|
+
and codec_id = ${request.profile.codecId}
|
|
382
|
+
and codec_version = ${request.profile.codecVersion}
|
|
383
|
+
and kernel_version = ${request.profile.kernelVersion}
|
|
384
|
+
and font_registry_hash = ${request.profile.fontRegistryHash}
|
|
385
|
+
and policy_hash = ${request.profile.policyHash}`,
|
|
386
|
+
);
|
|
387
|
+
selectedJobId = text(exactRow(cached, "Materialization cache read").id, "job id");
|
|
388
|
+
} else {
|
|
389
|
+
throw contractViolation("Materialization insert returned multiple rows");
|
|
390
|
+
}
|
|
391
|
+
await insertReceipt(tx, {
|
|
392
|
+
...request,
|
|
393
|
+
operationKind: "materialize",
|
|
394
|
+
resourceType: "materialization_job",
|
|
395
|
+
resourceId: selectedJobId,
|
|
396
|
+
result: {
|
|
397
|
+
schemaVersion: 1,
|
|
398
|
+
artifactId: request.artifactId,
|
|
399
|
+
materializationJobId: selectedJobId,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
const loaded = await loadJob(tx, request.scope, request.artifactId, selectedJobId, false);
|
|
403
|
+
if (!loaded) throw contractViolation("Inserted materialization job is missing");
|
|
404
|
+
return Object.freeze({
|
|
405
|
+
kind: "result" as const,
|
|
406
|
+
value: Object.freeze({ job: loaded.job, replayed: false }),
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
{ isolationLevel: "serializable" },
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async readVersion(
|
|
414
|
+
input: ExportContext & {
|
|
415
|
+
expectedAuthorizationRevision: number;
|
|
416
|
+
versionId: string;
|
|
417
|
+
},
|
|
418
|
+
): Promise<
|
|
419
|
+
PersistedEditableArtifactAuthorizationResult<PersistedEditableArtifactPinnedVersion | null>
|
|
420
|
+
> {
|
|
421
|
+
const request = validateReadRequest(input, "versionId");
|
|
422
|
+
return await withRlsContext(this.db, request.scope, async (tx) => {
|
|
423
|
+
const authorization = await authorize(tx, request);
|
|
424
|
+
if (
|
|
425
|
+
!authorization.allowed ||
|
|
426
|
+
authorization.revision !== request.expectedAuthorizationRevision
|
|
427
|
+
) {
|
|
428
|
+
return Object.freeze({ kind: "authorization_stale" as const });
|
|
429
|
+
}
|
|
430
|
+
return Object.freeze({
|
|
431
|
+
kind: "result" as const,
|
|
432
|
+
value: await loadVersion(tx, request.scope, request.artifactId, request.versionId),
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async readMaterialization(
|
|
438
|
+
input: ExportContext & {
|
|
439
|
+
expectedAuthorizationRevision: number;
|
|
440
|
+
jobId: string;
|
|
441
|
+
},
|
|
442
|
+
): Promise<
|
|
443
|
+
PersistedEditableArtifactAuthorizationResult<PersistedEditableArtifactMaterializationJob | null>
|
|
444
|
+
> {
|
|
445
|
+
const request = validateReadRequest(input, "jobId");
|
|
446
|
+
return await withRlsContext(this.db, request.scope, async (tx) => {
|
|
447
|
+
const authorization = await authorize(tx, request);
|
|
448
|
+
if (
|
|
449
|
+
!authorization.allowed ||
|
|
450
|
+
authorization.revision !== request.expectedAuthorizationRevision
|
|
451
|
+
) {
|
|
452
|
+
return Object.freeze({ kind: "authorization_stale" as const });
|
|
453
|
+
}
|
|
454
|
+
const loaded = await loadJob(tx, request.scope, request.artifactId, request.jobId, false);
|
|
455
|
+
return Object.freeze({ kind: "result" as const, value: loaded?.job ?? null });
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async readMaterializationDownload(
|
|
460
|
+
input: ExportContext & {
|
|
461
|
+
expectedAuthorizationRevision: number;
|
|
462
|
+
jobId: string;
|
|
463
|
+
},
|
|
464
|
+
): Promise<
|
|
465
|
+
PersistedEditableArtifactAuthorizationResult<
|
|
466
|
+
Readonly<{
|
|
467
|
+
job: PersistedEditableArtifactMaterializationJob | null;
|
|
468
|
+
objectReference: string | null;
|
|
469
|
+
}>
|
|
470
|
+
>
|
|
471
|
+
> {
|
|
472
|
+
const request = validateReadRequest(input, "jobId");
|
|
473
|
+
return await withRlsContext(this.db, request.scope, async (tx) => {
|
|
474
|
+
const authorization = await authorize(tx, request);
|
|
475
|
+
if (
|
|
476
|
+
!authorization.allowed ||
|
|
477
|
+
authorization.revision !== request.expectedAuthorizationRevision
|
|
478
|
+
) {
|
|
479
|
+
return Object.freeze({ kind: "authorization_stale" as const });
|
|
480
|
+
}
|
|
481
|
+
const loaded = await loadJob(tx, request.scope, request.artifactId, request.jobId, true);
|
|
482
|
+
return Object.freeze({
|
|
483
|
+
kind: "result" as const,
|
|
484
|
+
value: Object.freeze({
|
|
485
|
+
job: loaded?.job ?? null,
|
|
486
|
+
objectReference: loaded?.objectReference ?? null,
|
|
487
|
+
}),
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function authorize(
|
|
494
|
+
tx: Database,
|
|
495
|
+
request: ExportContext,
|
|
496
|
+
): Promise<Readonly<{ allowed: boolean; revision: number }>> {
|
|
497
|
+
const actor = request.actor;
|
|
498
|
+
const rows = await rawRows<AuthorizationRow>(
|
|
499
|
+
tx,
|
|
500
|
+
sql`select * from opengeni_private.authorize_editable_artifact_actor(
|
|
501
|
+
${request.scope.accountId}::uuid,
|
|
502
|
+
${request.scope.workspaceId}::uuid,
|
|
503
|
+
${request.artifactId},
|
|
504
|
+
${actor.kind},
|
|
505
|
+
${actor.subjectId},
|
|
506
|
+
${actor.kind === "agent" ? actor.sessionId : null},
|
|
507
|
+
${actor.kind === "agent" ? actor.turnId : null},
|
|
508
|
+
${actor.kind === "agent" ? actor.attemptId : null},
|
|
509
|
+
${actor.kind === "agent" ? actor.generation : null},
|
|
510
|
+
${actor.kind === "service" ? actor.service : null},
|
|
511
|
+
'export',
|
|
512
|
+
current_schema()
|
|
513
|
+
)`,
|
|
514
|
+
);
|
|
515
|
+
const row = exactRow(rows, "Editable artifact export authorization");
|
|
516
|
+
return Object.freeze({
|
|
517
|
+
allowed: row.allowed === true,
|
|
518
|
+
revision: integer(row.authorization_revision, "authorization revision", 1),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function lockArtifact(
|
|
523
|
+
tx: Database,
|
|
524
|
+
scope: PersistedEditableArtifactExportScope,
|
|
525
|
+
artifactId: string,
|
|
526
|
+
): Promise<Readonly<{
|
|
527
|
+
modality: PersistedEditableArtifactExportModality;
|
|
528
|
+
authorizationRevision: number;
|
|
529
|
+
headSequence: number;
|
|
530
|
+
stateHash: string;
|
|
531
|
+
currentSnapshotId: string;
|
|
532
|
+
}> | null> {
|
|
533
|
+
const rows = await rawRows<ArtifactRow>(
|
|
534
|
+
tx,
|
|
535
|
+
sql`select modality, authorization_revision, head_sequence, state_hash, current_snapshot_id
|
|
536
|
+
from editable_artifacts
|
|
537
|
+
where account_id = ${scope.accountId}::uuid
|
|
538
|
+
and workspace_id = ${scope.workspaceId}::uuid
|
|
539
|
+
and id = ${artifactId}
|
|
540
|
+
for share`,
|
|
541
|
+
);
|
|
542
|
+
if (rows.length === 0) return null;
|
|
543
|
+
const row = exactRow(rows, "Editable artifact lock");
|
|
544
|
+
return Object.freeze({
|
|
545
|
+
modality: modality(row.modality),
|
|
546
|
+
authorizationRevision: integer(row.authorization_revision, "authorization revision", 1),
|
|
547
|
+
headSequence: integer(row.head_sequence, "artifact head sequence", 0),
|
|
548
|
+
stateHash: hash(row.state_hash, "artifact state hash"),
|
|
549
|
+
currentSnapshotId: stableId(row.current_snapshot_id, "artifact current snapshot id"),
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async function findReceipt(
|
|
554
|
+
tx: Database,
|
|
555
|
+
request: ExportContext & Readonly<{ authorityKey: string }>,
|
|
556
|
+
operationKind: "version" | "materialize",
|
|
557
|
+
requestIdempotencyKey: string,
|
|
558
|
+
): Promise<Readonly<{ requestHash: string; resourceId: string }> | null> {
|
|
559
|
+
const rows = await rawRows<ReceiptRow>(
|
|
560
|
+
tx,
|
|
561
|
+
sql`select request_hash, resource_id
|
|
562
|
+
from editable_artifact_idempotency_receipts
|
|
563
|
+
where account_id = ${request.scope.accountId}::uuid
|
|
564
|
+
and workspace_id = ${request.scope.workspaceId}::uuid
|
|
565
|
+
and artifact_id = ${request.artifactId}
|
|
566
|
+
and operation_kind = ${operationKind}
|
|
567
|
+
and authority_key_digest = opengeni_private.editable_artifact_text_sha256(${request.authorityKey})
|
|
568
|
+
and authority_key = ${request.authorityKey}
|
|
569
|
+
and idempotency_key = ${requestIdempotencyKey}`,
|
|
570
|
+
);
|
|
571
|
+
if (rows.length === 0) return null;
|
|
572
|
+
const row = exactRow(rows, "Editable artifact export receipt read");
|
|
573
|
+
return Object.freeze({
|
|
574
|
+
requestHash: hash(row.request_hash, "receipt request hash"),
|
|
575
|
+
resourceId: stableId(row.resource_id, "receipt resource id"),
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async function lockIdempotencyRequest(
|
|
580
|
+
tx: Database,
|
|
581
|
+
request: ExportContext & Readonly<{ authorityKey: string; idempotencyKey: string }>,
|
|
582
|
+
operationKind: "version" | "materialize",
|
|
583
|
+
): Promise<void> {
|
|
584
|
+
await tx.execute(
|
|
585
|
+
sql`select pg_advisory_xact_lock(hashtextextended(
|
|
586
|
+
${`editable-artifact:${operationKind}:${request.scope.accountId}:${request.scope.workspaceId}:${request.artifactId}:${request.authorityKey}:${request.idempotencyKey}`},
|
|
587
|
+
0
|
|
588
|
+
))`,
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function insertReceipt(
|
|
593
|
+
tx: Database,
|
|
594
|
+
input: ExportContext &
|
|
595
|
+
Readonly<{
|
|
596
|
+
receiptId: string;
|
|
597
|
+
authorityKey: string;
|
|
598
|
+
idempotencyKey: string;
|
|
599
|
+
requestHash: string;
|
|
600
|
+
operationKind: "version" | "materialize";
|
|
601
|
+
resourceType: "artifact_version" | "materialization_job";
|
|
602
|
+
resourceId: string;
|
|
603
|
+
result: Readonly<Record<string, unknown>>;
|
|
604
|
+
}>,
|
|
605
|
+
): Promise<void> {
|
|
606
|
+
await tx.execute(sql`insert into editable_artifact_idempotency_receipts (
|
|
607
|
+
account_id, workspace_id, id, artifact_id, operation_kind, authority_key,
|
|
608
|
+
idempotency_key, request_hash, resource_type, resource_id, server_transaction_id, result
|
|
609
|
+
) values (
|
|
610
|
+
${input.scope.accountId}::uuid, ${input.scope.workspaceId}::uuid,
|
|
611
|
+
${input.receiptId}, ${input.artifactId}, ${input.operationKind}, ${input.authorityKey},
|
|
612
|
+
${input.idempotencyKey}, ${input.requestHash}, ${input.resourceType}, ${input.resourceId},
|
|
613
|
+
null, ${JSON.stringify(input.result)}::jsonb
|
|
614
|
+
)`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function loadVersion(
|
|
618
|
+
tx: Database,
|
|
619
|
+
scope: PersistedEditableArtifactExportScope,
|
|
620
|
+
artifactId: string,
|
|
621
|
+
versionId: string,
|
|
622
|
+
): Promise<PersistedEditableArtifactPinnedVersion | null> {
|
|
623
|
+
const rows = await rawRows<VersionRow>(
|
|
624
|
+
tx,
|
|
625
|
+
sql`select
|
|
626
|
+
version.account_id, version.workspace_id, version.artifact_id, version.id,
|
|
627
|
+
artifact.modality, version.snapshot_id, version.head_sequence,
|
|
628
|
+
version.causal_frontier, version.native_revision, version.state_hash,
|
|
629
|
+
version.name, version.pinned, version.created_by_subject_id, version.created_at
|
|
630
|
+
from editable_artifact_versions version
|
|
631
|
+
join editable_artifacts artifact
|
|
632
|
+
on artifact.account_id = version.account_id
|
|
633
|
+
and artifact.workspace_id = version.workspace_id
|
|
634
|
+
and artifact.id = version.artifact_id
|
|
635
|
+
where version.account_id = ${scope.accountId}::uuid
|
|
636
|
+
and version.workspace_id = ${scope.workspaceId}::uuid
|
|
637
|
+
and version.artifact_id = ${artifactId}
|
|
638
|
+
and version.id = ${versionId}`,
|
|
639
|
+
);
|
|
640
|
+
if (rows.length === 0) return null;
|
|
641
|
+
return versionFromRow(exactRow(rows, "Editable artifact version read"));
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async function loadJob(
|
|
645
|
+
tx: Database,
|
|
646
|
+
scope: PersistedEditableArtifactExportScope,
|
|
647
|
+
artifactId: string,
|
|
648
|
+
jobId: string,
|
|
649
|
+
includeObjectReference: boolean,
|
|
650
|
+
): Promise<Readonly<{
|
|
651
|
+
job: PersistedEditableArtifactMaterializationJob;
|
|
652
|
+
objectReference: string | null;
|
|
653
|
+
}> | null> {
|
|
654
|
+
const rows = await rawRows<JobRow>(
|
|
655
|
+
tx,
|
|
656
|
+
sql`select
|
|
657
|
+
job.account_id, job.workspace_id, job.artifact_id, job.id, job.version_id,
|
|
658
|
+
job.input_snapshot_id, job.target_head_sequence, job.state_hash, job.format,
|
|
659
|
+
job.state, job.attempt_count, job.error_code, job.created_at, job.started_at,
|
|
660
|
+
job.completed_at, result.id as result_id, result.byte_size as result_byte_size,
|
|
661
|
+
result.content_hash as result_content_hash, result.mime_type as result_mime_type,
|
|
662
|
+
result.verified_at as result_verified_at, result.created_at as result_created_at,
|
|
663
|
+
${includeObjectReference ? sql`blob.object_reference` : sql`null::text`} as object_reference
|
|
664
|
+
from editable_artifact_materialization_jobs job
|
|
665
|
+
left join editable_artifact_materialization_results result
|
|
666
|
+
on result.account_id = job.account_id and result.workspace_id = job.workspace_id
|
|
667
|
+
and result.artifact_id = job.artifact_id and result.job_id = job.id
|
|
668
|
+
left join editable_artifact_blob_refs blob
|
|
669
|
+
on blob.account_id = result.account_id and blob.workspace_id = result.workspace_id
|
|
670
|
+
and blob.artifact_id = result.artifact_id and blob.id = result.blob_ref_id
|
|
671
|
+
where job.account_id = ${scope.accountId}::uuid
|
|
672
|
+
and job.workspace_id = ${scope.workspaceId}::uuid
|
|
673
|
+
and job.artifact_id = ${artifactId}
|
|
674
|
+
and job.id = ${jobId}`,
|
|
675
|
+
);
|
|
676
|
+
if (rows.length === 0) return null;
|
|
677
|
+
const row = exactRow(rows, "Editable artifact materialization read");
|
|
678
|
+
const job = jobFromRow(row);
|
|
679
|
+
const objectReference = nullableText(row.object_reference, "materialization object reference");
|
|
680
|
+
if ((job.result === null) !== (objectReference === null) && includeObjectReference) {
|
|
681
|
+
throw contractViolation("Materialization result object reference is inconsistent");
|
|
682
|
+
}
|
|
683
|
+
return Object.freeze({ job, objectReference });
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function versionFromRow(row: VersionRow): PersistedEditableArtifactPinnedVersion {
|
|
687
|
+
const rowModality = modality(row.modality);
|
|
688
|
+
const causalFrontier = causalFrontierFromRow(row.causal_frontier);
|
|
689
|
+
const nativeRevision = nullableInteger(row.native_revision, "version native revision", 0);
|
|
690
|
+
if (
|
|
691
|
+
(rowModality === "spreadsheet" && (causalFrontier === null || nativeRevision !== null)) ||
|
|
692
|
+
(rowModality !== "spreadsheet" && (causalFrontier !== null || nativeRevision === null))
|
|
693
|
+
) {
|
|
694
|
+
throw contractViolation("Version modality state is inconsistent");
|
|
695
|
+
}
|
|
696
|
+
if (row.pinned !== true) throw contractViolation("Public artifact version is not pinned");
|
|
697
|
+
return Object.freeze({
|
|
698
|
+
scope: Object.freeze({
|
|
699
|
+
accountId: uuid(row.account_id, "version account id"),
|
|
700
|
+
workspaceId: uuid(row.workspace_id, "version workspace id"),
|
|
701
|
+
}),
|
|
702
|
+
artifactId: stableId(row.artifact_id, "version artifact id"),
|
|
703
|
+
id: stableId(row.id, "version id"),
|
|
704
|
+
modality: rowModality,
|
|
705
|
+
snapshotId: stableId(row.snapshot_id, "version snapshot id"),
|
|
706
|
+
headSequence: integer(row.head_sequence, "version head sequence", 0),
|
|
707
|
+
causalFrontier,
|
|
708
|
+
nativeRevision,
|
|
709
|
+
stateHash: hash(row.state_hash, "version state hash"),
|
|
710
|
+
name: boundedText(row.name, "version name", 256),
|
|
711
|
+
pinned: true,
|
|
712
|
+
createdBySubjectId: boundedText(row.created_by_subject_id, "version creator", 1_024),
|
|
713
|
+
createdAt: timestamp(row.created_at, "version creation time"),
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function jobFromRow(row: JobRow): PersistedEditableArtifactMaterializationJob {
|
|
718
|
+
const state = jobState(row.state);
|
|
719
|
+
const format = exportFormat(row.format);
|
|
720
|
+
const resultId = nullableStableId(row.result_id, "materialization result id");
|
|
721
|
+
let result: PersistedEditableArtifactMaterializationResult | null = null;
|
|
722
|
+
if (resultId !== null) {
|
|
723
|
+
result = Object.freeze({
|
|
724
|
+
id: resultId,
|
|
725
|
+
byteSize: integer(row.result_byte_size, "materialization result byte size", 1),
|
|
726
|
+
contentHash: hash(row.result_content_hash, "materialization result hash"),
|
|
727
|
+
mimeType: mimeType(row.result_mime_type, format),
|
|
728
|
+
verifiedAt: timestamp(row.result_verified_at, "materialization verification time"),
|
|
729
|
+
createdAt: timestamp(row.result_created_at, "materialization result creation time"),
|
|
730
|
+
});
|
|
731
|
+
} else if (
|
|
732
|
+
row.result_byte_size !== null ||
|
|
733
|
+
row.result_content_hash !== null ||
|
|
734
|
+
row.result_mime_type !== null ||
|
|
735
|
+
row.result_verified_at !== null ||
|
|
736
|
+
row.result_created_at !== null
|
|
737
|
+
) {
|
|
738
|
+
throw contractViolation("Materialization result columns are partial");
|
|
739
|
+
}
|
|
740
|
+
if ((state === "succeeded") !== (result !== null)) {
|
|
741
|
+
throw contractViolation("Materialization state and result are inconsistent");
|
|
742
|
+
}
|
|
743
|
+
return Object.freeze({
|
|
744
|
+
scope: Object.freeze({
|
|
745
|
+
accountId: uuid(row.account_id, "job account id"),
|
|
746
|
+
workspaceId: uuid(row.workspace_id, "job workspace id"),
|
|
747
|
+
}),
|
|
748
|
+
artifactId: stableId(row.artifact_id, "job artifact id"),
|
|
749
|
+
id: stableId(row.id, "job id"),
|
|
750
|
+
versionId: stableId(row.version_id, "job version id"),
|
|
751
|
+
inputSnapshotId: stableId(row.input_snapshot_id, "job snapshot id"),
|
|
752
|
+
targetHeadSequence: integer(row.target_head_sequence, "job target head", 0),
|
|
753
|
+
stateHash: hash(row.state_hash, "job state hash"),
|
|
754
|
+
format,
|
|
755
|
+
state,
|
|
756
|
+
attemptCount: integer(row.attempt_count, "job attempt count", 0),
|
|
757
|
+
errorCode: nullableText(row.error_code, "job error code"),
|
|
758
|
+
createdAt: timestamp(row.created_at, "job creation time"),
|
|
759
|
+
startedAt: nullableTimestamp(row.started_at, "job start time"),
|
|
760
|
+
completedAt: nullableTimestamp(row.completed_at, "job completion time"),
|
|
761
|
+
result,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function assertSnapshotPinsArtifact(
|
|
766
|
+
snapshot: PersistedEditableArtifactExportSnapshot,
|
|
767
|
+
artifact: Readonly<{
|
|
768
|
+
modality: PersistedEditableArtifactExportModality;
|
|
769
|
+
headSequence: number;
|
|
770
|
+
stateHash: string;
|
|
771
|
+
currentSnapshotId: string;
|
|
772
|
+
}>,
|
|
773
|
+
): void {
|
|
774
|
+
if (
|
|
775
|
+
snapshot.modality !== artifact.modality ||
|
|
776
|
+
snapshot.snapshotId !== artifact.currentSnapshotId ||
|
|
777
|
+
snapshot.coveredHeadSequence !== artifact.headSequence ||
|
|
778
|
+
snapshot.stateHash !== artifact.stateHash ||
|
|
779
|
+
(snapshot.modality === "spreadsheet" &&
|
|
780
|
+
(snapshot.coveredCausalFrontier === null || snapshot.nativeRevision !== null)) ||
|
|
781
|
+
(snapshot.modality !== "spreadsheet" &&
|
|
782
|
+
(snapshot.coveredCausalFrontier !== null || snapshot.nativeRevision === null))
|
|
783
|
+
) {
|
|
784
|
+
throw new EditableArtifactDurableExportPersistenceError(
|
|
785
|
+
"snapshot_conflict",
|
|
786
|
+
"Pinned version source is no longer the exact current verified snapshot",
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function validatePinRequest(
|
|
792
|
+
input: PinPersistedEditableArtifactVersionRequest,
|
|
793
|
+
): PinPersistedEditableArtifactVersionRequest {
|
|
794
|
+
const context = validateContext(input);
|
|
795
|
+
const snapshot = validateSnapshot(input.snapshot);
|
|
796
|
+
const request = Object.freeze({
|
|
797
|
+
...input,
|
|
798
|
+
...context,
|
|
799
|
+
expectedAuthorizationRevision: integer(
|
|
800
|
+
input.expectedAuthorizationRevision,
|
|
801
|
+
"expected authorization revision",
|
|
802
|
+
1,
|
|
803
|
+
),
|
|
804
|
+
authorityKey: boundedText(input.authorityKey, "authority key", 8_192),
|
|
805
|
+
receiptId: stableId(input.receiptId, "receipt id"),
|
|
806
|
+
versionId: stableId(input.versionId, "version id"),
|
|
807
|
+
idempotencyKey: idempotencyKey(input.idempotencyKey),
|
|
808
|
+
requestHash: hash(input.requestHash, "request hash"),
|
|
809
|
+
name: boundedText(input.name, "version name", 256),
|
|
810
|
+
snapshot,
|
|
811
|
+
});
|
|
812
|
+
validateAuthorityKey(request.actor, request.authorityKey);
|
|
813
|
+
return request;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function validateEnqueueRequest(
|
|
817
|
+
input: EnqueuePersistedEditableArtifactMaterializationRequest,
|
|
818
|
+
): EnqueuePersistedEditableArtifactMaterializationRequest {
|
|
819
|
+
const context = validateContext(input);
|
|
820
|
+
const request = Object.freeze({
|
|
821
|
+
...input,
|
|
822
|
+
...context,
|
|
823
|
+
expectedAuthorizationRevision: integer(
|
|
824
|
+
input.expectedAuthorizationRevision,
|
|
825
|
+
"expected authorization revision",
|
|
826
|
+
1,
|
|
827
|
+
),
|
|
828
|
+
authorityKey: boundedText(input.authorityKey, "authority key", 8_192),
|
|
829
|
+
receiptId: stableId(input.receiptId, "receipt id"),
|
|
830
|
+
jobId: stableId(input.jobId, "job id"),
|
|
831
|
+
idempotencyKey: idempotencyKey(input.idempotencyKey),
|
|
832
|
+
requestHash: hash(input.requestHash, "request hash"),
|
|
833
|
+
versionId: stableId(input.versionId, "version id"),
|
|
834
|
+
profile: validateProfile(input.profile),
|
|
835
|
+
});
|
|
836
|
+
validateAuthorityKey(request.actor, request.authorityKey);
|
|
837
|
+
return request;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function validateReadRequest<T extends "versionId" | "jobId">(
|
|
841
|
+
input: ExportContext & { expectedAuthorizationRevision: number } & Record<T, string>,
|
|
842
|
+
key: T,
|
|
843
|
+
): ExportContext & { expectedAuthorizationRevision: number } & Record<T, string> {
|
|
844
|
+
return Object.freeze({
|
|
845
|
+
...input,
|
|
846
|
+
...validateContext(input),
|
|
847
|
+
expectedAuthorizationRevision: integer(
|
|
848
|
+
input.expectedAuthorizationRevision,
|
|
849
|
+
"expected authorization revision",
|
|
850
|
+
1,
|
|
851
|
+
),
|
|
852
|
+
[key]: stableId(input[key], key === "versionId" ? "version id" : "job id"),
|
|
853
|
+
}) as ExportContext & { expectedAuthorizationRevision: number } & Record<T, string>;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function validateContext(input: ExportContext): ExportContext {
|
|
857
|
+
const scope = Object.freeze({
|
|
858
|
+
accountId: uuid(input.scope.accountId, "account id"),
|
|
859
|
+
workspaceId: uuid(input.scope.workspaceId, "workspace id"),
|
|
860
|
+
});
|
|
861
|
+
const artifactId = stableId(input.artifactId, "artifact id");
|
|
862
|
+
const actor = validateActor(input.actor);
|
|
863
|
+
return Object.freeze({ scope, artifactId, actor });
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function validateActor(
|
|
867
|
+
actor: PersistedEditableArtifactExportActor,
|
|
868
|
+
): PersistedEditableArtifactExportActor {
|
|
869
|
+
if (!plainRecord(actor)) throw new TypeError("Editable artifact export actor is invalid");
|
|
870
|
+
const replicaId = text(actor.replicaId, "actor replica id");
|
|
871
|
+
if (!/^[0-9a-f]{16}$/u.test(replicaId) || /^0+$/u.test(replicaId)) {
|
|
872
|
+
throw new TypeError("Editable artifact export actor replica id is invalid");
|
|
873
|
+
}
|
|
874
|
+
const subjectId = boundedText(actor.subjectId, "actor subject id", 1_024);
|
|
875
|
+
if (actor.kind === "human") {
|
|
876
|
+
exactKeys(actor, ["kind", "subjectId", "replicaId"]);
|
|
877
|
+
return Object.freeze({ kind: "human", subjectId, replicaId });
|
|
878
|
+
}
|
|
879
|
+
if (actor.kind === "agent") {
|
|
880
|
+
exactKeys(actor, [
|
|
881
|
+
"kind",
|
|
882
|
+
"subjectId",
|
|
883
|
+
"replicaId",
|
|
884
|
+
"sessionId",
|
|
885
|
+
"turnId",
|
|
886
|
+
"attemptId",
|
|
887
|
+
"generation",
|
|
888
|
+
]);
|
|
889
|
+
return Object.freeze({
|
|
890
|
+
kind: "agent",
|
|
891
|
+
subjectId,
|
|
892
|
+
replicaId,
|
|
893
|
+
sessionId: boundedText(actor.sessionId, "agent session id", 1_024),
|
|
894
|
+
turnId: boundedText(actor.turnId, "agent turn id", 1_024),
|
|
895
|
+
attemptId: boundedText(actor.attemptId, "agent attempt id", 1_024),
|
|
896
|
+
generation: integer(actor.generation, "agent generation", 0),
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
if (actor.kind === "service") {
|
|
900
|
+
exactKeys(actor, ["kind", "subjectId", "replicaId", "service"]);
|
|
901
|
+
return Object.freeze({
|
|
902
|
+
kind: "service",
|
|
903
|
+
subjectId,
|
|
904
|
+
replicaId,
|
|
905
|
+
service: boundedText(actor.service, "service name", 1_024),
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
throw new TypeError("Editable artifact export actor kind is invalid");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function validateSnapshot(
|
|
912
|
+
snapshot: PersistedEditableArtifactExportSnapshot,
|
|
913
|
+
): PersistedEditableArtifactExportSnapshot {
|
|
914
|
+
const snapshotModality = modality(snapshot.modality);
|
|
915
|
+
const coveredCausalFrontier = causalFrontierFromRow(snapshot.coveredCausalFrontier);
|
|
916
|
+
const nativeRevision = nullableInteger(snapshot.nativeRevision, "snapshot native revision", 0);
|
|
917
|
+
if (
|
|
918
|
+
(snapshotModality === "spreadsheet" &&
|
|
919
|
+
(coveredCausalFrontier === null || nativeRevision !== null)) ||
|
|
920
|
+
(snapshotModality !== "spreadsheet" &&
|
|
921
|
+
(coveredCausalFrontier !== null || nativeRevision === null))
|
|
922
|
+
) {
|
|
923
|
+
throw new TypeError("Editable artifact export snapshot modality state is invalid");
|
|
924
|
+
}
|
|
925
|
+
return Object.freeze({
|
|
926
|
+
modality: snapshotModality,
|
|
927
|
+
snapshotId: stableId(snapshot.snapshotId, "snapshot id"),
|
|
928
|
+
coveredHeadSequence: integer(snapshot.coveredHeadSequence, "snapshot head", 0),
|
|
929
|
+
stateHash: hash(snapshot.stateHash, "snapshot state hash"),
|
|
930
|
+
coveredCausalFrontier,
|
|
931
|
+
nativeRevision,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function validateProfile(
|
|
936
|
+
profile: PersistedEditableArtifactExportProfile,
|
|
937
|
+
): PersistedEditableArtifactExportProfile {
|
|
938
|
+
if (!plainRecord(profile)) throw new TypeError("Materialization profile is invalid");
|
|
939
|
+
const profileModality = modality(profile.modality);
|
|
940
|
+
const format = exportFormat(profile.format);
|
|
941
|
+
if (!formatMatchesModality(profileModality, format)) throw unsupportedFormat();
|
|
942
|
+
const normalizedOptions = boundedText(
|
|
943
|
+
profile.normalizedOptions,
|
|
944
|
+
"normalized materialization options",
|
|
945
|
+
256 * 1024,
|
|
946
|
+
);
|
|
947
|
+
let options: unknown;
|
|
948
|
+
try {
|
|
949
|
+
options = JSON.parse(normalizedOptions) as unknown;
|
|
950
|
+
} catch {
|
|
951
|
+
throw new TypeError("Normalized materialization options are invalid JSON");
|
|
952
|
+
}
|
|
953
|
+
if (!plainRecord(options) || JSON.stringify(sortRecord(options)) !== normalizedOptions) {
|
|
954
|
+
throw new TypeError("Normalized materialization options are not canonical");
|
|
955
|
+
}
|
|
956
|
+
return Object.freeze({
|
|
957
|
+
modality: profileModality,
|
|
958
|
+
format,
|
|
959
|
+
codecId: boundedText(profile.codecId, "codec id", 128),
|
|
960
|
+
codecVersion: boundedText(profile.codecVersion, "codec version", 128),
|
|
961
|
+
kernelVersion: boundedText(profile.kernelVersion, "kernel version", 512),
|
|
962
|
+
fontRegistryHash: hash(profile.fontRegistryHash, "font registry hash"),
|
|
963
|
+
policyHash: hash(profile.policyHash, "policy hash"),
|
|
964
|
+
normalizedOptions,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function formatMatchesModality(
|
|
969
|
+
artifactModality: PersistedEditableArtifactExportModality,
|
|
970
|
+
format: PersistedEditableArtifactExportFormat,
|
|
971
|
+
): boolean {
|
|
972
|
+
return (
|
|
973
|
+
(artifactModality === "spreadsheet" && ["xlsx", "pdf", "png", "webp"].includes(format)) ||
|
|
974
|
+
(artifactModality === "presentation" && ["pptx", "pdf", "png", "webp"].includes(format)) ||
|
|
975
|
+
(artifactModality === "document" && ["docx", "pdf", "png", "webp"].includes(format))
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function persistedActorKey(actor: PersistedEditableArtifactExportActor): string {
|
|
980
|
+
switch (actor.kind) {
|
|
981
|
+
case "human":
|
|
982
|
+
return JSON.stringify([actor.kind, actor.subjectId]);
|
|
983
|
+
case "agent":
|
|
984
|
+
return JSON.stringify([
|
|
985
|
+
actor.kind,
|
|
986
|
+
actor.subjectId,
|
|
987
|
+
actor.sessionId,
|
|
988
|
+
actor.turnId,
|
|
989
|
+
actor.attemptId,
|
|
990
|
+
actor.generation,
|
|
991
|
+
]);
|
|
992
|
+
case "service":
|
|
993
|
+
return JSON.stringify([actor.kind, actor.subjectId, actor.service]);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function validateAuthorityKey(
|
|
998
|
+
actor: PersistedEditableArtifactExportActor,
|
|
999
|
+
authorityKey: string,
|
|
1000
|
+
): void {
|
|
1001
|
+
if (authorityKey !== persistedActorKey(actor)) {
|
|
1002
|
+
throw new TypeError("Editable artifact export authority key does not match its actor");
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function exactRow<T>(rows: readonly T[], label: string): T {
|
|
1007
|
+
if (rows.length !== 1 || !rows[0])
|
|
1008
|
+
throw contractViolation(`${label} returned an invalid row count`);
|
|
1009
|
+
return rows[0];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function stableId(value: unknown, label: string): string {
|
|
1013
|
+
const parsed = text(value, label);
|
|
1014
|
+
if (!/^[0-9a-f]{32}$/u.test(parsed) || /^0+$/u.test(parsed)) {
|
|
1015
|
+
throw new TypeError(`${label} is invalid`);
|
|
1016
|
+
}
|
|
1017
|
+
return parsed;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function nullableStableId(value: unknown, label: string): string | null {
|
|
1021
|
+
return value === null ? null : stableId(value, label);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function uuid(value: unknown, label: string): string {
|
|
1025
|
+
const parsed = text(value, label);
|
|
1026
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(parsed)) {
|
|
1027
|
+
throw new TypeError(`${label} is invalid`);
|
|
1028
|
+
}
|
|
1029
|
+
return parsed;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function hash(value: unknown, label: string): string {
|
|
1033
|
+
const parsed = text(value, label);
|
|
1034
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(parsed)) throw new TypeError(`${label} is invalid`);
|
|
1035
|
+
return parsed;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function idempotencyKey(value: unknown): string {
|
|
1039
|
+
const parsed = text(value, "idempotency key");
|
|
1040
|
+
if (!/^[A-Za-z0-9._:-]{1,200}$/u.test(parsed)) throw new TypeError("Idempotency key is invalid");
|
|
1041
|
+
return parsed;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function text(value: unknown, label: string): string {
|
|
1045
|
+
if (typeof value !== "string") throw new TypeError(`${label} must be text`);
|
|
1046
|
+
return value;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function boundedText(value: unknown, label: string, maximumBytes: number): string {
|
|
1050
|
+
const parsed = text(value, label);
|
|
1051
|
+
const bytes = new TextEncoder().encode(parsed).byteLength;
|
|
1052
|
+
if (
|
|
1053
|
+
bytes < 1 ||
|
|
1054
|
+
bytes > maximumBytes ||
|
|
1055
|
+
parsed.trim() !== parsed ||
|
|
1056
|
+
/[\u0000-\u001f\u007f]/u.test(parsed)
|
|
1057
|
+
) {
|
|
1058
|
+
throw new TypeError(`${label} is invalid`);
|
|
1059
|
+
}
|
|
1060
|
+
return parsed;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function nullableText(value: unknown, label: string): string | null {
|
|
1064
|
+
return value === null ? null : boundedText(value, label, 2_048);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function integer(value: unknown, label: string, minimum: number): number {
|
|
1068
|
+
const parsed =
|
|
1069
|
+
typeof value === "bigint"
|
|
1070
|
+
? Number(value)
|
|
1071
|
+
: typeof value === "string" && /^\d+$/u.test(value)
|
|
1072
|
+
? Number(value)
|
|
1073
|
+
: value;
|
|
1074
|
+
if (!Number.isSafeInteger(parsed) || (parsed as number) < minimum) {
|
|
1075
|
+
throw new TypeError(`${label} is invalid`);
|
|
1076
|
+
}
|
|
1077
|
+
return parsed as number;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function nullableInteger(value: unknown, label: string, minimum: number): number | null {
|
|
1081
|
+
return value === null ? null : integer(value, label, minimum);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function timestamp(value: unknown, label: string): string {
|
|
1085
|
+
const parsed = value instanceof Date ? value.toISOString() : text(value, label);
|
|
1086
|
+
const milliseconds = Date.parse(parsed);
|
|
1087
|
+
if (!Number.isFinite(milliseconds)) {
|
|
1088
|
+
throw new TypeError(`${label} is invalid`);
|
|
1089
|
+
}
|
|
1090
|
+
return new Date(milliseconds).toISOString();
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function nullableTimestamp(value: unknown, label: string): string | null {
|
|
1094
|
+
return value === null ? null : timestamp(value, label);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function modality(value: unknown): PersistedEditableArtifactExportModality {
|
|
1098
|
+
if (value !== "spreadsheet" && value !== "document" && value !== "presentation") {
|
|
1099
|
+
throw new TypeError("Artifact modality is invalid");
|
|
1100
|
+
}
|
|
1101
|
+
return value;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function exportFormat(value: unknown): PersistedEditableArtifactExportFormat {
|
|
1105
|
+
if (!["xlsx", "pptx", "docx", "pdf", "png", "webp"].includes(String(value))) {
|
|
1106
|
+
throw new TypeError("Materialization format is invalid");
|
|
1107
|
+
}
|
|
1108
|
+
return value as PersistedEditableArtifactExportFormat;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function jobState(value: unknown): PersistedEditableArtifactMaterializationJob["state"] {
|
|
1112
|
+
if (!["pending", "running", "succeeded", "failed"].includes(String(value))) {
|
|
1113
|
+
throw contractViolation("Materialization state is invalid");
|
|
1114
|
+
}
|
|
1115
|
+
return value as PersistedEditableArtifactMaterializationJob["state"];
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function mimeType(value: unknown, format: PersistedEditableArtifactExportFormat): string {
|
|
1119
|
+
const expected = {
|
|
1120
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1121
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1122
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1123
|
+
pdf: "application/pdf",
|
|
1124
|
+
png: "image/png",
|
|
1125
|
+
webp: "image/webp",
|
|
1126
|
+
}[format];
|
|
1127
|
+
if (value !== expected) throw contractViolation("Materialization MIME type is inconsistent");
|
|
1128
|
+
return expected;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function causalFrontierFromRow(
|
|
1132
|
+
value: unknown,
|
|
1133
|
+
): readonly Readonly<{ replicaId: string; counter: number }>[] | null {
|
|
1134
|
+
if (value === null) return null;
|
|
1135
|
+
if (!Array.isArray(value) || value.length > 1_024) {
|
|
1136
|
+
throw new TypeError("Causal frontier is invalid");
|
|
1137
|
+
}
|
|
1138
|
+
let previous = "";
|
|
1139
|
+
const result = value.map((entry) => {
|
|
1140
|
+
if (!plainRecord(entry)) throw new TypeError("Causal frontier entry is invalid");
|
|
1141
|
+
const replicaId = text(entry.replicaId, "causal replica id");
|
|
1142
|
+
if (!/^[0-9a-f]{16}$/u.test(replicaId) || /^0+$/u.test(replicaId) || replicaId <= previous) {
|
|
1143
|
+
throw new TypeError("Causal frontier is not canonical");
|
|
1144
|
+
}
|
|
1145
|
+
previous = replicaId;
|
|
1146
|
+
return Object.freeze({
|
|
1147
|
+
replicaId,
|
|
1148
|
+
counter: integer(entry.counter, "causal counter", 1),
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
return Object.freeze(result);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function sha256(value: string): string {
|
|
1155
|
+
return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function sortRecord(value: Record<string, unknown>): Record<string, unknown> {
|
|
1159
|
+
return Object.fromEntries(
|
|
1160
|
+
Object.entries(value).sort(([left], [right]) => (left < right ? -1 : 1)),
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function plainRecord(value: unknown): value is Record<string, unknown> {
|
|
1165
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1166
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1167
|
+
return prototype === Object.prototype || prototype === null;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function exactKeys(value: Record<string, unknown>, keys: readonly string[]): void {
|
|
1171
|
+
const actual = Object.keys(value).sort();
|
|
1172
|
+
const expected = [...keys].sort();
|
|
1173
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
1174
|
+
throw new TypeError("Editable artifact export actor contains unknown properties");
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function notFound(): EditableArtifactDurableExportPersistenceError {
|
|
1179
|
+
return new EditableArtifactDurableExportPersistenceError(
|
|
1180
|
+
"not_found",
|
|
1181
|
+
"Editable artifact export source was not found",
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function idempotencyConflict(): EditableArtifactDurableExportPersistenceError {
|
|
1186
|
+
return new EditableArtifactDurableExportPersistenceError(
|
|
1187
|
+
"idempotency_conflict",
|
|
1188
|
+
"Editable artifact export idempotency key was reused with another request",
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function unsupportedFormat(): EditableArtifactDurableExportPersistenceError {
|
|
1193
|
+
return new EditableArtifactDurableExportPersistenceError(
|
|
1194
|
+
"unsupported_format",
|
|
1195
|
+
"Editable artifact export format is incompatible",
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function contractViolation(message: string): EditableArtifactDurableExportPersistenceError {
|
|
1200
|
+
return new EditableArtifactDurableExportPersistenceError("database_contract_violation", message);
|
|
1201
|
+
}
|