space-data-module-sdk 0.8.10 → 0.8.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.8.10",
3
+ "version": "0.8.11",
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",
@@ -62,8 +62,35 @@ function collectFlatbufferTableFields(idl, tableName) {
62
62
  .filter(Boolean);
63
63
  }
64
64
 
65
+ // A schema has TWO written forms in this ecosystem and they mean the same file:
66
+ //
67
+ // "SCV.fbs" — the catalog's canonical form, built as `<CODE>.fbs` from
68
+ // the standards manifest (parseStandardsEntry below).
69
+ // "SCV/main.fbs" — the PATH form, which is how the schema actually lives in
70
+ // spacedatastandards.org (`schema/SCV/main.fbs`) and is
71
+ // therefore what modules, tests and generated bindings across
72
+ // the stack write.
73
+ //
74
+ // Matching them as raw strings made every manifest using the path form fail
75
+ // resolution with `standards-type-identity-mismatch` — "mixes a known
76
+ // schemaName ... with a different standards entry" — because the `$SCV` file
77
+ // identifier DID resolve while the name did not. That is not a manifest defect
78
+ // and not a schema defect; it is this matcher refusing a spelling the standards
79
+ // repository itself uses. The rest of the toolchain already treats the two as
80
+ // one schema (see the closed-modules builder's
81
+ // `^([A-Z][A-Z0-9]{2})(?:\/main)?\.fbs$` header-injection regex), so the
82
+ // catalog must too, or a correct module cannot be compiled.
83
+ //
84
+ // Deliberately narrow: ONLY the exact `<CODE>/main.fbs` shape collapses. Any
85
+ // other path is left alone so two genuinely different schemas can never be
86
+ // merged by a loose rule.
87
+ const SCHEMA_PATH_FORM = /^([A-Za-z][A-Za-z0-9_]*)\/main\.fbs$/;
88
+
65
89
  function normalizeSchemaName(value) {
66
- return value === undefined || value === null ? "" : String(value);
90
+ if (value === undefined || value === null) return "";
91
+ const text = String(value);
92
+ const pathForm = SCHEMA_PATH_FORM.exec(text);
93
+ return pathForm ? `${pathForm[1]}.fbs` : text;
67
94
  }
68
95
 
