@dailephd/my-frontend-observer 0.8.1 → 0.9.0

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 (107) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +107 -7
  3. package/dist/application/projectWorkflowService.d.ts +18 -1
  4. package/dist/application/projectWorkflowService.js +40 -2
  5. package/dist/application/projectWorkflowService.js.map +1 -1
  6. package/dist/application/visualAnnotationContractPromotionService.d.ts +41 -0
  7. package/dist/application/visualAnnotationContractPromotionService.js +143 -0
  8. package/dist/application/visualAnnotationContractPromotionService.js.map +1 -0
  9. package/dist/application/visualAnnotationPersistenceService.d.ts +34 -0
  10. package/dist/application/visualAnnotationPersistenceService.js +68 -0
  11. package/dist/application/visualAnnotationPersistenceService.js.map +1 -0
  12. package/dist/application/visualAnnotationReferenceMaterializationService.d.ts +53 -0
  13. package/dist/application/visualAnnotationReferenceMaterializationService.js +194 -0
  14. package/dist/application/visualAnnotationReferenceMaterializationService.js.map +1 -0
  15. package/dist/artifacts/visualAnnotationArtifactReader.d.ts +19 -0
  16. package/dist/artifacts/visualAnnotationArtifactReader.js +66 -0
  17. package/dist/artifacts/visualAnnotationArtifactReader.js.map +1 -0
  18. package/dist/artifacts/visualAnnotationArtifactWriter.d.ts +42 -0
  19. package/dist/artifacts/visualAnnotationArtifactWriter.js +85 -0
  20. package/dist/artifacts/visualAnnotationArtifactWriter.js.map +1 -0
  21. package/dist/cli.js +464 -454
  22. package/dist/cli.js.map +1 -1
  23. package/dist/domain/visualAnnotation.d.ts +217 -0
  24. package/dist/domain/visualAnnotation.js +584 -0
  25. package/dist/domain/visualAnnotation.js.map +1 -0
  26. package/dist/domain/visualAnnotationIdentity.d.ts +17 -0
  27. package/dist/domain/visualAnnotationIdentity.js +47 -0
  28. package/dist/domain/visualAnnotationIdentity.js.map +1 -0
  29. package/dist/index.d.ts +11 -0
  30. package/dist/index.js +6 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/projectWorkflow/projectPaths.d.ts +9 -0
  33. package/dist/projectWorkflow/projectPaths.js +21 -0
  34. package/dist/projectWorkflow/projectPaths.js.map +1 -1
  35. package/dist/viewer/assets/{index-CN_yb9Uf.css → index-BN41MI7m.css} +1 -1
  36. package/dist/viewer/assets/index-CkKXnlrI.js +9 -0
  37. package/dist/viewer/index.html +2 -2
  38. package/dist/viewer/sw.js +1 -1
  39. package/dist/viewerServer/annotationAuthoring.d.ts +64 -0
  40. package/dist/viewerServer/annotationAuthoring.js +230 -0
  41. package/dist/viewerServer/annotationAuthoring.js.map +1 -0
  42. package/dist/viewerServer/annotationContractPromotion.d.ts +51 -0
  43. package/dist/viewerServer/annotationContractPromotion.js +105 -0
  44. package/dist/viewerServer/annotationContractPromotion.js.map +1 -0
  45. package/dist/viewerServer/annotationReferenceMaterialization.d.ts +40 -0
  46. package/dist/viewerServer/annotationReferenceMaterialization.js +91 -0
  47. package/dist/viewerServer/annotationReferenceMaterialization.js.map +1 -0
  48. package/dist/viewerServer/authoringSecurity.d.ts +59 -0
  49. package/dist/viewerServer/authoringSecurity.js +112 -0
  50. package/dist/viewerServer/authoringSecurity.js.map +1 -0
  51. package/dist/viewerServer/evidence/annotationView.d.ts +28 -0
  52. package/dist/viewerServer/evidence/annotationView.js +43 -0
  53. package/dist/viewerServer/evidence/annotationView.js.map +1 -0
  54. package/dist/viewerServer/evidence/classify.d.ts +3 -1
  55. package/dist/viewerServer/evidence/classify.js +12 -0
  56. package/dist/viewerServer/evidence/classify.js.map +1 -1
  57. package/dist/viewerServer/evidence/discovery.d.ts +2 -0
  58. package/dist/viewerServer/evidence/discovery.js +6 -0
  59. package/dist/viewerServer/evidence/discovery.js.map +1 -1
  60. package/dist/viewerServer/evidence/handles.js +1 -0
  61. package/dist/viewerServer/evidence/handles.js.map +1 -1
  62. package/dist/viewerServer/evidence/index.d.ts +29 -0
  63. package/dist/viewerServer/evidence/index.js +43 -1
  64. package/dist/viewerServer/evidence/index.js.map +1 -1
  65. package/dist/viewerServer/evidence/mediaResolver.d.ts +1 -1
  66. package/dist/viewerServer/evidence/mediaResolver.js +28 -2
  67. package/dist/viewerServer/evidence/mediaResolver.js.map +1 -1
  68. package/dist/viewerServer/evidence/projection.d.ts +5 -1
  69. package/dist/viewerServer/evidence/projection.js +19 -0
  70. package/dist/viewerServer/evidence/projection.js.map +1 -1
  71. package/dist/viewerServer/httpServer.d.ts +13 -2
  72. package/dist/viewerServer/httpServer.js +278 -4
  73. package/dist/viewerServer/httpServer.js.map +1 -1
  74. package/dist/viewerServer/viewerService.d.ts +8 -0
  75. package/dist/viewerServer/viewerService.js +38 -2
  76. package/dist/viewerServer/viewerService.js.map +1 -1
  77. package/docs/ARCHITECTURE.md +108 -21
  78. package/docs/CI_CD.md +57 -1
  79. package/docs/COMMANDS.md +44 -4
  80. package/docs/CONTRACTS.md +78 -8
  81. package/docs/CURRENT_STATE.md +157 -35
  82. package/docs/DEVELOPMENT.md +8 -3
  83. package/docs/PROJECT_DESCRIPTION.md +4 -1
  84. package/docs/PROJECT_MILESTONES.md +4 -0
  85. package/docs/PROJECT_OVERVIEW.md +41 -17
  86. package/docs/QUICKSTART.md +52 -39
  87. package/docs/RELEASE.md +16 -11
  88. package/docs/ROADMAP.md +406 -66
  89. package/docs/SECURITY.md +71 -14
  90. package/docs/WORKFLOWS.md +151 -23
  91. package/docs/plans/v0.9-implementation-plan.md +1529 -0
  92. package/docs/reports/v0.9-architecture-retrieval.md +567 -0
  93. package/docs/reports/v0.9-batch1-visual-annotation-foundation.md +351 -0
  94. package/docs/reports/v0.9-batch2-viewer-annotation-authoring-boundary.md +438 -0
  95. package/docs/reports/v0.9-batch3-runtime-screenshot-annotation-authoring.md +412 -0
  96. package/docs/reports/v0.9-batch4-external-reference-annotation-authoring.md +452 -0
  97. package/docs/reports/v0.9-batch5-runtime-intent-contract-promotion.md +535 -0
  98. package/docs/reports/v0.9-batch6-reference-materialization.md +514 -0
  99. package/docs/reports/v0.9-batch7-integrated-acceptance.md +644 -0
  100. package/docs/reports/v0.9-demo-foundation.md +589 -0
  101. package/docs/reports/v0.9-final-pre-release-readiness.md +209 -0
  102. package/docs/reports/v0.9-final-readiness-corrections.md +530 -0
  103. package/docs/reports/v0.9-pre-release-readiness.md +170 -0
  104. package/docs/reports/v0.9-tutorial-end-to-end-acceptance.md +980 -0
  105. package/docs/reports/v0.9-tutorial-integration.md +731 -0
  106. package/package.json +2 -2
  107. package/dist/viewer/assets/index-D98S1_2d.js +0 -9
