@pixel-point/toolcraft 0.0.18 → 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 (384) 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 +55 -53
  8. package/src/generate-test-doc-assertions.test.mjs +1 -1
  9. package/src/generate-test-doc-policy-assertions.mjs +1 -1
  10. package/src/generate-test-export-doc-assertions.mjs +53 -0
  11. package/src/generate-test-file-assertions.mjs +51 -8
  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 +0 -1
  20. package/src/generate-test-runtime-boundary-assertions.mjs +20 -0
  21. package/src/generate-test-standalone-assertions.mjs +0 -16
  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 +18 -3
  34. package/templates/runtime/contracts/component-contracts.choices.ts +8 -3
  35. package/templates/runtime/contracts/component-contracts.inputs.ts +2 -1
  36. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +18 -4
  37. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +1 -1
  38. package/templates/runtime/contracts/component-contracts.runtime.ts +7 -4
  39. package/templates/runtime/contracts/component-contracts.visual.test.ts +20 -0
  40. package/templates/runtime/contracts/component-contracts.visual.ts +4 -0
  41. package/templates/runtime/contracts/decision-contracts.test.ts +18 -12
  42. package/templates/runtime/contracts/decision-contracts.ts +4 -4
  43. package/templates/runtime/contracts/performance-verification-policy.test.ts +12 -12
  44. package/templates/runtime/contracts/performance-verification-policy.ts +8 -8
  45. package/templates/runtime/export/artifact-download.ts +1 -0
  46. package/templates/runtime/export/artifact-frame-renderer.ts +2 -15
  47. package/templates/runtime/export/artifact-frame-state.ts +16 -0
  48. package/templates/runtime/export/artifact-scene-frame.test.ts +3 -3
  49. package/templates/runtime/export/artifact-scene-frame.ts +3 -3
  50. package/templates/runtime/export/export-error.ts +5 -0
  51. package/templates/runtime/export/export-renderer-coverage.ts +54 -0
  52. package/templates/runtime/export/image-artifact-export.ts +2 -2
  53. package/templates/runtime/export/index.ts +13 -0
  54. package/templates/runtime/export/product-export-renderer.test.ts +38 -4
  55. package/templates/runtime/export/product-export-renderer.ts +0 -41
  56. package/templates/runtime/export/product-svg-export-renderer.ts +21 -0
  57. package/templates/runtime/export/svg-artifact-export.test.ts +98 -0
  58. package/templates/runtime/export/svg-artifact-export.ts +120 -0
  59. package/templates/runtime/export/svg-document.test.ts +221 -0
  60. package/templates/runtime/export/svg-document.ts +400 -0
  61. package/templates/runtime/export/svg-policy.ts +47 -0
  62. package/templates/runtime/index.ts +1 -0
  63. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +10 -0
  64. package/templates/runtime/react/app-shell/settings-transfer.test.ts +21 -0
  65. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +46 -1
  66. package/templates/runtime/react/app-shell/toolcraft-app.tsx +7 -2
  67. package/templates/runtime/react/canvas/canvas-shell-interactions.test.tsx +30 -0
  68. package/templates/runtime/react/canvas/canvas-shell-pinch-interactions.test.tsx +155 -0
  69. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +1 -1
  70. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -5
  71. package/templates/runtime/react/canvas/canvas-viewport-geometry.ts +133 -0
  72. package/templates/runtime/react/canvas/use-canvas-viewport-interactions.ts +285 -36
  73. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-applicability-layout.test.tsx +134 -0
  74. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-layout.test.tsx +16 -1
  75. package/templates/runtime/react/controls-panel/__tests__/controls-panel.render-isolation.test.tsx +2 -0
  76. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +39 -0
  77. package/templates/runtime/react/controls-panel/__tests__/controls-panel.setup-controls.test.tsx +3 -3
  78. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +1 -4
  79. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +2 -2
  80. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-interaction.test.tsx +6 -6
  81. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -2
  82. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +40 -11
  83. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +31 -2
  84. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +4 -1
  85. package/templates/runtime/react/controls-panel/renderers/controls-panel-action-renderer.tsx +2 -1
  86. package/templates/runtime/react/controls-panel/renderers/controls-panel-basic-renderers.tsx +2 -52
  87. package/templates/runtime/react/controls-panel/values/controls-panel-color-values.ts +1 -9
  88. package/templates/runtime/react/controls-panel/values/controls-panel-spatial-values.ts +7 -2
  89. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-math.ts +9 -69
  90. package/templates/runtime/schema/artifact-export-actions.test.ts +47 -0
  91. package/templates/runtime/schema/artifact-export-actions.ts +49 -0
  92. package/templates/runtime/schema/types.ts +1 -0
  93. package/templates/runtime/state/canvas-background-state.ts +1 -16
  94. package/templates/runtime/state/canvas-state.ts +32 -20
  95. package/templates/runtime/state/control-value-codecs.test.ts +98 -0
  96. package/templates/runtime/state/control-value-codecs.ts +700 -0
  97. package/templates/runtime/state/control-value-normalization.test.ts +1 -1
  98. package/templates/runtime/state/control-value-normalization.ts +127 -135
  99. package/templates/runtime/state/controls-reducer.ts +26 -3
  100. package/templates/runtime/state/create-template-state.test.ts +30 -0
  101. package/templates/runtime/state/create-template-state.ts +52 -31
  102. package/templates/runtime/state/orientation-pose.ts +80 -0
  103. package/templates/runtime/state/persistence-control-values.test.ts +59 -0
  104. package/templates/runtime/state/reducer-controls.test.ts +45 -0
  105. package/templates/runtime/state/reducer-media-reset.test.ts +1 -1
  106. package/templates/runtime/state/reducer-timeline.test.ts +70 -0
  107. package/templates/runtime/state/timeline-reducer.ts +40 -2
  108. package/templates/runtime/testing/performance-schema-queries.ts +2 -2
  109. package/templates/starter/AGENTS.md +21 -22
  110. package/templates/starter/docs/toolcraft/README.md +6 -6
  111. package/templates/starter/docs/toolcraft/acceptance-testing.md +20 -20
  112. package/templates/starter/docs/toolcraft/agent-worklog.md +88 -1
  113. package/templates/starter/docs/toolcraft/assembly-workflow.md +7 -6
  114. package/templates/starter/docs/toolcraft/component-rules.md +2 -2
  115. package/templates/starter/docs/toolcraft/core/layout.md +6 -1
  116. package/templates/starter/docs/toolcraft/core/media-upload.md +1 -1
  117. package/templates/starter/docs/toolcraft/core/performance.md +8 -10
  118. package/templates/starter/docs/toolcraft/core/reference-study.md +80 -19
  119. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +2 -1
  120. package/templates/starter/docs/toolcraft/core/setup-export.md +43 -13
  121. package/templates/starter/docs/toolcraft/decision-contract.md +2 -2
  122. package/templates/starter/docs/toolcraft/performance.md +8 -10
  123. package/templates/starter/docs/toolcraft/renderer-technique.md +2 -2
  124. package/templates/starter/docs/toolcraft/schema-reference.md +8 -8
  125. package/templates/starter/docs/toolcraft/workflow.md +14 -14
  126. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +38 -0
  127. package/templates/starter/e2e/app-browser-motion-reference-requirements.spec.ts +411 -0
  128. package/templates/starter/e2e/app-browser-proof-session.spec.ts +85 -0
  129. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +62 -0
  130. package/templates/starter/e2e/app-browser-selection-scope-evidence.spec.ts +137 -0
  131. package/templates/starter/e2e/browser-infinity-canvas-svg-evidence.ts +42 -0
  132. package/templates/starter/e2e/browser-layer-evidence-helpers.ts +15 -36
  133. package/templates/starter/e2e/browser-product-raster-snapshot.ts +213 -0
  134. package/templates/starter/e2e/browser-proof-session.ts +43 -15
  135. package/templates/starter/e2e/browser-proof-surface-action.ts +79 -0
  136. package/templates/starter/e2e/browser-proof-types.ts +15 -0
  137. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +22 -6
  138. package/templates/starter/e2e/browser-selection-scope-evidence.ts +269 -0
  139. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +97 -0
  140. package/templates/starter/e2e/browser-svg-export-evidence.spec.ts +79 -0
  141. package/templates/starter/e2e/browser-svg-export-evidence.ts +152 -0
  142. package/templates/starter/e2e/export-artifact-helpers.ts +70 -0
  143. package/templates/starter/e2e/product-observable-helpers.ts +8 -129
  144. package/templates/starter/e2e/svg-artifact-inspection.spec.ts +136 -0
  145. package/templates/starter/e2e/svg-artifact-inspection.ts +331 -0
  146. package/templates/starter/e2e/svg-download-test-fixtures.ts +34 -0
  147. package/templates/starter/e2e/toolcraft-delivery-catalog-reporter.ts +198 -15
  148. package/templates/starter/e2e/toolcraft-feature-verification-reporter.spec.ts +149 -0
  149. package/templates/starter/e2e/toolcraft-feature-verification-reporter.ts +223 -0
  150. package/templates/starter/package.json +2 -0
  151. package/templates/starter/scripts/check-toolcraft-docs.mjs +1 -0
  152. package/templates/starter/scripts/create-toolcraft-motion-reference-study.mjs +233 -0
  153. package/templates/starter/scripts/create-toolcraft-motion-reference-study.test.mjs +459 -0
  154. package/templates/starter/scripts/playwright-test-title-selection.d.mts +45 -0
  155. package/templates/starter/scripts/run-browser-performance-test-helpers.mjs +0 -27
  156. package/templates/starter/scripts/run-browser-performance.mjs +0 -6
  157. package/templates/starter/scripts/run-browser-performance.test.mjs +5 -13
  158. package/templates/starter/scripts/run-delivery-verification-test-helpers.mjs +0 -34
  159. package/templates/starter/scripts/run-delivery-verification.test.mjs +135 -31
  160. package/templates/starter/scripts/run-feature-verification.mjs +212 -0
  161. package/templates/starter/scripts/run-feature-verification.test.mjs +226 -0
  162. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.mjs +21 -8
  163. package/templates/starter/scripts/toolcraft-checkpoint-delivery-state.test.mjs +32 -92
  164. package/templates/starter/scripts/toolcraft-checkpoint-transaction.mjs +28 -6
  165. package/templates/starter/scripts/toolcraft-checkpoint-transaction.test.mjs +8 -63
  166. package/templates/starter/scripts/toolcraft-delivery-anchor.mjs +28 -8
  167. package/templates/starter/scripts/toolcraft-delivery-anchor.test.mjs +50 -61
  168. package/templates/starter/scripts/toolcraft-delivery-architecture-inventory.mjs +0 -4
  169. package/templates/starter/scripts/toolcraft-delivery-architecture-policy.mjs +0 -1
  170. package/templates/starter/scripts/toolcraft-delivery-evidence.test.mjs +2 -3
  171. package/templates/starter/scripts/toolcraft-delivery-executor.mjs +5 -1
  172. package/templates/starter/scripts/toolcraft-delivery-executor.test.mjs +5 -12
  173. package/templates/starter/scripts/toolcraft-delivery-functional-context.d.mts +1 -48
  174. package/templates/starter/scripts/toolcraft-delivery-functional-context.mjs +2 -30
  175. package/templates/starter/scripts/toolcraft-delivery-functional-context.test.mjs +4 -166
  176. package/templates/starter/scripts/toolcraft-delivery-functional-initial.test.mjs +4 -20
  177. package/templates/starter/scripts/toolcraft-delivery-lifecycle.mjs +43 -192
  178. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-lifecycle.test.mjs +79 -440
  179. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +102 -297
  180. package/templates/starter/scripts/toolcraft-delivery-plan-construction.mjs +46 -97
  181. package/templates/starter/scripts/toolcraft-delivery-plan-test-helpers.mjs +125 -0
  182. package/templates/starter/scripts/toolcraft-delivery-plan-validation.test.mjs +58 -0
  183. package/templates/starter/scripts/toolcraft-delivery-plan.d.mts +19 -31
  184. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +254 -252
  185. package/templates/starter/scripts/toolcraft-delivery-plan.test.mjs +29 -457
  186. package/templates/starter/scripts/toolcraft-delivery-receipt-performance.test.mjs +1 -1
  187. package/templates/starter/scripts/toolcraft-delivery-receipt-strictness.test.mjs +3 -72
  188. package/templates/starter/scripts/toolcraft-delivery-receipt-test-helpers.mjs +7 -36
  189. package/templates/starter/scripts/toolcraft-delivery-receipt.d.mts +3 -5
  190. package/templates/starter/scripts/toolcraft-delivery-receipt.mjs +15 -31
  191. package/templates/starter/scripts/toolcraft-delivery-receipt.test.mjs +28 -20
  192. package/templates/starter/scripts/toolcraft-delivery-receipt.types.test.mts +22 -30
  193. package/templates/starter/scripts/toolcraft-feature-verification-plan.d.mts +20 -0
  194. package/templates/starter/scripts/toolcraft-feature-verification-plan.mjs +146 -0
  195. package/templates/starter/scripts/toolcraft-feature-verification-plan.test.mjs +96 -0
  196. package/templates/starter/scripts/toolcraft-functional-proof-model.d.mts +3 -18
  197. package/templates/starter/scripts/toolcraft-functional-proof-model.mjs +39 -127
  198. package/templates/starter/scripts/toolcraft-functional-proof-model.test.mjs +25 -362
  199. package/templates/starter/scripts/toolcraft-functional-proof-model.types.test.mts +6 -83
  200. package/templates/starter/scripts/toolcraft-functional-proof-primitives.d.mts +11 -0
  201. package/templates/starter/scripts/toolcraft-functional-proof-primitives.mjs +61 -0
  202. package/templates/starter/scripts/toolcraft-functional-proof-primitives.test.mjs +43 -0
  203. package/templates/starter/scripts/toolcraft-integrity-policy.mjs +13 -0
  204. package/templates/starter/scripts/toolcraft-integrity-test-utils.mjs +1 -1
  205. package/templates/starter/scripts/toolcraft-layer-evidence-facade.test.mjs +58 -22
  206. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.mjs +111 -0
  207. package/templates/starter/scripts/toolcraft-motion-reference-analysis-protocol.test.mjs +116 -0
  208. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.d.mts +37 -0
  209. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.mjs +308 -0
  210. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.test.mjs +420 -0
  211. package/templates/starter/scripts/toolcraft-motion-reference-artifacts.types.test.mts +50 -0
  212. package/templates/starter/scripts/toolcraft-motion-reference-command-options.mjs +195 -0
  213. package/templates/starter/scripts/toolcraft-motion-reference-command-options.test.mjs +224 -0
  214. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.mjs +122 -0
  215. package/templates/starter/scripts/toolcraft-motion-reference-contact-sheet.test.mjs +153 -0
  216. package/templates/starter/scripts/toolcraft-motion-reference-evidence-assembly.mjs +270 -0
  217. package/templates/starter/scripts/toolcraft-motion-reference-evidence.test.mjs +432 -0
  218. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.mjs +275 -0
  219. package/templates/starter/scripts/toolcraft-motion-reference-ffmpeg-output.test.mjs +294 -0
  220. package/templates/starter/scripts/toolcraft-motion-reference-group-inventory.mjs +148 -0
  221. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.mjs +101 -0
  222. package/templates/starter/scripts/toolcraft-motion-reference-image-filter-graph.test.mjs +124 -0
  223. package/templates/starter/scripts/toolcraft-motion-reference-image-source.mjs +259 -0
  224. package/templates/starter/scripts/toolcraft-motion-reference-image-source.test.mjs +338 -0
  225. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.mjs +96 -0
  226. package/templates/starter/scripts/toolcraft-motion-reference-image-timing.test.mjs +81 -0
  227. package/templates/starter/scripts/toolcraft-motion-reference-lock-recovery.test.mjs +144 -0
  228. package/templates/starter/scripts/toolcraft-motion-reference-media-source.mjs +165 -0
  229. package/templates/starter/scripts/toolcraft-motion-reference-media-source.test.mjs +299 -0
  230. package/templates/starter/scripts/toolcraft-motion-reference-publication-discovery.test.mjs +142 -0
  231. package/templates/starter/scripts/toolcraft-motion-reference-publication-isolation.test.mjs +234 -0
  232. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-schema.mjs +122 -0
  233. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.mjs +89 -0
  234. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal-store.test.mjs +74 -0
  235. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.mjs +264 -0
  236. package/templates/starter/scripts/toolcraft-motion-reference-publication-journal.test.mjs +377 -0
  237. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.mjs +254 -0
  238. package/templates/starter/scripts/toolcraft-motion-reference-publication-lease.test.mjs +130 -0
  239. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.mjs +128 -0
  240. package/templates/starter/scripts/toolcraft-motion-reference-publication-staging.test.mjs +209 -0
  241. package/templates/starter/scripts/toolcraft-motion-reference-publication.fixtures.mjs +88 -0
  242. package/templates/starter/scripts/toolcraft-motion-reference-publication.mjs +249 -0
  243. package/templates/starter/scripts/toolcraft-motion-reference-publication.test.mjs +433 -0
  244. package/templates/starter/scripts/toolcraft-motion-reference-review-output.mjs +38 -0
  245. package/templates/starter/scripts/toolcraft-motion-reference-review-output.test.mjs +29 -0
  246. package/templates/starter/scripts/toolcraft-motion-reference-root-safety.test.mjs +84 -0
  247. package/templates/starter/scripts/toolcraft-motion-reference-roots.mjs +83 -0
  248. package/templates/starter/scripts/toolcraft-motion-reference-sampling.mjs +283 -0
  249. package/templates/starter/scripts/toolcraft-motion-reference-sampling.test.mjs +480 -0
  250. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.mjs +39 -0
  251. package/templates/starter/scripts/toolcraft-motion-reference-stage-registry.test.mjs +59 -0
  252. package/templates/starter/scripts/toolcraft-motion-reference-stage.mjs +71 -0
  253. package/templates/starter/scripts/toolcraft-motion-reference-stage.test.mjs +61 -0
  254. package/templates/starter/scripts/toolcraft-motion-review-numeric.mjs +74 -0
  255. package/templates/starter/scripts/toolcraft-motion-review-numeric.test.mjs +65 -0
  256. package/templates/starter/scripts/toolcraft-motion-review-partitioning.mjs +268 -0
  257. package/templates/starter/scripts/toolcraft-motion-review-partitioning.test.mjs +329 -0
  258. package/templates/starter/scripts/toolcraft-motion-review-protected-units.mjs +120 -0
  259. package/templates/starter/scripts/toolcraft-motion-review-protected-units.test.mjs +111 -0
  260. package/templates/starter/scripts/toolcraft-performance-escalation-policy.test.mjs +77 -345
  261. package/templates/starter/scripts/toolcraft-performance-receipt-validation.test.mjs +19 -21
  262. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +1 -1
  263. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +10 -1
  264. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +5 -1
  265. package/templates/starter/scripts/toolcraft-product-observable-facade.test.mjs +46 -1
  266. package/templates/starter/scripts/toolcraft-receipt-boundary.test.mjs +0 -1
  267. package/templates/starter/scripts/toolcraft-source-inventory.d.mts +5 -0
  268. package/templates/starter/scripts/toolcraft-source-inventory.mjs +25 -14
  269. package/templates/starter/scripts/toolcraft-source-inventory.test.mjs +48 -0
  270. package/templates/starter/scripts/toolcraft-source-ownership.mjs +0 -1
  271. package/templates/starter/scripts/toolcraft-source-ownership.test.mjs +30 -5
  272. package/templates/starter/scripts/toolcraft-verification-receipt-test-helpers.mjs +5 -26
  273. package/templates/starter/scripts/toolcraft-verification-receipt.d.mts +0 -7
  274. package/templates/starter/scripts/toolcraft-verification-receipt.mjs +3 -47
  275. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +6 -87
  276. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.d.mts +19 -0
  277. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.mjs +107 -0
  278. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.test.mjs +114 -0
  279. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.mjs +13 -2
  280. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.test.mjs +110 -0
  281. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +51 -20
  282. package/templates/starter/src/app/acceptance/canvas-coverage.ts +1 -0
  283. package/templates/starter/src/app/acceptance/color-bank-labels.ts +11 -15
  284. package/templates/starter/src/app/acceptance/color-row-grouping.ts +33 -0
  285. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +0 -11
  286. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -3
  287. package/templates/starter/src/app/acceptance/control-component-rules.ts +0 -10
  288. package/templates/starter/src/app/acceptance/control-labels.ts +30 -1
  289. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +18 -0
  290. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +25 -17
  291. package/templates/starter/src/app/acceptance/feature-verification-selection.test.ts +295 -0
  292. package/templates/starter/src/app/acceptance/feature-verification-selection.ts +161 -0
  293. package/templates/starter/src/app/acceptance/infinity-canvas.ts +10 -0
  294. package/templates/starter/src/app/acceptance/interaction-ownership-intent.ts +19 -1
  295. package/templates/starter/src/app/acceptance/interaction-ownership.ts +18 -2
  296. package/templates/starter/src/app/acceptance/motion-reference-evidence-validation.mjs +65 -0
  297. package/templates/starter/src/app/acceptance/motion-reference-evidence-windows.mjs +301 -0
  298. package/templates/starter/src/app/acceptance/motion-reference-evidence.d.mts +11 -0
  299. package/templates/starter/src/app/acceptance/motion-reference-evidence.mjs +145 -0
  300. package/templates/starter/src/app/acceptance/motion-reference-study-behaviors.ts +162 -0
  301. package/templates/starter/src/app/acceptance/motion-reference-study-coverage.ts +206 -0
  302. package/templates/starter/src/app/acceptance/motion-reference-study-identity.ts +129 -0
  303. package/templates/starter/src/app/acceptance/motion-reference-study-partitions.ts +186 -0
  304. package/templates/starter/src/app/acceptance/motion-reference-study.ts +90 -0
  305. package/templates/starter/src/app/acceptance/output-export-actions.test.ts +9 -1
  306. package/templates/starter/src/app/acceptance/output-export-actions.ts +14 -22
  307. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +5 -1
  308. package/templates/starter/src/app/acceptance/output-export-model.ts +4 -0
  309. package/templates/starter/src/app/acceptance/output-export.ts +4 -1
  310. package/templates/starter/src/app/acceptance/reference-study-types.ts +167 -17
  311. package/templates/starter/src/app/acceptance/runtime-coverage.ts +6 -1
  312. package/templates/starter/src/app/acceptance/selection-scope.test.ts +200 -0
  313. package/templates/starter/src/app/acceptance/selection-scope.ts +151 -0
  314. package/templates/starter/src/app/acceptance/transfer-mode-types.ts +114 -0
  315. package/templates/starter/src/app/acceptance/types.ts +43 -118
  316. package/templates/starter/src/app/acceptance/validate-coverage.ts +14 -3
  317. package/templates/starter/src/app/starter-acceptance-data.ts +1 -0
  318. package/templates/starter/src/app/starter-acceptance.animation-intent.test.ts +3 -0
  319. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +81 -8
  320. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +2 -0
  321. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +2 -0
  322. package/templates/starter/src/app/starter-acceptance.color-row-grouping.test.ts +118 -0
  323. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +1 -1
  324. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +1 -0
  325. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +1 -0
  326. package/templates/starter/src/app/starter-acceptance.control-label-context.test.ts +1 -0
  327. package/templates/starter/src/app/starter-acceptance.control-labels.test.ts +78 -3
  328. package/templates/starter/src/app/starter-acceptance.control-naming-rules.test.ts +77 -1
  329. package/templates/starter/src/app/starter-acceptance.custom-control-fit-check.test.ts +1 -1
  330. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +74 -3
  331. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +5 -1
  332. package/templates/starter/src/app/starter-acceptance.image-export-settings.test.ts +2 -0
  333. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +19 -3
  334. package/templates/starter/src/app/starter-acceptance.layers.test.ts +2 -1
  335. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  336. package/templates/starter/src/app/starter-acceptance.motion-reference-evidence.test.ts +17 -0
  337. package/templates/starter/src/app/starter-acceptance.motion-reference-identity.test.ts +163 -0
  338. package/templates/starter/src/app/starter-acceptance.motion-reference-mapping.test.ts +133 -0
  339. package/templates/starter/src/app/starter-acceptance.motion-reference-ownership.test.ts +121 -0
  340. package/templates/starter/src/app/starter-acceptance.motion-reference-partitions.test.ts +210 -0
  341. package/templates/starter/src/app/starter-acceptance.motion-reference-test-utils.ts +313 -0
  342. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +78 -4
  343. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +6 -1
  344. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +18 -1
  345. package/templates/starter/src/app/starter-acceptance.product-worklog.test.ts +123 -2
  346. package/templates/starter/src/app/starter-acceptance.reference-custom-timeline.test.ts +2 -0
  347. package/templates/starter/src/app/starter-acceptance.reference-foundation.test.ts +8 -0
  348. package/templates/starter/src/app/starter-acceptance.reference-timeline.test.ts +7 -0
  349. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +2 -0
  350. package/templates/starter/src/app/starter-acceptance.timeline-playback.test.ts +5 -0
  351. package/templates/starter/src/app/starter-acceptance.ts +34 -8
  352. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +2 -0
  353. package/templates/starter/src/app/starter-acceptance.video-reference-study.test.ts +34 -236
  354. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +4 -0
  355. package/templates/starter/src/app/starter-acceptance.worklog-test-utils.ts +122 -6
  356. package/templates/starter/src/app/starter-acceptance.worklog.test.ts +152 -16
  357. package/templates/starter/src/app/starter-performance.gates.test.ts +20 -18
  358. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +22 -6
  359. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +3 -0
  360. package/templates/starter/tsconfig.json +1 -0
  361. package/templates/starter/scripts/toolcraft-browser-impact-selection.mjs +0 -26
  362. package/templates/starter/scripts/toolcraft-delivery-framework-refresh.test.mjs +0 -141
  363. package/templates/starter/scripts/toolcraft-delivery-functional-changed.test.mjs +0 -468
  364. package/templates/starter/scripts/toolcraft-delivery-functional-model-lifecycle.test.mjs +0 -254
  365. package/templates/starter/scripts/toolcraft-delivery-functional-semantic-regression.test.mjs +0 -135
  366. package/templates/starter/scripts/toolcraft-delivery-performance-iteration-durability.test.mjs +0 -329
  367. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +0 -430
  368. package/templates/starter/scripts/toolcraft-delivery-plan-functional-model.test.mjs +0 -358
  369. package/templates/starter/scripts/toolcraft-functional-proof-regression-fixtures.mjs +0 -298
  370. package/templates/starter/scripts/toolcraft-verification-impact-inventory.d.mts +0 -62
  371. package/templates/starter/scripts/toolcraft-verification-impact-inventory.mjs +0 -344
  372. package/templates/starter/scripts/toolcraft-verification-impact-inventory.test.mjs +0 -204
  373. package/templates/starter/scripts/toolcraft-verification-impact-performance-candidates.test.mjs +0 -210
  374. package/templates/starter/scripts/toolcraft-verification-impact-resolution.mjs +0 -344
  375. package/templates/starter/scripts/toolcraft-verification-impact-resolution.test.mjs +0 -270
  376. package/templates/starter/scripts/toolcraft-verification-impact-semantic-resolution.test.mjs +0 -449
  377. package/templates/starter/scripts/toolcraft-verification-impact-support-resolution.test.mjs +0 -195
  378. package/templates/starter/scripts/toolcraft-verification-impact.d.mts +0 -132
  379. package/templates/starter/scripts/toolcraft-verification-impact.mjs +0 -169
  380. package/templates/starter/scripts/toolcraft-verification-impact.test.mjs +0 -300
  381. package/templates/starter/scripts/toolcraft-verification-input-roles.mjs +0 -197
  382. package/templates/starter/scripts/toolcraft-verification-input-roles.test.mjs +0 -225
  383. package/templates/starter/src/app/acceptance/video-reference-study.ts +0 -217
  384. package/templates/starter/src/app/starter-verification-impact.json +0 -15
