@aelionsdk/material-sdk 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/bundled-schemas.d.ts +5 -0
- package/dist/bundled-schemas.d.ts.map +1 -0
- package/dist/bundled-schemas.js +3 -0
- package/dist/canonical.d.ts +6 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +33 -0
- package/dist/catalog.d.ts +24 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +47 -0
- package/dist/composition.d.ts +42 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +169 -0
- package/dist/definition-builder.d.ts +48 -0
- package/dist/definition-builder.d.ts.map +1 -0
- package/dist/definition-builder.js +159 -0
- package/dist/graph-builder.d.ts +45 -0
- package/dist/graph-builder.d.ts.map +1 -0
- package/dist/graph-builder.js +125 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/lab.d.ts +48 -0
- package/dist/lab.d.ts.map +1 -0
- package/dist/lab.js +169 -0
- package/dist/migration.d.ts +22 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +59 -0
- package/dist/package-limits.d.ts +30 -0
- package/dist/package-limits.d.ts.map +1 -0
- package/dist/package-limits.js +202 -0
- package/dist/package-shape.d.ts +6 -0
- package/dist/package-shape.d.ts.map +1 -0
- package/dist/package-shape.js +547 -0
- package/dist/package-snapshot.d.ts +5 -0
- package/dist/package-snapshot.d.ts.map +1 -0
- package/dist/package-snapshot.js +31 -0
- package/dist/package.d.ts +12 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +244 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +150 -0
- package/dist/schema-validation.d.ts +4 -0
- package/dist/schema-validation.d.ts.map +1 -0
- package/dist/schema-validation.js +36 -0
- package/dist/security.d.ts +70 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +161 -0
- package/dist/types.d.ts +245 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/validation.d.ts +6 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +124 -0
- package/dist/zip.d.ts +4 -0
- package/dist/zip.d.ts.map +1 -0
- package/dist/zip.js +172 -0
- package/package.json +46 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { MATERIAL_DEFINITION_SCHEMA, MATERIAL_GRAPH_SCHEMA, MATERIAL_NODE_SET, MATERIAL_PACKAGE_SCHEMA, MATERIAL_PROTOCOL_VERSION, } from './types.js';
|
|
2
|
+
import { validMaterialPackagePath } from './package-limits.js';
|
|
3
|
+
import { assertMaterialDefinitionSchema, assertMaterialGraphSchema, assertMaterialManifestSchema, } from './schema-validation.js';
|
|
4
|
+
const MAX_MATERIALS = 256;
|
|
5
|
+
// The transport limit includes manifest.json itself.
|
|
6
|
+
const MAX_MANIFEST_FILES = 255;
|
|
7
|
+
const MAX_SCOPES = 5;
|
|
8
|
+
const MAX_PORTS = 16;
|
|
9
|
+
const MAX_PARAMETERS = 64;
|
|
10
|
+
const MAX_BUNDLED_RESOURCES = 32;
|
|
11
|
+
const MAX_RESOURCE_SLOTS = 16;
|
|
12
|
+
const MAX_IMPLEMENTATIONS = 8;
|
|
13
|
+
const MAX_ENUM_VALUES = 256;
|
|
14
|
+
const MAX_GRAPH_NODES = 128;
|
|
15
|
+
const MAX_GRAPH_INPUTS = 32;
|
|
16
|
+
const MAX_GRAPH_OUTPUTS = 8;
|
|
17
|
+
const MAX_OBJECT_KEYS = 4096;
|
|
18
|
+
const MAX_JSON_ARRAY_VALUES = 1024;
|
|
19
|
+
const MAX_JSON_OBJECT_KEYS = 128;
|
|
20
|
+
const MAX_JSON_DEPTH = 64;
|
|
21
|
+
const MAX_JSON_NODES = 262_144;
|
|
22
|
+
const MAX_STRING_BYTES = 16 * 1024;
|
|
23
|
+
const encoder = new TextEncoder();
|
|
24
|
+
function invalid(path, message) {
|
|
25
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: ${path} ${message}`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Performs bounded admission before Ajv sees an untrusted object. Ajv must not
|
|
29
|
+
* be the first code to discover a huge or sparse array: even a tiny sparse
|
|
30
|
+
* in-memory value can otherwise force it to walk and retain millions of
|
|
31
|
+
* errors without consuming any package byte budget.
|
|
32
|
+
*/
|
|
33
|
+
function admissionRecord(value, path) {
|
|
34
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
35
|
+
invalid(path, 'must be an object');
|
|
36
|
+
}
|
|
37
|
+
let prototype;
|
|
38
|
+
try {
|
|
39
|
+
prototype = Object.getPrototypeOf(value);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
invalid(path, 'has an inaccessible prototype');
|
|
43
|
+
}
|
|
44
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
45
|
+
invalid(path, 'must be a plain JSON object');
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function dataProperty(record_, key, path) {
|
|
50
|
+
let descriptor;
|
|
51
|
+
try {
|
|
52
|
+
descriptor = Object.getOwnPropertyDescriptor(record_, key);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
invalid(path, 'is inaccessible');
|
|
56
|
+
}
|
|
57
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
58
|
+
invalid(path, 'must be an own data property');
|
|
59
|
+
}
|
|
60
|
+
return descriptor.value;
|
|
61
|
+
}
|
|
62
|
+
function optionalDataProperty(record_, key, path) {
|
|
63
|
+
let descriptor;
|
|
64
|
+
try {
|
|
65
|
+
descriptor = Object.getOwnPropertyDescriptor(record_, key);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
invalid(path, 'is inaccessible');
|
|
69
|
+
}
|
|
70
|
+
if (descriptor === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
if (!('value' in descriptor))
|
|
73
|
+
invalid(path, 'must be an own data property');
|
|
74
|
+
return descriptor.value;
|
|
75
|
+
}
|
|
76
|
+
function admittedArray(value, path, maximum) {
|
|
77
|
+
if (!Array.isArray(value))
|
|
78
|
+
invalid(path, 'must be an array');
|
|
79
|
+
const length = value.length;
|
|
80
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > maximum) {
|
|
81
|
+
invalid(path, `has more than ${maximum} entries`);
|
|
82
|
+
}
|
|
83
|
+
// At most `maximum` descriptor reads: this rejects sparse/accessor arrays
|
|
84
|
+
// without invoking caller-controlled indexed getters.
|
|
85
|
+
for (let index = 0; index < length; index += 1) {
|
|
86
|
+
let descriptor;
|
|
87
|
+
try {
|
|
88
|
+
descriptor = Object.getOwnPropertyDescriptor(value, index.toString());
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
invalid(`${path}[${index}]`, 'is inaccessible');
|
|
92
|
+
}
|
|
93
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
94
|
+
invalid(`${path}[${index}]`, 'must be a dense data entry');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
function admittedObjectKeys(value, path, maximum) {
|
|
100
|
+
const object = admissionRecord(value, path);
|
|
101
|
+
let keys;
|
|
102
|
+
try {
|
|
103
|
+
keys = Object.keys(object);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
invalid(path, 'has inaccessible properties');
|
|
107
|
+
}
|
|
108
|
+
if (keys.length > maximum)
|
|
109
|
+
invalid(path, `has more than ${maximum} properties`);
|
|
110
|
+
return keys;
|
|
111
|
+
}
|
|
112
|
+
function admitJsonValue(value, path, budget, depth = 0) {
|
|
113
|
+
budget.remaining -= 1;
|
|
114
|
+
if (budget.remaining < 0)
|
|
115
|
+
invalid(path, `exceeds ${MAX_JSON_NODES} JSON values`);
|
|
116
|
+
if (depth > MAX_JSON_DEPTH)
|
|
117
|
+
invalid(path, 'exceeds JSON nesting limit');
|
|
118
|
+
if (value === null ||
|
|
119
|
+
typeof value === 'boolean' ||
|
|
120
|
+
(typeof value === 'number' && Number.isFinite(value))) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (typeof value === 'string') {
|
|
124
|
+
// UTF-8 bytes can never be fewer than this many UTF-16 code units.
|
|
125
|
+
if (value.length > MAX_STRING_BYTES)
|
|
126
|
+
invalid(path, 'string is too long');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(value)) {
|
|
130
|
+
const values = admittedArray(value, path, MAX_JSON_ARRAY_VALUES);
|
|
131
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
132
|
+
admitJsonValue(values[index], `${path}[${index}]`, budget, depth + 1);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const object = admissionRecord(value, path);
|
|
137
|
+
const keys = admittedObjectKeys(object, path, MAX_JSON_OBJECT_KEYS);
|
|
138
|
+
for (const key of keys) {
|
|
139
|
+
admitJsonValue(dataProperty(object, key, `${path}.${key}`), `${path}.${key}`, budget, depth + 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function admitManifest(value) {
|
|
143
|
+
const manifest = admissionRecord(value, 'manifest');
|
|
144
|
+
admittedArray(dataProperty(manifest, 'materials', 'manifest.materials'), 'manifest.materials', MAX_MATERIALS);
|
|
145
|
+
admittedArray(dataProperty(manifest, 'files', 'manifest.files'), 'manifest.files', MAX_MANIFEST_FILES);
|
|
146
|
+
}
|
|
147
|
+
function admitDefinition(value) {
|
|
148
|
+
const definition = admissionRecord(value, 'definition');
|
|
149
|
+
admittedArray(dataProperty(definition, 'scopes', 'definition.scopes'), 'definition.scopes', MAX_SCOPES);
|
|
150
|
+
admittedArray(dataProperty(definition, 'ports', 'definition.ports'), 'definition.ports', MAX_PORTS);
|
|
151
|
+
const parameters = admittedArray(dataProperty(definition, 'parameters', 'definition.parameters'), 'definition.parameters', MAX_PARAMETERS);
|
|
152
|
+
admittedArray(dataProperty(definition, 'bundledResources', 'definition.bundledResources'), 'definition.bundledResources', MAX_BUNDLED_RESOURCES);
|
|
153
|
+
admittedArray(dataProperty(definition, 'resourceSlots', 'definition.resourceSlots'), 'definition.resourceSlots', MAX_RESOURCE_SLOTS);
|
|
154
|
+
admittedArray(dataProperty(definition, 'implementations', 'definition.implementations'), 'definition.implementations', MAX_IMPLEMENTATIONS);
|
|
155
|
+
const display = admissionRecord(dataProperty(definition, 'display', 'definition.display'), 'definition.display');
|
|
156
|
+
const tags = optionalDataProperty(display, 'tags', 'definition.display.tags');
|
|
157
|
+
if (tags !== undefined)
|
|
158
|
+
admittedArray(tags, 'definition.display.tags', 32);
|
|
159
|
+
const budget = { remaining: MAX_JSON_NODES };
|
|
160
|
+
for (let index = 0; index < parameters.length; index += 1) {
|
|
161
|
+
const parameter = admissionRecord(parameters[index], `definition.parameters[${index}]`);
|
|
162
|
+
admitJsonValue(dataProperty(parameter, 'default', `definition.parameters[${index}].default`), `definition.parameters[${index}].default`, budget);
|
|
163
|
+
const values = optionalDataProperty(parameter, 'values', `definition.parameters[${index}].values`);
|
|
164
|
+
if (values !== undefined) {
|
|
165
|
+
admittedArray(values, `definition.parameters[${index}].values`, MAX_ENUM_VALUES);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function admitGraph(value) {
|
|
170
|
+
const graph = admissionRecord(value, 'graph');
|
|
171
|
+
const nodes = admittedArray(dataProperty(graph, 'nodes', 'graph.nodes'), 'graph.nodes', MAX_GRAPH_NODES);
|
|
172
|
+
admittedObjectKeys(dataProperty(graph, 'outputs', 'graph.outputs'), 'graph.outputs', MAX_GRAPH_OUTPUTS);
|
|
173
|
+
const budget = { remaining: MAX_JSON_NODES };
|
|
174
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
175
|
+
const node = admissionRecord(nodes[index], `graph.nodes[${index}]`);
|
|
176
|
+
const inputs = admissionRecord(dataProperty(node, 'inputs', `graph.nodes[${index}].inputs`), `graph.nodes[${index}].inputs`);
|
|
177
|
+
const inputNames = admittedObjectKeys(inputs, `graph.nodes[${index}].inputs`, MAX_GRAPH_INPUTS);
|
|
178
|
+
for (const name of inputNames) {
|
|
179
|
+
const binding = admissionRecord(dataProperty(inputs, name, `graph.nodes[${index}].inputs.${name}`), `graph.nodes[${index}].inputs.${name}`);
|
|
180
|
+
const literalValue = optionalDataProperty(binding, 'value', `graph.nodes[${index}].inputs.${name}.value`);
|
|
181
|
+
if (literalValue !== undefined) {
|
|
182
|
+
admitJsonValue(literalValue, `graph.nodes[${index}].inputs.${name}.value`, budget);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const config = optionalDataProperty(node, 'config', `graph.nodes[${index}].config`);
|
|
186
|
+
if (config !== undefined) {
|
|
187
|
+
const configRecord = admissionRecord(config, `graph.nodes[${index}].config`);
|
|
188
|
+
const configKeys = admittedObjectKeys(configRecord, `graph.nodes[${index}].config`, MAX_GRAPH_INPUTS);
|
|
189
|
+
for (const key of configKeys) {
|
|
190
|
+
admitJsonValue(dataProperty(configRecord, key, `graph.nodes[${index}].config.${key}`), `graph.nodes[${index}].config.${key}`, budget);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function record(value, path) {
|
|
196
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
197
|
+
invalid(path, 'must be an object');
|
|
198
|
+
}
|
|
199
|
+
let prototype;
|
|
200
|
+
try {
|
|
201
|
+
prototype = Object.getPrototypeOf(value);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
invalid(path, 'has an inaccessible prototype');
|
|
205
|
+
}
|
|
206
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
207
|
+
invalid(path, 'must be a plain JSON object');
|
|
208
|
+
}
|
|
209
|
+
const output = value;
|
|
210
|
+
let keys;
|
|
211
|
+
try {
|
|
212
|
+
keys = Object.keys(output);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
invalid(path, 'has inaccessible properties');
|
|
216
|
+
}
|
|
217
|
+
if (keys.length > MAX_OBJECT_KEYS)
|
|
218
|
+
invalid(path, 'has too many properties');
|
|
219
|
+
return output;
|
|
220
|
+
}
|
|
221
|
+
function array(value, path, maximum) {
|
|
222
|
+
if (!Array.isArray(value))
|
|
223
|
+
invalid(path, 'must be an array');
|
|
224
|
+
let length;
|
|
225
|
+
try {
|
|
226
|
+
length = value.length;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
invalid(path, 'has an inaccessible length');
|
|
230
|
+
}
|
|
231
|
+
if (!Number.isSafeInteger(length) || length > maximum) {
|
|
232
|
+
invalid(path, `has more than ${maximum} entries`);
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
function string(value, path, maximumBytes = MAX_STRING_BYTES) {
|
|
237
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
238
|
+
invalid(path, 'must be a non-empty string');
|
|
239
|
+
if (encoder.encode(value).byteLength > maximumBytes) {
|
|
240
|
+
invalid(path, `exceeds ${maximumBytes} UTF-8 bytes`);
|
|
241
|
+
}
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
function optionalString(value, path) {
|
|
245
|
+
if (value !== undefined)
|
|
246
|
+
string(value, path);
|
|
247
|
+
}
|
|
248
|
+
function member(value, allowed, path) {
|
|
249
|
+
if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
250
|
+
invalid(path, `must be one of ${allowed.join(', ')}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function boolean(value, path) {
|
|
254
|
+
if (typeof value !== 'boolean')
|
|
255
|
+
invalid(path, 'must be boolean');
|
|
256
|
+
}
|
|
257
|
+
function finiteNumber(value, path) {
|
|
258
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
259
|
+
invalid(path, 'must be finite');
|
|
260
|
+
}
|
|
261
|
+
function safeInteger(value, path) {
|
|
262
|
+
if (!Number.isSafeInteger(value) || Number(value) < 0) {
|
|
263
|
+
invalid(path, 'must be a non-negative safe integer');
|
|
264
|
+
}
|
|
265
|
+
return Number(value);
|
|
266
|
+
}
|
|
267
|
+
function literal(value, expected, path) {
|
|
268
|
+
if (value !== expected)
|
|
269
|
+
invalid(path, `must equal ${String(expected)}`);
|
|
270
|
+
}
|
|
271
|
+
function stringArray(value, path, maximum) {
|
|
272
|
+
for (const [index, item] of array(value, path, maximum).entries()) {
|
|
273
|
+
string(item, `${path}[${index}]`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function idCollection(value, path, maximum) {
|
|
277
|
+
const result = array(value, path, maximum).map((item, index) => {
|
|
278
|
+
const entry = record(item, `${path}[${index}]`);
|
|
279
|
+
string(entry.id, `${path}[${index}].id`);
|
|
280
|
+
return entry;
|
|
281
|
+
});
|
|
282
|
+
const ids = result.map(entry => entry.id);
|
|
283
|
+
if (new Set(ids).size !== ids.length)
|
|
284
|
+
invalid(path, 'contains duplicate ids');
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
function validateJsonValue(value, path, depth = 0, budget = { remaining: MAX_JSON_NODES }) {
|
|
288
|
+
budget.remaining -= 1;
|
|
289
|
+
if (budget.remaining < 0)
|
|
290
|
+
invalid(path, `exceeds ${MAX_JSON_NODES} JSON values`);
|
|
291
|
+
if (depth > MAX_JSON_DEPTH)
|
|
292
|
+
invalid(path, 'exceeds JSON nesting limit');
|
|
293
|
+
if (value === null ||
|
|
294
|
+
typeof value === 'boolean' ||
|
|
295
|
+
(typeof value === 'number' && Number.isFinite(value))) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (typeof value === 'string') {
|
|
299
|
+
if (encoder.encode(value).byteLength > MAX_STRING_BYTES) {
|
|
300
|
+
invalid(path, `string exceeds ${MAX_STRING_BYTES} UTF-8 bytes`);
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(value)) {
|
|
305
|
+
if (value.length > MAX_JSON_ARRAY_VALUES)
|
|
306
|
+
invalid(path, 'array has too many values');
|
|
307
|
+
value.forEach((item, index) => validateJsonValue(item, `${path}[${index}]`, depth + 1, budget));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const object = record(value, path);
|
|
311
|
+
if (Object.keys(object).length > MAX_JSON_OBJECT_KEYS)
|
|
312
|
+
invalid(path, 'has too many properties');
|
|
313
|
+
for (const [key, item] of Object.entries(object)) {
|
|
314
|
+
string(key, `${path} key`);
|
|
315
|
+
validateJsonValue(item, `${path}.${key}`, depth + 1, budget);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
export function assertMaterialPackageManifestShape(value) {
|
|
319
|
+
admitManifest(value);
|
|
320
|
+
assertMaterialManifestSchema(value);
|
|
321
|
+
const manifest = record(value, 'manifest');
|
|
322
|
+
literal(manifest.$schema, MATERIAL_PACKAGE_SCHEMA, 'manifest.$schema');
|
|
323
|
+
literal(manifest.protocolVersion, MATERIAL_PROTOCOL_VERSION, 'manifest.protocolVersion');
|
|
324
|
+
const metadata = record(manifest.package, 'manifest.package');
|
|
325
|
+
string(metadata.id, 'manifest.package.id');
|
|
326
|
+
string(metadata.version, 'manifest.package.version');
|
|
327
|
+
string(metadata.displayName, 'manifest.package.displayName');
|
|
328
|
+
string(metadata.license, 'manifest.package.license');
|
|
329
|
+
const publisher = record(metadata.publisher, 'manifest.package.publisher');
|
|
330
|
+
string(publisher.id, 'manifest.package.publisher.id');
|
|
331
|
+
string(publisher.name, 'manifest.package.publisher.name');
|
|
332
|
+
const engines = record(metadata.engines, 'manifest.package.engines');
|
|
333
|
+
string(engines.aelion, 'manifest.package.engines.aelion');
|
|
334
|
+
string(engines.nodeSet, 'manifest.package.engines.nodeSet');
|
|
335
|
+
if (metadata.trust !== 'declarative' && metadata.trust !== 'trusted-code') {
|
|
336
|
+
invalid('manifest.package.trust', 'must be declarative or trusted-code');
|
|
337
|
+
}
|
|
338
|
+
for (const [index, item] of array(manifest.materials, 'manifest.materials', MAX_MATERIALS).entries()) {
|
|
339
|
+
const material = record(item, `manifest.materials[${index}]`);
|
|
340
|
+
string(material.id, `manifest.materials[${index}].id`);
|
|
341
|
+
member(material.kind, ['visual-filter', 'visual-effect', 'visual-transition', 'visual-generator'], `manifest.materials[${index}].kind`);
|
|
342
|
+
string(material.definition, `manifest.materials[${index}].definition`, 512);
|
|
343
|
+
if (typeof material.definition !== 'string' || !validMaterialPackagePath(material.definition)) {
|
|
344
|
+
invalid(`manifest.materials[${index}].definition`, 'must be a safe relative package path');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const materialIds = manifest.materials.map(material => material.id);
|
|
348
|
+
if (new Set(materialIds).size !== materialIds.length) {
|
|
349
|
+
invalid('manifest.materials', 'contains duplicate ids');
|
|
350
|
+
}
|
|
351
|
+
const materialDefinitions = manifest.materials.map(material => material.definition);
|
|
352
|
+
if (new Set(materialDefinitions).size !== materialDefinitions.length) {
|
|
353
|
+
invalid('manifest.materials', 'contains duplicate definition paths');
|
|
354
|
+
}
|
|
355
|
+
for (const [index, item] of array(manifest.files, 'manifest.files', MAX_MANIFEST_FILES).entries()) {
|
|
356
|
+
const file = record(item, `manifest.files[${index}]`);
|
|
357
|
+
string(file.path, `manifest.files[${index}].path`, 512);
|
|
358
|
+
if (typeof file.path !== 'string' || !validMaterialPackagePath(file.path)) {
|
|
359
|
+
invalid(`manifest.files[${index}].path`, 'must be a safe relative package path');
|
|
360
|
+
}
|
|
361
|
+
string(file.mediaType, `manifest.files[${index}].mediaType`);
|
|
362
|
+
safeInteger(file.bytes, `manifest.files[${index}].bytes`);
|
|
363
|
+
if (typeof file.sha256 !== 'string' || !/^[0-9a-f]{64}$/u.test(file.sha256)) {
|
|
364
|
+
invalid(`manifest.files[${index}].sha256`, 'must be a lowercase SHA-256 hex digest');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const filePaths = manifest.files.map(file => file.path);
|
|
368
|
+
if (new Set(filePaths).size !== filePaths.length) {
|
|
369
|
+
invalid('manifest.files', 'contains duplicate paths');
|
|
370
|
+
}
|
|
371
|
+
validateJsonValue(manifest, 'manifest');
|
|
372
|
+
}
|
|
373
|
+
export function assertMaterialDefinitionShape(value) {
|
|
374
|
+
admitDefinition(value);
|
|
375
|
+
assertMaterialDefinitionSchema(value);
|
|
376
|
+
const definition = record(value, 'definition');
|
|
377
|
+
literal(definition.$schema, MATERIAL_DEFINITION_SCHEMA, 'definition.$schema');
|
|
378
|
+
literal(definition.protocolVersion, MATERIAL_PROTOCOL_VERSION, 'definition.protocolVersion');
|
|
379
|
+
string(definition.id, 'definition.id');
|
|
380
|
+
member(definition.kind, ['visual-filter', 'visual-effect', 'visual-transition', 'visual-generator'], 'definition.kind');
|
|
381
|
+
const display = record(definition.display, 'definition.display');
|
|
382
|
+
string(display.name, 'definition.display.name');
|
|
383
|
+
optionalString(display.description, 'definition.display.description');
|
|
384
|
+
optionalString(display.category, 'definition.display.category');
|
|
385
|
+
if (display.tags !== undefined)
|
|
386
|
+
stringArray(display.tags, 'definition.display.tags', 32);
|
|
387
|
+
const scopes = array(definition.scopes, 'definition.scopes', MAX_SCOPES);
|
|
388
|
+
scopes.forEach((scope, index) => {
|
|
389
|
+
member(scope, ['source', 'item', 'track', 'sequence', 'transition'], `definition.scopes[${index}]`);
|
|
390
|
+
});
|
|
391
|
+
const ports = idCollection(definition.ports, 'definition.ports', MAX_PORTS);
|
|
392
|
+
ports.forEach((port, index) => {
|
|
393
|
+
member(port.direction, ['input', 'output'], `definition.ports[${index}].direction`);
|
|
394
|
+
member(port.type, ['visual-frame', 'mask', 'depth', 'motion-vectors'], `definition.ports[${index}].type`);
|
|
395
|
+
member(port.role, ['source', 'from', 'to', 'auxiliary', 'result'], `definition.ports[${index}].role`);
|
|
396
|
+
member(port.binding, ['host', 'instance'], `definition.ports[${index}].binding`);
|
|
397
|
+
boolean(port.required, `definition.ports[${index}].required`);
|
|
398
|
+
});
|
|
399
|
+
const parameters = idCollection(definition.parameters, 'definition.parameters', MAX_PARAMETERS);
|
|
400
|
+
parameters.forEach((parameter, index) => {
|
|
401
|
+
member(parameter.type, [
|
|
402
|
+
'boolean',
|
|
403
|
+
'integer',
|
|
404
|
+
'float',
|
|
405
|
+
'enum',
|
|
406
|
+
'vec2',
|
|
407
|
+
'vec3',
|
|
408
|
+
'vec4',
|
|
409
|
+
'color',
|
|
410
|
+
'angle',
|
|
411
|
+
'duration',
|
|
412
|
+
'gradient',
|
|
413
|
+
'curve',
|
|
414
|
+
'string',
|
|
415
|
+
], `definition.parameters[${index}].type`);
|
|
416
|
+
validateJsonValue(parameter.default, `definition.parameters[${index}].default`);
|
|
417
|
+
boolean(parameter.animatable, `definition.parameters[${index}].animatable`);
|
|
418
|
+
member(parameter.affects, ['uniform', 'specialization', 'graph'], `definition.parameters[${index}].affects`);
|
|
419
|
+
const ui = record(parameter.ui, `definition.parameters[${index}].ui`);
|
|
420
|
+
string(ui.control, `definition.parameters[${index}].ui.control`);
|
|
421
|
+
string(ui.group, `definition.parameters[${index}].ui.group`);
|
|
422
|
+
safeInteger(ui.order, `definition.parameters[${index}].ui.order`);
|
|
423
|
+
string(ui.label, `definition.parameters[${index}].ui.label`);
|
|
424
|
+
});
|
|
425
|
+
const resources = idCollection(definition.bundledResources, 'definition.bundledResources', MAX_BUNDLED_RESOURCES);
|
|
426
|
+
resources.forEach((resource, index) => {
|
|
427
|
+
member(resource.kind, ['texture2d', 'texture3d', 'lut1d', 'lut3d', 'cube-texture', 'mask', 'binary-table'], `definition.bundledResources[${index}].kind`);
|
|
428
|
+
string(resource.path, `definition.bundledResources[${index}].path`, 512);
|
|
429
|
+
if (typeof resource.path !== 'string' || !validMaterialPackagePath(resource.path)) {
|
|
430
|
+
invalid(`definition.bundledResources[${index}].path`, 'must be a safe relative package path');
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
const resourceSlots = idCollection(definition.resourceSlots, 'definition.resourceSlots', MAX_RESOURCE_SLOTS);
|
|
434
|
+
resourceSlots.forEach((resource, index) => {
|
|
435
|
+
member(resource.kind, ['texture2d', 'texture3d', 'lut1d', 'lut3d', 'cube-texture', 'mask', 'binary-table'], `definition.resourceSlots[${index}].kind`);
|
|
436
|
+
boolean(resource.required, `definition.resourceSlots[${index}].required`);
|
|
437
|
+
optionalString(resource.fallbackResourceId, `definition.resourceSlots[${index}].fallbackResourceId`);
|
|
438
|
+
});
|
|
439
|
+
const execution = record(definition.execution, 'definition.execution');
|
|
440
|
+
const color = record(execution.color, 'definition.execution.color');
|
|
441
|
+
literal(color.input, 'working-linear', 'definition.execution.color.input');
|
|
442
|
+
literal(color.output, 'working-linear', 'definition.execution.color.output');
|
|
443
|
+
const alpha = record(execution.alpha, 'definition.execution.alpha');
|
|
444
|
+
literal(alpha.input, 'premultiplied', 'definition.execution.alpha.input');
|
|
445
|
+
literal(alpha.output, 'premultiplied', 'definition.execution.alpha.output');
|
|
446
|
+
boolean(alpha.preservesTransparency, 'definition.execution.alpha.preservesTransparency');
|
|
447
|
+
const resolution = record(execution.resolution, 'definition.execution.resolution');
|
|
448
|
+
member(resolution.policy, ['same-as-host', 'scale'], 'definition.execution.resolution.policy');
|
|
449
|
+
if (resolution.scale !== undefined)
|
|
450
|
+
finiteNumber(resolution.scale, 'definition.execution.resolution.scale');
|
|
451
|
+
const spatialPadding = record(execution.spatialPadding, 'definition.execution.spatialPadding');
|
|
452
|
+
member(spatialPadding.mode, ['none', 'fixed', 'parameter-bound'], 'definition.execution.spatialPadding.mode');
|
|
453
|
+
const temporal = record(execution.temporal, 'definition.execution.temporal');
|
|
454
|
+
safeInteger(temporal.pastUs, 'definition.execution.temporal.pastUs');
|
|
455
|
+
safeInteger(temporal.futureUs, 'definition.execution.temporal.futureUs');
|
|
456
|
+
boolean(temporal.stateful, 'definition.execution.temporal.stateful');
|
|
457
|
+
member(temporal.seekPolicy, ['stateless', 'reconstruct', 'reset-with-warning'], 'definition.execution.temporal.seekPolicy');
|
|
458
|
+
member(execution.determinism, ['strict', 'backend-tolerant', 'non-deterministic'], 'definition.execution.determinism');
|
|
459
|
+
const supports = record(execution.supports, 'definition.execution.supports');
|
|
460
|
+
for (const property of ['realtime', 'offline', 'alpha', 'hdr', 'tiled']) {
|
|
461
|
+
boolean(supports[property], `definition.execution.supports.${property}`);
|
|
462
|
+
}
|
|
463
|
+
for (const [index, item] of array(definition.implementations, 'definition.implementations', MAX_IMPLEMENTATIONS).entries()) {
|
|
464
|
+
const implementation = record(item, `definition.implementations[${index}]`);
|
|
465
|
+
string(implementation.type, `definition.implementations[${index}].type`);
|
|
466
|
+
if (implementation.type === 'graph') {
|
|
467
|
+
string(implementation.graph, `definition.implementations[${index}].graph`, 512);
|
|
468
|
+
string(implementation.nodeSet, `definition.implementations[${index}].nodeSet`);
|
|
469
|
+
if (!validMaterialPackagePath(String(implementation.graph))) {
|
|
470
|
+
invalid(`definition.implementations[${index}].graph`, 'must be a safe relative path');
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
else if (implementation.type === 'wasm') {
|
|
474
|
+
string(implementation.module, `definition.implementations[${index}].module`, 512);
|
|
475
|
+
if (!validMaterialPackagePath(String(implementation.module))) {
|
|
476
|
+
invalid(`definition.implementations[${index}].module`, 'must be a safe relative path');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else if (implementation.type === 'shader') {
|
|
480
|
+
string(implementation.backend, `definition.implementations[${index}].backend`);
|
|
481
|
+
if (implementation.backend === 'webgpu') {
|
|
482
|
+
string(implementation.module, `definition.implementations[${index}].module`, 512);
|
|
483
|
+
if (!validMaterialPackagePath(String(implementation.module))) {
|
|
484
|
+
invalid(`definition.implementations[${index}].module`, 'must be a safe relative path');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else if (implementation.backend === 'webgl2') {
|
|
488
|
+
string(implementation.fragmentModule, `definition.implementations[${index}].fragmentModule`, 512);
|
|
489
|
+
optionalString(implementation.vertexModule, `definition.implementations[${index}].vertexModule`);
|
|
490
|
+
if (!validMaterialPackagePath(String(implementation.fragmentModule))) {
|
|
491
|
+
invalid(`definition.implementations[${index}].fragmentModule`, 'must be a safe relative path');
|
|
492
|
+
}
|
|
493
|
+
if (typeof implementation.vertexModule === 'string' &&
|
|
494
|
+
!validMaterialPackagePath(implementation.vertexModule)) {
|
|
495
|
+
invalid(`definition.implementations[${index}].vertexModule`, 'must be a safe relative path');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else
|
|
499
|
+
invalid(`definition.implementations[${index}].backend`, 'is unsupported');
|
|
500
|
+
}
|
|
501
|
+
else
|
|
502
|
+
invalid(`definition.implementations[${index}].type`, 'is unsupported');
|
|
503
|
+
}
|
|
504
|
+
member(definition.splitPolicy, ['copy', 'reset', 'reject'], 'definition.splitPolicy');
|
|
505
|
+
validateJsonValue(definition, 'definition');
|
|
506
|
+
}
|
|
507
|
+
export function assertMaterialGraphShape(value) {
|
|
508
|
+
admitGraph(value);
|
|
509
|
+
assertMaterialGraphSchema(value);
|
|
510
|
+
const graph = record(value, 'graph');
|
|
511
|
+
literal(graph.$schema, MATERIAL_GRAPH_SCHEMA, 'graph.$schema');
|
|
512
|
+
string(graph.graphVersion, 'graph.graphVersion');
|
|
513
|
+
literal(graph.nodeSet, MATERIAL_NODE_SET, 'graph.nodeSet');
|
|
514
|
+
for (const [index, item] of array(graph.nodes, 'graph.nodes', MAX_GRAPH_NODES).entries()) {
|
|
515
|
+
const node = record(item, `graph.nodes[${index}]`);
|
|
516
|
+
string(node.id, `graph.nodes[${index}].id`);
|
|
517
|
+
string(node.type, `graph.nodes[${index}].type`);
|
|
518
|
+
string(node.typeVersion, `graph.nodes[${index}].typeVersion`);
|
|
519
|
+
const inputs = record(node.inputs, `graph.nodes[${index}].inputs`);
|
|
520
|
+
for (const [name, bindingValue] of Object.entries(inputs)) {
|
|
521
|
+
string(name, `graph.nodes[${index}].inputs key`);
|
|
522
|
+
const binding = record(bindingValue, `graph.nodes[${index}].inputs.${name}`);
|
|
523
|
+
const variants = ['value', 'parameter', 'inputPort', 'system', 'node', 'resource'].filter(key => binding[key] !== undefined);
|
|
524
|
+
if (variants.length !== 1) {
|
|
525
|
+
invalid(`graph.nodes[${index}].inputs.${name}`, 'must have one binding kind');
|
|
526
|
+
}
|
|
527
|
+
const variant = variants[0];
|
|
528
|
+
if (variant === 'value') {
|
|
529
|
+
validateJsonValue(binding.value, `graph.nodes[${index}].inputs.${name}.value`);
|
|
530
|
+
}
|
|
531
|
+
else if (variant !== undefined) {
|
|
532
|
+
string(binding[variant], `graph.nodes[${index}].inputs.${name}.${variant}`);
|
|
533
|
+
}
|
|
534
|
+
if (variant === 'node') {
|
|
535
|
+
string(binding.output, `graph.nodes[${index}].inputs.${name}.output`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const outputs = record(graph.outputs, 'graph.outputs');
|
|
540
|
+
for (const [name, outputValue] of Object.entries(outputs)) {
|
|
541
|
+
string(name, 'graph.outputs key');
|
|
542
|
+
const output = record(outputValue, `graph.outputs.${name}`);
|
|
543
|
+
string(output.node, `graph.outputs.${name}.node`);
|
|
544
|
+
string(output.output, `graph.outputs.${name}.output`);
|
|
545
|
+
}
|
|
546
|
+
validateJsonValue(graph, 'graph');
|
|
547
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type MaterialPackageTransportInspection } from './package-limits.js';
|
|
2
|
+
import type { MaterialPackageByteLimitOptions, PackedMaterialPackage } from './types.js';
|
|
3
|
+
/** Creates an ownership-isolated package value for trust decisions and registry storage. */
|
|
4
|
+
export declare function snapshotMaterialPackage(packed: PackedMaterialPackage, options?: MaterialPackageByteLimitOptions, inspection?: MaterialPackageTransportInspection): PackedMaterialPackage;
|
|
5
|
+
//# sourceMappingURL=package-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-snapshot.d.ts","sourceRoot":"","sources":["../src/package-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kCAAkC,EACxC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,+BAA+B,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIzF,4FAA4F;AAC5F,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,+BAAoC,EAC7C,UAAU,GAAE,kCAAkF,GAC7F,qBAAqB,CAmCvB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { copyMaterialBytes, inspectPackedMaterialPackage, } from './package-limits.js';
|
|
2
|
+
import { assertMaterialPackageManifestShape } from './package-shape.js';
|
|
3
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
4
|
+
/** Creates an ownership-isolated package value for trust decisions and registry storage. */
|
|
5
|
+
export function snapshotMaterialPackage(packed, options = {}, inspection = inspectPackedMaterialPackage(packed, options)) {
|
|
6
|
+
// Budget inspection is deliberately complete before parsing or the
|
|
7
|
+
// first transport byte copy. This prevents an untrusted package from using
|
|
8
|
+
// defensive ownership snapshots as a memory amplification primitive.
|
|
9
|
+
// `manifestBytes` is the signed transport authority. Never enumerate the
|
|
10
|
+
// caller's convenience `manifest` object: a Proxy ownKeys trap can allocate
|
|
11
|
+
// unbounded key lists before any object-key budget is observable.
|
|
12
|
+
const manifestBytes = copyMaterialBytes(inspection.manifestBytes, inspection.manifestByteLength, 'manifestBytes');
|
|
13
|
+
let manifest;
|
|
14
|
+
try {
|
|
15
|
+
manifest = JSON.parse(decoder.decode(manifestBytes));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new TypeError('MATERIAL_PACKAGE_INVALID: manifestBytes is not valid UTF-8 JSON');
|
|
19
|
+
}
|
|
20
|
+
assertMaterialPackageManifestShape(manifest);
|
|
21
|
+
return {
|
|
22
|
+
manifest,
|
|
23
|
+
manifestBytes,
|
|
24
|
+
files: new Map(inspection.files.map(({ path, data, byteLength }) => [
|
|
25
|
+
path,
|
|
26
|
+
copyMaterialBytes(data, byteLength, `file ${path}`),
|
|
27
|
+
])),
|
|
28
|
+
archiveBytes: copyMaterialBytes(inspection.archiveBytes, inspection.archiveByteLength, 'archiveBytes'),
|
|
29
|
+
integrity: inspection.integrity,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type MaterialPackageByteLimitOptions, type PackedMaterialPackage, type PackMaterialPackageOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a deterministic virtual AMP package. ZIP transport can be layered on top without
|
|
4
|
+
* changing manifest hashes, canonical bytes, or registry semantics.
|
|
5
|
+
*/
|
|
6
|
+
export declare function packMaterialPackage(options: PackMaterialPackageOptions): Promise<PackedMaterialPackage>;
|
|
7
|
+
export declare function verifyMaterialPackage(packed: PackedMaterialPackage, expectedIntegrity?: string, options?: MaterialPackageByteLimitOptions): Promise<void>;
|
|
8
|
+
/** Internal verification path for a package that is already ownership-isolated. */
|
|
9
|
+
/** @internal Ownership-isolated verification path; not exported by the package entrypoint. */
|
|
10
|
+
export declare function verifyMaterialPackageSnapshot(packed: PackedMaterialPackage, expectedIntegrity: string | undefined, options: MaterialPackageByteLimitOptions): Promise<void>;
|
|
11
|
+
export declare function decodeMaterialJson(data: Uint8Array): unknown;
|
|
12
|
+
//# sourceMappingURL=package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../src/package.ts"],"names":[],"mappings":"AAgBA,OAAO,EAKL,KAAK,+BAA+B,EACpC,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAChC,MAAM,YAAY,CAAC;AAmFpB;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CA8HhC;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,qBAAqB,EAC7B,iBAAiB,CAAC,EAAE,MAAM,EAC1B,OAAO,GAAE,+BAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,mFAAmF;AACnF,8FAA8F;AAC9F,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,qBAAqB,EAC7B,iBAAiB,EAAE,MAAM,GAAG,SAAS,EACrC,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAqFf;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAE5D"}
|