space-data-module-sdk 0.2.9 → 0.3.1

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
@@ -231,7 +231,7 @@ This repo exposes that deployment surface from
231
231
  `space-data-module-sdk/deployment`. Use it to:
232
232
 
233
233
  - validate resolved protocol installations
234
- - describe input bindings from producers to module ports
234
+ - describe input and publication bindings by declared `interfaceId`
235
235
  - attach a deployment plan to `sds.bundle`
236
236
 
237
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.9",
3
+ "version": "0.3.1",
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",
@@ -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,68 @@ function buildMethodLookup(manifest) {
392
401
  return lookup;
393
402
  }
394
403
 
404
+ function buildExternalInterfaceLookup(manifest) {
405
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
406
+ return null;
407
+ }
408
+ const lookup = new Map();
409
+ if (!Array.isArray(manifest?.externalInterfaces)) {
410
+ return lookup;
411
+ }
412
+ for (const externalInterface of manifest.externalInterfaces) {
413
+ if (
414
+ typeof externalInterface?.interfaceId === "string" &&
415
+ externalInterface.interfaceId.trim().length > 0
416
+ ) {
417
+ lookup.set(externalInterface.interfaceId, externalInterface);
418
+ }
419
+ }
420
+ return lookup;
421
+ }
422
+
423
+ function validateDeclaredInterface(
424
+ issues,
425
+ interfaceId,
426
+ location,
427
+ bindingLabel,
428
+ missingCode,
429
+ directionCode,
430
+ externalInterfaceLookup,
431
+ allowedDirections,
432
+ ) {
433
+ if (!externalInterfaceLookup) {
434
+ return;
435
+ }
436
+ const normalizedInterfaceId = normalizeString(interfaceId);
437
+ if (!normalizedInterfaceId) {
438
+ return;
439
+ }
440
+ const externalInterface = externalInterfaceLookup.get(normalizedInterfaceId);
441
+ if (!externalInterface) {
442
+ pushIssue(
443
+ issues,
444
+ "error",
445
+ missingCode,
446
+ `${bindingLabel} references unknown interface "${normalizedInterfaceId}".`,
447
+ location,
448
+ );
449
+ return;
450
+ }
451
+ if (
452
+ allowedDirections &&
453
+ typeof externalInterface.direction === "string" &&
454
+ !allowedDirections.has(externalInterface.direction)
455
+ ) {
456
+ pushIssue(
457
+ issues,
458
+ "error",
459
+ directionCode,
460
+ `${bindingLabel} references interface "${normalizedInterfaceId}" with direction "${externalInterface.direction}", which is incompatible with this binding.`,
461
+ location,
462
+ );
463
+ }
464
+ }
465
+
395
466
  function validateProtocolInstallation(
396
467
  installation,
397
468
  issues,
@@ -583,6 +654,7 @@ function validateInputBinding(
583
654
  location,
584
655
  manifest,
585
656
  manifestMethodLookup,
657
+ externalInterfaceLookup,
586
658
  ) {
587
659
  if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
588
660
  pushIssue(
@@ -600,6 +672,12 @@ function validateInputBinding(
600
672
  `${location}.bindingId`,
601
673
  "Input binding bindingId",
602
674
  );
675
+ const interfaceIdValid = validateStringField(
676
+ issues,
677
+ binding.interfaceId,
678
+ `${location}.interfaceId`,
679
+ "Input binding interfaceId",
680
+ );
603
681
  const targetPluginId = normalizeString(binding.targetPluginId);
604
682
  const targetMethodIdValid = validateStringField(
605
683
  issues,
@@ -639,24 +717,6 @@ function validateInputBinding(
639
717
  `${location}.targetPluginId`,
640
718
  "Input binding targetPluginId",
641
719
  );
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
720
  validateStringArrayField(
661
721
  issues,
662
722
  binding.multiaddrs,
@@ -687,27 +747,17 @@ function validateInputBinding(
687
747
  `${location}.description`,
688
748
  "Input binding description",
689
749
  );
690
- if (
691
- sourceKind === InputBindingSourceKind.PUBSUB &&
692
- !validateStringField(
750
+ if (interfaceIdValid) {
751
+ validateDeclaredInterface(
693
752
  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
753
+ binding.interfaceId,
754
+ `${location}.interfaceId`,
755
+ `Input binding "${binding.bindingId ?? "binding"}"`,
756
+ "unknown-input-binding-interface",
757
+ "input-binding-interface-direction-mismatch",
758
+ externalInterfaceLookup,
759
+ InputBindingInterfaceDirectionSet,
760
+ );
711
761
  }
712
762
  const targetsCurrentManifest =
713
763
  !targetPluginId ||
@@ -1161,6 +1211,7 @@ function validatePublicationBinding(
1161
1211
  location,
1162
1212
  manifest,
1163
1213
  manifestMethodLookup,
1214
+ externalInterfaceLookup,
1164
1215
  ) {
1165
1216
  if (!binding || typeof binding !== "object" || Array.isArray(binding)) {
1166
1217
  pushIssue(
@@ -1178,6 +1229,12 @@ function validatePublicationBinding(
1178
1229
  `${location}.publicationId`,
1179
1230
  "Publication binding publicationId",
1180
1231
  );
1232
+ const interfaceIdValid = validateStringField(
1233
+ issues,
1234
+ binding.interfaceId,
1235
+ `${location}.interfaceId`,
1236
+ "Publication binding interfaceId",
1237
+ );
1181
1238
  validateBindingModeField(
1182
1239
  issues,
1183
1240
  binding.bindingMode,
@@ -1214,18 +1271,6 @@ function validatePublicationBinding(
1214
1271
  `${location}.sourceTriggerId`,
1215
1272
  "Publication binding sourceTriggerId",
1216
1273
  );
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
1274
  validateOptionalStringField(
1230
1275
  issues,
1231
1276
  binding.schemaName,
@@ -1246,9 +1291,9 @@ function validatePublicationBinding(
1246
1291
  );
1247
1292
  validateOptionalStringField(
1248
1293
  issues,
1249
- binding.queryServiceId,
1250
- `${location}.queryServiceId`,
1251
- "Publication binding queryServiceId",
1294
+ binding.queryInterfaceId,
1295
+ `${location}.queryInterfaceId`,
1296
+ "Publication binding queryInterfaceId",
1252
1297
  );
1253
1298
  validateOptionalStringField(
1254
1299
  issues,
@@ -1287,18 +1332,28 @@ function validatePublicationBinding(
1287
1332
  location,
1288
1333
  );
1289
1334
  }
1290
- if (
1291
- binding.emitPnm === true &&
1292
- !binding.topic &&
1293
- !binding.wireId &&
1294
- !binding.schemaName
1295
- ) {
1296
- pushIssue(
1335
+ if (interfaceIdValid) {
1336
+ validateDeclaredInterface(
1297
1337
  issues,
1298
- "warning",
1299
- "pnm-without-routing-hint",
1300
- "PNM-emitting publication bindings should declare a topic, wireId, or schemaName.",
1301
- location,
1338
+ binding.interfaceId,
1339
+ `${location}.interfaceId`,
1340
+ `Publication binding "${binding.publicationId ?? "publication"}"`,
1341
+ "unknown-publication-binding-interface",
1342
+ "publication-binding-interface-direction-mismatch",
1343
+ externalInterfaceLookup,
1344
+ PublicationBindingInterfaceDirectionSet,
1345
+ );
1346
+ }
1347
+ if (binding.queryInterfaceId) {
1348
+ validateDeclaredInterface(
1349
+ issues,
1350
+ binding.queryInterfaceId,
1351
+ `${location}.queryInterfaceId`,
1352
+ `Publication binding "${binding.publicationId ?? "publication"}" query interface`,
1353
+ "unknown-publication-query-interface",
1354
+ "publication-query-interface-direction-mismatch",
1355
+ externalInterfaceLookup,
1356
+ null,
1302
1357
  );
1303
1358
  }
1304
1359
  if (binding.sourceMethodId && manifest) {
@@ -1344,6 +1399,7 @@ export function validateDeploymentPlan(plan, options = {}) {
1344
1399
  const issues = [];
1345
1400
  const manifest = options.manifest ?? null;
1346
1401
  const methodLookup = buildMethodLookup(manifest);
1402
+ const externalInterfaceLookup = buildExternalInterfaceLookup(manifest);
1347
1403
  const protocolLookup = new Map(
1348
1404
  Array.isArray(manifest?.protocols)
1349
1405
  ? manifest.protocols
@@ -1403,6 +1459,7 @@ export function validateDeploymentPlan(plan, options = {}) {
1403
1459
  `deploymentPlan.inputBindings[${index}]`,
1404
1460
  manifest,
1405
1461
  methodLookup,
1462
+ externalInterfaceLookup,
1406
1463
  );
1407
1464
  });
1408
1465
  normalizedPlan.scheduleBindings.forEach((binding, index) => {
@@ -1435,6 +1492,7 @@ export function validateDeploymentPlan(plan, options = {}) {
1435
1492
  `deploymentPlan.publicationBindings[${index}]`,
1436
1493
  manifest,
1437
1494
  methodLookup,
1495
+ externalInterfaceLookup,
1438
1496
  );
1439
1497
  });
1440
1498
 
@@ -1454,23 +1512,6 @@ export function validateDeploymentPlan(plan, options = {}) {
1454
1512
  );
1455
1513
  }
1456
1514
  });
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
1515
  const errors = issues.filter((issue) => issue.severity === "error");
1475
1516
  const warnings = issues.filter((issue) => issue.severity === "warning");
1476
1517
  return {