@forgeax/engine-pack 0.1.24 → 0.1.26
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 +129 -26
- package/dist/build.mjs.map +1 -1
- package/dist/cli-asset.mjs.map +1 -1
- package/dist/evidence/material-cook.d.ts +29 -3
- 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 +123 -24
- 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 +123 -24
- package/dist/material-cook.mjs.map +1 -1
- package/dist/pack-authoring-node.mjs.map +1 -1
- package/dist/scanner.d.ts +3 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.mjs +7 -3
- package/dist/scanner.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-declaration-boundary.unit.test.ts +35 -0
- package/src/__tests__/material-surface-consumer-census.test.ts +24 -0
- package/src/__tests__/material-surface-publication.unit.test.ts +162 -0
- package/src/__tests__/material-surface-wire.unit.test.ts +30 -0
- package/src/build.ts +2 -0
- package/src/evidence/material-cook.ts +183 -31
- package/src/index.ts +5 -0
- package/src/material-cook.ts +5 -0
- package/src/scanner.ts +7 -3
- package/src/schema/material-cook.schema.json +254 -50
- package/dist/__tests__/ai-recovery.unit.test.d.ts +0 -2
- package/dist/__tests__/ai-recovery.unit.test.d.ts.map +0 -1
- package/dist/__tests__/artifact-path.test.d.ts +0 -2
- package/dist/__tests__/artifact-path.test.d.ts.map +0 -1
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts +0 -2
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +0 -1
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +0 -2
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +0 -1
- package/dist/__tests__/asset-evidence-schema.test.d.ts +0 -2
- package/dist/__tests__/asset-evidence-schema.test.d.ts.map +0 -1
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +0 -2
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +0 -1
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +0 -2
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +0 -1
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +0 -2
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +0 -1
- package/dist/__tests__/cli.unit.test.d.ts +0 -2
- package/dist/__tests__/cli.unit.test.d.ts.map +0 -1
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +0 -2
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +0 -1
- package/dist/__tests__/guid-collision.unit.test.d.ts +0 -2
- package/dist/__tests__/guid-collision.unit.test.d.ts.map +0 -1
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/inventory-schema.test.d.ts +0 -2
- package/dist/__tests__/inventory-schema.test.d.ts.map +0 -1
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +0 -2
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts +0 -2
- package/dist/__tests__/material-cook-receipt-layer-plan.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts +0 -2
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-cook-schema.unit.test.d.ts +0 -2
- package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +0 -1
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +0 -2
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +0 -1
- package/dist/__tests__/meta-schema-optional.test.d.ts +0 -2
- package/dist/__tests__/meta-schema-optional.test.d.ts.map +0 -1
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts +0 -2
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +0 -1
- package/dist/__tests__/native-cooker-registry.test.d.ts +0 -2
- package/dist/__tests__/native-cooker-registry.test.d.ts.map +0 -1
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +0 -2
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +0 -1
- package/dist/__tests__/offline-evidence.integration.test.d.ts +0 -2
- package/dist/__tests__/offline-evidence.integration.test.d.ts.map +0 -1
- package/dist/__tests__/owner-chain.integration.test.d.ts +0 -2
- package/dist/__tests__/owner-chain.integration.test.d.ts.map +0 -1
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts +0 -2
- package/dist/__tests__/pack-authoring-gateway.unit.test.d.ts.map +0 -1
- package/dist/__tests__/pack-authoring.unit.test.d.ts +0 -2
- package/dist/__tests__/pack-authoring.unit.test.d.ts.map +0 -1
- package/dist/__tests__/pack-scanner.unit.test.d.ts +0 -2
- package/dist/__tests__/pack-scanner.unit.test.d.ts.map +0 -1
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +0 -2
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +0 -1
- package/dist/__tests__/pack-v2-schema.test.d.ts +0 -2
- package/dist/__tests__/pack-v2-schema.test.d.ts.map +0 -1
- package/dist/__tests__/pack.unit.test.d.ts +0 -2
- package/dist/__tests__/pack.unit.test.d.ts.map +0 -1
- package/dist/__tests__/package-finalizer.contract.test.d.ts +0 -2
- package/dist/__tests__/package-finalizer.contract.test.d.ts.map +0 -1
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts +0 -2
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +0 -1
- package/dist/__tests__/resolve-asset-source.test.d.ts +0 -2
- package/dist/__tests__/resolve-asset-source.test.d.ts.map +0 -1
- package/dist/__tests__/runtime-projection.unit.test.d.ts +0 -2
- package/dist/__tests__/runtime-projection.unit.test.d.ts.map +0 -1
- package/dist/__tests__/runtime.browser.test.d.ts +0 -2
- package/dist/__tests__/runtime.browser.test.d.ts.map +0 -1
- package/dist/__tests__/scanner-blacklist.test.d.ts +0 -2
- package/dist/__tests__/scanner-blacklist.test.d.ts.map +0 -1
- package/dist/__tests__/scanner-inventory.contract.test.d.ts +0 -2
- package/dist/__tests__/scanner-inventory.contract.test.d.ts.map +0 -1
- package/dist/__tests__/scanner-inventory.test.d.ts +0 -2
- package/dist/__tests__/scanner-inventory.test.d.ts.map +0 -1
- package/dist/__tests__/scanner-mount-cycle.test.d.ts +0 -2
- package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +0 -1
- package/dist/__tests__/topology-actions.unit.test.d.ts +0 -2
- package/dist/__tests__/topology-actions.unit.test.d.ts.map +0 -1
- package/dist/__tests__/topology.unit.test.d.ts +0 -2
- package/dist/__tests__/topology.unit.test.d.ts.map +0 -1
|
@@ -25,6 +25,28 @@ export interface MaterialCookArtifact {
|
|
|
25
25
|
readonly bytes: Uint8Array;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** Domain-owned inputs used to compile and select a material program. */
|
|
29
|
+
export interface MaterialCookProgramContext {
|
|
30
|
+
readonly backend: 'webgpu' | 'webgl2' | 'wgpu-native';
|
|
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';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Programs are derived artifacts, never independently authored assets. */
|
|
41
|
+
export interface MaterialCookProgram {
|
|
42
|
+
readonly specializationKey: string;
|
|
43
|
+
readonly artifact: MaterialCookArtifact;
|
|
44
|
+
readonly selections: readonly {
|
|
45
|
+
readonly pass: string;
|
|
46
|
+
readonly context: MaterialCookProgramContext;
|
|
47
|
+
}[];
|
|
48
|
+
}
|
|
49
|
+
|
|
28
50
|
export interface MaterialCookWasmProvenance {
|
|
29
51
|
readonly sourceContentKey: string;
|
|
30
52
|
readonly artifactSha256: string;
|
|
@@ -51,7 +73,7 @@ export interface MaterialCookIdentity {
|
|
|
51
73
|
export type MaterialCookIdentityInput = Omit<MaterialCookIdentity, 'cookIdentity'>;
|
|
52
74
|
|
|
53
75
|
export interface MaterialCookReceipt {
|
|
54
|
-
readonly schemaVersion: 'material-cook/
|
|
76
|
+
readonly schemaVersion: 'material-cook/4';
|
|
55
77
|
readonly sourceClosure: readonly string[];
|
|
56
78
|
readonly profile: string;
|
|
57
79
|
readonly compilerVersion: string;
|
|
@@ -69,12 +91,14 @@ export interface MaterialParameterContract {
|
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
export interface CookedMaterialRecord {
|
|
72
|
-
readonly schemaVersion: 'material-cook/
|
|
94
|
+
readonly schemaVersion: 'material-cook/4';
|
|
73
95
|
readonly guid: string;
|
|
74
96
|
readonly authored?: MaterialAsset;
|
|
75
97
|
readonly materialGuid?: string;
|
|
76
98
|
readonly publicationGeneration?: number;
|
|
77
99
|
readonly specializationKey?: string;
|
|
100
|
+
readonly programs: readonly MaterialCookProgram[];
|
|
101
|
+
/** Digest of the complete program/Pass/context manifest. */
|
|
78
102
|
readonly artifactDigest?: string;
|
|
79
103
|
readonly sourceClosure?: readonly string[];
|
|
80
104
|
readonly parameterContract?: MaterialParameterContract;
|
|
@@ -84,7 +108,6 @@ export interface CookedMaterialRecord {
|
|
|
84
108
|
readonly values: Readonly<Record<string, MaterialValue | null>>;
|
|
85
109
|
};
|
|
86
110
|
readonly refs: MaterialCookRefs;
|
|
87
|
-
readonly artifact: MaterialCookArtifact;
|
|
88
111
|
readonly receipt: MaterialCookReceipt;
|
|
89
112
|
}
|
|
90
113
|
|
|
@@ -248,7 +271,7 @@ export function serializeMaterialCookReceipt(receipt: MaterialCookReceipt): stri
|
|
|
248
271
|
function invalid(field: string, actual?: unknown): Result<never, MaterialCookRecordError> {
|
|
249
272
|
return err({
|
|
250
273
|
code: 'material-cook-record-invalid',
|
|
251
|
-
expected: 'a complete material-cook/
|
|
274
|
+
expected: 'a complete material-cook/4 record with layered identity and provenance',
|
|
252
275
|
hint: 're-cook the material and publish its record, artifact, references, and receipt together',
|
|
253
276
|
detail: {
|
|
254
277
|
field,
|
|
@@ -258,6 +281,17 @@ function invalid(field: string, actual?: unknown): Result<never, MaterialCookRec
|
|
|
258
281
|
});
|
|
259
282
|
}
|
|
260
283
|
|
|
284
|
+
function normalizeArtifactBytes(value: unknown): Uint8Array | undefined {
|
|
285
|
+
if (Array.isArray(value)) {
|
|
286
|
+
return value.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)
|
|
287
|
+
? Uint8Array.from(value)
|
|
288
|
+
: undefined;
|
|
289
|
+
}
|
|
290
|
+
if (!ArrayBuffer.isView(value)) return undefined;
|
|
291
|
+
const view = value as ArrayBufferView;
|
|
292
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength).slice();
|
|
293
|
+
}
|
|
294
|
+
|
|
261
295
|
const IDENTITY_FIELDS = [
|
|
262
296
|
'materialContractDigest',
|
|
263
297
|
'sourceRevision',
|
|
@@ -308,7 +342,7 @@ export function validateMaterialCookReceipt(
|
|
|
308
342
|
): Result<MaterialCookReceipt, MaterialCookRecordError> {
|
|
309
343
|
if (value === null || typeof value !== 'object') return invalid('receipt');
|
|
310
344
|
const candidate = value as Record<string, unknown>;
|
|
311
|
-
if (candidate.schemaVersion !== 'material-cook/
|
|
345
|
+
if (candidate.schemaVersion !== 'material-cook/4')
|
|
312
346
|
return invalid('receipt.schemaVersion', candidate.schemaVersion);
|
|
313
347
|
const identityResult = validateIdentity(candidate.identity);
|
|
314
348
|
if (!identityResult.ok) return identityResult;
|
|
@@ -359,7 +393,7 @@ export function validateMaterialCookReceipt(
|
|
|
359
393
|
return invalid('receipt.identity.cookIdentity', identity.cookIdentity);
|
|
360
394
|
}
|
|
361
395
|
return ok({
|
|
362
|
-
schemaVersion: 'material-cook/
|
|
396
|
+
schemaVersion: 'material-cook/4',
|
|
363
397
|
sourceClosure: candidate.sourceClosure as string[],
|
|
364
398
|
profile: candidate.profile as string,
|
|
365
399
|
compilerVersion: candidate.compilerVersion as string,
|
|
@@ -378,7 +412,7 @@ export function validateCookedMaterialRecord(
|
|
|
378
412
|
): Result<CookedMaterialRecord, MaterialCookRecordError> {
|
|
379
413
|
if (value === null || typeof value !== 'object') return invalid('record');
|
|
380
414
|
const candidate = value as Record<string, unknown>;
|
|
381
|
-
if (candidate.schemaVersion !== 'material-cook/
|
|
415
|
+
if (candidate.schemaVersion !== 'material-cook/4')
|
|
382
416
|
return invalid('schemaVersion', candidate.schemaVersion);
|
|
383
417
|
if (typeof candidate.guid !== 'string' || !candidate.guid) return invalid('guid');
|
|
384
418
|
if (candidate.materialGuid !== undefined && typeof candidate.materialGuid !== 'string')
|
|
@@ -390,6 +424,8 @@ export function validateCookedMaterialRecord(
|
|
|
390
424
|
return invalid('publicationGeneration', candidate.publicationGeneration);
|
|
391
425
|
if (candidate.specializationKey !== undefined && typeof candidate.specializationKey !== 'string')
|
|
392
426
|
return invalid('specializationKey');
|
|
427
|
+
if ('artifact' in candidate || 'variants' in candidate || 'variantContext' in candidate)
|
|
428
|
+
return invalid('programs', 'legacy single-artifact publication');
|
|
393
429
|
if (candidate.artifactDigest !== undefined && typeof candidate.artifactDigest !== 'string')
|
|
394
430
|
return invalid('artifactDigest');
|
|
395
431
|
if (
|
|
@@ -414,21 +450,8 @@ export function validateCookedMaterialRecord(
|
|
|
414
450
|
if (candidate.resolved === null || typeof candidate.resolved !== 'object')
|
|
415
451
|
return invalid('resolved');
|
|
416
452
|
if (candidate.refs === null || typeof candidate.refs !== 'object') return invalid('refs');
|
|
417
|
-
if (candidate.artifact === null || typeof candidate.artifact !== 'object')
|
|
418
|
-
return invalid('artifact');
|
|
419
453
|
if (candidate.receipt === null || typeof candidate.receipt !== 'object')
|
|
420
454
|
return invalid('receipt');
|
|
421
|
-
const artifact = candidate.artifact as Record<string, unknown>;
|
|
422
|
-
if (
|
|
423
|
-
typeof artifact.digest !== 'string' ||
|
|
424
|
-
(!Array.isArray(artifact.bytes) && !ArrayBuffer.isView(artifact.bytes))
|
|
425
|
-
) {
|
|
426
|
-
return invalid('artifact');
|
|
427
|
-
}
|
|
428
|
-
const receiptResult = validateMaterialCookReceipt(candidate.receipt, {
|
|
429
|
-
artifactDigest: artifact.digest as string,
|
|
430
|
-
});
|
|
431
|
-
if (!receiptResult.ok) return receiptResult;
|
|
432
455
|
const resolved = candidate.resolved as Record<string, unknown>;
|
|
433
456
|
if (!Array.isArray(resolved.passes)) return invalid('resolved.passes', resolved.passes);
|
|
434
457
|
if (!Array.isArray(resolved.parameters))
|
|
@@ -439,6 +462,84 @@ export function validateCookedMaterialRecord(
|
|
|
439
462
|
Array.isArray(resolved.values)
|
|
440
463
|
)
|
|
441
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;
|
|
442
543
|
let expectedLayerPlanIdentity: string | undefined;
|
|
443
544
|
try {
|
|
444
545
|
expectedLayerPlanIdentity = materialLayerPlanIdentity({
|
|
@@ -456,24 +557,18 @@ export function validateCookedMaterialRecord(
|
|
|
456
557
|
receiptResult.value.derivedInterface.layerPlanIdentity,
|
|
457
558
|
);
|
|
458
559
|
}
|
|
459
|
-
if (candidate.artifactDigest !== undefined && candidate.artifactDigest !==
|
|
560
|
+
if (candidate.artifactDigest !== undefined && candidate.artifactDigest !== manifestDigest)
|
|
460
561
|
return invalid('artifactDigest', candidate.artifactDigest);
|
|
461
562
|
if (
|
|
462
563
|
candidate.publicationGeneration !== undefined &&
|
|
463
564
|
receiptResult.value.identity.cookGeneration !== candidate.publicationGeneration
|
|
464
565
|
)
|
|
465
566
|
return invalid('receipt.identity.cookGeneration', receiptResult.value.identity.cookGeneration);
|
|
466
|
-
|
|
567
|
+
return ok({
|
|
467
568
|
...candidate,
|
|
468
|
-
|
|
469
|
-
...artifact,
|
|
470
|
-
bytes: ArrayBuffer.isView(artifact.bytes)
|
|
471
|
-
? Uint8Array.from(artifact.bytes as Uint8Array)
|
|
472
|
-
: Uint8Array.from(artifact.bytes as number[]),
|
|
473
|
-
},
|
|
569
|
+
programs,
|
|
474
570
|
receipt: receiptResult.value,
|
|
475
|
-
} as CookedMaterialRecord;
|
|
476
|
-
return ok(normalized);
|
|
571
|
+
} as unknown as CookedMaterialRecord);
|
|
477
572
|
}
|
|
478
573
|
|
|
479
574
|
export function projectCookedMaterialRecord(
|
|
@@ -482,8 +577,65 @@ export function projectCookedMaterialRecord(
|
|
|
482
577
|
return {
|
|
483
578
|
resolved: record.resolved,
|
|
484
579
|
refs: record.refs,
|
|
485
|
-
|
|
580
|
+
programs: record.programs,
|
|
486
581
|
receipt: record.receipt,
|
|
487
582
|
schemaVersion: record.schemaVersion,
|
|
488
583
|
};
|
|
489
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,21 +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
22
|
type MaterialCookWasmProvenance,
|
|
20
23
|
materialLayerPlanIdentity,
|
|
24
|
+
materialProgramContextKey,
|
|
21
25
|
projectCookedMaterialRecord,
|
|
22
26
|
serializeCookedMaterialRecord,
|
|
23
27
|
serializeMaterialCookReceipt,
|
|
24
28
|
validateCookedMaterialRecord,
|
|
29
|
+
validateMaterialCookProgramContext,
|
|
25
30
|
validateMaterialCookReceipt,
|
|
26
31
|
} from './evidence/material-cook.js';
|
|
27
32
|
export { buildOfflineAssetEvidence, packageVerification } from './evidence/offline-evidence.js';
|
package/src/material-cook.ts
CHANGED
|
@@ -3,14 +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
13
|
type MaterialCookWasmProvenance,
|
|
11
14
|
materialLayerPlanIdentity,
|
|
15
|
+
materialProgramContextKey,
|
|
12
16
|
projectCookedMaterialRecord,
|
|
13
17
|
serializeCookedMaterialRecord,
|
|
14
18
|
serializeMaterialCookReceipt,
|
|
15
19
|
validateCookedMaterialRecord,
|
|
20
|
+
validateMaterialCookProgramContext,
|
|
16
21
|
} from './evidence/material-cook.js';
|
package/src/scanner.ts
CHANGED
|
@@ -62,9 +62,13 @@ export interface ScriptablePackScanOptions {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** Stable default policy shared by inventory and production owners. */
|
|
65
|
-
export const STANDARD_SCRIPTABLE_PACK_SCAN_OPTIONS = Object.freeze(
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
export const STANDARD_SCRIPTABLE_PACK_SCAN_OPTIONS = Object.freeze({
|
|
66
|
+
// Scriptable Packs execute in an isolated worker. A cold worker must
|
|
67
|
+
// compile the authored source closure before it can return metadata;
|
|
68
|
+
// heavy procedural packs otherwise make the shared catalog fall back to
|
|
69
|
+
// an empty degraded projection at the old 15-second budget.
|
|
70
|
+
timeoutMs: 60_000,
|
|
71
|
+
}) satisfies ScriptablePackScanOptions;
|
|
68
72
|
|
|
69
73
|
export interface InventoryDeclaration {
|
|
70
74
|
readonly guid: string;
|