space-data-module-sdk 0.5.13 → 0.5.15

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/README.md CHANGED
@@ -321,6 +321,46 @@ The reference path lives in
321
321
  Standard bundle payloads now include the optional `deployment-plan` JSON entry
322
322
  for resolved protocol installations and producer input bindings.
323
323
 
324
+ ## Publication Protection Extensions
325
+
326
+ Digital signature and encrypted-delivery metadata are publication-layer
327
+ extensions, not a second module format. The canonical module is still:
328
+
329
+ - valid `.wasm`
330
+ - optionally carrying `sds.bundle` as a wasm custom section
331
+ - optionally carrying one appended SDS `REC` trailer after the wasm bytes
332
+
333
+ That trailing `REC` FlatBuffer is the standards-backed container for
334
+ publication records:
335
+
336
+ - `PNM` is the publication notice / digital-signature record
337
+ - identifies the artifact
338
+ - carries the CID and publish timestamp
339
+ - carries signature metadata so a loader can verify who published the module
340
+ - `ENC` is the encrypted-delivery record
341
+ - carries the X25519 / AES-CTR / HKDF parameters needed to decrypt the
342
+ protected transport payload
343
+ - does not change the canonical module ABI or manifest shape
344
+
345
+ The loader contract is always:
346
+
347
+ 1. Start with the protected blob.
348
+ 2. Scan backward for the trailing `REC` footer.
349
+ 3. Parse `REC` using the standards-generated `REC`, `PNM`, and `ENC`
350
+ FlatBuffers from `spacedatastandards.org`.
351
+ 4. Resolve `PNM` for signature/publication metadata.
352
+ 5. Resolve `ENC` if the delivery was encrypted.
353
+ 6. Strip the trailer or decrypt the protected payload.
354
+ 7. Hand the remaining raw wasm bytes to the runtime.
355
+
356
+ Aligned-binary payloads are a separate invoke-ABI optimization. They do not
357
+ replace the canonical FlatBuffer schema. If a port advertises
358
+ `wireFormat: "aligned-binary"`, it must also advertise the regular
359
+ `wireFormat: "flatbuffer"` fallback for the same schema and file identifier in
360
+ the same accepted type set. The publication demo in the local lab uses a
361
+ regular `OMM.fbs` request and a `StateVector.fbs` response that advertises both
362
+ the canonical FlatBuffer fallback and the aligned-binary layout metadata.
363
+
324
364
  ## Module Publication
325
365
 
326
366
  Packages that publish Space Data modules use the canonical `sdn-module`
@@ -424,10 +464,14 @@ npm run start:lab
424
464
 
425
465
  Then open `http://localhost:4318`.
426
466
 
467
+ Use the `Run Publication Demo` button to generate a protected demo artifact and
468
+ inspect the parsed `REC`, `PNM`, and `ENC` records produced by the real
469
+ `spacedatastandards.org` generated message classes.
470
+
427
471
  ## Related Projects
428
472
 
