@dailephd/my-frontend-observer 0.10.0 → 0.10.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 (75) hide show
  1. package/CHANGELOG.md +490 -479
  2. package/LICENSE +21 -21
  3. package/README.md +375 -365
  4. package/dist/application/projectCheckService.d.ts +6 -0
  5. package/dist/application/projectCheckService.js +8 -1
  6. package/dist/application/projectCheckService.js.map +1 -1
  7. package/dist/application/projectWorkflowService.d.ts +7 -2
  8. package/dist/application/projectWorkflowService.js +10 -3
  9. package/dist/application/projectWorkflowService.js.map +1 -1
  10. package/dist/cli.js +510 -510
  11. package/dist/viewer/index.html +13 -13
  12. package/dist/viewer/sw.js +1 -1
  13. package/docs/ARCHITECTURE.md +1394 -1385
  14. package/docs/CI_CD.md +349 -338
  15. package/docs/COMMANDS.md +1035 -1026
  16. package/docs/CONTRACTS.md +1971 -1960
  17. package/docs/CURRENT_STATE.md +1277 -1252
  18. package/docs/DEVELOPMENT.md +240 -237
  19. package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +50 -50
  20. package/docs/PROJECT_DESCRIPTION.md +2248 -2224
  21. package/docs/PROJECT_MILESTONES.md +2681 -2558
  22. package/docs/PROJECT_OVERVIEW.md +200 -196
  23. package/docs/QUICKSTART.md +100 -100
  24. package/docs/RELEASE.md +37 -36
  25. package/docs/ROADMAP.md +1105 -1034
  26. package/docs/SECURITY.md +297 -297
  27. package/docs/WORKFLOWS.md +806 -796
  28. package/docs/plans/v0.10-implementation-plan.md +1509 -1509
  29. package/docs/plans/v0.8-implementation-plan.md +655 -655
  30. package/docs/plans/v0.8.1-cli-usability-patch-plan.md +505 -505
  31. package/docs/plans/v0.9-implementation-plan.md +1529 -1529
  32. package/docs/plans/v0.9.1-implementation-plan.md +468 -468
  33. package/docs/reports/v0.10-batch1-visual-change-workflow-foundation.md +102 -102
  34. package/docs/reports/v0.10-batch2-project-composition-check-recording.md +103 -103
  35. package/docs/reports/v0.10-batch3-viewer-visual-change-workspace.md +93 -93
  36. package/docs/reports/v0.10-batch4-actual-frontend-entry.md +59 -59
  37. package/docs/reports/v0.10-batch5-reference-driven-entry.md +238 -238
  38. package/docs/reports/v0.10-batch6-coding-agent-handoff.md +85 -85
  39. package/docs/reports/v0.10-batch7-correction-review-acceptance.md +145 -145
  40. package/docs/reports/v0.10-batch8-integrated-acceptance.md +109 -109
  41. package/docs/reports/v0.10-implementation-completeness-documentation-reconciliation.md +344 -344
  42. package/docs/reports/v0.10-pre-release-readiness.md +120 -120
  43. package/docs/reports/v0.10-release-preparation.md +70 -70
  44. package/docs/reports/v0.10.1-project-check-baseline-context-implementation.md +86 -0
  45. package/docs/reports/v0.7-bounded-fidelity-context-prompt7.md +243 -243
  46. package/docs/reports/v0.7-implementation-completeness-documentation-reconciliation.md +497 -497
  47. package/docs/reports/v0.7-pre-release-readiness.md +337 -337
  48. package/docs/reports/v0.7-reference-binding-prompt5.md +223 -223
  49. package/docs/reports/v0.7-reference-compatibility-prompt4.md +234 -234
  50. package/docs/reports/v0.7-reference-correction-workflow-prompt8.md +222 -222
  51. package/docs/reports/v0.7-reference-fidelity-prompt6.md +216 -216
  52. package/docs/reports/v0.7-reference-foundation-prompt1.md +151 -151
  53. package/docs/reports/v0.7-reference-regions-prompt2.md +195 -195
  54. package/docs/reports/v0.7-reference-requirements-prompt3.md +217 -217
  55. package/docs/reports/v0.7-release-prep.md +423 -423
  56. package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -279
  57. package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -233
  58. package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -279
  59. package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -247
  60. package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -741
  61. package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -128
  62. package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -223
  63. package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -687
  64. package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -232
  65. package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -278
  66. package/docs/reports/v0.8.1-implementation-completeness-documentation-reconciliation.md +114 -114
  67. package/docs/reports/v0.8.1-prerelease-readiness-cross-platform-security-code-rot.md +170 -170
  68. package/docs/reports/v0.9-architecture-retrieval.md +14 -37
  69. package/docs/reports/v0.9-final-pre-release-readiness.md +209 -209
  70. package/docs/reports/v0.9-final-readiness-corrections.md +530 -530
  71. package/docs/reports/v0.9-pre-release-readiness.md +169 -169
  72. package/docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md +359 -359
  73. package/docs/reports/v0.9.1-batch2-hard-gate-validation-integration.md +262 -262
  74. package/docs/reports/v0.9.1-pre-release-readiness.md +206 -206
  75. package/package.json +59 -59
