@forgeax/engine-pack 0.1.2

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 (228) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +370 -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__/pack-v2-fixture-migration.test.d.ts +2 -0
  51. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
  52. package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
  53. package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
  54. package/dist/__tests__/pack.unit.test.d.ts +2 -0
  55. package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
  57. package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
  59. package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
  60. package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
  61. package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
  63. package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
  64. package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
  65. package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
  66. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
  67. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
  68. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
  69. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
  71. package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
  72. package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
  73. package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
  75. package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/topology.unit.test.d.ts +2 -0
  77. package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
  78. package/dist/artifact-path.d.ts +9 -0
  79. package/dist/artifact-path.d.ts.map +1 -0
  80. package/dist/atlas/run-atlas.d.ts +8 -0
  81. package/dist/atlas/run-atlas.d.ts.map +1 -0
  82. package/dist/atlas/shelf-pack.d.ts +55 -0
  83. package/dist/atlas/shelf-pack.d.ts.map +1 -0
  84. package/dist/bridge.d.ts +2 -0
  85. package/dist/bridge.d.ts.map +1 -0
  86. package/dist/bridge.mjs +3 -0
  87. package/dist/bridge.mjs.map +1 -0
  88. package/dist/builtin.d.ts +24 -0
  89. package/dist/builtin.d.ts.map +1 -0
  90. package/dist/cli-asset.d.ts +22 -0
  91. package/dist/cli-asset.d.ts.map +1 -0
  92. package/dist/cli-asset.mjs +9950 -0
  93. package/dist/cli-asset.mjs.map +1 -0
  94. package/dist/config.d.ts +6 -0
  95. package/dist/config.d.ts.map +1 -0
  96. package/dist/config.mjs +29 -0
  97. package/dist/config.mjs.map +1 -0
  98. package/dist/deriveAssetName.d.ts +15 -0
  99. package/dist/deriveAssetName.d.ts.map +1 -0
  100. package/dist/errors.d.ts +30 -0
  101. package/dist/errors.d.ts.map +1 -0
  102. package/dist/errors.mjs +19 -0
  103. package/dist/errors.mjs.map +1 -0
  104. package/dist/evidence/material-cook.d.ts +70 -0
  105. package/dist/evidence/material-cook.d.ts.map +1 -0
  106. package/dist/evidence/offline-evidence.d.ts +25 -0
  107. package/dist/evidence/offline-evidence.d.ts.map +1 -0
  108. package/dist/evidence/source-inventory.d.ts +29 -0
  109. package/dist/evidence/source-inventory.d.ts.map +1 -0
  110. package/dist/guid.d.ts +45 -0
  111. package/dist/guid.d.ts.map +1 -0
  112. package/dist/guid.mjs +94 -0
  113. package/dist/guid.mjs.map +1 -0
  114. package/dist/index.d.ts +15 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.mjs +8475 -0
  117. package/dist/index.mjs.map +1 -0
  118. package/dist/material/artifact-writer.d.ts +14 -0
  119. package/dist/material/artifact-writer.d.ts.map +1 -0
  120. package/dist/mesh-bin-contract.d.ts +36 -0
  121. package/dist/mesh-bin-contract.d.ts.map +1 -0
  122. package/dist/name.mjs +22 -0
  123. package/dist/name.mjs.map +1 -0
  124. package/dist/native-cooker-registry.d.ts +28 -0
  125. package/dist/native-cooker-registry.d.ts.map +1 -0
  126. package/dist/native-cooker.d.ts +3 -0
  127. package/dist/native-cooker.d.ts.map +1 -0
  128. package/dist/native-cooker.mjs +105 -0
  129. package/dist/native-cooker.mjs.map +1 -0
  130. package/dist/producer-contract.d.ts +19 -0
  131. package/dist/producer-contract.d.ts.map +1 -0
  132. package/dist/resolve-asset-source.d.ts +4 -0
  133. package/dist/resolve-asset-source.d.ts.map +1 -0
  134. package/dist/resolve-asset-source.mjs +99 -0
  135. package/dist/resolve-asset-source.mjs.map +1 -0
  136. package/dist/runtime-projection.d.ts +14 -0
  137. package/dist/runtime-projection.d.ts.map +1 -0
  138. package/dist/scanner.d.ts +80 -0
  139. package/dist/scanner.d.ts.map +1 -0
  140. package/dist/scanner.mjs +9015 -0
  141. package/dist/scanner.mjs.map +1 -0
  142. package/dist/schema-compiled.d.ts +12 -0
  143. package/dist/schema-compiled.d.ts.map +1 -0
  144. package/dist/schema.mjs +7369 -0
  145. package/dist/schema.mjs.map +1 -0
  146. package/dist/scriptable-pack-node.d.ts +67 -0
  147. package/dist/scriptable-pack-node.d.ts.map +1 -0
  148. package/dist/scriptable-pack-node.mjs +1590 -0
  149. package/dist/scriptable-pack-node.mjs.map +1 -0
  150. package/dist/scriptable-pack-worker.d.ts +2 -0
  151. package/dist/scriptable-pack-worker.d.ts.map +1 -0
  152. package/dist/scriptable-pack-worker.mjs +223 -0
  153. package/dist/scriptable-pack-worker.mjs.map +1 -0
  154. package/dist/scriptable-pack.d.ts +525 -0
  155. package/dist/scriptable-pack.d.ts.map +1 -0
  156. package/dist/scriptable-pack.mjs +583 -0
  157. package/dist/scriptable-pack.mjs.map +1 -0
  158. package/dist/topology.d.ts +10 -0
  159. package/dist/topology.d.ts.map +1 -0
  160. package/package.json +129 -0
  161. package/schema/meta.schema.json +173 -0
  162. package/schema/pack.schema.json +232 -0
  163. package/src/__tests__/ai-recovery.unit.test.ts +114 -0
  164. package/src/__tests__/artifact-path.test.ts +41 -0
  165. package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
  166. package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
  167. package/src/__tests__/asset-evidence-schema.test.ts +86 -0
  168. package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
  169. package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
  170. package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
  171. package/src/__tests__/cli.unit.test.ts +448 -0
  172. package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
  173. package/src/__tests__/guid-collision.unit.test.ts +112 -0
  174. package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
  175. package/src/__tests__/load-asset-config.test.ts +121 -0
  176. package/src/__tests__/material-cook-dependencies.unit.test.ts +45 -0
  177. package/src/__tests__/material-cook-receipt.unit.test.ts +91 -0
  178. package/src/__tests__/material-cook-schema.unit.test.ts +98 -0
  179. package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +21 -0
  180. package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
  181. package/src/__tests__/meta-schema-optional.test.ts +116 -0
  182. package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
  183. package/src/__tests__/native-cooker-registry.test.ts +64 -0
  184. package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
  185. package/src/__tests__/offline-evidence.integration.test.ts +106 -0
  186. package/src/__tests__/pack-v2-fixture-migration.test.ts +64 -0
  187. package/src/__tests__/pack-v2-schema.test.ts +142 -0
  188. package/src/__tests__/pack.unit.test.ts +2218 -0
  189. package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
  190. package/src/__tests__/resolve-asset-source.test.ts +121 -0
  191. package/src/__tests__/runtime-projection.unit.test.ts +87 -0
  192. package/src/__tests__/scanner-inventory.test.ts +72 -0
  193. package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
  194. package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
  195. package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
  196. package/src/__tests__/scriptable-pack.test-d.ts +131 -0
  197. package/src/__tests__/scriptable-pack.unit.test.ts +747 -0
  198. package/src/__tests__/topology-actions.unit.test.ts +94 -0
  199. package/src/__tests__/topology.unit.test.ts +85 -0
  200. package/src/artifact-path.ts +69 -0
  201. package/src/atlas/run-atlas.ts +358 -0
  202. package/src/atlas/shelf-pack.ts +176 -0
  203. package/src/atlas/upng-ambient.d.ts +13 -0
  204. package/src/bridge.ts +2 -0
  205. package/src/builtin.ts +79 -0
  206. package/src/cli-asset.ts +590 -0
  207. package/src/config.ts +36 -0
  208. package/src/deriveAssetName.ts +42 -0
  209. package/src/errors.ts +47 -0
  210. package/src/evidence/material-cook.ts +332 -0
  211. package/src/evidence/offline-evidence.ts +90 -0
  212. package/src/evidence/source-inventory.ts +193 -0
  213. package/src/guid.ts +99 -0
  214. package/src/index.ts +104 -0
  215. package/src/material/artifact-writer.ts +26 -0
  216. package/src/mesh-bin-contract.ts +150 -0
  217. package/src/native-cooker-registry.ts +163 -0
  218. package/src/native-cooker.ts +2 -0
  219. package/src/producer-contract.ts +277 -0
  220. package/src/resolve-asset-source.ts +93 -0
  221. package/src/runtime-projection.ts +100 -0
  222. package/src/scanner.ts +716 -0
  223. package/src/schema/material-cook.schema.json +79 -0
  224. package/src/schema-compiled.ts +238 -0
  225. package/src/scriptable-pack-node.ts +1390 -0
  226. package/src/scriptable-pack-worker.ts +306 -0
  227. package/src/scriptable-pack.ts +884 -0
  228. package/src/topology.ts +156 -0