429
473
  - [`spacedatastandards.org`](https://spacedatastandards.org)
430
- - [`@digitalarsenal/sdn-flow`](https://github.com/DigitalArsenal/sdn-flow)
474
+ - [`sdn-flow`](https://github.com/DigitalArsenal/sdn-flow)
431
475
  - [`hd-wallet-wasm`](https://github.com/nicktj-dev/hd-wallet-wasm)
432
476
 
433
477
  ## Development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
5
5
  "type": "module",
6
6
  "types": "./src/index.d.ts",
@@ -40,9 +40,6 @@
40
40
  "schemas/",
41
41
  "src/"
42
42
  ],
43
- "publishConfig": {
44
- "access": "public"
45
- },
46
43
  "scripts": {
47
44
  "test": "node --test",
48
45
  "test:host-surfaces": "node --test test/node-host.test.js test/host-abi.test.js",
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { readFile } from "node:fs/promises";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  import { FlatcRunner } from "flatc-wasm";
@@ -18,48 +18,6 @@ function loadFlatcRunner() {
18
18
  return flatcRunnerPromise;
19
19
  }
20
20
 
21
- const FLATBUFFERS_INCLUDE_ROOT_CANDIDATES = [
22
- process.env.FLATBUFFERS_INCLUDE_DIR,
23
- "/opt/homebrew/include",
24
- "/usr/local/include",
25
- "/usr/include",
26
- ].filter(Boolean);
27
-
28
- async function findFlatbuffersIncludeRoot() {
29
- for (const candidate of FLATBUFFERS_INCLUDE_ROOT_CANDIDATES) {
30
- const headerPath = path.join(candidate, "flatbuffers", "flatbuffers.h");
31
- try {
32
- const headerStat = await stat(headerPath);
33
- if (headerStat.isFile()) {
34
- return candidate;
35
- }
36
- } catch {
37
- // Try the next candidate.
38
- }
39
- }
40
- throw new Error(
41
- "Unable to locate the installed flatbuffers C++ headers. Set FLATBUFFERS_INCLUDE_DIR to the directory containing flatbuffers/flatbuffers.h.",
42
- );
43
- }
44
-
45
- async function readDirectoryTree(rootDir, currentDir = rootDir) {
46
- const entries = await readdir(currentDir, { withFileTypes: true });
47
- const files = [];
48
- for (const entry of entries) {
49
- const fullPath = path.join(currentDir, entry.name);
50
- if (entry.isDirectory()) {
51
- files.push(...(await readDirectoryTree(rootDir, fullPath)));
52
- continue;
53
- }
54
- if (!entry.isFile()) {
55
- continue;
56
- }
57
- const relativePath = path.relative(rootDir, fullPath).split(path.sep).join("/");
58
- files.push([`/${relativePath}`, await readFile(fullPath, "utf8")]);
59
- }
60
- return files;
61
- }
62
-
63
21
  async function loadInvokeSchemaFiles() {
64
22
  const filenames = [
65
23
  "TypedArenaBuffer.fbs",
@@ -78,10 +36,8 @@ async function loadInvokeSchemaFiles() {
78
36
  export async function getFlatbuffersCppRuntimeHeaders() {
79
37
  if (!flatbuffersCppRuntimeHeadersPromise) {
80
38
  flatbuffersCppRuntimeHeadersPromise = (async () => {
81
- const includeRoot = await findFlatbuffersIncludeRoot();
82
- return Object.fromEntries(
83
- await readDirectoryTree(includeRoot, path.join(includeRoot, "flatbuffers")),
84
- );
39
+ const flatc = await loadFlatcRunner();
40
+ return flatc.getEmbeddedRuntime("cpp");
85
41
  })();
86
42
  }
87
43
  return flatbuffersCppRuntimeHeadersPromise;
@@ -366,7 +366,7 @@ function validateAllowedType(type, issues, location) {
366
366
  "Allowed type fixedStringLength",
367
367
  { min: 0 },
368
368
  );
369
- validateOptionalIntegerField(
369
+ validateIntegerField(
370
370
  issues,
371
371
  type.byteLength,
372
372
  `${location}.byteLength`,
package/src/index.d.ts CHANGED
@@ -664,6 +664,8 @@ export type {
664
664
  ManifestHarnessPlan,
665
665
  ModuleHarness,
666
666
  ModuleHarnessRuntimeDescriptor,
667
+ PublicationProtectionDemoAlignedType,
668
+ PublicationProtectionDemoSummary,
667
669
  PluginInvokeProcessClient,
668
670
  PluginInvokeProcessLaunchPlan,
669
671
  WasmEdgeRunnerBuildPlan,
@@ -672,6 +674,8 @@ export type {
672
674
  export {
673
675
  buildWasmEdgeEmscriptenPthreadRunner,
674
676
  buildWasmEdgeSpawnEnv,
677
+ createPublicationProtectionDemoManifest,
678
+ createPublicationProtectionDemoSummary,
675
679
  createModuleHarness,
676
680
  createPluginInvokeProcessClient,
677
681
  describeCapabilityRuntimeSurface,
@@ -1,62 +1,31 @@
1
1
  function cloneSchemaHash(value) {
2
2
  if (value instanceof Uint8Array) {
3
- return value.byteLength > 0 ? new Uint8Array(value) : undefined;
3
+ return new Uint8Array(value);
4
4
  }
5
5
  if (Array.isArray(value)) {
6
- return value.length > 0 ? [...value] : undefined;
6
+ return [...value];
7
7
  }
8
8
  return value ?? undefined;
9
9
  }
10
10
 
11
- function normalizeNullableString(value) {
12
- if (value === undefined || value === null) {
13
- return null;
14
- }
15
- const normalized = String(value).trim();
16
- return normalized.length > 0 ? normalized : null;
17
- }
18
-
19
- function normalizeAlignedMetadataScalar(value) {
20
- if (value === undefined || value === null || value === "") {
21
- return undefined;
22
- }
23
- const numeric = Number(value);
24
- if (Number.isFinite(numeric)) {
25
- return numeric === 0 ? undefined : numeric;
26
- }
27
- return value;
28
- }
29
-
30
11
  export function clonePayloadTypeRef(value = null) {
31
12
  if (!value || typeof value !== "object") {
32
- return { acceptsAnyFlatbuffer: true, fileIdentifier: null };
13
+ return { acceptsAnyFlatbuffer: true };
33
14
  }
34
15
  return {
35
- schemaName: normalizeNullableString(
36
- value.schemaName ?? value.schema_name,
37
- ),
38
- fileIdentifier: normalizeNullableString(
39
- value.fileIdentifier ?? value.file_identifier,
40
- ),
16
+ schemaName: value.schemaName ?? value.schema_name ?? undefined,
17
+ fileIdentifier: value.fileIdentifier ?? value.file_identifier ?? undefined,
41
18
  schemaHash: cloneSchemaHash(value.schemaHash ?? value.schema_hash),
42
19
  acceptsAnyFlatbuffer: Boolean(
43
20
  value.acceptsAnyFlatbuffer ?? value.accepts_any_flatbuffer ?? false,
44
21
  ),
45
- wireFormat: normalizePayloadWireFormatName(
46
- value.wireFormat ?? value.wire_format,
47
- ),
48
- rootTypeName: normalizeNullableString(
49
- value.rootTypeName ?? value.root_type_name,
50
- ),
51
- fixedStringLength: normalizeAlignedMetadataScalar(
52
- value.fixedStringLength ?? value.fixed_string_length,
53
- ),
54
- byteLength: normalizeAlignedMetadataScalar(
55
- value.byteLength ?? value.byte_length,
56
- ),
57
- requiredAlignment: normalizeAlignedMetadataScalar(
58
- value.requiredAlignment ?? value.required_alignment,
59
- ),
22
+ wireFormat: value.wireFormat ?? value.wire_format ?? undefined,
23
+ rootTypeName: value.rootTypeName ?? value.root_type_name ?? undefined,
24
+ fixedStringLength:
25
+ value.fixedStringLength ?? value.fixed_string_length ?? undefined,
26
+ byteLength: value.byteLength ?? value.byte_length ?? undefined,
27
+ requiredAlignment:
28
+ value.requiredAlignment ?? value.required_alignment ?? undefined,
60
29
  };
61
30
  }
62
31
 
@@ -64,12 +33,6 @@ export function normalizePayloadWireFormatName(value) {
64
33
  if (value === undefined || value === null || value === "") {
65
34
  return null;
66
35
  }
67
- if (value === 1 || value === "1") {
68
- return "aligned-binary";
69
- }
70
- if (value === 0 || value === "0") {
71
- return "flatbuffer";
72
- }
73
36
  const normalized = String(value).trim().toLowerCase().replace(/_/g, "-");
74
37
  if (normalized === "aligned-binary") {
75
38
  return "aligned-binary";
@@ -57,6 +57,75 @@ export interface ManifestHarnessPlan {
57
57
  scenarios: Array<HarnessInvokeScenario | HarnessRawScenario>;
58
58
  }
59
59
 
60
+ export interface PublicationProtectionDemoAlignedType {
61
+ methodId: string | null;
62
+ portId: string | null;
63
+ setId: string | null;
64
+ schemaName: string | null;
65
+ fileIdentifier: string | null;
66
+ rootTypeName: string | null;
67
+ byteLength: number | null;
68
+ requiredAlignment: number | null;
69
+ hasFlatbufferFallback: boolean;
70
+ }
71
+
72
+ export interface PublicationProtectionDemoSummary {
73
+ manifest: PluginManifest;
74
+ recTrailer: {
75
+ fileIdentifier: string;
76
+ version: string | null;
77
+ recordCount: number;
78
+ recordStandards: Array<string | null>;
79
+ usesStandardsFlatbuffers: boolean;
80
+ records: Array<Record<string, unknown>>;
81
+ };
82
+ alignedBinaryContract: PublicationProtectionDemoAlignedType[];
83
+ signedOnly: {
84
+ artifactId: string;
85
+ encrypted: boolean;
86
+ trailer: PublicationProtectionDemoSummary["recTrailer"];
87
+ recordStandards: Array<string | null>;
88
+ pnm: {
89
+ fileName: string | null;
90
+ fileId: string | null;
91
+ cid: string | null;
92
+ hasSignature: boolean;
93
+ signatureType: string | null;
94
+ publishTimestamp: string | null;
95
+ } | null;
96
+ enc: null;
97
+ envelope: null;
98
+ };
99
+ encryptedDelivery: {
100
+ artifactId: string;
101
+ encrypted: boolean;
102
+ trailer: PublicationProtectionDemoSummary["recTrailer"];
103
+ recordStandards: Array<string | null>;
104
+ pnm: {
105
+ fileName: string | null;
106
+ fileId: string | null;
107
+ cid: string | null;
108
+ hasSignature: boolean;
109
+ signatureType: string | null;
110
+ publishTimestamp: string | null;
111
+ } | null;
112
+ enc: {
113
+ context: string | null;
114
+ rootType: string | null;
115
+ keyExchange: string | null;
116
+ symmetric: string | null;
117
+ keyDerivation: string | null;
118
+ nonceLength: number;
119
+ ephemeralPublicKeyLength: number;
120
+ } | null;
121
+ envelope: {
122
+ scheme: string | null;
123
+ hasEncRecord: boolean;
124
+ hasPnmRecord: boolean;
125
+ } | null;
126
+ };
127
+ }
128
+
60
129
  export interface PluginInvokeProcessLaunchPlan {
61
130
  command: string;
62
131
  args: string[];
@@ -124,6 +193,18 @@ export function describeCapabilityRuntimeSurface(
124
193
  capability: string,
125
194
  ): CapabilityRuntimeSurface;
126
195
 
196
+ export function createPublicationProtectionDemoManifest(): PluginManifest;
197
+
198
+ export function createPublicationProtectionDemoSummary(options?: {
199
+ manifest?: PluginManifest;
200
+ wasmBytes?: Uint8Array;
201
+ mnemonic?: string | null;
202
+ recipient?: {
203
+ publicKeyHex: string;
204
+ privateKeyHex: string;
205
+ };
206
+ }): Promise<PublicationProtectionDemoSummary>;
207
+
127
208
  export function generateManifestHarnessPlan(options: {
128
209
  manifest: PluginManifest;
129
210
  includeOptionalInputs?: boolean;
@@ -3,6 +3,10 @@ import { Buffer } from "node:buffer";
3
3
  import { encodePluginInvokeRequest } from "../invoke/codec.js";
4
4
  import { normalizeInvokeSurfaces } from "../invoke/index.js";
5
5
  import { selectPreferredPayloadTypeRef } from "../manifest/typeRefs.js";
6
+ export {
7
+ createPublicationProtectionDemoManifest,
8
+ createPublicationProtectionDemoSummary,
9
+ } from "./publicationProtectionDemo.js";
6
10
  export {
7
11
  buildWasmEdgeSpawnEnv,
8
12
  createPluginInvokeProcessClient,
@@ -0,0 +1,271 @@
1
+ import * as flatbuffers from "flatbuffers";
2
+
3
+ import { createRecipientKeypairHex, protectModuleArtifact } from "../compiler/compileModule.js";
4
+ import { extractPublicationRecordCollection } from "../transport/records.js";
5
+ import { REC } from "spacedatastandards.org/lib/js/REC/REC.js";
6
+ import { Record } from "spacedatastandards.org/lib/js/REC/Record.js";
7
+ import { PNM } from "spacedatastandards.org/lib/js/PNM/main.js";
8
+ import { ENC } from "spacedatastandards.org/lib/js/ENC/main.js";
9
+
10
+ const MINIMAL_WASM_BYTES = Uint8Array.of(
11
+ 0x00,
12
+ 0x61,
13
+ 0x73,
14
+ 0x6d,
15
+ 0x01,
16
+ 0x00,
17
+ 0x00,
18
+ 0x00,
19
+ );
20
+
21
+ export function createPublicationProtectionDemoManifest() {
22
+ return {
23
+ pluginId: "com.digitalarsenal.examples.publication-protection-demo",
24
+ name: "Publication Protection Demo",
25
+ version: "0.1.0",
26
+ pluginFamily: "propagator",
27
+ capabilities: ["clock", "crypto_sign", "crypto_encrypt"],
28
+ externalInterfaces: [],
29
+ methods: [
30
+ {
31
+ methodId: "propagate",
32
+ displayName: "Propagate",
33
+ inputPorts: [
34
+ {
35
+ portId: "request",
36
+ acceptedTypeSets: [
37
+ {
38
+ setId: "omm-request",
39
+ allowedTypes: [
40
+ {
41
+ schemaName: "OMM.fbs",
42
+ fileIdentifier: "$OMM",
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ minStreams: 1,
48
+ maxStreams: 1,
49
+ required: true,
50
+ },
51
+ ],
52
+ outputPorts: [
53
+ {
54
+ portId: "state",
55
+ acceptedTypeSets: [
56
+ {
57
+ setId: "state-vector",
58
+ allowedTypes: [
59
+ {
60
+ schemaName: "StateVector.fbs",
61
+ fileIdentifier: "STVC",
62
+ },
63
+ {
64
+ schemaName: "StateVector.fbs",
65
+ fileIdentifier: "STVC",
66
+ wireFormat: "aligned-binary",
67
+ rootTypeName: "StateVector",
68
+ byteLength: 72,
69
+ requiredAlignment: 8,
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ minStreams: 1,
75
+ maxStreams: 1,
76
+ required: true,
77
+ },
78
+ ],
79
+ maxBatch: 32,
80
+ drainPolicy: "drain-to-empty",
81
+ },
82
+ ],
83
+ schemasUsed: [
84
+ {
85
+ schemaName: "OMM.fbs",
86
+ fileIdentifier: "$OMM",
87
+ },
88
+ {
89
+ schemaName: "StateVector.fbs",
90
+ fileIdentifier: "STVC",
91
+ },
92
+ {
93
+ schemaName: "StateVector.fbs",
94
+ fileIdentifier: "STVC",
95
+ wireFormat: "aligned-binary",
96
+ rootTypeName: "StateVector",
97
+ byteLength: 72,
98
+ requiredAlignment: 8,
99
+ },
100
+ ],
101
+ };
102
+ }
103
+
104
+ function summarizeAlignedBinaryContract(manifest = {}) {
105
+ const summaries = [];
106
+ for (const method of Array.isArray(manifest.methods) ? manifest.methods : []) {
107
+ const ports = [
108
+ ...(Array.isArray(method.inputPorts) ? method.inputPorts : []),
109
+ ...(Array.isArray(method.outputPorts) ? method.outputPorts : []),
110
+ ];
111
+ for (const port of ports) {
112
+ for (const typeSet of Array.isArray(port.acceptedTypeSets)
113
+ ? port.acceptedTypeSets
114
+ : []) {
115
+ const allowedTypes = Array.isArray(typeSet.allowedTypes)
116
+ ? typeSet.allowedTypes
117
+ : [];
118
+ for (const allowedType of allowedTypes) {
119
+ if (allowedType?.wireFormat !== "aligned-binary") {
120
+ continue;
121
+ }
122
+ const hasFlatbufferFallback = allowedTypes.some(
123
+ (candidate) =>
124
+ (candidate?.wireFormat ?? "flatbuffer") === "flatbuffer" &&
125
+ candidate?.schemaName === allowedType.schemaName &&
126
+ candidate?.fileIdentifier === allowedType.fileIdentifier,
127
+ );
128
+ summaries.push({
129
+ methodId: method.methodId ?? null,
130
+ portId: port.portId ?? null,
131
+ setId: typeSet.setId ?? null,
132
+ schemaName: allowedType.schemaName ?? null,
133
+ fileIdentifier: allowedType.fileIdentifier ?? null,
134
+ rootTypeName: allowedType.rootTypeName ?? null,
135
+ byteLength: allowedType.byteLength ?? null,
136
+ requiredAlignment: allowedType.requiredAlignment ?? null,
137
+ hasFlatbufferFallback,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return summaries;
144
+ }
145
+
146
+ function parseStandardsRec(recordCollectionBytes) {
147
+ const recBuffer = new flatbuffers.ByteBuffer(recordCollectionBytes);
148
+ const rec = REC.getRootAsREC(recBuffer);
149
+ const records = [];
150
+ for (let index = 0; index < rec.recordsLength(); index += 1) {
151
+ const record = rec.RECORDS(index, new Record());
152
+ if (!record) {
153
+ continue;
154
+ }
155
+ const standard = record.standard() ?? null;
156
+ if (standard === "PNM") {
157
+ const pnm = record.value(new PNM());
158
+ records.push({
159
+ standard,
160
+ fileIdentifier: "$PNM",
161
+ fileName: pnm?.FILE_NAME() ?? null,
162
+ fileId: pnm?.FILE_ID() ?? null,
163
+ cid: pnm?.CID() ?? null,
164
+ hasSignature: Boolean(pnm?.SIGNATURE()),
165
+ });
166
+ continue;
167
+ }
168
+ if (standard === "ENC") {
169
+ const enc = record.value(new ENC());
170
+ records.push({
171
+ standard,
172
+ fileIdentifier: "$ENC",
173
+ context: enc?.CONTEXT() ?? null,
174
+ rootType: enc?.ROOT_TYPE() ?? null,
175
+ nonceLength: enc?.nonceStartLength() ?? 0,
176
+ ephemeralPublicKeyLength: enc?.ephemeralPublicKeyLength() ?? 0,
177
+ });
178
+ continue;
179
+ }
180
+ records.push({
181
+ standard,
182
+ fileIdentifier: null,
183
+ });
184
+ }
185
+ return {
186
+ fileIdentifier: "$REC",
187
+ version: rec.version() ?? null,
188
+ recordCount: records.length,
189
+ recordStandards: records.map((record) => record.standard),
190
+ usesStandardsFlatbuffers: REC.bufferHasIdentifier(
191
+ new flatbuffers.ByteBuffer(recordCollectionBytes),
192
+ ),
193
+ records,
194
+ };
195
+ }
196
+
197
+ function summarizeProtectedArtifact(protectedArtifact) {
198
+ const parsed = extractPublicationRecordCollection(
199
+ protectedArtifact.protectedArtifactBytes,
200
+ );
201
+ if (!parsed) {
202
+ throw new Error("Protected artifact is missing the REC publication trailer.");
203
+ }
204
+ const trailer = parseStandardsRec(parsed.recordCollectionBytes);
205
+ return {
206
+ artifactId: protectedArtifact.payload.artifactId,
207
+ encrypted: protectedArtifact.encrypted,
208
+ trailer,
209
+ recordStandards: trailer.recordStandards,
210
+ pnm: parsed.pnm
211
+ ? {
212
+ fileName: parsed.pnm.fileName ?? null,
213
+ fileId: parsed.pnm.fileId ?? null,
214
+ cid: parsed.pnm.cid ?? null,
215
+ hasSignature: Boolean(parsed.pnm.signature),
216
+ signatureType: parsed.pnm.signatureType ?? null,
217
+ publishTimestamp: parsed.pnm.publishTimestamp ?? null,
218
+ }
219
+ : null,
220
+ enc: parsed.enc
221
+ ? {
222
+ context: parsed.enc.context ?? null,
223
+ rootType: parsed.enc.rootType ?? null,
224
+ keyExchange: parsed.enc.keyExchange ?? null,
225
+ symmetric: parsed.enc.symmetric ?? null,
226
+ keyDerivation: parsed.enc.keyDerivation ?? null,
227
+ nonceLength: parsed.enc.nonceStart?.length ?? 0,
228
+ ephemeralPublicKeyLength: parsed.enc.ephemeralPublicKey?.length ?? 0,
229
+ }
230
+ : null,
231
+ envelope: protectedArtifact.encryptedEnvelope
232
+ ? {
233
+ scheme: protectedArtifact.encryptedEnvelope.scheme ?? null,
234
+ hasEncRecord: Boolean(
235
+ protectedArtifact.encryptedEnvelope.encRecordBase64,
236
+ ),
237
+ hasPnmRecord: Boolean(
238
+ protectedArtifact.encryptedEnvelope.pnmRecordBase64,
239
+ ),
240
+ }
241
+ : null,
242
+ };
243
+ }
244
+
245
+ export async function createPublicationProtectionDemoSummary(options = {}) {
246
+ const manifest = options.manifest ?? createPublicationProtectionDemoManifest();
247
+ const wasmBytes =
248
+ options.wasmBytes instanceof Uint8Array
249
+ ? options.wasmBytes
250
+ : MINIMAL_WASM_BYTES;
251
+ const recipient = options.recipient ?? (await createRecipientKeypairHex());
252
+ const signedOnly = await protectModuleArtifact({
253
+ manifest,
254
+ wasmBytes,
255
+ mnemonic: options.mnemonic ?? null,
256
+ });
257
+ const encryptedDelivery = await protectModuleArtifact({
258
+ manifest,
259
+ wasmBytes,
260
+ mnemonic: options.mnemonic ?? null,
261
+ recipientPublicKeyHex: recipient.publicKeyHex,
262
+ });
263
+
264
+ return {
265
+ manifest,
266
+ recTrailer: parseStandardsRec(signedOnly.publicationRecordsBytes),
267
+ alignedBinaryContract: summarizeAlignedBinaryContract(manifest),
268
+ signedOnly: summarizeProtectedArtifact(signedOnly),
269
+ encryptedDelivery: summarizeProtectedArtifact(encryptedDelivery),
270
+ };
271
+ }
@@ -612,13 +612,7 @@ export function decodePublicationRecordCollection(bytes) {
612
612
  const recordType =
613
613
  typeof recordTable.valueType === "function"
614
614
  ? recordTable.valueType()
615
- : typeof recordTable.value_type === "function"
616
- ? recordTable.value_type()
617
- : (() => {
618
- throw new TypeError(
619
- "REC Record wrapper is missing both valueType() and value_type() accessors.",
620
- );
621
- })();
615
+ : recordTable.value_type();
622
616
  const standard =
623
617
  normalizeStringField(recordTable.standard()) ??
624
618
  STANDARD_BY_RECORD_TYPE[recordType] ??