@dailephd/my-frontend-observer 0.8.1

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 (316) hide show
  1. package/CHANGELOG.md +397 -0
  2. package/LICENSE +21 -0
  3. package/README.md +255 -0
  4. package/dist/application/browserCaptureService.d.ts +11 -0
  5. package/dist/application/browserCaptureService.js +12 -0
  6. package/dist/application/browserCaptureService.js.map +1 -0
  7. package/dist/application/comparisonService.d.ts +56 -0
  8. package/dist/application/comparisonService.js +77 -0
  9. package/dist/application/comparisonService.js.map +1 -0
  10. package/dist/application/externalReferencePersistenceService.d.ts +75 -0
  11. package/dist/application/externalReferencePersistenceService.js +182 -0
  12. package/dist/application/externalReferencePersistenceService.js.map +1 -0
  13. package/dist/application/frontendContractEvaluationService.d.ts +49 -0
  14. package/dist/application/frontendContractEvaluationService.js +112 -0
  15. package/dist/application/frontendContractEvaluationService.js.map +1 -0
  16. package/dist/application/frontendContractPersistenceService.d.ts +56 -0
  17. package/dist/application/frontendContractPersistenceService.js +91 -0
  18. package/dist/application/frontendContractPersistenceService.js.map +1 -0
  19. package/dist/application/observationPersistence.d.ts +59 -0
  20. package/dist/application/observationPersistence.js +79 -0
  21. package/dist/application/observationPersistence.js.map +1 -0
  22. package/dist/application/projectCheckService.d.ts +3 -0
  23. package/dist/application/projectCheckService.js +169 -0
  24. package/dist/application/projectCheckService.js.map +1 -0
  25. package/dist/application/projectWorkflowService.d.ts +46 -0
  26. package/dist/application/projectWorkflowService.js +92 -0
  27. package/dist/application/projectWorkflowService.js.map +1 -0
  28. package/dist/application/referenceFidelityEvaluationService.d.ts +28 -0
  29. package/dist/application/referenceFidelityEvaluationService.js +45 -0
  30. package/dist/application/referenceFidelityEvaluationService.js.map +1 -0
  31. package/dist/artifacts/artifactReader.d.ts +19 -0
  32. package/dist/artifacts/artifactReader.js +36 -0
  33. package/dist/artifacts/artifactReader.js.map +1 -0
  34. package/dist/artifacts/artifactWriter.d.ts +25 -0
  35. package/dist/artifacts/artifactWriter.js +68 -0
  36. package/dist/artifacts/artifactWriter.js.map +1 -0
  37. package/dist/artifacts/comparisonArtifactReader.d.ts +18 -0
  38. package/dist/artifacts/comparisonArtifactReader.js +35 -0
  39. package/dist/artifacts/comparisonArtifactReader.js.map +1 -0
  40. package/dist/artifacts/comparisonArtifactWriter.d.ts +41 -0
  41. package/dist/artifacts/comparisonArtifactWriter.js +67 -0
  42. package/dist/artifacts/comparisonArtifactWriter.js.map +1 -0
  43. package/dist/artifacts/externalReferenceArtifactReader.d.ts +18 -0
  44. package/dist/artifacts/externalReferenceArtifactReader.js +35 -0
  45. package/dist/artifacts/externalReferenceArtifactReader.js.map +1 -0
  46. package/dist/artifacts/externalReferenceArtifactWriter.d.ts +44 -0
  47. package/dist/artifacts/externalReferenceArtifactWriter.js +77 -0
  48. package/dist/artifacts/externalReferenceArtifactWriter.js.map +1 -0
  49. package/dist/artifacts/frontendContractArtifactReader.d.ts +24 -0
  50. package/dist/artifacts/frontendContractArtifactReader.js +47 -0
  51. package/dist/artifacts/frontendContractArtifactReader.js.map +1 -0
  52. package/dist/artifacts/frontendContractArtifactWriter.d.ts +34 -0
  53. package/dist/artifacts/frontendContractArtifactWriter.js +70 -0
  54. package/dist/artifacts/frontendContractArtifactWriter.js.map +1 -0
  55. package/dist/artifacts/frontendContractEvaluationArtifactReader.d.ts +17 -0
  56. package/dist/artifacts/frontendContractEvaluationArtifactReader.js +34 -0
  57. package/dist/artifacts/frontendContractEvaluationArtifactReader.js.map +1 -0
  58. package/dist/artifacts/frontendContractEvaluationArtifactWriter.d.ts +32 -0
  59. package/dist/artifacts/frontendContractEvaluationArtifactWriter.js +58 -0
  60. package/dist/artifacts/frontendContractEvaluationArtifactWriter.js.map +1 -0
  61. package/dist/artifacts/types.d.ts +17 -0
  62. package/dist/artifacts/types.js +2 -0
  63. package/dist/artifacts/types.js.map +1 -0
  64. package/dist/browser/chromiumAdapter.d.ts +21 -0
  65. package/dist/browser/chromiumAdapter.js +204 -0
  66. package/dist/browser/chromiumAdapter.js.map +1 -0
  67. package/dist/browser/evidenceCapture.d.ts +62 -0
  68. package/dist/browser/evidenceCapture.js +500 -0
  69. package/dist/browser/evidenceCapture.js.map +1 -0
  70. package/dist/browser/scrollCapture.d.ts +32 -0
  71. package/dist/browser/scrollCapture.js +163 -0
  72. package/dist/browser/scrollCapture.js.map +1 -0
  73. package/dist/browser/types.d.ts +24 -0
  74. package/dist/browser/types.js +2 -0
  75. package/dist/browser/types.js.map +1 -0
  76. package/dist/cli.d.ts +8 -0
  77. package/dist/cli.js +2411 -0
  78. package/dist/cli.js.map +1 -0
  79. package/dist/domain/boundedAgentContext.d.ts +226 -0
  80. package/dist/domain/boundedAgentContext.js +355 -0
  81. package/dist/domain/boundedAgentContext.js.map +1 -0
  82. package/dist/domain/boundedAgentContextCorrelation.d.ts +74 -0
  83. package/dist/domain/boundedAgentContextCorrelation.js +441 -0
  84. package/dist/domain/boundedAgentContextCorrelation.js.map +1 -0
  85. package/dist/domain/boundedAgentContextIdentity.d.ts +26 -0
  86. package/dist/domain/boundedAgentContextIdentity.js +69 -0
  87. package/dist/domain/boundedAgentContextIdentity.js.map +1 -0
  88. package/dist/domain/boundedAgentContextProjection.d.ts +71 -0
  89. package/dist/domain/boundedAgentContextProjection.js +477 -0
  90. package/dist/domain/boundedAgentContextProjection.js.map +1 -0
  91. package/dist/domain/comparison.d.ts +220 -0
  92. package/dist/domain/comparison.js +350 -0
  93. package/dist/domain/comparison.js.map +1 -0
  94. package/dist/domain/comparisonEngine.d.ts +76 -0
  95. package/dist/domain/comparisonEngine.js +734 -0
  96. package/dist/domain/comparisonEngine.js.map +1 -0
  97. package/dist/domain/comparisonIdentity.d.ts +13 -0
  98. package/dist/domain/comparisonIdentity.js +46 -0
  99. package/dist/domain/comparisonIdentity.js.map +1 -0
  100. package/dist/domain/completion.d.ts +30 -0
  101. package/dist/domain/completion.js +22 -0
  102. package/dist/domain/completion.js.map +1 -0
  103. package/dist/domain/diagnostics.d.ts +17 -0
  104. package/dist/domain/diagnostics.js +77 -0
  105. package/dist/domain/diagnostics.js.map +1 -0
  106. package/dist/domain/evidence.d.ts +29 -0
  107. package/dist/domain/evidence.js +59 -0
  108. package/dist/domain/evidence.js.map +1 -0
  109. package/dist/domain/explicitState.d.ts +40 -0
  110. package/dist/domain/explicitState.js +54 -0
  111. package/dist/domain/explicitState.js.map +1 -0
  112. package/dist/domain/externalReference.d.ts +158 -0
  113. package/dist/domain/externalReference.js +167 -0
  114. package/dist/domain/externalReference.js.map +1 -0
  115. package/dist/domain/externalReferenceApplicability.d.ts +43 -0
  116. package/dist/domain/externalReferenceApplicability.js +52 -0
  117. package/dist/domain/externalReferenceApplicability.js.map +1 -0
  118. package/dist/domain/externalReferenceCompatibility.d.ts +40 -0
  119. package/dist/domain/externalReferenceCompatibility.js +48 -0
  120. package/dist/domain/externalReferenceCompatibility.js.map +1 -0
  121. package/dist/domain/externalReferenceFidelity.d.ts +171 -0
  122. package/dist/domain/externalReferenceFidelity.js +419 -0
  123. package/dist/domain/externalReferenceFidelity.js.map +1 -0
  124. package/dist/domain/externalReferenceIdentity.d.ts +38 -0
  125. package/dist/domain/externalReferenceIdentity.js +70 -0
  126. package/dist/domain/externalReferenceIdentity.js.map +1 -0
  127. package/dist/domain/externalReferenceImage.d.ts +35 -0
  128. package/dist/domain/externalReferenceImage.js +160 -0
  129. package/dist/domain/externalReferenceImage.js.map +1 -0
  130. package/dist/domain/externalReferenceRegionRelationships.d.ts +63 -0
  131. package/dist/domain/externalReferenceRegionRelationships.js +98 -0
  132. package/dist/domain/externalReferenceRegionRelationships.js.map +1 -0
  133. package/dist/domain/externalReferenceRegions.d.ts +65 -0
  134. package/dist/domain/externalReferenceRegions.js +105 -0
  135. package/dist/domain/externalReferenceRegions.js.map +1 -0
  136. package/dist/domain/externalReferenceRequirementIdentity.d.ts +12 -0
  137. package/dist/domain/externalReferenceRequirementIdentity.js +35 -0
  138. package/dist/domain/externalReferenceRequirementIdentity.js.map +1 -0
  139. package/dist/domain/externalReferenceRequirements.d.ts +215 -0
  140. package/dist/domain/externalReferenceRequirements.js +401 -0
  141. package/dist/domain/externalReferenceRequirements.js.map +1 -0
  142. package/dist/domain/externalReferenceRuntimeBinding.d.ts +146 -0
  143. package/dist/domain/externalReferenceRuntimeBinding.js +183 -0
  144. package/dist/domain/externalReferenceRuntimeBinding.js.map +1 -0
  145. package/dist/domain/frontendContractEvaluation.d.ts +57 -0
  146. package/dist/domain/frontendContractEvaluation.js +454 -0
  147. package/dist/domain/frontendContractEvaluation.js.map +1 -0
  148. package/dist/domain/frontendContractEvaluationArtifact.d.ts +65 -0
  149. package/dist/domain/frontendContractEvaluationArtifact.js +108 -0
  150. package/dist/domain/frontendContractEvaluationArtifact.js.map +1 -0
  151. package/dist/domain/frontendContractIdentity.d.ts +39 -0
  152. package/dist/domain/frontendContractIdentity.js +70 -0
  153. package/dist/domain/frontendContractIdentity.js.map +1 -0
  154. package/dist/domain/frontendContracts.d.ts +188 -0
  155. package/dist/domain/frontendContracts.js +260 -0
  156. package/dist/domain/frontendContracts.js.map +1 -0
  157. package/dist/domain/identity.d.ts +21 -0
  158. package/dist/domain/identity.js +47 -0
  159. package/dist/domain/identity.js.map +1 -0
  160. package/dist/domain/referenceCorrectionIdentity.d.ts +40 -0
  161. package/dist/domain/referenceCorrectionIdentity.js +77 -0
  162. package/dist/domain/referenceCorrectionIdentity.js.map +1 -0
  163. package/dist/domain/referenceCorrectionWorkflow.d.ts +160 -0
  164. package/dist/domain/referenceCorrectionWorkflow.js +165 -0
  165. package/dist/domain/referenceCorrectionWorkflow.js.map +1 -0
  166. package/dist/domain/referenceFidelityProjection.d.ts +65 -0
  167. package/dist/domain/referenceFidelityProjection.js +135 -0
  168. package/dist/domain/referenceFidelityProjection.js.map +1 -0
  169. package/dist/domain/relationships.d.ts +210 -0
  170. package/dist/domain/relationships.js +352 -0
  171. package/dist/domain/relationships.js.map +1 -0
  172. package/dist/domain/schema.d.ts +269 -0
  173. package/dist/domain/schema.js +442 -0
  174. package/dist/domain/schema.js.map +1 -0
  175. package/dist/domain/scrollEvidence.d.ts +51 -0
  176. package/dist/domain/scrollEvidence.js +134 -0
  177. package/dist/domain/scrollEvidence.js.map +1 -0
  178. package/dist/index.d.ts +114 -0
  179. package/dist/index.js +64 -0
  180. package/dist/index.js.map +1 -0
  181. package/dist/projectWorkflow/aliasCatalog.d.ts +21 -0
  182. package/dist/projectWorkflow/aliasCatalog.js +67 -0
  183. package/dist/projectWorkflow/aliasCatalog.js.map +1 -0
  184. package/dist/projectWorkflow/checkAcceptance.d.ts +25 -0
  185. package/dist/projectWorkflow/checkAcceptance.js +55 -0
  186. package/dist/projectWorkflow/checkAcceptance.js.map +1 -0
  187. package/dist/projectWorkflow/checkResult.d.ts +85 -0
  188. package/dist/projectWorkflow/checkResult.js +101 -0
  189. package/dist/projectWorkflow/checkResult.js.map +1 -0
  190. package/dist/projectWorkflow/projectConfig.d.ts +43 -0
  191. package/dist/projectWorkflow/projectConfig.js +84 -0
  192. package/dist/projectWorkflow/projectConfig.js.map +1 -0
  193. package/dist/projectWorkflow/projectDiscovery.d.ts +9 -0
  194. package/dist/projectWorkflow/projectDiscovery.js +20 -0
  195. package/dist/projectWorkflow/projectDiscovery.js.map +1 -0
  196. package/dist/projectWorkflow/projectPaths.d.ts +8 -0
  197. package/dist/projectWorkflow/projectPaths.js +23 -0
  198. package/dist/projectWorkflow/projectPaths.js.map +1 -0
  199. package/dist/request/paths.d.ts +14 -0
  200. package/dist/request/paths.js +33 -0
  201. package/dist/request/paths.js.map +1 -0
  202. package/dist/request/request.d.ts +111 -0
  203. package/dist/request/request.js +464 -0
  204. package/dist/request/request.js.map +1 -0
  205. package/dist/safety/policy.d.ts +14 -0
  206. package/dist/safety/policy.js +81 -0
  207. package/dist/safety/policy.js.map +1 -0
  208. package/dist/viewer/assets/index-CN_yb9Uf.css +1 -0
  209. package/dist/viewer/assets/index-D98S1_2d.js +9 -0
  210. package/dist/viewer/icons/icon-192.png +0 -0
  211. package/dist/viewer/icons/icon-512.png +0 -0
  212. package/dist/viewer/index.html +15 -0
  213. package/dist/viewer/manifest.webmanifest +1 -0
  214. package/dist/viewer/registerSW.js +1 -0
  215. package/dist/viewer/sw.js +1 -0
  216. package/dist/viewer/workbox-9c191d2f.js +1 -0
  217. package/dist/viewerServer/context.d.ts +48 -0
  218. package/dist/viewerServer/context.js +60 -0
  219. package/dist/viewerServer/context.js.map +1 -0
  220. package/dist/viewerServer/evidence/classify.d.ts +59 -0
  221. package/dist/viewerServer/evidence/classify.js +124 -0
  222. package/dist/viewerServer/evidence/classify.js.map +1 -0
  223. package/dist/viewerServer/evidence/comparisonView.d.ts +28 -0
  224. package/dist/viewerServer/evidence/comparisonView.js +43 -0
  225. package/dist/viewerServer/evidence/comparisonView.js.map +1 -0
  226. package/dist/viewerServer/evidence/contextSourceView.d.ts +25 -0
  227. package/dist/viewerServer/evidence/contextSourceView.js +20 -0
  228. package/dist/viewerServer/evidence/contextSourceView.js.map +1 -0
  229. package/dist/viewerServer/evidence/discovery.d.ts +31 -0
  230. package/dist/viewerServer/evidence/discovery.js +78 -0
  231. package/dist/viewerServer/evidence/discovery.js.map +1 -0
  232. package/dist/viewerServer/evidence/evaluationView.d.ts +32 -0
  233. package/dist/viewerServer/evidence/evaluationView.js +50 -0
  234. package/dist/viewerServer/evidence/evaluationView.js.map +1 -0
  235. package/dist/viewerServer/evidence/handles.d.ts +21 -0
  236. package/dist/viewerServer/evidence/handles.js +43 -0
  237. package/dist/viewerServer/evidence/handles.js.map +1 -0
  238. package/dist/viewerServer/evidence/index.d.ts +41 -0
  239. package/dist/viewerServer/evidence/index.js +82 -0
  240. package/dist/viewerServer/evidence/index.js.map +1 -0
  241. package/dist/viewerServer/evidence/limits.d.ts +27 -0
  242. package/dist/viewerServer/evidence/limits.js +28 -0
  243. package/dist/viewerServer/evidence/limits.js.map +1 -0
  244. package/dist/viewerServer/evidence/linkedEvidence.d.ts +43 -0
  245. package/dist/viewerServer/evidence/linkedEvidence.js +151 -0
  246. package/dist/viewerServer/evidence/linkedEvidence.js.map +1 -0
  247. package/dist/viewerServer/evidence/mediaResolver.d.ts +16 -0
  248. package/dist/viewerServer/evidence/mediaResolver.js +85 -0
  249. package/dist/viewerServer/evidence/mediaResolver.js.map +1 -0
  250. package/dist/viewerServer/evidence/observationView.d.ts +29 -0
  251. package/dist/viewerServer/evidence/observationView.js +46 -0
  252. package/dist/viewerServer/evidence/observationView.js.map +1 -0
  253. package/dist/viewerServer/evidence/pathSafety.d.ts +11 -0
  254. package/dist/viewerServer/evidence/pathSafety.js +31 -0
  255. package/dist/viewerServer/evidence/pathSafety.js.map +1 -0
  256. package/dist/viewerServer/evidence/projection.d.ts +55 -0
  257. package/dist/viewerServer/evidence/projection.js +152 -0
  258. package/dist/viewerServer/evidence/projection.js.map +1 -0
  259. package/dist/viewerServer/evidence/referenceView.d.ts +133 -0
  260. package/dist/viewerServer/evidence/referenceView.js +169 -0
  261. package/dist/viewerServer/evidence/referenceView.js.map +1 -0
  262. package/dist/viewerServer/httpServer.d.ts +27 -0
  263. package/dist/viewerServer/httpServer.js +381 -0
  264. package/dist/viewerServer/httpServer.js.map +1 -0
  265. package/dist/viewerServer/openBrowser.d.ts +7 -0
  266. package/dist/viewerServer/openBrowser.js +32 -0
  267. package/dist/viewerServer/openBrowser.js.map +1 -0
  268. package/dist/viewerServer/port.d.ts +16 -0
  269. package/dist/viewerServer/port.js +19 -0
  270. package/dist/viewerServer/port.js.map +1 -0
  271. package/dist/viewerServer/viewerService.d.ts +62 -0
  272. package/dist/viewerServer/viewerService.js +88 -0
  273. package/dist/viewerServer/viewerService.js.map +1 -0
  274. package/docs/ARCHITECTURE.md +1286 -0
  275. package/docs/CI_CD.md +250 -0
  276. package/docs/COMMANDS.md +972 -0
  277. package/docs/CONTRACTS.md +1856 -0
  278. package/docs/CURRENT_STATE.md +1049 -0
  279. package/docs/DEVELOPMENT.md +202 -0
  280. package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +50 -0
  281. package/docs/PROJECT_DESCRIPTION.md +2221 -0
  282. package/docs/PROJECT_MILESTONES.md +2526 -0
  283. package/docs/PROJECT_OVERVIEW.md +150 -0
  284. package/docs/QUICKSTART.md +83 -0
  285. package/docs/RELEASE.md +27 -0
  286. package/docs/ROADMAP.md +641 -0
  287. package/docs/SECURITY.md +218 -0
  288. package/docs/WORKFLOWS.md +642 -0
  289. package/docs/plans/v0.8-implementation-plan.md +655 -0
  290. package/docs/plans/v0.8.1-cli-usability-patch-plan.md +505 -0
  291. package/docs/reports/v0.7-bounded-fidelity-context-prompt7.md +243 -0
  292. package/docs/reports/v0.7-implementation-completeness-documentation-reconciliation.md +497 -0
  293. package/docs/reports/v0.7-pre-release-readiness.md +337 -0
  294. package/docs/reports/v0.7-reference-binding-prompt5.md +223 -0
  295. package/docs/reports/v0.7-reference-compatibility-prompt4.md +234 -0
  296. package/docs/reports/v0.7-reference-correction-workflow-prompt8.md +222 -0
  297. package/docs/reports/v0.7-reference-fidelity-prompt6.md +216 -0
  298. package/docs/reports/v0.7-reference-foundation-prompt1.md +151 -0
  299. package/docs/reports/v0.7-reference-regions-prompt2.md +195 -0
  300. package/docs/reports/v0.7-reference-requirements-prompt3.md +217 -0
  301. package/docs/reports/v0.7-release-prep.md +423 -0
  302. package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -0
  303. package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -0
  304. package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -0
  305. package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -0
  306. package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -0
  307. package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -0
  308. package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -0
  309. package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -0
  310. package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -0
  311. package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -0
  312. package/docs/reports/v0.8.1-check-orchestration-prompt2.md +69 -0
  313. package/docs/reports/v0.8.1-implementation-completeness-documentation-reconciliation.md +114 -0
  314. package/docs/reports/v0.8.1-prerelease-readiness-cross-platform-security-code-rot.md +170 -0
  315. package/docs/reports/v0.8.1-project-workflow-foundation-prompt1.md +66 -0
  316. package/package.json +58 -0