@@ -1,1385 +1,1394 @@
1
- # Architecture
2
-
3
- ## v0.10 visual workflow boundaries
4
-
5
- `VisualChangeWorkflowArtifact` (`1.0.0`) is the only new persisted v0.10
6
- family. It owns frozen scope, explicit activation/restoration, immutable
7
- attempt and review revisions, and optional references to governance results.
8
- The Viewer is the human workflow entry; `checkProject` remains the canonical
9
- evaluator. `VisualChangeAgentHandoff` (`1.0.0`) is a generated, non-persisted
10
- transfer contract. An external human or coding agent edits source. Optional
11
- orchestrator metadata is traceability only and Observer never imports or runs
12
- the orchestrator. Human acceptance is separate from baseline/reference
13
- governance and never rewrites prior workflow evidence.
14
-
15
- ## v0.8.1 project workflow
16
-
17
- Versioned project configuration (`1.0.0` compatibility plus current `1.1.0`
18
- acceptance input), upward discovery, centralized managed paths, and the atomic
19
- alias catalog live under `src/projectWorkflow`. Aliases select exact canonical
20
- artifact directories; they never replace artifact identities.
21
-
22
- `src/application/projectCheckService.ts` composes the existing observation,
23
- comparison, contract-evaluation, reference-reader, explicit-binding,
24
- compatibility, and fidelity owners. `src/projectWorkflow/checkAcceptance.ts`
25
- owns contained acceptance-input resolution and shared file-wrapper parsing.
26
- `checkResult.ts` owns the bounded ephemeral projection, not a persisted check
27
- artifact or evaluation engine. Status precedence is `BLOCKED`, then `FAIL`,
28
- then `PASS` when all configured executable dimensions pass, then
29
- `REVIEW_REQUIRED` when comparison is the only evidence. Canonical observations,
30
- comparisons, and contract evaluations remain persisted; reference fidelity and
31
- the workflow result remain in memory/presentation.
32
-
33
- ## Current package architecture
34
-
35
- The current repository is one published TypeScript ESM package
36
- (`@dailephd/my-frontend-observer@0.9.1`). The CLI remains
37
- `my-frontend-observer`; the npm scope does not rename the product or artifact
38
- identities.
39
-
40
- - `src/cli.ts` is the real, thin public CLI parsing/dispatch/presentation
41
- boundary for the current command surface (`observe`, `compare`,
42
- `approve-baseline`, `save-change-contract`, `evaluate-contract`,
43
- `import-reference`, `approve-reference`, `evaluate-reference-fidelity`,
44
- `view`, `init`, `capture`, `check`); argument parsing and output formatting
45
- only, per command - domain semantics remain owned by application/domain
46
- services rather than the CLI. v0.6 added no new CLI command; v0.7 added the
47
- three external-reference commands; v0.8 added `view`; v0.8.1 added `init`,
48
- `capture`, and `check` and made `view` project-aware while preserving its
49
- standalone `--root` behavior.
50
- - `src/index.ts` is the library entry point re-exporting the observer-owned
51
- contracts/functions from every layer below, including the v0.6 bounded-agent-
52
- context projection and runtime/static correlation surface, and the v0.7
53
- external-reference/region/requirement/applicability/compatibility/binding/
54
- fidelity/correction-workflow surface (`prepareReferenceCorrection`/
55
- `reviewReferenceCorrectionAttempt` remain programmatic-only, with no CLI
56
- command).
57
- - `scripts/clean.mjs` safely removes only the project `dist/` directory.
58
- - `scripts/check-docs.mjs` validates the canonical documentation foundation,
59
- roadmap version presence, and the no-batches rule.
60
- - TypeScript, ESLint, Vitest, and package configuration provide foundation
61
- validation, now exercised by real product tests (`tests/unit/`,
62
- `tests/browser/`).
63
-
64
- Batch 1 added the observation domain/schema and safety-policy layer
65
- (`src/domain/`, `src/request/`, `src/safety/`). Batch 2 added a real
66
- Playwright Chromium browser adapter (`src/browser/`), a minimal application
67
- seam invoking it (`src/application/`), and a deterministic browser
68
- fixture/test boundary (`tests/fixtures/`, `tests/browser/`, run via
69
- `npm run test:browser`). Batch 3 extended that single browser adapter with an
70
- internal page/target measurement module (`src/browser/evidenceCapture.ts`)
71
- that reads page and explicit-CSS-target evidence from the same live,
72
- already-ready page used for the screenshot - no second browser/page is ever
73
- opened, and Playwright objects still never leave `src/browser/`. Batch 4
74
- added the artifact ownership boundary itself: `src/artifacts/artifactWriter.ts`
75
- is the one canonical place that writes an observation to disk (temp
76
- directory, then one atomic rename into `<outputLocation>/<observationId>/`),
77
- and `src/application/observationPersistence.ts` assembles the frozen
78
- `ObservationArtifact` from a browser-capture result before handing it to the
79
- writer. The artifact layer has no Playwright dependency and is testable
80
- without launching Chromium. Batch 5 completed the boundary chain: `src/cli.ts`
81
- parses `observe` arguments (CLI-syntax errors only - e.g. malformed
82
- `WIDTHxHEIGHT`), constructs a raw request, and hands it to the existing
83
- Batch 1 `normalizeRequest`; on success it calls one new application-level use
84
- case, `observe()` in `src/application/observationPersistence.ts`, which runs
85
- the existing `runBrowserCapture` exactly once and, only on success, the
86
- existing artifact writer exactly once, then returns a small observer-owned
87
- `ApplicationObservationResult` (observation id, completion state, artifact
88
- path, target/diagnostic counts) for the CLI to print. The CLI never imports
89
- Playwright or the filesystem-write path directly. Batch 6 closed the
90
- remaining real-Chromium coverage gap (a genuine navigation failure, distinct
91
- from a readiness timeout or a pre-launch safety rejection) and validated the
92
- packed npm tarball end to end in a clean consumer environment, independent
93
- of the source checkout. At the end of the v0.1 implementation there was no
94
- controlled-scroll or comparison behavior.
95
-
96
- ## Current v0.2 architecture (released/current architecture)
97
-
98
- v0.2 extends the same architecture rather than adding a parallel one.
99
- `src/request/request.ts` now owns a canonical `{name, locators}` target
100
- model (`TargetLocator`, six frozen kinds) in place of the old CSS-only
101
- shape; the legacy `{name, selector}` input still normalizes into it. The one
102
- existing browser-side target resolver/measurement module,
103
- `src/browser/evidenceCapture.ts`, was extended - not replaced - to resolve
104
- all six locator kinds against the live page through a single Playwright
105
- `Locator` per attempt, honor the frozen ordered-fallback/ambiguity/
106
- unavailable-no-fallback contract, and converge every kind on the same
107
- measurement path (`captureResolvedTargetRecord`); it additionally computes
108
- bounded semantic state, derived landmark identity, and configured-target-
109
- only DOM containment from the same already-resolved elements in the same
110
- capture pass - no second browser/page, no second resolution algorithm.
111
- `src/domain/schema.ts` extends `TargetEvidenceRecord`/`TargetResolution`
112
- additively for schema `1.1.0`, with matching structural validation in
113
- `isValidObservationArtifact`. `src/cli.ts` gained one CLI/input-boundary-
114
- only addition, `--targets-file`: it reads and validates only the JSON root
115
- wrapper (via the already-imported `node:fs`, never `node:fs/promises`) and
116
- hands the parsed `targets` value into the existing `RawObservationRequest`/
117
- `normalizeRequest()` path unchanged - there is no second application
118
- observation use case, and Playwright objects still never leave
119
- `src/browser/`. The artifact writer, application observation use case, and
120
- overall boundary chain (`CLI → normalizeRequest → observe() →
121
- runBrowserCapture → artifact writer`) are unchanged from v0.1.
122
-
123
- ## Current v0.3 architecture (released/current architecture)
124
-
125
- v0.3 extends the same single-observation architecture again; it does not add
126
- a second browser lifecycle, target resolver, or artifact path.
127
- `src/request/request.ts` adds one optional `scrollScenario` field to
128
- `NormalizedObservationRequest` (`ScrollScenario { action }`, exactly
129
- `window-scroll-by` or `target-scroll-by`); `src/domain/schema.ts` adds the
130
- matching bounded runtime evidence types (`ScrollRuntimeSnapshot`,
131
- `ViewportRelationEvidence`, `OverflowEvidence`, `ScrollScenarioTransition`,
132
- `ScrollOwnerInterpretation`) and structural validation for schema `1.2.0`
133
- (additive over `1.1.0`). `src/domain/scrollEvidence.ts` holds the pure,
134
- browser-independent derivations (viewport relation, actual overflow,
135
- transitions, and `deriveScrollOwner`) so they are unit-testable without
136
- Chromium. `src/browser/scrollCapture.ts` holds the one browser-side scenario
137
- capture module: it reuses `evidenceCapture.ts#resolveConfiguredTargets` (now
138
- exported) to resolve configured targets exactly once, captures an initial
139
- `ScrollRuntimeSnapshot`, performs the one immediate scroll
140
- (`window.scrollBy`/`element.scrollBy`, both `behavior: 'instant'`), waits
141
- exactly two `requestAnimationFrame` cycles, and captures a final snapshot -
142
- all inside `chromiumAdapter.ts#captureViewportInternal`'s existing single
143
- navigate → ready → capture flow, strictly before the unchanged
144
- screenshot/`capturePageEvidence`/`captureTargetEvidence` calls, so every
145
- downstream capture (including a no-scenario request, which skips this block
146
- entirely) describes only the final state. `src/cli.ts` gained one CLI/input-
147
- boundary-only addition, `--scroll-scenario-file`: mirroring
148
- `--targets-file`, it reads and validates only the file readability/JSON-
149
- validity/non-array-object-root shape and hands the parsed value straight
150
- into `RawObservationRequest.scrollScenario` - every scenario/action rule
151
- (kind, deltas, target reference) stays owned by `normalizeRequest()`. There
152
- is still one canonical `observe()` application use case and one artifact
153
- writer; `scrollScenarioEvidence` is simply one more optional field on the
154
- same `ObservationArtifact`.
155
-
156
- ## Current v0.4 architecture (released/current architecture)
157
-
158
- v0.4 adds one new downstream pipeline that consumes `ObservationArtifact`
159
- values rather than producing them - it never adds a second browser lifecycle,
160
- target resolver, or observation engine:
161
-
162
- ```text
163
- ObservationArtifact before ObservationArtifact after
164
- \ /
165
- `--------. .-------'
166
- \ /
167
- artifact reader (src/artifacts/artifactReader.ts)
168
- ↓
169
- comparability evaluation (src/domain/comparisonEngine.ts)
170
- ↓
171
- canonical relationship derivation, called for each side independently
172
- (src/domain/relationships.ts#deriveLayoutRelationships)
173
- ↓
174
- canonical comparison derivation
175
- (src/domain/comparisonEngine.ts#compareObservations)
176
- ↓
177
- ComparisonArtifact
178
- ↓
179
- atomic comparison writer (src/artifacts/comparisonArtifactWriter.ts)
180
-
181
- CLI `compare`
182
- ↓
183
- application service only (src/application/comparisonService.ts)
184
- ↓
185
- [reader → domain comparison → writer, as above]
186
- ```
187
-
188
- `src/domain/relationships.ts` froze the layout-relationship contract and
189
- implements the one canonical pure derivation,
190
- `deriveLayoutRelationships(observation, options?)`: horizontal/vertical
191
- order, area overlap, relative width, geometric fit, vertical sequencing,
192
- page-width fit/exceeds, and a standalone `deriveTargetClipping(record)` -
193
- all computed only from an already-captured `ObservationArtifact`'s own
194
- `targetEvidence`/`pageEvidence`, never from a second browser query. DOM
195
- containment is read directly from the existing v0.2 `TargetContainment`
196
- evidence rather than re-derived, and stays a distinct concept from
197
- geometric fit.
198
-
199
- `src/domain/comparisonEngine.ts` implements the one canonical pure
200
- before/after engine, `compareObservations(before, after, config?)`:
201
- validates both source artifacts, evaluates comparability *before* any
202
- rendered difference is calculated, calls `deriveLayoutRelationships` once
203
- per side with the same tolerance, and derives target/page differences and
204
- relationship changes. `src/artifacts/comparisonArtifactWriter.ts` persists
205
- the result atomically (sibling temp directory, then one rename) as
206
- `<outputLocation>/<comparisonId>/manifest.json` only - no screenshot is
207
- copied; the manifest's `before`/`after` references point back to the
208
- source observations' own `screenshot.path`. `src/application/
209
- comparisonService.ts` is the one application-layer seam: `compareAndPersist`
210
- takes two in-memory `ObservationArtifact`s and does exactly one comparison
211
- plus exactly one persist; `compareAndPersistFromArtifactRoots` is a thin
212
- wrapper that additionally reads both sides from disk via the existing
213
- `src/artifacts/artifactReader.ts#readObservationArtifact` reader (itself
214
- just a `manifest.json` parse plus the same `isValidObservationArtifact`
215
- structural gate the writer uses).
216
-
217
- `src/cli.ts` gained one new top-level command, `compare`
218
- (`--before`/`--after`/`--output`/`--config-file`), implemented with the same
219
- thin-CLI-boundary discipline as `observe`: `parseCompareArgs` handles only
220
- argument shape/duplication, an optional `loadComparisonConfigFile` reads and
221
- validates only file readability/JSON-validity/non-array-object-root (exactly
222
- like `--targets-file`/`--scroll-scenario-file`), and the command body calls
223
- `compareAndPersistFromArtifactRoots` exactly once. **The CLI's comparison
224
- path never launches Chromium** - `src/cli.ts` imports nothing from
225
- `src/browser/` or `src/artifacts/` (it only reaches persistence and artifact
226
- reading indirectly, through the application-layer seam above), matching the
227
- same import-boundary discipline already enforced for `observe`.
228
-
229
- ## Current v0.5 architecture (released/current architecture)
230
-
231
- v0.5 adds one new downstream layer that consumes `ComparisonArtifact` values
232
- (plus the source `ObservationArtifact` pair) rather than producing them - no
233
- new browser lifecycle, target resolver, or comparison engine is added:
234
-
235
- ```text
236
- ObservationArtifact before + after
237
- ↓
238
- existing v0.4 comparison/relationship pipeline (unchanged)
239
- ↓
240
- ComparisonArtifact
241
- ↓ PersistentBaselineContract
242
- | +
243
- `------------------------→ PerChangeContract
244
- ↓
245
- canonical contract evaluation
246
- (src/domain/frontendContractEvaluation.ts#evaluateFrontendContract)
247
- ↓
248
- clause results + unexpected changes + overall PASS/FAIL
249
- ```
250
-
251
- `src/domain/frontendContracts.ts` froze the contract/change-scope type,
252
- constant, and structural-validator vocabulary (Batch 1); `src/domain/
253
- frontendContractIdentity.ts` froze deterministic contract/baseline/clause
254
- identity in the same canonicalize+sha256(+opaque-nonce) style as `src/domain/
255
- comparisonIdentity.ts`. `src/domain/frontendContractEvaluation.ts#evaluateFrontendContract`
256
- (Batch 2) is the one canonical pure evaluation entry point: it validates its
257
- five inputs (before/after `ObservationArtifact`, `ComparisonArtifact`,
258
- `PersistentBaselineContract`, `PerChangeContract`) are structurally coherent
259
- and mutually consistent, calculates the active baseline clause set after
260
- explicit supersession, detects bounded structural conflicts, evaluates every
261
- active clause via the frozen 15-primitive vocabulary against the existing
262
- `ComparisonArtifact`/`ObservationArtifact` evidence (never re-deriving
263
- clipping/relationship/scroll-owner facts), classifies unaccounted-for
264
- `ComparisonArtifact.differences` entries as `unexpected`, and derives one
265
- overall `PASS`/`FAIL` verdict. It performs no I/O, launches no browser, and
266
- persists nothing - `src/domain/frontendContractEvaluation.ts` itself remains
267
- untouched by the persistence layer below (Batch 3).
268
-
269
- Batch 3 adds the persistence/application boundary around this frozen domain,
270
- without redefining it:
271
-
272
- ```text
273
- ComparisonArtifact (read via new src/artifacts/comparisonArtifactReader.ts)
274
- +
275
- PersistentBaselineContract / PerChangeContract
276
- (read/written via src/artifacts/frontendContractArtifactReader.ts / ...Writer.ts)
277
- ↓
278
- src/application/frontendContractEvaluationService.ts#evaluateAndPersist
279
- ↓
280
- evaluateFrontendContract() [called exactly once, unmodified]
281
- ↓
282
- src/domain/frontendContractEvaluationArtifact.ts
283
- (minimal additive persisted envelope around the frozen result)
284
- ↓
285
- src/artifacts/frontendContractEvaluationArtifactWriter.ts
286
- (atomic write, exactly once on a structurally constructible result)
287
- ```
288
-
289
- `evaluateAndPersistFromArtifactRoots` is the CLI-facing wrapper, reading
290
- before/after observations through the existing `readObservationArtifact` (no
291
- second observation reader), the comparison and both contract classes
292
- through the new readers, then delegating to `evaluateAndPersist` exactly
293
- once - mirroring `application/comparisonService.ts#compareAndPersistFromArtifactRoots`'s
294
- own thin-wrapper shape.
295
-
296
- Batch 4 exposes this through the same thin-CLI boundary already established
297
- by `observe`/`compare`:
298
-
299
- ```text
300
- src/cli.ts (argument parsing, JSON-file-shape checks, help/output formatting,
301
- exit-code selection only)
302
- ↓
303
- src/application/frontendContractPersistenceService.ts#approveAndPersistBaseline
304
- src/application/frontendContractPersistenceService.ts#persistPerChangeContract
305
- src/application/frontendContractEvaluationService.ts#evaluateAndPersistFromArtifactRoots
306
- ↓
307
- domain validators (isValidPersistentBaselineContract / isValidPerChangeContract)
308
- + artifact readers/writers (Batch 3)
309
- + evaluateFrontendContract() (Batch 2, unmodified)
310
- ```
311
-
312
- Three new top-level commands - `approve-baseline`, `save-change-contract`,
313
- `evaluate-contract` - each parse only CLI-syntax concerns (duplicate/missing
314
- flags, JSON-file readability/parseability/object-root shape) and delegate to
315
- exactly one application-layer call; `src/cli.ts` imports no artifact writer/
316
- reader module and no browser code, matching the existing `observe`/`compare`
317
- import-boundary discipline exactly. `approveAndPersistBaseline` adds the one
318
- new coherence check Batch 3 did not need: verifying a baseline contract's
319
- frozen `sourceObservation` reference actually matches the supplied
320
- observation artifact before persisting - explicit approval only, never
321
- inferred from a `compare` or `evaluate-contract` result. `--enforce` on
322
- `evaluate-contract` is applied only after evaluation and persistence have
323
- already completed; it selects the process exit status for an already-final
324
- `FAIL` result and is never part of any identity or persisted field.
325
-
326
- ## Current v0.6 architecture (released as `0.6.0`)
327
-
328
- v0.6 adds one new downstream, read-only layer that consumes existing v0.1-v0.5
329
- evidence (`ObservationArtifact`, `ComparisonArtifact`,
330
- `PersistentBaselineContract`/`PerChangeContract`, and evaluation results)
331
- plus caller-supplied bounded static candidate evidence - it adds no new
332
- browser lifecycle, target resolver, observation/comparison/contract engine,
333
- or persisted artifact family:
334
-
335
- ```text
336
- ObservationArtifact(s) + ComparisonArtifact + contract/evaluation evidence
337
- ↓
338
- src/domain/boundedAgentContextProjection.ts#projectBoundedAgentContext
339
- ↓
340
- BoundedRuntimeTargetProjection
341
- (page/viewport identity, stable targets, geometry, runtime behavior,
342
- relationships, before/after differences, contract results,
343
- requested/expected-dependent/protected/preserved scope reused verbatim
344
- from src/domain/frontendContracts.ts, diagnostics, artifact/screenshot
345
- references, provenance, adequacy, omission, truncation)
346
- ↓
347
- src/domain/boundedAgentContextCorrelation.ts
348
- #deriveRuntimeStaticCorrelations / #attachRuntimeStaticCorrelations
349
- ↓
350
- RuntimeStaticCorrelation[] (correlated / ambiguous / unavailable,
351
- competing candidates preserved verbatim - never collapsed to one owner)
352
- ↓
353
- src/index.ts (public export/correlation boundary only)
354
- ```
355
-
356
- `src/domain/boundedAgentContext.ts` freezes the bounded-projection and
357
- correlation type/constant vocabulary (`Adequacy`, `ADEQUACY_REASON_CODES`,
358
- `OmissionRecord`, `TruncationRecord`, `BOUNDED_AGENT_CONTEXT_ARTIFACT_KIND`,
359
- schema `1.0.0`). `src/domain/boundedAgentContextIdentity.ts` derives a
360
- deterministic logical identity distinct from a fresh per-execution instance
361
- identity, in the same canonicalize+hash style as
362
- `comparisonIdentity.ts`/`frontendContractIdentity.ts`.
363
- `boundedAgentContextProjection.ts` performs no browser I/O and re-derives
364
- nothing already owned upstream - it reads already-captured artifacts and
365
- reuses the existing v0.4 relationship/comparison evidence and v0.5
366
- change-scope clause types directly. `boundedAgentContextCorrelation.ts`
367
- accepts only plain, caller-supplied candidate static-evidence records; it has
368
- **no** dependency on `@dailephd/my-dev-kit`, since the audit that preceded
369
- implementation found no generic static-side retrieval capability actually
370
- missing (see `docs/ROADMAP.md` v0.6 "Dependency direction" - the "determine
371
- whether my-dev-kit requires a static-side change" step concluded no).
372
- Runtime target identity is carried through this module verbatim; the module
373
- never adds a `sourceOwner`/`causedBy`-shaped field, preserving the
374
- architectural rule that runtime identity never silently becomes source
375
- ownership.
376
-
377
- This layer is a programmatic export/correlation boundary only: `src/index.ts`
378
- re-exports its full type/function surface, but there is no new CLI command,
379
- no `src/artifacts/boundedAgentContext*` writer/reader, and no orchestrator or
380
- lab code in this repository - those remain separate sibling-repository
381
- responsibilities per the Milestone 6 ownership split in
382
- `docs/PROJECT_MILESTONES.md`.
383
-
384
- ## v0.7-v0.10 reference-evidence architecture constraints
385
-
386
- The external visual-reference capability (v0.7) is released as package
387
- version `0.7.0` - see "v0.7 Prompt 1" through "v0.7 Prompt 8" below for the
388
- actual architecture, and `docs/CURRENT_STATE.md` for release state. It
389
- extends the existing v0.1-v0.6 evidence architecture rather than becoming a
390
- UI-only feature or a parallel visual-comparison stack. v0.8 (interactive
391
- viewer) is released as package version `0.8.0`. v0.9 (structured visual
392
- annotation) is released as package version `0.9.0` - see "v0.9 visual
393
- annotation architecture" below. v0.10 (full graphical human-LLM workflow) is
394
- implemented and documentation-reconciled but remains unreleased. The
395
- constraints below apply to both v0.9 and v0.10.
396
-
397
- The evidence domains remain distinct:
398
-
399
- ```text
400
- runtime observation A ↔ runtime observation B
401
- → existing before/after comparison
402
-
403
- approved baseline/per-change contract ↔ candidate runtime evidence
404
- → existing canonical contract evaluation
405
-
406
- external visual reference ↔ candidate runtime evidence
407
- → reference applicability + structured fidelity evaluation (v0.7,
408
- released as `0.7.0` - see "v0.7 Prompt 4" and "v0.7 Prompt 6" below)
409
- ```
410
-
411
- An external reference is not an `ObservationArtifact`, and a reference region
412
- is not a runtime target. The released v0.7 implementation preserves explicit
413
- identity and provenance for the reference image/version, reference regions,
414
- applicable viewport/theme/application state, authored requirements, tolerances,
415
- approval/supersession state, and reference-region/runtime-target bindings.
416
- Bindings are explicit and evaluate to `bound`, `ambiguous`, or `unavailable`;
417
- they never silently become source ownership.
418
-
419
- The non-UI reference model and structured reference-vs-candidate evaluation
420
- were established in v0.7 before v0.8. v0.8 may render side-by-side images,
421
- overlays, measurements, bindings, provenance, and fidelity results, but it must
422
- consume those existing engines and must not invent a second reference model or
423
- evaluation engine. v0.9 may author annotations against either runtime
424
- screenshots or external references, but both coordinate/identity domains remain
425
- explicit and feed the same canonical contract/change-scope semantics. v0.10
426
- combines both visual entry modes with the existing correction loop.
427
-
428
- Where a reference requirement is executable, it uses the existing v0.5
429
- requested/expected-dependent/protected/preserved semantics. Informational or
430
- unassessed reference evidence remains non-executable until explicitly selected.
431
- There is no reference-only PASS/FAIL taxonomy.
432
-
433
- The released reference evaluation reuses existing relationship/value
434
- conventions where they mean the same thing and adds distinct reference-owned
435
- units only where the image evidence requires them. v0.7 does not use pixel or
436
- image-region similarity as a success mechanism; a later bounded similarity
437
- feature may supplement structured evidence if separately designed, but it must
438
- not replace browser-authoritative runtime geometry, canonical contract
439
- evaluation, or explicit relationship evidence.
440
-
441
- Candidate rendering still uses the one existing Chromium observation engine.
442
- The observer remains non-mutating. `my-dev-kit` remains the static/source
443
- evidence owner, and the v0.6 correlation/bounded-context boundary remains the
444
- route for attaching relevant source evidence to reference-driven correction
445
- packets. Heavy reference image bytes are referenced rather than copied into
446
- every downstream context/evaluation record.
447
-
448
- Theme, application-state, viewport, and authenticated-state applicability are
449
- checked before reference fidelity is interpreted through the released v0.7
450
- compatibility path, which reuses v0.4 comparability conventions. If reference
451
- and candidate do not represent compatible intended states, the result is
452
- explicitly incompatible/incomparable rather than a fabricated visual difference
453
- set. v0.8, released as package version `0.8.0`, displays this result exactly
454
- as required rather than redefining the state model - see "v0.8 Batch 5"
455
- below.
456
-
457
- The constraints above were carried out by the actual v0.7 implementation
458
- described in "v0.7 Prompt 1" through "v0.7 Prompt 8" below: explicit
459
- identity/provenance, applicability/compatibility, region-to-target bindings,
460
- requested/expected-dependent/protected/preserved reuse, and the non-mutating
461
- Chromium/correlation boundaries all remain as constrained here. v0.8 (see
462
- "v0.8 Batch 1" through "v0.8 Batch 8" below) applied them unchanged, and so
463
- does the implemented v0.9 annotation layer. They continue to apply unchanged
464
- to the still-future v0.10 work.
465
-
466
- The exact public artifact names, schema versions, persistence layout, supported
467
- image formats, coordinate model, requirement/tolerance primitives, and fidelity
468
- behavior were frozen by the actual v0.7 implementation below, not by earlier
469
- planning language. Style/asset-similarity mechanisms remain future unless
470
- separately implemented.
471
-
472
- ## v0.8 Batch 1 (Viewer runtime and PWA foundation) — implemented
473
-
474
- Batch 1 of the frozen `docs/plans/v0.8-implementation-plan.md` establishes
475
- only the viewer runtime/build shell — no evidence indexing, artifact reading,
476
- or evidence UI. It does not implement any of the v0.7-derived reference/
477
- fidelity/binding display constraints above; those remain future work for
478
- later v0.8 batches, which must consume this runtime boundary rather than
479
- redefine it.
480
-
481
- ```text
482
- my-frontend-observer view [--root <evidence-root>]
483
- |
484
- v
485
- thin CLI dispatch (src/cli.ts: parseViewArgs/runViewCommand)
486
- |
487
- v
488
- viewer application seam (src/viewerServer/viewerService.ts: startViewer)
489
- |
490
- v
491
- Node local server, loopback-only (src/viewerServer/httpServer.ts)
492
- |
493
- +---------------------+----------------------+
494
- | |
495
- v v
496
- built viewer assets (dist/viewer) GET /api/status
497
- (React + TypeScript + Vite PWA) (session/root identity only)
498
- ```
499
-
500
- - **Node server boundary** (`src/viewerServer/`): binds only to `127.0.0.1`
501
- on one fixed default port (`4319`, `src/viewerServer/port.ts`); serves only
502
- the built viewer assets plus the one read-only status endpoint; resolves
503
- every requested path against the built assets root and fails closed on any
504
- path that would resolve outside it; accepts no write HTTP methods; performs
505
- no artifact reading, browser observation, or mutation. `--root` is
506
- validated operationally (exists, is a directory) and exposed only as an
507
- opaque status string — it is never interpreted as Observer evidence in this
508
- batch.
509
- - **Browser application** (`viewer/`): a React + TypeScript + Vite app, built
510
- independently of `src/` via `viewer/tsconfig.json` and `viewer/vite.config.ts`,
511
- output to `dist/viewer` inside the existing package `dist` allowlist (no
512
- second npm package). Renders an honest foundation shell only — product
513
- identity, live session status via `/api/status`, and placeholder
514
- navigation/workspace/details regions — never fabricated evidence.
515
- - **PWA**: `vite-plugin-pwa` generates a web app manifest (`standalone`
516
- display, stable `start_url`/`scope`, installability icons) and a service
517
- worker that precaches only the built application shell. It declares no
518
- `runtimeCaching` rules, so future evidence/media/API routes remain
519
- network/server-backed rather than silently served as stale cached truth
520
- when the local server is unavailable (enforced by
521
- `tests/unit/viewerPwaBuild.test.ts`, which asserts on the actual built
522
- `sw.js`, not a hand-written approximation). An install affordance appears
523
- only when the browser actually fires `beforeinstallprompt`; its absence is
524
- shown honestly, never as a disabled-looking fake control.
525
- - **CLI**: `view [--root <evidence-root>] [--port <n>] [--no-open]` remains a
526
- thin dispatcher — it parses syntax, delegates once to `startViewer`, prints
527
- the URL/root, and optionally best-effort opens the system browser (failure
528
- there is never fatal to server startup). All v0.1-v0.7 commands are
529
- unchanged.
530
-
531
- This batch introduces no second observer, relationship engine, comparison
532
- engine, contract engine, reference model, or bounded-context builder — there
533
- is nothing yet for the viewer to consume beyond its own runtime identity.
534
-
535
- ## v0.8 Batch 2 (Evidence indexing, canonical readers, and lazy data boundary) — implemented
536
-
537
- Batch 2 adds the safe, read-only data boundary between existing on-disk
538
- Observer evidence and the Batch 1 viewer runtime, entirely under
539
- `src/viewerServer/evidence/`. It introduces no new persisted artifact family,
540
- no schema migration, and no second validator — every recognized candidate is
541
- decided exclusively by the existing canonical reader/validator for its
542
- family (`src/artifacts/*Reader.ts`).
543
-
544
- ```text
545
- GET /api/index bounded discovery + classification -> metadata only
546
- GET /api/artifacts/<handle> one canonical-reader read, on demand -> full projection
547
- GET /api/media/<handle>/<role> one resolved, contained media file, streamed on demand
548
- ```
549
-
550
- - **Discovery** (`evidence/discovery.ts`): a bounded, deterministic walk
551
- beneath `--root` that opens only files literally named `manifest.json` (the
552
- one filename every current persisted family uses) — no other file is ever
553
- read or classified, so arbitrary files can never become evidence merely by
554
- existing under the root. Directory entries that are symlinks/junctions are
555
- never followed. Bounds (`evidence/limits.ts`): traversal depth 6,
556
- directories visited 2000, candidate manifests 1000, index records 500,
557
- manifest read size 2,000,000 bytes — chosen after inspecting that every
558
- current writer produces a shallow `<outputLocation>/<id>/manifest.json`
559
- shape (see `docs/CONTRACTS.md`), not a deep tree.
560
- - **Classification is not validation** (`evidence/classify.ts`): peeks only
561
- `artifactKind`/`schemaVersion` (plus, where the shared kind is ambiguous,
562
- tries each existing reader/validator in turn — e.g. baseline vs. per-change
563
- contract) to decide *which* existing canonical reader to call; the reader's
564
- own structural validator remains the sole authority. Six honest, mutually
565
- exclusive states: `supported`, `unsupported-version`, `invalid-structure`,
566
- `unrecognized-kind`, `malformed-json`, `unreadable` — never collapsed into
567
- one boolean, and never conflated with an artifact's own `completion`
568
- state (passed through separately, only for the families that carry one:
569
- observation and external-reference). A manifest declaring the
570
- `bounded-agent-context` kind is classified `unrecognized-kind`: v0.6/v0.7
571
- never added a disk writer/reader for that family (confirmed via direct
572
- source inspection and `@dailephd/my-dev-kit` search), so Batch 2 does not
573
- invent persistence-shaped handling for it.
574
- - **Viewer handles** (`evidence/handles.ts`, `evidence/pathSafety.ts`): a
575
- handle is a family-prefixed, percent-encoded, root-relative directory path
576
- — never a raw filesystem path accepted from the browser. Every route that
577
- accepts a handle re-decodes and re-resolves it against the evidence root,
578
- re-checks containment, and re-classifies that one candidate before serving
579
- anything; a handle whose backing directory or manifest no longer matches
580
- what was indexed fails closed as unknown, never stale.
581
- - **Ephemeral projection** (`evidence/projection.ts`): `EvidenceMetadataRecord`
582
- (bounded, `/api/index`-shaped: handle, family, support state, logical id,
583
- schema version, completion where applicable, media availability summary,
584
- a handful of related ids) and `EvidenceArtifactDetail` (the already-
585
- validated domain object, wrapped with `handle`/`family` — no new evidence
586
- schema, no recomputation, no persistence).
587
- - **Media resolution** (`evidence/mediaResolver.ts`): `screenshot` (observation),
588
- `image` (imported external reference), and `source-image` (approved
589
- external reference) are the only three recognized roles. An approved
590
- reference's image is never assumed to live in the approved artifact's own
591
- directory — its `sourceReference.referenceId` is looked up against the
592
- current index to find the actual owning imported artifact
593
- (`evidence/index.ts#findImportedReferenceDir`), exactly matching the v0.7
594
- reference-ownership contract in `docs/CONTRACTS.md`. A genuinely missing
595
- screenshot/image/source artifact is reported as 404, never fabricated.
596
- - **PWA cache boundary preserved, not re-verified from scratch**: every new
597
- route lives under `/api/`, already covered by Batch 1's
598
- `denylist:[/^\/api\//]` navigation-fallback rule — no new `runtimeCaching`
599
- entry was needed or added (`tests/unit/viewerPwaBuild.test.ts` asserts this
600
- against the real built `sw.js`).
601
- - **Minimal UI** (`viewer/src/hooks/useEvidenceIndex.ts`,
602
- `useArtifactDetail.ts`, `components/EvidenceList.tsx`,
603
- `ArtifactPreview.tsx`): a bounded evidence list (metadata-first) plus
604
- on-demand full-artifact loading on selection, with every support state
605
- shown honestly. No screenshot rendering, SVG overlay, or comparison/
606
- contract/reference visualization exists yet — that begins in Batch 3.
607
-
608
- ## v0.8 Batch 3 (Runtime observation inspection and SVG overlays) — implemented
609
-
610
- Batch 3 makes one already-supported `ObservationArtifact` (Batch 2's data
611
- boundary, unchanged) genuinely understandable: a real screenshot, SVG target
612
- overlays in the observation's own canonical coordinate domain, target
613
- selection/inspection, and canonical layout-relationship display. No second
614
- relationship engine, no client-side evidence derivation, no new persisted
615
- artifact.
616
-
617
- **Coordinate audit (the load-bearing decision for this batch)**: target
618
- geometry (`TargetGeometry.x/y/width/height`) is captured via
619
- `el.getBoundingClientRect()` (`src/browser/evidenceCapture.ts`) - CSS pixels,
620
- relative to the current viewport's top-left, at the same live page state the
621
- screenshot is taken from. The screenshot itself is `page.screenshot({type:
622
- 'png'})` (`src/browser/chromiumAdapter.ts`), Playwright's default
623
- (non-fullPage) mode, against a browser context created with no
624
- `deviceScaleFactor` override (`browser.newContext({viewport})`) - so it
625
- defaults to `1`, meaning every observation this repository can currently
626
- produce has a screenshot whose raw PNG pixel dimensions equal
627
- `requestConfig.viewport.width × requestConfig.viewport.height` exactly (1
628
- CSS pixel = 1 PNG pixel). `requestConfig.viewport` (a required, strongly-typed
629
- field on every valid `ObservationArtifact`, distinct from the loosely-typed
630
- `pageEvidence` bag) is therefore the canonical, always-present source for the
631
- SVG display frame.
632
-
633
- **SVG coordinate model** (`viewer/src/components/TargetOverlaySvg.tsx`): the
634
- `<svg>` root's `viewBox` is `0 0 {requestConfig.viewport.width}
635
- {requestConfig.viewport.height}` - the exact frame `getBoundingClientRect()`
636
- already used. The screenshot loads into a `<image>` element filling that same
637
- viewBox (`preserveAspectRatio="none"`, since the two frames are already
638
- pixel-identical). Target `<rect>` elements use `geometry.x/y/width/height`
639
- completely unchanged - no rounding, no `devicePixelRatio` multiplication, no
640
- clamping; geometry lying partly outside the viewBox is drawn at its real
641
- coordinates and clipped only by the SVG root's default `overflow: hidden`
642
- (a display-only effect, verified never to touch the underlying evidence
643
- value - `tests/unit/observationCoordinateMapping.test.ts`). This is robust
644
- even if a future capture path used a different `deviceScaleFactor`: the
645
- `<image>`/viewBox scaling is presentation-only browser behavior, never a
646
- manual pixel calculation in this codebase. `devicePixelRatio` (captured as
647
- `pageEvidence.devicePixelRatio`) is shown as informational observation-level
648
- evidence only and is never consulted for any geometry calculation.
649
-
650
- **Server additions** (`src/viewerServer/evidence/observationView.ts`, one new
651
- route `GET /api/observations/<handle>/relationships`): the only new
652
- server-side computation this batch adds is one thin, defense-in-depth-wrapped
653
- call to the existing canonical, pure `deriveLayoutRelationships` (`src/domain/
654
- relationships.ts`) - never a second relationship predicate implementation.
655
- Mirrors the exact handle-decode → contained-dir-resolve → re-classify
656
- discipline `loadArtifactByHandle`/`resolveMedia` already established in
657
- Batch 2; a handle for a non-`observation` family or a non-`supported`
658
- candidate is rejected (`409`) before derivation is even attempted. The
659
- existing `GET /api/artifacts/<handle>` (full `ObservationArtifact`) and
660
- `GET /api/media/<handle>/screenshot` (Batch 2, unchanged) remain the only
661
- other data sources the observation workspace uses - no new artifact
662
- projection endpoint was needed, since the full validated domain object
663
- already contains everything the target/observation inspector displays.
664
-
665
- **Client-side presentation only** (`viewer/src/observation/targetOrder.ts`,
666
- `viewer/src/components/{ObservationWorkspace,TargetList,TargetOverlaySvg,
667
- ObservationInspector,EvidenceFieldView}.tsx`): React selects, orders
668
- (by the observation's own authored `requestConfig.targets` order, not
669
- incidental object-key order), and formats already-fetched canonical fields.
670
- It never resolves targets, computes relationships, or derives
671
- visibility/overflow/scroll-owner semantics - `deriveLayoutRelationships`
672
- runs exclusively on the server (above). An unresolved target (`not-found`/
673
- `ambiguous`/`unavailable`) is selectable from the target list and shown
674
- honestly in the inspector, but never receives a fabricated `<rect>` -
675
- `orderedTargets()`'s `hasGeometry` flag is `true` only when
676
- `geometry.state` is `'available'` or `'partial'`.
677
-
678
- **Selection**: viewer presentation state only (React `useState`, reset on
679
- observation change), never persisted, synchronized in both directions
680
- between the target list, the SVG `<rect>` (`role="button"`, keyboard-
681
- operable), and the inspector via the target's existing stable `name`.
682
-
683
- **Overlay toggles**: geometry, labels (disabled when geometry is off), and
684
- relationships - each independently toggleable and purely presentational
685
- (hiding/showing already-rendered elements), never altering the underlying
686
- evidence or the fetched artifact/graph.
687
-
688
- **PWA cache boundary preserved**: the new `/api/observations/*` route lives
689
- under the same `/api/` prefix Batch 1's `navigateFallbackDenylist` already
690
- denylists - no service-worker configuration change was needed
691
- (`tests/unit/viewerPwaBuild.test.ts` asserts this against the real built
692
- `sw.js`).
693
-
694
- ## v0.8 Batch 4 (Before/after comparison and contract/change-scope inspection) — implemented
695
-
696
- Batch 4 exposes the existing v0.4 `ComparisonArtifact` and v0.5
697
- `FrontendContractEvaluationArtifact` through the viewer, entirely under
698
- `src/viewerServer/evidence/{linkedEvidence,comparisonView,evaluationView}.ts`
699
- and `viewer/src/components/{ComparisonWorkspace,EvaluationWorkspace,
700
- ComparisonObservationPane,ClauseResultRow}.tsx`. **`compareObservations` and
701
- `evaluateFrontendContract` are never called anywhere in this batch** - every
702
- displayed comparison/evaluation field is read unchanged from its persisted
703
- artifact via the existing Batch 2 `GET /api/artifacts/<handle>`.
704
-
705
- - **Exact linked-evidence resolution** (`evidence/linkedEvidence.ts`): given
706
- a `ComparisonSourceObservationReference`/`FrontendContractObservationReference`,
707
- a `comparisonId`+`comparisonRequestId` pair, or a `baselineId`/`contractId`,
708
- resolves the matching indexed artifact by **exact identity only**
709
- (`observationId`+`requestId`+`producer.version`+`observationSchemaVersion`
710
- for observations; the id fields themselves for comparisons/contracts) -
711
- never by folder name, screenshot filename, URL, target-set, or geometry
712
- similarity. Zero matches → `missing`; two or more exact matches →
713
- `ambiguous` (never silently picks one). Mirrors the exact bounded-walk
714
- pattern Batch 2's `findImportedReferenceDir` already established.
715
- - **Two additive, read-only routes**: `GET /api/comparisons/<handle>/view`
716
- (resolves the comparison's `before`/`after`) and
717
- `GET /api/evaluations/<handle>/view` (resolves `comparison`, `baseline`,
718
- `change`, `before`, `after`) - both under `/api/`, both GET/HEAD-only, both
719
- returning only resolved-handle-or-missing-or-ambiguous status, never a
720
- duplicated copy of the linked artifact's own payload (the browser fetches
721
- that separately through the existing `GET /api/artifacts/<handle>`, reusing
722
- Batch 2's on-demand-loading contract exactly).
723
- - **Before/after visual reuse, not reimplementation**: `ComparisonObservationPane.tsx`
724
- is built entirely from Batch 3's existing lower-level primitives
725
- (`useArtifactDetail`, `orderedTargets`, `TargetOverlaySvg`) - no second
726
- screenshot-loading, coordinate-transform, or geometry-rendering code
727
- exists. The comparison's own persisted `relationshipsBefore`/
728
- `relationshipsAfter` are passed directly into `TargetOverlaySvg`'s existing
729
- `relationships` prop - never recomputed via `deriveLayoutRelationships`.
730
- `TargetOverlaySvg` gained one small additive, optional `highlightNames`
731
- prop (alongside the existing single-select `selected`) so a
732
- relationship-subject difference or a two-target contract primitive
733
- (`targets-do-not-overlap`, `target-fits-inside`, etc.) can emphasize both
734
- named targets at once without changing Batch 3's existing single-select
735
- interaction contract.
736
- - **Difference/relationship-change/clause presentation is evidence display,
737
- not re-derivation**: `ComparisonWorkspace.tsx` renders `differences`,
738
- `relationshipChanges`, `configurationChanges` (kept visually distinct from
739
- appeared/disappeared runtime differences), and `expectedDependencyEvidence`
740
- exactly as persisted, labeling dependency outcomes as explicit non-causal
741
- evidence. `comparability` (comparable/comparable-with-warnings/incomparable
742
- plus blocking/warning/unassessed reasons) is shown honestly; an
743
- `incomparable` result is visually unmistakable
744
- (`.comparability-banner--incomparable`).
745
- - **Clause joining by exact `clauseId` only** (`EvaluationWorkspace.tsx`):
746
- baseline clauses (from the linked `PersistentBaselineContract`) and
747
- per-change clauses (from the linked `PerChangeContract`) are joined to the
748
- evaluation's `clauseResults` by exact id - never by target/primitive-shape/
749
- category/position. A `clauseId` absent from both loaded contracts is shown
750
- as an honest "unresolved clause definition", never fabricated. Baseline
751
- clause active/superseded status comes exclusively from the evaluation
752
- artifact's own `activeBaselineClauseIds`/`supersededBaselineClauseIds` -
753
- never recomputed from clause overlap. `pass`/`fail`/`unavailable`/
754
- `conflict` are preserved exactly (never collapsed to a boolean);
755
- `unavailable` shows its reason, `conflict` shows its reason and
756
- `conflictingClauseIds`.
757
- - **Overall verdict is authoritative and unmistakable**: `overallVerdict`
758
- (`PASS`/`FAIL`) is rendered directly from the artifact, in a large
759
- `.overall-verdict--PASS`/`.overall-verdict--FAIL` banner - the UI never
760
- computes it from visible rows. The required safety case (a `requested`
761
- clause `pass` alongside a `protected`/`preserved` clause `fail` still
762
- producing overall `FAIL`) and the all-pass case are both proven against
763
- real, canonically-evaluated fixtures (`tests/support/evidenceFixtures.ts#writeFullPipelineFixture`/
764
- `writeAllPassPipelineFixture`) in real Chromium
765
- (`tests/browser/comparisonEvaluationWorkspace.test.ts`) - `overallVerdict`
766
- is never hand-edited to construct either demonstration.
767
- - **Target/relationship cross-highlighting uses only explicit canonical
768
- identity**: `primitiveTargetNames()` (`viewer/src/contract/clauseTargets.ts`)
769
- extracts a contract primitive's named target field(s) (`target`, `targetA`/
770
- `targetB`, `target`+`container`, `subjectTarget`/`relatedTarget`) by an
771
- exhaustive switch over `ContractPrimitiveKind` - page-level primitives
772
- (`document-width-fits-viewport`, `scroll-owner-is-document`) return no
773
- names, so clicking them never fabricates a target highlight.
774
- - **PWA cache boundary preserved**: both new routes live under `/api/`,
775
- already covered by Batch 1's `navigateFallbackDenylist`; verified against
776
- the real built `sw.js`.
777
-
778
- ## v0.8 Batch 5 (External reference and reference/candidate inspection) — implemented
779
-
780
- - **Reference indexing/media already existed (Batch 2), unchanged**: the
781
- `external-reference-imported`/`external-reference-approved` families,
782
- `GET /api/media/<handle>/image` (imported), and
783
- `GET /api/media/<handle>/source-image` (approved, resolved through
784
- `findImportedReferenceDir`'s exact `referenceId` walk) were already built
785
- in Batch 2 and required no change here - Batch 5 only adds the visual
786
- workspace consuming them.
787
- - **Two new additive, read-only routes**
788
- (`src/viewerServer/evidence/referenceView.ts`):
789
- `GET /api/references/<handle>/view` derives the selected reference's own
790
- region-relationship graph (`deriveReferenceRegionRelationships`) and
791
- requirement adequacy (`deriveReferenceRequirementAdequacy`) - both pure
792
- functions over the artifact's own persisted `regions`/`requirements`,
793
- never persisted, never a second derivation engine (mirrors Batch 3's
794
- `getObservationRelationships` server-side-derivation pattern).
795
- `GET /api/references/<handle>/candidate/<handle>/view` evaluates
796
- reference/candidate compatibility through the existing canonical
797
- `evaluateReferenceCandidateCompatibility` (never a second, viewer-owned
798
- compatibility model) and separately lists every existing
799
- `FrontendContractEvaluationArtifact` whose own persisted `after` reference
800
- exactly identifies the candidate, for explicit, never-auto-selected
801
- optional display.
802
- - **Reference-image SVG coordinate model is a genuinely distinct domain from
803
- the candidate's runtime SVG** (`ReferenceRegionOverlaySvg.tsx`): `viewBox`
804
- is the reference image's own pixel dimensions (never the candidate's CSS
805
- viewport, never devicePixelRatio-multiplied); each region's canonical
806
- `{x, y, width, height}` is rendered unchanged. Because this is a different
807
- coordinate domain and data source from `TargetOverlaySvg` (runtime CSS
808
- pixels, `TargetGeometry`), it is a separate, sibling component rather than
809
- a parameterization of the existing one - reuse would have silently
810
- conflated the two domains. The candidate side, in contrast, reuses Batch
811
- 3/4's exact `ComparisonObservationPane`/`TargetOverlaySvg` machinery
812
- unchanged (a synthetic `{status:'resolved', handle}` `LinkStatus` is
813
- constructed once a candidate is explicitly chosen).
814
- - **Reference region selection and runtime target selection are two
815
- independent, never-synchronized selection domains**
816
- (`ReferenceWorkspace.tsx`): selecting a reference region never selects or
817
- highlights a runtime target, even when both happen to share the same
818
- string name (proven with a real Chromium fixture deliberately naming both
819
- `"header"` - `tests/browser/referenceCandidateWorkspace.test.ts`, Case E).
820
- No binding connector/highlight-across-panes exists in this batch - that is
821
- Batch 6's explicit-binding-interaction scope.
822
- - **Compatibility vs. reference adequacy vs. candidate fidelity are kept
823
- strictly distinct, never conflated**: compatibility
824
- (`comparable`/`comparable-with-warnings`/`incomparable` plus
825
- blocking/warning/unassessed reasons) comes only from
826
- `evaluateReferenceCandidateCompatibility`; reference-side requirement
827
- adequacy (`adequate`/`partial`/`inadequate`) comes only from
828
- `deriveReferenceRequirementAdequacy`; candidate fidelity is never computed
829
- in this batch at all - the UI always shows an explicit "not evaluated in
830
- this batch" note rather than ever implying a fidelity PASS from a
831
- compatibility PASS or an adequate reference (task §32/§33 boundary,
832
- `evaluateReferenceCandidateFidelity` is never imported/called anywhere in
833
- Batch 5).
834
- - **Optional contract/evaluation context is opt-in, never inferred**
835
- (`ReferenceWorkspace.tsx`): when the reference/candidate view lists more
836
- than one exactly-matching evaluation artifact, the developer must
837
- explicitly pick one from a `<select>` - the newest/first match is never
838
- silently chosen, and with zero selected the candidate's contract status
839
- reads "not selected/not available", never a fabricated PASS.
840
- - **Imported vs. approved image ownership preserved exactly as Batch 2 built
841
- it**: an imported reference's image is fetched from its own directory; an
842
- approved reference's image is fetched from its exact imported source via
843
- `sourceReference.referenceId`, never assumed co-located, never
844
- duplicated - proven with a real Chromium fixture asserting the two
845
- `<image href>` values resolve to the `image`/`source-image` roles
846
- respectively (Cases A/B).
847
- - **PWA cache boundary preserved**: both new routes live under `/api/`,
848
- already covered by Batch 1's `navigateFallbackDenylist`; verified against
849
- the real built `sw.js` (no new `registerRoute`, no `/api/references`
850
- precache entry).
851
-
852
- ## v0.8 Batch 6 (Explicit-binding interaction, zoom/pan, conditional lock, and on-demand reference fidelity) — implemented
853
-
854
- - **`view --bindings-file <json-file>`**: reuses the exact same operational
855
- binding-file wrapper parser (`loadBindingsFile` in `src/cli.ts`) that
856
- `evaluate-reference-fidelity --bindings-file` already used - one shared
857
- parser, never a second divergent one. The file is read once at startup;
858
- its declarations become `ViewerServerState.bindingDeclarations` (an opaque
859
- `unknown[]` until validated against a specific reference); the file path
860
- itself is never persisted, returned, or exposed to the browser. Reference-
861
- specific declaration validity (region existence, shape) is deferred to the
862
- moment a reference is actually selected server-side, via the existing
863
- canonical `isValidReferenceRuntimeBindingDeclarations` - never checked at
864
- startup without a reference.
865
- - **Two new additive, read-only routes**
866
- (`src/viewerServer/evidence/referenceView.ts`):
867
- `GET /api/references/<handle>/candidate/<handle>/bindings` validates the
868
- session's declarations against the selected reference and calls the
869
- existing canonical `evaluateReferenceRuntimeBindings` exactly once.
870
- `GET /api/references/<handle>/candidate/<handle>/fidelity` is the explicit
871
- on-demand fidelity trigger - calls the existing canonical
872
- `evaluateReferenceCandidateFidelity` exactly once, using the exact same
873
- session declarations, so its embedded `bindings` field and the `/bindings`
874
- route's own result always structurally agree for identical inputs (same
875
- pure function, same arguments). Neither route persists anything; both are
876
- `GET` (idempotent, deterministic, ephemeral over already-selected explicit
877
- input) - no mutation route was added.
878
- - **`deriveCoordinateScale` exported additively** from
879
- `externalReferenceFidelity.ts` (previously module-private) - Batch 6's
880
- view-lock eligibility reuses this exact function unchanged (same formula,
881
- same `ASPECT_RATIO_MAPPING_TOLERANCE`, same no-applicable-viewport
882
- failure) rather than a second aspect-ratio/scale implementation. The
883
- existing `GET /api/references/<handle>/candidate/<handle>/view` route now
884
- additionally returns `coordinateMapping: DeriveCoordinateScaleResult` -
885
- purely a function of the reference, independent of the candidate.
886
- - **Explicit-binding cross-selection uses only canonical
887
- `ReferenceRuntimeBindingResult.referenceRegion`/`.runtimeTarget` fields**
888
- (`ReferenceWorkspace.tsx`): selecting a `bound` reference region
889
- highlights (via `TargetOverlaySvg`'s existing Batch 4 `highlightNames`
890
- prop - never the primary `selected`/`aria-pressed` target) its exact
891
- declared runtime target; selecting a runtime target highlights every
892
- region whose `bound` result names it (many-to-one, via a new additive
893
- `highlightRegionIds` prop on `ReferenceRegionOverlaySvg`, matching
894
- `TargetOverlaySvg`'s established highlight pattern). `ambiguous`/
895
- `unavailable` results never cross-select. Proven with a real equal-name
896
- ("header" region + "header" target) Chromium fixture: no cross-selection
897
- without an explicit declaration, real cross-selection with one.
898
- - **Zoom/pan is a repository-owned, presentation-only hook**
899
- (`viewer/src/hooks/useZoomPan.ts`): bounded `[1x, 8x]` scale, `×1.25`/
900
- `÷1.25` step, expressed as one `{scale, focalX, focalY}` triple in the
901
- pane's own source-coordinate frame (reference-image pixels or candidate
902
- CSS pixels - never rewritten). Renders via an SVG `viewBox` override
903
- (additive `viewBoxOverride`/`svgRef`/pointer-handler props on
904
- `TargetOverlaySvg`/`ReferenceRegionOverlaySvg`, defaulting to Batch 3/5's
905
- exact prior behavior when omitted) - image and overlay stay one
906
- transformed unit automatically since both live inside the same `<svg>`
907
- root, and native SVG hit-testing means selection keeps working correctly
908
- under zoom/pan with no extra coordinate math. Panning uses the SVG
909
- element's own `getScreenCTM()` to convert screen-space pointer deltas into
910
- source-space deltas - reuses the browser's native transform rather than a
911
- custom aspect-ratio-aware pixel calculation - and only engages (calling
912
- `setPointerCapture`) once the pointer has moved past a small threshold, so
913
- an ordinary click on a region/target rect is never hijacked into a
914
- phantom drag. Fit and Reset are the same fitted-1x/centered default (task
915
- §26 - no second presentation-only default was introduced).
916
- - **View lock reuses one single shared state, never two independently
917
- synchronized states**: `ReferenceWorkspace.tsx` owns `refZoom`/`candZoom`
918
- directly (always-controlled `useZoomPan` calls) and each pane's zoom/pan
919
- action handler updates both states in one synchronous call when locked -
920
- no reactive effect watches one pane's state to update the other, so no
921
- feedback-loop risk exists. Lock is available only when a candidate is
922
- selected, compatibility is not `incomparable`, and `coordinateMapping.ok`
923
- is `true`; any change to that eligibility (including selecting a
924
- different reference/candidate) immediately disables lock and shows an
925
- actionable reason. Synchronization converts a candidate CSS-pixel focal
926
- point/scale into reference-image-pixel space (and back) using only
927
- `coordinateMapping.scale.scaleX`/`scaleY` - the exact same canonical
928
- factor `deriveCoordinateScale` already produces, applied as a straight
929
- multiply/divide (its own algebraic inverse), never a second mapping rule.
930
- - **Contract/fidelity independence is never collapsed into one status**:
931
- the existing Batch 5 "Optional contract/evaluation context" section
932
- (unchanged) and the new on-demand `ReferenceFidelityPanel` are two
933
- separate sections rendering two separate canonical results
934
- (`FrontendContractEvaluationArtifact.overallVerdict` and
935
- `ReferenceCandidateFidelityEvaluation.state`) side by side; when both are
936
- present, an explicit note states that fidelity does not override an
937
- active contract failure. No new coordinator/aggregate verdict is computed
938
- anywhere in this batch.
939
- - **PWA cache boundary preserved**: both new routes live under `/api/`,
940
- already covered by Batch 1's `navigateFallbackDenylist`.
941
-
942
- ## v0.8 Batch 7 (Bounded agent context, correlation, provenance, and raw evidence navigation) — implemented
943
-
944
- - **Bounded agent context remains programmatic-only**: no filesystem writer
945
- was added for `BoundedAgentContextArtifact` (it is still not an
946
- Observer-evidence-root artifact family - `src/viewerServer/evidence/classify.ts`'s
947
- "known but unreadered kind" comment is unchanged). `view --context-file
948
- <json-file>` reads exactly one already-serialized
949
- `BoundedAgentContextArtifact` value directly (no wrapper object) as
950
- explicit, session-only viewer input - read once at startup
951
- (`src/cli.ts#loadContextFile`, mirroring `loadBindingsFile`'s exact
952
- read/size-bound/parse shape), classified by
953
- `src/viewerServer/context.ts#classifyContextFileContent` (reuses the
954
- existing canonical `isValidBoundedAgentContextArtifact` - never a second
955
- validator), and held only in `ViewerServerState.context` for the life of
956
- the process. The file's size is bounded by the existing Batch 2
957
- `MAX_MANIFEST_CANDIDATE_BYTES` (2,000,000 bytes) rather than a second
958
- bound, since a context artifact's own frozen numeric caps already make it
959
- far smaller in any realistic case.
960
- - **Three honest session states, never coerced into one another**: `'none'`
961
- (no `--context-file`; every Batch 1-6 feature stays fully available),
962
- `'unsupported-version'` (recognized `artifactKind`, a `schemaVersion`
963
- other than the current one - the viewer still starts, showing this
964
- state explicitly rather than either failing or misinterpreting the
965
- fields), and `'valid'` (structurally validated current-schema context).
966
- Every other problem (unreadable file, wrong `artifactKind`, a
967
- structurally invalid *current*-schema artifact) fails viewer startup
968
- clearly - an explicitly supplied file is never silently ignored.
969
- - **`GET /api/context`** (`httpServer.ts`) returns the session's exact
970
- classified state; for `'valid'`, it additionally returns
971
- `sourceResolution` - the result of resolving
972
- `artifact.sources` against the current evidence root by **exact
973
- canonical identity only**
974
- (`src/viewerServer/evidence/contextSourceView.ts`, reusing/extending
975
- Batch 4's `linkedEvidence.ts` resolver pattern with three additive
976
- functions: `resolveObservationById` (bare `observationId`, the only
977
- identity a context source reference actually carries),
978
- `resolveEvaluationByIdentity`, and `resolveReferenceByIdentity`). Zero
979
- matches → `missing`; two or more → `ambiguous` (never silently picks
980
- one) - the same discipline every other Batch 4/5 resolver already
981
- established.
982
- - **Raw structured evidence reuses the existing Batch 2 artifact-detail
983
- route unchanged**: `RawEvidenceViewer.tsx` calls the existing
984
- `useArtifactDetail`/`GET /api/artifacts/<handle>` for any exactly-resolved
985
- source - no second full-artifact retrieval mechanism, no local filesystem
986
- read, no arbitrary path accepted from the browser.
987
- `EvidenceReference.path` values are always displayed as plain provenance
988
- text, never passed to `fs.readFile`/`path.resolve`/a static file server.
989
- - **Bounded runtime targets, adequacy, omissions, truncations, and
990
- correlation are rendered exactly as the validated artifact states them** -
991
- never recomputed, never boolean-collapsed
992
- (`ContextWorkspace.tsx`): `Adequacy.state`
993
- (`adequate`/`partial`/`inadequate`) and reasons are shown verbatim;
994
- absent bounded-target fields render "not included in this bounded
995
- context", never a fabricated falsy/zero value; `required: true`
996
- omissions/truncations render in a visually distinct
997
- `.context-required-loss` block, separate from optional ones;
998
- `correlations` absent renders "Static correlation not included in this
999
- context" - never "unavailable" (that status is reserved for a real
1000
- per-target `RuntimeStaticCorrelationRecord` with zero candidates).
1001
- `correlated`/`ambiguous`/`unavailable` are preserved exactly; a
1002
- `correlated` record's one candidate is labeled "Correlated candidate", an
1003
- `ambiguous` record shows **every** supplied candidate with none visually
1004
- promoted, and `unavailable` fabricates zero candidates - matching the
1005
- frozen `CORRELATION_STATUSES` invariants
1006
- (`domain/boundedAgentContext.ts`) the validator itself already enforces.
1007
- All new UI text was audited against ownership/edit-authorization language
1008
- (no "owner"/"source owner"/"owned by") - correlation is presented as
1009
- evidence, never as edit authorization.
1010
- - **Context-target ↔ runtime-target interaction never infers source
1011
- ownership**: selecting a bounded target or a correlation record uses only
1012
- exact `targetId`/`runtimeTargetId` string matching; for each *exactly
1013
- resolved* source observation, `SourceObservationTargetCheck` checks
1014
- membership in that observation's own already-fetched `targetEvidence`
1015
- (a plain lookup over already-loaded JSON, never a new derivation) and, if
1016
- more than one resolved source observation contains the same target id,
1017
- lists all of them rather than picking one.
1018
- - **Bounded reference-fidelity projection is never recomputed, and is kept
1019
- visibly distinct from a live on-demand evaluation**: absent `fidelity`
1020
- renders "Reference fidelity not included in this bounded context" - never
1021
- implied as passing. When present, `mismatches` and `protectedContext` are
1022
- rendered in separate sections from the artifact's own fields exactly as
1023
- supplied; a `state: 'not-evaluated'` blocked projection always shows
1024
- `blockedBy` prominently and never renders an empty mismatch list as "no
1025
- problems". When the context's `referenceId`/`candidateObservationId`
1026
- exactly resolve within the current evidence root, `ContextWorkspace.tsx`
1027
- embeds the existing, unchanged Batch 6 `ReferenceFidelityPanel` (the same
1028
- on-demand `evaluateReferenceCandidateFidelity` trigger) directly beneath
1029
- the bounded projection, labeled "Bounded context fidelity projection"
1030
- above and "Current on-demand fidelity evaluation" below - two separate,
1031
- clearly labeled evidence instances, never silently merged or replaced.
1032
- - **No runtime rebuild of context or correlation, and no my-dev-kit
1033
- execution from the shipped viewer**: grep-verified - `projectBoundedAgentContext(`,
1034
- `deriveRuntimeStaticCorrelations(`, and `attachRuntimeStaticCorrelations(`
1035
- appear nowhere under `src/viewerServer/` or `viewer/src/` (only in test/
1036
- fixture-generation code, per the frozen plan's explicit test-fixture
1037
- exception); no `child_process`/`npx @dailephd/my-dev-kit` invocation
1038
- exists in the viewer server or browser bundle.
1039
- - **PWA cache boundary preserved**: `GET /api/context` lives under `/api/`,
1040
- already covered by Batch 1's `navigateFallbackDenylist`.
1041
-
1042
- ## v0.8 Batch 8 (Integrated viewer acceptance, PWA hardening, and packaged proof) — implemented
1043
-
1044
- Batch 8 is the final v0.8 implementation batch. It is integration/hardening,
1045
- not a new architecture layer: no new API route, no new CLI flag, and no new
1046
- canonical-engine call site were added. See
1047
- `docs/reports/v0.8-integrated-viewer-acceptance-batch8.md` for the full
1048
- record.
1049
-
1050
- - **Closed three named real-browser coverage gaps**, each proved against the
1051
- actual built viewer through the actual loopback server, never a hand-edited
1052
- fixture verdict: many reference regions bound to one runtime target all
1053
- cross-highlight together (the pre-existing target→regions loop in
1054
- `ReferenceWorkspace.tsx` already iterated every matching binding - the gap
1055
- was in real-browser proof, not in the derivation); reference-fidelity
1056
- `fail` alongside a genuine frontend-contract `PASS` for the same candidate
1057
- display independently (the pre-existing independence note in
1058
- `ReferenceWorkspace.tsx` was already verdict-agnostic); a bounded context
1059
- whose sources include two observations sharing a stable target id lists
1060
- every matching source observation (the pre-existing
1061
- `SourceObservationTargetCheck` in `ContextWorkspace.tsx` already checked
1062
- membership per source independently, never picking one).
1063
- - **One real accessibility defect found and fixed**: a cross-highlighted,
1064
- non-selected region/target `<rect>` (`TargetOverlaySvg.tsx`,
1065
- `ReferenceRegionOverlaySvg.tsx`) exposed no accessible state distinguishing
1066
- it from a plain unselected rect - `aria-pressed` correctly stayed `false`
1067
- (it is not the primary single-selection), but nothing else communicated
1068
- the highlight to assistive technology. Fixed by adding
1069
- `data-highlighted="true"` and an `aria-label` suffix
1070
- (`" (highlighted: related to current selection)"`) when highlighted and
1071
- not selected, leaving `aria-pressed` semantics untouched.
1072
- - **First live-browser PWA proof suite** (`tests/browser/pwaHardening.test.ts`):
1073
- real service-worker registration and activation against the built shell;
1074
- the manifest fetched and confirmed `display: "standalone"`; zero Cache
1075
- Storage entries under any `/api/` pathname after normal use, confirming
1076
- the `navigateFallbackDenylist` boundary holds live, not just in the built
1077
- `sw.js` regex; and the hard server-down gate - after the server is closed
1078
- and the same page reloaded, the app shell still renders from the precache,
1079
- but the evidence-dependent surface shows the explicit
1080
- `.evidence-list__error` "Evidence index unavailable" state, with the
1081
- previously-visible evidence asserted absent. Install-control is proven
1082
- only via synthetic `beforeinstallprompt` dispatch (a genuine browser
1083
- install prompt was not observed under automation). Standalone-mode CDP
1084
- display-mode emulation was attempted but not observed to take effect -
1085
- recorded honestly, never overstated as actual OS-level installation proof.
1086
- - **Packaged-candidate proof**: `npm pack` → clean consumer install (outside
1087
- the repository) → the actually-installed CLI executable (not repo
1088
- `dist/cli.js`) → the installed `view` server → real Chromium against the
1089
- packaged/installed server, not a source-checkout dev server. Read-only
1090
- evidence-hash proof (SHA-256 of every file in the exercised evidence root,
1091
- taken before and after the packaged-browser session) confirmed no
1092
- mutation and no new viewer-created artifact anywhere in the evidence root.
1093
- - **Re-confirmed the no-second-engine invariant** across all eight batches by
1094
- re-running the exact `grep -rn` audit from earlier batches - unchanged
1095
- findings, no duplicate evidence engine exists.
1096
-
1097
- ## v0.9 visual annotation architecture (released in 0.9.0)
1098
-
1099
- v0.9 is released as package version `0.9.0`. It adds one new
1100
- evidence family and a narrow local authoring path to the existing viewer. It
1101
- adds no new evaluator, no second contract or reference model, and no new
1102
- PASS/FAIL semantics.
1103
-
1104
- **Evidence ownership**:
1105
-
1106
- - `src/domain/visualAnnotation.ts` owns `VisualAnnotationArtifact` (artifact
1107
- kind `my-frontend-observer/visual-annotation`, schema `1.0.0`): the source
1108
- union, structured marks, explicit associations, candidate/confirmed
1109
- interpretation, validation, and the pure overlay SVG renderer.
1110
- - `src/domain/visualAnnotationIdentity.ts` owns deterministic request identity
1111
- and fresh instance identity.
1112
- - `src/artifacts/visualAnnotationArtifactWriter.ts` and
1113
- `visualAnnotationArtifactReader.ts` own atomic persistence (temporary
1114
- `.tmp-<id>` directory, then rename) and canonical reading, including the
1115
- derived `annotation-overlay.svg` and its digest.
1116
- - `src/application/visualAnnotationPersistenceService.ts` owns saving one
1117
- annotation or one superseding revision.
1118
-
1119
- **Coordinate domains**: runtime annotations use the observation's runtime CSS
1120
- pixel space. Reference annotations use the reference image's own pixel space
1121
- (for an approved reference, the owning imported image). The two domains are
1122
- never mixed. Zoomed or panned drawing is mapped back through the SVG's own
1123
- transform (`viewer/src/svg/sourceCoordinates.ts`).
1124
-
1125
- **Viewer discovery and media** (`src/viewerServer/evidence/`): the index
1126
- classifies `visual-annotation` evidence and skips writer `.tmp-*`
1127
- directories. `annotationView.ts` resolves an annotation's exact canonical
1128
- source and reports `unavailable` instead of guessing a replacement. The media
1129
- resolver serves the `annotation-overlay` role only after re-rendering the SVG
1130
- from the artifact and verifying it, with a script-blocking sandbox policy.
1131
-
1132
- **Project-aware authoring security**: `src/viewerServer/authoringSecurity.ts`
1133
- owns the in-memory 32-byte session capability and the Host, Origin, token,
1134
- content-type, and content-encoding checks. `httpServer.ts` owns the shared
1135
- bounded JSON gate and routes exactly three `POST` responsibilities.
1136
- `view --root` never creates an authoring session, so the standalone
1137
- arbitrary-root viewer stays read-only.
1138
-
1139
- 1. `POST /api/annotations` (`src/viewerServer/annotationAuthoring.ts`) saves
1140
- a new annotation or a revision through the canonical persistence service,
1141
- with stale-parent conflict detection. This module also owns the single
1142
- per-session write queue used by all three routes.
1143
- 2. `POST /api/annotations/:handle/promote-contract`
1144
- (`src/viewerServer/annotationContractPromotion.ts`) calls
1145
- `src/application/visualAnnotationContractPromotionService.ts`. Selected
1146
- confirmed runtime intent becomes one canonical `PerChangeContract` through
1147
- the existing contract persistence service. Optional activation goes only
1148
- through `activateProjectChangeContract` in
1149
- `src/application/projectWorkflowService.ts`.
1150
- 3. `POST /api/annotations/:handle/materialize-reference`
1151
- (`src/viewerServer/annotationReferenceMaterialization.ts`) calls
1152
- `src/application/visualAnnotationReferenceMaterializationService.ts`.
1153
- Selected confirmed reference intent becomes a new imported
1154
- `ExternalReferenceArtifact` through the existing `importExternalReference`,
1155
- superseding the source. The image bytes come only from the safe media
1156
- resolver.
1157
-
1158
- Output locations come from `src/projectWorkflow/projectPaths.ts`
1159
- (`annotations`, `contracts`, and `references` under
1160
- `.frontend-observer/evidence`). The browser never supplies a path.
1161
-
1162
- **Viewer UI** (`viewer/src/`): `annotation/` holds pure presentation models
1163
- (`annotationGeometry.ts`, `runtimeIntent.ts`, `referenceIntent.ts`,
1164
- `confirmation.ts`). `components/AnnotationLayer.tsx`,
1165
- `AnnotationToolbar.tsx`, `RuntimeAnnotationPanel.tsx`,
1166
- `ReferenceAnnotationPanel.tsx`, `CandidateRegionPreviewLayer.tsx`, and
1167
- `AnnotationPanelSections.tsx` render marks, tools, intent, promotion, and
1168
- materialization. Hooks under `hooks/` hold draft, saved-list, session,
1169
- pointer, promotion, and materialization state. The authoring token lives only
1170
- in React memory.
1171
-
1172
- **Existing evaluators remain authoritative**: the canonical contract
1173
- evaluator, reference relationship derivation, requirement adequacy, reference
1174
- fidelity, and project `check` are unchanged. Annotation evidence feeds them
1175
- only through promoted contracts and materialized references.
1176
-
1177
- ## Retained v0.1 architecture constraints
1178
-
1179
- v0.1 planning preserved these approved boundaries without treating module
1180
- names from the historical run as mandatory:
1181
-
1182
- ```text
1183
- thin command-line boundary
1184
- ↓
1185
- reusable observation engine/application layer
1186
- ↓
1187
- browser automation boundary
1188
- ↓
1189
- observer-owned runtime evidence
1190
-
1191
- observer-owned domain/schema
1192
- ↓
1193
- artifact ownership boundary
1194
-
1195
- deterministic fixture/test boundary
1196
- ↓
1197
- browser-level validation
1198
- ```
1199
-
1200
- Use one browser engine implementation, keep browser logic out of presentation,
1201
- avoid speculative plugin/multi-browser abstractions, and keep observed
1202
- applications external. Versions before v0.6 did not add runtime coupling to
1203
- sibling ecosystem projects. v0.6 adds only explicit bounded context and
1204
- correlation/export contracts within this repository, preserving independent
1205
- ownership; orchestrator-consumption and lab-compatibility work are separate
1206
- sibling-repository deliverables, not part of this repository's architecture.
1207
-
1208
- The text/config-driven coding-agent workflow and the non-UI external-reference
1209
- evidence foundation are operational as of v0.7. The viewer and annotation
1210
- layers must consume the same canonical observation, relationship, comparison,
1211
- contract, change-scope, reference, correlation, and context boundaries rather
1212
- than creating parallel engines. The concrete implementation plan and module
1213
- layout for each future version must be designed only after that version's
1214
- planning workflow inspects the current repositories.
1215
-
1216
- v0.7 Prompt 1 implements only the bottom of that external-reference stack: a
1217
- new, standalone `ExternalReferenceArtifact` evidence root
1218
- (`src/domain/externalReference.ts`, `externalReferenceImage.ts`,
1219
- `externalReferenceIdentity.ts`, `src/artifacts/externalReferenceArtifact{Writer,Reader}.ts`,
1220
- `src/application/externalReferencePersistenceService.ts`) with its own
1221
- identity, provenance, bounded image metadata, and a two-state
1222
- (`imported`/`approved`) lifecycle - see `docs/CONTRACTS.md` "v0.7 Prompt 1
1223
- external-reference artifact contract" for the exact shape. It follows the
1224
- same identity/persistence/diagnostics/export conventions as every existing
1225
- artifact family (deterministic canonicalize-then-sha256 request identity,
1226
- nonce-based fresh instance identity, atomic temp-dir-then-rename persistence,
1227
- the shared `DIAGNOSTIC_CODES` vocabulary) without reusing or duplicating the
1228
- observation, comparison, or contract engines themselves - an external
1229
- reference is desired-design evidence, never an `ObservationArtifact`, an
1230
- approved baseline, or a runtime target.
1231
-
1232
- v0.7 Prompt 2 adds explicit reference regions and reusable reference-region
1233
- relationships on top of that foundation (`domain/externalReferenceRegions.ts`,
1234
- `externalReferenceRegionRelationships.ts`) - see `docs/CONTRACTS.md` "v0.7
1235
- Prompt 2 explicit reference regions and relationships" for the exact shape.
1236
- The relationship-derivation predicates are reused verbatim (now exported
1237
- additively) from `domain/relationships.ts` rather than reimplemented, so
1238
- reference-region geometry and runtime-target geometry can never diverge on
1239
- the same underlying formula; only the geometry-only relationship families
1240
- apply, since a static image exposes no DOM, scroll, or viewport evidence.
1241
- v0.7 Prompt 3 adds selected design requirements, tolerance semantics, and
1242
- reference-evidence adequacy on top of that region model
1243
- (`domain/externalReferenceRequirements.ts`,
1244
- `externalReferenceRequirementIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1245
- Prompt 3 selected design requirements, tolerance semantics, and
1246
- reference-evidence adequacy" for the exact shape. Requirement categories are
1247
- the exact v0.5 `AuthoredChangeScopeCategory` vocabulary, imported directly
1248
- rather than reinvented, since that type carries no runtime-only coupling of
1249
- its own; tolerance is a genuinely new, reference-owned type (never a reuse
1250
- of `frontendContracts.ts`'s runtime/CSS-pixel-implicit `ContractTolerance`);
1251
- and reference-evidence adequacy is a small, independently-owned vocabulary
1252
- distinct from `boundedAgentContext.ts`'s runtime/static-correlation
1253
- `Adequacy`. A region property or derived relationship is never promoted to
1254
- an executable requirement automatically - only explicit user/configuration
1255
- selection does that.
1256
-
1257
- v0.7 Prompt 4 adds explicit reference applicability
1258
- (`domain/externalReferenceApplicability.ts`) and observation-side explicit
1259
- state identity (`domain/explicitState.ts`, shared by both artifact
1260
- families), plus one new pure domain module,
1261
- `domain/externalReferenceCompatibility.ts`, that evaluates whether a
1262
- candidate `ObservationArtifact` describes the same frontend state as a
1263
- given `ExternalReferenceArtifact` - see `docs/CONTRACTS.md` "v0.7 Prompt 4
1264
- reference applicability and candidate-state compatibility" for the exact
1265
- shape. This is page/state-level only, never geometry or fidelity, and
1266
- remains a wholly separate concern from Prompt 3's reference-evidence
1267
- adequacy - the two can independently disagree (an adequate reference can be
1268
- incomparable against a given candidate, and vice versa). Rather than
1269
- inventing a second comparability engine, Prompt 4 extracts one new exported
1270
- pure helper from v0.4's own `domain/comparisonEngine.ts`
1271
- (`assessOptionalComparabilityDimension`) and reuses it from both v0.4's
1272
- `evaluateComparability` (Observation-vs-Observation) and the new
1273
- `evaluateReferenceCandidateCompatibility` (Reference-vs-Observation) - the
1274
- one additive behavior change to v0.4 itself is that `evaluateComparability`
1275
- now assesses (rather than always reporting unassessed) theme/authenticated-
1276
- state/application-state whenever both observations declare
1277
- `requestConfig.explicitState`, while every historical/legacy observation
1278
- pair retains the exact prior unassessed-only behavior. No new persisted
1279
- artifact kind is introduced for the compatibility result; it is a pure,
1280
- on-demand function of two already-persisted artifacts.
1281
-
1282
- v0.7 Prompt 5 adds explicit reference-region <-> runtime-target binding
1283
- (`domain/externalReferenceRuntimeBinding.ts`) - see `docs/CONTRACTS.md`
1284
- "v0.7 Prompt 5 explicit reference-region <-> runtime-target binding" for
1285
- the exact shape. It answers only "which stable v0.2 runtime target does
1286
- this candidate observation resolve for each explicitly declared reference
1287
- region", strictly downstream of Prompt 4's compatibility gate (reused
1288
- verbatim, never duplicated) and strictly upstream of v0.6's own
1289
- runtime/static correlation - the two identity domains (a Prompt 2
1290
- `ReferenceRegion.id` and a v0.2 `NamedTarget.name`) never collapse into
1291
- each other, and this stage stops at the runtime target, never reaching
1292
- source ownership. Following v0.6's uncertainty discipline
1293
- (`domain/boundedAgentContextCorrelation.ts`), binding never guesses through
1294
- ambiguity - an ambiguously or unavailably resolved v0.2 target is reported
1295
- as such, never silently treated as bound - though the actual per-status
1296
- mapping (`bound`/`ambiguous`/`unavailable`) is binding's own, independently
1297
- owned vocabulary, not a reuse of v0.6's `correlated`/`ambiguous`/
1298
- `unavailable` correlation-status semantics (a different evidence boundary:
1299
- correlation ranks *static candidates* for one runtime target, whereas
1300
- binding resolves *one runtime target's own existence* for one declared
1301
- correspondence). No second target resolver, no browser execution, and no
1302
- new persisted artifact family were introduced; neither
1303
- `ExternalReferenceArtifact` nor `ObservationArtifact` is mutated to carry a
1304
- binding result, since a reference may later be evaluated against several
1305
- candidates and an observation against several references.
1306
-
1307
- v0.7 Prompt 6 adds structured reference-vs-candidate fidelity evaluation
1308
- (`domain/externalReferenceFidelity.ts`, `application/referenceFidelityEvaluationService.ts`,
1309
- and the `evaluate-reference-fidelity` CLI command) - the first point in this
1310
- stack where a reference's authored expectation is compared against live
1311
- candidate evidence. See `docs/CONTRACTS.md` "v0.7 Prompt 6 structured
1312
- reference-vs-candidate fidelity evaluation" for the exact shape. It is
1313
- downstream of every prior v0.7 prompt and reuses each verbatim: Prompt 3's
1314
- `deriveReferenceRequirementAdequacy`/`deriveReferenceRequirementExpectation`/
1315
- `deriveReferenceRequirementMeasurement` (never redefined), Prompt 4's
1316
- `evaluateReferenceCandidateCompatibility` (a hard gate, never duplicated),
1317
- and Prompt 5's `evaluateReferenceRuntimeBindings` (the sole source of
1318
- runtime-target identity - no automatic binding, no second target resolver).
1319
- It also reuses v0.4's `deriveLayoutRelationships` for runtime relationship
1320
- evidence, scoped to the exact requested relationship family (the same
1321
- Prompt 3 bug-fix precedent). The one genuinely new problem this prompt
1322
- solves is the reference-image-pixel <-> CSS-pixel coordinate mapping: a
1323
- single explicit, deterministic full-frame scale derived from
1324
- `reference.applicability.viewport` and the reference image's own
1325
- dimensions, with a tiny independent aspect-ratio-coherence check (never a
1326
- design tolerance) gating whether that mapping exists at all. No new
1327
- persisted artifact family, no browser execution, and no source-ownership
1328
- attribution - this is reference fidelity only, a separate concern from any
1329
- later v0.5 baseline/per-change contract result or v0.7 overall verdict.
1330
-
1331
- v0.7 Prompt 7 adds bounded reference-fidelity projection into the existing
1332
- v0.6 bounded-agent-context architecture (`domain/referenceFidelityProjection.ts`,
1333
- plus additive extensions to `domain/boundedAgentContext.ts`,
1334
- `domain/boundedAgentContextIdentity.ts`, and
1335
- `domain/boundedAgentContextProjection.ts`) - see `docs/CONTRACTS.md` "v0.7
1336
- Prompt 7 bounded reference-fidelity projection and v0.6 bounded-agent-
1337
- context integration" for the exact shape. `projectBoundedAgentContext`
1338
- itself, not a new parallel context system, gains one new optional input (an
1339
- already-computed Prompt 6 fidelity evaluation): fidelity-relevant runtime
1340
- targets fold into the exact same required/permitted-target-allocation,
1341
- evidence-tiering, omission/truncation, and adequacy machinery v0.5 contract
1342
- clauses already compete in, and a new `fidelity?:
1343
- BoundedReferenceFidelityProjection` field on `BoundedAgentContextArtifact`
1344
- (mirroring `correlations?`'s own additive, non-version-bumping precedent
1345
- from v0.6 Batch 3) carries a bounded, priority-ordered selection of Prompt
1346
- 6's non-passing requirement results plus passing protected/preserved
1347
- context. No second bounded-context architecture, no recomputation of Prompt
1348
- 2-6/v0.4/v0.5 logic, and no change to v0.6's own runtime/static correlation
1349
- (`deriveRuntimeStaticCorrelations`/`attachRuntimeStaticCorrelations` are
1350
- untouched and reused exactly as before) - a caller joins fidelity, target,
1351
- and correlation evidence by the one stable v0.2 runtime target id all three
1352
- already share. Every new field is optional and additive; a pre-Prompt-7
1353
- caller supplying no fidelity evidence receives byte-identical output,
1354
- including logical identity.
1355
-
1356
- v0.7 Prompt 8 adds the first complete, controlled end-to-end external-
1357
- reference correction workflow (`domain/referenceCorrectionWorkflow.ts`,
1358
- `domain/referenceCorrectionIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1359
- Prompt 8 controlled end-to-end external-reference coding-agent correction
1360
- workflow" for the exact shape. This is a narrowly-scoped coordinator, not a
1361
- second workflow engine: it exposes exactly two pure operations -
1362
- `prepareReferenceCorrection` (pre-change evidence -> a bounded coding-agent
1363
- handoff, built from Prompt 1/3/4/5/6/7's existing engines) and
1364
- `reviewReferenceCorrectionAttempt` (a fresh post-edit candidate -> one
1365
- composed overall result, built from v0.4's `compareObservations`, v0.7
1366
- Prompt 6's `evaluateReferenceCandidateFidelity`, and v0.5's
1367
- `evaluateFrontendContract`) - with an explicit, un-automatable seam between
1368
- them where an external implementation actor edits target source. Overall
1369
- `'pass'` requires both reference fidelity `'pass'` and v0.5 contract
1370
- evaluation `'PASS'` - matching the selected design reference is necessary
1371
- but never sufficient, so a candidate that visually satisfies the reference
1372
- while regressing an active protected or preserved contract clause still
1373
- resolves to overall `'fail'`. Review identity is a deterministic hash of
1374
- `{referenceRequestId, baselineObservationId, baselineContractId,
1375
- baselineContractClauses, changeContractId, changeContractClauses,
1376
- bindingDeclarations}`; attempt identity is a deterministic hash of
1377
- `{reviewRequestId, candidateObservationId}`. `reviewReferenceCorrectionAttempt`
1378
- rejects any call whose supplied `reviewRequestId` does not match what its own
1379
- baseline/contract/reference/binding inputs recompute - the mechanism that
1380
- makes "every attempt evaluates against the same approved baseline" an
1381
- enforced invariant, not just a documented one. No new persisted artifact
1382
- family, no CLI surface, and - most importantly - no code path anywhere in
1383
- this module (or anything it calls) that opens, parses, or writes a target
1384
- source file: real candidate capture remains the caller's own responsibility
1385
- through the existing, unmodified real-Chromium observation pipeline.
1
+ # Architecture
2
+
3
+ ## v0.10 visual workflow boundaries
4
+
5
+ `VisualChangeWorkflowArtifact` (`1.0.0`) is the only new persisted v0.10
6
+ family. It owns frozen scope, explicit activation/restoration, immutable
7
+ attempt and review revisions, and optional references to governance results.
8
+ The Viewer is the human workflow entry; `checkProject` remains the canonical
9
+ evaluator. `VisualChangeAgentHandoff` (`1.0.0`) is a generated, non-persisted
10
+ transfer contract. An external human or coding agent edits source. Optional
11
+ orchestrator metadata is traceability only and Observer never imports or runs
12
+ the orchestrator. Human acceptance is separate from baseline/reference
13
+ governance and never rewrites prior workflow evidence.
14
+
15
+ ## v0.8.1 project workflow
16
+
17
+ Versioned project configuration (`1.0.0` compatibility plus current `1.1.0`
18
+ acceptance input), upward discovery, centralized managed paths, and the atomic
19
+ alias catalog live under `src/projectWorkflow`. Aliases select exact canonical
20
+ artifact directories; they never replace artifact identities.
21
+
22
+ Project `check` validates the selected baseline artifact before candidate
23
+ capture. The capture still takes URL, viewport, targets, and operational
24
+ defaults from current project configuration. Only the validated baseline's
25
+ optional `scrollScenario` and `explicitState` are replayed through the
26
+ existing normalized request and observation path. The project-config schema
27
+ does not own these fields. Explicit state remains declarative identity
28
+ metadata; replay does not establish application or session state.
29
+
30
+ `src/application/projectCheckService.ts` composes the existing observation,
31
+ comparison, contract-evaluation, reference-reader, explicit-binding,
32
+ compatibility, and fidelity owners. `src/projectWorkflow/checkAcceptance.ts`
33
+ owns contained acceptance-input resolution and shared file-wrapper parsing.
34
+ `checkResult.ts` owns the bounded ephemeral projection, not a persisted check
35
+ artifact or evaluation engine. Status precedence is `BLOCKED`, then `FAIL`,
36
+ then `PASS` when all configured executable dimensions pass, then
37
+ `REVIEW_REQUIRED` when comparison is the only evidence. Canonical observations,
38
+ comparisons, and contract evaluations remain persisted; reference fidelity and
39
+ the workflow result remain in memory/presentation.
40
+
41
+ ## Current package architecture
42
+
43
+ The current repository is one published TypeScript ESM package
44
+ (`@dailephd/my-frontend-observer@0.10.1`). The CLI remains
45
+ `my-frontend-observer`; the npm scope does not rename the product or artifact
46
+ identities.
47
+
48
+ - `src/cli.ts` is the real, thin public CLI parsing/dispatch/presentation
49
+ boundary for the current command surface (`observe`, `compare`,
50
+ `approve-baseline`, `save-change-contract`, `evaluate-contract`,
51
+ `import-reference`, `approve-reference`, `evaluate-reference-fidelity`,
52
+ `view`, `init`, `capture`, `check`); argument parsing and output formatting
53
+ only, per command - domain semantics remain owned by application/domain
54
+ services rather than the CLI. v0.6 added no new CLI command; v0.7 added the
55
+ three external-reference commands; v0.8 added `view`; v0.8.1 added `init`,
56
+ `capture`, and `check` and made `view` project-aware while preserving its
57
+ standalone `--root` behavior.
58
+ - `src/index.ts` is the library entry point re-exporting the observer-owned
59
+ contracts/functions from every layer below, including the v0.6 bounded-agent-
60
+ context projection and runtime/static correlation surface, and the v0.7
61
+ external-reference/region/requirement/applicability/compatibility/binding/
62
+ fidelity/correction-workflow surface (`prepareReferenceCorrection`/
63
+ `reviewReferenceCorrectionAttempt` remain programmatic-only, with no CLI
64
+ command).
65
+ - `scripts/clean.mjs` safely removes only the project `dist/` directory.
66
+ - `scripts/check-docs.mjs` validates the canonical documentation foundation,
67
+ roadmap version presence, and the no-batches rule.
68
+ - TypeScript, ESLint, Vitest, and package configuration provide foundation
69
+ validation, now exercised by real product tests (`tests/unit/`,
70
+ `tests/browser/`).
71
+
72
+ Batch 1 added the observation domain/schema and safety-policy layer
73
+ (`src/domain/`, `src/request/`, `src/safety/`). Batch 2 added a real
74
+ Playwright Chromium browser adapter (`src/browser/`), a minimal application
75
+ seam invoking it (`src/application/`), and a deterministic browser
76
+ fixture/test boundary (`tests/fixtures/`, `tests/browser/`, run via
77
+ `npm run test:browser`). Batch 3 extended that single browser adapter with an
78
+ internal page/target measurement module (`src/browser/evidenceCapture.ts`)
79
+ that reads page and explicit-CSS-target evidence from the same live,
80
+ already-ready page used for the screenshot - no second browser/page is ever
81
+ opened, and Playwright objects still never leave `src/browser/`. Batch 4
82
+ added the artifact ownership boundary itself: `src/artifacts/artifactWriter.ts`
83
+ is the one canonical place that writes an observation to disk (temp
84
+ directory, then one atomic rename into `<outputLocation>/<observationId>/`),
85
+ and `src/application/observationPersistence.ts` assembles the frozen
86
+ `ObservationArtifact` from a browser-capture result before handing it to the
87
+ writer. The artifact layer has no Playwright dependency and is testable
88
+ without launching Chromium. Batch 5 completed the boundary chain: `src/cli.ts`
89
+ parses `observe` arguments (CLI-syntax errors only - e.g. malformed
90
+ `WIDTHxHEIGHT`), constructs a raw request, and hands it to the existing
91
+ Batch 1 `normalizeRequest`; on success it calls one new application-level use
92
+ case, `observe()` in `src/application/observationPersistence.ts`, which runs
93
+ the existing `runBrowserCapture` exactly once and, only on success, the
94
+ existing artifact writer exactly once, then returns a small observer-owned
95
+ `ApplicationObservationResult` (observation id, completion state, artifact
96
+ path, target/diagnostic counts) for the CLI to print. The CLI never imports
97
+ Playwright or the filesystem-write path directly. Batch 6 closed the
98
+ remaining real-Chromium coverage gap (a genuine navigation failure, distinct
99
+ from a readiness timeout or a pre-launch safety rejection) and validated the
100
+ packed npm tarball end to end in a clean consumer environment, independent
101
+ of the source checkout. At the end of the v0.1 implementation there was no
102
+ controlled-scroll or comparison behavior.
103
+
104
+ ## Current v0.2 architecture (released/current architecture)
105
+
106
+ v0.2 extends the same architecture rather than adding a parallel one.
107
+ `src/request/request.ts` now owns a canonical `{name, locators}` target
108
+ model (`TargetLocator`, six frozen kinds) in place of the old CSS-only
109
+ shape; the legacy `{name, selector}` input still normalizes into it. The one
110
+ existing browser-side target resolver/measurement module,
111
+ `src/browser/evidenceCapture.ts`, was extended - not replaced - to resolve
112
+ all six locator kinds against the live page through a single Playwright
113
+ `Locator` per attempt, honor the frozen ordered-fallback/ambiguity/
114
+ unavailable-no-fallback contract, and converge every kind on the same
115
+ measurement path (`captureResolvedTargetRecord`); it additionally computes
116
+ bounded semantic state, derived landmark identity, and configured-target-
117
+ only DOM containment from the same already-resolved elements in the same
118
+ capture pass - no second browser/page, no second resolution algorithm.
119
+ `src/domain/schema.ts` extends `TargetEvidenceRecord`/`TargetResolution`
120
+ additively for schema `1.1.0`, with matching structural validation in
121
+ `isValidObservationArtifact`. `src/cli.ts` gained one CLI/input-boundary-
122
+ only addition, `--targets-file`: it reads and validates only the JSON root
123
+ wrapper (via the already-imported `node:fs`, never `node:fs/promises`) and
124
+ hands the parsed `targets` value into the existing `RawObservationRequest`/
125
+ `normalizeRequest()` path unchanged - there is no second application
126
+ observation use case, and Playwright objects still never leave
127
+ `src/browser/`. The artifact writer, application observation use case, and
128
+ overall boundary chain (`CLI → normalizeRequest → observe() →
129
+ runBrowserCapture → artifact writer`) are unchanged from v0.1.
130
+
131
+ ## Current v0.3 architecture (released/current architecture)
132
+
133
+ v0.3 extends the same single-observation architecture again; it does not add
134
+ a second browser lifecycle, target resolver, or artifact path.
135
+ `src/request/request.ts` adds one optional `scrollScenario` field to
136
+ `NormalizedObservationRequest` (`ScrollScenario { action }`, exactly
137
+ `window-scroll-by` or `target-scroll-by`); `src/domain/schema.ts` adds the
138
+ matching bounded runtime evidence types (`ScrollRuntimeSnapshot`,
139
+ `ViewportRelationEvidence`, `OverflowEvidence`, `ScrollScenarioTransition`,
140
+ `ScrollOwnerInterpretation`) and structural validation for schema `1.2.0`
141
+ (additive over `1.1.0`). `src/domain/scrollEvidence.ts` holds the pure,
142
+ browser-independent derivations (viewport relation, actual overflow,
143
+ transitions, and `deriveScrollOwner`) so they are unit-testable without
144
+ Chromium. `src/browser/scrollCapture.ts` holds the one browser-side scenario
145
+ capture module: it reuses `evidenceCapture.ts#resolveConfiguredTargets` (now
146
+ exported) to resolve configured targets exactly once, captures an initial
147
+ `ScrollRuntimeSnapshot`, performs the one immediate scroll
148
+ (`window.scrollBy`/`element.scrollBy`, both `behavior: 'instant'`), waits
149
+ exactly two `requestAnimationFrame` cycles, and captures a final snapshot -
150
+ all inside `chromiumAdapter.ts#captureViewportInternal`'s existing single
151
+ navigate → ready → capture flow, strictly before the unchanged
152
+ screenshot/`capturePageEvidence`/`captureTargetEvidence` calls, so every
153
+ downstream capture (including a no-scenario request, which skips this block
154
+ entirely) describes only the final state. `src/cli.ts` gained one CLI/input-
155
+ boundary-only addition, `--scroll-scenario-file`: mirroring
156
+ `--targets-file`, it reads and validates only the file readability/JSON-
157
+ validity/non-array-object-root shape and hands the parsed value straight
158
+ into `RawObservationRequest.scrollScenario` - every scenario/action rule
159
+ (kind, deltas, target reference) stays owned by `normalizeRequest()`. There
160
+ is still one canonical `observe()` application use case and one artifact
161
+ writer; `scrollScenarioEvidence` is simply one more optional field on the
162
+ same `ObservationArtifact`.
163
+
164
+ ## Current v0.4 architecture (released/current architecture)
165
+
166
+ v0.4 adds one new downstream pipeline that consumes `ObservationArtifact`
167
+ values rather than producing them - it never adds a second browser lifecycle,
168
+ target resolver, or observation engine:
169
+
170
+ ```text
171
+ ObservationArtifact before ObservationArtifact after
172
+ \ /
173
+ `--------. .-------'
174
+ \ /
175
+ artifact reader (src/artifacts/artifactReader.ts)
176
+ ↓
177
+ comparability evaluation (src/domain/comparisonEngine.ts)
178
+ ↓
179
+ canonical relationship derivation, called for each side independently
180
+ (src/domain/relationships.ts#deriveLayoutRelationships)
181
+ ↓
182
+ canonical comparison derivation
183
+ (src/domain/comparisonEngine.ts#compareObservations)
184
+ ↓
185
+ ComparisonArtifact
186
+ ↓
187
+ atomic comparison writer (src/artifacts/comparisonArtifactWriter.ts)
188
+
189
+ CLI `compare`
190
+ ↓
191
+ application service only (src/application/comparisonService.ts)
192
+ ↓
193
+ [reader → domain comparison → writer, as above]
194
+ ```
195
+
196
+ `src/domain/relationships.ts` froze the layout-relationship contract and
197
+ implements the one canonical pure derivation,
198
+ `deriveLayoutRelationships(observation, options?)`: horizontal/vertical
199
+ order, area overlap, relative width, geometric fit, vertical sequencing,
200
+ page-width fit/exceeds, and a standalone `deriveTargetClipping(record)` -
201
+ all computed only from an already-captured `ObservationArtifact`'s own
202
+ `targetEvidence`/`pageEvidence`, never from a second browser query. DOM
203
+ containment is read directly from the existing v0.2 `TargetContainment`
204
+ evidence rather than re-derived, and stays a distinct concept from
205
+ geometric fit.
206
+
207
+ `src/domain/comparisonEngine.ts` implements the one canonical pure
208
+ before/after engine, `compareObservations(before, after, config?)`:
209
+ validates both source artifacts, evaluates comparability *before* any
210
+ rendered difference is calculated, calls `deriveLayoutRelationships` once
211
+ per side with the same tolerance, and derives target/page differences and
212
+ relationship changes. `src/artifacts/comparisonArtifactWriter.ts` persists
213
+ the result atomically (sibling temp directory, then one rename) as
214
+ `<outputLocation>/<comparisonId>/manifest.json` only - no screenshot is
215
+ copied; the manifest's `before`/`after` references point back to the
216
+ source observations' own `screenshot.path`. `src/application/
217
+ comparisonService.ts` is the one application-layer seam: `compareAndPersist`
218
+ takes two in-memory `ObservationArtifact`s and does exactly one comparison
219
+ plus exactly one persist; `compareAndPersistFromArtifactRoots` is a thin
220
+ wrapper that additionally reads both sides from disk via the existing
221
+ `src/artifacts/artifactReader.ts#readObservationArtifact` reader (itself
222
+ just a `manifest.json` parse plus the same `isValidObservationArtifact`
223
+ structural gate the writer uses).
224
+
225
+ `src/cli.ts` gained one new top-level command, `compare`
226
+ (`--before`/`--after`/`--output`/`--config-file`), implemented with the same
227
+ thin-CLI-boundary discipline as `observe`: `parseCompareArgs` handles only
228
+ argument shape/duplication, an optional `loadComparisonConfigFile` reads and
229
+ validates only file readability/JSON-validity/non-array-object-root (exactly
230
+ like `--targets-file`/`--scroll-scenario-file`), and the command body calls
231
+ `compareAndPersistFromArtifactRoots` exactly once. **The CLI's comparison
232
+ path never launches Chromium** - `src/cli.ts` imports nothing from
233
+ `src/browser/` or `src/artifacts/` (it only reaches persistence and artifact
234
+ reading indirectly, through the application-layer seam above), matching the
235
+ same import-boundary discipline already enforced for `observe`.
236
+
237
+ ## Current v0.5 architecture (released/current architecture)
238
+
239
+ v0.5 adds one new downstream layer that consumes `ComparisonArtifact` values
240
+ (plus the source `ObservationArtifact` pair) rather than producing them - no
241
+ new browser lifecycle, target resolver, or comparison engine is added:
242
+
243
+ ```text
244
+ ObservationArtifact before + after
245
+ ↓
246
+ existing v0.4 comparison/relationship pipeline (unchanged)
247
+ ↓
248
+ ComparisonArtifact
249
+ ↓ PersistentBaselineContract
250
+ | +
251
+ `------------------------→ PerChangeContract
252
+ ↓
253
+ canonical contract evaluation
254
+ (src/domain/frontendContractEvaluation.ts#evaluateFrontendContract)
255
+ ↓
256
+ clause results + unexpected changes + overall PASS/FAIL
257
+ ```
258
+
259
+ `src/domain/frontendContracts.ts` froze the contract/change-scope type,
260
+ constant, and structural-validator vocabulary (Batch 1); `src/domain/
261
+ frontendContractIdentity.ts` froze deterministic contract/baseline/clause
262
+ identity in the same canonicalize+sha256(+opaque-nonce) style as `src/domain/
263
+ comparisonIdentity.ts`. `src/domain/frontendContractEvaluation.ts#evaluateFrontendContract`
264
+ (Batch 2) is the one canonical pure evaluation entry point: it validates its
265
+ five inputs (before/after `ObservationArtifact`, `ComparisonArtifact`,
266
+ `PersistentBaselineContract`, `PerChangeContract`) are structurally coherent
267
+ and mutually consistent, calculates the active baseline clause set after
268
+ explicit supersession, detects bounded structural conflicts, evaluates every
269
+ active clause via the frozen 15-primitive vocabulary against the existing
270
+ `ComparisonArtifact`/`ObservationArtifact` evidence (never re-deriving
271
+ clipping/relationship/scroll-owner facts), classifies unaccounted-for
272
+ `ComparisonArtifact.differences` entries as `unexpected`, and derives one
273
+ overall `PASS`/`FAIL` verdict. It performs no I/O, launches no browser, and
274
+ persists nothing - `src/domain/frontendContractEvaluation.ts` itself remains
275
+ untouched by the persistence layer below (Batch 3).
276
+
277
+ Batch 3 adds the persistence/application boundary around this frozen domain,
278
+ without redefining it:
279
+
280
+ ```text
281
+ ComparisonArtifact (read via new src/artifacts/comparisonArtifactReader.ts)
282
+ +
283
+ PersistentBaselineContract / PerChangeContract
284
+ (read/written via src/artifacts/frontendContractArtifactReader.ts / ...Writer.ts)
285
+ ↓
286
+ src/application/frontendContractEvaluationService.ts#evaluateAndPersist
287
+ ↓
288
+ evaluateFrontendContract() [called exactly once, unmodified]
289
+ ↓
290
+ src/domain/frontendContractEvaluationArtifact.ts
291
+ (minimal additive persisted envelope around the frozen result)
292
+ ↓
293
+ src/artifacts/frontendContractEvaluationArtifactWriter.ts
294
+ (atomic write, exactly once on a structurally constructible result)
295
+ ```
296
+
297
+ `evaluateAndPersistFromArtifactRoots` is the CLI-facing wrapper, reading
298
+ before/after observations through the existing `readObservationArtifact` (no
299
+ second observation reader), the comparison and both contract classes
300
+ through the new readers, then delegating to `evaluateAndPersist` exactly
301
+ once - mirroring `application/comparisonService.ts#compareAndPersistFromArtifactRoots`'s
302
+ own thin-wrapper shape.
303
+
304
+ Batch 4 exposes this through the same thin-CLI boundary already established
305
+ by `observe`/`compare`:
306
+
307
+ ```text
308
+ src/cli.ts (argument parsing, JSON-file-shape checks, help/output formatting,
309
+ exit-code selection only)
310
+ ↓
311
+ src/application/frontendContractPersistenceService.ts#approveAndPersistBaseline
312
+ src/application/frontendContractPersistenceService.ts#persistPerChangeContract
313
+ src/application/frontendContractEvaluationService.ts#evaluateAndPersistFromArtifactRoots
314
+ ↓
315
+ domain validators (isValidPersistentBaselineContract / isValidPerChangeContract)
316
+ + artifact readers/writers (Batch 3)
317
+ + evaluateFrontendContract() (Batch 2, unmodified)
318
+ ```
319
+
320
+ Three new top-level commands - `approve-baseline`, `save-change-contract`,
321
+ `evaluate-contract` - each parse only CLI-syntax concerns (duplicate/missing
322
+ flags, JSON-file readability/parseability/object-root shape) and delegate to
323
+ exactly one application-layer call; `src/cli.ts` imports no artifact writer/
324
+ reader module and no browser code, matching the existing `observe`/`compare`
325
+ import-boundary discipline exactly. `approveAndPersistBaseline` adds the one
326
+ new coherence check Batch 3 did not need: verifying a baseline contract's
327
+ frozen `sourceObservation` reference actually matches the supplied
328
+ observation artifact before persisting - explicit approval only, never
329
+ inferred from a `compare` or `evaluate-contract` result. `--enforce` on
330
+ `evaluate-contract` is applied only after evaluation and persistence have
331
+ already completed; it selects the process exit status for an already-final
332
+ `FAIL` result and is never part of any identity or persisted field.
333
+
334
+ ## Current v0.6 architecture (released as `0.6.0`)
335
+
336
+ v0.6 adds one new downstream, read-only layer that consumes existing v0.1-v0.5
337
+ evidence (`ObservationArtifact`, `ComparisonArtifact`,
338
+ `PersistentBaselineContract`/`PerChangeContract`, and evaluation results)
339
+ plus caller-supplied bounded static candidate evidence - it adds no new
340
+ browser lifecycle, target resolver, observation/comparison/contract engine,
341
+ or persisted artifact family:
342
+
343
+ ```text
344
+ ObservationArtifact(s) + ComparisonArtifact + contract/evaluation evidence
345
+ ↓
346
+ src/domain/boundedAgentContextProjection.ts#projectBoundedAgentContext
347
+ ↓
348
+ BoundedRuntimeTargetProjection
349
+ (page/viewport identity, stable targets, geometry, runtime behavior,
350
+ relationships, before/after differences, contract results,
351
+ requested/expected-dependent/protected/preserved scope reused verbatim
352
+ from src/domain/frontendContracts.ts, diagnostics, artifact/screenshot
353
+ references, provenance, adequacy, omission, truncation)
354
+ ↓
355
+ src/domain/boundedAgentContextCorrelation.ts
356
+ #deriveRuntimeStaticCorrelations / #attachRuntimeStaticCorrelations
357
+ ↓
358
+ RuntimeStaticCorrelation[] (correlated / ambiguous / unavailable,
359
+ competing candidates preserved verbatim - never collapsed to one owner)
360
+ ↓
361
+ src/index.ts (public export/correlation boundary only)
362
+ ```
363
+
364
+ `src/domain/boundedAgentContext.ts` freezes the bounded-projection and
365
+ correlation type/constant vocabulary (`Adequacy`, `ADEQUACY_REASON_CODES`,
366
+ `OmissionRecord`, `TruncationRecord`, `BOUNDED_AGENT_CONTEXT_ARTIFACT_KIND`,
367
+ schema `1.0.0`). `src/domain/boundedAgentContextIdentity.ts` derives a
368
+ deterministic logical identity distinct from a fresh per-execution instance
369
+ identity, in the same canonicalize+hash style as
370
+ `comparisonIdentity.ts`/`frontendContractIdentity.ts`.
371
+ `boundedAgentContextProjection.ts` performs no browser I/O and re-derives
372
+ nothing already owned upstream - it reads already-captured artifacts and
373
+ reuses the existing v0.4 relationship/comparison evidence and v0.5
374
+ change-scope clause types directly. `boundedAgentContextCorrelation.ts`
375
+ accepts only plain, caller-supplied candidate static-evidence records; it has
376
+ **no** dependency on `@dailephd/my-dev-kit`, since the audit that preceded
377
+ implementation found no generic static-side retrieval capability actually
378
+ missing (see `docs/ROADMAP.md` v0.6 "Dependency direction" - the "determine
379
+ whether my-dev-kit requires a static-side change" step concluded no).
380
+ Runtime target identity is carried through this module verbatim; the module
381
+ never adds a `sourceOwner`/`causedBy`-shaped field, preserving the
382
+ architectural rule that runtime identity never silently becomes source
383
+ ownership.
384
+
385
+ This layer is a programmatic export/correlation boundary only: `src/index.ts`
386
+ re-exports its full type/function surface, but there is no new CLI command,
387
+ no `src/artifacts/boundedAgentContext*` writer/reader, and no orchestrator or
388
+ lab code in this repository - those remain separate sibling-repository
389
+ responsibilities per the Milestone 6 ownership split in
390
+ `docs/PROJECT_MILESTONES.md`.
391
+
392
+ ## v0.7-v0.10 reference-evidence architecture constraints
393
+
394
+ The external visual-reference capability (v0.7) is released as package
395
+ version `0.7.0` - see "v0.7 Prompt 1" through "v0.7 Prompt 8" below for the
396
+ actual architecture, and `docs/CURRENT_STATE.md` for release state. It
397
+ extends the existing v0.1-v0.6 evidence architecture rather than becoming a
398
+ UI-only feature or a parallel visual-comparison stack. v0.8 (interactive
399
+ viewer) is released as package version `0.8.0`. v0.9 (structured visual
400
+ annotation) is released as package version `0.9.0` - see "v0.9 visual
401
+ annotation architecture" below. v0.10 (full graphical human-LLM workflow) is
402
+ released as `0.10.0`; v0.10.1 adds narrow project-check baseline context
403
+ replay without changing these evidence domains. The constraints below apply
404
+ to v0.9 and v0.10.
405
+
406
+ The evidence domains remain distinct:
407
+
408
+ ```text
409
+ runtime observation A ↔ runtime observation B
410
+ → existing before/after comparison
411
+
412
+ approved baseline/per-change contract ↔ candidate runtime evidence
413
+ → existing canonical contract evaluation
414
+
415
+ external visual reference ↔ candidate runtime evidence
416
+ → reference applicability + structured fidelity evaluation (v0.7,
417
+ released as `0.7.0` - see "v0.7 Prompt 4" and "v0.7 Prompt 6" below)
418
+ ```
419
+
420
+ An external reference is not an `ObservationArtifact`, and a reference region
421
+ is not a runtime target. The released v0.7 implementation preserves explicit
422
+ identity and provenance for the reference image/version, reference regions,
423
+ applicable viewport/theme/application state, authored requirements, tolerances,
424
+ approval/supersession state, and reference-region/runtime-target bindings.
425
+ Bindings are explicit and evaluate to `bound`, `ambiguous`, or `unavailable`;
426
+ they never silently become source ownership.
427
+
428
+ The non-UI reference model and structured reference-vs-candidate evaluation
429
+ were established in v0.7 before v0.8. v0.8 may render side-by-side images,
430
+ overlays, measurements, bindings, provenance, and fidelity results, but it must
431
+ consume those existing engines and must not invent a second reference model or
432
+ evaluation engine. v0.9 may author annotations against either runtime
433
+ screenshots or external references, but both coordinate/identity domains remain
434
+ explicit and feed the same canonical contract/change-scope semantics. v0.10
435
+ combines both visual entry modes with the existing correction loop.
436
+
437
+ Where a reference requirement is executable, it uses the existing v0.5
438
+ requested/expected-dependent/protected/preserved semantics. Informational or
439
+ unassessed reference evidence remains non-executable until explicitly selected.
440
+ There is no reference-only PASS/FAIL taxonomy.
441
+
442
+ The released reference evaluation reuses existing relationship/value
443
+ conventions where they mean the same thing and adds distinct reference-owned
444
+ units only where the image evidence requires them. v0.7 does not use pixel or
445
+ image-region similarity as a success mechanism; a later bounded similarity
446
+ feature may supplement structured evidence if separately designed, but it must
447
+ not replace browser-authoritative runtime geometry, canonical contract
448
+ evaluation, or explicit relationship evidence.
449
+
450
+ Candidate rendering still uses the one existing Chromium observation engine.
451
+ The observer remains non-mutating. `my-dev-kit` remains the static/source
452
+ evidence owner, and the v0.6 correlation/bounded-context boundary remains the
453
+ route for attaching relevant source evidence to reference-driven correction
454
+ packets. Heavy reference image bytes are referenced rather than copied into
455
+ every downstream context/evaluation record.
456
+
457
+ Theme, application-state, viewport, and authenticated-state applicability are
458
+ checked before reference fidelity is interpreted through the released v0.7
459
+ compatibility path, which reuses v0.4 comparability conventions. If reference
460
+ and candidate do not represent compatible intended states, the result is
461
+ explicitly incompatible/incomparable rather than a fabricated visual difference
462
+ set. v0.8, released as package version `0.8.0`, displays this result exactly
463
+ as required rather than redefining the state model - see "v0.8 Batch 5"
464
+ below.
465
+
466
+ The constraints above were carried out by the actual v0.7 implementation
467
+ described in "v0.7 Prompt 1" through "v0.7 Prompt 8" below: explicit
468
+ identity/provenance, applicability/compatibility, region-to-target bindings,
469
+ requested/expected-dependent/protected/preserved reuse, and the non-mutating
470
+ Chromium/correlation boundaries all remain as constrained here. v0.8 (see
471
+ "v0.8 Batch 1" through "v0.8 Batch 8" below) applied them unchanged, and so
472
+ does the implemented v0.9 annotation layer. They continue to apply unchanged
473
+ to the still-future v0.10 work.
474
+
475
+ The exact public artifact names, schema versions, persistence layout, supported
476
+ image formats, coordinate model, requirement/tolerance primitives, and fidelity
477
+ behavior were frozen by the actual v0.7 implementation below, not by earlier
478
+ planning language. Style/asset-similarity mechanisms remain future unless
479
+ separately implemented.
480
+
481
+ ## v0.8 Batch 1 (Viewer runtime and PWA foundation) — implemented
482
+
483
+ Batch 1 of the frozen `docs/plans/v0.8-implementation-plan.md` establishes
484
+ only the viewer runtime/build shell — no evidence indexing, artifact reading,
485
+ or evidence UI. It does not implement any of the v0.7-derived reference/
486
+ fidelity/binding display constraints above; those remain future work for
487
+ later v0.8 batches, which must consume this runtime boundary rather than
488
+ redefine it.
489
+
490
+ ```text
491
+ my-frontend-observer view [--root <evidence-root>]
492
+ |
493
+ v
494
+ thin CLI dispatch (src/cli.ts: parseViewArgs/runViewCommand)
495
+ |
496
+ v
497
+ viewer application seam (src/viewerServer/viewerService.ts: startViewer)
498
+ |
499
+ v
500
+ Node local server, loopback-only (src/viewerServer/httpServer.ts)
501
+ |
502
+ +---------------------+----------------------+
503
+ | |
504
+ v v
505
+ built viewer assets (dist/viewer) GET /api/status
506
+ (React + TypeScript + Vite PWA) (session/root identity only)
507
+ ```
508
+
509
+ - **Node server boundary** (`src/viewerServer/`): binds only to `127.0.0.1`
510
+ on one fixed default port (`4319`, `src/viewerServer/port.ts`); serves only
511
+ the built viewer assets plus the one read-only status endpoint; resolves
512
+ every requested path against the built assets root and fails closed on any
513
+ path that would resolve outside it; accepts no write HTTP methods; performs
514
+ no artifact reading, browser observation, or mutation. `--root` is
515
+ validated operationally (exists, is a directory) and exposed only as an
516
+ opaque status string — it is never interpreted as Observer evidence in this
517
+ batch.
518
+ - **Browser application** (`viewer/`): a React + TypeScript + Vite app, built
519
+ independently of `src/` via `viewer/tsconfig.json` and `viewer/vite.config.ts`,
520
+ output to `dist/viewer` inside the existing package `dist` allowlist (no
521
+ second npm package). Renders an honest foundation shell only — product
522
+ identity, live session status via `/api/status`, and placeholder
523
+ navigation/workspace/details regions — never fabricated evidence.
524
+ - **PWA**: `vite-plugin-pwa` generates a web app manifest (`standalone`
525
+ display, stable `start_url`/`scope`, installability icons) and a service
526
+ worker that precaches only the built application shell. It declares no
527
+ `runtimeCaching` rules, so future evidence/media/API routes remain
528
+ network/server-backed rather than silently served as stale cached truth
529
+ when the local server is unavailable (enforced by
530
+ `tests/unit/viewerPwaBuild.test.ts`, which asserts on the actual built
531
+ `sw.js`, not a hand-written approximation). An install affordance appears
532
+ only when the browser actually fires `beforeinstallprompt`; its absence is
533
+ shown honestly, never as a disabled-looking fake control.
534
+ - **CLI**: `view [--root <evidence-root>] [--port <n>] [--no-open]` remains a
535
+ thin dispatcher — it parses syntax, delegates once to `startViewer`, prints
536
+ the URL/root, and optionally best-effort opens the system browser (failure
537
+ there is never fatal to server startup). All v0.1-v0.7 commands are
538
+ unchanged.
539
+
540
+ This batch introduces no second observer, relationship engine, comparison
541
+ engine, contract engine, reference model, or bounded-context builder — there
542
+ is nothing yet for the viewer to consume beyond its own runtime identity.
543
+
544
+ ## v0.8 Batch 2 (Evidence indexing, canonical readers, and lazy data boundary) — implemented
545
+
546
+ Batch 2 adds the safe, read-only data boundary between existing on-disk
547
+ Observer evidence and the Batch 1 viewer runtime, entirely under
548
+ `src/viewerServer/evidence/`. It introduces no new persisted artifact family,
549
+ no schema migration, and no second validator — every recognized candidate is
550
+ decided exclusively by the existing canonical reader/validator for its
551
+ family (`src/artifacts/*Reader.ts`).
552
+
553
+ ```text
554
+ GET /api/index bounded discovery + classification -> metadata only
555
+ GET /api/artifacts/<handle> one canonical-reader read, on demand -> full projection
556
+ GET /api/media/<handle>/<role> one resolved, contained media file, streamed on demand
557
+ ```
558
+
559
+ - **Discovery** (`evidence/discovery.ts`): a bounded, deterministic walk
560
+ beneath `--root` that opens only files literally named `manifest.json` (the
561
+ one filename every current persisted family uses) — no other file is ever
562
+ read or classified, so arbitrary files can never become evidence merely by
563
+ existing under the root. Directory entries that are symlinks/junctions are
564
+ never followed. Bounds (`evidence/limits.ts`): traversal depth 6,
565
+ directories visited 2000, candidate manifests 1000, index records 500,
566
+ manifest read size 2,000,000 bytes — chosen after inspecting that every
567
+ current writer produces a shallow `<outputLocation>/<id>/manifest.json`
568
+ shape (see `docs/CONTRACTS.md`), not a deep tree.
569
+ - **Classification is not validation** (`evidence/classify.ts`): peeks only
570
+ `artifactKind`/`schemaVersion` (plus, where the shared kind is ambiguous,
571
+ tries each existing reader/validator in turn — e.g. baseline vs. per-change
572
+ contract) to decide *which* existing canonical reader to call; the reader's
573
+ own structural validator remains the sole authority. Six honest, mutually
574
+ exclusive states: `supported`, `unsupported-version`, `invalid-structure`,
575
+ `unrecognized-kind`, `malformed-json`, `unreadable` — never collapsed into
576
+ one boolean, and never conflated with an artifact's own `completion`
577
+ state (passed through separately, only for the families that carry one:
578
+ observation and external-reference). A manifest declaring the
579
+ `bounded-agent-context` kind is classified `unrecognized-kind`: v0.6/v0.7
580
+ never added a disk writer/reader for that family (confirmed via direct
581
+ source inspection and `@dailephd/my-dev-kit` search), so Batch 2 does not
582
+ invent persistence-shaped handling for it.
583
+ - **Viewer handles** (`evidence/handles.ts`, `evidence/pathSafety.ts`): a
584
+ handle is a family-prefixed, percent-encoded, root-relative directory path
585
+ — never a raw filesystem path accepted from the browser. Every route that
586
+ accepts a handle re-decodes and re-resolves it against the evidence root,
587
+ re-checks containment, and re-classifies that one candidate before serving
588
+ anything; a handle whose backing directory or manifest no longer matches
589
+ what was indexed fails closed as unknown, never stale.
590
+ - **Ephemeral projection** (`evidence/projection.ts`): `EvidenceMetadataRecord`
591
+ (bounded, `/api/index`-shaped: handle, family, support state, logical id,
592
+ schema version, completion where applicable, media availability summary,
593
+ a handful of related ids) and `EvidenceArtifactDetail` (the already-
594
+ validated domain object, wrapped with `handle`/`family` — no new evidence
595
+ schema, no recomputation, no persistence).
596
+ - **Media resolution** (`evidence/mediaResolver.ts`): `screenshot` (observation),
597
+ `image` (imported external reference), and `source-image` (approved
598
+ external reference) are the only three recognized roles. An approved
599
+ reference's image is never assumed to live in the approved artifact's own
600
+ directory — its `sourceReference.referenceId` is looked up against the
601
+ current index to find the actual owning imported artifact
602
+ (`evidence/index.ts#findImportedReferenceDir`), exactly matching the v0.7
603
+ reference-ownership contract in `docs/CONTRACTS.md`. A genuinely missing
604
+ screenshot/image/source artifact is reported as 404, never fabricated.
605
+ - **PWA cache boundary preserved, not re-verified from scratch**: every new
606
+ route lives under `/api/`, already covered by Batch 1's
607
+ `denylist:[/^\/api\//]` navigation-fallback rule — no new `runtimeCaching`
608
+ entry was needed or added (`tests/unit/viewerPwaBuild.test.ts` asserts this
609
+ against the real built `sw.js`).
610
+ - **Minimal UI** (`viewer/src/hooks/useEvidenceIndex.ts`,
611
+ `useArtifactDetail.ts`, `components/EvidenceList.tsx`,
612
+ `ArtifactPreview.tsx`): a bounded evidence list (metadata-first) plus
613
+ on-demand full-artifact loading on selection, with every support state
614
+ shown honestly. No screenshot rendering, SVG overlay, or comparison/
615
+ contract/reference visualization exists yet — that begins in Batch 3.
616
+
617
+ ## v0.8 Batch 3 (Runtime observation inspection and SVG overlays) — implemented
618
+
619
+ Batch 3 makes one already-supported `ObservationArtifact` (Batch 2's data
620
+ boundary, unchanged) genuinely understandable: a real screenshot, SVG target
621
+ overlays in the observation's own canonical coordinate domain, target
622
+ selection/inspection, and canonical layout-relationship display. No second
623
+ relationship engine, no client-side evidence derivation, no new persisted
624
+ artifact.
625
+
626
+ **Coordinate audit (the load-bearing decision for this batch)**: target
627
+ geometry (`TargetGeometry.x/y/width/height`) is captured via
628
+ `el.getBoundingClientRect()` (`src/browser/evidenceCapture.ts`) - CSS pixels,
629
+ relative to the current viewport's top-left, at the same live page state the
630
+ screenshot is taken from. The screenshot itself is `page.screenshot({type:
631
+ 'png'})` (`src/browser/chromiumAdapter.ts`), Playwright's default
632
+ (non-fullPage) mode, against a browser context created with no
633
+ `deviceScaleFactor` override (`browser.newContext({viewport})`) - so it
634
+ defaults to `1`, meaning every observation this repository can currently
635
+ produce has a screenshot whose raw PNG pixel dimensions equal
636
+ `requestConfig.viewport.width × requestConfig.viewport.height` exactly (1
637
+ CSS pixel = 1 PNG pixel). `requestConfig.viewport` (a required, strongly-typed
638
+ field on every valid `ObservationArtifact`, distinct from the loosely-typed
639
+ `pageEvidence` bag) is therefore the canonical, always-present source for the
640
+ SVG display frame.
641
+
642
+ **SVG coordinate model** (`viewer/src/components/TargetOverlaySvg.tsx`): the
643
+ `<svg>` root's `viewBox` is `0 0 {requestConfig.viewport.width}
644
+ {requestConfig.viewport.height}` - the exact frame `getBoundingClientRect()`
645
+ already used. The screenshot loads into a `<image>` element filling that same
646
+ viewBox (`preserveAspectRatio="none"`, since the two frames are already
647
+ pixel-identical). Target `<rect>` elements use `geometry.x/y/width/height`
648
+ completely unchanged - no rounding, no `devicePixelRatio` multiplication, no
649
+ clamping; geometry lying partly outside the viewBox is drawn at its real
650
+ coordinates and clipped only by the SVG root's default `overflow: hidden`
651
+ (a display-only effect, verified never to touch the underlying evidence
652
+ value - `tests/unit/observationCoordinateMapping.test.ts`). This is robust
653
+ even if a future capture path used a different `deviceScaleFactor`: the
654
+ `<image>`/viewBox scaling is presentation-only browser behavior, never a
655
+ manual pixel calculation in this codebase. `devicePixelRatio` (captured as
656
+ `pageEvidence.devicePixelRatio`) is shown as informational observation-level
657
+ evidence only and is never consulted for any geometry calculation.
658
+
659
+ **Server additions** (`src/viewerServer/evidence/observationView.ts`, one new
660
+ route `GET /api/observations/<handle>/relationships`): the only new
661
+ server-side computation this batch adds is one thin, defense-in-depth-wrapped
662
+ call to the existing canonical, pure `deriveLayoutRelationships` (`src/domain/
663
+ relationships.ts`) - never a second relationship predicate implementation.
664
+ Mirrors the exact handle-decode → contained-dir-resolve → re-classify
665
+ discipline `loadArtifactByHandle`/`resolveMedia` already established in
666
+ Batch 2; a handle for a non-`observation` family or a non-`supported`
667
+ candidate is rejected (`409`) before derivation is even attempted. The
668
+ existing `GET /api/artifacts/<handle>` (full `ObservationArtifact`) and
669
+ `GET /api/media/<handle>/screenshot` (Batch 2, unchanged) remain the only
670
+ other data sources the observation workspace uses - no new artifact
671
+ projection endpoint was needed, since the full validated domain object
672
+ already contains everything the target/observation inspector displays.
673
+
674
+ **Client-side presentation only** (`viewer/src/observation/targetOrder.ts`,
675
+ `viewer/src/components/{ObservationWorkspace,TargetList,TargetOverlaySvg,
676
+ ObservationInspector,EvidenceFieldView}.tsx`): React selects, orders
677
+ (by the observation's own authored `requestConfig.targets` order, not
678
+ incidental object-key order), and formats already-fetched canonical fields.
679
+ It never resolves targets, computes relationships, or derives
680
+ visibility/overflow/scroll-owner semantics - `deriveLayoutRelationships`
681
+ runs exclusively on the server (above). An unresolved target (`not-found`/
682
+ `ambiguous`/`unavailable`) is selectable from the target list and shown
683
+ honestly in the inspector, but never receives a fabricated `<rect>` -
684
+ `orderedTargets()`'s `hasGeometry` flag is `true` only when
685
+ `geometry.state` is `'available'` or `'partial'`.
686
+
687
+ **Selection**: viewer presentation state only (React `useState`, reset on
688
+ observation change), never persisted, synchronized in both directions
689
+ between the target list, the SVG `<rect>` (`role="button"`, keyboard-
690
+ operable), and the inspector via the target's existing stable `name`.
691
+
692
+ **Overlay toggles**: geometry, labels (disabled when geometry is off), and
693
+ relationships - each independently toggleable and purely presentational
694
+ (hiding/showing already-rendered elements), never altering the underlying
695
+ evidence or the fetched artifact/graph.
696
+
697
+ **PWA cache boundary preserved**: the new `/api/observations/*` route lives
698
+ under the same `/api/` prefix Batch 1's `navigateFallbackDenylist` already
699
+ denylists - no service-worker configuration change was needed
700
+ (`tests/unit/viewerPwaBuild.test.ts` asserts this against the real built
701
+ `sw.js`).
702
+
703
+ ## v0.8 Batch 4 (Before/after comparison and contract/change-scope inspection) — implemented
704
+
705
+ Batch 4 exposes the existing v0.4 `ComparisonArtifact` and v0.5
706
+ `FrontendContractEvaluationArtifact` through the viewer, entirely under
707
+ `src/viewerServer/evidence/{linkedEvidence,comparisonView,evaluationView}.ts`
708
+ and `viewer/src/components/{ComparisonWorkspace,EvaluationWorkspace,
709
+ ComparisonObservationPane,ClauseResultRow}.tsx`. **`compareObservations` and
710
+ `evaluateFrontendContract` are never called anywhere in this batch** - every
711
+ displayed comparison/evaluation field is read unchanged from its persisted
712
+ artifact via the existing Batch 2 `GET /api/artifacts/<handle>`.
713
+
714
+ - **Exact linked-evidence resolution** (`evidence/linkedEvidence.ts`): given
715
+ a `ComparisonSourceObservationReference`/`FrontendContractObservationReference`,
716
+ a `comparisonId`+`comparisonRequestId` pair, or a `baselineId`/`contractId`,
717
+ resolves the matching indexed artifact by **exact identity only**
718
+ (`observationId`+`requestId`+`producer.version`+`observationSchemaVersion`
719
+ for observations; the id fields themselves for comparisons/contracts) -
720
+ never by folder name, screenshot filename, URL, target-set, or geometry
721
+ similarity. Zero matches → `missing`; two or more exact matches →
722
+ `ambiguous` (never silently picks one). Mirrors the exact bounded-walk
723
+ pattern Batch 2's `findImportedReferenceDir` already established.
724
+ - **Two additive, read-only routes**: `GET /api/comparisons/<handle>/view`
725
+ (resolves the comparison's `before`/`after`) and
726
+ `GET /api/evaluations/<handle>/view` (resolves `comparison`, `baseline`,
727
+ `change`, `before`, `after`) - both under `/api/`, both GET/HEAD-only, both
728
+ returning only resolved-handle-or-missing-or-ambiguous status, never a
729
+ duplicated copy of the linked artifact's own payload (the browser fetches
730
+ that separately through the existing `GET /api/artifacts/<handle>`, reusing
731
+ Batch 2's on-demand-loading contract exactly).
732
+ - **Before/after visual reuse, not reimplementation**: `ComparisonObservationPane.tsx`
733
+ is built entirely from Batch 3's existing lower-level primitives
734
+ (`useArtifactDetail`, `orderedTargets`, `TargetOverlaySvg`) - no second
735
+ screenshot-loading, coordinate-transform, or geometry-rendering code
736
+ exists. The comparison's own persisted `relationshipsBefore`/
737
+ `relationshipsAfter` are passed directly into `TargetOverlaySvg`'s existing
738
+ `relationships` prop - never recomputed via `deriveLayoutRelationships`.
739
+ `TargetOverlaySvg` gained one small additive, optional `highlightNames`
740
+ prop (alongside the existing single-select `selected`) so a
741
+ relationship-subject difference or a two-target contract primitive
742
+ (`targets-do-not-overlap`, `target-fits-inside`, etc.) can emphasize both
743
+ named targets at once without changing Batch 3's existing single-select
744
+ interaction contract.
745
+ - **Difference/relationship-change/clause presentation is evidence display,
746
+ not re-derivation**: `ComparisonWorkspace.tsx` renders `differences`,
747
+ `relationshipChanges`, `configurationChanges` (kept visually distinct from
748
+ appeared/disappeared runtime differences), and `expectedDependencyEvidence`
749
+ exactly as persisted, labeling dependency outcomes as explicit non-causal
750
+ evidence. `comparability` (comparable/comparable-with-warnings/incomparable
751
+ plus blocking/warning/unassessed reasons) is shown honestly; an
752
+ `incomparable` result is visually unmistakable
753
+ (`.comparability-banner--incomparable`).
754
+ - **Clause joining by exact `clauseId` only** (`EvaluationWorkspace.tsx`):
755
+ baseline clauses (from the linked `PersistentBaselineContract`) and
756
+ per-change clauses (from the linked `PerChangeContract`) are joined to the
757
+ evaluation's `clauseResults` by exact id - never by target/primitive-shape/
758
+ category/position. A `clauseId` absent from both loaded contracts is shown
759
+ as an honest "unresolved clause definition", never fabricated. Baseline
760
+ clause active/superseded status comes exclusively from the evaluation
761
+ artifact's own `activeBaselineClauseIds`/`supersededBaselineClauseIds` -
762
+ never recomputed from clause overlap. `pass`/`fail`/`unavailable`/
763
+ `conflict` are preserved exactly (never collapsed to a boolean);
764
+ `unavailable` shows its reason, `conflict` shows its reason and
765
+ `conflictingClauseIds`.
766
+ - **Overall verdict is authoritative and unmistakable**: `overallVerdict`
767
+ (`PASS`/`FAIL`) is rendered directly from the artifact, in a large
768
+ `.overall-verdict--PASS`/`.overall-verdict--FAIL` banner - the UI never
769
+ computes it from visible rows. The required safety case (a `requested`
770
+ clause `pass` alongside a `protected`/`preserved` clause `fail` still
771
+ producing overall `FAIL`) and the all-pass case are both proven against
772
+ real, canonically-evaluated fixtures (`tests/support/evidenceFixtures.ts#writeFullPipelineFixture`/
773
+ `writeAllPassPipelineFixture`) in real Chromium
774
+ (`tests/browser/comparisonEvaluationWorkspace.test.ts`) - `overallVerdict`
775
+ is never hand-edited to construct either demonstration.
776
+ - **Target/relationship cross-highlighting uses only explicit canonical
777
+ identity**: `primitiveTargetNames()` (`viewer/src/contract/clauseTargets.ts`)
778
+ extracts a contract primitive's named target field(s) (`target`, `targetA`/
779
+ `targetB`, `target`+`container`, `subjectTarget`/`relatedTarget`) by an
780
+ exhaustive switch over `ContractPrimitiveKind` - page-level primitives
781
+ (`document-width-fits-viewport`, `scroll-owner-is-document`) return no
782
+ names, so clicking them never fabricates a target highlight.
783
+ - **PWA cache boundary preserved**: both new routes live under `/api/`,
784
+ already covered by Batch 1's `navigateFallbackDenylist`; verified against
785
+ the real built `sw.js`.
786
+
787
+ ## v0.8 Batch 5 (External reference and reference/candidate inspection) — implemented
788
+
789
+ - **Reference indexing/media already existed (Batch 2), unchanged**: the
790
+ `external-reference-imported`/`external-reference-approved` families,
791
+ `GET /api/media/<handle>/image` (imported), and
792
+ `GET /api/media/<handle>/source-image` (approved, resolved through
793
+ `findImportedReferenceDir`'s exact `referenceId` walk) were already built
794
+ in Batch 2 and required no change here - Batch 5 only adds the visual
795
+ workspace consuming them.
796
+ - **Two new additive, read-only routes**
797
+ (`src/viewerServer/evidence/referenceView.ts`):
798
+ `GET /api/references/<handle>/view` derives the selected reference's own
799
+ region-relationship graph (`deriveReferenceRegionRelationships`) and
800
+ requirement adequacy (`deriveReferenceRequirementAdequacy`) - both pure
801
+ functions over the artifact's own persisted `regions`/`requirements`,
802
+ never persisted, never a second derivation engine (mirrors Batch 3's
803
+ `getObservationRelationships` server-side-derivation pattern).
804
+ `GET /api/references/<handle>/candidate/<handle>/view` evaluates
805
+ reference/candidate compatibility through the existing canonical
806
+ `evaluateReferenceCandidateCompatibility` (never a second, viewer-owned
807
+ compatibility model) and separately lists every existing
808
+ `FrontendContractEvaluationArtifact` whose own persisted `after` reference
809
+ exactly identifies the candidate, for explicit, never-auto-selected
810
+ optional display.
811
+ - **Reference-image SVG coordinate model is a genuinely distinct domain from
812
+ the candidate's runtime SVG** (`ReferenceRegionOverlaySvg.tsx`): `viewBox`
813
+ is the reference image's own pixel dimensions (never the candidate's CSS
814
+ viewport, never devicePixelRatio-multiplied); each region's canonical
815
+ `{x, y, width, height}` is rendered unchanged. Because this is a different
816
+ coordinate domain and data source from `TargetOverlaySvg` (runtime CSS
817
+ pixels, `TargetGeometry`), it is a separate, sibling component rather than
818
+ a parameterization of the existing one - reuse would have silently
819
+ conflated the two domains. The candidate side, in contrast, reuses Batch
820
+ 3/4's exact `ComparisonObservationPane`/`TargetOverlaySvg` machinery
821
+ unchanged (a synthetic `{status:'resolved', handle}` `LinkStatus` is
822
+ constructed once a candidate is explicitly chosen).
823
+ - **Reference region selection and runtime target selection are two
824
+ independent, never-synchronized selection domains**
825
+ (`ReferenceWorkspace.tsx`): selecting a reference region never selects or
826
+ highlights a runtime target, even when both happen to share the same
827
+ string name (proven with a real Chromium fixture deliberately naming both
828
+ `"header"` - `tests/browser/referenceCandidateWorkspace.test.ts`, Case E).
829
+ No binding connector/highlight-across-panes exists in this batch - that is
830
+ Batch 6's explicit-binding-interaction scope.
831
+ - **Compatibility vs. reference adequacy vs. candidate fidelity are kept
832
+ strictly distinct, never conflated**: compatibility
833
+ (`comparable`/`comparable-with-warnings`/`incomparable` plus
834
+ blocking/warning/unassessed reasons) comes only from
835
+ `evaluateReferenceCandidateCompatibility`; reference-side requirement
836
+ adequacy (`adequate`/`partial`/`inadequate`) comes only from
837
+ `deriveReferenceRequirementAdequacy`; candidate fidelity is never computed
838
+ in this batch at all - the UI always shows an explicit "not evaluated in
839
+ this batch" note rather than ever implying a fidelity PASS from a
840
+ compatibility PASS or an adequate reference (task §32/§33 boundary,
841
+ `evaluateReferenceCandidateFidelity` is never imported/called anywhere in
842
+ Batch 5).
843
+ - **Optional contract/evaluation context is opt-in, never inferred**
844
+ (`ReferenceWorkspace.tsx`): when the reference/candidate view lists more
845
+ than one exactly-matching evaluation artifact, the developer must
846
+ explicitly pick one from a `<select>` - the newest/first match is never
847
+ silently chosen, and with zero selected the candidate's contract status
848
+ reads "not selected/not available", never a fabricated PASS.
849
+ - **Imported vs. approved image ownership preserved exactly as Batch 2 built
850
+ it**: an imported reference's image is fetched from its own directory; an
851
+ approved reference's image is fetched from its exact imported source via
852
+ `sourceReference.referenceId`, never assumed co-located, never
853
+ duplicated - proven with a real Chromium fixture asserting the two
854
+ `<image href>` values resolve to the `image`/`source-image` roles
855
+ respectively (Cases A/B).
856
+ - **PWA cache boundary preserved**: both new routes live under `/api/`,
857
+ already covered by Batch 1's `navigateFallbackDenylist`; verified against
858
+ the real built `sw.js` (no new `registerRoute`, no `/api/references`
859
+ precache entry).
860
+
861
+ ## v0.8 Batch 6 (Explicit-binding interaction, zoom/pan, conditional lock, and on-demand reference fidelity) — implemented
862
+
863
+ - **`view --bindings-file <json-file>`**: reuses the exact same operational
864
+ binding-file wrapper parser (`loadBindingsFile` in `src/cli.ts`) that
865
+ `evaluate-reference-fidelity --bindings-file` already used - one shared
866
+ parser, never a second divergent one. The file is read once at startup;
867
+ its declarations become `ViewerServerState.bindingDeclarations` (an opaque
868
+ `unknown[]` until validated against a specific reference); the file path
869
+ itself is never persisted, returned, or exposed to the browser. Reference-
870
+ specific declaration validity (region existence, shape) is deferred to the
871
+ moment a reference is actually selected server-side, via the existing
872
+ canonical `isValidReferenceRuntimeBindingDeclarations` - never checked at
873
+ startup without a reference.
874
+ - **Two new additive, read-only routes**
875
+ (`src/viewerServer/evidence/referenceView.ts`):
876
+ `GET /api/references/<handle>/candidate/<handle>/bindings` validates the
877
+ session's declarations against the selected reference and calls the
878
+ existing canonical `evaluateReferenceRuntimeBindings` exactly once.
879
+ `GET /api/references/<handle>/candidate/<handle>/fidelity` is the explicit
880
+ on-demand fidelity trigger - calls the existing canonical
881
+ `evaluateReferenceCandidateFidelity` exactly once, using the exact same
882
+ session declarations, so its embedded `bindings` field and the `/bindings`
883
+ route's own result always structurally agree for identical inputs (same
884
+ pure function, same arguments). Neither route persists anything; both are
885
+ `GET` (idempotent, deterministic, ephemeral over already-selected explicit
886
+ input) - no mutation route was added.
887
+ - **`deriveCoordinateScale` exported additively** from
888
+ `externalReferenceFidelity.ts` (previously module-private) - Batch 6's
889
+ view-lock eligibility reuses this exact function unchanged (same formula,
890
+ same `ASPECT_RATIO_MAPPING_TOLERANCE`, same no-applicable-viewport
891
+ failure) rather than a second aspect-ratio/scale implementation. The
892
+ existing `GET /api/references/<handle>/candidate/<handle>/view` route now
893
+ additionally returns `coordinateMapping: DeriveCoordinateScaleResult` -
894
+ purely a function of the reference, independent of the candidate.
895
+ - **Explicit-binding cross-selection uses only canonical
896
+ `ReferenceRuntimeBindingResult.referenceRegion`/`.runtimeTarget` fields**
897
+ (`ReferenceWorkspace.tsx`): selecting a `bound` reference region
898
+ highlights (via `TargetOverlaySvg`'s existing Batch 4 `highlightNames`
899
+ prop - never the primary `selected`/`aria-pressed` target) its exact
900
+ declared runtime target; selecting a runtime target highlights every
901
+ region whose `bound` result names it (many-to-one, via a new additive
902
+ `highlightRegionIds` prop on `ReferenceRegionOverlaySvg`, matching
903
+ `TargetOverlaySvg`'s established highlight pattern). `ambiguous`/
904
+ `unavailable` results never cross-select. Proven with a real equal-name
905
+ ("header" region + "header" target) Chromium fixture: no cross-selection
906
+ without an explicit declaration, real cross-selection with one.
907
+ - **Zoom/pan is a repository-owned, presentation-only hook**
908
+ (`viewer/src/hooks/useZoomPan.ts`): bounded `[1x, 8x]` scale, `×1.25`/
909
+ `÷1.25` step, expressed as one `{scale, focalX, focalY}` triple in the
910
+ pane's own source-coordinate frame (reference-image pixels or candidate
911
+ CSS pixels - never rewritten). Renders via an SVG `viewBox` override
912
+ (additive `viewBoxOverride`/`svgRef`/pointer-handler props on
913
+ `TargetOverlaySvg`/`ReferenceRegionOverlaySvg`, defaulting to Batch 3/5's
914
+ exact prior behavior when omitted) - image and overlay stay one
915
+ transformed unit automatically since both live inside the same `<svg>`
916
+ root, and native SVG hit-testing means selection keeps working correctly
917
+ under zoom/pan with no extra coordinate math. Panning uses the SVG
918
+ element's own `getScreenCTM()` to convert screen-space pointer deltas into
919
+ source-space deltas - reuses the browser's native transform rather than a
920
+ custom aspect-ratio-aware pixel calculation - and only engages (calling
921
+ `setPointerCapture`) once the pointer has moved past a small threshold, so
922
+ an ordinary click on a region/target rect is never hijacked into a
923
+ phantom drag. Fit and Reset are the same fitted-1x/centered default (task
924
+ §26 - no second presentation-only default was introduced).
925
+ - **View lock reuses one single shared state, never two independently
926
+ synchronized states**: `ReferenceWorkspace.tsx` owns `refZoom`/`candZoom`
927
+ directly (always-controlled `useZoomPan` calls) and each pane's zoom/pan
928
+ action handler updates both states in one synchronous call when locked -
929
+ no reactive effect watches one pane's state to update the other, so no
930
+ feedback-loop risk exists. Lock is available only when a candidate is
931
+ selected, compatibility is not `incomparable`, and `coordinateMapping.ok`
932
+ is `true`; any change to that eligibility (including selecting a
933
+ different reference/candidate) immediately disables lock and shows an
934
+ actionable reason. Synchronization converts a candidate CSS-pixel focal
935
+ point/scale into reference-image-pixel space (and back) using only
936
+ `coordinateMapping.scale.scaleX`/`scaleY` - the exact same canonical
937
+ factor `deriveCoordinateScale` already produces, applied as a straight
938
+ multiply/divide (its own algebraic inverse), never a second mapping rule.
939
+ - **Contract/fidelity independence is never collapsed into one status**:
940
+ the existing Batch 5 "Optional contract/evaluation context" section
941
+ (unchanged) and the new on-demand `ReferenceFidelityPanel` are two
942
+ separate sections rendering two separate canonical results
943
+ (`FrontendContractEvaluationArtifact.overallVerdict` and
944
+ `ReferenceCandidateFidelityEvaluation.state`) side by side; when both are
945
+ present, an explicit note states that fidelity does not override an
946
+ active contract failure. No new coordinator/aggregate verdict is computed
947
+ anywhere in this batch.
948
+ - **PWA cache boundary preserved**: both new routes live under `/api/`,
949
+ already covered by Batch 1's `navigateFallbackDenylist`.
950
+
951
+ ## v0.8 Batch 7 (Bounded agent context, correlation, provenance, and raw evidence navigation) — implemented
952
+
953
+ - **Bounded agent context remains programmatic-only**: no filesystem writer
954
+ was added for `BoundedAgentContextArtifact` (it is still not an
955
+ Observer-evidence-root artifact family - `src/viewerServer/evidence/classify.ts`'s
956
+ "known but unreadered kind" comment is unchanged). `view --context-file
957
+ <json-file>` reads exactly one already-serialized
958
+ `BoundedAgentContextArtifact` value directly (no wrapper object) as
959
+ explicit, session-only viewer input - read once at startup
960
+ (`src/cli.ts#loadContextFile`, mirroring `loadBindingsFile`'s exact
961
+ read/size-bound/parse shape), classified by
962
+ `src/viewerServer/context.ts#classifyContextFileContent` (reuses the
963
+ existing canonical `isValidBoundedAgentContextArtifact` - never a second
964
+ validator), and held only in `ViewerServerState.context` for the life of
965
+ the process. The file's size is bounded by the existing Batch 2
966
+ `MAX_MANIFEST_CANDIDATE_BYTES` (2,000,000 bytes) rather than a second
967
+ bound, since a context artifact's own frozen numeric caps already make it
968
+ far smaller in any realistic case.
969
+ - **Three honest session states, never coerced into one another**: `'none'`
970
+ (no `--context-file`; every Batch 1-6 feature stays fully available),
971
+ `'unsupported-version'` (recognized `artifactKind`, a `schemaVersion`
972
+ other than the current one - the viewer still starts, showing this
973
+ state explicitly rather than either failing or misinterpreting the
974
+ fields), and `'valid'` (structurally validated current-schema context).
975
+ Every other problem (unreadable file, wrong `artifactKind`, a
976
+ structurally invalid *current*-schema artifact) fails viewer startup
977
+ clearly - an explicitly supplied file is never silently ignored.
978
+ - **`GET /api/context`** (`httpServer.ts`) returns the session's exact
979
+ classified state; for `'valid'`, it additionally returns
980
+ `sourceResolution` - the result of resolving
981
+ `artifact.sources` against the current evidence root by **exact
982
+ canonical identity only**
983
+ (`src/viewerServer/evidence/contextSourceView.ts`, reusing/extending
984
+ Batch 4's `linkedEvidence.ts` resolver pattern with three additive
985
+ functions: `resolveObservationById` (bare `observationId`, the only
986
+ identity a context source reference actually carries),
987
+ `resolveEvaluationByIdentity`, and `resolveReferenceByIdentity`). Zero
988
+ matches → `missing`; two or more → `ambiguous` (never silently picks
989
+ one) - the same discipline every other Batch 4/5 resolver already
990
+ established.
991
+ - **Raw structured evidence reuses the existing Batch 2 artifact-detail
992
+ route unchanged**: `RawEvidenceViewer.tsx` calls the existing
993
+ `useArtifactDetail`/`GET /api/artifacts/<handle>` for any exactly-resolved
994
+ source - no second full-artifact retrieval mechanism, no local filesystem
995
+ read, no arbitrary path accepted from the browser.
996
+ `EvidenceReference.path` values are always displayed as plain provenance
997
+ text, never passed to `fs.readFile`/`path.resolve`/a static file server.
998
+ - **Bounded runtime targets, adequacy, omissions, truncations, and
999
+ correlation are rendered exactly as the validated artifact states them** -
1000
+ never recomputed, never boolean-collapsed
1001
+ (`ContextWorkspace.tsx`): `Adequacy.state`
1002
+ (`adequate`/`partial`/`inadequate`) and reasons are shown verbatim;
1003
+ absent bounded-target fields render "not included in this bounded
1004
+ context", never a fabricated falsy/zero value; `required: true`
1005
+ omissions/truncations render in a visually distinct
1006
+ `.context-required-loss` block, separate from optional ones;
1007
+ `correlations` absent renders "Static correlation not included in this
1008
+ context" - never "unavailable" (that status is reserved for a real
1009
+ per-target `RuntimeStaticCorrelationRecord` with zero candidates).
1010
+ `correlated`/`ambiguous`/`unavailable` are preserved exactly; a
1011
+ `correlated` record's one candidate is labeled "Correlated candidate", an
1012
+ `ambiguous` record shows **every** supplied candidate with none visually
1013
+ promoted, and `unavailable` fabricates zero candidates - matching the
1014
+ frozen `CORRELATION_STATUSES` invariants
1015
+ (`domain/boundedAgentContext.ts`) the validator itself already enforces.
1016
+ All new UI text was audited against ownership/edit-authorization language
1017
+ (no "owner"/"source owner"/"owned by") - correlation is presented as
1018
+ evidence, never as edit authorization.
1019
+ - **Context-target ↔ runtime-target interaction never infers source
1020
+ ownership**: selecting a bounded target or a correlation record uses only
1021
+ exact `targetId`/`runtimeTargetId` string matching; for each *exactly
1022
+ resolved* source observation, `SourceObservationTargetCheck` checks
1023
+ membership in that observation's own already-fetched `targetEvidence`
1024
+ (a plain lookup over already-loaded JSON, never a new derivation) and, if
1025
+ more than one resolved source observation contains the same target id,
1026
+ lists all of them rather than picking one.
1027
+ - **Bounded reference-fidelity projection is never recomputed, and is kept
1028
+ visibly distinct from a live on-demand evaluation**: absent `fidelity`
1029
+ renders "Reference fidelity not included in this bounded context" - never
1030
+ implied as passing. When present, `mismatches` and `protectedContext` are
1031
+ rendered in separate sections from the artifact's own fields exactly as
1032
+ supplied; a `state: 'not-evaluated'` blocked projection always shows
1033
+ `blockedBy` prominently and never renders an empty mismatch list as "no
1034
+ problems". When the context's `referenceId`/`candidateObservationId`
1035
+ exactly resolve within the current evidence root, `ContextWorkspace.tsx`
1036
+ embeds the existing, unchanged Batch 6 `ReferenceFidelityPanel` (the same
1037
+ on-demand `evaluateReferenceCandidateFidelity` trigger) directly beneath
1038
+ the bounded projection, labeled "Bounded context fidelity projection"
1039
+ above and "Current on-demand fidelity evaluation" below - two separate,
1040
+ clearly labeled evidence instances, never silently merged or replaced.
1041
+ - **No runtime rebuild of context or correlation, and no my-dev-kit
1042
+ execution from the shipped viewer**: grep-verified - `projectBoundedAgentContext(`,
1043
+ `deriveRuntimeStaticCorrelations(`, and `attachRuntimeStaticCorrelations(`
1044
+ appear nowhere under `src/viewerServer/` or `viewer/src/` (only in test/
1045
+ fixture-generation code, per the frozen plan's explicit test-fixture
1046
+ exception); no `child_process`/`npx @dailephd/my-dev-kit` invocation
1047
+ exists in the viewer server or browser bundle.
1048
+ - **PWA cache boundary preserved**: `GET /api/context` lives under `/api/`,
1049
+ already covered by Batch 1's `navigateFallbackDenylist`.
1050
+
1051
+ ## v0.8 Batch 8 (Integrated viewer acceptance, PWA hardening, and packaged proof) — implemented
1052
+
1053
+ Batch 8 is the final v0.8 implementation batch. It is integration/hardening,
1054
+ not a new architecture layer: no new API route, no new CLI flag, and no new
1055
+ canonical-engine call site were added. See
1056
+ `docs/reports/v0.8-integrated-viewer-acceptance-batch8.md` for the full
1057
+ record.
1058
+
1059
+ - **Closed three named real-browser coverage gaps**, each proved against the
1060
+ actual built viewer through the actual loopback server, never a hand-edited
1061
+ fixture verdict: many reference regions bound to one runtime target all
1062
+ cross-highlight together (the pre-existing target→regions loop in
1063
+ `ReferenceWorkspace.tsx` already iterated every matching binding - the gap
1064
+ was in real-browser proof, not in the derivation); reference-fidelity
1065
+ `fail` alongside a genuine frontend-contract `PASS` for the same candidate
1066
+ display independently (the pre-existing independence note in
1067
+ `ReferenceWorkspace.tsx` was already verdict-agnostic); a bounded context
1068
+ whose sources include two observations sharing a stable target id lists
1069
+ every matching source observation (the pre-existing
1070
+ `SourceObservationTargetCheck` in `ContextWorkspace.tsx` already checked
1071
+ membership per source independently, never picking one).
1072
+ - **One real accessibility defect found and fixed**: a cross-highlighted,
1073
+ non-selected region/target `<rect>` (`TargetOverlaySvg.tsx`,
1074
+ `ReferenceRegionOverlaySvg.tsx`) exposed no accessible state distinguishing
1075
+ it from a plain unselected rect - `aria-pressed` correctly stayed `false`
1076
+ (it is not the primary single-selection), but nothing else communicated
1077
+ the highlight to assistive technology. Fixed by adding
1078
+ `data-highlighted="true"` and an `aria-label` suffix
1079
+ (`" (highlighted: related to current selection)"`) when highlighted and
1080
+ not selected, leaving `aria-pressed` semantics untouched.
1081
+ - **First live-browser PWA proof suite** (`tests/browser/pwaHardening.test.ts`):
1082
+ real service-worker registration and activation against the built shell;
1083
+ the manifest fetched and confirmed `display: "standalone"`; zero Cache
1084
+ Storage entries under any `/api/` pathname after normal use, confirming
1085
+ the `navigateFallbackDenylist` boundary holds live, not just in the built
1086
+ `sw.js` regex; and the hard server-down gate - after the server is closed
1087
+ and the same page reloaded, the app shell still renders from the precache,
1088
+ but the evidence-dependent surface shows the explicit
1089
+ `.evidence-list__error` "Evidence index unavailable" state, with the
1090
+ previously-visible evidence asserted absent. Install-control is proven
1091
+ only via synthetic `beforeinstallprompt` dispatch (a genuine browser
1092
+ install prompt was not observed under automation). Standalone-mode CDP
1093
+ display-mode emulation was attempted but not observed to take effect -
1094
+ recorded honestly, never overstated as actual OS-level installation proof.
1095
+ - **Packaged-candidate proof**: `npm pack` → clean consumer install (outside
1096
+ the repository) → the actually-installed CLI executable (not repo
1097
+ `dist/cli.js`) → the installed `view` server → real Chromium against the
1098
+ packaged/installed server, not a source-checkout dev server. Read-only
1099
+ evidence-hash proof (SHA-256 of every file in the exercised evidence root,
1100
+ taken before and after the packaged-browser session) confirmed no
1101
+ mutation and no new viewer-created artifact anywhere in the evidence root.
1102
+ - **Re-confirmed the no-second-engine invariant** across all eight batches by
1103
+ re-running the exact `grep -rn` audit from earlier batches - unchanged
1104
+ findings, no duplicate evidence engine exists.
1105
+
1106
+ ## v0.9 visual annotation architecture (released in 0.9.0)
1107
+
1108
+ v0.9 is released as package version `0.9.0`. It adds one new
1109
+ evidence family and a narrow local authoring path to the existing viewer. It
1110
+ adds no new evaluator, no second contract or reference model, and no new
1111
+ PASS/FAIL semantics.
1112
+
1113
+ **Evidence ownership**:
1114
+
1115
+ - `src/domain/visualAnnotation.ts` owns `VisualAnnotationArtifact` (artifact
1116
+ kind `my-frontend-observer/visual-annotation`, schema `1.0.0`): the source
1117
+ union, structured marks, explicit associations, candidate/confirmed
1118
+ interpretation, validation, and the pure overlay SVG renderer.
1119
+ - `src/domain/visualAnnotationIdentity.ts` owns deterministic request identity
1120
+ and fresh instance identity.
1121
+ - `src/artifacts/visualAnnotationArtifactWriter.ts` and
1122
+ `visualAnnotationArtifactReader.ts` own atomic persistence (temporary
1123
+ `.tmp-<id>` directory, then rename) and canonical reading, including the
1124
+ derived `annotation-overlay.svg` and its digest.
1125
+ - `src/application/visualAnnotationPersistenceService.ts` owns saving one
1126
+ annotation or one superseding revision.
1127
+
1128
+ **Coordinate domains**: runtime annotations use the observation's runtime CSS
1129
+ pixel space. Reference annotations use the reference image's own pixel space
1130
+ (for an approved reference, the owning imported image). The two domains are
1131
+ never mixed. Zoomed or panned drawing is mapped back through the SVG's own
1132
+ transform (`viewer/src/svg/sourceCoordinates.ts`).
1133
+
1134
+ **Viewer discovery and media** (`src/viewerServer/evidence/`): the index
1135
+ classifies `visual-annotation` evidence and skips writer `.tmp-*`
1136
+ directories. `annotationView.ts` resolves an annotation's exact canonical
1137
+ source and reports `unavailable` instead of guessing a replacement. The media
1138
+ resolver serves the `annotation-overlay` role only after re-rendering the SVG
1139
+ from the artifact and verifying it, with a script-blocking sandbox policy.
1140
+
1141
+ **Project-aware authoring security**: `src/viewerServer/authoringSecurity.ts`
1142
+ owns the in-memory 32-byte session capability and the Host, Origin, token,
1143
+ content-type, and content-encoding checks. `httpServer.ts` owns the shared
1144
+ bounded JSON gate and routes exactly three `POST` responsibilities.
1145
+ `view --root` never creates an authoring session, so the standalone
1146
+ arbitrary-root viewer stays read-only.
1147
+
1148
+ 1. `POST /api/annotations` (`src/viewerServer/annotationAuthoring.ts`) saves
1149
+ a new annotation or a revision through the canonical persistence service,
1150
+ with stale-parent conflict detection. This module also owns the single
1151
+ per-session write queue used by all three routes.
1152
+ 2. `POST /api/annotations/:handle/promote-contract`
1153
+ (`src/viewerServer/annotationContractPromotion.ts`) calls
1154
+ `src/application/visualAnnotationContractPromotionService.ts`. Selected
1155
+ confirmed runtime intent becomes one canonical `PerChangeContract` through
1156
+ the existing contract persistence service. Optional activation goes only
1157
+ through `activateProjectChangeContract` in
1158
+ `src/application/projectWorkflowService.ts`.
1159
+ 3. `POST /api/annotations/:handle/materialize-reference`
1160
+ (`src/viewerServer/annotationReferenceMaterialization.ts`) calls
1161
+ `src/application/visualAnnotationReferenceMaterializationService.ts`.
1162
+ Selected confirmed reference intent becomes a new imported
1163
+ `ExternalReferenceArtifact` through the existing `importExternalReference`,
1164
+ superseding the source. The image bytes come only from the safe media
1165
+ resolver.
1166
+
1167
+ Output locations come from `src/projectWorkflow/projectPaths.ts`
1168
+ (`annotations`, `contracts`, and `references` under
1169
+ `.frontend-observer/evidence`). The browser never supplies a path.
1170
+
1171
+ **Viewer UI** (`viewer/src/`): `annotation/` holds pure presentation models
1172
+ (`annotationGeometry.ts`, `runtimeIntent.ts`, `referenceIntent.ts`,
1173
+ `confirmation.ts`). `components/AnnotationLayer.tsx`,
1174
+ `AnnotationToolbar.tsx`, `RuntimeAnnotationPanel.tsx`,
1175
+ `ReferenceAnnotationPanel.tsx`, `CandidateRegionPreviewLayer.tsx`, and
1176
+ `AnnotationPanelSections.tsx` render marks, tools, intent, promotion, and
1177
+ materialization. Hooks under `hooks/` hold draft, saved-list, session,
1178
+ pointer, promotion, and materialization state. The authoring token lives only
1179
+ in React memory.
1180
+
1181
+ **Existing evaluators remain authoritative**: the canonical contract
1182
+ evaluator, reference relationship derivation, requirement adequacy, reference
1183
+ fidelity, and project `check` are unchanged. Annotation evidence feeds them
1184
+ only through promoted contracts and materialized references.
1185
+
1186
+ ## Retained v0.1 architecture constraints
1187
+
1188
+ v0.1 planning preserved these approved boundaries without treating module
1189
+ names from the historical run as mandatory:
1190
+
1191
+ ```text
1192
+ thin command-line boundary
1193
+ ↓
1194
+ reusable observation engine/application layer
1195
+ ↓
1196
+ browser automation boundary
1197
+ ↓
1198
+ observer-owned runtime evidence
1199
+
1200
+ observer-owned domain/schema
1201
+ ↓
1202
+ artifact ownership boundary
1203
+
1204
+ deterministic fixture/test boundary
1205
+ ↓
1206
+ browser-level validation
1207
+ ```
1208
+
1209
+ Use one browser engine implementation, keep browser logic out of presentation,
1210
+ avoid speculative plugin/multi-browser abstractions, and keep observed
1211
+ applications external. Versions before v0.6 did not add runtime coupling to
1212
+ sibling ecosystem projects. v0.6 adds only explicit bounded context and
1213
+ correlation/export contracts within this repository, preserving independent
1214
+ ownership; orchestrator-consumption and lab-compatibility work are separate
1215
+ sibling-repository deliverables, not part of this repository's architecture.
1216
+
1217
+ The text/config-driven coding-agent workflow and the non-UI external-reference
1218
+ evidence foundation are operational as of v0.7. The viewer and annotation
1219
+ layers must consume the same canonical observation, relationship, comparison,
1220
+ contract, change-scope, reference, correlation, and context boundaries rather
1221
+ than creating parallel engines. The concrete implementation plan and module
1222
+ layout for each future version must be designed only after that version's
1223
+ planning workflow inspects the current repositories.
1224
+
1225
+ v0.7 Prompt 1 implements only the bottom of that external-reference stack: a
1226
+ new, standalone `ExternalReferenceArtifact` evidence root
1227
+ (`src/domain/externalReference.ts`, `externalReferenceImage.ts`,
1228
+ `externalReferenceIdentity.ts`, `src/artifacts/externalReferenceArtifact{Writer,Reader}.ts`,
1229
+ `src/application/externalReferencePersistenceService.ts`) with its own
1230
+ identity, provenance, bounded image metadata, and a two-state
1231
+ (`imported`/`approved`) lifecycle - see `docs/CONTRACTS.md` "v0.7 Prompt 1
1232
+ external-reference artifact contract" for the exact shape. It follows the
1233
+ same identity/persistence/diagnostics/export conventions as every existing
1234
+ artifact family (deterministic canonicalize-then-sha256 request identity,
1235
+ nonce-based fresh instance identity, atomic temp-dir-then-rename persistence,
1236
+ the shared `DIAGNOSTIC_CODES` vocabulary) without reusing or duplicating the
1237
+ observation, comparison, or contract engines themselves - an external
1238
+ reference is desired-design evidence, never an `ObservationArtifact`, an
1239
+ approved baseline, or a runtime target.
1240
+
1241
+ v0.7 Prompt 2 adds explicit reference regions and reusable reference-region
1242
+ relationships on top of that foundation (`domain/externalReferenceRegions.ts`,
1243
+ `externalReferenceRegionRelationships.ts`) - see `docs/CONTRACTS.md` "v0.7
1244
+ Prompt 2 explicit reference regions and relationships" for the exact shape.
1245
+ The relationship-derivation predicates are reused verbatim (now exported
1246
+ additively) from `domain/relationships.ts` rather than reimplemented, so
1247
+ reference-region geometry and runtime-target geometry can never diverge on
1248
+ the same underlying formula; only the geometry-only relationship families
1249
+ apply, since a static image exposes no DOM, scroll, or viewport evidence.
1250
+ v0.7 Prompt 3 adds selected design requirements, tolerance semantics, and
1251
+ reference-evidence adequacy on top of that region model
1252
+ (`domain/externalReferenceRequirements.ts`,
1253
+ `externalReferenceRequirementIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1254
+ Prompt 3 selected design requirements, tolerance semantics, and
1255
+ reference-evidence adequacy" for the exact shape. Requirement categories are
1256
+ the exact v0.5 `AuthoredChangeScopeCategory` vocabulary, imported directly
1257
+ rather than reinvented, since that type carries no runtime-only coupling of
1258
+ its own; tolerance is a genuinely new, reference-owned type (never a reuse
1259
+ of `frontendContracts.ts`'s runtime/CSS-pixel-implicit `ContractTolerance`);
1260
+ and reference-evidence adequacy is a small, independently-owned vocabulary
1261
+ distinct from `boundedAgentContext.ts`'s runtime/static-correlation
1262
+ `Adequacy`. A region property or derived relationship is never promoted to
1263
+ an executable requirement automatically - only explicit user/configuration
1264
+ selection does that.
1265
+
1266
+ v0.7 Prompt 4 adds explicit reference applicability
1267
+ (`domain/externalReferenceApplicability.ts`) and observation-side explicit
1268
+ state identity (`domain/explicitState.ts`, shared by both artifact
1269
+ families), plus one new pure domain module,
1270
+ `domain/externalReferenceCompatibility.ts`, that evaluates whether a
1271
+ candidate `ObservationArtifact` describes the same frontend state as a
1272
+ given `ExternalReferenceArtifact` - see `docs/CONTRACTS.md` "v0.7 Prompt 4
1273
+ reference applicability and candidate-state compatibility" for the exact
1274
+ shape. This is page/state-level only, never geometry or fidelity, and
1275
+ remains a wholly separate concern from Prompt 3's reference-evidence
1276
+ adequacy - the two can independently disagree (an adequate reference can be
1277
+ incomparable against a given candidate, and vice versa). Rather than
1278
+ inventing a second comparability engine, Prompt 4 extracts one new exported
1279
+ pure helper from v0.4's own `domain/comparisonEngine.ts`
1280
+ (`assessOptionalComparabilityDimension`) and reuses it from both v0.4's
1281
+ `evaluateComparability` (Observation-vs-Observation) and the new
1282
+ `evaluateReferenceCandidateCompatibility` (Reference-vs-Observation) - the
1283
+ one additive behavior change to v0.4 itself is that `evaluateComparability`
1284
+ now assesses (rather than always reporting unassessed) theme/authenticated-
1285
+ state/application-state whenever both observations declare
1286
+ `requestConfig.explicitState`, while every historical/legacy observation
1287
+ pair retains the exact prior unassessed-only behavior. No new persisted
1288
+ artifact kind is introduced for the compatibility result; it is a pure,
1289
+ on-demand function of two already-persisted artifacts.
1290
+
1291
+ v0.7 Prompt 5 adds explicit reference-region <-> runtime-target binding
1292
+ (`domain/externalReferenceRuntimeBinding.ts`) - see `docs/CONTRACTS.md`
1293
+ "v0.7 Prompt 5 explicit reference-region <-> runtime-target binding" for
1294
+ the exact shape. It answers only "which stable v0.2 runtime target does
1295
+ this candidate observation resolve for each explicitly declared reference
1296
+ region", strictly downstream of Prompt 4's compatibility gate (reused
1297
+ verbatim, never duplicated) and strictly upstream of v0.6's own
1298
+ runtime/static correlation - the two identity domains (a Prompt 2
1299
+ `ReferenceRegion.id` and a v0.2 `NamedTarget.name`) never collapse into
1300
+ each other, and this stage stops at the runtime target, never reaching
1301
+ source ownership. Following v0.6's uncertainty discipline
1302
+ (`domain/boundedAgentContextCorrelation.ts`), binding never guesses through
1303
+ ambiguity - an ambiguously or unavailably resolved v0.2 target is reported
1304
+ as such, never silently treated as bound - though the actual per-status
1305
+ mapping (`bound`/`ambiguous`/`unavailable`) is binding's own, independently
1306
+ owned vocabulary, not a reuse of v0.6's `correlated`/`ambiguous`/
1307
+ `unavailable` correlation-status semantics (a different evidence boundary:
1308
+ correlation ranks *static candidates* for one runtime target, whereas
1309
+ binding resolves *one runtime target's own existence* for one declared
1310
+ correspondence). No second target resolver, no browser execution, and no
1311
+ new persisted artifact family were introduced; neither
1312
+ `ExternalReferenceArtifact` nor `ObservationArtifact` is mutated to carry a
1313
+ binding result, since a reference may later be evaluated against several
1314
+ candidates and an observation against several references.
1315
+
1316
+ v0.7 Prompt 6 adds structured reference-vs-candidate fidelity evaluation
1317
+ (`domain/externalReferenceFidelity.ts`, `application/referenceFidelityEvaluationService.ts`,
1318
+ and the `evaluate-reference-fidelity` CLI command) - the first point in this
1319
+ stack where a reference's authored expectation is compared against live
1320
+ candidate evidence. See `docs/CONTRACTS.md` "v0.7 Prompt 6 structured
1321
+ reference-vs-candidate fidelity evaluation" for the exact shape. It is
1322
+ downstream of every prior v0.7 prompt and reuses each verbatim: Prompt 3's
1323
+ `deriveReferenceRequirementAdequacy`/`deriveReferenceRequirementExpectation`/
1324
+ `deriveReferenceRequirementMeasurement` (never redefined), Prompt 4's
1325
+ `evaluateReferenceCandidateCompatibility` (a hard gate, never duplicated),
1326
+ and Prompt 5's `evaluateReferenceRuntimeBindings` (the sole source of
1327
+ runtime-target identity - no automatic binding, no second target resolver).
1328
+ It also reuses v0.4's `deriveLayoutRelationships` for runtime relationship
1329
+ evidence, scoped to the exact requested relationship family (the same
1330
+ Prompt 3 bug-fix precedent). The one genuinely new problem this prompt
1331
+ solves is the reference-image-pixel <-> CSS-pixel coordinate mapping: a
1332
+ single explicit, deterministic full-frame scale derived from
1333
+ `reference.applicability.viewport` and the reference image's own
1334
+ dimensions, with a tiny independent aspect-ratio-coherence check (never a
1335
+ design tolerance) gating whether that mapping exists at all. No new
1336
+ persisted artifact family, no browser execution, and no source-ownership
1337
+ attribution - this is reference fidelity only, a separate concern from any
1338
+ later v0.5 baseline/per-change contract result or v0.7 overall verdict.
1339
+
1340
+ v0.7 Prompt 7 adds bounded reference-fidelity projection into the existing
1341
+ v0.6 bounded-agent-context architecture (`domain/referenceFidelityProjection.ts`,
1342
+ plus additive extensions to `domain/boundedAgentContext.ts`,
1343
+ `domain/boundedAgentContextIdentity.ts`, and
1344
+ `domain/boundedAgentContextProjection.ts`) - see `docs/CONTRACTS.md` "v0.7
1345
+ Prompt 7 bounded reference-fidelity projection and v0.6 bounded-agent-
1346
+ context integration" for the exact shape. `projectBoundedAgentContext`
1347
+ itself, not a new parallel context system, gains one new optional input (an
1348
+ already-computed Prompt 6 fidelity evaluation): fidelity-relevant runtime
1349
+ targets fold into the exact same required/permitted-target-allocation,
1350
+ evidence-tiering, omission/truncation, and adequacy machinery v0.5 contract
1351
+ clauses already compete in, and a new `fidelity?:
1352
+ BoundedReferenceFidelityProjection` field on `BoundedAgentContextArtifact`
1353
+ (mirroring `correlations?`'s own additive, non-version-bumping precedent
1354
+ from v0.6 Batch 3) carries a bounded, priority-ordered selection of Prompt
1355
+ 6's non-passing requirement results plus passing protected/preserved
1356
+ context. No second bounded-context architecture, no recomputation of Prompt
1357
+ 2-6/v0.4/v0.5 logic, and no change to v0.6's own runtime/static correlation
1358
+ (`deriveRuntimeStaticCorrelations`/`attachRuntimeStaticCorrelations` are
1359
+ untouched and reused exactly as before) - a caller joins fidelity, target,
1360
+ and correlation evidence by the one stable v0.2 runtime target id all three
1361
+ already share. Every new field is optional and additive; a pre-Prompt-7
1362
+ caller supplying no fidelity evidence receives byte-identical output,
1363
+ including logical identity.
1364
+
1365
+ v0.7 Prompt 8 adds the first complete, controlled end-to-end external-
1366
+ reference correction workflow (`domain/referenceCorrectionWorkflow.ts`,
1367
+ `domain/referenceCorrectionIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1368
+ Prompt 8 controlled end-to-end external-reference coding-agent correction
1369
+ workflow" for the exact shape. This is a narrowly-scoped coordinator, not a
1370
+ second workflow engine: it exposes exactly two pure operations -
1371
+ `prepareReferenceCorrection` (pre-change evidence -> a bounded coding-agent
1372
+ handoff, built from Prompt 1/3/4/5/6/7's existing engines) and
1373
+ `reviewReferenceCorrectionAttempt` (a fresh post-edit candidate -> one
1374
+ composed overall result, built from v0.4's `compareObservations`, v0.7
1375
+ Prompt 6's `evaluateReferenceCandidateFidelity`, and v0.5's
1376
+ `evaluateFrontendContract`) - with an explicit, un-automatable seam between
1377
+ them where an external implementation actor edits target source. Overall
1378
+ `'pass'` requires both reference fidelity `'pass'` and v0.5 contract
1379
+ evaluation `'PASS'` - matching the selected design reference is necessary
1380
+ but never sufficient, so a candidate that visually satisfies the reference
1381
+ while regressing an active protected or preserved contract clause still
1382
+ resolves to overall `'fail'`. Review identity is a deterministic hash of
1383
+ `{referenceRequestId, baselineObservationId, baselineContractId,
1384
+ baselineContractClauses, changeContractId, changeContractClauses,
1385
+ bindingDeclarations}`; attempt identity is a deterministic hash of
1386
+ `{reviewRequestId, candidateObservationId}`. `reviewReferenceCorrectionAttempt`
1387
+ rejects any call whose supplied `reviewRequestId` does not match what its own
1388
+ baseline/contract/reference/binding inputs recompute - the mechanism that
1389
+ makes "every attempt evaluates against the same approved baseline" an
1390
+ enforced invariant, not just a documented one. No new persisted artifact
1391
+ family, no CLI surface, and - most importantly - no code path anywhere in
1392
+ this module (or anything it calls) that opens, parses, or writes a target
1393
+ source file: real candidate capture remains the caller's own responsibility
1394
+ through the existing, unmodified real-Chromium observation pipeline.