@penpair/core 0.3.7 → 0.4.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.
Files changed (142) hide show
  1. package/dist/adapters.d.ts +1 -1
  2. package/dist/adapters.js +4 -0
  3. package/dist/approvals.d.ts +4 -1
  4. package/dist/approvals.js +7 -4
  5. package/dist/baseline-coverage.js +3 -3
  6. package/dist/baselines-config.d.ts +3 -0
  7. package/dist/baselines-config.js +3 -0
  8. package/dist/baselines.d.ts +7 -0
  9. package/dist/baselines.js +90 -21
  10. package/dist/bounded-file.d.ts +2 -0
  11. package/dist/bounded-file.js +38 -0
  12. package/dist/browser-wake.d.ts +22 -0
  13. package/dist/browser-wake.js +241 -0
  14. package/dist/cli-main.js +193 -18
  15. package/dist/design-commands/context.d.ts +9 -0
  16. package/dist/design-commands/context.js +1 -0
  17. package/dist/design-commands/index.d.ts +3 -0
  18. package/dist/design-commands/index.js +7 -0
  19. package/dist/design-commands/map-command.d.ts +4 -0
  20. package/dist/design-commands/map-command.js +685 -0
  21. package/dist/design-commands/release-command.d.ts +3 -0
  22. package/dist/design-commands/release-command.js +239 -0
  23. package/dist/design-releases/artifact-paths.d.ts +6 -0
  24. package/dist/design-releases/artifact-paths.js +82 -0
  25. package/dist/design-releases/assemble.d.ts +3 -0
  26. package/dist/design-releases/assemble.js +176 -0
  27. package/dist/design-releases/check.d.ts +16 -0
  28. package/dist/design-releases/check.js +323 -0
  29. package/dist/design-releases/context.d.ts +2 -0
  30. package/dist/design-releases/context.js +119 -0
  31. package/dist/design-releases/contract-graph.d.ts +13 -0
  32. package/dist/design-releases/contract-graph.js +75 -0
  33. package/dist/design-releases/fidelity.d.ts +23 -0
  34. package/dist/design-releases/fidelity.js +328 -0
  35. package/dist/design-releases/finalize.d.ts +4 -0
  36. package/dist/design-releases/finalize.js +201 -0
  37. package/dist/design-releases/index.d.ts +8 -0
  38. package/dist/design-releases/index.js +7 -0
  39. package/dist/design-releases/live.d.ts +22 -0
  40. package/dist/design-releases/live.js +97 -0
  41. package/dist/design-releases/snippets.d.ts +5 -0
  42. package/dist/design-releases/snippets.js +89 -0
  43. package/dist/design-releases/store.d.ts +10 -0
  44. package/dist/design-releases/store.js +205 -0
  45. package/dist/design-releases/types.d.ts +161 -0
  46. package/dist/design-releases/types.js +1 -0
  47. package/dist/design-releases/validation.d.ts +6 -0
  48. package/dist/design-releases/validation.js +384 -0
  49. package/dist/design-releases/verify.d.ts +3 -0
  50. package/dist/design-releases/verify.js +148 -0
  51. package/dist/errors.js +1 -1
  52. package/dist/image-codec.d.ts +3 -0
  53. package/dist/image-codec.js +78 -0
  54. package/dist/image-evidence.d.ts +15 -0
  55. package/dist/image-evidence.js +121 -0
  56. package/dist/index.d.ts +5 -0
  57. package/dist/index.js +5 -0
  58. package/dist/inspection.js +13 -5
  59. package/dist/instance/config-schema.d.ts +4 -0
  60. package/dist/instance/config-schema.js +7 -0
  61. package/dist/instance/connection.d.ts +2 -0
  62. package/dist/instance/connection.js +15 -3
  63. package/dist/kit-source.js +1 -1
  64. package/dist/layout-policy.js +21 -11
  65. package/dist/layout-settling.d.ts +1 -0
  66. package/dist/layout-settling.js +51 -17
  67. package/dist/layout.d.ts +7 -1
  68. package/dist/layout.js +19 -3
  69. package/dist/map-penpot/content-validation.d.ts +3 -0
  70. package/dist/map-penpot/content-validation.js +57 -0
  71. package/dist/map-penpot/content.d.ts +22 -0
  72. package/dist/map-penpot/content.js +173 -0
  73. package/dist/map-penpot/details.d.ts +4 -0
  74. package/dist/map-penpot/details.js +84 -0
  75. package/dist/map-penpot/evidence-error.d.ts +5 -0
  76. package/dist/map-penpot/evidence-error.js +9 -0
  77. package/dist/map-penpot/icons.d.ts +5 -0
  78. package/dist/map-penpot/icons.js +79 -0
  79. package/dist/map-penpot/import-content.d.ts +2 -0
  80. package/dist/map-penpot/import-content.js +150 -0
  81. package/dist/map-penpot/import.d.ts +5 -0
  82. package/dist/map-penpot/import.js +255 -0
  83. package/dist/map-penpot/index.d.ts +11 -0
  84. package/dist/map-penpot/index.js +10 -0
  85. package/dist/map-penpot/path-geometry.d.ts +5 -0
  86. package/dist/map-penpot/path-geometry.js +51 -0
  87. package/dist/map-penpot/presentation.d.ts +20 -0
  88. package/dist/map-penpot/presentation.js +205 -0
  89. package/dist/map-penpot/projection.d.ts +8 -0
  90. package/dist/map-penpot/projection.js +176 -0
  91. package/dist/map-penpot/publish.d.ts +4 -0
  92. package/dist/map-penpot/publish.js +301 -0
  93. package/dist/map-penpot/release-maps.d.ts +21 -0
  94. package/dist/map-penpot/release-maps.js +104 -0
  95. package/dist/map-penpot/snippets.d.ts +4 -0
  96. package/dist/map-penpot/snippets.js +164 -0
  97. package/dist/map-penpot/status-labels.d.ts +7 -0
  98. package/dist/map-penpot/status-labels.js +7 -0
  99. package/dist/map-penpot/types.d.ts +161 -0
  100. package/dist/map-penpot/types.js +1 -0
  101. package/dist/maps/annotation-layout.d.ts +24 -0
  102. package/dist/maps/annotation-layout.js +85 -0
  103. package/dist/maps/context.d.ts +13 -0
  104. package/dist/maps/context.js +207 -0
  105. package/dist/maps/index.d.ts +9 -0
  106. package/dist/maps/index.js +9 -0
  107. package/dist/maps/layout.d.ts +35 -0
  108. package/dist/maps/layout.js +364 -0
  109. package/dist/maps/proposal.d.ts +5 -0
  110. package/dist/maps/proposal.js +67 -0
  111. package/dist/maps/render.d.ts +4 -0
  112. package/dist/maps/render.js +331 -0
  113. package/dist/maps/review.d.ts +16 -0
  114. package/dist/maps/review.js +90 -0
  115. package/dist/maps/store.d.ts +21 -0
  116. package/dist/maps/store.js +222 -0
  117. package/dist/maps/types.d.ts +206 -0
  118. package/dist/maps/types.js +1 -0
  119. package/dist/maps/validation.d.ts +10 -0
  120. package/dist/maps/validation.js +375 -0
  121. package/dist/plugin-health.d.ts +13 -0
  122. package/dist/plugin-health.js +39 -0
  123. package/dist/skill-routing.js +4 -0
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/package.json +2 -1
  127. package/skills/penpair-design-release/SKILL.md +55 -0
  128. package/skills/penpair-design-release/agents/openai.yaml +6 -0
  129. package/skills/penpair-design-release/references/workflow.md +55 -0
  130. package/skills/penpair-diagrams/SKILL.md +52 -0
  131. package/skills/penpair-diagrams/agents/openai.yaml +6 -0
  132. package/skills/penpair-diagrams/references/workflow.md +84 -0
  133. package/skills/penpair-handoff/SKILL.md +7 -0
  134. package/skills/penpair-handoff/references/handoff-flow.md +7 -1
  135. package/skills/penpair-integration-map/SKILL.md +44 -0
  136. package/skills/penpair-integration-map/agents/openai.yaml +6 -0
  137. package/skills/penpair-integration-map/references/workflow.md +33 -0
  138. package/skills/penpair-journey-map/SKILL.md +42 -0
  139. package/skills/penpair-journey-map/agents/openai.yaml +6 -0
  140. package/skills/penpair-journey-map/references/workflow.md +35 -0
  141. package/skills/penpair-verify/SKILL.md +7 -0
  142. package/skills/penpair-verify/references/browser-evidence.md +10 -0
