space-data-module-sdk 0.8.9 → 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.
@@ -38,6 +38,8 @@ async function main(argv) {
38
38
  return runFlow(rest);
39
39
  case "parity":
40
40
  return runParity(rest);
41
+ case "parity-gate":
42
+ return runParityGateCommand(rest);
41
43
  case "protect":
42
44
  return runProtect(rest);
43
45
  case "sign":
@@ -142,6 +144,38 @@ function parseArgs(argv) {
142
144
  case "--allow-single-lane":
143
145
  options.allowSingleLane = true;
144
146
  break;
147
+ case "--gate-manifest":
148
+ options.gateManifestPath = path.resolve(
149
+ requireValue(argv, ++index, value),
150
+ );
151
+ break;
152
+ case "--artifact": {
153
+ // --artifact <id>=<path>[:<surface>] — inject an artifact owned by
154
+ // another repo (e.g. a decrypted closed rf-* module) WITHOUT this repo
155
+ // depending on that checkout.
156
+ const spec = requireValue(argv, ++index, value);
157
+ const eq = spec.indexOf("=");
158
+ if (eq < 1) {
159
+ throw new Error(`--artifact expects <id>=<path>[:<surface>], got "${spec}"`);
160
+ }
161
+ const id = spec.slice(0, eq);
162
+ const rest = spec.slice(eq + 1);
163
+ const colon = rest.lastIndexOf(":");
164
+ const hasSurface = colon > 0 && !rest.slice(colon + 1).includes("/");
165
+ options.extraArtifacts = options.extraArtifacts ?? [];
166
+ options.extraArtifacts.push({
167
+ id,
168
+ path: path.resolve(hasSurface ? rest.slice(0, colon) : rest),
169
+ surface: hasSurface ? rest.slice(colon + 1) : "module",
170
+ });
171
+ break;
172
+ }
173
+ case "--require-native-wasmedge":
174
+ options.requireNativeWasmEdge = true;
175
+ break;
176
+ case "--expect-fail":
177
+ options.expectFailure = true;
178
+ break;
145
179
  default:
146
180
  throw new Error(`Unknown argument: ${value}`);
147
181
  }
@@ -166,6 +200,9 @@ function printUsage() {
166
200
  space-data-module parity --wasm ./dist/isomorphic/module.wasm --fixture ./fixtures/parity/basic.json
167
201
  space-data-module parity --wasm ./dist/isomorphic/module.wasm --fixture ./f.json --lanes browser,wasmedge,docker-wasmedge [--json]
168
202
  space-data-module parity ... --self-test-divergence docker-wasmedge (fire drill: prove the diff fails loudly)
203
+ space-data-module parity-gate (THE isomorphism acceptance gate: certified artifact set x real lanes)
204
+ space-data-module parity-gate --artifact rf-fspl=./dist/isomorphic/module.wasm:module --json
205
+ space-data-module parity-gate --gate-manifest ./parity/negative-control.json --expect-fail (prove the gate can fail)
169
206
  space-data-module flow check ./flows/my.flow.json --deps ./modules-root
170
207
  space-data-module flow compile ./flows/my.flow.json --deps ./modules-root [--out ./flows/my/dist]
171
208
  space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --json
@@ -322,6 +359,61 @@ async function runParity(argv) {
322
359
  return report.ok ? 0 : 2;
323
360
  }
324
361
 
362
+ // space-data-module parity-gate [--gate-manifest ./parity/gate.json]
363
+ // [--artifact <id>=<path>[:<surface>]] [--lanes ...] [--json]
364
+ // [--require-native-wasmedge] [--expect-fail] [--timeout-sec N]
365
+ //
366
+ // THE acceptance gate for tri-runtime isomorphism: every artifact in the
367
+ // certified set is really instantiated under the pinned WasmEdge AND in a real
368
+ // headless Chrome behind COOP/COEP, its import set is classified against the
369
+ // declared host contract, and command-profile artifacts are additionally
370
+ // byte-diffed across lanes and thread counts.
371
+ //
372
+ // Exit 0 only when every artifact satisfies the contract in every lane and no
373
+ // behavioral divergence is found. `--expect-fail` inverts the verdict: it is
374
+ // how the negative control (a known-bad artifact) proves the gate can fail.
375
+ async function runParityGateCommand(argv) {
376
+ const options = parseArgs(argv);
377
+ const { runParityGate, formatGateReport, gateReceiptDigest } = await import(
378
+ "../src/testing/parityGate.js"
379
+ );
380
+ const report = await runParityGate({
381
+ manifestPath: options.gateManifestPath,
382
+ extraArtifacts: options.extraArtifacts,
383
+ lanes: options.lanes,
384
+ timeoutMs: options.timeoutMs,
385
+ chromeBinary: options.chromeBinary,
386
+ wasmedgeBinary: options.wasmedgeBinary,
387
+ dockerPlatform: options.dockerPlatform,
388
+ requireNativeWasmEdge: options.requireNativeWasmEdge === true,
389
+ log: (line) => console.error(line),
390
+ });
391
+ if (options.json) {
392
+ console.log(
393
+ JSON.stringify({ ...report, receiptDigest: gateReceiptDigest(report) }, null, 2),
394
+ );
395
+ } else {
396
+ const text = formatGateReport(report);
397
+ if (report.ok) console.log(text);
398
+ else console.error(text);
399
+ }
400
+
401
+ if (options.expectFailure) {
402
+ if (report.ok) {
403
+ console.error(
404
+ "NEGATIVE CONTROL BROKEN: --expect-fail was set and the gate PASSED. " +
405
+ "A gate that cannot fail on a known-bad artifact is not a gate.",
406
+ );
407
+ return 3;
408
+ }
409
+ console.error(
410
+ `negative control OK: gate failed as required (${report.failures.length} failure(s), kinds: ${[...new Set(report.failures.map((f) => f.kind))].join(", ")}).`,
411
+ );
412
+ return 0;
413
+ }
414
+ return report.ok ? 0 : 2;
415
+ }
416
+
325
417
  async function runCheck(argv) {
326
418
  const options = parseArgs(argv);
327
419
  if (options.manifestPath) {
@@ -0,0 +1,95 @@
1
+ # The FlatSQL host contract (why the floor moved to ^1.4.4)
2
+
3
+ ## The defect the bump repairs
4
+
5
+ The SDK floored `flatsql` at `^0.4.2`. `^0.4.2` admits nothing in 1.x, so the
6
+ floor sat a full major behind, and — the part that matters — the 0.4.x
7
+ `wasm/flatsql-wasi.wasm` is an **emscripten-glue artifact**. Measured on the
8
+ shipped bytes:
9
+
10
+ | version | imports | shape |
11
+ | --- | --- | --- |
12
+ | 0.4.2 | **69** | 59 on `env`: ~40 `invoke_*` EH trampolines, `__cxa_*`, `__resumeException`, `llvm_eh_typeid_for`, 10 `__syscall_*`, `emscripten_notify_memory_growth` |
13
+ | 1.4.4 | **13** | 7 `flatsql_io_*` + 6 WASI preview1 |
14
+
15
+ Under the pinned WasmEdge 0.16.4:
16
+
17
+ ```
18
+ 0.4.2 -> instantiation failed: unknown import … "env" "invoke_vi"
19
+ 1.4.4 -> links; blocked only on the DECLARED capability env.flatsql_io_open
20
+ ```
21
+
22
+ Modules are EH-free and `emcc`-shaped artifacts are a browser-only trap, so
23
+ `^0.4.2` pinned a **non-isomorphic engine**. That is an auto-reject under the
24
+ module contract, and the tri-runtime parity gate now says so out loud:
25
+ `flatsql-standalone :: FORBIDDEN import class [emscripten-eh,
26
+ emscripten-runtime, emscripten-syscall] — 59 import(s)`.
27
+
28
+ ## What the bump costs
29
+
30
+ Nothing at the JS call sites. The SDK uses `FlatSQLDatabase.fromSchema`,
31
+ `.query`, `.insert`, and `DirectAccessor.registerAccessor`/`.registerBuilder`
32
+ (`src/runtime-host/flatsqlRuntimeStore.js`); all are present in 1.4.4, and the
33
+ C ABI went 57 → 96 exports with **zero removed**.
34
+
35
+ One real behavioral change surfaced, and it is a *fix*, not a break:
36
+
37
+ > **0.4.2 silently ignored `ORDER BY`.** `listRows()` is
38
+ > `SELECT … ORDER BY schemaFileId, rowId`, and on 0.4.2 it returned INSERTION
39
+ > order. 1.4.4 honours the clause. `test/runtime-host-stream-ingest.test.js`
40
+ > had encoded the broken behaviour (OMM-then-ENTM); it now asserts the sorted
41
+ > contract and checks payloads by handle instead of by position, so it cannot
42
+ > re-encode an engine bug as SDK semantics.
43
+
44
+ ## The host contract 1.4.x makes unconditional
45
+
46
+ 1.4.x imports these seven functions on module `env` **unconditionally**. A host
47
+ that does not supply them cannot instantiate the artifact, so host wiring and
48
+ the artifact bump land together or not at all:
49
+
50
+ ```
51
+ i32 flatsql_io_open(ptr path, i32 pathLen, i32 flags)
52
+ i32 flatsql_io_read(i32 h, ptr dst, i32 len, f64 offset)
53
+ i32 flatsql_io_write(i32 h, ptr src, i32 len, f64 offset)
54
+ i32 flatsql_io_truncate(i32 h, f64 size)
55
+ i32 flatsql_io_sync(i32 h)
56
+ f64 flatsql_io_size(i32 h)
57
+ i32 flatsql_io_close(i32 h)
58
+ ```
59
+
60
+ **Offsets are `f64`, never `i64`.** emscripten legalizes i64 across the JS
61
+ boundary for the browser target and not for `STANDALONE_WASM`; using i64 would
62
+ give one import two different signatures in the two lanes, which is the exact
63
+ shape of a cross-runtime divergence.
64
+
65
+ The SDK publishes this contract so consumers wire it identically in both lanes
66
+ rather than each inventing it:
67
+
68
+ ```js
69
+ import {
70
+ FLATSQL_IO_IMPORTS, // the seven "env.flatsql_io_*" keys
71
+ FLATSQL_IO_SIGNATURES, // params/result valtypes, f64 offsets
72
+ HOST_SURFACES, // HOST_SURFACES["flatsql-engine"]
73
+ } from "space-data-module-sdk/testing";
74
+ ```
75
+
76
+ ## What the SDK does NOT do
77
+
78
+ module-sdk does not link the VFS into its own artifacts, and that is by design:
79
+ an SDK module exports **zero** `flatsql_io_*` imports. A module keeps the
80
+ generic hook set, which rides inside the one sanctioned
81
+ `space_data_module_host` bridge; a private import would be a NEW HOST
82
+ CAPABILITY, and that is an owner decision, never a dependency bump. The SDK
83
+ consumes flatsql through its **JS API** only
84
+ (`src/runtime-host/flatsqlRuntimeStore.js`); flow artifacts receive the live
85
+ engine from the caller as `engineLink` (`src/flow/flowRuntimeHost.js`), so the
86
+ host that instantiates the engine is the host that supplies the seven imports.
87
+
88
+ ## Evidence
89
+
90
+ `space-data-module parity-gate` — the `flatsql-standalone` artifact resolves
91
+ through this repo's own dependency, so the gate always classifies the engine a
92
+ consumer would actually load. At 1.4.4 it reports `in-surface (WASI + declared
93
+ capabilities: 7)`, `satisfied` in the real-browser lane and
94
+ `runner-cannot-supply-declared-capability` under the bare WasmEdge CLI, naming
95
+ `env.flatsql_io_open` as the single blocker.
@@ -0,0 +1,146 @@
1
+ # The Tri-Runtime Parity GATE
2
+
3
+ `docs/tri-runtime-parity.md` describes the parity **harness**: one
4
+ `module.wasm`, one fixture, byte-identical stdout across browser / native
5
+ WasmEdge / Docker WasmEdge. This document describes the **gate** — the thing
6
+ the dev-graph gauntlet actually runs, and the thing an isomorphism claim is
7
+ accepted against.
8
+
9
+ ## Why a gate and not just the harness
10
+
11
+ The harness is necessary and not sufficient:
12
+
13
+ - it can only speak about artifacts that already instantiate in every lane, so
14
+ the most important failure — *an artifact that is not isomorphic at all* —
15
+ is invisible to it;
16
+ - it takes ONE artifact, so it says nothing about the artifact **set** a
17
+ release ships;
18
+ - and until this gate landed, the gauntlet slot that was supposed to enforce
19
+ it was a hardcoded `echo TODO …; exit 1` marked `required: false`. It failed
20
+ on every run for every task, so every receipt read PASS-WITH-GAPS and the
21
+ red became background noise. That is the worst state a gate can be in:
22
+ present enough to look like coverage, incapable of ever providing it.
23
+
24
+ ```
25
+ space-data-module parity-gate # the certified set
26
+ space-data-module parity-gate --json # machine receipt
27
+ space-data-module parity-gate --gate-manifest ./parity/negative-control.json --expect-fail
28
+ ```
29
+
30
+ ## What it checks
31
+
32
+ ### Tier A — host-contract conformance (REAL instantiation, per lane)
33
+
34
+ Every artifact in the certified set is really instantiated:
35
+
36
+ | lane | what it actually is | what it proves |
37
+ | --- | --- | --- |
38
+ | `browser` | real headless Chrome behind COOP/COEP (cross-origin isolated, SAB-capable). Never jsdom. | The artifact instantiates against **exactly** the declared host surface — WASI preview1 stubs plus the declared capability imports, with signatures read from the binary. Anything else it demands is a `LinkError` naming the offender. |
39
+ | `wasmedge-native` | a native WasmEdge binary, version-checked against `src/testing/wasmedgePin.json`. | The artifact links on the real production runtime. |
40
+ | `wasmedge-docker` | the pinned WasmEdge container (image tag **derived** from the same pin). | Same, in the container the fleet ships. |
41
+
42
+ Alongside the runtime probes, every artifact's import section is classified
43
+ structurally against its declared surface, because *why* an instantiation
44
+ failed is the entire content of the finding:
45
+
46
+ - **forbidden import class** — `invoke_*` / `__cxa_*` / `__resumeException` /
47
+ `llvm_eh_typeid_for` (emscripten EH), `__syscall_*` (emscripten JS-library
48
+ syscalls), `emscripten_*` (emscripten runtime hooks), or a single-letter
49
+ import module (the minified emcc browser build). These are `emcc`-shaped
50
+ artifacts: browser-only by construction, EH-carrying, not instantiable on a
51
+ plain WasmEdge host. **Auto-reject.**
52
+ - **outside the declared surface** — a private import is a NEW HOST
53
+ CAPABILITY, which is an owner decision, never a PR.
54
+ - **in-surface** — WASI plus, at most, the declared capability imports.
55
+
56
+ ### Tier B — behavioral parity
57
+
58
+ Artifacts with `profile: "command"` and a fixture additionally go through
59
+ `runParityHarness()`: identical stdin bytes, identical explicit guest env,
60
+ byte-identical stdout and identical trap classes across every lane × thread
61
+ count (1/2/4/8).
62
+
63
+ ## Declared host surfaces
64
+
65
+ | surface | contents |
66
+ | --- | --- |
67
+ | `module` | WASI preview1 + wasi-threads (`wasi.thread-spawn`, `env.memory`) + the ONE sanctioned hostcall bridge `space_data_module_host.{call,response_len,read_response,clear_response,last_status_code,dispatch_current_invocation}` (plus the legacy `sdn_flow_host.dispatch_current_invocation`). The generic hook set (`http`/`tcp`/`wallet_sign`/`keyslot.sign`/clock/fs) rides *inside* that bridge, which is why a module needs no per-capability imports. |
68
+ | `module-standalone` | WASI preview1 + wasi-threads only. |
69
+ | `flatsql-engine` | WASI preview1 + exactly the seven `flatsql_io_*` VFS imports. Offsets are **f64, never i64** — emscripten legalizes i64 across the JS boundary for the browser target and not for `STANDALONE_WASM`, which would give one import two different signatures in the two lanes. |
70
+
71
+ ## Verdicts
72
+
73
+ | verdict | meaning |
74
+ | --- | --- |
75
+ | `satisfied` | instantiated on the declared surface. |
76
+ | `runner-cannot-supply-declared-capability` | did **not** instantiate, and the only blocker is a DECLARED capability the lane runner cannot register. The bare WasmEdge CLI has no mechanism for host functions, so an engine artifact reports this while the browser lane reports `satisfied`. **This is recorded verbatim, never counted as a silent pass**, and the two are treated as agreeing. Upgrade path: graph task `module-sdk-parity-lane-embedded-wasmedge` (run the guest through the node's real WasmEdge *embedding*, which also unlocks threaded guests — `--enable-threads` enables only the threads proposal, not the wasi-threads host module). |
77
+ | `violated` | forbidden class, out-of-surface import, or a link error the contract does not explain. **FAIL.** |
78
+ | `shim-gap` | a lane that *does* supply the declared surface still failed to link a declared capability — an SDK host-shim defect. **FAIL.** |
79
+ | `lane-unavailable` | the lane could not run. **FAIL**, and kept lexically distinct from divergence: a harness that cannot run and a runtime that disagrees must never look alike in a receipt. |
80
+
81
+ Rules, all hard: forbidden class ⇒ FAIL even when every lane agrees (lanes
82
+ agreeing that an artifact is broken everywhere is not parity); lane
83
+ disagreement ⇒ FAIL as a P1 cross-runtime divergence; behavioral divergence ⇒
84
+ FAIL.
85
+
86
+ ## The certified set — and fresh-worktree isolation
87
+
88
+ `parity/gate.json` lists the artifacts this SDK certifies. **Every entry
89
+ resolves inside this repo** — a repo-relative path or a node-resolved
90
+ dependency. That is deliberate: the previous tri-lane test defaulted its
91
+ artifact path to a sibling checkout that does not exist on a normal machine,
92
+ so the acceptance instrument was silently un-runnable and nobody who did not
93
+ set an env var by hand had ever seen it work (graph:
94
+ `parity-harness-cannot-run-locally`). The gate must run in ANY fresh worktree.
95
+
96
+ Artifacts owned by other repos are **injected by the caller**:
97
+
98
+ ```
99
+ space-data-module parity-gate \
100
+ --artifact rf-fspl=./packages/rf-fspl/dist/isomorphic/module.wasm:module
101
+ ```
102
+
103
+ so the owning repo's gauntlet profile supplies its own artifacts and this repo
104
+ never depends on that checkout.
105
+
106
+ ## The WasmEdge pin
107
+
108
+ `src/testing/wasmedgePin.json` remains the single source. Native and container
109
+ runtimes are both version-checked against it; drift is a loud failure, not a
110
+ warning. When no native WasmEdge exists on the box, the report carries an
111
+ explicit `pinVerification.native = absent` gap — the host/container pin PAIR
112
+ could not be cross-verified, which is *recorded*, not passed. Hosts that
113
+ provision the binary run with `--require-native-wasmedge`, which turns the gap
114
+ into a failure.
115
+
116
+ ## The one runtime difference the SDK absorbs (and where)
117
+
118
+ **Measured on WasmEdge 0.16.4, native and containerized: the CLI writes its own
119
+ diagnostics — `[2026-08-08 00:45:38.689] [error] …` — to STDOUT, not stderr.**
120
+ `src/testing/wasmedgeOutput.js` is the shim that absorbs this, and it is a
121
+ *host shim*, which is the only place a runtime difference is ever allowed to
122
+ live. It does two things and nothing else: it lifts the runtime's log lines out
123
+ of the captured stdout so the harness byte-compares the GUEST's bytes, and it
124
+ hands those lines to the classifier so a failed instantiation is actually seen.
125
+
126
+ Both halves were live defects before it existed:
127
+
128
+ - the gate's WasmEdge probe read only stderr and **defaulted to
129
+ `instantiated`** when it recognized nothing — so an artifact that
130
+ demonstrably cannot link (`flatsql-wasi` at 1.4.4 under the bare CLI) read as
131
+ a clean pass. A false pass in an acceptance instrument is worse than no
132
+ instrument. The classifier now requires POSITIVE evidence of linking (clean
133
+ exit, guest output, or a post-link diagnostic such as a missing `_start` on a
134
+ reactor artifact) and otherwise returns `probe-failure`, which fails the gate.
135
+ Regression test: *"a silent nonzero exit is a probe-failure, NEVER an
136
+ inferred pass"*.
137
+ - the parity harness compared those log lines as if they were guest output, so
138
+ any error-path fixture would have reported a phantom output divergence, and
139
+ its trap-class detection (which scanned stderr for trap markers) could never
140
+ fire on a WasmEdge trap.
141
+
142
+ ## Negative control
143
+
144
+ `parity/negative-control.json` declares a known-bad artifact and is run with
145
+ `--expect-fail`; a PASS there is itself an error (exit 3). Keep it. A gate
146
+ nobody has watched fail is indistinguishable from a gate that cannot fail.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.8.9",
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",
@@ -74,6 +74,7 @@
74
74
  "files": [
75
75
  "bin/",
76
76
  "docs/",
77
+ "parity/",
77
78
  "schemas/",
78
79
  "src/",
79
80
  "templates/"
@@ -84,6 +85,10 @@
84
85
  "test:runtime-matrix": "SPACE_DATA_MODULE_SDK_ENABLE_RUNTIME_MATRIX=1 node --test test/runtime-matrix.test.js",
85
86
  "test:parity": "node --test test/parity-harness.test.js",
86
87
  "test:parity-tri-lane": "SPACE_DATA_MODULE_SDK_ENABLE_TRI_RUNTIME_PARITY=1 node --test test/parity-tri-lane.test.js",
88
+ "test:parity-gate": "node --test test/parity-gate.test.js",
89
+ "test:parity-gate-lanes": "SPACE_DATA_MODULE_SDK_ENABLE_PARITY_GATE=1 node --test test/parity-gate-lanes.test.js",
90
+ "gate:parity": "node ./bin/space-data-module.js parity-gate",
91
+ "gate:parity-negative-control": "node ./bin/space-data-module.js parity-gate --gate-manifest ./parity/negative-control.json --expect-fail",
87
92
  "test:stream-ingest": "node --test test/runtime-host-stream-ingest.test.js",
88
93
  "test:module-stream": "node --test test/module-flatbuffer-stream-pump.test.js",
89
94
  "benchmark:stream-1gib": "SPACE_DATA_MODULE_SDK_ENABLE_1GB_STREAM_TEST=1 node --test test/runtime-host-stream-ingest.test.js",
@@ -98,7 +103,7 @@
98
103
  "dependencies": {
99
104
  "flatbuffers": "^25.9.23",
100
105
  "flatc-wasm": "^26.1.32",
101
- "flatsql": "^0.4.2",
106
+ "flatsql": "^1.4.4",
102
107
  "hd-wallet-wasm": "2.0.28",
103
108
  "sdn-emception": "1.0.0",
104
109
  "spacedatastandards.org": "https://github.com/DigitalArsenal/spacedatastandards.org/archive/06deda5079204a46bd97a7ce6ac2868e991c6b8f.tar.gz"
@@ -0,0 +1,28 @@
1
+ {
2
+ "//": "THE representative artifact set the module SDK certifies as isomorphic. Every entry resolves INSIDE this repo (a repo-relative path or a node-resolved dependency) so the gate runs in ANY fresh worktree with zero shared-checkout coupling — reaching into a sibling checkout is exactly what made the previous parity lane silently un-runnable (graph: parity-harness-cannot-run-locally). Artifacts owned by other repos (e.g. a decrypted closed rf-* module) are INJECTED by the caller with --artifact id=path:surface, so the owning repo's gauntlet profile supplies them without this repo ever depending on that checkout.",
3
+ "name": "module-sdk-tri-runtime",
4
+ "artifacts": [
5
+ {
6
+ "id": "sdk-command-module",
7
+ "path": "examples/single-file-bundle/vectors/base-module.wasm",
8
+ "surface": "module",
9
+ "profile": "command",
10
+ "fixture": "sdk-command.json",
11
+ "note": "An SDK-built module: clang wasm32-wasip1-threads, WASI-only imports, generic hooks over the PIV codec on stdin/stdout. Instantiates and RUNS in every lane, so it carries the behavioral (Tier B) parity too."
12
+ },
13
+ {
14
+ "id": "sdk-published-module",
15
+ "path": "examples/single-file-bundle/vectors/single-file-module.wasm",
16
+ "surface": "module",
17
+ "profile": "command",
18
+ "note": "The SAME module after publication: signature/manifest records appended as a trailing custom section. Proves the record-stripping path yields a loadable payload that instantiates identically in all lanes — a published artifact must never be a fourth runtime."
19
+ },
20
+ {
21
+ "id": "flatsql-standalone",
22
+ "packagePath": "flatsql/wasm/flatsql-wasi.wasm",
23
+ "surface": "flatsql-engine",
24
+ "profile": "library",
25
+ "note": "The FlatSQL engine as the SDK's own dependency pin resolves it. Contract: WASI preview1 + EXACTLY the seven declared flatsql_io_* VFS imports (offsets f64, never i64). An emscripten-glue build here is an auto-reject: it cannot instantiate on a plain WasmEdge host, which is precisely the ^0.4.2 floor defect (graph: module-sdk-flatsql-floor-not-isomorphic)."
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "//": "NEGATIVE CONTROL. This manifest deliberately declares an artifact known to violate the module contract, and the gate is run with --expect-fail. A gate that has never been observed to fail is indistinguishable from a gate that cannot fail — which is exactly the defect this whole task exists to repair (the previous slot was a hardcoded `exit 1` that could never go green, and before that a `required:false` red everyone learned to ignore). Run: `space-data-module parity-gate --gate-manifest ./parity/negative-control.json --expect-fail`.",
3
+ "name": "module-sdk-tri-runtime-NEGATIVE-CONTROL",
4
+ "artifacts": [
5
+ {
6
+ "id": "negative-control-flatsql-emscripten-glue",
7
+ "packagePath": "flatsql/wasm/flatsql.wasm",
8
+ "surface": "flatsql-engine",
9
+ "profile": "library",
10
+ "negativeControl": true,
11
+ "note": "The emscripten BROWSER build of the FlatSQL engine, declared against the isomorphic engine surface. It carries minified emscripten glue imports and cannot instantiate on a plain WasmEdge host. The gate MUST report a forbidden-import-class failure. If this ever passes, the classifier has been broken — not the artifact fixed."
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "sdk-command-parity",
3
+ "threadEnvVar": "SDM_PARITY_THREADS",
4
+ "threadCounts": [1, 2, 4, 8],
5
+ "cases": [
6
+ {
7
+ "id": "empty-stdin",
8
+ "stdinUtf8": "",
9
+ "//": "The command surface's no-input path. Every runtime must emit the SAME response frame bytes and take the SAME exit class — a deterministic error path is parity evidence exactly like a success path."
10
+ },
11
+ {
12
+ "id": "malformed-stdin",
13
+ "stdinUtf8": "not a PIV frame",
14
+ "//": "Malformed input: the trap/error CLASS must be identical across runtimes, not merely 'an error in each'."
15
+ },
16
+ {
17
+ "id": "truncated-piv-header",
18
+ "stdinHex": "24504956",
19
+ "//": "A valid PIV file identifier with nothing after it — the truncated-frame path, which is where lanes with different stdin buffering habits diverge if anything does."
20
+ }
21
+ ]
22
+ }
@@ -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) {