@opengeni/core 0.21.2 → 0.24.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/access/index.d.ts +9 -1
- package/dist/application/new-session-drafts.d.ts +1 -1
- package/dist/application/session-commands.d.ts +9 -4
- package/dist/billing/limits.d.ts +1 -1
- package/dist/chunk-6WKPWE5S.js +2380 -0
- package/dist/chunk-6WKPWE5S.js.map +1 -0
- package/dist/chunk-JJU6I5XD.js +2713 -0
- package/dist/chunk-JJU6I5XD.js.map +1 -0
- package/dist/chunk-NYPMQ7NO.js +2270 -0
- package/dist/chunk-NYPMQ7NO.js.map +1 -0
- package/dist/dependencies.d.ts +26 -6
- package/dist/domain/capabilities.d.ts +35 -3
- package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
- package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
- package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
- package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
- package/dist/domain/editable-artifacts/errors.d.ts +47 -0
- package/dist/domain/editable-artifacts/hash.d.ts +11 -0
- package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
- package/dist/domain/editable-artifacts/index.d.ts +14 -0
- package/dist/domain/editable-artifacts/ports.d.ts +341 -0
- package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
- package/dist/domain/editable-artifacts/service.d.ts +105 -0
- package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
- package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
- package/dist/domain/editable-artifacts/types.d.ts +410 -0
- package/dist/domain/memory-slack-delivery.d.ts +19 -0
- package/dist/domain/memory-slack-publication.d.ts +0 -2
- package/dist/domain/packs.d.ts +29 -1
- package/dist/domain/personal-connection-delegations.d.ts +9 -1
- package/dist/domain/scheduled-tasks.d.ts +27 -3
- package/dist/domain/session-tool-policy.d.ts +2 -2
- package/dist/domain/sessions.d.ts +44 -10
- package/dist/domain/skill-imports.d.ts +32 -0
- package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
- package/dist/domain/timeline-annotations.d.ts +5 -0
- package/dist/domain/video-generation-capabilities.d.ts +9 -0
- package/dist/domain/video-generation.d.ts +47 -0
- package/dist/editable-artifact-live/application.d.ts +69 -0
- package/dist/editable-artifact-live/errors.d.ts +1 -0
- package/dist/editable-artifact-live/index.d.ts +8 -0
- package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
- package/dist/editable-artifact-live/ports.d.ts +100 -0
- package/dist/editable-artifact-live/server.d.ts +71 -0
- package/dist/editable-artifact-live/ticket.d.ts +40 -0
- package/dist/editable-artifact-live/types.d.ts +213 -0
- package/dist/editable-artifact-live/wire.d.ts +34 -0
- package/dist/editable-artifact-live.d.ts +2 -0
- package/dist/editable-artifact-live.js +58 -0
- package/dist/editable-artifact-live.js.map +1 -0
- package/dist/editable-artifacts.d.ts +2 -0
- package/dist/editable-artifacts.js +179 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/index.d.ts +35 -25
- package/dist/index.js +3184 -652
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +53 -0
- package/dist/rigs/index.d.ts +1 -1
- package/dist/rigs/provider-images.d.ts +31 -0
- package/dist/sandbox/fleet.d.ts +9 -1
- package/dist/sandbox/routing.d.ts +1 -0
- package/dist/session-authorization.d.ts +10 -1
- package/dist/workflow-wake-contract.d.ts +7 -0
- package/package.json +18 -10
- package/src/access/index.ts +43 -3
- package/src/application/new-session-drafts.ts +2 -0
- package/src/application/session-commands.ts +217 -116
- package/src/dependencies.ts +30 -2
- package/src/domain/capabilities.ts +672 -218
- package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
- package/src/domain/durable-learning-slack-publication.ts +202 -0
- package/src/domain/editable-artifacts/agent-application.ts +687 -0
- package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
- package/src/domain/editable-artifacts/durable-export.ts +734 -0
- package/src/domain/editable-artifacts/errors.ts +115 -0
- package/src/domain/editable-artifacts/hash.ts +131 -0
- package/src/domain/editable-artifacts/in-memory.ts +1662 -0
- package/src/domain/editable-artifacts/index.ts +14 -0
- package/src/domain/editable-artifacts/ports.ts +462 -0
- package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
- package/src/domain/editable-artifacts/service.ts +2402 -0
- package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
- package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
- package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
- package/src/domain/editable-artifacts/types.ts +835 -0
- package/src/domain/insights.ts +136 -33
- package/src/domain/memory-slack-delivery.ts +204 -0
- package/src/domain/memory-slack-publication.ts +10 -19
- package/src/domain/packs.ts +266 -5
- package/src/domain/personal-connection-delegations.ts +97 -6
- package/src/domain/resources.ts +10 -20
- package/src/domain/scheduled-tasks.ts +365 -25
- package/src/domain/session-tool-policy.ts +8 -2
- package/src/domain/sessions.ts +388 -155
- package/src/domain/skill-imports.ts +350 -0
- package/src/domain/slack-publication-secret-safety.ts +42 -0
- package/src/domain/timeline-annotations.ts +204 -0
- package/src/domain/video-generation-capabilities.ts +69 -0
- package/src/domain/video-generation.ts +249 -0
- package/src/editable-artifact-live/application.ts +182 -0
- package/src/editable-artifact-live/errors.ts +4 -0
- package/src/editable-artifact-live/index.ts +8 -0
- package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
- package/src/editable-artifact-live/ports.ts +139 -0
- package/src/editable-artifact-live/server.ts +1707 -0
- package/src/editable-artifact-live/ticket.ts +217 -0
- package/src/editable-artifact-live/types.ts +286 -0
- package/src/editable-artifact-live/wire.ts +95 -0
- package/src/editable-artifact-live.ts +2 -0
- package/src/editable-artifacts.ts +2 -0
- package/src/index.ts +14 -0
- package/src/managed-auth-type.ts +4 -3
- package/src/managed-session.ts +36 -0
- package/src/rigs/index.ts +26 -19
- package/src/rigs/provider-images.ts +110 -0
- package/src/sandbox/fleet.ts +56 -1
- package/src/sandbox/routing.ts +32 -3
- package/src/session-authorization.ts +20 -0
- package/src/workflow-wake-contract.ts +8 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
BoundedObjectReadError,
|
|
4
|
+
MAX_BOUNDED_OBJECT_CHUNK_BYTES,
|
|
5
|
+
type BoundedObjectRead,
|
|
6
|
+
type BoundedObjectReadPort,
|
|
7
|
+
} from "@opengeni/storage";
|
|
8
|
+
import type { EditableArtifactSnapshotVerifierPort } from "./ports";
|
|
9
|
+
import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
|
|
10
|
+
import { EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES } from "@opengeni/contracts/editable-artifacts";
|
|
11
|
+
import {
|
|
12
|
+
causalFrontiersEqual,
|
|
13
|
+
editableArtifactCausalFrontier,
|
|
14
|
+
editableArtifactContentHash,
|
|
15
|
+
editableArtifactId,
|
|
16
|
+
editableArtifactStateHash,
|
|
17
|
+
type EditableArtifactCausalFrontier,
|
|
18
|
+
type EditableArtifactContentHash,
|
|
19
|
+
type EditableArtifactId,
|
|
20
|
+
type EditableArtifactScope,
|
|
21
|
+
type EditableArtifactModality,
|
|
22
|
+
type EditableArtifactStateHash,
|
|
23
|
+
type PublishEditableArtifactSnapshotRequest,
|
|
24
|
+
} from "./types";
|
|
25
|
+
|
|
26
|
+
export const MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES = EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES;
|
|
27
|
+
export const MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_BYTES = 256 * 1024 * 1024;
|
|
28
|
+
export const MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_SEGMENTS = 100_000;
|
|
29
|
+
export const MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_SEGMENT_BYTES = 8 * 1024 * 1024;
|
|
30
|
+
|
|
31
|
+
export type EditableArtifactSnapshotVerificationErrorCode =
|
|
32
|
+
| "cancelled"
|
|
33
|
+
| "canonical_mismatch"
|
|
34
|
+
| "content_hash_mismatch"
|
|
35
|
+
| "content_type_mismatch"
|
|
36
|
+
| "coverage_mismatch"
|
|
37
|
+
| "frontier_mismatch"
|
|
38
|
+
| "identity_mismatch"
|
|
39
|
+
| "kernel_rejected"
|
|
40
|
+
| "limit_exceeded"
|
|
41
|
+
| "object_changed"
|
|
42
|
+
| "object_missing"
|
|
43
|
+
| "replay_invalid"
|
|
44
|
+
| "state_hash_mismatch"
|
|
45
|
+
| "storage_failure"
|
|
46
|
+
| "truncated"
|
|
47
|
+
| "version_mismatch";
|
|
48
|
+
|
|
49
|
+
/** Intentionally contains no provider diagnostic, reference, key, or URL. */
|
|
50
|
+
export class EditableArtifactSnapshotVerificationError extends Error {
|
|
51
|
+
readonly code: EditableArtifactSnapshotVerificationErrorCode;
|
|
52
|
+
|
|
53
|
+
constructor(code: EditableArtifactSnapshotVerificationErrorCode) {
|
|
54
|
+
super(snapshotErrorMessage(code));
|
|
55
|
+
this.name = "EditableArtifactSnapshotVerificationError";
|
|
56
|
+
this.code = code;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type EditableArtifactSnapshotReplaySegmentCommon = Readonly<{
|
|
61
|
+
sequenceStart: number;
|
|
62
|
+
sequenceEnd: number;
|
|
63
|
+
contentHash: EditableArtifactContentHash;
|
|
64
|
+
bytes: Uint8Array;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type EditableArtifactSnapshotReplaySegment =
|
|
68
|
+
| (EditableArtifactSnapshotReplaySegmentCommon &
|
|
69
|
+
Readonly<{ modality: "spreadsheet"; operationProtocolVersion: number }>)
|
|
70
|
+
| (EditableArtifactSnapshotReplaySegmentCommon &
|
|
71
|
+
Readonly<{ modality: "document" | "presentation" }>);
|
|
72
|
+
|
|
73
|
+
type EditableArtifactSnapshotReplayPlanCommon = Readonly<{
|
|
74
|
+
modality: EditableArtifactModality;
|
|
75
|
+
/** Must equal the verified snapshot's coverage boundary. */
|
|
76
|
+
baseHeadSequence: number;
|
|
77
|
+
targetHeadSequence: number;
|
|
78
|
+
targetStateHash: EditableArtifactStateHash;
|
|
79
|
+
segments: AsyncIterable<EditableArtifactSnapshotReplaySegment>;
|
|
80
|
+
}>;
|
|
81
|
+
|
|
82
|
+
export type EditableArtifactSnapshotReplayPlan =
|
|
83
|
+
| (EditableArtifactSnapshotReplayPlanCommon &
|
|
84
|
+
Readonly<{
|
|
85
|
+
modality: "spreadsheet";
|
|
86
|
+
targetCausalFrontier: EditableArtifactCausalFrontier;
|
|
87
|
+
}>)
|
|
88
|
+
| (EditableArtifactSnapshotReplayPlanCommon &
|
|
89
|
+
Readonly<{
|
|
90
|
+
modality: "document" | "presentation";
|
|
91
|
+
targetNativeRevision: number;
|
|
92
|
+
}>);
|
|
93
|
+
|
|
94
|
+
/** Optional authoritative tail used to prove reconstruction to a later head. */
|
|
95
|
+
export interface EditableArtifactSnapshotReplayPlanPort {
|
|
96
|
+
resolve(input: {
|
|
97
|
+
scope: EditableArtifactScope;
|
|
98
|
+
artifactId: EditableArtifactId;
|
|
99
|
+
snapshot: PublishEditableArtifactSnapshotRequest;
|
|
100
|
+
signal?: AbortSignal;
|
|
101
|
+
}): Promise<EditableArtifactSnapshotReplayPlan | null>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type IsolatedEditableArtifactSnapshotKernelResultCommon = Readonly<{
|
|
105
|
+
modality: EditableArtifactModality;
|
|
106
|
+
/** True only when canonical re-encoding was byte-equivalent to the input. */
|
|
107
|
+
canonicalReencodeVerified: true;
|
|
108
|
+
scope: EditableArtifactScope;
|
|
109
|
+
artifactId: EditableArtifactId;
|
|
110
|
+
canonicalByteSize: number;
|
|
111
|
+
canonicalContentHash: EditableArtifactContentHash;
|
|
112
|
+
coveredHeadSequence: number;
|
|
113
|
+
stateHash: EditableArtifactStateHash;
|
|
114
|
+
modelSchemaVersion: number;
|
|
115
|
+
kernelVersion: string;
|
|
116
|
+
}>;
|
|
117
|
+
|
|
118
|
+
export type IsolatedEditableArtifactSnapshotKernelResult =
|
|
119
|
+
| (IsolatedEditableArtifactSnapshotKernelResultCommon &
|
|
120
|
+
Readonly<{
|
|
121
|
+
modality: "spreadsheet";
|
|
122
|
+
/** True only after decoding dots, vectors, tombstones, and undo metadata. */
|
|
123
|
+
fullCrdtStateVerified: true;
|
|
124
|
+
coveredCausalFrontier: EditableArtifactCausalFrontier;
|
|
125
|
+
operationProtocolVersion: number;
|
|
126
|
+
crdtStateVersion: number;
|
|
127
|
+
replayedTarget?: Readonly<{
|
|
128
|
+
headSequence: number;
|
|
129
|
+
causalFrontier: EditableArtifactCausalFrontier;
|
|
130
|
+
stateHash: EditableArtifactStateHash;
|
|
131
|
+
}>;
|
|
132
|
+
}>)
|
|
133
|
+
| (IsolatedEditableArtifactSnapshotKernelResultCommon &
|
|
134
|
+
Readonly<{
|
|
135
|
+
modality: "document" | "presentation";
|
|
136
|
+
nativeRevision: number;
|
|
137
|
+
replayedTarget?: Readonly<{
|
|
138
|
+
headSequence: number;
|
|
139
|
+
nativeRevision: number;
|
|
140
|
+
stateHash: EditableArtifactStateHash;
|
|
141
|
+
}>;
|
|
142
|
+
}>);
|
|
143
|
+
|
|
144
|
+
/** Adapter to the exact no-network native kernel; JavaScript models are never authority. */
|
|
145
|
+
export interface IsolatedEditableArtifactSnapshotKernelPort {
|
|
146
|
+
verifyAndReconstruct(input: {
|
|
147
|
+
scope: EditableArtifactScope;
|
|
148
|
+
artifactId: EditableArtifactId;
|
|
149
|
+
modality: EditableArtifactModality;
|
|
150
|
+
/** Durable coverage/version metadata stays distinct from native revision. */
|
|
151
|
+
expectedSnapshot: PublishEditableArtifactSnapshotRequest;
|
|
152
|
+
snapshotChunks: AsyncIterable<Uint8Array>;
|
|
153
|
+
expectedSnapshotByteSize: number;
|
|
154
|
+
replayPlan?: EditableArtifactSnapshotReplayPlan;
|
|
155
|
+
signal?: AbortSignal;
|
|
156
|
+
}): Promise<IsolatedEditableArtifactSnapshotKernelResult>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type ProductionEditableArtifactSnapshotVerifierDependencies = Readonly<{
|
|
160
|
+
objects: BoundedObjectReadPort;
|
|
161
|
+
kernel: IsolatedEditableArtifactSnapshotKernelPort;
|
|
162
|
+
replayPlans?: EditableArtifactSnapshotReplayPlanPort;
|
|
163
|
+
maxSnapshotBytes?: number;
|
|
164
|
+
maxReplayBytes?: number;
|
|
165
|
+
maxReplaySegments?: number;
|
|
166
|
+
snapshotChunkBytes?: number;
|
|
167
|
+
}>;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Production verifier for the existing domain port. Publication remains a
|
|
171
|
+
* separate, short forward-only database CAS after this method succeeds.
|
|
172
|
+
*/
|
|
173
|
+
export class ProductionEditableArtifactSnapshotVerifier implements EditableArtifactSnapshotVerifierPort {
|
|
174
|
+
readonly #dependencies: Readonly<{
|
|
175
|
+
objects: BoundedObjectReadPort;
|
|
176
|
+
kernel: IsolatedEditableArtifactSnapshotKernelPort;
|
|
177
|
+
replayPlans: EditableArtifactSnapshotReplayPlanPort | undefined;
|
|
178
|
+
maxSnapshotBytes: number;
|
|
179
|
+
maxReplayBytes: number;
|
|
180
|
+
maxReplaySegments: number;
|
|
181
|
+
snapshotChunkBytes: number;
|
|
182
|
+
}>;
|
|
183
|
+
|
|
184
|
+
constructor(dependencies: ProductionEditableArtifactSnapshotVerifierDependencies) {
|
|
185
|
+
const maxSnapshotBytes = dependencies.maxSnapshotBytes ?? MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES;
|
|
186
|
+
const maxReplayBytes = dependencies.maxReplayBytes ?? MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_BYTES;
|
|
187
|
+
const maxReplaySegments =
|
|
188
|
+
dependencies.maxReplaySegments ?? MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_SEGMENTS;
|
|
189
|
+
const snapshotChunkBytes = dependencies.snapshotChunkBytes ?? 1024 * 1024;
|
|
190
|
+
for (const value of [maxSnapshotBytes, maxReplayBytes, maxReplaySegments, snapshotChunkBytes]) {
|
|
191
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
192
|
+
throw new TypeError("Snapshot verifier limits must be positive safe integers");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (
|
|
196
|
+
maxSnapshotBytes > MAX_EDITABLE_ARTIFACT_SNAPSHOT_BYTES ||
|
|
197
|
+
maxReplayBytes > MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_BYTES ||
|
|
198
|
+
maxReplaySegments > MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_SEGMENTS ||
|
|
199
|
+
snapshotChunkBytes > MAX_BOUNDED_OBJECT_CHUNK_BYTES
|
|
200
|
+
) {
|
|
201
|
+
throw new TypeError("Snapshot verifier limits exceed absolute safety bounds");
|
|
202
|
+
}
|
|
203
|
+
this.#dependencies = Object.freeze({
|
|
204
|
+
objects: dependencies.objects,
|
|
205
|
+
kernel: dependencies.kernel,
|
|
206
|
+
replayPlans: dependencies.replayPlans,
|
|
207
|
+
maxSnapshotBytes,
|
|
208
|
+
maxReplayBytes,
|
|
209
|
+
maxReplaySegments,
|
|
210
|
+
snapshotChunkBytes,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async verify(
|
|
215
|
+
input: Parameters<EditableArtifactSnapshotVerifierPort["verify"]>[0] & {
|
|
216
|
+
signal?: AbortSignal;
|
|
217
|
+
},
|
|
218
|
+
): Promise<void> {
|
|
219
|
+
throwIfCancelled(input.signal);
|
|
220
|
+
const scope = validateScope(input.scope);
|
|
221
|
+
const artifactId = editableArtifactId(input.artifactId);
|
|
222
|
+
const snapshot = input.snapshot;
|
|
223
|
+
if (snapshot.byteSize > this.#dependencies.maxSnapshotBytes) {
|
|
224
|
+
throw new EditableArtifactSnapshotVerificationError("limit_exceeded");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let object: BoundedObjectRead | undefined;
|
|
228
|
+
try {
|
|
229
|
+
object = await this.#dependencies.objects.open({
|
|
230
|
+
opaqueReference: snapshot.blobReference,
|
|
231
|
+
maxBytes: this.#dependencies.maxSnapshotBytes,
|
|
232
|
+
expectedByteSize: snapshot.byteSize,
|
|
233
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
234
|
+
});
|
|
235
|
+
if (object.contentType !== undefined && object.contentType !== snapshot.mimeType) {
|
|
236
|
+
throw new EditableArtifactSnapshotVerificationError("content_type_mismatch");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sourceDigest = createHash("sha256");
|
|
240
|
+
let sourceBytes = 0;
|
|
241
|
+
let sourceFinished = false;
|
|
242
|
+
const sourceChunks = meterSnapshotChunks(
|
|
243
|
+
object.chunks({
|
|
244
|
+
chunkBytes: this.#dependencies.snapshotChunkBytes,
|
|
245
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
246
|
+
}),
|
|
247
|
+
snapshot.byteSize,
|
|
248
|
+
(chunk) => {
|
|
249
|
+
sourceBytes += chunk.byteLength;
|
|
250
|
+
sourceDigest.update(chunk);
|
|
251
|
+
},
|
|
252
|
+
() => {
|
|
253
|
+
sourceFinished = true;
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const replayPlan = this.#dependencies.replayPlans
|
|
258
|
+
? await this.#dependencies.replayPlans.resolve({
|
|
259
|
+
scope,
|
|
260
|
+
artifactId,
|
|
261
|
+
snapshot,
|
|
262
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
263
|
+
})
|
|
264
|
+
: null;
|
|
265
|
+
const normalizedReplay = replayPlan
|
|
266
|
+
? validateReplayPlan(
|
|
267
|
+
replayPlan,
|
|
268
|
+
snapshot,
|
|
269
|
+
this.#dependencies.maxReplayBytes,
|
|
270
|
+
this.#dependencies.maxReplaySegments,
|
|
271
|
+
input.signal,
|
|
272
|
+
)
|
|
273
|
+
: undefined;
|
|
274
|
+
|
|
275
|
+
const result = await this.#dependencies.kernel.verifyAndReconstruct({
|
|
276
|
+
scope,
|
|
277
|
+
artifactId,
|
|
278
|
+
modality: snapshot.modality,
|
|
279
|
+
expectedSnapshot: snapshot,
|
|
280
|
+
snapshotChunks: sourceChunks,
|
|
281
|
+
expectedSnapshotByteSize: snapshot.byteSize,
|
|
282
|
+
...(normalizedReplay ? { replayPlan: normalizedReplay.replayPlan } : {}),
|
|
283
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
284
|
+
});
|
|
285
|
+
throwIfCancelled(input.signal);
|
|
286
|
+
if (!sourceFinished || sourceBytes !== snapshot.byteSize) {
|
|
287
|
+
throw new EditableArtifactSnapshotVerificationError("truncated");
|
|
288
|
+
}
|
|
289
|
+
const sourceHash = editableArtifactContentHash(`sha256:${sourceDigest.digest("hex")}`);
|
|
290
|
+
if (sourceHash !== snapshot.contentHash) {
|
|
291
|
+
throw new EditableArtifactSnapshotVerificationError("content_hash_mismatch");
|
|
292
|
+
}
|
|
293
|
+
normalizedReplay?.assertConsumed();
|
|
294
|
+
validateKernelResult(result, scope, artifactId, snapshot, normalizedReplay?.replayPlan);
|
|
295
|
+
|
|
296
|
+
// The provider version is pinned during every range read and checked
|
|
297
|
+
// once more after native reconstruction. The underlying opaque reference
|
|
298
|
+
// is required to be immutable beyond this point.
|
|
299
|
+
await object.assertUnchanged(input.signal);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
throw mapVerificationFailure(error, input.signal);
|
|
302
|
+
} finally {
|
|
303
|
+
await object?.close();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function meterSnapshotChunks(
|
|
309
|
+
chunks: AsyncIterable<Uint8Array>,
|
|
310
|
+
expectedBytes: number,
|
|
311
|
+
onChunk: (chunk: Uint8Array) => void,
|
|
312
|
+
onFinished: () => void,
|
|
313
|
+
): AsyncIterable<Uint8Array> {
|
|
314
|
+
return {
|
|
315
|
+
async *[Symbol.asyncIterator](): AsyncIterator<Uint8Array> {
|
|
316
|
+
let bytes = 0;
|
|
317
|
+
for await (const chunk of chunks) {
|
|
318
|
+
if (!(chunk instanceof Uint8Array) || chunk.byteLength === 0) {
|
|
319
|
+
throw new EditableArtifactSnapshotVerificationError("truncated");
|
|
320
|
+
}
|
|
321
|
+
bytes += chunk.byteLength;
|
|
322
|
+
if (bytes > expectedBytes) {
|
|
323
|
+
throw new EditableArtifactSnapshotVerificationError("limit_exceeded");
|
|
324
|
+
}
|
|
325
|
+
onChunk(chunk);
|
|
326
|
+
yield chunk;
|
|
327
|
+
}
|
|
328
|
+
if (bytes !== expectedBytes) {
|
|
329
|
+
throw new EditableArtifactSnapshotVerificationError("truncated");
|
|
330
|
+
}
|
|
331
|
+
onFinished();
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function validateReplayPlan(
|
|
337
|
+
plan: EditableArtifactSnapshotReplayPlan,
|
|
338
|
+
snapshot: PublishEditableArtifactSnapshotRequest,
|
|
339
|
+
maxBytes: number,
|
|
340
|
+
maxSegments: number,
|
|
341
|
+
signal: AbortSignal | undefined,
|
|
342
|
+
): Readonly<{
|
|
343
|
+
replayPlan: NonNullable<
|
|
344
|
+
Parameters<IsolatedEditableArtifactSnapshotKernelPort["verifyAndReconstruct"]>[0]["replayPlan"]
|
|
345
|
+
>;
|
|
346
|
+
assertConsumed(): void;
|
|
347
|
+
}> {
|
|
348
|
+
if (
|
|
349
|
+
plan.modality !== snapshot.modality ||
|
|
350
|
+
!Number.isSafeInteger(plan.baseHeadSequence) ||
|
|
351
|
+
!Number.isSafeInteger(plan.targetHeadSequence) ||
|
|
352
|
+
plan.baseHeadSequence !== snapshot.coveredHeadSequence ||
|
|
353
|
+
plan.targetHeadSequence < plan.baseHeadSequence
|
|
354
|
+
) {
|
|
355
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
356
|
+
}
|
|
357
|
+
const targetStateHash = editableArtifactStateHash(plan.targetStateHash);
|
|
358
|
+
if (
|
|
359
|
+
plan.modality !== "spreadsheet" &&
|
|
360
|
+
(!Number.isSafeInteger(plan.targetNativeRevision) || plan.targetNativeRevision < 0)
|
|
361
|
+
) {
|
|
362
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
363
|
+
}
|
|
364
|
+
let consumed = false;
|
|
365
|
+
const segments = meterReplaySegments(
|
|
366
|
+
plan.segments,
|
|
367
|
+
snapshot,
|
|
368
|
+
plan,
|
|
369
|
+
maxBytes,
|
|
370
|
+
maxSegments,
|
|
371
|
+
signal,
|
|
372
|
+
() => {
|
|
373
|
+
consumed = true;
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
const replayPlan: EditableArtifactSnapshotReplayPlan =
|
|
377
|
+
plan.modality === "spreadsheet"
|
|
378
|
+
? Object.freeze({
|
|
379
|
+
modality: "spreadsheet",
|
|
380
|
+
baseHeadSequence: plan.baseHeadSequence,
|
|
381
|
+
targetHeadSequence: plan.targetHeadSequence,
|
|
382
|
+
targetCausalFrontier: editableArtifactCausalFrontier(plan.targetCausalFrontier),
|
|
383
|
+
targetStateHash,
|
|
384
|
+
segments,
|
|
385
|
+
})
|
|
386
|
+
: Object.freeze({
|
|
387
|
+
modality: plan.modality,
|
|
388
|
+
baseHeadSequence: plan.baseHeadSequence,
|
|
389
|
+
targetHeadSequence: plan.targetHeadSequence,
|
|
390
|
+
targetNativeRevision: plan.targetNativeRevision,
|
|
391
|
+
targetStateHash,
|
|
392
|
+
segments,
|
|
393
|
+
});
|
|
394
|
+
return Object.freeze({
|
|
395
|
+
replayPlan,
|
|
396
|
+
assertConsumed() {
|
|
397
|
+
if (!consumed) {
|
|
398
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function meterReplaySegments(
|
|
405
|
+
segments: AsyncIterable<EditableArtifactSnapshotReplaySegment>,
|
|
406
|
+
snapshot: PublishEditableArtifactSnapshotRequest,
|
|
407
|
+
plan: EditableArtifactSnapshotReplayPlan,
|
|
408
|
+
maxBytes: number,
|
|
409
|
+
maxSegments: number,
|
|
410
|
+
signal: AbortSignal | undefined,
|
|
411
|
+
onFinished: () => void,
|
|
412
|
+
): AsyncIterable<EditableArtifactSnapshotReplaySegment> {
|
|
413
|
+
return {
|
|
414
|
+
async *[Symbol.asyncIterator](): AsyncIterator<EditableArtifactSnapshotReplaySegment> {
|
|
415
|
+
let expectedSequence = plan.baseHeadSequence + 1;
|
|
416
|
+
let nativeRevision = snapshot.modality === "spreadsheet" ? null : snapshot.nativeRevision;
|
|
417
|
+
let priorStateHash = snapshot.stateHash;
|
|
418
|
+
let totalBytes = 0;
|
|
419
|
+
let count = 0;
|
|
420
|
+
for await (const segment of segments) {
|
|
421
|
+
throwIfCancelled(signal);
|
|
422
|
+
count += 1;
|
|
423
|
+
if (count > maxSegments) {
|
|
424
|
+
throw new EditableArtifactSnapshotVerificationError("limit_exceeded");
|
|
425
|
+
}
|
|
426
|
+
if (
|
|
427
|
+
!Number.isSafeInteger(segment.sequenceStart) ||
|
|
428
|
+
!Number.isSafeInteger(segment.sequenceEnd) ||
|
|
429
|
+
segment.sequenceStart !== expectedSequence ||
|
|
430
|
+
segment.sequenceEnd < segment.sequenceStart ||
|
|
431
|
+
segment.sequenceEnd > plan.targetHeadSequence ||
|
|
432
|
+
segment.modality !== snapshot.modality ||
|
|
433
|
+
!(segment.bytes instanceof Uint8Array) ||
|
|
434
|
+
segment.bytes.byteLength < 1 ||
|
|
435
|
+
segment.bytes.byteLength > MAX_EDITABLE_ARTIFACT_SNAPSHOT_TAIL_SEGMENT_BYTES
|
|
436
|
+
) {
|
|
437
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
438
|
+
}
|
|
439
|
+
totalBytes += segment.bytes.byteLength;
|
|
440
|
+
if (totalBytes > maxBytes) {
|
|
441
|
+
throw new EditableArtifactSnapshotVerificationError("limit_exceeded");
|
|
442
|
+
}
|
|
443
|
+
const hash = editableArtifactContentHash(
|
|
444
|
+
`sha256:${createHash("sha256").update(segment.bytes).digest("hex")}`,
|
|
445
|
+
);
|
|
446
|
+
if (hash !== segment.contentHash) {
|
|
447
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
448
|
+
}
|
|
449
|
+
if (snapshot.modality === "spreadsheet") {
|
|
450
|
+
if (
|
|
451
|
+
segment.modality !== "spreadsheet" ||
|
|
452
|
+
segment.operationProtocolVersion !== snapshot.operationProtocolVersion
|
|
453
|
+
) {
|
|
454
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
if (
|
|
458
|
+
segment.modality !== snapshot.modality ||
|
|
459
|
+
segment.sequenceEnd !== segment.sequenceStart
|
|
460
|
+
) {
|
|
461
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
const summary = decodeEditableArtifactSerializedCommit(
|
|
465
|
+
segment.bytes,
|
|
466
|
+
snapshot.modality,
|
|
467
|
+
);
|
|
468
|
+
if (
|
|
469
|
+
summary.parentHeadSequence !== segment.sequenceStart - 1 ||
|
|
470
|
+
summary.resultHeadSequence !== segment.sequenceEnd ||
|
|
471
|
+
summary.priorNativeRevision !== nativeRevision ||
|
|
472
|
+
summary.priorStateHash !== priorStateHash
|
|
473
|
+
) {
|
|
474
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
475
|
+
}
|
|
476
|
+
nativeRevision = summary.nativeReceipt.revision;
|
|
477
|
+
priorStateHash = editableArtifactStateHash(summary.stateHash);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (error instanceof EditableArtifactSnapshotVerificationError) throw error;
|
|
480
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
expectedSequence = segment.sequenceEnd + 1;
|
|
484
|
+
yield segment.modality === "spreadsheet"
|
|
485
|
+
? Object.freeze({
|
|
486
|
+
...segment,
|
|
487
|
+
modality: "spreadsheet" as const,
|
|
488
|
+
bytes: segment.bytes.slice(),
|
|
489
|
+
})
|
|
490
|
+
: Object.freeze({
|
|
491
|
+
...segment,
|
|
492
|
+
modality: segment.modality,
|
|
493
|
+
bytes: segment.bytes.slice(),
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
if (expectedSequence !== plan.targetHeadSequence + 1) {
|
|
497
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
498
|
+
}
|
|
499
|
+
if (
|
|
500
|
+
plan.modality !== "spreadsheet" &&
|
|
501
|
+
(nativeRevision !== plan.targetNativeRevision || priorStateHash !== plan.targetStateHash)
|
|
502
|
+
) {
|
|
503
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
504
|
+
}
|
|
505
|
+
onFinished();
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function validateKernelResult(
|
|
511
|
+
result: IsolatedEditableArtifactSnapshotKernelResult,
|
|
512
|
+
scope: EditableArtifactScope,
|
|
513
|
+
artifactId: EditableArtifactId,
|
|
514
|
+
snapshot: PublishEditableArtifactSnapshotRequest,
|
|
515
|
+
replayPlan:
|
|
516
|
+
| NonNullable<
|
|
517
|
+
Parameters<
|
|
518
|
+
IsolatedEditableArtifactSnapshotKernelPort["verifyAndReconstruct"]
|
|
519
|
+
>[0]["replayPlan"]
|
|
520
|
+
>
|
|
521
|
+
| undefined,
|
|
522
|
+
): void {
|
|
523
|
+
if (
|
|
524
|
+
result.canonicalReencodeVerified !== true ||
|
|
525
|
+
result.modality !== snapshot.modality ||
|
|
526
|
+
result.canonicalByteSize !== snapshot.byteSize ||
|
|
527
|
+
result.canonicalContentHash !== snapshot.contentHash
|
|
528
|
+
) {
|
|
529
|
+
throw new EditableArtifactSnapshotVerificationError("canonical_mismatch");
|
|
530
|
+
}
|
|
531
|
+
if (
|
|
532
|
+
result.scope.accountId !== scope.accountId ||
|
|
533
|
+
result.scope.workspaceId !== scope.workspaceId ||
|
|
534
|
+
result.artifactId !== artifactId
|
|
535
|
+
) {
|
|
536
|
+
throw new EditableArtifactSnapshotVerificationError("identity_mismatch");
|
|
537
|
+
}
|
|
538
|
+
if (result.coveredHeadSequence !== snapshot.coveredHeadSequence) {
|
|
539
|
+
throw new EditableArtifactSnapshotVerificationError("coverage_mismatch");
|
|
540
|
+
}
|
|
541
|
+
if (editableArtifactStateHash(result.stateHash) !== snapshot.stateHash) {
|
|
542
|
+
throw new EditableArtifactSnapshotVerificationError("state_hash_mismatch");
|
|
543
|
+
}
|
|
544
|
+
if (
|
|
545
|
+
result.modelSchemaVersion !== snapshot.modelSchemaVersion ||
|
|
546
|
+
result.kernelVersion !== snapshot.kernelVersion
|
|
547
|
+
) {
|
|
548
|
+
throw new EditableArtifactSnapshotVerificationError("version_mismatch");
|
|
549
|
+
}
|
|
550
|
+
if (snapshot.modality === "spreadsheet") {
|
|
551
|
+
if (
|
|
552
|
+
result.modality !== "spreadsheet" ||
|
|
553
|
+
result.fullCrdtStateVerified !== true ||
|
|
554
|
+
!causalFrontiersEqual(
|
|
555
|
+
editableArtifactCausalFrontier(result.coveredCausalFrontier),
|
|
556
|
+
snapshot.coveredCausalFrontier,
|
|
557
|
+
)
|
|
558
|
+
) {
|
|
559
|
+
throw new EditableArtifactSnapshotVerificationError("frontier_mismatch");
|
|
560
|
+
}
|
|
561
|
+
if (
|
|
562
|
+
result.operationProtocolVersion !== snapshot.operationProtocolVersion ||
|
|
563
|
+
result.crdtStateVersion !== snapshot.crdtStateVersion
|
|
564
|
+
) {
|
|
565
|
+
throw new EditableArtifactSnapshotVerificationError("version_mismatch");
|
|
566
|
+
}
|
|
567
|
+
if (replayPlan) {
|
|
568
|
+
if (
|
|
569
|
+
replayPlan.modality !== "spreadsheet" ||
|
|
570
|
+
!result.replayedTarget ||
|
|
571
|
+
result.replayedTarget.headSequence !== replayPlan.targetHeadSequence ||
|
|
572
|
+
editableArtifactStateHash(result.replayedTarget.stateHash) !== replayPlan.targetStateHash ||
|
|
573
|
+
!causalFrontiersEqual(
|
|
574
|
+
editableArtifactCausalFrontier(result.replayedTarget.causalFrontier),
|
|
575
|
+
replayPlan.targetCausalFrontier,
|
|
576
|
+
)
|
|
577
|
+
) {
|
|
578
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
579
|
+
}
|
|
580
|
+
} else if (result.replayedTarget !== undefined) {
|
|
581
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
582
|
+
}
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (result.modality !== snapshot.modality || result.nativeRevision !== snapshot.nativeRevision) {
|
|
586
|
+
throw new EditableArtifactSnapshotVerificationError("version_mismatch");
|
|
587
|
+
}
|
|
588
|
+
if (replayPlan) {
|
|
589
|
+
if (
|
|
590
|
+
replayPlan.modality !== snapshot.modality ||
|
|
591
|
+
!result.replayedTarget ||
|
|
592
|
+
result.replayedTarget.headSequence !== replayPlan.targetHeadSequence ||
|
|
593
|
+
result.replayedTarget.nativeRevision !== replayPlan.targetNativeRevision ||
|
|
594
|
+
editableArtifactStateHash(result.replayedTarget.stateHash) !== replayPlan.targetStateHash
|
|
595
|
+
) {
|
|
596
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
597
|
+
}
|
|
598
|
+
} else if (result.replayedTarget !== undefined) {
|
|
599
|
+
throw new EditableArtifactSnapshotVerificationError("replay_invalid");
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function validateScope(scope: EditableArtifactScope): EditableArtifactScope {
|
|
604
|
+
if (
|
|
605
|
+
typeof scope.accountId !== "string" ||
|
|
606
|
+
scope.accountId.length < 1 ||
|
|
607
|
+
typeof scope.workspaceId !== "string" ||
|
|
608
|
+
scope.workspaceId.length < 1
|
|
609
|
+
) {
|
|
610
|
+
throw new EditableArtifactSnapshotVerificationError("identity_mismatch");
|
|
611
|
+
}
|
|
612
|
+
return Object.freeze({
|
|
613
|
+
accountId: scope.accountId,
|
|
614
|
+
workspaceId: scope.workspaceId,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function throwIfCancelled(signal: AbortSignal | undefined): void {
|
|
619
|
+
if (signal?.aborted) {
|
|
620
|
+
throw new EditableArtifactSnapshotVerificationError("cancelled");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function mapVerificationFailure(
|
|
625
|
+
error: unknown,
|
|
626
|
+
signal: AbortSignal | undefined,
|
|
627
|
+
): EditableArtifactSnapshotVerificationError {
|
|
628
|
+
if (error instanceof EditableArtifactSnapshotVerificationError) return error;
|
|
629
|
+
if (signal?.aborted) {
|
|
630
|
+
return new EditableArtifactSnapshotVerificationError("cancelled");
|
|
631
|
+
}
|
|
632
|
+
if (error instanceof BoundedObjectReadError) {
|
|
633
|
+
switch (error.code) {
|
|
634
|
+
case "aborted":
|
|
635
|
+
return new EditableArtifactSnapshotVerificationError("cancelled");
|
|
636
|
+
case "object_changed":
|
|
637
|
+
return new EditableArtifactSnapshotVerificationError("object_changed");
|
|
638
|
+
case "object_missing":
|
|
639
|
+
return new EditableArtifactSnapshotVerificationError("object_missing");
|
|
640
|
+
case "size_limit":
|
|
641
|
+
return new EditableArtifactSnapshotVerificationError("limit_exceeded");
|
|
642
|
+
case "truncated":
|
|
643
|
+
return new EditableArtifactSnapshotVerificationError("truncated");
|
|
644
|
+
case "backend_failure":
|
|
645
|
+
case "invalid_request":
|
|
646
|
+
return new EditableArtifactSnapshotVerificationError("storage_failure");
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return new EditableArtifactSnapshotVerificationError("kernel_rejected");
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function snapshotErrorMessage(code: EditableArtifactSnapshotVerificationErrorCode): string {
|
|
653
|
+
switch (code) {
|
|
654
|
+
case "cancelled":
|
|
655
|
+
return "Snapshot verification was cancelled";
|
|
656
|
+
case "canonical_mismatch":
|
|
657
|
+
return "Snapshot is not the canonical kernel encoding";
|
|
658
|
+
case "content_hash_mismatch":
|
|
659
|
+
return "Snapshot content digest does not match immutable metadata";
|
|
660
|
+
case "content_type_mismatch":
|
|
661
|
+
return "Snapshot content type does not match immutable metadata";
|
|
662
|
+
case "coverage_mismatch":
|
|
663
|
+
return "Snapshot coverage does not match embedded state";
|
|
664
|
+
case "frontier_mismatch":
|
|
665
|
+
return "Snapshot causal frontier does not match embedded state";
|
|
666
|
+
case "identity_mismatch":
|
|
667
|
+
return "Snapshot authority identity does not match embedded state";
|
|
668
|
+
case "kernel_rejected":
|
|
669
|
+
return "Authoritative kernel rejected the snapshot";
|
|
670
|
+
case "limit_exceeded":
|
|
671
|
+
return "Snapshot exceeds a verification limit";
|
|
672
|
+
case "object_changed":
|
|
673
|
+
return "Snapshot object changed during verification";
|
|
674
|
+
case "object_missing":
|
|
675
|
+
return "Snapshot object is unavailable";
|
|
676
|
+
case "replay_invalid":
|
|
677
|
+
return "Snapshot operation-tail reconstruction is invalid";
|
|
678
|
+
case "state_hash_mismatch":
|
|
679
|
+
return "Snapshot state hash does not match reconstructed state";
|
|
680
|
+
case "storage_failure":
|
|
681
|
+
return "Snapshot object could not be read safely";
|
|
682
|
+
case "truncated":
|
|
683
|
+
return "Snapshot object is truncated";
|
|
684
|
+
case "version_mismatch":
|
|
685
|
+
return "Snapshot kernel or schema versions are incompatible";
|
|
686
|
+
}
|
|
687
|
+
}
|