@@ -0,0 +1,535 @@
1
+ # v0.9 Batch 5 Report: Runtime Intent and Contract Promotion
2
+
3
+ ## 1. VERDICT
4
+
5
+ PASS_V0_9_BATCH5_RUNTIME_INTENT_CONTRACT_PROMOTION
6
+
7
+ ## 2. Repository identity
8
+
9
+ 1. Repository: `C:\Users\daile\Projects\my-frontend-observer`
10
+ 2. Branch: `master`
11
+ 3. Starting HEAD: `194c8e3df49017e803c2b99c6cad7d7c7cd69508`
12
+ 4. Ending HEAD before the final commit: `194c8e3df49017e803c2b99c6cad7d7c7cd69508`
13
+ 5. Package version: `0.8.1` (unchanged in `package.json` and `package-lock.json`)
14
+ 6. Viewer protocol version: `1.2.0` (was `1.1.0`)
15
+
16
+ ## 3. Prompt 4 entry gate
17
+
18
+ 1. Prompt 4 verdict: `PASS_V0_9_BATCH4_EXTERNAL_REFERENCE_ANNOTATION_AUTHORING`
19
+ 2. Prompt 4 commit: `194c8e3df49017e803c2b99c6cad7d7c7cd69508`
20
+ 3. Viewer protocol at entry: `1.1.0`
21
+ 4. Annotation schema: `1.0.0` (unchanged)
22
+ 5. Frontend contract schema: `1.0.0` (unchanged)
23
+ 6. Preflight found a clean tracked worktree on `master` at the required HEAD.
24
+ 7. `git ls-files -v docs/reports/v0.9-architecture-retrieval.md` printed `S`
25
+ at entry and before commit. The `skip-worktree` state was preserved and
26
+ the file was not restored, overwritten, or committed. No pull, rebase,
27
+ reset, or clean was run.
28
+
29
+ ## 4. Runtime intent vocabulary
30
+
31
+ 1. New pure model `viewer/src/annotation/runtimeIntent.ts` builds only the
32
+ already-frozen runtime `VisualAnnotationIntent`.
33
+ 2. Operations are exactly `inspect`, `move`, `resize`, `remove`, `preserve`.
34
+ 3. Categories are exactly `requested`, `expected-dependent`, `protected`, and
35
+ `preserved`.
36
+ 1. `unexpected` is never offered.
37
+ 2. If `unexpected` is supplied anyway, construction refuses it
38
+ (unit-tested).
39
+ 3. The domain validator is unchanged.
40
+ 4. `expected-dependent` requires `required` or `permitted`. For every other
41
+ category the mode is absent.
42
+ 5. The model never reads mark geometry, never calls an API, and never
43
+ persists or evaluates anything.
44
+
45
+ ## 5. Move mappings
46
+
47
+ Move requires an explicit `runtime-target` association.
48
+
49
+ 1. `right` maps to `property-increases x`.
50
+ 2. `left` maps to `property-decreases x`.
51
+ 3. `down` maps to `property-increases y`.
52
+ 4. `up` maps to `property-decreases y`.
53
+
54
+ `minimumDeltaPx` is never set. Arrow direction and drawing position never
55
+ choose the direction. This is browser-verified: an arrow drawn pointing right
56
+ stays `uninterpreted`, and an explicit "move left" gives
57
+ `property-decreases header.x`.
58
+
59
+ ## 6. Resize mappings
60
+
61
+ Resize requires an explicit `runtime-target` association.
62
+
63
+ 1. `wider` maps to `property-increases width`.
64
+ 2. `narrower` maps to `property-decreases width`.
65
+ 3. `taller` maps to `property-increases height`.
66
+ 4. `shorter` maps to `property-decreases height`.
67
+
68
+ `minimumDeltaPx` is never set.
69
+
70
+ ## 7. Preserve mappings
71
+
72
+ 1. With a `runtime-target` association, the user picks one property (`x`,
73
+ `y`, `width`, `height`) and a contract tolerance (`exact`, `absolute-px`,
74
+ or `percent`, amount between 0 and 100). The result is
75
+ `property-unchanged-within-tolerance`.
76
+ 2. `absolute-px` is labeled "runtime CSS pixels". It is kept distinct from
77
+ the reference vocabulary's `absolute-reference-px`.
78
+ 3. With a `runtime-relationship` association, the result is
79
+ `relationship-unchanged` with the association's `relationshipKind`, and
80
+ with `subjectTarget` and `relatedTarget` only when present. Page-level
81
+ relationships get no invented targets.
82
+ 4. No preserve UI exists for visibility, clipping, scroll, document width,
83
+ bounds, fit, overlap, or relative width.
84
+
85
+ ## 8. Remove unsupported behavior
86
+
87
+ 1. `remove` requires a target association and has no `contractPrimitive`.
88
+ 2. Once confirmed, the UI shows exactly: "Confirmed but not canonically
89
+ promotable: the current frontend-contract vocabulary has no target-absent
90
+ primitive."
91
+ 3. Its promotion checkbox is disabled with that reason.
92
+ 4. A forced API promotion returns 422 and writes no contract
93
+ (browser-verified).
94
+
95
+ ## 9. Inspect informational behavior
96
+
97
+ 1. `inspect` can be bound or unbound.
98
+ 2. It can be a candidate and then confirmed.
99
+ 3. It is never listed for promotion.
100
+ 4. A forced API promotion returns 422 (browser-verified).
101
+
102
+ ## 10. Runtime confirmation workflow
103
+
104
+ 1. "Set candidate intent" sets `{ state: 'candidate', intent }` only after
105
+ the form is complete and the association is compatible.
106
+ 2. When the item is unbound, the UI shows "Select and explicitly associate a
107
+ runtime target or relationship first." and the button stays disabled.
108
+ 3. The selected item shows the exact intent as JSON: operation, category,
109
+ mode, and primitive.
110
+ 4. The item also shows a promotion status line.
111
+ 5. "Confirm runtime intent" is enabled only for a candidate. It sets
112
+ `confirmed` with a new `confirmedAt`.
113
+ 6. Nothing confirms automatically: not selecting an intent, association,
114
+ drawing, Save, or the promotion checkbox.
115
+ 7. The shared helper `viewer/src/annotation/confirmation.ts` now holds
116
+ `confirmCandidate` and `withdrawChangedConfirmation`. The code moved there
117
+ unchanged from `referenceIntent.ts`, which still re-exports
118
+ `confirmCandidate`. The Prompt 4 unit and browser tests pass unchanged.
119
+
120
+ ## 11. Confirmation invalidation
121
+
122
+ 1. `useRuntimeAnnotationDraft` now routes every edit through
123
+ `applyRuntimeItemEdit`: reconcile, then withdraw a changed confirmation.
124
+ Edits here are mark moves, note text, association set or clear, and new
125
+ intents.
126
+ 2. Reconciliation:
127
+ 1. A target change retargets `primitive.target`.
128
+ 2. A relationship change rewrites the `relationship-unchanged` primitive.
129
+ 3. An incompatible association, or clearing it, sets the change
130
+ interpretation back to `uninterpreted`. It is never coerced.
131
+ 3. A confirmed item whose mark, association, or intent changes goes back to
132
+ `candidate` without `confirmedAt`. A no-op edit keeps confirmation.
133
+ 4. Browser-verified: changing a confirmed move from `header` to `footer`
134
+ gives `primitive.target: footer` and state candidate. Reconfirmation
135
+ works. Moving the mark again gives candidate.
136
+
137
+ ## 12. Promotable-item rules
138
+
139
+ An item is promotable only when all of these hold:
140
+
141
+ 1. It is `confirmed`.
142
+ 2. `intent.kind` is `change`.
143
+ 3. The operation is `move`, `resize`, or `preserve`.
144
+ 4. A valid `contractPrimitive` is present, checked with the existing
145
+ `isValidContractPrimitive`.
146
+ 5. A runtime association is present.
147
+ 6. The primitive matches both the association and the operation:
148
+ 1. `move` uses `x` or `y`; `resize` uses `width` or `height`.
149
+ 2. Both use increases or decreases on the associated target, with no
150
+ `minimumDeltaPx`.
151
+ 3. Preserve on a target uses a property primitive on that target.
152
+ 4. Preserve on a relationship uses exactly that relationship.
153
+
154
+ Uninterpreted, candidate, inspect, remove, missing-primitive, unbound, and
155
+ mismatched items are not promotable. The viewer's `runtimePromotionStatus`
156
+ drives presentation only. The server repeats every check.
157
+
158
+ ## 13. Annotation-to-contract application service
159
+
160
+ New `src/application/visualAnnotationContractPromotionService.ts`,
161
+ `promoteVisualAnnotationContract(options)`:
162
+
163
+ 1. Validates the annotation with the canonical `isValidVisualAnnotationArtifact`.
164
+ 2. Requires a `runtime-observation` source. Anything else is
165
+ `unsupported-source`.
166
+ 3. Requires the supplied observation to exactly match the source.
167
+ Otherwise it is `source-mismatch`. The fields compared are
168
+ `observationId`, `requestId`, schema version, screenshot recorded and path
169
+ equal, and viewport width and height.
170
+ 4. Rejects the whole promotion (`invalid-selection`, nothing written) for any
171
+ of these: empty, more than 100, duplicate, unknown, or any non-promotable
172
+ selected item. It never writes a partial contract.
173
+ 5. Clause order follows `selectedItemIds` exactly.
174
+ 6. Persists exactly once through `persistPerChangeContract`, which is
175
+ unit-verified with a spy.
176
+ 7. The result is a discriminated union with a failure `code` and `reason`,
177
+ consistent with the other application-service result shapes.
178
+
179
+ ## 14. Contract identity and clauses
180
+
181
+ 1. Each clause is
182
+ `{ clauseId: buildClauseIdentity(primitive), category, expectedDependentMode?, primitive, supportingEvidence }`.
183
+ There is no `supersedesBaselineClauseIds`.
184
+ 2. `contractRequestId` is
185
+ `buildFrontendContractRequestIdentity(source.observationId, clauses.map(({ primitive }) => ({ primitive })), [])`.
186
+ 3. `contractId` is `buildFrontendContractInstanceIdentity(contractRequestId)`.
187
+ Unit-verified: the same request id with a fresh instance id.
188
+ 4. The contract is an ordinary `PerChangeContract` with canonical kind,
189
+ schema version `1.0.0`, and class `change`. It gets no annotation-specific
190
+ fields.
191
+
192
+ ## 15. Supporting evidence references
193
+
194
+ Each clause carries exactly these two existing `EvidenceReference` values:
195
+
196
+ 1. `visualAnnotation.<annotationId>.source`
197
+ 2. `visualAnnotation.<annotationId>.items.<annotationItemId>`
198
+
199
+ The annotation is never embedded (unit and browser verified).
200
+
201
+ ## 16. Configured baseline resolution
202
+
203
+ 1. The server promotion module reads the project config with
204
+ `readProjectConfig`.
205
+ 2. When `acceptance.contract` exists, it resolves `baselineArtifact` with
206
+ `resolveContainedAcceptancePath` and reads it with
207
+ `readPersistentBaselineContract`.
208
+ 1. A missing, unsafe, unreadable, or invalid baseline gives 409 and no
209
+ contract.
210
+ 2. Otherwise `activeBaselineIds` is `[baseline.baselineId]`.
211
+ 3. Without contract acceptance, `activeBaselineIds` is `[]`.
212
+ 4. Nothing is inferred from aliases, observations, annotations, existing
213
+ change contracts, or directory names.
214
+
215
+ ## 17. Project activation behavior
216
+
217
+ 1. `projectPaths.ts` adds `projectContractsRoot(projectRoot)` and
218
+ `contractOutputLocation()`, which returns
219
+ `.frontend-observer/evidence/contracts`.
220
+ 2. `projectWorkflowService.ts` adds
221
+ `activateProjectChangeContract(projectRoot, changeArtifactPath)`:
222
+ 1. It reads the raw on-disk JSON and validates it.
223
+ 2. It requires an existing `acceptance.contract`. Otherwise it returns
224
+ `not-configured` without writing.
225
+ 3. It changes only `acceptance.contract.changeArtifact`, keeping key order
226
+ and every other value.
227
+ 4. It validates the full updated configuration before writing and never
228
+ writes an invalid one.
229
+ 5. It writes atomically with the existing `atomicTextWrite`.
230
+ 3. Activation happens only when `activateForCheck` is `true`. The result is
231
+ `not-requested`, `activated`, `not-configured` (with the exact bounded
232
+ reason), or `failed`. A failure never deletes the persisted contract and
233
+ never retries.
234
+ 4. Unit-verified: the updated config is re-read through the unchanged
235
+ `check` acceptance helpers (`resolveContainedAcceptancePath` and
236
+ `readPerChangeContract`), and the new contract loads. `projectCheckService.ts`
237
+ was not modified.
238
+
239
+ ## 18. Promotion API
240
+
241
+ 1. `POST /api/annotations/:handle/promote-contract`. `GET` and `HEAD` on it
242
+ return 405 `Allow: POST`.
243
+ 2. `POST /api/annotations/:handle/materialize-reference` still returns 405.
244
+ PUT, PATCH, and DELETE stay unsupported.
245
+ 3. The route uses one shared gate, `readAuthoringJsonRequest`, extracted from
246
+ the Prompt 2 save handler and now used by both routes. It covers authoring
247
+ enabled, Host, Origin, token, JSON content type, identity encoding, the
248
+ 262144-byte limit, and JSON parsing. There is no second token, session, or
249
+ parser.
250
+ 4. The body is closed to `{ itemIds, activateForCheck }`:
251
+ 1. `itemIds` is non-empty, at most 100, non-empty strings, unique.
252
+ 2. `activateForCheck` is a required boolean.
253
+ 3. Unknown fields give 400.
254
+ 5. The new module `src/viewerServer/annotationContractPromotion.ts` owns the
255
+ route use case. It was added so the route stays thin.
256
+ 1. It resolves the annotation with `getAnnotationView`, which uses
257
+ `loadArtifactByHandle`.
258
+ 2. It requires the runtime source. Reference annotations give 409.
259
+ 3. It resolves the exact source through the Prompt 2 annotation source
260
+ view plus `loadArtifactByHandle`. A missing source gives 404.
261
+ 4. It resolves the configured baseline, calls
262
+ `promoteVisualAnnotationContract` once with `contractOutputLocation()`
263
+ and the project root, and then optionally activates.
264
+ 5. It runs through the session's single authoring write queue. That queue
265
+ is the Prompt 2 queue exposed as `runSerializedAuthoringWrite`, so
266
+ promotion never interleaves with saves.
267
+ 6. Status mapping:
268
+ 1. 201: success.
269
+ 2. 400: request shape or malformed handle.
270
+ 3. 403: security gate.
271
+ 4. 404: unknown annotation or missing source.
272
+ 5. 409: not an annotation, a reference annotation, invalid project config,
273
+ invalid configured baseline, or source mismatch.
274
+ 6. 413: body too large. 415: media type or encoding.
275
+ 7. 422: non-promotable selection.
276
+ 8. 500: persistence failure.
277
+ 7. The 201 body is exactly
278
+ `{ ok, contractId, contractRequestId, handle, clauseCount, activation }`.
279
+ The handle is `change-contract:contracts%2F<contractId>`. No paths are
280
+ returned.
281
+ 8. `VIEWER_PROTOCOL_VERSION` is now `1.2.0`. The Prompt 2 security test was
282
+ updated for the new version and for the now-real promote route. Its 405
283
+ list still includes `materialize-reference`.
284
+
285
+ ## 19. Runtime promotion UI
286
+
287
+ 1. `RuntimeAnnotationPanel` gains two sections. No runtime controls were
288
+ added to the reference panel.
289
+ 1. "Runtime intent": operation, direction, category, mode, property,
290
+ tolerance, "Set candidate intent", the promotion status line, and
291
+ "Confirm runtime intent".
292
+ 2. "Confirmed contract intent".
293
+ 2. Promotion is disabled with "Save the annotation before promoting contract
294
+ intent." while the draft is dirty or unsaved. The draft is never saved
295
+ silently.
296
+ 3. For a saved, clean annotation there is one checkbox per confirmed change
297
+ item, showing operation, category, and primitive summary.
298
+ 1. Non-promotable confirmed items such as remove show a disabled checkbox
299
+ and a reason.
300
+ 2. Inspect and candidate items are not listed.
301
+ 4. "Activate this change contract for project check" defaults to unchecked.
302
+ 5. "Promote selected to change contract" sends only the checked item ids, in
303
+ check order, through the new hook `useAnnotationContractPromotion` with the
304
+ in-memory token.
305
+ 6. Success shows the contract id, clause count, and activation state in
306
+ words. Local decision: a successful promotion clears the checkbox
307
+ selection so the same items are not promoted again by accident.
308
+ 7. Errors show a bounded message per status, keep the draft and
309
+ confirmations, and never retry.
310
+
311
+ ## 20. Existing evaluator authority
312
+
313
+ 1. Unit test: an annotation over real before/after observations (workspace
314
+ width 600 to 650) is promoted into a contract with
315
+ `resize wider (expected-dependent required)` and
316
+ `preserve height (absolute-px 2)`.
317
+ 2. The existing `evaluateFrontendContract` gives the same `overallVerdict`,
318
+ the same clause ids, and the same per-clause statuses (pass, pass) for
319
+ that contract and for an equivalent hand-authored `PerChangeContract`.
320
+ 3. A source search of all new and changed Batch 5 source found no PASS/FAIL
321
+ logic and no call to the evaluator. The only hit is a doc comment saying
322
+ the evaluator is the only authority.
323
+ 4. No annotation evaluator exists.
324
+
325
+ ## 21. Temporary-directory discovery hardening
326
+
327
+ 1. Implemented. `src/viewerServer/evidence/discovery.ts` never descends into a
328
+ directory whose basename starts with `.tmp-`, at any depth. The prefix is
329
+ the new exported `WRITER_TEMP_DIRECTORY_PREFIX`. Every canonical writer in
330
+ `src/artifacts/` uses it for its sibling temp directory.
331
+ 2. The skipped directory is not read, classified, returned, or deleted.
332
+ 3. Final artifact discovery is unchanged:
333
+ 1. A normal directory is discovered.
334
+ 2. The same artifact renamed from `.tmp-abc` to `final-abc` is discovered.
335
+ 3. A name like `x.tmp-y` is still discovered.
336
+ 4. All existing discovery tests pass.
337
+ 4. Structural test (`tests/unit/evidenceDiscoveryTempDirectories.test.ts`):
338
+ manifests under `.tmp-top`, `annotations/.tmp-abc`, `contracts/.tmp-def`,
339
+ and `references/nested/.tmp-ghi` are not discovered. This test failed
340
+ before the change and passes after it.
341
+ 5. Concurrency regression: 25 atomic annotation saves while
342
+ `buildEvidenceIndexMetadata` runs continuously. Before committing, a
343
+ heavier ad-hoc probe also ran against the unfixed code: 4 concurrent
344
+ refresh loops and 60 saves of 40 items each. The probe file was deleted.
345
+ Neither reproduced `EPERM` in-process, before or after the change.
346
+ 6. Conclusion: the Windows writer/discovery race is structurally mitigated.
347
+ Discovery can no longer hold handles inside a writer's temp directory. It
348
+ is not claimed as proven fixed, because no deterministic reproduction was
349
+ available.
350
+ 7. The concurrency test remains as a regression guard. The writers were not
351
+ changed and no retry loop was added.
352
+
353
+ ## 22. Unit tests
354
+
355
+ 1. `tests/unit/viewerRuntimeIntentModel.test.ts` (15 tests):
356
+ 1. Vocabulary and `unexpected` refused.
357
+ 2. All 4 move and all 4 resize mappings.
358
+ 3. Arrow geometry never used.
359
+ 4. Preserve property, tolerance bounds, pairwise and page-level
360
+ relationships.
361
+ 5. Remove without a primitive, inspect.
362
+ 6. Expected-dependent mode rule.
363
+ 7. Unbound and incompatible combinations refused.
364
+ 8. Built items accepted by the canonical annotation validator.
365
+ 9. Retargeting, relationship rewrite, and clearing on incompatible
366
+ association.
367
+ 10. Invalidation on move, intent change, and note edit; no-op keeps
368
+ confirmation.
369
+ 11. Promotion status, including the exact remove message.
370
+ 2. `tests/unit/visualAnnotationContractPromotion.test.ts` (5 tests):
371
+ 1. Canonical contract equality, including clause ids, request id, evidence
372
+ paths, selection order, and selected-only.
373
+ 2. Persistence called once, fresh contract id, `activeBaselineIds`
374
+ preserved.
375
+ 3. Expected-dependent mode, no `supersedesBaselineClauseIds`.
376
+ 4. Reference source rejected, and four source-identity mismatches.
377
+ 5. Ten invalid selections rejected atomically, and evaluator authority.
378
+ 3. `tests/unit/viewerAnnotationContractPromotion.test.ts` (9 tests):
379
+ 1. Security gate reuse: token, Origin, Host, 415, 413, closed shape, 405,
380
+ and materialize still 405.
381
+ 2. Read-only viewer 403.
382
+ 3. Unknown, malformed, wrong family, and reference annotation.
383
+ 4. Invalid configured baseline 409, source gone 404.
384
+ 5. Unsupported, mixed, or unknown selection 422 with no contract.
385
+ 6. Selected-only success with bounded response, no paths, loadable change
386
+ contract, config untouched.
387
+ 7. `not-configured` activation.
388
+ 8. Configured baseline id plus `activated` changing only `changeArtifact`,
389
+ with the check acceptance path readable.
390
+ 9. `failed` activation (mocked writer failure) keeps the contract.
391
+ 4. `tests/unit/projectWorkflow.test.ts` (+4 tests): contract path helpers;
392
+ activation updates only `changeArtifact` with key order preserved; no
393
+ acceptance means no mutation; an invalid full config is never written.
394
+ 5. `tests/unit/evidenceDiscoveryTempDirectories.test.ts` (2 tests), see
395
+ section 21.
396
+ 6. `tests/unit/viewerAuthoringSecurity.test.ts` updated for protocol `1.2.0`
397
+ and the new real POST route.
398
+
399
+ ## 23. Browser tests
400
+
401
+ New `tests/browser/runtimeAnnotationContractPromotion.test.ts` has 6
402
+ real-Chromium tests. It reuses `writeInitializedProject` and `TestResources`.
403
+ The file passed on repeated runs.
404
+
405
+ 1. Move to contract, and arrow non-inference:
406
+ 1. An arrow pointing right is uninterpreted and blocked while unbound.
407
+ 2. Associating `header` and choosing move left gives
408
+ `property-decreases header.x`.
409
+ 3. A rectangle on `sidebar` with move right gives
410
+ `property-increases sidebar.x`.
411
+ 4. Both are confirmed.
412
+ 5. Promotion is disabled until saved.
413
+ 6. Promotion yields exact clauses, evidence paths, and empty
414
+ `activeBaselineIds`.
415
+ 2. Selected only:
416
+ 1. Expected-dependent resize wider requires a mode (permitted).
417
+ 2. Preserve `sidebar.width` with absolute-px 2.
418
+ 3. Preserve a canonical relationship.
419
+ 4. Selecting 2 of 3 gives exactly 2 clauses in selection order. The third
420
+ item is promoted separately.
421
+ 3. Remove, inspect, and candidate:
422
+ 1. Confirmed remove shows the exact unsupported message and a disabled
423
+ checkbox.
424
+ 2. Inspect and candidate are not listed, and Promote is disabled.
425
+ 3. Forced API promotion of each gives 422, and no contract exists.
426
+ 4. Confirmation invalidation: target change retargets to `footer` and goes
427
+ back to candidate; reconfirm; a mark move goes back to candidate;
428
+ reconfirm; promotion yields `property-increases footer.y`.
429
+ 5. Explicit activation: with a configured baseline, promotion with the
430
+ checkbox gives `activated`. The config equals the original except
431
+ `changeArtifact`, and `activeBaselineIds` is the configured baseline id.
432
+ 6. Not configured: activation requested gives `not-configured`, the contract
433
+ is persisted, and the config bytes are unchanged.
434
+
435
+ ## 24. Regression validation
436
+
437
+ Every command below was run on Windows in the repository root.
438
+
439
+ 1. `npm run typecheck`: PASS
440
+ 2. `npm run lint`: PASS
441
+ 3. `npm test`: PASS (82 files, 1351 tests)
442
+ 4. `npm run build`: PASS
443
+ 5. `npm run test:browser`: PASS (24 files, 227 tests, real Chromium). This
444
+ includes `runtimeAnnotationAuthoring`, `referenceAnnotationAuthoring`,
445
+ `projectWorkflowViewer`, `pwaHardening`, `observationSvgWorkspace`, and
446
+ the existing contract evaluation and reference suites.
447
+ 6. `npm run test:security`: PASS (unit: 15 files, 158 tests. Browser: 3 files,
448
+ 77 tests). This is after adding the two new security-relevant unit suites
449
+ to the script.
450
+ 7. `npm run check:docs`: PASS
451
+ 8. `npm pack --dry-run`: PASS (352 files, 919.1 kB)
452
+
453
+ ## 25. Security regression
454
+
455
+ 1. Both authoring POST routes share one gate. The Prompt 2 Host, Origin,
456
+ token, body, method, and PWA tests pass, and the promotion route's own
457
+ gate tests pass.
458
+ 2. The token stays in React memory only. The promotion request sends it only
459
+ in the authoring header.
460
+ 3. The promotion route returns no filesystem paths. It writes only a
461
+ canonical contract under `.frontend-observer/evidence/contracts` and, when
462
+ explicitly requested and configured, one field of the project config.
463
+ 4. `package.json` (additional file): the `test:security` script now also runs
464
+ `viewerAnnotationContractPromotion.test.ts` and
465
+ `evidenceDiscoveryTempDirectories.test.ts`. No version or dependency
466
+ changed.
467
+
468
+ ## 26. Scope audit
469
+
470
+ 1. ContractPrimitive vocabulary changed: false
471
+ 2. frontend-contract schema changed: false
472
+ 3. annotation schema changed: false
473
+ 4. reference materialization implemented: false
474
+ 5. external-reference schema changed: false
475
+ 6. reference approval changed: false
476
+ 7. project config schema changed: false
477
+ 8. alias catalog schema changed: false
478
+ 9. annotation evaluator added: false
479
+ 10. package version changed: false
480
+ 11. dependency changed: false
481
+
482
+ No file under `src/domain/` or `src/artifacts/` changed.
483
+ `frontendContractPersistenceService.ts`, `frontendContractEvaluationService.ts`,
484
+ and `projectCheckService.ts` are unchanged. Prompt 4 reference authoring is
485
+ unchanged (its 19 browser tests and 18 unit tests pass).
486
+
487
+ ## 27. Changed files
488
+
489
+ ### 27.1 Added production files
490
+
491
+ 1. `src/application/visualAnnotationContractPromotionService.ts`
492
+ 2. `src/viewerServer/annotationContractPromotion.ts` (route use case, keeps
493
+ the HTTP layer thin)
494
+ 3. `viewer/src/annotation/runtimeIntent.ts`
495
+ 4. `viewer/src/annotation/confirmation.ts` (shared confirmation rules moved
496
+ from `referenceIntent.ts`)
497
+ 5. `viewer/src/hooks/useAnnotationContractPromotion.ts` (promotion request
498
+ state)
499
+
500
+ ### 27.2 Modified production files
501
+
502
+ 1. `src/viewerServer/httpServer.ts`
503
+ 2. `src/viewerServer/annotationAuthoring.ts` (exposes the shared write queue)
504
+ 3. `src/viewerServer/evidence/discovery.ts`
505
+ 4. `src/projectWorkflow/projectPaths.ts`
506
+ 5. `src/application/projectWorkflowService.ts`
507
+ 6. `viewer/src/components/RuntimeAnnotationPanel.tsx`
508
+ 7. `viewer/src/components/ObservationWorkspace.tsx`
509
+ 8. `viewer/src/hooks/useRuntimeAnnotationDraft.ts`
510
+ 9. `viewer/src/annotation/referenceIntent.ts` (imports the shared confirmation
511
+ helpers, behavior unchanged)
512
+ 10. `viewer/src/types/contracts.ts` (type-only `ContractTolerance` re-export)
513
+ 11. `viewer/src/styles/index.css`
514
+ 12. `package.json` (`test:security` script only)
515
+
516
+ ### 27.3 Tests and report
517
+
518
+ 1. Added: `tests/browser/runtimeAnnotationContractPromotion.test.ts`
519
+ 2. Added: `tests/unit/visualAnnotationContractPromotion.test.ts`
520
+ 3. Added: `tests/unit/viewerAnnotationContractPromotion.test.ts`
521
+ 4. Added: `tests/unit/viewerRuntimeIntentModel.test.ts`
522
+ 5. Added: `tests/unit/evidenceDiscoveryTempDirectories.test.ts`
523
+ 6. Modified: `tests/unit/projectWorkflow.test.ts`
524
+ 7. Modified: `tests/unit/viewerAuthoringSecurity.test.ts`
525
+ 8. Added: `docs/reports/v0.9-batch5-runtime-intent-contract-promotion.md`
526
+
527
+ ### 27.4 Generated paths
528
+
529
+ 1. `dist/` was rebuilt. It is ignored.
530
+ 2. Tests create and remove temporary directories under the OS temp directory.
531
+ 3. Validation logs went to the session scratchpad.
532
+
533
+ ## 28. Remaining next step
534
+
535
+ v0.9 Prompt 6 — Canonical external-reference region/requirement materialization