@penpair/core 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters.d.ts +1 -1
- package/dist/adapters.js +3 -0
- package/dist/approvals.d.ts +4 -1
- package/dist/approvals.js +7 -4
- package/dist/baseline-coverage.js +3 -3
- package/dist/baselines-config.d.ts +3 -0
- package/dist/baselines-config.js +3 -0
- package/dist/baselines.d.ts +7 -0
- package/dist/baselines.js +90 -21
- package/dist/bounded-file.d.ts +2 -0
- package/dist/bounded-file.js +38 -0
- package/dist/cli-main.js +64 -3
- package/dist/design-commands/context.d.ts +9 -0
- package/dist/design-commands/context.js +1 -0
- package/dist/design-commands/index.d.ts +3 -0
- package/dist/design-commands/index.js +7 -0
- package/dist/design-commands/map-command.d.ts +4 -0
- package/dist/design-commands/map-command.js +685 -0
- package/dist/design-commands/release-command.d.ts +3 -0
- package/dist/design-commands/release-command.js +239 -0
- package/dist/design-releases/artifact-paths.d.ts +6 -0
- package/dist/design-releases/artifact-paths.js +82 -0
- package/dist/design-releases/assemble.d.ts +3 -0
- package/dist/design-releases/assemble.js +176 -0
- package/dist/design-releases/check.d.ts +16 -0
- package/dist/design-releases/check.js +323 -0
- package/dist/design-releases/context.d.ts +2 -0
- package/dist/design-releases/context.js +119 -0
- package/dist/design-releases/contract-graph.d.ts +13 -0
- package/dist/design-releases/contract-graph.js +75 -0
- package/dist/design-releases/fidelity.d.ts +23 -0
- package/dist/design-releases/fidelity.js +328 -0
- package/dist/design-releases/finalize.d.ts +4 -0
- package/dist/design-releases/finalize.js +201 -0
- package/dist/design-releases/index.d.ts +8 -0
- package/dist/design-releases/index.js +7 -0
- package/dist/design-releases/live.d.ts +22 -0
- package/dist/design-releases/live.js +97 -0
- package/dist/design-releases/snippets.d.ts +5 -0
- package/dist/design-releases/snippets.js +89 -0
- package/dist/design-releases/store.d.ts +10 -0
- package/dist/design-releases/store.js +205 -0
- package/dist/design-releases/types.d.ts +161 -0
- package/dist/design-releases/types.js +1 -0
- package/dist/design-releases/validation.d.ts +6 -0
- package/dist/design-releases/validation.js +384 -0
- package/dist/design-releases/verify.d.ts +3 -0
- package/dist/design-releases/verify.js +148 -0
- package/dist/image-codec.d.ts +3 -0
- package/dist/image-codec.js +78 -0
- package/dist/image-evidence.d.ts +15 -0
- package/dist/image-evidence.js +121 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/inspection.js +13 -5
- package/dist/instance/config-schema.d.ts +4 -0
- package/dist/instance/config-schema.js +7 -0
- package/dist/map-penpot/content-validation.d.ts +3 -0
- package/dist/map-penpot/content-validation.js +57 -0
- package/dist/map-penpot/content.d.ts +22 -0
- package/dist/map-penpot/content.js +173 -0
- package/dist/map-penpot/details.d.ts +4 -0
- package/dist/map-penpot/details.js +84 -0
- package/dist/map-penpot/evidence-error.d.ts +5 -0
- package/dist/map-penpot/evidence-error.js +9 -0
- package/dist/map-penpot/icons.d.ts +5 -0
- package/dist/map-penpot/icons.js +79 -0
- package/dist/map-penpot/import-content.d.ts +2 -0
- package/dist/map-penpot/import-content.js +150 -0
- package/dist/map-penpot/import.d.ts +5 -0
- package/dist/map-penpot/import.js +255 -0
- package/dist/map-penpot/index.d.ts +11 -0
- package/dist/map-penpot/index.js +10 -0
- package/dist/map-penpot/path-geometry.d.ts +5 -0
- package/dist/map-penpot/path-geometry.js +51 -0
- package/dist/map-penpot/presentation.d.ts +20 -0
- package/dist/map-penpot/presentation.js +205 -0
- package/dist/map-penpot/projection.d.ts +8 -0
- package/dist/map-penpot/projection.js +176 -0
- package/dist/map-penpot/publish.d.ts +4 -0
- package/dist/map-penpot/publish.js +301 -0
- package/dist/map-penpot/release-maps.d.ts +21 -0
- package/dist/map-penpot/release-maps.js +104 -0
- package/dist/map-penpot/snippets.d.ts +4 -0
- package/dist/map-penpot/snippets.js +164 -0
- package/dist/map-penpot/status-labels.d.ts +7 -0
- package/dist/map-penpot/status-labels.js +7 -0
- package/dist/map-penpot/types.d.ts +161 -0
- package/dist/map-penpot/types.js +1 -0
- package/dist/maps/annotation-layout.d.ts +24 -0
- package/dist/maps/annotation-layout.js +85 -0
- package/dist/maps/context.d.ts +13 -0
- package/dist/maps/context.js +207 -0
- package/dist/maps/index.d.ts +9 -0
- package/dist/maps/index.js +9 -0
- package/dist/maps/layout.d.ts +35 -0
- package/dist/maps/layout.js +364 -0
- package/dist/maps/proposal.d.ts +5 -0
- package/dist/maps/proposal.js +67 -0
- package/dist/maps/render.d.ts +4 -0
- package/dist/maps/render.js +331 -0
- package/dist/maps/review.d.ts +16 -0
- package/dist/maps/review.js +90 -0
- package/dist/maps/store.d.ts +21 -0
- package/dist/maps/store.js +222 -0
- package/dist/maps/types.d.ts +206 -0
- package/dist/maps/types.js +1 -0
- package/dist/maps/validation.d.ts +10 -0
- package/dist/maps/validation.js +375 -0
- package/dist/skill-routing.js +4 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/skills/penpair-design-release/SKILL.md +55 -0
- package/skills/penpair-design-release/agents/openai.yaml +6 -0
- package/skills/penpair-design-release/references/workflow.md +55 -0
- package/skills/penpair-diagrams/SKILL.md +52 -0
- package/skills/penpair-diagrams/agents/openai.yaml +6 -0
- package/skills/penpair-diagrams/references/workflow.md +84 -0
- package/skills/penpair-handoff/SKILL.md +7 -0
- package/skills/penpair-handoff/references/handoff-flow.md +7 -1
- package/skills/penpair-integration-map/SKILL.md +44 -0
- package/skills/penpair-integration-map/agents/openai.yaml +6 -0
- package/skills/penpair-integration-map/references/workflow.md +33 -0
- package/skills/penpair-journey-map/SKILL.md +42 -0
- package/skills/penpair-journey-map/agents/openai.yaml +6 -0
- package/skills/penpair-journey-map/references/workflow.md +35 -0
- package/skills/penpair-verify/SKILL.md +7 -0
- package/skills/penpair-verify/references/browser-evidence.md +10 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { observationHash } from "../journal.js";
|
|
2
|
+
import { routeMapEdges, mapDrawingGeometryProblems } from "../maps/layout.js";
|
|
3
|
+
import { buildMapDrawingPlan } from "../maps/render.js";
|
|
4
|
+
import { hashMapContract } from "../maps/validation.js";
|
|
5
|
+
import { importMapProposal } from "./import.js";
|
|
6
|
+
import { drawingFromPlan, projection, validateDrawing } from "./projection.js";
|
|
7
|
+
export function validateNativeMapProjection(raw) {
|
|
8
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
9
|
+
throw new Error("Saved map projection must be an object");
|
|
10
|
+
}
|
|
11
|
+
const record = raw;
|
|
12
|
+
if (record.schemaVersion !== 1 ||
|
|
13
|
+
record.kind !== "native-map-projection" ||
|
|
14
|
+
typeof record.fileId !== "string" ||
|
|
15
|
+
!record.fileId ||
|
|
16
|
+
typeof record.pageId !== "string" ||
|
|
17
|
+
!record.pageId ||
|
|
18
|
+
typeof record.mapId !== "string" ||
|
|
19
|
+
!record.mapId ||
|
|
20
|
+
typeof record.fingerprint !== "string") {
|
|
21
|
+
throw new Error("Saved map projection is missing identity or fingerprint");
|
|
22
|
+
}
|
|
23
|
+
const checked = projection({
|
|
24
|
+
fileId: record.fileId,
|
|
25
|
+
pageId: record.pageId,
|
|
26
|
+
}, record.mapId, record);
|
|
27
|
+
if (checked.fingerprint !== record.fingerprint) {
|
|
28
|
+
throw new Error("Saved map projection fingerprint differs from its observations");
|
|
29
|
+
}
|
|
30
|
+
return structuredClone(checked);
|
|
31
|
+
}
|
|
32
|
+
function sameMap(left, right) {
|
|
33
|
+
return (left.fileId === right.fileId &&
|
|
34
|
+
left.pageId === right.pageId &&
|
|
35
|
+
left.boardId === right.boardId &&
|
|
36
|
+
left.mapId === right.mapId);
|
|
37
|
+
}
|
|
38
|
+
export function validateMapPresentationProposal(raw) {
|
|
39
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
40
|
+
throw new Error("Map presentation proposal must be an object");
|
|
41
|
+
}
|
|
42
|
+
const record = raw;
|
|
43
|
+
if (record.schemaVersion !== 1 ||
|
|
44
|
+
record.kind !== "map-presentation-proposal" ||
|
|
45
|
+
![
|
|
46
|
+
"journey-map",
|
|
47
|
+
"integration-map",
|
|
48
|
+
].includes(String(record.mapKind)) ||
|
|
49
|
+
typeof record.mapId !== "string" ||
|
|
50
|
+
!record.mapId ||
|
|
51
|
+
[
|
|
52
|
+
record.baseContractHash,
|
|
53
|
+
record.baselineFingerprint,
|
|
54
|
+
record.observedProjectionHash,
|
|
55
|
+
record.hash,
|
|
56
|
+
].some((hash) => typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash))) {
|
|
57
|
+
throw new Error("Unsupported map presentation proposal or missing hashes");
|
|
58
|
+
}
|
|
59
|
+
const observed = validateNativeMapProjection(record.projection);
|
|
60
|
+
if (observed.fingerprint !== record.observedProjectionHash ||
|
|
61
|
+
observed.contractHash !== record.baseContractHash ||
|
|
62
|
+
!observed.boardId ||
|
|
63
|
+
observed.conflicts.length) {
|
|
64
|
+
throw new Error("Presentation proposal observations conflict with their base contract");
|
|
65
|
+
}
|
|
66
|
+
const { hash, ...body } = record;
|
|
67
|
+
if (hash !== observationHash(body)) {
|
|
68
|
+
throw new Error("Map presentation proposal record hash differs");
|
|
69
|
+
}
|
|
70
|
+
return structuredClone(record);
|
|
71
|
+
}
|
|
72
|
+
export function createMapPresentationProposal(map, drawing, observed, baseline) {
|
|
73
|
+
const baseHash = hashMapContract(map);
|
|
74
|
+
const before = validateNativeMapProjection(baseline);
|
|
75
|
+
const after = validateNativeMapProjection(observed);
|
|
76
|
+
if (drawing.contractHash !== baseHash ||
|
|
77
|
+
before.contractHash !== baseHash ||
|
|
78
|
+
after.contractHash !== baseHash ||
|
|
79
|
+
!sameMap(before, after) ||
|
|
80
|
+
before.fingerprint === after.fingerprint) {
|
|
81
|
+
throw new Error("Presentation proposal requires a changed projection of the same exact contract and board");
|
|
82
|
+
}
|
|
83
|
+
const semantic = importMapProposal(drawing, after, before);
|
|
84
|
+
if (semantic.metadataChanges ||
|
|
85
|
+
semantic.changes.length ||
|
|
86
|
+
semantic.edgeChanges.length ||
|
|
87
|
+
semantic.title !== undefined ||
|
|
88
|
+
semantic.findings.length ||
|
|
89
|
+
before.conflicts.length) {
|
|
90
|
+
throw new Error("Semantic edits or unresolved findings require a canonical map proposal");
|
|
91
|
+
}
|
|
92
|
+
const priorShapes = new Map(before.shapes.map((shape) => [
|
|
93
|
+
shape.shapeId,
|
|
94
|
+
shape,
|
|
95
|
+
]));
|
|
96
|
+
if (priorShapes.size !== after.shapes.length ||
|
|
97
|
+
after.shapes.some((shape) => {
|
|
98
|
+
const prior = priorShapes.get(shape.shapeId);
|
|
99
|
+
return (!prior ||
|
|
100
|
+
prior.role !== shape.role ||
|
|
101
|
+
prior.logicalId !== shape.logicalId ||
|
|
102
|
+
observationHash(prior.appearance) !== observationHash(shape.appearance));
|
|
103
|
+
})) {
|
|
104
|
+
throw new Error("Presentation adoption supports geometry edits of existing managed objects; style or ownership changes need reconciliation");
|
|
105
|
+
}
|
|
106
|
+
const body = {
|
|
107
|
+
schemaVersion: 1,
|
|
108
|
+
kind: "map-presentation-proposal",
|
|
109
|
+
mapKind: map.kind,
|
|
110
|
+
mapId: map.id,
|
|
111
|
+
baseContractHash: baseHash,
|
|
112
|
+
baselineFingerprint: before.fingerprint,
|
|
113
|
+
observedProjectionHash: after.fingerprint,
|
|
114
|
+
projection: after,
|
|
115
|
+
};
|
|
116
|
+
return validateMapPresentationProposal({
|
|
117
|
+
...body,
|
|
118
|
+
hash: observationHash(body),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** The caller must independently observe the live projection before adopting this saved evidence. */
|
|
122
|
+
export function acceptMapPresentationProposal(current, baseline, raw, resolvedDrawing) {
|
|
123
|
+
const proposal = validateMapPresentationProposal(raw);
|
|
124
|
+
const before = validateNativeMapProjection(baseline);
|
|
125
|
+
if (proposal.mapId !== current.id ||
|
|
126
|
+
proposal.mapKind !== current.kind ||
|
|
127
|
+
hashMapContract(current) !== proposal.baseContractHash ||
|
|
128
|
+
before.contractHash !== proposal.baseContractHash ||
|
|
129
|
+
before.fingerprint !== proposal.baselineFingerprint ||
|
|
130
|
+
!sameMap(before, proposal.projection)) {
|
|
131
|
+
throw new Error("Presentation proposal is stale or belongs to another map");
|
|
132
|
+
}
|
|
133
|
+
const revalidated = createMapPresentationProposal(current, resolvedDrawing ?? drawingFromPlan(buildMapDrawingPlan(current)), proposal.projection, before);
|
|
134
|
+
if (revalidated.hash !== proposal.hash) {
|
|
135
|
+
throw new Error("Presentation proposal contains unsupported changes or metadata");
|
|
136
|
+
}
|
|
137
|
+
return structuredClone(proposal.projection);
|
|
138
|
+
}
|
|
139
|
+
/** Reuse accepted node geometry while preserving the new contract's semantic graph. */
|
|
140
|
+
export function preserveMapPresentation(drawing, savedProjection) {
|
|
141
|
+
validateDrawing(drawing);
|
|
142
|
+
const saved = validateNativeMapProjection(savedProjection);
|
|
143
|
+
if (saved.mapId !== drawing.mapId || !saved.boardBounds || saved.conflicts.length) {
|
|
144
|
+
throw new Error("Cannot preserve presentation from a different or conflicted map");
|
|
145
|
+
}
|
|
146
|
+
const bounds = saved.boardBounds;
|
|
147
|
+
const nodes = drawing.nodes.map((node) => {
|
|
148
|
+
const cards = saved.shapes.filter((shape) => shape.role === "node" && shape.logicalId === node.id);
|
|
149
|
+
const card = cards[0];
|
|
150
|
+
if (!card) {
|
|
151
|
+
return {
|
|
152
|
+
...node,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const appearance = card.appearance;
|
|
156
|
+
if (cards.length !== 1 ||
|
|
157
|
+
typeof appearance?.name !== "string" ||
|
|
158
|
+
!appearance.name.startsWith(`${node.kind}: `)) {
|
|
159
|
+
throw new Error(`Node kind changed or presentation is ambiguous: ${node.id}`);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
...node,
|
|
163
|
+
x: card.x - bounds.x,
|
|
164
|
+
y: card.y - bounds.y,
|
|
165
|
+
width: card.width,
|
|
166
|
+
height: card.height,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
const edges = routeMapEdges(nodes, drawing.edges);
|
|
170
|
+
const problems = mapDrawingGeometryProblems(nodes, edges);
|
|
171
|
+
if (problems.length) {
|
|
172
|
+
throw new Error(`Preserved map layout needs reconciliation: ${problems.join("; ")}`);
|
|
173
|
+
}
|
|
174
|
+
const flowBottom = (flow) => Math.max(0, ...flow.nodes.map((node) => node.y + node.height), ...flow.edges.flatMap((edge) => [
|
|
175
|
+
...(edge.points ?? []).map((point) => point.y),
|
|
176
|
+
...(edge.labelBox
|
|
177
|
+
? [
|
|
178
|
+
edge.labelBox.y + edge.labelBox.height,
|
|
179
|
+
]
|
|
180
|
+
: []),
|
|
181
|
+
]));
|
|
182
|
+
const noteOffset = Math.max(0, flowBottom({
|
|
183
|
+
nodes,
|
|
184
|
+
edges,
|
|
185
|
+
}) - flowBottom(drawing));
|
|
186
|
+
const preserved = {
|
|
187
|
+
...drawing,
|
|
188
|
+
...(drawing.annotations
|
|
189
|
+
? {
|
|
190
|
+
annotations: drawing.annotations.map((annotation) => ({
|
|
191
|
+
...annotation,
|
|
192
|
+
y: annotation.y + noteOffset,
|
|
193
|
+
})),
|
|
194
|
+
}
|
|
195
|
+
: {}),
|
|
196
|
+
origin: {
|
|
197
|
+
x: bounds.x,
|
|
198
|
+
y: bounds.y,
|
|
199
|
+
},
|
|
200
|
+
nodes,
|
|
201
|
+
edges,
|
|
202
|
+
};
|
|
203
|
+
validateDrawing(preserved);
|
|
204
|
+
return preserved;
|
|
205
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PenpotToolClient } from "../client.js";
|
|
2
|
+
import { type SnippetResult } from "../snippet.js";
|
|
3
|
+
import type { MapDrawingPlan } from "../maps/types.js";
|
|
4
|
+
import type { MapDrawing, MapTarget, NativeMapProjection, ProjectionReadOptions } from "./types.js";
|
|
5
|
+
export declare function drawingFromPlan(plan: MapDrawingPlan): MapDrawing;
|
|
6
|
+
export declare function validateDrawing(drawing: MapDrawing): void;
|
|
7
|
+
export declare function projection(target: MapTarget, mapId: string, result: SnippetResult): NativeMapProjection;
|
|
8
|
+
export declare function readProjection(client: PenpotToolClient, target: MapTarget, mapId: string, options?: ProjectionReadOptions): Promise<NativeMapProjection>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { nativeMapIcons } from "./icons.js";
|
|
2
|
+
import { validateMapDrawingContent } from "./content-validation.js";
|
|
3
|
+
import { nativeMapRoles } from "./content.js";
|
|
4
|
+
import { observationHash } from "../journal.js";
|
|
5
|
+
import { selectPage, withPageSession } from "../page-session.js";
|
|
6
|
+
import { runSnippet } from "../snippet.js";
|
|
7
|
+
import { inspectMapSnippet } from "./snippets.js";
|
|
8
|
+
export function drawingFromPlan(plan) {
|
|
9
|
+
if (!plan.complete) {
|
|
10
|
+
throw new Error(`Map requires smaller detail boards: ${plan.reasons.join("; ")}`);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
mapId: `${plan.mapKind}:${plan.mapId}`,
|
|
14
|
+
contractHash: plan.mapHash,
|
|
15
|
+
title: plan.title,
|
|
16
|
+
goal: plan.goal,
|
|
17
|
+
...(plan.summary !== undefined
|
|
18
|
+
? {
|
|
19
|
+
summary: plan.summary,
|
|
20
|
+
}
|
|
21
|
+
: {}),
|
|
22
|
+
...(plan.description !== undefined
|
|
23
|
+
? {
|
|
24
|
+
description: plan.description,
|
|
25
|
+
}
|
|
26
|
+
: {}),
|
|
27
|
+
...(plan.headerSummary !== undefined
|
|
28
|
+
? {
|
|
29
|
+
headerSummary: plan.headerSummary,
|
|
30
|
+
}
|
|
31
|
+
: {}),
|
|
32
|
+
header: plan.header,
|
|
33
|
+
annotations: plan.annotations,
|
|
34
|
+
fontFamily: plan.fontFamily,
|
|
35
|
+
nodes: plan.nodes,
|
|
36
|
+
edges: plan.edges,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function validateDrawing(drawing) {
|
|
40
|
+
validateMapDrawingContent(drawing);
|
|
41
|
+
if (!drawing.mapId ||
|
|
42
|
+
!/^[a-f0-9]{64}$/.test(drawing.contractHash) ||
|
|
43
|
+
!drawing.title ||
|
|
44
|
+
drawing.title.length > 1000) {
|
|
45
|
+
throw new Error("Map identity, title and SHA-256 contract hash are required");
|
|
46
|
+
}
|
|
47
|
+
if (drawing.nodes.length > 1000 || drawing.edges.length > 2000) {
|
|
48
|
+
throw new Error("Map exceeds graph limits");
|
|
49
|
+
}
|
|
50
|
+
if (drawing.origin &&
|
|
51
|
+
![
|
|
52
|
+
drawing.origin.x,
|
|
53
|
+
drawing.origin.y,
|
|
54
|
+
].every(Number.isFinite)) {
|
|
55
|
+
throw new Error("Map origin must be finite");
|
|
56
|
+
}
|
|
57
|
+
const identifiers = new Set(drawing.nodes.map((node) => node.id));
|
|
58
|
+
if (identifiers.size !== drawing.nodes.length ||
|
|
59
|
+
drawing.nodes.some((node) => !node.id ||
|
|
60
|
+
(node.icon !== undefined && !Object.hasOwn(nativeMapIcons, node.icon)) ||
|
|
61
|
+
(node.summary !== undefined &&
|
|
62
|
+
(typeof node.summary !== "string" || node.summary.length > 240)) ||
|
|
63
|
+
(node.description !== undefined &&
|
|
64
|
+
(typeof node.description !== "string" || node.description.length > 4000)) ||
|
|
65
|
+
(node.detail !== undefined &&
|
|
66
|
+
(node.kind !== "detail-link" ||
|
|
67
|
+
!node.detail.fileId ||
|
|
68
|
+
!node.detail.pageId ||
|
|
69
|
+
!node.detail.frameId ||
|
|
70
|
+
!node.detail.path ||
|
|
71
|
+
!/^[a-f0-9]{64}$/.test(node.detail.sha256))) ||
|
|
72
|
+
(node.origin !== undefined &&
|
|
73
|
+
![
|
|
74
|
+
"observed",
|
|
75
|
+
"proposed",
|
|
76
|
+
"owner-decision",
|
|
77
|
+
"unknown",
|
|
78
|
+
].includes(node.origin)) ||
|
|
79
|
+
!node.label ||
|
|
80
|
+
node.label.length > 4000 ||
|
|
81
|
+
![
|
|
82
|
+
node.x,
|
|
83
|
+
node.y,
|
|
84
|
+
node.width,
|
|
85
|
+
node.height,
|
|
86
|
+
].every(Number.isFinite) ||
|
|
87
|
+
node.width < 64 ||
|
|
88
|
+
node.height < 32)) {
|
|
89
|
+
throw new Error("Map nodes need unique IDs, bounded labels and finite positive geometry");
|
|
90
|
+
}
|
|
91
|
+
if (new Set(drawing.edges.map((edge) => edge.id)).size !== drawing.edges.length ||
|
|
92
|
+
drawing.edges.some((edge) => !edge.id ||
|
|
93
|
+
!identifiers.has(edge.from) ||
|
|
94
|
+
!identifiers.has(edge.to) ||
|
|
95
|
+
typeof edge.label !== "string" ||
|
|
96
|
+
edge.label.length > 4000 ||
|
|
97
|
+
(edge.labelBox !== undefined &&
|
|
98
|
+
(![
|
|
99
|
+
edge.labelBox.x,
|
|
100
|
+
edge.labelBox.y,
|
|
101
|
+
edge.labelBox.width,
|
|
102
|
+
edge.labelBox.height,
|
|
103
|
+
].every(Number.isFinite) ||
|
|
104
|
+
edge.labelBox.width <= 0 ||
|
|
105
|
+
edge.labelBox.height <= 0)) ||
|
|
106
|
+
(edge.points !== undefined &&
|
|
107
|
+
(!Array.isArray(edge.points) ||
|
|
108
|
+
edge.points.length < 2 ||
|
|
109
|
+
edge.points.length > 32 ||
|
|
110
|
+
edge.points.some((point) => ![
|
|
111
|
+
point.x,
|
|
112
|
+
point.y,
|
|
113
|
+
].every(Number.isFinite)))))) {
|
|
114
|
+
throw new Error("Map edges need unique IDs and existing endpoints");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export function projection(target, mapId, result) {
|
|
118
|
+
if (!Array.isArray(result.shapes) ||
|
|
119
|
+
!Array.isArray(result.conflicts) ||
|
|
120
|
+
!(result.boardId === null || typeof result.boardId === "string") ||
|
|
121
|
+
!(result.boardBounds === null ||
|
|
122
|
+
(typeof result.boardBounds === "object" &&
|
|
123
|
+
result.boardBounds &&
|
|
124
|
+
[
|
|
125
|
+
"x",
|
|
126
|
+
"y",
|
|
127
|
+
"width",
|
|
128
|
+
"height",
|
|
129
|
+
].every((key) => Number.isFinite(result.boardBounds[key])))) ||
|
|
130
|
+
!(result.contractHash === null || typeof result.contractHash === "string")) {
|
|
131
|
+
throw new Error("Native map inspection returned incomplete evidence");
|
|
132
|
+
}
|
|
133
|
+
if (result.shapes.some((shape) => !shape ||
|
|
134
|
+
typeof shape !== "object" ||
|
|
135
|
+
typeof shape.shapeId !== "string" ||
|
|
136
|
+
typeof shape.logicalId !== "string" ||
|
|
137
|
+
!nativeMapRoles.includes(shape.role) ||
|
|
138
|
+
![
|
|
139
|
+
shape.x,
|
|
140
|
+
shape.y,
|
|
141
|
+
shape.width,
|
|
142
|
+
shape.height,
|
|
143
|
+
].every(Number.isFinite) ||
|
|
144
|
+
!(shape.label === null || typeof shape.label === "string") ||
|
|
145
|
+
!(shape.content === null || typeof shape.content === "string")) ||
|
|
146
|
+
result.conflicts.some((conflict) => typeof conflict !== "string")) {
|
|
147
|
+
throw new Error("Native map inspection returned malformed shape evidence");
|
|
148
|
+
}
|
|
149
|
+
const observed = {
|
|
150
|
+
boardId: result.boardId,
|
|
151
|
+
boardBounds: result.boardBounds,
|
|
152
|
+
contractHash: result.contractHash,
|
|
153
|
+
shapes: result.shapes,
|
|
154
|
+
conflicts: result.conflicts,
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
schemaVersion: 1,
|
|
158
|
+
kind: "native-map-projection",
|
|
159
|
+
...target,
|
|
160
|
+
boardId: result.boardId,
|
|
161
|
+
boardBounds: result.boardBounds,
|
|
162
|
+
mapId,
|
|
163
|
+
contractHash: result.contractHash,
|
|
164
|
+
fingerprint: observationHash(observed),
|
|
165
|
+
shapes: result.shapes,
|
|
166
|
+
conflicts: result.conflicts,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export async function readProjection(client, target, mapId, options = {}) {
|
|
170
|
+
return withPageSession(client, async () => {
|
|
171
|
+
await selectPage(client, {
|
|
172
|
+
id: target.pageId,
|
|
173
|
+
}, options);
|
|
174
|
+
return projection(target, mapId, await runSnippet(client, inspectMapSnippet(target, mapId), options));
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PenpotToolClient } from "../client.js";
|
|
2
|
+
import type { MapDrawing, MapTarget, NativeMapProjection, PublishMapOptions, PublishMapResult } from "./types.js";
|
|
3
|
+
export declare function verificationProblems(drawing: MapDrawing, observed: NativeMapProjection): string[];
|
|
4
|
+
export declare function publishMap(client: PenpotToolClient, target: MapTarget, drawing: MapDrawing, options?: PublishMapOptions): Promise<PublishMapResult>;
|