space-data-module-sdk 0.8.11 → 0.8.13
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 +92 -0
- package/bin/space-data-module.js +85 -1
- package/docs/module-publication-standard.md +7 -3
- package/docs/propagator-abi.md +477 -0
- package/include/orbpro/orbpro_propagator_abi.h +312 -0
- package/package.json +7 -1
- package/schemas/PluginManifest.fbs +46 -1
- package/schemas/orbpro/Propagator.fbs +161 -3
- package/src/browser.js +11 -0
- package/src/bundle/index.js +1 -0
- package/src/bundle/sigdomain.js +22 -0
- package/src/capabilities.js +91 -0
- package/src/compliance/index.js +8 -0
- package/src/compliance/pluginCompliance.js +76 -32
- package/src/flow/flowCompiler.js +231 -3
- package/src/flow/flowRuntimeHost.js +26 -0
- package/src/flow/isomorphicFlowHost.js +8 -0
- package/src/generated/orbpro/manifest/plugin-family.d.ts +9 -1
- package/src/generated/orbpro/manifest/plugin-family.js +8 -0
- package/src/generated/orbpro/manifest/plugin-family.ts +8 -0
- package/src/generated/orbpro/propagator-abi.js +118 -0
- package/src/generated/orbpro/propagator-abi.ts +199 -0
- package/src/host/browserModuleHarness.js +26 -0
- package/src/host/isomorphicLoader.js +57 -11
- package/src/host/runtimeTargetGate.js +256 -0
- package/src/host/workerModuleHarness.js +7 -0
- package/src/index.d.ts +47 -0
- package/src/index.js +11 -0
- package/src/manifest/normalize.js +113 -4
- package/src/scaffold/copyTemplate.js +71 -0
- package/src/scaffold/index.js +150 -0
- package/src/scaffold/tokens.js +90 -0
- package/src/testing/parityBrowserRunner.js +11 -0
- package/src/testing/parityGate.js +287 -27
- package/templates/propagator-module/README.md +99 -0
- package/templates/propagator-module/build.js +103 -0
- package/templates/propagator-module/package.json +19 -0
- package/templates/propagator-module/plugin-manifest.json +66 -0
- package/templates/propagator-module/src/__MODULE_NAME_SNAKE__.cpp +450 -0
- package/templates/propagator-module/tests/module.build.test.mjs +103 -0
package/README.md
CHANGED
|
@@ -241,6 +241,98 @@ treats that as the explicit "one binary for both" profile. That pair now
|
|
|
241
241
|
defaults to a shared `single-thread` artifact so the compiled wasm can be loaded
|
|
242
242
|
unchanged by the browser harness and the WasmEdge harness.
|
|
243
243
|
|
|
244
|
+
### Composed flows derive their targets
|
|
245
|
+
|
|
246
|
+
A COMPOSED flow artifact (`space-data-module flow compile`) does not declare
|
|
247
|
+
its own `runtimeTargets` — it **derives** them, inside the universe a composed
|
|
248
|
+
artifact can reach at all (`browser` + `wasmedge`; the flow runtime template is
|
|
249
|
+
never a standalone `wasi` command, a `node` package, a `desktop` bundle or an
|
|
250
|
+
`edge` worker). Two things narrow that universe:
|
|
251
|
+
|
|
252
|
+
1. **Every part's declaration**, tested with `runtimeTargetSatisfies` — THE
|
|
253
|
+
SAME function every loader uses, so a declaration cannot mean one thing to
|
|
254
|
+
the compiler and another at the door. A flow runs only where all of its
|
|
255
|
+
parts run. A part that declares nothing constrains nothing. `wasi` is the
|
|
256
|
+
strict portability baseline rather than a fourth runtime, so a part
|
|
257
|
+
declaring it admits both legs — unless that part's own capabilities say
|
|
258
|
+
otherwise (`pipe` is in both the standalone-WASI subset and the
|
|
259
|
+
browser-incompatible set).
|
|
260
|
+
2. **The capability union.** A capability in `BrowserIncompatibleCapabilityIds`
|
|
261
|
+
(`wallet_sign`, `tcp`, `storage_write`, …) drops `browser` regardless of what
|
|
262
|
+
anything declared, because the composition provably cannot run there. This is
|
|
263
|
+
the proof-based half: it catches the commonest manifest shape, a plugin that
|
|
264
|
+
carries such a capability and simply omits `runtimeTargets`.
|
|
265
|
+
|
|
266
|
+
Outcomes:
|
|
267
|
+
|
|
268
|
+
- All parts isomorphic, no browser-incompatible capability → the flow keeps
|
|
269
|
+
`["browser", "wasmedge"]`.
|
|
270
|
+
- One WasmEdge-only part, or one browser-incompatible capability → the flow is
|
|
271
|
+
`["wasmedge"]`, its `buildArtifacts[].target` says `wasmedge`, and a
|
|
272
|
+
`narrowed-runtime-targets` warning names what cost it the browser. Losing a
|
|
273
|
+
runtime is never silent.
|
|
274
|
+
- No shared runtime → `empty-runtime-target-intersection`, a hard error naming
|
|
275
|
+
the constraining plugins.
|
|
276
|
+
|
|
277
|
+
This is what makes a host-only capability usable from a flow at all: the
|
|
278
|
+
composed manifest is compliance-validated before the bake, and a
|
|
279
|
+
browser-incompatible capability next to a `browser` target is a hard
|
|
280
|
+
`capability-runtime-conflict`. Deriving the target set keeps that rule intact —
|
|
281
|
+
the conflict still fires whenever a browser target is genuinely claimed — while
|
|
282
|
+
letting the legitimate WasmEdge-only composition exist. `wallet_sign` gates
|
|
283
|
+
`keyslot.unwrap`, so before this the hardcoded pair meant no flow could unwrap a
|
|
284
|
+
host-held key at all.
|
|
285
|
+
|
|
286
|
+
### Both legs refuse an artifact that is not theirs
|
|
287
|
+
|
|
288
|
+
A legitimate single-leg artifact now exists, so every loader that stands for a
|
|
289
|
+
runtime leg refuses one that declares itself out of scope — by name, quoting
|
|
290
|
+
the declaration, never a silent skip and never a trap five frames deep inside a
|
|
291
|
+
capability that leg cannot serve. The shared assert is
|
|
292
|
+
`src/host/runtimeTargetGate.js`:
|
|
293
|
+
|
|
294
|
+
| Loader | Leg |
|
|
295
|
+
| --- | --- |
|
|
296
|
+
| `createBrowserModuleHarness` | `browser` |
|
|
297
|
+
| `createWorkerModuleHarness` | `browser`, checked before any worker is spawned |
|
|
298
|
+
| `createIsomorphicFlowRuntimeHost` | `browser` (it mounts children in the browser harness) |
|
|
299
|
+
| `createFlowRuntimeHost` | stated via `runtimeTarget`; defaults to `browser` in a real browser, ungated elsewhere |
|
|
300
|
+
| `loadModule({runtimeKind: "wasmedge"})` | `wasmedge` |
|
|
301
|
+
|
|
302
|
+
Both declarations are consulted — the caller-supplied manifest and the
|
|
303
|
+
artifact's own embedded `$PLG` — and **the embedded one wins on conflict**,
|
|
304
|
+
because that is what the artifact's signature covers. The refusal throws a
|
|
305
|
+
`RuntimeTargetError` carrying `declaredTargets`, `leg` and `declarationSource`.
|
|
306
|
+
|
|
307
|
+
A declaration that names the leg outright is trusted as written; the `wasi`
|
|
308
|
+
baseline, which is an inference the SDK makes on the author's behalf, is
|
|
309
|
+
additionally capability-checked. Declaration → trust the author; inference →
|
|
310
|
+
prove it.
|
|
311
|
+
|
|
312
|
+
**Consumer migration.** `createFlowRuntimeHost` is runtime-agnostic and only
|
|
313
|
+
detects a real browser. If you drive a composed flow from Node — a flow test, a
|
|
314
|
+
server-side runner — state the leg, or the mirror gate is principle rather than
|
|
315
|
+
fact:
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
const host = await createFlowRuntimeHost({
|
|
319
|
+
wasmSource: bytes,
|
|
320
|
+
runtimeTarget: "wasmedge",
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The tri-runtime parity gate reads the same declaration: a lane the artifact
|
|
325
|
+
declared itself out of is scored `out-of-declared-scope`, skipped before it is
|
|
326
|
+
launched, and recorded as evidence rather than compared. Without that, a
|
|
327
|
+
correct refusal on the browser lane would have been counted as a P1
|
|
328
|
+
cross-runtime divergence.
|
|
329
|
+
|
|
330
|
+
Scoping is not a way to be certified without being run. An artifact in the
|
|
331
|
+
gate's certified set that scopes itself out of every active lane fails
|
|
332
|
+
(`artifact-out-of-every-lane`), and one that leaves fewer than two lanes to
|
|
333
|
+
compare fails too (`artifact-not-cross-runtime-comparable`) — a single lane
|
|
334
|
+
proves no parity, and one manifest string must never disarm the gate.
|
|
335
|
+
|
|
244
336
|
## WasmEdge Pthreads
|
|
245
337
|
|
|
246
338
|
`space-data-module-sdk` is also the source of truth for module thread-model
|
package/bin/space-data-module.js
CHANGED
|
@@ -34,6 +34,8 @@ async function main(argv) {
|
|
|
34
34
|
return runCheck(rest);
|
|
35
35
|
case "compile":
|
|
36
36
|
return runCompile(rest);
|
|
37
|
+
case "init":
|
|
38
|
+
return runInit(rest);
|
|
37
39
|
case "flow":
|
|
38
40
|
return runFlow(rest);
|
|
39
41
|
case "parity":
|
|
@@ -98,6 +100,18 @@ function parseArgs(argv) {
|
|
|
98
100
|
case "--out":
|
|
99
101
|
options.outputPath = path.resolve(requireValue(argv, ++index, value));
|
|
100
102
|
break;
|
|
103
|
+
case "--family":
|
|
104
|
+
options.family = requireValue(argv, ++index, value);
|
|
105
|
+
break;
|
|
106
|
+
case "--name":
|
|
107
|
+
options.name = requireValue(argv, ++index, value);
|
|
108
|
+
break;
|
|
109
|
+
case "--plugin-id":
|
|
110
|
+
options.pluginId = requireValue(argv, ++index, value);
|
|
111
|
+
break;
|
|
112
|
+
case "--force":
|
|
113
|
+
options.force = true;
|
|
114
|
+
break;
|
|
101
115
|
case "--recipient-public-key":
|
|
102
116
|
options.recipientPublicKeyHex = requireValue(argv, ++index, value);
|
|
103
117
|
break;
|
|
@@ -170,6 +184,28 @@ function parseArgs(argv) {
|
|
|
170
184
|
});
|
|
171
185
|
break;
|
|
172
186
|
}
|
|
187
|
+
case "--artifact-lanes": {
|
|
188
|
+
// --artifact-lanes <id>=<lane,lane> — the GATE'S claim about which
|
|
189
|
+
// lanes an artifact owes evidence on. Without this the strongest of the
|
|
190
|
+
// three scoping rules ("the artifact does not pick its examiners") was
|
|
191
|
+
// reachable only from a gate manifest file, i.e. never for the
|
|
192
|
+
// cross-repo artifacts injected with --artifact, which are exactly the
|
|
193
|
+
// ones whose declarations this repo does not own.
|
|
194
|
+
const spec = requireValue(argv, ++index, value);
|
|
195
|
+
const eq = spec.indexOf("=");
|
|
196
|
+
if (eq < 1) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`--artifact-lanes expects <id>=<lane,lane>, got "${spec}"`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
options.expectedLanesById = options.expectedLanesById ?? {};
|
|
202
|
+
options.expectedLanesById[spec.slice(0, eq)] = spec
|
|
203
|
+
.slice(eq + 1)
|
|
204
|
+
.split(",")
|
|
205
|
+
.map((lane) => lane.trim())
|
|
206
|
+
.filter(Boolean);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
173
209
|
case "--require-native-wasmedge":
|
|
174
210
|
options.requireNativeWasmEdge = true;
|
|
175
211
|
break;
|
|
@@ -197,6 +233,8 @@ function printUsage() {
|
|
|
197
233
|
space-data-module check --repo-root .
|
|
198
234
|
space-data-module check --manifest ./manifest.json --wasm ./dist/module.wasm
|
|
199
235
|
space-data-module compile --manifest ./manifest.json --source ./src/module.c --out ./dist/module.wasm
|
|
236
|
+
space-data-module init --family propagator --name my-propagator
|
|
237
|
+
space-data-module init --family propagator --name my-propagator --out ./modules/my-propagator [--plugin-id com.orbpro.my.propagator] [--force] [--json]
|
|
200
238
|
space-data-module parity --wasm ./dist/isomorphic/module.wasm --fixture ./fixtures/parity/basic.json
|
|
201
239
|
space-data-module parity --wasm ./dist/isomorphic/module.wasm --fixture ./f.json --lanes browser,wasmedge,docker-wasmedge [--json]
|
|
202
240
|
space-data-module parity ... --self-test-divergence docker-wasmedge (fire drill: prove the diff fails loudly)
|
|
@@ -264,6 +302,7 @@ async function runFlow(argv) {
|
|
|
264
302
|
console.log(` ${issue.severity.toUpperCase()} ${issue.code}: ${issue.message}`);
|
|
265
303
|
}
|
|
266
304
|
console.log(` capabilities: [${check.capabilities.join(", ")}]`);
|
|
305
|
+
console.log(` runtimeTargets: [${(check.runtimeTargets ?? []).join(", ")}]`);
|
|
267
306
|
for (const node of check.nodes) {
|
|
268
307
|
console.log(` node ${node.nodeId}: ${node.pluginId}:${node.methodId} (${node.dispatchModel})`);
|
|
269
308
|
}
|
|
@@ -307,6 +346,9 @@ async function runFlow(argv) {
|
|
|
307
346
|
} else {
|
|
308
347
|
console.log(`Wrote ${result.outputs.moduleWasmPath}`);
|
|
309
348
|
console.log(` capabilities: [${result.check.capabilities.join(", ")}]`);
|
|
349
|
+
console.log(
|
|
350
|
+
` runtimeTargets: [${(result.manifest?.runtimeTargets ?? result.check.runtimeTargets ?? []).join(", ")}]`,
|
|
351
|
+
);
|
|
310
352
|
for (const node of result.check.nodes) {
|
|
311
353
|
console.log(` node ${node.nodeId}: ${node.pluginId}:${node.methodId} (${node.dispatchModel})`);
|
|
312
354
|
}
|
|
@@ -379,7 +421,13 @@ async function runParityGateCommand(argv) {
|
|
|
379
421
|
);
|
|
380
422
|
const report = await runParityGate({
|
|
381
423
|
manifestPath: options.gateManifestPath,
|
|
382
|
-
extraArtifacts: options.extraArtifacts
|
|
424
|
+
extraArtifacts: (options.extraArtifacts ?? []).map((artifact) => ({
|
|
425
|
+
...artifact,
|
|
426
|
+
...(options.expectedLanesById?.[artifact.id]
|
|
427
|
+
? { expectedLanes: options.expectedLanesById[artifact.id] }
|
|
428
|
+
: {}),
|
|
429
|
+
})),
|
|
430
|
+
expectedLanesById: options.expectedLanesById,
|
|
383
431
|
lanes: options.lanes,
|
|
384
432
|
timeoutMs: options.timeoutMs,
|
|
385
433
|
chromeBinary: options.chromeBinary,
|
|
@@ -500,6 +548,42 @@ async function runCompile(argv) {
|
|
|
500
548
|
return result.report.ok ? 0 : 1;
|
|
501
549
|
}
|
|
502
550
|
|
|
551
|
+
// space-data-module init --family propagator --name <module-name>
|
|
552
|
+
// [--out <dir>] [--plugin-id <id>] [--force] [--json]
|
|
553
|
+
//
|
|
554
|
+
// Scaffolds a new SDN WASM module skeleton from templates/<family>-module/.
|
|
555
|
+
// An unrecognized --family FAILS LOUDLY (see src/scaffold/index.js) — there
|
|
556
|
+
// is no generic fallback template.
|
|
557
|
+
async function runInit(argv) {
|
|
558
|
+
const options = parseArgs(argv);
|
|
559
|
+
if (!options.family) {
|
|
560
|
+
throw new Error("init requires --family <family> (e.g. --family propagator).");
|
|
561
|
+
}
|
|
562
|
+
if (!options.name) {
|
|
563
|
+
throw new Error("init requires --name <module-name>.");
|
|
564
|
+
}
|
|
565
|
+
const { scaffoldModule } = await import("../src/scaffold/index.js");
|
|
566
|
+
const result = await scaffoldModule({
|
|
567
|
+
family: options.family,
|
|
568
|
+
name: options.name,
|
|
569
|
+
outDir: options.outputPath ?? undefined,
|
|
570
|
+
pluginId: options.pluginId,
|
|
571
|
+
force: options.force === true,
|
|
572
|
+
});
|
|
573
|
+
if (options.json) {
|
|
574
|
+
console.log(JSON.stringify(result, null, 2));
|
|
575
|
+
} else {
|
|
576
|
+
console.log(
|
|
577
|
+
`Scaffolded ${result.family} module "${result.name}" into ${result.outDir}`,
|
|
578
|
+
);
|
|
579
|
+
console.log(` pluginId=${result.pluginId}`);
|
|
580
|
+
for (const file of result.files) {
|
|
581
|
+
console.log(` created ${file}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return result.ok ? 0 : 1;
|
|
585
|
+
}
|
|
586
|
+
|
|
503
587
|
async function runProtect(argv) {
|
|
504
588
|
const options = parseArgs(argv);
|
|
505
589
|
if (!options.manifestPath || !options.wasmPath) {
|
|
@@ -128,7 +128,9 @@ instantiates and the node's capability policy identifies by content hash.
|
|
|
128
128
|
|
|
129
129
|
The domain prefix exists because the node's publisher key is the NODE key, and
|
|
130
130
|
that one bonded key signs several unrelated kinds of statement (dataset
|
|
131
|
-
publications, module artifacts,
|
|
131
|
+
publications, module artifacts, update SIGNALS — the advisory pub/sub nudge a
|
|
132
|
+
publisher pushes so every install upgrades itself in place, which has a live
|
|
133
|
+
producer — and, still reserved, update manifests). If every
|
|
132
134
|
statement were a bare SHA-256 digest, a caller who could reach the node's
|
|
133
135
|
signing endpoint could submit the bytes of one kind of document and staple the
|
|
134
136
|
returned signature onto another. An ASCII domain label, a `NUL` that cannot
|
|
@@ -137,8 +139,10 @@ in a disjoint message space with no length ambiguity anywhere in the preimage.
|
|
|
137
139
|
|
|
138
140
|
The domain registry is **closed**: a verifier refuses any label that is not
|
|
139
141
|
registered, and a module verifier additionally refuses any registered label
|
|
140
|
-
other than `SDN-MODULE-PUBLICATION-V1` — so a signature minted for
|
|
141
|
-
update can never be replayed into a module trailer
|
|
142
|
+
other than `SDN-MODULE-PUBLICATION-V1` — so a signature minted for an update
|
|
143
|
+
manifest or an update signal can never be replayed into a module trailer, which
|
|
144
|
+
the shared vectors pin as `foreign-registered-domain` and
|
|
145
|
+
`update-signal-domain-is-refused-for-a-module`. Adding a domain is a
|
|
142
146
|
reviewed change in every implementation at once
|
|
143
147
|
(`src/bundle/sigdomain.js` here; `internal/sigdomain` and its kubo twin in the
|
|
144
148
|
node), never a request parameter.
|