sdn-flow 0.2.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.
Files changed (69) hide show
  1. package/.claude/SKILLS.md +7 -0
  2. package/.claude/skills/sdn-plugin-abi-compliance/SKILL.md +56 -0
  3. package/.claude/todo/001-js-host-startup-and-deno.md +85 -0
  4. package/LICENSE +21 -0
  5. package/README.md +223 -0
  6. package/bin/sdn-flow-host.js +169 -0
  7. package/docs/.nojekyll +0 -0
  8. package/docs/ARCHITECTURE.md +200 -0
  9. package/docs/HOST_CAPABILITY_MODEL.md +317 -0
  10. package/docs/PLUGIN_ARCHITECTURE.md +145 -0
  11. package/docs/PLUGIN_COMPATIBILITY.md +61 -0
  12. package/docs/PLUGIN_COMPLIANCE_CHECKS.md +82 -0
  13. package/docs/PLUGIN_MANIFEST.md +94 -0
  14. package/docs/css/style.css +465 -0
  15. package/docs/index.html +218 -0
  16. package/docs/js/app.mjs +751 -0
  17. package/docs/js/editor-panel.mjs +203 -0
  18. package/docs/js/flow-canvas.mjs +515 -0
  19. package/docs/js/flow-model.mjs +391 -0
  20. package/docs/js/workers/emception.worker.js +146 -0
  21. package/docs/js/workers/pyodide.worker.js +134 -0
  22. package/native/flow_source_generator.cpp +1958 -0
  23. package/package.json +67 -0
  24. package/schemas/FlowRuntimeAbi.fbs +91 -0
  25. package/src/auth/canonicalize.js +5 -0
  26. package/src/auth/index.js +11 -0
  27. package/src/auth/permissions.js +8 -0
  28. package/src/compiler/CppFlowSourceGenerator.js +475 -0
  29. package/src/compiler/EmceptionCompilerAdapter.js +244 -0
  30. package/src/compiler/SignedArtifactCatalog.js +152 -0
  31. package/src/compiler/index.js +8 -0
  32. package/src/compiler/nativeFlowSourceGeneratorTool.js +144 -0
  33. package/src/compliance/index.js +13 -0
  34. package/src/compliance/pluginCompliance.js +11 -0
  35. package/src/deploy/FlowDeploymentClient.js +532 -0
  36. package/src/deploy/index.js +8 -0
  37. package/src/designer/FlowDesignerSession.js +158 -0
  38. package/src/designer/index.js +2 -0
  39. package/src/designer/requirements.js +184 -0
  40. package/src/generated/runtimeAbiLayouts.js +544 -0
  41. package/src/host/appHost.js +105 -0
  42. package/src/host/autoHost.js +113 -0
  43. package/src/host/browserHostAdapters.js +108 -0
  44. package/src/host/compiledFlowRuntimeHost.js +703 -0
  45. package/src/host/constants.js +55 -0
  46. package/src/host/dependencyRuntime.js +227 -0
  47. package/src/host/descriptorAbi.js +351 -0
  48. package/src/host/fetchService.js +237 -0
  49. package/src/host/httpHostAdapters.js +280 -0
  50. package/src/host/index.js +91 -0
  51. package/src/host/installedFlowHost.js +885 -0
  52. package/src/host/invocationAbi.js +440 -0
  53. package/src/host/normalize.js +372 -0
  54. package/src/host/packageManagers.js +369 -0
  55. package/src/host/profile.js +134 -0
  56. package/src/host/runtimeAbi.js +106 -0
  57. package/src/host/workspace.js +895 -0
  58. package/src/index.js +8 -0
  59. package/src/runtime/FlowRuntime.js +273 -0
  60. package/src/runtime/MethodRegistry.js +295 -0
  61. package/src/runtime/constants.js +44 -0
  62. package/src/runtime/index.js +19 -0
  63. package/src/runtime/normalize.js +377 -0
  64. package/src/transport/index.js +7 -0
  65. package/src/transport/pki.js +7 -0
  66. package/src/utils/crypto.js +7 -0
  67. package/src/utils/encoding.js +65 -0
  68. package/src/utils/wasmCrypto.js +69 -0
  69. package/tools/run-plugin-compliance-check.mjs +153 -0
