space-data-module-sdk 0.2.6 → 0.2.7
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 +59 -0
- package/package.json +5 -1
- package/schemas/PluginManifest.fbs +9 -0
- package/src/bundle/constants.js +4 -1
- package/src/bundle/wasm.js +30 -2
- package/src/compliance/pluginCompliance.js +306 -1
- package/src/deployment/index.d.ts +224 -0
- package/src/deployment/index.js +1552 -0
- package/src/generated/orbpro/manifest/protocol-spec.d.ts +35 -3
- package/src/generated/orbpro/manifest/protocol-spec.js +120 -6
- package/src/generated/orbpro/manifest/protocol-spec.ts +191 -1
- package/src/index.d.ts +136 -3
- package/src/index.js +4 -0
- package/src/manifest/index.js +7 -0
- package/src/manifest/normalize.js +75 -5
- package/src/manifest/typeRefs.js +143 -0
- package/src/runtime/constants.js +13 -0
- package/src/runtime/index.d.ts +2 -0
- package/src/testing/index.d.ts +84 -0
- package/src/testing/index.js +414 -0
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ A module built with this SDK is a `.wasm` artifact with:
|
|
|
34
34
|
- an exported `_start` entry when the artifact supports WASI command mode
|
|
35
35
|
- optional `sds.bundle` custom-section payloads for:
|
|
36
36
|
- manifest bytes
|
|
37
|
+
- resolved deployment plans and input bindings
|
|
37
38
|
- deployment authorization
|
|
38
39
|
- detached signatures
|
|
39
40
|
- encrypted transport envelopes
|
|
@@ -81,6 +82,14 @@ outputs. The reference invoke examples live in
|
|
|
81
82
|
- `manifest.hybrid.json`
|
|
82
83
|
- `module.c`
|
|
83
84
|
|
|
85
|
+
Input and output ports can independently declare regular `flatbuffer` payloads
|
|
86
|
+
or `aligned-binary` layouts. Mixed contracts are valid. When a port advertises
|
|
87
|
+
an `aligned-binary` layout, it must also advertise a regular `flatbuffer`
|
|
88
|
+
fallback for the same schema in the same accepted type set. A module can accept
|
|
89
|
+
a regular `OMM.fbs` request and emit an aligned-binary `StateVector.fbs`
|
|
90
|
+
response, provided the output port also declares the regular `StateVector.fbs`
|
|
91
|
+
fallback and the aligned type ref carries the correct layout metadata.
|
|
92
|
+
|
|
84
93
|
## Runtime Portability
|
|
85
94
|
|
|
86
95
|
The module format is language-neutral. A host can load modules from this SDK
|
|
@@ -115,6 +124,25 @@ This repo currently includes:
|
|
|
115
124
|
- a reference Node host and sync `sdn_host` bridge for the first hostcall
|
|
116
125
|
surface
|
|
117
126
|
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
This repo now exposes a manifest-driven harness generator from
|
|
130
|
+
`space-data-module-sdk/testing` and two complementary integration suites:
|
|
131
|
+
|
|
132
|
+
- `npm run test:runtime-matrix`
|
|
133
|
+
- cross-language runtime smoke across the same WASM in Node.js, Go, Python,
|
|
134
|
+
Rust, Java, C#, and Swift
|
|
135
|
+
- covers method calling, aligned-binary envelope metadata preservation,
|
|
136
|
+
stdin/stdout/stderr, args, env, preopened filesystem access, and basic WASI
|
|
137
|
+
clock/time smoke
|
|
138
|
+
- `npm run test:host-surfaces`
|
|
139
|
+
- authoritative Node-host coverage for HTTP, TCP, UDP, TLS, WebSocket, MQTT,
|
|
140
|
+
process execution, timers, filesystem, and the sync `sdn_host` ABI
|
|
141
|
+
|
|
142
|
+
The detailed edge cases and the current WASI-vs-host portability boundary are
|
|
143
|
+
documented in
|
|
144
|
+
[`docs/testing-harness.md`](./docs/testing-harness.md).
|
|
145
|
+
|
|
118
146
|
## Install
|
|
119
147
|
|
|
120
148
|
```bash
|
|
@@ -161,8 +189,36 @@ import { createDeploymentAuthorization } from "space-data-module-sdk/auth";
|
|
|
161
189
|
import { encryptJsonForRecipient } from "space-data-module-sdk/transport";
|
|
162
190
|
import { compileModuleFromSource } from "space-data-module-sdk/compiler";
|
|
163
191
|
import { createSingleFileBundle } from "space-data-module-sdk/bundle";
|
|
192
|
+
import { validateDeploymentPlan } from "space-data-module-sdk/deployment";
|
|
193
|
+
import { generateManifestHarnessPlan } from "space-data-module-sdk/testing";
|
|
164
194
|
```
|
|
165
195
|
|
|
196
|
+
## Protocol Installation
|
|
197
|
+
|
|
198
|
+
Modules can declare hosted protocol contracts in `manifest.protocols`.
|
|
199
|
+
|
|
200
|
+
Those declarations are for stable artifact identity:
|
|
201
|
+
|
|
202
|
+
- `wireId`
|
|
203
|
+
- `transportKind`
|
|
204
|
+
- `role`
|
|
205
|
+
- `specUri`
|
|
206
|
+
- hosting hints like `defaultPort` and `requireSecureTransport`
|
|
207
|
+
|
|
208
|
+
Concrete multiaddrs, peer IDs, and producer routing do not belong in the
|
|
209
|
+
canonical manifest. They belong in deployment metadata attached to the final
|
|
210
|
+
package or bundle.
|
|
211
|
+
|
|
212
|
+
This repo exposes that deployment surface from
|
|
213
|
+
`space-data-module-sdk/deployment`. Use it to:
|
|
214
|
+
|
|
215
|
+
- validate resolved protocol installations
|
|
216
|
+
- describe input bindings from producers to module ports
|
|
217
|
+
- attach a deployment plan to `sds.bundle`
|
|
218
|
+
|
|
219
|
+
The full contract split is documented in
|
|
220
|
+
[`docs/protocol-installation.md`](./docs/protocol-installation.md).
|
|
221
|
+
|
|
166
222
|
## Single-File Bundles
|
|
167
223
|
|
|
168
224
|
`sds.bundle` keeps module delivery to one file without changing WebAssembly
|
|
@@ -179,6 +235,9 @@ The reference path lives in
|
|
|
179
235
|
- the `go` and `python` directories show non-JS readers against the same
|
|
180
236
|
bundle contract
|
|
181
237
|
|
|
238
|
+
Standard bundle payloads now include the optional `deployment-plan` JSON entry
|
|
239
|
+
for resolved protocol installations and producer input bindings.
|
|
240
|
+
|
|
182
241
|
## Module Publication
|
|
183
242
|
|
|
184
243
|
Packages that publish SDN modules now use the canonical `sdn-module`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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",
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
"./compiler": "./src/compiler/index.js",
|
|
21
21
|
"./compiler/emception": "./src/compiler/emception.js",
|
|
22
22
|
"./bundle": "./src/bundle/index.js",
|
|
23
|
+
"./deployment": "./src/deployment/index.js",
|
|
23
24
|
"./invoke": "./src/invoke/index.js",
|
|
24
25
|
"./runtime": "./src/runtime/index.js",
|
|
26
|
+
"./testing": "./src/testing/index.js",
|
|
25
27
|
"./standards": "./src/standards/index.js",
|
|
26
28
|
"./schemas/*": "./schemas/*"
|
|
27
29
|
},
|
|
@@ -32,6 +34,8 @@
|
|
|
32
34
|
],
|
|
33
35
|
"scripts": {
|
|
34
36
|
"test": "node --test",
|
|
37
|
+
"test:host-surfaces": "node --test test/node-host.test.js test/host-abi.test.js",
|
|
38
|
+
"test:runtime-matrix": "SPACE_DATA_MODULE_SDK_ENABLE_RUNTIME_MATRIX=1 node --test test/runtime-matrix.test.js",
|
|
35
39
|
"start:lab": "node ./lab/server.js",
|
|
36
40
|
"check:compliance": "node ./bin/space-data-module.js check --repo-root .",
|
|
37
41
|
"generate:vectors": "node ./examples/single-file-bundle/generate-vectors.mjs",
|
|
@@ -134,6 +134,15 @@ table ProtocolSpec {
|
|
|
134
134
|
input_port_id: string;
|
|
135
135
|
output_port_id: string;
|
|
136
136
|
description: string;
|
|
137
|
+
wire_id: string;
|
|
138
|
+
transport_kind: string;
|
|
139
|
+
role: string;
|
|
140
|
+
spec_uri: string;
|
|
141
|
+
auto_install: bool = true;
|
|
142
|
+
advertise: bool = false;
|
|
143
|
+
discovery_key: string;
|
|
144
|
+
default_port: uint16;
|
|
145
|
+
require_secure_transport: bool = false;
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
/// Build artifact emitted by the plugin toolchain.
|
package/src/bundle/constants.js
CHANGED
|
@@ -5,4 +5,7 @@ export const DEFAULT_HASH_ALGORITHM = "sha256";
|
|
|
5
5
|
export const DEFAULT_MANIFEST_EXPORT_SYMBOL = "plugin_get_manifest_flatbuffer";
|
|
6
6
|
export const DEFAULT_MANIFEST_SIZE_SYMBOL =
|
|
7
7
|
"plugin_get_manifest_flatbuffer_size";
|
|
8
|
-
|
|
8
|
+
export const SDS_DEPLOYMENT_SECTION_NAME = "sds.deployment";
|
|
9
|
+
export const SDS_DEPLOYMENT_ENTRY_ID = "deployment-plan";
|
|
10
|
+
export const SDS_DEPLOYMENT_MEDIA_TYPE =
|
|
11
|
+
"application/vnd.space-data.module.deployment+json";
|
package/src/bundle/wasm.js
CHANGED
|
@@ -3,11 +3,14 @@ import { decodePluginManifest, encodePluginManifest } from "../manifest/index.js
|
|
|
3
3
|
import { toUint8Array } from "../runtime/bufferLike.js";
|
|
4
4
|
import { sha256Bytes } from "../utils/crypto.js";
|
|
5
5
|
import { bytesToHex } from "../utils/encoding.js";
|
|
6
|
+
import { createDeploymentPlanBundleEntry } from "../deployment/index.js";
|
|
6
7
|
import {
|
|
7
8
|
DEFAULT_MANIFEST_EXPORT_SYMBOL,
|
|
8
9
|
DEFAULT_MANIFEST_SIZE_SYMBOL,
|
|
9
10
|
SDS_BUNDLE_SECTION_NAME,
|
|
10
11
|
SDS_CUSTOM_SECTION_PREFIX,
|
|
12
|
+
SDS_DEPLOYMENT_ENTRY_ID,
|
|
13
|
+
SDS_DEPLOYMENT_SECTION_NAME,
|
|
11
14
|
} from "./constants.js";
|
|
12
15
|
import {
|
|
13
16
|
decodeModuleBundle,
|
|
@@ -268,6 +271,9 @@ function normalizeStandardEntries(options = {}) {
|
|
|
268
271
|
description: "Transport envelope metadata.",
|
|
269
272
|
});
|
|
270
273
|
}
|
|
274
|
+
if (options.deploymentPlan !== undefined) {
|
|
275
|
+
entries.push(createDeploymentPlanBundleEntry(options.deploymentPlan));
|
|
276
|
+
}
|
|
271
277
|
return entries.filter((entry) => entry.payload !== null);
|
|
272
278
|
}
|
|
273
279
|
|
|
@@ -282,7 +288,12 @@ function normalizeAdditionalEntries(entries = []) {
|
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
async function withSha256(entry) {
|
|
285
|
-
const payloadBytes =
|
|
291
|
+
const payloadBytes =
|
|
292
|
+
toUint8Array(entry.payload) ??
|
|
293
|
+
((entry.payloadEncoding === "json-utf8" ||
|
|
294
|
+
moduleBundleEncodingToName(entry.payloadEncoding) === "json-utf8")
|
|
295
|
+
? canonicalBytes(entry.payload)
|
|
296
|
+
: normalizeBytes(entry.payload, `entry "${entry.entryId}" payload`));
|
|
286
297
|
return {
|
|
287
298
|
...entry,
|
|
288
299
|
payload: payloadBytes,
|
|
@@ -315,6 +326,17 @@ function buildParsedEntries(bundle) {
|
|
|
315
326
|
parsedEntry.decodedManifest = null;
|
|
316
327
|
}
|
|
317
328
|
}
|
|
329
|
+
if (
|
|
330
|
+
parsedEntry.entryId === SDS_DEPLOYMENT_ENTRY_ID ||
|
|
331
|
+
parsedEntry.sectionName === SDS_DEPLOYMENT_SECTION_NAME
|
|
332
|
+
) {
|
|
333
|
+
parsedEntry.decodedDeploymentPlan =
|
|
334
|
+
parsedEntry.payloadEncodingName === "json-utf8" &&
|
|
335
|
+
parsedEntry.decodedPayload &&
|
|
336
|
+
typeof parsedEntry.decodedPayload === "object"
|
|
337
|
+
? parsedEntry.decodedPayload
|
|
338
|
+
: null;
|
|
339
|
+
}
|
|
318
340
|
return parsedEntry;
|
|
319
341
|
});
|
|
320
342
|
}
|
|
@@ -432,16 +454,22 @@ export async function parseSingleFileBundle(bytes, options = {}) {
|
|
|
432
454
|
manifest = null;
|
|
433
455
|
}
|
|
434
456
|
}
|
|
457
|
+
const deploymentEntry =
|
|
458
|
+
parsedEntries.find(
|
|
459
|
+
(entry) =>
|
|
460
|
+
entry.entryId === SDS_DEPLOYMENT_ENTRY_ID ||
|
|
461
|
+
entry.sectionName === SDS_DEPLOYMENT_SECTION_NAME,
|
|
462
|
+
) ?? null;
|
|
435
463
|
return {
|
|
436
464
|
wasmBytes,
|
|
437
465
|
bundleBytes,
|
|
438
466
|
bundle,
|
|
439
467
|
entries: parsedEntries,
|
|
440
468
|
manifest,
|
|
469
|
+
deploymentPlan: deploymentEntry?.decodedDeploymentPlan ?? null,
|
|
441
470
|
customSections,
|
|
442
471
|
canonicalWasmBytes: canonical.canonicalWasmBytes,
|
|
443
472
|
canonicalModuleHash: canonical.hashBytes,
|
|
444
473
|
canonicalModuleHashHex: canonical.hashHex,
|
|
445
474
|
};
|
|
446
475
|
}
|
|
447
|
-
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
ExternalInterfaceDirection,
|
|
9
9
|
ExternalInterfaceKind,
|
|
10
10
|
InvokeSurface,
|
|
11
|
+
ProtocolRole,
|
|
12
|
+
ProtocolTransportKind,
|
|
11
13
|
RuntimeTarget,
|
|
12
14
|
} from "../runtime/constants.js";
|
|
13
15
|
|
|
@@ -59,6 +61,8 @@ const ExternalInterfaceDirectionSet = new Set(
|
|
|
59
61
|
Object.values(ExternalInterfaceDirection),
|
|
60
62
|
);
|
|
61
63
|
const ExternalInterfaceKindSet = new Set(Object.values(ExternalInterfaceKind));
|
|
64
|
+
const ProtocolRoleSet = new Set(Object.values(ProtocolRole));
|
|
65
|
+
const ProtocolTransportKindSet = new Set(Object.values(ProtocolTransportKind));
|
|
62
66
|
const BrowserIncompatibleCapabilitySet = new Set([
|
|
63
67
|
"filesystem",
|
|
64
68
|
"pipe",
|
|
@@ -120,6 +124,57 @@ function hasNonEmptyByteSequence(value) {
|
|
|
120
124
|
return false;
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
function normalizeTypeIdentityString(value) {
|
|
128
|
+
if (!isNonEmptyString(value)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return value.trim().toLowerCase();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeTypeIdentityBytes(value) {
|
|
135
|
+
if (typeof value === "string") {
|
|
136
|
+
const trimmed = value.trim().toLowerCase();
|
|
137
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
138
|
+
}
|
|
139
|
+
const bytes = ArrayBuffer.isView(value)
|
|
140
|
+
? Array.from(value)
|
|
141
|
+
: Array.isArray(value)
|
|
142
|
+
? value
|
|
143
|
+
: null;
|
|
144
|
+
if (!bytes || bytes.length === 0) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return bytes
|
|
148
|
+
.map((entry) => Number(entry).toString(16).padStart(2, "0"))
|
|
149
|
+
.join("");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function allowedTypesReferToSameLogicalSchema(left, right) {
|
|
153
|
+
const leftSchemaName = normalizeTypeIdentityString(left?.schemaName);
|
|
154
|
+
const rightSchemaName = normalizeTypeIdentityString(right?.schemaName);
|
|
155
|
+
if (leftSchemaName && rightSchemaName && leftSchemaName === rightSchemaName) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const leftFileIdentifier = normalizeTypeIdentityString(left?.fileIdentifier);
|
|
160
|
+
const rightFileIdentifier = normalizeTypeIdentityString(right?.fileIdentifier);
|
|
161
|
+
if (
|
|
162
|
+
leftFileIdentifier &&
|
|
163
|
+
rightFileIdentifier &&
|
|
164
|
+
leftFileIdentifier === rightFileIdentifier
|
|
165
|
+
) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const leftSchemaHash = normalizeTypeIdentityBytes(left?.schemaHash);
|
|
170
|
+
const rightSchemaHash = normalizeTypeIdentityBytes(right?.schemaHash);
|
|
171
|
+
if (leftSchemaHash && rightSchemaHash && leftSchemaHash === rightSchemaHash) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
123
178
|
function normalizePayloadWireFormatName(value) {
|
|
124
179
|
if (value === undefined || value === null || value === "") {
|
|
125
180
|
return "flatbuffer";
|
|
@@ -151,7 +206,13 @@ function validateStringField(issues, value, location, label) {
|
|
|
151
206
|
return true;
|
|
152
207
|
}
|
|
153
208
|
|
|
154
|
-
function validateIntegerField(
|
|
209
|
+
function validateIntegerField(
|
|
210
|
+
issues,
|
|
211
|
+
value,
|
|
212
|
+
location,
|
|
213
|
+
label,
|
|
214
|
+
{ min = null, max = null } = {},
|
|
215
|
+
) {
|
|
155
216
|
if (!Number.isInteger(value)) {
|
|
156
217
|
pushIssue(issues, "error", "invalid-integer", `${label} must be an integer.`, location);
|
|
157
218
|
return false;
|
|
@@ -166,6 +227,16 @@ function validateIntegerField(issues, value, location, label, { min = null } = {
|
|
|
166
227
|
);
|
|
167
228
|
return false;
|
|
168
229
|
}
|
|
230
|
+
if (max !== null && value > max) {
|
|
231
|
+
pushIssue(
|
|
232
|
+
issues,
|
|
233
|
+
"error",
|
|
234
|
+
"integer-range",
|
|
235
|
+
`${label} must be less than or equal to ${max}.`,
|
|
236
|
+
location,
|
|
237
|
+
);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
169
240
|
return true;
|
|
170
241
|
}
|
|
171
242
|
|
|
@@ -182,6 +253,61 @@ function validateOptionalIntegerField(
|
|
|
182
253
|
return validateIntegerField(issues, value, location, label, options);
|
|
183
254
|
}
|
|
184
255
|
|
|
256
|
+
function validateOptionalBooleanField(issues, value, location, label) {
|
|
257
|
+
if (value === undefined || value === null) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
if (typeof value !== "boolean") {
|
|
261
|
+
pushIssue(
|
|
262
|
+
issues,
|
|
263
|
+
"error",
|
|
264
|
+
"invalid-boolean",
|
|
265
|
+
`${label} must be a boolean when present.`,
|
|
266
|
+
location,
|
|
267
|
+
);
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function validateOptionalStringField(issues, value, location, label) {
|
|
274
|
+
if (value === undefined || value === null) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
return validateStringField(issues, value, location, label);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function normalizeProtocolTransportKind(value) {
|
|
281
|
+
if (value === undefined || value === null || value === "") {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const normalized = String(value)
|
|
285
|
+
.trim()
|
|
286
|
+
.toLowerCase()
|
|
287
|
+
.replace(/_/g, "-");
|
|
288
|
+
if (normalized === "websocket") {
|
|
289
|
+
return ProtocolTransportKind.WS;
|
|
290
|
+
}
|
|
291
|
+
if (normalized === "pipe") {
|
|
292
|
+
return ProtocolTransportKind.WASI_PIPE;
|
|
293
|
+
}
|
|
294
|
+
return normalized;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizeProtocolRole(value) {
|
|
298
|
+
if (value === undefined || value === null || value === "") {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
const normalized = String(value)
|
|
302
|
+
.trim()
|
|
303
|
+
.toLowerCase()
|
|
304
|
+
.replace(/_/g, "-");
|
|
305
|
+
if (normalized === "handler") {
|
|
306
|
+
return ProtocolRole.HANDLE;
|
|
307
|
+
}
|
|
308
|
+
return normalized;
|
|
309
|
+
}
|
|
310
|
+
|
|
185
311
|
function validateAllowedType(type, issues, location) {
|
|
186
312
|
if (!type || typeof type !== "object" || Array.isArray(type)) {
|
|
187
313
|
pushIssue(issues, "error", "invalid-type-record", "Allowed type entries must be objects.", location);
|
|
@@ -307,6 +433,35 @@ function validateAcceptedTypeSet(typeSet, issues, location) {
|
|
|
307
433
|
typeSet.allowedTypes.forEach((allowedType, index) => {
|
|
308
434
|
validateAllowedType(allowedType, issues, `${location}.allowedTypes[${index}]`);
|
|
309
435
|
});
|
|
436
|
+
|
|
437
|
+
const regularConcreteTypes = [];
|
|
438
|
+
const alignedTypes = [];
|
|
439
|
+
|
|
440
|
+
typeSet.allowedTypes.forEach((allowedType, index) => {
|
|
441
|
+
const wireFormat = normalizePayloadWireFormatName(allowedType?.wireFormat);
|
|
442
|
+
if (wireFormat === "aligned-binary") {
|
|
443
|
+
alignedTypes.push({ allowedType, index });
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (wireFormat === "flatbuffer" && allowedType?.acceptsAnyFlatbuffer !== true) {
|
|
447
|
+
regularConcreteTypes.push({ allowedType, index });
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
alignedTypes.forEach(({ allowedType, index }) => {
|
|
452
|
+
const hasRegularFallback = regularConcreteTypes.some(({ allowedType: regularType }) =>
|
|
453
|
+
allowedTypesReferToSameLogicalSchema(allowedType, regularType),
|
|
454
|
+
);
|
|
455
|
+
if (!hasRegularFallback) {
|
|
456
|
+
pushIssue(
|
|
457
|
+
issues,
|
|
458
|
+
"error",
|
|
459
|
+
"missing-flatbuffer-fallback",
|
|
460
|
+
"Aligned-binary allowed types must be paired with a regular flatbuffer fallback for the same schema in the same acceptedTypeSet.",
|
|
461
|
+
`${location}.allowedTypes[${index}]`,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
310
465
|
}
|
|
311
466
|
|
|
312
467
|
function validatePort(port, issues, location, label) {
|
|
@@ -576,6 +731,85 @@ function validateProtocol(protocol, issues, location, methodLookup, declaredCapa
|
|
|
576
731
|
);
|
|
577
732
|
}
|
|
578
733
|
}
|
|
734
|
+
const wireIdValid = validateStringField(
|
|
735
|
+
issues,
|
|
736
|
+
protocol.wireId,
|
|
737
|
+
`${location}.wireId`,
|
|
738
|
+
"Protocol wireId",
|
|
739
|
+
);
|
|
740
|
+
const normalizedTransportKind = normalizeProtocolTransportKind(
|
|
741
|
+
protocol.transportKind,
|
|
742
|
+
);
|
|
743
|
+
if (!validateStringField(
|
|
744
|
+
issues,
|
|
745
|
+
protocol.transportKind,
|
|
746
|
+
`${location}.transportKind`,
|
|
747
|
+
"Protocol transportKind",
|
|
748
|
+
)) {
|
|
749
|
+
// already reported
|
|
750
|
+
} else if (!ProtocolTransportKindSet.has(normalizedTransportKind)) {
|
|
751
|
+
pushIssue(
|
|
752
|
+
issues,
|
|
753
|
+
"error",
|
|
754
|
+
"unknown-protocol-transport-kind",
|
|
755
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" transportKind must be one of: ${Array.from(ProtocolTransportKindSet).join(", ")}.`,
|
|
756
|
+
`${location}.transportKind`,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
const normalizedRole = normalizeProtocolRole(protocol.role);
|
|
760
|
+
if (!validateStringField(
|
|
761
|
+
issues,
|
|
762
|
+
protocol.role,
|
|
763
|
+
`${location}.role`,
|
|
764
|
+
"Protocol role",
|
|
765
|
+
)) {
|
|
766
|
+
// already reported
|
|
767
|
+
} else if (!ProtocolRoleSet.has(normalizedRole)) {
|
|
768
|
+
pushIssue(
|
|
769
|
+
issues,
|
|
770
|
+
"error",
|
|
771
|
+
"unknown-protocol-role",
|
|
772
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" role must be one of: ${Array.from(ProtocolRoleSet).join(", ")}.`,
|
|
773
|
+
`${location}.role`,
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
validateOptionalStringField(
|
|
777
|
+
issues,
|
|
778
|
+
protocol.specUri,
|
|
779
|
+
`${location}.specUri`,
|
|
780
|
+
"Protocol specUri",
|
|
781
|
+
);
|
|
782
|
+
validateOptionalStringField(
|
|
783
|
+
issues,
|
|
784
|
+
protocol.discoveryKey,
|
|
785
|
+
`${location}.discoveryKey`,
|
|
786
|
+
"Protocol discoveryKey",
|
|
787
|
+
);
|
|
788
|
+
validateOptionalBooleanField(
|
|
789
|
+
issues,
|
|
790
|
+
protocol.autoInstall,
|
|
791
|
+
`${location}.autoInstall`,
|
|
792
|
+
"Protocol autoInstall",
|
|
793
|
+
);
|
|
794
|
+
validateOptionalBooleanField(
|
|
795
|
+
issues,
|
|
796
|
+
protocol.advertise,
|
|
797
|
+
`${location}.advertise`,
|
|
798
|
+
"Protocol advertise",
|
|
799
|
+
);
|
|
800
|
+
validateOptionalBooleanField(
|
|
801
|
+
issues,
|
|
802
|
+
protocol.requireSecureTransport,
|
|
803
|
+
`${location}.requireSecureTransport`,
|
|
804
|
+
"Protocol requireSecureTransport",
|
|
805
|
+
);
|
|
806
|
+
validateOptionalIntegerField(
|
|
807
|
+
issues,
|
|
808
|
+
protocol.defaultPort,
|
|
809
|
+
`${location}.defaultPort`,
|
|
810
|
+
"Protocol defaultPort",
|
|
811
|
+
{ min: 0, max: 65535 },
|
|
812
|
+
);
|
|
579
813
|
if (
|
|
580
814
|
protocolIdValid &&
|
|
581
815
|
Array.isArray(declaredCapabilities) &&
|
|
@@ -590,6 +824,77 @@ function validateProtocol(protocol, issues, location, methodLookup, declaredCapa
|
|
|
590
824
|
location,
|
|
591
825
|
);
|
|
592
826
|
}
|
|
827
|
+
if (
|
|
828
|
+
protocolIdValid &&
|
|
829
|
+
normalizedRole &&
|
|
830
|
+
(normalizedRole === ProtocolRole.HANDLE ||
|
|
831
|
+
normalizedRole === ProtocolRole.BOTH) &&
|
|
832
|
+
Array.isArray(declaredCapabilities) &&
|
|
833
|
+
!declaredCapabilities.includes("protocol_handle")
|
|
834
|
+
) {
|
|
835
|
+
pushIssue(
|
|
836
|
+
issues,
|
|
837
|
+
"error",
|
|
838
|
+
"missing-handle-protocol-capability",
|
|
839
|
+
`Protocol "${protocol.protocolId}" with role "${normalizedRole}" requires the "protocol_handle" capability.`,
|
|
840
|
+
location,
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
if (
|
|
844
|
+
protocolIdValid &&
|
|
845
|
+
normalizedRole &&
|
|
846
|
+
(normalizedRole === ProtocolRole.DIAL ||
|
|
847
|
+
normalizedRole === ProtocolRole.BOTH) &&
|
|
848
|
+
Array.isArray(declaredCapabilities) &&
|
|
849
|
+
!declaredCapabilities.includes("protocol_dial")
|
|
850
|
+
) {
|
|
851
|
+
pushIssue(
|
|
852
|
+
issues,
|
|
853
|
+
"error",
|
|
854
|
+
"missing-dial-protocol-capability",
|
|
855
|
+
`Protocol "${protocol.protocolId}" with role "${normalizedRole}" requires the "protocol_dial" capability.`,
|
|
856
|
+
location,
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
if (
|
|
860
|
+
protocol.advertise === true &&
|
|
861
|
+
normalizedRole === ProtocolRole.DIAL
|
|
862
|
+
) {
|
|
863
|
+
pushIssue(
|
|
864
|
+
issues,
|
|
865
|
+
"error",
|
|
866
|
+
"protocol-advertise-role-conflict",
|
|
867
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" cannot advertise when role is "dial".`,
|
|
868
|
+
`${location}.advertise`,
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
if (
|
|
872
|
+
normalizedTransportKind === ProtocolTransportKind.LIBP2P &&
|
|
873
|
+
Array.isArray(declaredCapabilities) &&
|
|
874
|
+
!declaredCapabilities.includes("ipfs")
|
|
875
|
+
) {
|
|
876
|
+
pushIssue(
|
|
877
|
+
issues,
|
|
878
|
+
"error",
|
|
879
|
+
"missing-ipfs-capability",
|
|
880
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" with transportKind "libp2p" requires the "ipfs" capability.`,
|
|
881
|
+
location,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
if (
|
|
885
|
+
wireIdValid &&
|
|
886
|
+
normalizedTransportKind === ProtocolTransportKind.WS &&
|
|
887
|
+
protocol.defaultPort === 443 &&
|
|
888
|
+
protocol.requireSecureTransport !== true
|
|
889
|
+
) {
|
|
890
|
+
pushIssue(
|
|
891
|
+
issues,
|
|
892
|
+
"warning",
|
|
893
|
+
"insecure-secure-port-hint",
|
|
894
|
+
`Protocol "${protocol.protocolId ?? "protocol"}" uses defaultPort 443 without requireSecureTransport=true.`,
|
|
895
|
+
`${location}.defaultPort`,
|
|
896
|
+
);
|
|
897
|
+
}
|
|
593
898
|
}
|
|
594
899
|
|
|
595
900
|
function validateRuntimeTargets(runtimeTargets, declaredCapabilities, issues, sourceName) {
|