@forgeax/engine-pack 0.0.0-dev.8d955ade1c79
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 +202 -0
- package/README.md +429 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-path.test.d.ts +2 -0
- package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
- package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/cli.unit.test.d.ts +2 -0
- package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-asset-config.test.d.ts +2 -0
- package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
- package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
- package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
- package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
- package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
- package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
- package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
- package/dist/__tests__/pack.unit.test.d.ts +2 -0
- package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/package-finalizer.contract.test.d.ts +2 -0
- package/dist/__tests__/package-finalizer.contract.test.d.ts.map +1 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
- package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/runtime.browser.test.d.ts +2 -0
- package/dist/__tests__/runtime.browser.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.contract.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
- package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
- package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
- package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
- package/dist/__tests__/topology.unit.test.d.ts +2 -0
- package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
- package/dist/artifact-path.d.ts +9 -0
- package/dist/artifact-path.d.ts.map +1 -0
- package/dist/artifact-path.mjs +53 -0
- package/dist/artifact-path.mjs.map +1 -0
- package/dist/atlas/run-atlas.d.ts +8 -0
- package/dist/atlas/run-atlas.d.ts.map +1 -0
- package/dist/atlas/shelf-pack.d.ts +55 -0
- package/dist/atlas/shelf-pack.d.ts.map +1 -0
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.mjs +3 -0
- package/dist/bridge.mjs.map +1 -0
- package/dist/build.d.ts +19 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.mjs +10203 -0
- package/dist/build.mjs.map +1 -0
- package/dist/builtin.d.ts +44 -0
- package/dist/builtin.d.ts.map +1 -0
- package/dist/builtin.mjs +141 -0
- package/dist/builtin.mjs.map +1 -0
- package/dist/catalog-builder.d.ts +43 -0
- package/dist/catalog-builder.d.ts.map +1 -0
- package/dist/catalog-delta.d.ts +11 -0
- package/dist/catalog-delta.d.ts.map +1 -0
- package/dist/catalog-projection.d.ts +91 -0
- package/dist/catalog-projection.d.ts.map +1 -0
- package/dist/cli-asset.d.ts +22 -0
- package/dist/cli-asset.d.ts.map +1 -0
- package/dist/cli-asset.mjs +9697 -0
- package/dist/cli-asset.mjs.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.mjs +29 -0
- package/dist/config.mjs.map +1 -0
- package/dist/deriveAssetName.d.ts +15 -0
- package/dist/deriveAssetName.d.ts.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +19 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/evidence/material-cook.d.ts +92 -0
- package/dist/evidence/material-cook.d.ts.map +1 -0
- package/dist/evidence/offline-evidence.d.ts +25 -0
- package/dist/evidence/offline-evidence.d.ts.map +1 -0
- package/dist/evidence/source-inventory.d.ts +29 -0
- package/dist/evidence/source-inventory.d.ts.map +1 -0
- package/dist/guid.d.ts +45 -0
- package/dist/guid.d.ts.map +1 -0
- package/dist/guid.mjs +94 -0
- package/dist/guid.mjs.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +8612 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-writer.d.ts +14 -0
- package/dist/material/artifact-writer.d.ts.map +1 -0
- package/dist/material-cook.d.ts +2 -0
- package/dist/material-cook.d.ts.map +1 -0
- package/dist/material-cook.mjs +248 -0
- package/dist/material-cook.mjs.map +1 -0
- package/dist/mesh-bin-contract.d.ts +36 -0
- package/dist/mesh-bin-contract.d.ts.map +1 -0
- package/dist/mesh-bin-contract.mjs +96 -0
- package/dist/mesh-bin-contract.mjs.map +1 -0
- package/dist/name.mjs +22 -0
- package/dist/name.mjs.map +1 -0
- package/dist/native-cooker-registry.d.ts +47 -0
- package/dist/native-cooker-registry.d.ts.map +1 -0
- package/dist/native-cooker.d.ts +3 -0
- package/dist/native-cooker.d.ts.map +1 -0
- package/dist/native-cooker.mjs +142 -0
- package/dist/native-cooker.mjs.map +1 -0
- package/dist/package-finalizer.d.ts +121 -0
- package/dist/package-finalizer.d.ts.map +1 -0
- package/dist/producer-contract.d.ts +19 -0
- package/dist/producer-contract.d.ts.map +1 -0
- package/dist/resolve-asset-source.d.ts +4 -0
- package/dist/resolve-asset-source.d.ts.map +1 -0
- package/dist/resolve-asset-source.mjs +109 -0
- package/dist/resolve-asset-source.mjs.map +1 -0
- package/dist/runtime-projection.d.ts +14 -0
- package/dist/runtime-projection.d.ts.map +1 -0
- package/dist/runtime-publication.d.ts +44 -0
- package/dist/runtime-publication.d.ts.map +1 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.mjs +7261 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/scanner.d.ts +113 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.mjs +8819 -0
- package/dist/scanner.mjs.map +1 -0
- package/dist/schema-compiled.d.ts +12 -0
- package/dist/schema-compiled.d.ts.map +1 -0
- package/dist/schema.mjs +7369 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/scriptable-pack-node.d.ts +62 -0
- package/dist/scriptable-pack-node.d.ts.map +1 -0
- package/dist/scriptable-pack-node.mjs +1674 -0
- package/dist/scriptable-pack-node.mjs.map +1 -0
- package/dist/scriptable-pack-worker.d.ts +2 -0
- package/dist/scriptable-pack-worker.d.ts.map +1 -0
- package/dist/scriptable-pack-worker.mjs +224 -0
- package/dist/scriptable-pack-worker.mjs.map +1 -0
- package/dist/scriptable-pack.d.ts +558 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-pack.mjs +672 -0
- package/dist/scriptable-pack.mjs.map +1 -0
- package/dist/topology.d.ts +10 -0
- package/dist/topology.d.ts.map +1 -0
- package/package.json +154 -0
- package/schema/meta.schema.json +173 -0
- package/schema/pack.schema.json +232 -0
- package/src/__tests__/ai-recovery.unit.test.ts +114 -0
- package/src/__tests__/artifact-path.test.ts +41 -0
- package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
- package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
- package/src/__tests__/asset-evidence-schema.test.ts +86 -0
- package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
- package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
- package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
- package/src/__tests__/cli.unit.test.ts +448 -0
- package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
- package/src/__tests__/guid-collision.unit.test.ts +112 -0
- package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
- package/src/__tests__/load-asset-config.test.ts +121 -0
- package/src/__tests__/material-cook-dependencies.unit.test.ts +112 -0
- package/src/__tests__/material-cook-receipt.unit.test.ts +148 -0
- package/src/__tests__/material-cook-schema.unit.test.ts +152 -0
- package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +22 -0
- package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
- package/src/__tests__/meta-schema-optional.test.ts +116 -0
- package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
- package/src/__tests__/native-cooker-registry.test.ts +64 -0
- package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
- package/src/__tests__/offline-evidence.integration.test.ts +106 -0
- package/src/__tests__/owner-chain.integration.test.ts +78 -0
- package/src/__tests__/pack-v2-fixture-migration.test.ts +87 -0
- package/src/__tests__/pack-v2-schema.test.ts +142 -0
- package/src/__tests__/pack.unit.test.ts +2218 -0
- package/src/__tests__/package-finalizer.contract.test.ts +105 -0
- package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
- package/src/__tests__/resolve-asset-source.test.ts +121 -0
- package/src/__tests__/runtime-projection.unit.test.ts +87 -0
- package/src/__tests__/runtime.browser.test.ts +15 -0
- package/src/__tests__/scanner-inventory.contract.test.ts +167 -0
- package/src/__tests__/scanner-inventory.test.ts +76 -0
- package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
- package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
- package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
- package/src/__tests__/scriptable-pack.test-d.ts +131 -0
- package/src/__tests__/scriptable-pack.unit.test.ts +789 -0
- package/src/__tests__/topology-actions.unit.test.ts +94 -0
- package/src/__tests__/topology.unit.test.ts +85 -0
- package/src/artifact-path.ts +69 -0
- package/src/atlas/run-atlas.ts +358 -0
- package/src/atlas/shelf-pack.ts +172 -0
- package/src/atlas/upng-ambient.d.ts +13 -0
- package/src/bridge.ts +2 -0
- package/src/build.ts +139 -0
- package/src/builtin.ts +117 -0
- package/src/catalog-builder.ts +313 -0
- package/src/catalog-delta.ts +189 -0
- package/src/catalog-projection.ts +344 -0
- package/src/cli-asset.ts +586 -0
- package/src/config.ts +36 -0
- package/src/deriveAssetName.ts +42 -0
- package/src/errors.ts +47 -0
- package/src/evidence/material-cook.ts +411 -0
- package/src/evidence/offline-evidence.ts +90 -0
- package/src/evidence/source-inventory.ts +193 -0
- package/src/guid.ts +99 -0
- package/src/index.ts +108 -0
- package/src/material/artifact-writer.ts +26 -0
- package/src/material-cook.ts +15 -0
- package/src/mesh-bin-contract.ts +150 -0
- package/src/native-cooker-registry.ts +235 -0
- package/src/native-cooker.ts +2 -0
- package/src/package-finalizer.ts +394 -0
- package/src/producer-contract.ts +277 -0
- package/src/resolve-asset-source.ts +88 -0
- package/src/runtime-projection.ts +100 -0
- package/src/runtime-publication.ts +190 -0
- package/src/runtime.ts +23 -0
- package/src/scanner.ts +715 -0
- package/src/schema/material-cook.schema.json +119 -0
- package/src/schema-compiled.ts +238 -0
- package/src/scriptable-pack-node.ts +1394 -0
- package/src/scriptable-pack-worker.ts +308 -0
- package/src/scriptable-pack.ts +1006 -0
- package/src/topology.ts +157 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// errors.ts — PackError class (feat-20260513-guid-asset-package-system w15)
|
|
2
|
+
//
|
|
3
|
+
// Four-field surface: .code / .expected / .hint / .detail
|
|
4
|
+
// Structurally parallel to AssetError / RhiError / RemoteError
|
|
5
|
+
// (charter proposition 5 consistent abstraction).
|
|
6
|
+
//
|
|
7
|
+
// .detail is narrowed per .code via PackErrorDetail discriminated union
|
|
8
|
+
// (requirements §6.2 AC-07; plan-strategy §D-5).
|
|
9
|
+
|
|
10
|
+
import type { PackErrorCode, PackErrorDetail } from '@forgeax/engine-types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Structured error for the engine-pack disk scanner fail-fast chain.
|
|
14
|
+
*
|
|
15
|
+
* AI users consume via exhaustive switch on .code — no default case needed:
|
|
16
|
+
* ```ts
|
|
17
|
+
* switch (err.code) {
|
|
18
|
+
* case 'pack-guid-collision':
|
|
19
|
+
* console.error(err.detail.paths);
|
|
20
|
+
* break;
|
|
21
|
+
* case 'pack-cyclic-reference':
|
|
22
|
+
* console.error(err.detail.cycle);
|
|
23
|
+
* break;
|
|
24
|
+
* // ...all 8 cases
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class PackError extends Error {
|
|
29
|
+
readonly code: PackErrorCode;
|
|
30
|
+
readonly expected: string;
|
|
31
|
+
readonly hint: string;
|
|
32
|
+
readonly detail: PackErrorDetail;
|
|
33
|
+
|
|
34
|
+
constructor(args: {
|
|
35
|
+
code: PackErrorCode;
|
|
36
|
+
expected: string;
|
|
37
|
+
hint: string;
|
|
38
|
+
detail: PackErrorDetail;
|
|
39
|
+
}) {
|
|
40
|
+
super(`[PackError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);
|
|
41
|
+
this.name = 'PackError';
|
|
42
|
+
this.code = args.code;
|
|
43
|
+
this.expected = args.expected;
|
|
44
|
+
this.hint = args.hint;
|
|
45
|
+
this.detail = args.detail;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MaterialAsset,
|
|
3
|
+
MaterialParameter,
|
|
4
|
+
MaterialPass,
|
|
5
|
+
MaterialTextureReference,
|
|
6
|
+
MaterialTextureValue,
|
|
7
|
+
MaterialValue,
|
|
8
|
+
Result,
|
|
9
|
+
} from '@forgeax/engine-types';
|
|
10
|
+
import { err, MATERIAL_TEXTURE_SLOTS, ok } from '@forgeax/engine-types';
|
|
11
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
12
|
+
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
13
|
+
|
|
14
|
+
export interface MaterialCookRefs {
|
|
15
|
+
readonly parent: readonly string[];
|
|
16
|
+
readonly textures: readonly string[];
|
|
17
|
+
readonly samplers: readonly string[];
|
|
18
|
+
readonly modules: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MaterialCookArtifact {
|
|
22
|
+
readonly mediaType: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly digest: string;
|
|
25
|
+
readonly bytes: Uint8Array;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MaterialCookWasmProvenance {
|
|
29
|
+
readonly sourceContentKey: string;
|
|
30
|
+
readonly artifactSha256: string;
|
|
31
|
+
readonly glueSha256: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MaterialCookIdentity {
|
|
35
|
+
readonly materialContractDigest: string;
|
|
36
|
+
readonly sourceRevision: string;
|
|
37
|
+
readonly sourceClosureDigest: string;
|
|
38
|
+
readonly layoutIdentity: string;
|
|
39
|
+
readonly programIdentity: string;
|
|
40
|
+
readonly pipelineIdentity: string;
|
|
41
|
+
readonly materialPublicationIdentity: string;
|
|
42
|
+
readonly cookIdentity: string;
|
|
43
|
+
readonly compilerFingerprint: string;
|
|
44
|
+
readonly wasm: MaterialCookWasmProvenance;
|
|
45
|
+
readonly artifactDigest: string;
|
|
46
|
+
readonly valueGeneration: number;
|
|
47
|
+
readonly dependencyGeneration: number;
|
|
48
|
+
readonly cookGeneration: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type MaterialCookIdentityInput = Omit<MaterialCookIdentity, 'cookIdentity'>;
|
|
52
|
+
|
|
53
|
+
export interface MaterialCookReceipt {
|
|
54
|
+
readonly schemaVersion: 'material-cook/3';
|
|
55
|
+
readonly sourceClosure: readonly string[];
|
|
56
|
+
readonly profile: string;
|
|
57
|
+
readonly compilerVersion: string;
|
|
58
|
+
readonly identity: MaterialCookIdentity;
|
|
59
|
+
readonly derivedInterface: { readonly layoutIdentity: string };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MaterialParameterContract {
|
|
63
|
+
readonly parameters: readonly MaterialParameter[];
|
|
64
|
+
readonly values: Readonly<Record<string, MaterialValue | null>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CookedMaterialRecord {
|
|
68
|
+
readonly schemaVersion: 'material-cook/3';
|
|
69
|
+
readonly guid: string;
|
|
70
|
+
readonly authored?: MaterialAsset;
|
|
71
|
+
readonly materialGuid?: string;
|
|
72
|
+
readonly publicationGeneration?: number;
|
|
73
|
+
readonly specializationKey?: string;
|
|
74
|
+
readonly artifactDigest?: string;
|
|
75
|
+
readonly sourceClosure?: readonly string[];
|
|
76
|
+
readonly parameterContract?: MaterialParameterContract;
|
|
77
|
+
readonly resolved: {
|
|
78
|
+
readonly passes: readonly MaterialPass[];
|
|
79
|
+
readonly parameters: readonly MaterialParameter[];
|
|
80
|
+
readonly values: Readonly<Record<string, MaterialValue | null>>;
|
|
81
|
+
};
|
|
82
|
+
readonly refs: MaterialCookRefs;
|
|
83
|
+
readonly artifact: MaterialCookArtifact;
|
|
84
|
+
readonly receipt: MaterialCookReceipt;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MaterialCookRecordError {
|
|
88
|
+
readonly code: 'material-cook-record-invalid';
|
|
89
|
+
readonly expected: string;
|
|
90
|
+
readonly hint: string;
|
|
91
|
+
readonly detail: {
|
|
92
|
+
readonly field: string;
|
|
93
|
+
readonly actual?: unknown;
|
|
94
|
+
readonly action: string;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface MaterialCookIdentityExpectation {
|
|
99
|
+
readonly layoutIdentity?: string;
|
|
100
|
+
readonly artifactDigest?: string;
|
|
101
|
+
readonly inputDigest?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function unique(values: readonly string[]): readonly string[] {
|
|
105
|
+
return [...new Set(values)].sort();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function guidText(value: string | Uint8Array): string {
|
|
109
|
+
return typeof value === 'string'
|
|
110
|
+
? value
|
|
111
|
+
: Array.from(value, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function cookGuidText(value: MaterialTextureReference): string | undefined {
|
|
115
|
+
return typeof value === 'number' ? undefined : guidText(value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function textureValues(
|
|
119
|
+
values: Readonly<Record<string, MaterialValue | null>> | undefined,
|
|
120
|
+
textureFields: ReadonlySet<string>,
|
|
121
|
+
): readonly MaterialTextureValue[] {
|
|
122
|
+
return Object.entries(values ?? {}).flatMap(([name, value]) => {
|
|
123
|
+
if (value === null) return [];
|
|
124
|
+
if (typeof value === 'string') {
|
|
125
|
+
return textureFields.has(name) ? [{ texture: value }] : [];
|
|
126
|
+
}
|
|
127
|
+
return value !== null &&
|
|
128
|
+
typeof value === 'object' &&
|
|
129
|
+
!Array.isArray(value) &&
|
|
130
|
+
'texture' in value
|
|
131
|
+
? [value as MaterialTextureValue]
|
|
132
|
+
: [];
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function collectMaterialCookRefs(material: Partial<MaterialAsset>): MaterialCookRefs {
|
|
137
|
+
const textureFields =
|
|
138
|
+
material.parameters === undefined
|
|
139
|
+
? new Set<string>(MATERIAL_TEXTURE_SLOTS)
|
|
140
|
+
: new Set(
|
|
141
|
+
material.parameters
|
|
142
|
+
.filter((parameter) => parameter.type === 'texture')
|
|
143
|
+
.map((parameter) => parameter.name),
|
|
144
|
+
);
|
|
145
|
+
const textures = textureValues(material.values, textureFields);
|
|
146
|
+
return {
|
|
147
|
+
parent: material.parent ? [guidText(material.parent)] : [],
|
|
148
|
+
textures: unique(
|
|
149
|
+
textures.flatMap((value) => {
|
|
150
|
+
const guid = cookGuidText(value.texture);
|
|
151
|
+
return guid === undefined ? [] : [guid];
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
samplers: unique(
|
|
155
|
+
textures.flatMap((value) => {
|
|
156
|
+
if (value.sampler === undefined) return [];
|
|
157
|
+
const guid = cookGuidText(value.sampler);
|
|
158
|
+
return guid === undefined ? [] : [guid];
|
|
159
|
+
}),
|
|
160
|
+
),
|
|
161
|
+
modules: unique((material.passes ?? []).map((pass) => pass.program.module)),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createMaterialArtifactDigest(bytes: Uint8Array): string {
|
|
166
|
+
return `sha256:${bytesToHex(sha256(bytes))}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function jsonValue(value: unknown): unknown {
|
|
170
|
+
if (value instanceof Uint8Array) return [...value];
|
|
171
|
+
if (Array.isArray(value)) return value.map(jsonValue);
|
|
172
|
+
if (value !== null && typeof value === 'object') {
|
|
173
|
+
return Object.fromEntries(
|
|
174
|
+
Object.entries(value)
|
|
175
|
+
.filter(([, entry]) => entry !== undefined)
|
|
176
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
177
|
+
.map(([key, entry]) => [key, jsonValue(entry)]),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function createMaterialCookIdentity(input: MaterialCookIdentityInput): MaterialCookIdentity {
|
|
184
|
+
const cookIdentity = createMaterialArtifactDigest(
|
|
185
|
+
new TextEncoder().encode(
|
|
186
|
+
JSON.stringify(
|
|
187
|
+
jsonValue({
|
|
188
|
+
materialContractDigest: input.materialContractDigest,
|
|
189
|
+
sourceRevision: input.sourceRevision,
|
|
190
|
+
sourceClosureDigest: input.sourceClosureDigest,
|
|
191
|
+
layoutIdentity: input.layoutIdentity,
|
|
192
|
+
programIdentity: input.programIdentity,
|
|
193
|
+
pipelineIdentity: input.pipelineIdentity,
|
|
194
|
+
compilerFingerprint: input.compilerFingerprint,
|
|
195
|
+
wasm: input.wasm,
|
|
196
|
+
artifactDigest: input.artifactDigest,
|
|
197
|
+
}),
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
);
|
|
201
|
+
return { ...input, cookIdentity };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function serializeCookedMaterialRecord(record: CookedMaterialRecord): string {
|
|
205
|
+
return JSON.stringify(jsonValue(record));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function serializeMaterialCookReceipt(receipt: MaterialCookReceipt): string {
|
|
209
|
+
return JSON.stringify(jsonValue({ ...receipt, sourceClosure: unique(receipt.sourceClosure) }));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function invalid(field: string, actual?: unknown): Result<never, MaterialCookRecordError> {
|
|
213
|
+
return err({
|
|
214
|
+
code: 'material-cook-record-invalid',
|
|
215
|
+
expected: 'a complete material-cook/3 record with layered identity and provenance',
|
|
216
|
+
hint: 're-cook the material and publish its record, artifact, references, and receipt together',
|
|
217
|
+
detail: {
|
|
218
|
+
field,
|
|
219
|
+
...(actual === undefined ? {} : { actual }),
|
|
220
|
+
action: 'inspect the named field and recook the material generation',
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const IDENTITY_FIELDS = [
|
|
226
|
+
'materialContractDigest',
|
|
227
|
+
'sourceRevision',
|
|
228
|
+
'sourceClosureDigest',
|
|
229
|
+
'layoutIdentity',
|
|
230
|
+
'programIdentity',
|
|
231
|
+
'pipelineIdentity',
|
|
232
|
+
'materialPublicationIdentity',
|
|
233
|
+
'cookIdentity',
|
|
234
|
+
'compilerFingerprint',
|
|
235
|
+
'artifactDigest',
|
|
236
|
+
] as const;
|
|
237
|
+
|
|
238
|
+
function isGeneration(value: unknown): value is number {
|
|
239
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function validateIdentity(value: unknown): Result<MaterialCookIdentity, MaterialCookRecordError> {
|
|
243
|
+
if (value === null || typeof value !== 'object') return invalid('receipt.identity', value);
|
|
244
|
+
const candidate = value as Record<string, unknown>;
|
|
245
|
+
for (const field of IDENTITY_FIELDS) {
|
|
246
|
+
if (typeof candidate[field] !== 'string' || candidate[field].length === 0) {
|
|
247
|
+
return invalid(`receipt.identity.${field}`, candidate[field]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (candidate.wasm === null || typeof candidate.wasm !== 'object') {
|
|
251
|
+
return invalid('receipt.identity.wasm', candidate.wasm);
|
|
252
|
+
}
|
|
253
|
+
const wasm = candidate.wasm as Record<string, unknown>;
|
|
254
|
+
for (const field of ['sourceContentKey', 'artifactSha256', 'glueSha256']) {
|
|
255
|
+
if (typeof wasm[field] !== 'string' || wasm[field].length === 0) {
|
|
256
|
+
return invalid(`receipt.identity.wasm.${field}`, wasm[field]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const field of ['valueGeneration', 'dependencyGeneration', 'cookGeneration']) {
|
|
260
|
+
if (!isGeneration(candidate[field]))
|
|
261
|
+
return invalid(`receipt.identity.${field}`, candidate[field]);
|
|
262
|
+
}
|
|
263
|
+
return ok({
|
|
264
|
+
...candidate,
|
|
265
|
+
wasm: wasm as unknown as MaterialCookWasmProvenance,
|
|
266
|
+
} as MaterialCookIdentity);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function validateMaterialCookReceipt(
|
|
270
|
+
value: unknown,
|
|
271
|
+
expected: MaterialCookIdentityExpectation = {},
|
|
272
|
+
): Result<MaterialCookReceipt, MaterialCookRecordError> {
|
|
273
|
+
if (value === null || typeof value !== 'object') return invalid('receipt');
|
|
274
|
+
const candidate = value as Record<string, unknown>;
|
|
275
|
+
if (candidate.schemaVersion !== 'material-cook/3')
|
|
276
|
+
return invalid('receipt.schemaVersion', candidate.schemaVersion);
|
|
277
|
+
const identityResult = validateIdentity(candidate.identity);
|
|
278
|
+
if (!identityResult.ok) return identityResult;
|
|
279
|
+
const identity = identityResult.value;
|
|
280
|
+
if (candidate.derivedInterface === null || typeof candidate.derivedInterface !== 'object') {
|
|
281
|
+
return invalid('receipt.derivedInterface', candidate.derivedInterface);
|
|
282
|
+
}
|
|
283
|
+
const derivedInterface = candidate.derivedInterface as Record<string, unknown>;
|
|
284
|
+
if (derivedInterface.layoutIdentity !== identity.layoutIdentity) {
|
|
285
|
+
return invalid('receipt.derivedInterface.layoutIdentity', derivedInterface.layoutIdentity);
|
|
286
|
+
}
|
|
287
|
+
for (const field of ['sourceClosure', 'profile', 'compilerVersion']) {
|
|
288
|
+
const fieldValue = candidate[field];
|
|
289
|
+
if (
|
|
290
|
+
(field === 'sourceClosure' && !Array.isArray(fieldValue)) ||
|
|
291
|
+
(field !== 'sourceClosure' && typeof fieldValue !== 'string')
|
|
292
|
+
) {
|
|
293
|
+
return invalid(`receipt.${field}`, fieldValue);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const sourceClosure = candidate.sourceClosure;
|
|
297
|
+
if (Array.isArray(sourceClosure) && sourceClosure.some((path) => typeof path !== 'string')) {
|
|
298
|
+
return invalid('receipt.sourceClosure', sourceClosure);
|
|
299
|
+
}
|
|
300
|
+
if (
|
|
301
|
+
expected.layoutIdentity !== undefined &&
|
|
302
|
+
identity.layoutIdentity !== expected.layoutIdentity
|
|
303
|
+
) {
|
|
304
|
+
return invalid('receipt.identity.layoutIdentity', identity.layoutIdentity);
|
|
305
|
+
}
|
|
306
|
+
if (
|
|
307
|
+
expected.artifactDigest !== undefined &&
|
|
308
|
+
identity.artifactDigest !== expected.artifactDigest
|
|
309
|
+
) {
|
|
310
|
+
return invalid('receipt.identity.artifactDigest', identity.artifactDigest);
|
|
311
|
+
}
|
|
312
|
+
if (expected.inputDigest !== undefined && identity.cookIdentity !== expected.inputDigest) {
|
|
313
|
+
return invalid('receipt.identity.cookIdentity', identity.cookIdentity);
|
|
314
|
+
}
|
|
315
|
+
return ok({
|
|
316
|
+
schemaVersion: 'material-cook/3',
|
|
317
|
+
sourceClosure: candidate.sourceClosure as string[],
|
|
318
|
+
profile: candidate.profile as string,
|
|
319
|
+
compilerVersion: candidate.compilerVersion as string,
|
|
320
|
+
identity,
|
|
321
|
+
derivedInterface: { layoutIdentity: derivedInterface.layoutIdentity as string },
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function validateCookedMaterialRecord(
|
|
326
|
+
value: unknown,
|
|
327
|
+
): Result<CookedMaterialRecord, MaterialCookRecordError> {
|
|
328
|
+
if (value === null || typeof value !== 'object') return invalid('record');
|
|
329
|
+
const candidate = value as Record<string, unknown>;
|
|
330
|
+
if (candidate.schemaVersion !== 'material-cook/3')
|
|
331
|
+
return invalid('schemaVersion', candidate.schemaVersion);
|
|
332
|
+
if (typeof candidate.guid !== 'string' || !candidate.guid) return invalid('guid');
|
|
333
|
+
if (candidate.materialGuid !== undefined && typeof candidate.materialGuid !== 'string')
|
|
334
|
+
return invalid('materialGuid');
|
|
335
|
+
if (
|
|
336
|
+
candidate.publicationGeneration !== undefined &&
|
|
337
|
+
!isGeneration(candidate.publicationGeneration)
|
|
338
|
+
)
|
|
339
|
+
return invalid('publicationGeneration', candidate.publicationGeneration);
|
|
340
|
+
if (candidate.specializationKey !== undefined && typeof candidate.specializationKey !== 'string')
|
|
341
|
+
return invalid('specializationKey');
|
|
342
|
+
if (candidate.artifactDigest !== undefined && typeof candidate.artifactDigest !== 'string')
|
|
343
|
+
return invalid('artifactDigest');
|
|
344
|
+
if (
|
|
345
|
+
candidate.sourceClosure !== undefined &&
|
|
346
|
+
(!Array.isArray(candidate.sourceClosure) ||
|
|
347
|
+
candidate.sourceClosure.some((path) => typeof path !== 'string'))
|
|
348
|
+
)
|
|
349
|
+
return invalid('sourceClosure');
|
|
350
|
+
if (candidate.parameterContract !== undefined) {
|
|
351
|
+
if (candidate.parameterContract === null || typeof candidate.parameterContract !== 'object')
|
|
352
|
+
return invalid('parameterContract');
|
|
353
|
+
const parameterContract = candidate.parameterContract as Record<string, unknown>;
|
|
354
|
+
if (!Array.isArray(parameterContract.parameters))
|
|
355
|
+
return invalid('parameterContract.parameters');
|
|
356
|
+
if (
|
|
357
|
+
parameterContract.values === null ||
|
|
358
|
+
typeof parameterContract.values !== 'object' ||
|
|
359
|
+
Array.isArray(parameterContract.values)
|
|
360
|
+
)
|
|
361
|
+
return invalid('parameterContract.values');
|
|
362
|
+
}
|
|
363
|
+
if (candidate.resolved === null || typeof candidate.resolved !== 'object')
|
|
364
|
+
return invalid('resolved');
|
|
365
|
+
if (candidate.refs === null || typeof candidate.refs !== 'object') return invalid('refs');
|
|
366
|
+
if (candidate.artifact === null || typeof candidate.artifact !== 'object')
|
|
367
|
+
return invalid('artifact');
|
|
368
|
+
if (candidate.receipt === null || typeof candidate.receipt !== 'object')
|
|
369
|
+
return invalid('receipt');
|
|
370
|
+
const artifact = candidate.artifact as Record<string, unknown>;
|
|
371
|
+
if (
|
|
372
|
+
typeof artifact.digest !== 'string' ||
|
|
373
|
+
(!Array.isArray(artifact.bytes) && !ArrayBuffer.isView(artifact.bytes))
|
|
374
|
+
) {
|
|
375
|
+
return invalid('artifact');
|
|
376
|
+
}
|
|
377
|
+
const receiptResult = validateMaterialCookReceipt(candidate.receipt, {
|
|
378
|
+
artifactDigest: artifact.digest as string,
|
|
379
|
+
});
|
|
380
|
+
if (!receiptResult.ok) return receiptResult;
|
|
381
|
+
if (candidate.artifactDigest !== undefined && candidate.artifactDigest !== artifact.digest)
|
|
382
|
+
return invalid('artifactDigest', candidate.artifactDigest);
|
|
383
|
+
if (
|
|
384
|
+
candidate.publicationGeneration !== undefined &&
|
|
385
|
+
receiptResult.value.identity.cookGeneration !== candidate.publicationGeneration
|
|
386
|
+
)
|
|
387
|
+
return invalid('receipt.identity.cookGeneration', receiptResult.value.identity.cookGeneration);
|
|
388
|
+
const normalized = {
|
|
389
|
+
...candidate,
|
|
390
|
+
artifact: {
|
|
391
|
+
...artifact,
|
|
392
|
+
bytes: ArrayBuffer.isView(artifact.bytes)
|
|
393
|
+
? Uint8Array.from(artifact.bytes as Uint8Array)
|
|
394
|
+
: Uint8Array.from(artifact.bytes as number[]),
|
|
395
|
+
},
|
|
396
|
+
receipt: receiptResult.value,
|
|
397
|
+
} as CookedMaterialRecord;
|
|
398
|
+
return ok(normalized);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function projectCookedMaterialRecord(
|
|
402
|
+
record: CookedMaterialRecord,
|
|
403
|
+
): Omit<CookedMaterialRecord, 'guid' | 'authored'> {
|
|
404
|
+
return {
|
|
405
|
+
resolved: record.resolved,
|
|
406
|
+
refs: record.refs,
|
|
407
|
+
artifact: record.artifact,
|
|
408
|
+
receipt: record.receipt,
|
|
409
|
+
schemaVersion: record.schemaVersion,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ArtifactDescriptor,
|
|
3
|
+
ArtifactVerificationStatus,
|
|
4
|
+
AssetEvidence,
|
|
5
|
+
AssetEvidenceError,
|
|
6
|
+
AssetEvidenceInputs,
|
|
7
|
+
AssetEvidenceLocator,
|
|
8
|
+
CookProduct,
|
|
9
|
+
CookReceipt,
|
|
10
|
+
PackageVerificationEvidence,
|
|
11
|
+
SourceDeclarationEvidence,
|
|
12
|
+
} from '@forgeax/engine-types';
|
|
13
|
+
import {
|
|
14
|
+
projectAssetEvidence,
|
|
15
|
+
projectCookProductEvidence,
|
|
16
|
+
type Result,
|
|
17
|
+
} from '@forgeax/engine-types';
|
|
18
|
+
|
|
19
|
+
/** Pack artifact descriptor with verification supplied by an offline verifier. */
|
|
20
|
+
export interface OfflineArtifactInput extends ArtifactDescriptor {
|
|
21
|
+
readonly verification?: ArtifactVerificationStatus;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Validated Pack v2 package facts consumed by the offline projector. */
|
|
25
|
+
export interface OfflinePackageInput {
|
|
26
|
+
readonly guid: string;
|
|
27
|
+
readonly digest?: string;
|
|
28
|
+
readonly artifacts: Readonly<Record<string, OfflineArtifactInput>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Node/CLI input shape; it intentionally has no runtime or WebSocket fields. */
|
|
32
|
+
export interface OfflineAssetEvidenceInput {
|
|
33
|
+
readonly guid: string;
|
|
34
|
+
readonly source?: SourceDeclarationEvidence;
|
|
35
|
+
readonly locator?: AssetEvidenceLocator;
|
|
36
|
+
readonly receipt?: CookReceipt;
|
|
37
|
+
readonly package?: OfflinePackageInput;
|
|
38
|
+
readonly product?: CookProduct;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function packageEvidence(input: OfflinePackageInput): NonNullable<AssetEvidenceInputs['package']> {
|
|
42
|
+
return {
|
|
43
|
+
guid: input.guid,
|
|
44
|
+
...(input.digest === undefined ? {} : { digest: input.digest }),
|
|
45
|
+
artifacts: Object.fromEntries(
|
|
46
|
+
Object.entries(input.artifacts).map(([key, artifact]) => [
|
|
47
|
+
key,
|
|
48
|
+
{
|
|
49
|
+
descriptor: artifact,
|
|
50
|
+
verification: artifact.verification ?? 'passed',
|
|
51
|
+
},
|
|
52
|
+
]),
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Build the shared evidence view without loading runtime assets or opening WS. */
|
|
58
|
+
export function buildOfflineAssetEvidence(
|
|
59
|
+
input: OfflineAssetEvidenceInput,
|
|
60
|
+
): Promise<Result<AssetEvidence, AssetEvidenceError>> {
|
|
61
|
+
if (input.product !== undefined) {
|
|
62
|
+
return Promise.resolve(projectCookProductEvidence(input.product, input.locator));
|
|
63
|
+
}
|
|
64
|
+
const evidenceInput: AssetEvidenceInputs = {
|
|
65
|
+
guid: input.guid,
|
|
66
|
+
...(input.source === undefined ? {} : { source: input.source }),
|
|
67
|
+
...(input.locator === undefined ? {} : { locator: input.locator }),
|
|
68
|
+
...(input.receipt === undefined ? {} : { receipt: input.receipt }),
|
|
69
|
+
...(input.package === undefined ? {} : { package: packageEvidence(input.package) }),
|
|
70
|
+
};
|
|
71
|
+
return Promise.resolve(projectAssetEvidence(evidenceInput));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function packageVerification(
|
|
75
|
+
input: OfflinePackageInput | undefined,
|
|
76
|
+
): PackageVerificationEvidence | undefined {
|
|
77
|
+
if (input === undefined) return undefined;
|
|
78
|
+
const packageInput = packageEvidence(input);
|
|
79
|
+
const statuses = Object.values(packageInput.artifacts).map(
|
|
80
|
+
(artifact) => artifact.verification ?? 'notChecked',
|
|
81
|
+
);
|
|
82
|
+
return {
|
|
83
|
+
status: statuses.some((status) => status === 'failed')
|
|
84
|
+
? 'failed'
|
|
85
|
+
: statuses.length > 0 && statuses.every((status) => status === 'passed')
|
|
86
|
+
? 'passed'
|
|
87
|
+
: 'notChecked',
|
|
88
|
+
...(input?.digest === undefined ? {} : { digest: input.digest }),
|
|
89
|
+
};
|
|
90
|
+
}
|