@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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
5
|
+
import { loadAssetConfig } from '../config.js';
|
|
6
|
+
|
|
7
|
+
describe('loadAssetConfig SSOT (w8 / AC-13)', () => {
|
|
8
|
+
let tmpDir: string;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'forgeax-test-loadAssetConfig-'));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterAll(async () => {
|
|
15
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('missing package.json returns default roots + empty paths', () => {
|
|
19
|
+
const result = loadAssetConfig(join(tmpDir, 'nonexistent'));
|
|
20
|
+
expect(result.roots).toEqual([join(join(tmpDir, 'nonexistent'), 'assets')]);
|
|
21
|
+
expect(result.paths).toEqual({});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('package.json without forgeax.assets returns default', async () => {
|
|
25
|
+
const dir = join(tmpDir, 'no-forgeax');
|
|
26
|
+
await mkdir(dir);
|
|
27
|
+
await writeFile(join(dir, 'package.json'), JSON.stringify({ name: 'test' }));
|
|
28
|
+
const result = loadAssetConfig(dir);
|
|
29
|
+
expect(result.roots).toEqual([join(dir, 'assets')]);
|
|
30
|
+
expect(result.paths).toEqual({});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('reads forgeax.assets.roots from package.json', async () => {
|
|
34
|
+
const dir = join(tmpDir, 'with-roots');
|
|
35
|
+
await mkdir(dir);
|
|
36
|
+
await writeFile(
|
|
37
|
+
join(dir, 'package.json'),
|
|
38
|
+
JSON.stringify({
|
|
39
|
+
name: 'test',
|
|
40
|
+
forgeax: { assets: { roots: ['my-assets', 'more'] } },
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
const result = loadAssetConfig(dir);
|
|
44
|
+
expect(result.roots).toEqual([join(dir, 'my-assets'), join(dir, 'more')]);
|
|
45
|
+
expect(result.paths).toEqual({});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('empty forgeax.assets.roots array falls back to default', async () => {
|
|
49
|
+
const dir = join(tmpDir, 'empty-roots');
|
|
50
|
+
await mkdir(dir);
|
|
51
|
+
await writeFile(
|
|
52
|
+
join(dir, 'package.json'),
|
|
53
|
+
JSON.stringify({
|
|
54
|
+
name: 'test',
|
|
55
|
+
forgeax: { assets: { roots: [] } },
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
const result = loadAssetConfig(dir);
|
|
59
|
+
expect(result.roots).toEqual([join(dir, 'assets')]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('reads forgeax.assets.paths with cwd-relative resolution', async () => {
|
|
63
|
+
const dir = join(tmpDir, 'with-paths');
|
|
64
|
+
await mkdir(dir);
|
|
65
|
+
await writeFile(
|
|
66
|
+
join(dir, 'package.json'),
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
name: 'test',
|
|
69
|
+
forgeax: {
|
|
70
|
+
assets: {
|
|
71
|
+
paths: {
|
|
72
|
+
shared: 'lib/assets',
|
|
73
|
+
vendor: '../vendor-assets',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
const result = loadAssetConfig(dir);
|
|
80
|
+
expect(result.paths).toEqual({
|
|
81
|
+
shared: join(dir, 'lib/assets'),
|
|
82
|
+
vendor: join(dir, '../vendor-assets'),
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('paths absent defaults to empty object', async () => {
|
|
87
|
+
const dir = join(tmpDir, 'roots-only');
|
|
88
|
+
await mkdir(dir);
|
|
89
|
+
await writeFile(
|
|
90
|
+
join(dir, 'package.json'),
|
|
91
|
+
JSON.stringify({
|
|
92
|
+
name: 'test',
|
|
93
|
+
forgeax: { assets: { roots: ['a'] } },
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
const result = loadAssetConfig(dir);
|
|
97
|
+
expect(result.paths).toEqual({});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('non-string path values are skipped', async () => {
|
|
101
|
+
const dir = join(tmpDir, 'bad-paths');
|
|
102
|
+
await mkdir(dir);
|
|
103
|
+
await writeFile(
|
|
104
|
+
join(dir, 'package.json'),
|
|
105
|
+
JSON.stringify({
|
|
106
|
+
name: 'test',
|
|
107
|
+
forgeax: {
|
|
108
|
+
assets: {
|
|
109
|
+
paths: {
|
|
110
|
+
good: 'lib/a',
|
|
111
|
+
badNum: 42,
|
|
112
|
+
badBool: true,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
const result = loadAssetConfig(dir);
|
|
119
|
+
expect(result.paths).toEqual({ good: join(dir, 'lib/a') });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { AssetGuid } from '@forgeax/engine-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { collectMaterialCookRefs } from '../evidence/material-cook.js';
|
|
4
|
+
import { NativeCookerRegistry } from '../native-cooker-registry.js';
|
|
5
|
+
import { createRuntimePackPublication } from '../runtime-publication.js';
|
|
6
|
+
|
|
7
|
+
describe('material cook dependency closure', () => {
|
|
8
|
+
it('collects parent, texture, sampler, and module references', () => {
|
|
9
|
+
expect(
|
|
10
|
+
collectMaterialCookRefs({
|
|
11
|
+
parent: 'mat-parent' as unknown as AssetGuid,
|
|
12
|
+
passes: [{ name: 'forward', program: { module: 'module/pbr' } }],
|
|
13
|
+
values: {
|
|
14
|
+
baseColor: {
|
|
15
|
+
texture: 'texture/albedo' as unknown as AssetGuid,
|
|
16
|
+
sampler: 'sampler/linear' as unknown as AssetGuid,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
).toEqual({
|
|
21
|
+
parent: ['mat-parent'],
|
|
22
|
+
textures: ['texture/albedo'],
|
|
23
|
+
samplers: ['sampler/linear'],
|
|
24
|
+
modules: ['module/pbr'],
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('collects string shorthands only from declared texture parameters', () => {
|
|
29
|
+
expect(
|
|
30
|
+
collectMaterialCookRefs({
|
|
31
|
+
parameters: [
|
|
32
|
+
{ name: 'surface', type: 'texture' },
|
|
33
|
+
{ name: 'label', type: 'f32' },
|
|
34
|
+
],
|
|
35
|
+
values: { surface: 'texture/albedo', label: 'not-a-texture-ref' },
|
|
36
|
+
}),
|
|
37
|
+
).toEqual({ parent: [], textures: ['texture/albedo'], samplers: [], modules: [] });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('treats a string value as a texture shorthand when parameters are omitted', () => {
|
|
41
|
+
expect(
|
|
42
|
+
collectMaterialCookRefs({
|
|
43
|
+
values: { baseColorTexture: 'texture/albedo' },
|
|
44
|
+
}),
|
|
45
|
+
).toEqual({ parent: [], textures: ['texture/albedo'], samplers: [], modules: [] });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('keeps the last known good candidate when a generation fails before publication', async () => {
|
|
49
|
+
const registry = new NativeCookerRegistry();
|
|
50
|
+
let fail = false;
|
|
51
|
+
registry.register({
|
|
52
|
+
key: 'material',
|
|
53
|
+
cook: () => {
|
|
54
|
+
if (fail) throw new Error('candidate rejected');
|
|
55
|
+
return {
|
|
56
|
+
guid: 'material-child',
|
|
57
|
+
payload: { kind: 'material' },
|
|
58
|
+
refs: ['texture/albedo'],
|
|
59
|
+
artifacts: {
|
|
60
|
+
'material-child/shader.wgsl': {
|
|
61
|
+
mediaType: 'text/wgsl',
|
|
62
|
+
bytes: new TextEncoder().encode('shader'),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
inputFingerprint: 'sha256:material-candidate',
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const committed = await registry.runTransaction({ key: 'material', input: {} });
|
|
71
|
+
expect(committed.ok).toBe(true);
|
|
72
|
+
if (!committed.ok) throw new Error(committed.error.hint);
|
|
73
|
+
fail = true;
|
|
74
|
+
const recovered = await registry.runTransaction({
|
|
75
|
+
key: 'material',
|
|
76
|
+
input: {},
|
|
77
|
+
previous: committed.value,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(recovered).toMatchObject({
|
|
81
|
+
ok: true,
|
|
82
|
+
value: {
|
|
83
|
+
status: 'recovered',
|
|
84
|
+
generation: 1,
|
|
85
|
+
candidateGeneration: 2,
|
|
86
|
+
lastKnownGoodGeneration: 1,
|
|
87
|
+
lastKnownGood: committed.value.draft,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('keeps runtime publication identity independent from source-path diagnostics', () => {
|
|
93
|
+
const input = {
|
|
94
|
+
pack: {
|
|
95
|
+
assets: [{ guid: 'material-child', kind: 'material', payload: { roughness: 0.5 } }],
|
|
96
|
+
},
|
|
97
|
+
scopeId: 'project',
|
|
98
|
+
sourceRevision: 'rev-1',
|
|
99
|
+
packageUrl: '/packs/materials.json',
|
|
100
|
+
};
|
|
101
|
+
const first = createRuntimePackPublication({ ...input, sourcePath: '/src/materials.json' });
|
|
102
|
+
const moved = createRuntimePackPublication({ ...input, sourcePath: '/moved/materials.json' });
|
|
103
|
+
|
|
104
|
+
expect(moved.pack.digest).toBe(first.pack.digest);
|
|
105
|
+
expect(moved.publication.digest).toBe(first.publication.digest);
|
|
106
|
+
expect(moved.publication.outputSetDigest).toBe(first.publication.outputSetDigest);
|
|
107
|
+
expect(moved.publication.receipt.inputFingerprint).toBe(
|
|
108
|
+
first.publication.receipt.inputFingerprint,
|
|
109
|
+
);
|
|
110
|
+
expect(moved.publication.sourcePath).toBe('/moved/materials.json');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
type MaterialCookReceipt,
|
|
4
|
+
serializeMaterialCookReceipt,
|
|
5
|
+
validateMaterialCookReceipt,
|
|
6
|
+
} from '../evidence/material-cook.js';
|
|
7
|
+
|
|
8
|
+
const layoutIdentity = 'sha256:layout-identity';
|
|
9
|
+
const artifactDigest = 'sha256:output';
|
|
10
|
+
|
|
11
|
+
function receipt(overrides: Record<string, unknown> = {}): MaterialCookReceipt {
|
|
12
|
+
return {
|
|
13
|
+
schemaVersion: 'material-cook/3',
|
|
14
|
+
sourceClosure: ['b.wgsl', 'a.material.json'],
|
|
15
|
+
profile: 'webgpu/v1',
|
|
16
|
+
compilerVersion: 'compiler/1',
|
|
17
|
+
identity: {
|
|
18
|
+
materialContractDigest: 'sha256:material-contract',
|
|
19
|
+
sourceRevision: 'sha256:source-revision',
|
|
20
|
+
sourceClosureDigest: 'sha256:source-closure',
|
|
21
|
+
layoutIdentity,
|
|
22
|
+
programIdentity: 'sha256:program',
|
|
23
|
+
pipelineIdentity: 'sha256:pipeline',
|
|
24
|
+
materialPublicationIdentity: 'sha256:publication',
|
|
25
|
+
cookIdentity: 'sha256:input',
|
|
26
|
+
compilerFingerprint: 'sha256:compiler',
|
|
27
|
+
wasm: {
|
|
28
|
+
sourceContentKey: 'sha256:wasm-source',
|
|
29
|
+
artifactSha256: 'sha256:wasm-artifact',
|
|
30
|
+
glueSha256: 'sha256:wasm-glue',
|
|
31
|
+
},
|
|
32
|
+
artifactDigest,
|
|
33
|
+
valueGeneration: 1,
|
|
34
|
+
dependencyGeneration: 1,
|
|
35
|
+
cookGeneration: 1,
|
|
36
|
+
},
|
|
37
|
+
derivedInterface: { layoutIdentity },
|
|
38
|
+
...overrides,
|
|
39
|
+
} as unknown as MaterialCookReceipt;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('material cook receipt', () => {
|
|
43
|
+
it('requires the v3 identity and provenance tuple', () => {
|
|
44
|
+
const v3 = {
|
|
45
|
+
schemaVersion: 'material-cook/3',
|
|
46
|
+
sourceClosure: ['materials/mat.material.json', 'shaders/pbr.wgsl'],
|
|
47
|
+
profile: 'webgpu/v1',
|
|
48
|
+
compilerVersion: 'compiler/1',
|
|
49
|
+
identity: {
|
|
50
|
+
materialContractDigest: 'sha256:material-contract',
|
|
51
|
+
sourceRevision: 'sha256:source-revision',
|
|
52
|
+
sourceClosureDigest: 'sha256:source-closure',
|
|
53
|
+
layoutIdentity,
|
|
54
|
+
programIdentity: 'sha256:program',
|
|
55
|
+
pipelineIdentity: 'sha256:pipeline',
|
|
56
|
+
materialPublicationIdentity: 'sha256:publication',
|
|
57
|
+
cookIdentity: 'sha256:cook',
|
|
58
|
+
compilerFingerprint: 'sha256:compiler',
|
|
59
|
+
wasm: {
|
|
60
|
+
sourceContentKey: 'sha256:wasm-source',
|
|
61
|
+
artifactSha256: 'sha256:wasm-artifact',
|
|
62
|
+
glueSha256: 'sha256:wasm-glue',
|
|
63
|
+
},
|
|
64
|
+
artifactDigest,
|
|
65
|
+
valueGeneration: 3,
|
|
66
|
+
dependencyGeneration: 2,
|
|
67
|
+
cookGeneration: 7,
|
|
68
|
+
},
|
|
69
|
+
derivedInterface: { layoutIdentity },
|
|
70
|
+
};
|
|
71
|
+
expect(validateMaterialCookReceipt(v3)).toMatchObject({ ok: true, value: v3 });
|
|
72
|
+
expect(validateMaterialCookReceipt(receipt())).toMatchObject({ ok: true });
|
|
73
|
+
expect(
|
|
74
|
+
validateMaterialCookReceipt({
|
|
75
|
+
...v3,
|
|
76
|
+
identity: { ...v3.identity, compilerFingerprint: undefined },
|
|
77
|
+
}),
|
|
78
|
+
).toMatchObject({
|
|
79
|
+
ok: false,
|
|
80
|
+
error: { code: 'material-cook-record-invalid' },
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('serializes a material-cook/3 receipt stably for equivalent cooks', () => {
|
|
85
|
+
const first = serializeMaterialCookReceipt(receipt());
|
|
86
|
+
const second = serializeMaterialCookReceipt(
|
|
87
|
+
receipt({ sourceClosure: ['a.material.json', 'b.wgsl'] }),
|
|
88
|
+
);
|
|
89
|
+
expect(first).toBe(second);
|
|
90
|
+
expect(JSON.parse(first)).toMatchObject({
|
|
91
|
+
schemaVersion: 'material-cook/3',
|
|
92
|
+
identity: { layoutIdentity },
|
|
93
|
+
derivedInterface: { layoutIdentity },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('accepts the expected identity and rejects schema or interface mutations', () => {
|
|
98
|
+
const expected = { layoutIdentity, artifactDigest, inputDigest: 'sha256:input' };
|
|
99
|
+
expect(validateMaterialCookReceipt(receipt(), expected)).toEqual({
|
|
100
|
+
ok: true,
|
|
101
|
+
value: receipt(),
|
|
102
|
+
});
|
|
103
|
+
expect(
|
|
104
|
+
validateMaterialCookReceipt(
|
|
105
|
+
receipt({ identity: { ...receipt().identity, layoutIdentity: 'sha256:changed-layout' } }),
|
|
106
|
+
expected,
|
|
107
|
+
),
|
|
108
|
+
).toMatchObject({ ok: false, error: { code: 'material-cook-record-invalid' } });
|
|
109
|
+
expect(
|
|
110
|
+
validateMaterialCookReceipt(
|
|
111
|
+
receipt({ derivedInterface: { layoutIdentity: 'sha256:changed-layout' } }),
|
|
112
|
+
expected,
|
|
113
|
+
),
|
|
114
|
+
).toMatchObject({ ok: false, error: { code: 'material-cook-record-invalid' } });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('rejects missing, stale, mismatched, and material-cook/1 receipts fail-fast', () => {
|
|
118
|
+
const expected = { layoutIdentity, artifactDigest, inputDigest: 'sha256:input' };
|
|
119
|
+
for (const value of [
|
|
120
|
+
receipt({ identity: { ...receipt().identity, layoutIdentity: undefined } }),
|
|
121
|
+
receipt({ identity: { ...receipt().identity, artifactDigest: 'sha256:stale-artifact' } }),
|
|
122
|
+
receipt({ identity: { ...receipt().identity, cookIdentity: 'sha256:stale-input' } }),
|
|
123
|
+
{
|
|
124
|
+
sourceClosure: [],
|
|
125
|
+
profile: 'webgpu/v1',
|
|
126
|
+
compilerVersion: 'compiler/1',
|
|
127
|
+
identity: receipt().identity,
|
|
128
|
+
},
|
|
129
|
+
]) {
|
|
130
|
+
expect(validateMaterialCookReceipt(value as MaterialCookReceipt, expected)).toMatchObject({
|
|
131
|
+
ok: false,
|
|
132
|
+
error: { code: 'material-cook-record-invalid' },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('records the coordinator-owned publication generation', () => {
|
|
138
|
+
const generationReceipt = {
|
|
139
|
+
sourceClosure: ['materials/mat-root.material.json'],
|
|
140
|
+
profile: 'webgpu/v1',
|
|
141
|
+
compilerVersion: 'compiler/1',
|
|
142
|
+
identity: { ...receipt().identity, cookGeneration: 11 },
|
|
143
|
+
};
|
|
144
|
+
expect(generationReceipt).toMatchObject({
|
|
145
|
+
identity: { cookGeneration: 11 },
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
type CookedMaterialRecord,
|
|
6
|
+
createMaterialArtifactDigest,
|
|
7
|
+
projectCookedMaterialRecord,
|
|
8
|
+
serializeCookedMaterialRecord,
|
|
9
|
+
validateCookedMaterialRecord,
|
|
10
|
+
} from '../evidence/material-cook.js';
|
|
11
|
+
|
|
12
|
+
const record: CookedMaterialRecord = {
|
|
13
|
+
schemaVersion: 'material-cook/3',
|
|
14
|
+
guid: 'mat-root',
|
|
15
|
+
authored: {
|
|
16
|
+
kind: 'material',
|
|
17
|
+
passes: [{ name: 'forward', program: { module: 'core/pbr' } }],
|
|
18
|
+
parameters: [{ name: 'roughness', type: 'f32' }],
|
|
19
|
+
values: { roughness: 0.5 },
|
|
20
|
+
},
|
|
21
|
+
resolved: {
|
|
22
|
+
passes: [{ name: 'forward', program: { module: 'core/pbr' } }],
|
|
23
|
+
parameters: [{ name: 'roughness', type: 'f32' }],
|
|
24
|
+
values: { roughness: 0.5 },
|
|
25
|
+
},
|
|
26
|
+
refs: { parent: [], textures: [], samplers: [], modules: ['core/pbr'] },
|
|
27
|
+
artifact: {
|
|
28
|
+
mediaType: 'text/wgsl',
|
|
29
|
+
path: 'materials/mat-root/forward.wgsl',
|
|
30
|
+
digest: 'sha256:artifact',
|
|
31
|
+
bytes: new TextEncoder().encode('shader'),
|
|
32
|
+
},
|
|
33
|
+
receipt: {
|
|
34
|
+
schemaVersion: 'material-cook/3',
|
|
35
|
+
sourceClosure: ['materials/mat-root.material.json'],
|
|
36
|
+
profile: 'webgpu/v1',
|
|
37
|
+
compilerVersion: 'compiler/1',
|
|
38
|
+
identity: {
|
|
39
|
+
materialContractDigest: 'sha256:material-contract',
|
|
40
|
+
sourceRevision: 'sha256:source-revision',
|
|
41
|
+
sourceClosureDigest: 'sha256:source-closure',
|
|
42
|
+
layoutIdentity: 'sha256:layout',
|
|
43
|
+
programIdentity: 'sha256:program',
|
|
44
|
+
pipelineIdentity: 'sha256:pipeline',
|
|
45
|
+
materialPublicationIdentity: 'sha256:publication',
|
|
46
|
+
cookIdentity: 'sha256:input',
|
|
47
|
+
compilerFingerprint: 'sha256:compiler',
|
|
48
|
+
wasm: {
|
|
49
|
+
sourceContentKey: 'sha256:wasm-source',
|
|
50
|
+
artifactSha256: 'sha256:wasm-artifact',
|
|
51
|
+
glueSha256: 'sha256:wasm-glue',
|
|
52
|
+
},
|
|
53
|
+
artifactDigest: 'sha256:artifact',
|
|
54
|
+
valueGeneration: 1,
|
|
55
|
+
dependencyGeneration: 1,
|
|
56
|
+
cookGeneration: 1,
|
|
57
|
+
},
|
|
58
|
+
derivedInterface: { layoutIdentity: 'sha256:layout' },
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe('cooked material schema', () => {
|
|
63
|
+
it('requires material-cook/3 identity and derived interface association', () => {
|
|
64
|
+
const schema = JSON.parse(
|
|
65
|
+
readFileSync(
|
|
66
|
+
fileURLToPath(new URL('../schema/material-cook.schema.json', import.meta.url)),
|
|
67
|
+
'utf8',
|
|
68
|
+
),
|
|
69
|
+
) as {
|
|
70
|
+
properties: Record<
|
|
71
|
+
string,
|
|
72
|
+
{ const?: string; required?: string[]; properties?: Record<string, unknown> }
|
|
73
|
+
>;
|
|
74
|
+
required: string[];
|
|
75
|
+
};
|
|
76
|
+
expect(schema.properties.schemaVersion?.const).toBe('material-cook/3');
|
|
77
|
+
expect(schema.properties.receipt?.required).toEqual(
|
|
78
|
+
expect.arrayContaining(['identity', 'derivedInterface']),
|
|
79
|
+
);
|
|
80
|
+
expect(schema.properties.receipt?.properties?.derivedInterface).toBeDefined();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('defines the v3 identity, provenance, and generation contract', () => {
|
|
84
|
+
const schema = JSON.parse(
|
|
85
|
+
readFileSync(
|
|
86
|
+
fileURLToPath(new URL('../schema/material-cook.schema.json', import.meta.url)),
|
|
87
|
+
'utf8',
|
|
88
|
+
),
|
|
89
|
+
) as {
|
|
90
|
+
properties: Record<
|
|
91
|
+
string,
|
|
92
|
+
{ const?: string; required?: string[]; properties?: Record<string, unknown> }
|
|
93
|
+
>;
|
|
94
|
+
};
|
|
95
|
+
expect(schema.properties.schemaVersion?.const).toBe('material-cook/3');
|
|
96
|
+
expect(schema.properties.receipt?.properties?.identity).toMatchObject({
|
|
97
|
+
type: 'object',
|
|
98
|
+
required: expect.arrayContaining([
|
|
99
|
+
'materialContractDigest',
|
|
100
|
+
'sourceRevision',
|
|
101
|
+
'sourceClosureDigest',
|
|
102
|
+
'layoutIdentity',
|
|
103
|
+
'programIdentity',
|
|
104
|
+
'pipelineIdentity',
|
|
105
|
+
'materialPublicationIdentity',
|
|
106
|
+
'cookIdentity',
|
|
107
|
+
'compilerFingerprint',
|
|
108
|
+
'wasm',
|
|
109
|
+
'artifactDigest',
|
|
110
|
+
'valueGeneration',
|
|
111
|
+
'dependencyGeneration',
|
|
112
|
+
'cookGeneration',
|
|
113
|
+
]),
|
|
114
|
+
});
|
|
115
|
+
expect(schema.properties.receipt?.required).toEqual(
|
|
116
|
+
expect.arrayContaining(['identity', 'derivedInterface']),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('serializes a stable cooked DTO and derives an artifact digest', () => {
|
|
121
|
+
const first = serializeCookedMaterialRecord(record);
|
|
122
|
+
const second = serializeCookedMaterialRecord({ ...record });
|
|
123
|
+
|
|
124
|
+
expect(first).toBe(second);
|
|
125
|
+
expect(createMaterialArtifactDigest(record.artifact.bytes)).toBe(
|
|
126
|
+
'sha256:e137e75a5e0e7a623ca39de480667a55b43e0eedec58767634ebaef07c33383a',
|
|
127
|
+
);
|
|
128
|
+
expect(validateCookedMaterialRecord(JSON.parse(first))).toEqual({ ok: true, value: record });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('projects cooked data without exposing an authored GUID entry', () => {
|
|
132
|
+
const projection = projectCookedMaterialRecord(record);
|
|
133
|
+
|
|
134
|
+
expect(projection.resolved.values).toEqual({ roughness: 0.5 });
|
|
135
|
+
expect(projection.artifact.digest).toBe('sha256:artifact');
|
|
136
|
+
expect(projection).not.toHaveProperty('guid');
|
|
137
|
+
expect(projection).not.toHaveProperty('authored');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('rejects missing fields and unsupported schema versions', () => {
|
|
141
|
+
expect(validateCookedMaterialRecord({ ...record, refs: undefined })).toMatchObject({
|
|
142
|
+
ok: false,
|
|
143
|
+
error: { code: 'material-cook-record-invalid' },
|
|
144
|
+
});
|
|
145
|
+
expect(
|
|
146
|
+
validateCookedMaterialRecord({ ...record, schemaVersion: 'material-cook/1' }),
|
|
147
|
+
).toMatchObject({
|
|
148
|
+
ok: false,
|
|
149
|
+
error: { code: 'material-cook-record-invalid' },
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
const encoderSource = readFileSync(
|
|
5
|
+
new URL('../../../import/src/mesh-bin.ts', import.meta.url),
|
|
6
|
+
'utf8',
|
|
7
|
+
);
|
|
8
|
+
const decoderSource = readFileSync(
|
|
9
|
+
new URL('../../../geometry/src/assets/mesh-binary.ts', import.meta.url),
|
|
10
|
+
'utf8',
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
describe('mesh-bin consumer owner surface', () => {
|
|
14
|
+
it('routes both production halves through Pack-owned facts', () => {
|
|
15
|
+
expect(encoderSource).toContain("from '@forgeax/engine-pack/mesh-bin-contract'");
|
|
16
|
+
expect(decoderSource).toContain("from '@forgeax/engine-pack'");
|
|
17
|
+
for (const source of [encoderSource, decoderSource]) {
|
|
18
|
+
expect(source).toContain('MESH_BIN_HEADER_V4_BYTES');
|
|
19
|
+
expect(source).not.toContain('const HEADER_V2_BYTES = 28');
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
decodeMeshBinHeader,
|
|
4
|
+
MESH_BIN_HEADER_V4_BYTES,
|
|
5
|
+
MESH_BIN_VERSION,
|
|
6
|
+
} from '../mesh-bin-contract.js';
|
|
7
|
+
|
|
8
|
+
describe('mesh-bin v4 wire contract', () => {
|
|
9
|
+
it('owns a fixed v4 header and rejects legacy versions at the contract boundary', () => {
|
|
10
|
+
expect(MESH_BIN_VERSION).toBe(4);
|
|
11
|
+
expect(MESH_BIN_HEADER_V4_BYTES).toBeGreaterThan(28);
|
|
12
|
+
expect(decodeMeshBinHeader(new Uint8Array(MESH_BIN_HEADER_V4_BYTES))).toMatchObject({
|
|
13
|
+
ok: false,
|
|
14
|
+
error: { code: 'mesh-bin-version-unsupported' },
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('reports truncated headers as structured failures with recovery context', () => {
|
|
19
|
+
const result = decodeMeshBinHeader(new Uint8Array(MESH_BIN_HEADER_V4_BYTES - 1), 'mesh/source');
|
|
20
|
+
expect(result).toMatchObject({
|
|
21
|
+
ok: false,
|
|
22
|
+
error: {
|
|
23
|
+
code: 'mesh-bin-header-truncated',
|
|
24
|
+
subject: 'mesh-bin',
|
|
25
|
+
sourceKey: 'mesh/source',
|
|
26
|
+
expected: expect.any(String),
|
|
27
|
+
actual: expect.any(String),
|
|
28
|
+
recovery: expect.stringContaining('re-cook'),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|