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.
- package/bin/space-data-module.js +92 -0
- package/docs/flatsql-host-contract.md +95 -0
- package/docs/tri-runtime-parity-gate.md +146 -0
- package/package.json +7 -2
- package/parity/gate.json +28 -0
- package/parity/negative-control.json +14 -0
- package/parity/sdk-command.json +22 -0
- package/src/standards/catalogCore.js +28 -1
- package/src/testing/hostContract.js +467 -0
- package/src/testing/index.js +32 -0
- package/src/testing/parityGate.js +1147 -0
- package/src/testing/parityGateBrowserProbe.js +171 -0
- package/src/testing/parityLanes.js +11 -6
- package/src/testing/wasmedgeOutput.js +77 -0
package/bin/space-data-module.js
CHANGED
|
@@ -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.
|
|
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": "^
|
|
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"
|
package/parity/gate.json
ADDED
|
@@ -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
|
-
|
|
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) {
|