@12ui/design 0.2.58 → 0.2.60
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/SKILL.md +1 -1
- package/dist/atomic-file-replace.d.ts +21 -0
- package/dist/atomic-file-replace.d.ts.map +1 -0
- package/dist/atomic-file-replace.js +48 -0
- package/dist/atomic-file-replace.js.map +1 -0
- package/dist/cli-conversion-derivation.d.ts +14 -1
- package/dist/cli-conversion-derivation.d.ts.map +1 -1
- package/dist/cli-conversion-derivation.js +12 -5
- package/dist/cli-conversion-derivation.js.map +1 -1
- package/dist/cli-convert-command.d.ts.map +1 -1
- package/dist/cli-convert-command.js +13 -4
- package/dist/cli-convert-command.js.map +1 -1
- package/dist/cli-draft-command.d.ts +7 -0
- package/dist/cli-draft-command.d.ts.map +1 -1
- package/dist/cli-draft-command.js +21 -8
- package/dist/cli-draft-command.js.map +1 -1
- package/dist/cli-help/design.js +2 -2
- package/dist/cli-help/design.js.map +1 -1
- package/dist/cli-improve-command.d.ts.map +1 -1
- package/dist/cli-improve-command.js +36 -3
- package/dist/cli-improve-command.js.map +1 -1
- package/dist/cli-redesign-command.d.ts.map +1 -1
- package/dist/cli-redesign-command.js +177 -138
- package/dist/cli-redesign-command.js.map +1 -1
- package/dist/conversion-client.d.ts +48 -10
- package/dist/conversion-client.d.ts.map +1 -1
- package/dist/conversion-client.js +92 -20
- package/dist/conversion-client.js.map +1 -1
- package/dist/corpus-client.d.ts +5 -0
- package/dist/corpus-client.d.ts.map +1 -1
- package/dist/corpus-client.js +14 -1
- package/dist/corpus-client.js.map +1 -1
- package/dist/draft-identity.d.ts +7 -0
- package/dist/draft-identity.d.ts.map +1 -1
- package/dist/draft-identity.js +4 -0
- package/dist/draft-identity.js.map +1 -1
- package/dist/draft-run.d.ts +7 -0
- package/dist/draft-run.d.ts.map +1 -1
- package/dist/draft-run.js +8 -0
- package/dist/draft-run.js.map +1 -1
- package/dist/draft-workspace.d.ts +4 -0
- package/dist/draft-workspace.d.ts.map +1 -1
- package/dist/draft-workspace.js.map +1 -1
- package/dist/hosted-draft-identity.d.ts +10 -0
- package/dist/hosted-draft-identity.d.ts.map +1 -1
- package/dist/hosted-draft-identity.js +7 -0
- package/dist/hosted-draft-identity.js.map +1 -1
- package/dist/hosted-draft-run.d.ts +15 -0
- package/dist/hosted-draft-run.d.ts.map +1 -1
- package/dist/hosted-draft-run.js +27 -0
- package/dist/hosted-draft-run.js.map +1 -1
- package/dist/improve-conversion-stage.d.ts.map +1 -1
- package/dist/improve-conversion-stage.js +8 -4
- package/dist/improve-conversion-stage.js.map +1 -1
- package/dist/improve-convert-resilience.d.ts +1 -0
- package/dist/improve-convert-resilience.d.ts.map +1 -1
- package/dist/improve-convert-resilience.js +22 -4
- package/dist/improve-convert-resilience.js.map +1 -1
- package/dist/improve-core/improve-concept.d.ts +5 -1
- package/dist/improve-core/improve-concept.d.ts.map +1 -1
- package/dist/improve-core/improve-concept.js +11 -6
- package/dist/improve-core/improve-concept.js.map +1 -1
- package/dist/redesign-branch-adoption.d.ts +29 -0
- package/dist/redesign-branch-adoption.d.ts.map +1 -0
- package/dist/redesign-branch-adoption.js +183 -0
- package/dist/redesign-branch-adoption.js.map +1 -0
- package/dist/redesign-branch-identity.d.ts +23 -0
- package/dist/redesign-branch-identity.d.ts.map +1 -0
- package/dist/redesign-branch-identity.js +27 -0
- package/dist/redesign-branch-identity.js.map +1 -0
- package/dist/redesign-branch-record-verification.d.ts +51 -0
- package/dist/redesign-branch-record-verification.d.ts.map +1 -0
- package/dist/redesign-branch-record-verification.js +127 -0
- package/dist/redesign-branch-record-verification.js.map +1 -0
- package/dist/redesign-branch-stage.d.ts +17 -0
- package/dist/redesign-branch-stage.d.ts.map +1 -0
- package/dist/redesign-branch-stage.js +218 -0
- package/dist/redesign-branch-stage.js.map +1 -0
- package/dist/redesign-page-candidates.d.ts +40 -0
- package/dist/redesign-page-candidates.d.ts.map +1 -0
- package/dist/redesign-page-candidates.js +131 -0
- package/dist/redesign-page-candidates.js.map +1 -0
- package/dist/redesign-page-ownership.d.ts +36 -0
- package/dist/redesign-page-ownership.d.ts.map +1 -0
- package/dist/redesign-page-ownership.js +174 -0
- package/dist/redesign-page-ownership.js.map +1 -0
- package/dist/redesign-plan-identity.d.ts +3 -1
- package/dist/redesign-plan-identity.d.ts.map +1 -1
- package/dist/redesign-plan-identity.js +4 -1
- package/dist/redesign-plan-identity.js.map +1 -1
- package/dist/redesign-plan.d.ts.map +1 -1
- package/dist/redesign-plan.js +12 -6
- package/dist/redesign-plan.js.map +1 -1
- package/dist/redesign-record-validation.d.ts +4 -0
- package/dist/redesign-record-validation.d.ts.map +1 -0
- package/dist/redesign-record-validation.js +210 -0
- package/dist/redesign-record-validation.js.map +1 -0
- package/dist/redesign-workspace.d.ts +30 -5
- package/dist/redesign-workspace.d.ts.map +1 -1
- package/dist/redesign-workspace.js +52 -7
- package/dist/redesign-workspace.js.map +1 -1
- package/dist/run-journal.d.ts +3 -1
- package/dist/run-journal.d.ts.map +1 -1
- package/dist/run-journal.js +7 -1
- package/dist/run-journal.js.map +1 -1
- package/dist/spend-run-filter.d.ts.map +1 -1
- package/dist/spend-run-filter.js +3 -1
- package/dist/spend-run-filter.js.map +1 -1
- package/open-design.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { TwelveUiBranchClient } from './branch-client.js';
|
|
3
|
+
import { createConcurrencyLimiter } from './bounded-concurrency.js';
|
|
4
|
+
import { executeBranchRun } from './branch-execution.js';
|
|
5
|
+
import { branchAttemptPath, readBranchAttempt } from './branch-run-record.js';
|
|
6
|
+
import { imageWinner } from './branch-winner.js';
|
|
7
|
+
import { CliRefusal } from './cli-refusal.js';
|
|
8
|
+
import { prepareOutputDirectory } from './cli-output-paths.js';
|
|
9
|
+
import { BRANCH_PAGE_CONCURRENCY_DEFAULT } from './cli-progress.js';
|
|
10
|
+
import { readConceptValue } from './concept-input.js';
|
|
11
|
+
import { TwelveUiConversionPackageClient } from './package-client.js';
|
|
12
|
+
import { TwelveUiPrototypeLabelClient } from './prototype-label-client.js';
|
|
13
|
+
import { runPrototypePoststep } from './prototype-poststep.js';
|
|
14
|
+
import { InvalidRedesignBranchArtifactError, } from './redesign-branch-adoption.js';
|
|
15
|
+
import { redesignBranchIdempotencyKey, redesignBranchPageDirectory, } from './redesign-branch-identity.js';
|
|
16
|
+
import { InvalidRedesignBranchAttemptError, InvalidRedesignBranchRecordError, verifyResumableRedesignBranchAttempt, verifyRedesignBranchPages, } from './redesign-branch-record-verification.js';
|
|
17
|
+
import { InvalidRedesignPageOwnershipError } from './redesign-page-ownership.js';
|
|
18
|
+
import { writeRedesignRecord, } from './redesign-workspace.js';
|
|
19
|
+
const branchConcept = (direction, page) => readConceptValue({
|
|
20
|
+
value: `${direction} Rebuild the page at ${page.url}.`,
|
|
21
|
+
flag: '--direction',
|
|
22
|
+
trimHint: 'Shorten the direction; each page URL is appended automatically.',
|
|
23
|
+
});
|
|
24
|
+
const branchRefusal = (error) => {
|
|
25
|
+
if (error instanceof InvalidRedesignPageOwnershipError) {
|
|
26
|
+
throw new CliRefusal(`${error.message} Refusing branch dispatch.`, 'redesign_page_ownership_invalid', { cause: error });
|
|
27
|
+
}
|
|
28
|
+
if (error instanceof InvalidRedesignBranchArtifactError
|
|
29
|
+
|| error instanceof InvalidRedesignBranchRecordError
|
|
30
|
+
|| error instanceof InvalidRedesignBranchAttemptError) {
|
|
31
|
+
throw new CliRefusal(`${error.message} Refusing branch dispatch.`, 'redesign_branch_artifact_invalid', { cause: error });
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
};
|
|
35
|
+
const recordAdoptedPages = async (args) => {
|
|
36
|
+
const added = new Map(args.entries
|
|
37
|
+
.filter((entry) => !entry.recorded && entry.adopted)
|
|
38
|
+
.map((entry) => [entry.input.page.id, entry.adopted.page]));
|
|
39
|
+
if (added.size === 0)
|
|
40
|
+
return args.record;
|
|
41
|
+
const existing = new Map((args.record.branch?.pages ?? []).map((page) => [page.pageId, page]));
|
|
42
|
+
for (const [pageId, page] of added)
|
|
43
|
+
existing.set(pageId, page);
|
|
44
|
+
const pages = args.record.capture.pages.flatMap((page) => {
|
|
45
|
+
const settled = existing.get(page.id);
|
|
46
|
+
return settled ? [settled] : [];
|
|
47
|
+
});
|
|
48
|
+
const record = { ...args.record, branch: { pages } };
|
|
49
|
+
await writeRedesignRecord(args.paths.record, record);
|
|
50
|
+
return record;
|
|
51
|
+
};
|
|
52
|
+
/** Execute or adopt one exact child per captured page, never a shared winner. */
|
|
53
|
+
export const runRedesignBranchStage = async (args) => {
|
|
54
|
+
if (!args.record.capture)
|
|
55
|
+
throw new Error('Cannot branch before capture has run');
|
|
56
|
+
if (!args.record.pick)
|
|
57
|
+
throw new Error('Cannot branch before pick has run');
|
|
58
|
+
const capture = args.record.capture;
|
|
59
|
+
let preflight;
|
|
60
|
+
try {
|
|
61
|
+
preflight = await verifyRedesignBranchPages({
|
|
62
|
+
record: args.record,
|
|
63
|
+
pages: capture.pages,
|
|
64
|
+
branchDirectory: (page) => redesignBranchPageDirectory({
|
|
65
|
+
paths: args.paths,
|
|
66
|
+
record: args.record,
|
|
67
|
+
pageId: page.id,
|
|
68
|
+
}),
|
|
69
|
+
branchConcept: (page) => branchConcept(args.direction, page),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return branchRefusal(error);
|
|
74
|
+
}
|
|
75
|
+
// A completed child failed under native branch semantics. Persist a newly
|
|
76
|
+
// found child for truthful resume/spend evidence, then stop before either a
|
|
77
|
+
// sibling dispatch or polish can hide that failure.
|
|
78
|
+
const adoptedFailure = preflight.find((entry) => entry.adopted?.failure)?.adopted?.failure;
|
|
79
|
+
if (adoptedFailure) {
|
|
80
|
+
await recordAdoptedPages({ record: args.record, paths: args.paths, entries: preflight });
|
|
81
|
+
throw adoptedFailure;
|
|
82
|
+
}
|
|
83
|
+
const remaining = preflight.filter((entry) => !entry.recorded);
|
|
84
|
+
if (remaining.length === 0)
|
|
85
|
+
return args.record;
|
|
86
|
+
// Prove every resume target before the first sibling can dispatch. A
|
|
87
|
+
// redesign key is intentionally page/generation-scoped, so an old attempt
|
|
88
|
+
// with that key can still name a different picked candidate.
|
|
89
|
+
const prepared = await Promise.all(remaining.map(async (entry) => {
|
|
90
|
+
const branchDir = redesignBranchPageDirectory({
|
|
91
|
+
paths: args.paths,
|
|
92
|
+
record: args.record,
|
|
93
|
+
pageId: entry.input.page.id,
|
|
94
|
+
});
|
|
95
|
+
const winner = await imageWinner(entry.input.winner.path, `The picked candidate for ${entry.input.page.id}`);
|
|
96
|
+
if (winner.kind !== 'image'
|
|
97
|
+
|| winner.sha256 !== entry.input.winner.sha256
|
|
98
|
+
|| winner.bytes.byteLength !== entry.input.winner.bytes) {
|
|
99
|
+
throw new CliRefusal(`The picked candidate for ${entry.input.page.id} no longer matches its durable materialized digest.`, 'redesign_page_pick_changed');
|
|
100
|
+
}
|
|
101
|
+
const runKey = redesignBranchIdempotencyKey({
|
|
102
|
+
createdAt: args.record.createdAt,
|
|
103
|
+
...(args.record.redrawGeneration ? { redrawGeneration: args.record.redrawGeneration } : {}),
|
|
104
|
+
pageId: entry.input.page.id,
|
|
105
|
+
});
|
|
106
|
+
const attemptPath = branchAttemptPath(branchDir);
|
|
107
|
+
const attempt = await readBranchAttempt(attemptPath);
|
|
108
|
+
if (!attempt && !entry.adopted) {
|
|
109
|
+
const exists = await stat(attemptPath)
|
|
110
|
+
.then(() => true)
|
|
111
|
+
.catch((error) => {
|
|
112
|
+
if (error.code === 'ENOENT')
|
|
113
|
+
return false;
|
|
114
|
+
throw error;
|
|
115
|
+
});
|
|
116
|
+
if (exists) {
|
|
117
|
+
return branchRefusal(new InvalidRedesignBranchAttemptError(branchDir, 'branch.attempt.json is not a readable native attempt record'));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (attempt && !entry.adopted) {
|
|
121
|
+
try {
|
|
122
|
+
verifyResumableRedesignBranchAttempt({
|
|
123
|
+
attempt,
|
|
124
|
+
attemptPath,
|
|
125
|
+
branchDir,
|
|
126
|
+
input: entry.input,
|
|
127
|
+
winner,
|
|
128
|
+
runKey,
|
|
129
|
+
concept: branchConcept(args.direction, entry.input.page),
|
|
130
|
+
origin: args.config.origin,
|
|
131
|
+
maxScreens: args.maxScreens,
|
|
132
|
+
conversionModel: args.conversionModel,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return branchRefusal(error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { entry, branchDir, winner, attempt };
|
|
140
|
+
}));
|
|
141
|
+
const branchClient = (args.dependencies.createBranchClient
|
|
142
|
+
?? args.dependencies.createHostedClient
|
|
143
|
+
?? ((value) => new TwelveUiBranchClient(value)))(args.config);
|
|
144
|
+
const packageClient = (args.dependencies.createPackageClient
|
|
145
|
+
?? ((value) => new TwelveUiConversionPackageClient(value)))(args.config);
|
|
146
|
+
const limiter = createConcurrencyLimiter(BRANCH_PAGE_CONCURRENCY_DEFAULT);
|
|
147
|
+
const completed = await Promise.all(prepared.map(({ entry, branchDir, winner, attempt }) => limiter(async () => {
|
|
148
|
+
const labelClient = (args.dependencies.createPrototypeLabelClient
|
|
149
|
+
?? ((value) => new TwelveUiPrototypeLabelClient(value)))(args.config);
|
|
150
|
+
if (entry.adopted) {
|
|
151
|
+
await (args.dependencies.runPrototype ?? runPrototypePoststep)({
|
|
152
|
+
branchDir,
|
|
153
|
+
resume: true,
|
|
154
|
+
labelClient,
|
|
155
|
+
});
|
|
156
|
+
return { page: entry.adopted.page, failure: entry.adopted.failure };
|
|
157
|
+
}
|
|
158
|
+
await prepareOutputDirectory(branchDir);
|
|
159
|
+
const result = await (args.dependencies.executeBranch ?? executeBranchRun)({
|
|
160
|
+
branchClient,
|
|
161
|
+
packageClient,
|
|
162
|
+
inputs: {
|
|
163
|
+
outDir: branchDir,
|
|
164
|
+
scope: 'page',
|
|
165
|
+
concept: branchConcept(args.direction, entry.input.page),
|
|
166
|
+
idempotencyKey: redesignBranchIdempotencyKey({
|
|
167
|
+
createdAt: args.record.createdAt,
|
|
168
|
+
...(args.record.redrawGeneration ? { redrawGeneration: args.record.redrawGeneration } : {}),
|
|
169
|
+
pageId: entry.input.page.id,
|
|
170
|
+
}),
|
|
171
|
+
winner,
|
|
172
|
+
maxScreens: args.maxScreens,
|
|
173
|
+
convert: { output: 'html', model: args.conversionModel },
|
|
174
|
+
},
|
|
175
|
+
origin: args.config.origin,
|
|
176
|
+
resume: Boolean(attempt),
|
|
177
|
+
// The outer limiter owns redesign page fan-out. Each child has one page.
|
|
178
|
+
pageConcurrency: 1,
|
|
179
|
+
});
|
|
180
|
+
await (args.dependencies.runPrototype ?? runPrototypePoststep)({ branchDir, labelClient });
|
|
181
|
+
return {
|
|
182
|
+
page: {
|
|
183
|
+
pageId: entry.input.page.id,
|
|
184
|
+
branchDir,
|
|
185
|
+
runId: result.completion.runId,
|
|
186
|
+
status: result.completion.status,
|
|
187
|
+
completionPath: result.completionPath,
|
|
188
|
+
},
|
|
189
|
+
failure: result.failure,
|
|
190
|
+
};
|
|
191
|
+
})));
|
|
192
|
+
// Child work is parallel; redesign.json remains one capture-order writer.
|
|
193
|
+
const settled = new Map((args.record.branch?.pages ?? []).map((page) => [page.pageId, page]));
|
|
194
|
+
const completedByPageId = new Map(completed.map((entry) => [entry.page.pageId, entry]));
|
|
195
|
+
let record = args.record;
|
|
196
|
+
let failure = null;
|
|
197
|
+
for (const page of capture.pages) {
|
|
198
|
+
const completedPage = completedByPageId.get(page.id);
|
|
199
|
+
if (!completedPage)
|
|
200
|
+
continue;
|
|
201
|
+
settled.set(page.id, completedPage.page);
|
|
202
|
+
record = {
|
|
203
|
+
...record,
|
|
204
|
+
branch: {
|
|
205
|
+
pages: capture.pages.flatMap((capturedPage) => {
|
|
206
|
+
const child = settled.get(capturedPage.id);
|
|
207
|
+
return child ? [child] : [];
|
|
208
|
+
}),
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
await writeRedesignRecord(args.paths.record, record);
|
|
212
|
+
failure ??= completedPage.failure;
|
|
213
|
+
}
|
|
214
|
+
if (failure)
|
|
215
|
+
throw failure;
|
|
216
|
+
return record;
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=redesign-branch-stage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redesign-branch-stage.js","sourceRoot":"","sources":["../src/redesign-branch-stage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RedesignCapturedPageRecord, RedesignDraftPageRecord, RedesignPickPageRecord, RedesignRecord } from './redesign-workspace.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-page candidate ownership for `12ui redesign`.
|
|
4
|
+
*
|
|
5
|
+
* Capture already knows each page's route and screenshot. Draft and pick must
|
|
6
|
+
* use those facts: a shared homepage winner is not a design for a later
|
|
7
|
+
* captured page. This module is the one place that decides which pages still
|
|
8
|
+
* need their own draw, how that draw is identified, and that a missing pick
|
|
9
|
+
* is a failure rather than a silent reuse of another page's candidate.
|
|
10
|
+
*/
|
|
11
|
+
export declare const redesignPrimaryPage: (pages: readonly RedesignCapturedPageRecord[]) => RedesignCapturedPageRecord;
|
|
12
|
+
export declare const isRedesignPrimaryPage: (pages: readonly RedesignCapturedPageRecord[], pageId: string) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Primary pages keep the hosted plan concept so an ordinary one-page redesign
|
|
15
|
+
* claims the same draft it always did. Every other page appends its captured
|
|
16
|
+
* route so the image prompt, and therefore the purchase, is page-specific.
|
|
17
|
+
*/
|
|
18
|
+
export declare const redesignPageDraftConcept: (args: {
|
|
19
|
+
planConcept: string;
|
|
20
|
+
page: RedesignCapturedPageRecord;
|
|
21
|
+
primary: RedesignCapturedPageRecord;
|
|
22
|
+
}) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Paid-identity extra for a non-primary page. Omitted on the primary so keys
|
|
25
|
+
* claimed before per-page drafts existed still replay (CLAUDE.md item 3).
|
|
26
|
+
*/
|
|
27
|
+
export declare const redesignPageDraftRoute: (args: {
|
|
28
|
+
page: RedesignCapturedPageRecord;
|
|
29
|
+
primary: RedesignCapturedPageRecord;
|
|
30
|
+
}) => string | undefined;
|
|
31
|
+
export declare const recordedDraftPages: (record: RedesignRecord) => RedesignDraftPageRecord[];
|
|
32
|
+
export declare const recordedPickPages: (record: RedesignRecord) => RedesignPickPageRecord[];
|
|
33
|
+
export declare const recordedDraftRunIds: (record: RedesignRecord) => string[];
|
|
34
|
+
export declare const pagesNeedingDraft: (record: RedesignRecord) => RedesignCapturedPageRecord[];
|
|
35
|
+
export declare const pagesNeedingPick: (record: RedesignRecord) => RedesignCapturedPageRecord[];
|
|
36
|
+
export declare const draftForPage: (record: RedesignRecord, pageId: string) => RedesignDraftPageRecord;
|
|
37
|
+
export declare const pickForPage: (record: RedesignRecord, pageId: string) => RedesignPickPageRecord;
|
|
38
|
+
export declare const withDraftPages: (pages: readonly RedesignDraftPageRecord[], primaryPageId: string) => NonNullable<RedesignRecord["draft"]>;
|
|
39
|
+
export declare const withPickPages: (pages: readonly RedesignPickPageRecord[], primaryPageId: string) => NonNullable<RedesignRecord["pick"]>;
|
|
40
|
+
//# sourceMappingURL=redesign-page-candidates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redesign-page-candidates.d.ts","sourceRoot":"","sources":["../src/redesign-page-candidates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACf,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;GAQG;AAEH,eAAO,MAAM,mBAAmB,GAC9B,OAAO,SAAS,0BAA0B,EAAE,KAC3C,0BAIF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,OAAO,SAAS,0BAA0B,EAAE,EAC5C,QAAQ,MAAM,KACb,OAAkC,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,0BAA0B,CAAC;IACjC,OAAO,EAAE,0BAA0B,CAAC;CACrC,KAAG,MAOH,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM;IAC3C,IAAI,EAAE,0BAA0B,CAAC;IACjC,OAAO,EAAE,0BAA0B,CAAC;CACrC,KAAG,MAAM,GAAG,SAEZ,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,cAAc,KAAG,uBAAuB,EAgBlF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,QAAQ,cAAc,KAAG,sBAAsB,EAmBhF,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,cAAc,KAAG,MAAM,EAIlE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,QAAQ,cAAc,KAAG,0BAA0B,EAGpF,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,QAAQ,cAAc,KAAG,0BAA0B,EAGnF,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,QAAQ,cAAc,EACtB,QAAQ,MAAM,KACb,uBAQF,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,QAAQ,cAAc,EACtB,QAAQ,MAAM,KACb,sBAQF,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,OAAO,SAAS,uBAAuB,EAAE,EACzC,eAAe,MAAM,KACpB,WAAW,CAAC,cAAc,CAAC,OAAO,CAAC,CAQrC,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,OAAO,SAAS,sBAAsB,EAAE,EACxC,eAAe,MAAM,KACpB,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CASpC,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readConceptValue } from './concept-input.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-page candidate ownership for `12ui redesign`.
|
|
4
|
+
*
|
|
5
|
+
* Capture already knows each page's route and screenshot. Draft and pick must
|
|
6
|
+
* use those facts: a shared homepage winner is not a design for a later
|
|
7
|
+
* captured page. This module is the one place that decides which pages still
|
|
8
|
+
* need their own draw, how that draw is identified, and that a missing pick
|
|
9
|
+
* is a failure rather than a silent reuse of another page's candidate.
|
|
10
|
+
*/
|
|
11
|
+
export const redesignPrimaryPage = (pages) => {
|
|
12
|
+
const homepage = pages[0];
|
|
13
|
+
if (!homepage)
|
|
14
|
+
throw new Error('Capture recorded no pages');
|
|
15
|
+
return homepage;
|
|
16
|
+
};
|
|
17
|
+
export const isRedesignPrimaryPage = (pages, pageId) => pages[0]?.id === pageId;
|
|
18
|
+
/**
|
|
19
|
+
* Primary pages keep the hosted plan concept so an ordinary one-page redesign
|
|
20
|
+
* claims the same draft it always did. Every other page appends its captured
|
|
21
|
+
* route so the image prompt, and therefore the purchase, is page-specific.
|
|
22
|
+
*/
|
|
23
|
+
export const redesignPageDraftConcept = (args) => {
|
|
24
|
+
if (args.page.id === args.primary.id)
|
|
25
|
+
return args.planConcept;
|
|
26
|
+
return readConceptValue({
|
|
27
|
+
value: `${args.planConcept} Rebuild the captured page at ${args.page.url}.`,
|
|
28
|
+
flag: '--direction',
|
|
29
|
+
trimHint: 'Shorten the direction; each page URL is appended automatically.',
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Paid-identity extra for a non-primary page. Omitted on the primary so keys
|
|
34
|
+
* claimed before per-page drafts existed still replay (CLAUDE.md item 3).
|
|
35
|
+
*/
|
|
36
|
+
export const redesignPageDraftRoute = (args) => (args.page.id === args.primary.id ? undefined : args.page.url);
|
|
37
|
+
export const recordedDraftPages = (record) => {
|
|
38
|
+
if (!record.draft)
|
|
39
|
+
return [];
|
|
40
|
+
const primaryId = record.capture?.pages[0]?.id;
|
|
41
|
+
if (!primaryId)
|
|
42
|
+
return [];
|
|
43
|
+
const primary = {
|
|
44
|
+
pageId: primaryId,
|
|
45
|
+
runDir: record.draft.runDir,
|
|
46
|
+
runId: record.draft.runId,
|
|
47
|
+
};
|
|
48
|
+
if (!record.draft.pages || record.draft.pages.length === 0)
|
|
49
|
+
return [primary];
|
|
50
|
+
const recordedPrimary = record.draft.pages.find((page) => page.pageId === primaryId);
|
|
51
|
+
if (!recordedPrimary)
|
|
52
|
+
return [primary, ...record.draft.pages];
|
|
53
|
+
if (recordedPrimary.runDir !== primary.runDir || recordedPrimary.runId !== primary.runId) {
|
|
54
|
+
throw new Error('Recorded primary draft does not match the durable top-level draft');
|
|
55
|
+
}
|
|
56
|
+
return [...record.draft.pages];
|
|
57
|
+
};
|
|
58
|
+
export const recordedPickPages = (record) => {
|
|
59
|
+
if (!record.pick)
|
|
60
|
+
return [];
|
|
61
|
+
const primaryId = record.capture?.pages[0]?.id;
|
|
62
|
+
if (!primaryId)
|
|
63
|
+
return [];
|
|
64
|
+
const primary = {
|
|
65
|
+
pageId: primaryId,
|
|
66
|
+
slot: record.pick.slot,
|
|
67
|
+
imagePath: record.pick.imagePath,
|
|
68
|
+
sha256: record.pick.sha256,
|
|
69
|
+
};
|
|
70
|
+
if (!record.pick.pages || record.pick.pages.length === 0)
|
|
71
|
+
return [primary];
|
|
72
|
+
const recordedPrimary = record.pick.pages.find((page) => page.pageId === primaryId);
|
|
73
|
+
if (!recordedPrimary)
|
|
74
|
+
return [primary, ...record.pick.pages];
|
|
75
|
+
if (recordedPrimary.slot !== primary.slot
|
|
76
|
+
|| recordedPrimary.imagePath !== primary.imagePath
|
|
77
|
+
|| recordedPrimary.sha256 !== primary.sha256) {
|
|
78
|
+
throw new Error('Recorded primary pick does not match the durable top-level pick');
|
|
79
|
+
}
|
|
80
|
+
return [...record.pick.pages];
|
|
81
|
+
};
|
|
82
|
+
export const recordedDraftRunIds = (record) => {
|
|
83
|
+
const pages = recordedDraftPages(record);
|
|
84
|
+
if (pages.length > 0)
|
|
85
|
+
return pages.map((page) => page.runId);
|
|
86
|
+
return record.draft ? [record.draft.runId] : [];
|
|
87
|
+
};
|
|
88
|
+
export const pagesNeedingDraft = (record) => {
|
|
89
|
+
const done = new Set(recordedDraftPages(record).map((page) => page.pageId));
|
|
90
|
+
return (record.capture?.pages ?? []).filter((page) => !done.has(page.id));
|
|
91
|
+
};
|
|
92
|
+
export const pagesNeedingPick = (record) => {
|
|
93
|
+
const done = new Set(recordedPickPages(record).map((page) => page.pageId));
|
|
94
|
+
return (record.capture?.pages ?? []).filter((page) => !done.has(page.id));
|
|
95
|
+
};
|
|
96
|
+
export const draftForPage = (record, pageId) => {
|
|
97
|
+
const found = recordedDraftPages(record).find((page) => page.pageId === pageId);
|
|
98
|
+
if (!found) {
|
|
99
|
+
throw new Error(`Captured page ${pageId} has no page-specific draft; re-run from draft so it can buy its own candidates.`);
|
|
100
|
+
}
|
|
101
|
+
return found;
|
|
102
|
+
};
|
|
103
|
+
export const pickForPage = (record, pageId) => {
|
|
104
|
+
const found = recordedPickPages(record).find((page) => page.pageId === pageId);
|
|
105
|
+
if (!found) {
|
|
106
|
+
throw new Error(`Captured page ${pageId} has no page-specific pick; refusing to reuse another page's winner.`);
|
|
107
|
+
}
|
|
108
|
+
return found;
|
|
109
|
+
};
|
|
110
|
+
export const withDraftPages = (pages, primaryPageId) => {
|
|
111
|
+
const primary = pages.find((page) => page.pageId === primaryPageId) ?? pages[0];
|
|
112
|
+
if (!primary)
|
|
113
|
+
throw new Error('Cannot record a draft with no pages');
|
|
114
|
+
return {
|
|
115
|
+
runDir: primary.runDir,
|
|
116
|
+
runId: primary.runId,
|
|
117
|
+
pages: [...pages],
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
export const withPickPages = (pages, primaryPageId) => {
|
|
121
|
+
const primary = pages.find((page) => page.pageId === primaryPageId) ?? pages[0];
|
|
122
|
+
if (!primary)
|
|
123
|
+
throw new Error('Cannot record a pick with no pages');
|
|
124
|
+
return {
|
|
125
|
+
slot: primary.slot,
|
|
126
|
+
imagePath: primary.imagePath,
|
|
127
|
+
sha256: primary.sha256,
|
|
128
|
+
pages: [...pages],
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=redesign-page-candidates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redesign-page-candidates.js","sourceRoot":"","sources":["../src/redesign-page-candidates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { RedesignCapturedPageRecord, RedesignDraftPageRecord, RedesignPickPageRecord, RedesignRecord } from './redesign-workspace.js';
|
|
2
|
+
/**
|
|
3
|
+
* Durable proof that a branch page owns its selected draft. Parent records
|
|
4
|
+
* name file locations for navigation, but those mutable paths are never the
|
|
5
|
+
* identity: the route, capture digest, hosted run ID, completion run ID, and
|
|
6
|
+
* materialized candidate digest must all agree before branch can claim work.
|
|
7
|
+
*/
|
|
8
|
+
export declare class InvalidRedesignPageOwnershipError extends Error {
|
|
9
|
+
constructor(pageId: string, detail: string);
|
|
10
|
+
}
|
|
11
|
+
export type VerifiedRedesignPageBranchInput = {
|
|
12
|
+
page: RedesignCapturedPageRecord;
|
|
13
|
+
draft: RedesignDraftPageRecord;
|
|
14
|
+
pick: RedesignPickPageRecord;
|
|
15
|
+
/**
|
|
16
|
+
* The exact DraftCandidateRecord.outputPath, proven owned and read from disk
|
|
17
|
+
* in this verification pass. Child branch artifacts must bind to this value,
|
|
18
|
+
* never merely to a digest that another page could also name.
|
|
19
|
+
*/
|
|
20
|
+
winner: {
|
|
21
|
+
path: string;
|
|
22
|
+
sha256: string;
|
|
23
|
+
bytes: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/** Validate one page's draft, completion, and selected materialized candidate. */
|
|
27
|
+
export declare const verifyRedesignPageBranchInput: (args: {
|
|
28
|
+
record: RedesignRecord;
|
|
29
|
+
page: RedesignCapturedPageRecord;
|
|
30
|
+
}) => Promise<VerifiedRedesignPageBranchInput>;
|
|
31
|
+
/** Validate every pending branch input before any page is allowed to dispatch. */
|
|
32
|
+
export declare const verifyRedesignPageBranchInputs: (args: {
|
|
33
|
+
record: RedesignRecord;
|
|
34
|
+
pages: readonly RedesignCapturedPageRecord[];
|
|
35
|
+
}) => Promise<VerifiedRedesignPageBranchInput[]>;
|
|
36
|
+
//# sourceMappingURL=redesign-page-ownership.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redesign-page-ownership.d.ts","sourceRoot":"","sources":["../src/redesign-page-ownership.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACf,MAAM,yBAAyB,CAAC;AAEjC;;;;;GAKG;AAEH,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI3C;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,EAAE,0BAA0B,CAAC;IACjC,KAAK,EAAE,uBAAuB,CAAC;IAC/B,IAAI,EAAE,sBAAsB,CAAC;IAC7B;;;;OAIG;IACH,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACzD,CAAC;AAiGF,kFAAkF;AAClF,eAAO,MAAM,6BAA6B,GAAU,MAAM;IACxD,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,0BAA0B,CAAC;CAClC,KAAG,OAAO,CAAC,+BAA+B,CAqG1C,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,8BAA8B,GAAU,MAAM;IACzD,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,SAAS,0BAA0B,EAAE,CAAC;CAC9C,KAAG,OAAO,CAAC,+BAA+B,EAAE,CAE5C,CAAC"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { draftForPage, pickForPage, redesignPrimaryPage } from './redesign-page-candidates.js';
|
|
5
|
+
import { draftPaths } from './draft-workspace.js';
|
|
6
|
+
/**
|
|
7
|
+
* Durable proof that a branch page owns its selected draft. Parent records
|
|
8
|
+
* name file locations for navigation, but those mutable paths are never the
|
|
9
|
+
* identity: the route, capture digest, hosted run ID, completion run ID, and
|
|
10
|
+
* materialized candidate digest must all agree before branch can claim work.
|
|
11
|
+
*/
|
|
12
|
+
export class InvalidRedesignPageOwnershipError extends Error {
|
|
13
|
+
constructor(pageId, detail) {
|
|
14
|
+
super(`Redesign page ${pageId} has invalid draft ownership: ${detail}`);
|
|
15
|
+
this.name = 'InvalidRedesignPageOwnershipError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const samePath = (left, right) => path.resolve(left) === path.resolve(right);
|
|
19
|
+
const insideDirectory = (file, directory) => {
|
|
20
|
+
const relative = path.relative(path.resolve(directory), path.resolve(file));
|
|
21
|
+
return relative !== '' && !relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative);
|
|
22
|
+
};
|
|
23
|
+
const isRecord = (value) => (!!value && typeof value === 'object' && !Array.isArray(value));
|
|
24
|
+
const readJson = async (file, pageId, label) => {
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = await readFile(file, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new InvalidRedesignPageOwnershipError(pageId, `${label} is unreadable at ${file} (${error.code ?? 'unknown error'})`);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new InvalidRedesignPageOwnershipError(pageId, `${label} is not JSON at ${file}`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const validDraftRecord = (value) => (isRecord(value)
|
|
40
|
+
&& typeof value.runDir === 'string'
|
|
41
|
+
&& isRecord(value.redesignSource)
|
|
42
|
+
&& typeof value.redesignSource.path === 'string'
|
|
43
|
+
&& typeof value.redesignSource.sha256 === 'string'
|
|
44
|
+
&& isRecord(value.hosted)
|
|
45
|
+
&& typeof value.hosted.runId === 'string'
|
|
46
|
+
&& Array.isArray(value.hosted.candidates));
|
|
47
|
+
const validDraftCompletion = (value) => (isRecord(value)
|
|
48
|
+
&& typeof value.runId === 'string'
|
|
49
|
+
&& Array.isArray(value.candidates));
|
|
50
|
+
const materializedCandidateDigest = async (args) => {
|
|
51
|
+
let bytes;
|
|
52
|
+
try {
|
|
53
|
+
bytes = await readFile(args.file);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new InvalidRedesignPageOwnershipError(args.pageId, `selected DraftCandidateRecord.outputPath is unreadable at ${args.file}`
|
|
57
|
+
+ ` (${error.code ?? 'unknown error'})`);
|
|
58
|
+
}
|
|
59
|
+
return { sha256: createHash('sha256').update(bytes).digest('hex'), bytes: bytes.byteLength };
|
|
60
|
+
};
|
|
61
|
+
const assertOwnedCandidatePath = async (args) => {
|
|
62
|
+
if (!insideDirectory(args.candidatePath, args.candidatesDirectory)) {
|
|
63
|
+
throw new InvalidRedesignPageOwnershipError(args.pageId, 'selected DraftCandidateRecord.outputPath is outside its owning child draft candidates directory');
|
|
64
|
+
}
|
|
65
|
+
let resolvedCandidate;
|
|
66
|
+
let resolvedDirectory;
|
|
67
|
+
try {
|
|
68
|
+
[resolvedCandidate, resolvedDirectory] = await Promise.all([
|
|
69
|
+
realpath(args.candidatePath),
|
|
70
|
+
realpath(args.candidatesDirectory),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw new InvalidRedesignPageOwnershipError(args.pageId, `selected DraftCandidateRecord.outputPath cannot resolve inside its owning child draft directory`
|
|
75
|
+
+ ` (${error.code ?? 'unknown error'})`);
|
|
76
|
+
}
|
|
77
|
+
if (!insideDirectory(resolvedCandidate, resolvedDirectory)) {
|
|
78
|
+
throw new InvalidRedesignPageOwnershipError(args.pageId, 'selected DraftCandidateRecord.outputPath resolves outside its owning child draft candidates directory');
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** Validate one page's draft, completion, and selected materialized candidate. */
|
|
82
|
+
export const verifyRedesignPageBranchInput = async (args) => {
|
|
83
|
+
const primary = redesignPrimaryPage(args.record.capture?.pages ?? []);
|
|
84
|
+
const pageId = args.page.id;
|
|
85
|
+
let draft;
|
|
86
|
+
let pick;
|
|
87
|
+
try {
|
|
88
|
+
draft = draftForPage(args.record, pageId);
|
|
89
|
+
pick = pickForPage(args.record, pageId);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new InvalidRedesignPageOwnershipError(pageId, error instanceof Error ? error.message : String(error));
|
|
93
|
+
}
|
|
94
|
+
const paths = draftPaths(draft.runDir);
|
|
95
|
+
const draftRecord = await readJson(paths.draftRecord, pageId, 'draft record');
|
|
96
|
+
if (!validDraftRecord(draftRecord)) {
|
|
97
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft record has no complete hosted/source identity');
|
|
98
|
+
}
|
|
99
|
+
if (!samePath(draftRecord.runDir, draft.runDir)) {
|
|
100
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft record runDir differs from parent draft entry');
|
|
101
|
+
}
|
|
102
|
+
if (draftRecord.hosted.runId !== draft.runId) {
|
|
103
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft record hosted run ID differs from parent draft entry');
|
|
104
|
+
}
|
|
105
|
+
const source = draftRecord.redesignSource;
|
|
106
|
+
if (!source) {
|
|
107
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft record has no redesign source');
|
|
108
|
+
}
|
|
109
|
+
if (source.sha256 !== args.page.sha256) {
|
|
110
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft source digest differs from captured page');
|
|
111
|
+
}
|
|
112
|
+
if (!samePath(source.path, args.page.path)) {
|
|
113
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft source path differs from captured page');
|
|
114
|
+
}
|
|
115
|
+
if (args.page.id !== primary.id && draftRecord.pageRoute !== args.page.url) {
|
|
116
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'non-primary draft route differs from captured page URL');
|
|
117
|
+
}
|
|
118
|
+
if (args.page.id === primary.id && draftRecord.pageRoute !== undefined
|
|
119
|
+
&& draftRecord.pageRoute !== args.page.url) {
|
|
120
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'primary draft route differs from captured page URL');
|
|
121
|
+
}
|
|
122
|
+
const candidate = draftRecord.hosted.candidates.find((entry) => (isRecord(entry)
|
|
123
|
+
&& entry.id === pick.slot
|
|
124
|
+
&& typeof entry.slot === 'string'
|
|
125
|
+
&& typeof entry.outputPath === 'string'));
|
|
126
|
+
if (!candidate || typeof candidate.slot !== 'string' || typeof candidate.outputPath !== 'string') {
|
|
127
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'selected slot is absent from the draft run');
|
|
128
|
+
}
|
|
129
|
+
await assertOwnedCandidatePath({
|
|
130
|
+
pageId,
|
|
131
|
+
candidatePath: candidate.outputPath,
|
|
132
|
+
candidatesDirectory: paths.candidatesDir,
|
|
133
|
+
});
|
|
134
|
+
const completion = await readJson(paths.completion, pageId, 'draft completion');
|
|
135
|
+
if (!validDraftCompletion(completion)) {
|
|
136
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft completion has no run ID or candidates');
|
|
137
|
+
}
|
|
138
|
+
if (completion.runId !== draft.runId) {
|
|
139
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'draft completion run ID differs from parent draft entry');
|
|
140
|
+
}
|
|
141
|
+
const materialized = completion.candidates.find((entry) => (isRecord(entry)
|
|
142
|
+
&& entry.slot === candidate.slot
|
|
143
|
+
&& typeof entry.path === 'string'
|
|
144
|
+
&& typeof entry.sha256 === 'string'
|
|
145
|
+
&& typeof entry.bytes === 'number'
|
|
146
|
+
&& Number.isInteger(entry.bytes)
|
|
147
|
+
&& entry.bytes >= 0));
|
|
148
|
+
if (!materialized) {
|
|
149
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'selected draft candidate is not materialized');
|
|
150
|
+
}
|
|
151
|
+
if (!samePath(materialized.path, candidate.outputPath)
|
|
152
|
+
|| !samePath(pick.imagePath, candidate.outputPath)
|
|
153
|
+
|| materialized.sha256 !== pick.sha256) {
|
|
154
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'parent pick or completion differs from this draft run\'s selected DraftCandidateRecord.outputPath');
|
|
155
|
+
}
|
|
156
|
+
const onDisk = await materializedCandidateDigest({ file: candidate.outputPath, pageId });
|
|
157
|
+
if (onDisk.sha256 !== materialized.sha256 || onDisk.sha256 !== pick.sha256
|
|
158
|
+
|| onDisk.bytes !== materialized.bytes) {
|
|
159
|
+
throw new InvalidRedesignPageOwnershipError(pageId, 'selected DraftCandidateRecord.outputPath digest or byte count differs from its completion/pick proof');
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
page: args.page,
|
|
163
|
+
draft,
|
|
164
|
+
pick,
|
|
165
|
+
winner: {
|
|
166
|
+
path: candidate.outputPath,
|
|
167
|
+
sha256: onDisk.sha256,
|
|
168
|
+
bytes: onDisk.bytes,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
/** Validate every pending branch input before any page is allowed to dispatch. */
|
|
173
|
+
export const verifyRedesignPageBranchInputs = async (args) => Promise.all(args.pages.map((page) => verifyRedesignPageBranchInput({ record: args.record, page })));
|
|
174
|
+
//# sourceMappingURL=redesign-page-ownership.js.map
|