@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
package/dist/package.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { canonicalMaterialBytes, sha256Hex } from './canonical.js';
|
|
2
|
+
import { addMaterialPackageBytes, inspectPackedMaterialPackage, materialPackageBudgetExceeded, materialUint8ArrayByteLength, resolveMaterialPackageByteLimits, validMaterialPackagePath, } from './package-limits.js';
|
|
3
|
+
import { assertMaterialDefinitionShape, assertMaterialGraphShape, assertMaterialPackageManifestShape, } from './package-shape.js';
|
|
4
|
+
import { MATERIAL_PACKAGE_SCHEMA, MATERIAL_PROTOCOL_VERSION, } from './types.js';
|
|
5
|
+
import { validateAuthoredMaterial } from './validation.js';
|
|
6
|
+
import { createDeterministicMaterialArchive } from './zip.js';
|
|
7
|
+
import { snapshotMaterialPackage } from './package-snapshot.js';
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
10
|
+
const MEDIA_TYPE_DEFINITION = 'application/vnd.aelion.material+json';
|
|
11
|
+
const MEDIA_TYPE_GRAPH = 'application/vnd.aelion.material-graph+json';
|
|
12
|
+
function denseArraySnapshot(value, name, maximum) {
|
|
13
|
+
if (!Array.isArray(value)) {
|
|
14
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: ${name} must be an array`);
|
|
15
|
+
}
|
|
16
|
+
const length = value.length;
|
|
17
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > maximum) {
|
|
18
|
+
throw new RangeError(`MATERIAL_PACKAGE_BUDGET_EXCEEDED: ${name} has more than ${maximum.toString()} entries`);
|
|
19
|
+
}
|
|
20
|
+
const snapshot = [];
|
|
21
|
+
for (let index = 0; index < length; index += 1) {
|
|
22
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, index.toString());
|
|
23
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
24
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: ${name}[${index.toString()}] must be a dense data entry`);
|
|
25
|
+
}
|
|
26
|
+
snapshot.push(descriptor.value);
|
|
27
|
+
}
|
|
28
|
+
return snapshot;
|
|
29
|
+
}
|
|
30
|
+
function bytes(data, path) {
|
|
31
|
+
if (typeof data === 'string')
|
|
32
|
+
return encoder.encode(data);
|
|
33
|
+
const byteLength = materialUint8ArrayByteLength(data, `input file ${path}`);
|
|
34
|
+
const output = new Uint8Array(byteLength);
|
|
35
|
+
Uint8Array.prototype.set.call(output, data);
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
function inputByteLength(data, path) {
|
|
39
|
+
if (typeof data === 'string')
|
|
40
|
+
return encoder.encode(data).byteLength;
|
|
41
|
+
return materialUint8ArrayByteLength(data, `input file ${path}`);
|
|
42
|
+
}
|
|
43
|
+
function asJsonValue(value) {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function addFile(files, file, limits) {
|
|
47
|
+
if (!validMaterialPackagePath(file.path) ||
|
|
48
|
+
file.path === 'manifest.json' ||
|
|
49
|
+
file.path === 'signature.json') {
|
|
50
|
+
throw new TypeError(`Unsafe or reserved Material package path ${file.path}`);
|
|
51
|
+
}
|
|
52
|
+
if (files.has(file.path))
|
|
53
|
+
throw new TypeError(`Duplicate Material package path ${file.path}`);
|
|
54
|
+
if (files.size + 2 > limits.maxFiles) {
|
|
55
|
+
throw new RangeError(`MATERIAL_PACKAGE_BUDGET_EXCEEDED: package would have ${files.size + 2} files; limit is ${limits.maxFiles}`);
|
|
56
|
+
}
|
|
57
|
+
if (typeof file.mediaType !== 'string' || file.mediaType.length === 0) {
|
|
58
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: file ${file.path} requires a media type`);
|
|
59
|
+
}
|
|
60
|
+
const byteLength = inputByteLength(file.data, file.path);
|
|
61
|
+
if (byteLength > limits.maxFileBytes) {
|
|
62
|
+
materialPackageBudgetExceeded(`file ${file.path}`, byteLength, limits.maxFileBytes);
|
|
63
|
+
}
|
|
64
|
+
const data = bytes(file.data, file.path);
|
|
65
|
+
if (data.byteLength !== byteLength) {
|
|
66
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: file ${file.path} changed during snapshot`);
|
|
67
|
+
}
|
|
68
|
+
files.set(file.path, { mediaType: file.mediaType, data });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a deterministic virtual AMP package. ZIP transport can be layered on top without
|
|
72
|
+
* changing manifest hashes, canonical bytes, or registry semantics.
|
|
73
|
+
*/
|
|
74
|
+
export async function packMaterialPackage(options) {
|
|
75
|
+
const materials = denseArraySnapshot(options.materials, 'materials', 256);
|
|
76
|
+
if (materials.length === 0)
|
|
77
|
+
throw new TypeError('A Material package cannot be empty');
|
|
78
|
+
const limits = resolveMaterialPackageByteLimits(options.limits);
|
|
79
|
+
const packageFiles = new Map();
|
|
80
|
+
for (const material of materials) {
|
|
81
|
+
// Authoring and installation share one protocol contract. Reject values
|
|
82
|
+
// here rather than creating a signed deterministic package that the same
|
|
83
|
+
// SDK's registry is guaranteed to reject later.
|
|
84
|
+
assertMaterialDefinitionShape(material.definition);
|
|
85
|
+
if (material.graph !== undefined)
|
|
86
|
+
assertMaterialGraphShape(material.graph);
|
|
87
|
+
const validation = validateAuthoredMaterial(material);
|
|
88
|
+
if (!validation.valid) {
|
|
89
|
+
const codes = validation.diagnostics.map(value => value.code).join(', ');
|
|
90
|
+
throw new TypeError(`Material ${material.definition.id} is invalid: ${codes}`);
|
|
91
|
+
}
|
|
92
|
+
const definitionPath = material.definitionPath ?? `materials/${material.definition.id}.material.json`;
|
|
93
|
+
addFile(packageFiles, {
|
|
94
|
+
path: definitionPath,
|
|
95
|
+
mediaType: MEDIA_TYPE_DEFINITION,
|
|
96
|
+
data: canonicalMaterialBytes(asJsonValue(material.definition)),
|
|
97
|
+
}, limits);
|
|
98
|
+
if (material.graph !== undefined) {
|
|
99
|
+
const implementationPath = material.definition.implementations.find(value => value.type === 'graph')?.graph;
|
|
100
|
+
const graphPath = material.graphPath ?? implementationPath;
|
|
101
|
+
if (graphPath === undefined || graphPath !== implementationPath) {
|
|
102
|
+
throw new TypeError(`Material ${material.definition.id} Graph path does not match Definition`);
|
|
103
|
+
}
|
|
104
|
+
addFile(packageFiles, {
|
|
105
|
+
path: graphPath,
|
|
106
|
+
mediaType: MEDIA_TYPE_GRAPH,
|
|
107
|
+
data: canonicalMaterialBytes(asJsonValue(material.graph)),
|
|
108
|
+
}, limits);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const inputFiles = denseArraySnapshot(options.files ?? [], 'files', Math.max(0, limits.maxFiles - 1));
|
|
112
|
+
// Iteration is safe over our new intrinsic Array snapshot, not caller input.
|
|
113
|
+
for (const file of inputFiles) {
|
|
114
|
+
addFile(packageFiles, file, limits);
|
|
115
|
+
}
|
|
116
|
+
let payloadBytes = 0;
|
|
117
|
+
for (const [path, file] of packageFiles) {
|
|
118
|
+
payloadBytes = addMaterialPackageBytes(payloadBytes, file.data.byteLength, `file ${path}`);
|
|
119
|
+
if (payloadBytes > limits.maxPackageBytes) {
|
|
120
|
+
materialPackageBudgetExceeded('package payloads', payloadBytes, limits.maxPackageBytes);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const paths = [...packageFiles.keys()].sort();
|
|
124
|
+
const fileEntries = await Promise.all(paths.map(async (path) => {
|
|
125
|
+
const file = packageFiles.get(path);
|
|
126
|
+
if (file === undefined)
|
|
127
|
+
throw new ReferenceError(`Missing package file ${path}`);
|
|
128
|
+
return {
|
|
129
|
+
path,
|
|
130
|
+
mediaType: file.mediaType,
|
|
131
|
+
bytes: file.data.byteLength,
|
|
132
|
+
sha256: await sha256Hex(file.data),
|
|
133
|
+
};
|
|
134
|
+
}));
|
|
135
|
+
const manifest = {
|
|
136
|
+
$schema: MATERIAL_PACKAGE_SCHEMA,
|
|
137
|
+
protocolVersion: MATERIAL_PROTOCOL_VERSION,
|
|
138
|
+
package: options.metadata,
|
|
139
|
+
materials: materials.map(material => ({
|
|
140
|
+
id: material.definition.id,
|
|
141
|
+
kind: material.definition.kind,
|
|
142
|
+
definition: material.definitionPath ?? `materials/${material.definition.id}.material.json`,
|
|
143
|
+
})),
|
|
144
|
+
files: fileEntries,
|
|
145
|
+
};
|
|
146
|
+
assertMaterialPackageManifestShape(manifest);
|
|
147
|
+
const manifestBytes = canonicalMaterialBytes(asJsonValue(manifest));
|
|
148
|
+
if (manifestBytes.byteLength > limits.maxManifestBytes) {
|
|
149
|
+
materialPackageBudgetExceeded('manifestBytes', manifestBytes.byteLength, limits.maxManifestBytes);
|
|
150
|
+
}
|
|
151
|
+
const packageBytes = addMaterialPackageBytes(payloadBytes, manifestBytes.byteLength, 'package files');
|
|
152
|
+
if (packageBytes > limits.maxPackageBytes) {
|
|
153
|
+
materialPackageBudgetExceeded('package files', packageBytes, limits.maxPackageBytes);
|
|
154
|
+
}
|
|
155
|
+
const integrity = `sha256:${await sha256Hex(manifestBytes)}`;
|
|
156
|
+
const output = new Map();
|
|
157
|
+
output.set('manifest.json', manifestBytes);
|
|
158
|
+
for (const path of paths) {
|
|
159
|
+
const data = packageFiles.get(path)?.data;
|
|
160
|
+
if (data !== undefined)
|
|
161
|
+
output.set(path, new Uint8Array(data));
|
|
162
|
+
}
|
|
163
|
+
const archiveBytes = createDeterministicMaterialArchive(output, limits);
|
|
164
|
+
return {
|
|
165
|
+
manifest,
|
|
166
|
+
manifestBytes,
|
|
167
|
+
files: output,
|
|
168
|
+
archiveBytes,
|
|
169
|
+
integrity,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export async function verifyMaterialPackage(packed, expectedIntegrity, options = {}) {
|
|
173
|
+
// Validation is asynchronous. Snapshot synchronously so caller mutations
|
|
174
|
+
// cannot change the package halfway through an integrity/trust decision.
|
|
175
|
+
const inspection = inspectPackedMaterialPackage(packed, options);
|
|
176
|
+
packed = snapshotMaterialPackage(packed, options, inspection);
|
|
177
|
+
await verifyMaterialPackageSnapshot(packed, expectedIntegrity, inspection.limits);
|
|
178
|
+
}
|
|
179
|
+
/** Internal verification path for a package that is already ownership-isolated. */
|
|
180
|
+
/** @internal Ownership-isolated verification path; not exported by the package entrypoint. */
|
|
181
|
+
export async function verifyMaterialPackageSnapshot(packed, expectedIntegrity, options) {
|
|
182
|
+
const limits = resolveMaterialPackageByteLimits(options);
|
|
183
|
+
assertMaterialPackageManifestShape(packed.manifest);
|
|
184
|
+
const manifestBytes = canonicalMaterialBytes(asJsonValue(packed.manifest));
|
|
185
|
+
if (manifestBytes.byteLength > limits.maxManifestBytes) {
|
|
186
|
+
materialPackageBudgetExceeded('canonical manifest', manifestBytes.byteLength, limits.maxManifestBytes);
|
|
187
|
+
}
|
|
188
|
+
const storedManifest = packed.files.get('manifest.json');
|
|
189
|
+
if (storedManifest === undefined ||
|
|
190
|
+
packed.manifestBytes.byteLength !== manifestBytes.byteLength ||
|
|
191
|
+
storedManifest.byteLength !== manifestBytes.byteLength ||
|
|
192
|
+
packed.manifestBytes.some((value, index) => value !== manifestBytes[index]) ||
|
|
193
|
+
storedManifest.some((value, index) => value !== manifestBytes[index])) {
|
|
194
|
+
throw new TypeError('MATERIAL_INTEGRITY_MISMATCH: canonical manifest bytes differ');
|
|
195
|
+
}
|
|
196
|
+
const integrity = `sha256:${await sha256Hex(manifestBytes)}`;
|
|
197
|
+
if (packed.integrity !== integrity ||
|
|
198
|
+
(expectedIntegrity !== undefined && integrity !== expectedIntegrity)) {
|
|
199
|
+
throw new TypeError('MATERIAL_INTEGRITY_MISMATCH: manifest integrity differs');
|
|
200
|
+
}
|
|
201
|
+
const declaredPaths = new Set();
|
|
202
|
+
let declaredPackageBytes = manifestBytes.byteLength;
|
|
203
|
+
for (const entry of packed.manifest.files) {
|
|
204
|
+
if (!validMaterialPackagePath(entry.path) || declaredPaths.has(entry.path)) {
|
|
205
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: invalid or duplicate path ${entry.path}`);
|
|
206
|
+
}
|
|
207
|
+
if (entry.path === 'manifest.json' || entry.path === 'signature.json') {
|
|
208
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: reserved payload path ${entry.path}`);
|
|
209
|
+
}
|
|
210
|
+
if (entry.bytes > limits.maxFileBytes) {
|
|
211
|
+
materialPackageBudgetExceeded(`declared file ${entry.path}`, entry.bytes, limits.maxFileBytes);
|
|
212
|
+
}
|
|
213
|
+
declaredPackageBytes = addMaterialPackageBytes(declaredPackageBytes, entry.bytes, `declared file ${entry.path}`);
|
|
214
|
+
if (declaredPackageBytes > limits.maxPackageBytes) {
|
|
215
|
+
materialPackageBudgetExceeded('declared package files', declaredPackageBytes, limits.maxPackageBytes);
|
|
216
|
+
}
|
|
217
|
+
declaredPaths.add(entry.path);
|
|
218
|
+
const data = packed.files.get(entry.path);
|
|
219
|
+
if (data === undefined ||
|
|
220
|
+
data.byteLength !== entry.bytes ||
|
|
221
|
+
(await sha256Hex(data)) !== entry.sha256) {
|
|
222
|
+
throw new TypeError(`MATERIAL_INTEGRITY_MISMATCH: payload ${entry.path} differs`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const actualPayloads = [...packed.files.keys()].filter(path => path !== 'manifest.json');
|
|
226
|
+
if (actualPayloads.some(path => !declaredPaths.has(path)) ||
|
|
227
|
+
actualPayloads.length !== declaredPaths.size) {
|
|
228
|
+
throw new TypeError('MATERIAL_PACKAGE_INVALID: undeclared or missing payload');
|
|
229
|
+
}
|
|
230
|
+
const archiveFiles = new Map([['manifest.json', manifestBytes]]);
|
|
231
|
+
for (const entry of packed.manifest.files) {
|
|
232
|
+
const data = packed.files.get(entry.path);
|
|
233
|
+
if (data !== undefined)
|
|
234
|
+
archiveFiles.set(entry.path, data);
|
|
235
|
+
}
|
|
236
|
+
const expectedArchive = createDeterministicMaterialArchive(archiveFiles, limits);
|
|
237
|
+
if (packed.archiveBytes.byteLength !== expectedArchive.byteLength ||
|
|
238
|
+
packed.archiveBytes.some((value, index) => value !== expectedArchive[index])) {
|
|
239
|
+
throw new TypeError('MATERIAL_INTEGRITY_MISMATCH: archive bytes differ from verified files');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export function decodeMaterialJson(data) {
|
|
243
|
+
return JSON.parse(decoder.decode(data));
|
|
244
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MaterialTrustStore, SignedMaterialPackage } from './security.js';
|
|
2
|
+
import type { MaterialPackageByteLimitOptions, MaterialPackageReference, MaterialPackageResolver, PackedMaterialPackage, ResolvedMaterial } from './types.js';
|
|
3
|
+
export interface InstallMaterialPackageOptions {
|
|
4
|
+
readonly expectedIntegrity?: string;
|
|
5
|
+
/** Required in addition to a trusted-code manifest. Authorization is never inferred. */
|
|
6
|
+
readonly authorizeTrustedCode?: boolean;
|
|
7
|
+
readonly trustedPublisherIds?: ReadonlySet<string>;
|
|
8
|
+
/** Applied before package transport bytes are copied or its ZIP is rebuilt. */
|
|
9
|
+
readonly limits?: MaterialPackageByteLimitOptions;
|
|
10
|
+
}
|
|
11
|
+
export interface MaterialRegistryOptions {
|
|
12
|
+
/** Default fail-closed transport budget for install, resolve and registry snapshots. */
|
|
13
|
+
readonly limits?: MaterialPackageByteLimitOptions;
|
|
14
|
+
}
|
|
15
|
+
export declare class MaterialRegistry implements MaterialPackageResolver {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(options?: MaterialRegistryOptions);
|
|
18
|
+
install(packed: PackedMaterialPackage, options?: InstallMaterialPackageOptions): Promise<void>;
|
|
19
|
+
installSigned(signed: SignedMaterialPackage, trustStore: MaterialTrustStore, options?: InstallMaterialPackageOptions & {
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
readonly nowMs?: number;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
resolve(reference: Pick<MaterialPackageReference, 'packageId' | 'packageVersion' | 'packageIntegrity'>, options?: {
|
|
24
|
+
readonly signal?: AbortSignal;
|
|
25
|
+
}): Promise<PackedMaterialPackage>;
|
|
26
|
+
installFrom(resolver: MaterialPackageResolver, reference: Pick<MaterialPackageReference, 'packageId' | 'packageVersion' | 'packageIntegrity'>, options?: InstallMaterialPackageOptions & {
|
|
27
|
+
readonly signal?: AbortSignal;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
resolveMaterial(reference: MaterialPackageReference): Promise<ResolvedMaterial>;
|
|
30
|
+
uninstall(reference: Pick<MaterialPackageReference, 'packageId' | 'packageVersion' | 'packageIntegrity'>): boolean;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,KAAK,EAEV,+BAA+B,EAC/B,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,wFAAwF;IACxF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnD,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,+BAA+B,CAAC;CACnD;AAED,MAAM,WAAW,uBAAuB;IACtC,wFAAwF;IACxF,QAAQ,CAAC,MAAM,CAAC,EAAE,+BAA+B,CAAC;CACnD;AAkFD,qBAAa,gBAAiB,YAAW,uBAAuB;;gBAIlD,OAAO,GAAE,uBAA4B;IAI3C,OAAO,CACX,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC;IA+CV,aAAa,CACjB,MAAM,EAAE,qBAAqB,EAC7B,UAAU,EAAE,kBAAkB,EAC9B,OAAO,GAAE,6BAA6B,GAAG;QACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;QAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KACpB,GACL,OAAO,CAAC,IAAI,CAAC;IAShB,OAAO,CACL,SAAS,EAAE,IAAI,CAAC,wBAAwB,EAAE,WAAW,GAAG,gBAAgB,GAAG,kBAAkB,CAAC,EAC9F,OAAO,GAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9C,OAAO,CAAC,qBAAqB,CAAC;IAa3B,WAAW,CACf,QAAQ,EAAE,uBAAuB,EACjC,SAAS,EAAE,IAAI,CAAC,wBAAwB,EAAE,WAAW,GAAG,gBAAgB,GAAG,kBAAkB,CAAC,EAC9F,OAAO,GAAE,6BAA6B,GAAG;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC9E,OAAO,CAAC,IAAI,CAAC;IASV,eAAe,CAAC,SAAS,EAAE,wBAAwB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgBrF,SAAS,CACP,SAAS,EAAE,IAAI,CAAC,wBAAwB,EAAE,WAAW,GAAG,gBAAgB,GAAG,kBAAkB,CAAC,GAC7F,OAAO;CAKX"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { throwIfAborted } from '@aelionsdk/core';
|
|
2
|
+
import { decodeMaterialJson, verifyMaterialPackageSnapshot } from './package.js';
|
|
3
|
+
import { inspectPackedMaterialPackage, resolveMaterialPackageByteLimits, } from './package-limits.js';
|
|
4
|
+
import { assertMaterialDefinitionShape, assertMaterialGraphShape } from './package-shape.js';
|
|
5
|
+
import { snapshotMaterialPackage } from './package-snapshot.js';
|
|
6
|
+
import { validateAuthoredMaterial } from './validation.js';
|
|
7
|
+
function packageKey(packageId, packageVersion, integrity) {
|
|
8
|
+
return `${packageId}\0${packageVersion}\0${integrity}`;
|
|
9
|
+
}
|
|
10
|
+
function requiresTrustedCode(definition) {
|
|
11
|
+
return definition.implementations.some(value => value.type === 'shader' || value.type === 'wasm');
|
|
12
|
+
}
|
|
13
|
+
function readDefinition(packed, path) {
|
|
14
|
+
const data = packed.files.get(path);
|
|
15
|
+
if (data === undefined)
|
|
16
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: missing ${path}`);
|
|
17
|
+
let value;
|
|
18
|
+
try {
|
|
19
|
+
value = decodeMaterialJson(data);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: definition ${path} is not valid UTF-8 JSON`);
|
|
23
|
+
}
|
|
24
|
+
assertMaterialDefinitionShape(value);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function readGraph(packed, definition) {
|
|
28
|
+
const implementation = definition.implementations.find(value => value.type === 'graph');
|
|
29
|
+
if (implementation?.type !== 'graph')
|
|
30
|
+
return undefined;
|
|
31
|
+
const data = packed.files.get(implementation.graph);
|
|
32
|
+
if (data === undefined) {
|
|
33
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: missing ${implementation.graph}`);
|
|
34
|
+
}
|
|
35
|
+
let value;
|
|
36
|
+
try {
|
|
37
|
+
value = decodeMaterialJson(data);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: graph ${implementation.graph} is not valid UTF-8 JSON`);
|
|
41
|
+
}
|
|
42
|
+
assertMaterialGraphShape(value);
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function requirePayload(packed, path, owner) {
|
|
46
|
+
if (packed.files.get(path) === undefined) {
|
|
47
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: ${owner} references missing ${path}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function validateReferencedPayloads(packed, definition) {
|
|
51
|
+
for (const resource of definition.bundledResources) {
|
|
52
|
+
requirePayload(packed, resource.path, `resource ${resource.id}`);
|
|
53
|
+
}
|
|
54
|
+
for (const implementation of definition.implementations) {
|
|
55
|
+
if (implementation.type === 'graph') {
|
|
56
|
+
requirePayload(packed, implementation.graph, `graph implementation ${definition.id}`);
|
|
57
|
+
}
|
|
58
|
+
else if (implementation.type === 'wasm') {
|
|
59
|
+
requirePayload(packed, implementation.module, `WASM implementation ${definition.id}`);
|
|
60
|
+
}
|
|
61
|
+
else if (implementation.backend === 'webgpu') {
|
|
62
|
+
requirePayload(packed, implementation.module, `WebGPU implementation ${definition.id}`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
requirePayload(packed, implementation.fragmentModule, `WebGL2 implementation ${definition.id}`);
|
|
66
|
+
if (implementation.vertexModule !== undefined) {
|
|
67
|
+
requirePayload(packed, implementation.vertexModule, `WebGL2 implementation ${definition.id}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export class MaterialRegistry {
|
|
73
|
+
#packages = new Map();
|
|
74
|
+
#limits;
|
|
75
|
+
constructor(options = {}) {
|
|
76
|
+
this.#limits = resolveMaterialPackageByteLimits(options.limits);
|
|
77
|
+
}
|
|
78
|
+
async install(packed, options = {}) {
|
|
79
|
+
const limits = resolveMaterialPackageByteLimits(options.limits ?? this.#limits);
|
|
80
|
+
const inspection = inspectPackedMaterialPackage(packed, limits);
|
|
81
|
+
const snapshot = snapshotMaterialPackage(packed, limits, inspection);
|
|
82
|
+
await verifyMaterialPackageSnapshot(snapshot, options.expectedIntegrity, limits);
|
|
83
|
+
const { manifest } = snapshot;
|
|
84
|
+
const trusted = manifest.package.trust === 'trusted-code';
|
|
85
|
+
if (trusted) {
|
|
86
|
+
if (options.authorizeTrustedCode !== true ||
|
|
87
|
+
!options.trustedPublisherIds?.has(manifest.package.publisher.id)) {
|
|
88
|
+
throw new TypeError('MATERIAL_TRUST_REQUIRED: trusted code requires explicit host authorization and publisher allowlist');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const entry of manifest.materials) {
|
|
92
|
+
const definition = readDefinition(snapshot, entry.definition);
|
|
93
|
+
if (definition.id !== entry.id || definition.kind !== entry.kind) {
|
|
94
|
+
throw new TypeError(`MATERIAL_DEFINITION_INVALID: manifest identity differs for ${entry.id}`);
|
|
95
|
+
}
|
|
96
|
+
if (requiresTrustedCode(definition) && !trusted) {
|
|
97
|
+
throw new TypeError(`MATERIAL_TRUST_REQUIRED: ${entry.id} contains code in a declarative package`);
|
|
98
|
+
}
|
|
99
|
+
validateReferencedPayloads(snapshot, definition);
|
|
100
|
+
const graph = readGraph(snapshot, definition);
|
|
101
|
+
const result = validateAuthoredMaterial({
|
|
102
|
+
definition,
|
|
103
|
+
...(graph === undefined ? {} : { graph }),
|
|
104
|
+
});
|
|
105
|
+
if (!result.valid) {
|
|
106
|
+
throw new TypeError(`MATERIAL_DEFINITION_INVALID: ${result.diagnostics.map(value => value.code).join(', ')}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.#packages.set(packageKey(manifest.package.id, manifest.package.version, snapshot.integrity), snapshot);
|
|
110
|
+
}
|
|
111
|
+
async installSigned(signed, trustStore, options = {}) {
|
|
112
|
+
await trustStore.verify(signed, {
|
|
113
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
114
|
+
...(options.nowMs === undefined ? {} : { nowMs: options.nowMs }),
|
|
115
|
+
});
|
|
116
|
+
throwIfAborted(options.signal, 'Signed Material package install');
|
|
117
|
+
await this.install(signed.package, options);
|
|
118
|
+
}
|
|
119
|
+
resolve(reference, options = {}) {
|
|
120
|
+
throwIfAborted(options.signal, 'Material package resolve');
|
|
121
|
+
const result = this.#packages.get(packageKey(reference.packageId, reference.packageVersion, reference.packageIntegrity));
|
|
122
|
+
if (result === undefined) {
|
|
123
|
+
throw new ReferenceError(`MATERIAL_MISSING: ${reference.packageId}@${reference.packageVersion}#${reference.packageIntegrity}`);
|
|
124
|
+
}
|
|
125
|
+
return Promise.resolve(snapshotMaterialPackage(result, this.#limits));
|
|
126
|
+
}
|
|
127
|
+
async installFrom(resolver, reference, options = {}) {
|
|
128
|
+
const packed = await resolver.resolve(reference, options.signal === undefined ? {} : { signal: options.signal });
|
|
129
|
+
throwIfAborted(options.signal, 'Material package install');
|
|
130
|
+
await this.install(packed, { ...options, expectedIntegrity: reference.packageIntegrity });
|
|
131
|
+
}
|
|
132
|
+
async resolveMaterial(reference) {
|
|
133
|
+
const packed = await this.resolve(reference);
|
|
134
|
+
const entry = packed.manifest.materials.find(value => value.id === reference.materialId);
|
|
135
|
+
if (entry === undefined) {
|
|
136
|
+
throw new ReferenceError(`MATERIAL_MISSING: material ${reference.materialId}`);
|
|
137
|
+
}
|
|
138
|
+
const definition = readDefinition(packed, entry.definition);
|
|
139
|
+
const graph = readGraph(packed, definition);
|
|
140
|
+
return {
|
|
141
|
+
reference,
|
|
142
|
+
manifest: packed.manifest,
|
|
143
|
+
definition,
|
|
144
|
+
...(graph === undefined ? {} : { graph }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
uninstall(reference) {
|
|
148
|
+
return this.#packages.delete(packageKey(reference.packageId, reference.packageVersion, reference.packageIntegrity));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function assertMaterialManifestSchema(value: unknown): void;
|
|
2
|
+
export declare function assertMaterialDefinitionSchema(value: unknown): void;
|
|
3
|
+
export declare function assertMaterialGraphSchema(value: unknown): void;
|
|
4
|
+
//# sourceMappingURL=schema-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-validation.d.ts","sourceRoot":"","sources":["../src/schema-validation.ts"],"names":[],"mappings":"AAoCA,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAEjE;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAEnE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE9D"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Ajv2020, {} from 'ajv/dist/2020.js';
|
|
2
|
+
import addFormats from 'ajv-formats';
|
|
3
|
+
import { materialDefinitionSchema, materialGraphSchema, materialPackageSchema, } from './bundled-schemas.js';
|
|
4
|
+
const ajv = new Ajv2020({
|
|
5
|
+
// Shape admission runs before these validators and rejects oversized input.
|
|
6
|
+
// Stop at the first schema failure so invalid packages cannot amplify CPU or
|
|
7
|
+
// retained error state by asking Ajv to enumerate every violation.
|
|
8
|
+
allErrors: false,
|
|
9
|
+
allowUnionTypes: true,
|
|
10
|
+
strict: true,
|
|
11
|
+
validateFormats: true,
|
|
12
|
+
});
|
|
13
|
+
addFormats(ajv);
|
|
14
|
+
const validateManifest = ajv.compile(materialPackageSchema);
|
|
15
|
+
const validateDefinition = ajv.compile(materialDefinitionSchema);
|
|
16
|
+
const validateGraph = ajv.compile(materialGraphSchema);
|
|
17
|
+
function pointer(error) {
|
|
18
|
+
return error.instancePath === '' ? '/' : error.instancePath;
|
|
19
|
+
}
|
|
20
|
+
function assertSchema(value, name, validator) {
|
|
21
|
+
if (validator(value))
|
|
22
|
+
return;
|
|
23
|
+
const first = validator.errors?.[0];
|
|
24
|
+
const message = first?.message ?? `does not conform to the ${name} schema`;
|
|
25
|
+
const path = first === undefined ? '/' : pointer(first);
|
|
26
|
+
throw new TypeError(`MATERIAL_PACKAGE_INVALID: ${name} ${path} ${message}`);
|
|
27
|
+
}
|
|
28
|
+
export function assertMaterialManifestSchema(value) {
|
|
29
|
+
assertSchema(value, 'manifest', validateManifest);
|
|
30
|
+
}
|
|
31
|
+
export function assertMaterialDefinitionSchema(value) {
|
|
32
|
+
assertSchema(value, 'definition', validateDefinition);
|
|
33
|
+
}
|
|
34
|
+
export function assertMaterialGraphSchema(value) {
|
|
35
|
+
assertSchema(value, 'graph', validateGraph);
|
|
36
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { PackedMaterialPackage } from './types.js';
|
|
2
|
+
export type MaterialSignatureAlgorithm = 'Ed25519' | 'ECDSA-P256-SHA256';
|
|
3
|
+
export interface MaterialPackageSignature {
|
|
4
|
+
readonly version: 1;
|
|
5
|
+
readonly publisherId: string;
|
|
6
|
+
readonly keyId: string;
|
|
7
|
+
readonly algorithm: MaterialSignatureAlgorithm;
|
|
8
|
+
readonly signedAtMs: number;
|
|
9
|
+
readonly manifestIntegrity: string;
|
|
10
|
+
readonly signatureBase64Url: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SignedMaterialPackage {
|
|
13
|
+
readonly package: PackedMaterialPackage;
|
|
14
|
+
readonly signature: MaterialPackageSignature;
|
|
15
|
+
}
|
|
16
|
+
export interface SignMaterialPackageOptions {
|
|
17
|
+
readonly publisherId: string;
|
|
18
|
+
readonly keyId: string;
|
|
19
|
+
readonly algorithm: MaterialSignatureAlgorithm;
|
|
20
|
+
readonly privateKey: CryptoKey;
|
|
21
|
+
readonly signedAtMs?: number;
|
|
22
|
+
readonly signal?: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
export declare function signMaterialPackage(packed: PackedMaterialPackage, options: SignMaterialPackageOptions): Promise<SignedMaterialPackage>;
|
|
25
|
+
export interface MaterialPublisherKey {
|
|
26
|
+
readonly publisherId: string;
|
|
27
|
+
readonly keyId: string;
|
|
28
|
+
readonly algorithm: MaterialSignatureAlgorithm;
|
|
29
|
+
readonly publicKey: CryptoKey;
|
|
30
|
+
readonly validFromMs?: number;
|
|
31
|
+
readonly validUntilMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface MaterialTrustAuditEntry {
|
|
34
|
+
readonly sequence: number;
|
|
35
|
+
readonly timeMs: number;
|
|
36
|
+
readonly action: 'key-added' | 'key-revoked' | 'package-revoked' | 'verified' | 'rejected';
|
|
37
|
+
readonly publisherId?: string;
|
|
38
|
+
readonly keyId?: string;
|
|
39
|
+
readonly integrity?: string;
|
|
40
|
+
readonly reason?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare class MaterialTrustStore {
|
|
43
|
+
#private;
|
|
44
|
+
constructor(maxAuditEntries?: number);
|
|
45
|
+
addKey(key: MaterialPublisherKey, nowMs?: number): void;
|
|
46
|
+
revokeKey(publisherId: string, keyId: string, reason: string, nowMs?: number): void;
|
|
47
|
+
revokePackage(integrity: string, reason: string, nowMs?: number): void;
|
|
48
|
+
verify(signed: SignedMaterialPackage, options?: {
|
|
49
|
+
readonly nowMs?: number;
|
|
50
|
+
readonly signal?: AbortSignal;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
auditLog(): readonly MaterialTrustAuditEntry[];
|
|
53
|
+
}
|
|
54
|
+
export interface MaterialExecutionPermissions {
|
|
55
|
+
readonly allowShader: boolean;
|
|
56
|
+
readonly allowWasm: boolean;
|
|
57
|
+
readonly allowedNetworkOrigins: readonly string[];
|
|
58
|
+
readonly maxMemoryBytes: number;
|
|
59
|
+
readonly maxExecutionMs: number;
|
|
60
|
+
}
|
|
61
|
+
export interface MaterialHostSecurityPolicy {
|
|
62
|
+
readonly trustedPublisherIds: ReadonlySet<string>;
|
|
63
|
+
readonly allowShader: boolean;
|
|
64
|
+
readonly allowWasm: boolean;
|
|
65
|
+
readonly allowedNetworkOrigins: ReadonlySet<string>;
|
|
66
|
+
readonly maxMemoryBytes: number;
|
|
67
|
+
readonly maxExecutionMs: number;
|
|
68
|
+
}
|
|
69
|
+
export declare function enforceMaterialExecutionPolicy(publisherId: string, requested: MaterialExecutionPermissions, host: MaterialHostSecurityPolicy): void;
|
|
70
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"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"}
|