space-data-module-sdk 0.2.6 → 0.2.8

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
@@ -34,6 +34,7 @@ A module built with this SDK is a `.wasm` artifact with:
34
34
  - an exported `_start` entry when the artifact supports WASI command mode
35
35
  - optional `sds.bundle` custom-section payloads for:
36
36
  - manifest bytes
37
+ - resolved deployment plans and input bindings
37
38
  - deployment authorization
38
39
  - detached signatures
39
40
  - encrypted transport envelopes
@@ -81,6 +82,14 @@ outputs. The reference invoke examples live in
81
82
  - `manifest.hybrid.json`
82
83
  - `module.c`
83
84
 
85
+ Input and output ports can independently declare regular `flatbuffer` payloads
86
+ or `aligned-binary` layouts. Mixed contracts are valid. When a port advertises
87
+ an `aligned-binary` layout, it must also advertise a regular `flatbuffer`
88
+ fallback for the same schema in the same accepted type set. A module can accept
89
+ a regular `OMM.fbs` request and emit an aligned-binary `StateVector.fbs`
90
+ response, provided the output port also declares the regular `StateVector.fbs`
91
+ fallback and the aligned type ref carries the correct layout metadata.
92
+
84
93
  ## Runtime Portability
85
94
 
86
95
  The module format is language-neutral. A host can load modules from this SDK
@@ -115,6 +124,39 @@ This repo currently includes:
115
124
  - a reference Node host and sync `sdn_host` bridge for the first hostcall
116
125
  surface
117
126
 
127
+ ## Testing
128
+
129
+ This repo now exposes a manifest-driven harness generator from
130
+ `space-data-module-sdk/testing` and two complementary integration suites:
131
+
132
+ - `npm run test:runtime-matrix`
133
+ - cross-language runtime smoke across the same WASM in Node.js, Go, Python,
134
+ Rust, Java, C#, and Swift
135
+ - covers method calling, aligned-binary envelope metadata preservation,
136
+ stdin/stdout/stderr, args, env, preopened filesystem access, and basic WASI
137
+ clock/time smoke
138
+ - `npm run test:host-surfaces`
139
+ - authoritative Node-host coverage for HTTP, TCP, UDP, TLS, WebSocket, MQTT,
140
+ process execution, timers, filesystem, and the sync `sdn_host` ABI
141
+
142
+ The detailed edge cases and the current WASI-vs-host portability boundary are
143
+ documented in
144
+ [`docs/testing-harness.md`](./docs/testing-harness.md).
145
+
146
+ If a manifest declares `runtimeTargets: ["wasi"]`, this SDK now treats that as
147
+ "standalone WASI, no host wrapper required." In practice that currently means:
148
+
149
+ - the artifact must declare the `command` invoke surface
150
+ - declared capabilities must stay within the pure WASI subset:
151
+ `logging`, `clock`, `random`, `filesystem`, `pipe`
152
+ - hosted protocols may only use `wasi-pipe` transport
153
+
154
+ For maximum server-side portability with guest-owned network services, use
155
+ `runtimeTargets: ["wasmedge"]`. That target is intended for WasmEdge
156
+ environments with socket/TLS extensions, while plain `wasi` remains the strict
157
+ no-wrapper baseline. The Node-RED-oriented parity map lives in
158
+ [`docs/node-red-default-node-parity.md`](./docs/node-red-default-node-parity.md).
159
+
118
160
  ## Install
119
161
 
120
162
  ```bash
@@ -161,8 +203,36 @@ import { createDeploymentAuthorization } from "space-data-module-sdk/auth";
161
203
  import { encryptJsonForRecipient } from "space-data-module-sdk/transport";
162
204
  import { compileModuleFromSource } from "space-data-module-sdk/compiler";
163
205
  import { createSingleFileBundle } from "space-data-module-sdk/bundle";
206
+ import { validateDeploymentPlan } from "space-data-module-sdk/deployment";
207
+ import { generateManifestHarnessPlan } from "space-data-module-sdk/testing";
164
208
  ```
165
209
 
