@pixel-point/toolcraft 0.0.16 → 0.0.17

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 (170) hide show
  1. package/package.json +1 -1
  2. package/src/generate-performance-iteration-execution.test.mjs +2 -2
  3. package/src/generate-test-demand-only-lifecycle.mjs +14 -3
  4. package/src/generate-test-dependency-sandbox.mjs +42 -5
  5. package/src/generate-test-doc-assertions.mjs +68 -18
  6. package/src/generate-test-doc-assertions.test.mjs +22 -0
  7. package/src/generate-test-file-assertions.mjs +43 -76
  8. package/src/generate-test-output-assertions.mjs +95 -0
  9. package/src/generate-test-package-assertions.mjs +6 -0
  10. package/src/generate-test-performance-boundary-assertions.mjs +45 -0
  11. package/src/generate-test-product-fixture.mjs +1 -0
  12. package/src/generate-test-product-fixture.test.mjs +18 -0
  13. package/src/generate-test-product-iteration.mjs +1 -0
  14. package/src/generate-test-product-iteration.test.mjs +1 -0
  15. package/src/generate-test-standalone-delivery-stages.mjs +14 -6
  16. package/src/generate.test.mjs +14 -0
  17. package/templates/runtime/contracts/component-contracts.choices.test.ts +12 -9
  18. package/templates/runtime/contracts/component-contracts.choices.ts +10 -9
  19. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +17 -2
  20. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +1 -1
  21. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +1 -1
  22. package/templates/runtime/contracts/component-contracts.runtime.ts +3 -2
  23. package/templates/runtime/contracts/decision-contracts.test.ts +25 -4
  24. package/templates/runtime/contracts/decision-contracts.ts +3 -3
  25. package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
  26. package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
  27. package/templates/runtime/export/artifact-download.test.ts +71 -0
  28. package/templates/runtime/export/artifact-download.ts +80 -0
  29. package/templates/runtime/export/artifact-export-request.ts +23 -0
  30. package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
  31. package/templates/runtime/export/artifact-export-settings.ts +96 -0
  32. package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
  33. package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
  34. package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
  35. package/templates/runtime/export/artifact-frame-state.ts +44 -0
  36. package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
  37. package/templates/runtime/export/artifact-scene-frame.ts +118 -0
  38. package/templates/runtime/export/export-background.ts +65 -0
  39. package/templates/runtime/export/export-error.ts +43 -0
  40. package/templates/runtime/export/export.test.ts +6 -186
  41. package/templates/runtime/export/image-artifact-export.test.ts +111 -0
  42. package/templates/runtime/export/image-artifact-export.ts +145 -0
  43. package/templates/runtime/export/index.ts +14 -1
  44. package/templates/runtime/export/product-export-renderer.test.ts +77 -0
  45. package/templates/runtime/export/product-export-renderer.ts +63 -0
  46. package/templates/runtime/export/video-artifact-export.test.ts +166 -0
  47. package/templates/runtime/export/video-artifact-export.ts +164 -0
  48. package/templates/runtime/export/video-encoding-backend.ts +124 -0
  49. package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
  50. package/templates/runtime/export/video-encoding-policy.ts +86 -0
  51. package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
  52. package/templates/runtime/export/video-frame-schedule.ts +42 -0
  53. package/templates/runtime/index.ts +1 -0
  54. package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
  55. package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
  56. package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +4 -303
  57. package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +12 -27
  58. package/templates/runtime/react/app-shell/toolcraft-app.tsx +22 -4
  59. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
  60. package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
  61. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
  62. package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
  63. package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
  64. package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
  65. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
  66. package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
  67. package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
  68. package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +17 -83
  69. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +42 -84
  70. package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +62 -0
  71. package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
  72. package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
  73. package/templates/runtime/scene/scene-bounds.ts +1 -2
  74. package/templates/runtime/schema/canvas-render-scale.ts +62 -0
  75. package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
  76. package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +3 -3
  77. package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
  78. package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
  79. package/templates/runtime/schema/define-toolcraft.ts +1 -1
  80. package/templates/runtime/schema/schema-resolvers.ts +0 -56
  81. package/templates/runtime/schema/types.ts +4 -4
  82. package/templates/runtime/state/canvas-background-state.ts +6 -0
  83. package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
  84. package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
  85. package/templates/starter/AGENTS.md +8 -7
  86. package/templates/starter/NOTICE.md +6 -0
  87. package/templates/starter/docs/toolcraft/README.md +2 -1
  88. package/templates/starter/docs/toolcraft/acceptance-testing.md +8 -6
  89. package/templates/starter/docs/toolcraft/agent-worklog.md +3 -3
  90. package/templates/starter/docs/toolcraft/assembly-workflow.md +4 -3
  91. package/templates/starter/docs/toolcraft/core/media-upload.md +2 -2
  92. package/templates/starter/docs/toolcraft/core/performance.md +14 -3
  93. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +3 -1
  94. package/templates/starter/docs/toolcraft/core/setup-export.md +16 -12
  95. package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
  96. package/templates/starter/docs/toolcraft/performance.md +20 -6
  97. package/templates/starter/docs/toolcraft/renderer-technique.md +1 -1
  98. package/templates/starter/docs/toolcraft/schema-reference.md +2 -6
  99. package/templates/starter/docs/toolcraft/workflow.md +7 -2
  100. package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
  101. package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +43 -1
  102. package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
  103. package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
  104. package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
  105. package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
  106. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +4 -1
  107. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +0 -1
  108. package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
  109. package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
  110. package/templates/starter/e2e/app-performance.spec.ts +16 -9
  111. package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +9 -330
  112. package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
  113. package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
  114. package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
  115. package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
  116. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +4 -1
  117. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +43 -9
  118. package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
  119. package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
  120. package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
  121. package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
  122. package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
  123. package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
  124. package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
  125. package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
  126. package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
  127. package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
  128. package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
  129. package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
  130. package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
  131. package/templates/starter/e2e/performance-path-helpers.ts +210 -91
  132. package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
  133. package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
  134. package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
  135. package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
  136. package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
  137. package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
  138. package/templates/starter/gitignore +1 -0
  139. package/templates/starter/package.json +1 -0
  140. package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
  141. package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
  142. package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
  143. package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
  144. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +24 -2
  145. package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
  146. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +46 -0
  147. package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
  148. package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
  149. package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +168 -0
  150. package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
  151. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
  152. package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +155 -206
  153. package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
  154. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
  155. package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
  156. package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
  157. package/templates/starter/src/app/acceptance/output-background-rules.ts +1 -1
  158. package/templates/starter/src/app/acceptance/output-export.ts +13 -2
  159. package/templates/starter/src/app/acceptance/types.ts +7 -0
  160. package/templates/starter/src/app/acceptance/validate-coverage.ts +2 -1
  161. package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
  162. package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
  163. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +42 -1
  164. package/templates/starter/src/app/starter-acceptance.ts +2 -0
  165. package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
  166. package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
  167. package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
  168. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -0
  169. package/templates/starter/src/routes/index.tsx +1 -0
  170. package/templates/runtime/export/export.ts +0 -166