@@ -0,0 +1,377 @@
1
+ import {
2
+ BackpressurePolicy,
3
+ DefaultManifestExports,
4
+ DrainPolicy,
5
+ ExternalInterfaceDirection,
6
+ NodeKind,
7
+ TriggerKind,
8
+ } from "./constants.js";
9
+
10
+ function normalizeString(value, fallback = null) {
11
+ if (value === null || value === undefined) {
12
+ return fallback;
13
+ }
14
+ const normalized = String(value).trim();
15
+ return normalized.length > 0 ? normalized : fallback;
16
+ }
17
+
18
+ function normalizeArray(values) {
19
+ return Array.isArray(values) ? values : [];
20
+ }
21
+
22
+ function normalizeTypeRef(typeRef = {}) {
23
+ return {
24
+ schemaName: normalizeString(
25
+ typeRef.schemaName ?? typeRef.schema_name,
26
+ null,
27
+ ),
28
+ fileIdentifier: normalizeString(
29
+ typeRef.fileIdentifier ?? typeRef.file_identifier,
30
+ null,
31
+ ),
32
+ schemaHash: normalizeArray(typeRef.schemaHash ?? typeRef.schema_hash),
33
+ acceptsAnyFlatbuffer:
34
+ typeRef.acceptsAnyFlatbuffer ?? typeRef.accepts_any_flatbuffer ?? false,
35
+ };
36
+ }
37
+
38
+ function normalizeAcceptedTypeSet(typeSet = {}) {
39
+ return {
40
+ setId: normalizeString(typeSet.setId ?? typeSet.set_id, null),
41
+ allowedTypes: normalizeArray(
42
+ typeSet.allowedTypes ?? typeSet.allowed_types,
43
+ ).map(normalizeTypeRef),
44
+ description: normalizeString(typeSet.description, null),
45
+ };
46
+ }
47
+
48
+ function normalizeExternalInterface(externalInterface = {}) {
49
+ return {
50
+ interfaceId: normalizeString(
51
+ externalInterface.interfaceId ?? externalInterface.interface_id,
52
+ "",
53
+ ),
54
+ kind: normalizeString(externalInterface.kind, null),
55
+ direction:
56
+ normalizeString(externalInterface.direction, null) ??
57
+ ExternalInterfaceDirection.BIDIRECTIONAL,
58
+ capability: normalizeString(externalInterface.capability, null),
59
+ resource: normalizeString(externalInterface.resource, null),
60
+ protocolId: normalizeString(
61
+ externalInterface.protocolId ?? externalInterface.protocol_id,
62
+ null,
63
+ ),
64
+ topic: normalizeString(externalInterface.topic, null),
65
+ path: normalizeString(externalInterface.path, null),
66
+ description: normalizeString(externalInterface.description, null),
67
+ required: externalInterface.required !== false,
68
+ acceptedTypes: normalizeArray(
69
+ externalInterface.acceptedTypes ?? externalInterface.accepted_types,
70
+ ).map(normalizeTypeRef),
71
+ properties:
72
+ externalInterface.properties &&
73
+ typeof externalInterface.properties === "object"
74
+ ? { ...externalInterface.properties }
75
+ : {},
76
+ };
77
+ }
78
+
79
+ function normalizeArtifactDependency(dependency = {}) {
80
+ return {
81
+ dependencyId: normalizeString(
82
+ dependency.dependencyId ?? dependency.dependency_id,
83
+ "",
84
+ ),
85
+ pluginId: normalizeString(
86
+ dependency.pluginId ?? dependency.plugin_id,
87
+ null,
88
+ ),
89
+ version: normalizeString(dependency.version, null),
90
+ artifactId: normalizeString(
91
+ dependency.artifactId ?? dependency.artifact_id,
92
+ null,
93
+ ),
94
+ artifactUri: normalizeString(
95
+ dependency.artifactUri ?? dependency.artifact_uri,
96
+ null,
97
+ ),
98
+ mediaType:
99
+ normalizeString(dependency.mediaType ?? dependency.media_type, null) ??
100
+ "application/wasm",
101
+ sha256: normalizeString(dependency.sha256, null),
102
+ manifestHash: normalizeString(
103
+ dependency.manifestHash ?? dependency.manifest_hash,
104
+ null,
105
+ ),
106
+ signature: normalizeString(dependency.signature, null),
107
+ signerPublicKey: normalizeString(
108
+ dependency.signerPublicKey ?? dependency.signer_public_key,
109
+ null,
110
+ ),
111
+ entrypoint: normalizeString(dependency.entrypoint, null),
112
+ requiredCapabilities: normalizeArray(
113
+ dependency.requiredCapabilities ?? dependency.required_capabilities,
114
+ )
115
+ .map((value) => normalizeString(value, null))
116
+ .filter(Boolean),
117
+ exports: normalizeArray(dependency.exports)
118
+ .map((value) => normalizeString(value, null))
119
+ .filter(Boolean),
120
+ manifestExports: {
121
+ bytesSymbol:
122
+ normalizeString(
123
+ dependency.manifestExports?.bytesSymbol ??
124
+ dependency.manifest_exports?.bytes_symbol,
125
+ null,
126
+ ) ?? DefaultManifestExports.pluginBytesSymbol,
127
+ sizeSymbol:
128
+ normalizeString(
129
+ dependency.manifestExports?.sizeSymbol ??
130
+ dependency.manifest_exports?.size_symbol,
131
+ null,
132
+ ) ?? DefaultManifestExports.pluginSizeSymbol,
133
+ },
134
+ runtimeExports: {
135
+ initSymbol:
136
+ normalizeString(
137
+ dependency.runtimeExports?.initSymbol ??
138
+ dependency.runtime_exports?.init_symbol,
139
+ null,
140
+ ) ?? "plugin_init",
141
+ destroySymbol:
142
+ normalizeString(
143
+ dependency.runtimeExports?.destroySymbol ??
144
+ dependency.runtime_exports?.destroy_symbol,
145
+ null,
146
+ ) ?? "plugin_destroy",
147
+ mallocSymbol:
148
+ normalizeString(
149
+ dependency.runtimeExports?.mallocSymbol ??
150
+ dependency.runtime_exports?.malloc_symbol,
151
+ null,
152
+ ) ?? "malloc",
153
+ freeSymbol:
154
+ normalizeString(
155
+ dependency.runtimeExports?.freeSymbol ??
156
+ dependency.runtime_exports?.free_symbol,
157
+ null,
158
+ ) ?? "free",
159
+ streamInvokeSymbol: normalizeString(
160
+ dependency.runtimeExports?.streamInvokeSymbol ??
161
+ dependency.runtime_exports?.stream_invoke_symbol,
162
+ null,
163
+ ),
164
+ },
165
+ metadata:
166
+ dependency.metadata && typeof dependency.metadata === "object"
167
+ ? { ...dependency.metadata }
168
+ : {},
169
+ };
170
+ }
171
+
172
+ function normalizePort(port = {}) {
173
+ return {
174
+ portId: normalizeString(port.portId ?? port.port_id, ""),
175
+ displayName: normalizeString(port.displayName ?? port.display_name, null),
176
+ acceptedTypeSets: normalizeArray(
177
+ port.acceptedTypeSets ?? port.accepted_type_sets,
178
+ ).map(normalizeAcceptedTypeSet),
179
+ minStreams: Math.max(0, Number(port.minStreams ?? port.min_streams ?? 1)),
180
+ maxStreams: Math.max(0, Number(port.maxStreams ?? port.max_streams ?? 1)),
181
+ required: port.required !== false,
182
+ description: normalizeString(port.description, null),
183
+ };
184
+ }
185
+
186
+ function normalizeMethod(method = {}) {
187
+ return {
188
+ methodId: normalizeString(method.methodId ?? method.method_id, ""),
189
+ displayName: normalizeString(
190
+ method.displayName ?? method.display_name,
191
+ null,
192
+ ),
193
+ inputPorts: normalizeArray(method.inputPorts ?? method.input_ports).map(
194
+ normalizePort,
195
+ ),
196
+ outputPorts: normalizeArray(method.outputPorts ?? method.output_ports).map(
197
+ normalizePort,
198
+ ),
199
+ maxBatch: Math.max(1, Number(method.maxBatch ?? method.max_batch ?? 1)),
200
+ drainPolicy:
201
+ normalizeString(method.drainPolicy ?? method.drain_policy, null) ??
202
+ DrainPolicy.DRAIN_UNTIL_YIELD,
203
+ description: normalizeString(method.description, null),
204
+ };
205
+ }
206
+
207
+ export function normalizeManifest(manifest = {}) {
208
+ return {
209
+ pluginId: normalizeString(manifest.pluginId ?? manifest.plugin_id, ""),
210
+ name: normalizeString(manifest.name, null),
211
+ version: normalizeString(manifest.version, null),
212
+ pluginFamily: normalizeString(
213
+ manifest.pluginFamily ?? manifest.plugin_family,
214
+ null,
215
+ ),
216
+ methods: normalizeArray(manifest.methods).map(normalizeMethod),
217
+ capabilities: normalizeArray(manifest.capabilities),
218
+ timers: normalizeArray(manifest.timers),
219
+ protocols: normalizeArray(manifest.protocols),
220
+ schemasUsed: normalizeArray(
221
+ manifest.schemasUsed ?? manifest.schemas_used,
222
+ ).map(normalizeTypeRef),
223
+ externalInterfaces: normalizeArray(
224
+ manifest.externalInterfaces ?? manifest.external_interfaces,
225
+ ).map(normalizeExternalInterface),
226
+ buildArtifacts: normalizeArray(
227
+ manifest.buildArtifacts ?? manifest.build_artifacts,
228
+ ),
229
+ manifestBuffer: manifest.manifestBuffer ?? manifest.manifest_buffer ?? null,
230
+ manifestExports: {
231
+ bytesSymbol:
232
+ normalizeString(
233
+ manifest.manifestExports?.bytesSymbol ??
234
+ manifest.manifest_exports?.bytes_symbol,
235
+ null,
236
+ ) ?? DefaultManifestExports.pluginBytesSymbol,
237
+ sizeSymbol:
238
+ normalizeString(
239
+ manifest.manifestExports?.sizeSymbol ??
240
+ manifest.manifest_exports?.size_symbol,
241
+ null,
242
+ ) ?? DefaultManifestExports.pluginSizeSymbol,
243
+ },
244
+ };
245
+ }
246
+
247
+ function normalizeTrigger(trigger = {}) {
248
+ return {
249
+ triggerId: normalizeString(trigger.triggerId ?? trigger.trigger_id, ""),
250
+ kind: normalizeString(trigger.kind, null) ?? TriggerKind.MANUAL,
251
+ source: normalizeString(trigger.source, null),
252
+ protocolId: normalizeString(
253
+ trigger.protocolId ?? trigger.protocol_id,
254
+ null,
255
+ ),
256
+ defaultIntervalMs: Number(
257
+ trigger.defaultIntervalMs ?? trigger.default_interval_ms ?? 0,
258
+ ),
259
+ acceptedTypes: normalizeArray(
260
+ trigger.acceptedTypes ?? trigger.accepted_types,
261
+ ).map(normalizeTypeRef),
262
+ description: normalizeString(trigger.description, null),
263
+ };
264
+ }
265
+
266
+ function normalizeNode(node = {}) {
267
+ return {
268
+ nodeId: normalizeString(node.nodeId ?? node.node_id, ""),
269
+ pluginId: normalizeString(node.pluginId ?? node.plugin_id, ""),
270
+ methodId: normalizeString(node.methodId ?? node.method_id, ""),
271
+ kind: normalizeString(node.kind, null) ?? NodeKind.TRANSFORM,
272
+ drainPolicy:
273
+ normalizeString(node.drainPolicy ?? node.drain_policy, null) ??
274
+ DrainPolicy.DRAIN_UNTIL_YIELD,
275
+ timeSliceMicros: Number(
276
+ node.timeSliceMicros ?? node.time_slice_micros ?? 0,
277
+ ),
278
+ };
279
+ }
280
+
281
+ function normalizeEdge(edge = {}) {
282
+ return {
283
+ edgeId: normalizeString(edge.edgeId ?? edge.edge_id, ""),
284
+ fromNodeId: normalizeString(edge.fromNodeId ?? edge.from_node_id, ""),
285
+ fromPortId: normalizeString(edge.fromPortId ?? edge.from_port_id, ""),
286
+ toNodeId: normalizeString(edge.toNodeId ?? edge.to_node_id, ""),
287
+ toPortId: normalizeString(edge.toPortId ?? edge.to_port_id, ""),
288
+ acceptedTypes: normalizeArray(
289
+ edge.acceptedTypes ?? edge.accepted_types,
290
+ ).map(normalizeTypeRef),
291
+ backpressurePolicy:
292
+ normalizeString(
293
+ edge.backpressurePolicy ?? edge.backpressure_policy,
294
+ null,
295
+ ) ?? BackpressurePolicy.QUEUE,
296
+ queueDepth: Math.max(0, Number(edge.queueDepth ?? edge.queue_depth ?? 1)),
297
+ };
298
+ }
299
+
300
+ function normalizeTriggerBinding(binding = {}) {
301
+ return {
302
+ triggerId: normalizeString(binding.triggerId ?? binding.trigger_id, ""),
303
+ targetNodeId: normalizeString(
304
+ binding.targetNodeId ?? binding.target_node_id,
305
+ "",
306
+ ),
307
+ targetPortId: normalizeString(
308
+ binding.targetPortId ?? binding.target_port_id,
309
+ "",
310
+ ),
311
+ backpressurePolicy:
312
+ normalizeString(
313
+ binding.backpressurePolicy ?? binding.backpressure_policy,
314
+ null,
315
+ ) ?? BackpressurePolicy.QUEUE,
316
+ queueDepth: Math.max(
317
+ 0,
318
+ Number(binding.queueDepth ?? binding.queue_depth ?? 1),
319
+ ),
320
+ };
321
+ }
322
+
323
+ export function normalizeProgram(program = {}) {
324
+ return {
325
+ programId: normalizeString(program.programId ?? program.program_id, ""),
326
+ name: normalizeString(program.name, null),
327
+ version: normalizeString(program.version, null),
328
+ nodes: normalizeArray(program.nodes).map(normalizeNode),
329
+ edges: normalizeArray(program.edges).map(normalizeEdge),
330
+ triggers: normalizeArray(program.triggers).map(normalizeTrigger),
331
+ triggerBindings: normalizeArray(
332
+ program.triggerBindings ?? program.trigger_bindings,
333
+ ).map(normalizeTriggerBinding),
334
+ requiredPlugins: normalizeArray(
335
+ program.requiredPlugins ?? program.required_plugins,
336
+ )
337
+ .map((pluginId) => normalizeString(pluginId, null))
338
+ .filter(Boolean),
339
+ externalInterfaces: normalizeArray(
340
+ program.externalInterfaces ?? program.external_interfaces,
341
+ ).map(normalizeExternalInterface),
342
+ artifactDependencies: normalizeArray(
343
+ program.artifactDependencies ?? program.artifact_dependencies,
344
+ ).map(normalizeArtifactDependency),
345
+ editor:
346
+ program.editor && typeof program.editor === "object"
347
+ ? structuredClone(program.editor)
348
+ : null,
349
+ description: normalizeString(program.description, null),
350
+ };
351
+ }
352
+
353
+ export function normalizeFrame(frame = {}, defaultPortId = null) {
354
+ const normalized = {
355
+ typeRef: normalizeTypeRef(frame.typeRef ?? frame.type_ref ?? {}),
356
+ portId:
357
+ normalizeString(frame.portId ?? frame.port_id, null) ?? defaultPortId,
358
+ alignment: Math.max(1, Number(frame.alignment ?? 8)),
359
+ offset: Math.max(0, Number(frame.offset ?? 0)),
360
+ size: Math.max(0, Number(frame.size ?? 0)),
361
+ ownership: normalizeString(frame.ownership, null),
362
+ generation: Number(frame.generation ?? 0),
363
+ mutability: normalizeString(frame.mutability, null),
364
+ traceId: frame.traceId ?? frame.trace_id ?? null,
365
+ streamId: Number(frame.streamId ?? frame.stream_id ?? 0),
366
+ sequence: frame.sequence ?? 0,
367
+ endOfStream: frame.endOfStream ?? frame.end_of_stream ?? false,
368
+ payload: frame.payload ?? null,
369
+ metadata:
370
+ frame.metadata && typeof frame.metadata === "object"
371
+ ? structuredClone(frame.metadata)
372
+ : null,
373
+ };
374
+ return normalized;
375
+ }
376
+
377
+ export { normalizeExternalInterface, normalizeArtifactDependency };
@@ -0,0 +1,7 @@
1
+ export {
2
+ decryptBytesFromEnvelope,
3
+ decryptJsonFromEnvelope,
4
+ encryptBytesForRecipient,
5
+ encryptJsonForRecipient,
6
+ generateX25519Keypair,
7
+ } from "space-data-module-sdk/transport";
@@ -0,0 +1,7 @@
1
+ export {
2
+ decryptBytesFromEnvelope,
3
+ decryptJsonFromEnvelope,
4
+ encryptBytesForRecipient,
5
+ encryptJsonForRecipient,
6
+ generateX25519Keypair,
7
+ } from "space-data-module-sdk/transport";
@@ -0,0 +1,7 @@
1
+ export function getCrypto() {
2
+ throw new Error(
3
+ "Direct WebCrypto access is forbidden. Use the WASM crypto helpers instead.",
4
+ );
5
+ }
6
+
7
+ export { randomBytes, sha256Bytes } from "./wasmCrypto.js";
@@ -0,0 +1,65 @@
1
+ function assertByteValue(value) {
2
+ if (!Number.isInteger(value) || value < 0 || value > 255) {
3
+ throw new TypeError("Expected byte values in range 0..255.");
4
+ }
5
+ }
6
+
7
+ export function toUint8Array(value) {
8
+ if (value instanceof Uint8Array) {
9
+ return value;
10
+ }
11
+ if (ArrayBuffer.isView(value)) {
12
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
13
+ }
14
+ if (value instanceof ArrayBuffer) {
15
+ return new Uint8Array(value);
16
+ }
17
+ if (Array.isArray(value)) {
18
+ value.forEach(assertByteValue);
19
+ return Uint8Array.from(value);
20
+ }
21
+ throw new TypeError("Expected Uint8Array, ArrayBuffer, ArrayBufferView, or byte array.");
22
+ }
23
+
24
+ export function bytesToHex(bytes) {
25
+ return Array.from(toUint8Array(bytes), (byte) =>
26
+ byte.toString(16).padStart(2, "0"),
27
+ ).join("");
28
+ }
29
+
30
+ export function hexToBytes(hex) {
31
+ const normalized = String(hex ?? "").trim().toLowerCase();
32
+ if (normalized.length === 0 || normalized.length % 2 !== 0) {
33
+ throw new TypeError("Expected even-length hex string.");
34
+ }
35
+ const bytes = new Uint8Array(normalized.length / 2);
36
+ for (let index = 0; index < normalized.length; index += 2) {
37
+ bytes[index / 2] = parseInt(normalized.slice(index, index + 2), 16);
38
+ }
39
+ return bytes;
40
+ }
41
+
42
+ export function bytesToBase64(bytes) {
43
+ const normalized = toUint8Array(bytes);
44
+ if (typeof Buffer !== "undefined") {
45
+ return Buffer.from(normalized).toString("base64");
46
+ }
47
+ let binary = "";
48
+ for (const byte of normalized) {
49
+ binary += String.fromCharCode(byte);
50
+ }
51
+ return btoa(binary);
52
+ }
53
+
54
+ export function base64ToBytes(base64) {
55
+ const normalized = String(base64 ?? "").trim();
56
+ if (typeof Buffer !== "undefined") {
57
+ return new Uint8Array(Buffer.from(normalized, "base64"));
58
+ }
59
+ const binary = atob(normalized);
60
+ const bytes = new Uint8Array(binary.length);
61
+ for (let index = 0; index < binary.length; index += 1) {
62
+ bytes[index] = binary.charCodeAt(index);
63
+ }
64
+ return bytes;
65
+ }
@@ -0,0 +1,69 @@
1
+ import { toUint8Array } from "./encoding.js";
2
+
3
+ let walletPromise = null;
4
+
5
+ export async function getWasmWallet() {
6
+ if (!walletPromise) {
7
+ walletPromise = (async () => {
8
+ const module = await import("hd-wallet-wasm");
9
+ const init = module.default ?? module.createHDWallet;
10
+ return init();
11
+ })();
12
+ }
13
+
14
+ return walletPromise;
15
+ }
16
+
17
+ export async function randomBytes(length) {
18
+ const wallet = await getWasmWallet();
19
+ return wallet.utils.getRandomBytes(length);
20
+ }
21
+
22
+ export async function sha256Bytes(value) {
23
+ const wallet = await getWasmWallet();
24
+ return wallet.utils.sha256(toUint8Array(value));
25
+ }
26
+
27
+ export async function hkdfBytes(ikm, salt, info, length) {
28
+ const wallet = await getWasmWallet();
29
+ return wallet.utils.hkdf(
30
+ toUint8Array(ikm),
31
+ toUint8Array(salt),
32
+ toUint8Array(info),
33
+ length,
34
+ );
35
+ }
36
+
37
+ export async function x25519PublicKey(privateKey) {
38
+ const wallet = await getWasmWallet();
39
+ return wallet.curves.x25519.publicKey(toUint8Array(privateKey));
40
+ }
41
+
42
+ export async function x25519SharedSecret(privateKey, publicKey) {
43
+ const wallet = await getWasmWallet();
44
+ return wallet.curves.x25519.ecdh(
45
+ toUint8Array(privateKey),
46
+ toUint8Array(publicKey),
47
+ );
48
+ }
49
+
50
+ export async function aesGcmEncrypt(key, plaintext, iv, aad = null) {
51
+ const wallet = await getWasmWallet();
52
+ return wallet.utils.aesGcm.encrypt(
53
+ toUint8Array(key),
54
+ toUint8Array(plaintext),
55
+ toUint8Array(iv),
56
+ aad ? toUint8Array(aad) : undefined,
57
+ );
58
+ }
59
+
60
+ export async function aesGcmDecrypt(key, ciphertext, tag, iv, aad = null) {
61
+ const wallet = await getWasmWallet();
62
+ return wallet.utils.aesGcm.decrypt(
63
+ toUint8Array(key),
64
+ toUint8Array(ciphertext),
65
+ toUint8Array(tag),
66
+ toUint8Array(iv),
67
+ aad ? toUint8Array(aad) : undefined,
68
+ );
69
+ }
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+
6
+ import {
7
+ loadManifestFromFile,
8
+ loadComplianceConfig,
9
+ resolveManifestFiles,
10
+ validatePluginArtifact,
11
+ validatePluginManifest,
12
+ } from "../src/compliance/index.js";
13
+
14
+ async function main(argv) {
15
+ const options = parseArgs(argv);
16
+ if (options.help) {
17
+ printUsage();
18
+ return 0;
19
+ }
20
+
21
+ const reports = [];
22
+ if (options.manifests.length > 0) {
23
+ for (const manifestPath of options.manifests) {
24
+ const manifest = await loadManifestFromFile(manifestPath);
25
+ if (options.wasmPath) {
26
+ reports.push(
27
+ await validatePluginArtifact({
28
+ manifest,
29
+ manifestPath,
30
+ wasmPath: options.wasmPath,
31
+ }),
32
+ );
33
+ } else {
34
+ reports.push(validatePluginManifest(manifest, { sourceName: manifestPath }));
35
+ }
36
+ }
37
+ } else {
38
+ const repoRoot = path.resolve(options.repoRoot);
39
+ const loadedConfig = await loadComplianceConfig(repoRoot);
40
+ const manifestPaths = await resolveManifestFiles(repoRoot);
41
+ if (manifestPaths.length === 0) {
42
+ if (loadedConfig?.config?.allowEmpty === true) {
43
+ if (!options.json) {
44
+ console.log(`No manifests configured under ${repoRoot}; allowEmpty=true so the check passes.`);
45
+ }
46
+ return 0;
47
+ }
48
+ if (loadedConfig) {
49
+ console.error(`No manifest.json files matched ${loadedConfig.path}`);
50
+ } else {
51
+ console.error(`No manifest.json files found under ${repoRoot}`);
52
+ }
53
+ return 1;
54
+ }
55
+ for (const manifestPath of manifestPaths) {
56
+ const manifest = await loadManifestFromFile(manifestPath);
57
+ reports.push(validatePluginManifest(manifest, { sourceName: manifestPath }));
58
+ }
59
+ }
60
+
61
+ if (options.json) {
62
+ console.log(JSON.stringify(reports, null, 2));
63
+ } else {
64
+ printReports(reports);
65
+ }
66
+
67
+ return reports.every((report) => report.ok) ? 0 : 1;
68
+ }
69
+
70
+ function parseArgs(argv) {
71
+ const options = {
72
+ help: false,
73
+ json: false,
74
+ repoRoot: process.cwd(),
75
+ manifests: [],
76
+ wasmPath: null,
77
+ };
78
+
79
+ for (let index = 0; index < argv.length; index += 1) {
80
+ const value = argv[index];
81
+ switch (value) {
82
+ case "--help":
83
+ case "-h":
84
+ options.help = true;
85
+ break;
86
+ case "--json":
87
+ options.json = true;
88
+ break;
89
+ case "--repo-root":
90
+ options.repoRoot = requireValue(argv, ++index, "--repo-root");
91
+ break;
92
+ case "--manifest":
93
+ options.manifests.push(path.resolve(requireValue(argv, ++index, "--manifest")));
94
+ break;
95
+ case "--wasm":
96
+ options.wasmPath = path.resolve(requireValue(argv, ++index, "--wasm"));
97
+ break;
98
+ default:
99
+ throw new Error(`Unknown argument: ${value}`);
100
+ }
101
+ }
102
+
103
+ return options;
104
+ }
105
+
106
+ function requireValue(argv, index, flagName) {
107
+ const value = argv[index];
108
+ if (!value) {
109
+ throw new Error(`${flagName} requires a value.`);
110
+ }
111
+ return value;
112
+ }
113
+
114
+ function printUsage() {
115
+ console.log(`Usage:
116
+ node tools/run-plugin-compliance-check.mjs --repo-root .
117
+ node tools/run-plugin-compliance-check.mjs --manifest ./manifest.json
118
+ node tools/run-plugin-compliance-check.mjs --manifest ./manifest.json --wasm ./dist/plugin.wasm
119
+
120
+ Options:
121
+ --repo-root <path> Scan a repository for manifest.json files and validate them.
122
+ If sdn-plugin-compliance.json exists, its scan targets are used.
123
+ --manifest <path> Validate one or more specific manifest.json files.
124
+ --wasm <path> Also validate plugin ABI export symbols from a compiled wasm artifact.
125
+ --json Emit JSON instead of human-readable text.
126
+ --help Show this help text.`);
127
+ }
128
+
129
+ function printReports(reports) {
130
+ for (const report of reports) {
131
+ const status = report.ok ? "PASS" : "FAIL";
132
+ const artifactMode = report.checkedArtifact ? "manifest+abi" : "manifest";
133
+ console.log(`${status} ${report.sourceName} [${artifactMode}]`);
134
+ for (const issue of report.issues) {
135
+ console.log(` ${issue.severity.toUpperCase()} ${issue.code}: ${issue.message}`);
136
+ if (issue.location) {
137
+ console.log(` at ${issue.location}`);
138
+ }
139
+ }
140
+ if (report.issues.length === 0) {
141
+ console.log(" No issues found.");
142
+ }
143
+ }
144
+ }
145
+
146
+ main(process.argv.slice(2))
147
+ .then((exitCode) => {
148
+ process.exitCode = exitCode;
149
+ })
150
+ .catch((error) => {
151
+ console.error(error.message);
152
+ process.exitCode = 1;
153
+ });