@opengeni/artifact-tool 0.2.2

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 (203) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +104 -0
  3. package/dist/bounded-zip.d.ts +28 -0
  4. package/dist/chunk-4BSENMAE.js +39 -0
  5. package/dist/chunk-4BSENMAE.js.map +1 -0
  6. package/dist/chunk-4FRFNNBM.js +276 -0
  7. package/dist/chunk-4FRFNNBM.js.map +1 -0
  8. package/dist/chunk-5XIA6KHZ.js +49 -0
  9. package/dist/chunk-5XIA6KHZ.js.map +1 -0
  10. package/dist/chunk-66CT2ZRP.js +2229 -0
  11. package/dist/chunk-66CT2ZRP.js.map +1 -0
  12. package/dist/chunk-72SYDU4O.js +319 -0
  13. package/dist/chunk-72SYDU4O.js.map +1 -0
  14. package/dist/chunk-B2ZJRZDA.js +279 -0
  15. package/dist/chunk-B2ZJRZDA.js.map +1 -0
  16. package/dist/chunk-DCTLTNPW.js +2458 -0
  17. package/dist/chunk-DCTLTNPW.js.map +1 -0
  18. package/dist/chunk-FGZM4FQ6.js +473 -0
  19. package/dist/chunk-FGZM4FQ6.js.map +1 -0
  20. package/dist/chunk-HTJBLBDE.js +591 -0
  21. package/dist/chunk-HTJBLBDE.js.map +1 -0
  22. package/dist/chunk-IYFXZQ34.js +5306 -0
  23. package/dist/chunk-IYFXZQ34.js.map +1 -0
  24. package/dist/chunk-J6N7IR44.js +172 -0
  25. package/dist/chunk-J6N7IR44.js.map +1 -0
  26. package/dist/chunk-N2IHNWBS.js +1002 -0
  27. package/dist/chunk-N2IHNWBS.js.map +1 -0
  28. package/dist/chunk-NAG44K67.js +549 -0
  29. package/dist/chunk-NAG44K67.js.map +1 -0
  30. package/dist/chunk-O24KTEGP.js +33 -0
  31. package/dist/chunk-O24KTEGP.js.map +1 -0
  32. package/dist/chunk-PI2AZVGK.js +2074 -0
  33. package/dist/chunk-PI2AZVGK.js.map +1 -0
  34. package/dist/chunk-Q2FFHEY7.js +40 -0
  35. package/dist/chunk-Q2FFHEY7.js.map +1 -0
  36. package/dist/chunk-RBGQADXA.js +57 -0
  37. package/dist/chunk-RBGQADXA.js.map +1 -0
  38. package/dist/chunk-U5G2BKM2.js +613 -0
  39. package/dist/chunk-U5G2BKM2.js.map +1 -0
  40. package/dist/chunk-VOJHMSM3.js +39 -0
  41. package/dist/chunk-VOJHMSM3.js.map +1 -0
  42. package/dist/chunk-VUNV25KB.js +16 -0
  43. package/dist/chunk-VUNV25KB.js.map +1 -0
  44. package/dist/chunk-XIFUNVBO.js +101 -0
  45. package/dist/chunk-XIFUNVBO.js.map +1 -0
  46. package/dist/chunk-ZCJYRJGB.js +489 -0
  47. package/dist/chunk-ZCJYRJGB.js.map +1 -0
  48. package/dist/chunk-ZZXPEE4S.js +2260 -0
  49. package/dist/chunk-ZZXPEE4S.js.map +1 -0
  50. package/dist/document-docx-api.d.ts +49 -0
  51. package/dist/document-docx-codec.d.ts +9 -0
  52. package/dist/document-docx-codec.js +4765 -0
  53. package/dist/document-docx-codec.js.map +1 -0
  54. package/dist/document-docx-import.d.ts +261 -0
  55. package/dist/document-docx-packer.d.ts +18 -0
  56. package/dist/document-docx-state.d.ts +12 -0
  57. package/dist/document-render.d.ts +3 -0
  58. package/dist/document-render.js +492 -0
  59. package/dist/document-render.js.map +1 -0
  60. package/dist/document.d.ts +432 -0
  61. package/dist/document.js +37 -0
  62. package/dist/document.js.map +1 -0
  63. package/dist/errors.d.ts +23 -0
  64. package/dist/file-blob.d.ts +11 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.js +186 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/inspection.d.ts +12 -0
  69. package/dist/kernel.d.ts +101 -0
  70. package/dist/lib-GQLPACFK.js +9746 -0
  71. package/dist/lib-GQLPACFK.js.map +1 -0
  72. package/dist/materializer-cli-entry.d.ts +2 -0
  73. package/dist/materializer-cli-entry.js +29 -0
  74. package/dist/materializer-cli-entry.js.map +1 -0
  75. package/dist/materializer-cli.d.ts +24 -0
  76. package/dist/materializer-cli.js +24 -0
  77. package/dist/materializer-cli.js.map +1 -0
  78. package/dist/native-raster.d.ts +10 -0
  79. package/dist/native.d.ts +129 -0
  80. package/dist/native.js +31 -0
  81. package/dist/native.js.map +1 -0
  82. package/dist/office-import.d.ts +48 -0
  83. package/dist/office-import.js +154 -0
  84. package/dist/office-import.js.map +1 -0
  85. package/dist/presentation-pptx-api.d.ts +60 -0
  86. package/dist/presentation-pptx-import.d.ts +5 -0
  87. package/dist/presentation-pptx-state-digest-765CZZED.js +10 -0
  88. package/dist/presentation-pptx-state-digest-765CZZED.js.map +1 -0
  89. package/dist/presentation-pptx-state-digest.d.ts +3 -0
  90. package/dist/presentation-pptx-state.d.ts +4 -0
  91. package/dist/presentation-pptx-xml.d.ts +29 -0
  92. package/dist/presentation-pptx.d.ts +5 -0
  93. package/dist/presentation-pptx.js +2365 -0
  94. package/dist/presentation-pptx.js.map +1 -0
  95. package/dist/presentation-render.d.ts +29 -0
  96. package/dist/presentation-render.js +375 -0
  97. package/dist/presentation-render.js.map +1 -0
  98. package/dist/presentation.d.ts +565 -0
  99. package/dist/presentation.js +71 -0
  100. package/dist/presentation.js.map +1 -0
  101. package/dist/production-composite.d.ts +68 -0
  102. package/dist/production-document.d.ts +2 -0
  103. package/dist/production-document.js +176 -0
  104. package/dist/production-document.js.map +1 -0
  105. package/dist/production-native-codecs.d.ts +94 -0
  106. package/dist/production-presentation.d.ts +2 -0
  107. package/dist/production-presentation.js +176 -0
  108. package/dist/production-presentation.js.map +1 -0
  109. package/dist/production-sha256.d.ts +2 -0
  110. package/dist/production-spreadsheet-native.d.ts +12 -0
  111. package/dist/production-spreadsheet.d.ts +2 -0
  112. package/dist/production-spreadsheet.js +176 -0
  113. package/dist/production-spreadsheet.js.map +1 -0
  114. package/dist/production.d.ts +96 -0
  115. package/dist/raster-image.d.ts +34 -0
  116. package/dist/reference.d.ts +17 -0
  117. package/dist/reference.js +574 -0
  118. package/dist/reference.js.map +1 -0
  119. package/dist/runtime-cli-entry.d.ts +2 -0
  120. package/dist/runtime-cli-entry.js +21 -0
  121. package/dist/runtime-cli-entry.js.map +1 -0
  122. package/dist/runtime-cli.d.ts +76 -0
  123. package/dist/runtime-cli.js +26 -0
  124. package/dist/runtime-cli.js.map +1 -0
  125. package/dist/runtime-development.d.ts +73 -0
  126. package/dist/runtime-development.js +30 -0
  127. package/dist/runtime-development.js.map +1 -0
  128. package/dist/runtime-receipt.d.ts +19 -0
  129. package/dist/runtime.d.ts +355 -0
  130. package/dist/runtime.js +42 -0
  131. package/dist/runtime.js.map +1 -0
  132. package/dist/snapshot.d.ts +26 -0
  133. package/dist/snapshot.js +61 -0
  134. package/dist/snapshot.js.map +1 -0
  135. package/dist/spreadsheet-address.d.ts +18 -0
  136. package/dist/spreadsheet-file.d.ts +17 -0
  137. package/dist/spreadsheet-formula.d.ts +67 -0
  138. package/dist/spreadsheet-image.d.ts +17 -0
  139. package/dist/spreadsheet-render.d.ts +5 -0
  140. package/dist/spreadsheet-render.js +752 -0
  141. package/dist/spreadsheet-render.js.map +1 -0
  142. package/dist/spreadsheet-types.d.ts +155 -0
  143. package/dist/spreadsheet-xlsx-api.d.ts +88 -0
  144. package/dist/spreadsheet-xlsx-codec.d.ts +16 -0
  145. package/dist/spreadsheet-xlsx-codec.js +16 -0
  146. package/dist/spreadsheet-xlsx-codec.js.map +1 -0
  147. package/dist/spreadsheet-xlsx-state.d.ts +16 -0
  148. package/dist/spreadsheet.d.ts +724 -0
  149. package/dist/spreadsheet.js +67 -0
  150. package/dist/spreadsheet.js.map +1 -0
  151. package/package.json +129 -0
  152. package/src/bounded-zip.ts +450 -0
  153. package/src/document-docx-api.ts +64 -0
  154. package/src/document-docx-codec.ts +2253 -0
  155. package/src/document-docx-import.ts +3814 -0
  156. package/src/document-docx-packer.ts +72 -0
  157. package/src/document-docx-state.ts +59 -0
  158. package/src/document-render.ts +615 -0
  159. package/src/document.ts +2800 -0
  160. package/src/errors.ts +45 -0
  161. package/src/file-blob.ts +43 -0
  162. package/src/index.ts +4 -0
  163. package/src/inspection.ts +50 -0
  164. package/src/kernel.ts +525 -0
  165. package/src/materializer-cli-entry.ts +11 -0
  166. package/src/materializer-cli.ts +743 -0
  167. package/src/native-raster.ts +141 -0
  168. package/src/native.ts +390 -0
  169. package/src/office-import.ts +243 -0
  170. package/src/presentation-pptx-api.ts +107 -0
  171. package/src/presentation-pptx-import.ts +2065 -0
  172. package/src/presentation-pptx-state-digest.ts +46 -0
  173. package/src/presentation-pptx-state.ts +18 -0
  174. package/src/presentation-pptx-xml.ts +473 -0
  175. package/src/presentation-pptx.ts +447 -0
  176. package/src/presentation-render.ts +532 -0
  177. package/src/presentation.ts +2599 -0
  178. package/src/production-composite.ts +805 -0
  179. package/src/production-document.ts +2 -0
  180. package/src/production-native-codecs.ts +1141 -0
  181. package/src/production-presentation.ts +2 -0
  182. package/src/production-sha256.ts +150 -0
  183. package/src/production-spreadsheet-native.ts +686 -0
  184. package/src/production-spreadsheet.ts +2 -0
  185. package/src/production.ts +1022 -0
  186. package/src/raster-image.ts +586 -0
  187. package/src/reference.ts +17 -0
  188. package/src/runtime-cli-entry.ts +13 -0
  189. package/src/runtime-cli.ts +670 -0
  190. package/src/runtime-development.ts +689 -0
  191. package/src/runtime-receipt.ts +169 -0
  192. package/src/runtime.ts +1463 -0
  193. package/src/snapshot.ts +88 -0
  194. package/src/spreadsheet-address.ts +90 -0
  195. package/src/spreadsheet-file.ts +71 -0
  196. package/src/spreadsheet-formula.ts +1212 -0
  197. package/src/spreadsheet-image.ts +242 -0
  198. package/src/spreadsheet-render.ts +985 -0
  199. package/src/spreadsheet-types.ts +171 -0
  200. package/src/spreadsheet-xlsx-api.ts +123 -0
  201. package/src/spreadsheet-xlsx-codec.ts +3063 -0
  202. package/src/spreadsheet-xlsx-state.ts +191 -0
  203. package/src/spreadsheet.ts +5195 -0
