space-data-module-sdk 0.8.0 → 0.8.2

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 (43) hide show
  1. package/README.md +17 -3
  2. package/bin/space-data-module.js +8 -6
  3. package/package.json +2 -2
  4. package/schemas/spacedatastandards/PLG.fbs +191 -0
  5. package/src/bundle/wasm.js +31 -9
  6. package/src/compliance/index.js +4 -0
  7. package/src/compliance/pluginCompliance.js +158 -2
  8. package/src/embeddedManifest.js +25 -5
  9. package/src/generated/orbpro/invoke/plugin-invoke-request.js +2 -2
  10. package/src/generated/orbpro/invoke/plugin-invoke-response.js +2 -2
  11. package/src/generated/orbpro/stream/flat-buffer-type-ref.js +2 -2
  12. package/src/generated/orbpro/stream/typed-arena-buffer.js +2 -2
  13. package/src/generated/spacedatastandards/plg/entry-function.js +86 -0
  14. package/src/generated/spacedatastandards/plg/entry-function.ts +119 -0
  15. package/src/generated/spacedatastandards/plg/index.js +16 -0
  16. package/src/generated/spacedatastandards/plg/index.ts +11 -0
  17. package/src/generated/spacedatastandards/plg/plg.js +657 -0
  18. package/src/generated/spacedatastandards/plg/plg.ts +924 -0
  19. package/src/generated/spacedatastandards/plg/plugin-capability.js +66 -0
  20. package/src/generated/spacedatastandards/plg/plugin-capability.ts +84 -0
  21. package/src/generated/spacedatastandards/plg/plugin-category.js +15 -0
  22. package/src/generated/spacedatastandards/plg/plugin-category.ts +53 -0
  23. package/src/generated/spacedatastandards/plg/plugin-dependency.js +63 -0
  24. package/src/generated/spacedatastandards/plg/plugin-dependency.ts +86 -0
  25. package/src/generated/spacedatastandards/plg/publication-state.js +9 -0
  26. package/src/generated/spacedatastandards/plg/publication-state.ts +23 -0
  27. package/src/generated/spacedatastandards/plg/purchase-tier.js +9 -0
  28. package/src/generated/spacedatastandards/plg/purchase-tier.ts +23 -0
  29. package/src/invoke/codec.js +1 -1
  30. package/src/manifest/browser.js +7 -0
  31. package/src/manifest/codec.js +73 -0
  32. package/src/manifest/index.js +7 -0
  33. package/src/manifest/legacyToPlg.js +249 -0
  34. package/src/manifest/plgCodec.js +826 -0
  35. package/src/standards/sharedCatalog.js +132 -0
  36. package/src/testing/native/wasmedge_emscripten_pthread_runner.c +17 -3
  37. package/src/vendor/flatbuffers/LICENSE +202 -0
  38. package/src/vendor/flatbuffers/builder.js +534 -0
  39. package/src/vendor/flatbuffers/byte-buffer.js +253 -0
  40. package/src/vendor/flatbuffers/constants.js +4 -0
  41. package/src/vendor/flatbuffers/encoding.js +5 -0
  42. package/src/vendor/flatbuffers/flatbuffers.js +5 -0
  43. package/src/vendor/flatbuffers/utils.js +4 -0
package/README.md CHANGED
@@ -410,11 +410,22 @@ for resolved protocol installations and producer input bindings.
410
410
  ## Publication Protection Extensions
411
411
 
412
412
  Digital signature and encrypted-delivery metadata are publication-layer
413
- extensions, not a second module format. The canonical module is still:
413
+ extensions. The canonical runtime module is still:
414
414
 
415
415
  - valid `.wasm`
416
416
  - optionally carrying one appended SDS `REC` trailer after the wasm bytes
417
417
 
418
+ The same-file protected delivery layout is:
419
+
420
+ ```text
421
+ protected-payload-bytes || REC-flatbuffer-bytes || uint32le(REC length) || "$REC"
422
+ ```
423
+
424
+ For signed-only delivery, the protected payload bytes are the wasm bytes. For
425
+ encrypted binary delivery, the protected payload bytes are ciphertext and the
426
+ appended `REC` trailer carries `ENC` so a loader can decrypt them back to the
427
+ canonical wasm before runtime startup.
428
+
418
429
  That trailing `REC` FlatBuffer is the standards-backed container for
419
430
  publication records:
420
431
 
@@ -435,7 +446,7 @@ The loader contract is always:
435
446
  FlatBuffers from `spacedatastandards.org`.