210
+ ## Protocol Installation
211
+
212
+ Modules can declare hosted protocol contracts in `manifest.protocols`.
213
+
214
+ Those declarations are for stable artifact identity:
215
+
216
+ - `wireId`
217
+ - `transportKind`
218
+ - `role`
219
+ - `specUri`
220
+ - hosting hints like `defaultPort` and `requireSecureTransport`
221
+
222
+ Concrete multiaddrs, peer IDs, and producer routing do not belong in the
223
+ canonical manifest. They belong in deployment metadata attached to the final
224
+ package or bundle.
225
+
226
+ This repo exposes that deployment surface from
227
+ `space-data-module-sdk/deployment`. Use it to:
228
+
229
+ - validate resolved protocol installations
230
+ - describe input bindings from producers to module ports
231
+ - attach a deployment plan to `sds.bundle`
232
+
233
+ The full contract split is documented in
234
+ [`docs/protocol-installation.md`](./docs/protocol-installation.md).
235
+
166
236
  ## Single-File Bundles
167
237
 
168
238
  `sds.bundle` keeps module delivery to one file without changing WebAssembly
@@ -179,6 +249,9 @@ The reference path lives in
179
249
  - the `go` and `python` directories show non-JS readers against the same
180
250
  bundle contract
181
251
 
252
+ Standard bundle payloads now include the optional `deployment-plan` JSON entry
253
+ for resolved protocol installations and producer input bindings.
254
+
182
255
  ## Module Publication
183
256
 
184
257
  Packages that publish SDN modules now use the canonical `sdn-module`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
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",
@@ -20,8 +20,10 @@
20
20
  "./compiler": "./src/compiler/index.js",
21
21
  "./compiler/emception": "./src/compiler/emception.js",
22
22
  "./bundle": "./src/bundle/index.js",
23
+ "./deployment": "./src/deployment/index.js",
23
24
  "./invoke": "./src/invoke/index.js",
24
25
  "./runtime": "./src/runtime/index.js",
26
+ "./testing": "./src/testing/index.js",
25
27
  "./standards": "./src/standards/index.js",
26
28
  "./schemas/*": "./schemas/*"
27
29
  },
@@ -32,6 +34,8 @@
32
34
  ],
33
35
  "scripts": {
34
36
  "test": "node --test",
37
+ "test:host-surfaces": "node --test test/node-host.test.js test/host-abi.test.js",
38
+ "test:runtime-matrix": "SPACE_DATA_MODULE_SDK_ENABLE_RUNTIME_MATRIX=1 node --test test/runtime-matrix.test.js",
35
39
  "start:lab": "node ./lab/server.js",
36
40
  "check:compliance": "node ./bin/space-data-module.js check --repo-root .",
37
41
  "generate:vectors": "node ./examples/single-file-bundle/generate-vectors.mjs",
@@ -134,6 +134,15 @@ table ProtocolSpec {
134
134
  input_port_id: string;
135
135
  output_port_id: string;
136
136
  description: string;
137
+ wire_id: string;
138
+ transport_kind: string;
139
+ role: string;
140
+ spec_uri: string;
141
+ auto_install: bool = true;
142
+ advertise: bool = false;
143
+ discovery_key: string;
144
+ default_port: uint16;
145
+ require_secure_transport: bool = false;
137
146
  }
138
147
 
139
148
  /// Build artifact emitted by the plugin toolchain.
@@ -170,6 +179,7 @@ table PluginManifest {
170
179
  build_artifacts: [BuildArtifact];
171
180
  abi_version: uint32 = 1;
172
181
  invoke_surfaces: [InvokeSurface];
182
+ runtime_targets: [string];
173
183
  }
174
184
 
175
185
  root_type PluginManifest;
@@ -5,4 +5,7 @@ export const DEFAULT_HASH_ALGORITHM = "sha256";
5
5
  export const DEFAULT_MANIFEST_EXPORT_SYMBOL = "plugin_get_manifest_flatbuffer";
6
6
  export const DEFAULT_MANIFEST_SIZE_SYMBOL =
7
7
  "plugin_get_manifest_flatbuffer_size";
8
-
8
+ export const SDS_DEPLOYMENT_SECTION_NAME = "sds.deployment";
9
+ export const SDS_DEPLOYMENT_ENTRY_ID = "deployment-plan";
10
+ export const SDS_DEPLOYMENT_MEDIA_TYPE =
11
+ "application/vnd.space-data.module.deployment+json";
@@ -3,11 +3,14 @@ import { decodePluginManifest, encodePluginManifest } from "../manifest/index.js
3
3
  import { toUint8Array } from "../runtime/bufferLike.js";
4
4
  import { sha256Bytes } from "../utils/crypto.js";
5
5
  import { bytesToHex } from "../utils/encoding.js";
6
+ import { createDeploymentPlanBundleEntry } from "../deployment/index.js";
6
7
  import {
7
8
  DEFAULT_MANIFEST_EXPORT_SYMBOL,
8
9
  DEFAULT_MANIFEST_SIZE_SYMBOL,
9
10
  SDS_BUNDLE_SECTION_NAME,
10
11
  SDS_CUSTOM_SECTION_PREFIX,
12
+ SDS_DEPLOYMENT_ENTRY_ID,
13
+ SDS_DEPLOYMENT_SECTION_NAME,
11
14
  } from "./constants.js";
12
15
  import {
13
16
  decodeModuleBundle,
@@ -268,6 +271,9 @@ function normalizeStandardEntries(options = {}) {
268
271
  description: "Transport envelope metadata.",
269
272
  });
270
273
  }
