@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
@@ -6,8 +6,8 @@
6
6
  <meta name="theme-color" content="#0f172a" />
7
7
  <title>my-frontend-observer Viewer</title>
8
8
  <link rel="icon" type="image/png" href="/icons/icon-192.png" />
9
- <script type="module" crossorigin src="/assets/index-D98S1_2d.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-CN_yb9Uf.css">
9
+ <script type="module" crossorigin src="/assets/index-CkKXnlrI.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-BN41MI7m.css">
11
11
  <link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
12
12
  <body>
13
13
  <div id="root"></div>
package/dist/viewer/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(i[o])return;let c={};const t=e=>n(e,o),d={module:{uri:o},exports:c,require:t};i[o]=Promise.all(s.map(e=>d[e]||t(e))).then(e=>(r(...e),c))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"manifest.webmanifest",revision:"230563056861fe6705b913b3df23b258"},{url:"index.html",revision:"5cb88d42b2c80bd6eb33a9928a9463d9"},{url:"icons/icon-512.png",revision:"e92e70cceb9b8d7f52ea0cfe9099e7de"},{url:"icons/icon-192.png",revision:"175e5f2a92d509b08ccf2a917bb400f6"},{url:"assets/index-D98S1_2d.js",revision:null},{url:"assets/index-CN_yb9Uf.css",revision:null},{url:"icons/icon-192.png",revision:"175e5f2a92d509b08ccf2a917bb400f6"},{url:"icons/icon-512.png",revision:"e92e70cceb9b8d7f52ea0cfe9099e7de"},{url:"manifest.webmanifest",revision:"230563056861fe6705b913b3df23b258"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"),{denylist:[/^\/api\//]}))});
1
+ if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(i[o])return;let c={};const t=e=>n(e,o),d={module:{uri:o},exports:c,require:t};i[o]=Promise.all(s.map(e=>d[e]||t(e))).then(e=>(r(...e),c))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"manifest.webmanifest",revision:"230563056861fe6705b913b3df23b258"},{url:"index.html",revision:"5f6a87e03f6fdecdd8e252d369d5d9f1"},{url:"icons/icon-512.png",revision:"e92e70cceb9b8d7f52ea0cfe9099e7de"},{url:"icons/icon-192.png",revision:"175e5f2a92d509b08ccf2a917bb400f6"},{url:"assets/index-CkKXnlrI.js",revision:null},{url:"assets/index-BN41MI7m.css",revision:null},{url:"icons/icon-192.png",revision:"175e5f2a92d509b08ccf2a917bb400f6"},{url:"icons/icon-512.png",revision:"e92e70cceb9b8d7f52ea0cfe9099e7de"},{url:"manifest.webmanifest",revision:"230563056861fe6705b913b3df23b258"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"),{denylist:[/^\/api\//]}))});
@@ -0,0 +1,64 @@
1
+ import type { VisualAnnotationItem, VisualAnnotationSource } from '../domain/visualAnnotation.js';
2
+ import type { ViewerAuthoringSession } from './authoringSecurity.js';
3
+ /**
4
+ * v0.9 Batch 2 closed save request. The browser supplies only viewer handles
5
+ * and annotation items. Source identity, output location, project root,
6
+ * annotation identity, overlay metadata, timestamps, and producer metadata
7
+ * are always server/application owned.
8
+ */
9
+ export interface SaveAnnotationRequest {
10
+ sourceHandle: string;
11
+ parentAnnotationHandle?: string;
12
+ items: VisualAnnotationItem[];
13
+ }
14
+ export type ParseSaveAnnotationRequestResult = {
15
+ ok: true;
16
+ request: SaveAnnotationRequest;
17
+ } | {
18
+ ok: false;
19
+ error: string;
20
+ };
21
+ /** Maximum number of annotation item ids accepted by one promotion or materialization request. */
22
+ export declare const MAX_AUTHORING_ITEM_IDS = 100;
23
+ /**
24
+ * v0.9 Batch 5/6 shared `itemIds` shape rule for authoring requests that act on
25
+ * selected items of a saved annotation: a non-empty array of at most 100
26
+ * unique, non-empty strings.
27
+ */
28
+ export declare function parseAuthoringItemIds(value: unknown): {
29
+ ok: true;
30
+ itemIds: string[];
31
+ } | {
32
+ ok: false;
33
+ error: string;
34
+ };
35
+ /** Request-shape validation only. Item content is validated later by the canonical visual-annotation domain. */
36
+ export declare function parseSaveAnnotationRequest(value: unknown): ParseSaveAnnotationRequestResult;
37
+ /**
38
+ * Exact canonical source equality: every persisted source identity field must
39
+ * match. Never a logical-id-only comparison, never alias or filesystem based.
40
+ */
41
+ export declare function isSameVisualAnnotationSource(a: VisualAnnotationSource, b: VisualAnnotationSource): boolean;
42
+ export type SaveAnnotationFailureReason = 'unknown-source-handle' | 'source-not-loadable' | 'source-not-annotatable' | 'unknown-parent-handle' | 'parent-not-annotation' | 'parent-source-mismatch' | 'parent-has-child' | 'revision-lineage-unverifiable' | 'invalid-annotation' | 'persistence-failed';
43
+ export type SaveAnnotationResult = {
44
+ ok: true;
45
+ annotationId: string;
46
+ annotationRequestId: string;
47
+ handle: string;
48
+ } | {
49
+ ok: false;
50
+ reason: SaveAnnotationFailureReason;
51
+ error: string;
52
+ };
53
+ /** v0.9 Batch 5: runs one authoring write through the session's single in-process write queue. */
54
+ export declare function runSerializedAuthoringWrite<T>(session: ViewerAuthoringSession, write: () => Promise<T>): Promise<T>;
55
+ /**
56
+ * The one v0.9 Batch 2 viewer save use case. Resolves the source (and optional
57
+ * parent) through the canonical handle boundary, constructs the canonical
58
+ * annotation source server-side, enforces the same-source and stale-revision
59
+ * rules, and then persists exactly once through `persistVisualAnnotation`
60
+ * into the project-managed annotation location. It never writes directly,
61
+ * never builds identity or overlays itself, and never returns filesystem
62
+ * paths.
63
+ */
64
+ export declare function saveAnnotationFromViewer(root: string, session: ViewerAuthoringSession, request: SaveAnnotationRequest): Promise<SaveAnnotationResult>;
@@ -0,0 +1,230 @@
1
+ import { persistVisualAnnotation } from '../application/visualAnnotationPersistenceService.js';
2
+ import { annotationOutputLocation } from '../projectWorkflow/projectPaths.js';
3
+ import { loadArtifactByHandle } from './evidence/index.js';
4
+ import { discoverManifests } from './evidence/discovery.js';
5
+ import { classifyManifest } from './evidence/classify.js';
6
+ import { encodeArtifactHandle } from './evidence/handles.js';
7
+ import { MAX_INDEX_RECORDS } from './evidence/limits.js';
8
+ const SAVE_REQUEST_KEYS = ['sourceHandle', 'parentAnnotationHandle', 'items'];
9
+ function isPlainObject(value) {
10
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
11
+ }
12
+ function isNonEmptyString(value) {
13
+ return typeof value === 'string' && value.length > 0;
14
+ }
15
+ /** Maximum number of annotation item ids accepted by one promotion or materialization request. */
16
+ export const MAX_AUTHORING_ITEM_IDS = 100;
17
+ /**
18
+ * v0.9 Batch 5/6 shared `itemIds` shape rule for authoring requests that act on
19
+ * selected items of a saved annotation: a non-empty array of at most 100
20
+ * unique, non-empty strings.
21
+ */
22
+ export function parseAuthoringItemIds(value) {
23
+ if (!Array.isArray(value) || value.length === 0)
24
+ return { ok: false, error: 'itemIds must be a non-empty array' };
25
+ if (value.length > MAX_AUTHORING_ITEM_IDS)
26
+ return { ok: false, error: `itemIds must contain at most ${MAX_AUTHORING_ITEM_IDS} entries` };
27
+ if (!value.every((id) => typeof id === 'string' && id.length > 0))
28
+ return { ok: false, error: 'every itemIds entry must be a non-empty string' };
29
+ if (new Set(value).size !== value.length)
30
+ return { ok: false, error: 'itemIds must be unique' };
31
+ return { ok: true, itemIds: value };
32
+ }
33
+ /** Request-shape validation only. Item content is validated later by the canonical visual-annotation domain. */
34
+ export function parseSaveAnnotationRequest(value) {
35
+ if (!isPlainObject(value))
36
+ return { ok: false, error: 'request body must be a JSON object' };
37
+ const unknownKeys = Object.keys(value).filter((key) => !SAVE_REQUEST_KEYS.includes(key));
38
+ if (unknownKeys.length > 0)
39
+ return { ok: false, error: `unsupported request field(s): ${unknownKeys.slice(0, 10).join(', ')}` };
40
+ if (!isNonEmptyString(value.sourceHandle))
41
+ return { ok: false, error: 'sourceHandle must be a non-empty string' };
42
+ if ('parentAnnotationHandle' in value && !isNonEmptyString(value.parentAnnotationHandle)) {
43
+ return { ok: false, error: 'parentAnnotationHandle must be a non-empty string when present' };
44
+ }
45
+ if (!Array.isArray(value.items))
46
+ return { ok: false, error: 'items must be an array' };
47
+ return {
48
+ ok: true,
49
+ request: {
50
+ sourceHandle: value.sourceHandle,
51
+ ...(typeof value.parentAnnotationHandle === 'string' ? { parentAnnotationHandle: value.parentAnnotationHandle } : {}),
52
+ items: value.items,
53
+ },
54
+ };
55
+ }
56
+ /**
57
+ * Exact canonical source equality: every persisted source identity field must
58
+ * match. Never a logical-id-only comparison, never alias or filesystem based.
59
+ */
60
+ export function isSameVisualAnnotationSource(a, b) {
61
+ if (a.kind === 'runtime-observation' && b.kind === 'runtime-observation') {
62
+ return (a.observationId === b.observationId &&
63
+ a.requestId === b.requestId &&
64
+ a.observationSchemaVersion === b.observationSchemaVersion &&
65
+ a.screenshot.path === b.screenshot.path &&
66
+ a.coordinateSpace.kind === b.coordinateSpace.kind &&
67
+ a.coordinateSpace.width === b.coordinateSpace.width &&
68
+ a.coordinateSpace.height === b.coordinateSpace.height);
69
+ }
70
+ if (a.kind === 'external-reference' && b.kind === 'external-reference') {
71
+ return (a.referenceId === b.referenceId &&
72
+ a.referenceRequestId === b.referenceRequestId &&
73
+ a.referenceSchemaVersion === b.referenceSchemaVersion &&
74
+ a.lifecycle === b.lifecycle &&
75
+ a.imageOwnerReferenceId === b.imageOwnerReferenceId &&
76
+ a.imageSha256 === b.imageSha256 &&
77
+ a.coordinateSpace.kind === b.coordinateSpace.kind &&
78
+ a.coordinateSpace.width === b.coordinateSpace.width &&
79
+ a.coordinateSpace.height === b.coordinateSpace.height);
80
+ }
81
+ return false;
82
+ }
83
+ /** Builds the canonical annotation source from an already-loaded, already-validated artifact. Runtime frames are viewport CSS pixels, never screenshot device pixels. */
84
+ function buildAnnotationSource(detail) {
85
+ const { family, artifact } = detail;
86
+ if (family === 'observation' && 'observationId' in artifact) {
87
+ const screenshot = artifact.screenshot;
88
+ if (screenshot.state !== 'available' && screenshot.state !== 'partial') {
89
+ return { ok: false, error: 'the source observation has no recorded screenshot to annotate' };
90
+ }
91
+ return {
92
+ ok: true,
93
+ source: {
94
+ kind: 'runtime-observation',
95
+ observationId: artifact.observationId,
96
+ requestId: artifact.requestId,
97
+ observationSchemaVersion: artifact.schemaVersion,
98
+ screenshot: { path: screenshot.value.path },
99
+ coordinateSpace: { kind: 'runtime-css-px', width: artifact.requestConfig.viewport.width, height: artifact.requestConfig.viewport.height },
100
+ },
101
+ };
102
+ }
103
+ if (family === 'external-reference-imported' && 'referenceId' in artifact && 'image' in artifact) {
104
+ return {
105
+ ok: true,
106
+ source: {
107
+ kind: 'external-reference',
108
+ referenceId: artifact.referenceId,
109
+ referenceRequestId: artifact.referenceRequestId,
110
+ referenceSchemaVersion: artifact.schemaVersion,
111
+ lifecycle: 'imported',
112
+ imageOwnerReferenceId: artifact.referenceId,
113
+ imageSha256: artifact.image.sha256,
114
+ coordinateSpace: { kind: 'reference-image-px', width: artifact.image.width, height: artifact.image.height },
115
+ },
116
+ };
117
+ }
118
+ if (family === 'external-reference-approved' && 'referenceId' in artifact && 'sourceReference' in artifact) {
119
+ return {
120
+ ok: true,
121
+ source: {
122
+ kind: 'external-reference',
123
+ referenceId: artifact.referenceId,
124
+ referenceRequestId: artifact.referenceRequestId,
125
+ referenceSchemaVersion: artifact.schemaVersion,
126
+ lifecycle: 'approved',
127
+ imageOwnerReferenceId: artifact.sourceReference.referenceId,
128
+ imageSha256: artifact.sourceReference.image.sha256,
129
+ coordinateSpace: { kind: 'reference-image-px', width: artifact.sourceReference.image.width, height: artifact.sourceReference.image.height },
130
+ },
131
+ };
132
+ }
133
+ return { ok: false, error: `"${family}" evidence cannot be annotated; only observations and external references can` };
134
+ }
135
+ /**
136
+ * Bounded stale-revision scan: does any currently supported visual annotation
137
+ * already supersede `parentAnnotationId`? Uses the same bounded discovery and
138
+ * canonical classification as the index. If discovery was truncated the
139
+ * lineage cannot be proven child-free, so the caller fails closed.
140
+ */
141
+ async function scanForExistingChild(root, parentAnnotationId) {
142
+ const discovery = await discoverManifests(root);
143
+ for (const manifest of discovery.manifests.slice(0, MAX_INDEX_RECORDS)) {
144
+ const classified = await classifyManifest(manifest.absolutePath);
145
+ if (classified.supportState !== 'supported' || classified.family !== 'visual-annotation')
146
+ continue;
147
+ if (classified.artifact.supersedesAnnotationId === parentAnnotationId)
148
+ return { status: 'has-child' };
149
+ }
150
+ if (discovery.truncated || discovery.manifests.length > MAX_INDEX_RECORDS)
151
+ return { status: 'unverifiable' };
152
+ return { status: 'none' };
153
+ }
154
+ /** One in-process authoring write at a time per session, so read-check-write sequences (stale revisions, contract promotion and activation) cannot interleave. */
155
+ const saveQueues = new WeakMap();
156
+ /** v0.9 Batch 5: runs one authoring write through the session's single in-process write queue. */
157
+ export function runSerializedAuthoringWrite(session, write) {
158
+ const previous = saveQueues.get(session) ?? Promise.resolve();
159
+ const next = previous.then(write, write);
160
+ saveQueues.set(session, next);
161
+ return next;
162
+ }
163
+ /**
164
+ * The one v0.9 Batch 2 viewer save use case. Resolves the source (and optional
165
+ * parent) through the canonical handle boundary, constructs the canonical
166
+ * annotation source server-side, enforces the same-source and stale-revision
167
+ * rules, and then persists exactly once through `persistVisualAnnotation`
168
+ * into the project-managed annotation location. It never writes directly,
169
+ * never builds identity or overlays itself, and never returns filesystem
170
+ * paths.
171
+ */
172
+ export function saveAnnotationFromViewer(root, session, request) {
173
+ return runSerializedAuthoringWrite(session, () => performSave(root, session, request));
174
+ }
175
+ async function performSave(root, session, request) {
176
+ const sourceLoaded = await loadArtifactByHandle(root, request.sourceHandle);
177
+ if (!sourceLoaded.ok) {
178
+ return sourceLoaded.reason === 'unknown-handle'
179
+ ? { ok: false, reason: 'unknown-source-handle', error: 'unknown source evidence handle' }
180
+ : { ok: false, reason: 'source-not-loadable', error: 'source evidence is not currently loadable' };
181
+ }
182
+ const built = buildAnnotationSource(sourceLoaded.detail);
183
+ if (!built.ok)
184
+ return { ok: false, reason: 'source-not-annotatable', error: built.error };
185
+ const source = built.source;
186
+ let supersedesAnnotationId;
187
+ if (request.parentAnnotationHandle !== undefined) {
188
+ const parentLoaded = await loadArtifactByHandle(root, request.parentAnnotationHandle);
189
+ if (!parentLoaded.ok) {
190
+ return parentLoaded.reason === 'unknown-handle'
191
+ ? { ok: false, reason: 'unknown-parent-handle', error: 'unknown parent annotation handle' }
192
+ : { ok: false, reason: 'parent-not-annotation', error: 'parent annotation is not currently loadable visual-annotation evidence' };
193
+ }
194
+ if (parentLoaded.detail.family !== 'visual-annotation' || !('annotationId' in parentLoaded.detail.artifact)) {
195
+ return { ok: false, reason: 'parent-not-annotation', error: 'parentAnnotationHandle does not name visual-annotation evidence' };
196
+ }
197
+ const parent = parentLoaded.detail.artifact;
198
+ if (!isSameVisualAnnotationSource(parent.source, source)) {
199
+ return { ok: false, reason: 'parent-source-mismatch', error: 'the parent annotation was made over a different canonical source' };
200
+ }
201
+ const childScan = await scanForExistingChild(root, parent.annotationId);
202
+ if (childScan.status === 'has-child') {
203
+ return { ok: false, reason: 'parent-has-child', error: 'the parent annotation already has a saved revision; reload before revising' };
204
+ }
205
+ if (childScan.status === 'unverifiable') {
206
+ return { ok: false, reason: 'revision-lineage-unverifiable', error: 'the evidence root is too large to verify that the parent annotation has no saved revision' };
207
+ }
208
+ supersedesAnnotationId = parent.annotationId;
209
+ }
210
+ const persisted = await persistVisualAnnotation({
211
+ source,
212
+ items: request.items,
213
+ outputLocation: annotationOutputLocation(),
214
+ cwd: session.projectRoot,
215
+ ...(supersedesAnnotationId === undefined ? {} : { supersedesAnnotationId }),
216
+ });
217
+ if (!persisted.ok) {
218
+ const first = persisted.diagnostics[0];
219
+ if (first?.code === 'invalid-request')
220
+ return { ok: false, reason: 'invalid-annotation', error: first.message };
221
+ return { ok: false, reason: 'persistence-failed', error: 'the annotation could not be persisted' };
222
+ }
223
+ return {
224
+ ok: true,
225
+ annotationId: persisted.annotationId,
226
+ annotationRequestId: persisted.annotationRequestId,
227
+ handle: encodeArtifactHandle('visual-annotation', `annotations/${persisted.annotationId}`),
228
+ };
229
+ }
230
+ //# sourceMappingURL=annotationAuthoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotationAuthoring.js","sourceRoot":"","sources":["../../src/viewerServer/annotationAuthoring.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,sDAAsD,CAAC;AAC/F,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAezD,MAAM,iBAAiB,GAAG,CAAC,cAAc,EAAE,wBAAwB,EAAE,OAAO,CAAU,CAAC;AAIvF,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,kGAAkG;AAClG,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC;IAClH,IAAI,KAAK,CAAC,MAAM,GAAG,sBAAsB;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,sBAAsB,UAAU,EAAE,CAAC;IACzI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC;IACjJ,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC;IAChG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAiB,EAAE,CAAC;AAClD,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,0BAA0B,CAAC,KAAc;IACvD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,oCAAoC,EAAE,CAAC;IAC7F,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAE,iBAAuC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAChH,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iCAAiC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IAChI,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,YAAY,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC;IAClH,IAAI,wBAAwB,IAAI,KAAK,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACzF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gEAAgE,EAAE,CAAC;IAChG,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC;IACvF,OAAO;QACL,EAAE,EAAE,IAAI;QACR,OAAO,EAAE;YACP,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,GAAG,CAAC,OAAO,KAAK,CAAC,sBAAsB,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,KAAK,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrH,KAAK,EAAE,KAAK,CAAC,KAA+B;SAC7C;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,CAAyB,EAAE,CAAyB;IAC/F,IAAI,CAAC,CAAC,IAAI,KAAK,qBAAqB,IAAI,CAAC,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;QACzE,OAAO,CACL,CAAC,CAAC,aAAa,KAAK,CAAC,CAAC,aAAa;YACnC,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;YAC3B,CAAC,CAAC,wBAAwB,KAAK,CAAC,CAAC,wBAAwB;YACzD,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI;YACvC,CAAC,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI;YACjD,CAAC,CAAC,eAAe,CAAC,KAAK,KAAK,CAAC,CAAC,eAAe,CAAC,KAAK;YACnD,CAAC,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,eAAe,CAAC,MAAM,CACtD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,oBAAoB,IAAI,CAAC,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;QACvE,OAAO,CACL,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW;YAC/B,CAAC,CAAC,kBAAkB,KAAK,CAAC,CAAC,kBAAkB;YAC7C,CAAC,CAAC,sBAAsB,KAAK,CAAC,CAAC,sBAAsB;YACrD,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;YAC3B,CAAC,CAAC,qBAAqB,KAAK,CAAC,CAAC,qBAAqB;YACnD,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW;YAC/B,CAAC,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI;YACjD,CAAC,CAAC,eAAe,CAAC,KAAK,KAAK,CAAC,CAAC,eAAe,CAAC,KAAK;YACnD,CAAC,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,CAAC,eAAe,CAAC,MAAM,CACtD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAID,yKAAyK;AACzK,SAAS,qBAAqB,CAAC,MAA8B;IAC3D,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACpC,IAAI,MAAM,KAAK,aAAa,IAAI,eAAe,IAAI,QAAQ,EAAE,CAAC;QAC5D,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;QACvC,IAAI,UAAU,CAAC,KAAK,KAAK,WAAW,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACvE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,+DAA+D,EAAE,CAAC;QAC/F,CAAC;QACD,OAAO;YACL,EAAE,EAAE,IAAI;YACR,MAAM,EAAE;gBACN,IAAI,EAAE,qBAAqB;gBAC3B,aAAa,EAAE,QAAQ,CAAC,aAAa;gBACrC,SAAS,EAAE,QAAQ,CAAC,SAAS;gBAC7B,wBAAwB,EAAE,QAAQ,CAAC,aAAa;gBAChD,UAAU,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE;gBAC3C,eAAe,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,EAAE;aAC1I;SACF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,6BAA6B,IAAI,aAAa,IAAI,QAAQ,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;QACjG,OAAO;YACL,EAAE,EAAE,IAAI;YACR,MAAM,EAAE;gBACN,IAAI,EAAE,oBAAoB;gBAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;gBAC/C,sBAAsB,EAAE,QAAQ,CAAC,aAAa;gBAC9C,SAAS,EAAE,UAAU;gBACrB,qBAAqB,EAAE,QAAQ,CAAC,WAAW;gBAC3C,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;gBAClC,eAAe,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE;aAC5G;SACF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,6BAA6B,IAAI,aAAa,IAAI,QAAQ,IAAI,iBAAiB,IAAI,QAAQ,EAAE,CAAC;QAC3G,OAAO;YACL,EAAE,EAAE,IAAI;YACR,MAAM,EAAE;gBACN,IAAI,EAAE,oBAAoB;gBAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;gBAC/C,sBAAsB,EAAE,QAAQ,CAAC,aAAa;gBAC9C,SAAS,EAAE,UAAU;gBACrB,qBAAqB,EAAE,QAAQ,CAAC,eAAe,CAAC,WAAW;gBAC3D,WAAW,EAAE,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM;gBAClD,eAAe,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE;aAC5I;SACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,MAAM,+EAA+E,EAAE,CAAC;AACzH,CAAC;AAoBD;;;;;GAKG;AACH,KAAK,UAAU,oBAAoB,CAAC,IAAY,EAAE,kBAA0B;IAC1E,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,MAAM,QAAQ,IAAI,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACvE,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACjE,IAAI,UAAU,CAAC,YAAY,KAAK,WAAW,IAAI,UAAU,CAAC,MAAM,KAAK,mBAAmB;YAAE,SAAS;QACnG,IAAI,UAAU,CAAC,QAAQ,CAAC,sBAAsB,KAAK,kBAAkB;YAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACxG,CAAC;IACD,IAAI,SAAS,CAAC,SAAS,IAAI,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,iBAAiB;QAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAC7G,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,kKAAkK;AAClK,MAAM,UAAU,GAAG,IAAI,OAAO,EAA4C,CAAC;AAE3E,kGAAkG;AAClG,MAAM,UAAU,2BAA2B,CAAI,OAA+B,EAAE,KAAuB;IACrG,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACzC,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC9B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY,EAAE,OAA+B,EAAE,OAA8B;IACpH,OAAO,2BAA2B,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AACzF,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,OAA+B,EAAE,OAA8B;IACtG,MAAM,YAAY,GAAG,MAAM,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5E,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;QACrB,OAAO,YAAY,CAAC,MAAM,KAAK,gBAAgB;YAC7C,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,gCAAgC,EAAE;YACzF,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC;IACvG,CAAC;IACD,MAAM,KAAK,GAAG,qBAAqB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAC1F,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAE5B,IAAI,sBAA0C,CAAC;IAC/C,IAAI,OAAO,CAAC,sBAAsB,KAAK,SAAS,EAAE,CAAC;QACjD,MAAM,YAAY,GAAG,MAAM,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;QACtF,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;YACrB,OAAO,YAAY,CAAC,MAAM,KAAK,gBAAgB;gBAC7C,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,kCAAkC,EAAE;gBAC3F,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,wEAAwE,EAAE,CAAC;QACtI,CAAC;QACD,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,KAAK,mBAAmB,IAAI,CAAC,CAAC,cAAc,IAAI,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5G,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,iEAAiE,EAAE,CAAC;QAClI,CAAC;QACD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC;QAC5C,IAAI,CAAC,4BAA4B,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;YACzD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,KAAK,EAAE,kEAAkE,EAAE,CAAC;QACpI,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;QACxE,IAAI,SAAS,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACrC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,4EAA4E,EAAE,CAAC;QACxI,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YACxC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,+BAA+B,EAAE,KAAK,EAAE,2FAA2F,EAAE,CAAC;QACpK,CAAC;QACD,sBAAsB,GAAG,MAAM,CAAC,YAAY,CAAC;IAC/C,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC;QAC9C,MAAM;QACN,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,cAAc,EAAE,wBAAwB,EAAE;QAC1C,GAAG,EAAE,OAAO,CAAC,WAAW;QACxB,GAAG,CAAC,sBAAsB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC;KAC5E,CAAC,CAAC;IACH,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QACvC,IAAI,KAAK,EAAE,IAAI,KAAK,iBAAiB;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QAChH,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAC;IACrG,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,YAAY,EAAE,SAAS,CAAC,YAAY;QACpC,mBAAmB,EAAE,SAAS,CAAC,mBAAmB;QAClD,MAAM,EAAE,oBAAoB,CAAC,mBAAmB,EAAE,eAAe,SAAS,CAAC,YAAY,EAAE,CAAC;KAC3F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { PromoteVisualAnnotationContractFailureCode } from '../application/visualAnnotationContractPromotionService.js';
2
+ import type { ViewerAuthoringSession } from './authoringSecurity.js';
3
+ /** v0.9 Batch 5 closed promotion request. The browser supplies only annotation item ids and the explicit activation choice. */
4
+ export interface PromoteAnnotationContractRequest {
5
+ itemIds: string[];
6
+ activateForCheck: boolean;
7
+ }
8
+ export type ParsePromoteAnnotationContractRequestResult = {
9
+ ok: true;
10
+ request: PromoteAnnotationContractRequest;
11
+ } | {
12
+ ok: false;
13
+ error: string;
14
+ };
15
+ export declare function parsePromoteAnnotationContractRequest(value: unknown): ParsePromoteAnnotationContractRequestResult;
16
+ export type ContractActivation = {
17
+ state: 'not-requested';
18
+ } | {
19
+ state: 'activated';
20
+ } | {
21
+ state: 'not-configured';
22
+ reason: string;
23
+ } | {
24
+ state: 'failed';
25
+ reason: string;
26
+ };
27
+ export type PromoteAnnotationContractFailureReason = 'unknown-annotation-handle' | 'not-an-annotation' | 'reference-annotation' | 'source-unavailable' | 'project-config-invalid' | 'configured-baseline-invalid' | PromoteVisualAnnotationContractFailureCode;
28
+ export type PromoteAnnotationContractFromViewerResult = {
29
+ ok: true;
30
+ contractId: string;
31
+ contractRequestId: string;
32
+ handle: string;
33
+ clauseCount: number;
34
+ activation: ContractActivation;
35
+ } | {
36
+ ok: false;
37
+ reason: PromoteAnnotationContractFailureReason;
38
+ error: string;
39
+ };
40
+ /**
41
+ * v0.9 Batch 5 viewer promotion use case (`POST
42
+ * /api/annotations/:handle/promote-contract`). Resolves the saved annotation
43
+ * and its exact canonical source observation, derives `activeBaselineIds`
44
+ * only from an explicitly configured and valid baseline contract, then calls
45
+ * the canonical `promoteVisualAnnotationContract` exactly once. Activation for
46
+ * project `check` happens only when requested, and only through
47
+ * `activateProjectChangeContract`; an activation failure never undoes the
48
+ * persisted contract. Runs through the session's single authoring write
49
+ * queue. Never returns filesystem paths.
50
+ */
51
+ export declare function promoteAnnotationContractFromViewer(root: string, session: ViewerAuthoringSession, annotationHandle: string, request: PromoteAnnotationContractRequest): Promise<PromoteAnnotationContractFromViewerResult>;
@@ -0,0 +1,105 @@
1
+ import path from 'node:path';
2
+ import { readProjectConfig } from '../projectWorkflow/projectConfig.js';
3
+ import { contractOutputLocation, projectConfigPath } from '../projectWorkflow/projectPaths.js';
4
+ import { resolveContainedAcceptancePath } from '../projectWorkflow/checkAcceptance.js';
5
+ import { readPersistentBaselineContract } from '../artifacts/frontendContractArtifactReader.js';
6
+ import { promoteVisualAnnotationContract } from '../application/visualAnnotationContractPromotionService.js';
7
+ import { activateProjectChangeContract } from '../application/projectWorkflowService.js';
8
+ import { getAnnotationView } from './evidence/annotationView.js';
9
+ import { loadArtifactByHandle } from './evidence/index.js';
10
+ import { encodeArtifactHandle } from './evidence/handles.js';
11
+ import { parseAuthoringItemIds, runSerializedAuthoringWrite } from './annotationAuthoring.js';
12
+ const PROMOTE_REQUEST_KEYS = ['itemIds', 'activateForCheck'];
13
+ export function parsePromoteAnnotationContractRequest(value) {
14
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
15
+ return { ok: false, error: 'request body must be a JSON object' };
16
+ const record = value;
17
+ const unknownKeys = Object.keys(record).filter((key) => !PROMOTE_REQUEST_KEYS.includes(key));
18
+ if (unknownKeys.length > 0)
19
+ return { ok: false, error: `unsupported request field(s): ${unknownKeys.slice(0, 10).join(', ')}` };
20
+ const itemIds = parseAuthoringItemIds(record.itemIds);
21
+ if (!itemIds.ok)
22
+ return itemIds;
23
+ if (typeof record.activateForCheck !== 'boolean')
24
+ return { ok: false, error: 'activateForCheck must be a boolean' };
25
+ return { ok: true, request: { itemIds: itemIds.itemIds, activateForCheck: record.activateForCheck } };
26
+ }
27
+ const MAX_ACTIVATION_REASON_LENGTH = 300;
28
+ /**
29
+ * v0.9 Batch 5 viewer promotion use case (`POST
30
+ * /api/annotations/:handle/promote-contract`). Resolves the saved annotation
31
+ * and its exact canonical source observation, derives `activeBaselineIds`
32
+ * only from an explicitly configured and valid baseline contract, then calls
33
+ * the canonical `promoteVisualAnnotationContract` exactly once. Activation for
34
+ * project `check` happens only when requested, and only through
35
+ * `activateProjectChangeContract`; an activation failure never undoes the
36
+ * persisted contract. Runs through the session's single authoring write
37
+ * queue. Never returns filesystem paths.
38
+ */
39
+ export function promoteAnnotationContractFromViewer(root, session, annotationHandle, request) {
40
+ return runSerializedAuthoringWrite(session, () => performPromotion(root, session, annotationHandle, request));
41
+ }
42
+ async function performPromotion(root, session, annotationHandle, request) {
43
+ const view = await getAnnotationView(root, annotationHandle);
44
+ if (!view.ok) {
45
+ return view.reason === 'unknown-handle'
46
+ ? { ok: false, reason: 'unknown-annotation-handle', error: 'unknown annotation handle' }
47
+ : { ok: false, reason: 'not-an-annotation', error: 'the handle does not name loadable visual-annotation evidence' };
48
+ }
49
+ const { annotation, source } = view;
50
+ if (annotation.source.kind !== 'runtime-observation') {
51
+ return { ok: false, reason: 'reference-annotation', error: 'external-reference annotations are not promoted into frontend change contracts' };
52
+ }
53
+ if (source.status !== 'available' || source.family !== 'observation') {
54
+ return { ok: false, reason: 'source-unavailable', error: 'the annotation source observation is not uniquely available in this evidence root' };
55
+ }
56
+ const sourceLoaded = await loadArtifactByHandle(root, source.handle);
57
+ if (!sourceLoaded.ok || sourceLoaded.detail.family !== 'observation') {
58
+ return { ok: false, reason: 'source-unavailable', error: 'the annotation source observation is no longer loadable' };
59
+ }
60
+ const sourceObservation = sourceLoaded.detail.artifact;
61
+ const config = await readProjectConfig(projectConfigPath(session.projectRoot));
62
+ if (!config.ok)
63
+ return { ok: false, reason: 'project-config-invalid', error: 'the project configuration is invalid' };
64
+ const contractAcceptance = config.config.acceptance?.contract;
65
+ let activeBaselineIds = [];
66
+ if (contractAcceptance !== undefined) {
67
+ const baselineRoot = resolveContainedAcceptancePath(session.projectRoot, contractAcceptance.baselineArtifact);
68
+ if (!baselineRoot.ok)
69
+ return { ok: false, reason: 'configured-baseline-invalid', error: 'the configured baseline contract path is missing or outside the project' };
70
+ const baseline = await readPersistentBaselineContract(path.join(baselineRoot.path, 'manifest.json'));
71
+ if (!baseline.ok)
72
+ return { ok: false, reason: 'configured-baseline-invalid', error: 'the configured baseline contract is missing or invalid' };
73
+ activeBaselineIds = [baseline.contract.baselineId];
74
+ }
75
+ const promoted = await promoteVisualAnnotationContract({
76
+ annotation,
77
+ sourceObservation,
78
+ selectedItemIds: request.itemIds,
79
+ activeBaselineIds,
80
+ outputLocation: contractOutputLocation(),
81
+ cwd: session.projectRoot,
82
+ });
83
+ if (!promoted.ok) {
84
+ return { ok: false, reason: promoted.code, error: promoted.code === 'persistence-failed' ? 'the change contract could not be persisted' : promoted.reason };
85
+ }
86
+ let activation = { state: 'not-requested' };
87
+ if (request.activateForCheck) {
88
+ const activated = await activateProjectChangeContract(session.projectRoot, `${contractOutputLocation()}/${promoted.contractId}`);
89
+ if (activated.ok)
90
+ activation = { state: 'activated' };
91
+ else if (activated.code === 'not-configured')
92
+ activation = { state: 'not-configured', reason: activated.reason };
93
+ else
94
+ activation = { state: 'failed', reason: `The change contract was persisted, but project activation failed (${activated.code}).`.slice(0, MAX_ACTIVATION_REASON_LENGTH) };
95
+ }
96
+ return {
97
+ ok: true,
98
+ contractId: promoted.contractId,
99
+ contractRequestId: promoted.contractRequestId,
100
+ handle: encodeArtifactHandle('change-contract', `contracts/${promoted.contractId}`),
101
+ clauseCount: promoted.clauseCount,
102
+ activation,
103
+ };
104
+ }
105
+ //# sourceMappingURL=annotationContractPromotion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotationContractPromotion.js","sourceRoot":"","sources":["../../src/viewerServer/annotationContractPromotion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { MaterializeVisualAnnotationReferenceFailureCode } from '../application/visualAnnotationReferenceMaterializationService.js';
2
+ import type { ViewerAuthoringSession } from './authoringSecurity.js';
3
+ /** v0.9 Batch 6 closed materialization request. The browser supplies only the selected annotation item ids. */
4
+ export interface MaterializeAnnotationReferenceRequest {
5
+ itemIds: string[];
6
+ }
7
+ export type ParseMaterializeAnnotationReferenceRequestResult = {
8
+ ok: true;
9
+ request: MaterializeAnnotationReferenceRequest;
10
+ } | {
11
+ ok: false;
12
+ error: string;
13
+ };
14
+ export declare function parseMaterializeAnnotationReferenceRequest(value: unknown): ParseMaterializeAnnotationReferenceRequestResult;
15
+ export type MaterializeAnnotationReferenceFailureReason = 'unknown-annotation-handle' | 'not-an-annotation' | 'runtime-annotation' | 'source-unavailable' | 'source-image-unavailable' | 'image-read-failure' | MaterializeVisualAnnotationReferenceFailureCode;
16
+ export type MaterializeAnnotationReferenceFromViewerResult = {
17
+ ok: true;
18
+ referenceId: string;
19
+ referenceRequestId: string;
20
+ handle: string;
21
+ supersedesReferenceId: string;
22
+ regionCount: number;
23
+ requirementCount: number;
24
+ } | {
25
+ ok: false;
26
+ reason: MaterializeAnnotationReferenceFailureReason;
27
+ error: string;
28
+ };
29
+ /**
30
+ * v0.9 Batch 6 viewer materialization use case (`POST
31
+ * /api/annotations/:handle/materialize-reference`). Resolves the saved
32
+ * external-reference annotation and its exact canonical source reference,
33
+ * reads the canonical image bytes only through the existing safe media
34
+ * boundary (`image` for an imported source, `source-image` for an approved
35
+ * one), resolves the source artifact root from its own viewer handle, and
36
+ * calls the canonical `materializeVisualAnnotationReference` exactly once.
37
+ * Runs through the session's single authoring write queue. Never approves,
38
+ * never touches project configuration, and never returns filesystem paths.
39
+ */
40
+ export declare function materializeAnnotationReferenceFromViewer(root: string, session: ViewerAuthoringSession, annotationHandle: string, request: MaterializeAnnotationReferenceRequest): Promise<MaterializeAnnotationReferenceFromViewerResult>;
@@ -0,0 +1,91 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { referenceOutputLocation } from '../projectWorkflow/projectPaths.js';
3
+ import { materializeVisualAnnotationReference } from '../application/visualAnnotationReferenceMaterializationService.js';
4
+ import { getAnnotationView } from './evidence/annotationView.js';
5
+ import { loadArtifactByHandle } from './evidence/index.js';
6
+ import { resolveMedia } from './evidence/mediaResolver.js';
7
+ import { decodeArtifactHandle, encodeArtifactHandle } from './evidence/handles.js';
8
+ import { resolveContainedDir } from './evidence/pathSafety.js';
9
+ import { parseAuthoringItemIds, runSerializedAuthoringWrite } from './annotationAuthoring.js';
10
+ export function parseMaterializeAnnotationReferenceRequest(value) {
11
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
12
+ return { ok: false, error: 'request body must be a JSON object' };
13
+ const record = value;
14
+ const unknownKeys = Object.keys(record).filter((key) => key !== 'itemIds');
15
+ if (unknownKeys.length > 0)
16
+ return { ok: false, error: `unsupported request field(s): ${unknownKeys.slice(0, 10).join(', ')}` };
17
+ const itemIds = parseAuthoringItemIds(record.itemIds);
18
+ if (!itemIds.ok)
19
+ return itemIds;
20
+ return { ok: true, request: { itemIds: itemIds.itemIds } };
21
+ }
22
+ /**
23
+ * v0.9 Batch 6 viewer materialization use case (`POST
24
+ * /api/annotations/:handle/materialize-reference`). Resolves the saved
25
+ * external-reference annotation and its exact canonical source reference,
26
+ * reads the canonical image bytes only through the existing safe media
27
+ * boundary (`image` for an imported source, `source-image` for an approved
28
+ * one), resolves the source artifact root from its own viewer handle, and
29
+ * calls the canonical `materializeVisualAnnotationReference` exactly once.
30
+ * Runs through the session's single authoring write queue. Never approves,
31
+ * never touches project configuration, and never returns filesystem paths.
32
+ */
33
+ export function materializeAnnotationReferenceFromViewer(root, session, annotationHandle, request) {
34
+ return runSerializedAuthoringWrite(session, () => performMaterialization(root, session, annotationHandle, request));
35
+ }
36
+ async function performMaterialization(root, session, annotationHandle, request) {
37
+ const view = await getAnnotationView(root, annotationHandle);
38
+ if (!view.ok) {
39
+ return view.reason === 'unknown-handle'
40
+ ? { ok: false, reason: 'unknown-annotation-handle', error: 'unknown annotation handle' }
41
+ : { ok: false, reason: 'not-an-annotation', error: 'the handle does not name loadable visual-annotation evidence' };
42
+ }
43
+ const { annotation, source } = view;
44
+ if (annotation.source.kind !== 'external-reference') {
45
+ return { ok: false, reason: 'runtime-annotation', error: 'runtime-observation annotations are not materialized into external-reference revisions' };
46
+ }
47
+ if (source.status !== 'available' || (source.family !== 'external-reference-imported' && source.family !== 'external-reference-approved')) {
48
+ return { ok: false, reason: 'source-unavailable', error: 'the annotation source reference is not uniquely available in this evidence root' };
49
+ }
50
+ const sourceLoaded = await loadArtifactByHandle(root, source.handle);
51
+ if (!sourceLoaded.ok || sourceLoaded.detail.family !== source.family) {
52
+ return { ok: false, reason: 'source-unavailable', error: 'the annotation source reference is no longer loadable' };
53
+ }
54
+ const sourceReference = sourceLoaded.detail.artifact;
55
+ const decoded = decodeArtifactHandle(source.handle);
56
+ const sourceReferenceRoot = decoded.ok ? resolveContainedDir(root, decoded.relativeDir) : undefined;
57
+ if (sourceReferenceRoot === undefined)
58
+ return { ok: false, reason: 'source-unavailable', error: 'the annotation source reference directory could not be resolved' };
59
+ const media = await resolveMedia(root, source.handle, source.family === 'external-reference-approved' ? 'source-image' : 'image');
60
+ if (!media.ok)
61
+ return { ok: false, reason: 'source-image-unavailable', error: 'the canonical source reference image is not available' };
62
+ let imageBytes;
63
+ try {
64
+ imageBytes = await readFile(media.absolutePath);
65
+ }
66
+ catch {
67
+ return { ok: false, reason: 'image-read-failure', error: 'the canonical source reference image could not be read' };
68
+ }
69
+ const materialized = await materializeVisualAnnotationReference({
70
+ annotation,
71
+ sourceReference,
72
+ sourceReferenceRoot,
73
+ imageBytes,
74
+ selectedItemIds: request.itemIds,
75
+ outputLocation: referenceOutputLocation(),
76
+ cwd: session.projectRoot,
77
+ });
78
+ if (!materialized.ok) {
79
+ return { ok: false, reason: materialized.code, error: materialized.code === 'persistence-failure' ? 'the reference revision could not be persisted' : materialized.reason };
80
+ }
81
+ return {
82
+ ok: true,
83
+ referenceId: materialized.referenceId,
84
+ referenceRequestId: materialized.referenceRequestId,
85
+ handle: encodeArtifactHandle('external-reference-imported', `references/${materialized.referenceId}`),
86
+ supersedesReferenceId: materialized.supersedesReferenceId,
87
+ regionCount: materialized.regionCount,
88
+ requirementCount: materialized.requirementCount,
89
+ };
90
+ }
91
+ //# sourceMappingURL=annotationReferenceMaterialization.js.map