@@ -0,0 +1,65 @@
1
+ const SHA256 = /^[a-f0-9]{64}$/u;
2
+ const STUDY_ID = /^motion-v1-[a-f0-9]{64}$/u;
3
+ const GROUP_ID = /^motion-group-v1-[a-f0-9]{64}$/u;
4
+
5
+ export function isRecord(value) {
6
+ return value !== null && typeof value === "object" && !Array.isArray(value) &&
7
+ (Object.getPrototypeOf(value) === Object.prototype ||
8
+ Object.getPrototypeOf(value) === null);
9
+ }
10
+
11
+ export function hasExactKeys(
12
+ value,
13
+ required,
14
+ optional,
15
+ label,
16
+ errors,
17
+ ) {
18
+ if (!isRecord(value)) {
19
+ errors.push(`${label} must be an object.`);
20
+ return false;
21
+ }
22
+ const allowed = new Set([...required, ...optional]);
23
+ let exact = true;
24
+ for (const key of required) {
25
+ if (!Object.hasOwn(value, key)) {
26
+ errors.push(`${label} is missing ${key}.`);
27
+ exact = false;
28
+ }
29
+ }
30
+ for (const key of Object.keys(value)) {
31
+ if (!allowed.has(key)) {
32
+ errors.push(`${label} has unknown property ${key}.`);
33
+ exact = false;
34
+ }
35
+ }
36
+ return exact;
37
+ }
38
+
39
+ export function finite(value) {
40
+ return typeof value === "number" && Number.isFinite(value);
41
+ }
42
+ export function positive(value) {
43
+ return finite(value) && value > 0;
44
+ }
45
+ export function positiveInteger(value) {
46
+ return Number.isSafeInteger(value) && value > 0;
47
+ }
48
+ export function nonnegativeInteger(value) {
49
+ return Number.isSafeInteger(value) && value >= 0;
50
+ }
51
+ export function nonemptyString(value) {
52
+ return typeof value === "string" && value.trim().length > 0;
53
+ }
54
+ export function isSha256(value) {
55
+ return typeof value === "string" && SHA256.test(value);
56
+ }
57
+ export function isStudyId(value) {
58
+ return typeof value === "string" && STUDY_ID.test(value);
59
+ }
60
+ export function isGroupId(value) {
61
+ return typeof value === "string" && GROUP_ID.test(value);
62
+ }
63
+ export function requireValue(condition, message, errors) {
64
+ if (!condition) errors.push(message);
65
+ }
@@ -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
+ }