@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.
Files changed (276) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +429 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
  5. package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/artifact-path.test.d.ts +2 -0
  7. package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
  8. package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
  9. package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
  10. package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
  11. package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
  12. package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
  13. package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
  14. package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
  15. package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
  16. package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
  17. package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
  19. package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
  20. package/dist/__tests__/cli.unit.test.d.ts +2 -0
  21. package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
  23. package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
  25. package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
  27. package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/load-asset-config.test.d.ts +2 -0
  29. package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
  30. package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
  31. package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
  33. package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
  35. package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
  37. package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
  39. package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
  41. package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
  42. package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
  43. package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
  45. package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
  46. package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
  47. package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
  48. package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
  49. package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
  50. package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
  51. package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
  52. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
  53. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
  54. package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
  55. package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
  56. package/dist/__tests__/pack.unit.test.d.ts +2 -0
  57. package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/package-finalizer.contract.test.d.ts +2 -0
  59. package/dist/__tests__/package-finalizer.contract.test.d.ts.map +1 -0
  60. package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
  61. package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
  63. package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
  64. package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
  65. package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/runtime.browser.test.d.ts +2 -0
  67. package/dist/__tests__/runtime.browser.test.d.ts.map +1 -0
  68. package/dist/__tests__/scanner-inventory.contract.test.d.ts +2 -0
  69. package/dist/__tests__/scanner-inventory.contract.test.d.ts.map +1 -0
  70. package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
  71. package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
  72. package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
  73. package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
  74. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
  75. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
  76. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
  77. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
  79. package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
  81. package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
  83. package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/topology.unit.test.d.ts +2 -0
  85. package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
  86. package/dist/artifact-path.d.ts +9 -0
  87. package/dist/artifact-path.d.ts.map +1 -0
  88. package/dist/artifact-path.mjs +53 -0
  89. package/dist/artifact-path.mjs.map +1 -0
  90. package/dist/atlas/run-atlas.d.ts +8 -0
  91. package/dist/atlas/run-atlas.d.ts.map +1 -0
  92. package/dist/atlas/shelf-pack.d.ts +55 -0
  93. package/dist/atlas/shelf-pack.d.ts.map +1 -0
  94. package/dist/bridge.d.ts +2 -0
  95. package/dist/bridge.d.ts.map +1 -0
  96. package/dist/bridge.mjs +3 -0
  97. package/dist/bridge.mjs.map +1 -0
  98. package/dist/build.d.ts +19 -0
  99. package/dist/build.d.ts.map +1 -0
  100. package/dist/build.mjs +10203 -0
  101. package/dist/build.mjs.map +1 -0
  102. package/dist/builtin.d.ts +44 -0
  103. package/dist/builtin.d.ts.map +1 -0
  104. package/dist/builtin.mjs +141 -0
  105. package/dist/builtin.mjs.map +1 -0
  106. package/dist/catalog-builder.d.ts +43 -0
  107. package/dist/catalog-builder.d.ts.map +1 -0
  108. package/dist/catalog-delta.d.ts +11 -0
  109. package/dist/catalog-delta.d.ts.map +1 -0
  110. package/dist/catalog-projection.d.ts +91 -0
  111. package/dist/catalog-projection.d.ts.map +1 -0
  112. package/dist/cli-asset.d.ts +22 -0
  113. package/dist/cli-asset.d.ts.map +1 -0
  114. package/dist/cli-asset.mjs +9697 -0
  115. package/dist/cli-asset.mjs.map +1 -0
  116. package/dist/config.d.ts +6 -0
  117. package/dist/config.d.ts.map +1 -0
  118. package/dist/config.mjs +29 -0
  119. package/dist/config.mjs.map +1 -0
  120. package/dist/deriveAssetName.d.ts +15 -0
  121. package/dist/deriveAssetName.d.ts.map +1 -0
  122. package/dist/errors.d.ts +30 -0
  123. package/dist/errors.d.ts.map +1 -0
  124. package/dist/errors.mjs +19 -0
  125. package/dist/errors.mjs.map +1 -0
  126. package/dist/evidence/material-cook.d.ts +92 -0
  127. package/dist/evidence/material-cook.d.ts.map +1 -0
  128. package/dist/evidence/offline-evidence.d.ts +25 -0
  129. package/dist/evidence/offline-evidence.d.ts.map +1 -0
  130. package/dist/evidence/source-inventory.d.ts +29 -0
  131. package/dist/evidence/source-inventory.d.ts.map +1 -0
  132. package/dist/guid.d.ts +45 -0
  133. package/dist/guid.d.ts.map +1 -0
  134. package/dist/guid.mjs +94 -0
  135. package/dist/guid.mjs.map +1 -0
  136. package/dist/index.d.ts +15 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.mjs +8612 -0
  139. package/dist/index.mjs.map +1 -0
  140. package/dist/material/artifact-writer.d.ts +14 -0
  141. package/dist/material/artifact-writer.d.ts.map +1 -0
  142. package/dist/material-cook.d.ts +2 -0
  143. package/dist/material-cook.d.ts.map +1 -0
  144. package/dist/material-cook.mjs +248 -0
  145. package/dist/material-cook.mjs.map +1 -0
  146. package/dist/mesh-bin-contract.d.ts +36 -0
  147. package/dist/mesh-bin-contract.d.ts.map +1 -0
  148. package/dist/mesh-bin-contract.mjs +96 -0
  149. package/dist/mesh-bin-contract.mjs.map +1 -0
  150. package/dist/name.mjs +22 -0
  151. package/dist/name.mjs.map +1 -0
  152. package/dist/native-cooker-registry.d.ts +47 -0
  153. package/dist/native-cooker-registry.d.ts.map +1 -0
  154. package/dist/native-cooker.d.ts +3 -0
  155. package/dist/native-cooker.d.ts.map +1 -0
  156. package/dist/native-cooker.mjs +142 -0
  157. package/dist/native-cooker.mjs.map +1 -0
  158. package/dist/package-finalizer.d.ts +121 -0
  159. package/dist/package-finalizer.d.ts.map +1 -0
  160. package/dist/producer-contract.d.ts +19 -0
  161. package/dist/producer-contract.d.ts.map +1 -0
  162. package/dist/resolve-asset-source.d.ts +4 -0
  163. package/dist/resolve-asset-source.d.ts.map +1 -0
  164. package/dist/resolve-asset-source.mjs +109 -0
  165. package/dist/resolve-asset-source.mjs.map +1 -0
  166. package/dist/runtime-projection.d.ts +14 -0
  167. package/dist/runtime-projection.d.ts.map +1 -0
  168. package/dist/runtime-publication.d.ts +44 -0
  169. package/dist/runtime-publication.d.ts.map +1 -0
  170. package/dist/runtime.d.ts +8 -0
  171. package/dist/runtime.d.ts.map +1 -0
  172. package/dist/runtime.mjs +7261 -0
  173. package/dist/runtime.mjs.map +1 -0
  174. package/dist/scanner.d.ts +113 -0
  175. package/dist/scanner.d.ts.map +1 -0
  176. package/dist/scanner.mjs +8819 -0
  177. package/dist/scanner.mjs.map +1 -0
  178. package/dist/schema-compiled.d.ts +12 -0
  179. package/dist/schema-compiled.d.ts.map +1 -0
  180. package/dist/schema.mjs +7369 -0
  181. package/dist/schema.mjs.map +1 -0
  182. package/dist/scriptable-pack-node.d.ts +62 -0
  183. package/dist/scriptable-pack-node.d.ts.map +1 -0
  184. package/dist/scriptable-pack-node.mjs +1674 -0
  185. package/dist/scriptable-pack-node.mjs.map +1 -0
  186. package/dist/scriptable-pack-worker.d.ts +2 -0
  187. package/dist/scriptable-pack-worker.d.ts.map +1 -0
  188. package/dist/scriptable-pack-worker.mjs +224 -0
  189. package/dist/scriptable-pack-worker.mjs.map +1 -0
  190. package/dist/scriptable-pack.d.ts +558 -0
  191. package/dist/scriptable-pack.d.ts.map +1 -0
  192. package/dist/scriptable-pack.mjs +672 -0
  193. package/dist/scriptable-pack.mjs.map +1 -0
  194. package/dist/topology.d.ts +10 -0
  195. package/dist/topology.d.ts.map +1 -0
  196. package/package.json +154 -0
  197. package/schema/meta.schema.json +173 -0
  198. package/schema/pack.schema.json +232 -0
  199. package/src/__tests__/ai-recovery.unit.test.ts +114 -0
  200. package/src/__tests__/artifact-path.test.ts +41 -0
  201. package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
  202. package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
  203. package/src/__tests__/asset-evidence-schema.test.ts +86 -0
  204. package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
  205. package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
  206. package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
  207. package/src/__tests__/cli.unit.test.ts +448 -0
  208. package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
  209. package/src/__tests__/guid-collision.unit.test.ts +112 -0
  210. package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
  211. package/src/__tests__/load-asset-config.test.ts +121 -0
  212. package/src/__tests__/material-cook-dependencies.unit.test.ts +112 -0
  213. package/src/__tests__/material-cook-receipt.unit.test.ts +148 -0
  214. package/src/__tests__/material-cook-schema.unit.test.ts +152 -0
  215. package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +22 -0
  216. package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
  217. package/src/__tests__/meta-schema-optional.test.ts +116 -0
  218. package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
  219. package/src/__tests__/native-cooker-registry.test.ts +64 -0
  220. package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
  221. package/src/__tests__/offline-evidence.integration.test.ts +106 -0
  222. package/src/__tests__/owner-chain.integration.test.ts +78 -0
  223. package/src/__tests__/pack-v2-fixture-migration.test.ts +87 -0
  224. package/src/__tests__/pack-v2-schema.test.ts +142 -0
  225. package/src/__tests__/pack.unit.test.ts +2218 -0
  226. package/src/__tests__/package-finalizer.contract.test.ts +105 -0
  227. package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
  228. package/src/__tests__/resolve-asset-source.test.ts +121 -0
  229. package/src/__tests__/runtime-projection.unit.test.ts +87 -0
  230. package/src/__tests__/runtime.browser.test.ts +15 -0
  231. package/src/__tests__/scanner-inventory.contract.test.ts +167 -0
  232. package/src/__tests__/scanner-inventory.test.ts +76 -0
  233. package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
  234. package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
  235. package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
  236. package/src/__tests__/scriptable-pack.test-d.ts +131 -0
  237. package/src/__tests__/scriptable-pack.unit.test.ts +789 -0
  238. package/src/__tests__/topology-actions.unit.test.ts +94 -0
  239. package/src/__tests__/topology.unit.test.ts +85 -0
  240. package/src/artifact-path.ts +69 -0
  241. package/src/atlas/run-atlas.ts +358 -0
  242. package/src/atlas/shelf-pack.ts +172 -0
  243. package/src/atlas/upng-ambient.d.ts +13 -0
  244. package/src/bridge.ts +2 -0
  245. package/src/build.ts +139 -0
  246. package/src/builtin.ts +117 -0
  247. package/src/catalog-builder.ts +313 -0
  248. package/src/catalog-delta.ts +189 -0
  249. package/src/catalog-projection.ts +344 -0
  250. package/src/cli-asset.ts +586 -0
  251. package/src/config.ts +36 -0
  252. package/src/deriveAssetName.ts +42 -0
  253. package/src/errors.ts +47 -0
  254. package/src/evidence/material-cook.ts +411 -0
  255. package/src/evidence/offline-evidence.ts +90 -0
  256. package/src/evidence/source-inventory.ts +193 -0
  257. package/src/guid.ts +99 -0
  258. package/src/index.ts +108 -0
  259. package/src/material/artifact-writer.ts +26 -0
  260. package/src/material-cook.ts +15 -0
  261. package/src/mesh-bin-contract.ts +150 -0
  262. package/src/native-cooker-registry.ts +235 -0
  263. package/src/native-cooker.ts +2 -0
  264. package/src/package-finalizer.ts +394 -0
  265. package/src/producer-contract.ts +277 -0
  266. package/src/resolve-asset-source.ts +88 -0
  267. package/src/runtime-projection.ts +100 -0
  268. package/src/runtime-publication.ts +190 -0
  269. package/src/runtime.ts +23 -0
  270. package/src/scanner.ts +715 -0
  271. package/src/schema/material-cook.schema.json +119 -0
  272. package/src/schema-compiled.ts +238 -0
  273. package/src/scriptable-pack-node.ts +1394 -0
  274. package/src/scriptable-pack-worker.ts +308 -0
  275. package/src/scriptable-pack.ts +1006 -0
  276. package/src/topology.ts +157 -0