@@ -1,180 +1,25 @@
1
1
  import assert from "node:assert/strict";
2
- import fs from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
2
  import test from "node:test";
6
- import { pathToFileURL } from "node:url";
7
3
 
8
- import ts from "typescript";
4
+ import {
5
+ createArtifactAction,
6
+ createSession,
7
+ executeClosurelessObservation,
8
+ getHarnessState,
9
+ loadUnavailableResourceFacade,
10
+ resetHarness,
11
+ setActivationError,
12
+ setCleanupFailure,
13
+ setMalformedActivation,
14
+ } from "./toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs";
9
15
 
10
- const starterRoot = path.resolve(import.meta.dirname, "..");
11
- const helperPath = path.join(
12
- starterRoot,
13
- "e2e/browser-infinity-canvas-evidence.ts",
14
- );
15
-
16
- async function loadUnavailableResourceFacade(context) {
17
- const outputDir = await fs.mkdtemp(
18
- path.join(os.tmpdir(), "toolcraft-unavailable-resource-evidence-"),
16
+ test("closureless observation harness rejects browser callbacks with outer references", () => {
17
+ const readAttribute = (root) => root.getAttribute("data-value");
18
+ assert.throws(
19
+ () => executeClosurelessObservation((root) => readAttribute(root), {}),
20
+ /readAttribute is not defined/u,
19
21
  );
20
- context.after(() => fs.rm(outputDir, { force: true, recursive: true }));
21
- const outputPath = path.join(outputDir, "browser-infinity-canvas-evidence.mjs");
22
- const source = (await fs.readFile(helperPath, "utf8"))
23
- .replaceAll('"@playwright/test"', '"./playwright.mjs"')
24
- .replaceAll('"./browser-runtime-evidence"', '"./evidence.mjs"')
25
- .replaceAll('"./browser-proof-session"', '"./proof-session.mjs"')
26
- .replaceAll('"./export-artifact-helpers"', '"./artifact.mjs"');
27
- const { outputText } = ts.transpileModule(source, {
28
- compilerOptions: {
29
- module: ts.ModuleKind.ESNext,
30
- target: ts.ScriptTarget.ES2022,
31
- },
32
- fileName: helperPath,
33
- });
34
-
35
- await Promise.all([
36
- fs.writeFile(outputPath, outputText),
37
- fs.writeFile(
38
- path.join(outputDir, "playwright.mjs"),
39
- `
40
- import assert from "node:assert/strict";
41
- function partial(actual, expected, message) {
42
- for (const [key, value] of Object.entries(expected)) {
43
- assert.deepEqual(actual?.[key], value, message);
44
- }
45
- }
46
- export function expect(actual, message = "Expectation failed") {
47
- return {
48
- not: {
49
- toBe(expected) {
50
- assert.notEqual(actual, expected, message);
51
- },
52
- toEqual(expected) {
53
- assert.notDeepEqual(actual, expected, message);
54
- },
55
- },
56
- toBe(expected) {
57
- assert.equal(actual, expected, message);
58
- },
59
- toBeTruthy() {
60
- assert.ok(actual, message);
61
- },
62
- toEqual(expected) {
63
- assert.deepEqual(actual, expected, message);
64
- },
65
- toMatchObject(expected) {
66
- if (expected?.status === "cleaned") {
67
- globalThis.__toolcraftUnavailableEvidenceTrace.push(
68
- "assert:cleaned",
69
- );
70
- }
71
- partial(actual, expected, message);
72
- },
73
- };
74
- }
75
- `,
76
- ),
77
- fs.writeFile(
78
- path.join(outputDir, "evidence.mjs"),
79
- `
80
- export async function attachToolcraftBrowserRuntimeEvidence(evidence) {
81
- globalThis.__toolcraftUnavailableEvidenceTrace.push(
82
- "evidence:" + evidence.evidenceType,
83
- );
84
- globalThis.__toolcraftUnavailableEvidenceAttachments.push(evidence);
85
- }
86
- `,
87
- ),
88
- fs.writeFile(
89
- path.join(outputDir, "proof-session.mjs"),
90
- `
91
- export function assertToolcraftBrowserProofSession() {
92
- globalThis.__toolcraftUnavailableEvidenceSessionAssertions += 1;
93
- }
94
- export function getToolcraftBrowserActionTarget(action) {
95
- return action.target;
96
- }
97
- export async function readToolcraftBrowserObservation() {
98
- return globalThis.__toolcraftUnavailableEvidenceObservation;
99
- }
100
- export async function runToolcraftBrowserAction() {
101
- globalThis.__toolcraftUnavailableEvidenceActionCalls += 1;
102
- const actionName =
103
- globalThis.__toolcraftUnavailableEvidenceActionCalls === 1
104
- ? "activate"
105
- : "cleanup";
106
- globalThis.__toolcraftUnavailableEvidenceTrace.push(
107
- "action:" + actionName,
108
- );
109
- if (
110
- actionName === "cleanup" &&
111
- globalThis.__toolcraftUnavailableEvidenceCleanupFailure
112
- ) {
113
- throw new Error("cleanup failed");
114
- }
115
- globalThis.__toolcraftUnavailableEvidenceObservation =
116
- globalThis.__toolcraftUnavailableEvidenceActionCalls === 1
117
- ? {
118
- assetId: "image-2",
119
- fileName: "unavailable.png",
120
- lifecycle: "unavailable",
121
- requestId: "unavailable-image-1",
122
- resolverAvailable: false,
123
- status: "active",
124
- }
125
- : {
126
- assetId: "image-2",
127
- fileName: "unavailable.png",
128
- lifecycle: "ready",
129
- requestId: "unavailable-image-1",
130
- resolverAvailable: true,
131
- status: "cleaned",
132
- };
133
- }
134
- export async function runToolcraftBrowserValueAction(action) {
135
- globalThis.__toolcraftUnavailableEvidenceTrace.push("artifact");
136
- return action.result;
137
- }
138
- `,
139
- ),
140
- fs.writeFile(
141
- path.join(outputDir, "artifact.mjs"),
142
- `
143
- export function assertToolcraftProducedArtifact(artifact) {
144
- if (!artifact) throw new Error("missing artifact");
145
- }
146
- export function validateToolcraftExportArtifactInspection(inspection) {
147
- globalThis.__toolcraftUnavailableEvidenceArtifactValidations += 1;
148
- if (!inspection?.byteLength) throw new Error("invalid inspection");
149
- return inspection;
150
- }
151
- `,
152
- ),
153
- ]);
154
-
155
- return import(`${pathToFileURL(outputPath).href}?test=${Date.now()}`);
156
- }
157
-
158
- function createSession() {
159
- return {
160
- observe(read) {
161
- return { read };
162
- },
163
- targetAction(target, run) {
164
- return { run, target };
165
- },
166
- };
167
- }
168
-
169
- function resetHarness() {
170
- globalThis.__toolcraftUnavailableEvidenceActionCalls = 0;
171
- globalThis.__toolcraftUnavailableEvidenceArtifactValidations = 0;
172
- globalThis.__toolcraftUnavailableEvidenceAttachments = [];
173
- globalThis.__toolcraftUnavailableEvidenceCleanupFailure = false;
174
- globalThis.__toolcraftUnavailableEvidenceObservation = { status: "missing" };
175
- globalThis.__toolcraftUnavailableEvidenceSessionAssertions = 0;
176
- globalThis.__toolcraftUnavailableEvidenceTrace = [];
177
- }
22
+ });
178
23
 
179
24
  function createFiniteObservation(width, height) {
180
25
  return {
@@ -230,7 +75,7 @@ test("Infinity mode recipe rejects a wrong restored dormant finite size", async
230
75
  },
231
76
  ),
232
77
  );
233
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceAttachments, []);
78
+ assert.deepEqual(getHarnessState().attachments, []);
234
79
  });