436
447
  4. Resolve `PNM` for signature/publication metadata.
437
448
  5. Resolve `ENC` if the delivery was encrypted.
438
- 6. Strip the trailer or decrypt the protected payload.
449
+ 6. Strip the trailer or decrypt the protected payload bytes.
439
450
  7. Hand the remaining raw wasm bytes to the runtime.
440
451
 
441
452
  Aligned-binary payloads are a separate invoke-ABI optimization. They do not
@@ -547,9 +558,12 @@ npx space-data-module check --manifest ./manifest.json --wasm ./dist/isomorphic/
547
558
  # Compile C/C++ source and embed the manifest
548
559
  npx space-data-module compile --manifest ./manifest.json --source ./src/module.c --out ./dist/isomorphic/module.wasm
549
560
 
550
- # Sign and encrypt a deployment payload
561
+ # Sign a deployment payload and print JSON metadata
551
562
  npx space-data-module protect --manifest ./manifest.json --wasm ./dist/isomorphic/module.wasm --json
552
563
 
564
+ # Emit an encrypted binary with an appended REC trailer
565
+ npx space-data-module protect --manifest ./manifest.json --wasm ./dist/isomorphic/module.wasm --recipient-public-key <hex> --out ./dist/module.wasm.enc
566
+
553
567
  # Emit a single-file bundled wasm
554
568
  npx space-data-module protect --manifest ./manifest.json --wasm ./dist/isomorphic/module.wasm --single-file-bundle --out ./dist/module.bundle.wasm
555
569
  ```
@@ -106,6 +106,7 @@ function printUsage() {
106
106
  space-data-module check --manifest ./manifest.json --wasm ./dist/module.wasm
107
107
  space-data-module compile --manifest ./manifest.json --source ./src/module.c --out ./dist/module.wasm
108
108
  space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --json
109
+ space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --recipient-public-key <hex> --out ./dist/module.wasm.enc
109
110
  space-data-module protect --manifest ./manifest.json --wasm ./dist/module.wasm --single-file-bundle --out ./dist/module.bundle.wasm
110
111
  `);
111
112
  }