274
+ if (options.deploymentPlan !== undefined) {
275
+ entries.push(createDeploymentPlanBundleEntry(options.deploymentPlan));
276
+ }
271
277
  return entries.filter((entry) => entry.payload !== null);
272
278
  }
273
279
 
@@ -282,7 +288,12 @@ function normalizeAdditionalEntries(entries = []) {
282
288
  }
283
289
 
284
290
  async function withSha256(entry) {
285
- const payloadBytes = normalizeBytes(entry.payload, `entry "${entry.entryId}" payload`);
291
+ const payloadBytes =
292
+ toUint8Array(entry.payload) ??
293
+ ((entry.payloadEncoding === "json-utf8" ||
294
+ moduleBundleEncodingToName(entry.payloadEncoding) === "json-utf8")
295
+ ? canonicalBytes(entry.payload)
296
+ : normalizeBytes(entry.payload, `entry "${entry.entryId}" payload`));
286
297
  return {
287
298
  ...entry,
288
299
  payload: payloadBytes,
@@ -315,6 +326,17 @@ function buildParsedEntries(bundle) {
315
326
  parsedEntry.decodedManifest = null;
316
327
  }
317
328
  }
329
+ if (
330
+ parsedEntry.entryId === SDS_DEPLOYMENT_ENTRY_ID ||
331
+ parsedEntry.sectionName === SDS_DEPLOYMENT_SECTION_NAME
332
+ ) {
333
+ parsedEntry.decodedDeploymentPlan =
334
+ parsedEntry.payloadEncodingName === "json-utf8" &&
335
+ parsedEntry.decodedPayload &&
336
+ typeof parsedEntry.decodedPayload === "object"
337
+ ? parsedEntry.decodedPayload
338
+ : null;
339
+ }
318
340
  return parsedEntry;
319
341
  });
320
342
  }
@@ -432,16 +454,22 @@ export async function parseSingleFileBundle(bytes, options = {}) {
432
454
  manifest = null;
433
455
  }
434
456
  }
457
+ const deploymentEntry =
458
+ parsedEntries.find(
459
+ (entry) =>
460
+ entry.entryId === SDS_DEPLOYMENT_ENTRY_ID ||
461
+ entry.sectionName === SDS_DEPLOYMENT_SECTION_NAME,
462
+ ) ?? null;
435
463
  return {
436
464
  wasmBytes,
437
465
  bundleBytes,
438
466
  bundle,
439
467
  entries: parsedEntries,
440
468
  manifest,
469
+ deploymentPlan: deploymentEntry?.decodedDeploymentPlan ?? null,
441
470
  customSections,
442
471
  canonicalWasmBytes: canonical.canonicalWasmBytes,
443
472
  canonicalModuleHash: canonical.hashBytes,
444
473
  canonicalModuleHashHex: canonical.hashHex,
445
474
  };
446
475
  }
447
-
@@ -5,6 +5,7 @@ import {
5
5
  loadManifestFromFile,
6
6
  loadComplianceConfig,
7
7
  RecommendedCapabilityIds,
8
+ StandaloneWasiCapabilityIds,
8
9
  resolveManifestFiles,
9
10
  validatePluginArtifact,
10
11
  validatePluginManifest,
@@ -49,8 +50,8 @@ export {
49
50
  loadManifestFromFile,
50
51
  loadComplianceConfig,
51
52
  RecommendedCapabilityIds,
53
+ StandaloneWasiCapabilityIds,
52
54
  resolveManifestFiles,
53
55
  validatePluginArtifact,
54
56
  validatePluginManifest,
55
57
  };
56
-