@@ -0,0 +1,232 @@
1
+ // scanner-mount-cycle.test - scanner step-6 mount-asset cycle coverage
2
+ // (feat-20260608-scene-nesting-ecs-fication M1 / w13).
3
+ //
4
+ // Coverage (AC-12; plan-strategy D-1):
5
+ // (a) acyclic mount chain A -> B -> C: scanner returns Ok (no cycle).
6
+ // (b) two-asset cycle A -> B -> A: scanner returns
7
+ // Err(PackError{ code: 'pack-cyclic-reference', detail.kind: 'mount-asset',
8
+ // detail.cycle: GUID[] }) with the GUIDs in cycle order (first === last).
9
+ // (c) three-asset cycle A -> B -> C -> A: same, longer cycle.
10
+ // (d) self-mount A -> A: same, single-element cycle.
11
+ //
12
+ // The fixtures are hand-authored scene .pack.json files in a tmpdir;
13
+ // each scene asset uses `payload.mounts[].source` as an integer index into
14
+ // the parent scene's `refs[]` (D-1 + research Finding 11). The mount window
15
+ // (memberFirst / memberCount) is set conservatively so the scanner step-6
16
+ // cycle pass triggers before any mount-window collision check.
17
+ //
18
+ // TDD red signal: until w14 wires `payload.mounts[].source` -> `refsByGuid`
19
+ // edge into scanner step-6, the scanner does NOT see the mount edge in the
20
+ // DFS and the cycle assertions miss. With w14 the cycle producer surfaces
21
+ // `pack-cyclic-reference + detail.kind: 'mount-asset'` (w8 landed the
22
+ // detail.kind tagging at the producer).
23
+
24
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
25
+ import { tmpdir } from 'node:os';
26
+ import { join } from 'node:path';
27
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
28
+ import { scan } from '../scanner.js';
29
+
30
+ const GUID_A = 'aa000000-0000-4000-8000-000000000aaa';
31
+ const GUID_B = 'bb000000-0000-4000-8000-000000000bbb';
32
+ const GUID_C = 'cc000000-0000-4000-8000-000000000ccc';
33
+
34
+ interface MountSpec {
35
+ readonly localId: number;
36
+ readonly source: number;
37
+ readonly memberFirst: number;
38
+ readonly memberCount: number;
39
+ }
40
+
41
+ interface SceneAssetSpec {
42
+ readonly guid: string;
43
+ /** GUIDs the scene asset's `payload.mounts[].source` integers index into. */
44
+ readonly refs: readonly string[];
45
+ readonly mounts: readonly MountSpec[];
46
+ }
47
+
48
+ function sceneAssetPack(spec: SceneAssetSpec): unknown {
49
+ return {
50
+ schemaVersion: '1.0.0',
51
+ kind: 'internal-text-package',
52
+ assets: [
53
+ {
54
+ guid: spec.guid,
55
+ kind: 'scene',
56
+ refs: [...spec.refs],
57
+ payload: {
58
+ kind: 'scene',
59
+ entities: [{ localId: 0, components: {} }],
60
+ mounts: spec.mounts.map((m) => ({
61
+ localId: m.localId,
62
+ source: m.source,
63
+ memberFirst: m.memberFirst,
64
+ memberCount: m.memberCount,
65
+ })),
66
+ },
67
+ },
68
+ ],
69
+ };
70
+ }
71
+
72
+ interface CycleErrorShape {
73
+ readonly code: string;
74
+ readonly detail: {
75
+ readonly code?: string;
76
+ readonly kind?: string;
77
+ readonly cycle?: readonly string[];
78
+ };
79
+ }
80
+
81
+ describe('scanner step-6 mount-asset cycle detection (AC-12; D-1)', () => {
82
+ let dir: string;
83
+
84
+ beforeEach(async () => {
85
+ dir = await mkdtemp(join(tmpdir(), 'pack-mount-cycle-'));
86
+ });
87
+
88
+ afterEach(async () => {
89
+ await rm(dir, { recursive: true, force: true });
90
+ });
91
+
92
+ it('(a) accepts an acyclic mount chain A -> B -> C', async () => {
93
+ // A mounts B (source=0 in A.refs) -> B mounts C (source=0 in B.refs) ->
94
+ // C mounts nothing.
95
+ await writeFile(
96
+ join(dir, 'a.pack.json'),
97
+ JSON.stringify(
98
+ sceneAssetPack({
99
+ guid: GUID_A,
100
+ refs: [GUID_B],
101
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
102
+ }),
103
+ ),
104
+ 'utf-8',
105
+ );
106
+ await writeFile(
107
+ join(dir, 'b.pack.json'),
108
+ JSON.stringify(
109
+ sceneAssetPack({
110
+ guid: GUID_B,
111
+ refs: [GUID_C],
112
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
113
+ }),
114
+ ),
115
+ 'utf-8',
116
+ );
117
+ await writeFile(
118
+ join(dir, 'c.pack.json'),
119
+ JSON.stringify(sceneAssetPack({ guid: GUID_C, refs: [], mounts: [] })),
120
+ 'utf-8',
121
+ );
122
+
123
+ const result = await scan([dir]);
124
+ expect(result.ok).toBe(true);
125
+ });
126
+
127
+ it('(b) rejects a two-asset cycle A -> B -> A with kind: mount-asset', async () => {
128
+ await writeFile(
129
+ join(dir, 'a.pack.json'),
130
+ JSON.stringify(
131
+ sceneAssetPack({
132
+ guid: GUID_A,
133
+ refs: [GUID_B],
134
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
135
+ }),
136
+ ),
137
+ 'utf-8',
138
+ );
139
+ await writeFile(
140
+ join(dir, 'b.pack.json'),
141
+ JSON.stringify(
142
+ sceneAssetPack({
143
+ guid: GUID_B,
144
+ refs: [GUID_A],
145
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
146
+ }),
147
+ ),
148
+ 'utf-8',
149
+ );
150
+
151
+ const result = await scan([dir]);
152
+ expect(result.ok).toBe(false);
153
+ if (result.ok) return;
154
+ const err = result.error as unknown as CycleErrorShape;
155
+ expect(err.code).toBe('pack-cyclic-reference');
156
+ expect(err.detail.kind).toBe('mount-asset');
157
+ const cycle = err.detail.cycle ?? [];
158
+ expect(cycle.length).toBeGreaterThanOrEqual(2);
159
+ expect(cycle[0]).toBe(cycle[cycle.length - 1]);
160
+ // Both GUIDs participate in the cycle (case-insensitive compare; scanner
161
+ // lowercases before storing).
162
+ const lower = cycle.map((g) => g.toLowerCase());
163
+ expect(lower).toContain(GUID_A.toLowerCase());
164
+ expect(lower).toContain(GUID_B.toLowerCase());
165
+ });
166
+
167
+ it('(c) rejects a three-asset cycle A -> B -> C -> A with kind: mount-asset', async () => {
168
+ await writeFile(
169
+ join(dir, 'a.pack.json'),
170
+ JSON.stringify(
171
+ sceneAssetPack({
172
+ guid: GUID_A,
173
+ refs: [GUID_B],
174
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
175
+ }),
176
+ ),
177
+ 'utf-8',
178
+ );
179
+ await writeFile(
180
+ join(dir, 'b.pack.json'),
181
+ JSON.stringify(
182
+ sceneAssetPack({
183
+ guid: GUID_B,
184
+ refs: [GUID_C],
185
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
186
+ }),
187
+ ),
188
+ 'utf-8',
189
+ );
190
+ await writeFile(
191
+ join(dir, 'c.pack.json'),
192
+ JSON.stringify(
193
+ sceneAssetPack({
194
+ guid: GUID_C,
195
+ refs: [GUID_A],
196
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
197
+ }),
198
+ ),
199
+ 'utf-8',
200
+ );
201
+
202
+ const result = await scan([dir]);
203
+ expect(result.ok).toBe(false);
204
+ if (result.ok) return;
205
+ const err = result.error as unknown as CycleErrorShape;
206
+ expect(err.code).toBe('pack-cyclic-reference');
207
+ expect(err.detail.kind).toBe('mount-asset');
208
+ const cycle = err.detail.cycle ?? [];
209
+ expect(cycle.length).toBeGreaterThanOrEqual(3);
210
+ expect(cycle[0]).toBe(cycle[cycle.length - 1]);
211
+ });
212
+
213
+ it('(d) rejects a self-mount A -> A', async () => {
214
+ await writeFile(
215
+ join(dir, 'a.pack.json'),
216
+ JSON.stringify(
217
+ sceneAssetPack({
218
+ guid: GUID_A,
219
+ refs: [GUID_A],
220
+ mounts: [{ localId: 1, source: 0, memberFirst: 2, memberCount: 1 }],
221
+ }),
222
+ ),
223
+ 'utf-8',
224
+ );
225
+ const result = await scan([dir]);
226
+ expect(result.ok).toBe(false);
227
+ if (result.ok) return;
228
+ const err = result.error as unknown as CycleErrorShape;
229
+ expect(err.code).toBe('pack-cyclic-reference');
230
+ expect(err.detail.kind).toBe('mount-asset');
231
+ });
232
+ });
@@ -0,0 +1,47 @@
1
+ // @perf-budget-skip: intentional ScriptablePack CLI integration gate.
2
+
3
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { describe, expect, it } from 'vitest';
7
+ import { runCliAsset } from '../cli-asset.js';
8
+
9
+ describe('ScriptablePack CLI Meta inspection', () => {
10
+ it('loads the definition without invoking build', async () => {
11
+ const root = await mkdtemp(join(tmpdir(), 'forgeax-scriptable-pack-'));
12
+ try {
13
+ const source = join(root, 'house.pack.mjs');
14
+ await writeFile(
15
+ source,
16
+ `
17
+ const guid = (hex) => new Uint8Array(hex.match(/../g).map((byte) => Number.parseInt(byte, 16)));
18
+ export default {
19
+ schemaVersion: '1.0.0',
20
+ packageId: guid('019ffa978b397ad284cc273268591ab4'),
21
+ assets: {
22
+ wall: { guid: guid('019ffa97a5ee7645b613043323952808'), kind: 'mesh' }
23
+ },
24
+ externalAssets: {},
25
+ build() { throw new Error('META_MUST_NOT_BUILD'); }
26
+ };
27
+ `,
28
+ );
29
+ const stdout: string[] = [];
30
+ const stderr: string[] = [];
31
+ const code = await runCliAsset(['meta', source, '--json'], {
32
+ stdoutWrite: (line) => stdout.push(line),
33
+ stderrWrite: (line) => stderr.push(line),
34
+ cwd: root,
35
+ });
36
+ expect(code).toBe(0);
37
+ expect(stderr).toEqual([]);
38
+ expect(JSON.parse(stdout[0] ?? '{}')).toMatchObject({
39
+ importer: 'pack-ts',
40
+ source,
41
+ subAssets: [{ sourceKey: 'wall', kind: 'mesh' }],
42
+ });
43
+ } finally {
44
+ await rm(root, { recursive: true, force: true });
45
+ }
46
+ });
47
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ type ProducerSemanticIdentityInput,
4
+ producerRelativeDdcIdentity,
5
+ producerRelativeDdcKey,
6
+ producerRelativeLogicalPath,
7
+ } from '../evidence/source-inventory.js';
8
+ import { scriptablePackDdcKey } from '../scriptable-pack-node.js';
9
+
10
+ const base = (
11
+ overrides: Partial<ProducerSemanticIdentityInput> = {},
12
+ ): ProducerSemanticIdentityInput => ({
13
+ producerRoot: '/checkout/game-a',
14
+ sourcePath: '/checkout/game-a/assets/vehicle.pack.ts',
15
+ sourceDigest: 'sha256:source',
16
+ schemaVersion: '1.0.0',
17
+ importer: 'scriptable-pack-importer@2',
18
+ codec: 'pack-v2',
19
+ settings: { compression: 'basis', quality: 8 },
20
+ producer: 'scriptable-pack@4',
21
+ profile: 'webgpu/v1',
22
+ declaredGuids: ['guid-vehicle'],
23
+ ...overrides,
24
+ });
25
+
26
+ describe('producer-relative ScriptablePack DDC identity', () => {
27
+ it('uses producer-root-relative logical path rather than absolute checkout path', () => {
28
+ expect(
29
+ producerRelativeLogicalPath('/checkout/game-a', '/checkout/game-a/assets/vehicle.pack.ts'),
30
+ ).toBe('assets/vehicle.pack.ts');
31
+ expect(
32
+ producerRelativeDdcKey(
33
+ base({
34
+ producerRoot: '/checkout/game-b',
35
+ sourcePath: '/checkout/game-b/assets/vehicle.pack.ts',
36
+ }),
37
+ ),
38
+ ).toBe(producerRelativeDdcKey(base()));
39
+ });
40
+
41
+ it.each([
42
+ ['source', { sourceDigest: 'sha256:changed' }],
43
+ ['importer', { importer: 'other-importer@1' }],
44
+ ['codec', { codec: 'other-codec' }],
45
+ ['settings', { settings: { compression: 'raw', quality: 8 } }],
46
+ ['producer', { producer: 'other-producer@1' }],
47
+ ['profile', { profile: 'webgl2/fallback' }],
48
+ ['schema', { schemaVersion: '2.0.0' }],
49
+ ])('misses when semantic input %s changes', (_name, change) => {
50
+ expect(producerRelativeDdcKey(base(change))).not.toBe(producerRelativeDdcKey(base()));
51
+ });
52
+
53
+ it('returns an inspectable identity and preserves producer-relative path in the key input', () => {
54
+ const identity = producerRelativeDdcIdentity(base());
55
+
56
+ expect(identity).toMatchObject({
57
+ logicalPath: 'assets/vehicle.pack.ts',
58
+ sourceDigest: 'sha256:source',
59
+ key: expect.stringMatching(/^[a-f0-9]{64}$/),
60
+ });
61
+ expect(scriptablePackDdcKey(base())).toBe(identity.key);
62
+ });
63
+ });
@@ -0,0 +1,131 @@
1
+ import type {
2
+ AnimationClip,
3
+ AnimationGraph,
4
+ AssetGuid,
5
+ AudioClipAsset,
6
+ EquirectAsset,
7
+ FontAsset,
8
+ MaterialAsset,
9
+ MeshAsset,
10
+ ParticleEffectAsset,
11
+ RenderPipelineAsset,
12
+ SamplerAsset,
13
+ SceneAsset,
14
+ SkeletonAsset,
15
+ SkinAsset,
16
+ TextureAsset,
17
+ TilesetAsset,
18
+ VideoAsset,
19
+ } from '@forgeax/engine-types';
20
+ import { describe, expectTypeOf, it } from 'vitest';
21
+ import type {
22
+ ScriptablePackAssetDeclarations,
23
+ ScriptablePackAssetFor,
24
+ ScriptablePackAssetKind,
25
+ ScriptablePackGatewayOperation,
26
+ ScriptablePackOutputs,
27
+ } from '../scriptable-pack.js';
28
+ import { SCRIPTABLE_PACK_ASSET_KINDS } from '../scriptable-pack.js';
29
+
30
+ function typeContract(): void {
31
+ const meshGuid = null as unknown as AssetGuid;
32
+ const sceneGuid = null as unknown as AssetGuid;
33
+ const mesh = null as unknown as MeshAsset;
34
+ const scene = null as unknown as SceneAsset;
35
+
36
+ const assets = {
37
+ mesh: { guid: meshGuid, kind: 'mesh' },
38
+ scene: { guid: sceneGuid, kind: 'scene' },
39
+ } as const satisfies ScriptablePackAssetDeclarations;
40
+
41
+ const valid = { mesh, scene } satisfies ScriptablePackOutputs<typeof assets>;
42
+ void valid;
43
+
44
+ // @ts-expect-error mesh descriptor requires MeshAsset
45
+ const wrongKind = { mesh: scene, scene } satisfies ScriptablePackOutputs<typeof assets>;
46
+ void wrongKind;
47
+
48
+ const allKinds = SCRIPTABLE_PACK_ASSET_KINDS;
49
+ type ExpectedKinds =
50
+ | MeshAsset
51
+ | MaterialAsset
52
+ | SceneAsset
53
+ | TextureAsset
54
+ | EquirectAsset
55
+ | SamplerAsset
56
+ | FontAsset
57
+ | RenderPipelineAsset
58
+ | TilesetAsset
59
+ | VideoAsset
60
+ | SkeletonAsset
61
+ | SkinAsset
62
+ | AnimationClip
63
+ | AnimationGraph
64
+ | AudioClipAsset
65
+ | ParticleEffectAsset;
66
+ type DiscoveredKinds = ScriptablePackAssetFor<{
67
+ readonly guid: AssetGuid;
68
+ readonly kind: (typeof allKinds)[number];
69
+ }>;
70
+ expectTypeOf<DiscoveredKinds>().toEqualTypeOf<ExpectedKinds>();
71
+
72
+ // @ts-expect-error unknown kinds cannot enter the ordinary producer matrix
73
+ const unknownKind: ScriptablePackAssetKind = 'unknown-kind';
74
+ void unknownKind;
75
+
76
+ // @ts-expect-error scene output is required
77
+ const missing = { mesh } satisfies ScriptablePackOutputs<typeof assets>;
78
+ void missing;
79
+
80
+ // @ts-expect-error plain strings are not AssetGuid values
81
+ const plainStringGuid: AssetGuid = '019ffa97-a5ee-7645-b613-043323952808';
82
+ void plainStringGuid;
83
+
84
+ const gatewayOperations = [
85
+ {
86
+ requestId: 'create',
87
+ kind: 'create-scriptable-pack',
88
+ sourcePath: 'new.pack.ts',
89
+ initialOutput: { sourceKey: 'scene', kind: 'scene' },
90
+ },
91
+ {
92
+ requestId: 'add',
93
+ kind: 'add-output',
94
+ sourcePath: 'new.pack.ts',
95
+ sourceKey: 'mesh',
96
+ assetKind: 'mesh',
97
+ },
98
+ {
99
+ requestId: 'external',
100
+ kind: 'add-external-asset',
101
+ sourcePath: 'new.pack.ts',
102
+ alias: 'material',
103
+ guid: meshGuid,
104
+ },
105
+ {
106
+ requestId: 'rename',
107
+ kind: 'rename-display',
108
+ sourcePath: 'new.pack.ts',
109
+ target: { kind: 'output', sourceKey: 'mesh' },
110
+ name: 'Wall',
111
+ },
112
+ { requestId: 'remove', kind: 'remove-output', sourcePath: 'new.pack.ts', sourceKey: 'mesh' },
113
+ {
114
+ requestId: 'clone',
115
+ kind: 'clone-scriptable-pack',
116
+ sourcePath: 'new.pack.ts',
117
+ targetPath: 'clone.pack.ts',
118
+ },
119
+ { requestId: 'preflight', kind: 'preflight', sourcePath: 'new.pack.ts' },
120
+ { requestId: 'inspect', kind: 'inspect-meta', sourcePath: 'new.pack.ts' },
121
+ { requestId: 'rebuild', kind: 'rebuild', sourcePath: 'new.pack.ts' },
122
+ { requestId: 'cold-cook', kind: 'cold-cook', sourcePath: 'new.pack.ts' },
123
+ ] as const satisfies readonly ScriptablePackGatewayOperation[];
124
+ void gatewayOperations;
125
+ }
126
+
127
+ describe('ScriptablePack public type contract', () => {
128
+ it('constrains output payloads, GUID brands, and gateway operations', () => {
129
+ typeContract();
130
+ });
131
+ });