@pixel-point/toolcraft 0.0.17 → 0.0.19

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 (503) hide show
  1. package/package.json +1 -1
  2. package/src/generate-demand-only-lifecycle.test.mjs +9 -1
  3. package/src/generate-performance-iteration-execution.test.mjs +3 -3
  4. package/src/generate-test-delivery-architecture-assertions.mjs +11 -73
  5. package/src/generate-test-demand-only-lifecycle.mjs +34 -36
  6. package/src/generate-test-dependency-sandbox.mjs +4 -1
  7. package/src/generate-test-doc-assertions.mjs +81 -89
  8. package/src/generate-test-doc-assertions.test.mjs +44 -1
  9. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  10. package/src/generate-test-export-doc-assertions.mjs +53 -0
  11. package/src/generate-test-file-assertions.mjs +77 -83
  12. package/src/generate-test-integrity-assertions.mjs +5 -2
  13. package/src/generate-test-integrity-manifest-assertions.mjs +16 -5
  14. package/src/generate-test-integrity-ownership-assertions.mjs +36 -4
  15. package/src/generate-test-package-assertions.mjs +4 -0
  16. package/src/generate-test-product-fixture.mjs +1 -1
  17. package/src/generate-test-product-fixture.test.mjs +35 -38
  18. package/src/generate-test-product-iteration.mjs +0 -1
  19. package/src/generate-test-product-iteration.test.mjs +17 -1
  20. package/src/generate-test-runtime-boundary-assertions.mjs +178 -0
  21. package/src/generate-test-standalone-assertions.mjs +7 -13
  22. package/src/generate-test-standalone-delivery-stages.mjs +2 -86
  23. package/src/generate.test.mjs +4 -0
  24. package/src/generated-integrity-manifest.test.mjs +5 -2
  25. package/src/generated-source-policy.test.mjs +0 -4
  26. package/src/package-json.mjs +5 -0
  27. package/src/package-json.test.mjs +21 -0
  28. package/src/package-manager.mjs +2 -0
  29. package/src/package-manager.test.mjs +8 -0
  30. package/src/packaged-template-fallback-helpers.mjs +254 -0
  31. package/src/packaged-template-fallback.test.mjs +121 -46
  32. package/src/packaged-template-runtime-assertions.mjs +150 -0
  33. package/templates/runtime/contracts/component-contracts.choices.test.ts +64 -5
  34. package/templates/runtime/contracts/component-contracts.choices.ts +55 -5
  35. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  36. package/templates/runtime/contracts/component-contracts.inputs.ts +8 -7
  37. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  38. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  39. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +22 -8
  40. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  41. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
  42. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +4 -1
  43. package/templates/runtime/contracts/component-contracts.runtime.ts +19 -12
  44. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  45. package/templates/runtime/contracts/component-contracts.visual.test.ts +20 -0
  46. package/templates/runtime/contracts/component-contracts.visual.ts +4 -0
  47. package/templates/runtime/contracts/decision-contracts.test.ts +35 -11
  48. package/templates/runtime/contracts/decision-contracts.ts +12 -12
  49. package/templates/runtime/contracts/performance-verification-policy.test.ts +12 -12
  50. package/templates/runtime/contracts/performance-verification-policy.ts +8 -8
  51. package/templates/runtime/export/artifact-download.ts +1 -0
  52. package/templates/runtime/export/artifact-frame-renderer.ts +2 -15
  53. package/templates/runtime/export/artifact-frame-state.ts +16 -0
  54. package/templates/runtime/export/artifact-scene-frame.test.ts +3 -3
  55. package/templates/runtime/export/artifact-scene-frame.ts +3 -3
  56. package/templates/runtime/export/export-background.ts +0 -19
  57. package/templates/runtime/export/export-error.ts +5 -0
  58. package/templates/runtime/export/export-renderer-coverage.ts +54 -0
  59. package/templates/runtime/export/export.test.ts +45 -42
  60. package/templates/runtime/export/image-artifact-export.ts +2 -2
  61. package/templates/runtime/export/index.ts +57 -16
  62. package/templates/runtime/export/product-export-renderer.test.ts +38 -4
  63. package/templates/runtime/export/product-export-renderer.ts +0 -41
  64. package/templates/runtime/export/product-svg-export-renderer.ts +21 -0
  65. package/templates/runtime/export/svg-artifact-export.test.ts +98 -0
  66. package/templates/runtime/export/svg-artifact-export.ts +120 -0
  67. package/templates/runtime/export/svg-document.test.ts +221 -0
  68. package/templates/runtime/export/svg-document.ts +400 -0
  69. package/templates/runtime/export/svg-policy.ts +47 -0
  70. package/templates/runtime/index.ts +3 -0
  71. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  72. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +21 -1
  73. package/templates/runtime/react/app-shell/settings-transfer.test.ts +21 -0
  74. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +75 -1
  75. package/templates/runtime/react/app-shell/toolcraft-app.tsx +24 -9
  76. package/templates/runtime/react/canvas/canvas-shell-interactions.test.tsx +30 -0
  77. package/templates/runtime/react/canvas/canvas-shell-pinch-interactions.test.tsx +155 -0
  78. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +77 -1
  79. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  80. package/templates/runtime/react/canvas/canvas-shell.tsx +32 -6
  81. package/templates/runtime/react/canvas/canvas-viewport-geometry.ts +133 -0
  82. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  83. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  84. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  85. package/templates/runtime/react/canvas/use-canvas-viewport-interactions.ts +285 -36
  86. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  87. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  88. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-applicability-layout.test.tsx +134 -0
  89. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-layout.test.tsx +16 -1
  90. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  91. package/templates/runtime/react/controls-panel/__tests__/controls-panel.render-isolation.test.tsx +2 -0
  92. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +40 -1
  93. package/templates/runtime/react/controls-panel/__tests__/controls-panel.setup-controls.test.tsx +3 -3
  94. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +59 -0
  95. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  96. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-interaction.test.tsx +6 -6
  97. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +4 -6
  98. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +41 -6
  99. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  100. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  101. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +41 -10
  102. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +7 -4
  103. package/templates/runtime/react/controls-panel/renderers/controls-panel-action-renderer.tsx +2 -1
  104. package/templates/runtime/react/controls-panel/renderers/controls-panel-basic-renderers.tsx +2 -52
  105. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  106. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  107. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  108. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  109. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  110. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  111. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  112. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  113. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  114. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  115. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  116. package/templates/runtime/react/controls-panel/values/controls-panel-color-values.ts +1 -9
  117. package/templates/runtime/react/controls-panel/values/controls-panel-spatial-values.ts +7 -2
  118. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  119. package/templates/runtime/react/index.ts +4 -0
  120. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  121. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-math.ts +9 -69
  122. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  123. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  124. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  125. package/templates/runtime/scene/scene-bounds.ts +31 -17
  126. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  127. package/templates/runtime/schema/artifact-export-actions.test.ts +47 -0
  128. package/templates/runtime/schema/artifact-export-actions.ts +49 -0
  129. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  130. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  131. package/templates/runtime/schema/control-applicability.ts +433 -0
  132. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  133. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  134. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  135. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
  136. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  137. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  138. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  139. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  140. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  141. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  142. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  143. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  144. package/templates/runtime/schema/types.ts +69 -13
  145. package/templates/runtime/state/canvas-background-state.ts +1 -16
  146. package/templates/runtime/state/canvas-state.ts +32 -20
  147. package/templates/runtime/state/control-value-codecs.test.ts +98 -0
  148. package/templates/runtime/state/control-value-codecs.ts +700 -0
  149. package/templates/runtime/state/control-value-normalization.test.ts +1 -1
  150. package/templates/runtime/state/control-value-normalization.ts +127 -135
  151. package/templates/runtime/state/controls-reducer.ts +26 -3
  152. package/templates/runtime/state/create-template-state.test.ts +30 -0
  153. package/templates/runtime/state/create-template-state.ts +52 -31
  154. package/templates/runtime/state/orientation-pose.ts +80 -0
  155. package/templates/runtime/state/persistence-control-values.test.ts +59 -0
  156. package/templates/runtime/state/reducer-controls.test.ts +45 -0
  157. package/templates/runtime/state/reducer-media-reset.test.ts +1 -1
  158. package/templates/runtime/state/reducer-timeline.test.ts +70 -0
  159. package/templates/runtime/state/timeline-reducer.ts +40 -2
  160. package/templates/runtime/testing/performance-schema-queries.ts +2 -2
  161. package/templates/starter/AGENTS.md +29 -28
  162. package/templates/starter/docs/toolcraft/README.md +6 -6
  163. package/templates/starter/docs/toolcraft/acceptance-testing.md +34 -48
  164. package/templates/starter/docs/toolcraft/agent-worklog.md +103 -1
  165. package/templates/starter/docs/toolcraft/assembly-workflow.md +16 -8
  166. package/templates/starter/docs/toolcraft/component-rules.md +9 -7
  167. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  168. package/templates/starter/docs/toolcraft/core/layout.md +13 -8
  169. package/templates/starter/docs/toolcraft/core/media-upload.md +3 -1
  170. package/templates/starter/docs/toolcraft/core/performance.md +8 -10
  171. package/templates/starter/docs/toolcraft/core/reference-study.md +80 -19
  172. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +15 -3
  173. package/templates/starter/docs/toolcraft/core/setup-export.md +67 -11
  174. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  175. package/templates/starter/docs/toolcraft/decision-contract.md +4 -2
  176. package/templates/starter/docs/toolcraft/performance.md +8 -10
  177. package/templates/starter/docs/toolcraft/renderer-technique.md +6 -2
  178. package/templates/starter/docs/toolcraft/schema-reference.md +24 -22
  179. package/templates/starter/docs/toolcraft/workflow.md +14 -14
  180. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  181. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +45 -2
  182. package/templates/starter/e2e/app-browser-motion-reference-requirements.spec.ts +411 -0
  183. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  184. package/templates/starter/e2e/app-browser-proof-session.spec.ts +85 -0
  185. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +63 -33
  186. package/templates/starter/e2e/app-browser-selection-scope-evidence.spec.ts +137 -0
  187. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
  188. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  189. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  190. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
  191. package/templates/starter/e2e/browser-infinity-canvas-svg-evidence.ts +42 -0
  192. package/templates/starter/e2e/browser-layer-evidence-helpers.ts +15 -36
  193. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  194. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  195. package/templates/starter/e2e/browser-product-raster-snapshot.ts +213 -0
  196. package/templates/starter/e2e/browser-proof-session.ts +43 -15
  197. package/templates/starter/e2e/browser-proof-surface-action.ts +79 -0
  198. package/templates/starter/e2e/browser-proof-types.ts +15 -0
  199. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  200. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
  201. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +51 -32
  202. package/templates/starter/e2e/browser-selection-scope-evidence.ts +269 -0
  203. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +122 -0
  204. package/templates/starter/e2e/browser-svg-export-evidence.spec.ts +79 -0
  205. package/templates/starter/e2e/browser-svg-export-evidence.ts +152 -0
  206. package/templates/starter/e2e/export-artifact-helpers.ts +70 -0
  207. package/templates/starter/e2e/product-observable-helpers.ts +8 -129
  208. package/templates/starter/e2e/svg-artifact-inspection.spec.ts +136 -0
  209. package/templates/starter/e2e/svg-artifact-inspection.ts +331 -0
  210. package/templates/starter/e2e/svg-download-test-fixtures.ts +34 -0
  211. package/templates/starter/e2e/toolcraft-delivery-catalog-reporter.ts +198 -15
  212. package/templates/starter/e2e/toolcraft-feature-verification-reporter.spec.ts +149 -0
  213. package/templates/starter/e2e/toolcraft-feature-verification-reporter.ts +223 -0
  214. package/templates/starter/package.json +2 -0
  215. package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
  216. package/templates/starter/scripts/create-toolcraft-motion-reference-study.mjs +233 -0
  217. package/templates/starter/scripts/create-toolcraft-motion-reference-study.test.mjs +459 -0
  218. package/templates/starter/scripts/playwright-test-title-selection.d.mts +45 -0
  219. package/templates/starter/scripts/run-browser-performance-test-helpers.mjs +0 -27
  220. package/templates/starter/scripts/run-browser-performance.mjs +0 -6
  221. package/templates/starter/scripts/run-browser-performance.test.mjs +5 -13
  222. package/templates/starter/scripts/run-delivery-verification-test-helpers.mjs +0 -34
  223. package/templates/starter/scripts/run-delivery-verification.test.mjs +135 -31
  224. package/templates/starter/scripts/run-feature-verification.mjs +212 -0
  225. package/templates/starter/scripts/run-feature-verification.test.mjs +226 -0
  226. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.mjs +21 -8
  227. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.test.mjs +32 -92
  228. package/templates/starter/scripts/toolcraft-checkpoint-transaction.mjs +28 -6
  229. package/templates/starter/scripts/toolcraft-checkpoint-transaction.test.mjs +8 -63
  230. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  231. package/templates/starter/scripts/toolcraft-delivery-anchor.mjs +28 -8
  232. package/templates/starter/scripts/toolcraft-delivery-anchor.test.mjs +50 -61
  233. package/templates/starter/scripts/toolcraft-delivery-architecture-inventory.mjs +0 -4
  234. package/templates/starter/scripts/toolcraft-delivery-architecture-policy.mjs +0 -1
  235. package/templates/starter/scripts/toolcraft-delivery-evidence.test.mjs +2 -3
  236. package/templates/starter/scripts/toolcraft-delivery-executor.mjs +5 -1
  237. package/templates/starter/scripts/toolcraft-delivery-executor.test.mjs +5 -12
  238. package/templates/starter/scripts/toolcraft-delivery-functional-context.d.mts +1 -48
  239. package/templates/starter/scripts/toolcraft-delivery-functional-context.mjs +2 -30
  240. package/templates/starter/scripts/toolcraft-delivery-functional-context.test.mjs +4 -166
  241. package/templates/starter/scripts/toolcraft-delivery-functional-initial.test.mjs +4 -20
  242. package/templates/starter/scripts/toolcraft-delivery-lifecycle.mjs +43 -192
  243. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-lifecycle.test.mjs +79 -440
  244. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +102 -297
  245. package/templates/starter/scripts/toolcraft-delivery-plan-construction.mjs +46 -97
  246. package/templates/starter/scripts/toolcraft-delivery-plan-test-helpers.mjs +125 -0
  247. package/templates/starter/scripts/toolcraft-delivery-plan-validation.test.mjs +58 -0
  248. package/templates/starter/scripts/toolcraft-delivery-plan.d.mts +19 -31
  249. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +254 -252
  250. package/templates/starter/scripts/toolcraft-delivery-plan.test.mjs +29 -457
  251. package/templates/starter/scripts/toolcraft-delivery-receipt-performance.test.mjs +1 -1
  252. package/templates/starter/scripts/toolcraft-delivery-receipt-strictness.test.mjs +3 -72
  253. package/templates/starter/scripts/toolcraft-delivery-receipt-test-helpers.mjs +7 -36
  254. package/templates/starter/scripts/toolcraft-delivery-receipt.d.mts +3 -5
  255. package/templates/starter/scripts/toolcraft-delivery-receipt.mjs +15 -31
  256. package/templates/starter/scripts/toolcraft-delivery-receipt.test.mjs +28 -20
  257. package/templates/starter/scripts/toolcraft-delivery-receipt.types.test.mts +22 -30
  258. package/templates/starter/scripts/toolcraft-feature-verification-plan.d.mts +20 -0
  259. package/templates/starter/scripts/toolcraft-feature-verification-plan.mjs +146 -0
  260. package/templates/starter/scripts/toolcraft-feature-verification-plan.test.mjs +96 -0
  261. package/templates/starter/scripts/toolcraft-functional-proof-model.d.mts +3 -18
  262. package/templates/starter/scripts/toolcraft-functional-proof-model.mjs +39 -127
  263. package/templates/starter/scripts/toolcraft-functional-proof-model.test.mjs +25 -362
  264. package/templates/starter/scripts/toolcraft-functional-proof-model.types.test.mts +6 -83
  265. package/templates/starter/scripts/toolcraft-functional-proof-primitives.d.mts +11 -0
  266. package/templates/starter/scripts/toolcraft-functional-proof-primitives.mjs +61 -0
  267. package/templates/starter/scripts/toolcraft-functional-proof-primitives.test.mjs +43 -0
  268. package/templates/starter/scripts/toolcraft-integrity-policy.mjs +13 -0
  269. package/templates/starter/scripts/toolcraft-integrity-test-utils.mjs +1 -1
  270. package/templates/starter/scripts/toolcraft-layer-evidence-facade.test.mjs +58 -22
  271. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.mjs +111 -0
  272. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.test.mjs +116 -0
  273. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.d.mts +37 -0
  274. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.mjs +308 -0
  275. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.test.mjs +420 -0
  276. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.types.test.mts +50 -0
  277. package/templates/starter/scripts/toolcraft-motion-reference-command-options.mjs +195 -0
  278. package/templates/starter/scripts/toolcraft-motion-reference-command-options.test.mjs +224 -0
  279. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.mjs +122 -0
  280. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.test.mjs +153 -0
  281. package/templates/starter/scripts/toolcraft-motion-reference-evidence-assembly.mjs +270 -0
  282. package/templates/starter/scripts/toolcraft-motion-reference-evidence.test.mjs +432 -0
  283. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.mjs +275 -0
  284. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.test.mjs +294 -0
  285. package/templates/starter/scripts/toolcraft-motion-reference-group-inventory.mjs +148 -0
  286. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.mjs +101 -0
  287. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.test.mjs +124 -0
  288. package/templates/starter/scripts/toolcraft-motion-reference-image-source.mjs +259 -0
  289. package/templates/starter/scripts/toolcraft-motion-reference-image-source.test.mjs +338 -0
  290. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.mjs +96 -0
  291. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.test.mjs +81 -0
  292. package/templates/starter/scripts/toolcraft-motion-reference-lock-recovery.test.mjs +144 -0
  293. package/templates/starter/scripts/toolcraft-motion-reference-media-source.mjs +165 -0
  294. package/templates/starter/scripts/toolcraft-motion-reference-media-source.test.mjs +299 -0
  295. package/templates/starter/scripts/toolcraft-motion-reference-publication-discovery.test.mjs +142 -0
  296. package/templates/starter/scripts/toolcraft-motion-reference-publication-isolation.test.mjs +234 -0
  297. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-schema.mjs +122 -0
  298. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.mjs +89 -0
  299. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.test.mjs +74 -0
  300. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.mjs +264 -0
  301. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.test.mjs +377 -0
  302. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.mjs +254 -0
  303. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.test.mjs +130 -0
  304. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.mjs +128 -0
  305. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.test.mjs +209 -0
  306. package/templates/starter/scripts/toolcraft-motion-reference-publication.fixtures.mjs +88 -0
  307. package/templates/starter/scripts/toolcraft-motion-reference-publication.mjs +249 -0
  308. package/templates/starter/scripts/toolcraft-motion-reference-publication.test.mjs +433 -0
  309. package/templates/starter/scripts/toolcraft-motion-reference-review-output.mjs +38 -0
  310. package/templates/starter/scripts/toolcraft-motion-reference-review-output.test.mjs +29 -0
  311. package/templates/starter/scripts/toolcraft-motion-reference-root-safety.test.mjs +84 -0
  312. package/templates/starter/scripts/toolcraft-motion-reference-roots.mjs +83 -0
  313. package/templates/starter/scripts/toolcraft-motion-reference-sampling.mjs +283 -0
  314. package/templates/starter/scripts/toolcraft-motion-reference-sampling.test.mjs +480 -0
  315. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.mjs +39 -0
  316. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.test.mjs +59 -0
  317. package/templates/starter/scripts/toolcraft-motion-reference-stage.mjs +71 -0
  318. package/templates/starter/scripts/toolcraft-motion-reference-stage.test.mjs +61 -0
  319. package/templates/starter/scripts/toolcraft-motion-review-numeric.mjs +74 -0
  320. package/templates/starter/scripts/toolcraft-motion-review-numeric.test.mjs +65 -0
  321. package/templates/starter/scripts/toolcraft-motion-review-partitioning.mjs +268 -0
  322. package/templates/starter/scripts/toolcraft-motion-review-partitioning.test.mjs +329 -0
  323. package/templates/starter/scripts/toolcraft-motion-review-protected-units.mjs +120 -0
  324. package/templates/starter/scripts/toolcraft-motion-review-protected-units.test.mjs +111 -0
  325. package/templates/starter/scripts/toolcraft-performance-escalation-policy.test.mjs +77 -345
  326. package/templates/starter/scripts/toolcraft-performance-receipt-validation.test.mjs +19 -21
  327. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
  328. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  329. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
  330. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  331. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  332. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  333. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +16 -18
  334. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +200 -0
  335. package/templates/starter/scripts/toolcraft-product-observable-facade.test.mjs +46 -1
  336. package/templates/starter/scripts/toolcraft-receipt-boundary.test.mjs +0 -1
  337. package/templates/starter/scripts/toolcraft-source-inventory.d.mts +5 -0
  338. package/templates/starter/scripts/toolcraft-source-inventory.mjs +25 -14
  339. package/templates/starter/scripts/toolcraft-source-inventory.test.mjs +48 -0
  340. package/templates/starter/scripts/toolcraft-source-ownership.mjs +0 -1
  341. package/templates/starter/scripts/toolcraft-source-ownership.test.mjs +30 -5
  342. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  343. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  344. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
  345. package/templates/starter/scripts/toolcraft-verification-receipt-test-helpers.mjs +5 -26
  346. package/templates/starter/scripts/toolcraft-verification-receipt.d.mts +0 -7
  347. package/templates/starter/scripts/toolcraft-verification-receipt.mjs +3 -47
  348. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +6 -87
  349. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.d.mts +19 -0
  350. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.mjs +107 -0
  351. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.test.mjs +114 -0
  352. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.mjs +13 -2
  353. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.test.mjs +110 -0
  354. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +161 -0
  355. package/templates/starter/src/app/acceptance/canvas-coverage.ts +1 -0
  356. package/templates/starter/src/app/acceptance/color-bank-labels.ts +11 -15
  357. package/templates/starter/src/app/acceptance/color-row-grouping.ts +33 -0
  358. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  359. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  360. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -31
  361. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  362. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +4 -16
  363. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  364. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  365. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  366. package/templates/starter/src/app/acceptance/control-component-rules.ts +0 -10
  367. package/templates/starter/src/app/acceptance/control-labels.ts +30 -1
  368. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  369. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  370. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  371. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +21 -10
  372. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  373. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  374. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  375. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  376. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  377. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  378. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +25 -17
  379. package/templates/starter/src/app/acceptance/feature-verification-selection.test.ts +295 -0
  380. package/templates/starter/src/app/acceptance/feature-verification-selection.ts +161 -0
  381. package/templates/starter/src/app/acceptance/infinity-canvas.ts +10 -0
  382. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  383. package/templates/starter/src/app/acceptance/interaction-ownership-intent.ts +19 -1
  384. package/templates/starter/src/app/acceptance/interaction-ownership.ts +18 -2
  385. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  386. package/templates/starter/src/app/acceptance/motion-reference-evidence-validation.mjs +65 -0
  387. package/templates/starter/src/app/acceptance/motion-reference-evidence-windows.mjs +301 -0
  388. package/templates/starter/src/app/acceptance/motion-reference-evidence.d.mts +11 -0
  389. package/templates/starter/src/app/acceptance/motion-reference-evidence.mjs +145 -0
  390. package/templates/starter/src/app/acceptance/motion-reference-study-behaviors.ts +162 -0
  391. package/templates/starter/src/app/acceptance/motion-reference-study-coverage.ts +206 -0
  392. package/templates/starter/src/app/acceptance/motion-reference-study-identity.ts +129 -0
  393. package/templates/starter/src/app/acceptance/motion-reference-study-partitions.ts +186 -0
  394. package/templates/starter/src/app/acceptance/motion-reference-study.ts +90 -0
  395. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  396. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  397. package/templates/starter/src/app/acceptance/output-export-actions.test.ts +9 -1
  398. package/templates/starter/src/app/acceptance/output-export-actions.ts +14 -22
  399. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +22 -10
  400. package/templates/starter/src/app/acceptance/output-export-model.ts +58 -11
  401. package/templates/starter/src/app/acceptance/output-export.ts +25 -10
  402. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  403. package/templates/starter/src/app/acceptance/reference-study-types.ts +167 -17
  404. package/templates/starter/src/app/acceptance/runtime-coverage.ts +6 -1
  405. package/templates/starter/src/app/acceptance/selection-scope.test.ts +200 -0
  406. package/templates/starter/src/app/acceptance/selection-scope.ts +151 -0
  407. package/templates/starter/src/app/acceptance/transfer-mode-types.ts +114 -0
  408. package/templates/starter/src/app/acceptance/types.ts +65 -125
  409. package/templates/starter/src/app/acceptance/validate-coverage.ts +33 -5
  410. package/templates/starter/src/app/starter-acceptance-data.ts +1 -0
  411. package/templates/starter/src/app/starter-acceptance.animation-intent.test.ts +3 -0
  412. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +312 -0
  413. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +35 -0
  414. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +18 -1
  415. package/templates/starter/src/app/starter-acceptance.color-row-grouping.test.ts +118 -0
  416. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +66 -1
  417. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  418. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +113 -16
  419. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +225 -0
  420. package/templates/starter/src/app/starter-acceptance.control-label-context.test.ts +1 -0
  421. package/templates/starter/src/app/starter-acceptance.control-labels.test.ts +78 -3
  422. package/templates/starter/src/app/starter-acceptance.control-naming-rules.test.ts +77 -1
  423. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  424. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  425. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  426. package/templates/starter/src/app/starter-acceptance.custom-control-fit-check.test.ts +1 -1
  427. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  428. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +74 -3
  429. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +26 -0
  430. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +39 -9
  431. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +31 -3
  432. package/templates/starter/src/app/starter-acceptance.layers.test.ts +2 -1
  433. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  434. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +56 -0
  435. package/templates/starter/src/app/starter-acceptance.motion-reference-evidence.test.ts +17 -0
  436. package/templates/starter/src/app/starter-acceptance.motion-reference-identity.test.ts +163 -0
  437. package/templates/starter/src/app/starter-acceptance.motion-reference-mapping.test.ts +133 -0
  438. package/templates/starter/src/app/starter-acceptance.motion-reference-ownership.test.ts +121 -0
  439. package/templates/starter/src/app/starter-acceptance.motion-reference-partitions.test.ts +210 -0
  440. package/templates/starter/src/app/starter-acceptance.motion-reference-test-utils.ts +313 -0
  441. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +458 -0
  442. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
  443. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +116 -104
  444. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +29 -0
  445. package/templates/starter/src/app/starter-acceptance.product-worklog.test.ts +123 -2
  446. package/templates/starter/src/app/starter-acceptance.reference-custom-timeline.test.ts +2 -0
  447. package/templates/starter/src/app/starter-acceptance.reference-foundation.test.ts +8 -0
  448. package/templates/starter/src/app/starter-acceptance.reference-timeline.test.ts +7 -0
  449. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +3 -1
  450. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  451. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  452. package/templates/starter/src/app/starter-acceptance.timeline-playback.test.ts +5 -0
  453. package/templates/starter/src/app/starter-acceptance.ts +45 -10
  454. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +338 -0
  455. package/templates/starter/src/app/starter-acceptance.video-reference-study.test.ts +34 -236
  456. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +12 -0
  457. package/templates/starter/src/app/starter-acceptance.worklog-test-utils.ts +122 -6
  458. package/templates/starter/src/app/starter-acceptance.worklog.test.ts +152 -16
  459. package/templates/starter/src/app/starter-performance.gates.test.ts +20 -18
  460. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +22 -6
  461. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +5 -2
  462. package/templates/starter/src/routes/index.tsx +1 -0
  463. package/templates/starter/tsconfig.json +1 -0
  464. package/templates/ui/components/composites/tabs.tsx +2 -2
  465. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  466. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  467. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  468. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  469. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  470. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  471. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  472. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  473. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  474. package/templates/ui/components/controls/index.ts +3 -0
  475. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  476. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  477. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  478. package/templates/starter/scripts/toolcraft-browser-impact-selection.mjs +0 -26
  479. package/templates/starter/scripts/toolcraft-delivery-framework-refresh.test.mjs +0 -141
  480. package/templates/starter/scripts/toolcraft-delivery-functional-changed.test.mjs +0 -468
  481. package/templates/starter/scripts/toolcraft-delivery-functional-model-lifecycle.test.mjs +0 -254
  482. package/templates/starter/scripts/toolcraft-delivery-functional-semantic-regression.test.mjs +0 -135
  483. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-durability.test.mjs +0 -329
  484. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +0 -430
  485. package/templates/starter/scripts/toolcraft-delivery-plan-functional-model.test.mjs +0 -358
  486. package/templates/starter/scripts/toolcraft-functional-proof-regression-fixtures.mjs +0 -298
  487. package/templates/starter/scripts/toolcraft-verification-impact-inventory.d.mts +0 -62
  488. package/templates/starter/scripts/toolcraft-verification-impact-inventory.mjs +0 -344
  489. package/templates/starter/scripts/toolcraft-verification-impact-inventory.test.mjs +0 -204
  490. package/templates/starter/scripts/toolcraft-verification-impact-performance-candidates.test.mjs +0 -210
  491. package/templates/starter/scripts/toolcraft-verification-impact-resolution.mjs +0 -344
  492. package/templates/starter/scripts/toolcraft-verification-impact-resolution.test.mjs +0 -270
  493. package/templates/starter/scripts/toolcraft-verification-impact-semantic-resolution.test.mjs +0 -449
  494. package/templates/starter/scripts/toolcraft-verification-impact-support-resolution.test.mjs +0 -195
  495. package/templates/starter/scripts/toolcraft-verification-impact.d.mts +0 -132
  496. package/templates/starter/scripts/toolcraft-verification-impact.mjs +0 -169
  497. package/templates/starter/scripts/toolcraft-verification-impact.test.mjs +0 -300
  498. package/templates/starter/scripts/toolcraft-verification-input-roles.mjs +0 -197
  499. package/templates/starter/scripts/toolcraft-verification-input-roles.test.mjs +0 -225
  500. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  501. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
  502. package/templates/starter/src/app/acceptance/video-reference-study.ts +0 -217
  503. package/templates/starter/src/app/starter-verification-impact.json +0 -15