@@ -0,0 +1,743 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import { once } from "node:events";
5
+
6
+ import {
7
+ SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES,
8
+ SPREADSHEET_ARTIFACT_VIEWPORT_MAX_CELLS,
9
+ decodeSpreadsheetMetadataKernelProjection,
10
+ decodeSpreadsheetViewportKernelProjection,
11
+ encodeSpreadsheetMetadataKernelQuery,
12
+ encodeSpreadsheetViewportKernelQuery,
13
+ type SpreadsheetArtifactProjectedCellValue,
14
+ type SpreadsheetArtifactFormulaError,
15
+ } from "@opengeni/contracts/editable-artifacts";
16
+
17
+ import { NativeSpreadsheetSession } from "./native";
18
+ import { SpreadsheetXlsxCodec } from "./spreadsheet-xlsx-codec";
19
+ import { Workbook, type SerializedWorkbook } from "./spreadsheet";
20
+ import { loadConfiguredArtifactKernelRuntime } from "./runtime-development";
21
+ import type { ArtifactKernelRuntime } from "./runtime";
22
+
23
+ const IDENTITY_ARGUMENT = "--opengeni-materializer-identity-v1";
24
+ const MATERIALIZE_ARGUMENT = "--opengeni-materialize-v1";
25
+ const VERIFY_ARGUMENT = "--opengeni-verify-materialization-v1";
26
+ const INPUT_MAGIC = bytes("OGAMI001");
27
+ const VERIFY_INPUT_MAGIC = bytes("OGAVI001");
28
+ const OUTPUT_MAGIC = bytes("OGAMO001");
29
+ const VERIFY_OUTPUT_MAGIC = bytes("OGAVO001");
30
+ const ERROR_MAGIC = bytes("OGAME001");
31
+ const MAX_METADATA_BYTES = 256 * 1024;
32
+ const MAX_SOURCE_BYTES = 512 * 1024 * 1024;
33
+ const MAX_OUTPUT_BYTES = 512 * 1024 * 1024;
34
+ const MAX_PROJECTED_RECTANGLE_CELLS = 1_000_000;
35
+ const VIEWPORT_EDGE = 128;
36
+ const XLSX_CODEC_ID = "opengeni.xlsx";
37
+ const EMPTY_FONT_REGISTRY_HASH = sha256(bytes("opengeni:no-font-registry:v1"));
38
+ const POLICY_HASH = sha256(bytes("opengeni:materializer:xlsx-native-projection:v1"));
39
+
40
+ export type EditableArtifactNativeMaterializerCapabilities = Readonly<{
41
+ protocol: "OGAMC001";
42
+ runtimeKind: "native";
43
+ runtimeTarget: string;
44
+ kernelVersion: string;
45
+ codecVersions: Readonly<Record<typeof XLSX_CODEC_ID, string>>;
46
+ fontRegistryHash: string;
47
+ policyHash: string;
48
+ maxOutputBytes: number;
49
+ supportedModelSchemaVersions: readonly [1];
50
+ supportedOperationProtocolVersions: readonly [1];
51
+ supportedSnapshotProtocolVersions: readonly [1];
52
+ }>;
53
+
54
+ type MaterializationManifest = Readonly<{
55
+ protocol: "OGAMJ001";
56
+ artifactId: string;
57
+ jobId: string;
58
+ versionId: string;
59
+ modality: "spreadsheet" | "document" | "presentation";
60
+ inputSnapshotId: string;
61
+ targetHeadSequence: number;
62
+ stateHash: string;
63
+ sourceByteSize: number;
64
+ sourceContentHash: string;
65
+ modelSchemaVersion: number;
66
+ operationProtocolVersion: number;
67
+ snapshotProtocolVersion: number;
68
+ format: "xlsx" | "pptx" | "docx" | "pdf" | "png" | "webp";
69
+ codecId: string;
70
+ normalizedOptions: Readonly<Record<string, never>>;
71
+ optionsHash: string;
72
+ codecVersion: string;
73
+ kernelVersion: string;
74
+ fontRegistryHash: string;
75
+ policyHash: string;
76
+ }>;
77
+
78
+ type SemanticCell = Readonly<{
79
+ row: number;
80
+ column: number;
81
+ formula: string | null;
82
+ value: SpreadsheetArtifactProjectedCellValue;
83
+ }>;
84
+
85
+ type SemanticWorkbook = Readonly<{
86
+ version: 1;
87
+ sheets: readonly Readonly<{ name: string; cells: readonly SemanticCell[] }>[];
88
+ }>;
89
+
90
+ type ProjectedWorkbook = Readonly<{
91
+ workbook: Workbook;
92
+ semanticHash: string;
93
+ }>;
94
+
95
+ type NativeSourceDiagnosticSubcode = "input_framing" | "snapshot_open" | "state_mismatch";
96
+
97
+ export async function runArtifactMaterializerCli(
98
+ args: readonly string[],
99
+ environment: Readonly<Record<string, string | undefined>> = process.env,
100
+ ): Promise<void> {
101
+ if (args.length !== 1) throw new Error("unsupported materializer command");
102
+ const command = args[0];
103
+ if (command === IDENTITY_ARGUMENT) {
104
+ const capabilities = await loadCapabilities(environment);
105
+ process.stdout.write(JSON.stringify(capabilities));
106
+ return;
107
+ }
108
+ if (command === MATERIALIZE_ARGUMENT) {
109
+ try {
110
+ await materialize(environment);
111
+ } catch (error) {
112
+ if (!(error instanceof MaterializerCliError)) throw error;
113
+ await writeTypedError(error.code, error.diagnosticSubcode);
114
+ }
115
+ return;
116
+ }
117
+ if (command === VERIFY_ARGUMENT) {
118
+ try {
119
+ await verifyMaterialization(environment);
120
+ } catch (error) {
121
+ if (!(error instanceof MaterializerCliError)) throw error;
122
+ await writeTypedError(error.code, error.diagnosticSubcode);
123
+ }
124
+ return;
125
+ }
126
+ throw new Error("unsupported materializer command");
127
+ }
128
+
129
+ async function loadCapabilities(
130
+ environment: Readonly<Record<string, string | undefined>>,
131
+ ): Promise<EditableArtifactNativeMaterializerCapabilities> {
132
+ const loaded = await loadRuntime(environment);
133
+ return editableArtifactMaterializerCapabilitiesForRuntime(
134
+ loaded.runtime,
135
+ loaded.location.artifactTool.packageVersion,
136
+ );
137
+ }
138
+
139
+ async function materialize(
140
+ environment: Readonly<Record<string, string | undefined>>,
141
+ ): Promise<void> {
142
+ let manifest: MaterializationManifest;
143
+ let source: Uint8Array;
144
+ try {
145
+ const reader = new InputReader(process.stdin);
146
+ assertBytes(await reader.readExactly(INPUT_MAGIC.byteLength), INPUT_MAGIC, "input protocol");
147
+ const header = await reader.readExactly(12);
148
+ const headerView = new DataView(header.buffer, header.byteOffset, header.byteLength);
149
+ const metadataLength = headerView.getUint32(0, true);
150
+ const sourceLength = safeU64(headerView, 4, "source length");
151
+ if (metadataLength < 2 || metadataLength > MAX_METADATA_BYTES) throw invalidSource();
152
+ if (sourceLength <= 0 || sourceLength > MAX_SOURCE_BYTES) throw unsupported();
153
+ manifest = decodeManifest(await reader.readExactly(metadataLength));
154
+ if (sourceLength !== manifest.sourceByteSize) throw invalidSource();
155
+ source = await reader.readExactly(sourceLength);
156
+ await reader.assertEof();
157
+ if (sha256(source) !== manifest.sourceContentHash) throw invalidSource();
158
+ } catch (error) {
159
+ throw classifyInvalidSource(error, "input_framing");
160
+ }
161
+
162
+ const loaded = await loadRuntime(environment);
163
+ const capabilities = editableArtifactMaterializerCapabilitiesForRuntime(
164
+ loaded.runtime,
165
+ loaded.location.artifactTool.packageVersion,
166
+ );
167
+ assertManifestCompatibility(manifest, capabilities);
168
+ if (manifest.modality !== "spreadsheet" || manifest.format !== "xlsx") throw unsupported();
169
+
170
+ const projected = projectSpreadsheet(loaded.runtime, source, manifest);
171
+ const outputBlob = await SpreadsheetXlsxCodec.exportXlsx(projected.workbook, {
172
+ unsupportedContent: "error",
173
+ fileName: "workbook.xlsx",
174
+ });
175
+ const output = new Uint8Array(await outputBlob.arrayBuffer());
176
+ if (output.byteLength <= 0 || output.byteLength > MAX_OUTPUT_BYTES) throw unsupported();
177
+ // Export/import equivalence is checked here for early failure and again by a
178
+ // distinct verifier process after immutable object publication.
179
+ const imported = await SpreadsheetXlsxCodec.importXlsx(output, { unsupportedContent: "error" });
180
+ if (semanticHashForWorkbook(imported) !== projected.semanticHash) throw unsupported();
181
+
182
+ const metadata = canonicalBytes({
183
+ byteSize: output.byteLength,
184
+ codecId: manifest.codecId,
185
+ codecVersion: manifest.codecVersion,
186
+ contentHash: sha256(output),
187
+ fontRegistryHash: manifest.fontRegistryHash,
188
+ format: manifest.format,
189
+ headSequence: manifest.targetHeadSequence,
190
+ kernelVersion: manifest.kernelVersion,
191
+ mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
192
+ policyHash: manifest.policyHash,
193
+ protocol: "OGAMR001",
194
+ semanticHash: projected.semanticHash,
195
+ stateHash: manifest.stateHash,
196
+ });
197
+ await writeFrame(OUTPUT_MAGIC, metadata, output);
198
+ }
199
+
200
+ async function verifyMaterialization(
201
+ environment: Readonly<Record<string, string | undefined>>,
202
+ ): Promise<void> {
203
+ const reader = new InputReader(process.stdin);
204
+ assertBytes(
205
+ await reader.readExactly(VERIFY_INPUT_MAGIC.byteLength),
206
+ VERIFY_INPUT_MAGIC,
207
+ "verification protocol",
208
+ );
209
+ const header = await reader.readExactly(12);
210
+ const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
211
+ const metadataLength = view.getUint32(0, true);
212
+ const outputLength = safeU64(view, 4, "verification output length");
213
+ if (metadataLength < 2 || metadataLength > MAX_METADATA_BYTES) throw invalidSource();
214
+ if (outputLength <= 0 || outputLength > MAX_OUTPUT_BYTES) throw unsupported();
215
+ const metadata = decodeVerificationManifest(await reader.readExactly(metadataLength));
216
+ const output = await reader.readExactly(outputLength);
217
+ await reader.assertEof();
218
+ const loaded = await loadCapabilities(environment);
219
+ if (
220
+ metadata.format !== "xlsx" ||
221
+ metadata.codecId !== XLSX_CODEC_ID ||
222
+ loaded.codecVersions[XLSX_CODEC_ID] !== metadata.codecVersion
223
+ ) {
224
+ throw unsupported();
225
+ }
226
+ let semanticHash: string;
227
+ try {
228
+ const imported = await SpreadsheetXlsxCodec.importXlsx(output, { unsupportedContent: "error" });
229
+ semanticHash = semanticHashForWorkbook(imported);
230
+ } catch {
231
+ throw invalidOutput();
232
+ }
233
+ if (semanticHash !== metadata.expectedSemanticHash) throw invalidOutput();
234
+ await writeFrame(
235
+ VERIFY_OUTPUT_MAGIC,
236
+ canonicalBytes({ protocol: "OGAVR001", semanticHash }),
237
+ new Uint8Array(0),
238
+ );
239
+ }
240
+
241
+ function projectSpreadsheet(
242
+ runtime: ArtifactKernelRuntime,
243
+ source: Uint8Array,
244
+ manifest: MaterializationManifest,
245
+ ): ProjectedWorkbook {
246
+ let session: NativeSpreadsheetSession;
247
+ try {
248
+ session = NativeSpreadsheetSession.open(runtime, source);
249
+ } catch {
250
+ throw invalidSource("snapshot_open");
251
+ }
252
+ try {
253
+ if (session.stateHash() !== manifest.stateHash) throw invalidSource("state_mismatch");
254
+ const metadataLimit = Math.min(
255
+ session.capabilities.maxQueryResponseBytes,
256
+ SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES,
257
+ );
258
+ const metadata = decodeSpreadsheetMetadataKernelProjection(
259
+ session.query(
260
+ encodeSpreadsheetMetadataKernelQuery({
261
+ maxSheets: Math.max(1, session.capabilities.maxMetadataSheets),
262
+ maxBytes: metadataLimit,
263
+ }),
264
+ ),
265
+ {
266
+ maxSheets: Math.max(1, session.capabilities.maxMetadataSheets),
267
+ maxBytes: metadataLimit,
268
+ },
269
+ );
270
+ if (
271
+ metadata.modeledFeatures.dimensions !== false ||
272
+ metadata.modeledFeatures.hidden !== false ||
273
+ metadata.modeledFeatures.merges !== false
274
+ ) {
275
+ throw unsupported();
276
+ }
277
+ let projectedRectangleCells = 0;
278
+ const semanticSheets: Array<{ name: string; cells: SemanticCell[] }> = [];
279
+ for (const sheet of metadata.sheets) {
280
+ const cells: SemanticCell[] = [];
281
+ if (sheet.usedBounds) {
282
+ const rows = sheet.usedBounds.endRow - sheet.usedBounds.startRow + 1;
283
+ const columns = sheet.usedBounds.endColumn - sheet.usedBounds.startColumn + 1;
284
+ const area = rows * columns;
285
+ if (!Number.isSafeInteger(area) || area <= 0) throw invalidSource();
286
+ projectedRectangleCells += area;
287
+ if (projectedRectangleCells > MAX_PROJECTED_RECTANGLE_CELLS) throw unsupported();
288
+ for (
289
+ let row = sheet.usedBounds.startRow;
290
+ row <= sheet.usedBounds.endRow;
291
+ row += VIEWPORT_EDGE
292
+ ) {
293
+ const rowCount = Math.min(VIEWPORT_EDGE, sheet.usedBounds.endRow - row + 1);
294
+ for (
295
+ let column = sheet.usedBounds.startColumn;
296
+ column <= sheet.usedBounds.endColumn;
297
+ column += VIEWPORT_EDGE
298
+ ) {
299
+ const columnCount = Math.min(VIEWPORT_EDGE, sheet.usedBounds.endColumn - column + 1);
300
+ const maxCells = rowCount * columnCount;
301
+ if (maxCells > SPREADSHEET_ARTIFACT_VIEWPORT_MAX_CELLS) throw unsupported();
302
+ const query = {
303
+ sheetId: sheet.sheetId,
304
+ startRow: row,
305
+ startColumn: column,
306
+ rowCount,
307
+ columnCount,
308
+ maxCells,
309
+ maxBytes: metadataLimit,
310
+ } as const;
311
+ const viewport = decodeSpreadsheetViewportKernelProjection(
312
+ session.query(encodeSpreadsheetViewportKernelQuery(query)),
313
+ query,
314
+ );
315
+ for (const cell of viewport.cells) {
316
+ assertRepresentableValue(cell.value);
317
+ cells.push({
318
+ row: cell.row,
319
+ column: cell.column,
320
+ formula: cell.formula,
321
+ value: cell.value,
322
+ });
323
+ }
324
+ }
325
+ }
326
+ }
327
+ cells.sort((left, right) => left.row - right.row || left.column - right.column);
328
+ semanticSheets.push({ name: sheet.name, cells });
329
+ }
330
+ const semantic: SemanticWorkbook = Object.freeze({ version: 1, sheets: semanticSheets });
331
+ const workbook = Workbook.fromJSON(toSerializedWorkbook(semantic));
332
+ const semanticHash = sha256(canonicalBytes(semantic));
333
+ // Recalculation through the host projection must agree with every native
334
+ // formula result before the Office codec is allowed to run.
335
+ if (semanticHashForWorkbook(workbook) !== semanticHash) throw unsupported();
336
+ return Object.freeze({ workbook, semanticHash });
337
+ } finally {
338
+ session.dispose();
339
+ }
340
+ }
341
+
342
+ function toSerializedWorkbook(semantic: SemanticWorkbook): SerializedWorkbook {
343
+ return {
344
+ version: 1,
345
+ worksheets: semantic.sheets.map((sheet, index) => ({
346
+ id: `ws/native-${index + 1}`,
347
+ name: sheet.name,
348
+ showGridLines: true,
349
+ freezePanes: { rows: 0, columns: 0 },
350
+ cells: sheet.cells.map((cell) => ({
351
+ row: cell.row,
352
+ col: cell.column,
353
+ value: cell.formula ? null : hostValue(cell.value),
354
+ formula: cell.formula,
355
+ format: {},
356
+ })),
357
+ merges: [],
358
+ columnWidths: [],
359
+ rowHeights: [],
360
+ tables: [],
361
+ charts: [],
362
+ sparklines: [],
363
+ dataValidations: [],
364
+ conditionalFormattings: [],
365
+ images: [],
366
+ })),
367
+ comments: [],
368
+ } as SerializedWorkbook;
369
+ }
370
+
371
+ function semanticHashForWorkbook(workbook: Workbook): string {
372
+ const sheets = workbook.worksheets.items.map((sheet) => {
373
+ const cells: SemanticCell[] = [];
374
+ for (const { row, col, data } of sheet.cellEntries()) {
375
+ const value = data.formula ? workbook.valueAt(sheet, { row, col }) : data.value;
376
+ cells.push({
377
+ row,
378
+ column: col,
379
+ formula: data.formula,
380
+ value: semanticValue(value, data.formula !== null),
381
+ });
382
+ }
383
+ cells.sort((left, right) => left.row - right.row || left.column - right.column);
384
+ return { name: sheet.name, cells };
385
+ });
386
+ return sha256(canonicalBytes({ version: 1, sheets } satisfies SemanticWorkbook));
387
+ }
388
+
389
+ function semanticValue(value: unknown, formula: boolean): SpreadsheetArtifactProjectedCellValue {
390
+ if (value === null || value === undefined) return { kind: "empty" };
391
+ if (typeof value === "boolean") return { kind: "boolean", value };
392
+ if (typeof value === "number" && Number.isFinite(value)) return { kind: "number", value };
393
+ if (value instanceof Date) {
394
+ const milliseconds = Date.prototype.getTime.call(value);
395
+ if (!Number.isSafeInteger(milliseconds)) throw unsupported();
396
+ return { kind: "date", value: new Date(milliseconds).toISOString() };
397
+ }
398
+ if (typeof value === "string") {
399
+ if (formula) {
400
+ const error = FORMULA_ERROR_TO_NATIVE[value];
401
+ if (error) return { kind: "error", value: error };
402
+ if (value === "#CYCLE!") return { kind: "error", value: { custom: value } };
403
+ }
404
+ return { kind: "text", value };
405
+ }
406
+ throw unsupported();
407
+ }
408
+
409
+ function hostValue(
410
+ value: SpreadsheetArtifactProjectedCellValue,
411
+ ): string | number | boolean | Date | null {
412
+ switch (value.kind) {
413
+ case "empty":
414
+ return null;
415
+ case "boolean":
416
+ case "number":
417
+ case "text":
418
+ return value.value;
419
+ case "date":
420
+ return new Date(value.value);
421
+ case "error": {
422
+ if (typeof value.value === "object") return value.value.custom;
423
+ const projected = NATIVE_ERROR_TO_FORMULA[value.value];
424
+ if (!projected) throw unsupported();
425
+ return projected;
426
+ }
427
+ }
428
+ }
429
+
430
+ function assertRepresentableValue(value: SpreadsheetArtifactProjectedCellValue): void {
431
+ hostValue(value);
432
+ }
433
+
434
+ const NATIVE_ERROR_TO_FORMULA: Readonly<Record<string, string>> = Object.freeze({
435
+ divide_by_zero: "#DIV/0!",
436
+ value: "#VALUE!",
437
+ reference: "#REF!",
438
+ name: "#NAME?",
439
+ number: "#NUM!",
440
+ not_available: "#N/A",
441
+ });
442
+ const FORMULA_ERROR_TO_NATIVE: Readonly<Record<string, SpreadsheetArtifactFormulaError>> =
443
+ Object.freeze({
444
+ "#DIV/0!": "divide_by_zero",
445
+ "#VALUE!": "value",
446
+ "#REF!": "reference",
447
+ "#NAME?": "name",
448
+ "#NUM!": "number",
449
+ "#N/A": "not_available",
450
+ });
451
+
452
+ /**
453
+ * Exact producer profile derived from the same verified native runtime used by
454
+ * the materializer child. The API uses this rather than accepting job identity
455
+ * facts from clients.
456
+ */
457
+ export function editableArtifactMaterializerCapabilitiesForRuntime(
458
+ runtime: ArtifactKernelRuntime,
459
+ packageVersion: string,
460
+ ): EditableArtifactNativeMaterializerCapabilities {
461
+ return Object.freeze({
462
+ protocol: "OGAMC001",
463
+ runtimeKind: "native",
464
+ runtimeTarget: runtime.target,
465
+ kernelVersion: runtime.buildIdentity,
466
+ codecVersions: Object.freeze({ [XLSX_CODEC_ID]: codecVersion(packageVersion) }),
467
+ fontRegistryHash: EMPTY_FONT_REGISTRY_HASH,
468
+ policyHash: POLICY_HASH,
469
+ maxOutputBytes: MAX_OUTPUT_BYTES,
470
+ supportedModelSchemaVersions: Object.freeze([1] as const),
471
+ supportedOperationProtocolVersions: Object.freeze([1] as const),
472
+ supportedSnapshotProtocolVersions: Object.freeze([1] as const),
473
+ });
474
+ }
475
+
476
+ function assertManifestCompatibility(
477
+ manifest: MaterializationManifest,
478
+ capabilities: EditableArtifactNativeMaterializerCapabilities,
479
+ ): void {
480
+ if (
481
+ manifest.modelSchemaVersion !== 1 ||
482
+ manifest.operationProtocolVersion !== 1 ||
483
+ manifest.snapshotProtocolVersion !== 1 ||
484
+ manifest.kernelVersion !== capabilities.kernelVersion ||
485
+ manifest.codecVersion !==
486
+ (capabilities.codecVersions as Readonly<Record<string, string>>)[manifest.codecId] ||
487
+ manifest.fontRegistryHash !== capabilities.fontRegistryHash ||
488
+ manifest.policyHash !== capabilities.policyHash ||
489
+ Object.keys(manifest.normalizedOptions).length !== 0
490
+ ) {
491
+ throw incompatible();
492
+ }
493
+ }
494
+
495
+ function decodeManifest(value: Uint8Array): MaterializationManifest {
496
+ const record = strictCanonicalRecord(value, [
497
+ "artifactId",
498
+ "codecId",
499
+ "codecVersion",
500
+ "fontRegistryHash",
501
+ "format",
502
+ "inputSnapshotId",
503
+ "jobId",
504
+ "kernelVersion",
505
+ "modality",
506
+ "modelSchemaVersion",
507
+ "normalizedOptions",
508
+ "operationProtocolVersion",
509
+ "optionsHash",
510
+ "policyHash",
511
+ "protocol",
512
+ "snapshotProtocolVersion",
513
+ "sourceByteSize",
514
+ "sourceContentHash",
515
+ "stateHash",
516
+ "targetHeadSequence",
517
+ "versionId",
518
+ ]);
519
+ if (
520
+ record.protocol !== "OGAMJ001" ||
521
+ !positiveInteger(record.sourceByteSize) ||
522
+ !positiveInteger(record.targetHeadSequence) ||
523
+ !isHash(record.sourceContentHash) ||
524
+ !isHash(record.stateHash) ||
525
+ !isHash(record.optionsHash) ||
526
+ !isHash(record.fontRegistryHash) ||
527
+ !isHash(record.policyHash) ||
528
+ !plainRecord(record.normalizedOptions)
529
+ ) {
530
+ throw invalidSource();
531
+ }
532
+ return record as MaterializationManifest;
533
+ }
534
+
535
+ function decodeVerificationManifest(value: Uint8Array): Readonly<{
536
+ protocol: "OGAVJ001";
537
+ format: string;
538
+ codecId: string;
539
+ codecVersion: string;
540
+ expectedSemanticHash: string;
541
+ }> {
542
+ const record = strictCanonicalRecord(value, [
543
+ "codecId",
544
+ "codecVersion",
545
+ "expectedSemanticHash",
546
+ "format",
547
+ "protocol",
548
+ ]);
549
+ if (record.protocol !== "OGAVJ001" || !isHash(record.expectedSemanticHash)) {
550
+ throw invalidSource();
551
+ }
552
+ return record as never;
553
+ }
554
+
555
+ function strictCanonicalRecord(
556
+ value: Uint8Array,
557
+ keys: readonly string[],
558
+ ): Record<string, unknown> {
559
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(value);
560
+ const parsed = JSON.parse(text) as unknown;
561
+ if (!plainRecord(parsed) || JSON.stringify(parsed) !== text) throw invalidSource();
562
+ if (Object.keys(parsed).sort().join("\0") !== [...keys].sort().join("\0")) throw invalidSource();
563
+ return parsed;
564
+ }
565
+
566
+ async function writeTypedError(
567
+ code: string,
568
+ diagnosticSubcode?: NativeSourceDiagnosticSubcode,
569
+ ): Promise<void> {
570
+ await writeFrame(
571
+ ERROR_MAGIC,
572
+ canonicalBytes({
573
+ code,
574
+ protocol: "OGAMERR1",
575
+ ...(diagnosticSubcode ? { subcode: diagnosticSubcode } : {}),
576
+ }),
577
+ new Uint8Array(0),
578
+ );
579
+ }
580
+
581
+ async function writeFrame(
582
+ magic: Uint8Array,
583
+ metadata: Uint8Array,
584
+ payload: Uint8Array,
585
+ ): Promise<void> {
586
+ const header = new Uint8Array(12);
587
+ const view = new DataView(header.buffer);
588
+ view.setUint32(0, metadata.byteLength, true);
589
+ view.setBigUint64(4, BigInt(payload.byteLength), true);
590
+ await writeStdout(magic);
591
+ await writeStdout(header);
592
+ await writeStdout(metadata);
593
+ for (let offset = 0; offset < payload.byteLength; offset += 1024 * 1024) {
594
+ await writeStdout(payload.subarray(offset, Math.min(payload.byteLength, offset + 1024 * 1024)));
595
+ }
596
+ }
597
+
598
+ async function writeStdout(value: Uint8Array): Promise<void> {
599
+ if (!process.stdout.write(value)) await once(process.stdout, "drain");
600
+ }
601
+
602
+ class InputReader {
603
+ readonly #iterator: AsyncIterator<Buffer | string>;
604
+ #buffer = new Uint8Array(0);
605
+ #done = false;
606
+
607
+ constructor(stream: NodeJS.ReadableStream & AsyncIterable<Buffer | string>) {
608
+ this.#iterator = stream[Symbol.asyncIterator]();
609
+ }
610
+
611
+ async readExactly(length: number): Promise<Uint8Array> {
612
+ if (!Number.isSafeInteger(length) || length < 0 || length > MAX_SOURCE_BYTES)
613
+ throw invalidSource();
614
+ const result = new Uint8Array(length);
615
+ let offset = 0;
616
+ while (offset < length) {
617
+ if (this.#buffer.byteLength === 0) await this.#fill();
618
+ if (this.#buffer.byteLength === 0) throw invalidSource();
619
+ const count = Math.min(length - offset, this.#buffer.byteLength);
620
+ result.set(this.#buffer.subarray(0, count), offset);
621
+ this.#buffer = this.#buffer.subarray(count);
622
+ offset += count;
623
+ }
624
+ return result;
625
+ }
626
+
627
+ async assertEof(): Promise<void> {
628
+ if (this.#buffer.byteLength > 0) throw invalidSource();
629
+ if (!this.#done) await this.#fill();
630
+ if (!this.#done || this.#buffer.byteLength > 0) throw invalidSource();
631
+ }
632
+
633
+ async #fill(): Promise<void> {
634
+ const next = await this.#iterator.next();
635
+ if (next.done) {
636
+ this.#done = true;
637
+ return;
638
+ }
639
+ if (typeof next.value === "string") throw invalidSource();
640
+ this.#buffer = new Uint8Array(
641
+ next.value.buffer,
642
+ next.value.byteOffset,
643
+ next.value.byteLength,
644
+ ).slice();
645
+ }
646
+ }
647
+
648
+ function codecVersion(packageVersion: string): string {
649
+ return `xlsx-v1@${packageVersion}`;
650
+ }
651
+
652
+ function canonicalBytes(value: unknown): Uint8Array {
653
+ return bytes(JSON.stringify(value));
654
+ }
655
+
656
+ function bytes(value: string): Uint8Array {
657
+ return new TextEncoder().encode(value);
658
+ }
659
+
660
+ function sha256(value: Uint8Array): string {
661
+ return `sha256:${createHash("sha256").update(value).digest("hex")}`;
662
+ }
663
+
664
+ function safeU64(view: DataView, offset: number, label: string): number {
665
+ const value = view.getBigUint64(offset, true);
666
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`${label} exceeds safe integer`);
667
+ return Number(value);
668
+ }
669
+
670
+ function assertBytes(actual: Uint8Array, expected: Uint8Array, label: string): void {
671
+ if (
672
+ actual.byteLength !== expected.byteLength ||
673
+ !actual.every((byte, index) => byte === expected[index])
674
+ ) {
675
+ throw new Error(`${label} is invalid`);
676
+ }
677
+ }
678
+
679
+ function plainRecord(value: unknown): value is Record<string, unknown> {
680
+ return (
681
+ typeof value === "object" &&
682
+ value !== null &&
683
+ !Array.isArray(value) &&
684
+ Object.getPrototypeOf(value) === Object.prototype
685
+ );
686
+ }
687
+
688
+ function positiveInteger(value: unknown): value is number {
689
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
690
+ }
691
+
692
+ function isHash(value: unknown): value is string {
693
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/u.test(value);
694
+ }
695
+
696
+ class MaterializerCliError extends Error {
697
+ readonly code:
698
+ | "unsupported_semantics"
699
+ | "source_identity_mismatch"
700
+ | "kernel_incompatible"
701
+ | "output_verification_failed";
702
+
703
+ constructor(
704
+ code: MaterializerCliError["code"],
705
+ readonly diagnosticSubcode?: NativeSourceDiagnosticSubcode,
706
+ ) {
707
+ super(code);
708
+ this.name = "MaterializerCliError";
709
+ this.code = code;
710
+ }
711
+ }
712
+
713
+ function unsupported(): MaterializerCliError {
714
+ return new MaterializerCliError("unsupported_semantics");
715
+ }
716
+ function invalidSource(diagnosticSubcode?: NativeSourceDiagnosticSubcode): MaterializerCliError {
717
+ return new MaterializerCliError("source_identity_mismatch", diagnosticSubcode);
718
+ }
719
+ function incompatible(): MaterializerCliError {
720
+ return new MaterializerCliError("kernel_incompatible");
721
+ }
722
+ function invalidOutput(): MaterializerCliError {
723
+ return new MaterializerCliError("output_verification_failed");
724
+ }
725
+
726
+ function classifyInvalidSource(
727
+ error: unknown,
728
+ diagnosticSubcode: NativeSourceDiagnosticSubcode,
729
+ ): unknown {
730
+ return error instanceof MaterializerCliError && error.code === "source_identity_mismatch"
731
+ ? invalidSource(error.diagnosticSubcode ?? diagnosticSubcode)
732
+ : error;
733
+ }
734
+
735
+ async function loadRuntime(
736
+ environment: Readonly<Record<string, string | undefined>>,
737
+ ): Promise<Awaited<ReturnType<typeof loadConfiguredArtifactKernelRuntime>>> {
738
+ try {
739
+ return await loadConfiguredArtifactKernelRuntime(environment);
740
+ } catch {
741
+ throw incompatible();
742
+ }
743
+ }