235
80
 
236
81
  test("Infinity mode recipe accepts exact restored dormant finite dimensions", async (context) => {
@@ -259,7 +104,7 @@ test("Infinity mode recipe accepts exact restored dormant finite dimensions", as
259
104
  target: "canvas.infinity",
260
105
  },
261
106
  );
262
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceAttachments, [
107
+ assert.deepEqual(getHarnessState().attachments, [
263
108
  {
264
109
  evidenceType: "viewport-side-effect",
265
110
  requirementId: "canvas.infinity.mode",
@@ -276,18 +121,20 @@ test("Infinity mode recipe accepts exact restored dormant finite dimensions", as
276
121
  test("unavailable-image export recipe proves typed fault before evidence and cleans up", async (context) => {
277
122
  const facade = await loadUnavailableResourceFacade(context);
278
123
  resetHarness();
124
+ const session = createSession();
279
125
  const fixture = facade.createToolcraftUnavailableImageResourceFixture(
280
- createSession(),
126
+ session,
281
127
  {
282
128
  fileName: "unavailable.png",
283
129
  sourceTarget: "source.images",
284
130
  target: "canvas.infinity",
285
131
  },
286
132
  );
287
- const exportAction = {
288
- result: new Uint8Array([1, 2, 3]),
289
- target: "canvas.infinity",
290
- };
133
+ const exportAction = createArtifactAction(
134
+ session,
135
+ "canvas.infinity",
136
+ new Uint8Array([1, 2, 3]),
137
+ );
291
138
  const inspection =
292
139
  await facade.expectToolcraftInfinityCanvasUnavailableImageExportEvidence(
293
140
  fixture,
@@ -304,10 +151,12 @@ test("unavailable-image export recipe proves typed fault before evidence and cle
304
151
  height: 2004,
305
152
  width: 2048,
306
153
  });
307
- assert.equal(globalThis.__toolcraftUnavailableEvidenceActionCalls, 2);
308
- assert.equal(globalThis.__toolcraftUnavailableEvidenceArtifactValidations, 1);
309
- assert.equal(globalThis.__toolcraftUnavailableEvidenceSessionAssertions, 1);
310
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceAttachments, [
154
+ const state = getHarnessState();
155
+ assert.equal(state.actionCalls, 2);
156
+ assert.equal(state.artifactValidations, 1);
157
+ assert.equal(state.closurelessObservationCalls, 4);
158
+ assert.equal(state.sessionAssertions, 1);
159
+ assert.deepEqual(state.attachments, [
311
160
  {
312
161
  evidenceType: "exported-artifact",
313
162
  requirementId: "canvas.infinity.image-export",
@@ -319,21 +168,40 @@ test("unavailable-image export recipe proves typed fault before evidence and cle
319
168
  target: "canvas.infinity",
320
169
  },
321
170
  ]);
322
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceTrace, [
171
+ assert.deepEqual(state.trace, [
323
172
  "action:activate",
173
+ "assert:active",
324
174
  "artifact",
175
+ "assert:active",
325
176
  "action:cleanup",
326
177
  "assert:cleaned",
327
178
  "evidence:exported-artifact",
328
179
  "evidence:infinity-scene-bounds-image-export",
329
180
  ]);
181
+ assert.deepEqual(state.bridgeActions, ["activate", "cleanup"]);
182
+ assert.deepEqual(state.eventNames, [
183
+ "toolcraft.browser-proof.unavailable-resource-request",
184
+ "toolcraft.browser-proof.unavailable-resource-request",
185
+ ]);
186
+ assert.deepEqual(state.selectorNames, [
187
+ '[data-slot="toolcraft-runtime-app"]',
188
+ '[data-slot="toolcraft-runtime-app"]',
189
+ ]);
190
+ assert.ok(
191
+ state.attributeNames.length > 0 &&
192
+ state.attributeNames.every(
193
+ (name) => name === "data-toolcraft-unavailable-resource-evidence",
194
+ ),
195
+ );
196
+ assert.equal(state.pollCalls, 2);
330
197
  });
331
198
 
332
199
  test("unavailable-image export recipe cleans up and emits no evidence when decoded bounds fail", async (context) => {
333
200
  const facade = await loadUnavailableResourceFacade(context);
334
201
  resetHarness();
202
+ const session = createSession();
335
203
  const fixture = facade.createToolcraftUnavailableImageResourceFixture(
336
- createSession(),
204
+ session,
337
205
  {
338
206
  fileName: "unavailable.png",
339
207
  target: "canvas.infinity",
@@ -343,10 +211,11 @@ test("unavailable-image export recipe cleans up and emits no evidence when decod
343
211
  await assert.rejects(
344
212
  facade.expectToolcraftInfinityCanvasUnavailableImageExportEvidence(
345
213
  fixture,
346
- {
347
- result: new Uint8Array([1, 2, 3]),
348
- target: "canvas.infinity",
349
- },
214
+ createArtifactAction(
215
+ session,
216
+ "canvas.infinity",
217
+ new Uint8Array([1, 2, 3]),
218
+ ),
350
219
  async () => ({ byteLength: 3, height: 100, width: 100 }),
351
220
  {
352
221
  expectedSize: { height: 2004, width: 2048 },
@@ -355,20 +224,18 @@ test("unavailable-image export recipe cleans up and emits no evidence when decod
355
224
  ),
356
225
  );
357
226
 
358
- assert.equal(globalThis.__toolcraftUnavailableEvidenceActionCalls, 2);
359
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceAttachments, []);
360
- assert.equal(
361
- globalThis.__toolcraftUnavailableEvidenceObservation.status,
362
- "cleaned",
363
- );
227
+ assert.equal(getHarnessState().actionCalls, 2);
228
+ assert.deepEqual(getHarnessState().attachments, []);
229
+ assert.equal(getHarnessState().observation.status, "cleaned");
364
230
  });
365
231
 
366
232
  test("unavailable-image export recipe emits no evidence when cleanup fails", async (context) => {
367
233
  const facade = await loadUnavailableResourceFacade(context);
368
234
  resetHarness();
369
- globalThis.__toolcraftUnavailableEvidenceCleanupFailure = true;
235
+ setCleanupFailure();
236
+ const session = createSession();
370
237
  const fixture = facade.createToolcraftUnavailableImageResourceFixture(
371
- createSession(),
238
+ session,
372
239
  {
373
240
  fileName: "unavailable.png",
374
241
  target: "canvas.infinity",
@@ -378,10 +245,11 @@ test("unavailable-image export recipe emits no evidence when cleanup fails", asy
378
245
  await assert.rejects(
379
246
  facade.expectToolcraftInfinityCanvasUnavailableImageExportEvidence(
380
247
  fixture,
381
- {
382
- result: new Uint8Array([1, 2, 3]),
383
- target: "canvas.infinity",
384
- },
248
+ createArtifactAction(
249
+ session,
250
+ "canvas.infinity",
251
+ new Uint8Array([1, 2, 3]),
252
+ ),
385
253
  async () => ({ byteLength: 3, height: 2004, width: 2048 }),
386
254
  {
387
255
  expectedSize: { height: 2004, width: 2048 },
@@ -391,14 +259,75 @@ test("unavailable-image export recipe emits no evidence when cleanup fails", asy
391
259
  /cleanup failed/u,
392
260
  );
393
261
 
394
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceAttachments, []);
395
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceTrace, [
262
+ assert.deepEqual(getHarnessState().attachments, []);
263
+ assert.deepEqual(getHarnessState().trace, [
396
264
  "action:activate",
265
+ "assert:active",
397
266
  "artifact",
267
+ "assert:active",
398
268
  "action:cleanup",
399
269
  ]);
400
270
  });
401
271
 
272
+ test("unavailable-image export recipe surfaces bridge errors before artifact work", async (context) => {
273
+ const facade = await loadUnavailableResourceFacade(context);
274
+ resetHarness();
275
+ setActivationError("activation failed");
276
+ const session = createSession();
277
+ const fixture = facade.createToolcraftUnavailableImageResourceFixture(
278
+ session,
279
+ { fileName: "unavailable.png", target: "canvas.infinity" },
280
+ );
281
+
282
+ await assert.rejects(
283
+ facade.expectToolcraftInfinityCanvasUnavailableImageExportEvidence(
284
+ fixture,
285
+ createArtifactAction(
286
+ session,
287
+ "canvas.infinity",
288
+ new Uint8Array([1]),
289
+ ),
290
+ async () => ({ byteLength: 1, height: 1, width: 1 }),
291
+ {
292
+ expectedSize: { height: 1, width: 1 },
293
+ requirementId: "canvas.infinity.image-export",
294
+ },
295
+ ),
296
+ /activation failed/u,
297
+ );
298
+ assert.deepEqual(getHarnessState().trace, ["action:activate"]);
299
+ assert.deepEqual(getHarnessState().attachments, []);
300
+ });
301
+
302
+ test("unavailable-image export recipe rejects malformed bridge observations", async (context) => {
303
+ const facade = await loadUnavailableResourceFacade(context);
304
+ resetHarness();
305
+ setMalformedActivation();
306
+ const session = createSession();
307
+ const fixture = facade.createToolcraftUnavailableImageResourceFixture(
308
+ session,
309
+ { fileName: "unavailable.png", target: "canvas.infinity" },
310
+ );
311
+
312
+ await assert.rejects(
313
+ facade.expectToolcraftInfinityCanvasUnavailableImageExportEvidence(
314
+ fixture,
315
+ createArtifactAction(
316
+ session,
317
+ "canvas.infinity",
318
+ new Uint8Array([1]),
319
+ ),
320
+ async () => ({ byteLength: 1, height: 1, width: 1 }),
321
+ {
322
+ expectedSize: { height: 1, width: 1 },
323
+ requirementId: "canvas.infinity.image-export",
324
+ },
325
+ ),
326
+ /published malformed state/u,
327
+ );
328
+ assert.deepEqual(getHarnessState().attachments, []);
329
+ });
330
+
402
331
  test("unavailable-image export recipe rejects forged fixtures and target mismatch", async (context) => {
403
332
  const facade = await loadUnavailableResourceFacade(context);
404
333
  resetHarness();
@@ -426,14 +355,34 @@ test("unavailable-image export recipe rejects forged fixtures and target mismatc
426
355
  await assert.rejects(
427
356
  facade.expectToolcraftInfinityCanvasUnavailableImageExportEvidence(
428
357
  fixture,
429
- { result: new Uint8Array([1]), target: "actions.output" },
358
+ createArtifactAction(
359
+ session,
360
+ "actions.output",
361
+ new Uint8Array([1]),
362
+ ),
363
+ async () => ({ byteLength: 1, height: 1, width: 1 }),
364
+ {
365
+ expectedSize: { height: 1, width: 1 },
366
+ requirementId: "canvas.infinity.image-export",
367
+ },
368
+ ),
369
+ );
370
+ await assert.rejects(
371
+ facade.expectToolcraftInfinityCanvasUnavailableImageExportEvidence(
372
+ fixture,
373
+ createArtifactAction(
374
+ createSession(),
375
+ "canvas.infinity",
376
+ new Uint8Array([1]),
377
+ ),
430
378
  async () => ({ byteLength: 1, height: 1, width: 1 }),
431
379
  {
432
380
  expectedSize: { height: 1, width: 1 },
433
381
  requirementId: "canvas.infinity.image-export",
434
382
  },
435
383
  ),
384
+ /same browser proof session/u,
436
385
  );
437
- assert.equal(globalThis.__toolcraftUnavailableEvidenceActionCalls, 0);
438
- assert.deepEqual(globalThis.__toolcraftUnavailableEvidenceAttachments, []);
386
+ assert.equal(getHarnessState().actionCalls, 0);
387
+ assert.deepEqual(getHarnessState().attachments, []);
439
388
  });
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  const ignoredDirectoryNames = new Set([
6
6
  ".agents",
7
7
  ".git",
8
+ ".playwright-mcp",
8
9
  ".toolcraft",
9
10
  ".vite",
10
11
  "coverage",
@@ -193,6 +193,57 @@ test("ignores workflow skills, generated output, and checkpoint directories thro
193
193
  );
194
194
  });
195
195
 
196
+ test("ignores browser-tool artifact create change and delete", async (t) => {
197
+ const rootDir = await createRootWideInventoryFixture();
198
+ t.after(() => fs.rm(rootDir, { force: true, recursive: true }));
199
+ const baseline = await collectToolcraftVerificationInputs(rootDir);
200
+ const browserArtifact = path.join(
201
+ rootDir,
202
+ ".playwright-mcp",
203
+ "nested",
204
+ "page.yml",
205
+ );
206
+
207
+ for (const content of ["page: initial\n", "page: updated\n"]) {
208
+ await fs.mkdir(path.dirname(browserArtifact), { recursive: true });
209
+ await fs.writeFile(browserArtifact, content);
210
+ const current = await collectToolcraftVerificationInputs(rootDir);
211
+
212
+ assert.equal(current.sourceHash, baseline.sourceHash);
213
+ assert.deepEqual(
214
+ getToolcraftChangedVerificationFiles(baseline.entries, current.entries),
215
+ [],
216
+ );
217
+ }
218
+
219
+ await fs.rm(path.join(rootDir, ".playwright-mcp"), {
220
+ force: true,
221
+ recursive: true,
222
+ });
223
+ const current = await collectToolcraftVerificationInputs(rootDir);
224
+
225
+ assert.equal(current.sourceHash, baseline.sourceHash);
226
+ assert.deepEqual(
227
+ getToolcraftChangedVerificationFiles(baseline.entries, current.entries),
228
+ [],
229
+ );
230
+ });
231
+
232
+ test("continues to hash real product raster assets", async (t) => {
233
+ const rootDir = await createRootWideInventoryFixture();
234
+ t.after(() => fs.rm(rootDir, { force: true, recursive: true }));
235
+ const baseline = await collectToolcraftVerificationInputs(rootDir);
236
+
237
+ await fs.writeFile(path.join(rootDir, "public", "source.png"), "updated-source-image");
238
+ const current = await collectToolcraftVerificationInputs(rootDir);
239
+
240
+ assert.notEqual(current.sourceHash, baseline.sourceHash);
241
+ assert.deepEqual(
242
+ getToolcraftChangedVerificationFiles(baseline.entries, current.entries),
243
+ ["public/source.png"],
244
+ );
245
+ });
246
+
196
247
  test("does not report legacy workflow-skill receipt entries as product changes", async (t) => {
197
248
  const rootDir = await createRootWideInventoryFixture();
198
249
  t.after(() => fs.rm(rootDir, { force: true, recursive: true }));