69
96
  function normalizeFileIdentifier(value) {
@@ -61,6 +61,7 @@ import { toLoadableWasmBytes } from "../bundle/artifactBytes.js";
61
61
  import {
62
62
  classifyArtifactImports,
63
63
  describeClassification,
64
+ readWasmExportNames,
64
65
  resolveHostSurface,
65
66
  } from "./hostContract.js";
66
67
  import { normalizeWasmEdgeOutcome } from "./wasmedgeOutput.js";
@@ -250,6 +251,21 @@ export function classifyWasmEdgeProbe({
250
251
  if (/(loading failed|validation failed|magic header|malformed|invalid section)/i.test(text)) {
251
252
  return { outcome: "compile-error", missingImport: null, detail: head() };
252
253
  }
254
+ // The CLI rejected the INVOCATION, before loading anything: reactor mode
255
+ // needs an entry name. This says nothing about the artifact, so it must not
256
+ // be reported against the artifact — stageArtifact() now names `_initialize`
257
+ // for reactor artifacts, and this branch exists so a regression there is
258
+ // legible instead of masquerading as a cross-runtime divergence.
259
+ if (/function name is required when reactor mode is enabled/i.test(text)) {
260
+ return {
261
+ outcome: "probe-failure",
262
+ missingImport: null,
263
+ detail:
264
+ "WasmEdge refused the invocation: reactor mode requires an entry " +
265
+ "function name. This is a PROBE defect, not an artifact defect — the " +
266
+ "lane must pass the artifact's reactor entry (see resolveReactorEntry).",
267
+ };
268
+ }
253
269
  // Linked, then the CLI could not find a start entry: a reactor artifact.
254
270
  // Only reachable after successful instantiation.
255
271
  if (/(wasm function not found|function not found|_start|_initialize)/i.test(text)) {
@@ -359,15 +375,11 @@ async function probeWithNativeWasmEdge(context, staged) {
359
375
  context.pin,
360
376
  `native binary ${detected.binary}`,
361
377
  );
362
- const outcome = await spawnCapture(
363
- detected.binary,
364
- ["--enable-threads", staged.basename],
365
- {
366
- cwd: staged.dir,
367
- env: { PATH: process.env.PATH ?? "" },
368
- timeoutMs: context.timeoutMs,
369
- },
370
- );
378
+ const outcome = await spawnCapture(detected.binary, wasmEdgeProbeArgs(staged), {
379
+ cwd: staged.dir,
380
+ env: { PATH: process.env.PATH ?? "" },
381
+ timeoutMs: context.timeoutMs,
382
+ });
371
383
  const normalized = normalizeWasmEdgeOutcome(outcome);
372
384
  return classifyWasmEdgeProbe({
373
385
  code: outcome.code,
@@ -391,7 +403,7 @@ async function probeWithDockerWasmEdge(context, staged) {
391
403
  "/parity",
392
404
  ];
393
405
  if (context.dockerPlatform) args.push("--platform", String(context.dockerPlatform));
394
- args.push(context.pin.dockerImage, "--enable-threads", staged.basename);
406
+ args.push(context.pin.dockerImage, ...wasmEdgeProbeArgs(staged));
395
407
  const outcome = await spawnCapture(context.dockerBinary ?? "docker", args, {
396
408
  cwd: staged.dir,
397
409
  env: process.env,
@@ -688,11 +700,52 @@ export function lanesAgree(a, b) {
688
700
 
689
701
  // --- Orchestrator ---------------------------------------------------------------
690
702
 
703
+ /**
704
+ * A REACTOR artifact has no `_start`; its initialisation entry is `_initialize`
705
+ * (clang `-mexec-model=reactor`). The WasmEdge CLI refuses to run one without
706
+ * being told which function to call — "A function name is required when reactor
707
+ * mode is enabled." on stderr, exit 1, and NO runtime diagnostic — which the
708
+ * probe classifier could only honestly report as `probe-failure`. The effect
709
+ * was that a CORRECTLY built library module (the shape the module contract
710
+ * mandates for the RF family) was reported as a P1 cross-runtime divergence
711
+ * while the browser lane passed: the gate failed the artifact for the gate's
712
+ * own inability to invoke it.
713
+ *
714
+ * Naming the entry is also STRICTLY STRONGER evidence than the old bare
715
+ * invocation: a clean exit 0 means the runtime linked the imports, instantiated
716
+ * the module, and RAN its initialiser — observed, not inferred from an error
717
+ * string.
718
+ */
719
+ export function resolveReactorEntry(loadableBytes) {
720
+ let exportNames;
721
+ try {
722
+ exportNames = readWasmExportNames(loadableBytes);
723
+ } catch {
724
+ return null;
725
+ }
726
+ if (exportNames.includes("_start")) return null;
727
+ return exportNames.includes("_initialize") ? "_initialize" : null;
728
+ }
729
+
691
730
  async function stageArtifact(artifact) {
692
731
  const dir = await mkdtemp(path.join(os.tmpdir(), `sdm-gate-${artifact.id}-`));
693
732
  const basename = "artifact.wasm";
694
733
  await writeFile(path.join(dir, basename), artifact.loadableBytes);
695
- return { dir, basename };
734
+ return {
735
+ dir,
736
+ basename,
737
+ reactorEntry: resolveReactorEntry(artifact.loadableBytes),
738
+ };
739
+ }
740
+
741
+ /**
742
+ * The invocation tail shared by both WasmEdge lanes, so the native and Docker
743
+ * lanes can never drift into probing the same artifact two different ways.
744
+ */
745
+ export function wasmEdgeProbeArgs(staged) {
746
+ return staged.reactorEntry
747
+ ? ["--enable-threads", "--reactor", staged.basename, staged.reactorEntry]
748
+ : ["--enable-threads", staged.basename];
696
749
  }
697
750
 
698
751
  /**