space-data-module-sdk 0.2.8 → 0.3.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 CHANGED
@@ -223,11 +223,15 @@ Concrete multiaddrs, peer IDs, and producer routing do not belong in the
223
223
  canonical manifest. They belong in deployment metadata attached to the final
224
224
  package or bundle.
225
225
 
226
+ Deployment plans should key resolved protocol installations by `protocolId`.
227
+ They may include `wireId` as optional legacy metadata when a concrete transport
228
+ still needs it.
229
+
226
230
  This repo exposes that deployment surface from
227
231
  `space-data-module-sdk/deployment`. Use it to:
228
232
 
229
233
  - validate resolved protocol installations
230
- - describe input bindings from producers to module ports
234
+ - describe input and publication bindings by declared `interfaceId`
231
235
  - attach a deployment plan to `sds.bundle`
232
236
 
233
237
  The full contract split is documented in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
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",
@@ -14,7 +14,7 @@ export type ScheduleBindingKindName = "interval" | "cron" | "once";
14
14
 
15
15
  export interface ResolvedProtocolInstallation {
16
16
  protocolId: string;
17
- wireId: string;
17
+ wireId?: string | null;
18
18
  transportKind: ProtocolTransportKindName | string;
19
19
  role: ProtocolRoleName | string;
20
20
  peerId?: string | null;
@@ -29,13 +29,11 @@ export interface ResolvedProtocolInstallation {
29
29
 
30
30
  export interface InputBinding {
31
31
  bindingId: string;
32
+ interfaceId: string;
32
33
  targetPluginId?: string | null;
33
34
  targetMethodId: string;
34
35
  targetInputPortId: string;
35
36
  sourceKind: InputBindingSourceKindName | string;
36
- topic?: string | null;
37
- wireId?: string | null;
38
- nodeInfoUrl?: string | null;
39
37
  multiaddrs?: string[];
40
38
  allowPeerIds?: string[];
41
39
  allowServerKeys?: string[];
@@ -96,18 +94,17 @@ export interface AuthPolicy {
96
94
 
97
95
  export interface PublicationBinding {
98
96
  publicationId: string;
97
+ interfaceId: string;
99
98
  bindingMode: DeploymentBindingModeName | string;
100
99
  sourceKind: string;
101
100
  sourceMethodId?: string | null;
102
101
  sourceOutputPortId?: string | null;
103
102
  sourceNodeId?: string | null;
104
103
  sourceTriggerId?: string | null;
105
- topic?: string | null;
106
- wireId?: string | null;
107
104
  schemaName?: string | null;
108
105
  mediaType?: string | null;
109
106
  archivePath?: string | null;
110
- queryServiceId?: string | null;
107
+ queryInterfaceId?: string | null;
111
108
  emitPnm?: boolean;
112
109
  emitFlatbufferArchive?: boolean;
113
110
  pinPolicy?: string | null;
@@ -7,7 +7,11 @@ import {
7
7
  SDS_DEPLOYMENT_MEDIA_TYPE,
8
8
  SDS_DEPLOYMENT_SECTION_NAME,
9
9
  } from "../bundle/constants.js";
10
- import { ProtocolRole, ProtocolTransportKind } from "../runtime/constants.js";
10
+ import {
11
+ ExternalInterfaceDirection,
12
+ ProtocolRole,
13
+ ProtocolTransportKind,
14
+ } from "../runtime/constants.js";
11
15
 
12
16
  export const DEPLOYMENT_PLAN_FORMAT_VERSION = 1;
13
17
 
@@ -33,6 +37,14 @@ const DeploymentBindingModeSet = new Set(Object.values(DeploymentBindingMode));
33
37
  const ProtocolRoleSet = new Set(Object.values(ProtocolRole));
34
38
  const ProtocolTransportKindSet = new Set(Object.values(ProtocolTransportKind));
35
39
  const ScheduleBindingKindSet = new Set(Object.values(ScheduleBindingKind));
40
+ const InputBindingInterfaceDirectionSet = new Set([
41
+ ExternalInterfaceDirection.INPUT,
42
+ ExternalInterfaceDirection.BIDIRECTIONAL,
43
+ ]);
44
+ const PublicationBindingInterfaceDirectionSet = new Set([
45
+ ExternalInterfaceDirection.OUTPUT,
46
+ ExternalInterfaceDirection.BIDIRECTIONAL,
47
+ ]);
36
48
 
37
49
  function normalizeString(value) {
38
50
  if (value === undefined || value === null) {
@@ -163,13 +175,11 @@ function normalizeProtocolInstallation(value = {}) {
163
175
  function normalizeInputBinding(value = {}) {
164
176
  return {
165
177
  bindingId: normalizeString(value.bindingId),
178
+ interfaceId: normalizeString(value.interfaceId),
166
179
  targetPluginId: normalizeString(value.targetPluginId),
167
180
  targetMethodId: normalizeString(value.targetMethodId),
168
181
  targetInputPortId: normalizeString(value.targetInputPortId),
169
182
  sourceKind: normalizeInputBindingSourceKindName(value.sourceKind),
170
- topic: normalizeString(value.topic),
171
- wireId: normalizeString(value.wireId),
172
- nodeInfoUrl: normalizeString(value.nodeInfoUrl),
173
183
  multiaddrs: normalizeStringArray(value.multiaddrs),
174
184
  allowPeerIds: normalizeStringArray(value.allowPeerIds),
175
185
  allowServerKeys: normalizeStringArray(value.allowServerKeys),
@@ -238,18 +248,17 @@ function normalizeAuthPolicy(value = {}) {
238
248
  function normalizePublicationBinding(value = {}) {
239
249
  return {
240
250
  publicationId: normalizeString(value.publicationId),
251
+ interfaceId: normalizeString(value.interfaceId),
241
252
  bindingMode: normalizeDeploymentBindingModeName(value.bindingMode),
242
253
  sourceKind: normalizeString(value.sourceKind),
243
254
  sourceMethodId: normalizeString(value.sourceMethodId),
244
255
  sourceOutputPortId: normalizeString(value.sourceOutputPortId),
245
256
  sourceNodeId: normalizeString(value.sourceNodeId),
246
257
  sourceTriggerId: normalizeString(value.sourceTriggerId),
247
- topic: normalizeString(value.topic),
248
- wireId: normalizeString(value.wireId),
249
258
  schemaName: normalizeString(value.schemaName),
250
259
  mediaType: normalizeString(value.mediaType),
251
260
  archivePath: normalizeString(value.archivePath),
252
- queryServiceId: normalizeString(value.queryServiceId),
261
+ queryInterfaceId: normalizeString(value.queryInterfaceId),
253
262
  emitPnm: normalizeBoolean(value.emitPnm),
254
263
  emitFlatbufferArchive: normalizeBoolean(value.emitFlatbufferArchive),
255
264
  pinPolicy: normalizeString(value.pinPolicy),
@@ -392,6 +401,62 @@ function buildMethodLookup(manifest) {
392
401
  return lookup;
393
402
  }
394
403
 
404
+ function buildExternalInterfaceLookup(manifest) {
405
+ const lookup = new Map();
406
+ if (!Array.isArray(manifest?.externalInterfaces)) {
407
+ return lookup;
408
+ }
409
+ for (const externalInterface of manifest.externalInterfaces) {
410
+ if (
411
+ typeof externalInterface?.interfaceId === "string" &&
412
+ externalInterface.interfaceId.trim().length > 0
413
+ ) {
414
+ lookup.set(externalInterface.interfaceId, externalInterface);
415
+ }
416
+ }
417
+ return lookup;
418
+ }
419
+
420
+ function validateDeclaredInterface(
421
+ issues,
422
+ interfaceId,
423
+ location,
424
+ bindingLabel,
425
+ missingCode,
426
+ directionCode,
427
+ externalInterfaceLookup,
428
+ allowedDirections,
429
+ ) {
430
+ const normalizedInterfaceId = normalizeString(interfaceId);
431
+ if (!normalizedInterfaceId) {
432
+ return;
433
+ }
434
+ const externalInterface = externalInterfaceLookup.get(normalizedInterfaceId);
435
+ if (!externalInterface) {
436
+ pushIssue(
437
+ issues,
438
+ "error",
439
+ missingCode,
440
+ `${bindingLabel} references unknown interface "${normalizedInterfaceId}".`,
441
+ location,
442
+ );
443
+ return;
444
+ }
445
+ if (
446
+ allowedDirections &&
447
+ typeof externalInterface.direction === "string" &&
448
+ !allowedDirections.has(externalInterface.direction)
449
+ ) {
450
+ pushIssue(
451
+ issues,
452
+ "error",
453
+ directionCode,
454
+ `${bindingLabel} references interface "${normalizedInterfaceId}" with direction "${externalInterface.direction}", which is incompatible with this binding.`,
455
+ location,
456
+ );
457
+ }
458
+ }
459
+
395
460
  function validateProtocolInstallation(
396
461
  installation,
397
462
  issues,
@@ -414,7 +479,7 @@ function validateProtocolInstallation(
414
479
  `${location}.protocolId`,
415
480
  "Protocol installation protocolId",
416
481
  );
417
- validateStringField(
482
+ validateOptionalStringField(
418
483
  issues,
419
484
  installation.wireId,
420
485
  `${location}.wireId`,
@@ -583,6 +648,7 @@ function validateInputBinding(
583
648
  location,
584
649
  manifest,
585
650
  manifestMethodLookup,
651
+ externalInterfaceLookup,
586
652
  ) {
587
653
  if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
588
654
  pushIssue(
@@ -600,6 +666,12 @@ function validateInputBinding(
600
666
  `${location}.bindingId`,
601
667
  "Input binding bindingId",
602
668
  );
669
+ const interfaceIdValid = validateStringField(
670
+ issues,
671
+ binding.interfaceId,
672
+ `${location}.interfaceId`,
673
+ "Input binding interfaceId",
674
+ );
603
675
  const targetPluginId = normalizeString(binding.targetPluginId);
604
676
  const targetMethodIdValid = validateStringField(
605
677
  issues,
@@ -639,24 +711,6 @@ function validateInputBinding(
639
711
  `${location}.targetPluginId`,
640
712
  "Input binding targetPluginId",
641
713
  );
642
- validateOptionalStringField(
643
- issues,
644
- binding.topic,
645
- `${location}.topic`,
646
- "Input binding topic",
647
- );
648
- validateOptionalStringField(
649
- issues,
650
- binding.wireId,
651
- `${location}.wireId`,
652
- "Input binding wireId",
653
- );
654
- validateOptionalStringField(
655
- issues,
656
- binding.nodeInfoUrl,
657
- `${location}.nodeInfoUrl`,
658
- "Input binding nodeInfoUrl",
659
- );
660
714
  validateStringArrayField(
661
715
  issues,
662
716
  binding.multiaddrs,
@@ -687,27 +741,17 @@ function validateInputBinding(
687
741
  `${location}.description`,
688
742
  "Input binding description",
689
743
  );
690
- if (
691
- sourceKind === InputBindingSourceKind.PUBSUB &&
692
- !validateStringField(
744
+ if (interfaceIdValid) {
745
+ validateDeclaredInterface(
693
746
  issues,
694
- binding.topic,
695
- `${location}.topic`,
696
- "Pubsub input binding topic",
697
- )
698
- ) {
699
- // error already recorded
700
- }
701
- if (
702
- sourceKind === InputBindingSourceKind.PROTOCOL_STREAM &&
703
- !validateStringField(
704
- issues,
705
- binding.wireId,
706
- `${location}.wireId`,
707
- "Protocol-stream input binding wireId",
708
- )
709
- ) {
710
- // error already recorded
747
+ binding.interfaceId,
748
+ `${location}.interfaceId`,
749
+ `Input binding "${binding.bindingId ?? "binding"}"`,
750
+ "unknown-input-binding-interface",
751
+ "input-binding-interface-direction-mismatch",
752
+ externalInterfaceLookup,
753
+ InputBindingInterfaceDirectionSet,
754
+ );
711
755
  }
712
756
  const targetsCurrentManifest =
713
757
  !targetPluginId ||
@@ -1161,6 +1205,7 @@ function validatePublicationBinding(
1161
1205
  location,
1162
1206
  manifest,
1163
1207
  manifestMethodLookup,
1208
+ externalInterfaceLookup,
1164
1209
  ) {
1165
1210
  if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
1166
1211
  pushIssue(
@@ -1178,6 +1223,12 @@ function validatePublicationBinding(
1178
1223
  `${location}.publicationId`,
1179
1224
  "Publication binding publicationId",
1180
1225
  );
1226
+ const interfaceIdValid = validateStringField(
1227
+ issues,
1228
+ binding.interfaceId,
1229
+ `${location}.interfaceId`,
1230
+ "Publication binding interfaceId",
1231
+ );
1181
1232
  validateBindingModeField(
1182
1233
  issues,
1183
1234
  binding.bindingMode,
@@ -1214,18 +1265,6 @@ function validatePublicationBinding(
1214
1265
  `${location}.sourceTriggerId`,
1215
1266
  "Publication binding sourceTriggerId",
1216
1267
  );
1217
- validateOptionalStringField(
1218
- issues,
1219
- binding.topic,
1220
- `${location}.topic`,
1221
- "Publication binding topic",
1222
- );
1223
- validateOptionalStringField(
1224
- issues,
1225
- binding.wireId,
1226
- `${location}.wireId`,
1227
- "Publication binding wireId",
1228
- );
1229
1268
  validateOptionalStringField(
1230
1269
  issues,
1231
1270
  binding.schemaName,
@@ -1246,9 +1285,9 @@ function validatePublicationBinding(
1246
1285
  );
1247
1286
  validateOptionalStringField(
1248
1287
  issues,
1249
- binding.queryServiceId,
1250
- `${location}.queryServiceId`,
1251
- "Publication binding queryServiceId",
1288
+ binding.queryInterfaceId,
1289
+ `${location}.queryInterfaceId`,
1290
+ "Publication binding queryInterfaceId",
1252
1291
  );
1253
1292
  validateOptionalStringField(
1254
1293
  issues,
@@ -1287,18 +1326,28 @@ function validatePublicationBinding(
1287
1326
  location,
1288
1327
  );
1289
1328
  }
1290
- if (
1291
- binding.emitPnm === true &&
1292
- !binding.topic &&
1293
- !binding.wireId &&
1294
- !binding.schemaName
1295
- ) {
1296
- pushIssue(
1329
+ if (interfaceIdValid) {
1330
+ validateDeclaredInterface(
1297
1331
  issues,
1298
- "warning",
1299
- "pnm-without-routing-hint",
1300
- "PNM-emitting publication bindings should declare a topic, wireId, or schemaName.",
1301
- location,
1332
+ binding.interfaceId,
1333
+ `${location}.interfaceId`,
1334
+ `Publication binding "${binding.publicationId ?? "publication"}"`,
1335
+ "unknown-publication-binding-interface",
1336
+ "publication-binding-interface-direction-mismatch",
1337
+ externalInterfaceLookup,
1338
+ PublicationBindingInterfaceDirectionSet,
1339
+ );
1340
+ }
1341
+ if (binding.queryInterfaceId) {
1342
+ validateDeclaredInterface(
1343
+ issues,
1344
+ binding.queryInterfaceId,
1345
+ `${location}.queryInterfaceId`,
1346
+ `Publication binding "${binding.publicationId ?? "publication"}" query interface`,
1347
+ "unknown-publication-query-interface",
1348
+ "publication-query-interface-direction-mismatch",
1349
+ externalInterfaceLookup,
1350
+ null,
1302
1351
  );
1303
1352
  }
1304
1353
  if (binding.sourceMethodId && manifest) {
@@ -1344,6 +1393,7 @@ export function validateDeploymentPlan(plan, options = {}) {
1344
1393
  const issues = [];
1345
1394
  const manifest = options.manifest ?? null;
1346
1395
  const methodLookup = buildMethodLookup(manifest);
1396
+ const externalInterfaceLookup = buildExternalInterfaceLookup(manifest);
1347
1397
  const protocolLookup = new Map(
1348
1398
  Array.isArray(manifest?.protocols)
1349
1399
  ? manifest.protocols
@@ -1403,6 +1453,7 @@ export function validateDeploymentPlan(plan, options = {}) {
1403
1453
  `deploymentPlan.inputBindings[${index}]`,
1404
1454
  manifest,
1405
1455
  methodLookup,
1456
+ externalInterfaceLookup,
1406
1457
  );
1407
1458
  });
1408
1459
  normalizedPlan.scheduleBindings.forEach((binding, index) => {
@@ -1435,6 +1486,7 @@ export function validateDeploymentPlan(plan, options = {}) {
1435
1486
  `deploymentPlan.publicationBindings[${index}]`,
1436
1487
  manifest,
1437
1488
  methodLookup,
1489
+ externalInterfaceLookup,
1438
1490
  );
1439
1491
  });
1440
1492
 
@@ -1454,23 +1506,6 @@ export function validateDeploymentPlan(plan, options = {}) {
1454
1506
  );
1455
1507
  }
1456
1508
  });
1457
- const serviceIds = new Set(
1458
- normalizedPlan.serviceBindings
1459
- .map((entry) => entry.serviceId)
1460
- .filter((entry) => typeof entry === "string" && entry.length > 0),
1461
- );
1462
- normalizedPlan.publicationBindings.forEach((binding, index) => {
1463
- if (binding.queryServiceId && !serviceIds.has(binding.queryServiceId)) {
1464
- pushIssue(
1465
- issues,
1466
- "error",
1467
- "unknown-publication-query-service",
1468
- `Publication binding "${binding.publicationId ?? "publication"}" references unknown query service "${binding.queryServiceId}".`,
1469
- `deploymentPlan.publicationBindings[${index}].queryServiceId`,
1470
- );
1471
- }
1472
- });
1473
-
1474
1509
  const errors = issues.filter((issue) => issue.severity === "error");
1475
1510
  const warnings = issues.filter((issue) => issue.severity === "warning");
1476
1511
  return {