@opengeni/sdk 1.0.1 → 2.3.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +117 -2
  2. package/dist/artifacts.js +5 -5
  3. package/dist/automations-client.d.ts +17 -0
  4. package/dist/automations.d.ts +3 -0
  5. package/dist/automations.js +78 -0
  6. package/dist/automations.js.map +1 -0
  7. package/dist/{chunk-YGH5P47G.js → chunk-6IPZY27S.js} +744 -57
  8. package/dist/chunk-6IPZY27S.js.map +1 -0
  9. package/dist/{chunk-TYZ4JL4J.js → chunk-AGUKZAXR.js} +2 -2
  10. package/dist/{chunk-A5DC5WEM.js → chunk-C2H7HBNP.js} +22 -2
  11. package/dist/chunk-C2H7HBNP.js.map +1 -0
  12. package/dist/{chunk-GU6JF75T.js → chunk-C4DJPZRU.js} +1 -1
  13. package/dist/{chunk-GU6JF75T.js.map → chunk-C4DJPZRU.js.map} +1 -1
  14. package/dist/{chunk-ST5DDKJP.js → chunk-GW3EJ2GP.js} +52 -1
  15. package/dist/chunk-GW3EJ2GP.js.map +1 -0
  16. package/dist/{chunk-4DV37UUA.js → chunk-TW5MNHUX.js} +16 -2
  17. package/dist/chunk-TW5MNHUX.js.map +1 -0
  18. package/dist/{chunk-VWE2QIVO.js → chunk-XNG7YGR3.js} +23 -6
  19. package/dist/chunk-XNG7YGR3.js.map +1 -0
  20. package/dist/client.d.ts +159 -26
  21. package/dist/codex-realtime-controller.d.ts +1 -0
  22. package/dist/codex-realtime-controller.js +2 -2
  23. package/dist/company-brain.d.ts +32 -0
  24. package/dist/company-profile.d.ts +1 -1
  25. package/dist/core.js +4 -4
  26. package/dist/editable-artifacts/browser-session.d.ts +7 -3
  27. package/dist/editable-artifacts/controller.d.ts +4 -1
  28. package/dist/editable-artifacts/errors.d.ts +1 -1
  29. package/dist/editable-artifacts/http-live-transport.d.ts +6 -2
  30. package/dist/editable-artifacts/index.d.ts +2 -0
  31. package/dist/editable-artifacts/open-failure.d.ts +9 -0
  32. package/dist/editable-artifacts/types.d.ts +4 -3
  33. package/dist/editable-artifacts/worker/kernel-adapter.d.ts +1 -1
  34. package/dist/editable-artifacts-worker.js +44 -17
  35. package/dist/editable-artifacts-worker.js.map +1 -1
  36. package/dist/editable-artifacts.js +222 -119
  37. package/dist/editable-artifacts.js.map +1 -1
  38. package/dist/errors.d.ts +1 -1
  39. package/dist/index.d.ts +7 -4
  40. package/dist/index.js +61 -12
  41. package/dist/index.js.map +1 -1
  42. package/dist/interaction.d.ts +23 -0
  43. package/dist/interaction.js +4 -1
  44. package/dist/organization-private-session-settings.d.ts +6 -0
  45. package/dist/organization-private-session-settings.js +22 -0
  46. package/dist/organization-private-session-settings.js.map +1 -0
  47. package/dist/pr-review-client.d.ts +15 -0
  48. package/dist/pr-review.d.ts +4 -0
  49. package/dist/pr-review.js +56 -0
  50. package/dist/pr-review.js.map +1 -0
  51. package/dist/preference-registry.d.ts +8 -0
  52. package/dist/realtime.js +2 -2
  53. package/dist/retained-artifacts.d.ts +5 -1
  54. package/dist/types.d.ts +1023 -13
  55. package/dist/workspace-instruction-policies.d.ts +22 -0
  56. package/dist/workspace-learning.d.ts +124 -0
  57. package/package.json +14 -2
  58. package/src/automations-client.ts +118 -0
  59. package/src/automations.ts +15 -0
  60. package/src/client.ts +1049 -71
  61. package/src/codex-realtime-controller.ts +21 -0
  62. package/src/company-brain.ts +99 -0
  63. package/src/company-profile.ts +4 -1
  64. package/src/editable-artifacts/browser-session.ts +36 -22
  65. package/src/editable-artifacts/controller.ts +66 -45
  66. package/src/editable-artifacts/errors.ts +2 -0
  67. package/src/editable-artifacts/http-live-transport.ts +62 -37
  68. package/src/editable-artifacts/index.ts +6 -0
  69. package/src/editable-artifacts/open-failure.ts +79 -0
  70. package/src/editable-artifacts/storage.ts +31 -40
  71. package/src/editable-artifacts/types.ts +4 -3
  72. package/src/editable-artifacts/worker/kernel-adapter.ts +23 -7
  73. package/src/editable-artifacts/worker/runtime.ts +26 -13
  74. package/src/editable-artifacts/worker/wire-codec.ts +23 -5
  75. package/src/errors.ts +1 -1
  76. package/src/index.ts +134 -0
  77. package/src/interaction.ts +95 -0
  78. package/src/organization-private-session-settings.ts +33 -0
  79. package/src/pr-review-client.ts +84 -0
  80. package/src/pr-review.ts +15 -0
  81. package/src/preference-registry.ts +9 -0
  82. package/src/retained-artifacts.ts +101 -0
  83. package/src/stream.ts +13 -2
  84. package/src/types.ts +1174 -12
  85. package/src/workspace-instruction-policies.ts +24 -0
  86. package/src/workspace-learning.ts +138 -0
  87. package/dist/chunk-4DV37UUA.js.map +0 -1
  88. package/dist/chunk-A5DC5WEM.js.map +0 -1
  89. package/dist/chunk-ST5DDKJP.js.map +0 -1
  90. package/dist/chunk-VWE2QIVO.js.map +0 -1
  91. package/dist/chunk-YGH5P47G.js.map +0 -1
  92. /package/dist/{chunk-TYZ4JL4J.js.map → chunk-AGUKZAXR.js.map} +0 -0
