@pixel-point/toolcraft 0.0.17 → 0.0.18

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 (196) hide show
  1. package/package.json +1 -1
  2. package/src/generate-test-doc-assertions.mjs +58 -68
  3. package/src/generate-test-doc-assertions.test.mjs +43 -0
  4. package/src/generate-test-doc-policy-assertions.mjs +92 -0
  5. package/src/generate-test-file-assertions.mjs +26 -75
  6. package/src/generate-test-product-iteration.test.mjs +17 -0
  7. package/src/generate-test-runtime-boundary-assertions.mjs +158 -0
  8. package/src/generate-test-standalone-assertions.mjs +10 -0
  9. package/templates/runtime/contracts/component-contracts.choices.test.ts +47 -3
  10. package/templates/runtime/contracts/component-contracts.choices.ts +48 -3
  11. package/templates/runtime/contracts/component-contracts.inputs.test.ts +6 -6
  12. package/templates/runtime/contracts/component-contracts.inputs.ts +6 -6
  13. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +8 -2
  14. package/templates/runtime/contracts/component-contracts.media-custom.ts +8 -2
  15. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +4 -4
  16. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +7 -1
  17. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +3 -0
  18. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +3 -0
  19. package/templates/runtime/contracts/component-contracts.runtime.ts +12 -8
  20. package/templates/runtime/contracts/component-contracts.test.ts +2 -0
  21. package/templates/runtime/contracts/decision-contracts.test.ts +18 -0
  22. package/templates/runtime/contracts/decision-contracts.ts +10 -10
  23. package/templates/runtime/export/export-background.ts +0 -19
  24. package/templates/runtime/export/export.test.ts +45 -42
  25. package/templates/runtime/export/index.ts +44 -16
  26. package/templates/runtime/index.ts +2 -0
  27. package/templates/runtime/model-import/model-import-limits.ts +15 -4
  28. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +11 -1
  29. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +29 -0
  30. package/templates/runtime/react/app-shell/toolcraft-app.tsx +18 -8
  31. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +76 -0
  32. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +13 -1
  33. package/templates/runtime/react/canvas/canvas-shell.tsx +16 -1
  34. package/templates/runtime/react/canvas/product-scene-surface.test.tsx +195 -0
  35. package/templates/runtime/react/canvas/product-scene-surface.tsx +155 -0
  36. package/templates/runtime/react/canvas/runtime-scene-export.ts +1 -1
  37. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +61 -0
  38. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +89 -0
  39. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +40 -19
  40. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +1 -1
  41. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-collection.test.tsx +62 -0
  42. package/templates/runtime/react/controls-panel/__tests__/controls-panel.source-color-bank.test.tsx +67 -0
  43. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +2 -4
  44. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +10 -4
  45. package/templates/runtime/react/controls-panel/conditions/control-conditions.ts +32 -119
  46. package/templates/runtime/react/controls-panel/layout/controls-panel-control-group.tsx +5 -2
  47. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +10 -8
  48. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +3 -3
  49. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.test.tsx +63 -0
  50. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-item-fields.tsx +138 -0
  51. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-items.tsx +104 -0
  52. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +115 -236
  53. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.test.tsx +95 -0
  54. package/templates/runtime/react/controls-panel/renderers/controls-panel-file-drop-item-controls.tsx +82 -0
  55. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.test.tsx +73 -1
  56. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +50 -1
  57. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +1 -1
  58. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +2 -1
  59. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +38 -6
  60. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +1 -0
  61. package/templates/runtime/react/index.ts +4 -0
  62. package/templates/runtime/react/model-rendering/model-export.ts +1 -1
  63. package/templates/runtime/react/orientation-gizmo/orientation-gizmo-selection.ts +5 -5
  64. package/templates/runtime/react/orientation-gizmo/use-toolcraft-orientation-control-selection.ts +2 -2
  65. package/templates/runtime/scene/scene-bounds.test.ts +23 -0
  66. package/templates/runtime/scene/scene-bounds.ts +31 -17
  67. package/templates/runtime/schema/app-capabilities.test.ts +4 -0
  68. package/templates/runtime/schema/collection-item-controls.ts +29 -0
  69. package/templates/runtime/schema/control-applicability.test.ts +127 -0
  70. package/templates/runtime/schema/control-applicability.ts +433 -0
  71. package/templates/runtime/schema/control-schema-normalization.ts +177 -11
  72. package/templates/runtime/schema/controls-panel-actions.ts +2 -2
  73. package/templates/runtime/schema/controls-panel-section-layout.ts +14 -9
  74. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +59 -0
  75. package/templates/runtime/schema/define-toolcraft.collection-actions-item-controls.test.ts +127 -0
  76. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +10 -4
  77. package/templates/runtime/schema/define-toolcraft.file-drop-item-controls.test.ts +98 -0
  78. package/templates/runtime/schema/define-toolcraft.file-drop-normalization.test.ts +27 -0
  79. package/templates/runtime/schema/define-toolcraft.segmented-control-fit.test.ts +20 -2
  80. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +16 -3
  81. package/templates/runtime/schema/define-toolcraft.source-collection.test.ts +79 -0
  82. package/templates/runtime/schema/runtime-setup-section.ts +24 -5
  83. package/templates/runtime/schema/types.ts +68 -13
  84. package/templates/starter/AGENTS.md +12 -10
  85. package/templates/starter/docs/toolcraft/acceptance-testing.md +17 -31
  86. package/templates/starter/docs/toolcraft/agent-worklog.md +15 -0
  87. package/templates/starter/docs/toolcraft/assembly-workflow.md +10 -3
  88. package/templates/starter/docs/toolcraft/component-rules.md +8 -6
  89. package/templates/starter/docs/toolcraft/core/control-selection.md +9 -3
  90. package/templates/starter/docs/toolcraft/core/layout.md +7 -7
  91. package/templates/starter/docs/toolcraft/core/media-upload.md +2 -0
  92. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +13 -2
  93. package/templates/starter/docs/toolcraft/core/setup-export.md +33 -7
  94. package/templates/starter/docs/toolcraft/custom-controls.md +6 -4
  95. package/templates/starter/docs/toolcraft/decision-contract.md +3 -1
  96. package/templates/starter/docs/toolcraft/renderer-technique.md +4 -0
  97. package/templates/starter/docs/toolcraft/schema-reference.md +21 -19
  98. package/templates/starter/e2e/app-browser-control-applicability-requirements.spec.ts +163 -0
  99. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +7 -2
  100. package/templates/starter/e2e/app-browser-orientation-evidence.spec.ts +174 -21
  101. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +1 -33
  102. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +61 -15
  103. package/templates/starter/e2e/{browser-conditional-output-evidence-helpers.ts → browser-background-output-evidence.ts} +0 -68
  104. package/templates/starter/e2e/browser-control-applicability-evidence.ts +114 -0
  105. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +20 -11
  106. package/templates/starter/e2e/browser-orientation-gizmo-evidence-helpers.ts +4 -0
  107. package/templates/starter/e2e/browser-orientation-gizmo-live-preconditions.ts +86 -0
  108. package/templates/starter/e2e/browser-runtime-applicability-requirements.ts +84 -0
  109. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +9 -2
  110. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +29 -26
  111. package/templates/starter/e2e/browser-semantic-evidence-test-helpers.ts +25 -0
  112. package/templates/starter/scripts/toolcraft-contract-manifest.json +1 -0
  113. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +85 -115
  114. package/templates/starter/scripts/toolcraft-product-boundary-module-policy.mjs +101 -0
  115. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +0 -72
  116. package/templates/starter/scripts/toolcraft-product-control-bindings.mjs +182 -0
  117. package/templates/starter/scripts/toolcraft-product-control-boundary.mjs +146 -0
  118. package/templates/starter/scripts/toolcraft-product-control-boundary.test.mjs +186 -0
  119. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +6 -17
  120. package/templates/starter/scripts/toolcraft-product-export-boundary.test.mjs +196 -0
  121. package/templates/starter/scripts/toolcraft-typescript-source-evidence-structure.test.mjs +20 -0
  122. package/templates/starter/scripts/toolcraft-typescript-source-evidence.mjs +1 -0
  123. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +2 -0
  124. package/templates/starter/src/app/acceptance/artifact-export-intent.ts +130 -0
  125. package/templates/starter/src/app/acceptance/conditions.ts +1 -1
  126. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +2 -2
  127. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +6 -20
  128. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +24 -5
  129. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +2 -13
  130. package/templates/starter/src/app/acceptance/control-applicability-cases.test.ts +321 -0
  131. package/templates/starter/src/app/acceptance/control-applicability-cases.ts +290 -0
  132. package/templates/starter/src/app/acceptance/control-applicability.ts +332 -0
  133. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +20 -9
  134. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +4 -6
  135. package/templates/starter/src/app/acceptance/control-layout-model.ts +4 -4
  136. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +3 -10
  137. package/templates/starter/src/app/acceptance/control-parts.ts +8 -0
  138. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.test.ts +215 -0
  139. package/templates/starter/src/app/acceptance/control-section-entity-cohesion.ts +132 -0
  140. package/templates/starter/src/app/acceptance/control-section-inventory.test.ts +87 -0
  141. package/templates/starter/src/app/acceptance/control-section-inventory.ts +74 -44
  142. package/templates/starter/src/app/acceptance/custom-controls.ts +11 -3
  143. package/templates/starter/src/app/acceptance/inline-layout.ts +64 -9
  144. package/templates/starter/src/app/acceptance/media-upload.ts +5 -1
  145. package/templates/starter/src/app/acceptance/orientation-gizmo.ts +18 -11
  146. package/templates/starter/src/app/acceptance/output-background-rules.ts +2 -2
  147. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +18 -10
  148. package/templates/starter/src/app/acceptance/output-export-model.ts +54 -11
  149. package/templates/starter/src/app/acceptance/output-export.ts +22 -10
  150. package/templates/starter/src/app/acceptance/output-video-export-rules.ts +99 -0
  151. package/templates/starter/src/app/acceptance/types.ts +23 -8
  152. package/templates/starter/src/app/acceptance/validate-coverage.ts +19 -2
  153. package/templates/starter/src/app/starter-acceptance.artifact-export-intent.test.ts +239 -0
  154. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +33 -0
  155. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +16 -1
  156. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +65 -0
  157. package/templates/starter/src/app/starter-acceptance.contract-fixtures.test.ts +41 -0
  158. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +112 -16
  159. package/templates/starter/src/app/starter-acceptance.control-applicability.test.ts +224 -0
  160. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +58 -3
  161. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +0 -111
  162. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +3 -1
  163. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +1 -1
  164. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +22 -0
  165. package/templates/starter/src/app/{starter-acceptance.image-video-export-settings.test.ts → starter-acceptance.image-export-settings.test.ts} +37 -9
  166. package/templates/starter/src/app/starter-acceptance.interaction-ownership.test.ts +12 -0
  167. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +11 -1
  168. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +28 -0
  169. package/templates/starter/src/app/starter-acceptance.output-export-intent.test.ts +384 -0
  170. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +4 -0
  171. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +112 -105
  172. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +12 -0
  173. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +1 -1
  174. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +35 -24
  175. package/templates/starter/src/app/starter-acceptance.section-size.test.ts +72 -0
  176. package/templates/starter/src/app/starter-acceptance.ts +11 -2
  177. package/templates/starter/src/app/starter-acceptance.video-export-settings.test.ts +336 -0
  178. package/templates/starter/src/app/starter-acceptance.view-interaction.test.ts +8 -0
  179. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -2
  180. package/templates/starter/src/routes/index.tsx +1 -0
  181. package/templates/ui/components/composites/tabs.tsx +2 -2
  182. package/templates/ui/components/controls/collection-actions/collection-item-groups.test.tsx +38 -0
  183. package/templates/ui/components/controls/collection-actions/collection-item-groups.tsx +36 -0
  184. package/templates/ui/components/controls/collection-actions/index.ts +2 -0
  185. package/templates/ui/components/controls/file-drop/file-drop-collection-control.tsx +137 -0
  186. package/templates/ui/components/controls/file-drop/file-drop-control.test.tsx +189 -0
  187. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +81 -88
  188. package/templates/ui/components/controls/file-drop/file-drop-presentation.tsx +131 -9
  189. package/templates/ui/components/controls/file-drop/file-drop-types.ts +36 -22
  190. package/templates/ui/components/controls/file-drop/index.ts +1 -0
  191. package/templates/ui/components/controls/index.ts +3 -0
  192. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +1 -1
  193. package/templates/ui/components/controls/range-slider/range-slider-value.test.ts +14 -0
  194. package/templates/ui/components/controls/range-slider/range-slider-value.ts +3 -2
  195. package/templates/starter/src/app/acceptance/condition-exclusivity.test.ts +0 -60
  196. package/templates/starter/src/app/acceptance/condition-exclusivity.ts +0 -221
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixel-point/toolcraft",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,6 +2,21 @@ import assert from "node:assert/strict";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
 
