space-data-module-sdk 0.1.0
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 +139 -0
- package/bin/space-data-module.js +230 -0
- package/package.json +40 -0
- package/schemas/PluginManifest.fbs +144 -0
- package/schemas/TypedArenaBuffer.fbs +78 -0
- package/src/auth/canonicalize.js +72 -0
- package/src/auth/index.js +10 -0
- package/src/auth/permissions.js +190 -0
- package/src/compiler/compileModule.js +237 -0
- package/src/compiler/index.js +7 -0
- package/src/compliance/index.js +56 -0
- package/src/compliance/pluginCompliance.js +574 -0
- package/src/embeddedManifest.js +124 -0
- package/src/generated/orbpro/manifest/accepted-type-set.d.ts +45 -0
- package/src/generated/orbpro/manifest/accepted-type-set.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/accepted-type-set.js +100 -0
- package/src/generated/orbpro/manifest/accepted-type-set.js.map +1 -0
- package/src/generated/orbpro/manifest/accepted-type-set.ts +200 -0
- package/src/generated/orbpro/manifest/build-artifact.d.ts +41 -0
- package/src/generated/orbpro/manifest/build-artifact.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/build-artifact.js +105 -0
- package/src/generated/orbpro/manifest/build-artifact.js.map +1 -0
- package/src/generated/orbpro/manifest/build-artifact.ts +193 -0
- package/src/generated/orbpro/manifest/capability-kind.d.ts +17 -0
- package/src/generated/orbpro/manifest/capability-kind.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/capability-kind.js +20 -0
- package/src/generated/orbpro/manifest/capability-kind.js.map +1 -0
- package/src/generated/orbpro/manifest/capability-kind.ts +20 -0
- package/src/generated/orbpro/manifest/drain-policy.d.ts +9 -0
- package/src/generated/orbpro/manifest/drain-policy.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/drain-policy.js +12 -0
- package/src/generated/orbpro/manifest/drain-policy.js.map +1 -0
- package/src/generated/orbpro/manifest/drain-policy.ts +12 -0
- package/src/generated/orbpro/manifest/host-capability.d.ts +36 -0
- package/src/generated/orbpro/manifest/host-capability.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/host-capability.js +91 -0
- package/src/generated/orbpro/manifest/host-capability.js.map +1 -0
- package/src/generated/orbpro/manifest/host-capability.ts +161 -0
- package/src/generated/orbpro/manifest/method-manifest.d.ts +53 -0
- package/src/generated/orbpro/manifest/method-manifest.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/method-manifest.js +154 -0
- package/src/generated/orbpro/manifest/method-manifest.js.map +1 -0
- package/src/generated/orbpro/manifest/method-manifest.ts +306 -0
- package/src/generated/orbpro/manifest/plugin-family.d.ts +17 -0
- package/src/generated/orbpro/manifest/plugin-family.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/plugin-family.js +20 -0
- package/src/generated/orbpro/manifest/plugin-family.js.map +1 -0
- package/src/generated/orbpro/manifest/plugin-family.ts +20 -0
- package/src/generated/orbpro/manifest/plugin-manifest.d.ts +85 -0
- package/src/generated/orbpro/manifest/plugin-manifest.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/plugin-manifest.js +268 -0
- package/src/generated/orbpro/manifest/plugin-manifest.js.map +1 -0
- package/src/generated/orbpro/manifest/plugin-manifest.ts +562 -0
- package/src/generated/orbpro/manifest/port-manifest.d.ts +70 -0
- package/src/generated/orbpro/manifest/port-manifest.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/port-manifest.js +150 -0
- package/src/generated/orbpro/manifest/port-manifest.js.map +1 -0
- package/src/generated/orbpro/manifest/port-manifest.ts +284 -0
- package/src/generated/orbpro/manifest/protocol-spec.d.ts +41 -0
- package/src/generated/orbpro/manifest/protocol-spec.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/protocol-spec.js +105 -0
- package/src/generated/orbpro/manifest/protocol-spec.js.map +1 -0
- package/src/generated/orbpro/manifest/protocol-spec.ts +205 -0
- package/src/generated/orbpro/manifest/timer-spec.d.ts +40 -0
- package/src/generated/orbpro/manifest/timer-spec.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/timer-spec.js +104 -0
- package/src/generated/orbpro/manifest/timer-spec.js.map +1 -0
- package/src/generated/orbpro/manifest/timer-spec.ts +195 -0
- package/src/generated/orbpro/manifest.js +14 -0
- package/src/generated/orbpro/stream/buffer-mutability.d.ts +9 -0
- package/src/generated/orbpro/stream/buffer-mutability.d.ts.map +1 -0
- package/src/generated/orbpro/stream/buffer-mutability.js +12 -0
- package/src/generated/orbpro/stream/buffer-mutability.js.map +1 -0
- package/src/generated/orbpro/stream/buffer-mutability.ts +12 -0
- package/src/generated/orbpro/stream/buffer-ownership.d.ts +10 -0
- package/src/generated/orbpro/stream/buffer-ownership.d.ts.map +1 -0
- package/src/generated/orbpro/stream/buffer-ownership.js +13 -0
- package/src/generated/orbpro/stream/buffer-ownership.js.map +1 -0
- package/src/generated/orbpro/stream/buffer-ownership.ts +13 -0
- package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts +51 -0
- package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts.map +1 -0
- package/src/generated/orbpro/stream/flat-buffer-type-ref.js +115 -0
- package/src/generated/orbpro/stream/flat-buffer-type-ref.js.map +1 -0
- package/src/generated/orbpro/stream/flat-buffer-type-ref.ts +222 -0
- package/src/generated/orbpro/stream/typed-arena-buffer.d.ts +100 -0
- package/src/generated/orbpro/stream/typed-arena-buffer.d.ts.map +1 -0
- package/src/generated/orbpro/stream/typed-arena-buffer.js +215 -0
- package/src/generated/orbpro/stream/typed-arena-buffer.js.map +1 -0
- package/src/generated/orbpro/stream/typed-arena-buffer.ts +344 -0
- package/src/index.js +8 -0
- package/src/manifest/codec.js +40 -0
- package/src/manifest/index.js +9 -0
- package/src/manifest/normalize.js +275 -0
- package/src/runtime/bufferLike.js +28 -0
- package/src/runtime/constants.js +34 -0
- package/src/standards/index.js +153 -0
- package/src/standards/sharedCatalog.js +196 -0
- package/src/transport/index.js +8 -0
- package/src/transport/pki.js +140 -0
- package/src/utils/crypto.js +8 -0
- package/src/utils/encoding.js +54 -0
- package/src/utils/wasmCrypto.js +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Space Data Module SDK
|
|
2
|
+
|
|
3
|
+
A unified SDK for building, validating, signing, and deploying **WebAssembly plugin modules** that run anywhere on the [Space Data Network](https://digitalarsenal.github.io/space-data-network/) — from [OrbPro](https://orbpro.ai) desktops to SDN peer nodes, ground stations, and browsers.
|
|
4
|
+
|
|
5
|
+
The space domain has a fragmentation problem: every platform ships its own plugin format, its own manifest schema, its own packaging conventions. A propagator written for one system can't run on another without a rewrite. This SDK solves that by defining a **single canonical module format** — a WebAssembly binary with an embedded [FlatBuffers](https://digitalarsenal.github.io/flatbuffers/) manifest — that every runtime in the ecosystem understands.
|
|
6
|
+
|
|
7
|
+
Modules declare **typed streaming ports** that accept data conforming to [spacedatastandards.org](https://spacedatastandards.org) schemas (OMM, CAT, EPM, CDM, and 40+ others). A propagator that consumes OMM messages and emits state vectors works identically whether it's running inside OrbPro's 3D scene, processing data on an SDN relay node, or executing at the edge on a ground station.
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="docs/architecture.svg" alt="Architecture overview" width="820" />
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
The SDK handles the full module lifecycle — from source code to a signed, encrypted, deployment-ready package:
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="docs/module-lifecycle.svg" alt="Module lifecycle" width="820" />
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
1. **Author** a JSON manifest declaring your module's identity, methods, typed I/O ports, host capabilities, and the [spacedatastandards.org](https://spacedatastandards.org) schemas it consumes and produces.
|
|
22
|
+
2. **Compile** your C/C++ source (via Emscripten) into a `.wasm` binary with the manifest automatically embedded as a FlatBuffers blob that runtimes can read at load time.
|
|
23
|
+
3. **Validate** the manifest and artifact against compliance rules — correct port declarations, canonical capability IDs, required WASM ABI exports (`plugin_get_manifest_flatbuffer`, `plugin_get_manifest_flatbuffer_size`), and schema resolution against the standards catalog.
|
|
24
|
+
4. **Sign** the package with an HD-wallet-derived secp256k1 key, producing a deployment authorization that binds the manifest hash, WASM hash, target, and granted capabilities.
|
|
25
|
+
5. **Protect** the signed package by encrypting it for a specific recipient using X25519 key agreement + AES-256-GCM, so modules can be transported securely across the network.
|
|
26
|
+
|
|
27
|
+
## Manifest & Typed Ports
|
|
28
|
+
|
|
29
|
+
Every module carries a manifest that declares **what data it can process**. Methods expose typed input and output ports, and each port declares the FlatBuffer schemas it accepts — referencing standards by schema name and file identifier:
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img src="docs/manifest-structure.svg" alt="Manifest structure" width="820" />
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
This means runtimes can **automatically wire modules together** — connecting a propagator's `CAT` output to a conjunction screener's `CAT` input — without any glue code. The type system ensures only compatible modules get connected.
|
|
36
|
+
|
|
37
|
+
## Ecosystem
|
|
38
|
+
|
|
39
|
+
This SDK is one piece of the Space Data Network stack:
|
|
40
|
+
|
|
41
|
+
| Project | Role |
|
|
42
|
+
|---|---|
|
|
43
|
+
| [Space Data Network](https://digitalarsenal.github.io/space-data-network/) | Peer-to-peer network for space data exchange |
|
|
44
|
+
| [spacedatastandards.org](https://spacedatastandards.org) | 40+ canonical FlatBuffer schemas for space operations data (OMM, EPM, CAT, CDM, etc.) |
|
|
45
|
+
| [FlatBuffers schemas](https://digitalarsenal.github.io/flatbuffers/) | Binary serialization layer used across the entire network |
|
|
46
|
+
| [OrbPro](https://orbpro.ai) | Space domain awareness platform — one of the runtimes that hosts these modules |
|
|
47
|
+
| [hd-wallet-wasm](https://github.com/nicktj-dev/hd-wallet-wasm) | HD wallet primitives for module signing and identity |
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install space-data-module-sdk
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
import {
|
|
59
|
+
encodeManifest, decodeManifest, // FlatBuffers manifest codec
|
|
60
|
+
checkCompliance, // validate against standards
|
|
61
|
+
signManifest, verifyManifest, // HD wallet auth
|
|
62
|
+
encryptPayload, decryptPayload, // X25519 + AES-256-GCM transport
|
|
63
|
+
compileModule, // source-to-wasm compilation
|
|
64
|
+
} from "space-data-module-sdk";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Subpath Exports
|
|
68
|
+
|
|
69
|
+
Each subsystem is available as a standalone import:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { encodeManifest } from "space-data-module-sdk/manifest";
|
|
73
|
+
import { checkCompliance } from "space-data-module-sdk/compliance";
|
|
74
|
+
import { signManifest } from "space-data-module-sdk/auth";
|
|
75
|
+
import { encryptPayload } from "space-data-module-sdk/transport";
|
|
76
|
+
import { compileModule } from "space-data-module-sdk/compiler";
|
|
77
|
+
import { resolveStandard } from "space-data-module-sdk/standards";
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## CLI
|
|
81
|
+
|
|
82
|
+
Every SDK operation is also available from the command line:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Validate a manifest + wasm pair against compliance rules
|
|
86
|
+
npx space-data-module check --manifest ./manifest.json --wasm ./dist/module.wasm
|
|
87
|
+
|
|
88
|
+
# Compile C/C++ source to a wasm module with embedded manifest
|
|
89
|
+
npx space-data-module compile --manifest ./manifest.json --source ./src/module.c --out ./dist/module.wasm
|
|
90
|
+
|
|
91
|
+
# Sign and encrypt a module package for transport
|
|
92
|
+
npx space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Module Lab
|
|
96
|
+
|
|
97
|
+
An interactive browser tool for compiling, validating, and packaging modules — useful for exploring the manifest format and testing modules without touching the CLI.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm run start:lab
|
|
101
|
+
# http://localhost:4318
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Plugin Families
|
|
105
|
+
|
|
106
|
+
Modules declare a `pluginFamily` that tells runtimes what role they serve:
|
|
107
|
+
|
|
108
|
+
| Family | Purpose |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `sensor` | Ingest raw data feeds (radar, optical, RF) |
|
|
111
|
+
| `propagator` | Orbit propagation and state prediction |
|
|
112
|
+
| `renderer` | 3D visualization and scene rendering |
|
|
113
|
+
| `analysis` | Data processing, filtering, aggregation |
|
|
114
|
+
| `data_source` | External data connectors |
|
|
115
|
+
| `comms` | Communications link modeling |
|
|
116
|
+
| `shader` | GPU shader programs |
|
|
117
|
+
| `sdf` | Signed distance field geometry |
|
|
118
|
+
| `infrastructure` | Network and platform services |
|
|
119
|
+
| `flow` | Multi-module data flow orchestration |
|
|
120
|
+
| `bridge` | Cross-runtime adapters |
|
|
121
|
+
|
|
122
|
+
## Host Capabilities
|
|
123
|
+
|
|
124
|
+
Modules request host capabilities by name. The runtime grants or denies them at load time based on the deployment authorization:
|
|
125
|
+
|
|
126
|
+
`clock` `random` `timers` `http` `network` `filesystem` `pipe` `pubsub` `protocol_handle` `protocol_dial` `database` `storage_adapter` `storage_query` `storage_write` `wallet_sign` `ipfs` `scene_access` `render_hooks`
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm install
|
|
132
|
+
npm test
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Requires Node.js >= 20. Compilation requires [Emscripten](https://emscripten.org/) (`emcc`/`em++`) on `PATH`.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
compileModuleFromSource,
|
|
9
|
+
loadComplianceConfig,
|
|
10
|
+
protectModuleArtifact,
|
|
11
|
+
resolveManifestFiles,
|
|
12
|
+
validateArtifactWithStandards,
|
|
13
|
+
validateManifestWithStandards,
|
|
14
|
+
loadManifestFromFile,
|
|
15
|
+
} from "../src/index.js";
|
|
16
|
+
import { bytesToBase64 } from "../src/utils/encoding.js";
|
|
17
|
+
|
|
18
|
+
async function main(argv) {
|
|
19
|
+
const [command, ...rest] = argv;
|
|
20
|
+
switch (command) {
|
|
21
|
+
case "check":
|
|
22
|
+
return runCheck(rest);
|
|
23
|
+
case "compile":
|
|
24
|
+
return runCompile(rest);
|
|
25
|
+
case "protect":
|
|
26
|
+
return runProtect(rest);
|
|
27
|
+
default:
|
|
28
|
+
printUsage();
|
|
29
|
+
return command ? 1 : 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseArgs(argv) {
|
|
34
|
+
const options = {
|
|
35
|
+
json: false,
|
|
36
|
+
repoRoot: process.cwd(),
|
|
37
|
+
manifestPath: null,
|
|
38
|
+
wasmPath: null,
|
|
39
|
+
sourcePath: null,
|
|
40
|
+
language: "c",
|
|
41
|
+
outputPath: null,
|
|
42
|
+
recipientPublicKeyHex: null,
|
|
43
|
+
mnemonic: null,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
47
|
+
const value = argv[index];
|
|
48
|
+
switch (value) {
|
|
49
|
+
case "--json":
|
|
50
|
+
options.json = true;
|
|
51
|
+
break;
|
|
52
|
+
case "--repo-root":
|
|
53
|
+
options.repoRoot = path.resolve(requireValue(argv, ++index, value));
|
|
54
|
+
break;
|
|
55
|
+
case "--manifest":
|
|
56
|
+
options.manifestPath = path.resolve(requireValue(argv, ++index, value));
|
|
57
|
+
break;
|
|
58
|
+
case "--wasm":
|
|
59
|
+
options.wasmPath = path.resolve(requireValue(argv, ++index, value));
|
|
60
|
+
break;
|
|
61
|
+
case "--source":
|
|
62
|
+
options.sourcePath = path.resolve(requireValue(argv, ++index, value));
|
|
63
|
+
break;
|
|
64
|
+
case "--language":
|
|
65
|
+
options.language = requireValue(argv, ++index, value);
|
|
66
|
+
break;
|
|
67
|
+
case "--out":
|
|
68
|
+
options.outputPath = path.resolve(requireValue(argv, ++index, value));
|
|
69
|
+
break;
|
|
70
|
+
case "--recipient-public-key":
|
|
71
|
+
options.recipientPublicKeyHex = requireValue(argv, ++index, value);
|
|
72
|
+
break;
|
|
73
|
+
case "--mnemonic":
|
|
74
|
+
options.mnemonic = requireValue(argv, ++index, value);
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
throw new Error(`Unknown argument: ${value}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return options;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function requireValue(argv, index, flagName) {
|
|
85
|
+
const value = argv[index];
|
|
86
|
+
if (!value) {
|
|
87
|
+
throw new Error(`${flagName} requires a value.`);
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function printUsage() {
|
|
93
|
+
console.log(`Usage:
|
|
94
|
+
space-data-module check --repo-root .
|
|
95
|
+
space-data-module check --manifest ./manifest.json --wasm ./dist/module.wasm
|
|
96
|
+
space-data-module compile --manifest ./manifest.json --source ./src/module.c --out ./dist/module.wasm
|
|
97
|
+
space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --json
|
|
98
|
+
`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function printReport(report) {
|
|
102
|
+
console.log(`${report.ok ? "PASS" : "FAIL"} ${report.sourceName}`);
|
|
103
|
+
for (const issue of report.issues) {
|
|
104
|
+
console.log(
|
|
105
|
+
` ${issue.severity.toUpperCase()} ${issue.code}: ${issue.message}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (report.issues.length === 0) {
|
|
109
|
+
console.log(" No issues found.");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function runCheck(argv) {
|
|
114
|
+
const options = parseArgs(argv);
|
|
115
|
+
if (options.manifestPath) {
|
|
116
|
+
const manifest = await loadManifestFromFile(options.manifestPath);
|
|
117
|
+
const report = options.wasmPath
|
|
118
|
+
? await validateArtifactWithStandards({
|
|
119
|
+
manifest,
|
|
120
|
+
manifestPath: options.manifestPath,
|
|
121
|
+
wasmPath: options.wasmPath,
|
|
122
|
+
})
|
|
123
|
+
: await validateManifestWithStandards(manifest, {
|
|
124
|
+
sourceName: options.manifestPath,
|
|
125
|
+
});
|
|
126
|
+
if (options.json) {
|
|
127
|
+
console.log(JSON.stringify(report, null, 2));
|
|
128
|
+
} else {
|
|
129
|
+
printReport(report);
|
|
130
|
+
}
|
|
131
|
+
return report.ok ? 0 : 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const manifestPaths = await resolveManifestFiles(options.repoRoot);
|
|
135
|
+
if (manifestPaths.length === 0) {
|
|
136
|
+
const loadedConfig = await loadComplianceConfig(options.repoRoot);
|
|
137
|
+
if (loadedConfig?.config?.allowEmpty === true) {
|
|
138
|
+
if (!options.json) {
|
|
139
|
+
console.log(
|
|
140
|
+
`No manifests configured under ${options.repoRoot}; allowEmpty=true so the check passes.`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
console.error(`No manifest.json files found under ${options.repoRoot}`);
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
const reports = [];
|
|
149
|
+
for (const manifestPath of manifestPaths) {
|
|
150
|
+
const manifest = await loadManifestFromFile(manifestPath);
|
|
151
|
+
reports.push(
|
|
152
|
+
await validateManifestWithStandards(manifest, { sourceName: manifestPath }),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (options.json) {
|
|
156
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
157
|
+
} else {
|
|
158
|
+
reports.forEach(printReport);
|
|
159
|
+
}
|
|
160
|
+
return reports.every((report) => report.ok) ? 0 : 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function runCompile(argv) {
|
|
164
|
+
const options = parseArgs(argv);
|
|
165
|
+
if (!options.manifestPath || !options.sourcePath || !options.outputPath) {
|
|
166
|
+
throw new Error("compile requires --manifest, --source, and --out.");
|
|
167
|
+
}
|
|
168
|
+
const manifest = await loadManifestFromFile(options.manifestPath);
|
|
169
|
+
const sourceCode = await readFile(options.sourcePath, "utf8");
|
|
170
|
+
const result = await compileModuleFromSource({
|
|
171
|
+
manifest,
|
|
172
|
+
sourceCode,
|
|
173
|
+
language: options.language,
|
|
174
|
+
outputPath: options.outputPath,
|
|
175
|
+
});
|
|
176
|
+
await writeFile(options.outputPath, result.wasmBytes);
|
|
177
|
+
if (options.json) {
|
|
178
|
+
console.log(
|
|
179
|
+
JSON.stringify(
|
|
180
|
+
{
|
|
181
|
+
outputPath: options.outputPath,
|
|
182
|
+
manifestWarnings: result.manifestWarnings,
|
|
183
|
+
report: result.report,
|
|
184
|
+
},
|
|
185
|
+
null,
|
|
186
|
+
2,
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
} else {
|
|
190
|
+
console.log(`Wrote ${options.outputPath}`);
|
|
191
|
+
result.manifestWarnings.forEach((warning) =>
|
|
192
|
+
console.log(` WARNING ${warning}`),
|
|
193
|
+
);
|
|
194
|
+
printReport(result.report);
|
|
195
|
+
}
|
|
196
|
+
return result.report.ok ? 0 : 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function runProtect(argv) {
|
|
200
|
+
const options = parseArgs(argv);
|
|
201
|
+
if (!options.manifestPath || !options.wasmPath) {
|
|
202
|
+
throw new Error("protect requires --manifest and --wasm.");
|
|
203
|
+
}
|
|
204
|
+
const manifest = await loadManifestFromFile(options.manifestPath);
|
|
205
|
+
const wasmBytes = new Uint8Array(await readFile(options.wasmPath));
|
|
206
|
+
const result = await protectModuleArtifact({
|
|
207
|
+
manifest,
|
|
208
|
+
wasmBytes,
|
|
209
|
+
recipientPublicKeyHex: options.recipientPublicKeyHex,
|
|
210
|
+
mnemonic: options.mnemonic,
|
|
211
|
+
});
|
|
212
|
+
if (options.json) {
|
|
213
|
+
console.log(JSON.stringify(result, null, 2));
|
|
214
|
+
} else {
|
|
215
|
+
console.log(`artifactId=${result.payload.artifactId}`);
|
|
216
|
+
console.log(`signingPublicKeyHex=${result.signingPublicKeyHex}`);
|
|
217
|
+
console.log(`encrypted=${result.encrypted}`);
|
|
218
|
+
console.log(`wasmBase64Length=${bytesToBase64(wasmBytes).length}`);
|
|
219
|
+
}
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main(process.argv.slice(2))
|
|
224
|
+
.then((exitCode) => {
|
|
225
|
+
process.exitCode = exitCode;
|
|
226
|
+
})
|
|
227
|
+
.catch((error) => {
|
|
228
|
+
console.error(error.message);
|
|
229
|
+
process.exitCode = 1;
|
|
230
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "space-data-module-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"space-data-module": "./bin/space-data-module.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js",
|
|
11
|
+
"./manifest": "./src/manifest/index.js",
|
|
12
|
+
"./compliance": "./src/compliance/index.js",
|
|
13
|
+
"./auth": "./src/auth/index.js",
|
|
14
|
+
"./transport": "./src/transport/index.js",
|
|
15
|
+
"./compiler": "./src/compiler/index.js",
|
|
16
|
+
"./standards": "./src/standards/index.js",
|
|
17
|
+
"./schemas/*": "./schemas/*"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin/",
|
|
21
|
+
"schemas/",
|
|
22
|
+
"src/"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test",
|
|
26
|
+
"start:lab": "node ./lab/server.js",
|
|
27
|
+
"check:compliance": "node ./bin/space-data-module.js check --repo-root ."
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"flatbuffers": "^25.9.23",
|
|
31
|
+
"hd-wallet-wasm": "^1.5.4",
|
|
32
|
+
"spacedatastandards.org": "23.3.3-0.3.4"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"express": "^4.21.2"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// OrbPro Plugin SDK - Plugin Manifest Schema
|
|
2
|
+
//
|
|
3
|
+
// Canonical manifest for unified OrbPro + SDN plugins.
|
|
4
|
+
|
|
5
|
+
include "TypedArenaBuffer.fbs";
|
|
6
|
+
|
|
7
|
+
namespace orbpro.manifest;
|
|
8
|
+
|
|
9
|
+
/// Canonical plugin family.
|
|
10
|
+
enum PluginFamily : ubyte {
|
|
11
|
+
SENSOR = 0,
|
|
12
|
+
PROPAGATOR = 1,
|
|
13
|
+
RENDERER = 2,
|
|
14
|
+
ANALYSIS = 3,
|
|
15
|
+
DATA_SOURCE = 4,
|
|
16
|
+
COMMS = 5,
|
|
17
|
+
SHADER = 6,
|
|
18
|
+
SDF = 7,
|
|
19
|
+
INFRASTRUCTURE = 8,
|
|
20
|
+
FLOW = 9,
|
|
21
|
+
BRIDGE = 10
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Host capability requested by a plugin.
|
|
25
|
+
enum CapabilityKind : ushort {
|
|
26
|
+
CLOCK = 0,
|
|
27
|
+
RANDOM = 1,
|
|
28
|
+
LOGGING = 2,
|
|
29
|
+
TIMERS = 3,
|
|
30
|
+
PUBSUB = 4,
|
|
31
|
+
PROTOCOL_DIAL = 5,
|
|
32
|
+
PROTOCOL_HANDLE = 6,
|
|
33
|
+
STORAGE_QUERY = 7,
|
|
34
|
+
SCENE_ACCESS = 8,
|
|
35
|
+
ENTITY_ACCESS = 9,
|
|
36
|
+
RENDER_HOOKS = 10
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Drain policy for methods that operate over queued stream frames.
|
|
40
|
+
enum DrainPolicy : ubyte {
|
|
41
|
+
SINGLE_SHOT = 0,
|
|
42
|
+
DRAIN_UNTIL_YIELD = 1,
|
|
43
|
+
DRAIN_TO_EMPTY = 2
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Accepted schema family for a port.
|
|
47
|
+
table AcceptedTypeSet {
|
|
48
|
+
/// Stable type-set identifier.
|
|
49
|
+
set_id: string (required);
|
|
50
|
+
|
|
51
|
+
/// Specific FlatBuffer types accepted by the set.
|
|
52
|
+
allowed_types: [orbpro.stream.FlatBufferTypeRef];
|
|
53
|
+
|
|
54
|
+
/// Human-readable explanation of the accepted schema family.
|
|
55
|
+
description: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// One input or output port on a method.
|
|
59
|
+
table PortManifest {
|
|
60
|
+
/// Stable port identifier within the method.
|
|
61
|
+
port_id: string (required);
|
|
62
|
+
|
|
63
|
+
/// Human-readable name for UIs.
|
|
64
|
+
display_name: string;
|
|
65
|
+
|
|
66
|
+
/// Type sets accepted on this port.
|
|
67
|
+
accepted_type_sets: [AcceptedTypeSet];
|
|
68
|
+
|
|
69
|
+
/// Minimum number of streams that must be connected.
|
|
70
|
+
min_streams: uint16 = 1;
|
|
71
|
+
|
|
72
|
+
/// Maximum number of streams that may be connected.
|
|
73
|
+
max_streams: uint16 = 1;
|
|
74
|
+
|
|
75
|
+
/// Whether the port must be connected for invocation.
|
|
76
|
+
required: bool = true;
|
|
77
|
+
|
|
78
|
+
/// Optional human-readable description.
|
|
79
|
+
description: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// One host capability dependency.
|
|
83
|
+
table HostCapability {
|
|
84
|
+
capability: CapabilityKind;
|
|
85
|
+
scope: string;
|
|
86
|
+
required: bool = true;
|
|
87
|
+
description: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Timer entry declared by a plugin.
|
|
91
|
+
table TimerSpec {
|
|
92
|
+
timer_id: string (required);
|
|
93
|
+
method_id: string (required);
|
|
94
|
+
input_port_id: string;
|
|
95
|
+
default_interval_ms: uint64;
|
|
96
|
+
description: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Protocol handler declared by a plugin.
|
|
100
|
+
table ProtocolSpec {
|
|
101
|
+
protocol_id: string (required);
|
|
102
|
+
method_id: string (required);
|
|
103
|
+
input_port_id: string;
|
|
104
|
+
output_port_id: string;
|
|
105
|
+
description: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Build artifact emitted by the plugin toolchain.
|
|
109
|
+
table BuildArtifact {
|
|
110
|
+
artifact_id: string (required);
|
|
111
|
+
kind: string;
|
|
112
|
+
path: string (required);
|
|
113
|
+
target: string;
|
|
114
|
+
entry_symbol: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Canonical method declaration.
|
|
118
|
+
table MethodManifest {
|
|
119
|
+
method_id: string (required);
|
|
120
|
+
display_name: string;
|
|
121
|
+
input_ports: [PortManifest];
|
|
122
|
+
output_ports: [PortManifest];
|
|
123
|
+
max_batch: uint32 = 1;
|
|
124
|
+
drain_policy: DrainPolicy = DRAIN_UNTIL_YIELD;
|
|
125
|
+
description: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Canonical plugin manifest.
|
|
129
|
+
table PluginManifest {
|
|
130
|
+
plugin_id: string (required, key);
|
|
131
|
+
name: string;
|
|
132
|
+
version: string;
|
|
133
|
+
plugin_family: PluginFamily = ANALYSIS;
|
|
134
|
+
methods: [MethodManifest];
|
|
135
|
+
capabilities: [HostCapability];
|
|
136
|
+
timers: [TimerSpec];
|
|
137
|
+
protocols: [ProtocolSpec];
|
|
138
|
+
schemas_used: [orbpro.stream.FlatBufferTypeRef];
|
|
139
|
+
build_artifacts: [BuildArtifact];
|
|
140
|
+
abi_version: uint32 = 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
root_type PluginManifest;
|
|
144
|
+
file_identifier "PMAN";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// OrbPro Plugin SDK - Typed Arena Buffer Schema
|
|
2
|
+
//
|
|
3
|
+
// Canonical descriptor for schema-tagged FlatBuffer frames moving through
|
|
4
|
+
// arena-backed plugin streams.
|
|
5
|
+
|
|
6
|
+
namespace orbpro.stream;
|
|
7
|
+
|
|
8
|
+
/// FlatBuffer schema identity for a stream frame or accepted port type.
|
|
9
|
+
table FlatBufferTypeRef {
|
|
10
|
+
/// Logical schema name, for example `OMM.fbs`.
|
|
11
|
+
schema_name: string;
|
|
12
|
+
|
|
13
|
+
/// Optional 4-byte FlatBuffer file identifier.
|
|
14
|
+
file_identifier: string;
|
|
15
|
+
|
|
16
|
+
/// Optional schema hash bytes for stronger compatibility checks.
|
|
17
|
+
schema_hash: [ubyte];
|
|
18
|
+
|
|
19
|
+
/// True when this port/type set accepts any FlatBuffer frame.
|
|
20
|
+
accepts_any_flatbuffer: bool = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Ownership mode for an arena-backed frame.
|
|
24
|
+
enum BufferOwnership : ubyte {
|
|
25
|
+
BORROWED = 0,
|
|
26
|
+
PRODUCER_OWNED = 1,
|
|
27
|
+
HOST_OWNED = 2,
|
|
28
|
+
SHARED = 3
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Mutability contract for an arena-backed frame.
|
|
32
|
+
enum BufferMutability : ubyte {
|
|
33
|
+
IMMUTABLE = 0,
|
|
34
|
+
APPEND_ONLY = 1,
|
|
35
|
+
MUTABLE = 2
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Runtime descriptor for one FlatBuffer frame stored in an arena.
|
|
39
|
+
table TypedArenaBuffer {
|
|
40
|
+
/// Runtime schema identity for this frame.
|
|
41
|
+
type_ref: FlatBufferTypeRef;
|
|
42
|
+
|
|
43
|
+
/// Port that produced or will consume this frame.
|
|
44
|
+
port_id: string;
|
|
45
|
+
|
|
46
|
+
/// Required alignment of the underlying frame bytes.
|
|
47
|
+
alignment: uint16 = 8;
|
|
48
|
+
|
|
49
|
+
/// Frame byte offset from the arena base.
|
|
50
|
+
offset: uint32;
|
|
51
|
+
|
|
52
|
+
/// Frame size in bytes.
|
|
53
|
+
size: uint32;
|
|
54
|
+
|
|
55
|
+
/// Ownership contract for the buffer.
|
|
56
|
+
ownership: BufferOwnership = BORROWED;
|
|
57
|
+
|
|
58
|
+
/// Generation counter for stale-reference detection.
|
|
59
|
+
generation: uint32;
|
|
60
|
+
|
|
61
|
+
/// Mutability contract for downstream consumers.
|
|
62
|
+
mutability: BufferMutability = IMMUTABLE;
|
|
63
|
+
|
|
64
|
+
/// Flow/runtime trace identifier.
|
|
65
|
+
trace_id: uint64;
|
|
66
|
+
|
|
67
|
+
/// Logical stream identifier.
|
|
68
|
+
stream_id: uint32;
|
|
69
|
+
|
|
70
|
+
/// Monotonic frame sequence number within a stream.
|
|
71
|
+
sequence: uint64;
|
|
72
|
+
|
|
73
|
+
/// True if this frame closes the stream.
|
|
74
|
+
end_of_stream: bool = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
root_type TypedArenaBuffer;
|
|
78
|
+
file_identifier "TABF";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { bytesToBase64 } from "../utils/encoding.js";
|
|
2
|
+
import { sha256Bytes } from "../utils/crypto.js";
|
|
3
|
+
|
|
4
|
+
function normalizeCanonicalValue(value) {
|
|
5
|
+
if (value === undefined) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
if (
|
|
9
|
+
value === null ||
|
|
10
|
+
typeof value === "boolean" ||
|
|
11
|
+
typeof value === "string"
|
|
12
|
+
) {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === "number") {
|
|
16
|
+
if (!Number.isFinite(value)) {
|
|
17
|
+
throw new TypeError("Canonical payloads cannot contain non-finite numbers.");
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === "bigint") {
|
|
22
|
+
return value.toString();
|
|
23
|
+
}
|
|
24
|
+
if (
|
|
25
|
+
value instanceof Uint8Array ||
|
|
26
|
+
value instanceof ArrayBuffer ||
|
|
27
|
+
ArrayBuffer.isView(value)
|
|
28
|
+
) {
|
|
29
|
+
return {
|
|
30
|
+
__type: "bytes",
|
|
31
|
+
base64: bytesToBase64(
|
|
32
|
+
value instanceof Uint8Array
|
|
33
|
+
? value
|
|
34
|
+
: new Uint8Array(
|
|
35
|
+
value.buffer ?? value,
|
|
36
|
+
value.byteOffset ?? 0,
|
|
37
|
+
value.byteLength ?? value.byteLength,
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (value instanceof Date) {
|
|
43
|
+
return value.toISOString();
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.map((item) => {
|
|
47
|
+
const normalized = normalizeCanonicalValue(item);
|
|
48
|
+
return normalized === undefined ? null : normalized;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "object") {
|
|
52
|
+
const entries = Object.entries(value)
|
|
53
|
+
.filter(([, nestedValue]) => nestedValue !== undefined)
|
|
54
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
55
|
+
.map(([key, nestedValue]) => [key, normalizeCanonicalValue(nestedValue)]);
|
|
56
|
+
return Object.fromEntries(entries);
|
|
57
|
+
}
|
|
58
|
+
throw new TypeError(`Unsupported canonical value type: ${typeof value}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function stableStringify(value) {
|
|
62
|
+
return JSON.stringify(normalizeCanonicalValue(value));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function canonicalBytes(value) {
|
|
66
|
+
return new TextEncoder().encode(stableStringify(value));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function hashCanonicalValue(value) {
|
|
70
|
+
return sha256Bytes(canonicalBytes(value));
|
|
71
|
+
}
|
|
72
|
+
|