@@ -50,6 +50,7 @@ export const CODEX_REALTIME_NEGOTIATION_TIMEOUT_MS = 20_000;
50
50
  // OpenGeni policy: rotate conservatively without asserting an upstream lifetime.
51
51
  const DEFAULT_CONNECTION_ROTATION_INTERVAL_MS = 15 * 60_000;
52
52
  const DEFAULT_RECONNECT_BACKOFF_MS = [250, 1_000, 2_000, 5_000] as const;
53
+ const MAX_BROWSER_TIMEOUT_MS = 2_147_483_647;
53
54
  const OWNER_RECORD_VERSION = 1;
54
55
  const OWNER_DELEGATION_REPLAY_VERSION = 1;
55
56
  const OWNER_DELEGATION_REPLAY_MAX_CALLS = 4_096;
@@ -224,6 +225,7 @@ export type CreateCodexRealtimeControllerOptions = {
224
225
  createPeerConnection?: (() => RTCPeerConnection) | undefined;
225
226
  getUserMedia?: ((constraints: MediaStreamConstraints) => Promise<MediaStream>) | undefined;
226
227
  randomUUID?: (() => string) | undefined;
228
+ now?: (() => Date) | undefined;
227
229
  setInterval?: ((callback: () => void, delayMs: number) => unknown) | undefined;
228
230
  clearInterval?: ((handle: unknown) => void) | undefined;
229
231
  setTimeout?: ((callback: () => void, delayMs: number) => unknown) | undefined;
@@ -316,6 +318,7 @@ export function createCodexRealtimeController(
316
318
  );
317
319
  const model = options.model ?? "gpt-live-1-boulder-alpha";
318
320
  const randomUUID = options.randomUUID ?? defaultRandomUUID;
321
+ const now = options.now ?? (() => new Date());
319
322
  const scheduleInterval =
320
323
  options.setInterval ?? ((callback, delay) => globalThis.setInterval(callback, delay));
321
324
  const unscheduleInterval =
@@ -362,6 +365,7 @@ export function createCodexRealtimeController(
362
365
  let rotationTimer: unknown = null;
363
366
  let reconnectTimer: unknown = null;
364
367
  let negotiationTimer: unknown = null;
368
+ let lostOwnerExpiryTimer: unknown = null;
365
369
  let closed = false;
366
370
  let stopping = false;
367
371
  let generation = 0;
@@ -456,11 +460,13 @@ export function createCodexRealtimeController(
456
460
  if (rotationTimer !== null) unscheduleTimeout(rotationTimer);
457
461
  if (reconnectTimer !== null) unscheduleTimeout(reconnectTimer);
458
462
  if (negotiationTimer !== null) unscheduleTimeout(negotiationTimer);
463
+ if (lostOwnerExpiryTimer !== null) unscheduleTimeout(lostOwnerExpiryTimer);
459
464
  heartbeatTimer = null;
460
465
  syncTimer = null;
461
466
  rotationTimer = null;
462
467
  reconnectTimer = null;
463
468
  negotiationTimer = null;
469
+ lostOwnerExpiryTimer = null;
464
470
  };
465
471
 
466
472
  const stopNegotiationTimers = (): void => {
@@ -1255,6 +1261,12 @@ export function createCodexRealtimeController(
1255
1261
  if (!record || record.operationId !== lifecycle.operationId) {
1256
1262
  closeBrowserResources();
1257
1263
  owner = null;
1264
+ const leaseExpiresAt = Date.parse(lifecycle.leaseExpiresAt);
1265
+ const remainingLeaseMs = leaseExpiresAt - now().getTime();
1266
+ if (Number.isFinite(remainingLeaseMs) && remainingLeaseMs <= 0) {
1267
+ transitionEnded("Realtime lease expired");
1268
+ return;
1269
+ }
1258
1270
  const message =
1259
1271
  "Realtime is active in another browser owner. It can resume after that owner stops or its lease expires.";
1260
1272
  publish({
@@ -1265,6 +1277,15 @@ export function createCodexRealtimeController(
1265
1277
  diagnostic: diagnostic("lost_owner", message, false),
1266
1278
  error: message,
1267
1279
  });
1280
+ if (Number.isFinite(remainingLeaseMs) && remainingLeaseMs <= MAX_BROWSER_TIMEOUT_MS) {
1281
+ const observedRealtimeId = lifecycle.realtimeId;
1282
+ lostOwnerExpiryTimer = scheduleTimeout(() => {
1283
+ lostOwnerExpiryTimer = null;
1284
+ if (state.status === "lost_owner" && state.realtimeId === observedRealtimeId) {
1285
+ transitionEnded("Realtime lease expired");
1286
+ }
1287
+ }, remainingLeaseMs);
1288
+ }
1268
1289
  return;
1269
1290
  }
1270
1291
  if (connectionTask || state.status === "starting") return;
@@ -0,0 +1,99 @@
1
+ import type {
2
+ CompanyBrainContextReceiptPage as ContractCompanyBrainContextReceiptPage,
3
+ CompanyBrainKnowledgeProposalPage as ContractCompanyBrainKnowledgeProposalPage,
4
+ CompanyBrainOkfPackage as ContractCompanyBrainOkfPackage,
5
+ KnowledgeBrowseRequest as ContractKnowledgeBrowseRequest,
6
+ KnowledgeBrowseResponse as ContractKnowledgeBrowseResponse,
7
+ KnowledgeGetResponse as ContractKnowledgeGetResponse,
8
+ KnowledgeRecord as ContractKnowledgeRecord,
9
+ KnowledgeSearchRequest as ContractKnowledgeSearchRequest,
10
+ KnowledgeSearchResponse as ContractKnowledgeSearchResponse,
11
+ } from "@opengeni/contracts";
12
+
13
+ export type CompanyBrainOkfPackage = ContractCompanyBrainOkfPackage;
14
+ export type CompanyBrainContextReceiptPage = ContractCompanyBrainContextReceiptPage;
15
+ export type CompanyBrainKnowledgeProposalPage = ContractCompanyBrainKnowledgeProposalPage;
16
+ export type CompanyBrainKnowledgeSearchRequest = ContractKnowledgeSearchRequest;
17
+ export type CompanyBrainKnowledgeSearchResponse = ContractKnowledgeSearchResponse;
18
+ export type CompanyBrainKnowledgeBrowseRequest = ContractKnowledgeBrowseRequest;
19
+ export type CompanyBrainKnowledgeBrowseResponse = ContractKnowledgeBrowseResponse;
20
+ export type CompanyBrainKnowledgeGetResponse = ContractKnowledgeGetResponse;
21
+ export type CompanyBrainKnowledgeRecord = ContractKnowledgeRecord;
22
+
23
+ export type CompanyBrainContextReceiptListOptions = {
24
+ attemptId?: string;
25
+ cursor?: string;
26
+ limit?: number;
27
+ };
28
+
29
+ export type CompanyBrainKnowledgeProposalListOptions = {
30
+ limit?: number;
31
+ };
32
+
33
+ export type CompanyBrainInspectorTransport = {
34
+ requestJson<T>(method: string, path: string, body?: unknown): Promise<T>;
35
+ };
36
+
37
+ function inspectorPath(workspaceId: string, suffix: string): string {
38
+ return `/v1/workspaces/${workspaceId}/company-brain/${suffix}`;
39
+ }
40
+
41
+ /** Human-authorized, permission-first Knowledge search for Company Brain inspectors. */
42
+ export function searchCompanyBrainKnowledge(
43
+ client: CompanyBrainInspectorTransport,
44
+ workspaceId: string,
45
+ request: CompanyBrainKnowledgeSearchRequest,
46
+ ): Promise<CompanyBrainKnowledgeSearchResponse> {
47
+ return client.requestJson("POST", inspectorPath(workspaceId, "knowledge/search"), request);
48
+ }
49
+
50
+ export function getCompanyBrainKnowledge(
51
+ client: CompanyBrainInspectorTransport,
52
+ workspaceId: string,
53
+ recordId: string,
54
+ ): Promise<CompanyBrainKnowledgeGetResponse> {
55
+ return client.requestJson(
56
+ "GET",
57
+ `${inspectorPath(workspaceId, "knowledge/record")}?id=${encodeURIComponent(recordId)}`,
58
+ );
59
+ }
60
+
61
+ export function browseCompanyBrainKnowledge(
62
+ client: CompanyBrainInspectorTransport,
63
+ workspaceId: string,
64
+ request: CompanyBrainKnowledgeBrowseRequest = {},
65
+ ): Promise<CompanyBrainKnowledgeBrowseResponse> {
66
+ return client.requestJson("POST", inspectorPath(workspaceId, "knowledge/browse"), request);
67
+ }
68
+
69
+ export function listCompanyBrainContextReceipts(
70
+ client: CompanyBrainInspectorTransport,
71
+ workspaceId: string,
72
+ options: CompanyBrainContextReceiptListOptions = {},
73
+ ): Promise<CompanyBrainContextReceiptPage> {
74
+ const params = new URLSearchParams();
75
+ if (options.attemptId) params.set("attemptId", options.attemptId);
76
+ if (options.cursor) params.set("cursor", options.cursor);
77
+ if (options.limit !== undefined) params.set("limit", String(options.limit));
78
+ const query = params.toString();
79
+ return client.requestJson(
80
+ "GET",
81
+ `${inspectorPath(workspaceId, "context-receipts")}${query ? `?${query}` : ""}`,
82
+ );
83
+ }
84
+
85
+ export function listCompanyBrainKnowledgeProposals(
86
+ client: CompanyBrainInspectorTransport,
87
+ workspaceId: string,
88
+ options: CompanyBrainKnowledgeProposalListOptions = {},
89
+ ): Promise<CompanyBrainKnowledgeProposalPage> {
90
+ const query =
91
+ options.limit === undefined ? "" : `?limit=${encodeURIComponent(String(options.limit))}`;
92
+ return client.requestJson("GET", `${inspectorPath(workspaceId, "knowledge-proposals")}${query}`);
93
+ }
94
+
95
+ export type CompanyBrainOkfDownload = {
96
+ content: string;
97
+ contentType: string;
98
+ filename: string;
99
+ };
@@ -21,7 +21,10 @@ export type CompanyProfileRevision = CompanyProfileRevisionIdentity & {
21
21
  accountId: string;
22
22
  intent: "active" | "proposal";
23
23
  profile: CompanyProfileContent;
24
- provenance: { source: "human" | "durable_learning" | "migration"; sourceId: string | null };
24
+ provenance: {
25
+ source: "human" | "agent_admin" | "durable_learning" | "migration";
26
+ sourceId: string | null;
27
+ };
25
28
  supersedesRevisionId: string | null;
26
29
  createdBySubjectId: string;
27
30
  createdAt: string;
@@ -1,6 +1,8 @@
1
- import { DOCUMENT_ARTIFACT_COMMAND_VERSION } from "@opengeni/contracts/document-artifact-commands";
2
- import { PRESENTATION_ARTIFACT_COMMAND_VERSION } from "@opengeni/contracts/presentation-artifact-commands";
3
- import { SPREADSHEET_ARTIFACT_COMMAND_VERSION } from "@opengeni/contracts/spreadsheet-artifact-commands";
1
+ import { EDITABLE_ARTIFACT_LIVE_WIRE_VERSION } from "@opengeni/contracts/editable-artifact-live";
2
+ import {
3
+ EDITABLE_ARTIFACT_INTENT_PROTOCOL_VERSION,
4
+ currentEditableArtifactCompatibility,
5
+ } from "@opengeni/contracts/editable-artifacts";
4
6
 
5
7
  import type { EditableArtifactCacheAuthority } from "./controller";
6
8
  import {
@@ -10,6 +12,7 @@ import {
10
12
  import { createEditableArtifactSession, type EditableArtifactSession } from "./session";
11
13
  import type { EditableArtifactStoragePort } from "./storage";
12
14
  import type { EditableArtifactModality } from "./types";
15
+ import type { EditableArtifactOpenFailureReporter } from "./open-failure";
13
16
  import {
14
17
  createBrowserEditableArtifactWorkerKernel,
15
18
  type CreateBrowserEditableArtifactWorkerKernelOptions,
@@ -20,10 +23,11 @@ export type EditableArtifactBrowserRuntime = Omit<
20
23
  "modality" | "kernelVersion" | "protocolVersion" | "modelSchemaVersion" | "commandVersion"
21
24
  > &
22
25
  Readonly<{
23
- /** Exact native/WASM build identity admitted by the API. */
26
+ /** Exact package/executable build identity; the API records but does not compatibility-gate it. */
24
27
  kernelVersion: string;
25
28
  modelSchemaVersion: number;
26
- protocolVersion?: number;
29
+ /** Exact OGATX001 intent protocol version. */
30
+ protocolVersion: number;
27
31
  commandVersion: number;
28
32
  }>;
29
33
 
@@ -36,11 +40,21 @@ export type CreateBrowserEditableArtifactSessionOptions = Readonly<{
36
40
  runtime: EditableArtifactBrowserRuntime;
37
41
  transport?: Omit<
38
42
  CreateEditableArtifactHttpLiveTransportOptions,
39
- "baseUrl" | "workspaceId" | "protocolVersion" | "kernelVersion" | "modelSchemaVersion"
43
+ | "baseUrl"
44
+ | "workspaceId"
45
+ | "modality"
46
+ | "liveProtocolVersion"
47
+ | "kernelVersion"
48
+ | "modelSchemaVersion"
49
+ | "snapshotVersion"
50
+ | "commandProtocolVersion"
51
+ | "committedTransactionProtocolVersion"
40
52
  >;
41
53
  storage?: EditableArtifactStoragePort;
42
54
  /** Reuse the replica used to create the artifact when opening it immediately. */
43
55
  replicaId?: string;
56
+ /** Receives bounded categories/codes only; never workbook or formula content. */
57
+ onOpenFailure?: EditableArtifactOpenFailureReporter;
44
58
  }>;
45
59
 
46
60
  /**
@@ -53,17 +67,23 @@ export function createBrowserEditableArtifactSession(
53
67
  const baseUrl = canonicalBaseUrl(options.baseUrl);
54
68
  const workspaceId = boundedIdentity(options.workspaceId, "workspaceId");
55
69
  assertCacheAuthority(options.storageAuthority, baseUrl, workspaceId);
56
- const protocolVersion = positiveVersion(options.runtime.protocolVersion ?? 1, "protocolVersion");
70
+ const protocolVersion = positiveVersion(options.runtime.protocolVersion, "protocolVersion");
71
+ if (protocolVersion !== EDITABLE_ARTIFACT_INTENT_PROTOCOL_VERSION) {
72
+ throw new TypeError("protocolVersion is incompatible with the current intent protocol");
73
+ }
57
74
  const modelSchemaVersion = positiveVersion(
58
75
  options.runtime.modelSchemaVersion,
59
76
  "modelSchemaVersion",
60
77
  );
61
78
  const kernelVersion = boundedIdentity(options.runtime.kernelVersion, "kernelVersion", 512);
62
79
  const commandVersion = positiveVersion(options.runtime.commandVersion, "commandVersion");
63
- const expectedCommandVersion = commandVersionFor(options.artifact.modality);
64
- if (commandVersion !== expectedCommandVersion) {
80
+ const current = currentEditableArtifactCompatibility(options.artifact.modality);
81
+ if (
82
+ modelSchemaVersion !== current.modelSchemaVersion ||
83
+ commandVersion !== current.commandProtocolVersion
84
+ ) {
65
85
  throw new TypeError(
66
- `commandVersion ${commandVersion} is incompatible with ${options.artifact.modality} ${expectedCommandVersion}`,
86
+ `runtime versions are incompatible with current ${options.artifact.modality} compatibility`,
67
87
  );
68
88
  }
69
89
  const {
@@ -87,9 +107,13 @@ export function createBrowserEditableArtifactSession(
87
107
  ...options.transport,
88
108
  baseUrl,
89
109
  workspaceId,
90
- protocolVersion,
110
+ modality: options.artifact.modality,
111
+ liveProtocolVersion: EDITABLE_ARTIFACT_LIVE_WIRE_VERSION,
91
112
  kernelVersion,
92
113
  modelSchemaVersion,
114
+ snapshotVersion: current.snapshotVersion,
115
+ commandProtocolVersion: current.commandProtocolVersion,
116
+ committedTransactionProtocolVersion: current.committedTransactionProtocolVersion,
93
117
  });
94
118
  return createEditableArtifactSession({
95
119
  artifactId: options.artifact.id,
@@ -102,6 +126,7 @@ export function createBrowserEditableArtifactSession(
102
126
  modelSchemaVersion,
103
127
  commandVersion,
104
128
  protocolVersion,
129
+ ...(options.onOpenFailure ? { onOpenFailure: options.onOpenFailure } : {}),
105
130
  ...(replicaId ? { writerReplicaIdFactory: () => replicaId } : {}),
106
131
  ownsWorker: true,
107
132
  });
@@ -121,17 +146,6 @@ export function createEditableArtifactReplicaId(): string {
121
146
  }
122
147
  }
123
148
 
124
- function commandVersionFor(modality: EditableArtifactModality): number {
125
- switch (modality) {
126
- case "spreadsheet":
127
- return SPREADSHEET_ARTIFACT_COMMAND_VERSION;
128
- case "document":
129
- return DOCUMENT_ARTIFACT_COMMAND_VERSION;
130
- case "presentation":
131
- return PRESENTATION_ARTIFACT_COMMAND_VERSION;
132
- }
133
- }
134
-
135
149
  function canonicalBaseUrl(value: string | URL): URL {
136
150
  const url = new URL(value);
137
151
  if (url.username || url.password || url.search || url.hash) {
@@ -2,16 +2,24 @@ import {
2
2
  decodeEditableArtifactMutationIntent,
3
3
  EDITABLE_ARTIFACT_COMMAND_MAX_BYTES,
4
4
  EDITABLE_ARTIFACT_INTENT_MAX_BYTES,
5
+ EDITABLE_ARTIFACT_INTENT_PROTOCOL_VERSION,
5
6
  EDITABLE_ARTIFACT_PRODUCT_MAX_SNAPSHOT_BYTES,
6
7
  hashEditableArtifactMutationIntentBytes,
7
8
  } from "@opengeni/contracts/editable-artifacts";
8
9
  import {
10
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
9
11
  decodeCommittedTransactionSummary,
10
12
  MAX_COMMITTED_TRANSACTION_BYTES,
11
13
  } from "@opengeni/contracts/editable-artifact-committed-transaction";
12
14
  import { editableArtifactCodecFor } from "@opengeni/contracts/editable-artifact-codec-registry";
15
+ import { EDITABLE_ARTIFACT_LIVE_WIRE_VERSION } from "@opengeni/contracts/editable-artifact-live";
13
16
  import { decodeEditableArtifactSerializedCommit } from "@opengeni/contracts/editable-artifact-serialized-commit";
17
+ import { SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION } from "@opengeni/contracts/editable-artifact-versions";
14
18
  import { EditableArtifactSyncError } from "./errors";
19
+ import {
20
+ classifyEditableArtifactOpenFailure,
21
+ type EditableArtifactOpenFailureReporter,
22
+ } from "./open-failure";
15
23
  import { EditableArtifactStorageConflictError } from "./storage";
16
24
  import type { EditableArtifactStoragePort, EditableArtifactStorageScope } from "./storage";
17
25
  import type {
@@ -42,7 +50,6 @@ import type {
42
50
  EditableArtifactWorkerKernel,
43
51
  } from "./types";
44
52
 
45
- const DEFAULT_PROTOCOL_VERSION = 1;
46
53
  const DEFAULT_REPLAY_PAGE_SIZE = 256;
47
54
  const DEFAULT_MAX_QUEUE_MESSAGES = 512;
48
55
  const DEFAULT_MAX_QUEUE_BYTES = 8 * 1024 * 1024;
@@ -70,7 +77,7 @@ export type CreateEditableArtifactSyncControllerOptions = {
70
77
  kernelVersion: string;
71
78
  modelSchemaVersion: number;
72
79
  commandVersion: number;
73
- protocolVersion?: number;
80
+ protocolVersion: number;
74
81
  replayPageSize?: number;
75
82
  maxQueuedMessages?: number;
76
83
  maxQueuedBytes?: number;
@@ -87,6 +94,8 @@ export type CreateEditableArtifactSyncControllerOptions = {
87
94
  maxReconnectAttempts?: number;
88
95
  scheduler?: EditableArtifactSyncScheduler;
89
96
  clientTransactionIdFactory?: () => string;
97
+ /** Receives bounded categories/codes only; never workbook or formula content. */
98
+ onOpenFailure?: EditableArtifactOpenFailureReporter;
90
99
  /** Tests/embedded runtimes may inject; defaults to a fresh per-controller UUID. */
91
100
  writerReplicaIdFactory?: () => string;
92
101
  };
@@ -186,6 +195,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
186
195
  private readonly maxReconnectAttempts: number;
187
196
  private readonly scheduler: EditableArtifactSyncScheduler;
188
197
  private readonly clientTransactionIdFactory: () => string;
198
+ private readonly onOpenFailure: EditableArtifactOpenFailureReporter | undefined;
189
199
 
190
200
  private readonly abortController = new AbortController();
191
201
  private readonly listeners = new Set<EditableArtifactSyncListener>();
@@ -258,10 +268,10 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
258
268
  );
259
269
  this.modelSchemaVersion = positiveSafeInteger(options.modelSchemaVersion, "modelSchemaVersion");
260
270
  this.commandVersion = positiveSafeInteger(options.commandVersion, "commandVersion");
261
- this.protocolVersion = positiveSafeInteger(
262
- options.protocolVersion ?? DEFAULT_PROTOCOL_VERSION,
263
- "protocolVersion",
264
- );
271
+ this.protocolVersion = positiveSafeInteger(options.protocolVersion, "protocolVersion");
272
+ if (this.protocolVersion !== EDITABLE_ARTIFACT_INTENT_PROTOCOL_VERSION) {
273
+ throw new TypeError("protocolVersion is incompatible with the current intent protocol");
274
+ }
265
275
  this.replayPageSize = positiveSafeInteger(
266
276
  options.replayPageSize ?? DEFAULT_REPLAY_PAGE_SIZE,
267
277
  "replayPageSize",
@@ -324,6 +334,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
324
334
  this.scheduler = options.scheduler ?? defaultScheduler;
325
335
  this.clientTransactionIdFactory =
326
336
  options.clientTransactionIdFactory ?? (() => crypto.randomUUID());
337
+ this.onOpenFailure = options.onOpenFailure;
327
338
  }
328
339
 
329
340
  start(): void {
@@ -331,6 +342,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
331
342
  this.runTask = this.run().catch((error: unknown) => {
332
343
  if (this.abortController.signal.aborted) return;
333
344
  const failure = asError(error);
345
+ this.reportOpenFailure(failure);
334
346
  if (failure instanceof EditableArtifactSyncError && failure.code === "unsupported_protocol") {
335
347
  this.lastError = failure;
336
348
  this.setState("unsupported");
@@ -664,7 +676,8 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
664
676
  try {
665
677
  await this.restoreReplica(stored);
666
678
  await this.replacePendingOverlay();
667
- } catch {
679
+ } catch (error) {
680
+ this.reportOpenFailure(error);
668
681
  await this.kernel.reset();
669
682
  this.replica = null;
670
683
  this.retainedTailTransactions = 0;
@@ -680,8 +693,8 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
680
693
  stored.snapshot,
681
694
  this.artifactId,
682
695
  this.modality,
683
- this.protocolVersion,
684
- this.kernelVersion,
696
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
697
+ SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
685
698
  this.modelSchemaVersion,
686
699
  this.maxSnapshotBytes,
687
700
  );
@@ -706,7 +719,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
706
719
  transaction,
707
720
  this.artifactId,
708
721
  this.modality,
709
- this.protocolVersion,
722
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
710
723
  this.maxCommittedTransactionBytes,
711
724
  );
712
725
  if (transaction.startSequence !== cursor + 1) {
@@ -776,6 +789,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
776
789
  delayMs = this.reconnectDelayMs;
777
790
  }
778
791
  const failure = asError(error);
792
+ this.reportOpenFailure(failure);
779
793
  this.lastError = failure;
780
794
  if (failure instanceof EditableArtifactSyncError && failure.requiresSnapshot) {
781
795
  this.requireSnapshot = true;
@@ -956,14 +970,11 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
956
970
  if (bootstrap.modality !== this.modality) {
957
971
  throw invalidBootstrap("bootstrap modality does not match the controller");
958
972
  }
959
- this.requireProtocol(bootstrap.protocolVersion);
960
- if (
961
- bootstrap.kernelVersion !== this.kernelVersion ||
962
- bootstrap.modelSchemaVersion !== this.modelSchemaVersion
963
- ) {
973
+ this.requireLiveProtocol(bootstrap.liveProtocolVersion);
974
+ if (bootstrap.modelSchemaVersion !== this.modelSchemaVersion) {
964
975
  throw new EditableArtifactSyncError(
965
976
  "unsupported_protocol",
966
- `artifact kernel/schema ${bootstrap.kernelVersion}/${bootstrap.modelSchemaVersion} is incompatible with ${this.kernelVersion}/${this.modelSchemaVersion}`,
977
+ `artifact schema ${bootstrap.modelSchemaVersion} is incompatible with ${this.modelSchemaVersion}`,
967
978
  );
968
979
  }
969
980
  requireSha256(bootstrap.headStateHash, "bootstrap.headStateHash");
@@ -1035,8 +1046,8 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1035
1046
  snapshot,
1036
1047
  this.artifactId,
1037
1048
  this.modality,
1038
- this.protocolVersion,
1039
- this.kernelVersion,
1049
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1050
+ SPREADSHEET_COLLABORATION_SNAPSHOT_VERSION,
1040
1051
  this.modelSchemaVersion,
1041
1052
  this.maxSnapshotBytes,
1042
1053
  );
@@ -1136,7 +1147,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1136
1147
  transaction,
1137
1148
  this.artifactId,
1138
1149
  this.modality,
1139
- this.protocolVersion,
1150
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1140
1151
  this.maxCommittedTransactionBytes,
1141
1152
  );
1142
1153
  pageBytes += estimateCommittedTransactionBytes(transaction);
@@ -1167,7 +1178,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1167
1178
  message,
1168
1179
  this.artifactId,
1169
1180
  this.modality,
1170
- this.protocolVersion,
1181
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1171
1182
  this.maxCommittedTransactionBytes,
1172
1183
  );
1173
1184
  bytes = estimateLiveMessageBytes(message);
@@ -1271,7 +1282,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1271
1282
  transaction,
1272
1283
  this.artifactId,
1273
1284
  this.modality,
1274
- this.protocolVersion,
1285
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1275
1286
  this.maxCommittedTransactionBytes,
1276
1287
  );
1277
1288
  if (embeddedPending) {
@@ -1568,7 +1579,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1568
1579
  receipt.committed,
1569
1580
  this.artifactId,
1570
1581
  this.modality,
1571
- this.protocolVersion,
1582
+ COMMITTED_TRANSACTION_PROTOCOL_VERSION,
1572
1583
  this.maxCommittedTransactionBytes,
1573
1584
  );
1574
1585
  this.recordAcceptedMapping(receipt);
@@ -1641,7 +1652,7 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1641
1652
  "sync ticket is not bound to the requested writer replica",
1642
1653
  );
1643
1654
  }
1644
- this.requireProtocol(ticket.protocolVersion);
1655
+ this.requireLiveProtocol(ticket.protocolVersion);
1645
1656
  boundedNonEmpty(ticket.token, "ticket.token", MAX_TICKET_BYTES);
1646
1657
  const expiresAt = Date.parse(ticket.expiresAt);
1647
1658
  if (!Number.isFinite(expiresAt)) {
@@ -1758,11 +1769,11 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1758
1769
  );
1759
1770
  }
1760
1771
 
1761
- private requireProtocol(actual: number): void {
1762
- if (actual !== this.protocolVersion) {
1772
+ private requireLiveProtocol(actual: number): void {
1773
+ if (actual !== EDITABLE_ARTIFACT_LIVE_WIRE_VERSION) {
1763
1774
  throw new EditableArtifactSyncError(
1764
1775
  "unsupported_protocol",
1765
- `artifact protocol ${actual} is incompatible with client ${this.protocolVersion}`,
1776
+ `live protocol ${actual} is incompatible with ${EDITABLE_ARTIFACT_LIVE_WIRE_VERSION}`,
1766
1777
  );
1767
1778
  }
1768
1779
  }
@@ -1794,6 +1805,16 @@ class EditableArtifactSyncControllerImpl implements EditableArtifactSyncControll
1794
1805
  this.liveWaiters.clear();
1795
1806
  }
1796
1807
 
1808
+ private reportOpenFailure(error: unknown): void {
1809
+ const event = classifyEditableArtifactOpenFailure(error);
1810
+ if (event === null || this.onOpenFailure === undefined) return;
1811
+ try {
1812
+ this.onOpenFailure(event);
1813
+ } catch {
1814
+ // Observability must never change recovery or availability behavior.
1815
+ }
1816
+ }
1817
+
1797
1818
  private fail(error: Error): void {
1798
1819
  this.lastError = error;
1799
1820
  this.setState("failed");
@@ -1971,7 +1992,7 @@ function validateCommittedTransaction(
1971
1992
  transaction: EditableArtifactCommittedTransaction,
1972
1993
  artifactId: EditableArtifactId,
1973
1994
  modality: EditableArtifactModality,
1974
- protocolVersion: number,
1995
+ operationProtocolVersion: number,
1975
1996
  maxCommittedTransactionBytes: number,
1976
1997
  ): Readonly<{
1977
1998
  clientTransactionId: string;
@@ -2000,10 +2021,10 @@ function validateCommittedTransaction(
2000
2021
  throw invalidSequence("transaction committedTransactionBytes exceeds the configured bound");
2001
2022
  }
2002
2023
  if (transaction.modality === "spreadsheet") {
2003
- if (transaction.protocolVersion !== protocolVersion) {
2024
+ if (transaction.operationProtocolVersion !== operationProtocolVersion) {
2004
2025
  throw new EditableArtifactSyncError(
2005
2026
  "unsupported_protocol",
2006
- `transaction protocol ${transaction.protocolVersion} is incompatible with client ${protocolVersion}`,
2027
+ `transaction operation protocol ${transaction.operationProtocolVersion} is incompatible with ${operationProtocolVersion}`,
2007
2028
  );
2008
2029
  }
2009
2030
  validateFrontier(transaction.causalFrontier, "causalFrontier");
@@ -2015,7 +2036,7 @@ function validateCommittedTransaction(
2015
2036
  }
2016
2037
  if (
2017
2038
  summary.transactionId !== transaction.transactionId ||
2018
- summary.operationProtocolVersion !== transaction.protocolVersion ||
2039
+ summary.operationProtocolVersion !== transaction.operationProtocolVersion ||
2019
2040
  summary.priorStateHash !== transaction.priorStateHash ||
2020
2041
  summary.stateHash !== transaction.stateHash ||
2021
2042
  !frontiersEqual(summary.resultingCausalFrontier, transaction.causalFrontier)
@@ -2061,8 +2082,8 @@ function validateSnapshot(
2061
2082
  snapshot: EditableArtifactStoredReplica["snapshot"],
2062
2083
  artifactId: EditableArtifactId,
2063
2084
  modality: EditableArtifactModality,
2064
- protocolVersion: number,
2065
- kernelVersion: string,
2085
+ operationProtocolVersion: number,
2086
+ snapshotVersion: number,
2066
2087
  modelSchemaVersion: number,
2067
2088
  maxSnapshotBytes: number,
2068
2089
  ): void {
@@ -2076,22 +2097,22 @@ function validateSnapshot(
2076
2097
  positiveSafeInteger(snapshot.modelSchemaVersion, "snapshot.modelSchemaVersion");
2077
2098
  if (snapshot.modality === "spreadsheet") {
2078
2099
  validateFrontier(snapshot.causalFrontier, "snapshot.causalFrontier");
2079
- if (snapshot.protocolVersion !== protocolVersion) {
2100
+ if (
2101
+ snapshot.operationProtocolVersion !== operationProtocolVersion ||
2102
+ snapshot.snapshotVersion !== snapshotVersion
2103
+ ) {
2080
2104
  throw new EditableArtifactSyncError(
2081
2105
  "unsupported_protocol",
2082
- `snapshot protocol ${snapshot.protocolVersion} is incompatible with client ${protocolVersion}`,
2106
+ `snapshot operation/state versions ${snapshot.operationProtocolVersion}/${snapshot.snapshotVersion} are incompatible with ${operationProtocolVersion}/${snapshotVersion}`,
2083
2107
  );
2084
2108
  }
2085
2109
  } else {
2086
2110
  requireNativeRevision(snapshot.nativeRevision, "snapshot.nativeRevision");
2087
2111
  }
2088
- if (
2089
- snapshot.kernelVersion !== kernelVersion ||
2090
- snapshot.modelSchemaVersion !== modelSchemaVersion
2091
- ) {
2112
+ if (snapshot.modelSchemaVersion !== modelSchemaVersion) {
2092
2113
  throw new EditableArtifactSyncError(
2093
2114
  "unsupported_protocol",
2094
- `snapshot kernel/schema ${snapshot.kernelVersion}/${snapshot.modelSchemaVersion} is incompatible with ${kernelVersion}/${modelSchemaVersion}`,
2115
+ `snapshot schema ${snapshot.modelSchemaVersion} is incompatible with ${modelSchemaVersion}`,
2095
2116
  );
2096
2117
  }
2097
2118
  if (!(snapshot.bytes instanceof Uint8Array)) {
@@ -2131,7 +2152,7 @@ function validateLiveMessage(
2131
2152
  message: EditableArtifactLiveMessage,
2132
2153
  artifactId: EditableArtifactId,
2133
2154
  modality: EditableArtifactModality,
2134
- protocolVersion: number,
2155
+ operationProtocolVersion: number,
2135
2156
  maxCommittedTransactionBytes: number,
2136
2157
  ): void {
2137
2158
  if (!message || typeof message !== "object") {
@@ -2142,7 +2163,7 @@ function validateLiveMessage(
2142
2163
  message.transaction,
2143
2164
  artifactId,
2144
2165
  modality,
2145
- protocolVersion,
2166
+ operationProtocolVersion,
2146
2167
  maxCommittedTransactionBytes,
2147
2168
  );
2148
2169
  return;
@@ -2198,7 +2219,7 @@ function estimateCommittedTransactionBytes(
2198
2219
  function requireStateHash(actual: string, expected: string, source: string): void {
2199
2220
  if (actual !== expected) {
2200
2221
  throw new EditableArtifactSyncError(
2201
- "kernel_diverged",
2222
+ "authored_causal_mismatch",
2202
2223
  `${source} produced state hash ${actual}; expected ${expected}`,
2203
2224
  { retryable: true, requiresSnapshot: true },
2204
2225
  );
@@ -2209,7 +2230,7 @@ function requireDigest(actual: string, expected: string, source: string): void {
2209
2230
  requireSha256(actual, `${source} computed digest`);
2210
2231
  if (actual !== expected) {
2211
2232
  throw new EditableArtifactSyncError(
2212
- "kernel_diverged",
2233
+ "byte_corruption",
2213
2234
  `${source} bytes produced digest ${actual}; expected ${expected}`,
2214
2235
  { retryable: true, requiresSnapshot: true },
2215
2236
  );
@@ -2230,7 +2251,7 @@ function requireFrontier(
2230
2251
  )
2231
2252
  ) {
2232
2253
  throw new EditableArtifactSyncError(
2233
- "kernel_diverged",
2254
+ "authored_causal_mismatch",
2234
2255
  `${source} causal frontier does not match the authoritative barrier`,
2235
2256
  { retryable: true, requiresSnapshot: true },
2236
2257
  );
@@ -2416,7 +2437,7 @@ function requireNativeRevisionEqual(actual: number, expected: number, source: st
2416
2437
  requireNativeRevision(expected, `${source} expected native revision`);
2417
2438
  if (actual !== expected) {
2418
2439
  throw new EditableArtifactSyncError(
2419
- "kernel_diverged",
2440
+ "authored_causal_mismatch",
2420
2441
  `${source} native revision ${actual} does not match ${expected}`,
2421
2442
  { retryable: true, requiresSnapshot: true },
2422
2443
  );
@@ -3,6 +3,8 @@ export type EditableArtifactSyncErrorCode =
3
3
  | "invalid_bootstrap"
4
4
  | "invalid_sequence"
5
5
  | "kernel_diverged"
6
+ | "byte_corruption"
7
+ | "authored_causal_mismatch"
6
8
  | "queue_overflow"
7
9
  | "resync_required"
8
10
  | "permission_changed"