@@ -0,0 +1,79 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "ForgeaX cooked material record",
4
+ "type": "object",
5
+ "required": ["schemaVersion", "guid", "resolved", "refs", "artifact", "receipt"],
6
+ "properties": {
7
+ "schemaVersion": { "const": "material-cook/2" },
8
+ "guid": { "type": "string", "minLength": 1 },
9
+ "materialGuid": { "type": "string", "minLength": 1 },
10
+ "publicationGeneration": { "type": "integer", "minimum": 1 },
11
+ "specializationKey": { "type": "string", "minLength": 1 },
12
+ "artifactDigest": { "type": "string", "pattern": "^sha256:" },
13
+ "sourceClosure": { "type": "array", "items": { "type": "string" } },
14
+ "parameterContract": {
15
+ "type": "object",
16
+ "required": ["parameters", "values"],
17
+ "properties": {
18
+ "parameters": { "type": "array" },
19
+ "values": { "type": "object" }
20
+ },
21
+ "additionalProperties": false
22
+ },
23
+ "authored": { "type": "object", "required": ["kind"] },
24
+ "resolved": { "type": "object", "required": ["passes", "parameters", "values"] },
25
+ "refs": {
26
+ "type": "object",
27
+ "required": ["parent", "textures", "samplers", "modules"],
28
+ "properties": {
29
+ "parent": { "type": "array", "items": { "type": "string" } },
30
+ "textures": { "type": "array", "items": { "type": "string" } },
31
+ "samplers": { "type": "array", "items": { "type": "string" } },
32
+ "modules": { "type": "array", "items": { "type": "string" } }
33
+ },
34
+ "additionalProperties": false
35
+ },
36
+ "artifact": {
37
+ "type": "object",
38
+ "required": ["mediaType", "path", "digest", "bytes"],
39
+ "properties": {
40
+ "mediaType": { "type": "string" },
41
+ "path": { "type": "string" },
42
+ "digest": { "type": "string", "pattern": "^sha256:" },
43
+ "bytes": { "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 255 } }
44
+ },
45
+ "additionalProperties": false
46
+ },
47
+ "receipt": {
48
+ "type": "object",
49
+ "required": [
50
+ "schemaVersion",
51
+ "sourceClosure",
52
+ "profile",
53
+ "compilerVersion",
54
+ "inputDigest",
55
+ "outputDigest",
56
+ "layoutIdentity",
57
+ "derivedInterface"
58
+ ],
59
+ "properties": {
60
+ "schemaVersion": { "const": "material-cook/2" },
61
+ "sourceClosure": { "type": "array", "items": { "type": "string" } },
62
+ "profile": { "type": "string" },
63
+ "compilerVersion": { "type": "string" },
64
+ "inputDigest": { "type": "string" },
65
+ "outputDigest": { "type": "string" },
66
+ "layoutIdentity": { "type": "string", "minLength": 1 },
67
+ "derivedInterface": {
68
+ "type": "object",
69
+ "required": ["layoutIdentity"],
70
+ "properties": { "layoutIdentity": { "type": "string", "minLength": 1 } },
71
+ "additionalProperties": false
72
+ },
73
+ "publicationGeneration": { "type": "integer", "minimum": 1 }
74
+ },
75
+ "additionalProperties": false
76
+ }
77
+ },
78
+ "additionalProperties": false
79
+ }
@@ -0,0 +1,238 @@
1
+ import type { PackV2 } from '@forgeax/engine-types';
2
+ import Ajv, { type ValidateFunction } from 'ajv';
3
+ import addFormats from 'ajv-formats';
4
+ import metaSchemaJson from '../schema/meta.schema.json' with { type: 'json' };
5
+ import packSchemaJson from '../schema/pack.schema.json' with { type: 'json' };
6
+
7
+ const ajv = new Ajv({ strict: true, allErrors: false });
8
+ addFormats(ajv, ['uuid']);
9
+
10
+ // Module-top-level compiled validators - compiled once on import, never recreated.
11
+ export const validateMeta = ajv.compile(metaSchemaJson);
12
+ export const validatePack = ajv.compile(packSchemaJson);
13
+
14
+ export function validatePackV2(value: unknown): value is PackV2 {
15
+ if (!validatePack(value) || !isPackV2Shape(value)) return false;
16
+
17
+ const seenGuids = new Set<string>();
18
+ for (const asset of value.assets) {
19
+ if (seenGuids.has(asset.guid)) return false;
20
+ seenGuids.add(asset.guid);
21
+ }
22
+ return true;
23
+ }
24
+
25
+ function isPackV2Shape(value: unknown): value is PackV2 {
26
+ return (
27
+ typeof value === 'object' &&
28
+ value !== null &&
29
+ 'schemaVersion' in value &&
30
+ value.schemaVersion === '2.0.0'
31
+ );
32
+ }
33
+
34
+ // === SceneAsset payload validator factory (feat-20260514 w5 / D-P4) =============
35
+ //
36
+ // Decision anchors:
37
+ // - plan-strategy §D-P4 (per-component additionalProperties:false, path a;
38
+ // path b "extend PackErrorDetail" rejected to keep the 8-member
39
+ // PackErrorCode closed union frozen)
40
+ // - requirements §AC-08(b) (typo field name fail-fast through ajv with the
41
+ // verbatim 'additional properties' message wording)
42
+ // - requirements §AC-10 (no new SceneErrorCode / PackErrorCode introduced)
43
+ //
44
+ // `buildSceneAssetValidator(componentSchemas)` returns a fresh ajv
45
+ // `ValidateFunction` whose JSON Schema sub-tree describes:
46
+ // {
47
+ // kind: 'scene',
48
+ // nodes: SceneEntity[]
49
+ // }
50
+ // where each SceneEntity has the closed shape
51
+ // { localId: integer, components: <closed map keyed by registered tokens> }
52
+ // and the per-component sub-schema is composed verbatim from the caller's
53
+ // `componentSchemas[name]` entry. `additionalProperties: false` is applied
54
+ // at three layers: top-level SceneAsset, SceneEntity, and SceneEntity.components,
55
+ // so ajv emits an `additional properties` ajvError on any typo or unknown
56
+ // component token (AC-08(b)).
57
+ //
58
+ // The runtime layer feeds `componentSchemas` from the live `defineComponent`
59
+ // registry; this factory itself stays ECS-free (charter proposition 5
60
+ // consistent abstraction: pack package is layered below ecs and runtime,
61
+ // the per-component schemas arrive as plain ajv-compatible JSON Schema
62
+ // objects).
63
+ export function buildSceneAssetValidator(
64
+ componentSchemas: Readonly<Record<string, object>>,
65
+ ): ValidateFunction {
66
+ // Use a separate ajv instance so the dynamically registered closed map of
67
+ // component sub-schemas does not pollute the module-top-level validators
68
+ // above (they may be re-built across test fixtures with different
69
+ // component sets).
70
+ const localAjv = new Ajv({ strict: true, allErrors: false });
71
+ addFormats(localAjv, ['uuid']);
72
+
73
+ const componentsProperties: Record<string, object> = {};
74
+ for (const [name, sub] of Object.entries(componentSchemas)) {
75
+ componentsProperties[name] = sub;
76
+ }
77
+
78
+ // MountOverride schema (feat-20260608-scene-nesting-ecs-fication M1 / w11;
79
+ // feat-20260713-mount-override-component-add-and-shared-ref-round M1 / w3;
80
+ // requirements §S-10 / AC-03): validates each entry in mounts[].overrides[].
81
+ // `field` is optional — it is a component-granular add-or-patch discriminant
82
+ // (present -> patch one field with `value`; absent -> add/upsert the whole
83
+ // `comp` with `value` as its per-field map). `localId` / `comp` / `value`
84
+ // stay required; `additionalProperties: false` rejects any `op`-tag
85
+ // discriminant so downstream consumers never branch on it. Field-level type
86
+ // validation (matching the schema vocab) is enforced at runtime via
87
+ // setSceneOverride (D-9 / EcsErrorCode 'scene-override-type-mismatch').
88
+ const mountOverrideSchema = {
89
+ type: 'object',
90
+ additionalProperties: false,
91
+ required: ['localId', 'comp', 'value'],
92
+ properties: {
93
+ localId: { type: 'integer', minimum: 0 },
94
+ comp: { type: 'string', minLength: 1 },
95
+ field: { type: 'string', minLength: 1 },
96
+ value: {},
97
+ },
98
+ };
99
+
100
+ // SceneInstanceMount schema (feat-20260608-scene-nesting-ecs-fication
101
+ // M1 / w11; requirements §S-10): validates each mounts[] entry. parent /
102
+ // components / overrides are optional; cross-mount window collision and
103
+ // mount.source / mount.memberCount agreement with referenced child
104
+ // SceneAsset are checked at the build-time scanner (D-1, w14), not here.
105
+ const mountSchema = {
106
+ type: 'object',
107
+ additionalProperties: false,
108
+ required: ['localId', 'source', 'memberFirst', 'memberCount'],
109
+ properties: {
110
+ localId: { type: 'integer', minimum: 0 },
111
+ source: { type: 'integer', minimum: 0 },
112
+ memberFirst: { type: 'integer', minimum: 0 },
113
+ memberCount: { type: 'integer', minimum: 0 },
114
+ parent: { type: 'integer', minimum: 0 },
115
+ components: {
116
+ type: 'object',
117
+ additionalProperties: false,
118
+ properties: componentsProperties,
119
+ },
120
+ overrides: {
121
+ type: 'array',
122
+ items: mountOverrideSchema,
123
+ },
124
+ publicationFence: {
125
+ type: 'object',
126
+ additionalProperties: false,
127
+ required: [
128
+ 'schemaVersion',
129
+ 'sourcePath',
130
+ 'sourceRevision',
131
+ 'publicationGeneration',
132
+ 'outputDigest',
133
+ 'outputSetDigest',
134
+ 'receiptIdentity',
135
+ ],
136
+ properties: {
137
+ schemaVersion: { type: 'string', const: 'scene-publication-fence/1' },
138
+ sourcePath: { type: 'string', minLength: 1 },
139
+ sourceRevision: { type: 'string', minLength: 1 },
140
+ publicationGeneration: { type: 'integer', minimum: 1 },
141
+ outputDigest: { type: 'string', minLength: 1 },
142
+ outputSetDigest: { type: 'string', minLength: 1 },
143
+ receiptIdentity: { type: 'string', minLength: 1 },
144
+ },
145
+ },
146
+ },
147
+ };
148
+
149
+ const sceneSchema = {
150
+ type: 'object',
151
+ additionalProperties: false,
152
+ required: ['kind', 'entities'],
153
+ properties: {
154
+ kind: { type: 'string', const: 'scene' },
155
+ entities: {
156
+ type: 'array',
157
+ items: {
158
+ type: 'object',
159
+ additionalProperties: false,
160
+ required: ['localId', 'components'],
161
+ properties: {
162
+ localId: { type: 'integer', minimum: 0 },
163
+ components: {
164
+ type: 'object',
165
+ additionalProperties: false,
166
+ properties: componentsProperties,
167
+ },
168
+ },
169
+ },
170
+ },
171
+ // Optional top-level mounts[] (feat-20260608-scene-nesting-ecs-fication
172
+ // M1 / w11). Missing mounts is semantically equivalent to mounts: []
173
+ // (plan-strategy §6.3 back-compat); ajv default produces the empty
174
+ // array on absent input.
175
+ mounts: {
176
+ type: 'array',
177
+ items: mountSchema,
178
+ },
179
+ },
180
+ };
181
+
182
+ return localAjv.compile(sceneSchema);
183
+ }
184
+
185
+ // === MaterialAsset payload validator factory (feat-20260523-shader-template-instance-split M1-T06) ===
186
+ //
187
+ // Decision anchors:
188
+ // - plan-strategy D-PackKind (factory pattern, same shape as buildSceneAssetValidator)
189
+ // - plan-strategy D-ParamTypeWhitelist (paramTypeWhitelist consumed here)
190
+ // - requirements AC-04 (v1 type set SSOT; validator covers all param type boundaries)
191
+ //
192
+ // `buildMaterialAssetValidator(paramTypeWhitelist)` returns an ajv
193
+ // `ValidateFunction` that validates:
194
+ // {
195
+ // materialShader: string,
196
+ // paramSchema: ParamSchemaEntry[],
197
+ // values: object
198
+ // }
199
+ // Each ParamSchemaEntry.type must be in the whitelist (D-ParamTypeWhitelist).
200
+ // `additionalProperties: false` at top level so ajv rejects unknown fields.
201
+ // values is free-form (object) — the runtime layer does deeper validation.
202
+ //
203
+ // Uses a separate ajv instance per call so different whitelists across
204
+ // test fixtures do not pollute the module-top-level validators.
205
+ export function buildMaterialAssetValidator(
206
+ paramTypeWhitelist: ReadonlySet<string>,
207
+ ): ValidateFunction {
208
+ const localAjv = new Ajv({ strict: true, allErrors: false });
209
+
210
+ const whitelistEnum = Array.from(paramTypeWhitelist);
211
+
212
+ const paramSchemaEntry = {
213
+ type: 'object',
214
+ additionalProperties: false,
215
+ required: ['name', 'type'],
216
+ properties: {
217
+ name: { type: 'string', minLength: 1 },
218
+ type: { type: 'string', enum: whitelistEnum },
219
+ default: {},
220
+ },
221
+ };
222
+
223
+ const materialSchema = {
224
+ type: 'object',
225
+ additionalProperties: false,
226
+ required: ['materialShader', 'paramSchema', 'values'],
227
+ properties: {
228
+ materialShader: { type: 'string', minLength: 1 },
229
+ paramSchema: {
230
+ type: 'array',
231
+ items: paramSchemaEntry,
232
+ },
233
+ values: { type: 'object' },
234
+ },
235
+ };
236
+
237
+ return localAjv.compile(materialSchema);
238
+ }