@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,514 @@
1
+ # v0.9 Batch 6 Report: Reference Materialization
2
+
3
+ ## 1. VERDICT
4
+
5
+ PASS_V0_9_BATCH6_REFERENCE_MATERIALIZATION
6
+
7
+ ## 2. Repository identity
8
+
9
+ 1. Repository: `C:\Users\daile\Projects\my-frontend-observer`
10
+ 2. Branch: `master`
11
+ 3. Starting HEAD: `44415bc652f73e44bbfd5c457edf7f0f7daf2b5d`
12
+ 4. Ending HEAD before the final commit: `44415bc652f73e44bbfd5c457edf7f0f7daf2b5d`
13
+ 5. Package version: `0.8.1` (unchanged in `package.json` and `package-lock.json`)
14
+ 6. Viewer protocol version: `1.3.0` (was `1.2.0`)
15
+
16
+ ## 3. Prompt 5 entry gate
17
+
18
+ 1. Prompt 5 verdict: `PASS_V0_9_BATCH5_RUNTIME_INTENT_CONTRACT_PROMOTION`
19
+ 2. Prompt 5 commit: `44415bc652f73e44bbfd5c457edf7f0f7daf2b5d`
20
+ 3. Viewer protocol at entry: `1.2.0`
21
+ 4. Annotation schema: `1.0.0` (unchanged)
22
+ 5. External-reference 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. The
26
+ file was not restored, overwritten, deleted, or committed. No pull,
27
+ rebase, reset, stash, or clean was run.
28
+
29
+ ## 4. Materializable intent boundary
30
+
31
+ 1. Only confirmed `reference-region` (`create` or `refine`) and confirmed
32
+ `reference-requirement` items can be materialized.
33
+ 2. `referenceMaterializationBlockReason` in the new application service is
34
+ the single server rule:
35
+ 1. uninterpreted items are refused;
36
+ 2. candidate items are refused;
37
+ 3. `inspect` and `asset-sensitive` are refused as informational intent;
38
+ 4. runtime `change` intent is refused as not external-reference intent.
39
+ 3. Only the explicitly selected item ids are used. Unselected items never
40
+ contribute, even when they are confirmed.
41
+ 4. Selection is atomic. An empty, oversized (more than 100), duplicate, or
42
+ unknown selection, or any blocked item, rejects the whole request before
43
+ any write.
44
+ 5. The annotation must be saved. The API takes a saved annotation handle only,
45
+ and the UI disables materialization while the draft is unsaved or dirty.
46
+
47
+ ## 5. Source reference validation
48
+
49
+ The service `materializeVisualAnnotationReference` checks, in order:
50
+
51
+ 1. The annotation passes the canonical `isValidVisualAnnotationArtifact`.
52
+ 2. `annotation.source.kind` is `external-reference`. A runtime annotation is
53
+ `invalid-source`.
54
+ 3. The supplied source passes the canonical
55
+ `isValidExternalReferenceArtifact`.
56
+ 4. The source exactly matches the annotation source identity:
57
+ `referenceId`, `referenceRequestId`, schema version, lifecycle, image owner
58
+ id, image SHA-256, and coordinate width and height. For an approved source
59
+ the image fields come from `sourceReference.image`. A mismatch is
60
+ `source-mismatch`.
61
+ 5. The supplied `sourceReferenceRoot` is read through the canonical
62
+ `readExternalReferenceArtifact` and must hold the same `referenceId`.
63
+ This keeps canonical supersession tied to the verified source.
64
+
65
+ ## 6. Source image resolution and verification
66
+
67
+ 1. The route never builds an image path from browser data or from the
68
+ annotation's image owner id.
69
+ 2. The route reads image bytes only through the existing safe
70
+ `resolveMedia` boundary:
71
+ 1. `image` role for an imported source;
72
+ 2. `source-image` role for an approved source (the owning imported image).
73
+ 3. The source artifact root comes from the source's own viewer handle through
74
+ `decodeArtifactHandle` and `resolveContainedDir`.
75
+ 4. The service recomputes SHA-256 over the supplied bytes and requires it to
76
+ equal the source image SHA-256.
77
+ 5. The bytes are passed unchanged to `importExternalReference`. Nothing is
78
+ transcoded, recompressed, or rasterized. Annotation marks are never baked
79
+ into the image.
80
+
81
+ ## 7. Region composition
82
+
83
+ 1. Final regions start from a copy of `sourceReference.regions ?? []`. The
84
+ source array and its region objects are never mutated.
85
+ 2. Selected refinements are applied first, in place.
86
+ 3. Selected creates are then appended in selection order.
87
+ 4. The final set is validated with the canonical
88
+ `isValidReferenceRegions(finalRegions, image.width, image.height)`. A
89
+ failure is `invalid-materialized-reference` and nothing is written.
90
+
91
+ ## 8. Region create behavior
92
+
93
+ 1. The confirmed `intent.region` is used exactly: id casing and rectangle are
94
+ kept, and no geometry is inferred from the mark.
95
+ 2. A create id that case-insensitively matches an existing source region is
96
+ rejected.
97
+ 3. Two selected creates with case-insensitively equal ids are rejected.
98
+ 4. Nothing is renamed.
99
+
100
+ ## 9. Region refine behavior
101
+
102
+ 1. A refine must target a region that exists in the selected source
103
+ reference. Ids match case-insensitively, as canonical region ids do.
104
+ 2. The refined region keeps its source id spelling and its position. Only the
105
+ rectangle changes. Unrelated regions keep their order and geometry.
106
+ 3. Two selected refinements of the same source region are rejected. Item
107
+ order never chooses a winner.
108
+ 4. Create then refine of the same new region in one request is rejected,
109
+ because the refine target is not a source region.
110
+
111
+ ## 10. Requirement composition
112
+
113
+ 1. Requirements are composed after regions, so a selected requirement may use
114
+ a region created in the same request.
115
+ 2. Each selected `intent.requirement` is validated with the canonical
116
+ `isValidRawReferenceRequirement` and is not reinterpreted.
117
+ 3. A requirement that references a region that is not in the final set (for
118
+ example an unselected create) is rejected by the canonical
119
+ `isValidReferenceRequirements`.
120
+ 4. A selected `region-relationship` requirement must hold for the final
121
+ geometry. The service checks it through the canonical
122
+ `deriveReferenceRequirementExpectation`. A contradicted relationship is
123
+ `invalid-materialized-reference`. This was needed because
124
+ `importExternalReference` validates relationship subjects structurally but
125
+ not against geometry.
126
+ 5. `region-measurement` requirements persist only the raw subject and
127
+ tolerance. No measured value is stored.
128
+ 6. Duplicates and conflicts are left to the canonical
129
+ `buildReferenceRequirement` and `isValidReferenceRequirements`. Nothing is
130
+ silently dropped.
131
+
132
+ ## 11. Existing requirement preservation
133
+
134
+ 1. Source requirements stay first, in their existing order.
135
+ 2. A private `toRawReferenceRequirement` copies only `category`,
136
+ `expectedDependentMode`, `subject`, and `tolerance`. `requirementId` is
137
+ never copied.
138
+ 3. The canonical import recomputes the ids. A unit test proves the ids of
139
+ unchanged source requirements are identical in the new reference.
140
+ 4. Selected new requirements are appended in `selectedItemIds` order.
141
+
142
+ ## 12. Applicability and label preservation
143
+
144
+ 1. `sourceReference.applicability` is passed through exactly when present and
145
+ omitted when absent. The annotation never changes viewport, theme,
146
+ application state, or authenticated state.
147
+ 2. `sourceReference.provenance.label` is passed through when present. When the
148
+ source has no label, none is invented. Viewer alias text is never used.
149
+
150
+ ## 13. Canonical external-reference import
151
+
152
+ 1. The new reference is persisted by exactly one call to the canonical
153
+ `importExternalReference(imageBytes, { outputLocation, cwd, label,
154
+ supersedesReferenceRoot, regions, requirements, applicability })`.
155
+ 2. `referenceRequestId` and `referenceId` are exactly the canonical service
156
+ output. Neither is constructed by Observer code outside that service.
157
+ 3. Import diagnostics `invalid-reference-region`,
158
+ `invalid-reference-requirement`, and `invalid-reference-applicability` map
159
+ to `invalid-materialized-reference`. Any other failure maps to
160
+ `persistence-failure`.
161
+ 4. `importExternalReference` and the external-reference writer were not
162
+ changed. No `BLOCKED_V0_9_BATCH6_CANONICAL_REFERENCE_SERVICE_CONFLICT` was
163
+ needed.
164
+ 5. New project path helpers in `src/projectWorkflow/projectPaths.ts`:
165
+ `projectReferencesRoot(projectRoot)` and `referenceOutputLocation()`
166
+ (`.frontend-observer/evidence/references`). Existing references are not
167
+ migrated.
168
+
169
+ ## 14. Supersession and lifecycle
170
+
171
+ 1. `supersedesReferenceRoot` is the verified source root, so the canonical
172
+ import sets `supersedesReferenceId` to the selected source `referenceId`.
173
+ 2. For an approved source this is the approved reference id, not the imported
174
+ image owner id. Unit and browser tests prove it.
175
+ 3. The new lifecycle is always `imported`, even from an approved source.
176
+ 4. Identical inputs give the same canonical `referenceRequestId` and a fresh
177
+ distinct `referenceId` (unit-tested).
178
+ 5. Source manifests and images are byte-identical after materialization. For
179
+ an approved source both the approved directory and the owning imported
180
+ directory were snapshotted.
181
+
182
+ ## 15. Materialization API
183
+
184
+ 1. Route: `POST /api/annotations/:handle/materialize-reference`, in
185
+ `src/viewerServer/httpServer.ts`. The use case lives in the new
186
+ `src/viewerServer/annotationReferenceMaterialization.ts` so the HTTP layer
187
+ stays thin.
188
+ 2. It uses the shared Prompt 2/5 gate `readAuthoringJsonRequest` (Host,
189
+ Origin, token, JSON content type, content encoding, body limit) and the
190
+ shared session write queue `runSerializedAuthoringWrite`. No second parser
191
+ or queue exists.
192
+ 3. The body is exactly `{ itemIds }`. The `itemIds` rule is now one shared
193
+ helper, `parseAuthoringItemIds` in `annotationAuthoring.ts`: a non-empty
194
+ array of at most 100 unique non-empty strings. The promotion parser uses
195
+ the same helper, with identical behavior and messages. Unknown fields
196
+ return 400.
197
+ 4. Resolution:
198
+ 1. `getAnnotationView` resolves the annotation and its exact source handle.
199
+ 2. The source must be `available` with family
200
+ `external-reference-imported` or `external-reference-approved`.
201
+ 3. `loadArtifactByHandle` loads the source artifact.
202
+ 4. Image bytes and the source root are resolved as in section 6.
203
+ 5. The canonical service is called once with `referenceOutputLocation()`
204
+ and `cwd` set to the authoring project root.
205
+ 5. Success returns 201 with exactly `ok`, `referenceId`, `referenceRequestId`,
206
+ `handle` (`external-reference-imported:references%2F<referenceId>`),
207
+ `lifecycle: 'imported'`, `supersedesReferenceId`, `regionCount`,
208
+ `requirementCount`, and `approvalRequired: true`. It contains no paths, no
209
+ project root, and no token.
210
+ 6. Failure status mapping:
211
+ 1. 400: malformed handle, invalid JSON, or invalid body shape.
212
+ 2. 403: authoring or security failure, including a read-only viewer.
213
+ 3. 404: unknown annotation, source reference unavailable, or source image
214
+ unavailable.
215
+ 4. 409: handle is not a visual annotation, runtime annotation, invalid
216
+ source, or source mismatch.
217
+ 5. 413: body too large.
218
+ 6. 415: wrong content type or content encoding.
219
+ 7. 422: unsupported or unconfirmed selection, invalid region or requirement
220
+ composition, stale relationship, or create/refine conflict.
221
+ 8. 500: image read failure or persistence failure.
222
+ 9. 405 with `Allow: POST` for GET and HEAD on the route. PUT, PATCH, and
223
+ DELETE keep the existing global 405.
224
+ 7. `VIEWER_PROTOCOL_VERSION` is `1.3.0`. The allowed POST surface is exactly
225
+ `POST /api/annotations`, `POST /api/annotations/:handle/promote-contract`,
226
+ and `POST /api/annotations/:handle/materialize-reference`.
227
+ 8. `materializeVisualAnnotationReference` and its options, result, and failure
228
+ code types are exported from `src/index.ts`.
229
+
230
+ ## 16. Viewer materialization UI
231
+
232
+ 1. `ReferenceAnnotationPanel` has a new "Confirmed reference intent" section,
233
+ shown only when authoring is available.
234
+ 2. It lists reference-region, reference-requirement, inspect, and
235
+ asset-sensitive items.
236
+ 1. Only confirmed region and requirement items have enabled checkboxes.
237
+ 2. Informational items are disabled with "Informational annotation intent
238
+ is not materialized into the external-reference contract."
239
+ 3. Candidate items are disabled with "Candidate intent must be confirmed
240
+ before materialization."
241
+ 3. While the draft is unsaved or dirty, the section shows "Save the annotation
242
+ before materializing reference intent." and the button stays disabled.
243
+ 4. The button is "Materialize selected into new reference revision".
244
+ 5. The warning is always shown: "This creates a new imported reference
245
+ revision. The current reference remains unchanged. The new reference is
246
+ not approved automatically."
247
+ 6. Success shows the new reference id, lifecycle imported, supersedes,
248
+ region and requirement counts, approval required, and "The new reference
249
+ is imported but not approved. Use the existing explicit approval workflow
250
+ when ready."
251
+ 7. The selection resets after success and when the loaded annotation changes.
252
+ 8. The viewed reference is never switched. The new reference is discoverable
253
+ through the normal evidence index.
254
+ 9. The new hook `viewer/src/hooks/useAnnotationReferenceMaterialization.ts`
255
+ sends the in-memory token only in the authoring header, uses
256
+ `cache: 'no-store'`, never retries, and maps each failure status to a
257
+ bounded message.
258
+ 10. The presentation helpers `referenceMaterializationStatus` and
259
+ `isReferenceMaterializationListItem` were added to
260
+ `viewer/src/annotation/referenceIntent.ts`. The server remains
261
+ authoritative.
262
+ 11. There is no approval button and no project reference activation.
263
+
264
+ ## 17. No-approval/no-config-mutation guarantees
265
+
266
+ 1. The materialization path never calls `approveExternalReference`.
267
+ 2. Unit, route, and browser tests prove the reference directory gains exactly
268
+ one new imported artifact and no approved sibling.
269
+ 3. The route never reads or writes the project config. Route and browser tests
270
+ configure `acceptance.reference.approvedArtifact` and prove
271
+ `frontend-observer.json` is byte-identical after materialization.
272
+ 4. `src/projectWorkflow/projectConfig.ts` and its schema are unchanged.
273
+ 5. A unit test proves the new imported reference is accepted by the existing
274
+ explicit `approveExternalReference` workflow when run separately.
275
+
276
+ ## 18. Existing relationship and adequacy behavior
277
+
278
+ 1. A unit test runs the canonical `deriveReferenceRegionRelationships` on the
279
+ new reference and finds the normal `content follows-vertically header`
280
+ relationship.
281
+ 2. The same test runs the canonical `deriveReferenceRequirementAdequacy` and
282
+ gets the normal `adequate` result over all requirements.
283
+ 3. No annotation-specific relationship, adequacy, fidelity, or contract result
284
+ was added.
285
+
286
+ ## 19. Unit tests
287
+
288
+ 1. New `tests/unit/visualAnnotationReferenceMaterialization.test.ts` (17
289
+ tests):
290
+ 1. Imported source create: regions appended after the source, imported
291
+ lifecycle, supersession, canonical identity, the canonical import called
292
+ exactly once, and source bytes unchanged.
293
+ 2. Refine in place with order kept and the source rectangle untouched.
294
+ 3. Case-insensitive refine that keeps the source id spelling.
295
+ 4. Create plus requirement succeeds. The requirement alone (unselected
296
+ create) is rejected with no write.
297
+ 5. Existing requirements stay first with identical recomputed ids, and new
298
+ requirements follow selection order.
299
+ 6. Selected-only composition.
300
+ 7. Applicability, label, image SHA, format, dimensions, and exact bytes are
301
+ preserved.
302
+ 8. No invented label or applicability.
303
+ 9. Approved source: supersedes the approved id, imported lifecycle, owning
304
+ image, both source directories unchanged, and no approved sibling.
305
+ 10. The result is accepted by the existing approval workflow.
306
+ 11. Same request identity with a fresh reference id.
307
+ 12. Canonical relationship and adequacy output.
308
+ 13. Rejections with no import call and no write: candidate, informational
309
+ (inspect and asset-sensitive), uninterpreted, unknown, empty,
310
+ duplicate, and mixed selections.
311
+ 14. Create collisions, duplicate refine, and create then refine.
312
+ 15. Stale `left-of` relationship after refine is rejected. The same
313
+ relationship with unchanged geometry succeeds.
314
+ 16. A duplicate of an existing source requirement is rejected.
315
+ 17. Runtime annotation, mismatched source, mismatched root, and wrong image
316
+ bytes.
317
+ 2. New `tests/unit/viewerAnnotationReferenceMaterialization.test.ts` (9
318
+ tests):
319
+ 1. Closed body parser rules, including the 100-id bound.
320
+ 2. Shared gate: bad token, Origin, Host 403, content type and content
321
+ encoding 415, shape and invalid JSON 400, oversized body 413, method
322
+ 405. The service is never called and nothing is written.
323
+ 3. Read-only viewer 403.
324
+ 4. Unknown 404, malformed handle 400, wrong family 409, runtime annotation
325
+ 409.
326
+ 5. Source image moved away 404, approved source moved away 404.
327
+ 6. Candidate, informational, unknown, and uncomposable selections 422 with
328
+ no write.
329
+ 7. Selected-only success with an exact bounded key set, no paths or token,
330
+ a loadable `external-reference-imported` handle, imported lifecycle, no
331
+ approved sibling, and byte-identical project config with
332
+ `acceptance.reference` configured.
333
+ 8. Approved source supersession and exact image bytes.
334
+ 9. Shared write queue: while a materialization is held inside the queue, a
335
+ concurrent annotation save and a contract promotion do not complete.
336
+ Both complete only after it is released.
337
+ 3. Updated `tests/unit/viewerAuthoringSecurity.test.ts`: protocol `1.3.0`
338
+ (two assertions), and the materialize path removed from the POST 405 list
339
+ because the route now exists.
340
+ 4. Updated `tests/unit/viewerAnnotationContractPromotion.test.ts`: the earlier
341
+ "materialize-reference is 405" assertion now expects 400, because the route
342
+ exists and its closed body rejects `{}`.
343
+ 5. Updated `tests/unit/viewerPwaBuild.test.ts` (+1 test): the built service
344
+ worker still has one route with the `/api/` denylist, and precaches no
345
+ annotation or materialization path.
346
+
347
+ ## 20. Browser tests
348
+
349
+ New `tests/browser/referenceAnnotationMaterialization.test.ts` has 4
350
+ real-Chromium tests. It reuses `writeReferenceCandidateFixture`,
351
+ `TestResources`, and the Prompt 4 interaction patterns. No new fixture
352
+ framework was added.
353
+
354
+ 1. Create, through the real UI, in a project with `acceptance.reference`
355
+ configured:
356
+ 1. draw a rectangle, create a candidate region, and confirm;
357
+ 2. the unsaved message, disabled button, and warning are shown;
358
+ 3. save, select, and materialize.
359
+ It then checks:
360
+ 1. 201 with imported lifecycle, approval required, and supersession;
361
+ 2. every success UI line;
362
+ 3. the selection reset;
363
+ 4. canonical regions;
364
+ 5. exactly one new directory and no approved sibling;
365
+ 6. byte-identical sources and project config;
366
+ 7. the new handle in `/api/index` as `external-reference-imported`, while
367
+ the inspector still shows the original reference;
368
+ 8. after reload, two imported references in the evidence list.
369
+ 2. Refine from an approved source: select `sidebar`, draw, refine, confirm,
370
+ save, and materialize. The new geometry is correct and `header` is
371
+ unchanged. Supersession is the approved id. Image bytes equal the owning
372
+ imported image. Both source directories are unchanged and there is still
373
+ one approved record.
374
+ 3. Create plus requirement, selected only: two confirmed creates and a
375
+ confirmed protected `region-a width` requirement with an
376
+ absolute-reference-px 4 tolerance. Selecting `region-a` and the
377
+ requirement materializes both. `region-b` is absent.
378
+ 4. Informational and candidate: confirmed inspect and asset-sensitive items
379
+ show the informational reason with disabled checkboxes. A candidate create
380
+ is disabled with its reason. Forced API requests for each return 422, and
381
+ no reference is written.
382
+ 5. `tests/browser/pwaHardening.test.ts` now also sends a POST to the
383
+ materialization route from the page. It returns 403 with `no-store`, and
384
+ cache storage holds zero `/api/` entries.
385
+ 6. Focused run of the new browser test plus the affected
386
+ `referenceAnnotationAuthoring`, `runtimeAnnotationContractPromotion`,
387
+ `referenceCandidateWorkspace`, `referenceBindingFidelityWorkspace`, and
388
+ `pwaHardening` suites: PASS (6 files, 53 tests).
389
+
390
+ ## 21. Regression validation
391
+
392
+ Every command below was run on Windows in the repository root.
393
+
394
+ 1. Focused unit run of the two new unit suites plus
395
+ `externalReferencePersistenceService`, `externalReferenceRequirements`,
396
+ `externalReferenceRegions`, `externalReferenceRegionRelationships`,
397
+ `viewerReferenceAnnotationModel`, and `evidenceDiscoveryTempDirectories`:
398
+ PASS (8 files, 138 tests)
399
+ 2. `npm run typecheck`: PASS
400
+ 3. `npm run lint`: PASS. The first run reported one unused test helper, which
401
+ was removed. The rerun passed.
402
+ 4. `npm test`: PASS (84 files, 1378 tests)
403
+ 5. `npm run build`: PASS
404
+ 6. `npm run test:browser`: PASS (25 files, 231 tests, real Chromium)
405
+ 7. `npm run test:security`: PASS (unit: 16 files, 167 tests. Browser: 3 files,
406
+ 77 tests)
407
+ 8. `npm run check:docs`: PASS (17 required files)
408
+ 9. `npm pack --dry-run`: PASS (359 files, 934.5 kB, version 0.8.1)
409
+
410
+ ## 22. Security regression
411
+
412
+ 1. All three authoring POST routes share one gate and one session write
413
+ queue. The Prompt 2 Host, Origin, token, body, content-type,
414
+ content-encoding, and method tests pass. The new route's own gate tests
415
+ pass.
416
+ 2. The browser supplies only item ids. It cannot choose the output location,
417
+ source root, image path, or project path. Unknown body fields return 400.
418
+ 3. Image bytes are read only through the existing `resolveMedia` containment
419
+ and symlink checks. The source root is resolved only through
420
+ `resolveContainedDir`.
421
+ 4. Responses contain no filesystem paths or token. Failures return bounded
422
+ messages and no stack traces.
423
+ 5. The PWA service worker does not cache the new route. This is proven both
424
+ at build level and in live Chromium cache storage.
425
+ 6. `package.json` (script only): `test:security` now also runs
426
+ `tests/unit/viewerAnnotationReferenceMaterialization.test.ts`. No version
427
+ or dependency changed.
428
+
429
+ ## 23. Temporary-directory hardening regression
430
+
431
+ 1. The canonical reference writer still uses its existing temporary directory
432
+ and atomic rename. Writer retry behavior was not changed.
433
+ 2. The Prompt 5 discovery rule that skips `.tmp-*` directories covers the
434
+ reference writer too. `tests/unit/evidenceDiscoveryTempDirectories.test.ts`
435
+ passes (2 tests), in the focused run and in `npm test` and
436
+ `npm run test:security`.
437
+ 3. Browser tests that materialize through the live viewer and then read
438
+ `/api/index` saw only complete artifacts.
439
+
440
+ ## 24. Scope audit
441
+
442
+ 1. external-reference schema changed: false
443
+ 2. annotation schema changed: false
444
+ 3. reference approval changed: false
445
+ 4. automatic reference approval added: false
446
+ 5. project reference acceptance updated: false
447
+ 6. project config schema changed: false
448
+ 7. alias catalog schema changed: false
449
+ 8. runtime contract promotion semantics changed: false
450
+ 9. ContractPrimitive vocabulary changed: false
451
+ 10. annotation evaluator added: false
452
+ 11. reference evaluator added: false
453
+ 12. package version changed: false
454
+ 13. dependency changed: false
455
+
456
+ No file under `src/domain/` or `src/artifacts/` changed.
457
+ `externalReferencePersistenceService.ts`,
458
+ `visualAnnotationContractPromotionService.ts`, and `projectConfig.ts` are
459
+ unchanged. The promotion route parser now calls the shared `itemIds` helper
460
+ with identical rules and messages. Its tests pass unchanged except for the
461
+ materialize assertion described in section 19. No broad documentation file
462
+ was changed. `dist/`, `node_modules/`, and workflow state are not tracked.
463
+
464
+ ## 25. Changed files
465
+
466
+ ### 25.1 Added production files
467
+
468
+ 1. `src/application/visualAnnotationReferenceMaterializationService.ts`
469
+ 2. `src/viewerServer/annotationReferenceMaterialization.ts` (route use case,
470
+ keeps the HTTP layer thin)
471
+ 3. `viewer/src/hooks/useAnnotationReferenceMaterialization.ts`
472
+
473
+ ### 25.2 Modified production files
474
+
475
+ 1. `src/viewerServer/httpServer.ts` (route, status map, protocol 1.3.0)
476
+ 2. `src/viewerServer/annotationAuthoring.ts` (shared `parseAuthoringItemIds`
477
+ and `MAX_AUTHORING_ITEM_IDS`, so the new route reuses the Prompt 5 body
478
+ rule instead of copying it)
479
+ 3. `src/viewerServer/annotationContractPromotion.ts` (uses the shared helper,
480
+ behavior unchanged)
481
+ 4. `src/projectWorkflow/projectPaths.ts`
482
+ 5. `src/index.ts`
483
+ 6. `viewer/src/components/ReferenceAnnotationPanel.tsx`
484
+ 7. `viewer/src/components/ReferenceWorkspace.tsx` (wires the hook and the
485
+ in-memory token into the panel)
486
+ 8. `viewer/src/annotation/referenceIntent.ts` (presentation eligibility
487
+ helpers)
488
+ 9. `viewer/src/styles/index.css`
489
+ 10. `package.json` (`test:security` script only)
490
+
491
+ ### 25.3 Tests and report
492
+
493
+ 1. Added: `tests/unit/visualAnnotationReferenceMaterialization.test.ts`
494
+ 2. Added: `tests/unit/viewerAnnotationReferenceMaterialization.test.ts`
495
+ 3. Added: `tests/browser/referenceAnnotationMaterialization.test.ts`
496
+ 4. Modified: `tests/unit/viewerAuthoringSecurity.test.ts`
497
+ 5. Modified: `tests/unit/viewerAnnotationContractPromotion.test.ts`
498
+ 6. Modified: `tests/unit/viewerPwaBuild.test.ts`
499
+ 7. Modified: `tests/browser/pwaHardening.test.ts`
500
+ 8. Added: `docs/reports/v0.9-batch6-reference-materialization.md`
501
+
502
+ ### 25.4 Generated paths
503
+
504
+ Test temporary directories were created under the OS temp directory by the
505
+ existing test resource helpers and removed after each test. Validation logs
506
+ were written to the session scratchpad, outside the repository. `npm run
507
+ build` refreshed the ignored `dist/`. No workflow state was created.
508
+
509
+ ## 26. Remaining next step
510
+
511
+ v0.9 Prompt 7: integrated acceptance, packaging, documentation, and
512
+ regression. Prompt 7 owns broad documentation reconciliation for the new
513
+ route, the protocol version 1.3.0, the project reference output location, and
514
+ the materialization UI.