@@ -0,0 +1,89 @@
1
+ import { snippet } from "../snippet.js";
2
+ const inspectBody = `
3
+ if (penpot.currentFile.id !== CFG.fileId) throw new Error("Pinned release file changed");
4
+ const read = page => {
5
+ if (!page.root || typeof page.root.getSharedPluginData !== "function") throw new Error("Release ownership metadata API unavailable");
6
+ const raw = page.root.getSharedPluginData("penpair","design-release");
7
+ if (!raw) return null;
8
+ try { return JSON.parse(raw); } catch { throw new Error("Malformed release ownership metadata"); }
9
+ };
10
+ const owned = penpot.currentFile.pages.filter(page => { const meta = read(page); return meta && meta.ownerKey === CFG.ownerKey; });
11
+ if (owned.length > 1) throw new Error("Duplicate release page ownership");
12
+ const named = penpot.currentFile.pages.filter(page => page.name === CFG.name);
13
+ const page = owned[0] || null;
14
+ if (named.some(candidate => !page || candidate.id !== page.id)) throw new Error("Release page name belongs to an unrelated page");
15
+ if (CFG.pageId && (!page || page.id !== CFG.pageId)) throw new Error("Pinned release page identity changed");
16
+ const marker = page ? read(page) : null;
17
+ if (marker && (marker.schemaVersion !== 1 || marker.owner !== "penpair" || marker.releaseId !== CFG.releaseId || marker.productVersion !== CFG.productVersion)) throw new Error("Release ownership marker changed");
18
+ let overview = null;
19
+ if (page) {
20
+ const children = Array.isArray(page.root.children) ? page.root.children : [];
21
+ if (children.length > 20000) throw new Error("Release page traversal limit exceeded");
22
+ const boards = children.filter(shape => typeof shape.getSharedPluginData === "function" && shape.getSharedPluginData("penpair","release-overview") === CFG.ownerKey);
23
+ if (boards.length > 1) throw new Error("Duplicate release overview ownership");
24
+ overview = boards[0] || null;
25
+ }
26
+ const observation = {pageId:page ? page.id : null,overviewFrameId:overview ? overview.id : null,marker};
27
+ `;
28
+ function configuration(release) {
29
+ return {
30
+ fileId: release.fileId,
31
+ releaseId: release.id,
32
+ productVersion: release.productVersion,
33
+ ownerKey: release.target.ownerKey,
34
+ name: release.target.name,
35
+ pageId: release.target.pageId,
36
+ };
37
+ }
38
+ export function inspectReleaseSnippet(release) {
39
+ return snippet("design-release-inspect", `${inspectBody}\nreturn {ok:true,...observation};`, configuration(release), "read");
40
+ }
41
+ export function assembleReleaseSnippet(release) {
42
+ return snippet("design-release-assemble", `${inspectBody}
43
+ if (marker && marker.status !== "draft") throw new Error("Finalized release page is immutable to assembly");
44
+ if (typeof penpot.createPage !== "function" || typeof penpot.openPage !== "function" || typeof penpot.createBoard !== "function" || typeof penpot.createText !== "function") throw new Error("Native release assembly API unavailable");
45
+ const matchingFonts=Array.isArray(penpot.fonts.all)?penpot.fonts.all.filter(font=>font.name==="Inter"):[];
46
+ if(matchingFonts.length!==1)throw new Error("Inter font unavailable or ambiguous");
47
+ const font=matchingFonts[0];
48
+ const variants = font && Array.isArray(font.variants) ? font.variants.filter(variant => String(variant.fontWeight) === "400" && (variant.fontStyle || "normal") === "normal") : [];
49
+ if (!font || typeof font.applyToText !== "function" || variants.length !== 1) throw new Error("Inter regular font unavailable or ambiguous");
50
+ let target = page;
51
+ if (!target) { target = penpot.createPage(); target.name = CFG.name; }
52
+ await penpot.openPage(target);
53
+ if (penpot.currentPage.id !== target.id || penpot.currentFile.id !== CFG.fileId) throw new Error("New release page did not become active");
54
+ if (typeof target.root.setSharedPluginData !== "function") throw new Error("Release ownership write API unavailable");
55
+ target.root.setSharedPluginData("penpair","design-release",JSON.stringify({schemaVersion:1,owner:"penpair",releaseId:CFG.releaseId,productVersion:CFG.productVersion,ownerKey:CFG.ownerKey,status:"draft"}));
56
+ if (!overview) {
57
+ if (target.root.children.length) throw new Error("Unexplained shapes on interrupted release page; inspect before resuming");
58
+ overview = penpot.createBoard(); overview.name = "Release overview"; overview.resize(1200,800); overview.x=0; overview.y=0; overview.fills=[{fillColor:"#FFFFFF",fillOpacity:1}];
59
+ overview.setSharedPluginData("penpair","release-overview",CFG.ownerKey);
60
+ const text=penpot.createText(CFG.summary); font.applyToText(text,variants[0]); text.fontSize=20; text.resize(1100,700); text.growType="auto-height"; text.x=40;text.y=40;text.fills=[{fillColor:"#172033",fillOpacity:1}]; overview.appendChild(text);
61
+ }
62
+ return {ok:true,pageId:target.id,overviewFrameId:overview.id};`, {
63
+ ...configuration(release),
64
+ summary: [
65
+ release.title,
66
+ `Product ${release.productVersion}`,
67
+ "Journey and integration contracts",
68
+ ...release.contracts.map((contract) => `${contract.kind}: ${contract.id}`),
69
+ "Native screen transfer uses explicit source-to-target frame ID receipts; no source image approval is reused. Review the release record for current design status.",
70
+ ...release.sources.map((source) => source.label.slice(0, 200)),
71
+ ].join("\n"),
72
+ }, "write");
73
+ }
74
+ export function finalizeReleaseSnippet(release, bundleHash, reviewHash) {
75
+ return snippet("design-release-finalize", `${inspectBody}
76
+ if (!page || !overview) throw new Error("Owned release page or overview is missing");
77
+ if (marker.status === "released") {
78
+ if (marker.bundleHash !== CFG.bundleHash || marker.reviewHash !== CFG.reviewHash) throw new Error("Release version already finalized with different evidence");
79
+ return {ok:true,marker};
80
+ }
81
+ if (marker.status !== "draft") throw new Error("Release marker is unsupported");
82
+ await penpot.openPage(page);
83
+ page.root.setSharedPluginData("penpair","design-release",JSON.stringify({...marker,status:"released",bundleHash:CFG.bundleHash,reviewHash:CFG.reviewHash}));
84
+ return {ok:true};`, {
85
+ ...configuration(release),
86
+ bundleHash,
87
+ reviewHash,
88
+ }, "write");
89
+ }
@@ -0,0 +1,10 @@
1
+ import { type InspectionCapture } from "../inspection.js";
2
+ import type { MapContract, MapContext } from "../maps/types.js";
3
+ import type { DesignRelease, ReleaseArtifactRef, ReleaseContract, ReleaseOptions } from "./types.js";
4
+ export declare function readDesignRelease(root: string, file: string): Promise<DesignRelease>;
5
+ export declare function writeDesignRelease(root: string, file: string, raw: DesignRelease, options?: ReleaseOptions): Promise<void>;
6
+ export declare function readReleaseInspection(root: string, reference: ReleaseArtifactRef): Promise<InspectionCapture>;
7
+ export declare function readReleaseContract(root: string, reference: ReleaseArtifactRef): Promise<MapContract>;
8
+ export declare function releaseContractContexts(root: string, contracts: ReleaseContract[], reader?: (reference: ReleaseArtifactRef) => Promise<Buffer>, fileId?: string): Promise<MapContext[]>;
9
+ export declare function assertReleaseContracts(root: string, contracts: ReleaseContract[], reviewed?: boolean, reader?: (reference: ReleaseArtifactRef) => Promise<Buffer>, fileId?: string): Promise<void>;
10
+ export declare function planRelease(root: string, raw: unknown, options?: ReleaseOptions): Promise<DesignRelease>;
@@ -0,0 +1,205 @@
1
+ import { open, unlink } from "node:fs/promises";
2
+ import { readProjectBytes } from "../bounded-file.js";
3
+ import { atomicWriteFile } from "../atomic-output.js";
4
+ import { validateInspectionDocument } from "../inspection.js";
5
+ import { observationHash } from "../journal.js";
6
+ import { validateMapContract, hashMapContract } from "../maps/validation.js";
7
+ import { buildMapContext } from "../maps/context.js";
8
+ import { artifactPath, maximumArtifactBytes, readArtifact } from "./artifact-paths.js";
9
+ import { inspectionContentHash, decodedPixels } from "./fidelity.js";
10
+ import { releaseContractGraph, nativeContractId } from "./contract-graph.js";
11
+ import { validateReleasePlan, validateDesignRelease } from "./validation.js";
12
+ export async function readDesignRelease(root, file) {
13
+ await artifactPath(root, file);
14
+ return validateDesignRelease(JSON.parse((await readProjectBytes(root, file, maximumArtifactBytes)).toString("utf8")));
15
+ }
16
+ export async function writeDesignRelease(root, file, raw, options = {}) {
17
+ const release = validateDesignRelease(raw);
18
+ const serialized = `${JSON.stringify(release, null, 2)}\n`;
19
+ if (Buffer.byteLength(serialized) > maximumArtifactBytes) {
20
+ throw new Error("Design release exceeds the 16-MiB artifact budget");
21
+ }
22
+ if (options.redact && options.redact(serialized) !== serialized) {
23
+ throw new Error("Release record contains protected data and cannot be persisted");
24
+ }
25
+ const destination = await artifactPath(root, file, true);
26
+ const lockPath = `${destination}.write-lock`;
27
+ const lock = await open(lockPath, "wx", 0o600).catch((error) => {
28
+ if (error.code === "EEXIST") {
29
+ throw new Error("Release pointer has another writer; inspect or retry after it finishes");
30
+ }
31
+ throw error;
32
+ });
33
+ try {
34
+ try {
35
+ const previous = await readDesignRelease(root, file);
36
+ if (previous.status === "final" ||
37
+ previous.id !== release.id ||
38
+ previous.productVersion !== release.productVersion) {
39
+ throw new Error("Cannot overwrite a finalized design release or change a draft identity");
40
+ }
41
+ }
42
+ catch (error) {
43
+ if (error.code !== "ENOENT") {
44
+ throw error;
45
+ }
46
+ }
47
+ await atomicWriteFile(destination, serialized, {
48
+ replace: true,
49
+ });
50
+ }
51
+ finally {
52
+ await lock.close();
53
+ await unlink(lockPath);
54
+ }
55
+ }
56
+ export async function readReleaseInspection(root, reference) {
57
+ const bytes = await readArtifact(root, reference);
58
+ const value = JSON.parse(bytes.toString("utf8"));
59
+ validateInspectionDocument(value);
60
+ if (!value.complete || value.kind !== "shape-inspection") {
61
+ throw new Error("Release source inspection must be complete shape evidence");
62
+ }
63
+ return value;
64
+ }
65
+ export async function readReleaseContract(root, reference) {
66
+ return validateMapContract(JSON.parse((await readArtifact(root, reference)).toString("utf8")));
67
+ }
68
+ export async function releaseContractContexts(root, contracts, reader = (reference) => readArtifact(root, reference, 64 * 1024 * 1024), fileId) {
69
+ const maps = await Promise.all(contracts.map(async (reference) => ({
70
+ reference,
71
+ map: validateMapContract(JSON.parse((await reader(reference)).toString("utf8"))),
72
+ })));
73
+ if (!maps.some((entry) => entry.map.kind === "journey-map") ||
74
+ !maps.some((entry) => entry.map.kind === "integration-map")) {
75
+ throw new Error("A design release requires journey and integration contracts");
76
+ }
77
+ const graph = releaseContractGraph(maps, fileId);
78
+ const contexts = new Map();
79
+ for (const { reference, map } of graph.ordered) {
80
+ if (reference.kind !== map.kind || reference.id !== map.id) {
81
+ throw new Error("Release contract identity changed");
82
+ }
83
+ const journey = maps.find((entry) => entry.reference.path === map.journey?.path && entry.map.kind === "journey-map");
84
+ if (map.journey && (!journey || hashMapContract(journey.map) !== map.journey.sha256)) {
85
+ throw new Error("Integration contract does not reference the exact release journey revision");
86
+ }
87
+ const sourceHashes = {};
88
+ for (const source of map.sources) {
89
+ if (source.path) {
90
+ await reader({
91
+ path: source.path,
92
+ sha256: source.sha256,
93
+ });
94
+ sourceHashes[source.id] = source.sha256;
95
+ }
96
+ }
97
+ for (const artifact of reference.review?.artifacts ?? []) {
98
+ await reader(artifact);
99
+ }
100
+ const detailMaps = graph.details.get(nativeContractId(map)) ?? {};
101
+ const detailReadiness = Object.fromEntries(Object.entries(detailMaps).map(([id, child]) => [
102
+ id,
103
+ contexts.get(nativeContractId(child))?.ready === true,
104
+ ]));
105
+ const context = buildMapContext(map, {
106
+ reviews: reference.review
107
+ ? [
108
+ reference.review,
109
+ ]
110
+ : [],
111
+ sourceHashes,
112
+ detailMaps,
113
+ detailReadiness,
114
+ ...(journey
115
+ ? {
116
+ journeyHash: hashMapContract(journey.map),
117
+ journeyRequirementIds: journey.map.requirements.map((item) => item.id),
118
+ }
119
+ : {}),
120
+ maxItems: 1000,
121
+ maxBytes: 128 * 1024,
122
+ });
123
+ contexts.set(nativeContractId(map), context);
124
+ }
125
+ return graph.ordered.map((entry) => contexts.get(nativeContractId(entry.map)));
126
+ }
127
+ export async function assertReleaseContracts(root, contracts, reviewed = false, reader, fileId) {
128
+ const contexts = await releaseContractContexts(root, contracts, reader, fileId);
129
+ if (reviewed) {
130
+ if (contexts.some((context) => !context.reviewed)) {
131
+ throw new Error("Release contracts need exact owner reviews of their current revisions, including every detail contract");
132
+ }
133
+ const blocked = contexts.find((context) => !context.ready);
134
+ if (blocked) {
135
+ throw new Error(`Release contract is not ready: ${blocked.reasons.join("; ")}`);
136
+ }
137
+ }
138
+ }
139
+ export async function planRelease(root, raw, options = {}) {
140
+ const input = validateReleasePlan(raw);
141
+ const safe = (value) => {
142
+ const serialized = JSON.stringify(value);
143
+ if (options.redact && options.redact(serialized) !== serialized) {
144
+ throw new Error("Protected data in release evidence");
145
+ }
146
+ };
147
+ safe(input);
148
+ const sources = [];
149
+ for (const source of input.screens) {
150
+ const inspection = await readReleaseInspection(root, source.inspection);
151
+ safe(inspection);
152
+ if (inspection.fileId !== input.fileId ||
153
+ inspection.pageId !== source.pageId ||
154
+ inspection.nodes?.[0]?.id !== source.frameId) {
155
+ throw new Error("Source screen inspection does not match pinned file/page/frame IDs");
156
+ }
157
+ if (source.render) {
158
+ await decodedPixels(await readArtifact(root, source.render, 64 * 1024 * 1024));
159
+ }
160
+ if (source.dependencies) {
161
+ await readArtifact(root, source.dependencies);
162
+ }
163
+ sources.push({
164
+ ...source,
165
+ contentHash: inspectionContentHash(inspection),
166
+ });
167
+ }
168
+ const contracts = await Promise.all(input.contracts.map(async (reference) => {
169
+ const map = await readReleaseContract(root, reference);
170
+ safe(map);
171
+ return {
172
+ ...reference,
173
+ kind: map.kind,
174
+ id: map.id,
175
+ };
176
+ }));
177
+ await assertReleaseContracts(root, contracts, false, undefined, input.fileId);
178
+ return validateDesignRelease({
179
+ schemaVersion: 1,
180
+ kind: "design-release",
181
+ id: input.id,
182
+ title: input.title,
183
+ productVersion: input.productVersion,
184
+ fileId: input.fileId,
185
+ status: "planned",
186
+ sources,
187
+ contracts,
188
+ target: {
189
+ name: `release/${input.productVersion}`,
190
+ ownerKey: observationHash({
191
+ id: input.id,
192
+ fileId: input.fileId,
193
+ productVersion: input.productVersion,
194
+ }),
195
+ },
196
+ maps: [],
197
+ receipts: [],
198
+ reviews: [],
199
+ implementation: {
200
+ status: "not-assessed",
201
+ evidence: [],
202
+ },
203
+ createdAt: new Date().toISOString(),
204
+ });
205
+ }
@@ -0,0 +1,161 @@
1
+ import type { ArtifactReview, MapKind, MapContext } from "../maps/types.js";
2
+ import type { ExportOptions } from "../export.js";
3
+ export interface ReleaseArtifactRef {
4
+ path: string;
5
+ sha256: string;
6
+ }
7
+ export interface ReleaseScreenInput {
8
+ id: string;
9
+ label: string;
10
+ pageId: string;
11
+ frameId: string;
12
+ inspection: ReleaseArtifactRef;
13
+ render?: ReleaseArtifactRef;
14
+ dependencies?: ReleaseArtifactRef;
15
+ }
16
+ export interface ReleaseSource extends ReleaseScreenInput {
17
+ contentHash: string;
18
+ }
19
+ export interface ReleaseContractInput extends ReleaseArtifactRef {
20
+ review?: ArtifactReview;
21
+ }
22
+ export interface ReleaseContract extends ReleaseContractInput {
23
+ kind: MapKind;
24
+ id: string;
25
+ }
26
+ export interface DesignReleasePlanInput {
27
+ schemaVersion: 1;
28
+ kind: "design-release-plan";
29
+ id: string;
30
+ title: string;
31
+ productVersion: string;
32
+ fileId: string;
33
+ screens: ReleaseScreenInput[];
34
+ contracts: ReleaseContractInput[];
35
+ }
36
+ export interface ReceiptInput {
37
+ sourceId: string;
38
+ targetFrameId: string;
39
+ }
40
+ export interface ReleaseReceipt extends ReceiptInput {
41
+ sourceFrameId: string;
42
+ targetPageId: string;
43
+ nodeMap: Record<string, string>;
44
+ sourceContentHash: string;
45
+ targetContentHash: string;
46
+ sourceInspection: ReleaseArtifactRef;
47
+ targetInspection: ReleaseArtifactRef;
48
+ originalRender: ReleaseArtifactRef;
49
+ targetRender: ReleaseArtifactRef;
50
+ decodedPixelsHash: string;
51
+ width: number;
52
+ height: number;
53
+ }
54
+ export interface ReleaseFidelity {
55
+ schemaVersion: 1;
56
+ kind: "design-release-fidelity";
57
+ verifiedAt: string;
58
+ receiptsHash: string;
59
+ exactDecodedPixels: true;
60
+ nativeStructure: true;
61
+ }
62
+ export interface ReleaseMapEvidence {
63
+ kind: MapKind;
64
+ id: string;
65
+ boardId: string;
66
+ contractHash: string;
67
+ projection: ReleaseArtifactRef;
68
+ render: ReleaseArtifactRef;
69
+ }
70
+ export interface ReleaseOverviewEvidence {
71
+ contentHash: string;
72
+ render: ReleaseArtifactRef;
73
+ decodedPixelsHash: string;
74
+ }
75
+ export interface DesignRelease {
76
+ schemaVersion: 1;
77
+ kind: "design-release";
78
+ id: string;
79
+ title: string;
80
+ productVersion: string;
81
+ fileId: string;
82
+ status: "planned" | "awaiting-copy" | "verified" | "final";
83
+ sources: ReleaseSource[];
84
+ contracts: ReleaseContract[];
85
+ target: {
86
+ name: string;
87
+ ownerKey: string;
88
+ pageId?: string;
89
+ overviewFrameId?: string;
90
+ };
91
+ receipts: ReleaseReceipt[];
92
+ maps: ReleaseMapEvidence[];
93
+ overview?: ReleaseOverviewEvidence;
94
+ fidelity?: ReleaseFidelity;
95
+ reviews: ArtifactReview[];
96
+ implementation: {
97
+ status: "not-assessed" | "in-progress" | "verified";
98
+ evidence: ReleaseArtifactRef[];
99
+ };
100
+ createdAt: string;
101
+ finalized?: {
102
+ bundle: ReleaseArtifactRef;
103
+ markerHash: string;
104
+ reviewHash: string;
105
+ };
106
+ }
107
+ export interface ReleaseOptions {
108
+ apply?: boolean;
109
+ runId?: string;
110
+ resume?: boolean;
111
+ exportOptions?: ExportOptions;
112
+ onStep?: (message: string) => void;
113
+ pointerPath?: string;
114
+ redact?: (text: string) => string;
115
+ }
116
+ export interface ReleaseCheck {
117
+ valid: boolean;
118
+ finalized: boolean;
119
+ live: boolean;
120
+ designHash: string;
121
+ reasons: string[];
122
+ implementation: DesignRelease["implementation"]["status"];
123
+ }
124
+ export interface ReleaseContext {
125
+ schemaVersion: 1;
126
+ kind: "release-context";
127
+ id: string;
128
+ productVersion: string;
129
+ designHash: string;
130
+ finalized: boolean;
131
+ ready: boolean;
132
+ reasons: string[];
133
+ implementation: DesignRelease["implementation"];
134
+ screens: Array<{
135
+ id: string;
136
+ label: string;
137
+ sourceFrameId: string;
138
+ targetFrameId: string | null;
139
+ }>;
140
+ contracts: ReleaseContract[];
141
+ mapContexts: MapContext[];
142
+ mapTargets: Array<{
143
+ kind: MapKind;
144
+ id: string;
145
+ fileId: string;
146
+ pageId: string;
147
+ frameId: string;
148
+ contractHash: string;
149
+ }>;
150
+ nextActions: string[];
151
+ proposedBaselines: {
152
+ page: string;
153
+ root: string;
154
+ format: "webp";
155
+ frames: Array<{
156
+ id: string;
157
+ frameId: string;
158
+ }>;
159
+ };
160
+ truncated: boolean;
161
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { DesignRelease, DesignReleasePlanInput, ReleaseArtifactRef } from "./types.js";
2
+ export declare function releaseReference(raw: unknown): ReleaseArtifactRef;
3
+ export declare function validateReleasePlan(raw: unknown): DesignReleasePlanInput;
4
+ export declare function validateDesignRelease(raw: unknown): DesignRelease;
5
+ /** Owner review scopes immutable design evidence; implementation acceptance is independent. */
6
+ export declare function hashDesignRelease(raw: DesignRelease): string;