@@ -213,9 +214,6 @@ async function runProtect(argv) {
213
214
  if (!options.manifestPath || !options.wasmPath) {
214
215
  throw new Error("protect requires --manifest and --wasm.");
215
216
  }
216
- if (options.outputPath && options.singleFileBundle !== true) {
217
- throw new Error("protect only supports --out together with --single-file-bundle.");
218
- }
219
217
  const manifest = await loadManifestFromFile(options.manifestPath);
220
218
  const wasmBytes = new Uint8Array(await readFile(options.wasmPath));
221
219
  const result = await protectModuleArtifact({
@@ -225,8 +223,11 @@ async function runProtect(argv) {
225
223
  mnemonic: options.mnemonic,
226
224
  singleFileBundle: options.singleFileBundle,
227
225
  });
228
- if (options.singleFileBundle && options.outputPath && result.bundledWasmBytes) {
229
- await writeFile(options.outputPath, result.bundledWasmBytes);
226
+ if (options.outputPath) {
227
+ await writeFile(
228
+ options.outputPath,
229
+ result.bundledWasmBytes ?? result.protectedArtifactBytes,
230
+ );
230
231
  }
231
232
  if (options.json) {
232
233
  console.log(JSON.stringify(result, null, 2));
@@ -235,7 +236,8 @@ async function runProtect(argv) {
235
236
  console.log(`signingPublicKeyHex=${result.signingPublicKeyHex}`);
236
237
  console.log(`encrypted=${result.encrypted}`);
237
238
  console.log(`wasmBase64Length=${bytesToBase64(wasmBytes).length}`);
238
- if (result.bundledWasmBytes) {
239
+ console.log(`protectedArtifactBytes=${result.protectedArtifactBytes.length}`);
240
+ if (options.singleFileBundle && result.bundledWasmBytes) {
239
241
  console.log(`singleFileBundle=true`);
240
242
  console.log(`bundledWasmBytes=${result.bundledWasmBytes.length}`);
241
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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",
@@ -72,7 +72,7 @@
72
72
  "flatsql": "^0.4.0",
73
73
  "hd-wallet-wasm": "1.6.0",
74
74
  "sdn-emception": "1.0.0",
75
- "spacedatastandards.org": "1.85.0"
75
+ "spacedatastandards.org": "^1.92.0"
76
76
  },
77
77
  "devDependencies": {
78
78
  "express": "^4.21.2"
@@ -0,0 +1,191 @@
1
+ // Canonical plugin manifest schema.
2
+ //
3
+ // Upstream source: https://github.com/DigitalArsenal/spacedatastandards.org
4
+ // Path: schema/PLG/main.fbs
5
+ // Version: 1.0.6
6
+ // Upstream hash: 2c9ca90ec923b2476832517dee715559c45dd198d3bdd4b5b3dfe0a4d05c4882
7
+ //
8
+ // This is the canonical spacedatastandards.org "PLG" manifest.
9
+ // It is vendored here verbatim so the plugin SDK can depend on a stable copy
10
+ // without adding a network or submodule fetch at build time. Keep this file
11
+ // byte-for-byte identical to the upstream version; if a refresh is needed,
12
+ // re-vendor from the upstream path above rather than hand-editing.
13
+
14
+ // Hash: 2c9ca90ec923b2476832517dee715559c45dd198d3bdd4b5b3dfe0a4d05c4882
15
+ // Version: 1.0.6
16
+ // -----------------------------------END_HEADER
17
+ /// Plugin type category
18
+ enum pluginCategory : byte {
19
+ /// Sensor simulation and analysis
20
+ Sensor,
21
+ /// Orbital propagation algorithms
22
+ Propagator,
23
+ /// Custom rendering/visualization
24
+ Renderer,
25
+ /// Data analysis and processing
26
+ Analysis,
27
+ /// External data source integration
28
+ DataSource,
29
+ /// Electronic warfare simulation
30
+ EW,
31
+ /// Communications modeling
32
+ Comms,
33
+ /// Physics simulation
34
+ Physics,
35
+ /// GLSL shader plugins for custom visualization
36
+ Shader
37
+ }
38
+
39
+ /// Storefront payment model for the plugin listing
40
+ enum purchaseTier : byte {
41
+ /// No payment required
42
+ Free,
43
+ /// Single one-time purchase
44
+ OneTime,
45
+ /// Recurring subscription purchase
46
+ Subscription
47
+ }
48
+
49
+ /// Publication visibility for the plugin listing
50
+ enum publicationState : byte {
51
+ /// Discoverable in public storefront listings
52
+ Public,
53
+ /// Addressable directly but hidden from public browse surfaces
54
+ Unlisted,
55
+ /// No longer offered for new installs or purchases
56
+ Retired
57
+ }
58
+
59
+ /// Plugin capability declaration
60
+ table PluginCapability {
61
+ /// Capability name, e.g., "gpu_compute", "wasm_simd"
62
+ NAME: string;
63
+ /// Capability version
64
+ VERSION: string;
65
+ /// Whether this capability is required
66
+ REQUIRED: bool;
67
+ }
68
+
69
+ /// Plugin dependency on another plugin
70
+ table PluginDependency {
71
+ /// Plugin ID of the dependency
72
+ PLUGIN_ID: string;
73
+ /// Minimum version required (semver)
74
+ MIN_VERSION: string;
75
+ /// Maximum version allowed (optional)
76
+ MAX_VERSION: string;
77
+ }
78
+
79
+ /// Plugin entry point function definition
80
+ table EntryFunction {
81
+ /// Function name as exported from WASM
82
+ NAME: string (required);
83
+ /// Human-readable description
84
+ DESCRIPTION: string;
85
+ /// Input parameter types (FlatBuffer schema names)
86
+ INPUT_SCHEMAS: [string];
87
+ /// Output type (FlatBuffer schema name)
88
+ OUTPUT_SCHEMA: string;
89
+ }
90
+
91
+ /// Plugin Manifest - canonical signed storefront and WASM distribution record
92
+ table PLG {
93
+ /// Unique identifier for the plugin
94
+ PLUGIN_ID: string (required);
95
+ /// Human-readable plugin name
96
+ NAME: string (required);
97
+ /// Plugin version (semver format)
98
+ VERSION: string (required);
99
+ /// Detailed description of plugin functionality
100
+ DESCRIPTION: string;
101
+ /// Short marketing summary shown in storefront listings
102
+ TAGLINE: string;
103
+ /// Type/category of the plugin
104
+ PLUGIN_TYPE: pluginCategory;
105
+ /// Human-readable publisher or organization name
106
+ PUBLISHER_NAME: string;
107
+ /// Publisher handle or username
108
+ PUBLISHER_HANDLE: string;
109
+ /// Canonical publisher website
110
+ PUBLISHER_URL: string;
111
+ /// Support or helpdesk URL for this plugin
112
+ SUPPORT_URL: string;
113
+ /// Search and categorization tags for discovery
114
+ TAGS: [string];
115
+ /// Short feature bullets highlighted in storefront listings
116
+ FEATURES: [string];
117
+ /// Screenshot URLs showing the plugin in use
118
+ SCREENSHOT_URLS: [string];
119
+ /// Optional hero/banner image URL for the listing
120
+ BANNER_URL: string;
121
+
122
+ /// ABI version for compatibility checking
123
+ ABI_VERSION: uint32 = 1;
124
+
125
+ /// SHA256 hash of the decrypted WASM binary
126
+ WASM_HASH: [ubyte];
127
+ /// Size of decrypted WASM binary in bytes
128
+ WASM_SIZE: uint64;
129
+ /// IPFS CID of the encrypted WASM binary
130
+ WASM_CID: string;
131
+ /// SHA256 hash of the encrypted delivery artifact bytes
132
+ ENCRYPTED_WASM_HASH: [ubyte];
133
+ /// Size of the encrypted delivery artifact in bytes
134
+ ENCRYPTED_WASM_SIZE: uint64;
135
+
136
+ /// Entry point functions exported by the plugin
137
+ ENTRY_FUNCTIONS: [EntryFunction];
138
+ /// FlatBuffer schemas required by this plugin
139
+ REQUIRED_SCHEMAS: [string];
140
+ /// Other plugins this depends on
141
+ DEPENDENCIES: [PluginDependency];
142
+ /// Capabilities provided by this plugin
143
+ CAPABILITIES: [PluginCapability];
144
+
145
+ /// Peer ID of the plugin provider
146
+ PROVIDER_PEER_ID: string;
147
+ /// IPFS CID of provider's EPM (Entity Profile Message)
148
+ PROVIDER_EPM_CID: string;
149
+
150
+ /// Whether the WASM binary is encrypted
151
+ ENCRYPTED: bool = true;
152
+ /// Canonical required scope for grant issuance
153
+ REQUIRED_SCOPE: string;
154
+ /// Provider-local identifier for the module content key
155
+ KEY_ID: string;
156
+ /// Allowed requester domains for module grants
157
+ ALLOWED_DOMAINS: [string];
158
+ /// Maximum grant timeout allowed for this module publication
159
+ MAX_GRANT_TIMEOUT_MS: uint64;
160
+ /// Minimum permissions required to run
161
+ MIN_PERMISSIONS: [string];
162
+
163
+ /// Unix timestamp when plugin was created
164
+ CREATED_AT: uint64;
165
+ /// Unix timestamp when plugin was last updated
166
+ UPDATED_AT: uint64;
167
+ /// URL to plugin documentation
168
+ DOCUMENTATION_URL: string;
169
+ /// URL to plugin changelog or release notes
170
+ CHANGELOG_URL: string;
171
+ /// URL to plugin icon/logo
172
+ ICON_URL: string;
173
+ /// License identifier (SPDX format)
174
+ LICENSE: string;
175
+ /// Commercial model used for storefront purchase flows
176
+ PAYMENT_MODEL: purchaseTier = Free;
177
+ /// Price in USD cents for one-time purchase or subscription period
178
+ PRICE_USD_CENTS: uint32;
179
+ /// Subscription billing period length in days
180
+ SUBSCRIPTION_PERIOD_DAYS: uint32;
181
+ /// Accepted payment methods, e.g. "stripe", "sol", "usdc"
182
+ ACCEPTED_PAYMENT_METHODS: [string];
183
+ /// Storefront publication state for this manifest version
184
+ LISTING_STATUS: publicationState = Public;
185
+
186
+ /// Ed25519 signature from provider over manifest
187
+ SIGNATURE: [ubyte];
188
+ }
189
+
190
+ root_type PLG;
191
+ file_identifier "$PLG";
@@ -1,5 +1,11 @@
1
1
  import { canonicalBytes } from "../auth/canonicalize.js";
2
- import { decodePluginManifest, encodePluginManifest } from "../manifest/index.js";
2
+ import {
3
+ decodePluginManifest,
4
+ decodePlgManifest,
5
+ encodePlgManifest,
6
+ isPlgManifestBuffer,
7
+ legacyManifestToPlg,
8
+ } from "../manifest/index.js";
3
9
  import { toUint8Array } from "../runtime/bufferLike.js";
4
10
  import { sha256Bytes } from "../utils/crypto.js";
5
11
  import { bytesToHex } from "../utils/encoding.js";
@@ -226,20 +232,36 @@ function normalizeJsonPayload(value) {
226
232
  return new Uint8Array(canonicalBytes(value));
227
233
  }
228
234
 
235
+ function encodeBundleManifest(manifest) {
236
+ return encodePlgManifest(legacyManifestToPlg(manifest));
237
+ }
238
+
239
+ function decodeBundleManifest(payloadBytes) {
240
+ return isPlgManifestBuffer(payloadBytes)
241
+ ? decodePlgManifest(payloadBytes)
242
+ : decodePluginManifest(payloadBytes);
243
+ }
244
+
229
245
  function normalizeManifestEntry(manifest, manifestBytes) {
230
246
  if (!manifest && !manifestBytes) {
231
247
  return null;
232
248
  }
233
- const payload = manifestBytes ?? encodePluginManifest(manifest);
249
+ const payload = manifestBytes ?? encodeBundleManifest(manifest);
250
+ const isPlg = isPlgManifestBuffer(payload);
234
251
  return {
235
252
  entryId: "manifest",
236
253
  role: "manifest",
237
254
  sectionName: "sds.manifest",
238
255
  payloadEncoding: "flatbuffer",
239
- typeRef: {
240
- schemaName: "PluginManifest.fbs",
241
- fileIdentifier: "PMAN",
242
- },
256
+ typeRef: isPlg
257
+ ? {
258
+ schemaName: "PLG.fbs",
259
+ fileIdentifier: "$PLG",
260
+ }
261
+ : {
262
+ schemaName: "PluginManifest.fbs",
263
+ fileIdentifier: "PMAN",
264
+ },
243
265
  payload,
244
266
  description: "Canonical plugin manifest.",
245
267
  };
@@ -330,7 +352,7 @@ function buildParsedEntries(bundle) {
330
352
  parsedEntry.payloadEncodingName === "flatbuffer"
331
353
  ) {
332
354
  try {
333
- parsedEntry.decodedManifest = decodePluginManifest(payloadBytes);
355
+ parsedEntry.decodedManifest = decodeBundleManifest(payloadBytes);
334
356
  } catch {
335
357
  parsedEntry.decodedManifest = null;
336
358
  }
@@ -380,7 +402,7 @@ export async function createSingleFileBundle(options = {}) {
380
402
  options.manifestBytes !== undefined
381
403
  ? normalizeBytes(options.manifestBytes, "manifestBytes")
382
404
  : options.manifest
383
- ? encodePluginManifest(options.manifest)
405
+ ? encodeBundleManifest(options.manifest)
384
406
  : null;
385
407
  const manifestEntry = normalizeManifestEntry(options.manifest, manifestBytes);
386
408
  const rawEntries = [
@@ -463,7 +485,7 @@ export async function parseSingleFileBundle(bytes, options = {}) {
463
485
  let manifest = null;
464
486
  if (manifestEntry) {
465
487
  try {
466
- manifest = decodePluginManifest(new Uint8Array(manifestEntry.payload ?? []));
488
+ manifest = decodeBundleManifest(new Uint8Array(manifestEntry.payload ?? []));
467
489
  } catch {
468
490
  manifest = null;
469
491
  }
@@ -2,8 +2,10 @@ import {
2
2
  findManifestFiles,
3
3
  getWasmExportNames,
4
4
  getWasmExportNamesFromFile,
5
+ hasLegacyPmanManifest,
5
6
  loadManifestFromFile,
6
7
  loadComplianceConfig,
8
+ locateEmbeddedPlgManifest,
7
9
  resolveManifestFiles,
8
10
  validatePluginArtifact,
9
11
  validatePluginManifest,
@@ -49,8 +51,10 @@ export {
49
51
  findManifestFiles,
50
52
  getWasmExportNames,
51
53
  getWasmExportNamesFromFile,
54
+ hasLegacyPmanManifest,
52
55
  loadManifestFromFile,
53
56
  loadComplianceConfig,
57
+ locateEmbeddedPlgManifest,
54
58
  RecommendedCapabilityIds,
55
59
  StandaloneWasiCapabilityIds,
56
60
  resolveManifestFiles,
@@ -16,6 +16,11 @@ import {
16
16
  RecommendedCapabilityIds,
17
17
  StandaloneWasiCapabilityIds,
18
18
  } from "../capabilities.js";
19
+ import {
20
+ PLG_FILE_IDENTIFIER,
21
+ decodePlgManifest,
22
+ isPlgManifestBuffer,
23
+ } from "../manifest/plgCodec.js";
19
24
 
20
25
  const RecommendedCapabilitySet = new Set(RecommendedCapabilityIds);
21
26
  const StandaloneWasiCapabilitySet = new Set(StandaloneWasiCapabilityIds);
@@ -1356,11 +1361,143 @@ export async function getWasmExportNamesFromFile(wasmPath) {
1356
1361
  return getWasmExportNames(wasmBytes);
1357
1362
  }
1358
1363
 
1364
+ const PLG_IDENTIFIER_BYTES = Uint8Array.from(
1365
+ Array.from(PLG_FILE_IDENTIFIER, (ch) => ch.charCodeAt(0)),
1366
+ );
1367
+ const LEGACY_PMAN_IDENTIFIER_BYTES = Uint8Array.from([0x50, 0x4d, 0x41, 0x4e]);
1368
+
1369
+ function indexOfBytes(haystack, needle, startIndex = 0) {
1370
+ if (!haystack || needle.length === 0 || haystack.length < needle.length) {
1371
+ return -1;
1372
+ }
1373
+ for (let i = startIndex; i <= haystack.length - needle.length; i += 1) {
1374
+ let matched = true;
1375
+ for (let j = 0; j < needle.length; j += 1) {
1376
+ if (haystack[i + j] !== needle[j]) {
1377
+ matched = false;
1378
+ break;
1379
+ }
1380
+ }
1381
+ if (matched) {
1382
+ return i;
1383
+ }
1384
+ }
1385
+ return -1;
1386
+ }
1387
+
1388
+ /**
1389
+ * Scan wasm bytes for the canonical `$PLG` FlatBuffer identifier and, when
1390
+ * found, return both the raw offset and the smallest aligned candidate buffer
1391
+ * that a downstream decoder can try to parse. The wasm `data` sections embed
1392
+ * the manifest bytes verbatim; FlatBuffer headers are `<root_offset:u32><$PLG>`
1393
+ * so we walk backwards one 4-byte word and hand the caller a slice starting
1394
+ * there.
1395
+ */
1396
+ export function locateEmbeddedPlgManifest(wasmBytes) {
1397
+ const bytes = wasmBytes instanceof Uint8Array
1398
+ ? wasmBytes
1399
+ : new Uint8Array(wasmBytes);
1400
+ const identifierOffset = indexOfBytes(bytes, PLG_IDENTIFIER_BYTES);
1401
+ if (identifierOffset < 4) {
1402
+ return null;
1403
+ }
1404
+ const start = identifierOffset - 4;
1405
+ // Upper bound on the manifest size — grab up to 64KB and let the caller
1406
+ // decide how much to trim. FlatBuffer decoders only read what they need.
1407
+ const end = Math.min(bytes.length, start + 65536);
1408
+ const candidate = bytes.subarray(start, end);
1409
+ if (!isPlgManifestBuffer(candidate)) {
1410
+ return null;
1411
+ }
1412
+ return { start, identifierOffset, bytes: candidate };
1413
+ }
1414
+
1415
+ export function hasLegacyPmanManifest(wasmBytes) {
1416
+ const bytes = wasmBytes instanceof Uint8Array
1417
+ ? wasmBytes
1418
+ : new Uint8Array(wasmBytes);
1419
+ return indexOfBytes(bytes, LEGACY_PMAN_IDENTIFIER_BYTES) !== -1;
1420
+ }
1421
+
1422
+ function validateEmbeddedManifestBytes({
1423
+ wasmBytes,
1424
+ manifest,
1425
+ issues,
1426
+ sourceLabel,
1427
+ }) {
1428
+ if (hasLegacyPmanManifest(wasmBytes)) {
1429
+ pushIssue(
1430
+ issues,
1431
+ "error",
1432
+ "legacy-pman-manifest-embedded",
1433
+ "Plugin artifact embeds legacy PMAN manifest bytes — rebuild with the PLG codec.",
1434
+ sourceLabel,
1435
+ );
1436
+ }
1437
+
1438
+ const located = locateEmbeddedPlgManifest(wasmBytes);
1439
+ if (!located) {
1440
+ pushIssue(
1441
+ issues,
1442
+ "error",
1443
+ "missing-plg-manifest-bytes",
1444
+ `Plugin artifact does not embed a canonical \"${PLG_FILE_IDENTIFIER}\" manifest buffer.`,
1445
+ sourceLabel,
1446
+ );
1447
+ return;
1448
+ }
1449
+
1450
+ let decoded;
1451
+ try {
1452
+ decoded = decodePlgManifest(located.bytes);
1453
+ } catch (error) {
1454
+ pushIssue(
1455
+ issues,
1456
+ "error",
1457
+ "plg-manifest-decode-failed",
1458
+ `Failed to decode embedded PLG manifest: ${error?.message ?? error}`,
1459
+ sourceLabel,
1460
+ );
1461
+ return;
1462
+ }
1463
+
1464
+ const sourcePluginId = manifest?.pluginId ?? manifest?.plugin_id ?? null;
1465
+ if (
1466
+ isNonEmptyString(sourcePluginId) &&
1467
+ isNonEmptyString(decoded?.pluginId) &&
1468
+ sourcePluginId !== decoded.pluginId
1469
+ ) {
1470
+ pushIssue(
1471
+ issues,
1472
+ "error",
1473
+ "plg-manifest-plugin-id-mismatch",
1474
+ `Embedded PLG manifest pluginId \"${decoded.pluginId}\" does not match source manifest pluginId \"${sourcePluginId}\".`,
1475
+ sourceLabel,
1476
+ );
1477
+ }
1478
+
1479
+ const sourceVersion = manifest?.version ?? null;
1480
+ if (
1481
+ isNonEmptyString(sourceVersion) &&
1482
+ isNonEmptyString(decoded?.version) &&
1483
+ sourceVersion !== decoded.version
1484
+ ) {
1485
+ pushIssue(
1486
+ issues,
1487
+ "warning",
1488
+ "plg-manifest-version-mismatch",
1489
+ `Embedded PLG manifest version \"${decoded.version}\" does not match source manifest version \"${sourceVersion}\".`,
1490
+ sourceLabel,
1491
+ );
1492
+ }
1493
+ }
1494
+
1359
1495
  export async function validatePluginArtifact(options) {
1360
1496
  const {
1361
1497
  manifest,
1362
1498
  manifestPath = null,
1363
1499
  wasmPath = null,
1500
+ wasmBytes: inputWasmBytes = null,
1364
1501
  exportNames = null,
1365
1502
  sourceName = manifestPath ?? "manifest",
1366
1503
  } = options;
@@ -1368,15 +1505,26 @@ export async function validatePluginArtifact(options) {
1368
1505
  const issues = [...report.issues];
1369
1506
  let resolvedExportNames = [];
1370
1507
  let checkedArtifact = false;
1508
+ let loadedWasmBytes = null;
1371
1509
  const declaredInvokeSurfaces = Array.isArray(manifest?.invokeSurfaces)
1372
1510
  ? manifest.invokeSurfaces.filter((surface) => InvokeSurfaceSet.has(surface))
1373
1511
  : [];
1374
1512
 
1375
- if (Array.isArray(exportNames)) {
1513
+ if (inputWasmBytes) {
1514
+ loadedWasmBytes =
1515
+ inputWasmBytes instanceof Uint8Array
1516
+ ? inputWasmBytes
1517
+ : new Uint8Array(inputWasmBytes);
1518
+ resolvedExportNames = Array.isArray(exportNames)
1519
+ ? [...exportNames]
1520
+ : getWasmExportNames(loadedWasmBytes);
1521
+ checkedArtifact = true;
1522
+ } else if (Array.isArray(exportNames)) {
1376
1523
  resolvedExportNames = [...exportNames];
1377
1524
  checkedArtifact = true;
1378
1525
  } else if (isNonEmptyString(wasmPath)) {
1379
- resolvedExportNames = await getWasmExportNamesFromFile(wasmPath);
1526
+ loadedWasmBytes = new Uint8Array(await readFile(wasmPath));
1527
+ resolvedExportNames = getWasmExportNames(loadedWasmBytes);
1380
1528
  checkedArtifact = true;
1381
1529
  }
1382
1530
 
@@ -1424,6 +1572,14 @@ export async function validatePluginArtifact(options) {
1424
1572
  wasmPath ?? sourceName,
1425
1573
  );
1426
1574
  }
1575
+ if (loadedWasmBytes) {
1576
+ validateEmbeddedManifestBytes({
1577
+ wasmBytes: loadedWasmBytes,
1578
+ manifest,
1579
+ issues,
1580
+ sourceLabel: wasmPath ?? sourceName,
1581
+ });
1582
+ }
1427
1583
  } else {
1428
1584
  pushIssue(
1429
1585
  issues,
@@ -2,8 +2,19 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { encodePluginManifest } from "./manifest/index.js";
5
-
6
- function toManifestBytes(manifest) {
5
+ import { legacyManifestToPlg } from "./manifest/legacyToPlg.js";
6
+ import { encodePlgManifest } from "./manifest/plgCodec.js";
7
+
8
+ /**
9
+ * Convert a manifest input to the raw FlatBuffer bytes that will be
10
+ * embedded in the plugin artifact.
11
+ *
12
+ * The embedded manifest is the canonical spacedatastandards.org PLG
13
+ * schema (`$PLG` file_identifier) by default. Pass `format: "pman"` to
14
+ * emit the internal PluginManifest schema instead (legacy path, retained
15
+ * for tests that still compare against PMAN bytes).
16
+ */
17
+ function toManifestBytes(manifest, format = "plg") {
7
18
  if (manifest instanceof Uint8Array) {
8
19
  return manifest;
9
20
  }
@@ -17,7 +28,13 @@ function toManifestBytes(manifest) {
17
28
  manifest.byteLength,
18
29
  );
19
30
  }
20
- return encodePluginManifest(manifest);
31
+ if (format === "pman") {
32
+ return encodePluginManifest(manifest);
33
+ }
34
+ // Default: PLG. Accept both PLG-shaped and legacy PluginManifest-shaped
35
+ // inputs; the converter is a no-op for PLG-native shapes.
36
+ const plgShape = legacyManifestToPlg(manifest);
37
+ return encodePlgManifest(plgShape);
21
38
  }
22
39
 
23
40
  function renderByteRows(bytes) {
@@ -34,7 +51,10 @@ function renderByteRows(bytes) {
34
51
  }
35
52
 
36
53
  export function generateEmbeddedManifestSource(options = {}) {
37
- const manifestBytes = toManifestBytes(options.manifest);
54
+ const manifestBytes = toManifestBytes(
55
+ options.manifest,
56
+ options.format ?? "plg",
57
+ );
38
58
  if (manifestBytes.length === 0) {
39
59
  throw new Error("generateEmbeddedManifestSource requires manifest bytes.");
40
60
  }
@@ -91,7 +111,7 @@ export function writeEmbeddedManifestArtifacts(options = {}) {
91
111
  throw new Error("writeEmbeddedManifestArtifacts requires outputDir.");
92
112
  }
93
113
 
94
- const manifestBytes = toManifestBytes(manifest);
114
+ const manifestBytes = toManifestBytes(manifest, options.format ?? "plg");
95
115
  fs.mkdirSync(outputDir, { recursive: true });
96
116
 
97
117
  const binaryPath = path.join(outputDir, binaryFileName);
@@ -1,6 +1,6 @@
1
1
  // automatically generated by the FlatBuffers compiler, do not modify
2
2
  /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
3
- import * as flatbuffers from "flatbuffers/mjs/flatbuffers.js";
3
+ import * as flatbuffers from "../../../vendor/flatbuffers/flatbuffers.js";
4
4
  import { TypedArenaBuffer } from '../../orbpro/stream/typed-arena-buffer.js';
5
5
  /**
6
6
  * Canonical invoke envelope consumed by direct ABI and command-mode execution.
@@ -128,4 +128,4 @@ export class PluginInvokeRequestT {
128
128
  return PluginInvokeRequest.createPluginInvokeRequest(builder, methodId, inputFrames, payloadArena);
129
129
  }
130
130
  }
131
- //# sourceMappingURL=plugin-invoke-request.js.map
131
+ //# sourceMappingURL=plugin-invoke-request.js.map
@@ -1,6 +1,6 @@
1
1
  // automatically generated by the FlatBuffers compiler, do not modify
2
2
  /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
3
- import * as flatbuffers from "flatbuffers/mjs/flatbuffers.js";
3
+ import * as flatbuffers from "../../../vendor/flatbuffers/flatbuffers.js";
4
4
  import { TypedArenaBuffer } from '../../orbpro/stream/typed-arena-buffer.js';
5
5
  /**
6
6
  * Canonical invoke result emitted by direct ABI and command-mode execution.
@@ -181,4 +181,4 @@ export class PluginInvokeResponseT {
181
181
  return PluginInvokeResponse.createPluginInvokeResponse(builder, this.statusCode, this.yielded, this.backlogRemaining, outputFrames, payloadArena, errorCode, errorMessage);
182
182
  }
183
183
  }
184
- //# sourceMappingURL=plugin-invoke-response.js.map
184
+ //# sourceMappingURL=plugin-invoke-response.js.map