5
+ import {
6
+ assertGeneratedExportIntentDocsDoNotInferVideo,
7
+ collectGeneratedAgentFacingDocs,
8
+ getMissingGeneratedPerformanceLifecycleMarkers,
9
+ getStaleAgentPerformanceWorkflowFailures,
10
+ } from "./generate-test-doc-policy-assertions.mjs";
11
+
12
+ export {
13
+ assertGeneratedExportIntentDocsDoNotInferVideo,
14
+ collectGeneratedAgentFacingDocs,
15
+ getMissingGeneratedPerformanceLifecycleMarkers,
16
+ getStaleAgentPerformanceWorkflowFailures,
17
+ staleAnimationDerivedVideoPattern,
18
+ } from "./generate-test-doc-policy-assertions.mjs";
19
+
5
20
  const generatedDocPaths = [
6
21
  "README.md",
7
22
  "workflow.md",
@@ -23,70 +38,6 @@ const generatedDocPaths = [
23
38
  "core/reference-study.md",
24
39
  ];
25
40
 
26
- const staleAgentPerformanceWorkflowPattern =
27
- /app-performance-impact|explicit-performance-work|test:browser:perf|verify:(?:final|quick|ui)|verify:perf:(?:playwright|record-iteration|refresh)|--reason=performance-iteration|--performance-test/iu;
28
-
29
- const generatedPerformanceLifecycleMarkers = [
30
- "<!-- toolcraft-performance-lifecycle: first-delivery=functional; later-delivery=functional-targeted; complaint=one-authority-targeted-performance-iteration; full-audit=explicit-only -->",
31
- "<!-- toolcraft-performance-iteration: authority=exact-request-evidence+canonical-path-ids; fixture=reachable-development; after-pass=return-app-to-user+stop -->",
32
- "<!-- toolcraft-performance-full-authority: automatic=forbidden; recommendation=two-compatible-iterations-or-broad-unlocalizable-problem; command=pnpm verify:perf; authority=explicit-user-request-or-accepted-offer -->",
33
- "<!-- toolcraft-performance-routing: localized=agent-targeted; ambiguous=one-user-facing-choice; broad=offer-targeted-or-full; full=explicit-only -->",
34
- ];
35
-
36
- export function getMissingGeneratedPerformanceLifecycleMarkers(sources) {
37
- return sources.flatMap(({ docPath, source }) =>
38
- generatedPerformanceLifecycleMarkers.flatMap((marker) =>
39
- source.includes(marker)
40
- ? []
41
- : [`docs/toolcraft/${docPath} must include canonical marker: ${marker}`],
42
- ),
43
- );
44
- }
45
-
46
- async function collectMarkdownPaths(rootDir, relativeDir = "") {
47
- const currentDir = path.join(rootDir, relativeDir);
48
- const entries = await fs.readdir(currentDir, { withFileTypes: true });
49
- const markdownPaths = [];
50
-
51
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
52
- const relativePath = path.join(relativeDir, entry.name);
53
- if (entry.isDirectory()) {
54
- markdownPaths.push(...(await collectMarkdownPaths(rootDir, relativePath)));
55
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
56
- markdownPaths.push(relativePath);
57
- }
58
- }
59
-
60
- return markdownPaths;
61
- }
62
-
63
- export async function collectGeneratedAgentFacingDocs(targetDir) {
64
- const docsRoot = path.join(targetDir, "docs/toolcraft");
65
- const docPaths = await collectMarkdownPaths(docsRoot);
66
- const docs = await Promise.all(
67
- docPaths.map(async (docPath) => ({
68
- docPath: path.posix.join("docs/toolcraft", docPath.split(path.sep).join("/")),
69
- source: await fs.readFile(path.join(docsRoot, docPath), "utf8"),
70
- })),
71
- );
72
-
73
- return [
74
- {
75
- docPath: "AGENTS.md",
76
- source: await fs.readFile(path.join(targetDir, "AGENTS.md"), "utf8"),
77
- },
78
- ...docs,
79
- ];
80
- }
81
-
82
- export function getStaleAgentPerformanceWorkflowFailures(sources) {
83
- return sources.flatMap(({ docPath, source }) =>
84
- staleAgentPerformanceWorkflowPattern.test(source)
85
- ? [`${docPath} contains removed performance workflow guidance`]
86
- : [],
87
- );
88
- }
89
-
90
41
  export async function assertGeneratedDocs(targetDir) {
91
42
  await Promise.all(
92
43
  generatedDocPaths.map((docPath) => fs.stat(path.join(targetDir, "docs/toolcraft", docPath))),
@@ -96,8 +47,9 @@ export async function assertGeneratedDocs(targetDir) {
96
47
  const licenseSource = await fs.readFile(path.join(targetDir, "LICENSE.md"), "utf8");
97
48
  const noticeSource = await fs.readFile(path.join(targetDir, "NOTICE.md"), "utf8");
98
49
  const localDocPaths = generatedDocPaths.filter((docPath) => docPath !== "README.md");
50
+ const generatedAgentFacingDocs = await collectGeneratedAgentFacingDocs(targetDir);
99
51
  const staleWorkflowFailures = getStaleAgentPerformanceWorkflowFailures(
100
- await collectGeneratedAgentFacingDocs(targetDir),
52
+ generatedAgentFacingDocs,
101
53
  );
102
54
 
103
55
  assert.match(noticeSource, /Mediabunny/u);
@@ -109,6 +61,7 @@ export async function assertGeneratedDocs(targetDir) {
109
61
  [],
110
62
  `Generated agent-facing docs contain stale performance workflow guidance:\n${staleWorkflowFailures.join("\n")}`,
111
63
  );
64
+ assertGeneratedExportIntentDocsDoNotInferVideo(generatedAgentFacingDocs);
112
65
 
113
66
  assert.match(agentsSource, /Quick Entry Contract/);
114
67
  assert.match(agentsSource, /Local Reference Docs/);
@@ -240,12 +193,44 @@ export async function assertGeneratedDocs(targetDir) {
240
193
  assert.match(coreRuntimeBoundarySource, /does not suppress runtime model layers/);
241
194
  assert.match(componentRulesSource, /Do not show Layers for a single-layer app/);
242
195
  assert.match(coreTimelineSource, /Any app with `Export Video` must enable the top Toolcraft timeline/);
243
- assert.match(coreSetupExportSource, /Every product exposes `Export PNG`/);
196
+ assert.match(coreSetupExportSource, /productReadiness\.exportIntent/);
244
197
  assert.match(
245
198
  coreSetupExportSource,
246
- /Products whose requested delivery includes animation also expose `Export Video`/,
199
+ /image[^\n]*`toolcraft-default`[^\n]*`user-requested`[^\n]*`user-removed`/i,
200
+ );
201
+ assert.match(
202
+ coreSetupExportSource,
203
+ /video[^\n]*`not-requested`[^\n]*`user-requested`/i,
204
+ );
205
+ assert.match(
206
+ coreSetupExportSource,
207
+ /image[^\n]*`user-requested`[^\n]*non-empty[^\n]*evidence/i,
208
+ );
209
+ assert.match(
210
+ coreSetupExportSource,
211
+ /image[^\n]*`user-removed`[^\n]*non-empty[^\n]*evidence/i,
212
+ );
213
+ assert.match(
214
+ coreSetupExportSource,
215
+ /video[^\n]*`user-requested`[^\n]*non-empty[^\n]*evidence/i,
216
+ );
217
+ for (const decisionStep of [
218
+ "Start every product with image export.",
219
+ "Add video export only when the user explicitly requests video delivery.",
220
+ "Do not infer video from animation, playback, keyframes, or timeline.",
221
+ "Keep image with video unless the user explicitly requests removal.",
222
+ "Record in `productReadiness.exportIntent`.",
223
+ ]) {
224
+ assert.equal(
225
+ coreSetupExportSource.split(decisionStep).length - 1,
226
+ 1,
227
+ `Canonical export-intent step must appear exactly once: ${decisionStep}`,
228
+ );
229
+ }
230
+ assert.match(
231
+ coreSetupExportSource,
232
+ /Clipboard copy may be an additional product action, but it never changes or substitutes for the recorded artifact intent/,
247
233
  );
248
- assert.match(coreSetupExportSource, /Clipboard copy is optional and never replaces export/);
249
234
  assert.match(coreSetupExportSource, /renderScaleCoverage/);
250
235
  assert.match(coreSetupExportSource, /selected-backing-pixels/);
251
236
  assert.match(coreSetupExportSource, /canvas-render-scale-backing/);
@@ -271,6 +256,10 @@ export async function assertGeneratedDocs(targetDir) {
271
256
  );
272
257
  assert.match(assemblyDocsSource, /@\/toolcraft\/runtime/);
273
258
  assert.match(assemblyDocsSource, /ToolcraftAppComposition/);
259
+ assert.match(
260
+ assemblyDocsSource,
261
+ /Every product declaration requires `productReadiness\.exportIntent`/,
262
+ );
274
263
  assert.match(assemblyDocsSource, /protected route/);
275
264
  assert.match(assemblyDocsSource, /transferMode: "reference-runtime-clone"/);
276
265
  assert.match(assemblyDocsSource, /`canvasContent` must not contain app UI/);
@@ -284,6 +273,7 @@ export async function assertGeneratedDocs(targetDir) {
284
273
  assert.match(acceptanceDocsSource, /automatedTestName/);
285
274
  assert.match(acceptanceDocsSource, /browserTestName/);
286
275
  assert.match(acceptanceDocsSource, /app-acceptance-data\.ts/);
276
+ assert.match(acceptanceDocsSource, /productReadiness\.exportIntent/);
287
277
  assert.match(
288
278
  acceptanceDocsSource,
289
279
  /Contract docs are signed except product-owned `agent-worklog\.md` and optional `workflow-observation\.md`; only the observation stays outside `sourceHash`/,
@@ -5,11 +5,54 @@ import path from "node:path";
5
5
  import test from "node:test";
6
6
 
7
7
  import {
8
+ assertGeneratedExportIntentDocsDoNotInferVideo,
8
9
  collectGeneratedAgentFacingDocs,
9
10
  getMissingGeneratedPerformanceLifecycleMarkers,
10
11
  getStaleAgentPerformanceWorkflowFailures,
11
12
  } from "./generate-test-doc-assertions.mjs";
12
13
 
14
+ test("rejects animation-derived video rules without rejecting explicit authority", () => {
15
+ for (const source of [
16
+ [
17
+ "Products whose requested delivery includes",
18
+ "animation also expose",
19
+ "`Export Video`.",
20
+ ].join(" "),
21
+ ["Animated apps automatically show", "Export Video."].join(" "),
22
+ ["Animation also", "requires", "Export Video."].join(" "),
23
+ "Timeline playback enables Export Video.",
24
+ "Animated apps automatically expose video export.",
25
+ "Timeline playback enables video export.",
26
+ "Animation implies video delivery.",
27
+ ]) {
28
+ assert.throws(
29
+ () =>
30
+ assertGeneratedExportIntentDocsDoNotInferVideo([
31
+ { docPath: "docs/toolcraft/stale.md", source },
32
+ ]),
33
+ /docs\/toolcraft\/stale\.md contains animation-derived video export guidance/,
34
+ );
35
+ }
36
+
37
+ assert.doesNotThrow(() =>
38
+ assertGeneratedExportIntentDocsDoNotInferVideo([
39
+ {
40
+ docPath: "docs/toolcraft/allowed.md",
41
+ source: [
42
+ "Do not infer video from animation, playback, keyframes, or timeline.",
43
+ "Animation, playback, keyframes, and timeline presence never add Export Video.",
44
+ "Animated apps do not automatically expose video export.",
45
+ "Timeline playback never enables video export.",
46
+ "Animation does not imply video delivery.",
47
+ "An animated app includes Export Video because the user explicitly requested video delivery.",
48
+ "An animated app exposes video export because the user explicitly requested video delivery.",
49
+ "Any app with Export Video must enable the top Toolcraft timeline.",
50
+ ].join("\n"),
51
+ },
52
+ ]),
53
+ );
54
+ });
55
+
13
56
  test("scans every generated agent-facing Markdown source independently", async (t) => {
14
57
  const targetDir = await fs.mkdtemp(path.join(os.tmpdir(), "toolcraft-doc-scan-"));
15
58
  t.after(() => fs.rm(targetDir, { force: true, recursive: true }));
@@ -0,0 +1,92 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const staleAgentPerformanceWorkflowPattern =
6
+ /app-performance-impact|explicit-performance-work|test:browser:perf|verify:(?:final|quick|ui)|verify:perf:(?:playwright|record-iteration|refresh)|--reason=performance-iteration|--performance-test/iu;
7
+
8
+ const exportIntentSentencePatternSource = String.raw`[^\n.!?]`;
9
+ const videoArtifactTargetPatternSource = String.raw`(?:Export Video|video\s+(?:export|delivery))`;
10
+ const explicitVideoAuthorityPatternSource = String.raw`(?:explicit(?:ly)?\s+(?:(?:user[- ]?)?request(?:ed|s)?\s+)?${videoArtifactTargetPatternSource}|user\s+explicitly\s+request(?:ed|s)?\s+${videoArtifactTargetPatternSource}|user[- ]request(?:ed|s)?\s+${videoArtifactTargetPatternSource})`;
11
+ const animationInferenceVerbPatternSource = String.raw`(?:exposes?|includes?|shows?|adds?|enables?|implies?|has|needs?|requires?|produces?|proves?)`;
12
+ const staleAnimationInferencePatternSource = [
13
+ String.raw`products?\s+whose\s+requested\s+delivery\s+includes\s+animation`,
14
+ String.raw`animated\s+apps?\b(?!${exportIntentSentencePatternSource}{0,100}\b(?:do(?:es)?\s+not|never)\b)${exportIntentSentencePatternSource}{0,100}\b${animationInferenceVerbPatternSource}\b${exportIntentSentencePatternSource}{0,60}\b${videoArtifactTargetPatternSource}\b`,
15
+ String.raw`(?:animation|playback|keyframes?|timeline(?:\s+playback)?)\b\s+(?:also\s+)?${animationInferenceVerbPatternSource}\b${exportIntentSentencePatternSource}{0,60}\b${videoArtifactTargetPatternSource}\b`,
16
+ ].join("|");
17
+
18
+ export const staleAnimationDerivedVideoPattern = new RegExp(
19
+ String.raw`(?:^|[\n.!?]\s*)(?!${exportIntentSentencePatternSource}{0,240}\b${explicitVideoAuthorityPatternSource}\b)${exportIntentSentencePatternSource}{0,240}(?:${staleAnimationInferencePatternSource})`,
20
+ "iu",
21
+ );
22
+
23
+ const generatedPerformanceLifecycleMarkers = [
24
+ "<!-- toolcraft-performance-lifecycle: first-delivery=functional; later-delivery=functional-targeted; complaint=one-authority-targeted-performance-iteration; full-audit=explicit-only -->",
25
+ "<!-- toolcraft-performance-iteration: authority=exact-request-evidence+canonical-path-ids; fixture=reachable-development; after-pass=return-app-to-user+stop -->",
26
+ "<!-- toolcraft-performance-full-authority: automatic=forbidden; recommendation=two-compatible-iterations-or-broad-unlocalizable-problem; command=pnpm verify:perf; authority=explicit-user-request-or-accepted-offer -->",
27
+ "<!-- toolcraft-performance-routing: localized=agent-targeted; ambiguous=one-user-facing-choice; broad=offer-targeted-or-full; full=explicit-only -->",
28
+ ];
29
+
30
+ export function getMissingGeneratedPerformanceLifecycleMarkers(sources) {
31
+ return sources.flatMap(({ docPath, source }) =>
32
+ generatedPerformanceLifecycleMarkers.flatMap((marker) =>
33
+ source.includes(marker)
34
+ ? []
35
+ : [`docs/toolcraft/${docPath} must include canonical marker: ${marker}`],
36
+ ),
37
+ );
38
+ }
39
+
40
+ async function collectMarkdownPaths(rootDir, relativeDir = "") {
41
+ const currentDir = path.join(rootDir, relativeDir);
42
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
43
+ const markdownPaths = [];
44
+
45
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
46
+ const relativePath = path.join(relativeDir, entry.name);
47
+ if (entry.isDirectory()) {
48
+ markdownPaths.push(...(await collectMarkdownPaths(rootDir, relativePath)));
49
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
50
+ markdownPaths.push(relativePath);
51
+ }
52
+ }
53
+
54
+ return markdownPaths;
55
+ }
56
+
57
+ export async function collectGeneratedAgentFacingDocs(targetDir) {
58
+ const docsRoot = path.join(targetDir, "docs/toolcraft");
59
+ const docPaths = await collectMarkdownPaths(docsRoot);
60
+ const docs = await Promise.all(
61
+ docPaths.map(async (docPath) => ({
62
+ docPath: path.posix.join("docs/toolcraft", docPath.split(path.sep).join("/")),
63
+ source: await fs.readFile(path.join(docsRoot, docPath), "utf8"),
64
+ })),
65
+ );
66
+
67
+ return [
68
+ {
69
+ docPath: "AGENTS.md",
70
+ source: await fs.readFile(path.join(targetDir, "AGENTS.md"), "utf8"),
71
+ },
72
+ ...docs,
73
+ ];
74
+ }
75
+
76
+ export function getStaleAgentPerformanceWorkflowFailures(sources) {
77
+ return sources.flatMap(({ docPath, source }) =>
78
+ staleAgentPerformanceWorkflowPattern.test(source)
79
+ ? [`${docPath} contains removed performance workflow guidance`]
80
+ : [],
81
+ );
82
+ }
83
+
84
+ export function assertGeneratedExportIntentDocsDoNotInferVideo(sources) {
85
+ for (const { docPath, source } of sources) {
86
+ assert.doesNotMatch(
87
+ source,
88
+ staleAnimationDerivedVideoPattern,
89
+ `${docPath} contains animation-derived video export guidance`,
90
+ );
91
+ }
92
+ }
@@ -8,7 +8,7 @@ import {
8
8
  } from "./generate-test-delivery-architecture-assertions.mjs";
9
9
  import { listRelativeFiles, repoRoot } from "./generate-test-fs-utils.mjs";
10
10
  import { assertGeneratedPerformanceCanvasBoundary } from "./generate-test-performance-boundary-assertions.mjs";
11
- import { isGeneratedTestSupportPath } from "./generated-source-policy.mjs";
11
+ import { assertGeneratedRuntimePublicBoundary } from "./generate-test-runtime-boundary-assertions.mjs";
12
12
 
13
13
  export {
14
14
  assertGeneratedRoutesAndStyles,
@@ -44,16 +44,7 @@ export async function assertGeneratedFiles(targetDir) {
44
44
  "scripts/toolcraft-targeted-verification-selection.mjs",
45
45
  ];
46
46
 
47
- assert.ok(await fs.stat(path.join(targetDir, "src/toolcraft/ui/index.ts")));
48
- assert.ok(await fs.stat(path.join(targetDir, "src/toolcraft/runtime/react/index.ts")));
49
- assert.ok(
50
- await fs.stat(
51
- path.join(
52
- targetDir,
53
- "src/toolcraft/runtime/model-import/worker/model-import.worker.bundle.js",
54
- ),
55
- ),
56
- );
47
+ await assertGeneratedRuntimePublicBoundary(targetDir);
57
48
  assert.ok(await fs.stat(path.join(targetDir, "playwright.config.ts")));
58
49
  await assert.rejects(
59
50
  fs.stat(path.join(targetDir, ".toolcraft")),
@@ -61,70 +52,6 @@ export async function assertGeneratedFiles(targetDir) {
61
52
  "Fresh generated apps must not inherit starter server ports or verification receipts.",
62
53
  );
63
54
 
64
- const generatedToolcraftFiles = await listRelativeFiles(
65
- path.join(targetDir, "src/toolcraft"),
66
- );
67
- const expectedRuntimeFiles = (await listRelativeFiles(
68
- path.join(repoRoot, "packages/toolcraft-runtime/src"),
69
- ))
70
- .filter((relativePath) => !isGeneratedTestSupportPath(relativePath))
71
- .map((relativePath) => `runtime/${relativePath}`);
72
- assert.deepEqual(
73
- generatedToolcraftFiles.filter((relativePath) => relativePath.startsWith("runtime/")),
74
- expectedRuntimeFiles,
75
- "Generated standalone runtime must contain every production runtime module.",
76
- );
77
- for (const relativePath of [
78
- "runtime/export/artifact-export-settings.ts",
79
- "runtime/export/artifact-export-request.ts",
80
- "runtime/export/artifact-frame-renderer.ts",
81
- "runtime/export/artifact-frame-state.ts",
82
- "runtime/export/artifact-scene-frame.ts",
83
- "runtime/export/image-artifact-export.ts",
84
- "runtime/export/product-export-renderer.ts",
85
- "runtime/export/video-artifact-export.ts",
86
- "runtime/export/video-encoding-backend.ts",
87
- "runtime/export/video-frame-schedule.ts",
88
- ]) {
89
- assert.ok(
90
- generatedToolcraftFiles.includes(relativePath),
91
- `Generated runtime must contain ${relativePath}.`,
92
- );
93
- }
94
- assert.equal(
95
- generatedToolcraftFiles.includes("runtime/export/export.ts"),
96
- false,
97
- "Generated runtime must not copy the removed legacy export helper.",
98
- );
99
- for (const relativePath of [
100
- "e2e/browser-media-export-evidence.ts",
101
- "e2e/decoded-pixel-observation.ts",
102
- "e2e/image-artifact-inspection.ts",
103
- "e2e/video-artifact-inspection.ts",
104
- ]) {
105
- assert.ok(
106
- await fs.stat(path.join(targetDir, relativePath)),
107
- `Generated output must contain ${relativePath}.`,
108
- );
109
- }
110
- for (const relativePath of generatedToolcraftFiles) {
111
- assert.equal(
112
- isGeneratedTestSupportPath(relativePath),
113
- false,
114
- `Generated runtime contains test support: ${relativePath}`,
115
- );
116
- }
117
-
118
- const removedRuntimeTestHelpers = [
119
- "runtime/react/canvas/canvas-shell-test-utils.tsx",
120
- "runtime/react/controls-panel/testing/controls-panel-test-utils.tsx",
121
- "runtime/react/layers/layers-panel-test-utils.tsx",
122
- "runtime/react/timeline/timeline-panel-test-utils.tsx",
123
- ];
124
- for (const relativePath of removedRuntimeTestHelpers) {
125
- assert.equal(generatedToolcraftFiles.includes(relativePath), false);
126
- }
127
-
128
55
  const playwrightConfigSource = await fs.readFile(
129
56
  path.join(targetDir, "playwright.config.ts"),
130
57
  "utf8",
@@ -144,11 +71,35 @@ export async function assertGeneratedFiles(targetDir) {
144
71
  assert.ok(await fs.stat(path.join(targetDir, "src/app/app-acceptance-data.ts")));
145
72
  assert.ok(await fs.stat(path.join(targetDir, "src/app/app-composition.tsx")));
146
73
  assert.ok(await fs.stat(path.join(targetDir, "src/app/app-acceptance.base-coverage.test.ts")));
74
+ assert.ok(
75
+ await fs.stat(
76
+ path.join(
77
+ targetDir,
78
+ "src/app/acceptance/control-section-entity-cohesion.ts",
79
+ ),
80
+ ),
81
+ );
82
+ const generatedApplicabilityValidatorSource = await fs.readFile(
83
+ path.join(targetDir, "src/app/acceptance/control-applicability.ts"),
84
+ "utf8",
85
+ );
86
+ assert.match(
87
+ generatedApplicabilityValidatorSource,
88
+ /getToolcraftControlApplicabilityErrors/,
89
+ );
147
90
  const appAcceptanceSource = await fs.readFile(
148
91
  path.join(targetDir, "src/app/app-acceptance.ts"),
149
92
  "utf8",
150
93
  );
151
94
  assert.match(appAcceptanceSource, /from "\.\/app-acceptance-data"/);
95
+ const generatedValidationPipeline = await fs.readFile(
96
+ path.join(targetDir, "src/app/acceptance/validate-coverage.ts"),
97
+ "utf8",
98
+ );
99
+ assert.match(
100
+ generatedValidationPipeline,
101
+ /validate:\s*\(\{ sectionInventory \}\)\s*=>\s*getToolcraftControlSectionEntityCohesionErrors\(sectionInventory\)/,
102
+ );
152
103
  assert.ok(await fs.stat(path.join(targetDir, "src/app/app-performance.ts")));
153
104
  assert.ok(
154
105
  await fs.stat(path.join(targetDir, "src/app/app-verification-impact.json")),
@@ -11,6 +11,11 @@ import {
11
11
  generatedProductMaterialIterationFixturePaths,
12
12
  } from "./generate-test-product-iteration.mjs";
13
13
 
14
+ const materialIterationFixtureRoot = new URL(
15
+ "../fixtures/generated-product-material-iteration/",
16
+ import.meta.url,
17
+ );
18
+
14
19
  const expectedPaths = [
15
20
  "e2e/product-material.spec.ts",
16
21
  "public/material-chocolate.svg",
@@ -83,6 +88,18 @@ it("keeps the material iteration overlay exact and immutable", () => {
83
88
  );
84
89
  });
85
90
 
91
+ it("keeps the material iteration video request explicit", async () => {
92
+ const acceptanceSource = await fs.readFile(
93
+ new URL("src/app/app-acceptance-data.ts", materialIterationFixtureRoot),
94
+ "utf8",
95
+ );
96
+
97
+ assert.match(
98
+ acceptanceSource,
99
+ /video:\s*\{\s*evidence:\s*"The generated export validation fixture explicitly requests one-second video export coverage\.",\s*mode: "user-requested",\s*\}/u,
100
+ );
101
+ });
102
+
86
103
  it("atomically applies only the material product overlay", async () => {
87
104
  const targetDir = await fs.mkdtemp(
88
105
  path.join(os.tmpdir(), "toolcraft-material-iteration-target-"),
@@ -0,0 +1,158 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import { listRelativeFiles, repoRoot } from "./generate-test-fs-utils.mjs";
6
+ import { isGeneratedTestSupportPath } from "./generated-source-policy.mjs";
7
+
8
+ const productSafeRuntimeExportIndex = [
9
+ 'export {',
10
+ ' toolcraftImageExportFormatTarget,',
11
+ ' toolcraftImageExportResolutionTarget,',
12
+ ' toolcraftVideoExportFormatTarget,',
13
+ ' toolcraftVideoExportResolutionTarget,',
14
+ '} from "./artifact-export-settings";',
15
+ 'export type {',
16
+ ' ToolcraftImageExportFormat,',
17
+ ' ToolcraftImageExportPresetResolution,',
18
+ ' ToolcraftResolvedImageExportSettings,',
19
+ ' ToolcraftResolvedVideoExportSettings,',
20
+ ' ToolcraftVideoExportFormat,',
21
+ ' ToolcraftVideoExportPresetResolution,',
22
+ '} from "./artifact-export-settings";',
23
+ 'export { shouldIncludeToolcraftPreviewBackground } from "./export-background";',
24
+ 'export type { ToolcraftPreviewBackgroundOptions } from "./export-background";',
25
+ 'export {',
26
+ ' TOOLCRAFT_MAX_EXPORT_EDGE_PX,',
27
+ ' TOOLCRAFT_MAX_EXPORT_PIXELS,',
28
+ ' validateToolcraftArtifactSize,',
29
+ '} from "./export-frame";',
30
+ 'export type {',
31
+ ' ToolcraftArtifactSize,',
32
+ ' ToolcraftExportFrame,',
33
+ '} from "./export-frame";',
34
+ 'export {',
35
+ ' getToolcraftImageExportSize,',
36
+ ' getToolcraftRetinaExportPixelRatio,',
37
+ ' getToolcraftRetinaExportSize,',
38
+ ' getToolcraftVideoExportSize,',
39
+ '} from "./export-sizing";',
40
+ 'export type {',
41
+ ' ToolcraftExportSizeOptions,',
42
+ ' ToolcraftImageExportResolution,',
43
+ ' ToolcraftImageExportSizeOptions,',
44
+ ' ToolcraftRetinaExportSize,',
45
+ ' ToolcraftVideoExportResolution,',
46
+ ' ToolcraftVideoExportSizeOptions,',
47
+ '} from "./export-sizing";',
48
+ 'export type {',
49
+ ' ToolcraftProductExportFrameContext,',
50
+ ' ToolcraftProductExportFrameRenderer,',
51
+ ' ToolcraftProductExportRenderer,',
52
+ '} from "./product-export-renderer";',
53
+ '',
54
+ ].join("\n");
55
+
56
+ const requiredRuntimeExportImplementationPaths = [
57
+ "runtime/export/artifact-export-settings.ts",
58
+ "runtime/export/artifact-export-request.ts",
59
+ "runtime/export/artifact-frame-renderer.ts",
60
+ "runtime/export/artifact-frame-state.ts",
61
+ "runtime/export/artifact-scene-frame.ts",
62
+ "runtime/export/image-artifact-export.ts",
63
+ "runtime/export/product-export-renderer.ts",
64
+ "runtime/export/video-artifact-export.ts",
65
+ "runtime/export/video-encoding-backend.ts",
66
+ "runtime/export/video-frame-schedule.ts",
67
+ ];
68
+
69
+ const generatedMediaExportEvidencePaths = [
70
+ "e2e/browser-media-export-evidence.ts",
71
+ "e2e/decoded-pixel-observation.ts",
72
+ "e2e/image-artifact-inspection.ts",
73
+ "e2e/video-artifact-inspection.ts",
74
+ ];
75
+
76
+ const removedRuntimeTestHelperPaths = [
77
+ "runtime/react/canvas/canvas-shell-test-utils.tsx",
78
+ "runtime/react/controls-panel/testing/controls-panel-test-utils.tsx",
79
+ "runtime/react/layers/layers-panel-test-utils.tsx",
80
+ "runtime/react/timeline/timeline-panel-test-utils.tsx",
81
+ ];
82
+
83
+ export async function assertGeneratedRuntimePublicBoundary(targetDir) {
84
+ assert.ok(await fs.stat(path.join(targetDir, "src/toolcraft/ui/index.ts")));
85
+ assert.ok(await fs.stat(path.join(targetDir, "src/toolcraft/runtime/react/index.ts")));
86
+
87
+ const generatedRuntimeRootSource = await fs.readFile(
88
+ path.join(targetDir, "src/toolcraft/runtime/index.ts"),
89
+ "utf8",
90
+ );
91
+ assert.match(generatedRuntimeRootSource, /export \* from "\.\/export";/u);
92
+ assert.doesNotMatch(
93
+ generatedRuntimeRootSource,
94
+ /exportToolcraft(?:Image|Video)Artifact|createToolcraftVideoEncoderBackend/u,
95
+ );
96
+
97
+ const generatedRuntimeExportIndexSource = await fs.readFile(
98
+ path.join(targetDir, "src/toolcraft/runtime/export/index.ts"),
99
+ "utf8",
100
+ );
101
+ assert.equal(
102
+ generatedRuntimeExportIndexSource,
103
+ productSafeRuntimeExportIndex,
104
+ "Generated runtime export index must expose the exact product-safe allowlist.",
105
+ );
106
+
107
+ assert.ok(
108
+ await fs.stat(
109
+ path.join(
110
+ targetDir,
111
+ "src/toolcraft/runtime/model-import/worker/model-import.worker.bundle.js",
112
+ ),
113
+ ),
114
+ );
115
+
116
+ const generatedToolcraftFiles = await listRelativeFiles(
117
+ path.join(targetDir, "src/toolcraft"),
118
+ );
119
+ const expectedRuntimeFiles = (await listRelativeFiles(
120
+ path.join(repoRoot, "packages/toolcraft-runtime/src"),
121
+ ))
122
+ .filter((relativePath) => !isGeneratedTestSupportPath(relativePath))
123
+ .map((relativePath) => `runtime/${relativePath}`);
124
+ assert.deepEqual(
125
+ generatedToolcraftFiles.filter((relativePath) => relativePath.startsWith("runtime/")),
126
+ expectedRuntimeFiles,
127
+ "Generated standalone runtime must contain every production runtime module.",
128
+ );
129
+
130
+ for (const relativePath of requiredRuntimeExportImplementationPaths) {
131
+ assert.ok(
132
+ generatedToolcraftFiles.includes(relativePath),
133
+ `Generated runtime must contain ${relativePath}.`,
134
+ );
135
+ }
136
+ assert.equal(
137
+ generatedToolcraftFiles.includes("runtime/export/export.ts"),
138
+ false,
139
+ "Generated runtime must not copy the removed legacy export helper.",
140
+ );
141
+
142
+ for (const relativePath of generatedMediaExportEvidencePaths) {
143
+ assert.ok(
144
+ await fs.stat(path.join(targetDir, relativePath)),
145
+ `Generated output must contain ${relativePath}.`,
146
+ );
147
+ }
148
+ for (const relativePath of generatedToolcraftFiles) {
149
+ assert.equal(
150
+ isGeneratedTestSupportPath(relativePath),
151
+ false,
152
+ `Generated runtime contains test support: ${relativePath}`,
153
+ );
154
+ }
155
+ for (const relativePath of removedRuntimeTestHelperPaths) {
156
+ assert.equal(generatedToolcraftFiles.includes(relativePath), false);
157
+ }
158
+ }
@@ -87,6 +87,16 @@ export async function assertGeneratedProductStandalone({ execFileAsync, targetDi
87
87
  await writeGeneratedProductFixture(targetDir);
88
88
  await assertGeneratedProductWorklog(targetDir);
89
89
 
90
+ const appSchemaSource = await fs.readFile(
91
+ path.join(targetDir, "src/app/app-schema.ts"),
92
+ "utf8",
93
+ );
94
+ assert.match(
95
+ appSchemaSource,
96
+ /applicability:\s*\{\s*mode:\s*"always"\s*\}/u,
97
+ );
98
+ assert.doesNotMatch(appSchemaSource, /visibleWhen\s*:/u);
99
+
90
100
  const verificationImpact = JSON.parse(
91
101
  await fs.readFile(
92
102
  path.join(targetDir, "src/app/app-verification-impact.json"),