@@ -0,0 +1,301 @@
1
+ import {
2
+ finite,
3
+ hasExactKeys,
4
+ isGroupId,
5
+ isRecord,
6
+ isSha256,
7
+ nonnegativeInteger,
8
+ positive,
9
+ positiveInteger,
10
+ requireValue,
11
+ } from "./motion-reference-evidence-validation.mjs";
12
+
13
+ function validateStrictRange(value, label, durationSeconds, errors) {
14
+ if (!hasExactKeys(value, ["endSeconds", "startSeconds"], [], label, errors)) {
15
+ return false;
16
+ }
17
+ const valid = finite(value.startSeconds) && value.startSeconds >= 0 &&
18
+ finite(value.endSeconds) && value.endSeconds > value.startSeconds &&
19
+ value.endSeconds <= durationSeconds;
20
+ requireValue(valid,
21
+ `${label} must satisfy 0 <= startSeconds < endSeconds <= durationSeconds.`,
22
+ errors);
23
+ return valid;
24
+ }
25
+ function validateAuthority(evidence, errors) {
26
+ const authority = evidence.timing?.authority;
27
+ const keys = authority === "declared-fps" ? ["authority", "framesPerSecond"] :
28
+ authority === "declared-timestamps" ? ["authority", "timestampsSha256"] :
29
+ ["authority"];
30
+ const label = "timed motion reference evidence timing";
31
+ if (!hasExactKeys(evidence.timing, keys, [], label, errors)) return;
32
+ requireValue(["decoded-timestamps", "declared-fps", "declared-timestamps"]
33
+ .includes(authority), `${label} authority is unsupported.`, errors);
34
+ if (authority === "declared-fps") requireValue(
35
+ positive(evidence.timing.framesPerSecond),
36
+ "declared timing framesPerSecond must be positive and finite.", errors,
37
+ );
38
+ if (authority === "declared-timestamps") requireValue(
39
+ isSha256(evidence.timing.timestampsSha256),
40
+ "declared timing timestampsSha256 must be a lowercase SHA-256 hash.", errors,
41
+ );
42
+ }
43
+ function validatePartition(evidence, segmentValid, errors) {
44
+ const partition = evidence.reviewPartition;
45
+ if (partition === undefined) return;
46
+ const label = "timed motion reference evidence reviewPartition";
47
+ if (!hasExactKeys(partition,
48
+ ["count", "groupId", "index", "planSha256", "scope"], [], label, errors)) return;
49
+ requireValue(positiveInteger(partition.count),
50
+ `${label}.count must be a positive safe integer.`, errors);
51
+ requireValue(nonnegativeInteger(partition.index) && partition.index < partition.count,
52
+ `${label}.index must be within count.`, errors);
53
+ requireValue(isGroupId(partition.groupId), `${label}.groupId is invalid.`, errors);
54
+ requireValue(isSha256(partition.planSha256),
55
+ `${label}.planSha256 must be a lowercase SHA-256 hash.`, errors);
56
+ const scopeValid = validateStrictRange(
57
+ partition.scope, `${label}.scope`, evidence.durationSeconds, errors,
58
+ );
59
+ requireValue(evidence.segment !== undefined,
60
+ "partitioned timed motion reference evidence must declare a segment.", errors);
61
+ if (scopeValid && segmentValid) requireValue(
62
+ evidence.segment.startSeconds >= partition.scope.startSeconds &&
63
+ evidence.segment.endSeconds <= partition.scope.endSeconds,
64
+ `${label} segment must be contained within its partition scope.`, errors,
65
+ );
66
+ }
67
+ function validateTimedShape(evidence, errors) {
68
+ const durationValid = positive(evidence.durationSeconds);
69
+ requireValue(durationValid,
70
+ "timed motion reference evidence durationSeconds must be positive and finite.", errors);
71
+ requireValue(positive(evidence.overviewTargetFps),
72
+ "timed motion reference evidence overviewTargetFps must be positive and finite.", errors);
73
+ requireValue(finite(evidence.maximumSourceFrameGapSeconds) &&
74
+ evidence.maximumSourceFrameGapSeconds >= 0,
75
+ "timed motion reference evidence maximumSourceFrameGapSeconds must be finite and nonnegative.", errors);
76
+ if (evidence.nominalFrameRate !== undefined) requireValue(
77
+ positive(evidence.nominalFrameRate),
78
+ "timed motion reference evidence nominalFrameRate must be positive and finite.", errors,
79
+ );
80
+ requireValue(Array.isArray(evidence.focusWindows),
81
+ "timed motion reference evidence focusWindows must be an array.", errors);
82
+ validateAuthority(evidence, errors);
83
+ const segmentValid = evidence.segment === undefined ? false :
84
+ validateStrictRange(evidence.segment, "timed motion reference evidence segment",
85
+ evidence.durationSeconds, errors);
86
+ validatePartition(evidence, segmentValid, errors);
87
+ return evidence.segment !== undefined && segmentValid ? evidence.segment :
88
+ { endSeconds: durationValid ? evidence.durationSeconds : 0, startSeconds: 0 };
89
+ }
90
+ const EVENT_KEYS = [
91
+ "afterFrameId", "beforeFrameId", "eventId", "kind", "peakFrameId", "score",
92
+ "windowFrameIds",
93
+ ];
94
+ function expectedLoopFrameIds(evidence) {
95
+ const lastIndex = evidence.decodedFrameCount - 1;
96
+ return [...new Set([
97
+ `source-frame:${lastIndex}`, "source-frame:0",
98
+ `source-frame:${Math.min(1, lastIndex)}`,
99
+ ])];
100
+ }
101
+ function isExactLoopEvent(evidence, event, frames) {
102
+ const shapeErrors = [];
103
+ if (!hasExactKeys(event, EVENT_KEYS, [], "loop event", shapeErrors) ||
104
+ shapeErrors.length > 0 || event.kind !== "loop-seam" ||
105
+ !finite(event.score) || event.score < 0 || event.score > 1 ||
106
+ !Array.isArray(event.windowFrameIds)) return false;
107
+ const expectedIds = expectedLoopFrameIds(evidence);
108
+ return expectedIds.every((frameId) => frames.has(frameId)) &&
109
+ event.windowFrameIds.length === expectedIds.length &&
110
+ event.windowFrameIds.every((frameId, index) => frameId === expectedIds[index]) &&
111
+ event.beforeFrameId === expectedIds[0] &&
112
+ event.peakFrameId === "source-frame:0" &&
113
+ event.afterFrameId === expectedIds.at(-1) &&
114
+ event.eventId === `motion-event:loop-seam:${expectedIds
115
+ .map((frameId) => frames.get(frameId).sourceFrameIndex).join("-")}`;
116
+ }
117
+ function canonicalExternalSeamFrameIds(evidence, frames) {
118
+ const partition = evidence.reviewPartition;
119
+ const completePartitionZero = isRecord(evidence.segment) &&
120
+ isRecord(partition) && isRecord(partition.scope) &&
121
+ evidence.segment.startSeconds === 0 && partition?.index === 0 &&
122
+ partition.scope?.startSeconds === 0 &&
123
+ partition.scope?.endSeconds === evidence.durationSeconds;
124
+ if (!completePartitionZero || !Array.isArray(evidence.detectedEvents)) {
125
+ return new Set();
126
+ }
127
+ const loopEvent = evidence.detectedEvents.find((event) =>
128
+ isExactLoopEvent(evidence, event, frames));
129
+ if (!loopEvent) return new Set();
130
+ return new Set(loopEvent.windowFrameIds.filter((frameId) => {
131
+ const time = frames.get(frameId)?.timeSeconds;
132
+ return finite(time) && (time < evidence.segment.startSeconds ||
133
+ time > evidence.segment.endSeconds);
134
+ }));
135
+ }
136
+ function validateFrameTimes(evidence, activeScope, externalSeamFrameIds, errors) {
137
+ let previousTime = -Infinity;
138
+ for (const [index, frame] of evidence.reviewedFrames.entries()) {
139
+ if (!isRecord(frame)) continue;
140
+ const inScope = finite(frame.timeSeconds) &&
141
+ frame.timeSeconds >= activeScope.startSeconds &&
142
+ frame.timeSeconds <= activeScope.endSeconds;
143
+ requireValue(inScope || externalSeamFrameIds.has(frame.frameId),
144
+ `motion reference evidence reviewedFrames[${index}].timeSeconds must be within the active timed scope.`,
145
+ errors);
146
+ requireValue(finite(frame.timeSeconds) && frame.timeSeconds > previousTime,
147
+ `motion reference evidence reviewedFrames[${index}].timeSeconds must be strictly increasing.`,
148
+ errors);
149
+ if (finite(frame.timeSeconds)) previousTime = frame.timeSeconds;
150
+ }
151
+ }
152
+ function validateOverview(evidence, frames, externalSeamFrameIds, errors) {
153
+ if (!Array.isArray(evidence.overviewFrameIds) || evidence.overviewFrameIds.length === 0) {
154
+ errors.push("motion reference evidence overviewFrameIds must be a nonempty array.");
155
+ return;
156
+ }
157
+ if (Array.isArray(evidence.reviewedFrames) && evidence.reviewedFrames.length > 0) {
158
+ requireValue(evidence.overviewFrameIds[0] === evidence.reviewedFrames[0]?.frameId,
159
+ "motion reference evidence overviewFrameIds must include the first reviewed frame.", errors);
160
+ requireValue(evidence.overviewFrameIds.at(-1) === evidence.reviewedFrames.at(-1)?.frameId,
161
+ "motion reference evidence overviewFrameIds must include the last reviewed frame.", errors);
162
+ }
163
+ let previousIndex = -1;
164
+ let previousTime;
165
+ const seen = new Set();
166
+ for (const frameId of evidence.overviewFrameIds) {
167
+ const frame = frames.get(frameId);
168
+ requireValue(frame !== undefined,
169
+ `motion reference evidence overview has unknown frame ${String(frameId)}.`, errors);
170
+ if (!frame) continue;
171
+ requireValue(frame.sourceFrameIndex > previousIndex && !seen.has(frameId),
172
+ "motion reference evidence overviewFrameIds must follow canonical reviewed-frame order.", errors);
173
+ const externalSeamFrame = externalSeamFrameIds.has(frameId);
174
+ if (!externalSeamFrame && evidence.timingMode === "seconds" &&
175
+ previousTime !== undefined &&
176
+ positive(evidence.overviewTargetFps) &&
177
+ finite(evidence.maximumSourceFrameGapSeconds)) {
178
+ const allowance = 1 / evidence.overviewTargetFps +
179
+ evidence.maximumSourceFrameGapSeconds;
180
+ requireValue(frame.timeSeconds - previousTime <= allowance + Number.EPSILON,
181
+ "motion reference evidence overview timestamp gap exceeds its recorded density allowance.", errors);
182
+ }
183
+ previousIndex = frame.sourceFrameIndex;
184
+ if (!externalSeamFrame) previousTime = frame.timeSeconds;
185
+ seen.add(frameId);
186
+ }
187
+ }
188
+ function validateChangeEvent(event, ids, sourceIndices, label, errors) {
189
+ const contiguous = ids.length >= 3 && sourceIndices.every((value, index) =>
190
+ nonnegativeInteger(value) && (index === 0 || value === sourceIndices[index - 1] + 1));
191
+ requireValue(contiguous,
192
+ `${label} change window must use strictly contiguous source-frame order.`, errors);
193
+ requireValue(event.beforeFrameId === ids[0] && event.afterFrameId === ids.at(-1) &&
194
+ ids.includes(event.peakFrameId),
195
+ `${label} change window must bind before, peak, and after frames.`, errors);
196
+ const peakIndex = sourceIndices[ids.indexOf(event.peakFrameId)];
197
+ requireValue(nonnegativeInteger(peakIndex) && peakIndex > sourceIndices[0] &&
198
+ peakIndex < sourceIndices.at(-1),
199
+ `${label} change peak must be strictly interior to its frame window.`, errors);
200
+ }
201
+ function validateLoopEvent(evidence, event, ids, label, frames, errors) {
202
+ requireValue(isExactLoopEvent(evidence, event, frames),
203
+ `${label} loop-seam must use the exact cyclic source endpoint window.`, errors);
204
+ }
205
+ function validateEvents(evidence, frames, errors) {
206
+ if (!Array.isArray(evidence.detectedEvents)) {
207
+ errors.push("motion reference evidence detectedEvents must be an array.");
208
+ return;
209
+ }
210
+ const seen = new Set();
211
+ for (const [index, event] of evidence.detectedEvents.entries()) {
212
+ const label = `motion reference evidence event[${index}]`;
213
+ if (!hasExactKeys(event, EVENT_KEYS, [], label, errors)) continue;
214
+ requireValue(event.kind === "change" || event.kind === "loop-seam",
215
+ `${label}.kind is invalid.`, errors);
216
+ requireValue(finite(event.score) && event.score >= 0 && event.score <= 1,
217
+ `${label}.score must be within [0, 1].`, errors);
218
+ const ids = Array.isArray(event.windowFrameIds) ? event.windowFrameIds : [];
219
+ requireValue(ids.length > 0, `${label}.windowFrameIds must be nonempty.`, errors);
220
+ for (const frameId of new Set([
221
+ ...ids, event.beforeFrameId, event.peakFrameId, event.afterFrameId,
222
+ ])) requireValue(frames.has(frameId),
223
+ `${label} references unknown frame ${String(frameId)}.`, errors);
224
+ const sourceIndices = ids.map((id) => frames.get(id)?.sourceFrameIndex);
225
+ if (event.kind === "change") {
226
+ validateChangeEvent(event, ids, sourceIndices, label, errors);
227
+ } else if (event.kind === "loop-seam") {
228
+ validateLoopEvent(evidence, event, ids, label, frames, errors);
229
+ }
230
+ requireValue(event.eventId ===
231
+ `motion-event:${event.kind}:${sourceIndices.join("-")}`,
232
+ `${label}.eventId must be canonical for its complete frame window.`, errors);
233
+ requireValue(!seen.has(event.eventId), `${label}.eventId is duplicated.`, errors);
234
+ seen.add(event.eventId);
235
+ }
236
+ }
237
+ function validateFocusWindows(evidence, frames, activeScope, errors) {
238
+ if (!Array.isArray(evidence.focusWindows)) return;
239
+ let previousStartIndex = -1;
240
+ for (const [index, focus] of evidence.focusWindows.entries()) {
241
+ const label = `motion reference evidence focusWindows[${index}]`;
242
+ if (!hasExactKeys(focus, ["endSeconds", "frameIds", "sourceFrameEndIndex",
243
+ "sourceFrameStartIndex", "startSeconds"], [], label, errors)) continue;
244
+ const rangeValid = finite(focus.startSeconds) && finite(focus.endSeconds) &&
245
+ focus.startSeconds >= activeScope.startSeconds &&
246
+ focus.endSeconds > focus.startSeconds &&
247
+ focus.endSeconds <= activeScope.endSeconds;
248
+ requireValue(rangeValid,
249
+ `${label} must have positive duration bounded by the active timed scope.`, errors);
250
+ if (!Array.isArray(focus.frameIds)) {
251
+ errors.push(`${label}.frameIds must be a nonempty array.`);
252
+ continue;
253
+ }
254
+ const derivedFrames = rangeValid && Array.isArray(evidence.reviewedFrames) ?
255
+ evidence.reviewedFrames.filter((frame) => isRecord(frame) &&
256
+ finite(frame.timeSeconds) && frame.timeSeconds >= focus.startSeconds &&
257
+ frame.timeSeconds <= focus.endSeconds) : [];
258
+ const derivedIds = derivedFrames.map(({ frameId }) => frameId);
259
+ const sourceIndices = derivedFrames.map(({ sourceFrameIndex }) => sourceFrameIndex);
260
+ requireValue(derivedIds.length > 0,
261
+ `${label} must derive a nonempty reviewed-frame membership.`, errors);
262
+ requireValue(focus.frameIds.length === derivedIds.length &&
263
+ focus.frameIds.every((frameId, position) => frameId === derivedIds[position]),
264
+ `${label}.frameIds must equal the exact ordered membership of reviewed frames in its timed range.`,
265
+ errors);
266
+ const contiguous = sourceIndices.length > 0 && sourceIndices.every((value, position) =>
267
+ nonnegativeInteger(value) &&
268
+ (position === 0 || value === sourceIndices[position - 1] + 1));
269
+ requireValue(contiguous, `${label}.frameIds must cover contiguous source indices.`, errors);
270
+ requireValue(focus.sourceFrameStartIndex === sourceIndices[0] &&
271
+ focus.sourceFrameEndIndex === sourceIndices.at(-1),
272
+ `${label} source bounds must match its first and last referenced frames.`, errors);
273
+ requireValue(sourceIndices[0] >= previousStartIndex,
274
+ `${label} must follow canonical source-frame order.`, errors);
275
+ if (nonnegativeInteger(sourceIndices[0])) previousStartIndex = sourceIndices[0];
276
+ }
277
+ }
278
+
279
+ export function validateToolcraftMotionReferenceTimingAndWindows(
280
+ evidence,
281
+ frames,
282
+ errors,
283
+ ) {
284
+ let activeScope;
285
+ let externalSeamFrameIds = new Set();
286
+ if (evidence.timingMode === "ordinal") {
287
+ requireValue(Array.isArray(evidence.focusWindows) && evidence.focusWindows.length === 0,
288
+ "ordinal motion reference evidence focusWindows must be an empty array.", errors);
289
+ } else if (evidence.timingMode === "seconds") {
290
+ activeScope = validateTimedShape(evidence, errors);
291
+ if (Array.isArray(evidence.reviewedFrames)) {
292
+ externalSeamFrameIds = canonicalExternalSeamFrameIds(evidence, frames);
293
+ validateFrameTimes(evidence, activeScope, externalSeamFrameIds, errors);
294
+ }
295
+ }
296
+ validateOverview(evidence, frames, externalSeamFrameIds, errors);
297
+ validateEvents(evidence, frames, errors);
298
+ if (evidence.timingMode === "seconds") {
299
+ validateFocusWindows(evidence, frames, activeScope, errors);
300
+ }
301
+ }
@@ -0,0 +1,11 @@
1
+ import type { ToolcraftMotionReferenceEvidence } from "./reference-study-types";
2
+
3
+ export function defineToolcraftMotionReferenceEvidence(
4
+ value: unknown,
5
+ ): ToolcraftMotionReferenceEvidence;
6
+ export function getToolcraftMotionReferenceEvidenceErrors(
7
+ value: unknown,
8
+ ): string[];
9
+ export function writeCanonicalToolcraftMotionReferenceEvidence(
10
+ evidence: ToolcraftMotionReferenceEvidence,
11
+ ): string;
@@ -0,0 +1,145 @@
1
+ import {
2
+ validateToolcraftMotionReferenceTimingAndWindows,
3
+ } from "./motion-reference-evidence-windows.mjs";
4
+ import {
5
+ hasExactKeys,
6
+ isRecord,
7
+ isSha256,
8
+ isStudyId,
9
+ nonemptyString,
10
+ nonnegativeInteger,
11
+ positiveInteger,
12
+ requireValue,
13
+ } from "./motion-reference-evidence-validation.mjs";
14
+
15
+ const SOURCE_KINDS = new Set([
16
+ "contact-sheet", "frame-sequence", "gif", "screen-recording", "video",
17
+ ]);
18
+ function validateContactSheet(sheet, evidence, errors) {
19
+ const label = "motion reference evidence contactSheet";
20
+ if (!hasExactKeys(sheet,
21
+ ["columns", "height", "path", "rows", "sha256", "width"], [], label, errors)) return;
22
+ requireValue(positiveInteger(sheet.columns), `${label}.columns must be a positive safe integer.`, errors);
23
+ requireValue(positiveInteger(sheet.rows), `${label}.rows must be a positive safe integer.`, errors);
24
+ requireValue(positiveInteger(sheet.width), `${label}.width must be a positive safe integer.`, errors);
25
+ requireValue(positiveInteger(sheet.height), `${label}.height must be a positive safe integer.`, errors);
26
+ requireValue(isSha256(sheet.sha256), `${label}.sha256 must be a lowercase SHA-256 hash.`, errors);
27
+ requireValue(
28
+ sheet.path === `src/app/reference-studies/${evidence.studyId}/contact-sheet.png`,
29
+ `${label}.path must name the contact sheet in the canonical study directory.`, errors,
30
+ );
31
+ }
32
+ function validateSource(evidence, errors) {
33
+ requireValue(SOURCE_KINDS.has(evidence.sourceKind),
34
+ "motion reference evidence sourceKind is unsupported.", errors);
35
+ requireValue(isSha256(evidence.sourceSha256),
36
+ "motion reference evidence sourceSha256 must be a lowercase SHA-256 hash.", errors);
37
+ requireValue(nonemptyString(evidence.sourceBasename) &&
38
+ !/[\\/]/u.test(evidence.sourceBasename),
39
+ "motion reference evidence sourceBasename must be a basename.", errors);
40
+ if (evidence.sourceKind === "contact-sheet") {
41
+ if (hasExactKeys(evidence.sourceGrid, ["columns", "rows"], [],
42
+ "motion reference evidence sourceGrid", errors)) {
43
+ requireValue(positiveInteger(evidence.sourceGrid.columns) &&
44
+ positiveInteger(evidence.sourceGrid.rows),
45
+ "motion reference evidence sourceGrid dimensions must be positive safe integers.", errors);
46
+ }
47
+ }
48
+ }
49
+ function validateFrames(evidence, errors) {
50
+ if (!Array.isArray(evidence.reviewedFrames)) {
51
+ errors.push("motion reference evidence reviewedFrames must be an array.");
52
+ return new Map();
53
+ }
54
+ requireValue(evidence.reviewedFrames.length > 0 && evidence.reviewedFrames.length <= 120,
55
+ "motion reference evidence must contain between 1 and at most 120 reviewed frames.", errors);
56
+ const frames = new Map();
57
+ let previousIndex = -1;
58
+ for (const [position, frame] of evidence.reviewedFrames.entries()) {
59
+ const label = `motion reference evidence reviewedFrames[${position}]`;
60
+ const keys = ["cell", "frameId", "sourceFrameIndex", "sourceLocator", "sourceSha256",
61
+ ...(evidence.timingMode === "seconds" ? ["timeSeconds"] : [])];
62
+ if (!hasExactKeys(frame, keys, [], label, errors)) continue;
63
+ if (hasExactKeys(frame.cell, ["column", "row"], [], `${label}.cell`, errors)) {
64
+ requireValue(nonnegativeInteger(frame.cell.column) && nonnegativeInteger(frame.cell.row),
65
+ `${label}.cell coordinates must be nonnegative safe integers.`, errors);
66
+ requireValue(frame.cell.column === position % evidence.contactSheet?.columns &&
67
+ frame.cell.row === Math.floor(position / evidence.contactSheet?.columns),
68
+ `${label}.cell must follow canonical contact-sheet order.`, errors);
69
+ }
70
+ requireValue(nonnegativeInteger(frame.sourceFrameIndex) &&
71
+ frame.sourceFrameIndex < evidence.decodedFrameCount &&
72
+ frame.sourceFrameIndex > previousIndex,
73
+ `${label}.sourceFrameIndex must be in strictly increasing order.`, errors);
74
+ requireValue(frame.frameId === `source-frame:${frame.sourceFrameIndex}`,
75
+ `${label}.frameId must be canonical for its sourceFrameIndex.`, errors);
76
+ requireValue(nonemptyString(frame.sourceLocator), `${label}.sourceLocator must be nonempty.`, errors);
77
+ requireValue(frame.sourceSha256 === evidence.sourceSha256 && isSha256(frame.sourceSha256),
78
+ `${label}.sourceSha256 must match the evidence source hash.`, errors);
79
+ previousIndex = frame.sourceFrameIndex;
80
+ if (frames.has(frame.frameId)) errors.push(`${label}.frameId is duplicated.`);
81
+ frames.set(frame.frameId, frame);
82
+ }
83
+ if (positiveInteger(evidence.contactSheet?.columns)) {
84
+ requireValue(evidence.contactSheet.rows ===
85
+ Math.ceil(evidence.reviewedFrames.length / evidence.contactSheet.columns),
86
+ "motion reference evidence contactSheet.rows must fit reviewedFrames exactly.", errors);
87
+ }
88
+ return frames;
89
+ }
90
+
91
+ export function getToolcraftMotionReferenceEvidenceErrors(value) {
92
+ const errors = [];
93
+ const timingMode = value?.timingMode;
94
+ const sourceKind = value?.sourceKind;
95
+ const required = ["changeMetric", "contactSheet", "decodedFrameCount", "detectedEvents",
96
+ "evidenceVersion", "ffmpegVersion", "ffprobeVersion", "focusWindows", "height",
97
+ "overviewFrameIds", "reviewedFrames", "scannedFrameCount", "sourceBasename",
98
+ "sourceKind", "sourceSha256", "studyId", "timingMode", "width",
99
+ ...(sourceKind === "contact-sheet" ? ["sourceGrid"] : []),
100
+ ...(timingMode === "seconds" ? ["durationSeconds", "maximumSourceFrameGapSeconds",
101
+ "overviewTargetFps", "timing"] : [])];
102
+ const optional = timingMode === "seconds" ?
103
+ ["nominalFrameRate", "reviewPartition", "segment"] : [];
104
+ if (!hasExactKeys(value, required, optional, "motion reference evidence", errors)) return errors;
105
+ requireValue(value.evidenceVersion === 1, "motion reference evidence evidenceVersion must be 1.", errors);
106
+ requireValue(value.changeMetric === "max-yuva-diff-v1", "motion reference evidence changeMetric is invalid.", errors);
107
+ requireValue(isStudyId(value.studyId), "motion reference evidence studyId must match motion-v1-<64 lowercase hex>.", errors);
108
+ requireValue(positiveInteger(value.decodedFrameCount) && positiveInteger(value.scannedFrameCount),
109
+ "motion reference evidence frame counts must be positive safe integers.", errors);
110
+ requireValue(value.decodedFrameCount === value.scannedFrameCount,
111
+ "motion reference evidence decodedFrameCount must equal scannedFrameCount.", errors);
112
+ requireValue(positiveInteger(value.width) && positiveInteger(value.height),
113
+ "motion reference evidence dimensions must be positive safe integers.", errors);
114
+ requireValue(nonemptyString(value.ffmpegVersion) && nonemptyString(value.ffprobeVersion),
115
+ "motion reference evidence tool versions must be nonempty.", errors);
116
+ requireValue(value.timingMode === "ordinal" || value.timingMode === "seconds",
117
+ "motion reference evidence timingMode is invalid.", errors);
118
+ validateSource(value, errors);
119
+ validateContactSheet(value.contactSheet, value, errors);
120
+ const frames = validateFrames(value, errors);
121
+ validateToolcraftMotionReferenceTimingAndWindows(value, frames, errors);
122
+ return errors;
123
+ }
124
+
125
+ function deepFreeze(value) {
126
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
127
+ for (const child of Object.values(value)) deepFreeze(child);
128
+ Object.freeze(value);
129
+ }
130
+ return value;
131
+ }
132
+ export function defineToolcraftMotionReferenceEvidence(value) {
133
+ const errors = getToolcraftMotionReferenceEvidenceErrors(value);
134
+ if (errors.length > 0) throw new TypeError(`Invalid Toolcraft motion reference evidence:\n${errors.join("\n")}`);
135
+ return deepFreeze(value);
136
+ }
137
+ function sortJson(value) {
138
+ if (Array.isArray(value)) return value.map(sortJson);
139
+ if (isRecord(value)) return Object.fromEntries(Object.keys(value).sort()
140
+ .map((key) => [key, sortJson(value[key])]));
141
+ return value;
142
+ }
143
+ export function writeCanonicalToolcraftMotionReferenceEvidence(evidence) {
144
+ return `${JSON.stringify(sortJson(evidence), null, 2)}\n`;
145
+ }
@@ -0,0 +1,162 @@
1
+ import type {
2
+ ToolcraftComponentAcceptance,
3
+ ToolcraftMotionReferenceInput,
4
+ } from "./types";
5
+
6
+ type AcceptanceRowsById = ReadonlyMap<
7
+ string,
8
+ readonly ToolcraftComponentAcceptance[]
9
+ >;
10
+
11
+ function validateAcceptanceFields({
12
+ acceptance,
13
+ behaviorId,
14
+ }: {
15
+ acceptance: ToolcraftComponentAcceptance;
16
+ behaviorId: string;
17
+ }): string[] {
18
+ const errors: string[] = [];
19
+ if (!acceptance.automated || !acceptance.automatedTestName.trim()) {
20
+ errors.push(
21
+ `motion reference behavior "${behaviorId}" acceptance "${acceptance.id}" must have automated coverage.`,
22
+ );
23
+ }
24
+ if (!acceptance.browser || !acceptance.browserTestName.trim()) {
25
+ errors.push(
26
+ `motion reference behavior "${behaviorId}" acceptance "${acceptance.id}" must have browser coverage.`,
27
+ );
28
+ }
29
+ if (!acceptance.expectedObservable.trim()) {
30
+ errors.push(
31
+ `motion reference behavior "${behaviorId}" acceptance "${acceptance.id}" must describe its observable result.`,
32
+ );
33
+ }
34
+ return errors;
35
+ }
36
+
37
+ export function validateBehaviorCoverage(
38
+ input: ToolcraftMotionReferenceInput,
39
+ acceptanceRowsById: AcceptanceRowsById,
40
+ ): string[] {
41
+ const errors: string[] = [];
42
+ const studiesById = new Map<
43
+ string,
44
+ ToolcraftMotionReferenceInput["studies"]
45
+ >();
46
+ const usedBehaviorIds = new Set<string>();
47
+
48
+ for (const study of input.studies) {
49
+ studiesById.set(study.studyId, [
50
+ ...(studiesById.get(study.studyId) ?? []),
51
+ study,
52
+ ]);
53
+ for (const phase of study.phases) {
54
+ phase.behaviorIds.forEach((behaviorId) => usedBehaviorIds.add(behaviorId));
55
+ }
56
+ for (const event of study.events) {
57
+ if (event.classification === "product-behavior") {
58
+ event.behaviorIds.forEach((behaviorId) => usedBehaviorIds.add(behaviorId));
59
+ }
60
+ }
61
+ }
62
+
63
+ for (const behavior of input.behaviors) {
64
+ if (!behavior.description.trim()) {
65
+ errors.push(
66
+ `motion reference behavior "${behavior.id}" must include a non-empty description.`,
67
+ );
68
+ }
69
+ if (!behavior.implementationIntent.trim()) {
70
+ errors.push(
71
+ `motion reference behavior "${behavior.id}" must include a non-empty implementationIntent.`,
72
+ );
73
+ }
74
+ if (!usedBehaviorIds.has(behavior.id)) {
75
+ errors.push(
76
+ `motion reference behavior "${behavior.id}" must be used by a phase or product event.`,
77
+ );
78
+ }
79
+
80
+ const timingClaimStudies = new Map<string, Set<string>>();
81
+ for (const timingClaim of behavior.timingClaims) {
82
+ const studyIds = timingClaimStudies.get(timingClaim.claim) ?? new Set();
83
+ if (studyIds.has(timingClaim.studyId)) {
84
+ errors.push(
85
+ `motion reference behavior "${behavior.id}" timing claim "${timingClaim.claim}" for study "${timingClaim.studyId}" is duplicated.`,
86
+ );
87
+ }
88
+ studyIds.add(timingClaim.studyId);
89
+ timingClaimStudies.set(timingClaim.claim, studyIds);
90
+
91
+ const studies = studiesById.get(timingClaim.studyId) ?? [];
92
+ if (studies.length === 0) {
93
+ errors.push(
94
+ `motion reference behavior "${behavior.id}" timing claim "${timingClaim.claim}" names missing study "${timingClaim.studyId}".`,
95
+ );
96
+ } else if (studies.length > 1) {
97
+ errors.push(
98
+ `motion reference behavior "${behavior.id}" timing claim "${timingClaim.claim}" studyId "${timingClaim.studyId}" resolves to ${studies.length} studies; expected exactly one.`,
99
+ );
100
+ } else {
101
+ const [study] = studies;
102
+ if (study.evidence.timingMode !== "seconds") {
103
+ errors.push(
104
+ `motion reference behavior "${behavior.id}" timing claim "${timingClaim.claim}" must name a seconds-timed study; "${timingClaim.studyId}" is ordinal.`,
105
+ );
106
+ }
107
+ }
108
+ }
109
+
110
+ const acceptanceRows = acceptanceRowsById.get(behavior.acceptanceId) ?? [];
111
+ if (acceptanceRows.length === 0) {
112
+ errors.push(
113
+ `motion reference behavior "${behavior.id}" points to missing acceptanceId "${behavior.acceptanceId}".`,
114
+ );
115
+ continue;
116
+ }
117
+ for (const acceptance of acceptanceRows) {
118
+ errors.push(...validateAcceptanceFields({ acceptance, behaviorId: behavior.id }));
119
+ }
120
+ if (acceptanceRows.length > 1) {
121
+ errors.push(
122
+ `motion reference behavior "${behavior.id}" acceptanceId "${behavior.acceptanceId}" resolves to ${acceptanceRows.length} acceptance rows; expected exactly one.`,
123
+ );
124
+ continue;
125
+ }
126
+ const [acceptance] = acceptanceRows;
127
+
128
+ const matchingReverseCoverage = (
129
+ acceptance.motionReferenceCoverage ?? []
130
+ ).filter(
131
+ (coverage) =>
132
+ coverage.referenceId === input.referenceId &&
133
+ coverage.behaviorId === behavior.id,
134
+ );
135
+ if (matchingReverseCoverage.length !== 1) {
136
+ errors.push(
137
+ `motion reference behavior "${behavior.id}" acceptance "${acceptance.id}" must own exactly one reverse coverage pair; found ${matchingReverseCoverage.length}.`,
138
+ );
139
+ }
140
+
141
+ let coverageOwnerCount = 0;
142
+ for (const rows of acceptanceRowsById.values()) {
143
+ for (const entry of rows) {
144
+ const ownsCoverage = (entry.motionReferenceCoverage ?? []).some(
145
+ (coverage) =>
146
+ coverage.referenceId === input.referenceId &&
147
+ coverage.behaviorId === behavior.id,
148
+ );
149
+ if (ownsCoverage) {
150
+ coverageOwnerCount += 1;
151
+ }
152
+ }
153
+ }
154
+ if (coverageOwnerCount > 1) {
155
+ errors.push(
156
+ `motion reference behavior "${behavior.id}" coverage pair has ${coverageOwnerCount} acceptance owners; expected exactly one.`,
157
+ );
158
+ }
159
+ }
160
+
161
+ return errors;
162
+ }