@forgeax/engine-pack 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/build.d.ts +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.mjs +115 -67
- package/dist/build.mjs.map +1 -1
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/evidence/material-cook.d.ts +23 -12
- package/dist/evidence/material-cook.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +116 -68
- package/dist/index.mjs.map +1 -1
- package/dist/material-cook.d.ts +1 -1
- package/dist/material-cook.d.ts.map +1 -1
- package/dist/material-cook.mjs +116 -68
- package/dist/material-cook.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/material-cook-receipt.unit.test.ts +9 -9
- package/src/__tests__/material-cook-schema.unit.test.ts +172 -21
- package/src/__tests__/material-surface-publication.unit.test.ts +12 -9
- package/src/__tests__/material-surface-wire.unit.test.ts +2 -2
- package/src/build.ts +2 -2
- package/src/evidence/material-cook.ts +164 -99
- package/src/index.ts +5 -2
- package/src/material-cook.ts +5 -2
- package/src/schema/material-cook.schema.json +249 -88
|
@@ -25,17 +25,26 @@ export interface MaterialCookArtifact {
|
|
|
25
25
|
readonly bytes: Uint8Array;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
export interface
|
|
28
|
+
/** Domain-owned inputs used to compile and select a material program. */
|
|
29
|
+
export interface MaterialCookProgramContext {
|
|
30
30
|
readonly backend: 'webgpu' | 'webgl2' | 'wgpu-native';
|
|
31
31
|
readonly capability: 'storage-buffer' | 'uniform-fallback';
|
|
32
|
+
readonly pipeline: 'forward' | 'deferred';
|
|
33
|
+
readonly geometry: 'mesh' | 'skinned' | 'sprite';
|
|
34
|
+
readonly pass: 'forward' | 'shadow' | 'depth';
|
|
35
|
+
readonly profile: 'forgeax-material-wgsl-v1';
|
|
36
|
+
readonly toolchain: 'naga-oil';
|
|
37
|
+
readonly instrumentation: 'none' | 'validation';
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
/**
|
|
35
|
-
export interface
|
|
36
|
-
readonly context: MaterialCookVariantContext;
|
|
40
|
+
/** Programs are derived artifacts, never independently authored assets. */
|
|
41
|
+
export interface MaterialCookProgram {
|
|
37
42
|
readonly specializationKey: string;
|
|
38
43
|
readonly artifact: MaterialCookArtifact;
|
|
44
|
+
readonly selections: readonly {
|
|
45
|
+
readonly pass: string;
|
|
46
|
+
readonly context: MaterialCookProgramContext;
|
|
47
|
+
}[];
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
export interface MaterialCookWasmProvenance {
|
|
@@ -64,7 +73,7 @@ export interface MaterialCookIdentity {
|
|
|
64
73
|
export type MaterialCookIdentityInput = Omit<MaterialCookIdentity, 'cookIdentity'>;
|
|
65
74
|
|
|
66
75
|
export interface MaterialCookReceipt {
|
|
67
|
-
readonly schemaVersion: 'material-cook/
|
|
76
|
+
readonly schemaVersion: 'material-cook/4';
|
|
68
77
|
readonly sourceClosure: readonly string[];
|
|
69
78
|
readonly profile: string;
|
|
70
79
|
readonly compilerVersion: string;
|
|
@@ -82,16 +91,14 @@ export interface MaterialParameterContract {
|
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
export interface CookedMaterialRecord {
|
|
85
|
-
readonly schemaVersion: 'material-cook/
|
|
94
|
+
readonly schemaVersion: 'material-cook/4';
|
|
86
95
|
readonly guid: string;
|
|
87
96
|
readonly authored?: MaterialAsset;
|
|
88
97
|
readonly materialGuid?: string;
|
|
89
98
|
readonly publicationGeneration?: number;
|
|
90
99
|
readonly specializationKey?: string;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/** Additional immutable artifacts for other backend capability lanes. */
|
|
94
|
-
readonly variants?: readonly MaterialCookVariant[];
|
|
100
|
+
readonly programs: readonly MaterialCookProgram[];
|
|
101
|
+
/** Digest of the complete program/Pass/context manifest. */
|
|
95
102
|
readonly artifactDigest?: string;
|
|
96
103
|
readonly sourceClosure?: readonly string[];
|
|
97
104
|
readonly parameterContract?: MaterialParameterContract;
|
|
@@ -101,7 +108,6 @@ export interface CookedMaterialRecord {
|
|
|
101
108
|
readonly values: Readonly<Record<string, MaterialValue | null>>;
|
|
102
109
|
};
|
|
103
110
|
readonly refs: MaterialCookRefs;
|
|
104
|
-
readonly artifact: MaterialCookArtifact;
|
|
105
111
|
readonly receipt: MaterialCookReceipt;
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -127,7 +133,6 @@ const STANDARD_ROOT_MODULES = new Set([
|
|
|
127
133
|
'forgeax::pbr-skin',
|
|
128
134
|
'forgeax_material::standard',
|
|
129
135
|
'forgeax_material::pbr-skin',
|
|
130
|
-
'forgeax::default-shadow-caster',
|
|
131
136
|
]);
|
|
132
137
|
|
|
133
138
|
/** Shared ownership predicate for the built-in Standard material root. */
|
|
@@ -266,7 +271,7 @@ export function serializeMaterialCookReceipt(receipt: MaterialCookReceipt): stri
|
|
|
266
271
|
function invalid(field: string, actual?: unknown): Result<never, MaterialCookRecordError> {
|
|
267
272
|
return err({
|
|
268
273
|
code: 'material-cook-record-invalid',
|
|
269
|
-
expected: 'a complete material-cook/
|
|
274
|
+
expected: 'a complete material-cook/4 record with layered identity and provenance',
|
|
270
275
|
hint: 're-cook the material and publish its record, artifact, references, and receipt together',
|
|
271
276
|
detail: {
|
|
272
277
|
field,
|
|
@@ -337,7 +342,7 @@ export function validateMaterialCookReceipt(
|
|
|
337
342
|
): Result<MaterialCookReceipt, MaterialCookRecordError> {
|
|
338
343
|
if (value === null || typeof value !== 'object') return invalid('receipt');
|
|
339
344
|
const candidate = value as Record<string, unknown>;
|
|
340
|
-
if (candidate.schemaVersion !== 'material-cook/
|
|
345
|
+
if (candidate.schemaVersion !== 'material-cook/4')
|
|
341
346
|
return invalid('receipt.schemaVersion', candidate.schemaVersion);
|
|
342
347
|
const identityResult = validateIdentity(candidate.identity);
|
|
343
348
|
if (!identityResult.ok) return identityResult;
|
|
@@ -388,7 +393,7 @@ export function validateMaterialCookReceipt(
|
|
|
388
393
|
return invalid('receipt.identity.cookIdentity', identity.cookIdentity);
|
|
389
394
|
}
|
|
390
395
|
return ok({
|
|
391
|
-
schemaVersion: 'material-cook/
|
|
396
|
+
schemaVersion: 'material-cook/4',
|
|
392
397
|
sourceClosure: candidate.sourceClosure as string[],
|
|
393
398
|
profile: candidate.profile as string,
|
|
394
399
|
compilerVersion: candidate.compilerVersion as string,
|
|
@@ -407,7 +412,7 @@ export function validateCookedMaterialRecord(
|
|
|
407
412
|
): Result<CookedMaterialRecord, MaterialCookRecordError> {
|
|
408
413
|
if (value === null || typeof value !== 'object') return invalid('record');
|
|
409
414
|
const candidate = value as Record<string, unknown>;
|
|
410
|
-
if (candidate.schemaVersion !== 'material-cook/
|
|
415
|
+
if (candidate.schemaVersion !== 'material-cook/4')
|
|
411
416
|
return invalid('schemaVersion', candidate.schemaVersion);
|
|
412
417
|
if (typeof candidate.guid !== 'string' || !candidate.guid) return invalid('guid');
|
|
413
418
|
if (candidate.materialGuid !== undefined && typeof candidate.materialGuid !== 'string')
|
|
@@ -419,49 +424,8 @@ export function validateCookedMaterialRecord(
|
|
|
419
424
|
return invalid('publicationGeneration', candidate.publicationGeneration);
|
|
420
425
|
if (candidate.specializationKey !== undefined && typeof candidate.specializationKey !== 'string')
|
|
421
426
|
return invalid('specializationKey');
|
|
422
|
-
if (candidate
|
|
423
|
-
|
|
424
|
-
return invalid('variantContext');
|
|
425
|
-
const context = candidate.variantContext as Record<string, unknown>;
|
|
426
|
-
if (
|
|
427
|
-
(context.backend !== 'webgpu' &&
|
|
428
|
-
context.backend !== 'webgl2' &&
|
|
429
|
-
context.backend !== 'wgpu-native') ||
|
|
430
|
-
(context.capability !== 'storage-buffer' && context.capability !== 'uniform-fallback')
|
|
431
|
-
)
|
|
432
|
-
return invalid('variantContext', candidate.variantContext);
|
|
433
|
-
}
|
|
434
|
-
if (candidate.variants !== undefined) {
|
|
435
|
-
if (!Array.isArray(candidate.variants)) return invalid('variants');
|
|
436
|
-
for (const [index, value] of candidate.variants.entries()) {
|
|
437
|
-
if (value === null || typeof value !== 'object') return invalid(`variants[${index}]`);
|
|
438
|
-
const variant = value as Record<string, unknown>;
|
|
439
|
-
const context = variant.context;
|
|
440
|
-
if (context === null || typeof context !== 'object')
|
|
441
|
-
return invalid(`variants[${index}].context`);
|
|
442
|
-
const contextRecord = context as Record<string, unknown>;
|
|
443
|
-
if (
|
|
444
|
-
(contextRecord.backend !== 'webgpu' &&
|
|
445
|
-
contextRecord.backend !== 'webgl2' &&
|
|
446
|
-
contextRecord.backend !== 'wgpu-native') ||
|
|
447
|
-
(contextRecord.capability !== 'storage-buffer' &&
|
|
448
|
-
contextRecord.capability !== 'uniform-fallback')
|
|
449
|
-
)
|
|
450
|
-
return invalid(`variants[${index}].context`, context);
|
|
451
|
-
if (typeof variant.specializationKey !== 'string' || variant.specializationKey.length === 0)
|
|
452
|
-
return invalid(`variants[${index}].specializationKey`);
|
|
453
|
-
if (variant.artifact === null || typeof variant.artifact !== 'object')
|
|
454
|
-
return invalid(`variants[${index}].artifact`);
|
|
455
|
-
const variantArtifact = variant.artifact as Record<string, unknown>;
|
|
456
|
-
if (
|
|
457
|
-
typeof variantArtifact.mediaType !== 'string' ||
|
|
458
|
-
typeof variantArtifact.path !== 'string' ||
|
|
459
|
-
typeof variantArtifact.digest !== 'string' ||
|
|
460
|
-
normalizeArtifactBytes(variantArtifact.bytes) === undefined
|
|
461
|
-
)
|
|
462
|
-
return invalid(`variants[${index}].artifact`);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
427
|
+
if ('artifact' in candidate || 'variants' in candidate || 'variantContext' in candidate)
|
|
428
|
+
return invalid('programs', 'legacy single-artifact publication');
|
|
465
429
|
if (candidate.artifactDigest !== undefined && typeof candidate.artifactDigest !== 'string')
|
|
466
430
|
return invalid('artifactDigest');
|
|
467
431
|
if (
|
|
@@ -486,21 +450,8 @@ export function validateCookedMaterialRecord(
|
|
|
486
450
|
if (candidate.resolved === null || typeof candidate.resolved !== 'object')
|
|
487
451
|
return invalid('resolved');
|
|
488
452
|
if (candidate.refs === null || typeof candidate.refs !== 'object') return invalid('refs');
|
|
489
|
-
if (candidate.artifact === null || typeof candidate.artifact !== 'object')
|
|
490
|
-
return invalid('artifact');
|
|
491
453
|
if (candidate.receipt === null || typeof candidate.receipt !== 'object')
|
|
492
454
|
return invalid('receipt');
|
|
493
|
-
const artifact = candidate.artifact as Record<string, unknown>;
|
|
494
|
-
if (
|
|
495
|
-
typeof artifact.digest !== 'string' ||
|
|
496
|
-
(!Array.isArray(artifact.bytes) && !ArrayBuffer.isView(artifact.bytes))
|
|
497
|
-
) {
|
|
498
|
-
return invalid('artifact');
|
|
499
|
-
}
|
|
500
|
-
const receiptResult = validateMaterialCookReceipt(candidate.receipt, {
|
|
501
|
-
artifactDigest: artifact.digest as string,
|
|
502
|
-
});
|
|
503
|
-
if (!receiptResult.ok) return receiptResult;
|
|
504
455
|
const resolved = candidate.resolved as Record<string, unknown>;
|
|
505
456
|
if (!Array.isArray(resolved.passes)) return invalid('resolved.passes', resolved.passes);
|
|
506
457
|
if (!Array.isArray(resolved.parameters))
|
|
@@ -511,6 +462,84 @@ export function validateCookedMaterialRecord(
|
|
|
511
462
|
Array.isArray(resolved.values)
|
|
512
463
|
)
|
|
513
464
|
return invalid('resolved.values', resolved.values);
|
|
465
|
+
const passes = resolved.passes as MaterialPass[];
|
|
466
|
+
const passNames = new Set<string>();
|
|
467
|
+
for (const [index, pass] of passes.entries()) {
|
|
468
|
+
if (
|
|
469
|
+
pass === null ||
|
|
470
|
+
typeof pass !== 'object' ||
|
|
471
|
+
typeof pass.name !== 'string' ||
|
|
472
|
+
!pass.name ||
|
|
473
|
+
passNames.has(pass.name) ||
|
|
474
|
+
pass.program === null ||
|
|
475
|
+
typeof pass.program !== 'object' ||
|
|
476
|
+
typeof pass.program.module !== 'string'
|
|
477
|
+
)
|
|
478
|
+
return invalid(`resolved.passes[${index}]`);
|
|
479
|
+
passNames.add(pass.name);
|
|
480
|
+
}
|
|
481
|
+
if (!Array.isArray(candidate.programs) || candidate.programs.length === 0)
|
|
482
|
+
return invalid('programs');
|
|
483
|
+
const programs: MaterialCookProgram[] = [];
|
|
484
|
+
const programKeys = new Set<string>();
|
|
485
|
+
const selections = new Set<string>();
|
|
486
|
+
const selectedPasses = new Set<string>();
|
|
487
|
+
for (const [index, entry] of candidate.programs.entries()) {
|
|
488
|
+
const field = `programs[${index}]`;
|
|
489
|
+
if (entry === null || typeof entry !== 'object') return invalid(field);
|
|
490
|
+
const program = entry as Record<string, unknown>;
|
|
491
|
+
if (
|
|
492
|
+
typeof program.specializationKey !== 'string' ||
|
|
493
|
+
!program.specializationKey ||
|
|
494
|
+
programKeys.has(program.specializationKey)
|
|
495
|
+
)
|
|
496
|
+
return invalid(`${field}.specializationKey`);
|
|
497
|
+
programKeys.add(program.specializationKey);
|
|
498
|
+
if (program.artifact === null || typeof program.artifact !== 'object')
|
|
499
|
+
return invalid(`${field}.artifact`);
|
|
500
|
+
const artifact = program.artifact as Record<string, unknown>;
|
|
501
|
+
const bytes = normalizeArtifactBytes(artifact.bytes);
|
|
502
|
+
if (
|
|
503
|
+
artifact.mediaType !== 'text/wgsl' ||
|
|
504
|
+
typeof artifact.path !== 'string' ||
|
|
505
|
+
!artifact.path ||
|
|
506
|
+
typeof artifact.digest !== 'string' ||
|
|
507
|
+
!artifact.digest ||
|
|
508
|
+
bytes === undefined
|
|
509
|
+
)
|
|
510
|
+
return invalid(`${field}.artifact`);
|
|
511
|
+
if (!Array.isArray(program.selections) || program.selections.length === 0)
|
|
512
|
+
return invalid(`${field}.selections`);
|
|
513
|
+
const programSelections: MaterialCookProgram['selections'][number][] = [];
|
|
514
|
+
for (const [selectionIndex, selection] of program.selections.entries()) {
|
|
515
|
+
const selectionField = `${field}.selections[${selectionIndex}]`;
|
|
516
|
+
if (selection === null || typeof selection !== 'object' || !passNames.has(selection.pass))
|
|
517
|
+
return invalid(selectionField);
|
|
518
|
+
const context = validateMaterialCookProgramContext(selection.context);
|
|
519
|
+
if (!context.ok)
|
|
520
|
+
return invalid(
|
|
521
|
+
`${selectionField}.${context.error.detail.field}`,
|
|
522
|
+
context.error.detail.actual,
|
|
523
|
+
);
|
|
524
|
+
const key = JSON.stringify([selection.pass, materialProgramContextKey(context.value)]);
|
|
525
|
+
if (selections.has(key)) return invalid(selectionField, 'ambiguous Pass/context selection');
|
|
526
|
+
selections.add(key);
|
|
527
|
+
selectedPasses.add(selection.pass);
|
|
528
|
+
programSelections.push({ pass: selection.pass, context: context.value });
|
|
529
|
+
}
|
|
530
|
+
programs.push({
|
|
531
|
+
specializationKey: program.specializationKey,
|
|
532
|
+
artifact: { mediaType: 'text/wgsl', path: artifact.path, digest: artifact.digest, bytes },
|
|
533
|
+
selections: programSelections,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if ([...passNames].some((pass) => !selectedPasses.has(pass)))
|
|
537
|
+
return invalid('programs.selections', 'unpublished Pass');
|
|
538
|
+
const manifestDigest = createMaterialProgramSetDigest(programs, passes);
|
|
539
|
+
const receiptResult = validateMaterialCookReceipt(candidate.receipt, {
|
|
540
|
+
artifactDigest: manifestDigest,
|
|
541
|
+
});
|
|
542
|
+
if (!receiptResult.ok) return receiptResult;
|
|
514
543
|
let expectedLayerPlanIdentity: string | undefined;
|
|
515
544
|
try {
|
|
516
545
|
expectedLayerPlanIdentity = materialLayerPlanIdentity({
|
|
@@ -528,37 +557,18 @@ export function validateCookedMaterialRecord(
|
|
|
528
557
|
receiptResult.value.derivedInterface.layerPlanIdentity,
|
|
529
558
|
);
|
|
530
559
|
}
|
|
531
|
-
if (candidate.artifactDigest !== undefined && candidate.artifactDigest !==
|
|
560
|
+
if (candidate.artifactDigest !== undefined && candidate.artifactDigest !== manifestDigest)
|
|
532
561
|
return invalid('artifactDigest', candidate.artifactDigest);
|
|
533
562
|
if (
|
|
534
563
|
candidate.publicationGeneration !== undefined &&
|
|
535
564
|
receiptResult.value.identity.cookGeneration !== candidate.publicationGeneration
|
|
536
565
|
)
|
|
537
566
|
return invalid('receipt.identity.cookGeneration', receiptResult.value.identity.cookGeneration);
|
|
538
|
-
|
|
567
|
+
return ok({
|
|
539
568
|
...candidate,
|
|
540
|
-
|
|
541
|
-
...artifact,
|
|
542
|
-
bytes: normalizeArtifactBytes(artifact.bytes) as Uint8Array,
|
|
543
|
-
},
|
|
544
|
-
...(Array.isArray(candidate.variants)
|
|
545
|
-
? {
|
|
546
|
-
variants: candidate.variants.map((value) => {
|
|
547
|
-
const variant = value as Record<string, unknown>;
|
|
548
|
-
const variantArtifact = variant.artifact as Record<string, unknown>;
|
|
549
|
-
return {
|
|
550
|
-
...variant,
|
|
551
|
-
artifact: {
|
|
552
|
-
...variantArtifact,
|
|
553
|
-
bytes: normalizeArtifactBytes(variantArtifact.bytes) as Uint8Array,
|
|
554
|
-
},
|
|
555
|
-
};
|
|
556
|
-
}),
|
|
557
|
-
}
|
|
558
|
-
: {}),
|
|
569
|
+
programs,
|
|
559
570
|
receipt: receiptResult.value,
|
|
560
|
-
} as unknown as CookedMaterialRecord;
|
|
561
|
-
return ok(normalized);
|
|
571
|
+
} as unknown as CookedMaterialRecord);
|
|
562
572
|
}
|
|
563
573
|
|
|
564
574
|
export function projectCookedMaterialRecord(
|
|
@@ -567,10 +577,65 @@ export function projectCookedMaterialRecord(
|
|
|
567
577
|
return {
|
|
568
578
|
resolved: record.resolved,
|
|
569
579
|
refs: record.refs,
|
|
570
|
-
|
|
571
|
-
...(record.variantContext === undefined ? {} : { variantContext: record.variantContext }),
|
|
572
|
-
...(record.variants === undefined ? {} : { variants: record.variants }),
|
|
580
|
+
programs: record.programs,
|
|
573
581
|
receipt: record.receipt,
|
|
574
582
|
schemaVersion: record.schemaVersion,
|
|
575
583
|
};
|
|
576
584
|
}
|
|
585
|
+
|
|
586
|
+
/** Canonical context key shared by publication validation and runtime selection. */
|
|
587
|
+
export function materialProgramContextKey(context: MaterialCookProgramContext): string {
|
|
588
|
+
return JSON.stringify(jsonValue(context));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export function validateMaterialCookProgramContext(
|
|
592
|
+
value: unknown,
|
|
593
|
+
): Result<MaterialCookProgramContext, MaterialCookRecordError> {
|
|
594
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
595
|
+
return invalid('context', value);
|
|
596
|
+
const context = value as Record<string, unknown>;
|
|
597
|
+
const fields = {
|
|
598
|
+
backend: ['webgpu', 'webgl2', 'wgpu-native'],
|
|
599
|
+
capability: ['storage-buffer', 'uniform-fallback'],
|
|
600
|
+
pipeline: ['forward', 'deferred'],
|
|
601
|
+
geometry: ['mesh', 'skinned', 'sprite'],
|
|
602
|
+
pass: ['forward', 'shadow', 'depth'],
|
|
603
|
+
profile: ['forgeax-material-wgsl-v1'],
|
|
604
|
+
toolchain: ['naga-oil'],
|
|
605
|
+
instrumentation: ['none', 'validation'],
|
|
606
|
+
};
|
|
607
|
+
for (const field of Object.keys(context)) {
|
|
608
|
+
if (!(field in fields)) return invalid(`context.${field}`, context[field]);
|
|
609
|
+
}
|
|
610
|
+
for (const [field, allowed] of Object.entries(fields)) {
|
|
611
|
+
if (!allowed.includes(context[field] as string))
|
|
612
|
+
return invalid(`context.${field}`, context[field]);
|
|
613
|
+
}
|
|
614
|
+
return ok({ ...context } as unknown as MaterialCookProgramContext);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Covers every selection and artifact while excluding transport copies of the bytes. */
|
|
618
|
+
export function createMaterialProgramSetDigest(
|
|
619
|
+
programs: readonly MaterialCookProgram[],
|
|
620
|
+
passes: readonly MaterialPass[],
|
|
621
|
+
): string {
|
|
622
|
+
const manifest = {
|
|
623
|
+
passes,
|
|
624
|
+
programs: programs
|
|
625
|
+
.map((program) => ({
|
|
626
|
+
specializationKey: program.specializationKey,
|
|
627
|
+
artifact: {
|
|
628
|
+
path: program.artifact.path,
|
|
629
|
+
mediaType: program.artifact.mediaType,
|
|
630
|
+
digest: program.artifact.digest,
|
|
631
|
+
},
|
|
632
|
+
selections: [...program.selections].sort((a, b) =>
|
|
633
|
+
JSON.stringify(jsonValue(a)).localeCompare(JSON.stringify(jsonValue(b))),
|
|
634
|
+
),
|
|
635
|
+
}))
|
|
636
|
+
.sort((a, b) => a.specializationKey.localeCompare(b.specializationKey)),
|
|
637
|
+
};
|
|
638
|
+
return createMaterialArtifactDigest(
|
|
639
|
+
new TextEncoder().encode(JSON.stringify(jsonValue(manifest))),
|
|
640
|
+
);
|
|
641
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,23 +7,26 @@ export {
|
|
|
7
7
|
collectMaterialCookRefs,
|
|
8
8
|
createMaterialArtifactDigest,
|
|
9
9
|
createMaterialCookIdentity,
|
|
10
|
+
createMaterialProgramSetDigest,
|
|
10
11
|
isStandardMaterialRecord,
|
|
11
12
|
isStandardRootModule,
|
|
12
13
|
type MaterialCookArtifact,
|
|
13
14
|
type MaterialCookIdentity,
|
|
14
15
|
type MaterialCookIdentityExpectation,
|
|
15
16
|
type MaterialCookIdentityInput,
|
|
17
|
+
type MaterialCookProgram,
|
|
18
|
+
type MaterialCookProgramContext,
|
|
16
19
|
type MaterialCookReceipt,
|
|
17
20
|
type MaterialCookRecordError,
|
|
18
21
|
type MaterialCookRefs,
|
|
19
|
-
type MaterialCookVariant,
|
|
20
|
-
type MaterialCookVariantContext,
|
|
21
22
|
type MaterialCookWasmProvenance,
|
|
22
23
|
materialLayerPlanIdentity,
|
|
24
|
+
materialProgramContextKey,
|
|
23
25
|
projectCookedMaterialRecord,
|
|
24
26
|
serializeCookedMaterialRecord,
|
|
25
27
|
serializeMaterialCookReceipt,
|
|
26
28
|
validateCookedMaterialRecord,
|
|
29
|
+
validateMaterialCookProgramContext,
|
|
27
30
|
validateMaterialCookReceipt,
|
|
28
31
|
} from './evidence/material-cook.js';
|
|
29
32
|
export { buildOfflineAssetEvidence, packageVerification } from './evidence/offline-evidence.js';
|
package/src/material-cook.ts
CHANGED
|
@@ -3,16 +3,19 @@ export {
|
|
|
3
3
|
collectMaterialCookRefs,
|
|
4
4
|
createMaterialArtifactDigest,
|
|
5
5
|
createMaterialCookIdentity,
|
|
6
|
+
createMaterialProgramSetDigest,
|
|
6
7
|
type MaterialCookArtifact,
|
|
8
|
+
type MaterialCookProgram,
|
|
9
|
+
type MaterialCookProgramContext,
|
|
7
10
|
type MaterialCookReceipt,
|
|
8
11
|
type MaterialCookRecordError,
|
|
9
12
|
type MaterialCookRefs,
|
|
10
|
-
type MaterialCookVariant,
|
|
11
|
-
type MaterialCookVariantContext,
|
|
12
13
|
type MaterialCookWasmProvenance,
|
|
13
14
|
materialLayerPlanIdentity,
|
|
15
|
+
materialProgramContextKey,
|
|
14
16
|
projectCookedMaterialRecord,
|
|
15
17
|
serializeCookedMaterialRecord,
|
|
16
18
|
serializeMaterialCookReceipt,
|
|
17
19
|
validateCookedMaterialRecord,
|
|
20
|
+
validateMaterialCookProgramContext,
|
|
18
21
|
} from './evidence/material-cook.js';
|