ultra-builder-pro-cli 0.24.2 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/adapters/claude.js +104 -0
- package/adapters/codex.js +363 -40
- package/adapters/grok.js +1254 -58
- package/adapters/kimi.js +682 -13
- package/adapters/opencode.js +132 -11
- package/docs/AGENT-CONTEXT.md +14 -0
- package/docs/ARTIFACT-AUTHORITY.md +13 -0
- package/docs/COMMIT-HASH-BACKFILL.md +25 -18
- package/docs/RUNTIME-COMPAT-MATRIX.md +18 -2
- package/mcp-server/lib/artifact-registry.cjs +181 -51
- package/mcp-server/lib/baseline-workflow.cjs +160 -23
- package/mcp-server/lib/change-workflow.cjs +21 -16
- package/mcp-server/lib/context-envelope.cjs +124 -51
- package/mcp-server/lib/decision-records.cjs +164 -78
- package/mcp-server/lib/kernel-change-workflow.cjs +2029 -0
- package/mcp-server/lib/legacy-change-workflow.cjs +294 -71
- package/mcp-server/lib/managed-file-write.cjs +56 -0
- package/mcp-server/lib/prd-parser.cjs +2 -2
- package/mcp-server/lib/runtime-paths.cjs +43 -16
- package/mcp-server/lib/stage-checkpoints.cjs +163 -9
- package/mcp-server/lib/state-db.cjs +186 -8
- package/mcp-server/lib/state-ops.cjs +214 -31
- package/mcp-server/lib/task-contract.cjs +404 -0
- package/mcp-server/lib/task-expander.cjs +2 -2
- package/mcp-server/lib/task-ledger.cjs +175 -62
- package/mcp-server/lib/ultra-facade.cjs +1786 -169
- package/mcp-server/lib/worker-packet.cjs +2 -2
- package/mcp-server/server.cjs +23 -4
- package/orchestrator/parallel-orchestrator.cjs +114 -27
- package/orchestrator/session-runner.cjs +25 -136
- package/package.json +1 -1
- package/skills/ultra-change/SKILL.md +6 -1
- package/skills/ultra-dev/SKILL.md +9 -4
- package/skills/ultra-plan/SKILL.md +13 -4
- package/skills/ultra-plan/references/semantic-preflight.md +6 -4
- package/spec/mcp-tools.yaml +114 -9
- package/spec/schemas/context-file.v4.5.schema.json +2 -2
- package/spec/schemas/state-db.sql +10 -6
- package/spec/schemas/task-ledger.v2.schema.json +23 -8
- package/spec/schemas/tasks.v4.5.schema.json +34 -12
|
@@ -109,8 +109,64 @@ function writeManagedJson(rootDir, relativePath, value) {
|
|
|
109
109
|
return writeManagedFile(rootDir, relativePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
function snapshotManagedFile(rootDir, relativePath) {
|
|
113
|
+
const relative = normalizeProjectRelative(relativePath);
|
|
114
|
+
const directoryRelative = path.posix.dirname(relative);
|
|
115
|
+
const basename = path.posix.basename(relative);
|
|
116
|
+
const prepared = ensureDirectory(rootDir, directoryRelative);
|
|
117
|
+
const target = path.join(prepared.directory, basename);
|
|
118
|
+
const stat = fs.lstatSync(target, { throwIfNoEntry: false });
|
|
119
|
+
if (!stat) return { path: relative, existed: false, bytes: null };
|
|
120
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
121
|
+
throw new ManagedFileWriteError(
|
|
122
|
+
'PATH_AUTHORITY_VIOLATION',
|
|
123
|
+
`managed artifact target is unsafe: ${relative}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
path: relative,
|
|
128
|
+
existed: true,
|
|
129
|
+
bytes: fs.readFileSync(target),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function removeManagedFile(rootDir, relativePath) {
|
|
134
|
+
const relative = normalizeProjectRelative(relativePath);
|
|
135
|
+
const directoryRelative = path.posix.dirname(relative);
|
|
136
|
+
const basename = path.posix.basename(relative);
|
|
137
|
+
const prepared = ensureDirectory(rootDir, directoryRelative);
|
|
138
|
+
const target = path.join(prepared.directory, basename);
|
|
139
|
+
const stat = fs.lstatSync(target, { throwIfNoEntry: false });
|
|
140
|
+
if (!stat) return false;
|
|
141
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
142
|
+
throw new ManagedFileWriteError(
|
|
143
|
+
'PATH_AUTHORITY_VIOLATION',
|
|
144
|
+
`managed artifact rollback target is unsafe: ${relative}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
fs.unlinkSync(target);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function restoreManagedFile(rootDir, snapshot) {
|
|
152
|
+
if (!snapshot || typeof snapshot.path !== 'string') {
|
|
153
|
+
throw new ManagedFileWriteError(
|
|
154
|
+
'PATH_AUTHORITY_VIOLATION',
|
|
155
|
+
'managed artifact rollback snapshot is invalid',
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (snapshot.existed) {
|
|
159
|
+
return writeManagedFile(rootDir, snapshot.path, snapshot.bytes);
|
|
160
|
+
}
|
|
161
|
+
removeManagedFile(rootDir, snapshot.path);
|
|
162
|
+
return { path: snapshot.path, removed: true };
|
|
163
|
+
}
|
|
164
|
+
|
|
112
165
|
module.exports = {
|
|
113
166
|
ManagedFileWriteError,
|
|
167
|
+
removeManagedFile,
|
|
168
|
+
restoreManagedFile,
|
|
169
|
+
snapshotManagedFile,
|
|
114
170
|
writeManagedFile,
|
|
115
171
|
writeManagedJson,
|
|
116
172
|
};
|
|
@@ -19,8 +19,8 @@ const TASK_LIST_SCHEMA = Object.freeze({
|
|
|
19
19
|
properties: {
|
|
20
20
|
id: { type: 'string', pattern: '^[a-zA-Z0-9_\\-]+$' },
|
|
21
21
|
title: { type: 'string', minLength: 3, maxLength: 200 },
|
|
22
|
-
type: { type: 'string',
|
|
23
|
-
priority: { type: 'string',
|
|
22
|
+
type: { type: 'string', minLength: 1, maxLength: 80 },
|
|
23
|
+
priority: { type: 'string', minLength: 1, maxLength: 80 },
|
|
24
24
|
complexity: { type: 'integer', minimum: 1, maximum: 10 },
|
|
25
25
|
deps: { type: 'array', items: { type: 'string' } },
|
|
26
26
|
files_modified: { type: 'array', items: { type: 'string' } },
|
|
@@ -599,15 +599,37 @@ function processIsAlive(pid) {
|
|
|
599
599
|
|
|
600
600
|
function readMigrationGate(gatePath) {
|
|
601
601
|
const stat = lstatOrNull(gatePath);
|
|
602
|
-
if (!stat
|
|
602
|
+
if (!stat) return { missing: true };
|
|
603
|
+
if (stat.isSymbolicLink() || !stat.isFile()) return null;
|
|
603
604
|
try {
|
|
604
605
|
const owner = JSON.parse(fs.readFileSync(gatePath, 'utf8'));
|
|
605
606
|
return { owner, stat };
|
|
606
|
-
} catch {
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (error.code === 'ENOENT') return { missing: true };
|
|
607
609
|
return null;
|
|
608
610
|
}
|
|
609
611
|
}
|
|
610
612
|
|
|
613
|
+
function createMigrationGateCandidate(gatePath, owner) {
|
|
614
|
+
while (true) {
|
|
615
|
+
const candidatePath = `${gatePath}.candidate-${process.pid}-${randomUUID()}`;
|
|
616
|
+
let descriptor;
|
|
617
|
+
try {
|
|
618
|
+
descriptor = fs.openSync(candidatePath, 'wx', 0o600);
|
|
619
|
+
fs.writeFileSync(descriptor, `${JSON.stringify(owner)}\n`);
|
|
620
|
+
fs.fsyncSync(descriptor);
|
|
621
|
+
return { candidatePath, descriptor };
|
|
622
|
+
} catch (error) {
|
|
623
|
+
if (descriptor !== undefined) {
|
|
624
|
+
try { fs.closeSync(descriptor); } catch { /* best effort */ }
|
|
625
|
+
}
|
|
626
|
+
fs.rmSync(candidatePath, { force: true });
|
|
627
|
+
if (error.code === 'EEXIST') continue;
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
611
633
|
function reclaimDeadMigrationGate(gatePath, observed) {
|
|
612
634
|
const quarantine = `${gatePath}.stale-${process.pid}-${randomUUID()}`;
|
|
613
635
|
fs.renameSync(gatePath, quarantine);
|
|
@@ -635,28 +657,28 @@ function acquireStateMigrationGate(paths, {
|
|
|
635
657
|
} = {}) {
|
|
636
658
|
const gatePath = path.join(paths.runtimeDir, 'state-migration.lock');
|
|
637
659
|
const token = randomUUID();
|
|
660
|
+
const owner = {
|
|
661
|
+
version: 2,
|
|
662
|
+
pid: process.pid,
|
|
663
|
+
owner_started_at: processStartMarker(process.pid),
|
|
664
|
+
token,
|
|
665
|
+
legacy_state_db: paths.legacyStateDbPath,
|
|
666
|
+
runtime_state_db: paths.stateDbPath,
|
|
667
|
+
};
|
|
638
668
|
const deadline = Date.now() + timeoutMs;
|
|
639
669
|
let descriptor;
|
|
640
670
|
while (descriptor === undefined) {
|
|
671
|
+
const candidate = createMigrationGateCandidate(gatePath, owner);
|
|
672
|
+
let published = false;
|
|
641
673
|
try {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
pid: process.pid,
|
|
646
|
-
owner_started_at: processStartMarker(process.pid),
|
|
647
|
-
token,
|
|
648
|
-
legacy_state_db: paths.legacyStateDbPath,
|
|
649
|
-
runtime_state_db: paths.stateDbPath,
|
|
650
|
-
})}\n`);
|
|
651
|
-
fs.fsyncSync(descriptor);
|
|
674
|
+
fs.linkSync(candidate.candidatePath, gatePath);
|
|
675
|
+
descriptor = candidate.descriptor;
|
|
676
|
+
published = true;
|
|
652
677
|
break;
|
|
653
678
|
} catch (error) {
|
|
654
|
-
if (descriptor !== undefined) {
|
|
655
|
-
try { fs.closeSync(descriptor); } catch { /* best effort */ }
|
|
656
|
-
descriptor = undefined;
|
|
657
|
-
}
|
|
658
679
|
if (error.code !== 'EEXIST') throw error;
|
|
659
680
|
const observed = readMigrationGate(gatePath);
|
|
681
|
+
if (observed?.missing) continue;
|
|
660
682
|
const ownerPid = Number(observed?.owner?.pid);
|
|
661
683
|
const live = processIsAlive(ownerPid);
|
|
662
684
|
const currentStart = live ? processStartMarker(ownerPid) : null;
|
|
@@ -684,6 +706,11 @@ function acquireStateMigrationGate(paths, {
|
|
|
684
706
|
continue;
|
|
685
707
|
}
|
|
686
708
|
reclaimDeadMigrationGate(gatePath, observed);
|
|
709
|
+
} finally {
|
|
710
|
+
fs.rmSync(candidate.candidatePath, { force: true });
|
|
711
|
+
if (!published) {
|
|
712
|
+
try { fs.closeSync(candidate.descriptor); } catch { /* best effort */ }
|
|
713
|
+
}
|
|
687
714
|
}
|
|
688
715
|
}
|
|
689
716
|
return () => {
|
|
@@ -55,10 +55,10 @@ function normalizeDiagnostics(value) {
|
|
|
55
55
|
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
56
56
|
throw new StageCheckpointError('VALIDATION_ERROR', `diagnostics[${index}] must be an object`);
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
if (!code) {
|
|
58
|
+
if (typeof item.code !== 'string' || !item.code.trim()) {
|
|
60
59
|
throw new StageCheckpointError('VALIDATION_ERROR', `diagnostics[${index}].code is required`);
|
|
61
60
|
}
|
|
61
|
+
const code = item.code.trim();
|
|
62
62
|
const severity = item.severity || 'needs_attention';
|
|
63
63
|
if (!['warning', 'needs_attention', 'hard_conflict'].includes(severity)) {
|
|
64
64
|
throw new StageCheckpointError(
|
|
@@ -66,15 +66,79 @@ function normalizeDiagnostics(value) {
|
|
|
66
66
|
`diagnostics[${index}].severity is invalid`,
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
|
+
if (item.message !== undefined && typeof item.message !== 'string') {
|
|
70
|
+
throw new StageCheckpointError(
|
|
71
|
+
'VALIDATION_ERROR',
|
|
72
|
+
`diagnostics[${index}].message must be a string`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
69
75
|
return {
|
|
70
76
|
code,
|
|
71
77
|
severity,
|
|
72
|
-
...(item.message ? { message:
|
|
78
|
+
...(item.message ? { message: item.message } : {}),
|
|
73
79
|
...(item.details === undefined ? {} : { details: item.details }),
|
|
74
80
|
};
|
|
75
81
|
});
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
function normalizePayload(value) {
|
|
85
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
86
|
+
throw new StageCheckpointError('VALIDATION_ERROR', 'checkpoint payload must be an object');
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeEvidence(value) {
|
|
92
|
+
if (!Array.isArray(value)) {
|
|
93
|
+
throw new StageCheckpointError('VALIDATION_ERROR', 'evidence must be an array');
|
|
94
|
+
}
|
|
95
|
+
return value.map((item, index) => {
|
|
96
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
97
|
+
throw new StageCheckpointError('VALIDATION_ERROR', `evidence[${index}] must be an object`);
|
|
98
|
+
}
|
|
99
|
+
return item;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function idempotencyConflict(idempotencyKey, priorDigest, requestedDigest) {
|
|
104
|
+
return new StageCheckpointError(
|
|
105
|
+
'IDEMPOTENCY_KEY_CONFLICT',
|
|
106
|
+
`idempotency key ${idempotencyKey} was already used for another checkpoint request`,
|
|
107
|
+
{
|
|
108
|
+
idempotency_key: idempotencyKey,
|
|
109
|
+
prior_request_digest: priorDigest,
|
|
110
|
+
requested_request_digest: requestedDigest,
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function draftRequestDigest({
|
|
116
|
+
stage,
|
|
117
|
+
scopeType,
|
|
118
|
+
scopeId,
|
|
119
|
+
payload,
|
|
120
|
+
}) {
|
|
121
|
+
const requestPayload = stage === 'plan' && payload && typeof payload === 'object'
|
|
122
|
+
? Object.fromEntries(Object.entries(payload).filter(([key]) => key !== 'plan'))
|
|
123
|
+
: payload;
|
|
124
|
+
return canonical.digest({
|
|
125
|
+
operation: 'checkpoint:draft',
|
|
126
|
+
stage,
|
|
127
|
+
scope_type: scopeType,
|
|
128
|
+
scope_id: scopeId,
|
|
129
|
+
payload: requestPayload,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function checkpointDraftRequestDigest(checkpoint) {
|
|
134
|
+
return draftRequestDigest({
|
|
135
|
+
stage: checkpoint.stage,
|
|
136
|
+
scopeType: checkpoint.scope_type,
|
|
137
|
+
scopeId: checkpoint.scope_id,
|
|
138
|
+
payload: checkpoint.payload,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
78
142
|
function checkpointDigest(value) {
|
|
79
143
|
return canonical.digest({
|
|
80
144
|
stage: value.stage,
|
|
@@ -151,6 +215,28 @@ function existingByIdempotency(db, idempotencyKey) {
|
|
|
151
215
|
);
|
|
152
216
|
}
|
|
153
217
|
|
|
218
|
+
function findIdempotentDraft(db, input = {}) {
|
|
219
|
+
validateStage(input.stage);
|
|
220
|
+
const scope = normalizeScope(input.scope);
|
|
221
|
+
const idempotencyKey = String(input.idempotency_key || '').trim();
|
|
222
|
+
if (!idempotencyKey) {
|
|
223
|
+
throw new StageCheckpointError('VALIDATION_ERROR', 'idempotency_key is required');
|
|
224
|
+
}
|
|
225
|
+
const existing = existingByIdempotency(db, idempotencyKey);
|
|
226
|
+
if (!existing) return null;
|
|
227
|
+
const requestedDigest = draftRequestDigest({
|
|
228
|
+
stage: input.stage,
|
|
229
|
+
scopeType: scope.type,
|
|
230
|
+
scopeId: scope.id,
|
|
231
|
+
payload: normalizePayload(input.payload),
|
|
232
|
+
});
|
|
233
|
+
const priorDigest = checkpointDraftRequestDigest(existing);
|
|
234
|
+
if (priorDigest !== requestedDigest) {
|
|
235
|
+
throw idempotencyConflict(idempotencyKey, priorDigest, requestedDigest);
|
|
236
|
+
}
|
|
237
|
+
return existing;
|
|
238
|
+
}
|
|
239
|
+
|
|
154
240
|
function saveDraft(db, input = {}) {
|
|
155
241
|
validateStage(input.stage);
|
|
156
242
|
const scope = normalizeScope(input.scope);
|
|
@@ -158,12 +244,23 @@ function saveDraft(db, input = {}) {
|
|
|
158
244
|
if (!idempotencyKey) {
|
|
159
245
|
throw new StageCheckpointError('VALIDATION_ERROR', 'idempotency_key is required');
|
|
160
246
|
}
|
|
247
|
+
const payload = normalizePayload(input.payload);
|
|
248
|
+
const evidence = normalizeEvidence(input.evidence);
|
|
249
|
+
const diagnostics = normalizeDiagnostics(input.diagnostics);
|
|
250
|
+
const requestedDigest = draftRequestDigest({
|
|
251
|
+
stage: input.stage,
|
|
252
|
+
scopeType: scope.type,
|
|
253
|
+
scopeId: scope.id,
|
|
254
|
+
payload,
|
|
255
|
+
});
|
|
161
256
|
const existingRetry = existingByIdempotency(db, idempotencyKey);
|
|
162
|
-
if (existingRetry)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
257
|
+
if (existingRetry) {
|
|
258
|
+
const priorDigest = checkpointDraftRequestDigest(existingRetry);
|
|
259
|
+
if (priorDigest !== requestedDigest) {
|
|
260
|
+
throw idempotencyConflict(idempotencyKey, priorDigest, requestedDigest);
|
|
261
|
+
}
|
|
262
|
+
return existingRetry;
|
|
263
|
+
}
|
|
167
264
|
return ops.tx(db, () => {
|
|
168
265
|
const draft = db.prepare(
|
|
169
266
|
`SELECT * FROM stage_checkpoints
|
|
@@ -241,6 +338,33 @@ function saveDraft(db, input = {}) {
|
|
|
241
338
|
});
|
|
242
339
|
}
|
|
243
340
|
|
|
341
|
+
function acceptanceReceipt(db, idempotencyKey) {
|
|
342
|
+
if (!idempotencyKey) return null;
|
|
343
|
+
const rows = db.prepare(
|
|
344
|
+
`SELECT payload_json FROM events
|
|
345
|
+
WHERE type = 'ultra_checkpoint_accepted'
|
|
346
|
+
ORDER BY id DESC`,
|
|
347
|
+
).all();
|
|
348
|
+
for (const row of rows) {
|
|
349
|
+
const payload = parseJson(row.payload_json, {});
|
|
350
|
+
if (payload.idempotency_key === idempotencyKey) return payload;
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function acceptanceReceiptForCheckpoint(db, checkpointId) {
|
|
356
|
+
const rows = db.prepare(
|
|
357
|
+
`SELECT payload_json FROM events
|
|
358
|
+
WHERE type = 'ultra_checkpoint_accepted'
|
|
359
|
+
ORDER BY id DESC`,
|
|
360
|
+
).all();
|
|
361
|
+
for (const row of rows) {
|
|
362
|
+
const payload = parseJson(row.payload_json, {});
|
|
363
|
+
if (payload.checkpoint_id === checkpointId && payload.idempotency_key) return payload;
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
244
368
|
function acceptDraft(db, input = {}) {
|
|
245
369
|
const idempotencyKey = String(input.idempotency_key || '').trim();
|
|
246
370
|
if (!idempotencyKey) {
|
|
@@ -249,7 +373,34 @@ function acceptDraft(db, input = {}) {
|
|
|
249
373
|
return ops.tx(db, () => {
|
|
250
374
|
const row = db.prepare('SELECT * FROM stage_checkpoints WHERE id = ?').get(input.id);
|
|
251
375
|
if (!row) throw new StageCheckpointError('CHECKPOINT_NOT_FOUND', `checkpoint not found: ${input.id}`);
|
|
252
|
-
|
|
376
|
+
const checkpoint = rowToCheckpoint(row);
|
|
377
|
+
const requestedDigest = canonical.digest({
|
|
378
|
+
operation: 'checkpoint:accept',
|
|
379
|
+
checkpoint_id: checkpoint.id,
|
|
380
|
+
checkpoint_digest: checkpoint.digest,
|
|
381
|
+
});
|
|
382
|
+
const prior = acceptanceReceipt(db, idempotencyKey);
|
|
383
|
+
if (prior) {
|
|
384
|
+
if (prior.request_digest !== requestedDigest) {
|
|
385
|
+
throw idempotencyConflict(
|
|
386
|
+
idempotencyKey,
|
|
387
|
+
prior.request_digest || null,
|
|
388
|
+
requestedDigest,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return checkpoint;
|
|
392
|
+
}
|
|
393
|
+
if (row.status === 'accepted') {
|
|
394
|
+
const checkpointReceipt = acceptanceReceiptForCheckpoint(db, checkpoint.id);
|
|
395
|
+
if (checkpointReceipt) {
|
|
396
|
+
throw idempotencyConflict(
|
|
397
|
+
idempotencyKey,
|
|
398
|
+
checkpointReceipt.request_digest || null,
|
|
399
|
+
requestedDigest,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return checkpoint;
|
|
403
|
+
}
|
|
253
404
|
if (row.status !== 'draft') {
|
|
254
405
|
throw new StageCheckpointError(
|
|
255
406
|
'CHECKPOINT_NOT_MUTABLE',
|
|
@@ -279,6 +430,8 @@ function acceptDraft(db, input = {}) {
|
|
|
279
430
|
stage: accepted.stage,
|
|
280
431
|
revision: accepted.revision,
|
|
281
432
|
digest: accepted.digest,
|
|
433
|
+
idempotency_key: idempotencyKey,
|
|
434
|
+
request_digest: requestedDigest,
|
|
282
435
|
},
|
|
283
436
|
});
|
|
284
437
|
return accepted;
|
|
@@ -303,6 +456,7 @@ module.exports = {
|
|
|
303
456
|
readCheckpoint,
|
|
304
457
|
listCheckpoints,
|
|
305
458
|
saveDraft,
|
|
459
|
+
findIdempotentDraft,
|
|
306
460
|
acceptDraft,
|
|
307
461
|
currentCheckpoint,
|
|
308
462
|
checkpointDigest,
|