@dailephd/my-frontend-observer 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/README.md +109 -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 +78 -1
- package/docs/COMMANDS.md +44 -4
- package/docs/CONTRACTS.md +78 -8
- package/docs/CURRENT_STATE.md +224 -35
- package/docs/DEVELOPMENT.md +38 -3
- package/docs/PROJECT_DESCRIPTION.md +4 -1
- package/docs/PROJECT_MILESTONES.md +32 -0
- package/docs/PROJECT_OVERVIEW.md +58 -17
- package/docs/QUICKSTART.md +52 -39
- package/docs/RELEASE.md +17 -11
- package/docs/ROADMAP.md +458 -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/plans/v0.9.1-implementation-plan.md +468 -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/docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md +359 -0
- package/docs/reports/v0.9.1-batch2-hard-gate-validation-integration.md +262 -0
- package/docs/reports/v0.9.1-pre-release-readiness.md +206 -0
- package/package.json +3 -2
- package/dist/viewer/assets/index-D98S1_2d.js +0 -9
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
|
|
2
|
+
import { getProducerInfo } from '../domain/schema.js';
|
|
3
|
+
import { VISUAL_ANNOTATION_ARTIFACT_KIND, VISUAL_ANNOTATION_SCHEMA_VERSION, VISUAL_ANNOTATION_OVERLAY_PATH, isValidVisualAnnotationContent, renderVisualAnnotationOverlaySvg, } from '../domain/visualAnnotation.js';
|
|
4
|
+
import { buildVisualAnnotationRequestIdentity, buildVisualAnnotationInstanceIdentity } from '../domain/visualAnnotationIdentity.js';
|
|
5
|
+
import { normalizeOutputLocation } from '../request/paths.js';
|
|
6
|
+
import { writeVisualAnnotationArtifact, computeVisualAnnotationOverlaySha256 } from '../artifacts/visualAnnotationArtifactWriter.js';
|
|
7
|
+
function failure(code, message) {
|
|
8
|
+
return { ok: false, diagnostics: [{ code, severity: DIAGNOSTIC_SEVERITY[code], message }] };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The one canonical Batch 1 application use case for saving a visual
|
|
12
|
+
* annotation: validates source/items through the canonical annotation domain,
|
|
13
|
+
* normalizes the portable output location, derives the deterministic request
|
|
14
|
+
* identity and a fresh instance identity, renders the deterministic overlay
|
|
15
|
+
* and its SHA-256, and persists exactly once through the canonical writer.
|
|
16
|
+
*
|
|
17
|
+
* It never reads a viewer alias, resolves another artifact, checks revision
|
|
18
|
+
* lineage, checks whether an associated target/region exists, promotes a
|
|
19
|
+
* contract, materializes a reference, or modifies project configuration.
|
|
20
|
+
*/
|
|
21
|
+
export async function persistVisualAnnotation(options) {
|
|
22
|
+
const contentValidation = isValidVisualAnnotationContent(options.source, options.items);
|
|
23
|
+
if (!contentValidation.valid)
|
|
24
|
+
return failure('invalid-request', `invalid visual annotation: ${contentValidation.reason}`);
|
|
25
|
+
if (options.supersedesAnnotationId !== undefined && options.supersedesAnnotationId.length === 0) {
|
|
26
|
+
return failure('invalid-request', 'supersedesAnnotationId must be a non-empty string when supplied');
|
|
27
|
+
}
|
|
28
|
+
const normalizedOutput = normalizeOutputLocation(options.outputLocation);
|
|
29
|
+
if (!normalizedOutput.ok)
|
|
30
|
+
return { ok: false, diagnostics: [normalizedOutput.diagnostic] };
|
|
31
|
+
const { source, items } = options;
|
|
32
|
+
const annotationRequestId = buildVisualAnnotationRequestIdentity(source, items, options.supersedesAnnotationId);
|
|
33
|
+
const annotationId = buildVisualAnnotationInstanceIdentity(annotationRequestId);
|
|
34
|
+
const overlaySha256 = computeVisualAnnotationOverlaySha256(renderVisualAnnotationOverlaySvg(source, items));
|
|
35
|
+
const artifact = {
|
|
36
|
+
artifactKind: VISUAL_ANNOTATION_ARTIFACT_KIND,
|
|
37
|
+
schemaVersion: VISUAL_ANNOTATION_SCHEMA_VERSION,
|
|
38
|
+
annotationRequestId,
|
|
39
|
+
annotationId,
|
|
40
|
+
...(options.supersedesAnnotationId === undefined ? {} : { supersedesAnnotationId: options.supersedesAnnotationId }),
|
|
41
|
+
producer: getProducerInfo(),
|
|
42
|
+
source,
|
|
43
|
+
items,
|
|
44
|
+
overlay: {
|
|
45
|
+
path: VISUAL_ANNOTATION_OVERLAY_PATH,
|
|
46
|
+
format: 'svg',
|
|
47
|
+
width: source.coordinateSpace.width,
|
|
48
|
+
height: source.coordinateSpace.height,
|
|
49
|
+
sha256: overlaySha256,
|
|
50
|
+
},
|
|
51
|
+
provenance: {
|
|
52
|
+
createdAt: new Date().toISOString(),
|
|
53
|
+
origin: 'viewer',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const persisted = await writeVisualAnnotationArtifact(artifact, normalizedOutput.value, options.cwd === undefined ? {} : { cwd: options.cwd });
|
|
57
|
+
if (!persisted.ok)
|
|
58
|
+
return { ok: false, diagnostics: persisted.diagnostics };
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
annotationId,
|
|
62
|
+
annotationRequestId,
|
|
63
|
+
artifactRoot: persisted.artifactRoot,
|
|
64
|
+
manifestPath: persisted.manifestPath,
|
|
65
|
+
overlayPath: persisted.overlayPath,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=visualAnnotationPersistenceService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualAnnotationPersistenceService.js","sourceRoot":"","sources":["../../src/application/visualAnnotationPersistenceService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { VisualAnnotationArtifact, VisualAnnotationItem } from '../domain/visualAnnotation.js';
|
|
2
|
+
import type { ExternalReferenceArtifact } from '../domain/externalReference.js';
|
|
3
|
+
export interface MaterializeVisualAnnotationReferenceOptions {
|
|
4
|
+
annotation: VisualAnnotationArtifact;
|
|
5
|
+
sourceReference: ExternalReferenceArtifact;
|
|
6
|
+
/** Root directory of the selected source reference artifact, used only for canonical explicit supersession. */
|
|
7
|
+
sourceReferenceRoot: string;
|
|
8
|
+
/** The canonical source image bytes (verified here against the source image SHA-256). */
|
|
9
|
+
imageBytes: Uint8Array;
|
|
10
|
+
selectedItemIds: readonly string[];
|
|
11
|
+
outputLocation: string;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
}
|
|
14
|
+
export type MaterializeVisualAnnotationReferenceFailureCode = 'invalid-source' | 'source-mismatch' | 'invalid-selection' | 'invalid-materialized-reference' | 'persistence-failure';
|
|
15
|
+
export type MaterializeVisualAnnotationReferenceResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
referenceId: string;
|
|
18
|
+
referenceRequestId: string;
|
|
19
|
+
artifactRoot: string;
|
|
20
|
+
manifestPath: string;
|
|
21
|
+
imagePath: string;
|
|
22
|
+
lifecycle: 'imported';
|
|
23
|
+
supersedesReferenceId: string;
|
|
24
|
+
regionCount: number;
|
|
25
|
+
requirementCount: number;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
code: MaterializeVisualAnnotationReferenceFailureCode;
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
/** Maximum number of selected items accepted in one materialization (the annotation itself is bounded to 100 items). */
|
|
32
|
+
export declare const MAX_MATERIALIZATION_ITEM_IDS = 100;
|
|
33
|
+
/** Why one selected item cannot be materialized into a reference revision, or undefined when it can. */
|
|
34
|
+
export declare function referenceMaterializationBlockReason(item: VisualAnnotationItem): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* v0.9 Batch 6: the one canonical annotation-to-reference materialization use
|
|
37
|
+
* case. Explicitly selected, confirmed `reference-region` (create/refine) and
|
|
38
|
+
* `reference-requirement` items of one saved external-reference annotation are
|
|
39
|
+
* composed onto the selected source reference's own regions, requirements,
|
|
40
|
+
* applicability, and label, and persisted exactly once through the canonical
|
|
41
|
+
* `importExternalReference` as a new imported revision that explicitly
|
|
42
|
+
* supersedes the source. The source reference is never modified, never
|
|
43
|
+
* approved, and its image bytes are reused verbatim.
|
|
44
|
+
*
|
|
45
|
+
* Composition order: source regions keep their order (a refined region keeps
|
|
46
|
+
* its position and id, with the confirmed rectangle); created regions are
|
|
47
|
+
* appended in selection order; then source requirements (content only) are
|
|
48
|
+
* followed by selected requirements in selection order. Every selected
|
|
49
|
+
* relationship requirement must still hold for the final region geometry,
|
|
50
|
+
* checked through the canonical requirement expectation derivation. Any
|
|
51
|
+
* invalid selected item rejects the whole materialization before any write.
|
|
52
|
+
*/
|
|
53
|
+
export declare function materializeVisualAnnotationReference(options: MaterializeVisualAnnotationReferenceOptions): Promise<MaterializeVisualAnnotationReferenceResult>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isValidVisualAnnotationArtifact } from '../domain/visualAnnotation.js';
|
|
4
|
+
import { isApprovedExternalReferenceArtifact, isValidExternalReferenceArtifact } from '../domain/externalReference.js';
|
|
5
|
+
import { isValidReferenceRegions } from '../domain/externalReferenceRegions.js';
|
|
6
|
+
import { buildReferenceRequirement, deriveReferenceRequirementExpectation, isValidRawReferenceRequirement, isValidReferenceRequirements } from '../domain/externalReferenceRequirements.js';
|
|
7
|
+
import { importExternalReference } from './externalReferencePersistenceService.js';
|
|
8
|
+
import { readExternalReferenceArtifact } from '../artifacts/externalReferenceArtifactReader.js';
|
|
9
|
+
import { EXTERNAL_REFERENCE_MANIFEST_FILENAME } from '../artifacts/externalReferenceArtifactWriter.js';
|
|
10
|
+
/** Maximum number of selected items accepted in one materialization (the annotation itself is bounded to 100 items). */
|
|
11
|
+
export const MAX_MATERIALIZATION_ITEM_IDS = 100;
|
|
12
|
+
function fail(code, reason) {
|
|
13
|
+
return { ok: false, code, reason };
|
|
14
|
+
}
|
|
15
|
+
/** Why one selected item cannot be materialized into a reference revision, or undefined when it can. */
|
|
16
|
+
export function referenceMaterializationBlockReason(item) {
|
|
17
|
+
const { interpretation } = item;
|
|
18
|
+
if (interpretation.state === 'uninterpreted')
|
|
19
|
+
return 'the item has no interpretation';
|
|
20
|
+
if (interpretation.state === 'candidate')
|
|
21
|
+
return 'the item interpretation is a candidate, not confirmed';
|
|
22
|
+
const kind = interpretation.intent.kind;
|
|
23
|
+
if (kind === 'inspect' || kind === 'asset-sensitive')
|
|
24
|
+
return 'informational annotation intent is not materialized into the external-reference contract';
|
|
25
|
+
if (kind !== 'reference-region' && kind !== 'reference-requirement')
|
|
26
|
+
return `"${kind}" intent is not external-reference intent`;
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
/** The persisted requirement's authored content only - `requirementId` is never carried over; the canonical import recomputes it. */
|
|
30
|
+
function toRawReferenceRequirement(requirement) {
|
|
31
|
+
return {
|
|
32
|
+
category: requirement.category,
|
|
33
|
+
...(requirement.expectedDependentMode === undefined ? {} : { expectedDependentMode: requirement.expectedDependentMode }),
|
|
34
|
+
subject: requirement.subject,
|
|
35
|
+
...(requirement.tolerance === undefined ? {} : { tolerance: requirement.tolerance }),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function sourceImageOf(reference) {
|
|
39
|
+
return isApprovedExternalReferenceArtifact(reference) ? reference.sourceReference.image : reference.image;
|
|
40
|
+
}
|
|
41
|
+
function sourceMatches(annotation, reference) {
|
|
42
|
+
const source = annotation.source;
|
|
43
|
+
if (source.kind !== 'external-reference')
|
|
44
|
+
return false;
|
|
45
|
+
const approved = isApprovedExternalReferenceArtifact(reference);
|
|
46
|
+
const image = sourceImageOf(reference);
|
|
47
|
+
const imageOwnerReferenceId = approved ? reference.sourceReference.referenceId : reference.referenceId;
|
|
48
|
+
return (source.referenceId === reference.referenceId &&
|
|
49
|
+
source.referenceRequestId === reference.referenceRequestId &&
|
|
50
|
+
source.referenceSchemaVersion === reference.schemaVersion &&
|
|
51
|
+
source.lifecycle === reference.lifecycle.state &&
|
|
52
|
+
source.imageOwnerReferenceId === imageOwnerReferenceId &&
|
|
53
|
+
source.imageSha256 === image.sha256 &&
|
|
54
|
+
source.coordinateSpace.width === image.width &&
|
|
55
|
+
source.coordinateSpace.height === image.height);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* v0.9 Batch 6: the one canonical annotation-to-reference materialization use
|
|
59
|
+
* case. Explicitly selected, confirmed `reference-region` (create/refine) and
|
|
60
|
+
* `reference-requirement` items of one saved external-reference annotation are
|
|
61
|
+
* composed onto the selected source reference's own regions, requirements,
|
|
62
|
+
* applicability, and label, and persisted exactly once through the canonical
|
|
63
|
+
* `importExternalReference` as a new imported revision that explicitly
|
|
64
|
+
* supersedes the source. The source reference is never modified, never
|
|
65
|
+
* approved, and its image bytes are reused verbatim.
|
|
66
|
+
*
|
|
67
|
+
* Composition order: source regions keep their order (a refined region keeps
|
|
68
|
+
* its position and id, with the confirmed rectangle); created regions are
|
|
69
|
+
* appended in selection order; then source requirements (content only) are
|
|
70
|
+
* followed by selected requirements in selection order. Every selected
|
|
71
|
+
* relationship requirement must still hold for the final region geometry,
|
|
72
|
+
* checked through the canonical requirement expectation derivation. Any
|
|
73
|
+
* invalid selected item rejects the whole materialization before any write.
|
|
74
|
+
*/
|
|
75
|
+
export async function materializeVisualAnnotationReference(options) {
|
|
76
|
+
const { annotation, sourceReference, selectedItemIds } = options;
|
|
77
|
+
const annotationValidation = isValidVisualAnnotationArtifact(annotation);
|
|
78
|
+
if (!annotationValidation.valid)
|
|
79
|
+
return fail('invalid-source', `visual annotation is invalid: ${annotationValidation.reason}`);
|
|
80
|
+
if (annotation.source.kind !== 'external-reference')
|
|
81
|
+
return fail('invalid-source', 'only external-reference annotations can be materialized into a reference revision');
|
|
82
|
+
const referenceValidation = isValidExternalReferenceArtifact(sourceReference);
|
|
83
|
+
if (!referenceValidation.valid)
|
|
84
|
+
return fail('invalid-source', `source external reference is invalid: ${referenceValidation.reason}`);
|
|
85
|
+
if (!sourceMatches(annotation, sourceReference))
|
|
86
|
+
return fail('source-mismatch', 'the supplied reference is not the exact canonical source of this annotation');
|
|
87
|
+
// Supersession is resolved by the canonical import service from this root, so it must hold exactly the supplied source.
|
|
88
|
+
const rootRead = await readExternalReferenceArtifact(path.join(options.sourceReferenceRoot, EXTERNAL_REFERENCE_MANIFEST_FILENAME));
|
|
89
|
+
if (!rootRead.ok || rootRead.artifact.referenceId !== sourceReference.referenceId) {
|
|
90
|
+
return fail('source-mismatch', 'the source reference root does not contain the supplied source reference');
|
|
91
|
+
}
|
|
92
|
+
const image = sourceImageOf(sourceReference);
|
|
93
|
+
if (createHash('sha256').update(options.imageBytes).digest('hex') !== image.sha256) {
|
|
94
|
+
return fail('source-mismatch', 'the supplied image bytes do not match the source reference image SHA-256');
|
|
95
|
+
}
|
|
96
|
+
if (selectedItemIds.length === 0)
|
|
97
|
+
return fail('invalid-selection', 'select at least one confirmed reference intent item to materialize');
|
|
98
|
+
if (selectedItemIds.length > MAX_MATERIALIZATION_ITEM_IDS)
|
|
99
|
+
return fail('invalid-selection', `at most ${MAX_MATERIALIZATION_ITEM_IDS} items can be materialized at once`);
|
|
100
|
+
if (new Set(selectedItemIds).size !== selectedItemIds.length)
|
|
101
|
+
return fail('invalid-selection', 'selected annotation item ids must be unique');
|
|
102
|
+
const itemsById = new Map(annotation.items.map((item) => [item.annotationItemId, item]));
|
|
103
|
+
const selected = [];
|
|
104
|
+
for (const itemId of selectedItemIds) {
|
|
105
|
+
const item = itemsById.get(itemId);
|
|
106
|
+
if (item === undefined)
|
|
107
|
+
return fail('invalid-selection', `selected annotation item "${itemId}" does not exist in this annotation`);
|
|
108
|
+
const blocked = referenceMaterializationBlockReason(item);
|
|
109
|
+
if (blocked !== undefined)
|
|
110
|
+
return fail('invalid-selection', `annotation item "${itemId}" cannot be materialized: ${blocked}`);
|
|
111
|
+
selected.push({ itemId, intent: item.interpretation.intent });
|
|
112
|
+
}
|
|
113
|
+
// --- regions: refinements in place, then creates appended in selection order ---
|
|
114
|
+
const sourceRegions = sourceReference.regions ?? [];
|
|
115
|
+
const finalRegions = sourceRegions.map((region) => ({ id: region.id, rectangle: { ...region.rectangle } }));
|
|
116
|
+
const refinedIndexes = new Set();
|
|
117
|
+
const createdIds = new Set();
|
|
118
|
+
for (const { itemId, intent } of selected) {
|
|
119
|
+
if (intent.kind !== 'reference-region')
|
|
120
|
+
continue;
|
|
121
|
+
const lowerId = intent.region.id.toLowerCase();
|
|
122
|
+
if (intent.mode === 'refine') {
|
|
123
|
+
const index = sourceRegions.findIndex((region) => region.id.toLowerCase() === lowerId);
|
|
124
|
+
if (index < 0)
|
|
125
|
+
return fail('invalid-selection', `annotation item "${itemId}" refines region "${intent.region.id}", which does not exist in the source reference`);
|
|
126
|
+
if (refinedIndexes.has(index))
|
|
127
|
+
return fail('invalid-selection', `more than one selected item refines region "${intent.region.id}"`);
|
|
128
|
+
refinedIndexes.add(index);
|
|
129
|
+
finalRegions[index] = { id: sourceRegions[index].id, rectangle: { ...intent.region.rectangle } };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const { itemId, intent } of selected) {
|
|
133
|
+
if (intent.kind !== 'reference-region' || intent.mode !== 'create')
|
|
134
|
+
continue;
|
|
135
|
+
const lowerId = intent.region.id.toLowerCase();
|
|
136
|
+
if (sourceRegions.some((region) => region.id.toLowerCase() === lowerId))
|
|
137
|
+
return fail('invalid-selection', `annotation item "${itemId}" creates region "${intent.region.id}", which already exists in the source reference`);
|
|
138
|
+
if (createdIds.has(lowerId))
|
|
139
|
+
return fail('invalid-selection', `more than one selected item creates region "${intent.region.id}"`);
|
|
140
|
+
createdIds.add(lowerId);
|
|
141
|
+
finalRegions.push({ id: intent.region.id, rectangle: { ...intent.region.rectangle } });
|
|
142
|
+
}
|
|
143
|
+
const regionsValidation = isValidReferenceRegions(finalRegions, image.width, image.height);
|
|
144
|
+
if (!regionsValidation.valid)
|
|
145
|
+
return fail('invalid-materialized-reference', `resulting regions are invalid: ${regionsValidation.reason}`);
|
|
146
|
+
// --- requirements: source content first, then selected requirements in selection order ---
|
|
147
|
+
const rawRequirements = (sourceReference.requirements ?? []).map(toRawReferenceRequirement);
|
|
148
|
+
for (const { itemId, intent } of selected) {
|
|
149
|
+
if (intent.kind !== 'reference-requirement')
|
|
150
|
+
continue;
|
|
151
|
+
const rawValidation = isValidRawReferenceRequirement(intent.requirement);
|
|
152
|
+
if (!rawValidation.valid)
|
|
153
|
+
return fail('invalid-selection', `annotation item "${itemId}" has an invalid requirement: ${rawValidation.reason}`);
|
|
154
|
+
if (intent.requirement.subject.kind === 'region-relationship') {
|
|
155
|
+
const expectation = deriveReferenceRequirementExpectation(intent.requirement.subject, finalRegions);
|
|
156
|
+
if (!expectation.available || expectation.kind !== 'relationship' || !expectation.matches) {
|
|
157
|
+
return fail('invalid-materialized-reference', `annotation item "${itemId}" requires a region relationship that the resulting region geometry does not exhibit`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
rawRequirements.push(intent.requirement);
|
|
161
|
+
}
|
|
162
|
+
const builtRequirements = rawRequirements.map((raw) => buildReferenceRequirement(raw));
|
|
163
|
+
const requirementsValidation = isValidReferenceRequirements(builtRequirements, finalRegions);
|
|
164
|
+
if (!requirementsValidation.valid)
|
|
165
|
+
return fail('invalid-materialized-reference', `resulting requirements are invalid: ${requirementsValidation.reason}`);
|
|
166
|
+
const label = sourceReference.provenance.label;
|
|
167
|
+
const imported = await importExternalReference(options.imageBytes, {
|
|
168
|
+
outputLocation: options.outputLocation,
|
|
169
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
170
|
+
...(label === undefined ? {} : { label }),
|
|
171
|
+
supersedesReferenceRoot: options.sourceReferenceRoot,
|
|
172
|
+
...(finalRegions.length === 0 ? {} : { regions: finalRegions }),
|
|
173
|
+
...(rawRequirements.length === 0 ? {} : { requirements: rawRequirements }),
|
|
174
|
+
...(sourceReference.applicability === undefined ? {} : { applicability: sourceReference.applicability }),
|
|
175
|
+
});
|
|
176
|
+
if (!imported.ok) {
|
|
177
|
+
const first = imported.diagnostics[0];
|
|
178
|
+
const invalidComposition = first !== undefined && ['invalid-reference-region', 'invalid-reference-requirement', 'invalid-reference-applicability'].includes(first.code);
|
|
179
|
+
return fail(invalidComposition ? 'invalid-materialized-reference' : 'persistence-failure', imported.diagnostics.map((d) => d.message).join('; '));
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
referenceId: imported.referenceId,
|
|
184
|
+
referenceRequestId: imported.referenceRequestId,
|
|
185
|
+
artifactRoot: imported.artifactRoot,
|
|
186
|
+
manifestPath: imported.manifestPath,
|
|
187
|
+
imagePath: imported.imagePath,
|
|
188
|
+
lifecycle: 'imported',
|
|
189
|
+
supersedesReferenceId: sourceReference.referenceId,
|
|
190
|
+
regionCount: imported.regionCount,
|
|
191
|
+
requirementCount: imported.requirementCount,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=visualAnnotationReferenceMaterializationService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualAnnotationReferenceMaterializationService.js","sourceRoot":"","sources":["../../src/application/visualAnnotationReferenceMaterializationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { VisualAnnotationArtifact } from '../domain/visualAnnotation.js';
|
|
2
|
+
export type ReadVisualAnnotationArtifactResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
artifact: VisualAnnotationArtifact;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
reason: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Narrow canonical reader counterpart to
|
|
11
|
+
* `visualAnnotationArtifactWriter.ts#writeVisualAnnotationArtifact`: reads one
|
|
12
|
+
* `manifest.json`, parses it, validates it through the same
|
|
13
|
+
* `isValidVisualAnnotationArtifact` gate the writer uses (never a second
|
|
14
|
+
* validator), then verifies the owned sibling `annotation-overlay.svg`: it
|
|
15
|
+
* must be a regular file (lstat, never following a symlink/junction) whose
|
|
16
|
+
* SHA-256 equals `overlay.sha256`. Read-only: never repairs a manifest, never
|
|
17
|
+
* regenerates a missing overlay, never accepts an unsupported schema version.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readVisualAnnotationArtifact(manifestPath: string): Promise<ReadVisualAnnotationArtifactResult>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { lstat, readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isValidVisualAnnotationArtifact } from '../domain/visualAnnotation.js';
|
|
4
|
+
import { VISUAL_ANNOTATION_OVERLAY_FILENAME, computeVisualAnnotationOverlaySha256 } from './visualAnnotationArtifactWriter.js';
|
|
5
|
+
/**
|
|
6
|
+
* Narrow canonical reader counterpart to
|
|
7
|
+
* `visualAnnotationArtifactWriter.ts#writeVisualAnnotationArtifact`: reads one
|
|
8
|
+
* `manifest.json`, parses it, validates it through the same
|
|
9
|
+
* `isValidVisualAnnotationArtifact` gate the writer uses (never a second
|
|
10
|
+
* validator), then verifies the owned sibling `annotation-overlay.svg`: it
|
|
11
|
+
* must be a regular file (lstat, never following a symlink/junction) whose
|
|
12
|
+
* SHA-256 equals `overlay.sha256`. Read-only: never repairs a manifest, never
|
|
13
|
+
* regenerates a missing overlay, never accepts an unsupported schema version.
|
|
14
|
+
*/
|
|
15
|
+
export async function readVisualAnnotationArtifact(manifestPath) {
|
|
16
|
+
let raw;
|
|
17
|
+
try {
|
|
18
|
+
raw = await readFile(manifestPath, 'utf8');
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22
|
+
return { ok: false, reason: `failed to read visual-annotation artifact manifest at "${manifestPath}": ${message}` };
|
|
23
|
+
}
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30
|
+
return { ok: false, reason: `visual-annotation artifact manifest at "${manifestPath}" is not valid JSON: ${message}` };
|
|
31
|
+
}
|
|
32
|
+
const validation = isValidVisualAnnotationArtifact(parsed);
|
|
33
|
+
if (!validation.valid) {
|
|
34
|
+
return { ok: false, reason: `visual-annotation artifact manifest at "${manifestPath}" failed structural validation: ${validation.reason}` };
|
|
35
|
+
}
|
|
36
|
+
const artifact = parsed;
|
|
37
|
+
// The validator already pins overlay.path to the exact owned filename; this re-check keeps the
|
|
38
|
+
// filesystem resolution below fail-closed even if that contract ever changed.
|
|
39
|
+
if (artifact.overlay.path !== VISUAL_ANNOTATION_OVERLAY_FILENAME) {
|
|
40
|
+
return { ok: false, reason: `visual-annotation artifact manifest at "${manifestPath}" references an unsupported overlay path` };
|
|
41
|
+
}
|
|
42
|
+
const overlayPath = path.join(path.dirname(manifestPath), VISUAL_ANNOTATION_OVERLAY_FILENAME);
|
|
43
|
+
try {
|
|
44
|
+
const info = await lstat(overlayPath);
|
|
45
|
+
if (!info.isFile()) {
|
|
46
|
+
return { ok: false, reason: `visual-annotation overlay at "${overlayPath}" is not a regular file` };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
return { ok: false, reason: `visual-annotation overlay at "${overlayPath}" is missing: ${message}` };
|
|
52
|
+
}
|
|
53
|
+
let overlayBytes;
|
|
54
|
+
try {
|
|
55
|
+
overlayBytes = await readFile(overlayPath);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59
|
+
return { ok: false, reason: `failed to read visual-annotation overlay at "${overlayPath}": ${message}` };
|
|
60
|
+
}
|
|
61
|
+
if (computeVisualAnnotationOverlaySha256(overlayBytes) !== artifact.overlay.sha256) {
|
|
62
|
+
return { ok: false, reason: `visual-annotation overlay at "${overlayPath}" does not match overlay.sha256` };
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, artifact };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=visualAnnotationArtifactReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualAnnotationArtifactReader.js","sourceRoot":"","sources":["../../src/artifacts/visualAnnotationArtifactReader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { VisualAnnotationArtifact } from '../domain/visualAnnotation.js';
|
|
2
|
+
import type { Diagnostic } from '../domain/diagnostics.js';
|
|
3
|
+
export declare const VISUAL_ANNOTATION_MANIFEST_FILENAME = "manifest.json";
|
|
4
|
+
export declare const VISUAL_ANNOTATION_OVERLAY_FILENAME: "annotation-overlay.svg";
|
|
5
|
+
/**
|
|
6
|
+
* Observer-owned result of persisting one VisualAnnotationArtifact. Mirrors
|
|
7
|
+
* PersistedExternalReferenceResult's shape: the artifact directory owns
|
|
8
|
+
* exactly manifest.json and its derived annotation-overlay.svg.
|
|
9
|
+
*/
|
|
10
|
+
export type PersistedVisualAnnotationResult = {
|
|
11
|
+
ok: true;
|
|
12
|
+
artifactRoot: string;
|
|
13
|
+
manifestPath: string;
|
|
14
|
+
overlayPath: string;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
diagnostics: Diagnostic[];
|
|
18
|
+
};
|
|
19
|
+
export interface WriteVisualAnnotationArtifactOptions {
|
|
20
|
+
/** Base directory that `outputLocation` is resolved against. Defaults to `process.cwd()`. */
|
|
21
|
+
cwd?: string;
|
|
22
|
+
}
|
|
23
|
+
/** SHA-256 lowercase hex over the exact UTF-8 bytes of an overlay SVG - shared by the writer, reader, and persistence service. */
|
|
24
|
+
export declare function computeVisualAnnotationOverlaySha256(overlay: string | Uint8Array): string;
|
|
25
|
+
/**
|
|
26
|
+
* Persists one already-assembled VisualAnnotationArtifact as one portable
|
|
27
|
+
* artifact directory: `<outputLocation>/<annotationId>/{annotation-overlay.svg,manifest.json}`.
|
|
28
|
+
*
|
|
29
|
+
* The overlay is always re-derived here through the one canonical renderer
|
|
30
|
+
* (`domain/visualAnnotation.ts#renderVisualAnnotationOverlaySvg`) and its
|
|
31
|
+
* digest must equal `artifact.overlay.sha256`; a mismatch is refused rather
|
|
32
|
+
* than persisted. Never reads or copies the source observation screenshot or
|
|
33
|
+
* reference image.
|
|
34
|
+
*
|
|
35
|
+
* `annotationId` is fresh per persisted instance, so an existing directory at
|
|
36
|
+
* the final path is treated as a genuine collision and refused rather than
|
|
37
|
+
* overwritten. Writing happens in a sibling temporary directory
|
|
38
|
+
* (`.tmp-<annotationId>`), owned overlay media before manifest.json
|
|
39
|
+
* (manifest-last), and the whole directory is exposed under its real name
|
|
40
|
+
* only via one atomic rename. The temporary directory is removed on failure.
|
|
41
|
+
*/
|
|
42
|
+
export declare function writeVisualAnnotationArtifact(artifact: VisualAnnotationArtifact, outputLocation: string, options?: WriteVisualAnnotationArtifactOptions): Promise<PersistedVisualAnnotationResult>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { mkdir, writeFile, rename, rm, access } from 'node:fs/promises';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { isValidVisualAnnotationArtifact, renderVisualAnnotationOverlaySvg, VISUAL_ANNOTATION_OVERLAY_PATH } from '../domain/visualAnnotation.js';
|
|
5
|
+
import { DIAGNOSTIC_SEVERITY } from '../domain/diagnostics.js';
|
|
6
|
+
export const VISUAL_ANNOTATION_MANIFEST_FILENAME = 'manifest.json';
|
|
7
|
+
export const VISUAL_ANNOTATION_OVERLAY_FILENAME = VISUAL_ANNOTATION_OVERLAY_PATH;
|
|
8
|
+
/** SHA-256 lowercase hex over the exact UTF-8 bytes of an overlay SVG - shared by the writer, reader, and persistence service. */
|
|
9
|
+
export function computeVisualAnnotationOverlaySha256(overlay) {
|
|
10
|
+
return createHash('sha256')
|
|
11
|
+
.update(typeof overlay === 'string' ? Buffer.from(overlay, 'utf8') : overlay)
|
|
12
|
+
.digest('hex');
|
|
13
|
+
}
|
|
14
|
+
function writeFailure(message, details) {
|
|
15
|
+
const diagnostic = {
|
|
16
|
+
code: 'artifact-write-failure',
|
|
17
|
+
severity: DIAGNOSTIC_SEVERITY['artifact-write-failure'],
|
|
18
|
+
message,
|
|
19
|
+
...(details === undefined ? {} : { details }),
|
|
20
|
+
};
|
|
21
|
+
return { ok: false, diagnostics: [diagnostic] };
|
|
22
|
+
}
|
|
23
|
+
async function pathExists(candidate) {
|
|
24
|
+
return access(candidate).then(() => true, () => false);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Persists one already-assembled VisualAnnotationArtifact as one portable
|
|
28
|
+
* artifact directory: `<outputLocation>/<annotationId>/{annotation-overlay.svg,manifest.json}`.
|
|
29
|
+
*
|
|
30
|
+
* The overlay is always re-derived here through the one canonical renderer
|
|
31
|
+
* (`domain/visualAnnotation.ts#renderVisualAnnotationOverlaySvg`) and its
|
|
32
|
+
* digest must equal `artifact.overlay.sha256`; a mismatch is refused rather
|
|
33
|
+
* than persisted. Never reads or copies the source observation screenshot or
|
|
34
|
+
* reference image.
|
|
35
|
+
*
|
|
36
|
+
* `annotationId` is fresh per persisted instance, so an existing directory at
|
|
37
|
+
* the final path is treated as a genuine collision and refused rather than
|
|
38
|
+
* overwritten. Writing happens in a sibling temporary directory
|
|
39
|
+
* (`.tmp-<annotationId>`), owned overlay media before manifest.json
|
|
40
|
+
* (manifest-last), and the whole directory is exposed under its real name
|
|
41
|
+
* only via one atomic rename. The temporary directory is removed on failure.
|
|
42
|
+
*/
|
|
43
|
+
export async function writeVisualAnnotationArtifact(artifact, outputLocation, options = {}) {
|
|
44
|
+
const validation = isValidVisualAnnotationArtifact(artifact);
|
|
45
|
+
if (!validation.valid) {
|
|
46
|
+
return writeFailure(`refusing to persist an invalid VisualAnnotationArtifact: ${validation.reason}`);
|
|
47
|
+
}
|
|
48
|
+
const overlaySvg = renderVisualAnnotationOverlaySvg(artifact.source, artifact.items);
|
|
49
|
+
const overlayBytes = Buffer.from(overlaySvg, 'utf8');
|
|
50
|
+
const overlaySha256 = computeVisualAnnotationOverlaySha256(overlayBytes);
|
|
51
|
+
if (overlaySha256 !== artifact.overlay.sha256) {
|
|
52
|
+
return writeFailure('refusing to persist a VisualAnnotationArtifact whose overlay.sha256 does not match the deterministically rendered overlay', {
|
|
53
|
+
expectedSha256: overlaySha256,
|
|
54
|
+
declaredSha256: artifact.overlay.sha256,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const cwd = options.cwd ?? process.cwd();
|
|
58
|
+
const outputRoot = path.resolve(cwd, outputLocation);
|
|
59
|
+
const finalRoot = path.join(outputRoot, artifact.annotationId);
|
|
60
|
+
const tempRoot = path.join(outputRoot, `.tmp-${artifact.annotationId}`);
|
|
61
|
+
if (await pathExists(finalRoot)) {
|
|
62
|
+
return writeFailure(`a visual-annotation artifact already exists at "${artifact.annotationId}"; refusing to overwrite it`, {
|
|
63
|
+
artifactRoot: finalRoot,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
68
|
+
await mkdir(tempRoot, { recursive: true });
|
|
69
|
+
await writeFile(path.join(tempRoot, VISUAL_ANNOTATION_OVERLAY_FILENAME), overlayBytes);
|
|
70
|
+
await writeFile(path.join(tempRoot, VISUAL_ANNOTATION_MANIFEST_FILENAME), JSON.stringify(artifact, null, 2), 'utf8');
|
|
71
|
+
await rename(tempRoot, finalRoot);
|
|
72
|
+
return {
|
|
73
|
+
ok: true,
|
|
74
|
+
artifactRoot: finalRoot,
|
|
75
|
+
manifestPath: path.join(finalRoot, VISUAL_ANNOTATION_MANIFEST_FILENAME),
|
|
76
|
+
overlayPath: path.join(finalRoot, VISUAL_ANNOTATION_OVERLAY_FILENAME),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
await rm(tempRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
81
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
82
|
+
return writeFailure(`failed to persist visual-annotation artifact: ${message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=visualAnnotationArtifactWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visualAnnotationArtifactWriter.js","sourceRoot":"","sources":["../../src/artifacts/visualAnnotationArtifactWriter.ts"],"names":[],"mappings":"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"}
|