@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.
- package/CHANGELOG.md +57 -0
- package/README.md +107 -7
- package/dist/application/projectWorkflowService.d.ts +18 -1
- package/dist/application/projectWorkflowService.js +40 -2
- package/dist/application/projectWorkflowService.js.map +1 -1
- package/dist/application/visualAnnotationContractPromotionService.d.ts +41 -0
- package/dist/application/visualAnnotationContractPromotionService.js +143 -0
- package/dist/application/visualAnnotationContractPromotionService.js.map +1 -0
- package/dist/application/visualAnnotationPersistenceService.d.ts +34 -0
- package/dist/application/visualAnnotationPersistenceService.js +68 -0
- package/dist/application/visualAnnotationPersistenceService.js.map +1 -0
- package/dist/application/visualAnnotationReferenceMaterializationService.d.ts +53 -0
- package/dist/application/visualAnnotationReferenceMaterializationService.js +194 -0
- package/dist/application/visualAnnotationReferenceMaterializationService.js.map +1 -0
- package/dist/artifacts/visualAnnotationArtifactReader.d.ts +19 -0
- package/dist/artifacts/visualAnnotationArtifactReader.js +66 -0
- package/dist/artifacts/visualAnnotationArtifactReader.js.map +1 -0
- package/dist/artifacts/visualAnnotationArtifactWriter.d.ts +42 -0
- package/dist/artifacts/visualAnnotationArtifactWriter.js +85 -0
- package/dist/artifacts/visualAnnotationArtifactWriter.js.map +1 -0
- package/dist/cli.js +464 -454
- package/dist/cli.js.map +1 -1
- package/dist/domain/visualAnnotation.d.ts +217 -0
- package/dist/domain/visualAnnotation.js +584 -0
- package/dist/domain/visualAnnotation.js.map +1 -0
- package/dist/domain/visualAnnotationIdentity.d.ts +17 -0
- package/dist/domain/visualAnnotationIdentity.js +47 -0
- package/dist/domain/visualAnnotationIdentity.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/projectWorkflow/projectPaths.d.ts +9 -0
- package/dist/projectWorkflow/projectPaths.js +21 -0
- package/dist/projectWorkflow/projectPaths.js.map +1 -1
- package/dist/viewer/assets/{index-CN_yb9Uf.css → index-BN41MI7m.css} +1 -1
- package/dist/viewer/assets/index-CkKXnlrI.js +9 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/sw.js +1 -1
- package/dist/viewerServer/annotationAuthoring.d.ts +64 -0
- package/dist/viewerServer/annotationAuthoring.js +230 -0
- package/dist/viewerServer/annotationAuthoring.js.map +1 -0
- package/dist/viewerServer/annotationContractPromotion.d.ts +51 -0
- package/dist/viewerServer/annotationContractPromotion.js +105 -0
- package/dist/viewerServer/annotationContractPromotion.js.map +1 -0
- package/dist/viewerServer/annotationReferenceMaterialization.d.ts +40 -0
- package/dist/viewerServer/annotationReferenceMaterialization.js +91 -0
- package/dist/viewerServer/annotationReferenceMaterialization.js.map +1 -0
- package/dist/viewerServer/authoringSecurity.d.ts +59 -0
- package/dist/viewerServer/authoringSecurity.js +112 -0
- package/dist/viewerServer/authoringSecurity.js.map +1 -0
- package/dist/viewerServer/evidence/annotationView.d.ts +28 -0
- package/dist/viewerServer/evidence/annotationView.js +43 -0
- package/dist/viewerServer/evidence/annotationView.js.map +1 -0
- package/dist/viewerServer/evidence/classify.d.ts +3 -1
- package/dist/viewerServer/evidence/classify.js +12 -0
- package/dist/viewerServer/evidence/classify.js.map +1 -1
- package/dist/viewerServer/evidence/discovery.d.ts +2 -0
- package/dist/viewerServer/evidence/discovery.js +6 -0
- package/dist/viewerServer/evidence/discovery.js.map +1 -1
- package/dist/viewerServer/evidence/handles.js +1 -0
- package/dist/viewerServer/evidence/handles.js.map +1 -1
- package/dist/viewerServer/evidence/index.d.ts +29 -0
- package/dist/viewerServer/evidence/index.js +43 -1
- package/dist/viewerServer/evidence/index.js.map +1 -1
- package/dist/viewerServer/evidence/mediaResolver.d.ts +1 -1
- package/dist/viewerServer/evidence/mediaResolver.js +28 -2
- package/dist/viewerServer/evidence/mediaResolver.js.map +1 -1
- package/dist/viewerServer/evidence/projection.d.ts +5 -1
- package/dist/viewerServer/evidence/projection.js +19 -0
- package/dist/viewerServer/evidence/projection.js.map +1 -1
- package/dist/viewerServer/httpServer.d.ts +13 -2
- package/dist/viewerServer/httpServer.js +278 -4
- package/dist/viewerServer/httpServer.js.map +1 -1
- package/dist/viewerServer/viewerService.d.ts +8 -0
- package/dist/viewerServer/viewerService.js +38 -2
- package/dist/viewerServer/viewerService.js.map +1 -1
- package/docs/ARCHITECTURE.md +108 -21
- package/docs/CI_CD.md +57 -1
- package/docs/COMMANDS.md +44 -4
- package/docs/CONTRACTS.md +78 -8
- package/docs/CURRENT_STATE.md +157 -35
- package/docs/DEVELOPMENT.md +8 -3
- package/docs/PROJECT_DESCRIPTION.md +4 -1
- package/docs/PROJECT_MILESTONES.md +4 -0
- package/docs/PROJECT_OVERVIEW.md +41 -17
- package/docs/QUICKSTART.md +52 -39
- package/docs/RELEASE.md +16 -11
- package/docs/ROADMAP.md +406 -66
- package/docs/SECURITY.md +71 -14
- package/docs/WORKFLOWS.md +151 -23
- package/docs/plans/v0.9-implementation-plan.md +1529 -0
- package/docs/reports/v0.9-architecture-retrieval.md +567 -0
- package/docs/reports/v0.9-batch1-visual-annotation-foundation.md +351 -0
- package/docs/reports/v0.9-batch2-viewer-annotation-authoring-boundary.md +438 -0
- package/docs/reports/v0.9-batch3-runtime-screenshot-annotation-authoring.md +412 -0
- package/docs/reports/v0.9-batch4-external-reference-annotation-authoring.md +452 -0
- package/docs/reports/v0.9-batch5-runtime-intent-contract-promotion.md +535 -0
- package/docs/reports/v0.9-batch6-reference-materialization.md +514 -0
- package/docs/reports/v0.9-batch7-integrated-acceptance.md +644 -0
- package/docs/reports/v0.9-demo-foundation.md +589 -0
- package/docs/reports/v0.9-final-pre-release-readiness.md +209 -0
- package/docs/reports/v0.9-final-readiness-corrections.md +530 -0
- package/docs/reports/v0.9-pre-release-readiness.md +170 -0
- package/docs/reports/v0.9-tutorial-end-to-end-acceptance.md +980 -0
- package/docs/reports/v0.9-tutorial-integration.md +731 -0
- package/package.json +2 -2
- package/dist/viewer/assets/index-D98S1_2d.js +0 -9
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# v0.9 Batch 2 Report: Viewer Annotation Authoring Boundary
|
|
2
|
+
|
|
3
|
+
## 1. VERDICT
|
|
4
|
+
|
|
5
|
+
PASS_V0_9_BATCH2_VIEWER_ANNOTATION_AUTHORING_BOUNDARY
|
|
6
|
+
|
|
7
|
+
## 2. Repository identity
|
|
8
|
+
|
|
9
|
+
1. Repository: `C:\Users\daile\Projects\my-frontend-observer`
|
|
10
|
+
2. Branch: `master`
|
|
11
|
+
3. Starting HEAD: `3618c7b0d2a9cdec80004be4c9cc5bb823bb42f4`
|
|
12
|
+
4. Ending HEAD before the final commit: `3618c7b0d2a9cdec80004be4c9cc5bb823bb42f4`
|
|
13
|
+
5. Package version: `0.8.1` (unchanged in `package.json` and `package-lock.json`)
|
|
14
|
+
6. Viewer protocol version: `1.1.0` (was `1.0.0`)
|
|
15
|
+
|
|
16
|
+
## 3. Prompt 1 entry gate
|
|
17
|
+
|
|
18
|
+
1. Prompt 1 verdict: `PASS_V0_9_BATCH1_VISUAL_ANNOTATION_FOUNDATION`
|
|
19
|
+
2. Prompt 1 commit: `3618c7b0d2a9cdec80004be4c9cc5bb823bb42f4`
|
|
20
|
+
3. Annotation artifact kind: `my-frontend-observer/visual-annotation`
|
|
21
|
+
4. Annotation schema: `1.0.0`
|
|
22
|
+
5. Preflight found a clean tracked worktree on `master` at the required HEAD.
|
|
23
|
+
6. `git ls-files -v docs/reports/v0.9-architecture-retrieval.md` printed `S`
|
|
24
|
+
at entry and again before commit. The `skip-worktree` state was preserved.
|
|
25
|
+
The user's local copy of that report was not read into this commit or
|
|
26
|
+
changed. The preserved copies under
|
|
27
|
+
`.my-dev-kit-workflow/adhoc/preserved-local-docs/` were not touched.
|
|
28
|
+
7. No pull, merge, rebase, reset, stash, or clean was run.
|
|
29
|
+
|
|
30
|
+
## 4. Annotation evidence discovery
|
|
31
|
+
|
|
32
|
+
1. `ArtifactFamily` in `src/viewerServer/evidence/classify.ts` gains exactly
|
|
33
|
+
`'visual-annotation'`, mapped to `VisualAnnotationArtifact`.
|
|
34
|
+
2. Classification for `artifactKind === my-frontend-observer/visual-annotation`:
|
|
35
|
+
1. A schema version other than `1.0.0` gives `unsupported-version` with
|
|
36
|
+
family `visual-annotation`.
|
|
37
|
+
2. Otherwise the canonical `readVisualAnnotationArtifact` runs. If it
|
|
38
|
+
succeeds, the record is `supported`. If it fails, the record is
|
|
39
|
+
`invalid-structure` with family `visual-annotation`.
|
|
40
|
+
3. The classifier copies no annotation validation. The reader stays the only
|
|
41
|
+
authority, including its owned-overlay SHA-256 check.
|
|
42
|
+
4. `src/viewerServer/evidence/handles.ts` gains the `visual-annotation` handle
|
|
43
|
+
slug. This additional file was required because the slug table is typed as
|
|
44
|
+
a complete record over `ArtifactFamily`.
|
|
45
|
+
|
|
46
|
+
## 5. Annotation metadata projection
|
|
47
|
+
|
|
48
|
+
For a supported annotation, `buildMetadataRecord` returns:
|
|
49
|
+
|
|
50
|
+
1. `logicalId`: `annotationId`
|
|
51
|
+
2. `schemaVersion` and `producerVersion` from the artifact
|
|
52
|
+
3. `annotationSourceKind`: `source.kind`
|
|
53
|
+
4. `annotationItemCount`: `items.length`
|
|
54
|
+
5. `annotationConfirmedItemCount`: the number of items whose interpretation
|
|
55
|
+
state is `confirmed`
|
|
56
|
+
6. `media`: one `annotation-overlay` summary (a cheap availability check only)
|
|
57
|
+
7. `relatedIds`: `sourceObservationId` or `sourceReferenceId`, plus
|
|
58
|
+
`supersedesAnnotationId` when present
|
|
59
|
+
|
|
60
|
+
Items are never embedded in metadata. Observation alias metadata is applied
|
|
61
|
+
only to observation records, so an annotation record never gets an alias.
|
|
62
|
+
|
|
63
|
+
## 6. Annotation media resolution
|
|
64
|
+
|
|
65
|
+
1. `MediaSummary.role` and `MediaRole` gain exactly `annotation-overlay`.
|
|
66
|
+
2. `resolveMedia` accepts the role only for the `visual-annotation` family. It
|
|
67
|
+
resolves only `artifact.overlay.path` through `resolveContainedFile`. It
|
|
68
|
+
uses `lstat` and requires a regular file. The MIME type is `image/svg+xml`.
|
|
69
|
+
3. It rejects a missing file, a directory, a symlink or other non-regular
|
|
70
|
+
entry, an unsafe path, the wrong family, an unknown handle, and an unknown
|
|
71
|
+
role. A browser-supplied filename is never used.
|
|
72
|
+
4. Defense in depth (additional local decision): the served bytes must equal
|
|
73
|
+
`renderVisualAnnotationOverlaySvg(artifact.source, artifact.items)` exactly.
|
|
74
|
+
Otherwise the overlay is refused. A hand-edited SVG whose digest was copied
|
|
75
|
+
into the manifest therefore cannot be served from the viewer origin.
|
|
76
|
+
5. Defense in depth (additional local decision): every `image/svg+xml` media
|
|
77
|
+
response also carries
|
|
78
|
+
`content-security-policy: default-src 'none'; style-src 'unsafe-inline'; sandbox`
|
|
79
|
+
and `x-content-type-options: nosniff`. Even direct navigation to the
|
|
80
|
+
overlay cannot run script in the viewer origin, where the authoring token
|
|
81
|
+
lives. Existing media types (PNG, JPEG, WebP) get no new headers.
|
|
82
|
+
|
|
83
|
+
## 7. Annotation source-view projection
|
|
84
|
+
|
|
85
|
+
1. `src/viewerServer/evidence/index.ts` adds `findObservationByIdentity(root,
|
|
86
|
+
observationId, requestId)` and `findExternalReferenceByIdentity(root,
|
|
87
|
+
referenceId, referenceRequestId)`. Both use `discoverManifests`,
|
|
88
|
+
`MAX_INDEX_RECORDS`, `classifyManifest`, and `encodeArtifactHandle`. Both
|
|
89
|
+
require the logical id and the request id to match.
|
|
90
|
+
2. Local decision: when more than one exact match exists, both return
|
|
91
|
+
`undefined` instead of picking one. This follows the existing
|
|
92
|
+
`linkedEvidence.ts` rule of never choosing silently.
|
|
93
|
+
3. New `src/viewerServer/evidence/annotationView.ts` provides
|
|
94
|
+
`getAnnotationView(root, annotationHandle)`. It loads the annotation through
|
|
95
|
+
`loadArtifactByHandle` and requires the `visual-annotation` family. It then
|
|
96
|
+
resolves the source:
|
|
97
|
+
1. A runtime source maps to the observation handle with media role
|
|
98
|
+
`screenshot`.
|
|
99
|
+
2. An imported reference maps to the imported handle with role `image`.
|
|
100
|
+
3. An approved reference maps to the approved handle with role `source-image`.
|
|
101
|
+
4. A missing or ambiguous source returns `source.status: 'unavailable'` with a
|
|
102
|
+
reason. The annotation itself stays loadable.
|
|
103
|
+
5. `GET` and `HEAD` `/api/annotations/:handle/view` answer with no-store JSON:
|
|
104
|
+
1. 400 for a malformed handle.
|
|
105
|
+
2. 404 for an unknown handle.
|
|
106
|
+
3. 409 when the handle is not an annotation or not currently loadable.
|
|
107
|
+
4. 200 otherwise, including when the source is unavailable.
|
|
108
|
+
|
|
109
|
+
## 8. Project-aware authoring mode
|
|
110
|
+
|
|
111
|
+
1. `StartViewerOptions` gains `authoringProjectRoot?: string`. When it is
|
|
112
|
+
absent, the viewer is read-only exactly as before.
|
|
113
|
+
2. When it is supplied, `startViewer` calls the existing
|
|
114
|
+
`loadProjectViewerState`, which validates the project configuration and
|
|
115
|
+
alias catalog. It then requires the viewer root to be that project's
|
|
116
|
+
`projectEvidenceRoot`. The comparison uses resolved paths, with a
|
|
117
|
+
real-path fallback for drive-letter casing and symlinked parents.
|
|
118
|
+
3. On any mismatch or invalid project, the viewer does not start. It returns
|
|
119
|
+
the existing `viewer-root-invalid` diagnostic. No new diagnostic code was
|
|
120
|
+
added.
|
|
121
|
+
4. Authoring is never inferred from the root alone.
|
|
122
|
+
5. `loadProjectViewerState` now also returns `projectRoot`, resolved once.
|
|
123
|
+
Alias behavior is unchanged.
|
|
124
|
+
6. `src/projectWorkflow/projectPaths.ts` adds `projectAnnotationsRoot(projectRoot)`
|
|
125
|
+
and `annotationOutputLocation()`. The latter returns
|
|
126
|
+
`.frontend-observer/evidence/annotations`. Observation paths, project
|
|
127
|
+
config schema, and catalog schema are unchanged. No migration was added.
|
|
128
|
+
7. In `src/cli.ts`, the normal `my-frontend-observer view` now also passes
|
|
129
|
+
`authoringProjectRoot` from `loadProjectViewerState`. `view --root <root>`
|
|
130
|
+
passes nothing new, so it stays read-only. No flag, command, or help text
|
|
131
|
+
was added or changed.
|
|
132
|
+
|
|
133
|
+
## 9. Authoring session capability
|
|
134
|
+
|
|
135
|
+
1. New `src/viewerServer/authoringSecurity.ts` defines:
|
|
136
|
+
1. `VIEWER_AUTHORING_TOKEN_BYTES = 32`
|
|
137
|
+
2. `VIEWER_AUTHORING_TOKEN_HEADER = 'x-frontend-observer-authoring-token'`
|
|
138
|
+
3. `MAX_ANNOTATION_AUTHORING_BODY_BYTES = 256 * 1024`
|
|
139
|
+
2. One token is created per authoring server session with
|
|
140
|
+
`randomBytes(32).toString('hex')`, which gives 64 lowercase hex characters.
|
|
141
|
+
It lives only in the in-memory `ViewerAuthoringSession`
|
|
142
|
+
`{ projectRoot, token, expectedHost, expectedOrigin }`. It is never
|
|
143
|
+
persisted, never put in a cookie, storage, a URL, a cache, or an artifact,
|
|
144
|
+
and never logged.
|
|
145
|
+
3. `expectedHost` and `expectedOrigin` are set after the listener binds to its
|
|
146
|
+
actual port. They are `127.0.0.1:<port>` and `http://127.0.0.1:<port>`.
|
|
147
|
+
Before that they are empty strings, and an empty expectation matches
|
|
148
|
+
nothing.
|
|
149
|
+
4. `GET` and `HEAD` `/api/authoring/session` always send `cache-control: no-store`:
|
|
150
|
+
1. A read-only viewer returns `{ ok: true, enabled: false }`.
|
|
151
|
+
2. An authoring viewer returns `{ ok: true, enabled: true, token }` and
|
|
152
|
+
nothing else.
|
|
153
|
+
5. Defense in depth (additional local decision): in authoring mode the session
|
|
154
|
+
route returns 403 unless the `Host` header equals `expectedHost`. This keeps
|
|
155
|
+
the token away from a DNS-rebinding page.
|
|
156
|
+
|
|
157
|
+
## 10. POST security policy
|
|
158
|
+
|
|
159
|
+
`POST /api/annotations` runs these checks in order:
|
|
160
|
+
|
|
161
|
+
1. Authoring disabled gives 403.
|
|
162
|
+
2. `Host` must equal `expectedHost`, else 403.
|
|
163
|
+
3. `Origin` must equal `expectedOrigin` exactly, else 403. A missing Origin is
|
|
164
|
+
also 403.
|
|
165
|
+
4. The token header must match exactly, else 403. The comparison uses
|
|
166
|
+
`timingSafeEqual` when lengths match, and the response does not say which
|
|
167
|
+
part differed.
|
|
168
|
+
5. `Content-Type` must be `application/json`, optionally with a UTF-8 charset,
|
|
169
|
+
compared case-insensitively. Anything else gives 415.
|
|
170
|
+
6. `Content-Encoding` present and not `identity` gives 415.
|
|
171
|
+
7. A declared `Content-Length` above 262144 gives 413 before any body is read.
|
|
172
|
+
An undeclared chunked body stops being collected once it passes 262144
|
|
173
|
+
bytes and also gives 413. Rejections send `connection: close`, so the
|
|
174
|
+
server stops receiving further body bytes.
|
|
175
|
+
8. Malformed JSON gives 400.
|
|
176
|
+
9. Unknown top-level request fields give 400.
|
|
177
|
+
|
|
178
|
+
Method policy:
|
|
179
|
+
|
|
180
|
+
1. `POST` is accepted only for `/api/annotations`. `GET` or `HEAD` on that path
|
|
181
|
+
returns 405 with `Allow: POST`.
|
|
182
|
+
2. `POST` on any other path returns 405 with `Allow: GET, HEAD`. This includes
|
|
183
|
+
the Prompt 5 and Prompt 6 route names, which do not exist.
|
|
184
|
+
3. `PUT`, `PATCH`, and `DELETE` return 405 everywhere.
|
|
185
|
+
4. No CORS headers are sent.
|
|
186
|
+
|
|
187
|
+
## 11. Save request contract
|
|
188
|
+
|
|
189
|
+
`SaveAnnotationRequest` is `{ sourceHandle, parentAnnotationHandle?, items }`,
|
|
190
|
+
defined in the new `src/viewerServer/annotationAuthoring.ts`.
|
|
191
|
+
|
|
192
|
+
1. Exactly those three top-level keys are allowed.
|
|
193
|
+
2. `sourceHandle` must be a non-empty string.
|
|
194
|
+
3. `parentAnnotationHandle`, when present, must be a non-empty string.
|
|
195
|
+
4. `items` must be an array. Its content is validated by the canonical
|
|
196
|
+
annotation domain inside `persistVisualAnnotation`.
|
|
197
|
+
5. The browser cannot supply a source path, output location, project root,
|
|
198
|
+
annotation id, request id, overlay path or SHA, `createdAt`, producer, or
|
|
199
|
+
alias.
|
|
200
|
+
|
|
201
|
+
## 12. Server-owned source construction
|
|
202
|
+
|
|
203
|
+
1. `sourceHandle` is resolved through `loadArtifactByHandle`.
|
|
204
|
+
1. Unknown gives 404.
|
|
205
|
+
2. Not loadable gives 409.
|
|
206
|
+
3. A family other than observation, imported reference, or approved
|
|
207
|
+
reference gives 409.
|
|
208
|
+
2. Observation: the source is built exactly as the prompt specifies. The
|
|
209
|
+
screenshot must be `available` or `partial`, otherwise 409. The coordinate
|
|
210
|
+
space is `requestConfig.viewport` in CSS pixels. It never uses screenshot
|
|
211
|
+
device pixels or devicePixelRatio.
|
|
212
|
+
3. Imported reference: built from `referenceId` and `image.sha256`, `width`,
|
|
213
|
+
`height`. The image owner is the reference itself.
|
|
214
|
+
4. Approved reference: built from `sourceReference.referenceId` and
|
|
215
|
+
`sourceReference.image`.
|
|
216
|
+
5. An alias is never used as source identity.
|
|
217
|
+
|
|
218
|
+
## 13. Server-owned output location
|
|
219
|
+
|
|
220
|
+
1. The service calls `persistVisualAnnotation` exactly once with
|
|
221
|
+
`outputLocation: annotationOutputLocation()` and
|
|
222
|
+
`cwd: session.projectRoot`. Artifacts land in
|
|
223
|
+
`.frontend-observer/evidence/annotations/<annotationId>/`.
|
|
224
|
+
2. The server never calls the writer directly. It never builds identity or
|
|
225
|
+
overlays itself.
|
|
226
|
+
3. A 201 response is exactly
|
|
227
|
+
`{ ok, annotationId, annotationRequestId, handle }`. The handle is
|
|
228
|
+
`encodeArtifactHandle('visual-annotation', 'annotations/<annotationId>')`.
|
|
229
|
+
4. The response carries no absolute path, manifest or overlay path, project
|
|
230
|
+
root, or token.
|
|
231
|
+
|
|
232
|
+
## 14. Revision and stale-edit behavior
|
|
233
|
+
|
|
234
|
+
1. With no parent, `supersedesAnnotationId` is undefined.
|
|
235
|
+
2. With a parent, the parent is resolved through `loadArtifactByHandle` and
|
|
236
|
+
must be `visual-annotation`.
|
|
237
|
+
1. Unknown gives 404.
|
|
238
|
+
2. Not an annotation, or not loadable, gives 409.
|
|
239
|
+
3. `isSameVisualAnnotationSource` compares every canonical source field for
|
|
240
|
+
each source kind. A mismatch gives 409. It is a pure local helper and is
|
|
241
|
+
not exported from the package.
|
|
242
|
+
4. Stale check: a bounded scan (`discoverManifests`, `MAX_INDEX_RECORDS`,
|
|
243
|
+
`classifyManifest`) looks for any supported annotation whose
|
|
244
|
+
`supersedesAnnotationId` equals the parent id.
|
|
245
|
+
1. Any such child gives 409, including when several children exist.
|
|
246
|
+
2. No child is ever selected by timestamp.
|
|
247
|
+
5. Local decision: if discovery was truncated, the lineage cannot be proven
|
|
248
|
+
free of children. The save fails closed with 409 instead of silently
|
|
249
|
+
branching.
|
|
250
|
+
6. Local decision: saves are serialized per authoring session with an
|
|
251
|
+
in-process promise queue. The stale check and the write cannot interleave.
|
|
252
|
+
A test sends three concurrent revisions of one parent and gets exactly one
|
|
253
|
+
201 and two 409s.
|
|
254
|
+
7. The parent artifact is never modified.
|
|
255
|
+
|
|
256
|
+
## 15. HTTP status behavior
|
|
257
|
+
|
|
258
|
+
1. 201: a save succeeded.
|
|
259
|
+
2. 400: malformed JSON, invalid request shape, an unknown top-level field, a
|
|
260
|
+
malformed view or media handle, or an invalid Content-Length.
|
|
261
|
+
3. 403: authoring disabled, or a Host, Origin, or token failure. Also the
|
|
262
|
+
session route on an unexpected Host.
|
|
263
|
+
4. 404: unknown source handle, unknown parent handle, unknown view or media
|
|
264
|
+
handle, or a rejected media role or file.
|
|
265
|
+
5. 405: an unsupported method or route combination.
|
|
266
|
+
6. 409: source not loadable or not annotatable, parent not an annotation,
|
|
267
|
+
parent source mismatch, parent already has a child, lineage unverifiable,
|
|
268
|
+
or a view handle that is not a loadable annotation.
|
|
269
|
+
7. 413: body over 262144 bytes.
|
|
270
|
+
8. 415: wrong content type or a compressed body.
|
|
271
|
+
9. 422: canonical annotation validation failure, such as out-of-frame
|
|
272
|
+
geometry or a cross-domain item.
|
|
273
|
+
10. 500: persistence failure, returned with a generic message and no stack
|
|
274
|
+
trace or path.
|
|
275
|
+
|
|
276
|
+
## 16. Tests added/modified
|
|
277
|
+
|
|
278
|
+
### 16.1 Added
|
|
279
|
+
|
|
280
|
+
1. `tests/support/annotationAuthoringFixtures.ts` holds shared helpers. They
|
|
281
|
+
build real evidence through the canonical writers, create a real
|
|
282
|
+
initialized project, and send raw `node:http` requests with full Host and
|
|
283
|
+
Origin control.
|
|
284
|
+
2. `tests/unit/viewerAnnotationEvidence.test.ts` (15 tests) covers:
|
|
285
|
+
1. Discovery A to E: supported metadata and counts, unsupported schema,
|
|
286
|
+
invalid structure, full artifact load, and no alias projection.
|
|
287
|
+
2. Media: exact SVG, headers, and HEAD.
|
|
288
|
+
3. Media rejections: wrong family, unknown role, malformed handle, unknown
|
|
289
|
+
handle, traversal handle, missing overlay, directory overlay, symlink
|
|
290
|
+
overlay, and a hand-edited overlay whose digest was updated.
|
|
291
|
+
4. Source view A to E: runtime, imported, approved, missing source, and
|
|
292
|
+
identity mismatch.
|
|
293
|
+
5. View status mapping and write-method rejection.
|
|
294
|
+
3. `tests/unit/viewerAuthoringSecurity.test.ts` (14 tests) covers:
|
|
295
|
+
1. The constants and protocol version.
|
|
296
|
+
2. Token format and freshness, and content-type parsing.
|
|
297
|
+
3. The session route when disabled, enabled, and on a rebinding Host.
|
|
298
|
+
4. Security cases A to Q, including token lifetime across two servers.
|
|
299
|
+
5. The exact 262144-byte body and 413 for declared and chunked oversize.
|
|
300
|
+
6. The method matrix, and read-only routes still working in authoring mode.
|
|
301
|
+
4. `tests/unit/viewerAnnotationAuthoring.test.ts` (15 tests) covers:
|
|
302
|
+
1. Project-aware cases A to D.
|
|
303
|
+
2. Save cases A to C, plus the 404, 409, and 422 mappings.
|
|
304
|
+
3. Revision cases A to D, plus concurrent revisions.
|
|
305
|
+
4. Request-shape and same-source helper unit tests.
|
|
306
|
+
|
|
307
|
+
### 16.2 Modified
|
|
308
|
+
|
|
309
|
+
1. `tests/unit/projectWorkflow.test.ts` gains 2 tests for the annotation path
|
|
310
|
+
helpers and for `loadProjectViewerState` returning `projectRoot`.
|
|
311
|
+
2. `tests/unit/cliViewDispatch.test.ts` gains 1 test. Project-aware `view`
|
|
312
|
+
passes `authoringProjectRoot`, and `view --root` does not.
|
|
313
|
+
3. `tests/browser/projectWorkflowViewer.test.ts` gains 1 real-Chromium test.
|
|
314
|
+
A same-origin page gets the token and saves an annotation (201) with the
|
|
315
|
+
Origin Chromium really sends. The same page gets 403 without the token, and
|
|
316
|
+
403 on a read-only viewer.
|
|
317
|
+
4. `tests/browser/pwaHardening.test.ts`: the existing cache-boundary test now
|
|
318
|
+
fetches `/api/authoring/session`, `/api/annotations/:handle/view`,
|
|
319
|
+
`/api/media/:handle/annotation-overlay`, and `POST /api/annotations` from a
|
|
320
|
+
real page. It asserts `no-store` and still finds zero `/api/` cache entries.
|
|
321
|
+
5. `package.json` (additional file): the `test:security` script now also runs
|
|
322
|
+
the three new unit suites. No version, dependency, or other script changed.
|
|
323
|
+
|
|
324
|
+
No existing test was weakened, skipped, or removed.
|
|
325
|
+
|
|
326
|
+
## 17. Regression validation
|
|
327
|
+
|
|
328
|
+
Every command below was run on Windows in the repository root.
|
|
329
|
+
|
|
330
|
+
1. `npm run typecheck`: PASS (exit 0)
|
|
331
|
+
2. `npm run lint`: PASS (exit 0)
|
|
332
|
+
3. `npm test`: PASS (76 files, 1286 tests)
|
|
333
|
+
4. `npm run build`: PASS (exit 0)
|
|
334
|
+
5. `npm run test:browser`: PASS (21 files, 187 tests, real Chromium)
|
|
335
|
+
6. `npm run test:security`: PASS (unit: 13 files, 143 tests. Browser: 3 files,
|
|
336
|
+
77 tests)
|
|
337
|
+
7. `npm run check:docs`: PASS ("Documentation check passed (17 required files).")
|
|
338
|
+
8. `npm pack --dry-run`: PASS (343 files, 875.9 kB). It includes the new
|
|
339
|
+
`dist/viewerServer` modules. No agent-control files are included.
|
|
340
|
+
|
|
341
|
+
The existing read-only coverage all still passes: status, index, artifacts,
|
|
342
|
+
observation media, comparison, evaluation, reference, binding, fidelity,
|
|
343
|
+
context, static assets, and GET/HEAD behavior. The packed viewer smoke
|
|
344
|
+
script's check (`POST /api/status` returns 405) still holds by construction.
|
|
345
|
+
|
|
346
|
+
## 18. Security validation
|
|
347
|
+
|
|
348
|
+
1. Host enforcement: PASS (wrong and rebinding Host give 403 on save and on
|
|
349
|
+
the session route).
|
|
350
|
+
2. Origin enforcement: PASS (missing, foreign, `localhost`, and `null` Origin
|
|
351
|
+
give 403. A real Chromium same-origin POST succeeds).
|
|
352
|
+
3. Token enforcement: PASS (missing, wrong, short, and cross-session tokens
|
|
353
|
+
give 403).
|
|
354
|
+
4. JSON media type and encoding: PASS (415).
|
|
355
|
+
5. Body size: PASS (exactly 262144 bytes is accepted. Larger bodies give 413,
|
|
356
|
+
both declared and chunked).
|
|
357
|
+
6. Path safety: PASS (a traversal handle gives 404, and no browser-chosen
|
|
358
|
+
path or identity is accepted).
|
|
359
|
+
7. Symlink safety: PASS. File symlink creation was confirmed to work on this
|
|
360
|
+
Windows host, so the symlinked-overlay test really exercised the rejection
|
|
361
|
+
(404). On hosts that refuse to create file symlinks, that test returns
|
|
362
|
+
early by design, the same as the existing viewer symlink test. Directory
|
|
363
|
+
and missing overlay rejection ran unconditionally.
|
|
364
|
+
8. Method surface: PASS (only `POST /api/annotations`. PUT, PATCH, and DELETE
|
|
365
|
+
are 405 everywhere).
|
|
366
|
+
9. PWA: PASS (new API responses are `no-store` and never in cache storage).
|
|
367
|
+
|
|
368
|
+
## 19. Scope audit
|
|
369
|
+
|
|
370
|
+
1. viewer React UI changed: false
|
|
371
|
+
2. annotation drawing UI implemented: false
|
|
372
|
+
3. ContractPrimitive vocabulary changed: false
|
|
373
|
+
4. contract evaluator changed: false
|
|
374
|
+
5. external-reference schema changed: false
|
|
375
|
+
6. reference promotion implemented: false
|
|
376
|
+
7. contract promotion implemented: false
|
|
377
|
+
8. project config schema changed: false
|
|
378
|
+
9. alias catalog schema changed: false
|
|
379
|
+
10. package version changed: false
|
|
380
|
+
11. dependency changed: false
|
|
381
|
+
|
|
382
|
+
Prompt 1 owners (`visualAnnotation.ts`, `visualAnnotationIdentity.ts`, the
|
|
383
|
+
annotation reader and writer, and `visualAnnotationPersistenceService.ts`) were
|
|
384
|
+
not modified. No broad documentation file was modified.
|
|
385
|
+
|
|
386
|
+
## 20. Changed files
|
|
387
|
+
|
|
388
|
+
### 20.1 Added
|
|
389
|
+
|
|
390
|
+
1. `src/viewerServer/authoringSecurity.ts`
|
|
391
|
+
2. `src/viewerServer/annotationAuthoring.ts`
|
|
392
|
+
3. `src/viewerServer/evidence/annotationView.ts`
|
|
393
|
+
4. `tests/support/annotationAuthoringFixtures.ts`
|
|
394
|
+
5. `tests/unit/viewerAnnotationEvidence.test.ts`
|
|
395
|
+
6. `tests/unit/viewerAuthoringSecurity.test.ts`
|
|
396
|
+
7. `tests/unit/viewerAnnotationAuthoring.test.ts`
|
|
397
|
+
8. `docs/reports/v0.9-batch2-viewer-annotation-authoring-boundary.md`
|
|
398
|
+
|
|
399
|
+
### 20.2 Modified
|
|
400
|
+
|
|
401
|
+
1. `src/viewerServer/evidence/classify.ts`
|
|
402
|
+
2. `src/viewerServer/evidence/handles.ts` (additional, see section 4)
|
|
403
|
+
3. `src/viewerServer/evidence/projection.ts`
|
|
404
|
+
4. `src/viewerServer/evidence/index.ts`
|
|
405
|
+
5. `src/viewerServer/evidence/mediaResolver.ts`
|
|
406
|
+
6. `src/viewerServer/httpServer.ts`
|
|
407
|
+
7. `src/viewerServer/viewerService.ts`
|
|
408
|
+
8. `src/projectWorkflow/projectPaths.ts`
|
|
409
|
+
9. `src/application/projectWorkflowService.ts`
|
|
410
|
+
10. `src/cli.ts`
|
|
411
|
+
11. `package.json` (additional, `test:security` script only, see section 16)
|
|
412
|
+
12. `tests/unit/projectWorkflow.test.ts`
|
|
413
|
+
13. `tests/unit/cliViewDispatch.test.ts`
|
|
414
|
+
14. `tests/browser/projectWorkflowViewer.test.ts`
|
|
415
|
+
15. `tests/browser/pwaHardening.test.ts`
|
|
416
|
+
|
|
417
|
+
### 20.3 Generated paths
|
|
418
|
+
|
|
419
|
+
1. `dist/` was rebuilt. It is ignored by Git.
|
|
420
|
+
2. Tests create temporary directories under the OS temp directory, following
|
|
421
|
+
the existing convention, and remove them.
|
|
422
|
+
3. The existing PWA test profile under `.my-dev-kit-workflow/v0.8/batch-08/`
|
|
423
|
+
was reused. It is ignored by Git.
|
|
424
|
+
4. Validation logs were written to the session scratchpad, outside the
|
|
425
|
+
repository.
|
|
426
|
+
|
|
427
|
+
### 20.4 Remaining risks
|
|
428
|
+
|
|
429
|
+
1. `startViewer` now imports the project workflow service to reuse
|
|
430
|
+
`loadProjectViewerState`. That module graph was already loaded by the CLI
|
|
431
|
+
and the package entry point.
|
|
432
|
+
2. `docs/CI_CD.md`, `docs/SECURITY.md`, and `docs/ARCHITECTURE.md` do not yet
|
|
433
|
+
describe the authoring boundary, protocol 1.1.0, or the extended security
|
|
434
|
+
script. Batch 7 owns that reconciliation.
|
|
435
|
+
|
|
436
|
+
## 21. Remaining next step
|
|
437
|
+
|
|
438
|
+
v0.9 Prompt 3 — Runtime screenshot annotation authoring
|