@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
package/docs/CONTRACTS.md CHANGED
@@ -1,1960 +1,1971 @@
1
- # Contracts
2
-
3
- ## Visual-change workflow artifact (`1.0.0`, v0.10 Batch 1)
4
-
5
- `my-frontend-observer/visual-change-workflow` is the immutable Observer-owned history envelope for one frozen visual-change request. It records a deterministic `visualChangeRequestId`, a fresh `visualChangeWorkflowId`, optional forward-only `supersedesVisualChangeWorkflowId`, exact references to existing canonical evidence, zero to twenty bounded attempt records, optional activation/governance result references, producer metadata, and creation provenance.
6
-
7
- The two entry modes are exactly `actual-frontend` and `reference`. Reference mode stores validated explicit runtime binding declarations in canonical `bindings.json`; the manifest pins its SHA-256 and declaration count. Actual mode never writes that file. Project-relative evidence references must remain contained portable paths; source artifacts and media are referenced rather than copied.
8
-
9
- Request identity hashes semantic scope only. It excludes timestamps and operational storage locations. Workflow instance identity is fresh for every explicit persistence. Attempt identity is deterministic over the visual-change request ID and candidate observation ID. Human review state is exactly `pending`, `correction-requested`, `accepted`, or `abandoned`; this foundation validates structure but does not implement the later acceptance rule or execute checks.
10
-
11
- The v0.10 Batch 2 application composition explicitly activates either the frozen per-change contract or the frozen approved reference and workflow-owned bindings in project acceptance. Activation and restoration preserve unrelated configuration, use compensating atomic writes across mutable project configuration and immutable workflow revisions, and refuse acceptance drift. A workflow check resolves an alias only when its catalog identity and artifact location exactly match the frozen baseline, then invokes the existing `checkProject` owner once. Only a canonical result containing both baseline and candidate summaries can append a pending attempt revision; the snapshot is a bounded direct projection of that result and does not repeat evaluation.
12
-
13
- The v0.10 Batch 3 Viewer exposes bounded workflow inspection at `GET /api/visual-changes/:handle/view` in both standalone and project-aware sessions. Create, activate, check, and restore POST operations are project-aware only, reuse the existing in-memory authoring capability and request guards, accept no filesystem paths, and delegate to the Batch 2 application service. Every new response is `no-store`. Successful immutable mutations return the exact new workflow ID so the Viewer can refresh and reselect that revision without guessing. The Viewer protocol remains `1.3.0`.
14
-
15
- The v0.10 Batch 4 actual-frontend entry route promotes only explicitly selected, saved, confirmed, canonically promotable runtime intent without activating project acceptance. It freezes the exact resulting contract instance with the saved annotation, source observation, and configured persistent baseline contract through the Batch 2 workflow-creation owner. Repeated equivalent starts share `contractRequestId` while receiving fresh contract, visual-change request, and workflow instance identities. Activation remains a later explicit workflow action.
16
-
17
- Reference entry freezes only an exact approved reference, an annotation authored
18
- against that exact instance, an exact baseline observation, and explicitly
19
- authored valid bindings. Adequacy, compatibility, complete required-region
20
- coverage, and canonical binding evaluation fail closed. Imported references,
21
- inferred bindings, and session bindings are not executable workflow scope.
22
-
23
- `VisualChangeAgentHandoff` uses handoff kind
24
- `my-frontend-observer/visual-change-agent-handoff` and version `1.0.0`. It is a
25
- bounded non-artifact transfer contract and is never discovered or persisted as
26
- Observer evidence. It includes confirmed scope, Observer-owned bounded context,
27
- optional unchanged supplemental context, the exact post-edit check instruction,
28
- and optional non-authoritative orchestrator correlation.
29
-
30
- The current cycle is derived from the latest attempt. A pending attempt blocks
31
- another check or handoff until explicit correction, acceptance, or abandonment.
32
- Only the latest canonical PASS may be accepted. Review changes only the latest
33
- attempt's review object in a fresh workflow revision. Governance references may
34
- be recorded only after acceptance and only for already-persisted canonical
35
- approval results; they do not perform approval or change project configuration.
36
- No existing evidence schema changed for v0.10.
37
-
38
- ## Current contracts
39
-
40
- The observation artifact contract is published in the current
41
- `my-frontend-observer@0.7.0` package and proven both from the source checkout
42
- and from the packed npm tarball, on Windows, Linux, and macOS. The observation
43
- schema is `1.2.0` (see "v0.2 target contract" and "v0.3 scroll scenario
44
- contract" below):
45
-
46
- - artifact kind `my-frontend-observer/observation`, schema version `1.2.0`
47
- (independent of the package version);
48
- - one artifact root per observation, `<outputLocation>/<observationId>/`,
49
- containing exactly `manifest.json` (the full `ObservationArtifact`, with
50
- page/target evidence embedded inline) and `screenshot.png` - there is no
51
- separate `evidence.json`;
52
- - `manifest.json` is written last, after `screenshot.png`, via one atomic
53
- directory rename, so a consumer never observes a partially-written
54
- artifact; a filesystem failure anywhere in that sequence reports the
55
- `artifact-write-failure` diagnostic and leaves no completed artifact;
56
- - internal artifact references (e.g. `screenshot.png`) are relative to the
57
- artifact root, never an absolute machine path; the observation's logical
58
- identity is its `observationId`, not its filesystem location;
59
- - evidence states `available`, `unavailable`, `not-applicable`, `partial`;
60
- evidence sources `browser`, `computed-browser`, `derived`;
61
- - a stable diagnostic vocabulary (`src/domain/diagnostics.ts`) and completion
62
- states `complete`, `partial`, `warning`, `invalid-request`, `fatal`
63
- (`src/domain/completion.ts`);
64
- - observation/request identity, producer/package identity, and browser
65
- provenance are all present in every persisted manifest.
66
-
67
- This contract is implemented and published; no public programmatic-API
68
- compatibility promise has been made for the observation engine itself. v0.6
69
- additionally publishes the bounded-agent-context/correlation programmatic surface
70
- described later in this document.
71
-
72
- ## v0.2 target contract (shipped as part of this release)
73
-
74
- v0.2 introduces a canonical target-configuration model: each configured target has a stable
75
- observer-level `name` plus an ordered array of bounded `locators`
76
- (`role`, `id`, `data-attribute`, `semantic-element`, `css`, `text`). This
77
- identity is distinct from both the browser locator definition that resolves
78
- it and any source-code identity. The legacy `{name, selector}` shape remains
79
- accepted and normalizes to a one-item `css` locator, so every published
80
- `0.1.0` CLI invocation continues to work unchanged. Locator precedence is the
81
- configured array order; resolution stops on the first unique match, on any
82
- ambiguous match (never falling through to a later locator), or on an
83
- unevaluable locator - never silently. All six frozen locator kinds are now
84
- resolved against a real Chromium page (`role` via Playwright's accessibility-
85
- role/name locator with exact name matching, `id`/`data-attribute` via exact
86
- CSS attribute-equals matching that never reinterprets the configured value as
87
- selector syntax, `semantic-element` via the frozen tag set, `css` via the
88
- existing v0.1 behavior, `text` via exact-text matching only); every kind
89
- converges on the same measurement path, so locator strategy never changes the
90
- resulting target evidence shape.
91
-
92
- Each resolved target's evidence record additionally carries three bounded
93
- fields: `semanticState` (a first family of `disabled`/`expanded`/
94
- `checked`/`selected`/`pressed`/`current` values read from the element's own
95
- native form-control properties and explicit `aria-*` attributes - a key is
96
- present only when the browser exposes that state as applicable to this
97
- element, so an explicit `false` is always distinguishable from "not
98
- applicable"; `not-applicable` when no supported state applies at all);
99
- `landmark` (derived only from the already-captured browser-exposed
100
- role - never from locator kind or HTML tag - against the standard landmark
101
- role set `banner`/`navigation`/`main`/`complementary`/`contentinfo`/`form`/
102
- `region`/`search`); and `containment` (bounded DOM containment checked only
103
- among the other explicitly configured targets in the same observation, in
104
- configured order, never a layout/relationship graph - `available` when every
105
- other configured target was itself resolved and checked, `partial` when one
106
- or more could not be, `unavailable` when the target itself never resolved).
107
- Stable observer target identity is proven, not just declared: the same
108
- target configuration produces the same `requestId` across repeated
109
- observations (with a fresh `observationId` each time); changing a target's
110
- locator strategy while keeping its stable name changes `requestId` but not
111
- the `targetEvidence` key; and actual runtime disappearance of a
112
- still-configured target changes only its resolution status, never the
113
- `requestId`.
114
-
115
- The full canonical semantic target model above is reachable through the
116
- real public CLI: `my-frontend-observer observe --targets-file <json-file>`
117
- supplies the structured `{ "targets": [...] }` collection (see
118
- `docs/COMMANDS.md` "Structured semantic targets") as an alternative to the
119
- existing `--target id=css-selector` shorthand - the two are mutually
120
- exclusive per invocation, and both converge on the same
121
- `normalizeRequest()`/browser-resolver/artifact path, so a semantic
122
- observation produces exactly the same `manifest.json` shape as a
123
- CSS-shorthand one. Schema `1.1.0` was the v0.2 published artifact schema;
124
- schema `1.2.0` has been emitted since v0.3 and remains the observation schema
125
- in the current published v0.7.0 package, for both target-input modes
126
- (target semantics are unchanged from v0.2 - see the v0.3 scroll scenario
127
- contract below for what schema `1.2.0` actually adds). `--targets-file`'s
128
- local input path is never part of the persisted request identity or
129
- artifact.
130
-
131
- ## v0.3 scroll scenario contract (shipped as part of this release)
132
-
133
- v0.3 introduces one optional, additive request/evidence concern: a bounded
134
- runtime scroll scenario, schema `1.2.0`.
135
-
136
- A normalized request may carry `scrollScenario: { action }` with exactly one
137
- of two frozen action kinds:
138
-
139
- - `{ "kind": "window-scroll-by", "deltaX": <int>, "deltaY": <int> }`
140
- - `{ "kind": "target-scroll-by", "target": "<stable target name>", "deltaX": <int>, "deltaY": <int> }`
141
-
142
- `deltaX`/`deltaY` are signed integers bounded to `[-20000, 20000]`; at least
143
- one must be non-zero. `target-scroll-by.target` refers only to an existing
144
- stable configured target `name` (never a selector) and resolves through the
145
- same canonical `resolveConfiguredTargets` algorithm every v0.2 locator kind
146
- already uses - there is no second target-resolution path. A request with no
147
- scenario normalizes and identifies exactly as it did before v0.3.
148
-
149
- Execution (both action kinds share one code path): perform the immediate,
150
- non-smooth scroll (`window.scrollBy`/`element.scrollBy`, `behavior:
151
- 'instant'`) on the already-navigated, already-ready page; wait exactly two
152
- `requestAnimationFrame` cycles; capture a final runtime snapshot. No second
153
- browser, page, or navigation is ever created. The resulting scroll position
154
- is browser-authoritative and may be clamped by document/element boundaries;
155
- a scenario producing no movement is still a valid, successfully persisted
156
- observation.
157
-
158
- The scenario evidence lives entirely inside the existing `manifest.json` as
159
- one additional optional `scrollScenarioEvidence` field on `ObservationArtifact`
160
- - there is no separate `scroll.json`/`scenario.json`. It contains:
161
-
162
- - `initial`/`final`: bounded `ScrollRuntimeSnapshot`s (window `scrollX`/
163
- `scrollY`; the browser's own scrolling-root/`documentElement`/`body`
164
- metrics; per-configured-target `scrollTop`/`scrollLeft`/`scrollWidth`/
165
- `scrollHeight`/`clientWidth`/`clientHeight`, actual overflow, bounding
166
- rectangle, and viewport relation);
167
- - `transition`: bounded before/after change evidence (window scroll deltas;
168
- per-target `scrollTop`/`scrollLeft`/bounding-position/viewport-relation
169
- changes; `enteredViewport`/`leftViewport`) - never a generic recursive
170
- diff, and a target is simply omitted when either side's evidence isn't
171
- itself usable (e.g. it never resolved);
172
- - `scrollOwner`: one derived `EvidenceField<ScrollOwnerInterpretation>`
173
- (`document` | `target:<stable-name>` | `none` | `indeterminate`), always
174
- `source: "derived"` with non-empty `derivedFrom` naming the exact
175
- contributing scroll-position measurements. Ownership is derived only from
176
- observed `scrollTop`/`scrollLeft`/`window.scrollX`/`window.scrollY`
177
- changes - never from bounding-rectangle movement (which moves for every
178
- configured target whenever the document scrolls), computed overflow,
179
- `position: fixed`/`sticky`, or DOM hierarchy.
180
-
181
- Actual dimensional overflow (`scrollWidth > clientWidth` /
182
- `scrollHeight > clientHeight`) is always reported separately from the
183
- computed `overflow-x`/`overflow-y` CSS declaration; a declared
184
- `overflow: auto` container with content that fits produces
185
- `horizontalOverflow`/`verticalOverflow: false`. Viewport relation
186
- (`above`/`intersecting`/`below`, `intersectsViewport`, `fullyWithinViewport`)
187
- is derived only from bounding geometry plus viewport size, relative to the
188
- browser viewport; a hidden/non-rendered target's viewport relation is
189
- `not-applicable`, never a fabricated geometry claim - hidden and offscreen
190
- remain distinct evidence concepts, and the existing `target-hidden`
191
- diagnostic is unaffected.
192
-
193
- The ordinary, already-existing `pageEvidence`/`targetEvidence`/
194
- `screenshot.png` for a scenario observation always describe this same final
195
- post-action state, never the pre-action state.
196
-
197
- The scenario request participates in `requestId`; the runtime result
198
- (actual scroll distance, clamping, or scroll-owner outcome) never does. The
199
- public entry point is `my-frontend-observer observe --scroll-scenario-file
200
- <json-file>` (see `docs/COMMANDS.md`); the file supplies the scenario value
201
- directly, and its local path is operational input only, exactly like
202
- `--targets-file`'s path - never persisted, never part of request identity.
203
-
204
- ## v0.4 comparison contract (shipped as part of this release)
205
-
206
- **Current status: shipped as part of the published `my-frontend-observer@0.4.0`
207
- package and unchanged through the current `0.7.0` release.** Observation
208
- schema remains `1.2.0`. Comparison is a distinct artifact kind and schema,
209
- never a bump to the observation schema:
210
-
211
- - artifact kind: `my-frontend-observer/comparison`;
212
- - comparison schema: `1.0.0`.
213
-
214
- **Geometry tolerance**: `ComparisonConfig.geometryTolerancePx`, default
215
- `0.5` CSS px, bounded `[0, 10]`. Suppresses insignificant subpixel noise
216
- only - never a design contract, never permission for a change.
217
-
218
- **Layout relationship graph**: `deriveLayoutRelationships(observation,
219
- options?)` derives, per observation, a bounded `LayoutRelationshipGraph`
220
- among configured targets only (≤20 targets, ≤190 unordered pairs):
221
- horizontal order (`left-of`/`right-of`/`horizontally-overlapping`),
222
- vertical order (`above`/`below`/`vertically-overlapping`), area overlap
223
- (`overlaps`/`does-not-overlap`), relative width (`wider-than`/
224
- `narrower-than`/`equal-width-within-tolerance`), geometric fit
225
- (`fits-inside`/`does-not-fit-inside` - geometry-only, deliberately distinct
226
- from DOM containment), vertical sequencing (`follows-vertically`), and one
227
- page-level relationship (`document-width-fits-viewport`/
228
- `document-width-exceeds-viewport`). Every relationship carries explicit
229
- evidence-path provenance back to the source observation. A configured
230
- target lacking usable geometry is listed as honestly unresolved
231
- (`not-found`/`ambiguous`/`unavailable`/`hidden`), never fabricated as a
232
- zero-sized region.
233
-
234
- **Comparability**: evaluated before any rendered difference, using exactly
235
- three states (`comparable`/`comparable-with-warnings`/`incomparable`) with
236
- structured reasons, never a bare boolean. Hard incompatibilities (page URL,
237
- viewport, browser engine, scroll-scenario configuration mismatch) force
238
- `incomparable`; producer-version, browser-version, and target-configuration
239
- differences are warning-only; theme/authenticated-state/application-state
240
- identity are recorded as `unassessed` dimensions the observer does not yet
241
- model - never silently claimed identical. An `incomparable` result still
242
- persists a structurally valid `ComparisonArtifact` with empty rendered
243
- differences, not a fabricated comparison.
244
-
245
- **Difference categories**: `appeared`/`disappeared` (only for a stable
246
- target name configured on both sides, transitioning between a definite
247
- `not-found` and `matched` resolution status - never for a target merely
248
- added/removed from configuration, which is its own separate
249
- `configurationChanges` entry), `moved`/`resized` (tolerance-aware, a target
250
- may be both), `visibility-changed`, `clipping-changed` (reusing the
251
- canonical `deriveTargetClipping` helper, never re-derived), `horizontal-
252
- overflow-changed`/`vertical-overflow-changed` (actual dimensional overflow,
253
- reusing the existing `deriveOverflowEvidence` helper - never inferred from
254
- a CSS declaration alone), `containment-changed` (reusing existing v0.2
255
- `TargetContainment` evidence), `page-size-changed`, `scroll-owner-changed`
256
- (comparing `scrollScenarioEvidence.scrollOwner` only when scenario
257
- *configuration* already matched), `relative-position-changed` (a relation
258
- in the horizontal-order/vertical-order/area-overlap families changed - kept
259
- distinct from plain absolute target movement) and `relationship-changed`
260
- (every other relationship-family transition). Relationship changes are
261
- matched by structural identity (family + subject/related target, or the
262
- page-level key), never by array position.
263
-
264
- **Explicit dependency evidence**: `ComparisonConfig.expectedDependencies`
265
- lets a caller declare an expected relationship between two targets' numeric
266
- properties (`x`/`y`/`width`/`height`) and directions (`increase`/
267
- `decrease`/`change`/`unchanged`), always carrying `source:
268
- "explicit-config"`. The observer never synthesizes a declaration from
269
- observed co-change. Each declaration evaluates independently to exactly one
270
- of `consistent`/`not-observed`/`contradictory-to-declaration`/
271
- `unavailable` - never a causal claim (no `causedBy`/`causalConfidence`/
272
- `causalScore`/`dependencyStrength`) and never a PASS/FAIL/approval verdict.
273
- That distinction (evidence vs. contract verdict) is the boundary between
274
- v0.4 and v0.5+.
275
-
276
- **Comparison identity**: `comparisonRequestId` is a pure, deterministic
277
- function of `{beforeObservationId, afterObservationId, normalized
278
- ComparisonConfig}` - direction-sensitive (`compare(A, B) !==
279
- compare(B, A)`), and never includes an operational filesystem path.
280
- `comparisonId` is fresh per execution (same pattern as `observationId`).
281
-
282
- **Source references**: the comparison artifact retains enough logical
283
- identity to trace back to its authoritative source observations
284
- (`observationId`, `requestId`, `producer`, `observationSchemaVersion`, and
285
- the source `screenshot.path`) without embedding the full
286
- `ObservationArtifact` or copying screenshot bytes. The persisted comparison
287
- directory contains `manifest.json` only.
288
-
289
- The public entry point is `my-frontend-observer compare --before <root>
290
- --after <root> --output <directory> [--config-file <json-file>]` (see
291
- `docs/COMMANDS.md`) - comparison itself never launches a browser.
292
-
293
- ## v0.5 frontend contract and evaluation (shipped as part of this release)
294
-
295
- Downstream of the v0.4 observation/comparison/relationship evidence above,
296
- `src/domain/frontendContracts.ts` freezes the v0.5 contract/change-scope
297
- model, `src/domain/frontendContractIdentity.ts` freezes deterministic
298
- contract/baseline/clause identity, and `src/domain/frontendContractEvaluation.ts`
299
- implements the one canonical pure evaluation engine. Baseline/per-change
300
- contract persistence, evaluation-artifact persistence, explicit baseline
301
- approval, and public CLI exposure are all implemented and shipped (see
302
- "v0.5 contract and evaluation persistence" and "v0.5 public contract/
303
- evaluation commands" below).
304
-
305
- **Contract classes**: a `PersistentBaselineContract` (append/supersession-based
306
- history via an optional `supersedesBaselineId`) and a `PerChangeContract`
307
- (the allowed scope of one requested change). Both share `artifactKind:
308
- "my-frontend-observer/frontend-contract"` and `schemaVersion: "1.0.0"` - an
309
- independent family from the observation (`1.2.0`) and comparison (`1.0.0`)
310
- schemas; the frontend-contract schema constant happens to share the version
311
- string `1.0.0` with comparison's by coincidence only.
312
-
313
- **Four authored categories, one derived classification**: every per-change
314
- clause is authored as exactly one of `requested`, `expected-dependent`,
315
- `protected`, or `preserved`. `unexpected` is a fifth, *derived-only*
316
- classification the evaluator produces for a meaningful rendered difference no
317
- active clause accounts for - it can never be authored as a permission.
318
-
319
- **Bounded contract primitives**: 15 frozen `ContractPrimitive` kinds cover
320
- visibility, clipping, width bounds, non-overlap, relative width, vertical
321
- sequence, geometric fit (explicitly distinct from DOM containment),
322
- document-width-vs-viewport, scroll ownership, initial-viewport position,
323
- relationship-unchanged, and property-unchanged/increases/decreases - a closed
324
- vocabulary, never a generic expression language.
325
-
326
- **Contract tolerance**: `exact` / `absolute-px` / `percent`, independent of
327
- `ComparisonConfig.geometryTolerancePx` (which only suppresses insignificant
328
- comparison noise and is never contract authorization). Percent tolerance's
329
- denominator is the absolute before-value.
330
-
331
- **Required vs. permitted expected-dependent**: `required` clauses must occur
332
- compliantly to pass; `permitted` clauses accept no change or a compliant
333
- change, and fail only on a strictly contradictory change.
334
-
335
- **Evaluation result vocabulary**: each clause resolves to `pass` / `fail` /
336
- `unavailable` (with a required non-empty reason - required evidence gaps and
337
- an `incomparable` source comparison never fabricate a `pass`) / `conflict`
338
- (with at least two `conflictingClauseIds` - covers both an unresolved
339
- baseline/per-change contradiction and an unknown `supersedesBaselineClauseIds`
340
- reference). The overall verdict is `PASS` only when every clause result is
341
- `pass` and no unexpected change remains; otherwise `FAIL` - there is no
342
- partial-pass scoring.
343
-
344
- **Explicit supersession, never inferred**: a per-change clause may list
345
- `supersedesBaselineClauseIds` to remove specific baseline clauses from active
346
- evaluation. Two clauses that structurally contradict each other on the same
347
- (target, property) without explicit supersession produce a `conflict`, never
348
- a silent preference for one side.
349
-
350
- **Reuses existing v0.4 evidence directly**: the evaluator consumes an
351
- already-computed `ComparisonArtifact` (`differences`, `relationshipChanges`,
352
- `relationshipsBefore`/`relationshipsAfter`, `comparability`) and the source
353
- `ObservationArtifact` pair - it never re-launches a browser, re-resolves a
354
- target, or reimplements clipping/relationship/scroll-owner derivation.
355
- Unexpected-change derivation reads `ComparisonArtifact.differences` only
356
- (which already includes one difference per relationship change), so a single
357
- logical transition is never double-counted.
358
-
359
- ## v0.5 contract and evaluation persistence (shipped as part of this release)
360
-
361
- Persistence consumes the frozen v0.5 domain above; it never redefines it.
362
- `src/artifacts/frontendContractArtifactWriter.ts`/`frontendContractArtifactReader.ts`
363
- persist and read both `PersistentBaselineContract` and `PerChangeContract`
364
- symmetrically (both already share `CONTRACT_ARTIFACT_KIND`/`CONTRACT_SCHEMA_VERSION`,
365
- so one writer/reader pair serves both contract classes) as
366
- `<outputLocation>/<baselineId|contractId>/manifest.json`, following the same
367
- atomic-write discipline as `artifacts/artifactWriter.ts`/`artifacts/comparisonArtifactWriter.ts`
368
- (sibling temporary directory, then one atomic rename; an existing directory at
369
- the final identity is a genuine collision and is rejected, never overwritten -
370
- prior baseline history is never rewritten). `src/artifacts/comparisonArtifactReader.ts`
371
- is a new Batch 3 addition (no comparison reader existed before) mirroring
372
- `artifacts/artifactReader.ts`'s discipline exactly, changing no comparison
373
- semantics and keeping comparison schema `1.0.0`.
374
-
375
- **Evaluation artifact envelope**: Batch 1 froze the evaluation-result
376
- vocabulary (`ClauseEvaluationResult`, `OverallVerdict`) but not a persistable
377
- envelope, so `src/domain/frontendContractEvaluationArtifact.ts` adds exactly
378
- that - `artifactKind: "my-frontend-observer/frontend-contract-evaluation"`,
379
- `schemaVersion: "1.0.0"` (its own independent family, distinct from
380
- observation/comparison/frontend-contract), an `evaluationId`/`evaluationRequestId`
381
- pair, bounded `before`/`after` source-observation references, and
382
- `comparisonId`/`comparisonRequestId` plus `contracts: {baselineId,
383
- contractId}` references - never an embedded `ObservationArtifact` or copied
384
- screenshot. It reuses `ClauseEvaluationResult`/`OverallVerdict`/
385
- `UnexpectedChangeResult` unchanged and contains no evaluation logic itself.
386
- `evaluationRequestId` is a deterministic function of `{baselineId,
387
- contractId, beforeObservationId, afterObservationId, comparisonRequestId}`
388
- (`frontendContractIdentity.ts#buildFrontendContractEvaluationRequestIdentity` -
389
- deliberately `comparisonRequestId`, not the fresh-per-execution
390
- `comparisonId`, so semantically identical evaluations share an identity);
391
- `evaluationId` reuses the existing generic `buildFrontendContractInstanceIdentity`
392
- unchanged. `src/artifacts/frontendContractEvaluationArtifactWriter.ts`/
393
- `frontendContractEvaluationArtifactReader.ts` persist/read it with the same
394
- atomic-write discipline as above.
395
-
396
- **Application seam**: `src/application/frontendContractEvaluationService.ts#evaluateAndPersist`
397
- calls the existing pure `evaluateFrontendContract` exactly once and - only
398
- for a structurally constructible result, whether the verdict is `PASS` or
399
- `FAIL` - persists exactly one evaluation artifact; an `{ok: false}` evaluator
400
- result (evidence could not be constructed into an evaluation at all) is never
401
- persisted as a fabricated artifact. `evaluateAndPersistFromArtifactRoots` is
402
- the future-CLI-facing wrapper: it reads two observations through the existing
403
- `readObservationArtifact` (never a second observation reader), the
404
- comparison and the two contracts through the readers above, then delegates
405
- to `evaluateAndPersist` exactly once.
406
-
407
- ## v0.5 public contract/evaluation commands (shipped as part of this release)
408
-
409
- Three public commands expose the persistence/evaluation contract above (see
410
- `docs/COMMANDS.md` for exact flags/output/exit behavior, not duplicated
411
- here):
412
-
413
- - `approve-baseline` → `frontendContractPersistenceService.ts#approveAndPersistBaseline`
414
- → validates a `PersistentBaselineContract` and its `sourceObservation`
415
- coherence against a supplied observation artifact → persists via
416
- `frontendContractArtifactWriter.ts`. The only baseline-approval act in the
417
- observer.
418
- - `save-change-contract` → `frontendContractPersistenceService.ts#persistPerChangeContract`
419
- → validates a `PerChangeContract` (rejecting a baseline contract, an
420
- authored `unexpected` category, or any other structural violation) →
421
- persists via the same writer. Persistence only, never approval.
422
- - `evaluate-contract` → `frontendContractEvaluationService.ts#evaluateAndPersistFromArtifactRoots`
423
- → `evaluateFrontendContract` exactly once → `frontendContractEvaluationArtifactWriter.ts`
424
- exactly once. `--enforce` affects only the process exit status for an
425
- already-persisted `FAIL` verdict.
426
-
427
- No command infers baseline approval or supersession automatically - not
428
- `compare`, not a `PASS` evaluation, not any artifact writer.
429
-
430
- This full command sequence is proven against real Chromium observations (not
431
- hand-constructed artifacts) - see "v0.5 real-browser workflow proof" below.
432
-
433
- ## v0.5 real-browser workflow proof (shipped as part of this release)
434
-
435
- `tests/browser/cliFrontendContracts.test.ts` and
436
- `scripts/dev/builtCliFrontendContractsBrowserSmoke.mjs` drive the complete
437
- `observe` → `approve-baseline` → `save-change-contract` → `observe` →
438
- `compare` → `evaluate-contract` sequence against a real disposable local HTTP
439
- fixture and real Chromium, proving two scenarios:
440
-
441
- - a fully successful contract change - a real observed navigation-width
442
- decrease and workspace-width increase, both satisfying their authored
443
- `requested`/`expected-dependent` clauses, an unchanged `protected` rail
444
- width, and an unclipped `preserved` navigation - overall `PASS`;
445
- - the "milestone signature" failure - the same locally successful requested
446
- change (navigation shrinks, workspace expands, both still `pass`)
447
- co-occurring with a genuine `protected` right-rail width regression (a real
448
- `resized` comparison difference) and a genuine `preserved` navigation
449
- clipping regression (a real `clipping-changed` difference, `not-clipped` →
450
- `clipped`) - overall `FAIL`.
451
-
452
- Both scenarios confirm: `--enforce` changes only the process exit status
453
- (`0` without it, nonzero with it) for the identical persisted
454
- `evaluationRequestId`/`clauseResults`; every source observation and
455
- comparison artifact is byte-identical before and after evaluation; the
456
- evaluation directory contains `manifest.json` only (no copied screenshot);
457
- and no operational filesystem path is ever serialized into a persisted
458
- manifest. This is real-browser evidence layered on top of the CLI-level
459
- proof in `tests/unit/cliFrontendContracts.test.ts` and the Chromium-free
460
- `scripts/dev/builtCliFrontendContractsSmoke.mjs` - it does not replace them.
461
-
462
- ## v0.6 bounded agent context and correlation contract (released as `0.6.0`)
463
-
464
- **Current status: released as package version `0.6.0`, tag `v0.6.0`, from
465
- the canonical `canonicalization/v0.6` lineage.** Bounded-agent-context is a new, independent
466
- artifact-kind family, schema `1.0.0` (`BOUNDED_AGENT_CONTEXT_ARTIFACT_KIND =
467
- "my-frontend-observer/bounded-agent-context"`) - never a bump to
468
- observation/comparison/frontend-contract/evaluation schemas, which remain
469
- `1.2.0`/`1.0.0`/`1.0.0`/`1.0.0` respectively. Unlike those families, there is
470
- **no disk artifact writer/reader** for bounded-agent-context: it is a pure
471
- programmatic contract and derivation layer, exported from `src/index.ts`
472
- only.
473
-
474
- **Bounded runtime projection**
475
- (`src/domain/boundedAgentContextProjection.ts#projectBoundedAgentContext`)
476
- produces a `BoundedRuntimeTargetProjection` from already-captured v0.1-v0.5
477
- evidence, containing: page/viewport identity; stable target identities;
478
- important geometry and runtime behavior; layout/behavior relationships;
479
- before/after differences; contract clause results; requested/expected-
480
- dependent/protected/preserved scope - reusing `src/domain/
481
- frontendContracts.ts`'s existing clause types verbatim, never a
482
- reimplementation; diagnostics; screenshot/artifact references; provenance;
483
- and explicit `OmissionRecord`/`TruncationRecord` metadata with bounded
484
- aggregate-cap summarization once a limit is reached.
485
-
486
- **Adequacy**: every projection carries an `Adequacy` value
487
- (`adequate`/`partial`/`inadequate`) plus a structured, closed
488
- `ADEQUACY_REASON_CODES` vocabulary - evidence existing is not itself
489
- adequacy; a required omission or an `incomparable`/unavailable upstream
490
- source is reflected honestly rather than silently reported as sufficient.
491
-
492
- **Runtime/static correlation**
493
- (`src/domain/boundedAgentContextCorrelation.ts#deriveRuntimeStaticCorrelations`/
494
- `attachRuntimeStaticCorrelations`) evaluates each stable runtime target
495
- against caller-supplied candidate static-evidence records into exactly one of
496
- three outcomes: `correlated`, `ambiguous` (multiple competing candidates,
497
- all preserved and visible - never silently resolved to one), or
498
- `unavailable` (no supported candidate). The module accepts only plain,
499
- already-retrieved candidate records and has no dependency on
500
- `@dailephd/my-dev-kit` - the audit preceding implementation found no generic
501
- static-side retrieval capability actually missing (see `docs/ROADMAP.md` v0.6
502
- "Dependency direction"). A runtime target identity is carried through
503
- verbatim; correlation never produces a `sourceOwner`/`causedBy`-shaped field,
504
- so a stable runtime identity is never silently reported as source ownership.
505
-
506
- **Identity**: `src/domain/boundedAgentContextIdentity.ts#buildBoundedAgentContextRequestIdentity`/
507
- `buildBoundedAgentContextInstanceIdentity` follow the same
508
- canonicalize+sha256(+opaque-nonce) pattern as `comparisonIdentity.ts`/
509
- `frontendContractIdentity.ts`: a deterministic logical identity distinct from
510
- a fresh per-execution instance identity.
511
-
512
- **Export/public boundary**: `src/index.ts` exports the complete
513
- bounded-agent-context/correlation type and function surface as a
514
- programmatic library contract. There is no CLI command (`observe`/`compare`/
515
- `approve-baseline`/`save-change-contract`/`evaluate-contract` remain the only
516
- public commands) and no orchestrator/lab code in this repository - bounded
517
- runtime-evidence consumption by `my-dev-kit-orchestrator` and exact
518
- readers/fixtures/evaluation in `my-dev-kit-lab` are separate sibling-
519
- repository deliverables outside `my-frontend-observer`'s public surface.
520
-
521
- **Compatibility evidence**: cross-repository neutral verification (observer
522
- `514bf3bb513764815a0a5b9e508d5836aa7d7fd8`, orchestrator `9473e4c`, lab
523
- `271e72c`) passed with 6/6 requirement coverage and no known product
524
- blockers; on the canonical worktree, `npm run typecheck`, `npm run lint`,
525
- `npm test` (627 tests), `npm run test:browser` (120 tests), `npm run
526
- test:security`, `npm run build`, and `npm run check:docs` all pass.
527
-
528
- ## v0.7 external visual-reference contract direction (preserved through implemented, unreleased v0.10)
529
-
530
- External visual-reference support is released as package version `0.7.0`
531
- (see "v0.7 Prompt 1" through "v0.7 Prompt 8" below for the exact contract).
532
- The exact public type names, artifact kinds, schema versions, persistence
533
- layout, and command/programmatic entry points were designed during v0.7
534
- implementation from current repository precedent, following the constraints
535
- below. v0.8 (released as package version `0.8.0` - see
536
- `docs/CURRENT_STATE.md`) has preserved them. v0.9 is released and preserves
537
- them. The implemented, unreleased v0.10 workflow preserves them too.
538
-
539
- **Distinct evidence domain**: an external reference is desired-design evidence,
540
- not an `ObservationArtifact` and not the "before" side of a v0.4
541
- `ComparisonArtifact`. Reference design vs candidate is distinct from both
542
- before vs after comparison and frontend-contract evaluation. The implementation
543
- must not fake this distinction by wrapping a raster image in an observation
544
- shape.
545
-
546
- **Reference identity and provenance**: a future reference contract must preserve
547
- a deterministic logical reference identity/version where appropriate, source
548
- image reference plus dimensions/format, provenance, bounded region definitions,
549
- applicable viewport/theme/application-state identity, authored design intent,
550
- relationship/style evidence where supported, limits/diagnostics, and approval/
551
- supersession history. Operational filesystem paths must not become semantic
552
- identity. A raw imported image never silently becomes an approved active
553
- reference.
554
-
555
- **Reference regions and runtime targets stay distinct**: a reference region
556
- must have its own identity and coordinate semantics. Reference-region to runtime-
557
- target association must be explicit and capable of representing ambiguity or
558
- unavailability. Runtime target identity and reference identity must never
559
- silently become static source ownership; static association still goes through
560
- the v0.6 runtime/static correlation boundary.
561
-
562
- **Applicability before fidelity**: viewport, theme, application state, and
563
- other selected compatibility dimensions must be evaluated before ordinary
564
- reference/candidate differences are produced. If the reference and candidate
565
- represent different intended states, the result must be explicitly incompatible
566
- or incomparable rather than filled with fabricated visual failures. Planning
567
- should reuse or extend the canonical v0.4 comparability conventions where they
568
- mean the same thing rather than invent an unrelated reference-only state model.
569
-
570
- **Canonical contract semantics remain authoritative**: executable reference-
571
- derived requirements must map into the existing v0.5 authored categories
572
- `requested`, `expected-dependent`, `protected`, or `preserved`. The derived-only
573
- `unexpected` classification remains derived-only. Informational or unassessed
574
- reference evidence may stay outside executable contract evaluation until
575
- explicitly promoted. A second reference-only PASS/FAIL taxonomy is forbidden.
576
-
577
- **Tolerance separation**: reference-fidelity tolerances are not automatically
578
- the same as v0.4 `ComparisonConfig.geometryTolerancePx` or v0.5 contract
579
- tolerances. Planning must define property-specific semantics for reference
580
- geometry, spacing, selected style evidence, text/font rendering differences,
581
- asset-sensitive regions, and optional image similarity. One global pixel-perfect
582
- threshold is not an acceptable contract.
583
-
584
- **Structured evidence first**: geometry, relationships, authored requirements,
585
- applicability, provenance, and selected bounded style/asset evidence remain
586
- inspectable primary evidence. Screenshot-region or image-similarity evidence may
587
- supplement them where reliable, but pixel similarity alone must not determine
588
- success and must never override active baseline/per-change contracts.
589
-
590
- **Bounded correction evidence**: future reference/candidate results must support
591
- a bounded projection suitable for coding-agent correction, such as reference
592
- measurement, candidate measurement, delta, failed relationship/style condition,
593
- relevant reference/runtime identities, provenance, and active protected/
594
- preserved constraints. Heavy reference image bytes should be referenced, not
595
- copied into every downstream context packet.
596
-
597
- **Approval and supersession**: reference import, reference approval, baseline
598
- approval, reference supersession, and baseline supersession are separate acts.
599
- A reference-fidelity `PASS`, a frontend-contract `PASS`, or a successful
600
- before/after comparison must not silently approve or replace any reference or
601
- baseline.
602
-
603
- The v0.8 viewer, released as package version `0.8.0`, consumes this v0.7
604
- reference/evaluation contract exactly as required - it creates no UI-only
605
- reference model (see `docs/ARCHITECTURE.md` "v0.8 Batch 5"/"v0.8 Batch 6"
606
- and `docs/reports/v0.8-reference-candidate-inspection-batch5.md`). v0.9
607
- annotations (released in `0.9.0`) originate from runtime
608
- screenshots or external references, preserve which source identity and
609
- coordinate system they belong to, and feed the same canonical contract and
610
- reference semantics - see "v0.9 visual annotation contract" below. v0.10
611
- combines both entry modes into the full correction/approval workflow.
612
-
613
- ## v0.9 visual annotation contract (released in 0.9.0)
614
-
615
- v0.9 is released as `@dailephd/my-frontend-observer@0.9.0`.
616
-
617
- **Artifact**: `VisualAnnotationArtifact`, artifact kind
618
- `my-frontend-observer/visual-annotation`, schema version `1.0.0`. It stores one
619
- exact canonical source (a runtime observation, or an imported or approved
620
- external reference), the source coordinate space (runtime CSS pixels or
621
- reference-image pixels), and bounded structured items. Each item has a stable
622
- `annotationItemId`, one mark (point, rectangle, line, arrow, or note), an
623
- optional explicit association, and an interpretation. A revision sets
624
- `supersedesAnnotationId` and never rewrites its parent. The overlay SVG is
625
- derived from the artifact and verified before it is served.
626
-
627
- **An annotation is not a contract.** Saving an annotation never creates a
628
- contract clause, a reference requirement, or a PASS/FAIL rule. Marks and
629
- visible pixels are evidence. Overlap between a mark and a target or region is
630
- not ownership and never creates an association.
631
-
632
- **Interpretation states**: `uninterpreted`, `candidate`, and `confirmed`.
633
- Confirmation is explicit and records `confirmedAt`. Editing the mark,
634
- association, or intent of a confirmed item withdraws the confirmation.
635
-
636
- **Runtime intent to contract**:
637
-
638
- - Only selected, confirmed, supported runtime intent is promoted. Promotion
639
- creates one normal canonical `PerChangeContract` through the existing
640
- contract persistence service.
641
- - Supported mappings use the existing `ContractPrimitive` vocabulary only.
642
- `move` maps to `property-increases`/`property-decreases` on `x` or `y`.
643
- `resize` maps to `property-increases`/`property-decreases` on `width` or
644
- `height`. `preserve` maps to `property-unchanged-within-tolerance` for a
645
- target property, or to `relationship-unchanged` for an explicitly associated
646
- canonical relationship.
647
- - Categories are the canonical `requested`, `expected-dependent` (with a
648
- required `required` or `permitted` mode), `protected`, and `preserved`.
649
- `unexpected` is never authored; it stays evaluator-derived.
650
- - `remove` can be confirmed and saved, but it is not promotable in v0.9. The
651
- contract vocabulary has no target-absent primitive, and no approximate
652
- clause is fabricated.
653
- - `inspect` intent and notes are informational and never promoted.
654
- - Each clause's `supportingEvidence` records the annotation source and item
655
- paths. Promotion never activates the contract unless explicitly requested,
656
- and it never approves a baseline.
657
-
658
- **Reference intent to a new reference revision**:
659
-
660
- - Only selected, confirmed `reference-region` (`create` or `refine`) and
661
- `reference-requirement` items are materialized. `inspect` and
662
- `asset-sensitive` intent stay informational.
663
- - The result is a new imported `ExternalReferenceArtifact`, created through the
664
- existing canonical import service. It supersedes the selected source
665
- reference (the approved reference id when the source is approved) and has
666
- lifecycle `imported`. It is never approved automatically.
667
- - Source regions keep their order. A refine replaces the rectangle of an
668
- existing source region and keeps its id. Creates are appended in selection
669
- order. Duplicate creates, duplicate refines, and create-then-refine in one
670
- request are rejected.
671
- - Source requirements stay first with identical recomputed ids. Selected
672
- requirements are appended in selection order and validated against the final
673
- regions. A selected relationship requirement must still hold for the final
674
- geometry. Measurement requirements store only the subject and tolerance.
675
- - Applicability, label, and the exact source image bytes are preserved. The
676
- source reference is never modified, and project reference acceptance is not
677
- changed.
678
-
679
- Existing contract, reference relationship, adequacy, and fidelity evaluators
680
- remain the only source of verdicts. v0.9 adds no annotation evaluator.
681
-
682
- ## Approved v0.1 design inputs
683
-
684
- The historical greenfield scaffold plan recorded these v0.1 design decisions:
685
-
686
- - artifact kind `my-frontend-observer/observation`;
687
- - schema version `1.0.0`, independent of package version;
688
- - one portable directory containing `manifest.json`, `evidence.json`, and
689
- `screenshot.png`;
690
- - evidence states `available`, `unavailable`, `not-applicable`, and `partial`;
691
- - evidence sources `browser`, `computed-browser`, and `derived`;
692
- - bounded explicitly requested targets, provenance, diagnostics, completion
693
- state, limits, and relative artifact references.
694
-
695
- These were planning inputs only at the time they were recorded. As shown in
696
- "Current contracts" above, the implemented contract matches them except for
697
- the file layout: there is no separate `evidence.json` - page/target evidence
698
- is embedded directly inside `manifest.json`.
699
-
700
- Comparison and relationship contracts belong to v0.4, and canonical
701
- change-scope contracts belong to v0.5 - see "v0.5 frontend contract and
702
- evaluation" above for the full shipped contract model, identity, evaluation
703
- engine, persistence, baseline approval, and CLI exposure. Bounded
704
- agent-context and runtime/static correlation contracts are v0.6 - see "v0.6
705
- bounded agent context and correlation contract" above for the full released
706
- model. The text/config-driven coding-agent review plus non-graphical external
707
- visual-reference foundation is v0.7 - see "v0.7 Prompt 1 external-reference
708
- artifact contract" below for the foundation layer implemented so far. Viewer
709
- consumption of that reference model is released in v0.8 (see "v0.7 external
710
- visual-reference contract direction" above); dual-context annotation follows
711
- in v0.9; both visual entry modes converge with the existing workflow in
712
- v0.10.
713
-
714
- ## v0.7 Prompt 1 external-reference artifact contract
715
-
716
- Released as `0.7.0`. This is the foundation layer only: identity,
717
- provenance, bounded image metadata, and a two-state lifecycle for one
718
- externally supplied design-reference image. It implements no region,
719
- geometry, relationship, requirement, tolerance, binding, or fidelity-
720
- evaluation contract - those belong to later v0.7 prompts.
721
-
722
- An external reference is a distinct evidence root, not a variant of
723
- `ObservationArtifact`: it never reuses `ARTIFACT_KIND`/`SCHEMA_VERSION`
724
- (observation), `COMPARISON_ARTIFACT_KIND`, or `CONTRACT_ARTIFACT_KIND`, and
725
- those existing types gain no new field from this contract.
726
-
727
- ```ts
728
- const EXTERNAL_REFERENCE_ARTIFACT_KIND = 'my-frontend-observer/external-reference';
729
- const EXTERNAL_REFERENCE_SCHEMA_VERSION = '1.0.0'; // independent of package.json version and every other family's schema version
730
-
731
- type ExternalReferenceImageFormat = 'png' | 'jpeg' | 'webp';
732
-
733
- interface ExternalReferenceImageReference {
734
- path: string; // bare relative filename within the artifact's own directory
735
- format: ExternalReferenceImageFormat;
736
- width: number;
737
- height: number;
738
- byteLength: number;
739
- sha256: string; // identity-bearing content hash of the raw image bytes
740
- }
741
-
742
- // Points back to the imported artifact that owns the image, without copying its bytes - mirrors ComparisonSourceObservationReference.
743
- interface ExternalReferenceSourceReference {
744
- referenceId: string;
745
- referenceRequestId: string;
746
- producer: { name: 'my-frontend-observer'; version: string };
747
- schemaVersion: '1.0.0';
748
- image: ExternalReferenceImageReference;
749
- }
750
-
751
- // Exactly two persisted states - no literal 'superseded' variant (see below).
752
- type ExternalReferenceLifecycleState = { state: 'imported' } | { state: 'approved'; approvedAt: string };
753
-
754
- interface ExternalReferenceArtifactBase {
755
- artifactKind: 'my-frontend-observer/external-reference';
756
- schemaVersion: '1.0.0';
757
- referenceRequestId: string; // deterministic logical identity - shared by an imported artifact and every artifact produced by approving it
758
- referenceId: string; // fresh per-persisted-instance identity
759
- producer: { name: 'my-frontend-observer'; version: string };
760
- provenance: { importedAt: string; label?: string };
761
- supersedesReferenceId?: string; // explicit, forward-only supersession of a prior reference's referenceId
762
- diagnostics: Diagnostic[];
763
- completion: CompletionState;
764
- }
765
-
766
- // lifecycle.state === 'imported': owns the image.
767
- interface ImportedExternalReferenceArtifact extends ExternalReferenceArtifactBase {
768
- lifecycle: { state: 'imported' };
769
- image: ExternalReferenceImageReference;
770
- }
771
-
772
- // lifecycle.state === 'approved': references, never copies, the imported artifact's image.
773
- interface ApprovedExternalReferenceArtifact extends ExternalReferenceArtifactBase {
774
- lifecycle: { state: 'approved'; approvedAt: string };
775
- sourceReference: ExternalReferenceSourceReference;
776
- }
777
-
778
- type ExternalReferenceArtifact = ImportedExternalReferenceArtifact | ApprovedExternalReferenceArtifact;
779
- ```
780
-
781
- Key rules:
782
-
783
- - `referenceRequestId` is a pure function of `{imageSha256, format, width,
784
- height, supersedesReferenceId}` only - never a filesystem path, output
785
- location, label, or timestamp. Byte-identical image content imported from a
786
- different operational root produces the same `referenceRequestId`;
787
- changing any of those fields changes it.
788
- - `referenceId` is fresh (nonce-based) on every persisted write, including
789
- every approval of an already-imported reference.
790
- - Importing an image never approves it (`lifecycle.state` is always
791
- `'imported'` immediately after import, regardless of a supplied label or
792
- supersession target). Approval is a single explicit act
793
- (`approveExternalReference`, mirroring `approveAndPersistBaseline`) that
794
- refuses anything not currently in the `'imported'` state.
795
- - Approving persists a *new* artifact instance (same `referenceRequestId`,
796
- fresh `referenceId`) carrying a `sourceReference` back to the imported
797
- artifact - it never mutates the imported artifact's own manifest, and never
798
- copies the image bytes a second time.
799
- - Supersession is represented only as a forward pointer
800
- (`supersedesReferenceId` on the newer artifact); there is deliberately no
801
- literal `'superseded'` lifecycle state, so an existing persisted artifact's
802
- own manifest is never rewritten - immutability holds unconditionally rather
803
- than depending on careful mutation discipline.
804
- - Supported formats are frozen to exactly `png`/`jpeg`/`webp`, detected from
805
- header/magic bytes only (never a caller-declared file extension), bounded
806
- to `EXTERNAL_REFERENCE_MAX_IMAGE_BYTES` (20,000,000 bytes) and
807
- `[EXTERNAL_REFERENCE_MIN_DIMENSION_PX, EXTERNAL_REFERENCE_MAX_DIMENSION_PX]`
808
- (`[1, 8192]`) pixels per side. No OCR, no raster decode, no computer
809
- vision, no automatic region detection.
810
-
811
- Persisted as `<outputLocation>/<referenceId>/manifest.json` (+
812
- `reference.<ext>` for an `'imported'` artifact only), via the same atomic
813
- temp-dir-then-rename discipline as every other artifact family
814
- (`src/artifacts/externalReferenceArtifactWriter.ts` /
815
- `externalReferenceArtifactReader.ts`). CLI: `import-reference <image-file>
816
- --output <dir> [--label] [--supersedes <root>]` and `approve-reference
817
- --reference <root> --output <dir> [--supersedes <root>]`.
818
-
819
- ## v0.7 Prompt 2 explicit reference regions and relationships
820
-
821
- Released as `0.7.0`. Additive extension of the Prompt 1 contract above:
822
- one new, optional `regions?: ReferenceRegion[]` field on
823
- `ExternalReferenceArtifact` (both lifecycle variants), plus a pure,
824
- non-persisted relationship-derivation capability. No schema version bump -
825
- `EXTERNAL_REFERENCE_SCHEMA_VERSION` remains `'1.0.0'`, because the field is
826
- genuinely optional/additive and every Prompt 1 artifact (which predates this
827
- field entirely) remains valid without it.
828
-
829
- ```ts
830
- // domain/externalReferenceRegions.ts
831
- interface ReferenceRegionRectangle { x: number; y: number; width: number; height: number; }
832
- interface ReferenceRegion { id: string; rectangle: ReferenceRegionRectangle; }
833
-
834
- // Pure derived geometry - never persisted, always recomputed, so it can never drift from the rectangle above.
835
- interface ReferenceRegionGeometry {
836
- x: number; y: number; width: number; height: number;
837
- right: number; bottom: number; centerX: number; centerY: number;
838
- }
839
-
840
- const REFERENCE_REGION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/; // same convention as request/request.ts's target-name pattern
841
- const MAX_REFERENCE_REGIONS = 20; // same bound value as request/request.ts's MAX_TARGETS - independently owned, coincidentally equal
842
- ```
843
-
844
- Region coordinate semantics: origin at the reference image's top-left
845
- corner, x increasing rightward, y increasing downward, unit is
846
- reference-image pixels (explicitly not CSS pixels - a static image has no
847
- CSS box model), coordinates may be fractional. A region's rectangle must lie
848
- entirely within its owning image's own already-validated
849
- width/height - out-of-bounds geometry is rejected outright, never clamped.
850
-
851
- Key rules:
852
-
853
- - Only `{x, y, width, height}` is canonical/authored. `right`, `bottom`,
854
- `centerX`, `centerY` are pure calculations over it
855
- (`deriveReferenceRegionGeometry`) - never a second, potentially-drifting
856
- stored copy of the same fact.
857
- - Region content is identity-bearing:
858
- `buildExternalReferenceRequestIdentity` gained an additive, optional
859
- trailing `regions` parameter. Omitting it entirely (every Prompt 1 call
860
- site, and any Prompt 2 call that legitimately has no regions) produces the
861
- byte-identical hash Prompt 1 already produced - the parameter is left out
862
- of the hashed view rather than defaulted to `null`, unlike
863
- `supersedesReferenceId`. Authored region order participates in identity
864
- (arrays are never reordered by the shared `canonicalize()`), mirroring
865
- `domain/identity.ts`'s treatment of configured targets.
866
- - Region IDs are unique case-insensitively within one artifact (mirroring
867
- `request/request.ts`'s target-name dedup convention exactly).
868
- - `import-reference` gained an optional `--regions-file <json-file>` of the
869
- form `{ "regions": [...] }` (same object-root-wrapper convention as
870
- `--targets-file`); a legacy invocation without it behaves exactly as in
871
- Prompt 1. `approve-reference` carries an imported artifact's `regions`
872
- forward verbatim (never re-validated, never re-derived, never dropped) -
873
- approval never adds, removes, or edits regions.
874
- - One new diagnostic code, `invalid-reference-region` (error), covers every
875
- region-validation failure (missing/duplicate/malformed id,
876
- non-finite/negative/zero geometry, out-of-image-bounds, over the bounded
877
- region count) - deliberately not split into several codes, per the
878
- "don't proliferate diagnostics" convention.
879
-
880
- Reference-region relationships (`domain/externalReferenceRegionRelationships.ts`)
881
- reuse the exact same pure, tolerance-aware geometry predicates that
882
- `domain/relationships.ts#deriveLayoutRelationships` uses for runtime targets
883
- (`horizontalOrderOf`/`verticalOrderOf`/`areaOverlapOf`/`relativeWidthOf`/
884
- `geometricFitOf`/`verticalSequenceOf`, now exported additively from that
885
- module with unchanged formulas) and the same `PairwiseRelationshipKind`
886
- vocabulary and `EvidenceReference` type - never a duplicated or
887
- reinterpreted copy. Only the six geometry-only families apply (horizontal
888
- order, vertical order, area overlap, relative width, geometric fit, vertical
889
- sequencing); DOM containment, scroll ownership, runtime visibility, and
890
- page-width-vs-viewport are runtime/browser concepts with no reference-image
891
- equivalent and are not reused. `fits-inside`/`does-not-fit-inside` is
892
- geometry-only fit - it never claims DOM containment, which an external image
893
- cannot expose.
894
-
895
- ```ts
896
- interface ReferenceRegionRelationship {
897
- kind: PairwiseRelationshipKind;
898
- subjectRegion: string; // deliberately distinct field name from PairwiseLayoutRelationship's subjectTarget
899
- relatedRegion: string;
900
- evidence: EvidenceReference[]; // e.g. { path: 'regions.header.rectangle' } - never a targetEvidence/browser path
901
- }
902
- ```
903
-
904
- Relationships are **not persisted** on the artifact - `deriveReferenceRegionRelationships(referenceRequestId, regions, options)`
905
- is a pure, deterministic, synchronous function any caller (a future prompt,
906
- a test) calls on demand against an artifact's own `regions` field, avoiding
907
- any possibility of a persisted relationship graph drifting from the region
908
- data it was derived from. Bounded at `MAX_REFERENCE_REGIONS` regions ->
909
- `MAX_REFERENCE_REGION_PAIRS` pairs `x` 6 families =
910
- `MAX_REFERENCE_REGION_RELATIONSHIP_RECORDS` records maximum - the same
911
- bounding shape as `relationships.ts`'s `MAX_PAIRWISE_RELATIONSHIP_RECORDS`.
912
- This is a maximum capacity, never a required minimum region count - there is
913
- no contract requiring any specific number of authored regions.
914
-
915
- A reference relationship is a fact about the reference image's geometry
916
- only. It is not a design requirement, not a pass/fail verdict, and does not
917
- claim a runtime target or source owner exists - see
918
- `docs/WORKFLOWS.md` "Current external-reference foundation workflow" for
919
- where those later concepts (Prompt 3+) will attach.
920
-
921
- ## v0.7 Prompt 3 selected design requirements, tolerance semantics, and reference-evidence adequacy
922
-
923
- Released as `0.7.0`. Additive extension of the Prompt 1/2 contracts
924
- above: one new, optional `requirements?: ExternalReferenceRequirement[]`
925
- field on `ExternalReferenceArtifact` (both lifecycle variants). No schema
926
- version bump - same reasoning as Prompt 2's `regions` field.
927
-
928
- **Central distinction**: a region's geometry is REFERENCE EVIDENCE -
929
- everything visibly/measurably present in the image. A requirement is
930
- SELECTED DESIGN INTENT - only what the user/configuration explicitly chose
931
- as mattering for later candidate evaluation. Nothing in this repository ever
932
- turns a region property or a derived relationship into a requirement
933
- automatically.
934
-
935
- ```ts
936
- // domain/externalReferenceRequirements.ts
937
-
938
- // Reused directly from domain/frontendContracts.ts - not reinvented as a
939
- // "reference-only" taxonomy; that type carries no runtime-only coupling.
940
- // 'unexpected' remains impossible to author (not a member of this union).
941
- type AuthoredChangeScopeCategory = 'requested' | 'expected-dependent' | 'protected' | 'preserved';
942
- type ExpectedDependentMode = 'required' | 'permitted'; // required only (and exactly) when category === 'expected-dependent'
943
-
944
- type ReferenceRequirementRegionProperty = 'x' | 'y' | 'width' | 'height' | 'right' | 'bottom' | 'centerX' | 'centerY'; // exactly ReferenceRegionGeometry's own fields
945
- type ReferenceRequirementMeasurement = 'vertical-gap' | 'horizontal-gap' | 'center-x-delta' | 'center-y-delta' | 'left-edge-delta' | 'right-edge-delta';
946
-
947
- type ReferenceRequirementSubject =
948
- | { kind: 'region-property'; region: string; property: ReferenceRequirementRegionProperty }
949
- | { kind: 'region-relationship'; subjectRegion: string; relatedRegion: string; relationship: PairwiseRelationshipKind } // reused from relationships.ts - geometry-only families only
950
- | { kind: 'region-measurement'; subjectRegion: string; relatedRegion: string; measurement: ReferenceRequirementMeasurement };
951
-
952
- // Deliberately NOT a reuse of frontendContracts.ts's ContractTolerance: that
953
- // type's 'absolute-px' is implicitly runtime/CSS pixels. Reference-image
954
- // pixels are a distinct, explicitly-labeled unit - nothing here assumes
955
- // 1 reference pixel = 1 CSS pixel (Prompt 6 will need an explicit mapping).
956
- type ReferenceRequirementTolerance = { kind: 'exact' } | { kind: 'absolute-reference-px'; amount: number } | { kind: 'percent'; amount: number };
957
-
958
- interface ExternalReferenceRequirement {
959
- requirementId: string; // system-computed from {subject, category, expectedDependentMode, tolerance} only - never authored
960
- category: AuthoredChangeScopeCategory;
961
- expectedDependentMode?: ExpectedDependentMode;
962
- subject: ReferenceRequirementSubject;
963
- tolerance?: ReferenceRequirementTolerance; // required for region-property/region-measurement; must be absent for region-relationship
964
- }
965
- ```
966
-
967
- Key rules:
968
-
969
- - Requirement identity (`requirementId`) is always system-computed
970
- (`buildReferenceRequirementIdentity`, mirroring
971
- `frontendContractIdentity.ts#buildClauseIdentity`'s exact shape) - the raw
972
- authored input (`RawReferenceRequirement`) has no `requirementId` field at
973
- all, and supplying one is a validation error. Unlike v0.5's
974
- `BaselineClause`/`PerChangeClause` (which need an author-visible `clauseId`
975
- for cross-document `supersedesBaselineClauseIds` references), Prompt 3
976
- requirements have no cross-document reference need yet, so trusting an
977
- authored id would only invite drift between a user-typed id and the
978
- content it claims to identify.
979
- - The reference-side expected value/relationship is never stored on the
980
- requirement or the artifact - `deriveReferenceRequirementExpectation()` is
981
- a pure function computed on demand from the artifact's own `regions`,
982
- eliminating the exact drift risk of persisting e.g. `width: 424` alongside
983
- a region whose rectangle could (in principle) later disagree with it.
984
- - A requirement referencing a region id that does not exist in the
985
- artifact's own `regions` is a **structural validation failure** (rejected
986
- at construction/import time), never merely "unavailable" reference
987
- evidence - `isValidReferenceRequirements` checks this before any
988
- requirement reaches adequacy computation.
989
- - **Duplicate/conflicting subject rule**: no two requirements in one
990
- collection may share the same structural subject (same region+property,
991
- or the same unordered region pair + relationship, or + measurement),
992
- regardless of category. This single rule covers both "duplicate
993
- requirement" and "conflicting categories on the same subject" (e.g. the
994
- same region/property authored as both `requested` and `protected`) -
995
- v0.5's `evaluateFrontendContract#primitivesConflict` is a *runtime-
996
- evaluation-time* detector (it needs before/after `ObservationArtifact`
997
- evidence that does not exist yet at this stage) and could not be reused
998
- safely; Prompt 3 restricts invalid combinations at authoring time instead,
999
- per the documented precedent-review outcome.
1000
- - Bounded at `MAX_REFERENCE_REQUIREMENTS` (50) requirements per artifact -
1001
- a maximum capacity, never a required minimum (there is no contract
1002
- requiring any specific number of authored requirements).
1003
- - One new diagnostic code, `invalid-reference-requirement` (error), covers
1004
- every requirement-authoring validation failure - deliberately not split
1005
- further, per the "don't proliferate diagnostics" convention already used
1006
- for `invalid-reference-region`.
1007
- - `import-reference` gained an optional `--requirements-file <json-file>`
1008
- (`{ "requirements": [...] }`, same object-root-wrapper convention as
1009
- `--regions-file`/`--targets-file`); `approve-reference` carries an
1010
- imported artifact's `requirements` forward verbatim (never re-validated,
1011
- never re-derived, never dropped), exactly mirroring how it already
1012
- handles `regions`.
1013
-
1014
- **Reference-evidence adequacy** (`deriveReferenceRequirementAdequacy(regions, requirements)`)
1015
- answers only "does the reference definition itself contain enough evidence
1016
- to understand every selected requirement?" - never "does a runtime
1017
- target/candidate exist" (that is Prompt 4/5's responsibility). It is its own
1018
- small, reference-owned vocabulary (`REFERENCE_REQUIREMENT_ADEQUACY_STATES` =
1019
- `'adequate' | 'partial' | 'inadequate'`, and exactly two reason codes,
1020
- `no-selected-requirements` and `missing-reference-relationship-evidence`) -
1021
- deliberately **not** a reuse of
1022
- `boundedAgentContext.ts`'s `Adequacy`/`ADEQUACY_REASON_CODES`, which
1023
- describe runtime-target/static-correlation concerns that do not exist at
1024
- this stage; mislabeling reference adequacy as bounded-agent-context adequacy
1025
- would conflate two genuinely different evidence domains. Zero selected
1026
- requirements is explicitly `inadequate` (a region-rich, fully-valid
1027
- reference is still not usable for a correction task until the user has
1028
- actually selected what matters) - this is a documented product decision,
1029
- not an oversight. The result is never a numeric score, always structured
1030
- and inspectable, with reasons ordered deterministically by authored
1031
- requirement position.
1032
-
1033
- ```ts
1034
- interface ReferenceRequirementAdequacy {
1035
- status: 'adequate' | 'partial' | 'inadequate';
1036
- totalRequirements: number;
1037
- evaluableRequirements: number;
1038
- unavailableRequirements: number;
1039
- reasons: { code: 'no-selected-requirements' | 'missing-reference-relationship-evidence'; requirementId?: string; detail?: string }[];
1040
- }
1041
- ```
1042
-
1043
- ## v0.7 Prompt 4 reference applicability and candidate-state compatibility
1044
-
1045
- Released as `0.7.0`. Additive extension of the Prompt 1/2/3 contracts
1046
- above: one new, optional `applicability?: ExternalReferenceApplicability`
1047
- field on `ExternalReferenceArtifact` (both lifecycle variants), one new,
1048
- optional `explicitState?: ExplicitStateDimensions` field on
1049
- `ObservationArtifact.requestConfig`, and one new pure module,
1050
- `domain/externalReferenceCompatibility.ts`, that answers a single question:
1051
- "does this external reference describe the same frontend state as this
1052
- candidate `ObservationArtifact`?" No schema version bump on either artifact
1053
- - same reasoning as Prompt 2/3's additive fields.
1054
-
1055
- **Central distinction**: this is page/state-level compatibility only -
1056
- never geometry, never fidelity, never a visual/pixel comparison, and never
1057
- region-to-runtime-target binding (Prompt 5). It answers "should a
1058
- reference-vs-candidate geometry comparison even be attempted", not "does the
1059
- candidate match the reference". Reference-evidence adequacy (Prompt 3) and
1060
- reference/candidate compatibility (Prompt 4) are deliberately independent:
1061
- a reference can be `adequate` (enough selected requirements to evaluate)
1062
- while simultaneously `incomparable` against a given candidate (wrong
1063
- viewport/theme/state), and vice versa - neither result constrains the
1064
- other.
1065
-
1066
- **State identity is always explicit, never inferred.** `theme`,
1067
- `applicationState`, and `authenticatedState` are caller/configuration-
1068
- supplied labels only. The observer never reads screenshot pixels, CSS, DOM
1069
- classes/text, URLs, source code, filenames, accessibility labels,
1070
- localStorage, or cookies to determine state - there is no automatic state
1071
- detection anywhere in this codebase, and Prompt 4 does not add any. Labels
1072
- are bounded opaque identities (`^[A-Za-z0-9_-]{1,64}$`, the same pattern
1073
- already used for target names and region ids) compared by exact,
1074
- case-sensitive string equality only - `"dark"` and `"one-dark"` are
1075
- unrelated labels, never fuzzy-matched or normalized.
1076
-
1077
- ```ts
1078
- // domain/explicitState.ts - shared by both ObservationArtifact and ExternalReferenceArtifact
1079
- type AuthenticatedState = 'authenticated' | 'unauthenticated'; // closed vocabulary - never a place for credentials/tokens/cookies/session ids
1080
- interface ExplicitStateDimensions {
1081
- theme?: string;
1082
- applicationState?: string;
1083
- authenticatedState?: AuthenticatedState;
1084
- }
1085
- // isValidExplicitStateDimensions requires at least one dimension declared and rejects any unsupported field -
1086
- // this is a bounded, closed shape, never an arbitrary Record<string, unknown> metadata bag.
1087
-
1088
- // domain/externalReferenceApplicability.ts
1089
- interface ApplicableViewport { width: number; height: number } // CSS pixels, bounds [200, 3840] mirroring request.ts's own viewport bounds
1090
- interface ExternalReferenceApplicability extends ExplicitStateDimensions {
1091
- viewport?: ApplicableViewport;
1092
- }
1093
- ```
1094
-
1095
- **Reference image size is never the same concept as applicable viewport.**
1096
- `ExternalReferenceImageReference.width/height` (Prompt 1) describes the
1097
- reference image's own pixel dimensions - a property of the image file,
1098
- detected from its header bytes. `applicability.viewport` describes the
1099
- CSS-pixel runtime viewport the design *represents* - a reference image may
1100
- be captured at any resolution or device-pixel-ratio (e.g. a 1920x1080
1101
- screenshot representing a 960x540 CSS-pixel layout at 2x DPR). Nothing in
1102
- `externalReferenceApplicability.ts` reads or derives a viewport from image
1103
- dimensions; `isValidExternalReferenceApplicability` is its own validator
1104
- (not a reuse of `isValidExplicitStateDimensions`, whose "at least one
1105
- dimension" rule would incorrectly reject a viewport-only applicability
1106
- object).
1107
-
1108
- **v0.4 comparability is reused, not duplicated.** `domain/comparison.ts`
1109
- gained four additive reason codes (`viewport-unassessed`, `theme-mismatch`,
1110
- `authenticated-state-mismatch`, `application-state-mismatch` - the
1111
- `*-unassessed` codes for theme/authenticated-state/application-state
1112
- already existed from v0.4) and two optional fields on `ComparabilityReason`
1113
- (`referenceValue?: string`, `candidateValue?: string`, populated only for a
1114
- mismatch reason). `domain/comparisonEngine.ts` gained one new exported pure
1115
- helper, `assessOptionalComparabilityDimension(mismatchCode, unassessedCode,
1116
- beforeValue, afterValue, mismatchMessage, unassessedMessage)`, extracted
1117
- from - and now used by - both v0.4's own `evaluateComparability`
1118
- (Observation-vs-Observation) and the new
1119
- `evaluateReferenceCandidateCompatibility` (Reference-vs-Observation). The
1120
- rule is identical either way: both values defined and equal -> no reason;
1121
- both defined and different -> a `blocking` mismatch reason (with
1122
- `referenceValue`/`candidateValue` populated); either value undefined ->
1123
- an `unassessed` reason. This is a genuine, additive improvement to v0.4's
1124
- own behavior: `evaluateComparability` now assesses theme/authenticated-
1125
- state/application-state as matching or blocking-mismatched whenever *both*
1126
- observations declare `requestConfig.explicitState`, rather than always
1127
- reporting them unassessed - but every historical observation pair (and any
1128
- pair where either side omits `explicitState`) retains the exact old
1129
- unassessed-only behavior, verified by the frozen `evaluateComparability`
1130
- regression test that predates this batch.
1131
-
1132
- ```ts
1133
- // domain/externalReferenceCompatibility.ts
1134
- interface ReferenceCandidateCompatibilityResult {
1135
- referenceId: string;
1136
- referenceRequestId: string;
1137
- candidateObservationId: string;
1138
- candidateRequestId: string;
1139
- compatibility: ComparabilityResult; // v0.4's own reused result type - state/reasons, never a boolean or a visual score
1140
- }
1141
- function evaluateReferenceCandidateCompatibility(reference: ExternalReferenceArtifact, candidate: ObservationArtifact): ReferenceCandidateCompatibilityResult;
1142
- ```
1143
-
1144
- Key rules:
1145
-
1146
- - Pure and synchronous - no browser, no filesystem, no network, no target
1147
- binding. Only `reference.applicability` and
1148
- `candidate.requestConfig.viewport`/`candidate.requestConfig.explicitState`
1149
- are consulted; reference regions/requirements are never read here (a
1150
- distinct, separate concern - see Prompt 3 above).
1151
- - A dimension the reference constrains but the candidate entirely omits
1152
- (or vice versa) is `unassessed`, never treated as compatible-by-default
1153
- and never fabricated as a mismatch - fail-closed, honest non-assessment.
1154
- - A reference that declares no `applicability` at all produces a fully
1155
- `unassessed` (never automatically `incomparable`, never automatically
1156
- `comparable` beyond "no blocking reasons found") result across all four
1157
- dimensions - Prompt 1/2/3 references remain fully usable, just
1158
- unassessed for compatibility until applicability is authored.
1159
- - No automatic persisted compatibility artifact. This is a pure
1160
- programmatic result, produced on demand by an application/CLI caller
1161
- that already holds both a reference and a candidate artifact - inventing
1162
- a new persisted artifact kind for a value this cheap to recompute would
1163
- add drift risk (a candidate/reference re-imported later could silently
1164
- disagree with a stale persisted compatibility record) with no
1165
- corresponding benefit; this may be revisited only if a later prompt's
1166
- architecture proves persistence necessary.
1167
- - Identity impact: `buildExternalReferenceRequestIdentity` gained a final
1168
- optional `applicability` parameter (omitted, never `null`, when absent -
1169
- byte-identical to Prompt 1/2/3 hashes for every call that doesn't supply
1170
- it); `buildRequestIdentity` gained a final optional `explicitState`
1171
- parameter with the identical omission convention. Neither identity
1172
- function ever takes a file path.
1173
- - CLI: `import-reference` gained an optional `--applicability-file
1174
- <json-file>` (the raw, unwrapped applicability object - not a
1175
- `{ "requirements": [...] }`-style wrapper, since applicability is a
1176
- single object rather than a named list); `observe` gained an optional
1177
- `--state-file <json-file>` (the raw, unwrapped `ExplicitStateDimensions`
1178
- object). Both follow the existing `--scroll-scenario-file` convention
1179
- exactly: relative paths resolve from the current working directory, the
1180
- path itself is never persisted or included in any identity, and CLI code
1181
- owns only flag syntax/file reading/JSON parsing/object-root validation -
1182
- all semantic validation happens in the domain layer.
1183
-
1184
- ## v0.7 Prompt 5 explicit reference-region <-> runtime-target binding
1185
-
1186
- Released as `0.7.0`. One new pure domain module,
1187
- `domain/externalReferenceRuntimeBinding.ts`, answering "which stable
1188
- observer runtime target, if any, does this candidate observation resolve
1189
- for each explicitly declared reference region?" No new field is added to
1190
- either `ExternalReferenceArtifact` or `ObservationArtifact` - both remain
1191
- exactly as Prompt 4 left them - and no schema version bump on either.
1192
-
1193
- **Two identity domains, kept strictly separate.** A binding declaration
1194
- names a Prompt 2 `ReferenceRegion.id` and a v0.2 `NamedTarget.name` (the
1195
- stable observer runtime target identity established since v0.2 - never a
1196
- CSS selector, DOM node handle, source file, React component name, or
1197
- my-dev-kit node id). These two strings living in the same textual namespace
1198
- never implies a binding - a region id `"header"` and a target name
1199
- `"header"` bind to each other only because of an explicit declaration, not
1200
- because the strings match (verified by a dedicated test: the same
1201
- observation with and without the explicit declaration produces `bound`
1202
- only in the former case).
1203
-
1204
- ```ts
1205
- // domain/externalReferenceRuntimeBinding.ts
1206
- interface ReferenceRuntimeBindingDeclaration {
1207
- referenceRegion: string; // Prompt 2 ReferenceRegion.id
1208
- runtimeTarget: string; // v0.2 NamedTarget.name
1209
- }
1210
-
1211
- const REFERENCE_RUNTIME_BINDING_STATUSES = ['bound', 'ambiguous', 'unavailable'] as const;
1212
-
1213
- interface ReferenceRuntimeBindingResult {
1214
- referenceRegion: string;
1215
- runtimeTarget: string;
1216
- status: 'bound' | 'ambiguous' | 'unavailable';
1217
- reasonCode?: 'runtime-target-not-configured' | 'runtime-target-not-found' | 'runtime-target-ambiguous' | 'runtime-target-evidence-unavailable';
1218
- detail: string;
1219
- targetResolutionStatus?: TargetSelectionStatus; // v0.2's own resolution status, when evidence for it exists
1220
- targetVisible?: boolean; // provenance only - never affects status
1221
- }
1222
-
1223
- interface ReferenceRuntimeBindingEvaluation {
1224
- referenceId: string;
1225
- referenceRequestId: string;
1226
- candidateObservationId: string;
1227
- candidateRequestId: string;
1228
- compatibility: ComparabilityResult; // reused verbatim from v0.7 Prompt 4
1229
- bindings: ReferenceRuntimeBindingResult[]; // empty exactly when compatibility.state === 'incomparable'
1230
- }
1231
-
1232
- function evaluateReferenceRuntimeBindings(
1233
- reference: ExternalReferenceArtifact,
1234
- candidate: ObservationArtifact,
1235
- declarations: readonly ReferenceRuntimeBindingDeclaration[],
1236
- ): { ok: true; evaluation: ReferenceRuntimeBindingEvaluation } | { ok: false; reason: string };
1237
- ```
1238
-
1239
- Key rules:
1240
-
1241
- - **Explicit, never inferred.** A binding declaration is user/configuration
1242
- input asserting a conceptual correspondence; this module never discovers
1243
- it from screenshot geometry, matching names, matching text, or source
1244
- code. There is no automatic-matching algorithm anywhere in this module.
1245
- - **Reuses, never duplicates.** The compatibility gate reuses
1246
- `evaluateReferenceCandidateCompatibility` (Prompt 4) verbatim - viewport/
1247
- theme/application-state/authenticated-state comparison logic is never
1248
- re-implemented here. Runtime-target resolution reuses `targetPresence`
1249
- (v0.4 `comparisonEngine.ts`, now additively exported alongside
1250
- `assessOptionalComparabilityDimension`) - the exact same "how do I read a
1251
- `TargetEvidenceRecord`'s resolution" rule v0.4's own before/after target
1252
- comparison already uses. No second target resolver, no browser launch, no
1253
- Chromium query, no selector evaluation, no live-DOM inspection - this
1254
- module consumes only an already-captured `ObservationArtifact`'s
1255
- `requestConfig.targets`/`targetEvidence`.
1256
- - **Compatibility gates before evaluation, structurally.** If
1257
- `evaluateReferenceCandidateCompatibility` reports `incomparable`,
1258
- `bindings` is the empty array and the caller reads the reason from the
1259
- embedded `compatibility` field - there is no binding-local "incompatible"
1260
- status; Prompt 4's compatibility result is represented exactly once, not
1261
- duplicated into a parallel vocabulary.
1262
- - **Two-layer validation.** Reference-region existence, declaration shape,
1263
- bounds, and duplicate/conflict rules are validated structurally against
1264
- the `ExternalReferenceArtifact` alone (`isValidReferenceRuntimeBindingDeclarations`)
1265
- - independent of any candidate, mirroring Prompt 3's "unknown region
1266
- reference is a structural validation failure" precedent exactly: a
1267
- declaration naming a nonexistent reference region, or any reference with
1268
- no `regions` declared at all, fails the whole evaluation closed before a
1269
- candidate is even considered. Runtime-target availability, by contrast,
1270
- is evaluated per-candidate inside `evaluateReferenceRuntimeBindings`
1271
- itself, since the same declaration can be `bound` against one candidate
1272
- and `unavailable` against another.
1273
- - **Duplicate/conflicting-declaration rule** (mirrors Prompt 3's
1274
- requirement-subject uniqueness rule): no two declarations may name the
1275
- same `referenceRegion` (case-insensitively), whether they agree on
1276
- `runtimeTarget` (an exact duplicate) or disagree (a conflict) - both fail
1277
- the same way, never silently resolved by keeping the first. The reverse -
1278
- several distinct reference regions naming the same `runtimeTarget` - is
1279
- deliberately allowed (e.g. two design sub-regions legitimately
1280
- corresponding to one runtime container element).
1281
- - **Target-resolution-state handling.** `targetPresence`'s four outcomes
1282
- map onto binding status as: `matched` -> `bound`; `ambiguous` -> `ambiguous`
1283
- (the candidate's own configured target resolved ambiguously - never
1284
- reported bound even though its stable name exists); `not-found` ->
1285
- `unavailable` (`runtime-target-not-found` - the target was configured but
1286
- the resolver found nothing on the page); no usable resolution evidence at
1287
- all -> `unavailable` (`runtime-target-evidence-unavailable`). A declared
1288
- `runtimeTarget` that was never part of the candidate's configured target
1289
- set at all is a fifth, CLI/config-boundary-only outcome -> `unavailable`
1290
- (`runtime-target-not-configured`) - never a dynamic page search.
1291
- - **Hidden-target decision.** A uniquely resolved (`matched`) but hidden
1292
- target is still reported `bound` - visibility never changes `status`.
1293
- `targetVisible` (from the existing `TargetVisibility` evidence, when
1294
- available) is carried as provenance only. Binding identity (does a stable
1295
- correspondence exist) and later fidelity evaluability (can this evidence
1296
- actually be used to check the design) are treated as distinct questions;
1297
- this prompt answers only the former.
1298
- - **Not every region needs a binding.** `isValidReferenceRuntimeBindingDeclarations`
1299
- never requires full region coverage - a reference may have regions no
1300
- declaration names at all (they simply have no bound runtime target for
1301
- this candidate). This is not a completeness gate; Prompt 6 (or later) may
1302
- add one for the regions that selected requirements actually need.
1303
- - **No persisted artifact family.** `evaluateReferenceRuntimeBindings` is a
1304
- pure, on-demand function over an already-persisted reference, an
1305
- already-persisted candidate observation, and an in-memory declaration
1306
- collection. No `ExternalReferenceBindingArtifact` (or equivalent) is
1307
- introduced - the same "cheap to recompute, persisting invites drift"
1308
- reasoning Prompt 4 already applied to its own compatibility result.
1309
- Neither the reference nor the observation artifact is ever rewritten to
1310
- carry a binding result: a design reference may later be evaluated against
1311
- several different candidates, and one observation may be evaluated
1312
- against several different references, so binding is kept as downstream,
1313
- candidate-specific, reference-specific derived evidence rather than
1314
- mutating either immutable source artifact.
1315
- - **No new identity function.** Unlike `buildRequestIdentity`/
1316
- `buildExternalReferenceRequestIdentity`, no hash-based logical identity is
1317
- computed for a binding declaration or its evaluated result - there is no
1318
- persistence and no cross-document reference-by-id need yet (mirroring
1319
- Prompt 4's `ReferenceCandidateCompatibilityResult`, which took the same
1320
- approach). Provenance is instead carried directly as plain fields
1321
- (`referenceId`, `referenceRequestId`, `candidateObservationId`,
1322
- `candidateRequestId`, plus each result's own `referenceRegion`/
1323
- `runtimeTarget`) - already deterministic, already sufficient for a caller
1324
- to trace every result back to its inputs, without inventing a fifth
1325
- identity-hashing convention for a value this prompt does not persist.
1326
- - **Deterministic ordering.** `bindings` preserves authored declaration
1327
- order (mirroring the "authored order is semantic" convention already used
1328
- for regions/requirements) rather than sorting by any derived key.
1329
- - **Bounded.** `MAX_REFERENCE_RUNTIME_BINDINGS` (20) caps the declaration
1330
- collection, mirroring `MAX_REFERENCE_REGIONS`.
1331
- - **No public CLI surface yet.** Only the programmatic
1332
- `evaluateReferenceRuntimeBindings`/`isValidReferenceRuntimeBindingDeclarations`
1333
- functions are exported. A standalone CLI command was deliberately not
1334
- added merely for symmetry with `import-reference`/`observe`; Prompt 6
1335
- (structured fidelity evaluation) is expected to become the first concrete
1336
- consumer and public-surface owner for this capability.
1337
-
1338
- ## v0.7 Prompt 6 structured reference-vs-candidate fidelity evaluation
1339
-
1340
- Released as `0.7.0`. One new pure domain module,
1341
- `domain/externalReferenceFidelity.ts`, and its CLI-facing counterpart,
1342
- `application/referenceFidelityEvaluationService.ts` plus the new
1343
- `evaluate-reference-fidelity` CLI command - the first point in this whole
1344
- v0.7 stack where a reference's authored expectation is actually compared
1345
- against live candidate evidence. No new artifact field, no schema version
1346
- bump: this prompt reuses Prompt 1-5's artifacts and result types entirely.
1347
-
1348
- ```ts
1349
- // domain/externalReferenceFidelity.ts
1350
- const REFERENCE_REQUIREMENT_FIDELITY_STATUSES = ['pass', 'fail', 'unavailable'] as const;
1351
- const REFERENCE_FIDELITY_STATES = ['not-evaluated', 'pass', 'fail'] as const;
1352
- const REFERENCE_FIDELITY_BLOCK_REASONS = ['reference-inadequate', 'incompatible'] as const;
1353
-
1354
- interface ReferenceRequirementFidelityResult {
1355
- requirementId: string;
1356
- category: AuthoredChangeScopeCategory;
1357
- expectedDependentMode?: ExpectedDependentMode;
1358
- subject: ReferenceRequirementSubject;
1359
- boundRuntimeTargets: string[];
1360
- status: 'pass' | 'fail' | 'unavailable';
1361
- reasonCode?: 'reference-evidence-unavailable' | 'reference-relationship-not-exhibited' | 'binding-unavailable' | 'candidate-evidence-unavailable' | 'coordinate-mapping-unavailable'; // present iff status === 'unavailable'
1362
- detail?: string;
1363
- // region-property/region-measurement subjects only:
1364
- referenceValue?: number; // reference-image pixels
1365
- candidateRawValue?: number; // CSS pixels, as captured
1366
- candidateValue?: number; // candidateRawValue converted into reference-image-pixel space
1367
- delta?: number; // candidateValue - referenceValue
1368
- tolerance?: ReferenceRequirementTolerance;
1369
- // region-relationship subjects only:
1370
- expectedRelationship?: PairwiseRelationshipKind;
1371
- actualRelationship?: PairwiseRelationshipKind;
1372
- }
1373
-
1374
- interface ReferenceCandidateFidelityEvaluation {
1375
- referenceId: string;
1376
- referenceRequestId: string;
1377
- candidateObservationId: string;
1378
- candidateRequestId: string;
1379
- adequacy: ReferenceRequirementAdequacy; // reused verbatim from Prompt 3
1380
- compatibility?: ComparabilityResult; // reused verbatim from Prompt 4; absent only when adequacy itself is inadequate
1381
- bindings?: ReferenceRuntimeBindingEvaluation; // reused verbatim from Prompt 5; absent when an earlier gate blocked
1382
- state: 'not-evaluated' | 'pass' | 'fail';
1383
- blockedBy?: 'reference-inadequate' | 'incompatible'; // present iff state === 'not-evaluated'
1384
- requirementResults: ReferenceRequirementFidelityResult[]; // empty iff state === 'not-evaluated'
1385
- }
1386
-
1387
- function evaluateReferenceCandidateFidelity(
1388
- reference: ExternalReferenceArtifact,
1389
- candidate: ObservationArtifact,
1390
- bindingDeclarations: readonly ReferenceRuntimeBindingDeclaration[],
1391
- options?: { geometryTolerancePx?: number },
1392
- ): { ok: true; evaluation: ReferenceCandidateFidelityEvaluation } | { ok: false; reason: string };
1393
- ```
1394
-
1395
- **Result vocabulary.** `pass`/`fail`/`unavailable` is reused from v0.5's
1396
- `CLAUSE_RESULT_STATUSES` shape (the same honest three-state idea: a result
1397
- either satisfies its condition, fails it, or cannot be evaluated - never a
1398
- score) but is its own independently-owned constant, deliberately excluding
1399
- v0.5's fourth member, `'conflict'` - Prompt 6 has no cross-requirement
1400
- authoring-conflict concept (each requirement is evaluated independently
1401
- against its own subject), so reusing `conflict` would invite a status this
1402
- prompt can never actually produce.
1403
-
1404
- **Evaluation order (frozen, never reordered):** reference structural
1405
- validation -> candidate structural validation -> binding-declaration
1406
- structural validation -> Prompt 3 reference adequacy -> Prompt 4
1407
- compatibility -> Prompt 5 binding evaluation -> per-requirement candidate-
1408
- evidence/coordinate-mapping checks -> per-requirement tolerance/
1409
- relationship comparison -> overall result. The first three (structural
1410
- validation) failures return `{ ok: false, reason }` - a caller/config error,
1411
- never a fidelity outcome. The next two (adequacy `inadequate`, compatibility
1412
- `incomparable`) short-circuit to `state: 'not-evaluated'` with an empty
1413
- `requirementResults` - an earlier blocking gate never lets an ordinary
1414
- PASS/FAIL requirement set get fabricated past it. `adequacy` "partial" (some,
1415
- but not all, authored requirements individually unavailable) does **not**
1416
- block evaluation - it proceeds normally, and the individual unavailable
1417
- reference-side requirements simply also report `unavailable` at the
1418
- per-requirement level (their own reference-evidence problem, re-derived
1419
- identically by `evaluateOneRequirement`, not looked up from the adequacy
1420
- result).
1421
-
1422
- **Coordinate mapping - the central problem this prompt solves.** Prompt 2
1423
- regions and Prompt 3 tolerances are authored in reference-image pixels;
1424
- `ObservationArtifact` target geometry is CSS pixels. This module establishes
1425
- exactly one explicit, deterministic scale from `reference.applicability.viewport`
1426
- (the CSS-pixel runtime viewport, Prompt 4) and the reference image's own
1427
- pixel dimensions (Prompt 1) - `scaleX = imageWidth / viewportWidth`,
1428
- `scaleY = imageHeight / viewportHeight` - and converts every candidate
1429
- measurement into reference-image-pixel space before comparing it against a
1430
- Prompt 3 tolerance. It never assumes 1 reference-image pixel equals 1 CSS
1431
- pixel, and it never performs cropping, offset, rotation, or perspective
1432
- registration - only a deliberately bounded full-frame mapping. `scaleX`/
1433
- `scaleY` must agree within a small, independently-owned coordinate-mapping-
1434
- validity tolerance (1% relative, never a user-authored design tolerance) or
1435
- the mapping is rejected outright; a reference with no applicable viewport at
1436
- all likewise has no mapping. Either way, every numeric (`region-property`/
1437
- `region-measurement`) requirement becomes `unavailable`/`coordinate-mapping-unavailable`
1438
- - categorical `region-relationship` requirements are unaffected (they never
1439
- need a scale). Horizontal fields (`x`/`width`/`right`/`centerX` and the
1440
- horizontal measurements) always scale by `scaleX`; vertical fields (`y`/
1441
- `height`/`bottom`/`centerY` and the vertical measurements) always scale by
1442
- `scaleY` - this falls out automatically from converting a full
1443
- `TargetGeometry` into a `ReferenceRegionGeometry`-shaped value per axis,
1444
- never a hand-picked per-property axis table.
1445
-
1446
- **Tolerance is reused exactly, never redefined.** A single rule -
1447
- `abs(delta) <= allowedAmount` - covers all three Prompt 3 tolerance kinds:
1448
- `exact` is simply the zero-tolerance case (`allowedAmount = 0`);
1449
- `absolute-reference-px` uses its authored `amount` directly (already in
1450
- reference-image pixels); `percent`'s denominator is `Math.abs(referenceValue)`,
1451
- mirroring v0.5's own `toleranceToPx` "may vary by up to N%" convention
1452
- exactly (independently reimplemented in reference-image-pixel units, never
1453
- imported - `frontendContractEvaluation.ts`'s `ContractTolerance` is a
1454
- different, CSS-pixel-implicit unit). No hidden epsilon is added anywhere;
1455
- subpixel precision is preserved through to the final comparison, so a
1456
- tolerance-boundary value (e.g. delta exactly equal to the allowed amount)
1457
- passes and one unit past it fails, exactly as authored.
1458
-
1459
- **Region-property evaluation** reads `TargetGeometry` from the bound
1460
- target's `targetEvidence` entry, converts it into a `ReferenceRegionGeometry`-
1461
- shaped value (adding `centerX`/`centerY`, computed identically to
1462
- `deriveReferenceRegionGeometry`) both raw (CSS) and scaled (reference-image
1463
- pixels), and reads `[subject.property]` off each - `candidateRawValue`
1464
- (CSS) and `candidateValue` (reference-image pixels) are both reported.
1465
-
1466
- **Region-measurement evaluation** converts *both* bound targets' geometries
1467
- the same way and calls the existing `deriveReferenceRequirementMeasurement`
1468
- (Prompt 3) on the converted geometries directly - reusing Prompt 3's exact
1469
- gap/delta formulas rather than reimplementing a parallel "runtime version"
1470
- of them, and never inventing a generic geometry expression language. A
1471
- geometrically-undefined gap (the two targets overlap on the relevant axis)
1472
- is `unavailable`, mirroring Prompt 3's own reference-side treatment of the
1473
- identical situation.
1474
-
1475
- **Region-relationship evaluation** first confirms the reference itself
1476
- actually exhibits its own selected relationship (`deriveReferenceRequirementExpectation`'s
1477
- `matches` field) - if not, the result is `unavailable`/
1478
- `reference-relationship-not-exhibited` (a reference-authoring problem, never
1479
- a candidate `fail`). It then resolves both bound targets and calls the
1480
- canonical `deriveLayoutRelationships` (v0.4) over the *whole* candidate
1481
- observation - never a second, parallel relationship formula - and looks up
1482
- the pairwise record for the bound target pair **scoped to the exact
1483
- requested relationship family** (an independently-owned, third duplicate of
1484
- the same `RELATIONSHIP_FAMILY_GROUPS` shape already used by
1485
- `frontendContractEvaluation.ts` and `externalReferenceRequirements.ts` -
1486
- this is the same real bug class Prompt 3 fixed: matching the first record
1487
- for a target pair regardless of family would silently compare against the
1488
- wrong relationship kind). A record only derivable in the reversed target
1489
- order is `unavailable`, never auto-flipped - identical to Prompt 3's own
1490
- reference-side handling of the same situation. `pass` requires the
1491
- candidate's actual relationship kind to equal the requirement's authored
1492
- `relationship` exactly.
1493
-
1494
- **Binding gate.** Every subject's dependent reference region(s) must have a
1495
- `bound` (never `ambiguous`/`unavailable`, and never simply absent from the
1496
- supplied declarations) Prompt 5 binding result, or the requirement is
1497
- `unavailable`/`binding-unavailable` - this module never guesses another
1498
- target and never auto-binds based on geometry or names. A `bound` target
1499
- that is not visible (`TargetVisibility.visible !== true`, including when
1500
- visibility evidence itself is unavailable) is treated as having no usable
1501
- geometry - `unavailable`/`candidate-evidence-unavailable` - preserving the
1502
- distinction between "binding succeeded" (Prompt 5's question) and "this
1503
- evidence is usable for fidelity evaluation" (this prompt's question): a
1504
- hidden-but-uniquely-resolved target still has a stable identity, but its
1505
- geometry is never treated as meaningful for a numeric/relationship
1506
- comparison.
1507
-
1508
- **Categories are preserved, never given different PASS/FAIL rules.**
1509
- `category`/`expectedDependentMode` are carried through to each result as
1510
- provenance only; Prompt 3 never implemented a `required`-vs-`permitted`
1511
- directional evaluation difference for its own expectation/adequacy
1512
- derivation (unlike v0.5's runtime-directional contract clauses), so Prompt 6
1513
- does not invent one now - every requirement in the reference's authored
1514
- collection is evaluated by the identical rule and counts identically toward
1515
- the overall result, regardless of category.
1516
-
1517
- **Overall fidelity result.** For an evaluated (non-blocked) pair, `state`
1518
- is `'pass'` only when every requirement result is `'pass'`; any `'fail'` or
1519
- `'unavailable'` result forces `state: 'fail'` - there is no meaningful third
1520
- overall bucket once evaluation has actually run, since "some/all
1521
- unavailable" and "some/all fail" both equally mean "not every selected
1522
- requirement is confirmed satisfied." A reference with zero selected
1523
- requirements never reaches this stage at all - it is `inadequate` (Prompt
1524
- 3's own zero-requirements rule) and therefore `not-evaluated`, never a
1525
- meaningless `pass`.
1526
-
1527
- **Persistence decision: none.** `evaluateReferenceCandidateFidelity` (and
1528
- its CLI-facing wrapper, `evaluateReferenceCandidateFidelityFromArtifactRoots`)
1529
- is a pure, on-demand function over already-persisted/in-memory evidence -
1530
- no new `ExternalReferenceFidelityEvaluationArtifact` (or equivalent) is
1531
- introduced. Rationale, identical to Prompt 4/5's own precedent: the result
1532
- is cheap to recompute deterministically from its inputs (a reference, a
1533
- candidate, and a caller-supplied binding-declaration collection), and
1534
- persisting it would invite drift with no corresponding benefit at this
1535
- stage; this may be revisited only if Prompt 7's architecture proves
1536
- persistence necessary.
1537
-
1538
- **CLI**: `evaluate-reference-fidelity --reference <root> --candidate <root>
1539
- [--bindings-file <json-file>] [--enforce]` - the CLI surface Prompt 5
1540
- deliberately deferred. `--bindings-file` follows the exact
1541
- `--requirements-file`/`--regions-file` wrapped-object convention
1542
- (`{ "bindings": [...] }`); CLI code owns only flag syntax/file reading/JSON
1543
- parsing/root-shape validation, with every binding-declaration rule staying
1544
- owned by `isValidReferenceRuntimeBindingDeclarations`. `--enforce` mirrors
1545
- `evaluate-contract`'s exact precedent: it changes only the process exit
1546
- status for an already-computed `state: 'fail'` result, never the printed
1547
- content - and has no effect on `not-evaluated`, which always exits 0 (a
1548
- compatibility/adequacy blocker is a successful, structured, honest
1549
- non-evaluation, never an execution error and never a design mismatch).
1550
- Persists nothing; there is no `--output` flag.
1551
-
1552
- ## v0.7 Prompt 7 bounded reference-fidelity projection and v0.6 bounded-agent-context integration
1553
-
1554
- Released as `0.7.0`. Additive extension of the v0.6 bounded-agent-context
1555
- contract above and of the v0.7 Prompt 6 fidelity evaluator - no new bounded-
1556
- context artifact family, no second visual-context system, no schema version
1557
- bump (`BOUNDED_AGENT_CONTEXT_SCHEMA_VERSION` stays `1.0.0`, following the
1558
- exact precedent already set when `correlations?` was added in v0.6 Batch 3).
1559
-
1560
- **Chosen integration owner.** `projectBoundedAgentContext` itself gains one
1561
- new optional input (`fidelity?: ReferenceCandidateFidelityEvaluation`, plus
1562
- `fidelityRequired?: boolean`) rather than a separate `VisualAgentContext`/
1563
- `VisualPromptPacket`/`ReferencePromptBuilder`. This was chosen over a pure
1564
- post-hoc "attach" step (the shape `attachRuntimeStaticCorrelations` uses)
1565
- because fidelity-relevant runtime targets must compete fairly for
1566
- `MAX_RUNTIME_TARGETS` capacity and receive the exact same geometry/
1567
- visibility/screenshot assembly contract-clause-derived targets already get -
1568
- an attach-only step run after target allocation could never produce that. A
1569
- new pure module, `domain/referenceFidelityProjection.ts`
1570
- (`projectReferenceFidelity`), derives the bounded, prioritized fidelity
1571
- content plus the target-id/omission/truncation contributions
1572
- `projectBoundedAgentContext` folds into its own existing pipeline - it is
1573
- not a second fidelity-evaluation engine, only a selection over Prompt 6's
1574
- already-computed result.
1575
-
1576
- ```ts
1577
- // domain/boundedAgentContext.ts - additive
1578
- interface BoundedAgentContextSourceReferences {
1579
- // ...unchanged fields...
1580
- referenceId?: string; // new, optional
1581
- referenceRequestId?: string; // new, optional
1582
- }
1583
-
1584
- const MAX_FIDELITY_MISMATCHES = 15;
1585
- const MAX_FIDELITY_PROTECTED_CONTEXT = 10; // reuses MAX_RELATIONSHIP_EVIDENCE_PER_TARGET's value
1586
-
1587
- interface BoundedReferenceFidelityProjection {
1588
- referenceId: string;
1589
- referenceRequestId: string;
1590
- candidateObservationId: string;
1591
- candidateRequestId: string;
1592
- adequacy: ReferenceRequirementAdequacy; // reused verbatim from Prompt 3
1593
- compatibility?: ComparabilityResult; // reused verbatim from Prompt 4
1594
- state: ReferenceFidelityState; // reused verbatim from Prompt 6
1595
- blockedBy?: ReferenceFidelityBlockReason; // reused verbatim from Prompt 6
1596
- mismatches: ReferenceRequirementFidelityResult[]; // bounded, prioritized non-pass requirements (Prompt 6 type, unmodified)
1597
- protectedContext: ReferenceRequirementFidelityResult[]; // bounded passing protected/preserved requirements, as "do not break this" context
1598
- }
1599
-
1600
- interface BoundedAgentContextArtifact {
1601
- // ...unchanged fields...
1602
- fidelity?: BoundedReferenceFidelityProjection; // new, optional - mirrors `correlations?`'s own additive precedent exactly
1603
- }
1604
- ```
1605
-
1606
- **Selection policy** (`domain/referenceFidelityProjection.ts#projectReferenceFidelity`):
1607
- only Prompt 6's non-`pass` requirement results are ever candidates for
1608
- `mismatches` - passing requirements are never dumped by default, satisfying
1609
- this prompt's "bounded coding-agent use" design goal. Each candidate is
1610
- classified into a tier by its authored category/mode, reusing
1611
- `boundedAgentContextProjection.ts#clauseTier`'s exact rule (duplicated, not
1612
- imported, per this repository's established per-module small-helper
1613
- convention - never a reference-specific protected/preserved taxonomy):
1614
- `protected`/`preserved` are always `required`; `expected-dependent` is
1615
- `required` only in `'required'` mode; `requested` and `expected-dependent`/
1616
- `'permitted'` are `optional`.
1617
-
1618
- **Priority policy**: 1) `fail` + `required` tier, 2) `unavailable` +
1619
- `required` tier, 3) any other non-`pass` (optional-tier) result. Within one
1620
- priority class, Prompt 6's own authored requirement order is preserved (a
1621
- stable sort by priority rank only) - never re-ranked by an opaque score.
1622
- The final `mismatches` array is reported in priority order (highest first),
1623
- not restored to authored order, since the whole point of prioritization is
1624
- that the most actionable evidence appears first when the set is large.
1625
-
1626
- **Cap values**: `MAX_FIDELITY_MISMATCHES = 15` and
1627
- `MAX_FIDELITY_PROTECTED_CONTEXT = 10` (reusing
1628
- `MAX_RELATIONSHIP_EVIDENCE_PER_TARGET`'s value) - both judgment-call bounds
1629
- in the same spirit as v0.6 Batch 1's own frozen caps (no measured fixture
1630
- corpus exists yet for either concept).
1631
-
1632
- **Omission/truncation behavior**: reuses `OmissionRecord`/`TruncationRecord`
1633
- wholesale, no second reporting model. When mismatches exceed the cap, a
1634
- `{subject: 'fidelity-mismatches', limit, actualCount, required}` truncation
1635
- is recorded, plus one `{subject: 'fidelity-mismatch:<requirementId>',
1636
- reason: 'required-evidence-lost-by-bound', required: true}` omission for
1637
- *each* dropped required-tier mismatch (optional-tier drops are truncated
1638
- but never separately omitted as "required loss", since they were never
1639
- required). `protectedContext` truncation is always `required: false` - it
1640
- is confirmatory/passing context, never a design-fidelity failure. These
1641
- records are folded into `projectBoundedAgentContext`'s own `omissions`/
1642
- `truncations` arrays *before* its existing aggregate `capOmissions`/
1643
- `capTruncations` calls and its existing adequacy computation run - fidelity
1644
- loss is never a separate adequacy code path, it simply participates in the
1645
- exact same `anyRequiredLoss`/`anyOptionalLoss` rule every other evidence
1646
- source already uses.
1647
-
1648
- **Adequacy behavior**: a `not-evaluated` fidelity (blocked by Prompt 6's own
1649
- `reference-inadequate`/`incompatible` gates) is never converted into "no
1650
- problems" - `projectReferenceFidelity` records an explicit
1651
- `{subject: 'fidelity', reason: 'unsupported-or-unavailable', required,
1652
- detail}` omission, where `required` defaults to `true` (supplying a
1653
- fidelity evaluation to be projected at all is itself the signal that the
1654
- task depends on it, mirroring `CorrelationTargetInput.required`'s existing
1655
- v0.6 convention - callers who want fidelity as purely incidental context set
1656
- `fidelityRequired: false`). A `required: true` fidelity omission, folded
1657
- into the existing adequacy computation, prevents `adequacy.state` from
1658
- remaining `'adequate'` (it becomes `'partial'`, or `'inadequate'` when
1659
- combined with other required loss reaching the existing threshold) - it is
1660
- never silently ignored. A `required: false` omission can degrade adequacy
1661
- to at most `'partial'`, per v0.6's own pre-existing "optional-only loss
1662
- never means inadequate" rule - unchanged, not redefined. A `pass` fidelity
1663
- result contributes no omissions/truncations at all and never degrades
1664
- adequacy.
1665
-
1666
- **Not-evaluated fidelity behavior**: preserved exactly as Prompt 6 reported
1667
- it - `fidelity.state`/`fidelity.blockedBy` on the output artifact are a
1668
- direct pass-through of Prompt 6's own values, with `mismatches`/
1669
- `protectedContext` both empty (there is nothing to select from an empty
1670
- `requirementResults`).
1671
-
1672
- **Per-target organization**: every fidelity mismatch's `boundRuntimeTargets`
1673
- (Prompt 5/6's own field, never truncated) becomes a required- or permitted-
1674
- tier addition to `projectBoundedAgentContext`'s existing target-id sets,
1675
- so those runtime targets receive full `BoundedRuntimeTargetProjection`
1676
- treatment (geometry/visibility/overflow/scrollOwner/screenshotRef) through
1677
- the exact existing assembly code - no duplicated target-projection logic.
1678
- Reference regions are never used as a correlation or target-selection key;
1679
- only the already-bound stable v0.2 runtime target ids are.
1680
-
1681
- **Multi-target relationship representation**: a `region-relationship`
1682
- mismatch's `boundRuntimeTargets` array (already carrying both bound
1683
- targets, from Prompt 6) is used as-is - both targets are added to the
1684
- required/permitted set, so both appear in `targets`. Nothing collapses a
1685
- two-target relationship failure onto a single target.
1686
-
1687
- **Static-correlation reuse**: entirely unchanged. `deriveRuntimeStaticCorrelations`/
1688
- `attachRuntimeStaticCorrelations` are not modified, not called from within
1689
- this prompt's new code, and remain the caller's own separate step -
1690
- `BoundedRuntimeTargetProjection.targetId`/`RuntimeStaticCorrelationRecord.runtimeTargetId`
1691
- already share the same stable v0.2 identity a fidelity mismatch's
1692
- `boundRuntimeTargets` also uses, so a caller (Prompt 8) joins fidelity,
1693
- target, and correlation evidence by that one shared id without this module
1694
- ever needing to read source, run my-dev-kit, or choose among ambiguous
1695
- candidates itself.
1696
-
1697
- **Ambiguous/unavailable correlation behavior**: unaffected - a
1698
- `RuntimeStaticCorrelationRecord` with `status: 'ambiguous'` continues to
1699
- preserve every competing candidate (v0.6's own frozen invariant,
1700
- untouched), and `status: 'unavailable'` never causes a fidelity mismatch
1701
- for that same runtime target to be dropped - the two evidence kinds
1702
- (runtime fidelity, static correlation) are attached independently and
1703
- neither erases the other.
1704
-
1705
- **Provenance**: every included mismatch remains traceable to the reference
1706
- (`sources.referenceId`/`referenceRequestId`, new), the requirement
1707
- (`requirementId`, `category`, `subject` - naming its reference region(s)),
1708
- the Prompt 5 binding (`boundRuntimeTargets`), the candidate
1709
- (`sources.observationIds`), and the full Prompt 6 evidence
1710
- (`referenceValue`/`candidateRawValue`/`candidateValue`/`delta`/`tolerance`
1711
- or `expectedRelationship`/`actualRelationship`) - nothing is replaced by a
1712
- prose-only summary. No raw image bytes are ever embedded (fidelity carries
1713
- only identifiers and numeric/categorical evidence, never pixels), and no
1714
- source-ownership field (`sourceOwner`/`sourceFile`/`component`/`symbol`/
1715
- `causedBy`) is ever produced - Prompt 7 stops at the runtime target exactly
1716
- as Prompt 6 did; v0.6's own, unmodified static correlation is the only
1717
- source-adjacent evidence this context ever carries, and it remains
1718
- evidence, never edit authorization.
1719
-
1720
- **Identity impact**: `buildBoundedAgentContextRequestIdentity` gained a
1721
- final optional `fidelity?: unknown` parameter - omitted (never `null`) from
1722
- the hashed semantic view when absent, so every pre-Prompt-7 call site keeps
1723
- producing its exact byte-identical hash (verified by a frozen-vector-style
1724
- regression test). When present, the caller's already-derived, bounded
1725
- `BoundedReferenceFidelityProjection` (not the raw Prompt 6 evaluation) is
1726
- hashed, so identity changes exactly when the content a caller would
1727
- actually receive changes - never merely because an unselected, dropped
1728
- requirement result changed somewhere upstream. `sources.referenceId`/
1729
- `referenceRequestId` follow the identical omit-when-absent convention.
1730
- Operational paths were never an identity input for this artifact family to
1731
- begin with (no path parameter exists anywhere in this contract), so path
1732
- independence holds trivially.
1733
-
1734
- **Schema-version decision**: no bump. Every new field
1735
- (`BoundedAgentContextSourceReferences.referenceId`/`referenceRequestId`,
1736
- `BoundedAgentContextArtifact.fidelity`) is additive and optional; a
1737
- pre-Prompt-7 artifact/consumer remains fully valid and behaviorally
1738
- unchanged with all of them absent, matching the exact precedent
1739
- `correlations?` already established without a version bump in v0.6 Batch 3.
1740
-
1741
- **Persistence decision: none.** `projectBoundedAgentContext` and
1742
- `projectReferenceFidelity` both remain pure, programmatic, in-memory
1743
- functions - no new writer/reader, no new artifact family. This mirrors
1744
- Prompt 6's own "no persisted fidelity artifact" decision and v0.6's
1745
- existing "bounded agent context is library-only" architecture.
1746
-
1747
- **CLI decision**: none added. v0.6 bounded agent context has never had a
1748
- CLI surface, and this prompt does not introduce one - Prompt 8 is expected
1749
- to become the first concrete consumer of `projectBoundedAgentContext`'s
1750
- (now fidelity-aware) programmatic output.
1751
-
1752
- ## v0.7 Prompt 8 controlled end-to-end external-reference coding-agent correction workflow
1753
-
1754
- Released as `0.7.0`. One new pure domain module,
1755
- `domain/referenceCorrectionWorkflow.ts`, plus its identity counterpart,
1756
- `domain/referenceCorrectionIdentity.ts` - the first stage that composes
1757
- every Prompt 1-7 and v0.1/v0.4/v0.5/v0.6 owner into one traceable
1758
- reference-driven correction cycle. It reimplements none of them: reference
1759
- lifecycle/adequacy (Prompt 1/3), compatibility (Prompt 4), binding (Prompt
1760
- 5), fidelity (Prompt 6), bounded context (Prompt 7), runtime comparison
1761
- (v0.4 `compareObservations`), and contract evaluation (v0.5
1762
- `evaluateFrontendContract`) are all called, never re-derived. No new
1763
- persisted artifact family, no CLI surface, no remote AI dependency, and no
1764
- mechanism anywhere in this module (or any module it calls) that edits
1765
- target source.
1766
-
1767
- ```ts
1768
- // domain/referenceCorrectionWorkflow.ts
1769
- function prepareReferenceCorrection(input: {
1770
- reference: ExternalReferenceArtifact; // must be approved
1771
- baselineObservation: ObservationArtifact; // approved baseline / pre-change state
1772
- baselineContract: PersistentBaselineContract;
1773
- changeContract: PerChangeContract;
1774
- bindingDeclarations: readonly ReferenceRuntimeBindingDeclaration[];
1775
- currentObservation: ObservationArtifact; // fidelity is measured against this (= baselineObservation for the canonical proof)
1776
- generatedAt: string; producerVersion: string; projectionProfile: ProjectionProfile;
1777
- }): { ok: true; status: 'handoff-ready'; reviewRequestId: string; fidelity: ReferenceCandidateFidelityEvaluation; handoff: ReferenceCorrectionHandoff }
1778
- | { ok: true; status: 'blocked-not-evaluated'; reviewRequestId: string; fidelity: ReferenceCandidateFidelityEvaluation }
1779
- | { ok: false; reason: string };
1780
-
1781
- function reviewReferenceCorrectionAttempt(input: {
1782
- // ...same reference/baselineObservation/baselineContract/changeContract/bindingDeclarations...
1783
- reviewRequestId: string; // must match the id prepareReferenceCorrection returned for this exact semantic review
1784
- candidateObservation: ObservationArtifact; // fresh, post-edit capture
1785
- priorAttemptId?: string;
1786
- }): { ok: true; attempt: ReferenceCorrectionAttemptResult } | { ok: false; reason: string };
1787
- ```
1788
-
1789
- **Workflow architecture.** A narrowly-scoped coordinator, not a second
1790
- workflow engine: it holds no stage catalog, no job scheduler, and no
1791
- generic orchestration graph. It performs exactly two operations - "prepare"
1792
- (pre-change evidence -> bounded handoff) and "review" (post-edit candidate
1793
- -> one composed overall result) - matching this prompt's own explicit
1794
- guidance that the external-edit boundary must remain a visible seam between
1795
- two separate calls, never one command that blocks waiting for an external
1796
- actor.
1797
-
1798
- **New owners introduced**: `prepareReferenceCorrection`,
1799
- `reviewReferenceCorrectionAttempt` (composition only - no new evaluation
1800
- logic), `buildReferenceCorrectionReviewIdentity`/
1801
- `buildReferenceCorrectionAttemptIdentity` (deterministic identity, see
1802
- below), and the plain `ReferenceCorrectionHandoff`/
1803
- `ReferenceCorrectionAttemptResult` result shapes.
1804
-
1805
- **Existing owners reused, verbatim**: `isApprovedExternalReferenceArtifact`
1806
- (Prompt 1), `isValidReferenceRuntimeBindingDeclarations` (Prompt 5),
1807
- `evaluateReferenceCandidateFidelity` (Prompt 6), `projectBoundedAgentContext`
1808
- (Prompt 7, itself now fidelity-aware), `compareObservations` (v0.4),
1809
- `evaluateFrontendContract` (v0.5). None of their internal logic is
1810
- inspected, duplicated, or reimplemented by this module - only their
1811
- top-level results are read.
1812
-
1813
- **Approved-reference/approved-baseline requirement.** `prepareReferenceCorrection`
1814
- and `reviewReferenceCorrectionAttempt` both fail closed (`{ok: false}`) if
1815
- `reference` is not in the `'approved'` lifecycle state (Prompt 1's own
1816
- `isApprovedExternalReferenceArtifact` guard) - an imported-but-unapproved
1817
- reference is never treated as an authoritative target design. Neither
1818
- function ever calls `approveExternalReference`/`approveAndPersistBaseline`
1819
- itself; approval remains the caller's own separate, explicit action.
1820
-
1821
- **Pre-change candidate = approved baseline observation**, for the canonical
1822
- proof: `prepareReferenceCorrection`'s `currentObservation` and
1823
- `baselineObservation` are the same value, so the initial reference fidelity
1824
- can genuinely `FAIL` (measuring the gap between the current, already-
1825
- approved implementation and the desired new design) while the baseline
1826
- itself stays fully valid and approved. A caller's own architecture may
1827
- supply a distinct `currentObservation` only when justified - the workflow
1828
- does not require them to be identical, only that `currentObservation` and
1829
- `candidateObservation` are always independently validated
1830
- `ObservationArtifact`s.
1831
-
1832
- **Preparation (phase A)**: validates the common preconditions (approved
1833
- reference, matching baseline/contract coherence, valid binding
1834
- declarations), evaluates reference fidelity via Prompt 6 against
1835
- `currentObservation`, and - only when that evaluation actually produced a
1836
- result (`state !== 'not-evaluated'`) - projects it into a bounded context
1837
- via Prompt 7/v0.6 and returns the `ReferenceCorrectionHandoff`. A
1838
- `not-evaluated` fidelity (inadequate reference, or reference/candidate
1839
- incompatible state) is reported as `status: 'blocked-not-evaluated'` -
1840
- carrying the full Prompt 6 result for inspection, but never a fabricated
1841
- handoff pretending evidence is adequate. An ambiguous or unavailable
1842
- required binding does **not** block preparation outright - it still
1843
- produces a `handoff-ready` result, with the ambiguity/unavailability
1844
- visible directly in that requirement's own `unavailable`/`binding-
1845
- unavailable` mismatch (Prompt 6's own honest per-requirement reporting,
1846
- unchanged), so the external actor sees exactly why that specific
1847
- requirement cannot yet be assessed.
1848
-
1849
- **The handoff** (`ReferenceCorrectionHandoff`) carries `reviewRequestId`,
1850
- `referenceId`/`referenceRequestId`, `baselineObservationId`,
1851
- `currentObservationId`, the full Prompt 7 `boundedContext` (already
1852
- containing bounded fidelity mismatches, protected/preserved context,
1853
- adequacy/omission/truncation, and - when the caller supplied it - runtime/
1854
- static correlation), and a fixed, four-line `verificationPlan` explaining
1855
- in plain language what will be re-checked after the edit (fresh Chromium
1856
- capture, reference re-evaluation, v0.4/v0.5 re-evaluation, and the exact
1857
- overall-PASS rule) - never reduced to "make it look like the screenshot".
1858
- No raw reference image bytes, no full `ObservationArtifact`, and no source
1859
- excerpt are ever included.
1860
-
1861
- **Handoff persistence: none.** The handoff is a plain, JSON-serializable,
1862
- in-memory value returned directly to the caller - no new writer/reader, no
1863
- new artifact family. A caller that needs the handoff to cross a process/
1864
- session boundary (e.g. to hand it to an external coding-agent process) is
1865
- free to serialize it with its own mechanism; observer product code does not
1866
- own a persisted handoff artifact. This was a deliberate "smallest possible"
1867
- choice: the handoff's only genuinely new identity is `reviewRequestId`
1868
- (already deterministic and recomputable from stable inputs - see below), so
1869
- nothing about it requires observer-managed persistence to remain
1870
- traceable.
1871
-
1872
- **Review identity** (`buildReferenceCorrectionReviewIdentity`): a pure
1873
- function of `{referenceRequestId, baselineObservationId,
1874
- baselineContractId, baselineContractClauses, changeContractId,
1875
- changeContractClauses, bindingDeclarations}` only - never a timestamp,
1876
- never an operational file path. Deliberately hashes each contract's own
1877
- authored `clauses` content, not merely its `baselineId`/`contractId` label:
1878
- unlike this repository's content-derived identities elsewhere (e.g.
1879
- `ObservationArtifact.observationId`), a `PersistentBaselineContract`'s
1880
- `baselineId` and a `PerChangeContract`'s `contractId` are plain, caller-
1881
- authored strings (`approveAndPersistBaseline` persists `contract.baselineId`
1882
- verbatim, never recomputing it from `clauses`) - so two structurally valid
1883
- contracts could in principle share an id while authoring different clauses.
1884
- Hashing clause content directly closes that gap (caught during this
1885
- prompt's own independent-judge review before being reported PASS - see the
1886
- report's Tooling incidents section). `reviewReferenceCorrectionAttempt`
1887
- recomputes this same hash from its own inputs and rejects the call
1888
- (`{ok: false}`) if the caller-supplied `reviewRequestId` does not match -
1889
- this is the mechanism that makes "no hidden baseline change" an enforced
1890
- invariant rather than a documented intention: an attempt claiming to belong
1891
- to a review while actually supplying a different baseline observation,
1892
- baseline contract (id or clause content), per-change contract (id or clause
1893
- content), reference, or binding set can never silently succeed.
1894
-
1895
- **Attempt identity** (`buildReferenceCorrectionAttemptIdentity`): a pure,
1896
- deterministic function of `{reviewRequestId, candidateObservationId}` only
1897
- - deliberately never a fresh random nonce. Every candidate observation
1898
- already carries its own fresh, collision-resistant instance identity (v0.1's
1899
- `buildObservationIdentity`), so hashing it together with the review it was
1900
- captured for gives an attempt id that is both reproducible (the same
1901
- review+candidate pair always yields the same `attemptId`) and guaranteed
1902
- distinct per real capture.
1903
-
1904
- **Attempt history**: caller-managed, not observer-persisted. Because both
1905
- workflow functions are pure (no internal mutable state, no side effects),
1906
- an already-returned `ReferenceCorrectionAttemptResult` can never be
1907
- overwritten by a later call - a caller that keeps every attempt result it
1908
- receives (in memory, in its own log, or in its own storage) has a complete,
1909
- immutable, traceable history for free, linked via each attempt's own
1910
- `reviewRequestId` (shared across all attempts of one review),
1911
- `priorAttemptId` (an optional, purely informational link to the immediately
1912
- preceding attempt, carried through unchanged - never consulted by the
1913
- evaluation logic itself), and `attemptId`.
1914
-
1915
- **Baseline-across-attempts rule**: enforced structurally, not merely
1916
- documented. Every call to `reviewReferenceCorrectionAttempt` requires the
1917
- caller to re-supply `baselineObservation`/`baselineContract` in full, and
1918
- `compareObservations`/`evaluateFrontendContract` are always invoked with
1919
- that same baseline against the fresh `candidateObservation` - there is no
1920
- code path anywhere in this module that compares one candidate against a
1921
- prior candidate instead. Combined with the `reviewRequestId` coherence
1922
- check above, a caller cannot silently swap in a different baseline between
1923
- attempts of the same review without the call being rejected.
1924
-
1925
- **Overall result composition.** `ReferenceCorrectionOverallState =
1926
- 'not-evaluated' | 'pass' | 'fail'`:
1927
-
1928
- - `fidelity.state === 'not-evaluated'` -> overall `'not-evaluated'` - Prompt
1929
- 6's own explicit blocked state is preserved exactly, never collapsed into
1930
- an ordinary `'fail'`.
1931
- - otherwise, `fidelity.state === 'pass' && contractEvaluation.overallVerdict === 'PASS'`
1932
- -> overall `'pass'`; anything else -> overall `'fail'`.
1933
-
1934
- A structurally-incomparable baseline/candidate pair is *not* given its own
1935
- third overall bucket - v0.5's own `evaluateFrontendContract` already
1936
- returns `'FAIL'` (never `'PASS'`) for that case, per its own established,
1937
- unmodified precedent, and this workflow reuses that decision rather than
1938
- re-litigating it. `approvalEligible` is a plain, read-only boolean
1939
- (`true` iff `overallState === 'pass'`) - it is never itself an approval
1940
- action; the caller must still invoke the existing explicit
1941
- `approveAndPersistBaseline`/`approveExternalReference` owners separately,
1942
- and neither is ever called from within this module.
1943
-
1944
- **Correction iteration**: `reviewReferenceCorrectionAttempt` is called once
1945
- per candidate; the caller decides whether and when to call it again after
1946
- another external edit. There is no loop, no polling, no automatic retry,
1947
- and no mechanism in this module that itself waits for or drives an external
1948
- implementation step - the production boundary between "prepare a handoff"
1949
- and "review a candidate" is the explicit seam a human or an external
1950
- process controls.
1951
-
1952
- **Source-editing boundary**: absolute. Neither this module nor anything it
1953
- calls opens, reads, parses, or writes any target source file; both public
1954
- operations accept only already-captured `ObservationArtifact`s and already-
1955
- approved contract/reference artifacts. Real-Chromium candidate capture is
1956
- always the caller's own responsibility, through the existing, unmodified
1957
- observation pipeline (`runBrowserCapture`/`buildObservationArtifact`, the
1958
- same functions `application/observationPersistence.ts#observe` already
1959
- uses) - Prompt 8 adds no second browser adapter, screenshot engine, target
1960
- resolver, or evidence-capture path.
1
+ # Contracts
2
+
3
+ ## Visual-change workflow artifact (`1.0.0`, v0.10 Batch 1)
4
+
5
+ `my-frontend-observer/visual-change-workflow` is the immutable Observer-owned history envelope for one frozen visual-change request. It records a deterministic `visualChangeRequestId`, a fresh `visualChangeWorkflowId`, optional forward-only `supersedesVisualChangeWorkflowId`, exact references to existing canonical evidence, zero to twenty bounded attempt records, optional activation/governance result references, producer metadata, and creation provenance.
6
+
7
+ The two entry modes are exactly `actual-frontend` and `reference`. Reference mode stores validated explicit runtime binding declarations in canonical `bindings.json`; the manifest pins its SHA-256 and declaration count. Actual mode never writes that file. Project-relative evidence references must remain contained portable paths; source artifacts and media are referenced rather than copied.
8
+
9
+ Request identity hashes semantic scope only. It excludes timestamps and operational storage locations. Workflow instance identity is fresh for every explicit persistence. Attempt identity is deterministic over the visual-change request ID and candidate observation ID. Human review state is exactly `pending`, `correction-requested`, `accepted`, or `abandoned`; this foundation validates structure but does not implement the later acceptance rule or execute checks.
10
+
11
+ The v0.10 Batch 2 application composition explicitly activates either the frozen per-change contract or the frozen approved reference and workflow-owned bindings in project acceptance. Activation and restoration preserve unrelated configuration, use compensating atomic writes across mutable project configuration and immutable workflow revisions, and refuse acceptance drift. A workflow check resolves an alias only when its catalog identity and artifact location exactly match the frozen baseline, then invokes the existing `checkProject` owner once. Only a canonical result containing both baseline and candidate summaries can append a pending attempt revision; the snapshot is a bounded direct projection of that result and does not repeat evaluation.
12
+
13
+ The v0.10 Batch 3 Viewer exposes bounded workflow inspection at `GET /api/visual-changes/:handle/view` in both standalone and project-aware sessions. Create, activate, check, and restore POST operations are project-aware only, reuse the existing in-memory authoring capability and request guards, accept no filesystem paths, and delegate to the Batch 2 application service. Every new response is `no-store`. Successful immutable mutations return the exact new workflow ID so the Viewer can refresh and reselect that revision without guessing. The Viewer protocol remains `1.3.0`.
14
+
15
+ Project-aware `check` resolves and validates its immutable baseline before
16
+ candidate capture. Candidate URL, viewport, targets, and ordinary operational
17
+ settings still come from project configuration. The check replays only the
18
+ validated baseline request's optional `scrollScenario` and `explicitState`
19
+ through `normalizeRequest` and the canonical observer. Scroll is executed by
20
+ the normal browser capture path. `explicitState` replay retains the
21
+ caller-declared comparison identity only; it never infers or establishes
22
+ browser, application, theme, or session state. Project config remains schema
23
+ 1.1.0 and does not accept either field; `init` and normal `capture` remain
24
+ project-config-only.
25
+
26
+ The v0.10 Batch 4 actual-frontend entry route promotes only explicitly selected, saved, confirmed, canonically promotable runtime intent without activating project acceptance. It freezes the exact resulting contract instance with the saved annotation, source observation, and configured persistent baseline contract through the Batch 2 workflow-creation owner. Repeated equivalent starts share `contractRequestId` while receiving fresh contract, visual-change request, and workflow instance identities. Activation remains a later explicit workflow action.
27
+
28
+ Reference entry freezes only an exact approved reference, an annotation authored
29
+ against that exact instance, an exact baseline observation, and explicitly
30
+ authored valid bindings. Adequacy, compatibility, complete required-region
31
+ coverage, and canonical binding evaluation fail closed. Imported references,
32
+ inferred bindings, and session bindings are not executable workflow scope.
33
+
34
+ `VisualChangeAgentHandoff` uses handoff kind
35
+ `my-frontend-observer/visual-change-agent-handoff` and version `1.0.0`. It is a
36
+ bounded non-artifact transfer contract and is never discovered or persisted as
37
+ Observer evidence. It includes confirmed scope, Observer-owned bounded context,
38
+ optional unchanged supplemental context, the exact post-edit check instruction,
39
+ and optional non-authoritative orchestrator correlation.
40
+
41
+ The current cycle is derived from the latest attempt. A pending attempt blocks
42
+ another check or handoff until explicit correction, acceptance, or abandonment.
43
+ Only the latest canonical PASS may be accepted. Review changes only the latest
44
+ attempt's review object in a fresh workflow revision. Governance references may
45
+ be recorded only after acceptance and only for already-persisted canonical
46
+ approval results; they do not perform approval or change project configuration.
47
+ No existing evidence schema changed for v0.10.
48
+
49
+ ## Current contracts
50
+
51
+ The observation artifact contract is published in the current
52
+ `my-frontend-observer@0.7.0` package and proven both from the source checkout
53
+ and from the packed npm tarball, on Windows, Linux, and macOS. The observation
54
+ schema is `1.2.0` (see "v0.2 target contract" and "v0.3 scroll scenario
55
+ contract" below):
56
+
57
+ - artifact kind `my-frontend-observer/observation`, schema version `1.2.0`
58
+ (independent of the package version);
59
+ - one artifact root per observation, `<outputLocation>/<observationId>/`,
60
+ containing exactly `manifest.json` (the full `ObservationArtifact`, with
61
+ page/target evidence embedded inline) and `screenshot.png` - there is no
62
+ separate `evidence.json`;
63
+ - `manifest.json` is written last, after `screenshot.png`, via one atomic
64
+ directory rename, so a consumer never observes a partially-written
65
+ artifact; a filesystem failure anywhere in that sequence reports the
66
+ `artifact-write-failure` diagnostic and leaves no completed artifact;
67
+ - internal artifact references (e.g. `screenshot.png`) are relative to the
68
+ artifact root, never an absolute machine path; the observation's logical
69
+ identity is its `observationId`, not its filesystem location;
70
+ - evidence states `available`, `unavailable`, `not-applicable`, `partial`;
71
+ evidence sources `browser`, `computed-browser`, `derived`;
72
+ - a stable diagnostic vocabulary (`src/domain/diagnostics.ts`) and completion
73
+ states `complete`, `partial`, `warning`, `invalid-request`, `fatal`
74
+ (`src/domain/completion.ts`);
75
+ - observation/request identity, producer/package identity, and browser
76
+ provenance are all present in every persisted manifest.
77
+
78
+ This contract is implemented and published; no public programmatic-API
79
+ compatibility promise has been made for the observation engine itself. v0.6
80
+ additionally publishes the bounded-agent-context/correlation programmatic surface
81
+ described later in this document.
82
+
83
+ ## v0.2 target contract (shipped as part of this release)
84
+
85
+ v0.2 introduces a canonical target-configuration model: each configured target has a stable
86
+ observer-level `name` plus an ordered array of bounded `locators`
87
+ (`role`, `id`, `data-attribute`, `semantic-element`, `css`, `text`). This
88
+ identity is distinct from both the browser locator definition that resolves
89
+ it and any source-code identity. The legacy `{name, selector}` shape remains
90
+ accepted and normalizes to a one-item `css` locator, so every published
91
+ `0.1.0` CLI invocation continues to work unchanged. Locator precedence is the
92
+ configured array order; resolution stops on the first unique match, on any
93
+ ambiguous match (never falling through to a later locator), or on an
94
+ unevaluable locator - never silently. All six frozen locator kinds are now
95
+ resolved against a real Chromium page (`role` via Playwright's accessibility-
96
+ role/name locator with exact name matching, `id`/`data-attribute` via exact
97
+ CSS attribute-equals matching that never reinterprets the configured value as
98
+ selector syntax, `semantic-element` via the frozen tag set, `css` via the
99
+ existing v0.1 behavior, `text` via exact-text matching only); every kind
100
+ converges on the same measurement path, so locator strategy never changes the
101
+ resulting target evidence shape.
102
+
103
+ Each resolved target's evidence record additionally carries three bounded
104
+ fields: `semanticState` (a first family of `disabled`/`expanded`/
105
+ `checked`/`selected`/`pressed`/`current` values read from the element's own
106
+ native form-control properties and explicit `aria-*` attributes - a key is
107
+ present only when the browser exposes that state as applicable to this
108
+ element, so an explicit `false` is always distinguishable from "not
109
+ applicable"; `not-applicable` when no supported state applies at all);
110
+ `landmark` (derived only from the already-captured browser-exposed
111
+ role - never from locator kind or HTML tag - against the standard landmark
112
+ role set `banner`/`navigation`/`main`/`complementary`/`contentinfo`/`form`/
113
+ `region`/`search`); and `containment` (bounded DOM containment checked only
114
+ among the other explicitly configured targets in the same observation, in
115
+ configured order, never a layout/relationship graph - `available` when every
116
+ other configured target was itself resolved and checked, `partial` when one
117
+ or more could not be, `unavailable` when the target itself never resolved).
118
+ Stable observer target identity is proven, not just declared: the same
119
+ target configuration produces the same `requestId` across repeated
120
+ observations (with a fresh `observationId` each time); changing a target's
121
+ locator strategy while keeping its stable name changes `requestId` but not
122
+ the `targetEvidence` key; and actual runtime disappearance of a
123
+ still-configured target changes only its resolution status, never the
124
+ `requestId`.
125
+
126
+ The full canonical semantic target model above is reachable through the
127
+ real public CLI: `my-frontend-observer observe --targets-file <json-file>`
128
+ supplies the structured `{ "targets": [...] }` collection (see
129
+ `docs/COMMANDS.md` "Structured semantic targets") as an alternative to the
130
+ existing `--target id=css-selector` shorthand - the two are mutually
131
+ exclusive per invocation, and both converge on the same
132
+ `normalizeRequest()`/browser-resolver/artifact path, so a semantic
133
+ observation produces exactly the same `manifest.json` shape as a
134
+ CSS-shorthand one. Schema `1.1.0` was the v0.2 published artifact schema;
135
+ schema `1.2.0` has been emitted since v0.3 and remains the observation schema
136
+ in the current published v0.7.0 package, for both target-input modes
137
+ (target semantics are unchanged from v0.2 - see the v0.3 scroll scenario
138
+ contract below for what schema `1.2.0` actually adds). `--targets-file`'s
139
+ local input path is never part of the persisted request identity or
140
+ artifact.
141
+
142
+ ## v0.3 scroll scenario contract (shipped as part of this release)
143
+
144
+ v0.3 introduces one optional, additive request/evidence concern: a bounded
145
+ runtime scroll scenario, schema `1.2.0`.
146
+
147
+ A normalized request may carry `scrollScenario: { action }` with exactly one
148
+ of two frozen action kinds:
149
+
150
+ - `{ "kind": "window-scroll-by", "deltaX": <int>, "deltaY": <int> }`
151
+ - `{ "kind": "target-scroll-by", "target": "<stable target name>", "deltaX": <int>, "deltaY": <int> }`
152
+
153
+ `deltaX`/`deltaY` are signed integers bounded to `[-20000, 20000]`; at least
154
+ one must be non-zero. `target-scroll-by.target` refers only to an existing
155
+ stable configured target `name` (never a selector) and resolves through the
156
+ same canonical `resolveConfiguredTargets` algorithm every v0.2 locator kind
157
+ already uses - there is no second target-resolution path. A request with no
158
+ scenario normalizes and identifies exactly as it did before v0.3.
159
+
160
+ Execution (both action kinds share one code path): perform the immediate,
161
+ non-smooth scroll (`window.scrollBy`/`element.scrollBy`, `behavior:
162
+ 'instant'`) on the already-navigated, already-ready page; wait exactly two
163
+ `requestAnimationFrame` cycles; capture a final runtime snapshot. No second
164
+ browser, page, or navigation is ever created. The resulting scroll position
165
+ is browser-authoritative and may be clamped by document/element boundaries;
166
+ a scenario producing no movement is still a valid, successfully persisted
167
+ observation.
168
+
169
+ The scenario evidence lives entirely inside the existing `manifest.json` as
170
+ one additional optional `scrollScenarioEvidence` field on `ObservationArtifact`
171
+ - there is no separate `scroll.json`/`scenario.json`. It contains:
172
+
173
+ - `initial`/`final`: bounded `ScrollRuntimeSnapshot`s (window `scrollX`/
174
+ `scrollY`; the browser's own scrolling-root/`documentElement`/`body`
175
+ metrics; per-configured-target `scrollTop`/`scrollLeft`/`scrollWidth`/
176
+ `scrollHeight`/`clientWidth`/`clientHeight`, actual overflow, bounding
177
+ rectangle, and viewport relation);
178
+ - `transition`: bounded before/after change evidence (window scroll deltas;
179
+ per-target `scrollTop`/`scrollLeft`/bounding-position/viewport-relation
180
+ changes; `enteredViewport`/`leftViewport`) - never a generic recursive
181
+ diff, and a target is simply omitted when either side's evidence isn't
182
+ itself usable (e.g. it never resolved);
183
+ - `scrollOwner`: one derived `EvidenceField<ScrollOwnerInterpretation>`
184
+ (`document` | `target:<stable-name>` | `none` | `indeterminate`), always
185
+ `source: "derived"` with non-empty `derivedFrom` naming the exact
186
+ contributing scroll-position measurements. Ownership is derived only from
187
+ observed `scrollTop`/`scrollLeft`/`window.scrollX`/`window.scrollY`
188
+ changes - never from bounding-rectangle movement (which moves for every
189
+ configured target whenever the document scrolls), computed overflow,
190
+ `position: fixed`/`sticky`, or DOM hierarchy.
191
+
192
+ Actual dimensional overflow (`scrollWidth > clientWidth` /
193
+ `scrollHeight > clientHeight`) is always reported separately from the
194
+ computed `overflow-x`/`overflow-y` CSS declaration; a declared
195
+ `overflow: auto` container with content that fits produces
196
+ `horizontalOverflow`/`verticalOverflow: false`. Viewport relation
197
+ (`above`/`intersecting`/`below`, `intersectsViewport`, `fullyWithinViewport`)
198
+ is derived only from bounding geometry plus viewport size, relative to the
199
+ browser viewport; a hidden/non-rendered target's viewport relation is
200
+ `not-applicable`, never a fabricated geometry claim - hidden and offscreen
201
+ remain distinct evidence concepts, and the existing `target-hidden`
202
+ diagnostic is unaffected.
203
+
204
+ The ordinary, already-existing `pageEvidence`/`targetEvidence`/
205
+ `screenshot.png` for a scenario observation always describe this same final
206
+ post-action state, never the pre-action state.
207
+
208
+ The scenario request participates in `requestId`; the runtime result
209
+ (actual scroll distance, clamping, or scroll-owner outcome) never does. The
210
+ public entry point is `my-frontend-observer observe --scroll-scenario-file
211
+ <json-file>` (see `docs/COMMANDS.md`); the file supplies the scenario value
212
+ directly, and its local path is operational input only, exactly like
213
+ `--targets-file`'s path - never persisted, never part of request identity.
214
+
215
+ ## v0.4 comparison contract (shipped as part of this release)
216
+
217
+ **Current status: shipped as part of the published `my-frontend-observer@0.4.0`
218
+ package and unchanged through the current `0.7.0` release.** Observation
219
+ schema remains `1.2.0`. Comparison is a distinct artifact kind and schema,
220
+ never a bump to the observation schema:
221
+
222
+ - artifact kind: `my-frontend-observer/comparison`;
223
+ - comparison schema: `1.0.0`.
224
+
225
+ **Geometry tolerance**: `ComparisonConfig.geometryTolerancePx`, default
226
+ `0.5` CSS px, bounded `[0, 10]`. Suppresses insignificant subpixel noise
227
+ only - never a design contract, never permission for a change.
228
+
229
+ **Layout relationship graph**: `deriveLayoutRelationships(observation,
230
+ options?)` derives, per observation, a bounded `LayoutRelationshipGraph`
231
+ among configured targets only (≤20 targets, ≤190 unordered pairs):
232
+ horizontal order (`left-of`/`right-of`/`horizontally-overlapping`),
233
+ vertical order (`above`/`below`/`vertically-overlapping`), area overlap
234
+ (`overlaps`/`does-not-overlap`), relative width (`wider-than`/
235
+ `narrower-than`/`equal-width-within-tolerance`), geometric fit
236
+ (`fits-inside`/`does-not-fit-inside` - geometry-only, deliberately distinct
237
+ from DOM containment), vertical sequencing (`follows-vertically`), and one
238
+ page-level relationship (`document-width-fits-viewport`/
239
+ `document-width-exceeds-viewport`). Every relationship carries explicit
240
+ evidence-path provenance back to the source observation. A configured
241
+ target lacking usable geometry is listed as honestly unresolved
242
+ (`not-found`/`ambiguous`/`unavailable`/`hidden`), never fabricated as a
243
+ zero-sized region.
244
+
245
+ **Comparability**: evaluated before any rendered difference, using exactly
246
+ three states (`comparable`/`comparable-with-warnings`/`incomparable`) with
247
+ structured reasons, never a bare boolean. Hard incompatibilities (page URL,
248
+ viewport, browser engine, scroll-scenario configuration mismatch) force
249
+ `incomparable`; producer-version, browser-version, and target-configuration
250
+ differences are warning-only; theme/authenticated-state/application-state
251
+ identity are recorded as `unassessed` dimensions the observer does not yet
252
+ model - never silently claimed identical. An `incomparable` result still
253
+ persists a structurally valid `ComparisonArtifact` with empty rendered
254
+ differences, not a fabricated comparison.
255
+
256
+ **Difference categories**: `appeared`/`disappeared` (only for a stable
257
+ target name configured on both sides, transitioning between a definite
258
+ `not-found` and `matched` resolution status - never for a target merely
259
+ added/removed from configuration, which is its own separate
260
+ `configurationChanges` entry), `moved`/`resized` (tolerance-aware, a target
261
+ may be both), `visibility-changed`, `clipping-changed` (reusing the
262
+ canonical `deriveTargetClipping` helper, never re-derived), `horizontal-
263
+ overflow-changed`/`vertical-overflow-changed` (actual dimensional overflow,
264
+ reusing the existing `deriveOverflowEvidence` helper - never inferred from
265
+ a CSS declaration alone), `containment-changed` (reusing existing v0.2
266
+ `TargetContainment` evidence), `page-size-changed`, `scroll-owner-changed`
267
+ (comparing `scrollScenarioEvidence.scrollOwner` only when scenario
268
+ *configuration* already matched), `relative-position-changed` (a relation
269
+ in the horizontal-order/vertical-order/area-overlap families changed - kept
270
+ distinct from plain absolute target movement) and `relationship-changed`
271
+ (every other relationship-family transition). Relationship changes are
272
+ matched by structural identity (family + subject/related target, or the
273
+ page-level key), never by array position.
274
+
275
+ **Explicit dependency evidence**: `ComparisonConfig.expectedDependencies`
276
+ lets a caller declare an expected relationship between two targets' numeric
277
+ properties (`x`/`y`/`width`/`height`) and directions (`increase`/
278
+ `decrease`/`change`/`unchanged`), always carrying `source:
279
+ "explicit-config"`. The observer never synthesizes a declaration from
280
+ observed co-change. Each declaration evaluates independently to exactly one
281
+ of `consistent`/`not-observed`/`contradictory-to-declaration`/
282
+ `unavailable` - never a causal claim (no `causedBy`/`causalConfidence`/
283
+ `causalScore`/`dependencyStrength`) and never a PASS/FAIL/approval verdict.
284
+ That distinction (evidence vs. contract verdict) is the boundary between
285
+ v0.4 and v0.5+.
286
+
287
+ **Comparison identity**: `comparisonRequestId` is a pure, deterministic
288
+ function of `{beforeObservationId, afterObservationId, normalized
289
+ ComparisonConfig}` - direction-sensitive (`compare(A, B) !==
290
+ compare(B, A)`), and never includes an operational filesystem path.
291
+ `comparisonId` is fresh per execution (same pattern as `observationId`).
292
+
293
+ **Source references**: the comparison artifact retains enough logical
294
+ identity to trace back to its authoritative source observations
295
+ (`observationId`, `requestId`, `producer`, `observationSchemaVersion`, and
296
+ the source `screenshot.path`) without embedding the full
297
+ `ObservationArtifact` or copying screenshot bytes. The persisted comparison
298
+ directory contains `manifest.json` only.
299
+
300
+ The public entry point is `my-frontend-observer compare --before <root>
301
+ --after <root> --output <directory> [--config-file <json-file>]` (see
302
+ `docs/COMMANDS.md`) - comparison itself never launches a browser.
303
+
304
+ ## v0.5 frontend contract and evaluation (shipped as part of this release)
305
+
306
+ Downstream of the v0.4 observation/comparison/relationship evidence above,
307
+ `src/domain/frontendContracts.ts` freezes the v0.5 contract/change-scope
308
+ model, `src/domain/frontendContractIdentity.ts` freezes deterministic
309
+ contract/baseline/clause identity, and `src/domain/frontendContractEvaluation.ts`
310
+ implements the one canonical pure evaluation engine. Baseline/per-change
311
+ contract persistence, evaluation-artifact persistence, explicit baseline
312
+ approval, and public CLI exposure are all implemented and shipped (see
313
+ "v0.5 contract and evaluation persistence" and "v0.5 public contract/
314
+ evaluation commands" below).
315
+
316
+ **Contract classes**: a `PersistentBaselineContract` (append/supersession-based
317
+ history via an optional `supersedesBaselineId`) and a `PerChangeContract`
318
+ (the allowed scope of one requested change). Both share `artifactKind:
319
+ "my-frontend-observer/frontend-contract"` and `schemaVersion: "1.0.0"` - an
320
+ independent family from the observation (`1.2.0`) and comparison (`1.0.0`)
321
+ schemas; the frontend-contract schema constant happens to share the version
322
+ string `1.0.0` with comparison's by coincidence only.
323
+
324
+ **Four authored categories, one derived classification**: every per-change
325
+ clause is authored as exactly one of `requested`, `expected-dependent`,
326
+ `protected`, or `preserved`. `unexpected` is a fifth, *derived-only*
327
+ classification the evaluator produces for a meaningful rendered difference no
328
+ active clause accounts for - it can never be authored as a permission.
329
+
330
+ **Bounded contract primitives**: 15 frozen `ContractPrimitive` kinds cover
331
+ visibility, clipping, width bounds, non-overlap, relative width, vertical
332
+ sequence, geometric fit (explicitly distinct from DOM containment),
333
+ document-width-vs-viewport, scroll ownership, initial-viewport position,
334
+ relationship-unchanged, and property-unchanged/increases/decreases - a closed
335
+ vocabulary, never a generic expression language.
336
+
337
+ **Contract tolerance**: `exact` / `absolute-px` / `percent`, independent of
338
+ `ComparisonConfig.geometryTolerancePx` (which only suppresses insignificant
339
+ comparison noise and is never contract authorization). Percent tolerance's
340
+ denominator is the absolute before-value.
341
+
342
+ **Required vs. permitted expected-dependent**: `required` clauses must occur
343
+ compliantly to pass; `permitted` clauses accept no change or a compliant
344
+ change, and fail only on a strictly contradictory change.
345
+
346
+ **Evaluation result vocabulary**: each clause resolves to `pass` / `fail` /
347
+ `unavailable` (with a required non-empty reason - required evidence gaps and
348
+ an `incomparable` source comparison never fabricate a `pass`) / `conflict`
349
+ (with at least two `conflictingClauseIds` - covers both an unresolved
350
+ baseline/per-change contradiction and an unknown `supersedesBaselineClauseIds`
351
+ reference). The overall verdict is `PASS` only when every clause result is
352
+ `pass` and no unexpected change remains; otherwise `FAIL` - there is no
353
+ partial-pass scoring.
354
+
355
+ **Explicit supersession, never inferred**: a per-change clause may list
356
+ `supersedesBaselineClauseIds` to remove specific baseline clauses from active
357
+ evaluation. Two clauses that structurally contradict each other on the same
358
+ (target, property) without explicit supersession produce a `conflict`, never
359
+ a silent preference for one side.
360
+
361
+ **Reuses existing v0.4 evidence directly**: the evaluator consumes an
362
+ already-computed `ComparisonArtifact` (`differences`, `relationshipChanges`,
363
+ `relationshipsBefore`/`relationshipsAfter`, `comparability`) and the source
364
+ `ObservationArtifact` pair - it never re-launches a browser, re-resolves a
365
+ target, or reimplements clipping/relationship/scroll-owner derivation.
366
+ Unexpected-change derivation reads `ComparisonArtifact.differences` only
367
+ (which already includes one difference per relationship change), so a single
368
+ logical transition is never double-counted.
369
+
370
+ ## v0.5 contract and evaluation persistence (shipped as part of this release)
371
+
372
+ Persistence consumes the frozen v0.5 domain above; it never redefines it.
373
+ `src/artifacts/frontendContractArtifactWriter.ts`/`frontendContractArtifactReader.ts`
374
+ persist and read both `PersistentBaselineContract` and `PerChangeContract`
375
+ symmetrically (both already share `CONTRACT_ARTIFACT_KIND`/`CONTRACT_SCHEMA_VERSION`,
376
+ so one writer/reader pair serves both contract classes) as
377
+ `<outputLocation>/<baselineId|contractId>/manifest.json`, following the same
378
+ atomic-write discipline as `artifacts/artifactWriter.ts`/`artifacts/comparisonArtifactWriter.ts`
379
+ (sibling temporary directory, then one atomic rename; an existing directory at
380
+ the final identity is a genuine collision and is rejected, never overwritten -
381
+ prior baseline history is never rewritten). `src/artifacts/comparisonArtifactReader.ts`
382
+ is a new Batch 3 addition (no comparison reader existed before) mirroring
383
+ `artifacts/artifactReader.ts`'s discipline exactly, changing no comparison
384
+ semantics and keeping comparison schema `1.0.0`.
385
+
386
+ **Evaluation artifact envelope**: Batch 1 froze the evaluation-result
387
+ vocabulary (`ClauseEvaluationResult`, `OverallVerdict`) but not a persistable
388
+ envelope, so `src/domain/frontendContractEvaluationArtifact.ts` adds exactly
389
+ that - `artifactKind: "my-frontend-observer/frontend-contract-evaluation"`,
390
+ `schemaVersion: "1.0.0"` (its own independent family, distinct from
391
+ observation/comparison/frontend-contract), an `evaluationId`/`evaluationRequestId`
392
+ pair, bounded `before`/`after` source-observation references, and
393
+ `comparisonId`/`comparisonRequestId` plus `contracts: {baselineId,
394
+ contractId}` references - never an embedded `ObservationArtifact` or copied
395
+ screenshot. It reuses `ClauseEvaluationResult`/`OverallVerdict`/
396
+ `UnexpectedChangeResult` unchanged and contains no evaluation logic itself.
397
+ `evaluationRequestId` is a deterministic function of `{baselineId,
398
+ contractId, beforeObservationId, afterObservationId, comparisonRequestId}`
399
+ (`frontendContractIdentity.ts#buildFrontendContractEvaluationRequestIdentity` -
400
+ deliberately `comparisonRequestId`, not the fresh-per-execution
401
+ `comparisonId`, so semantically identical evaluations share an identity);
402
+ `evaluationId` reuses the existing generic `buildFrontendContractInstanceIdentity`
403
+ unchanged. `src/artifacts/frontendContractEvaluationArtifactWriter.ts`/
404
+ `frontendContractEvaluationArtifactReader.ts` persist/read it with the same
405
+ atomic-write discipline as above.
406
+
407
+ **Application seam**: `src/application/frontendContractEvaluationService.ts#evaluateAndPersist`
408
+ calls the existing pure `evaluateFrontendContract` exactly once and - only
409
+ for a structurally constructible result, whether the verdict is `PASS` or
410
+ `FAIL` - persists exactly one evaluation artifact; an `{ok: false}` evaluator
411
+ result (evidence could not be constructed into an evaluation at all) is never
412
+ persisted as a fabricated artifact. `evaluateAndPersistFromArtifactRoots` is
413
+ the future-CLI-facing wrapper: it reads two observations through the existing
414
+ `readObservationArtifact` (never a second observation reader), the
415
+ comparison and the two contracts through the readers above, then delegates
416
+ to `evaluateAndPersist` exactly once.
417
+
418
+ ## v0.5 public contract/evaluation commands (shipped as part of this release)
419
+
420
+ Three public commands expose the persistence/evaluation contract above (see
421
+ `docs/COMMANDS.md` for exact flags/output/exit behavior, not duplicated
422
+ here):
423
+
424
+ - `approve-baseline` → `frontendContractPersistenceService.ts#approveAndPersistBaseline`
425
+ → validates a `PersistentBaselineContract` and its `sourceObservation`
426
+ coherence against a supplied observation artifact → persists via
427
+ `frontendContractArtifactWriter.ts`. The only baseline-approval act in the
428
+ observer.
429
+ - `save-change-contract` → `frontendContractPersistenceService.ts#persistPerChangeContract`
430
+ → validates a `PerChangeContract` (rejecting a baseline contract, an
431
+ authored `unexpected` category, or any other structural violation) →
432
+ persists via the same writer. Persistence only, never approval.
433
+ - `evaluate-contract` → `frontendContractEvaluationService.ts#evaluateAndPersistFromArtifactRoots`
434
+ → `evaluateFrontendContract` exactly once → `frontendContractEvaluationArtifactWriter.ts`
435
+ exactly once. `--enforce` affects only the process exit status for an
436
+ already-persisted `FAIL` verdict.
437
+
438
+ No command infers baseline approval or supersession automatically - not
439
+ `compare`, not a `PASS` evaluation, not any artifact writer.
440
+
441
+ This full command sequence is proven against real Chromium observations (not
442
+ hand-constructed artifacts) - see "v0.5 real-browser workflow proof" below.
443
+
444
+ ## v0.5 real-browser workflow proof (shipped as part of this release)
445
+
446
+ `tests/browser/cliFrontendContracts.test.ts` and
447
+ `scripts/dev/builtCliFrontendContractsBrowserSmoke.mjs` drive the complete
448
+ `observe` → `approve-baseline` → `save-change-contract` → `observe` →
449
+ `compare` → `evaluate-contract` sequence against a real disposable local HTTP
450
+ fixture and real Chromium, proving two scenarios:
451
+
452
+ - a fully successful contract change - a real observed navigation-width
453
+ decrease and workspace-width increase, both satisfying their authored
454
+ `requested`/`expected-dependent` clauses, an unchanged `protected` rail
455
+ width, and an unclipped `preserved` navigation - overall `PASS`;
456
+ - the "milestone signature" failure - the same locally successful requested
457
+ change (navigation shrinks, workspace expands, both still `pass`)
458
+ co-occurring with a genuine `protected` right-rail width regression (a real
459
+ `resized` comparison difference) and a genuine `preserved` navigation
460
+ clipping regression (a real `clipping-changed` difference, `not-clipped` →
461
+ `clipped`) - overall `FAIL`.
462
+
463
+ Both scenarios confirm: `--enforce` changes only the process exit status
464
+ (`0` without it, nonzero with it) for the identical persisted
465
+ `evaluationRequestId`/`clauseResults`; every source observation and
466
+ comparison artifact is byte-identical before and after evaluation; the
467
+ evaluation directory contains `manifest.json` only (no copied screenshot);
468
+ and no operational filesystem path is ever serialized into a persisted
469
+ manifest. This is real-browser evidence layered on top of the CLI-level
470
+ proof in `tests/unit/cliFrontendContracts.test.ts` and the Chromium-free
471
+ `scripts/dev/builtCliFrontendContractsSmoke.mjs` - it does not replace them.
472
+
473
+ ## v0.6 bounded agent context and correlation contract (released as `0.6.0`)
474
+
475
+ **Current status: released as package version `0.6.0`, tag `v0.6.0`, from
476
+ the canonical `canonicalization/v0.6` lineage.** Bounded-agent-context is a new, independent
477
+ artifact-kind family, schema `1.0.0` (`BOUNDED_AGENT_CONTEXT_ARTIFACT_KIND =
478
+ "my-frontend-observer/bounded-agent-context"`) - never a bump to
479
+ observation/comparison/frontend-contract/evaluation schemas, which remain
480
+ `1.2.0`/`1.0.0`/`1.0.0`/`1.0.0` respectively. Unlike those families, there is
481
+ **no disk artifact writer/reader** for bounded-agent-context: it is a pure
482
+ programmatic contract and derivation layer, exported from `src/index.ts`
483
+ only.
484
+
485
+ **Bounded runtime projection**
486
+ (`src/domain/boundedAgentContextProjection.ts#projectBoundedAgentContext`)
487
+ produces a `BoundedRuntimeTargetProjection` from already-captured v0.1-v0.5
488
+ evidence, containing: page/viewport identity; stable target identities;
489
+ important geometry and runtime behavior; layout/behavior relationships;
490
+ before/after differences; contract clause results; requested/expected-
491
+ dependent/protected/preserved scope - reusing `src/domain/
492
+ frontendContracts.ts`'s existing clause types verbatim, never a
493
+ reimplementation; diagnostics; screenshot/artifact references; provenance;
494
+ and explicit `OmissionRecord`/`TruncationRecord` metadata with bounded
495
+ aggregate-cap summarization once a limit is reached.
496
+
497
+ **Adequacy**: every projection carries an `Adequacy` value
498
+ (`adequate`/`partial`/`inadequate`) plus a structured, closed
499
+ `ADEQUACY_REASON_CODES` vocabulary - evidence existing is not itself
500
+ adequacy; a required omission or an `incomparable`/unavailable upstream
501
+ source is reflected honestly rather than silently reported as sufficient.
502
+
503
+ **Runtime/static correlation**
504
+ (`src/domain/boundedAgentContextCorrelation.ts#deriveRuntimeStaticCorrelations`/
505
+ `attachRuntimeStaticCorrelations`) evaluates each stable runtime target
506
+ against caller-supplied candidate static-evidence records into exactly one of
507
+ three outcomes: `correlated`, `ambiguous` (multiple competing candidates,
508
+ all preserved and visible - never silently resolved to one), or
509
+ `unavailable` (no supported candidate). The module accepts only plain,
510
+ already-retrieved candidate records and has no dependency on
511
+ `@dailephd/my-dev-kit` - the audit preceding implementation found no generic
512
+ static-side retrieval capability actually missing (see `docs/ROADMAP.md` v0.6
513
+ "Dependency direction"). A runtime target identity is carried through
514
+ verbatim; correlation never produces a `sourceOwner`/`causedBy`-shaped field,
515
+ so a stable runtime identity is never silently reported as source ownership.
516
+
517
+ **Identity**: `src/domain/boundedAgentContextIdentity.ts#buildBoundedAgentContextRequestIdentity`/
518
+ `buildBoundedAgentContextInstanceIdentity` follow the same
519
+ canonicalize+sha256(+opaque-nonce) pattern as `comparisonIdentity.ts`/
520
+ `frontendContractIdentity.ts`: a deterministic logical identity distinct from
521
+ a fresh per-execution instance identity.
522
+
523
+ **Export/public boundary**: `src/index.ts` exports the complete
524
+ bounded-agent-context/correlation type and function surface as a
525
+ programmatic library contract. There is no CLI command (`observe`/`compare`/
526
+ `approve-baseline`/`save-change-contract`/`evaluate-contract` remain the only
527
+ public commands) and no orchestrator/lab code in this repository - bounded
528
+ runtime-evidence consumption by `my-dev-kit-orchestrator` and exact
529
+ readers/fixtures/evaluation in `my-dev-kit-lab` are separate sibling-
530
+ repository deliverables outside `my-frontend-observer`'s public surface.
531
+
532
+ **Compatibility evidence**: cross-repository neutral verification (observer
533
+ `514bf3bb513764815a0a5b9e508d5836aa7d7fd8`, orchestrator `9473e4c`, lab
534
+ `271e72c`) passed with 6/6 requirement coverage and no known product
535
+ blockers; on the canonical worktree, `npm run typecheck`, `npm run lint`,
536
+ `npm test` (627 tests), `npm run test:browser` (120 tests), `npm run
537
+ test:security`, `npm run build`, and `npm run check:docs` all pass.
538
+
539
+ ## v0.7 external visual-reference contract direction (preserved through released v0.10)
540
+
541
+ External visual-reference support is released as package version `0.7.0`
542
+ (see "v0.7 Prompt 1" through "v0.7 Prompt 8" below for the exact contract).
543
+ The exact public type names, artifact kinds, schema versions, persistence
544
+ layout, and command/programmatic entry points were designed during v0.7
545
+ implementation from current repository precedent, following the constraints
546
+ below. v0.8 (released as package version `0.8.0` - see
547
+ `docs/CURRENT_STATE.md`) has preserved them. v0.9 is released and preserves
548
+ them. The implemented and released v0.10 workflow preserves them too.
549
+
550
+ **Distinct evidence domain**: an external reference is desired-design evidence,
551
+ not an `ObservationArtifact` and not the "before" side of a v0.4
552
+ `ComparisonArtifact`. Reference design vs candidate is distinct from both
553
+ before vs after comparison and frontend-contract evaluation. The implementation
554
+ must not fake this distinction by wrapping a raster image in an observation
555
+ shape.
556
+
557
+ **Reference identity and provenance**: a future reference contract must preserve
558
+ a deterministic logical reference identity/version where appropriate, source
559
+ image reference plus dimensions/format, provenance, bounded region definitions,
560
+ applicable viewport/theme/application-state identity, authored design intent,
561
+ relationship/style evidence where supported, limits/diagnostics, and approval/
562
+ supersession history. Operational filesystem paths must not become semantic
563
+ identity. A raw imported image never silently becomes an approved active
564
+ reference.
565
+
566
+ **Reference regions and runtime targets stay distinct**: a reference region
567
+ must have its own identity and coordinate semantics. Reference-region to runtime-
568
+ target association must be explicit and capable of representing ambiguity or
569
+ unavailability. Runtime target identity and reference identity must never
570
+ silently become static source ownership; static association still goes through
571
+ the v0.6 runtime/static correlation boundary.
572
+
573
+ **Applicability before fidelity**: viewport, theme, application state, and
574
+ other selected compatibility dimensions must be evaluated before ordinary
575
+ reference/candidate differences are produced. If the reference and candidate
576
+ represent different intended states, the result must be explicitly incompatible
577
+ or incomparable rather than filled with fabricated visual failures. Planning
578
+ should reuse or extend the canonical v0.4 comparability conventions where they
579
+ mean the same thing rather than invent an unrelated reference-only state model.
580
+
581
+ **Canonical contract semantics remain authoritative**: executable reference-
582
+ derived requirements must map into the existing v0.5 authored categories
583
+ `requested`, `expected-dependent`, `protected`, or `preserved`. The derived-only
584
+ `unexpected` classification remains derived-only. Informational or unassessed
585
+ reference evidence may stay outside executable contract evaluation until
586
+ explicitly promoted. A second reference-only PASS/FAIL taxonomy is forbidden.
587
+
588
+ **Tolerance separation**: reference-fidelity tolerances are not automatically
589
+ the same as v0.4 `ComparisonConfig.geometryTolerancePx` or v0.5 contract
590
+ tolerances. Planning must define property-specific semantics for reference
591
+ geometry, spacing, selected style evidence, text/font rendering differences,
592
+ asset-sensitive regions, and optional image similarity. One global pixel-perfect
593
+ threshold is not an acceptable contract.
594
+
595
+ **Structured evidence first**: geometry, relationships, authored requirements,
596
+ applicability, provenance, and selected bounded style/asset evidence remain
597
+ inspectable primary evidence. Screenshot-region or image-similarity evidence may
598
+ supplement them where reliable, but pixel similarity alone must not determine
599
+ success and must never override active baseline/per-change contracts.
600
+
601
+ **Bounded correction evidence**: future reference/candidate results must support
602
+ a bounded projection suitable for coding-agent correction, such as reference
603
+ measurement, candidate measurement, delta, failed relationship/style condition,
604
+ relevant reference/runtime identities, provenance, and active protected/
605
+ preserved constraints. Heavy reference image bytes should be referenced, not
606
+ copied into every downstream context packet.
607
+
608
+ **Approval and supersession**: reference import, reference approval, baseline
609
+ approval, reference supersession, and baseline supersession are separate acts.
610
+ A reference-fidelity `PASS`, a frontend-contract `PASS`, or a successful
611
+ before/after comparison must not silently approve or replace any reference or
612
+ baseline.
613
+
614
+ The v0.8 viewer, released as package version `0.8.0`, consumes this v0.7
615
+ reference/evaluation contract exactly as required - it creates no UI-only
616
+ reference model (see `docs/ARCHITECTURE.md` "v0.8 Batch 5"/"v0.8 Batch 6"
617
+ and `docs/reports/v0.8-reference-candidate-inspection-batch5.md`). v0.9
618
+ annotations (released in `0.9.0`) originate from runtime
619
+ screenshots or external references, preserve which source identity and
620
+ coordinate system they belong to, and feed the same canonical contract and
621
+ reference semantics - see "v0.9 visual annotation contract" below. v0.10
622
+ combines both entry modes into the full correction/approval workflow.
623
+
624
+ ## v0.9 visual annotation contract (released in 0.9.0)
625
+
626
+ v0.9 is released as `@dailephd/my-frontend-observer@0.9.0`.
627
+
628
+ **Artifact**: `VisualAnnotationArtifact`, artifact kind
629
+ `my-frontend-observer/visual-annotation`, schema version `1.0.0`. It stores one
630
+ exact canonical source (a runtime observation, or an imported or approved
631
+ external reference), the source coordinate space (runtime CSS pixels or
632
+ reference-image pixels), and bounded structured items. Each item has a stable
633
+ `annotationItemId`, one mark (point, rectangle, line, arrow, or note), an
634
+ optional explicit association, and an interpretation. A revision sets
635
+ `supersedesAnnotationId` and never rewrites its parent. The overlay SVG is
636
+ derived from the artifact and verified before it is served.
637
+
638
+ **An annotation is not a contract.** Saving an annotation never creates a
639
+ contract clause, a reference requirement, or a PASS/FAIL rule. Marks and
640
+ visible pixels are evidence. Overlap between a mark and a target or region is
641
+ not ownership and never creates an association.
642
+
643
+ **Interpretation states**: `uninterpreted`, `candidate`, and `confirmed`.
644
+ Confirmation is explicit and records `confirmedAt`. Editing the mark,
645
+ association, or intent of a confirmed item withdraws the confirmation.
646
+
647
+ **Runtime intent to contract**:
648
+
649
+ - Only selected, confirmed, supported runtime intent is promoted. Promotion
650
+ creates one normal canonical `PerChangeContract` through the existing
651
+ contract persistence service.
652
+ - Supported mappings use the existing `ContractPrimitive` vocabulary only.
653
+ `move` maps to `property-increases`/`property-decreases` on `x` or `y`.
654
+ `resize` maps to `property-increases`/`property-decreases` on `width` or
655
+ `height`. `preserve` maps to `property-unchanged-within-tolerance` for a
656
+ target property, or to `relationship-unchanged` for an explicitly associated
657
+ canonical relationship.
658
+ - Categories are the canonical `requested`, `expected-dependent` (with a
659
+ required `required` or `permitted` mode), `protected`, and `preserved`.
660
+ `unexpected` is never authored; it stays evaluator-derived.
661
+ - `remove` can be confirmed and saved, but it is not promotable in v0.9. The
662
+ contract vocabulary has no target-absent primitive, and no approximate
663
+ clause is fabricated.
664
+ - `inspect` intent and notes are informational and never promoted.
665
+ - Each clause's `supportingEvidence` records the annotation source and item
666
+ paths. Promotion never activates the contract unless explicitly requested,
667
+ and it never approves a baseline.
668
+
669
+ **Reference intent to a new reference revision**:
670
+
671
+ - Only selected, confirmed `reference-region` (`create` or `refine`) and
672
+ `reference-requirement` items are materialized. `inspect` and
673
+ `asset-sensitive` intent stay informational.
674
+ - The result is a new imported `ExternalReferenceArtifact`, created through the
675
+ existing canonical import service. It supersedes the selected source
676
+ reference (the approved reference id when the source is approved) and has
677
+ lifecycle `imported`. It is never approved automatically.
678
+ - Source regions keep their order. A refine replaces the rectangle of an
679
+ existing source region and keeps its id. Creates are appended in selection
680
+ order. Duplicate creates, duplicate refines, and create-then-refine in one
681
+ request are rejected.
682
+ - Source requirements stay first with identical recomputed ids. Selected
683
+ requirements are appended in selection order and validated against the final
684
+ regions. A selected relationship requirement must still hold for the final
685
+ geometry. Measurement requirements store only the subject and tolerance.
686
+ - Applicability, label, and the exact source image bytes are preserved. The
687
+ source reference is never modified, and project reference acceptance is not
688
+ changed.
689
+
690
+ Existing contract, reference relationship, adequacy, and fidelity evaluators
691
+ remain the only source of verdicts. v0.9 adds no annotation evaluator.
692
+
693
+ ## Approved v0.1 design inputs
694
+
695
+ The historical greenfield scaffold plan recorded these v0.1 design decisions:
696
+
697
+ - artifact kind `my-frontend-observer/observation`;
698
+ - schema version `1.0.0`, independent of package version;
699
+ - one portable directory containing `manifest.json`, `evidence.json`, and
700
+ `screenshot.png`;
701
+ - evidence states `available`, `unavailable`, `not-applicable`, and `partial`;
702
+ - evidence sources `browser`, `computed-browser`, and `derived`;
703
+ - bounded explicitly requested targets, provenance, diagnostics, completion
704
+ state, limits, and relative artifact references.
705
+
706
+ These were planning inputs only at the time they were recorded. As shown in
707
+ "Current contracts" above, the implemented contract matches them except for
708
+ the file layout: there is no separate `evidence.json` - page/target evidence
709
+ is embedded directly inside `manifest.json`.
710
+
711
+ Comparison and relationship contracts belong to v0.4, and canonical
712
+ change-scope contracts belong to v0.5 - see "v0.5 frontend contract and
713
+ evaluation" above for the full shipped contract model, identity, evaluation
714
+ engine, persistence, baseline approval, and CLI exposure. Bounded
715
+ agent-context and runtime/static correlation contracts are v0.6 - see "v0.6
716
+ bounded agent context and correlation contract" above for the full released
717
+ model. The text/config-driven coding-agent review plus non-graphical external
718
+ visual-reference foundation is v0.7 - see "v0.7 Prompt 1 external-reference
719
+ artifact contract" below for the foundation layer implemented so far. Viewer
720
+ consumption of that reference model is released in v0.8 (see "v0.7 external
721
+ visual-reference contract direction" above); dual-context annotation follows
722
+ in v0.9; both visual entry modes converge with the existing workflow in
723
+ v0.10.
724
+
725
+ ## v0.7 Prompt 1 external-reference artifact contract
726
+
727
+ Released as `0.7.0`. This is the foundation layer only: identity,
728
+ provenance, bounded image metadata, and a two-state lifecycle for one
729
+ externally supplied design-reference image. It implements no region,
730
+ geometry, relationship, requirement, tolerance, binding, or fidelity-
731
+ evaluation contract - those belong to later v0.7 prompts.
732
+
733
+ An external reference is a distinct evidence root, not a variant of
734
+ `ObservationArtifact`: it never reuses `ARTIFACT_KIND`/`SCHEMA_VERSION`
735
+ (observation), `COMPARISON_ARTIFACT_KIND`, or `CONTRACT_ARTIFACT_KIND`, and
736
+ those existing types gain no new field from this contract.
737
+
738
+ ```ts
739
+ const EXTERNAL_REFERENCE_ARTIFACT_KIND = 'my-frontend-observer/external-reference';
740
+ const EXTERNAL_REFERENCE_SCHEMA_VERSION = '1.0.0'; // independent of package.json version and every other family's schema version
741
+
742
+ type ExternalReferenceImageFormat = 'png' | 'jpeg' | 'webp';
743
+
744
+ interface ExternalReferenceImageReference {
745
+ path: string; // bare relative filename within the artifact's own directory
746
+ format: ExternalReferenceImageFormat;
747
+ width: number;
748
+ height: number;
749
+ byteLength: number;
750
+ sha256: string; // identity-bearing content hash of the raw image bytes
751
+ }
752
+
753
+ // Points back to the imported artifact that owns the image, without copying its bytes - mirrors ComparisonSourceObservationReference.
754
+ interface ExternalReferenceSourceReference {
755
+ referenceId: string;
756
+ referenceRequestId: string;
757
+ producer: { name: 'my-frontend-observer'; version: string };
758
+ schemaVersion: '1.0.0';
759
+ image: ExternalReferenceImageReference;
760
+ }
761
+
762
+ // Exactly two persisted states - no literal 'superseded' variant (see below).
763
+ type ExternalReferenceLifecycleState = { state: 'imported' } | { state: 'approved'; approvedAt: string };
764
+
765
+ interface ExternalReferenceArtifactBase {
766
+ artifactKind: 'my-frontend-observer/external-reference';
767
+ schemaVersion: '1.0.0';
768
+ referenceRequestId: string; // deterministic logical identity - shared by an imported artifact and every artifact produced by approving it
769
+ referenceId: string; // fresh per-persisted-instance identity
770
+ producer: { name: 'my-frontend-observer'; version: string };
771
+ provenance: { importedAt: string; label?: string };
772
+ supersedesReferenceId?: string; // explicit, forward-only supersession of a prior reference's referenceId
773
+ diagnostics: Diagnostic[];
774
+ completion: CompletionState;
775
+ }
776
+
777
+ // lifecycle.state === 'imported': owns the image.
778
+ interface ImportedExternalReferenceArtifact extends ExternalReferenceArtifactBase {
779
+ lifecycle: { state: 'imported' };
780
+ image: ExternalReferenceImageReference;
781
+ }
782
+
783
+ // lifecycle.state === 'approved': references, never copies, the imported artifact's image.
784
+ interface ApprovedExternalReferenceArtifact extends ExternalReferenceArtifactBase {
785
+ lifecycle: { state: 'approved'; approvedAt: string };
786
+ sourceReference: ExternalReferenceSourceReference;
787
+ }
788
+
789
+ type ExternalReferenceArtifact = ImportedExternalReferenceArtifact | ApprovedExternalReferenceArtifact;
790
+ ```
791
+
792
+ Key rules:
793
+
794
+ - `referenceRequestId` is a pure function of `{imageSha256, format, width,
795
+ height, supersedesReferenceId}` only - never a filesystem path, output
796
+ location, label, or timestamp. Byte-identical image content imported from a
797
+ different operational root produces the same `referenceRequestId`;
798
+ changing any of those fields changes it.
799
+ - `referenceId` is fresh (nonce-based) on every persisted write, including
800
+ every approval of an already-imported reference.
801
+ - Importing an image never approves it (`lifecycle.state` is always
802
+ `'imported'` immediately after import, regardless of a supplied label or
803
+ supersession target). Approval is a single explicit act
804
+ (`approveExternalReference`, mirroring `approveAndPersistBaseline`) that
805
+ refuses anything not currently in the `'imported'` state.
806
+ - Approving persists a *new* artifact instance (same `referenceRequestId`,
807
+ fresh `referenceId`) carrying a `sourceReference` back to the imported
808
+ artifact - it never mutates the imported artifact's own manifest, and never
809
+ copies the image bytes a second time.
810
+ - Supersession is represented only as a forward pointer
811
+ (`supersedesReferenceId` on the newer artifact); there is deliberately no
812
+ literal `'superseded'` lifecycle state, so an existing persisted artifact's
813
+ own manifest is never rewritten - immutability holds unconditionally rather
814
+ than depending on careful mutation discipline.
815
+ - Supported formats are frozen to exactly `png`/`jpeg`/`webp`, detected from
816
+ header/magic bytes only (never a caller-declared file extension), bounded
817
+ to `EXTERNAL_REFERENCE_MAX_IMAGE_BYTES` (20,000,000 bytes) and
818
+ `[EXTERNAL_REFERENCE_MIN_DIMENSION_PX, EXTERNAL_REFERENCE_MAX_DIMENSION_PX]`
819
+ (`[1, 8192]`) pixels per side. No OCR, no raster decode, no computer
820
+ vision, no automatic region detection.
821
+
822
+ Persisted as `<outputLocation>/<referenceId>/manifest.json` (+
823
+ `reference.<ext>` for an `'imported'` artifact only), via the same atomic
824
+ temp-dir-then-rename discipline as every other artifact family
825
+ (`src/artifacts/externalReferenceArtifactWriter.ts` /
826
+ `externalReferenceArtifactReader.ts`). CLI: `import-reference <image-file>
827
+ --output <dir> [--label] [--supersedes <root>]` and `approve-reference
828
+ --reference <root> --output <dir> [--supersedes <root>]`.
829
+
830
+ ## v0.7 Prompt 2 explicit reference regions and relationships
831
+
832
+ Released as `0.7.0`. Additive extension of the Prompt 1 contract above:
833
+ one new, optional `regions?: ReferenceRegion[]` field on
834
+ `ExternalReferenceArtifact` (both lifecycle variants), plus a pure,
835
+ non-persisted relationship-derivation capability. No schema version bump -
836
+ `EXTERNAL_REFERENCE_SCHEMA_VERSION` remains `'1.0.0'`, because the field is
837
+ genuinely optional/additive and every Prompt 1 artifact (which predates this
838
+ field entirely) remains valid without it.
839
+
840
+ ```ts
841
+ // domain/externalReferenceRegions.ts
842
+ interface ReferenceRegionRectangle { x: number; y: number; width: number; height: number; }
843
+ interface ReferenceRegion { id: string; rectangle: ReferenceRegionRectangle; }
844
+
845
+ // Pure derived geometry - never persisted, always recomputed, so it can never drift from the rectangle above.
846
+ interface ReferenceRegionGeometry {
847
+ x: number; y: number; width: number; height: number;
848
+ right: number; bottom: number; centerX: number; centerY: number;
849
+ }
850
+
851
+ const REFERENCE_REGION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/; // same convention as request/request.ts's target-name pattern
852
+ const MAX_REFERENCE_REGIONS = 20; // same bound value as request/request.ts's MAX_TARGETS - independently owned, coincidentally equal
853
+ ```
854
+
855
+ Region coordinate semantics: origin at the reference image's top-left
856
+ corner, x increasing rightward, y increasing downward, unit is
857
+ reference-image pixels (explicitly not CSS pixels - a static image has no
858
+ CSS box model), coordinates may be fractional. A region's rectangle must lie
859
+ entirely within its owning image's own already-validated
860
+ width/height - out-of-bounds geometry is rejected outright, never clamped.
861
+
862
+ Key rules:
863
+
864
+ - Only `{x, y, width, height}` is canonical/authored. `right`, `bottom`,
865
+ `centerX`, `centerY` are pure calculations over it
866
+ (`deriveReferenceRegionGeometry`) - never a second, potentially-drifting
867
+ stored copy of the same fact.
868
+ - Region content is identity-bearing:
869
+ `buildExternalReferenceRequestIdentity` gained an additive, optional
870
+ trailing `regions` parameter. Omitting it entirely (every Prompt 1 call
871
+ site, and any Prompt 2 call that legitimately has no regions) produces the
872
+ byte-identical hash Prompt 1 already produced - the parameter is left out
873
+ of the hashed view rather than defaulted to `null`, unlike
874
+ `supersedesReferenceId`. Authored region order participates in identity
875
+ (arrays are never reordered by the shared `canonicalize()`), mirroring
876
+ `domain/identity.ts`'s treatment of configured targets.
877
+ - Region IDs are unique case-insensitively within one artifact (mirroring
878
+ `request/request.ts`'s target-name dedup convention exactly).
879
+ - `import-reference` gained an optional `--regions-file <json-file>` of the
880
+ form `{ "regions": [...] }` (same object-root-wrapper convention as
881
+ `--targets-file`); a legacy invocation without it behaves exactly as in
882
+ Prompt 1. `approve-reference` carries an imported artifact's `regions`
883
+ forward verbatim (never re-validated, never re-derived, never dropped) -
884
+ approval never adds, removes, or edits regions.
885
+ - One new diagnostic code, `invalid-reference-region` (error), covers every
886
+ region-validation failure (missing/duplicate/malformed id,
887
+ non-finite/negative/zero geometry, out-of-image-bounds, over the bounded
888
+ region count) - deliberately not split into several codes, per the
889
+ "don't proliferate diagnostics" convention.
890
+
891
+ Reference-region relationships (`domain/externalReferenceRegionRelationships.ts`)
892
+ reuse the exact same pure, tolerance-aware geometry predicates that
893
+ `domain/relationships.ts#deriveLayoutRelationships` uses for runtime targets
894
+ (`horizontalOrderOf`/`verticalOrderOf`/`areaOverlapOf`/`relativeWidthOf`/
895
+ `geometricFitOf`/`verticalSequenceOf`, now exported additively from that
896
+ module with unchanged formulas) and the same `PairwiseRelationshipKind`
897
+ vocabulary and `EvidenceReference` type - never a duplicated or
898
+ reinterpreted copy. Only the six geometry-only families apply (horizontal
899
+ order, vertical order, area overlap, relative width, geometric fit, vertical
900
+ sequencing); DOM containment, scroll ownership, runtime visibility, and
901
+ page-width-vs-viewport are runtime/browser concepts with no reference-image
902
+ equivalent and are not reused. `fits-inside`/`does-not-fit-inside` is
903
+ geometry-only fit - it never claims DOM containment, which an external image
904
+ cannot expose.
905
+
906
+ ```ts
907
+ interface ReferenceRegionRelationship {
908
+ kind: PairwiseRelationshipKind;
909
+ subjectRegion: string; // deliberately distinct field name from PairwiseLayoutRelationship's subjectTarget
910
+ relatedRegion: string;
911
+ evidence: EvidenceReference[]; // e.g. { path: 'regions.header.rectangle' } - never a targetEvidence/browser path
912
+ }
913
+ ```
914
+
915
+ Relationships are **not persisted** on the artifact - `deriveReferenceRegionRelationships(referenceRequestId, regions, options)`
916
+ is a pure, deterministic, synchronous function any caller (a future prompt,
917
+ a test) calls on demand against an artifact's own `regions` field, avoiding
918
+ any possibility of a persisted relationship graph drifting from the region
919
+ data it was derived from. Bounded at `MAX_REFERENCE_REGIONS` regions ->
920
+ `MAX_REFERENCE_REGION_PAIRS` pairs `x` 6 families =
921
+ `MAX_REFERENCE_REGION_RELATIONSHIP_RECORDS` records maximum - the same
922
+ bounding shape as `relationships.ts`'s `MAX_PAIRWISE_RELATIONSHIP_RECORDS`.
923
+ This is a maximum capacity, never a required minimum region count - there is
924
+ no contract requiring any specific number of authored regions.
925
+
926
+ A reference relationship is a fact about the reference image's geometry
927
+ only. It is not a design requirement, not a pass/fail verdict, and does not
928
+ claim a runtime target or source owner exists - see
929
+ `docs/WORKFLOWS.md` "Current external-reference foundation workflow" for
930
+ where those later concepts (Prompt 3+) will attach.
931
+
932
+ ## v0.7 Prompt 3 selected design requirements, tolerance semantics, and reference-evidence adequacy
933
+
934
+ Released as `0.7.0`. Additive extension of the Prompt 1/2 contracts
935
+ above: one new, optional `requirements?: ExternalReferenceRequirement[]`
936
+ field on `ExternalReferenceArtifact` (both lifecycle variants). No schema
937
+ version bump - same reasoning as Prompt 2's `regions` field.
938
+
939
+ **Central distinction**: a region's geometry is REFERENCE EVIDENCE -
940
+ everything visibly/measurably present in the image. A requirement is
941
+ SELECTED DESIGN INTENT - only what the user/configuration explicitly chose
942
+ as mattering for later candidate evaluation. Nothing in this repository ever
943
+ turns a region property or a derived relationship into a requirement
944
+ automatically.
945
+
946
+ ```ts
947
+ // domain/externalReferenceRequirements.ts
948
+
949
+ // Reused directly from domain/frontendContracts.ts - not reinvented as a
950
+ // "reference-only" taxonomy; that type carries no runtime-only coupling.
951
+ // 'unexpected' remains impossible to author (not a member of this union).
952
+ type AuthoredChangeScopeCategory = 'requested' | 'expected-dependent' | 'protected' | 'preserved';
953
+ type ExpectedDependentMode = 'required' | 'permitted'; // required only (and exactly) when category === 'expected-dependent'
954
+
955
+ type ReferenceRequirementRegionProperty = 'x' | 'y' | 'width' | 'height' | 'right' | 'bottom' | 'centerX' | 'centerY'; // exactly ReferenceRegionGeometry's own fields
956
+ type ReferenceRequirementMeasurement = 'vertical-gap' | 'horizontal-gap' | 'center-x-delta' | 'center-y-delta' | 'left-edge-delta' | 'right-edge-delta';
957
+
958
+ type ReferenceRequirementSubject =
959
+ | { kind: 'region-property'; region: string; property: ReferenceRequirementRegionProperty }
960
+ | { kind: 'region-relationship'; subjectRegion: string; relatedRegion: string; relationship: PairwiseRelationshipKind } // reused from relationships.ts - geometry-only families only
961
+ | { kind: 'region-measurement'; subjectRegion: string; relatedRegion: string; measurement: ReferenceRequirementMeasurement };
962
+
963
+ // Deliberately NOT a reuse of frontendContracts.ts's ContractTolerance: that
964
+ // type's 'absolute-px' is implicitly runtime/CSS pixels. Reference-image
965
+ // pixels are a distinct, explicitly-labeled unit - nothing here assumes
966
+ // 1 reference pixel = 1 CSS pixel (Prompt 6 will need an explicit mapping).
967
+ type ReferenceRequirementTolerance = { kind: 'exact' } | { kind: 'absolute-reference-px'; amount: number } | { kind: 'percent'; amount: number };
968
+
969
+ interface ExternalReferenceRequirement {
970
+ requirementId: string; // system-computed from {subject, category, expectedDependentMode, tolerance} only - never authored
971
+ category: AuthoredChangeScopeCategory;
972
+ expectedDependentMode?: ExpectedDependentMode;
973
+ subject: ReferenceRequirementSubject;
974
+ tolerance?: ReferenceRequirementTolerance; // required for region-property/region-measurement; must be absent for region-relationship
975
+ }
976
+ ```
977
+
978
+ Key rules:
979
+
980
+ - Requirement identity (`requirementId`) is always system-computed
981
+ (`buildReferenceRequirementIdentity`, mirroring
982
+ `frontendContractIdentity.ts#buildClauseIdentity`'s exact shape) - the raw
983
+ authored input (`RawReferenceRequirement`) has no `requirementId` field at
984
+ all, and supplying one is a validation error. Unlike v0.5's
985
+ `BaselineClause`/`PerChangeClause` (which need an author-visible `clauseId`
986
+ for cross-document `supersedesBaselineClauseIds` references), Prompt 3
987
+ requirements have no cross-document reference need yet, so trusting an
988
+ authored id would only invite drift between a user-typed id and the
989
+ content it claims to identify.
990
+ - The reference-side expected value/relationship is never stored on the
991
+ requirement or the artifact - `deriveReferenceRequirementExpectation()` is
992
+ a pure function computed on demand from the artifact's own `regions`,
993
+ eliminating the exact drift risk of persisting e.g. `width: 424` alongside
994
+ a region whose rectangle could (in principle) later disagree with it.
995
+ - A requirement referencing a region id that does not exist in the
996
+ artifact's own `regions` is a **structural validation failure** (rejected
997
+ at construction/import time), never merely "unavailable" reference
998
+ evidence - `isValidReferenceRequirements` checks this before any
999
+ requirement reaches adequacy computation.
1000
+ - **Duplicate/conflicting subject rule**: no two requirements in one
1001
+ collection may share the same structural subject (same region+property,
1002
+ or the same unordered region pair + relationship, or + measurement),
1003
+ regardless of category. This single rule covers both "duplicate
1004
+ requirement" and "conflicting categories on the same subject" (e.g. the
1005
+ same region/property authored as both `requested` and `protected`) -
1006
+ v0.5's `evaluateFrontendContract#primitivesConflict` is a *runtime-
1007
+ evaluation-time* detector (it needs before/after `ObservationArtifact`
1008
+ evidence that does not exist yet at this stage) and could not be reused
1009
+ safely; Prompt 3 restricts invalid combinations at authoring time instead,
1010
+ per the documented precedent-review outcome.
1011
+ - Bounded at `MAX_REFERENCE_REQUIREMENTS` (50) requirements per artifact -
1012
+ a maximum capacity, never a required minimum (there is no contract
1013
+ requiring any specific number of authored requirements).
1014
+ - One new diagnostic code, `invalid-reference-requirement` (error), covers
1015
+ every requirement-authoring validation failure - deliberately not split
1016
+ further, per the "don't proliferate diagnostics" convention already used
1017
+ for `invalid-reference-region`.
1018
+ - `import-reference` gained an optional `--requirements-file <json-file>`
1019
+ (`{ "requirements": [...] }`, same object-root-wrapper convention as
1020
+ `--regions-file`/`--targets-file`); `approve-reference` carries an
1021
+ imported artifact's `requirements` forward verbatim (never re-validated,
1022
+ never re-derived, never dropped), exactly mirroring how it already
1023
+ handles `regions`.
1024
+
1025
+ **Reference-evidence adequacy** (`deriveReferenceRequirementAdequacy(regions, requirements)`)
1026
+ answers only "does the reference definition itself contain enough evidence
1027
+ to understand every selected requirement?" - never "does a runtime
1028
+ target/candidate exist" (that is Prompt 4/5's responsibility). It is its own
1029
+ small, reference-owned vocabulary (`REFERENCE_REQUIREMENT_ADEQUACY_STATES` =
1030
+ `'adequate' | 'partial' | 'inadequate'`, and exactly two reason codes,
1031
+ `no-selected-requirements` and `missing-reference-relationship-evidence`) -
1032
+ deliberately **not** a reuse of
1033
+ `boundedAgentContext.ts`'s `Adequacy`/`ADEQUACY_REASON_CODES`, which
1034
+ describe runtime-target/static-correlation concerns that do not exist at
1035
+ this stage; mislabeling reference adequacy as bounded-agent-context adequacy
1036
+ would conflate two genuinely different evidence domains. Zero selected
1037
+ requirements is explicitly `inadequate` (a region-rich, fully-valid
1038
+ reference is still not usable for a correction task until the user has
1039
+ actually selected what matters) - this is a documented product decision,
1040
+ not an oversight. The result is never a numeric score, always structured
1041
+ and inspectable, with reasons ordered deterministically by authored
1042
+ requirement position.
1043
+
1044
+ ```ts
1045
+ interface ReferenceRequirementAdequacy {
1046
+ status: 'adequate' | 'partial' | 'inadequate';
1047
+ totalRequirements: number;
1048
+ evaluableRequirements: number;
1049
+ unavailableRequirements: number;
1050
+ reasons: { code: 'no-selected-requirements' | 'missing-reference-relationship-evidence'; requirementId?: string; detail?: string }[];
1051
+ }
1052
+ ```
1053
+
1054
+ ## v0.7 Prompt 4 reference applicability and candidate-state compatibility
1055
+
1056
+ Released as `0.7.0`. Additive extension of the Prompt 1/2/3 contracts
1057
+ above: one new, optional `applicability?: ExternalReferenceApplicability`
1058
+ field on `ExternalReferenceArtifact` (both lifecycle variants), one new,
1059
+ optional `explicitState?: ExplicitStateDimensions` field on
1060
+ `ObservationArtifact.requestConfig`, and one new pure module,
1061
+ `domain/externalReferenceCompatibility.ts`, that answers a single question:
1062
+ "does this external reference describe the same frontend state as this
1063
+ candidate `ObservationArtifact`?" No schema version bump on either artifact
1064
+ - same reasoning as Prompt 2/3's additive fields.
1065
+
1066
+ **Central distinction**: this is page/state-level compatibility only -
1067
+ never geometry, never fidelity, never a visual/pixel comparison, and never
1068
+ region-to-runtime-target binding (Prompt 5). It answers "should a
1069
+ reference-vs-candidate geometry comparison even be attempted", not "does the
1070
+ candidate match the reference". Reference-evidence adequacy (Prompt 3) and
1071
+ reference/candidate compatibility (Prompt 4) are deliberately independent:
1072
+ a reference can be `adequate` (enough selected requirements to evaluate)
1073
+ while simultaneously `incomparable` against a given candidate (wrong
1074
+ viewport/theme/state), and vice versa - neither result constrains the
1075
+ other.
1076
+
1077
+ **State identity is always explicit, never inferred.** `theme`,
1078
+ `applicationState`, and `authenticatedState` are caller/configuration-
1079
+ supplied labels only. The observer never reads screenshot pixels, CSS, DOM
1080
+ classes/text, URLs, source code, filenames, accessibility labels,
1081
+ localStorage, or cookies to determine state - there is no automatic state
1082
+ detection anywhere in this codebase, and Prompt 4 does not add any. Labels
1083
+ are bounded opaque identities (`^[A-Za-z0-9_-]{1,64}$`, the same pattern
1084
+ already used for target names and region ids) compared by exact,
1085
+ case-sensitive string equality only - `"dark"` and `"one-dark"` are
1086
+ unrelated labels, never fuzzy-matched or normalized.
1087
+
1088
+ ```ts
1089
+ // domain/explicitState.ts - shared by both ObservationArtifact and ExternalReferenceArtifact
1090
+ type AuthenticatedState = 'authenticated' | 'unauthenticated'; // closed vocabulary - never a place for credentials/tokens/cookies/session ids
1091
+ interface ExplicitStateDimensions {
1092
+ theme?: string;
1093
+ applicationState?: string;
1094
+ authenticatedState?: AuthenticatedState;
1095
+ }
1096
+ // isValidExplicitStateDimensions requires at least one dimension declared and rejects any unsupported field -
1097
+ // this is a bounded, closed shape, never an arbitrary Record<string, unknown> metadata bag.
1098
+
1099
+ // domain/externalReferenceApplicability.ts
1100
+ interface ApplicableViewport { width: number; height: number } // CSS pixels, bounds [200, 3840] mirroring request.ts's own viewport bounds
1101
+ interface ExternalReferenceApplicability extends ExplicitStateDimensions {
1102
+ viewport?: ApplicableViewport;
1103
+ }
1104
+ ```
1105
+
1106
+ **Reference image size is never the same concept as applicable viewport.**
1107
+ `ExternalReferenceImageReference.width/height` (Prompt 1) describes the
1108
+ reference image's own pixel dimensions - a property of the image file,
1109
+ detected from its header bytes. `applicability.viewport` describes the
1110
+ CSS-pixel runtime viewport the design *represents* - a reference image may
1111
+ be captured at any resolution or device-pixel-ratio (e.g. a 1920x1080
1112
+ screenshot representing a 960x540 CSS-pixel layout at 2x DPR). Nothing in
1113
+ `externalReferenceApplicability.ts` reads or derives a viewport from image
1114
+ dimensions; `isValidExternalReferenceApplicability` is its own validator
1115
+ (not a reuse of `isValidExplicitStateDimensions`, whose "at least one
1116
+ dimension" rule would incorrectly reject a viewport-only applicability
1117
+ object).
1118
+
1119
+ **v0.4 comparability is reused, not duplicated.** `domain/comparison.ts`
1120
+ gained four additive reason codes (`viewport-unassessed`, `theme-mismatch`,
1121
+ `authenticated-state-mismatch`, `application-state-mismatch` - the
1122
+ `*-unassessed` codes for theme/authenticated-state/application-state
1123
+ already existed from v0.4) and two optional fields on `ComparabilityReason`
1124
+ (`referenceValue?: string`, `candidateValue?: string`, populated only for a
1125
+ mismatch reason). `domain/comparisonEngine.ts` gained one new exported pure
1126
+ helper, `assessOptionalComparabilityDimension(mismatchCode, unassessedCode,
1127
+ beforeValue, afterValue, mismatchMessage, unassessedMessage)`, extracted
1128
+ from - and now used by - both v0.4's own `evaluateComparability`
1129
+ (Observation-vs-Observation) and the new
1130
+ `evaluateReferenceCandidateCompatibility` (Reference-vs-Observation). The
1131
+ rule is identical either way: both values defined and equal -> no reason;
1132
+ both defined and different -> a `blocking` mismatch reason (with
1133
+ `referenceValue`/`candidateValue` populated); either value undefined ->
1134
+ an `unassessed` reason. This is a genuine, additive improvement to v0.4's
1135
+ own behavior: `evaluateComparability` now assesses theme/authenticated-
1136
+ state/application-state as matching or blocking-mismatched whenever *both*
1137
+ observations declare `requestConfig.explicitState`, rather than always
1138
+ reporting them unassessed - but every historical observation pair (and any
1139
+ pair where either side omits `explicitState`) retains the exact old
1140
+ unassessed-only behavior, verified by the frozen `evaluateComparability`
1141
+ regression test that predates this batch.
1142
+
1143
+ ```ts
1144
+ // domain/externalReferenceCompatibility.ts
1145
+ interface ReferenceCandidateCompatibilityResult {
1146
+ referenceId: string;
1147
+ referenceRequestId: string;
1148
+ candidateObservationId: string;
1149
+ candidateRequestId: string;
1150
+ compatibility: ComparabilityResult; // v0.4's own reused result type - state/reasons, never a boolean or a visual score
1151
+ }
1152
+ function evaluateReferenceCandidateCompatibility(reference: ExternalReferenceArtifact, candidate: ObservationArtifact): ReferenceCandidateCompatibilityResult;
1153
+ ```
1154
+
1155
+ Key rules:
1156
+
1157
+ - Pure and synchronous - no browser, no filesystem, no network, no target
1158
+ binding. Only `reference.applicability` and
1159
+ `candidate.requestConfig.viewport`/`candidate.requestConfig.explicitState`
1160
+ are consulted; reference regions/requirements are never read here (a
1161
+ distinct, separate concern - see Prompt 3 above).
1162
+ - A dimension the reference constrains but the candidate entirely omits
1163
+ (or vice versa) is `unassessed`, never treated as compatible-by-default
1164
+ and never fabricated as a mismatch - fail-closed, honest non-assessment.
1165
+ - A reference that declares no `applicability` at all produces a fully
1166
+ `unassessed` (never automatically `incomparable`, never automatically
1167
+ `comparable` beyond "no blocking reasons found") result across all four
1168
+ dimensions - Prompt 1/2/3 references remain fully usable, just
1169
+ unassessed for compatibility until applicability is authored.
1170
+ - No automatic persisted compatibility artifact. This is a pure
1171
+ programmatic result, produced on demand by an application/CLI caller
1172
+ that already holds both a reference and a candidate artifact - inventing
1173
+ a new persisted artifact kind for a value this cheap to recompute would
1174
+ add drift risk (a candidate/reference re-imported later could silently
1175
+ disagree with a stale persisted compatibility record) with no
1176
+ corresponding benefit; this may be revisited only if a later prompt's
1177
+ architecture proves persistence necessary.
1178
+ - Identity impact: `buildExternalReferenceRequestIdentity` gained a final
1179
+ optional `applicability` parameter (omitted, never `null`, when absent -
1180
+ byte-identical to Prompt 1/2/3 hashes for every call that doesn't supply
1181
+ it); `buildRequestIdentity` gained a final optional `explicitState`
1182
+ parameter with the identical omission convention. Neither identity
1183
+ function ever takes a file path.
1184
+ - CLI: `import-reference` gained an optional `--applicability-file
1185
+ <json-file>` (the raw, unwrapped applicability object - not a
1186
+ `{ "requirements": [...] }`-style wrapper, since applicability is a
1187
+ single object rather than a named list); `observe` gained an optional
1188
+ `--state-file <json-file>` (the raw, unwrapped `ExplicitStateDimensions`
1189
+ object). Both follow the existing `--scroll-scenario-file` convention
1190
+ exactly: relative paths resolve from the current working directory, the
1191
+ path itself is never persisted or included in any identity, and CLI code
1192
+ owns only flag syntax/file reading/JSON parsing/object-root validation -
1193
+ all semantic validation happens in the domain layer.
1194
+
1195
+ ## v0.7 Prompt 5 explicit reference-region <-> runtime-target binding
1196
+
1197
+ Released as `0.7.0`. One new pure domain module,
1198
+ `domain/externalReferenceRuntimeBinding.ts`, answering "which stable
1199
+ observer runtime target, if any, does this candidate observation resolve
1200
+ for each explicitly declared reference region?" No new field is added to
1201
+ either `ExternalReferenceArtifact` or `ObservationArtifact` - both remain
1202
+ exactly as Prompt 4 left them - and no schema version bump on either.
1203
+
1204
+ **Two identity domains, kept strictly separate.** A binding declaration
1205
+ names a Prompt 2 `ReferenceRegion.id` and a v0.2 `NamedTarget.name` (the
1206
+ stable observer runtime target identity established since v0.2 - never a
1207
+ CSS selector, DOM node handle, source file, React component name, or
1208
+ my-dev-kit node id). These two strings living in the same textual namespace
1209
+ never implies a binding - a region id `"header"` and a target name
1210
+ `"header"` bind to each other only because of an explicit declaration, not
1211
+ because the strings match (verified by a dedicated test: the same
1212
+ observation with and without the explicit declaration produces `bound`
1213
+ only in the former case).
1214
+
1215
+ ```ts
1216
+ // domain/externalReferenceRuntimeBinding.ts
1217
+ interface ReferenceRuntimeBindingDeclaration {
1218
+ referenceRegion: string; // Prompt 2 ReferenceRegion.id
1219
+ runtimeTarget: string; // v0.2 NamedTarget.name
1220
+ }
1221
+
1222
+ const REFERENCE_RUNTIME_BINDING_STATUSES = ['bound', 'ambiguous', 'unavailable'] as const;
1223
+
1224
+ interface ReferenceRuntimeBindingResult {
1225
+ referenceRegion: string;
1226
+ runtimeTarget: string;
1227
+ status: 'bound' | 'ambiguous' | 'unavailable';
1228
+ reasonCode?: 'runtime-target-not-configured' | 'runtime-target-not-found' | 'runtime-target-ambiguous' | 'runtime-target-evidence-unavailable';
1229
+ detail: string;
1230
+ targetResolutionStatus?: TargetSelectionStatus; // v0.2's own resolution status, when evidence for it exists
1231
+ targetVisible?: boolean; // provenance only - never affects status
1232
+ }
1233
+
1234
+ interface ReferenceRuntimeBindingEvaluation {
1235
+ referenceId: string;
1236
+ referenceRequestId: string;
1237
+ candidateObservationId: string;
1238
+ candidateRequestId: string;
1239
+ compatibility: ComparabilityResult; // reused verbatim from v0.7 Prompt 4
1240
+ bindings: ReferenceRuntimeBindingResult[]; // empty exactly when compatibility.state === 'incomparable'
1241
+ }
1242
+
1243
+ function evaluateReferenceRuntimeBindings(
1244
+ reference: ExternalReferenceArtifact,
1245
+ candidate: ObservationArtifact,
1246
+ declarations: readonly ReferenceRuntimeBindingDeclaration[],
1247
+ ): { ok: true; evaluation: ReferenceRuntimeBindingEvaluation } | { ok: false; reason: string };
1248
+ ```
1249
+
1250
+ Key rules:
1251
+
1252
+ - **Explicit, never inferred.** A binding declaration is user/configuration
1253
+ input asserting a conceptual correspondence; this module never discovers
1254
+ it from screenshot geometry, matching names, matching text, or source
1255
+ code. There is no automatic-matching algorithm anywhere in this module.
1256
+ - **Reuses, never duplicates.** The compatibility gate reuses
1257
+ `evaluateReferenceCandidateCompatibility` (Prompt 4) verbatim - viewport/
1258
+ theme/application-state/authenticated-state comparison logic is never
1259
+ re-implemented here. Runtime-target resolution reuses `targetPresence`
1260
+ (v0.4 `comparisonEngine.ts`, now additively exported alongside
1261
+ `assessOptionalComparabilityDimension`) - the exact same "how do I read a
1262
+ `TargetEvidenceRecord`'s resolution" rule v0.4's own before/after target
1263
+ comparison already uses. No second target resolver, no browser launch, no
1264
+ Chromium query, no selector evaluation, no live-DOM inspection - this
1265
+ module consumes only an already-captured `ObservationArtifact`'s
1266
+ `requestConfig.targets`/`targetEvidence`.
1267
+ - **Compatibility gates before evaluation, structurally.** If
1268
+ `evaluateReferenceCandidateCompatibility` reports `incomparable`,
1269
+ `bindings` is the empty array and the caller reads the reason from the
1270
+ embedded `compatibility` field - there is no binding-local "incompatible"
1271
+ status; Prompt 4's compatibility result is represented exactly once, not
1272
+ duplicated into a parallel vocabulary.
1273
+ - **Two-layer validation.** Reference-region existence, declaration shape,
1274
+ bounds, and duplicate/conflict rules are validated structurally against
1275
+ the `ExternalReferenceArtifact` alone (`isValidReferenceRuntimeBindingDeclarations`)
1276
+ - independent of any candidate, mirroring Prompt 3's "unknown region
1277
+ reference is a structural validation failure" precedent exactly: a
1278
+ declaration naming a nonexistent reference region, or any reference with
1279
+ no `regions` declared at all, fails the whole evaluation closed before a
1280
+ candidate is even considered. Runtime-target availability, by contrast,
1281
+ is evaluated per-candidate inside `evaluateReferenceRuntimeBindings`
1282
+ itself, since the same declaration can be `bound` against one candidate
1283
+ and `unavailable` against another.
1284
+ - **Duplicate/conflicting-declaration rule** (mirrors Prompt 3's
1285
+ requirement-subject uniqueness rule): no two declarations may name the
1286
+ same `referenceRegion` (case-insensitively), whether they agree on
1287
+ `runtimeTarget` (an exact duplicate) or disagree (a conflict) - both fail
1288
+ the same way, never silently resolved by keeping the first. The reverse -
1289
+ several distinct reference regions naming the same `runtimeTarget` - is
1290
+ deliberately allowed (e.g. two design sub-regions legitimately
1291
+ corresponding to one runtime container element).
1292
+ - **Target-resolution-state handling.** `targetPresence`'s four outcomes
1293
+ map onto binding status as: `matched` -> `bound`; `ambiguous` -> `ambiguous`
1294
+ (the candidate's own configured target resolved ambiguously - never
1295
+ reported bound even though its stable name exists); `not-found` ->
1296
+ `unavailable` (`runtime-target-not-found` - the target was configured but
1297
+ the resolver found nothing on the page); no usable resolution evidence at
1298
+ all -> `unavailable` (`runtime-target-evidence-unavailable`). A declared
1299
+ `runtimeTarget` that was never part of the candidate's configured target
1300
+ set at all is a fifth, CLI/config-boundary-only outcome -> `unavailable`
1301
+ (`runtime-target-not-configured`) - never a dynamic page search.
1302
+ - **Hidden-target decision.** A uniquely resolved (`matched`) but hidden
1303
+ target is still reported `bound` - visibility never changes `status`.
1304
+ `targetVisible` (from the existing `TargetVisibility` evidence, when
1305
+ available) is carried as provenance only. Binding identity (does a stable
1306
+ correspondence exist) and later fidelity evaluability (can this evidence
1307
+ actually be used to check the design) are treated as distinct questions;
1308
+ this prompt answers only the former.
1309
+ - **Not every region needs a binding.** `isValidReferenceRuntimeBindingDeclarations`
1310
+ never requires full region coverage - a reference may have regions no
1311
+ declaration names at all (they simply have no bound runtime target for
1312
+ this candidate). This is not a completeness gate; Prompt 6 (or later) may
1313
+ add one for the regions that selected requirements actually need.
1314
+ - **No persisted artifact family.** `evaluateReferenceRuntimeBindings` is a
1315
+ pure, on-demand function over an already-persisted reference, an
1316
+ already-persisted candidate observation, and an in-memory declaration
1317
+ collection. No `ExternalReferenceBindingArtifact` (or equivalent) is
1318
+ introduced - the same "cheap to recompute, persisting invites drift"
1319
+ reasoning Prompt 4 already applied to its own compatibility result.
1320
+ Neither the reference nor the observation artifact is ever rewritten to
1321
+ carry a binding result: a design reference may later be evaluated against
1322
+ several different candidates, and one observation may be evaluated
1323
+ against several different references, so binding is kept as downstream,
1324
+ candidate-specific, reference-specific derived evidence rather than
1325
+ mutating either immutable source artifact.
1326
+ - **No new identity function.** Unlike `buildRequestIdentity`/
1327
+ `buildExternalReferenceRequestIdentity`, no hash-based logical identity is
1328
+ computed for a binding declaration or its evaluated result - there is no
1329
+ persistence and no cross-document reference-by-id need yet (mirroring
1330
+ Prompt 4's `ReferenceCandidateCompatibilityResult`, which took the same
1331
+ approach). Provenance is instead carried directly as plain fields
1332
+ (`referenceId`, `referenceRequestId`, `candidateObservationId`,
1333
+ `candidateRequestId`, plus each result's own `referenceRegion`/
1334
+ `runtimeTarget`) - already deterministic, already sufficient for a caller
1335
+ to trace every result back to its inputs, without inventing a fifth
1336
+ identity-hashing convention for a value this prompt does not persist.
1337
+ - **Deterministic ordering.** `bindings` preserves authored declaration
1338
+ order (mirroring the "authored order is semantic" convention already used
1339
+ for regions/requirements) rather than sorting by any derived key.
1340
+ - **Bounded.** `MAX_REFERENCE_RUNTIME_BINDINGS` (20) caps the declaration
1341
+ collection, mirroring `MAX_REFERENCE_REGIONS`.
1342
+ - **No public CLI surface yet.** Only the programmatic
1343
+ `evaluateReferenceRuntimeBindings`/`isValidReferenceRuntimeBindingDeclarations`
1344
+ functions are exported. A standalone CLI command was deliberately not
1345
+ added merely for symmetry with `import-reference`/`observe`; Prompt 6
1346
+ (structured fidelity evaluation) is expected to become the first concrete
1347
+ consumer and public-surface owner for this capability.
1348
+
1349
+ ## v0.7 Prompt 6 structured reference-vs-candidate fidelity evaluation
1350
+
1351
+ Released as `0.7.0`. One new pure domain module,
1352
+ `domain/externalReferenceFidelity.ts`, and its CLI-facing counterpart,
1353
+ `application/referenceFidelityEvaluationService.ts` plus the new
1354
+ `evaluate-reference-fidelity` CLI command - the first point in this whole
1355
+ v0.7 stack where a reference's authored expectation is actually compared
1356
+ against live candidate evidence. No new artifact field, no schema version
1357
+ bump: this prompt reuses Prompt 1-5's artifacts and result types entirely.
1358
+
1359
+ ```ts
1360
+ // domain/externalReferenceFidelity.ts
1361
+ const REFERENCE_REQUIREMENT_FIDELITY_STATUSES = ['pass', 'fail', 'unavailable'] as const;
1362
+ const REFERENCE_FIDELITY_STATES = ['not-evaluated', 'pass', 'fail'] as const;
1363
+ const REFERENCE_FIDELITY_BLOCK_REASONS = ['reference-inadequate', 'incompatible'] as const;
1364
+
1365
+ interface ReferenceRequirementFidelityResult {
1366
+ requirementId: string;
1367
+ category: AuthoredChangeScopeCategory;
1368
+ expectedDependentMode?: ExpectedDependentMode;
1369
+ subject: ReferenceRequirementSubject;
1370
+ boundRuntimeTargets: string[];
1371
+ status: 'pass' | 'fail' | 'unavailable';
1372
+ reasonCode?: 'reference-evidence-unavailable' | 'reference-relationship-not-exhibited' | 'binding-unavailable' | 'candidate-evidence-unavailable' | 'coordinate-mapping-unavailable'; // present iff status === 'unavailable'
1373
+ detail?: string;
1374
+ // region-property/region-measurement subjects only:
1375
+ referenceValue?: number; // reference-image pixels
1376
+ candidateRawValue?: number; // CSS pixels, as captured
1377
+ candidateValue?: number; // candidateRawValue converted into reference-image-pixel space
1378
+ delta?: number; // candidateValue - referenceValue
1379
+ tolerance?: ReferenceRequirementTolerance;
1380
+ // region-relationship subjects only:
1381
+ expectedRelationship?: PairwiseRelationshipKind;
1382
+ actualRelationship?: PairwiseRelationshipKind;
1383
+ }
1384
+
1385
+ interface ReferenceCandidateFidelityEvaluation {
1386
+ referenceId: string;
1387
+ referenceRequestId: string;
1388
+ candidateObservationId: string;
1389
+ candidateRequestId: string;
1390
+ adequacy: ReferenceRequirementAdequacy; // reused verbatim from Prompt 3
1391
+ compatibility?: ComparabilityResult; // reused verbatim from Prompt 4; absent only when adequacy itself is inadequate
1392
+ bindings?: ReferenceRuntimeBindingEvaluation; // reused verbatim from Prompt 5; absent when an earlier gate blocked
1393
+ state: 'not-evaluated' | 'pass' | 'fail';
1394
+ blockedBy?: 'reference-inadequate' | 'incompatible'; // present iff state === 'not-evaluated'
1395
+ requirementResults: ReferenceRequirementFidelityResult[]; // empty iff state === 'not-evaluated'
1396
+ }
1397
+
1398
+ function evaluateReferenceCandidateFidelity(
1399
+ reference: ExternalReferenceArtifact,
1400
+ candidate: ObservationArtifact,
1401
+ bindingDeclarations: readonly ReferenceRuntimeBindingDeclaration[],
1402
+ options?: { geometryTolerancePx?: number },
1403
+ ): { ok: true; evaluation: ReferenceCandidateFidelityEvaluation } | { ok: false; reason: string };
1404
+ ```
1405
+
1406
+ **Result vocabulary.** `pass`/`fail`/`unavailable` is reused from v0.5's
1407
+ `CLAUSE_RESULT_STATUSES` shape (the same honest three-state idea: a result
1408
+ either satisfies its condition, fails it, or cannot be evaluated - never a
1409
+ score) but is its own independently-owned constant, deliberately excluding
1410
+ v0.5's fourth member, `'conflict'` - Prompt 6 has no cross-requirement
1411
+ authoring-conflict concept (each requirement is evaluated independently
1412
+ against its own subject), so reusing `conflict` would invite a status this
1413
+ prompt can never actually produce.
1414
+
1415
+ **Evaluation order (frozen, never reordered):** reference structural
1416
+ validation -> candidate structural validation -> binding-declaration
1417
+ structural validation -> Prompt 3 reference adequacy -> Prompt 4
1418
+ compatibility -> Prompt 5 binding evaluation -> per-requirement candidate-
1419
+ evidence/coordinate-mapping checks -> per-requirement tolerance/
1420
+ relationship comparison -> overall result. The first three (structural
1421
+ validation) failures return `{ ok: false, reason }` - a caller/config error,
1422
+ never a fidelity outcome. The next two (adequacy `inadequate`, compatibility
1423
+ `incomparable`) short-circuit to `state: 'not-evaluated'` with an empty
1424
+ `requirementResults` - an earlier blocking gate never lets an ordinary
1425
+ PASS/FAIL requirement set get fabricated past it. `adequacy` "partial" (some,
1426
+ but not all, authored requirements individually unavailable) does **not**
1427
+ block evaluation - it proceeds normally, and the individual unavailable
1428
+ reference-side requirements simply also report `unavailable` at the
1429
+ per-requirement level (their own reference-evidence problem, re-derived
1430
+ identically by `evaluateOneRequirement`, not looked up from the adequacy
1431
+ result).
1432
+
1433
+ **Coordinate mapping - the central problem this prompt solves.** Prompt 2
1434
+ regions and Prompt 3 tolerances are authored in reference-image pixels;
1435
+ `ObservationArtifact` target geometry is CSS pixels. This module establishes
1436
+ exactly one explicit, deterministic scale from `reference.applicability.viewport`
1437
+ (the CSS-pixel runtime viewport, Prompt 4) and the reference image's own
1438
+ pixel dimensions (Prompt 1) - `scaleX = imageWidth / viewportWidth`,
1439
+ `scaleY = imageHeight / viewportHeight` - and converts every candidate
1440
+ measurement into reference-image-pixel space before comparing it against a
1441
+ Prompt 3 tolerance. It never assumes 1 reference-image pixel equals 1 CSS
1442
+ pixel, and it never performs cropping, offset, rotation, or perspective
1443
+ registration - only a deliberately bounded full-frame mapping. `scaleX`/
1444
+ `scaleY` must agree within a small, independently-owned coordinate-mapping-
1445
+ validity tolerance (1% relative, never a user-authored design tolerance) or
1446
+ the mapping is rejected outright; a reference with no applicable viewport at
1447
+ all likewise has no mapping. Either way, every numeric (`region-property`/
1448
+ `region-measurement`) requirement becomes `unavailable`/`coordinate-mapping-unavailable`
1449
+ - categorical `region-relationship` requirements are unaffected (they never
1450
+ need a scale). Horizontal fields (`x`/`width`/`right`/`centerX` and the
1451
+ horizontal measurements) always scale by `scaleX`; vertical fields (`y`/
1452
+ `height`/`bottom`/`centerY` and the vertical measurements) always scale by
1453
+ `scaleY` - this falls out automatically from converting a full
1454
+ `TargetGeometry` into a `ReferenceRegionGeometry`-shaped value per axis,
1455
+ never a hand-picked per-property axis table.
1456
+
1457
+ **Tolerance is reused exactly, never redefined.** A single rule -
1458
+ `abs(delta) <= allowedAmount` - covers all three Prompt 3 tolerance kinds:
1459
+ `exact` is simply the zero-tolerance case (`allowedAmount = 0`);
1460
+ `absolute-reference-px` uses its authored `amount` directly (already in
1461
+ reference-image pixels); `percent`'s denominator is `Math.abs(referenceValue)`,
1462
+ mirroring v0.5's own `toleranceToPx` "may vary by up to N%" convention
1463
+ exactly (independently reimplemented in reference-image-pixel units, never
1464
+ imported - `frontendContractEvaluation.ts`'s `ContractTolerance` is a
1465
+ different, CSS-pixel-implicit unit). No hidden epsilon is added anywhere;
1466
+ subpixel precision is preserved through to the final comparison, so a
1467
+ tolerance-boundary value (e.g. delta exactly equal to the allowed amount)
1468
+ passes and one unit past it fails, exactly as authored.
1469
+
1470
+ **Region-property evaluation** reads `TargetGeometry` from the bound
1471
+ target's `targetEvidence` entry, converts it into a `ReferenceRegionGeometry`-
1472
+ shaped value (adding `centerX`/`centerY`, computed identically to
1473
+ `deriveReferenceRegionGeometry`) both raw (CSS) and scaled (reference-image
1474
+ pixels), and reads `[subject.property]` off each - `candidateRawValue`
1475
+ (CSS) and `candidateValue` (reference-image pixels) are both reported.
1476
+
1477
+ **Region-measurement evaluation** converts *both* bound targets' geometries
1478
+ the same way and calls the existing `deriveReferenceRequirementMeasurement`
1479
+ (Prompt 3) on the converted geometries directly - reusing Prompt 3's exact
1480
+ gap/delta formulas rather than reimplementing a parallel "runtime version"
1481
+ of them, and never inventing a generic geometry expression language. A
1482
+ geometrically-undefined gap (the two targets overlap on the relevant axis)
1483
+ is `unavailable`, mirroring Prompt 3's own reference-side treatment of the
1484
+ identical situation.
1485
+
1486
+ **Region-relationship evaluation** first confirms the reference itself
1487
+ actually exhibits its own selected relationship (`deriveReferenceRequirementExpectation`'s
1488
+ `matches` field) - if not, the result is `unavailable`/
1489
+ `reference-relationship-not-exhibited` (a reference-authoring problem, never
1490
+ a candidate `fail`). It then resolves both bound targets and calls the
1491
+ canonical `deriveLayoutRelationships` (v0.4) over the *whole* candidate
1492
+ observation - never a second, parallel relationship formula - and looks up
1493
+ the pairwise record for the bound target pair **scoped to the exact
1494
+ requested relationship family** (an independently-owned, third duplicate of
1495
+ the same `RELATIONSHIP_FAMILY_GROUPS` shape already used by
1496
+ `frontendContractEvaluation.ts` and `externalReferenceRequirements.ts` -
1497
+ this is the same real bug class Prompt 3 fixed: matching the first record
1498
+ for a target pair regardless of family would silently compare against the
1499
+ wrong relationship kind). A record only derivable in the reversed target
1500
+ order is `unavailable`, never auto-flipped - identical to Prompt 3's own
1501
+ reference-side handling of the same situation. `pass` requires the
1502
+ candidate's actual relationship kind to equal the requirement's authored
1503
+ `relationship` exactly.
1504
+
1505
+ **Binding gate.** Every subject's dependent reference region(s) must have a
1506
+ `bound` (never `ambiguous`/`unavailable`, and never simply absent from the
1507
+ supplied declarations) Prompt 5 binding result, or the requirement is
1508
+ `unavailable`/`binding-unavailable` - this module never guesses another
1509
+ target and never auto-binds based on geometry or names. A `bound` target
1510
+ that is not visible (`TargetVisibility.visible !== true`, including when
1511
+ visibility evidence itself is unavailable) is treated as having no usable
1512
+ geometry - `unavailable`/`candidate-evidence-unavailable` - preserving the
1513
+ distinction between "binding succeeded" (Prompt 5's question) and "this
1514
+ evidence is usable for fidelity evaluation" (this prompt's question): a
1515
+ hidden-but-uniquely-resolved target still has a stable identity, but its
1516
+ geometry is never treated as meaningful for a numeric/relationship
1517
+ comparison.
1518
+
1519
+ **Categories are preserved, never given different PASS/FAIL rules.**
1520
+ `category`/`expectedDependentMode` are carried through to each result as
1521
+ provenance only; Prompt 3 never implemented a `required`-vs-`permitted`
1522
+ directional evaluation difference for its own expectation/adequacy
1523
+ derivation (unlike v0.5's runtime-directional contract clauses), so Prompt 6
1524
+ does not invent one now - every requirement in the reference's authored
1525
+ collection is evaluated by the identical rule and counts identically toward
1526
+ the overall result, regardless of category.
1527
+
1528
+ **Overall fidelity result.** For an evaluated (non-blocked) pair, `state`
1529
+ is `'pass'` only when every requirement result is `'pass'`; any `'fail'` or
1530
+ `'unavailable'` result forces `state: 'fail'` - there is no meaningful third
1531
+ overall bucket once evaluation has actually run, since "some/all
1532
+ unavailable" and "some/all fail" both equally mean "not every selected
1533
+ requirement is confirmed satisfied." A reference with zero selected
1534
+ requirements never reaches this stage at all - it is `inadequate` (Prompt
1535
+ 3's own zero-requirements rule) and therefore `not-evaluated`, never a
1536
+ meaningless `pass`.
1537
+
1538
+ **Persistence decision: none.** `evaluateReferenceCandidateFidelity` (and
1539
+ its CLI-facing wrapper, `evaluateReferenceCandidateFidelityFromArtifactRoots`)
1540
+ is a pure, on-demand function over already-persisted/in-memory evidence -
1541
+ no new `ExternalReferenceFidelityEvaluationArtifact` (or equivalent) is
1542
+ introduced. Rationale, identical to Prompt 4/5's own precedent: the result
1543
+ is cheap to recompute deterministically from its inputs (a reference, a
1544
+ candidate, and a caller-supplied binding-declaration collection), and
1545
+ persisting it would invite drift with no corresponding benefit at this
1546
+ stage; this may be revisited only if Prompt 7's architecture proves
1547
+ persistence necessary.
1548
+
1549
+ **CLI**: `evaluate-reference-fidelity --reference <root> --candidate <root>
1550
+ [--bindings-file <json-file>] [--enforce]` - the CLI surface Prompt 5
1551
+ deliberately deferred. `--bindings-file` follows the exact
1552
+ `--requirements-file`/`--regions-file` wrapped-object convention
1553
+ (`{ "bindings": [...] }`); CLI code owns only flag syntax/file reading/JSON
1554
+ parsing/root-shape validation, with every binding-declaration rule staying
1555
+ owned by `isValidReferenceRuntimeBindingDeclarations`. `--enforce` mirrors
1556
+ `evaluate-contract`'s exact precedent: it changes only the process exit
1557
+ status for an already-computed `state: 'fail'` result, never the printed
1558
+ content - and has no effect on `not-evaluated`, which always exits 0 (a
1559
+ compatibility/adequacy blocker is a successful, structured, honest
1560
+ non-evaluation, never an execution error and never a design mismatch).
1561
+ Persists nothing; there is no `--output` flag.
1562
+
1563
+ ## v0.7 Prompt 7 bounded reference-fidelity projection and v0.6 bounded-agent-context integration
1564
+
1565
+ Released as `0.7.0`. Additive extension of the v0.6 bounded-agent-context
1566
+ contract above and of the v0.7 Prompt 6 fidelity evaluator - no new bounded-
1567
+ context artifact family, no second visual-context system, no schema version
1568
+ bump (`BOUNDED_AGENT_CONTEXT_SCHEMA_VERSION` stays `1.0.0`, following the
1569
+ exact precedent already set when `correlations?` was added in v0.6 Batch 3).
1570
+
1571
+ **Chosen integration owner.** `projectBoundedAgentContext` itself gains one
1572
+ new optional input (`fidelity?: ReferenceCandidateFidelityEvaluation`, plus
1573
+ `fidelityRequired?: boolean`) rather than a separate `VisualAgentContext`/
1574
+ `VisualPromptPacket`/`ReferencePromptBuilder`. This was chosen over a pure
1575
+ post-hoc "attach" step (the shape `attachRuntimeStaticCorrelations` uses)
1576
+ because fidelity-relevant runtime targets must compete fairly for
1577
+ `MAX_RUNTIME_TARGETS` capacity and receive the exact same geometry/
1578
+ visibility/screenshot assembly contract-clause-derived targets already get -
1579
+ an attach-only step run after target allocation could never produce that. A
1580
+ new pure module, `domain/referenceFidelityProjection.ts`
1581
+ (`projectReferenceFidelity`), derives the bounded, prioritized fidelity
1582
+ content plus the target-id/omission/truncation contributions
1583
+ `projectBoundedAgentContext` folds into its own existing pipeline - it is
1584
+ not a second fidelity-evaluation engine, only a selection over Prompt 6's
1585
+ already-computed result.
1586
+
1587
+ ```ts
1588
+ // domain/boundedAgentContext.ts - additive
1589
+ interface BoundedAgentContextSourceReferences {
1590
+ // ...unchanged fields...
1591
+ referenceId?: string; // new, optional
1592
+ referenceRequestId?: string; // new, optional
1593
+ }
1594
+
1595
+ const MAX_FIDELITY_MISMATCHES = 15;
1596
+ const MAX_FIDELITY_PROTECTED_CONTEXT = 10; // reuses MAX_RELATIONSHIP_EVIDENCE_PER_TARGET's value
1597
+
1598
+ interface BoundedReferenceFidelityProjection {
1599
+ referenceId: string;
1600
+ referenceRequestId: string;
1601
+ candidateObservationId: string;
1602
+ candidateRequestId: string;
1603
+ adequacy: ReferenceRequirementAdequacy; // reused verbatim from Prompt 3
1604
+ compatibility?: ComparabilityResult; // reused verbatim from Prompt 4
1605
+ state: ReferenceFidelityState; // reused verbatim from Prompt 6
1606
+ blockedBy?: ReferenceFidelityBlockReason; // reused verbatim from Prompt 6
1607
+ mismatches: ReferenceRequirementFidelityResult[]; // bounded, prioritized non-pass requirements (Prompt 6 type, unmodified)
1608
+ protectedContext: ReferenceRequirementFidelityResult[]; // bounded passing protected/preserved requirements, as "do not break this" context
1609
+ }
1610
+
1611
+ interface BoundedAgentContextArtifact {
1612
+ // ...unchanged fields...
1613
+ fidelity?: BoundedReferenceFidelityProjection; // new, optional - mirrors `correlations?`'s own additive precedent exactly
1614
+ }
1615
+ ```
1616
+
1617
+ **Selection policy** (`domain/referenceFidelityProjection.ts#projectReferenceFidelity`):
1618
+ only Prompt 6's non-`pass` requirement results are ever candidates for
1619
+ `mismatches` - passing requirements are never dumped by default, satisfying
1620
+ this prompt's "bounded coding-agent use" design goal. Each candidate is
1621
+ classified into a tier by its authored category/mode, reusing
1622
+ `boundedAgentContextProjection.ts#clauseTier`'s exact rule (duplicated, not
1623
+ imported, per this repository's established per-module small-helper
1624
+ convention - never a reference-specific protected/preserved taxonomy):
1625
+ `protected`/`preserved` are always `required`; `expected-dependent` is
1626
+ `required` only in `'required'` mode; `requested` and `expected-dependent`/
1627
+ `'permitted'` are `optional`.
1628
+
1629
+ **Priority policy**: 1) `fail` + `required` tier, 2) `unavailable` +
1630
+ `required` tier, 3) any other non-`pass` (optional-tier) result. Within one
1631
+ priority class, Prompt 6's own authored requirement order is preserved (a
1632
+ stable sort by priority rank only) - never re-ranked by an opaque score.
1633
+ The final `mismatches` array is reported in priority order (highest first),
1634
+ not restored to authored order, since the whole point of prioritization is
1635
+ that the most actionable evidence appears first when the set is large.
1636
+
1637
+ **Cap values**: `MAX_FIDELITY_MISMATCHES = 15` and
1638
+ `MAX_FIDELITY_PROTECTED_CONTEXT = 10` (reusing
1639
+ `MAX_RELATIONSHIP_EVIDENCE_PER_TARGET`'s value) - both judgment-call bounds
1640
+ in the same spirit as v0.6 Batch 1's own frozen caps (no measured fixture
1641
+ corpus exists yet for either concept).
1642
+
1643
+ **Omission/truncation behavior**: reuses `OmissionRecord`/`TruncationRecord`
1644
+ wholesale, no second reporting model. When mismatches exceed the cap, a
1645
+ `{subject: 'fidelity-mismatches', limit, actualCount, required}` truncation
1646
+ is recorded, plus one `{subject: 'fidelity-mismatch:<requirementId>',
1647
+ reason: 'required-evidence-lost-by-bound', required: true}` omission for
1648
+ *each* dropped required-tier mismatch (optional-tier drops are truncated
1649
+ but never separately omitted as "required loss", since they were never
1650
+ required). `protectedContext` truncation is always `required: false` - it
1651
+ is confirmatory/passing context, never a design-fidelity failure. These
1652
+ records are folded into `projectBoundedAgentContext`'s own `omissions`/
1653
+ `truncations` arrays *before* its existing aggregate `capOmissions`/
1654
+ `capTruncations` calls and its existing adequacy computation run - fidelity
1655
+ loss is never a separate adequacy code path, it simply participates in the
1656
+ exact same `anyRequiredLoss`/`anyOptionalLoss` rule every other evidence
1657
+ source already uses.
1658
+
1659
+ **Adequacy behavior**: a `not-evaluated` fidelity (blocked by Prompt 6's own
1660
+ `reference-inadequate`/`incompatible` gates) is never converted into "no
1661
+ problems" - `projectReferenceFidelity` records an explicit
1662
+ `{subject: 'fidelity', reason: 'unsupported-or-unavailable', required,
1663
+ detail}` omission, where `required` defaults to `true` (supplying a
1664
+ fidelity evaluation to be projected at all is itself the signal that the
1665
+ task depends on it, mirroring `CorrelationTargetInput.required`'s existing
1666
+ v0.6 convention - callers who want fidelity as purely incidental context set
1667
+ `fidelityRequired: false`). A `required: true` fidelity omission, folded
1668
+ into the existing adequacy computation, prevents `adequacy.state` from
1669
+ remaining `'adequate'` (it becomes `'partial'`, or `'inadequate'` when
1670
+ combined with other required loss reaching the existing threshold) - it is
1671
+ never silently ignored. A `required: false` omission can degrade adequacy
1672
+ to at most `'partial'`, per v0.6's own pre-existing "optional-only loss
1673
+ never means inadequate" rule - unchanged, not redefined. A `pass` fidelity
1674
+ result contributes no omissions/truncations at all and never degrades
1675
+ adequacy.
1676
+
1677
+ **Not-evaluated fidelity behavior**: preserved exactly as Prompt 6 reported
1678
+ it - `fidelity.state`/`fidelity.blockedBy` on the output artifact are a
1679
+ direct pass-through of Prompt 6's own values, with `mismatches`/
1680
+ `protectedContext` both empty (there is nothing to select from an empty
1681
+ `requirementResults`).
1682
+
1683
+ **Per-target organization**: every fidelity mismatch's `boundRuntimeTargets`
1684
+ (Prompt 5/6's own field, never truncated) becomes a required- or permitted-
1685
+ tier addition to `projectBoundedAgentContext`'s existing target-id sets,
1686
+ so those runtime targets receive full `BoundedRuntimeTargetProjection`
1687
+ treatment (geometry/visibility/overflow/scrollOwner/screenshotRef) through
1688
+ the exact existing assembly code - no duplicated target-projection logic.
1689
+ Reference regions are never used as a correlation or target-selection key;
1690
+ only the already-bound stable v0.2 runtime target ids are.
1691
+
1692
+ **Multi-target relationship representation**: a `region-relationship`
1693
+ mismatch's `boundRuntimeTargets` array (already carrying both bound
1694
+ targets, from Prompt 6) is used as-is - both targets are added to the
1695
+ required/permitted set, so both appear in `targets`. Nothing collapses a
1696
+ two-target relationship failure onto a single target.
1697
+
1698
+ **Static-correlation reuse**: entirely unchanged. `deriveRuntimeStaticCorrelations`/
1699
+ `attachRuntimeStaticCorrelations` are not modified, not called from within
1700
+ this prompt's new code, and remain the caller's own separate step -
1701
+ `BoundedRuntimeTargetProjection.targetId`/`RuntimeStaticCorrelationRecord.runtimeTargetId`
1702
+ already share the same stable v0.2 identity a fidelity mismatch's
1703
+ `boundRuntimeTargets` also uses, so a caller (Prompt 8) joins fidelity,
1704
+ target, and correlation evidence by that one shared id without this module
1705
+ ever needing to read source, run my-dev-kit, or choose among ambiguous
1706
+ candidates itself.
1707
+
1708
+ **Ambiguous/unavailable correlation behavior**: unaffected - a
1709
+ `RuntimeStaticCorrelationRecord` with `status: 'ambiguous'` continues to
1710
+ preserve every competing candidate (v0.6's own frozen invariant,
1711
+ untouched), and `status: 'unavailable'` never causes a fidelity mismatch
1712
+ for that same runtime target to be dropped - the two evidence kinds
1713
+ (runtime fidelity, static correlation) are attached independently and
1714
+ neither erases the other.
1715
+
1716
+ **Provenance**: every included mismatch remains traceable to the reference
1717
+ (`sources.referenceId`/`referenceRequestId`, new), the requirement
1718
+ (`requirementId`, `category`, `subject` - naming its reference region(s)),
1719
+ the Prompt 5 binding (`boundRuntimeTargets`), the candidate
1720
+ (`sources.observationIds`), and the full Prompt 6 evidence
1721
+ (`referenceValue`/`candidateRawValue`/`candidateValue`/`delta`/`tolerance`
1722
+ or `expectedRelationship`/`actualRelationship`) - nothing is replaced by a
1723
+ prose-only summary. No raw image bytes are ever embedded (fidelity carries
1724
+ only identifiers and numeric/categorical evidence, never pixels), and no
1725
+ source-ownership field (`sourceOwner`/`sourceFile`/`component`/`symbol`/
1726
+ `causedBy`) is ever produced - Prompt 7 stops at the runtime target exactly
1727
+ as Prompt 6 did; v0.6's own, unmodified static correlation is the only
1728
+ source-adjacent evidence this context ever carries, and it remains
1729
+ evidence, never edit authorization.
1730
+
1731
+ **Identity impact**: `buildBoundedAgentContextRequestIdentity` gained a
1732
+ final optional `fidelity?: unknown` parameter - omitted (never `null`) from
1733
+ the hashed semantic view when absent, so every pre-Prompt-7 call site keeps
1734
+ producing its exact byte-identical hash (verified by a frozen-vector-style
1735
+ regression test). When present, the caller's already-derived, bounded
1736
+ `BoundedReferenceFidelityProjection` (not the raw Prompt 6 evaluation) is
1737
+ hashed, so identity changes exactly when the content a caller would
1738
+ actually receive changes - never merely because an unselected, dropped
1739
+ requirement result changed somewhere upstream. `sources.referenceId`/
1740
+ `referenceRequestId` follow the identical omit-when-absent convention.
1741
+ Operational paths were never an identity input for this artifact family to
1742
+ begin with (no path parameter exists anywhere in this contract), so path
1743
+ independence holds trivially.
1744
+
1745
+ **Schema-version decision**: no bump. Every new field
1746
+ (`BoundedAgentContextSourceReferences.referenceId`/`referenceRequestId`,
1747
+ `BoundedAgentContextArtifact.fidelity`) is additive and optional; a
1748
+ pre-Prompt-7 artifact/consumer remains fully valid and behaviorally
1749
+ unchanged with all of them absent, matching the exact precedent
1750
+ `correlations?` already established without a version bump in v0.6 Batch 3.
1751
+
1752
+ **Persistence decision: none.** `projectBoundedAgentContext` and
1753
+ `projectReferenceFidelity` both remain pure, programmatic, in-memory
1754
+ functions - no new writer/reader, no new artifact family. This mirrors
1755
+ Prompt 6's own "no persisted fidelity artifact" decision and v0.6's
1756
+ existing "bounded agent context is library-only" architecture.
1757
+
1758
+ **CLI decision**: none added. v0.6 bounded agent context has never had a
1759
+ CLI surface, and this prompt does not introduce one - Prompt 8 is expected
1760
+ to become the first concrete consumer of `projectBoundedAgentContext`'s
1761
+ (now fidelity-aware) programmatic output.
1762
+
1763
+ ## v0.7 Prompt 8 controlled end-to-end external-reference coding-agent correction workflow
1764
+
1765
+ Released as `0.7.0`. One new pure domain module,
1766
+ `domain/referenceCorrectionWorkflow.ts`, plus its identity counterpart,
1767
+ `domain/referenceCorrectionIdentity.ts` - the first stage that composes
1768
+ every Prompt 1-7 and v0.1/v0.4/v0.5/v0.6 owner into one traceable
1769
+ reference-driven correction cycle. It reimplements none of them: reference
1770
+ lifecycle/adequacy (Prompt 1/3), compatibility (Prompt 4), binding (Prompt
1771
+ 5), fidelity (Prompt 6), bounded context (Prompt 7), runtime comparison
1772
+ (v0.4 `compareObservations`), and contract evaluation (v0.5
1773
+ `evaluateFrontendContract`) are all called, never re-derived. No new
1774
+ persisted artifact family, no CLI surface, no remote AI dependency, and no
1775
+ mechanism anywhere in this module (or any module it calls) that edits
1776
+ target source.
1777
+
1778
+ ```ts
1779
+ // domain/referenceCorrectionWorkflow.ts
1780
+ function prepareReferenceCorrection(input: {
1781
+ reference: ExternalReferenceArtifact; // must be approved
1782
+ baselineObservation: ObservationArtifact; // approved baseline / pre-change state
1783
+ baselineContract: PersistentBaselineContract;
1784
+ changeContract: PerChangeContract;
1785
+ bindingDeclarations: readonly ReferenceRuntimeBindingDeclaration[];
1786
+ currentObservation: ObservationArtifact; // fidelity is measured against this (= baselineObservation for the canonical proof)
1787
+ generatedAt: string; producerVersion: string; projectionProfile: ProjectionProfile;
1788
+ }): { ok: true; status: 'handoff-ready'; reviewRequestId: string; fidelity: ReferenceCandidateFidelityEvaluation; handoff: ReferenceCorrectionHandoff }
1789
+ | { ok: true; status: 'blocked-not-evaluated'; reviewRequestId: string; fidelity: ReferenceCandidateFidelityEvaluation }
1790
+ | { ok: false; reason: string };
1791
+
1792
+ function reviewReferenceCorrectionAttempt(input: {
1793
+ // ...same reference/baselineObservation/baselineContract/changeContract/bindingDeclarations...
1794
+ reviewRequestId: string; // must match the id prepareReferenceCorrection returned for this exact semantic review
1795
+ candidateObservation: ObservationArtifact; // fresh, post-edit capture
1796
+ priorAttemptId?: string;
1797
+ }): { ok: true; attempt: ReferenceCorrectionAttemptResult } | { ok: false; reason: string };
1798
+ ```
1799
+
1800
+ **Workflow architecture.** A narrowly-scoped coordinator, not a second
1801
+ workflow engine: it holds no stage catalog, no job scheduler, and no
1802
+ generic orchestration graph. It performs exactly two operations - "prepare"
1803
+ (pre-change evidence -> bounded handoff) and "review" (post-edit candidate
1804
+ -> one composed overall result) - matching this prompt's own explicit
1805
+ guidance that the external-edit boundary must remain a visible seam between
1806
+ two separate calls, never one command that blocks waiting for an external
1807
+ actor.
1808
+
1809
+ **New owners introduced**: `prepareReferenceCorrection`,
1810
+ `reviewReferenceCorrectionAttempt` (composition only - no new evaluation
1811
+ logic), `buildReferenceCorrectionReviewIdentity`/
1812
+ `buildReferenceCorrectionAttemptIdentity` (deterministic identity, see
1813
+ below), and the plain `ReferenceCorrectionHandoff`/
1814
+ `ReferenceCorrectionAttemptResult` result shapes.
1815
+
1816
+ **Existing owners reused, verbatim**: `isApprovedExternalReferenceArtifact`
1817
+ (Prompt 1), `isValidReferenceRuntimeBindingDeclarations` (Prompt 5),
1818
+ `evaluateReferenceCandidateFidelity` (Prompt 6), `projectBoundedAgentContext`
1819
+ (Prompt 7, itself now fidelity-aware), `compareObservations` (v0.4),
1820
+ `evaluateFrontendContract` (v0.5). None of their internal logic is
1821
+ inspected, duplicated, or reimplemented by this module - only their
1822
+ top-level results are read.
1823
+
1824
+ **Approved-reference/approved-baseline requirement.** `prepareReferenceCorrection`
1825
+ and `reviewReferenceCorrectionAttempt` both fail closed (`{ok: false}`) if
1826
+ `reference` is not in the `'approved'` lifecycle state (Prompt 1's own
1827
+ `isApprovedExternalReferenceArtifact` guard) - an imported-but-unapproved
1828
+ reference is never treated as an authoritative target design. Neither
1829
+ function ever calls `approveExternalReference`/`approveAndPersistBaseline`
1830
+ itself; approval remains the caller's own separate, explicit action.
1831
+
1832
+ **Pre-change candidate = approved baseline observation**, for the canonical
1833
+ proof: `prepareReferenceCorrection`'s `currentObservation` and
1834
+ `baselineObservation` are the same value, so the initial reference fidelity
1835
+ can genuinely `FAIL` (measuring the gap between the current, already-
1836
+ approved implementation and the desired new design) while the baseline
1837
+ itself stays fully valid and approved. A caller's own architecture may
1838
+ supply a distinct `currentObservation` only when justified - the workflow
1839
+ does not require them to be identical, only that `currentObservation` and
1840
+ `candidateObservation` are always independently validated
1841
+ `ObservationArtifact`s.
1842
+
1843
+ **Preparation (phase A)**: validates the common preconditions (approved
1844
+ reference, matching baseline/contract coherence, valid binding
1845
+ declarations), evaluates reference fidelity via Prompt 6 against
1846
+ `currentObservation`, and - only when that evaluation actually produced a
1847
+ result (`state !== 'not-evaluated'`) - projects it into a bounded context
1848
+ via Prompt 7/v0.6 and returns the `ReferenceCorrectionHandoff`. A
1849
+ `not-evaluated` fidelity (inadequate reference, or reference/candidate
1850
+ incompatible state) is reported as `status: 'blocked-not-evaluated'` -
1851
+ carrying the full Prompt 6 result for inspection, but never a fabricated
1852
+ handoff pretending evidence is adequate. An ambiguous or unavailable
1853
+ required binding does **not** block preparation outright - it still
1854
+ produces a `handoff-ready` result, with the ambiguity/unavailability
1855
+ visible directly in that requirement's own `unavailable`/`binding-
1856
+ unavailable` mismatch (Prompt 6's own honest per-requirement reporting,
1857
+ unchanged), so the external actor sees exactly why that specific
1858
+ requirement cannot yet be assessed.
1859
+
1860
+ **The handoff** (`ReferenceCorrectionHandoff`) carries `reviewRequestId`,
1861
+ `referenceId`/`referenceRequestId`, `baselineObservationId`,
1862
+ `currentObservationId`, the full Prompt 7 `boundedContext` (already
1863
+ containing bounded fidelity mismatches, protected/preserved context,
1864
+ adequacy/omission/truncation, and - when the caller supplied it - runtime/
1865
+ static correlation), and a fixed, four-line `verificationPlan` explaining
1866
+ in plain language what will be re-checked after the edit (fresh Chromium
1867
+ capture, reference re-evaluation, v0.4/v0.5 re-evaluation, and the exact
1868
+ overall-PASS rule) - never reduced to "make it look like the screenshot".
1869
+ No raw reference image bytes, no full `ObservationArtifact`, and no source
1870
+ excerpt are ever included.
1871
+
1872
+ **Handoff persistence: none.** The handoff is a plain, JSON-serializable,
1873
+ in-memory value returned directly to the caller - no new writer/reader, no
1874
+ new artifact family. A caller that needs the handoff to cross a process/
1875
+ session boundary (e.g. to hand it to an external coding-agent process) is
1876
+ free to serialize it with its own mechanism; observer product code does not
1877
+ own a persisted handoff artifact. This was a deliberate "smallest possible"
1878
+ choice: the handoff's only genuinely new identity is `reviewRequestId`
1879
+ (already deterministic and recomputable from stable inputs - see below), so
1880
+ nothing about it requires observer-managed persistence to remain
1881
+ traceable.
1882
+
1883
+ **Review identity** (`buildReferenceCorrectionReviewIdentity`): a pure
1884
+ function of `{referenceRequestId, baselineObservationId,
1885
+ baselineContractId, baselineContractClauses, changeContractId,
1886
+ changeContractClauses, bindingDeclarations}` only - never a timestamp,
1887
+ never an operational file path. Deliberately hashes each contract's own
1888
+ authored `clauses` content, not merely its `baselineId`/`contractId` label:
1889
+ unlike this repository's content-derived identities elsewhere (e.g.
1890
+ `ObservationArtifact.observationId`), a `PersistentBaselineContract`'s
1891
+ `baselineId` and a `PerChangeContract`'s `contractId` are plain, caller-
1892
+ authored strings (`approveAndPersistBaseline` persists `contract.baselineId`
1893
+ verbatim, never recomputing it from `clauses`) - so two structurally valid
1894
+ contracts could in principle share an id while authoring different clauses.
1895
+ Hashing clause content directly closes that gap (caught during this
1896
+ prompt's own independent-judge review before being reported PASS - see the
1897
+ report's Tooling incidents section). `reviewReferenceCorrectionAttempt`
1898
+ recomputes this same hash from its own inputs and rejects the call
1899
+ (`{ok: false}`) if the caller-supplied `reviewRequestId` does not match -
1900
+ this is the mechanism that makes "no hidden baseline change" an enforced
1901
+ invariant rather than a documented intention: an attempt claiming to belong
1902
+ to a review while actually supplying a different baseline observation,
1903
+ baseline contract (id or clause content), per-change contract (id or clause
1904
+ content), reference, or binding set can never silently succeed.
1905
+
1906
+ **Attempt identity** (`buildReferenceCorrectionAttemptIdentity`): a pure,
1907
+ deterministic function of `{reviewRequestId, candidateObservationId}` only
1908
+ - deliberately never a fresh random nonce. Every candidate observation
1909
+ already carries its own fresh, collision-resistant instance identity (v0.1's
1910
+ `buildObservationIdentity`), so hashing it together with the review it was
1911
+ captured for gives an attempt id that is both reproducible (the same
1912
+ review+candidate pair always yields the same `attemptId`) and guaranteed
1913
+ distinct per real capture.
1914
+
1915
+ **Attempt history**: caller-managed, not observer-persisted. Because both
1916
+ workflow functions are pure (no internal mutable state, no side effects),
1917
+ an already-returned `ReferenceCorrectionAttemptResult` can never be
1918
+ overwritten by a later call - a caller that keeps every attempt result it
1919
+ receives (in memory, in its own log, or in its own storage) has a complete,
1920
+ immutable, traceable history for free, linked via each attempt's own
1921
+ `reviewRequestId` (shared across all attempts of one review),
1922
+ `priorAttemptId` (an optional, purely informational link to the immediately
1923
+ preceding attempt, carried through unchanged - never consulted by the
1924
+ evaluation logic itself), and `attemptId`.
1925
+
1926
+ **Baseline-across-attempts rule**: enforced structurally, not merely
1927
+ documented. Every call to `reviewReferenceCorrectionAttempt` requires the
1928
+ caller to re-supply `baselineObservation`/`baselineContract` in full, and
1929
+ `compareObservations`/`evaluateFrontendContract` are always invoked with
1930
+ that same baseline against the fresh `candidateObservation` - there is no
1931
+ code path anywhere in this module that compares one candidate against a
1932
+ prior candidate instead. Combined with the `reviewRequestId` coherence
1933
+ check above, a caller cannot silently swap in a different baseline between
1934
+ attempts of the same review without the call being rejected.
1935
+
1936
+ **Overall result composition.** `ReferenceCorrectionOverallState =
1937
+ 'not-evaluated' | 'pass' | 'fail'`:
1938
+
1939
+ - `fidelity.state === 'not-evaluated'` -> overall `'not-evaluated'` - Prompt
1940
+ 6's own explicit blocked state is preserved exactly, never collapsed into
1941
+ an ordinary `'fail'`.
1942
+ - otherwise, `fidelity.state === 'pass' && contractEvaluation.overallVerdict === 'PASS'`
1943
+ -> overall `'pass'`; anything else -> overall `'fail'`.
1944
+
1945
+ A structurally-incomparable baseline/candidate pair is *not* given its own
1946
+ third overall bucket - v0.5's own `evaluateFrontendContract` already
1947
+ returns `'FAIL'` (never `'PASS'`) for that case, per its own established,
1948
+ unmodified precedent, and this workflow reuses that decision rather than
1949
+ re-litigating it. `approvalEligible` is a plain, read-only boolean
1950
+ (`true` iff `overallState === 'pass'`) - it is never itself an approval
1951
+ action; the caller must still invoke the existing explicit
1952
+ `approveAndPersistBaseline`/`approveExternalReference` owners separately,
1953
+ and neither is ever called from within this module.
1954
+
1955
+ **Correction iteration**: `reviewReferenceCorrectionAttempt` is called once
1956
+ per candidate; the caller decides whether and when to call it again after
1957
+ another external edit. There is no loop, no polling, no automatic retry,
1958
+ and no mechanism in this module that itself waits for or drives an external
1959
+ implementation step - the production boundary between "prepare a handoff"
1960
+ and "review a candidate" is the explicit seam a human or an external
1961
+ process controls.
1962
+
1963
+ **Source-editing boundary**: absolute. Neither this module nor anything it
1964
+ calls opens, reads, parses, or writes any target source file; both public
1965
+ operations accept only already-captured `ObservationArtifact`s and already-
1966
+ approved contract/reference artifacts. Real-Chromium candidate capture is
1967
+ always the caller's own responsibility, through the existing, unmodified
1968
+ observation pipeline (`runBrowserCapture`/`buildObservationArtifact`, the
1969
+ same functions `application/observationPersistence.ts#observe` already
1970
+ uses) - Prompt 8 adds no second browser adapter, screenshot engine, target
1971
+ resolver, or evidence-capture path.