@@ -0,0 +1,279 @@
1
+ # v0.8 Batch 4 — Before/After Comparison and Contract/Change-Scope Inspection — Implementation Report
2
+
3
+ ## 1. Starting state
4
+
5
+ - Branch: `master`
6
+ - Starting HEAD: `05d504a2c3b2419502faf48ff5661abb02b0e10d` ("feat: add v0.8 observation inspection and SVG overlays")
7
+ - `origin/master` after `git fetch`: `a1de8ac01e1367b60021cb04226f56369fa2debb`
8
+ - `git rev-list --left-right --count origin/master...HEAD`: `0 3` — local is exactly Batches 1-3 ahead of origin, no divergence.
9
+ - Starting `git status --short`: clean.
10
+ - Package version confirmed `0.7.0` throughout; never bumped.
11
+
12
+ ## 2. A resolved contradiction in the task's path instructions (task §5)
13
+
14
+ The task gave an explicit `Join-Path`-based algorithm (`$WORKFLOW_BASE = Join-Path $REPO_ROOT ".my-dev-kit-workflow"`; `$WORKFLOW_ROOT = Join-Path $WORKFLOW_BASE "v0.8\batch-04"`) together with five verifiable assertions, then separately restated a "Required Batch 4 root" as the old Batches-1-3 sibling path (`...\my-frontend-observer.my-dev-kit-workflow\v0.8\batch-04`). These two are inconsistent: the sibling path **fails assertion 1** (`$WORKFLOW_ROOT` must begin with `$REPO_ROOT\`), since it is a sibling directory name, not a path under the repository root.
15
+
16
+ Verified programmatically in PowerShell:
17
+
18
+ ```
19
+ REPO_ROOT=Z:\Users\newuser\Projects\my-frontend-observer
20
+ WORKFLOW_BASE=Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow
21
+ WORKFLOW_ROOT=Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\v0.8\batch-04
22
+ Assertion1 (starts with REPO_ROOT\): True
23
+ Assertion2 (WORKFLOW_BASE parent = REPO_ROOT): True
24
+ Assertion3 (WORKFLOW_ROOT parent = REPO_ROOT\.my-dev-kit-workflow\v0.8): True
25
+ Assertion4 (not sibling path): True
26
+ Assertion5 (not on C:): True
27
+ ```
28
+
29
+ All five assertions pass for the `Join-Path`-derived (inside-repository) path and would fail for the restated sibling path. A decisive tiebreaker was also found in the repository's own `.gitignore` (`.my-dev-kit-workflow/`, present since before this batch) - a pattern that only makes sense for a directory *inside* the repository, since a sibling directory outside the repo is never a `git` ignore-pattern candidate in the first place. Per the task's own instruction ("Do not manually repair the string by guessing"), the literal, executable `Join-Path` algorithm was followed exactly rather than the inconsistent restated path, and this resolution is recorded here rather than silently picked.
30
+
31
+ **Resolved `WORKFLOW_ROOT` for Batch 4:** `Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\v0.8\batch-04` (inside the repository, gitignored).
32
+
33
+ ## 3. Prior generated-path audit (task §6)
34
+
35
+ - **Sibling location** `Z:\Users\newuser\Projects\my-frontend-observer.my-dev-kit-workflow\v0.8\` exists and contains exactly `batch-01`, `batch-02`, `batch-03` (Batches 1-3's own generated state, per their reports) - confirmed present, **not modified, not migrated, not reused** by this batch.
36
+ - **Inside-repository location** `Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\` also pre-existed, but held only unrelated pre-v0.8 tooling state (`npm-cache`, `pw-browsers`, `readiness` - no `v0.8` subdirectory at all before this batch). Batch 4 added a new `v0.8\batch-04` subtree there without touching those pre-existing siblings.
37
+ - No third, differently-malformed sibling path was found anywhere under `Z:\Users\newuser\Projects\`.
38
+
39
+ ## 4. Predecessor reports inspected
40
+
41
+ All three read in full (not console summaries) - previously written in this same session, so their exact content was already held in full working memory and was re-confirmed against this batch's actual needs:
42
+
43
+ - **Batch 1**: host `127.0.0.1`/port `4319`, `src/viewerServer/{httpServer,viewerService}.ts`, PWA `navigateFallbackDenylist: [/^\/api\//]`.
44
+ - **Batch 2**: `evidence/{discovery,classify,handles,pathSafety,index,projection,mediaResolver}.ts` exact module boundaries; `EvidenceMetadataRecord`/`EvidenceArtifactDetail` shapes; `GET /api/index`/`/api/artifacts/<handle>`/`/api/media/<handle>/<role>` exact status-code semantics (404 unknown handle, 409 not-currently-loadable, 405 write methods); the `<family-slug>:<percent-encoded-relativeDir>` handle format; `resolveContainedDir`'s known forward-slash-root bugfix (reused unchanged, not re-litigated).
45
+ - **Batch 3**: `observationView.ts#getObservationRelationships` and `GET /api/observations/<handle>/relationships`; `ObservationWorkspace`/`TargetOverlaySvg`/`ObservationInspector`/`EvidenceFieldView`/`useArtifactDetail`/`targetOrder.ts` component/hook boundaries; the coordinate-mapping decision (`requestConfig.viewport` as the canonical SVG `viewBox` source, geometry never rewritten); the `data-target-name` attribute already present on rendered `<rect>`/`<g>` elements.
46
+
47
+ Batch 4 extends these mechanisms exactly - no second observation viewer, no second evidence index, no second media-loading path was created.
48
+
49
+ ## 5. Frozen planning authority inspected
50
+
51
+ `docs/DOCUMENTATION_PRESERVATION_POLICY.md`, `docs/PROJECT_MILESTONES.md` (Milestone 8), `docs/ROADMAP.md` (v0.8), `docs/plans/v0.8-implementation-plan.md` (Batch 4 section + cross-batch invariants) - all previously read in full during Batches 1-3, re-confirmed unchanged. `docs/ARCHITECTURE.md`, `docs/CONTRACTS.md` (previously read in full), `docs/WORKFLOWS.md`, `docs/COMMANDS.md` (then edited). New this batch, read in full: `src/domain/comparison.ts`, `src/domain/frontendContracts.ts`, `src/domain/frontendContractEvaluationArtifact.ts`; targeted reads of `src/domain/frontendContractEvaluation.ts` (`UnexpectedChangeResult`, `FrontendContractEvaluationInput/Result`) and the three readers (`comparisonArtifactReader.ts`, `frontendContractArtifactReader.ts`, `frontendContractEvaluationArtifactReader.ts` - function names confirmed, bodies already known from having authored `classify.ts` in Batch 2).
52
+
53
+ Confirmed Batch 4's title/scope in `docs/plans/v0.8-implementation-plan.md` match the task exactly; no material difference found.
54
+
55
+ ## 6. my-dev-kit retrieval
56
+
57
+ Index built successfully at `$WORKFLOW_ROOT\my-dev-kit-index`. All seven required searches were run (comparison artifact ownership, comparison engine ownership, frontend contract shapes, contract evaluation ownership, viewer linked-evidence/index APIs, Batch 3 observation viewer, existing protected/preserved-regression tests). Every result matched direct source inspection - `src/domain/comparison.ts`, `comparisonEngine.ts`, `frontendContracts.ts`, `frontendContractEvaluation.ts`, `frontendContractEvaluationArtifact.ts`, the three readers, and the application services were all surfaced consistently.
58
+
59
+ ## 7. Comparison contract audit (task §10)
60
+
61
+ Recorded from `src/domain/comparison.ts` (full read):
62
+
63
+ - `comparisonId` (fresh per execution), `comparisonRequestId` (deterministic, `compare(A,B) !== compare(B,A)`).
64
+ - `before`/`after`: **ordered**, distinct fields (`ComparisonSourceObservationReference` = `{observationId, requestId, producer:{name,version}, observationSchemaVersion, screenshot:{path}}`) - never swappable, confirmed by the type's own doc comment ("Comparison is ordered... never a swappable pair").
65
+ - `config: ComparisonConfig`, `comparability: ComparabilityResult` (`state` + `reasons[]`, each reason carrying `code`/`severity`/`message`), `configurationChanges: TargetConfigurationChange[]` (`added`/`removed`/`locator-changed`), `relationshipsBefore`/`relationshipsAfter: LayoutRelationshipGraph` (the pre-persisted graphs, not re-derivable data), `differences: ComparisonDifference[]`, `relationshipChanges: RelationshipChangeRecord[]`, `expectedDependencyEvidence: ExpectedDependencyEvidence[]`, `diagnostics: Diagnostic[]`, `limits: {truncated, omittedFields, omittedTargetPairs}`.
66
+ - Source references carry logical identity (`observationId`/`requestId`/`producer`/`observationSchemaVersion`) - never a persisted filesystem path. Confirmed: no path field anywhere in `ComparisonSourceObservationReference`.
67
+
68
+ ## 8. Source-observation resolution rule (task §11)
69
+
70
+ **Exact match on all four available identity fields simultaneously**: `observationId`, `requestId`, `producer.version` (`producer.name` is already a fixed constant, `PRODUCER_NAME`, so only `.version` varies), and `observationSchemaVersion` - implemented in `src/viewerServer/evidence/linkedEvidence.ts#resolveObservationByReference`. Zero matches → `{status: 'missing'}`. Two or more exact matches → `{status: 'ambiguous', count}` - **never silently picks one** (proven with a real duplicated-manifest fixture in `tests/unit/linkedEvidenceServer.test.ts`). No folder-name/screenshot-filename/URL/target-set/geometry-based matching exists anywhere in this module. The resolution walks the bounded evidence tree once per lookup (mirroring Batch 2's `findImportedReferenceDir` pattern exactly, applied consistently here for comparisons/baseline-contracts/change-contracts too).
71
+
72
+ ## 9. Evaluation linked-artifact resolution rule
73
+
74
+ `FrontendContractEvaluationArtifact` references are resolved by exact identity, each independently:
75
+
76
+ - **Comparison**: exact `comparisonId` **and** `comparisonRequestId` (`resolveComparisonByIdentity`).
77
+ - **Baseline contract**: exact `baselineId` (`resolveBaselineContractById`).
78
+ - **Per-change contract**: exact `contractId` (`resolveChangeContractById`).
79
+ - **Before/after observations**: the same exact-reference rule as §8 (`resolveObservationByReference`), applied to `evaluation.before`/`evaluation.after`.
80
+
81
+ All five resolutions run in parallel (`Promise.all`) inside `src/viewerServer/evidence/evaluationView.ts#getEvaluationView`, each independently reporting `resolved`/`missing`/`ambiguous` - one missing/ambiguous linked artifact never blocks resolution of the others (proven in `tests/unit/linkedEvidenceServer.test.ts`: a missing baseline and a missing comparison are each tested independently while the rest of the evaluation view still resolves correctly).
82
+
83
+ ## 10. Comparison-view architecture
84
+
85
+ `src/viewerServer/evidence/comparisonView.ts#getComparisonView(root, handle)`: same handle-decode → contained-dir-resolve → re-stat → re-classify discipline as `observationView.ts`/`mediaResolver.ts` (Batches 2-3). Requires `family === 'comparison'` and `supportState === 'supported'` (else `404`/`409`), then resolves `artifact.before`/`artifact.after` via §8's rule. Route: `GET /api/comparisons/<handle>/view` → `{ok:true, before: LinkStatus, after: LinkStatus}`. The comparison's own full payload is **not** duplicated in this response - the browser fetches it separately through the existing, unchanged `GET /api/artifacts/<handle>` (Batch 2), and the resolved before/after `handle`s are fed straight into that same existing route for the source observations. This keeps the new route minimal/ephemeral and avoids inventing a second artifact-detail contract.
86
+
87
+ ## 11. Before/after reuse of Batch 3 components (task §35)
88
+
89
+ `viewer/src/components/ComparisonObservationPane.tsx` is built **entirely** from Batch 3's existing lower-level primitives: `useArtifactDetail` (unchanged), `orderedTargets` (unchanged), `TargetOverlaySvg` (one small additive change, below). No second screenshot-loading, coordinate-transform, or geometry-rendering implementation exists anywhere in Batch 4. The comparison's own persisted `relationshipsBefore`/`relationshipsAfter` are passed directly into `TargetOverlaySvg`'s existing `relationships: LayoutRelationshipGraph | undefined` prop - **`deriveLayoutRelationships` is never called to replace them** (grep-verified: no import of `deriveLayoutRelationships` exists in any Batch-4 file except the pre-existing Batch 3 `observationView.ts`, which Batch 4 does not modify).
90
+
91
+ `TargetOverlaySvg.tsx` gained one additive, optional prop: `highlightNames?: ReadonlySet<string> | undefined`, rendering an extra `--highlighted` CSS class on any rectangle whose name is in the set, alongside (never replacing) the existing single-select `selected`/`aria-pressed` interaction. This lets a relationship-subject difference or a two-target contract primitive (e.g. `targets-do-not-overlap`) emphasize both named targets without changing Batch 3's existing single-select contract or its passing tests (`npm run test:browser` re-run in full, §21 below - all pre-existing Batch 3 tests pass unmodified).
92
+
93
+ ## 12. Confirmation: `compareObservations` was **not** called for viewer reconstruction
94
+
95
+ Grep-verified across every Batch 4 file (`src/viewerServer/evidence/{linkedEvidence,comparisonView,evaluationView}.ts`, every `viewer/src/**` file touched this batch): zero imports of `compareObservations` or `comparisonEngine.js`. The persisted `ComparisonArtifact` (`differences`, `relationshipsBefore`/`After`, `comparability`, `configurationChanges`, `expectedDependencyEvidence`, `diagnostics`, `limits`) is read unchanged via the existing Batch 2 `GET /api/artifacts/<handle>` and rendered as-is in `ComparisonWorkspace.tsx`.
96
+
97
+ ## 13. Differences displayed (task §15)
98
+
99
+ All 13 canonical `DifferenceKind` values are rendered generically (kind/subject/before/after/delta, exactly as persisted, `JSON.stringify`'d for `before`/`after`/`delta` since their shape is difference-kind-dependent per the domain type's own design): `appeared`, `disappeared`, `moved`, `resized`, `visibility-changed`, `clipping-changed`, `containment-changed`, `horizontal-overflow-changed`, `vertical-overflow-changed`, `page-size-changed`, `scroll-owner-changed`, `relative-position-changed`, `relationship-changed`. No new PASS/FAIL severity is assigned anywhere - `ComparisonWorkspace.tsx`'s difference list is presentation-only.
100
+
101
+ ## 14. Difference target highlighting (task §16)
102
+
103
+ `subjectTargetNames(subject: ComparisonDifferenceSubject)`: `type: 'target'` → `[subject.target]`; `type: 'relationship'` → `[subjectTarget, relatedTarget].filter(defined)`; `type: 'page'` → `[]` (no target ever fabricated for a page-level subject). Clicking a difference calls `highlightSubject`, which sets the first name as the single interactive `selected` target and any remaining name(s) as `highlightNames`. Because `TargetOverlaySvg` only ever renders a rectangle for a target with real geometry in *that specific* observation, an `appeared` target (absent in `before`) simply has no rectangle in the Before pane, and a `disappeared` target (absent in `after`) has none in the After pane - **never fabricated**, proven with a real Chromium test (`comparisonEvaluationWorkspace.test.ts`, "appeared/disappeared target behavior") that asserts a rectangle count of exactly `0` on the absent side and `1` on the present side, for both targets.
104
+
105
+ ## 15. Comparability presentation (task §18)
106
+
107
+ `.comparability-banner--{comparable|comparable-with-warnings|incomparable}` renders `artifact.comparability.state` and every reason with its own severity class (`blocking`/`warning`/`unassessed`) and message unchanged. An `incomparable` result gets `role="alert"` and a red-bordered banner - visually unmistakable, proven with a real Chromium test against a genuinely `incomparable` fixture (two observations with different `requestConfig.viewport` producing a real `viewport-mismatch` blocking reason from `compareObservations` itself, never asserted/faked).
108
+
109
+ ## 16. Target configuration changes (task §19)
110
+
111
+ Rendered as their own `Configuration changes` section (`kind` + `target`), entirely separate from the `Differences` section that shows `appeared`/`disappeared` - the two lists never merge or share a component.
112
+
113
+ ## 17. Expected dependency evidence (task §20)
114
+
115
+ Rendered under the heading "Expected dependency evidence (explicit, non-causal)"; each entry shows `cause.target.property direction → effect.target.property direction: outcome`, where `outcome` is exactly one of `consistent`/`not-observed`/`contradictory-to-declaration`/`unavailable` - never rephrased as a pass/fail or causal claim.
116
+
117
+ ## 18. Comparison diagnostics and limits (task §21)
118
+
119
+ `artifact.limits.truncated === true` renders a visible, non-suppressible banner listing `omittedFields`/`omittedTargetPairs`; `artifact.diagnostics` (when non-empty) render as their own section. Neither is hidden behind a successful-looking default state.
120
+
121
+ ## 19. Contract/evaluation architecture
122
+
123
+ `src/viewerServer/evidence/evaluationView.ts#getEvaluationView(root, handle)` mirrors `comparisonView.ts` exactly, resolving all five linked references (§9) in parallel. Route: `GET /api/evaluations/<handle>/view` → `{ok:true, comparison, baseline, change, before, after}` (each a `LinkStatus`). `viewer/src/components/EvaluationWorkspace.tsx` fetches the evaluation's own already-loaded artifact (from `ArtifactPreview`'s existing `useArtifactDetail`) plus this view route, then calls `useArtifactDetail` four more times (baseline/change/comparison handles, plus reusing `ComparisonObservationPane` for before/after) - all through the same existing Batch 2 on-demand-loading contract, never a new artifact-fetching mechanism.
124
+
125
+ ## 20. Confirmation: `evaluateFrontendContract` was **not** called for viewer reconstruction
126
+
127
+ Grep-verified: zero imports of `evaluateFrontendContract` or `frontendContractEvaluation.js`'s evaluator export anywhere in Batch 4's `src/viewerServer/` or `viewer/src/` code (only `UnexpectedChangeResult`, a pure type, is imported for typing). `overallVerdict`, `clauseResults`, `activeBaselineClauseIds`, `supersededBaselineClauseIds`, and `unexpectedChanges` are the persisted artifact's own fields, read unchanged via `GET /api/artifacts/<handle>` and rendered as-is.
128
+
129
+ ## 21. Baseline clauses / change-scope / clause results / active-superseded / unexpected changes / overall verdict
130
+
131
+ - **Clause joining by exact `clauseId` only** (`viewer/src/components/ClauseResultRow.tsx`, `EvaluationWorkspace.tsx`): a `Map<clauseId, {source, clause}>` is built from the loaded baseline's `clauses` and the loaded change contract's `clauses`; each `clauseResults` entry is looked up by its own `clauseId` - never by target/primitive-shape/category/position/text similarity. An id present in neither loaded contract renders as an honest "unresolved clause definition" (never a fabricated category/primitive) - a distinct "Unresolved clause definitions" section exists specifically for this case.
132
+ - **Baseline clauses** are shown in their own section, distinctly from per-change categories, each tagged `active` or `superseded` **exclusively from the evaluation artifact's own `activeBaselineClauseIds`/`supersededBaselineClauseIds`** - never recomputed from clause-id overlap or any other inference.
133
+ - **Per-change clauses** are grouped into four sections in the frozen category order (`requested`, `expected-dependent`, `protected`, `preserved`); `expected-dependent` clauses additionally show their `required`/`permitted` mode without merging the two modes' meaning.
134
+ - **Clause result status** (`pass`/`fail`/`unavailable`/`conflict`) is preserved exactly via `statusLabel()` - `unavailable` always shows its `reason`, `conflict` always shows its `reason` and `conflictingClauseIds`; neither is ever collapsed to a boolean or converted to `fail`.
135
+ - **Unexpected changes** render in their own section, `classification: 'unexpected'` preserved verbatim, with target highlighting from the same `subjectTargetNames`-style extraction as ordinary differences (since `UnexpectedChangeResult.subject` reuses `ComparisonDifferenceSubject` unchanged - confirmed in `frontendContractEvaluation.ts`).
136
+ - **Overall verdict**: `artifact.overallVerdict` renders directly in a large `.overall-verdict--PASS`/`.overall-verdict--FAIL` banner - the UI performs no verdict computation of its own anywhere in this batch's code (grep-verified: no `every(...status==='pass')`-style logic exists in `EvaluationWorkspace.tsx`).
137
+
138
+ ## 22. Target cross-highlighting from contract primitives (task §34)
139
+
140
+ `viewer/src/contract/clauseTargets.ts#primitiveTargetNames` is an exhaustive `switch` over every `ContractPrimitiveKind`: single-target primitives (`target-visible`, `target-not-clipped`, `target-width-within-bound`, `target-does-not-own-scroll`, `target-begins-below-initial-viewport`, `property-unchanged-within-tolerance`, `property-increases`, `property-decreases`) return `[target]`; two-target primitives (`targets-do-not-overlap`, `target-wider-than`, `target-follows-vertically`) return `[targetA, targetB]`; `target-fits-inside` returns `[target, container]`; `relationship-unchanged` returns `[subjectTarget, relatedTarget].filter(defined)`; the two page-level primitives (`document-width-fits-viewport`, `scroll-owner-is-document`) return `[]` - clicking their clause row is disabled (`names.length === 0` → `disabled`) rather than highlighting an invented target.
141
+
142
+ ## 23. All-pass proof (task §33/§44 Case A)
143
+
144
+ **Fixture**: `tests/support/evidenceFixtures.ts#writeAllPassPipelineFixture` - the deliberate all-pass counterpart to the existing `writeFullPipelineFixture`, differing only in the *actual rendered geometry/style* fed to the real `compareObservations`/`evaluateFrontendContract` workflow (via `evaluateAndPersistFromArtifactRoots`): `rightAd` genuinely unchanged (protected clause genuinely satisfied) and `navigation` genuinely never clipped (`scrollWidth === clientWidth`, preserved clause genuinely satisfied). Verified directly against the real evaluator's output before use (§30 below) - `overallVerdict: "PASS"`, all four clauses `pass`. `overallVerdict` is never hand-edited.
145
+
146
+ **Real-browser proof**: `tests/browser/comparisonEvaluationWorkspace.test.ts`, "Case A" describe block - selects the evaluation, confirms `.overall-verdict--PASS`, confirms both before/after screenshots load through the real `/api/media/observation:...` endpoint, confirms `requested-nav`/`protected-rightad`/`preserved-nav-unclipped` are each `.clause-row--pass`, and confirms clicking the `requested-nav` clause row highlights the exact `navigation` target (`aria-pressed="true"` on its real SVG rectangle).
147
+
148
+ ## 24. Safety-failure proof (task §32/§44 Case B)
149
+
150
+ **Fixture**: the existing `writeFullPipelineFixture` (reused unchanged, not a new fixture) - already produces, through the real canonical evaluator, `overallVerdict: "FAIL"` with `requested-nav: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail` (confirmed by direct inspection before use, §30). This is exactly the required safety case: **a requested/local change passes while a protected clause and a preserved clause both fail, and the overall verdict is FAIL** - never hand-constructed.
151
+
152
+ **Real-browser proof**: `tests/browser/comparisonEvaluationWorkspace.test.ts`, "Case B" describe block - selects the evaluation, confirms `.overall-verdict--FAIL`, confirms `requested-nav` is `.clause-row--pass` while `protected-rightad` and `preserved-nav-unclipped` are both `.clause-row--fail`, confirms before/after visual context remains fully available (both screenshots render) despite the overall failure, and confirms clicking the failing `protected-rightad` clause highlights the exact `rightAd` target.
153
+
154
+ ## 25. API changes
155
+
156
+ | Route | Method | Semantics |
157
+ |---|---|---|
158
+ | `GET /api/comparisons/<handle>/view` | GET/HEAD | `{ok:true, before: LinkStatus, after: LinkStatus}`. `404` unknown handle, `409` not-a-comparison/not-currently-loadable, `405` any write method. |
159
+ | `GET /api/evaluations/<handle>/view` | GET/HEAD | `{ok:true, comparison, baseline, change, before, after}` (each a `LinkStatus`). `404`/`409`/`405` as above. |
160
+
161
+ `/api/status`, `/api/index`, `/api/artifacts/<handle>`, `/api/media/<handle>/<role>`, `/api/observations/<handle>/relationships` are byte-for-byte unchanged.
162
+
163
+ ## 26. PWA cache boundary
164
+
165
+ **PASS.** Both new routes live under `/api/`, already covered by Batch 1's `navigateFallbackDenylist: [/^\/api\//]` - no service-worker configuration change was needed. `tests/unit/viewerPwaBuild.test.ts` gained one explicit assertion against the real built `sw.js`: still exactly one `registerRoute` call, and the precache manifest contains neither `/api/comparisons` nor `/api/evaluations`.
166
+
167
+ ## 27. Files created
168
+
169
+ - `src/viewerServer/evidence/linkedEvidence.ts`, `comparisonView.ts`, `evaluationView.ts`
170
+ - `viewer/src/components/ComparisonObservationPane.tsx`, `ComparisonWorkspace.tsx`, `EvaluationWorkspace.tsx`, `ClauseResultRow.tsx`
171
+ - `viewer/src/contract/clauseTargets.ts`
172
+ - `viewer/src/hooks/useLinkedEvidence.ts`
173
+ - `viewer/src/types/comparison.ts`, `contracts.ts`
174
+ - `tests/unit/linkedEvidenceServer.test.ts`
175
+ - `tests/browser/comparisonEvaluationWorkspace.test.ts`
176
+ - `docs/reports/v0.8-comparison-contract-inspection-batch4.md` (this file)
177
+
178
+ ## 28. Files modified
179
+
180
+ - `src/viewerServer/httpServer.ts` - added the two new routes (§25); `/api/status`, `/api/index`, `/api/artifacts/<handle>`, `/api/media/<handle>/<role>`, `/api/observations/<handle>/relationships`, and static-asset serving unchanged.
181
+ - `viewer/src/components/TargetOverlaySvg.tsx` - one additive, optional `highlightNames` prop (§11); existing `selected`/`onSelect`/`toggles` behavior unchanged.
182
+ - `viewer/src/components/ArtifactPreview.tsx` - branches to `ComparisonWorkspace`/`EvaluationWorkspace` for their families; every other family's raw-JSON preview unchanged.
183
+ - `viewer/src/styles/index.css` - additive rules for the new workspaces.
184
+ - `tests/support/evidenceFixtures.ts` - added `writeAllPassPipelineFixture`, `writeBaselineSupersessionFixture`, `writeAppearedDisappearedComparisonFixture`, `writeIncomparableComparisonFixture` (all built through the real canonical `compareObservations`/`evaluateFrontendContract` workflow, never hand-edited results); `buildObservation` gained optional `pageEvidence`/`viewport` parameters (already added in Batch 3, unchanged here).
185
+ - `tests/browser/viewerEvidenceShell.test.ts` - two pre-existing Batch 2 tests that selected a `comparison` record to exercise the *generic* raw-JSON-preview/no-visualization path now legitimately conflict with Batch 4's real comparison workspace; both were repointed to `baseline-contract` (which still exercises exactly the generic path/invariant they were written to protect) - the same kind of sanctioned evolution as Batch 1→2's placeholder-text update, Batch 2's `TST-401`, and Batch 3's analogous `viewerEvidenceShell.test.ts` update.
186
+ - `tests/unit/viewerPwaBuild.test.ts` - added the explicit Batch 4 cache-boundary assertion (§26).
187
+ - `docs/ARCHITECTURE.md`, `docs/COMMANDS.md` - new/updated Batch 4 sections (condensed versions of §7-§22 above).
188
+
189
+ ## 29. Tests added/modified and behavior protected
190
+
191
+ | Test file | Level | Protects |
192
+ |---|---|---|
193
+ | `linkedEvidenceServer.test.ts` (15 tests) | unit/integration (real HTTP) | Exact before/after resolution; honest `missing` when a source observation is removed; honest `ambiguous` with a real duplicated-identity fixture (count=2); 409 for a non-comparison/non-evaluation handle; 404 unknown handle; 405 write methods; exact resolution of all five evaluation links; missing baseline reported honestly; missing comparison reported honestly; the all-pass fixture is a genuine `PASS` with every clause `pass`; the full-pipeline fixture is a genuine `FAIL` with the exact required requested-pass/protected-fail/preserved-fail signature; the baseline-supersession fixture reports real active/superseded ids and a real unexpected change. |
194
+ | `comparisonEvaluationWorkspace.test.ts` (4 tests, real Chromium) | browser | Case A (all-pass) full proof (§23); Case B (safety failure) full proof (§24); appeared/disappeared honest rectangle presence/absence; incomparable state visibly unmistakable with its real blocking reason. |
195
+ | `viewerEvidenceShell.test.ts` (2 updated) | browser | Generic Batch 2 on-demand-load/no-visualization invariants still hold for families without a Batch 3/4 workspace. |
196
+ | `viewerPwaBuild.test.ts` (+1) | build/integration | New routes covered by the existing `/api/` denylist, no new runtime-caching rule. |
197
+
198
+ ## 30. A methodology note: fixtures verified against the real evaluator before use
199
+
200
+ Before writing any assertion against `writeAllPassPipelineFixture`, `writeFullPipelineFixture` (reused), or `writeBaselineSupersessionFixture`, each was run once through the real `readFrontendContractEvaluationArtifact` reader in an isolated scratch test and its actual `overallVerdict`/`clauseResults`/`activeBaselineClauseIds`/`supersededBaselineClauseIds` were inspected directly (not assumed) - this is how the exact safety-case signature (`requested-nav: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail`, `overallVerdict: "FAIL"`) in the pre-existing `writeFullPipelineFixture` was *discovered*, not designed - it already existed from Batch 2/3's fixture reuse and turned out to exactly match Batch 4's required safety case. The scratch inspection tests were deleted before finalizing (never committed); this report documents the methodology rather than leaving throwaway files behind.
201
+
202
+ ## 31. Validation results
203
+
204
+ | Command | Result |
205
+ |---|---|
206
+ | `npm run typecheck` | **PASS** (zero errors, both `tsconfig.json` and `viewer/tsconfig.json`) |
207
+ | `npm run lint` | **PASS** (zero errors/warnings) |
208
+ | `npm test` | **PASS** — 1101/1101 tests, 60/60 files |
209
+ | `npm run build` | **PASS** — unchanged Node/library output plus `dist/viewerServer/evidence/{comparisonView,evaluationView,linkedEvidence}.js` and the rebuilt `dist/viewer/**` PWA |
210
+ | `npm run check:docs` | **PASS** — "Documentation check passed (17 required files)." |
211
+ | `npm run test:browser` | **PASS** — 141/141 tests, 14/14 files (real Chromium; run in full per task §46) |
212
+ | `git diff --check` | **PASS** — no whitespace errors (only expected LF→CRLF notices) |
213
+
214
+ ## 32. Built viewer smoke (task §47)
215
+
216
+ Fixture: one real before/after observation pair, a real comparison, a real baseline contract, a real per-change contract (the "milestone signature" clause set), and a real evaluation - all built via a one-off script (not committed, `$WORKFLOW_ROOT\tmp`) calling the actual compiled `dist/artifacts/*.js`/`dist/application/frontendContractEvaluationService.js` modules directly, under `$WORKFLOW_ROOT\smoke\evidence-root`.
217
+
218
+ Command: `node dist/cli.js view --root "<WORKFLOW_ROOT>\smoke\evidence-root" --port 4319 --no-open`
219
+
220
+ All required checks passed against the real running built server:
221
+
222
+ - `/api/status` → `200`; `/api/index` → all 6 real records (`observation` x2, `comparison`, `baseline-contract`, `change-contract`, `contract-evaluation`), all `supported`.
223
+ - `/api/comparisons/<handle>/view` → `200`, both `before`/`after` `resolved` to the exact real observation handles.
224
+ - `/api/evaluations/<handle>/view` → `200`, all five links (`comparison`/`baseline`/`change`/`before`/`after`) `resolved`.
225
+ - `/api/artifacts/<evaluation-handle>` → `overallVerdict: "FAIL"`, `clauseResults`: `requested-nav: pass`, `expected-workspace: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail` - the exact required safety signature, produced by the real canonical workflow, not edited.
226
+ - Both before/after screenshots → `200 image/png`.
227
+ - Unknown comparison/evaluation handles → `404`; write method (`POST`) → `405`.
228
+ - PWA still loads (`/`, `/sw.js` both `200`); `sw.js` contains no `api/comparisons`/`api/evaluations` reference.
229
+ - `netstat` confirmed `127.0.0.1:4319` only, never `0.0.0.0`.
230
+ - Server located by its real PID and terminated with `taskkill /F`; a follow-up `netstat` confirmed the port was released.
231
+ - A post-shutdown listing of the smoke evidence root shows exactly the 8 fixture files the script wrote - no stray writes, no modification.
232
+
233
+ **Result: PASS.** Logs retained under `$WORKFLOW_ROOT\logs\` (`smoke-server.log`, `index-response.json`, `eval-detail.json`, `smoke-checks-1.log`, `smoke-checks-2.log`).
234
+
235
+ ## 33. Generated path inventory
236
+
237
+ | Path | Disposition |
238
+ |---|---|
239
+ | `WORKFLOW_ROOT\cache\npm` | Retained (npm cache from the my-dev-kit-index `npx` invocation) |
240
+ | `WORKFLOW_ROOT\tmp\vite-cache`, `WORKFLOW_ROOT\tmp\build-smoke-comparison.mjs` | Retained (build cache empty again, same finding as every prior batch; the smoke-fixture script is dev/readiness tooling only, not committed) |
241
+ | `WORKFLOW_ROOT\logs\*` | Retained (smoke evidence, §32) |
242
+ | `WORKFLOW_ROOT\smoke\evidence-root` | Retained (real evidence fixture tree built for the smoke test) |
243
+ | `WORKFLOW_ROOT\my-dev-kit-index\*` | Retained (successful index + cache-metadata) |
244
+ | `WORKFLOW_ROOT\fixtures` | Retained, empty/unused (no committed deterministic repository fixture was needed - all Batch 4 fixtures are built programmatically via `tests/support/evidenceFixtures.ts`, matching the existing repository convention) |
245
+ | Repo-root `dist/` | Ordinary build output (gitignored); rebuilt cleanly by `scripts/clean.mjs` on every `npm run build` |
246
+ | Pre-existing repo-root `.my-dev-kit*`/`baselines`/`comparisons`/`contracts`/`evaluations`/`observations` | Pre-existing, empty, untouched (same finding as every prior batch) |
247
+ | Sibling `...my-frontend-observer.my-dev-kit-workflow\v0.8\{batch-01,batch-02,batch-03}` | Untouched (verified, §3) |
248
+
249
+ ## 34. Repository pollution check
250
+
251
+ **PASS.** `git status --short` before staging showed only the 9 modified + 13 new Batch-4-owned paths listed in §27/§28. No unexpected file or directory appeared anywhere in the repository. No malformed sibling Batch 4 workflow path exists anywhere under `Z:\Users\newuser\Projects\` (verified directly, §3).
252
+
253
+ ## 35. Batch 1/2/3 regression check
254
+
255
+ **PASS.** All Batch 1 tests (`view` CLI, `127.0.0.1`/port `4319`, PWA shell/installability, cache boundary, server cleanup), Batch 2 tests (evidence indexing, unsupported-version handling, safe handles, on-demand artifact/media loading, filesystem containment), and Batch 3 tests (observation SVG workspace, coordinate mapping, relationship reuse, unresolved-target honesty) pass unmodified except the two `viewerEvidenceShell.test.ts` updates described in §28, which preserve the exact invariants they originally protected while accounting for Batch 4's legitimate new comparison-family behavior.
256
+
257
+ ## 36. v0.1-v0.7 regression check
258
+
259
+ **PASS.** Every pre-v0.8 unit and browser test suite (`observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, `evaluate-reference-fidelity`) remains covered and passing - none was touched by this batch's diff. `src/domain/`, `src/artifacts/*Writer.ts`, and every existing reader/engine (`compareObservations`, `evaluateFrontendContract`, `deriveLayoutRelationships`) are byte-for-byte unchanged.
260
+
261
+ ## 37. Deviations
262
+
263
+ - The task's §5 "Required Batch 4 root" restatement conflicted with its own `Join-Path` algorithm and verification assertions; resolved per §2 above by following the literal, executable, self-verifying algorithm. This is the only deviation from the task's literal text, and it was necessary to satisfy the task's own stated assertions rather than an arbitrary choice.
264
+ - No other deviations. Every other task step (predecessor-report inspection, my-dev-kit retrieval, the coordinate/contract audits, all required test categories, the full validation chain including `test:browser`, and the built-CLI smoke) was executed as specified.
265
+
266
+ ## 38. Remaining uncovered risks
267
+
268
+ - **`highlightNames` only distinguishes "the primary selected target" from "secondary highlighted targets" visually (different CSS classes), not through `aria-pressed`** (which remains true only for the single `selected` target, matching Batch 3's existing accessibility contract). A screen-reader user selecting a relationship-subject difference will not hear the second (related) target announced as selected, only visually distinguished. Not a regression (Batch 3 never had multi-target selection at all), but a genuine accessibility gap for the new multi-target case specifically.
269
+ - **`resolveObservationByReference`/`resolveComparisonByIdentity`/etc. each independently re-walk the bounded evidence tree** (same pattern as Batch 2's `findImportedReferenceDir`, carried forward deliberately for consistency rather than introducing a new caching layer). An evaluation view triggers five such walks in parallel; for an evidence root near the `MAX_MANIFEST_CANDIDATES` bound, this is more filesystem work per evaluation-view request than a single shared index pass would need. Not a correctness risk; the same category of "report rather than pre-optimize" risk Batch 2 already recorded.
270
+ - **`ComparisonWorkspace`/`EvaluationWorkspace` do not yet offer a synchronized zoom/pan or a locked view between before/after panes** - explicitly out of scope for this batch (task §42), planned for a later batch.
271
+ - Batches 1-3's previously reported risks (install-prompt "available" branch untested in headless Chromium, no live service-worker execution test) remain unresolved and out of this batch's scope.
272
+
273
+ ## 39. Out-of-scope confirmation
274
+
275
+ Confirmed absent from this batch's diff: external-reference image display, reference regions, reference/candidate side-by-side mode, reference applicability UI, reference fidelity UI, reference/runtime binding, explicit-binding cross-selection, image zoom/pan, synchronized view lock, bounded-agent-context UI, runtime/static correlation UI, annotation, contract editing, baseline approval, contract approval, reference approval, source editing, automatic binding, automatic visual analysis, pixel difference, cloud hosting, database, authentication, collaboration.
276
+
277
+ ## 40. Final verdict
278
+
279
+ Batch 4 ("Before/after comparison and contract/change-scope inspection") is implemented and independently validated: a developer can select an existing `ComparisonArtifact`, see its exact persisted before/after observations resolved by canonical identity and displayed side by side through the reused Batch 3 screenshot/SVG machinery, inspect every canonical difference/relationship-change/comparability/dependency-evidence category exactly as persisted, select an existing `FrontendContractEvaluationArtifact`, see its exact linked baseline/per-change contracts/comparison/observations resolved by canonical identity, inspect every clause result (baseline active/superseded, requested/expected-dependent/protected/preserved, unexpected) joined by exact `clauseId`, and see the authoritative overall `PASS`/`FAIL` verdict rendered unmistakably - including the required safety case where a requested change passes while a protected and a preserved clause both fail, proven against a real canonically-evaluated fixture in real Chromium. `compareObservations` and `evaluateFrontendContract` were never invoked for viewer reconstruction anywhere in this batch. No Batch 5+ visualization, no v0.9 annotation, and no release/publication action was taken. Package version remains `0.7.0`.
@@ -0,0 +1,247 @@
1
+ # v0.8 Batch 2 — Evidence Indexing, Canonical Readers, and Lazy Data Boundary — Implementation Report
2
+
3
+ ## 1. Starting state
4
+
5
+ - Starting branch: `master`
6
+ - Starting HEAD: `3fb9df9db9335bb1efbd1cd90c00dfb0dc3cd41d` ("feat: add v0.8 viewer runtime and PWA foundation")
7
+ - `origin/master` HEAD after `git fetch`: `a1de8ac01e1367b60021cb04226f56369fa2debb`
8
+ - `git rev-list --left-right --count origin/master...HEAD`: `0 1` — local is exactly one commit (Batch 1) ahead of origin, no divergence. Per task §4, no pull/reset/merge was performed.
9
+ - Starting `git status --short`: clean.
10
+ - Package version confirmed `0.7.0` throughout; never bumped.
11
+
12
+ ## 2. Batch 1 report inspected
13
+
14
+ Read the complete local `docs/reports/v0.8-viewer-runtime-pwa-batch1.md` (not just its console summary). Confirmed and reused without modification:
15
+
16
+ - host `127.0.0.1`, fixed default port `4319` (`src/viewerServer/port.ts`);
17
+ - server owner module `src/viewerServer/httpServer.ts` (`createViewerServer`/`handleRequest`) and application seam `src/viewerServer/viewerService.ts` (`startViewer`);
18
+ - `/api/status` as the sole Batch 1 route, with an existing `pathname.startsWith('/api/')` fallback that Batch 2 routes were inserted before;
19
+ - CLI dispatch ownership (`src/cli.ts` `runViewCommand`/`parseViewArgs`) - unchanged this batch;
20
+ - PWA cache boundary: `viewer/vite.config.ts`'s `workbox.navigateFallbackDenylist: [/^\/api\//]`, which - being a prefix match on `/api/` - already covers every new Batch 2 route without modification;
21
+ - generated-path convention (`WORKFLOW_ROOT` sibling directory, `npm_config_cache`/`VITE_CACHE_DIR` redirection);
22
+ - Batch 1's own unresolved risks (install-prompt "available" branch untested in headless Chromium; no live service-worker execution test) - unrelated to Batch 2, not re-litigated.
23
+
24
+ No contradiction between the report and the task's Batch 1 summary was found.
25
+
26
+ ## 3. Frozen planning authority inspected
27
+
28
+ Read (or re-confirmed already-cached knowledge of, where noted) in order:
29
+
30
+ 1. `docs/DOCUMENTATION_PRESERVATION_POLICY.md` (previously read, unchanged)
31
+ 2. `docs/PROJECT_MILESTONES.md` (Milestone 8, previously read in full)
32
+ 3. `docs/ROADMAP.md` (v0.8 section, previously read)
33
+ 4. `docs/plans/v0.8-implementation-plan.md` Batch 2 section (`### Batch 2 — Evidence indexing, canonical readers, and lazy data boundary`) plus cross-batch invariants (§7)
34
+ 5. `docs/reports/v0.8-viewer-runtime-pwa-batch1.md` (§2 above)
35
+ 6. `docs/ARCHITECTURE.md` (relevant sections, then edited)
36
+ 7. `docs/CONTRACTS.md` (fully, in two paginated reads - artifact-kind/schema-version constants, directory/manifest-filename conventions, and the v0.7 external-reference media-ownership contract)
37
+ 8. `docs/COMMANDS.md` (`view` section, then edited)
38
+ 9. `docs/WORKFLOWS.md` (bounded-agent-context and external-reference workflow sections)
39
+ 10. `package.json`, `src/viewerServer/httpServer.ts`, `src/viewerServer/viewerService.ts`, `src/viewerServer/port.ts`
40
+ 11. `src/artifacts/artifactReader.ts`, `comparisonArtifactReader.ts`, `externalReferenceArtifactReader.ts`, `frontendContractArtifactReader.ts`, `frontendContractEvaluationArtifactReader.ts`, `types.ts`
41
+ 12. `src/artifacts/externalReferenceArtifactWriter.ts` (directory/media-ownership shape)
42
+ 13. `src/domain/schema.ts`, `comparison.ts`, `frontendContracts.ts`, `frontendContractEvaluationArtifact.ts`, `externalReference.ts`, `boundedAgentContext.ts`, `evidence.ts`, `completion.ts`
43
+ 14. `tests/unit/cliFrontendContracts.test.ts` (fixture-construction pattern reused for Batch 2 test fixtures), `tests/unit/diagnostics.test.ts`, `tests/unit/externalReferenceImageFixtures.ts`
44
+
45
+ Confirmed the frozen plan is unchanged and Batch 2's title/scope match the task exactly.
46
+
47
+ ## 4. my-dev-kit retrieval
48
+
49
+ Unlike Batch 1, this step was **run**, not skipped. Index built successfully:
50
+
51
+ ```
52
+ npx @dailephd/my-dev-kit@latest index --root . --src src --src tests --src viewer \
53
+ --out "<WORKFLOW_ROOT>\my-dev-kit-index" --call-graph --json
54
+ ```
55
+
56
+ Result: `status: "complete"`, 73 files indexed. Ran all five required searches (reader ownership, artifact-write ownership, bounded-agent-context persistence, screenshot/media ownership, viewer server routes, and artifact-kind/schema-version constants). Every result matched direct source inspection exactly - in particular, the `BoundedAgentContextArtifact` search returned only `src/domain/boundedAgentContext.ts` and application-service files, **no** `boundedAgentContextArtifactReader.ts`/`Writer.ts` - corroborating (not merely asserting) that no disk reader/writer exists for that family. No `lookup`/`source`/`slice` follow-up was needed since search results were unambiguous. `MY_DEV_KIT_INDEX` = `Z:\Users\newuser\Projects\my-frontend-observer.my-dev-kit-workflow\v0.8\batch-02\my-dev-kit-index`.
57
+
58
+ ## 5. Evidence-family inventory
59
+
60
+ | Family | Kind constant | Schema | Persisted? | Writer | Reader (existing) | Discriminant | Media owned/referenced |
61
+ |---|---|---|---|---|---|---|---|
62
+ | Observation | `my-frontend-observer/observation` | `1.2.0` | Yes | `artifactWriter.ts` | `artifactReader.ts#readObservationArtifact` | - | owns `screenshot.png` (via `screenshot: EvidenceField<{path}>`) |
63
+ | Comparison | `my-frontend-observer/comparison` | `1.0.0` | Yes | `comparisonArtifactWriter.ts` | `comparisonArtifactReader.ts#readComparisonArtifact` | - | none (references `before`/`after` observation ids only) |
64
+ | Persistent baseline contract | `my-frontend-observer/frontend-contract` | `1.0.0` | Yes | `frontendContractArtifactWriter.ts#writePersistentBaselineContract` | `frontendContractArtifactReader.ts#readPersistentBaselineContract` | `contractClass: 'baseline'` | none |
65
+ | Per-change contract | `my-frontend-observer/frontend-contract` | `1.0.0` | Yes | `...#writePerChangeContract` | `...#readPerChangeContract` | `contractClass: 'change'` | none |
66
+ | Contract evaluation | `my-frontend-observer/frontend-contract-evaluation` | `1.0.0` | Yes | `frontendContractEvaluationArtifactWriter.ts` | `frontendContractEvaluationArtifactReader.ts` | - | none |
67
+ | External reference (imported) | `my-frontend-observer/external-reference` | `1.0.0` | Yes | `externalReferenceArtifactWriter.ts` | `externalReferenceArtifactReader.ts#readExternalReferenceArtifact` | `lifecycle.state: 'imported'` | owns `image.<ext>` (bare filename) |
68
+ | External reference (approved) | `my-frontend-observer/external-reference` | `1.0.0` | Yes | same | same | `lifecycle.state: 'approved'` | references, never copies, the imported artifact's image via `sourceReference.{referenceId, image}` |
69
+ | Bounded agent context | `my-frontend-observer/bounded-agent-context` | `1.0.0` | **No** | none | none | - | n/a |
70
+
71
+ Every family (all filename `manifest.json`, one artifact directory per instance) belongs in Batch 2 discovery **except** bounded-agent-context, which - per `docs/CONTRACTS.md` "v0.6 bounded agent context and correlation contract" ("there is no disk artifact writer/reader... it is a pure programmatic contract and derivation layer") and confirmed by my-dev-kit search (§4) - has no persisted filesystem representation to discover. Per task §8/§21, no persistence was invented for it; a manifest declaring that kind is classified `unrecognized-kind` (test: `evidenceDiscovery.test.ts` "a bounded-agent-context-kind manifest is treated as unrecognized").
72
+
73
+ ## 6. Existing readers reused (no new readers were needed)
74
+
75
+ All five persisted families already had a canonical reader before this batch (`readObservationArtifact`, `readComparisonArtifact`, `readPersistentBaselineContract`, `readPerChangeContract`, `readFrontendContractEvaluationArtifact`, `readExternalReferenceArtifact`) - confirmed by direct inspection (§3) and my-dev-kit search (§4). **No new reader was added.** `src/viewerServer/evidence/classify.ts` calls these six functions unchanged; it never re-implements JSON parsing or structural validation.
76
+
77
+ ## 7. Canonical validators reused
78
+
79
+ Every classification decision for a "supported" or "invalid-structure" result comes from the family's own existing validator, invoked transitively by its reader (`isValidObservationArtifact`, `isValidComparisonArtifact`, `isValidPersistentBaselineContract`, `isValidPerChangeContract`, `isValidFrontendContractEvaluationArtifact`, `isValidExternalReferenceArtifact`). No second/parallel validator was introduced anywhere in `src/viewerServer/evidence/`. For the shared `frontend-contract` kind, `classify.ts` tries `readPersistentBaselineContract` then `readPerChangeContract` in sequence - the **existing validators**, not classifier-owned logic, decide which (if either) a candidate actually is.
80
+
81
+ ## 8. Index bounds selected (and rationale)
82
+
83
+ Defined in `src/viewerServer/evidence/limits.ts`, chosen after inspecting that every current writer produces a shallow `<outputLocation>/<id>/manifest.json` shape (§5) rather than a deep tree:
84
+
85
+ - `MAX_DISCOVERY_DEPTH = 6` (typical real shape needs depth 2; generous slack for a nested `--output`, still bounded);
86
+ - `MAX_DIRECTORIES_VISITED = 2000`;
87
+ - `MAX_MANIFEST_CANDIDATES = 1000` (candidate `manifest.json` files classified);
88
+ - `MAX_INDEX_RECORDS = 500` (metadata records returned in one `/api/index` response);
89
+ - `MAX_MANIFEST_CANDIDATE_BYTES = 2,000,000` - a candidate exceeding this is never read into memory; classified `unreadable` instead. Real manifests are small (identity/geometry/config JSON only; screenshots/images are always separate sibling files - §5), so this is generous headroom, not a tight fit.
90
+
91
+ `discoverManifests` opens **only** files literally named `manifest.json` - no other filename is ever read or classified, regardless of content (even valid JSON), satisfying "do not treat every `manifest.json` as valid Observer evidence... do not treat arbitrary JSON files as viewer artifacts" without needing a broader allow/deny list.
92
+
93
+ ## 9. Symlink/junction policy
94
+
95
+ `discovery.ts#walk` checks `dirent.isSymbolicLink()` on every entry (file or directory) via `readdir(dir, {withFileTypes:true})` and skips it entirely - never followed, never opened. Verified with a real symlinked directory pointing outside the evidence root (`evidenceDiscovery.test.ts` "never follows a directory symlink out of the evidence root"; the test degrades gracefully rather than failing if symlink creation requires elevated privileges in a given Windows configuration - confirmed working in this environment). Since symlinks are never followed, no separate `realpath`-based re-containment check was needed for the walk itself; `resolveContainedDir`/`resolveContainedFile` (§13) provide that containment guarantee independently for handle-driven (not walk-driven) access.
96
+
97
+ ## 10. Viewer handle design
98
+
99
+ A handle is `<family-slug>:<percent-encoded root-relative directory path>` (`evidence/handles.ts`) - e.g. `observation:observations%2Fsmoke-obs-before`. It is "server-issued" only in the sense that it is exactly what discovery/classification already produced; it grants no filesystem access by itself. **Every** route that accepts a handle (`/api/artifacts/<handle>`, `/api/media/<handle>/<role>`) independently decodes it, re-resolves it against the evidence root via `resolveContainedDir` (re-checking containment), and re-classifies that one candidate through the same canonical-reader dispatch the index uses - it never trusts a cached record or a client-supplied path string. A handle whose backing directory no longer exists, or whose re-classified family no longer matches what the handle claims, fails closed as "unknown handle" (404), never served as stale.
100
+
101
+ ## 11. API routes and semantics
102
+
103
+ | Route | Method | Semantics |
104
+ |---|---|---|
105
+ | `GET /api/status` | GET/HEAD | Unchanged from Batch 1. |
106
+ | `GET /api/index` | GET/HEAD | Bounded, metadata-only, deterministic (rebuilt fresh every call - no persistent cache). |
107
+ | `GET /api/artifacts/<handle>` | GET/HEAD | Known handle only; one canonical-reader read; on demand; no schema upgrade; no mutation. `200` supported / `404` unknown handle / `409` handle resolves but is not currently loadable (unsupported-version, invalid-structure, malformed-json, unreadable - carries the same honest `metadata` the index would show). |
108
+ | `GET /api/media/<handle>/<role>` | GET/HEAD | Known handle + known role only; root/artifact-contained; on demand; streamed. `200` / `404` for any failure (unknown handle, unknown/inapplicable role, missing file). |
109
+
110
+ All four reject every write method (`POST`/`PUT`/`PATCH`/`DELETE`) with `405 Method Not Allowed` (enforced once, ahead of all routing, in `handleRequest` - unchanged Batch 1 mechanism now covers the new routes too; verified in `viewerEvidenceServer.test.ts`).
111
+
112
+ ## 12. Viewer adapter architecture
113
+
114
+ `src/viewerServer/evidence/projection.ts`:
115
+
116
+ - `EvidenceMetadataRecord` - the `/api/index` shape: `handle`, `family`, `supportState`, `relativeDir`, and (only when meaningful for that family/state) `logicalId`, `schemaVersion`, `foundSchemaVersion`, `producerVersion`, `completion`, `lifecycleState`, `contractClass`, `overallVerdict`, `comparability`, `media` (availability summary only, never bytes), `relatedIds`, `message`. Verified to exclude full-payload fields (`targetEvidence`, `pageEvidence`, `requestConfig`) via a direct serialization assertion.
117
+ - `EvidenceArtifactDetail` - the `/api/artifacts/<handle>` shape: the already-validated domain object wrapped with `handle`/`family`. Selects/wraps existing canonical fields only; derives nothing new, recomputes nothing, persists nothing (no `viewer.json`/viewer-manifest/cache artifact of any kind exists anywhere in this batch).
118
+
119
+ Both are ephemeral: constructed fresh per request, held only in memory for the duration of that request/response.
120
+
121
+ ## 13. Absolute-path exposure policy
122
+
123
+ Metadata records expose only a root-relative `relativeDir` (POSIX-normalized), never an absolute filesystem path. `/api/status`'s pre-existing `root` field (Batch 1, unchanged) remains the only place an absolute path is echoed back, and only as an opaque display string the server itself supplied - never accepted as input from the browser.
124
+
125
+ ## 14. Media ownership/resolution behavior
126
+
127
+ `src/viewerServer/evidence/mediaResolver.ts` recognizes exactly three roles: `screenshot` (observation only), `image` (imported external reference only), `source-image` (approved external reference only) - any other role, or a role requested against a family it doesn't apply to, is rejected (`404`). Every resolved path goes through `resolveContainedFile`, which additionally rejects any filename containing a path separator outright (every real media reference is contractually a bare filename - `docs/CONTRACTS.md` "v0.7 Prompt 1" `ExternalReferenceImageReference.path`).
128
+
129
+ ## 15. Approved external-reference source-image behavior
130
+
131
+ Confirmed via `docs/CONTRACTS.md`/`externalReference.ts` (§3/§5) and proven by a real fixture (`writeExternalReferencePairFixture`, using the actual `importExternalReference`/`approveExternalReference` application services): an approved artifact's directory contains **only** `manifest.json` - no image file. `source-image` resolution reads the approved artifact's `sourceReference.referenceId`, then calls `evidence/index.ts#findImportedReferenceDir` - a bounded search (reusing the same discovery+classify pipeline, capped at `MAX_INDEX_RECORDS`) for the **imported** artifact whose own `referenceId` matches - and only then resolves `sourceReference.image.path` against *that* artifact's real directory. Never assumes co-location. Two tests prove this exactly: "resolves an approved reference's source-image through the owning imported artifact, never assuming co-location" (success case, real cross-artifact resolution) and "an approved reference whose owning imported artifact is absent from the root reports missing media honestly" (the imported artifact deliberately left out of the root - `404`, not a crash or a fabricated image). The full built-CLI smoke test (§20) additionally proves this against the real compiled server.
132
+
133
+ ## 16. Unsupported-version behavior
134
+
135
+ `classify.ts` compares the candidate's `schemaVersion` against the current canonical constant (`SCHEMA_VERSION`, `COMPARISON_SCHEMA_VERSION`, `CONTRACT_SCHEMA_VERSION`, `EVALUATION_SCHEMA_VERSION`, `EXTERNAL_REFERENCE_SCHEMA_VERSION`) **before** attempting a canonical read, so an unsupported version never even reaches (and is never silently accepted by) the reader/validator pair pinned to the current schema. `/api/index` reports `supportState: 'unsupported-version'` plus both the found and supported version strings; `/api/artifacts/<handle>` refuses to load it (`409`, carrying that same honest metadata) - never coerced into a fabricated current-shape domain object. Proven with a real fixture (`writeUnsupportedVersionManifest`: a genuine `my-frontend-observer/observation` kind at schema `99.0.0`) at the unit level and in the real-Chromium/CLI smoke.
136
+
137
+ ## 17. Malformed/unavailable/missing behavior
138
+
139
+ Six independent, honestly distinguished states (`ViewerSupportState` in `classify.ts`): `supported`, `unsupported-version`, `invalid-structure` (kind+version match but the canonical validator itself rejects it), `unrecognized-kind` (no known `artifactKind`, including the bounded-agent-context case), `malformed-json` (not valid JSON at all), `unreadable` (stat/read failure or over the size bound). None is ever silently dropped or reinterpreted as `supported`; one malformed/unrelated candidate never erases valid neighboring evidence (`evidenceDiscovery.test.ts`: 6 real pipeline artifacts + 1 malformed all appear in one index response). Missing media is reported per-role (`available: false` with an honest `reason`) rather than as a whole-artifact failure - a genuinely deleted `screenshot.png` still yields a `supported` observation record whose `media` summary honestly says `unavailable`.
140
+
141
+ ## 18. PWA cache verification
142
+
143
+ No `vite.config.ts`/service-worker configuration change was needed: every new route lives under `/api/`, already covered by Batch 1's `navigateFallbackDenylist: [/^\/api\//]`. Extended `tests/unit/viewerPwaBuild.test.ts` with an explicit Batch 2 assertion against the **real built** `sw.js`: still exactly one `registerRoute` call after the server-side additions, and the precache manifest contains no `/api/index`, `/api/artifacts`, or `/api/media` entry.
144
+
145
+ ## 19. Batch 2 UI changes
146
+
147
+ `viewer/src/hooks/useEvidenceIndex.ts` (fetches `/api/index`), `useArtifactDetail.ts` (fetches `/api/artifacts/<handle>` only when a handle is selected - never eagerly), `components/EvidenceList.tsx` (bounded list, one item per record, honest support-state label per item), `components/ArtifactPreview.tsx` (loading/loaded/error/unsupported states; a bounded raw-JSON preview of the loaded artifact - explicitly *not* a real visualization). `App.tsx` wires these into the existing Batch 1 shell regions (nav = list, workspace = preview, details aside = selected record's own already-fetched metadata fields - no additional request). No screenshot rendering, SVG overlay, or comparison/contract/reference visualization exists anywhere in `viewer/src/` (verified by a real-Chromium test asserting zero `<img>`/`<svg>` elements after loading a real observation).
148
+
149
+ ## 20. Files created
150
+
151
+ - `src/viewerServer/evidence/limits.ts`, `discovery.ts`, `classify.ts`, `handles.ts`, `pathSafety.ts`, `projection.ts`, `index.ts`, `mediaResolver.ts`
152
+ - `tests/support/evidenceFixtures.ts` (shared real-evidence fixture builders, reusing the existing writer/application-service functions - mirrors `tests/unit/cliFrontendContracts.test.ts`'s established construction pattern)
153
+ - `tests/unit/evidenceDiscovery.test.ts`, `viewerEvidenceServer.test.ts`
154
+ - `tests/browser/viewerEvidenceShell.test.ts`
155
+ - `viewer/src/hooks/useEvidenceIndex.ts`, `useArtifactDetail.ts`
156
+ - `viewer/src/components/EvidenceList.tsx`, `ArtifactPreview.tsx`
157
+ - `docs/reports/v0.8-evidence-index-readers-batch2.md` (this file)
158
+
159
+ ## 21. Files modified
160
+
161
+ - `src/viewerServer/httpServer.ts` - added `/api/index`, `/api/artifacts/<handle>`, `/api/media/<handle>/<role>` routing plus shared `writeJsonBody`/`writeJsonError`/`streamFile`/`decodeURIComponentSafe` helpers; existing `/api/status` and static-asset serving unchanged.
162
+ - `viewer/src/App.tsx` - wires the new hooks/components into the existing shell regions.
163
+ - `viewer/src/styles/index.css` - additive rules for the evidence list/preview/details UI.
164
+ - `tests/browser/viewerShell.test.ts` - one pre-existing Batch 1 assertion (`toContain('Evidence navigation will appear here')`, a *static placeholder* string) legitimately no longer holds now that the nav region is live; updated to assert the new, honest "no recognized evidence" message for an empty root - the same kind of sanctioned evolution as Batch 1's own `TST-401` update.
165
+ - `tests/unit/viewerPwaBuild.test.ts` - added the explicit Batch 2 cache-boundary assertion (§18).
166
+ - `docs/ARCHITECTURE.md` - new "v0.8 Batch 2" section.
167
+ - `docs/COMMANDS.md` - `view` section updated to describe the new routes/behavior.
168
+
169
+ ## 22. A production bug found and fixed by real-CLI smoke testing
170
+
171
+ `src/viewerServer/evidence/pathSafety.ts#resolveContainedDir` originally compared a `path.resolve()`-normalized candidate path against the **un-normalized** `root` string. Every unit test's `root` came from `path.join(tmpdir(), ...)`, which Node already normalizes to the platform separator, masking the bug entirely - all 19 initial `viewerEvidenceServer.test.ts` tests passed. The real built-CLI smoke test (§23), run with `--root` supplied using forward slashes (as a user typing a path in a shell commonly would, even on Windows), reproduced it immediately: every artifact/media request returned `404 unknown handle` even for evidence that genuinely existed. Root-caused via direct `node -e` reproduction against the compiled `dist` output, fixed by resolving `root` itself before the prefix comparison, and covered by a new dedicated regression test (`viewerEvidenceServer.test.ts` "root path normalization" describe block) that deliberately constructs a forward-slash root and asserts a real handle still resolves. This is exactly the kind of defect the task's mandated built-CLI smoke step (§34) exists to catch, and it did.
172
+
173
+ ## 23. Validation results
174
+
175
+ | Command | Result |
176
+ |---|---|
177
+ | `npm run typecheck` | **PASS** (zero errors, both `tsconfig.json` and `viewer/tsconfig.json`) |
178
+ | `npm run lint` | **PASS** (zero errors/warnings) |
179
+ | `npm test` | **PASS** - 1071/1071 tests, 56/56 files |
180
+ | `npm run build` | **PASS** - unchanged Node/library output plus `dist/viewerServer/evidence/**` and the rebuilt `dist/viewer/**` PWA |
181
+ | `npm run check:docs` | **PASS** - "Documentation check passed (17 required files)." |
182
+ | `npm run test:browser` | **PASS** - 132/132 tests, 12/12 files (real Chromium; run in full, not skipped, per task §33's instruction since this batch directly changes the browser-facing data boundary) |
183
+ | `git diff --check` | **PASS** - no whitespace errors (only expected LF→CRLF line-ending notices) |
184
+
185
+ ## 24. Built viewer Batch 2 smoke (§34)
186
+
187
+ Fixture: a real evidence root built via a one-off script (not committed; lives under `WORKFLOW_ROOT\tmp`) that calls the actual compiled `dist/artifacts/*Writer.js` and `dist/application/*Service.js` modules directly - the same real writers/services the CLI itself uses - to produce two real observations, a real comparison, a real baseline contract, a real per-change contract, a real evaluation, a real imported external reference (with a real image file), and a real approved external reference, plus a malformed-JSON candidate, an unsupported-future-schema-version candidate, and one unrelated `README.txt`, all under `WORKFLOW_ROOT\smoke\evidence-root`.
188
+
189
+ Command: `node dist/cli.js view --root "<WORKFLOW_ROOT>\smoke\evidence-root" --port 4319 --no-open`
190
+
191
+ All required checks (§34), against the real running built server:
192
+
193
+ - `/api/status` → `200`, correct root echoed.
194
+ - `/api/index` → `200`, all 9 real evidence records present with correct `family`/`supportState`, plus the malformed and unsupported-version candidates shown honestly; response body contains no full-artifact fields (`targetEvidence` etc.) and no `README` reference.
195
+ - Selected supported artifact (`observation:observations%2Fsmoke-obs-before`) loaded on demand via `/api/artifacts/<handle>` → `200` with its full payload.
196
+ - Selected media loaded on demand: observation `screenshot` → `200 image/png`; imported reference `image` → `200 image/png`; **approved reference `source-image` → `200 image/png`, resolved through the separate imported artifact's directory** (§15/§22).
197
+ - Unsupported-version and malformed candidates both correctly refused at `/api/artifacts/<handle>` → `409` (not fabricated as loaded).
198
+ - Unrelated `README.txt` never appears in `/api/index` and is not directly servable (`GET /README.txt` → `404`).
199
+ - Path-traversal attempt (`/api/artifacts/observation:..%2F..%2Fetc`) → `404`.
200
+ - Absolute-path-shaped handle attempt → `404`.
201
+ - PWA still loads: `/` → `200`, `/manifest.webmanifest` → `200`, `/sw.js` → `200`.
202
+ - `netstat` confirmed the listener bound to `127.0.0.1:4319` only (never `0.0.0.0`).
203
+ - Server located by its real PID via `netstat` and terminated with `taskkill /F`; a follow-up `netstat` confirmed the port was released and no server process remained.
204
+ - A post-run directory listing of the smoke evidence root shows exactly the fixture's own files - no stray `.tmp-*` write-in-progress directories, no modified/deleted files.
205
+
206
+ **Result: PASS.** Logs retained under `WORKFLOW_ROOT\logs\` (`smoke-server.log`, `smoke-server-2.log` [post-fix rerun], `index-response.json`, `index-response-2.json`, `smoke-checks-final.log`).
207
+
208
+ ## 25. Existing command regression check
209
+
210
+ `observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, `evaluate-reference-fidelity`, Batch 1 `view` startup, PWA shell, fixed host/port behavior, and the PWA cache boundary all remain covered by their existing, unmodified test suites (all 1071 unit + 132 browser tests pass - §23). Canonical v0.1-v0.7 semantics were not touched anywhere in this batch's diff; `src/domain/`, `src/artifacts/*Writer.ts`, and every existing reader are byte-for-byte unchanged.
211
+
212
+ ## 26. Generated path inventory
213
+
214
+ | Path | Disposition |
215
+ |---|---|
216
+ | `WORKFLOW_ROOT\cache\npm` | Retained (npm cache from the my-dev-kit-index `npx` invocation; harmless, reusable) |
217
+ | `WORKFLOW_ROOT\tmp\vite-cache`, `WORKFLOW_ROOT\tmp\build-smoke-evidence.mjs` | Retained (build cache dir was empty again, same as Batch 1 - Vite build mode doesn't populate it; the smoke-fixture script is dev/readiness tooling only, not committed) |
218
+ | `WORKFLOW_ROOT\logs\*` | Retained (smoke evidence - §24) |
219
+ | `WORKFLOW_ROOT\smoke\evidence-root` | Retained (real evidence fixture tree built for the smoke test - §24) |
220
+ | `WORKFLOW_ROOT\my-dev-kit-index\*` | Retained (successful index + cache-metadata from §4) |
221
+ | `WORKFLOW_ROOT\{fixtures,candidate,pack}` | Retained, empty/unused this batch (no `npm pack`/candidate-install step was needed since no packaging-boundary change occurred beyond the existing `dist` allowlist) |
222
+ | Repo-root `dist/` | Ordinary build output (gitignored); rebuilt cleanly by `scripts/clean.mjs` on every `npm run build` |
223
+ | Pre-existing repo-root `.my-dev-kit*`/`baselines`/`comparisons`/`contracts`/`evaluations`/`observations` | Pre-existing, empty, untouched (same finding as the Batch 1 report) |
224
+
225
+ `Z:\Users\newuser\Projects\my-frontend-observer.my-dev-kit-workflow\v0.8\batch-01` was never targeted by any command in this session (verified) - preserved exactly as Batch 1 left it.
226
+
227
+ ## 27. Repository pollution check
228
+
229
+ `git status --short` before staging showed only the seven modified + eight new Batch-2-owned paths listed in §20/§21 (plus their containing directories). No unexpected file or directory appeared anywhere in the repository as a result of this batch's work.
230
+
231
+ ## 28. Skipped work / deviations
232
+
233
+ - None. Every task step (my-dev-kit retrieval, all six required test categories, the full validation chain including `test:browser`, and the built-CLI smoke) was executed, not skipped or substituted.
234
+
235
+ ## 29. Remaining uncovered risks
236
+
237
+ - **`findImportedReferenceDir` re-walks the evidence tree per `source-image` request** rather than reusing a cached index (by design - no persistent cache exists, per task §30). For an evidence root at the bound edge (near `MAX_MANIFEST_CANDIDATES`), an approved-reference-heavy workload would re-pay that walk cost on every source-image request. Not a correctness risk; a potential future performance consideration only if real usage ever approaches the bound (task §30 explicitly says to report rather than pre-optimize).
238
+ - **The bounded-agent-context "unrecognized-kind" classification is asserted only at the unit level**, not against a real persisted fixture (none exists to build, by design - §5). The behavior is exercised with a hand-written manifest declaring that kind, which is the most realistic proof achievable without inventing persistence for a family that has none.
239
+ - Batch 1's previously reported risks (install-prompt "available" branch, live service-worker execution) remain unresolved and out of this batch's scope.
240
+
241
+ ## 30. Out-of-scope confirmation
242
+
243
+ Confirmed absent from this batch's diff: target SVG overlays, screenshot geometry rendering, runtime target inspection UI, relationship/scroll visualization, before/after visual comparison, contract/change-scope visualization, external-reference/candidate side-by-side visual inspection, region/target overlays, explicit-binding cross-selection, zoom/pan, synchronized lock, on-demand fidelity computation, bounded-agent-context visual inspection, correlation UI, arbitrary filesystem navigation, annotation, region/requirement authoring, source editing, automatic binding/target discovery, pixel-diff scoring, computer vision, cloud hosting, database, authentication, collaboration. `ArtifactPreview.tsx`'s raw-JSON preview is explicitly not a visualization (no image, no SVG, no geometry rendering - proven by the zero-`<img>`/zero-`<svg>` browser assertion, §19).
244
+
245
+ ## 31. Final verdict
246
+
247
+ Batch 2 ("Evidence indexing, canonical readers, and lazy data boundary") is implemented and independently validated: a built candidate's viewer can enumerate real recognized evidence from bounded metadata, load one selected artifact on demand through its existing canonical validation boundary, load its owned/referenced media safely (including the cross-artifact approved-reference case), and visibly distinguish malformed/unsupported/missing evidence rather than silently dropping it - proven at the unit, real-Chromium, and real-built-CLI-smoke levels, with one genuine cross-platform path-handling defect found and fixed along the way. No Batch 3+ visualization, no v0.9 annotation, and no release/publication action was taken. Package version remains `0.7.0`.