space-data-module-sdk 0.8.7 → 0.8.8
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/docs/browser-wasmedge-isomorphic.md +1 -1
- package/docs/language-runtime-matrix.md +2 -2
- package/package.json +13 -5
- package/src/browser.js +14 -4
- package/src/bundle/artifactBytes.js +44 -0
- package/src/bundle/index.js +1 -0
- package/src/flow/isomorphicFlowHost.js +1 -1
- package/src/{testing → host}/browserModuleHarness.js +21 -34
- package/src/host/isomorphicLoader.js +17 -45
- package/src/host/isomorphicLoaderBrowser.js +47 -0
- package/src/host/isomorphicLoaderCore.js +58 -0
- package/src/host/nodeBuiltinSpecifier.js +43 -0
- package/src/host/sabHostcallChannel.js +1 -1
- package/src/host/wasiThreadHost.js +2 -3
- package/src/{testing → host}/workerModuleHarness.js +9 -5
- package/src/{testing → host}/workerModuleHarnessWorker.js +1 -1
- package/src/standards/browser.js +95 -0
- package/src/standards/catalogCore.js +290 -0
- package/src/standards/index.js +27 -251
- package/src/testing/AGENTS.md +25 -2
- package/src/testing/browser.js +32 -0
- package/src/testing/index.js +3 -3
- package/src/testing/parityBrowserRunner.js +1 -1
- package/src/testing/parityHarness.js +1 -1
- /package/src/{testing → host}/moduleFlatbufferStreamPump.js +0 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards catalog — the runtime-independent half.
|
|
3
|
+
*
|
|
4
|
+
* Parsing, freshness validation, type-ref resolution and manifest checking are
|
|
5
|
+
* identical everywhere. Only HOW the `spacedatastandards.org` manifest is
|
|
6
|
+
* OBTAINED differs: Node reads it off disk (`./index.js`), a browser is handed
|
|
7
|
+
* it or fetches it (`./browser.js`). The package's `browser` export condition
|
|
8
|
+
* picks the leg, so a browser bundle never statically resolves
|
|
9
|
+
* node:fs/promises, node:path or node:module
|
|
10
|
+
* (`module-sdk-browser-entry-node-builtins`).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { sharedModuleCatalog } from "./sharedCatalog.js";
|
|
14
|
+
import { normalizePayloadSchemaHash } from "../manifest/typeRefs.js";
|
|
15
|
+
const requiredCurrentScvTokens = Object.freeze([
|
|
16
|
+
"SENSOR_LOCAL",
|
|
17
|
+
"table SCVResult",
|
|
18
|
+
"table SCVSensorShapeContract",
|
|
19
|
+
"SAR_ANNULAR_SECTOR",
|
|
20
|
+
"enum scvSensorRangeBoundaryKind",
|
|
21
|
+
"table SCVAggregateStatistics",
|
|
22
|
+
"AGGREGATE_STATISTICS:SCVAggregateStatistics",
|
|
23
|
+
"table SCVPackedRasterProducts",
|
|
24
|
+
"table SCVPackedRasterBand",
|
|
25
|
+
"RASTER_PRODUCTS:SCVPackedRasterProducts",
|
|
26
|
+
"enum scvRasterProductKind",
|
|
27
|
+
]);
|
|
28
|
+
const currentScvResultFields = Object.freeze(
|
|
29
|
+
new Set([
|
|
30
|
+
"JOB_ID",
|
|
31
|
+
"TRACE_ID",
|
|
32
|
+
"STATUS",
|
|
33
|
+
"TIME_GRID",
|
|
34
|
+
"TARGET_BODY",
|
|
35
|
+
"TOTAL_SENSORS",
|
|
36
|
+
"TOTAL_WINDOWS",
|
|
37
|
+
"CELL_STATS",
|
|
38
|
+
"INTERVALS",
|
|
39
|
+
"LATITUDE_BANDS",
|
|
40
|
+
"TIME_SERIES",
|
|
41
|
+
"HISTOGRAMS",
|
|
42
|
+
"CONTRIBUTIONS",
|
|
43
|
+
"GEOMETRY",
|
|
44
|
+
"RASTER_PRODUCTS",
|
|
45
|
+
"MESSAGE",
|
|
46
|
+
"AGGREGATE_STATISTICS",
|
|
47
|
+
"TARGET_RESULTS",
|
|
48
|
+
]),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
function collectFlatbufferTableFields(idl, tableName) {
|
|
52
|
+
const match = String(idl ?? "").match(
|
|
53
|
+
new RegExp(`\\btable\\s+${tableName}\\s*\\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
54
|
+
);
|
|
55
|
+
if (!match) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
return match[1]
|
|
59
|
+
.split("\n")
|
|
60
|
+
.map((line) => line.replace(/\/\/.*$/, "").trim())
|
|
61
|
+
.map((line) => line.match(/^([A-Z0-9_]+)\s*:/)?.[1] ?? null)
|
|
62
|
+
.filter(Boolean);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeSchemaName(value) {
|
|
66
|
+
return value === undefined || value === null ? "" : String(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeFileIdentifier(value) {
|
|
70
|
+
return value === undefined || value === null ? "" : String(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseStandardsEntry([schemaCode, entry]) {
|
|
74
|
+
const idl = String(entry?.IDL ?? "");
|
|
75
|
+
const fileIdentifierMatch = idl.match(/file_identifier\s+"([^"]+)"/i);
|
|
76
|
+
const hashMatch = idl.match(/\/\/ Hash:\s*([a-f0-9]+)/i);
|
|
77
|
+
const versionMatch = idl.match(/\/\/ Version:\s*([^\n]+)/i);
|
|
78
|
+
const rootTypeMatch = idl.match(/root_type\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/i);
|
|
79
|
+
return {
|
|
80
|
+
schemaCode: schemaCode.toUpperCase(),
|
|
81
|
+
schemaName: `${schemaCode.toUpperCase()}.fbs`,
|
|
82
|
+
fileIdentifier: fileIdentifierMatch
|
|
83
|
+
? normalizeFileIdentifier(fileIdentifierMatch[1])
|
|
84
|
+
: null,
|
|
85
|
+
hash: hashMatch ? hashMatch[1].toLowerCase() : null,
|
|
86
|
+
rootTypeName: rootTypeMatch ? rootTypeMatch[1] : null,
|
|
87
|
+
idl,
|
|
88
|
+
version: versionMatch ? versionMatch[1].trim() : null,
|
|
89
|
+
files: Array.isArray(entry?.files) ? entry.files : [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateStandardsCatalogFreshness(catalog, sourceName) {
|
|
94
|
+
const issues = [];
|
|
95
|
+
const scvEntry = catalog.find((entry) => entry.schemaCode === "SCV");
|
|
96
|
+
if (!scvEntry) {
|
|
97
|
+
return issues;
|
|
98
|
+
}
|
|
99
|
+
const missingTokens = requiredCurrentScvTokens.filter(
|
|
100
|
+
(token) => !scvEntry.idl.includes(token),
|
|
101
|
+
);
|
|
102
|
+
if (missingTokens.length > 0) {
|
|
103
|
+
issues.push({
|
|
104
|
+
severity: "error",
|
|
105
|
+
code: "stale-scv-contract",
|
|
106
|
+
message:
|
|
107
|
+
"The loaded spacedatastandards.org SCV catalog is stale and does not " +
|
|
108
|
+
`include required current coverage fields: ${missingTokens.join(", ")}.`,
|
|
109
|
+
location: `${sourceName}.standards.SCV`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const unsupportedResultFields = collectFlatbufferTableFields(
|
|
113
|
+
scvEntry.idl,
|
|
114
|
+
"SCVResult",
|
|
115
|
+
).filter((field) => !currentScvResultFields.has(field));
|
|
116
|
+
if (unsupportedResultFields.length > 0) {
|
|
117
|
+
issues.push({
|
|
118
|
+
severity: "error",
|
|
119
|
+
code: "stale-scv-contract",
|
|
120
|
+
message:
|
|
121
|
+
"The loaded spacedatastandards.org SCV catalog contains unsupported " +
|
|
122
|
+
`SCVResult fields that are not part of the current contract: ${unsupportedResultFields.join(", ")}.`,
|
|
123
|
+
location: `${sourceName}.standards.SCV`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return issues;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build the standards catalog from an already-parsed manifest object. Same
|
|
131
|
+
* bytes in, same catalog out, in every runtime.
|
|
132
|
+
*
|
|
133
|
+
* @param {object} manifest - the parsed `spacedatastandards.org` manifest.json
|
|
134
|
+
* @returns {Array} sorted standards catalog
|
|
135
|
+
*/
|
|
136
|
+
export function buildStandardsCatalog(manifest) {
|
|
137
|
+
return Object.entries(manifest?.STANDARDS ?? {})
|
|
138
|
+
.map(parseStandardsEntry)
|
|
139
|
+
.sort((left, right) => left.schemaCode.localeCompare(right.schemaCode));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Append the SDK's own shared-module catalog to a standards catalog.
|
|
144
|
+
*
|
|
145
|
+
* @param {Array} catalog
|
|
146
|
+
* @returns {Array}
|
|
147
|
+
*/
|
|
148
|
+
export function withSharedModuleCatalog(catalog) {
|
|
149
|
+
return [...catalog, ...sharedModuleCatalog];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function resolveStandardsTypeRef(typeRef, catalog = []) {
|
|
153
|
+
const schemaName = normalizeSchemaName(typeRef?.schemaName);
|
|
154
|
+
const fileIdentifier = normalizeFileIdentifier(typeRef?.fileIdentifier);
|
|
155
|
+
if (!schemaName && !fileIdentifier) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return (
|
|
159
|
+
catalog.find(
|
|
160
|
+
(entry) =>
|
|
161
|
+
(!schemaName || normalizeSchemaName(entry.schemaName) === schemaName) &&
|
|
162
|
+
(!fileIdentifier ||
|
|
163
|
+
normalizeFileIdentifier(entry.fileIdentifier) === fileIdentifier),
|
|
164
|
+
) ?? null
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function schemaHashMatchesCatalog(typeRefHash, catalogHash) {
|
|
169
|
+
if (typeRefHash === undefined || typeRefHash === null) return true;
|
|
170
|
+
const declared = normalizePayloadSchemaHash(typeRefHash);
|
|
171
|
+
if (!declared) return true;
|
|
172
|
+
const canonical = normalizePayloadSchemaHash(catalogHash);
|
|
173
|
+
if (!canonical || declared.length !== canonical.length) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return declared.every((byte, index) => byte === canonical[index]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function collectTypeRefs(manifest) {
|
|
180
|
+
const refs = [];
|
|
181
|
+
for (const method of Array.isArray(manifest?.methods) ? manifest.methods : []) {
|
|
182
|
+
for (const portsKey of ["inputPorts", "outputPorts"]) {
|
|
183
|
+
for (const port of Array.isArray(method?.[portsKey]) ? method[portsKey] : []) {
|
|
184
|
+
for (const typeSet of Array.isArray(port?.acceptedTypeSets)
|
|
185
|
+
? port.acceptedTypeSets
|
|
186
|
+
: []) {
|
|
187
|
+
for (const allowedType of Array.isArray(typeSet?.allowedTypes)
|
|
188
|
+
? typeSet.allowedTypes
|
|
189
|
+
: []) {
|
|
190
|
+
refs.push({
|
|
191
|
+
source: `${method?.methodId ?? "method"}.${port?.portId ?? "port"}`,
|
|
192
|
+
typeRef: allowedType,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const typeRef of Array.isArray(manifest?.schemasUsed)
|
|
200
|
+
? manifest.schemasUsed
|
|
201
|
+
: []) {
|
|
202
|
+
refs.push({
|
|
203
|
+
source: "schemasUsed",
|
|
204
|
+
typeRef,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return refs;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Validate a module manifest against an ALREADY-RESOLVED catalog.
|
|
212
|
+
*
|
|
213
|
+
* Both legs of `./standards` funnel into this, so the verdicts cannot diverge
|
|
214
|
+
* between runtimes; only catalog acquisition differs.
|
|
215
|
+
*
|
|
216
|
+
* @param {Array} catalog
|
|
217
|
+
* @param {object} manifest
|
|
218
|
+
* @param {object} [options]
|
|
219
|
+
* @returns {{catalog: Array, issues: Array}}
|
|
220
|
+
*/
|
|
221
|
+
export function validateManifestAgainstCatalog(catalog, manifest, options = {}) {
|
|
222
|
+
const sourceName = options.sourceName ?? "manifest";
|
|
223
|
+
const issues = validateStandardsCatalogFreshness(catalog, sourceName);
|
|
224
|
+
for (const { source, typeRef } of collectTypeRefs(manifest)) {
|
|
225
|
+
if (typeRef?.acceptsAnyFlatbuffer === true) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const resolved = resolveStandardsTypeRef(typeRef, catalog);
|
|
229
|
+
if (!resolved) {
|
|
230
|
+
const schemaName = normalizeSchemaName(typeRef?.schemaName);
|
|
231
|
+
const fileIdentifier = normalizeFileIdentifier(typeRef?.fileIdentifier);
|
|
232
|
+
const partiallyKnown = catalog.some(
|
|
233
|
+
(entry) =>
|
|
234
|
+
(schemaName && normalizeSchemaName(entry.schemaName) === schemaName) ||
|
|
235
|
+
(fileIdentifier &&
|
|
236
|
+
normalizeFileIdentifier(entry.fileIdentifier) === fileIdentifier),
|
|
237
|
+
);
|
|
238
|
+
issues.push({
|
|
239
|
+
severity: "error",
|
|
240
|
+
code: partiallyKnown
|
|
241
|
+
? "standards-type-identity-mismatch"
|
|
242
|
+
: "unresolved-standards-type",
|
|
243
|
+
message: partiallyKnown
|
|
244
|
+
? `Type reference from ${source} mixes a known schemaName or exact four-byte fileIdentifier with a different standards entry.`
|
|
245
|
+
: `Type reference from ${source} does not resolve to a known shared-module or \`spacedatastandards.org\` schema by its exact schemaName and fileIdentifier.`,
|
|
246
|
+
location: `${sourceName}.${source}`,
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (
|
|
251
|
+
typeRef?.rootTypeName &&
|
|
252
|
+
resolved.rootTypeName &&
|
|
253
|
+
typeRef.rootTypeName !== resolved.rootTypeName
|
|
254
|
+
) {
|
|
255
|
+
issues.push({
|
|
256
|
+
severity: "error",
|
|
257
|
+
code: "standards-root-type-mismatch",
|
|
258
|
+
message: `Type reference from ${source} declares rootTypeName ${JSON.stringify(typeRef.rootTypeName)} but the canonical SDS root is ${JSON.stringify(resolved.rootTypeName)}.`,
|
|
259
|
+
location: `${sourceName}.${source}`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (
|
|
263
|
+
typeRef?.schemaVersion &&
|
|
264
|
+
resolved.version &&
|
|
265
|
+
typeRef.schemaVersion !== resolved.version
|
|
266
|
+
) {
|
|
267
|
+
issues.push({
|
|
268
|
+
severity: "error",
|
|
269
|
+
code: "standards-schema-version-mismatch",
|
|
270
|
+
message: `Type reference from ${source} declares schemaVersion ${JSON.stringify(typeRef.schemaVersion)} but the canonical SDS version is ${JSON.stringify(resolved.version)}.`,
|
|
271
|
+
location: `${sourceName}.${source}`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (
|
|
275
|
+
resolved.hash &&
|
|
276
|
+
!schemaHashMatchesCatalog(typeRef?.schemaHash, resolved.hash)
|
|
277
|
+
) {
|
|
278
|
+
issues.push({
|
|
279
|
+
severity: "error",
|
|
280
|
+
code: "standards-schema-hash-mismatch",
|
|
281
|
+
message: `Type reference from ${source} declares a schemaHash that differs from the canonical SDS schema hash.`,
|
|
282
|
+
location: `${sourceName}.${source}`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
catalog,
|
|
288
|
+
issues,
|
|
289
|
+
};
|
|
290
|
+
}
|
package/src/standards/index.js
CHANGED
|
@@ -1,62 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards catalog — NODE leg (the `default` condition of `./standards`).
|
|
3
|
+
*
|
|
4
|
+
* Reads the pinned `spacedatastandards.org` manifest off disk. Everything the
|
|
5
|
+
* catalog is then USED for lives in `catalogCore.js` and is shared with the
|
|
6
|
+
* browser leg, so a manifest validated here and a manifest validated in a
|
|
7
|
+
* browser produce the same issues in the same order.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
import { readFile } from "node:fs/promises";
|
|
2
11
|
import path from "node:path";
|
|
3
12
|
import { createRequire } from "node:module";
|
|
4
13
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
14
|
+
import {
|
|
15
|
+
buildStandardsCatalog,
|
|
16
|
+
validateManifestAgainstCatalog,
|
|
17
|
+
withSharedModuleCatalog,
|
|
18
|
+
} from "./catalogCore.js";
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
buildStandardsCatalog,
|
|
22
|
+
resolveStandardsTypeRef,
|
|
23
|
+
validateManifestAgainstCatalog,
|
|
24
|
+
withSharedModuleCatalog,
|
|
25
|
+
} from "./catalogCore.js";
|
|
7
26
|
|
|
8
27
|
const require = createRequire(import.meta.url);
|
|
9
28
|
const standardsCatalogPromises = new Map();
|
|
10
29
|
const knownTypeCatalogPromises = new Map();
|
|
11
|
-
const requiredCurrentScvTokens = Object.freeze([
|
|
12
|
-
"SENSOR_LOCAL",
|
|
13
|
-
"table SCVResult",
|
|
14
|
-
"table SCVSensorShapeContract",
|
|
15
|
-
"SAR_ANNULAR_SECTOR",
|
|
16
|
-
"enum scvSensorRangeBoundaryKind",
|
|
17
|
-
"table SCVAggregateStatistics",
|
|
18
|
-
"AGGREGATE_STATISTICS:SCVAggregateStatistics",
|
|
19
|
-
"table SCVPackedRasterProducts",
|
|
20
|
-
"table SCVPackedRasterBand",
|
|
21
|
-
"RASTER_PRODUCTS:SCVPackedRasterProducts",
|
|
22
|
-
"enum scvRasterProductKind",
|
|
23
|
-
]);
|
|
24
|
-
const currentScvResultFields = Object.freeze(
|
|
25
|
-
new Set([
|
|
26
|
-
"JOB_ID",
|
|
27
|
-
"TRACE_ID",
|
|
28
|
-
"STATUS",
|
|
29
|
-
"TIME_GRID",
|
|
30
|
-
"TARGET_BODY",
|
|
31
|
-
"TOTAL_SENSORS",
|
|
32
|
-
"TOTAL_WINDOWS",
|
|
33
|
-
"CELL_STATS",
|
|
34
|
-
"INTERVALS",
|
|
35
|
-
"LATITUDE_BANDS",
|
|
36
|
-
"TIME_SERIES",
|
|
37
|
-
"HISTOGRAMS",
|
|
38
|
-
"CONTRIBUTIONS",
|
|
39
|
-
"GEOMETRY",
|
|
40
|
-
"RASTER_PRODUCTS",
|
|
41
|
-
"MESSAGE",
|
|
42
|
-
"AGGREGATE_STATISTICS",
|
|
43
|
-
"TARGET_RESULTS",
|
|
44
|
-
]),
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
function collectFlatbufferTableFields(idl, tableName) {
|
|
48
|
-
const match = String(idl ?? "").match(
|
|
49
|
-
new RegExp(`\\btable\\s+${tableName}\\s*\\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
50
|
-
);
|
|
51
|
-
if (!match) {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
return match[1]
|
|
55
|
-
.split("\n")
|
|
56
|
-
.map((line) => line.replace(/\/\/.*$/, "").trim())
|
|
57
|
-
.map((line) => line.match(/^([A-Z0-9_]+)\s*:/)?.[1] ?? null)
|
|
58
|
-
.filter(Boolean);
|
|
59
|
-
}
|
|
60
30
|
|
|
61
31
|
function resolveStandardsManifestPath(options = {}) {
|
|
62
32
|
const standardsRoot =
|
|
@@ -69,81 +39,15 @@ function resolveStandardsManifestPath(options = {}) {
|
|
|
69
39
|
return path.join(path.dirname(packageEntry), "dist", "manifest.json");
|
|
70
40
|
}
|
|
71
41
|
|
|
72
|
-
function normalizeSchemaName(value) {
|
|
73
|
-
return value === undefined || value === null ? "" : String(value);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function normalizeFileIdentifier(value) {
|
|
77
|
-
return value === undefined || value === null ? "" : String(value);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function parseStandardsEntry([schemaCode, entry]) {
|
|
81
|
-
const idl = String(entry?.IDL ?? "");
|
|
82
|
-
const fileIdentifierMatch = idl.match(/file_identifier\s+"([^"]+)"/i);
|
|
83
|
-
const hashMatch = idl.match(/\/\/ Hash:\s*([a-f0-9]+)/i);
|
|
84
|
-
const versionMatch = idl.match(/\/\/ Version:\s*([^\n]+)/i);
|
|
85
|
-
const rootTypeMatch = idl.match(/root_type\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/i);
|
|
86
|
-
return {
|
|
87
|
-
schemaCode: schemaCode.toUpperCase(),
|
|
88
|
-
schemaName: `${schemaCode.toUpperCase()}.fbs`,
|
|
89
|
-
fileIdentifier: fileIdentifierMatch
|
|
90
|
-
? normalizeFileIdentifier(fileIdentifierMatch[1])
|
|
91
|
-
: null,
|
|
92
|
-
hash: hashMatch ? hashMatch[1].toLowerCase() : null,
|
|
93
|
-
rootTypeName: rootTypeMatch ? rootTypeMatch[1] : null,
|
|
94
|
-
idl,
|
|
95
|
-
version: versionMatch ? versionMatch[1].trim() : null,
|
|
96
|
-
files: Array.isArray(entry?.files) ? entry.files : [],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function validateStandardsCatalogFreshness(catalog, sourceName) {
|
|
101
|
-
const issues = [];
|
|
102
|
-
const scvEntry = catalog.find((entry) => entry.schemaCode === "SCV");
|
|
103
|
-
if (!scvEntry) {
|
|
104
|
-
return issues;
|
|
105
|
-
}
|
|
106
|
-
const missingTokens = requiredCurrentScvTokens.filter(
|
|
107
|
-
(token) => !scvEntry.idl.includes(token),
|
|
108
|
-
);
|
|
109
|
-
if (missingTokens.length > 0) {
|
|
110
|
-
issues.push({
|
|
111
|
-
severity: "error",
|
|
112
|
-
code: "stale-scv-contract",
|
|
113
|
-
message:
|
|
114
|
-
"The loaded spacedatastandards.org SCV catalog is stale and does not " +
|
|
115
|
-
`include required current coverage fields: ${missingTokens.join(", ")}.`,
|
|
116
|
-
location: `${sourceName}.standards.SCV`,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
const unsupportedResultFields = collectFlatbufferTableFields(
|
|
120
|
-
scvEntry.idl,
|
|
121
|
-
"SCVResult",
|
|
122
|
-
).filter((field) => !currentScvResultFields.has(field));
|
|
123
|
-
if (unsupportedResultFields.length > 0) {
|
|
124
|
-
issues.push({
|
|
125
|
-
severity: "error",
|
|
126
|
-
code: "stale-scv-contract",
|
|
127
|
-
message:
|
|
128
|
-
"The loaded spacedatastandards.org SCV catalog contains unsupported " +
|
|
129
|
-
`SCVResult fields that are not part of the current contract: ${unsupportedResultFields.join(", ")}.`,
|
|
130
|
-
location: `${sourceName}.standards.SCV`,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
return issues;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
42
|
export async function loadStandardsCatalog(options = {}) {
|
|
137
43
|
const manifestPath = resolveStandardsManifestPath(options);
|
|
138
44
|
if (!standardsCatalogPromises.has(manifestPath)) {
|
|
139
45
|
standardsCatalogPromises.set(
|
|
140
46
|
manifestPath,
|
|
141
|
-
(async () =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
.sort((left, right) => left.schemaCode.localeCompare(right.schemaCode));
|
|
146
|
-
})(),
|
|
47
|
+
(async () =>
|
|
48
|
+
buildStandardsCatalog(
|
|
49
|
+
JSON.parse(await readFile(manifestPath, "utf8")),
|
|
50
|
+
))(),
|
|
147
51
|
);
|
|
148
52
|
}
|
|
149
53
|
return standardsCatalogPromises.get(manifestPath);
|
|
@@ -154,144 +58,16 @@ export async function loadKnownTypeCatalog(options = {}) {
|
|
|
154
58
|
if (!knownTypeCatalogPromises.has(manifestPath)) {
|
|
155
59
|
knownTypeCatalogPromises.set(
|
|
156
60
|
manifestPath,
|
|
157
|
-
loadStandardsCatalog(options).then(
|
|
158
|
-
...catalog,
|
|
159
|
-
...sharedModuleCatalog,
|
|
160
|
-
]),
|
|
61
|
+
loadStandardsCatalog(options).then(withSharedModuleCatalog),
|
|
161
62
|
);
|
|
162
63
|
}
|
|
163
64
|
return knownTypeCatalogPromises.get(manifestPath);
|
|
164
65
|
}
|
|
165
66
|
|
|
166
|
-
export function resolveStandardsTypeRef(typeRef, catalog = []) {
|
|
167
|
-
const schemaName = normalizeSchemaName(typeRef?.schemaName);
|
|
168
|
-
const fileIdentifier = normalizeFileIdentifier(typeRef?.fileIdentifier);
|
|
169
|
-
if (!schemaName && !fileIdentifier) {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
return (
|
|
173
|
-
catalog.find(
|
|
174
|
-
(entry) =>
|
|
175
|
-
(!schemaName || normalizeSchemaName(entry.schemaName) === schemaName) &&
|
|
176
|
-
(!fileIdentifier ||
|
|
177
|
-
normalizeFileIdentifier(entry.fileIdentifier) === fileIdentifier),
|
|
178
|
-
) ?? null
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function schemaHashMatchesCatalog(typeRefHash, catalogHash) {
|
|
183
|
-
if (typeRefHash === undefined || typeRefHash === null) return true;
|
|
184
|
-
const declared = normalizePayloadSchemaHash(typeRefHash);
|
|
185
|
-
if (!declared) return true;
|
|
186
|
-
const canonical = normalizePayloadSchemaHash(catalogHash);
|
|
187
|
-
if (!canonical || declared.length !== canonical.length) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
return declared.every((byte, index) => byte === canonical[index]);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function collectTypeRefs(manifest) {
|
|
194
|
-
const refs = [];
|
|
195
|
-
for (const method of Array.isArray(manifest?.methods) ? manifest.methods : []) {
|
|
196
|
-
for (const portsKey of ["inputPorts", "outputPorts"]) {
|
|
197
|
-
for (const port of Array.isArray(method?.[portsKey]) ? method[portsKey] : []) {
|
|
198
|
-
for (const typeSet of Array.isArray(port?.acceptedTypeSets)
|
|
199
|
-
? port.acceptedTypeSets
|
|
200
|
-
: []) {
|
|
201
|
-
for (const allowedType of Array.isArray(typeSet?.allowedTypes)
|
|
202
|
-
? typeSet.allowedTypes
|
|
203
|
-
: []) {
|
|
204
|
-
refs.push({
|
|
205
|
-
source: `${method?.methodId ?? "method"}.${port?.portId ?? "port"}`,
|
|
206
|
-
typeRef: allowedType,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
for (const typeRef of Array.isArray(manifest?.schemasUsed)
|
|
214
|
-
? manifest.schemasUsed
|
|
215
|
-
: []) {
|
|
216
|
-
refs.push({
|
|
217
|
-
source: "schemasUsed",
|
|
218
|
-
typeRef,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
return refs;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
67
|
export async function validateManifestAgainstStandardsCatalog(
|
|
225
68
|
manifest,
|
|
226
69
|
options = {},
|
|
227
70
|
) {
|
|
228
71
|
const catalog = options.catalog ?? (await loadKnownTypeCatalog(options));
|
|
229
|
-
|
|
230
|
-
const issues = validateStandardsCatalogFreshness(catalog, sourceName);
|
|
231
|
-
for (const { source, typeRef } of collectTypeRefs(manifest)) {
|
|
232
|
-
if (typeRef?.acceptsAnyFlatbuffer === true) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
const resolved = resolveStandardsTypeRef(typeRef, catalog);
|
|
236
|
-
if (!resolved) {
|
|
237
|
-
const schemaName = normalizeSchemaName(typeRef?.schemaName);
|
|
238
|
-
const fileIdentifier = normalizeFileIdentifier(typeRef?.fileIdentifier);
|
|
239
|
-
const partiallyKnown = catalog.some(
|
|
240
|
-
(entry) =>
|
|
241
|
-
(schemaName && normalizeSchemaName(entry.schemaName) === schemaName) ||
|
|
242
|
-
(fileIdentifier &&
|
|
243
|
-
normalizeFileIdentifier(entry.fileIdentifier) === fileIdentifier),
|
|
244
|
-
);
|
|
245
|
-
issues.push({
|
|
246
|
-
severity: "error",
|
|
247
|
-
code: partiallyKnown
|
|
248
|
-
? "standards-type-identity-mismatch"
|
|
249
|
-
: "unresolved-standards-type",
|
|
250
|
-
message: partiallyKnown
|
|
251
|
-
? `Type reference from ${source} mixes a known schemaName or exact four-byte fileIdentifier with a different standards entry.`
|
|
252
|
-
: `Type reference from ${source} does not resolve to a known shared-module or \`spacedatastandards.org\` schema by its exact schemaName and fileIdentifier.`,
|
|
253
|
-
location: `${sourceName}.${source}`,
|
|
254
|
-
});
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
if (
|
|
258
|
-
typeRef?.rootTypeName &&
|
|
259
|
-
resolved.rootTypeName &&
|
|
260
|
-
typeRef.rootTypeName !== resolved.rootTypeName
|
|
261
|
-
) {
|
|
262
|
-
issues.push({
|
|
263
|
-
severity: "error",
|
|
264
|
-
code: "standards-root-type-mismatch",
|
|
265
|
-
message: `Type reference from ${source} declares rootTypeName ${JSON.stringify(typeRef.rootTypeName)} but the canonical SDS root is ${JSON.stringify(resolved.rootTypeName)}.`,
|
|
266
|
-
location: `${sourceName}.${source}`,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
if (
|
|
270
|
-
typeRef?.schemaVersion &&
|
|
271
|
-
resolved.version &&
|
|
272
|
-
typeRef.schemaVersion !== resolved.version
|
|
273
|
-
) {
|
|
274
|
-
issues.push({
|
|
275
|
-
severity: "error",
|
|
276
|
-
code: "standards-schema-version-mismatch",
|
|
277
|
-
message: `Type reference from ${source} declares schemaVersion ${JSON.stringify(typeRef.schemaVersion)} but the canonical SDS version is ${JSON.stringify(resolved.version)}.`,
|
|
278
|
-
location: `${sourceName}.${source}`,
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (
|
|
282
|
-
resolved.hash &&
|
|
283
|
-
!schemaHashMatchesCatalog(typeRef?.schemaHash, resolved.hash)
|
|
284
|
-
) {
|
|
285
|
-
issues.push({
|
|
286
|
-
severity: "error",
|
|
287
|
-
code: "standards-schema-hash-mismatch",
|
|
288
|
-
message: `Type reference from ${source} declares a schemaHash that differs from the canonical SDS schema hash.`,
|
|
289
|
-
location: `${sourceName}.${source}`,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return {
|
|
294
|
-
catalog,
|
|
295
|
-
issues,
|
|
296
|
-
};
|
|
72
|
+
return validateManifestAgainstCatalog(catalog, manifest, options);
|
|
297
73
|
}
|
package/src/testing/AGENTS.md
CHANGED
|
@@ -22,10 +22,33 @@ author-facing harnesses and streaming helpers.
|
|
|
22
22
|
- Avoid hiding stateful behavior inside one-off demos; if a harness contract is
|
|
23
23
|
real, test it here.
|
|
24
24
|
|
|
25
|
+
## Browser-Facing Code Does NOT Live Here (ruling 2026-08-07)
|
|
26
|
+
|
|
27
|
+
`src/testing/**` is HARNESS surface: it spawns WasmEdge, shells out, and opens
|
|
28
|
+
files. NOTHING browser-facing may resolve into it, because a browser bundler
|
|
29
|
+
resolves every branch statically and emits `node:` specifiers the browser then
|
|
30
|
+
tries to FETCH — that is how all 275 OrbPro gallery demos went dark
|
|
31
|
+
(`orbpro-engine-bundle-ships-node-builtins`).
|
|
32
|
+
|
|
33
|
+
The browser runtime surfaces moved OUT of here and into `src/host/`:
|
|
34
|
+
|
|
35
|
+
| was | is |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `testing/browserModuleHarness.js` | `host/browserModuleHarness.js` (`./host/browser-module`) |
|
|
38
|
+
| `testing/workerModuleHarness.js` | `host/workerModuleHarness.js` (`./host/worker-module`) |
|
|
39
|
+
| `testing/moduleFlatbufferStreamPump.js` | `host/moduleFlatbufferStreamPump.js` |
|
|
40
|
+
| `toLoadableWasmBytes` | `bundle/artifactBytes.js` (`./bundle`) |
|
|
41
|
+
|
|
42
|
+
`./testing/browser` survives as `browser.js`, a pure re-export shim of those
|
|
43
|
+
runtime surfaces and nothing else. Both guards
|
|
44
|
+
(`test/browser-reachable-node-builtins.test.js`,
|
|
45
|
+
`test/browser-bundle-node-builtins.test.js`) enforce this with no exception
|
|
46
|
+
list; the second one bundles the artifact and greps it.
|
|
47
|
+
|
|
25
48
|
## Key Files To Read
|
|
26
49
|
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
50
|
+
- `moduleHarness.js`
|
|
51
|
+
- `parityHarness.js`
|
|
29
52
|
- `processInvoke.js`
|
|
30
53
|
|
|
31
54
|
## Note
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `space-data-module-sdk/testing/browser` — browser-safe alias, kept honest.
|
|
3
|
+
*
|
|
4
|
+
* RULING 2026-08-07 (`orbpro-engine-bundle-ships-node-builtins`): `testing/*`
|
|
5
|
+
* is HARNESS surface. Production browser code must NOT reach for it to load a
|
|
6
|
+
* module. The two things callers actually wanted moved to runtime surfaces:
|
|
7
|
+
*
|
|
8
|
+
* toLoadableWasmBytes, ... -> space-data-module-sdk/bundle
|
|
9
|
+
* createBrowserModuleHarness,
|
|
10
|
+
* createWorkerModuleHarness,
|
|
11
|
+
* detectArtifactProfile,
|
|
12
|
+
* zeroWasmBytes -> space-data-module-sdk/host/browser-module
|
|
13
|
+
*
|
|
14
|
+
* This file exists so that subpath stays browser-SAFE for the tests and
|
|
15
|
+
* consumers still pointed at it: it re-exports the runtime surfaces and nothing
|
|
16
|
+
* else. It can never reach a Node harness again — the whole `src/testing/`
|
|
17
|
+
* neighbourhood is off the browser graph now, and
|
|
18
|
+
* test/browser-reachable-node-builtins.test.js plus
|
|
19
|
+
* test/browser-bundle-node-builtins.test.js enforce that at module level AND at
|
|
20
|
+
* bundled-artifact level, with no exception list.
|
|
21
|
+
*
|
|
22
|
+
* New browser code: import from the runtime subpaths above.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
createBrowserModuleHarness,
|
|
27
|
+
detectArtifactProfile,
|
|
28
|
+
toLoadableWasmBytes,
|
|
29
|
+
zeroWasmBytes,
|
|
30
|
+
isSharedArrayBufferLike,
|
|
31
|
+
} from "../host/browserModuleHarness.js";
|
|
32
|
+
export { createWorkerModuleHarness } from "../host/workerModuleHarness.js";
|
package/src/testing/index.js
CHANGED
|
@@ -10,8 +10,8 @@ export {
|
|
|
10
10
|
export {
|
|
11
11
|
createBrowserModuleHarness,
|
|
12
12
|
detectArtifactProfile,
|
|
13
|
-
} from "
|
|
14
|
-
export { createWorkerModuleHarness } from "
|
|
13
|
+
} from "../host/browserModuleHarness.js";
|
|
14
|
+
export { createWorkerModuleHarness } from "../host/workerModuleHarness.js";
|
|
15
15
|
export {
|
|
16
16
|
buildWasmEdgeSpawnEnv,
|
|
17
17
|
createPluginInvokeProcessClient,
|
|
@@ -27,7 +27,7 @@ export {
|
|
|
27
27
|
createModuleHarness,
|
|
28
28
|
resolveModuleHarnessLaunchPlan,
|
|
29
29
|
} from "./moduleHarness.js";
|
|
30
|
-
export { createModuleFlatBufferStreamPump } from "
|
|
30
|
+
export { createModuleFlatBufferStreamPump } from "../host/moduleFlatbufferStreamPump.js";
|
|
31
31
|
export {
|
|
32
32
|
PARITY_LANES,
|
|
33
33
|
ExitClass as ParityExitClass,
|