@zq-silk/yui 0.6.12 → 0.6.14
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/README.md +34 -5
- package/dist/cli/commandCatalog.js +173 -57
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli.js +99 -11
- package/dist/commands/configCommands.js +521 -171
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +56 -3
- package/dist/commands/projectCommands.js +504 -2
- package/dist/commands/releaseCommands.js +0 -1
- package/dist/commands/taskActor.js +17 -0
- package/dist/commands/taskBaseCommands.js +29 -0
- package/dist/commands/taskCommands.js +577 -126
- package/dist/commands/taskContextCommand.js +36 -2
- package/dist/commands/taskNextActionCommand.js +48 -3
- package/dist/commands/taskPublicationCommands.js +319 -0
- package/dist/commands/taskRoleRuntimeStatus.js +83 -59
- package/dist/commands/telemetryCommands.js +14 -13
- package/dist/config/yuiConfig.js +161 -8
- package/dist/context/sessionContextBudget.js +68 -0
- package/dist/context/wakeNotification.js +65 -0
- package/dist/controller/clientRuntime.js +2 -1
- package/dist/controller/ephemeralResourceReaper.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +183 -16
- package/dist/controller/jobSupervisor.js +5 -4
- package/dist/controller/resourceCleanupLinux.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +3 -3
- package/dist/controller/runtime.js +88 -10
- package/dist/controller/updateReconciliation.js +4 -3
- package/dist/doctor/doctor.js +26 -7
- package/dist/executor/agentConfigurationCatalog.js +18 -0
- package/dist/executor/fileRoleLaunchPlanner.js +3 -3
- package/dist/lifecycle/contextBudgetRollover.js +81 -0
- package/dist/lifecycle/exactRunTerminalization.js +11 -1
- package/dist/lifecycle/providerErrorClass.js +33 -12
- package/dist/observability/executionAudit.js +214 -6
- package/dist/output/table.js +18 -0
- package/dist/repository/gitWorkspace.js +92 -0
- package/dist/repository/project.js +218 -4
- package/dist/repository/taskBaseFreshness.js +318 -0
- package/dist/repository/taskWorkspacePreparer.js +16 -2
- package/dist/review/deltaRecheck.js +232 -0
- package/dist/review/reviewConfig.js +31 -0
- package/dist/review/reviewFindingLedger.js +5 -1
- package/dist/review/reviewRound.js +156 -1
- package/dist/run/providerRetry.js +21 -3
- package/dist/run/providerRetryConfig.js +13 -60
- package/dist/run/recoveryProjection.js +199 -0
- package/dist/runtime/builtinAgentDrivers.js +3 -0
- package/dist/runtime/builtinTranscriptUsage.js +76 -32
- package/dist/runtime/continuationManager.js +17 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/launchDiagnostics.js +154 -0
- package/dist/runtime/lifecycleReservation.js +13 -0
- package/dist/runtime/providerContinuation.js +38 -0
- package/dist/runtime/providerContinuationReconciliationService.js +1 -0
- package/dist/runtime/providerErrorCodes.js +278 -0
- package/dist/runtime/runtimeHealthPolicy.js +20 -0
- package/dist/runtime/runtimeObservation.js +1 -0
- package/dist/runtime/runtimeProjection.js +115 -23
- package/dist/runtime/tmuxAdapters.js +242 -48
- package/dist/scheduler/activeRoleRunDelivery.js +139 -3
- package/dist/scheduler/activeTaskProgress.js +4 -3
- package/dist/scheduler/leaderWakeupProcessor.js +105 -15
- package/dist/scheduler/roleRunLiveness.js +2 -1
- package/dist/scheduler/roleRunStall.js +3 -2
- package/dist/scheduler/taskWake.js +72 -0
- package/dist/scheduler/wakeReason.js +64 -0
- package/dist/scheduler/wakeupQueue.js +2 -1
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/migration/productionRegistry.js +325 -1
- package/dist/storage/sqliteSchema.js +61 -2
- package/dist/storage/sqliteStore.js +129 -2
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/taskStore.js +262 -5
- package/dist/storage/upgrade/recordVersions.js +6 -1
- package/dist/storage/upgrade/sqliteFileSet.js +50 -0
- package/dist/storage/upgrade/sqliteMigrationTarget.js +6 -4
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +6 -4
- package/dist/storage/upgrade/sqliteStateMigration.js +22 -2
- package/dist/task/completionReadiness.js +282 -0
- package/dist/task/publicationReference.js +123 -0
- package/dist/task/taskRecordReference.js +3 -1
- package/dist/telemetry/telemetryConfig.js +23 -18
- package/dist/telemetry/telemetryWiring.js +8 -8
- package/dist/tmux/tmuxManager.js +50 -9
- package/dist/web/assets/client/i18n.js +4 -0
- package/dist/web/assets/client/view.js +18 -0
- package/dist/web/webSnapshot.js +100 -10
- package/i18n/README.zh-CN.md +5 -5
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +49 -10
- package/skills/yui-operator/SKILL.md +13 -3
- package/skills/yui-worker/SKILL.md +8 -0
|
@@ -5,6 +5,7 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
5
5
|
import { validateConfiguredAgent } from "../agent/agent.js";
|
|
6
6
|
import { validateCapabilityGrant } from "../grant/capabilityGrant.js";
|
|
7
7
|
import { validateReleaseWorkflow } from "../release/releaseWorkflow.js";
|
|
8
|
+
import { publicationExternalKey, validatePublicationReference } from "../task/publicationReference.js";
|
|
8
9
|
import { reconciliationIntervalMilliseconds, resolveLeaderNextActionMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode } from "../config/yuiConfig.js";
|
|
9
10
|
import { resolveTimeZone } from "../output/timePresentation.js";
|
|
10
11
|
import { mailboxBatches, consumePendingBatch, mailboxHasWork, mailboxTargetKey, pendingLane, validateWorkMailbox } from "../coordination/workMailbox.js";
|
|
@@ -16,6 +17,7 @@ import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from
|
|
|
16
17
|
import { FileSessionOwnerRegistry } from "../runtime/sessionOwnerRegistry.js";
|
|
17
18
|
import { validateReviewConfig } from "../review/reviewConfig.js";
|
|
18
19
|
import { validateReviewRound } from "../review/reviewRound.js";
|
|
20
|
+
import { reviewFindingLedgerMode } from "../review/reviewFindingLedger.js";
|
|
19
21
|
import { sameTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
|
|
20
22
|
import { assertProjectCatalog, validateProject } from "../repository/project.js";
|
|
21
23
|
import { generateHomeIdentity, validateHomeIdentity } from "../repository/homeIdentity.js";
|
|
@@ -27,6 +29,7 @@ import { CURRENT_DURABLE_JOB_SCHEMA_VERSION, validDurableJobTransition, validate
|
|
|
27
29
|
import { validateGlobalRole, validateTaskRole } from "../role/role.js";
|
|
28
30
|
import { CURRENT_LEADER_FAILURE_SCHEMA_VERSION } from "../scheduler/leaderFailure.js";
|
|
29
31
|
import { CURRENT_OPERATOR_NOTIFICATION_SCHEMA_VERSION } from "../scheduler/operatorNotification.js";
|
|
32
|
+
import { CURRENT_TASK_WAKE_SCHEMA_VERSION, validateTaskWake } from "../scheduler/taskWake.js";
|
|
30
33
|
import { validateTask } from "../task/task.js";
|
|
31
34
|
import { formatAgentRunReceiptId, TASK_RECORD_ID_PREFIXES, validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
32
35
|
import { workItemExecutionGroupById, validateWorkItem } from "../workItem/workItem.js";
|
|
@@ -51,7 +54,7 @@ export const CURRENT_ACTIVE_RUN_POINTER_SCHEMA_VERSION = 3;
|
|
|
51
54
|
* below, but are not independent record-axis families.
|
|
52
55
|
*/
|
|
53
56
|
export const CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION = 2;
|
|
54
|
-
export const CURRENT_PROJECT_SCHEMA_VERSION =
|
|
57
|
+
export const CURRENT_PROJECT_SCHEMA_VERSION = 4;
|
|
55
58
|
export const CURRENT_AGENT_PROFILE_SCHEMA_VERSION = 2;
|
|
56
59
|
export const CURRENT_GLOBAL_ROLE_SCHEMA_VERSION = 3;
|
|
57
60
|
export const CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION = 3;
|
|
@@ -71,6 +74,7 @@ export const CURRENT_EVENT_SCHEMA_VERSION = 2;
|
|
|
71
74
|
export const CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION = 1;
|
|
72
75
|
export const CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION = 1;
|
|
73
76
|
export const CURRENT_WORK_MAILBOX_SCHEMA_VERSION = 2;
|
|
77
|
+
export const CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION = 1;
|
|
74
78
|
export const CURRENT_ROLE_AGENT_SESSION_SCHEMA_VERSION = 3;
|
|
75
79
|
export const CURRENT_PENDING_WAKEUP_SCHEMA_VERSION = 1;
|
|
76
80
|
const STORAGE_LOCK_DIRECTORY = ".state.lock";
|
|
@@ -104,7 +108,7 @@ export function executionLaneActiveRunKeyParts(key) {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
/** The schema version of each persisted `state.json#/tasks/*` aggregate. */
|
|
107
|
-
export const CURRENT_STORED_TASK_SCHEMA_VERSION =
|
|
111
|
+
export const CURRENT_STORED_TASK_SCHEMA_VERSION = 17;
|
|
108
112
|
/**
|
|
109
113
|
* Persisted nested-record versions consumed by this store's strict parser.
|
|
110
114
|
* Keep these named at the storage boundary so the upgrade record-axis map can
|
|
@@ -406,7 +410,8 @@ export class FileTaskStore {
|
|
|
406
410
|
task: {
|
|
407
411
|
id: aggregate.task.id,
|
|
408
412
|
status: aggregate.task.status,
|
|
409
|
-
projectBindings: aggregate.task.projectBindings
|
|
413
|
+
projectBindings: aggregate.task.projectBindings,
|
|
414
|
+
requireIntegration: aggregate.task.requireIntegration
|
|
410
415
|
},
|
|
411
416
|
workItems: values(aggregate.workItems, "id"),
|
|
412
417
|
changeSets: values(aggregate.changeSets, "id"),
|
|
@@ -419,6 +424,31 @@ export class FileTaskStore {
|
|
|
419
424
|
leaderRuns: agentRuns.filter((run) => run.roleName === "leader")
|
|
420
425
|
};
|
|
421
426
|
}
|
|
427
|
+
readCompletionReadinessFacts(taskId) {
|
|
428
|
+
const base = this.readNextActionFacts(taskId);
|
|
429
|
+
if (base === null)
|
|
430
|
+
return null;
|
|
431
|
+
const aggregate = this.#state().tasks[taskId];
|
|
432
|
+
const ledgerMode = reviewFindingLedgerMode(this.#state().config);
|
|
433
|
+
// The file-rollback backend does not persist the finding ledger. Under
|
|
434
|
+
// `enforce` the completion gate must fail closed with the same bounded
|
|
435
|
+
// diagnosis as `listReviewFindings` instead of silently treating
|
|
436
|
+
// "unsupported" as "no open findings".
|
|
437
|
+
if (ledgerMode === "enforce") {
|
|
438
|
+
throw new StorageRecordError(`Review findings require the SQLite backend (yui.db); migrate this Home with \`yui update\` before using the finding ledger on Task ${taskId}.`);
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
...base,
|
|
442
|
+
managedWorkspaces: values(aggregate.managedWorkspaces, (workspace) => managedWorkspaceKey(workspace.owner)),
|
|
443
|
+
durableJobs: values(aggregate.durableJobs, "id"),
|
|
444
|
+
integrationQueueEntries: values(aggregate.integrationQueue, "id"),
|
|
445
|
+
// The SQLite backend supplies real findings; the file backend never
|
|
446
|
+
// reaches this point under `enforce` (throw above).
|
|
447
|
+
reviewFindings: [],
|
|
448
|
+
reviewFindingLedgerMode: ledgerMode,
|
|
449
|
+
events: values(aggregate.events, "id")
|
|
450
|
+
};
|
|
451
|
+
}
|
|
422
452
|
getReviewConfig() {
|
|
423
453
|
return optional(this.#state().config.review);
|
|
424
454
|
}
|
|
@@ -1246,6 +1276,28 @@ export class FileTaskStore {
|
|
|
1246
1276
|
});
|
|
1247
1277
|
}
|
|
1248
1278
|
listEvents(taskId) { return values(this.#requireTask(taskId).events, "id"); }
|
|
1279
|
+
nextTaskWakeId(taskId) {
|
|
1280
|
+
return this.#nextTaskRecordId(taskId, "taskWake");
|
|
1281
|
+
}
|
|
1282
|
+
peekNextTaskWakeId(taskId) {
|
|
1283
|
+
return this.#peekTaskRecordId(taskId, "taskWake");
|
|
1284
|
+
}
|
|
1285
|
+
saveTaskWake(taskId, wake) {
|
|
1286
|
+
if (wake.taskId !== taskId) {
|
|
1287
|
+
throw new StorageRecordError(`Task wake belongs to another Task: ${wake.taskId}`);
|
|
1288
|
+
}
|
|
1289
|
+
this.#mutate((state) => {
|
|
1290
|
+
const aggregate = requireTaskFromState(state, taskId);
|
|
1291
|
+
observeTaskRecordId(aggregate, "taskWake", wake.id);
|
|
1292
|
+
aggregate.wakes[wake.id] = wake;
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
getTaskWake(taskId, wakeId) {
|
|
1296
|
+
return this.#state().tasks[taskId]?.wakes[wakeId] ?? null;
|
|
1297
|
+
}
|
|
1298
|
+
listTaskWakes(taskId) {
|
|
1299
|
+
return values(this.#requireTask(taskId).wakes, "id");
|
|
1300
|
+
}
|
|
1249
1301
|
removeEvents(taskId, eventIds) {
|
|
1250
1302
|
if (eventIds.length === 0)
|
|
1251
1303
|
return 0;
|
|
@@ -1317,6 +1369,62 @@ export class FileTaskStore {
|
|
|
1317
1369
|
getReleaseWorkflow(taskId, workflowId) {
|
|
1318
1370
|
return this.#requireTask(taskId).releaseWorkflows[workflowId] ?? null;
|
|
1319
1371
|
}
|
|
1372
|
+
nextPublicationReferenceId(taskId) {
|
|
1373
|
+
return this.#nextTaskRecordId(taskId, "publicationReference");
|
|
1374
|
+
}
|
|
1375
|
+
savePublicationReference(taskId, reference) {
|
|
1376
|
+
const stored = storedPublicationReference(reference);
|
|
1377
|
+
if (stored.taskId !== taskId) {
|
|
1378
|
+
throw new StorageRecordError(`Publication reference belongs to another Task: ${stored.taskId}`);
|
|
1379
|
+
}
|
|
1380
|
+
this.#mutate((state) => {
|
|
1381
|
+
const aggregate = requireTaskFromState(state, taskId);
|
|
1382
|
+
const existing = aggregate.publicationReferences[stored.id];
|
|
1383
|
+
if (existing !== undefined) {
|
|
1384
|
+
if (!isDeepStrictEqual(existing, stored)) {
|
|
1385
|
+
throw new StorageRecordError(`Publication reference cannot be overwritten: ${taskId}/${stored.id}`);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
const key = publicationExternalKey(stored);
|
|
1390
|
+
const sameExternal = [];
|
|
1391
|
+
for (const [otherTaskId, otherAggregate] of Object.entries(state.tasks)) {
|
|
1392
|
+
for (const candidate of Object.values(otherAggregate.publicationReferences)) {
|
|
1393
|
+
if (publicationExternalKey(candidate) === key) {
|
|
1394
|
+
if (otherTaskId !== taskId) {
|
|
1395
|
+
throw new StorageRecordError(`Publication external identity already belongs to Task ${otherTaskId}: ${key}.`);
|
|
1396
|
+
}
|
|
1397
|
+
sameExternal.push(candidate);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
if (sameExternal.length > 0) {
|
|
1402
|
+
const latest = latestPublicationReference(sameExternal);
|
|
1403
|
+
if (stored.supersedes !== latest.id) {
|
|
1404
|
+
throw new StorageRecordError(`Publication external identity conflicts with ${latest.id}; `
|
|
1405
|
+
+ `explicitly supersede that record: ${key}.`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
else if (stored.supersedes !== undefined) {
|
|
1409
|
+
throw new StorageRecordError(`Publication supersedes target has a different external identity: ${stored.supersedes}.`);
|
|
1410
|
+
}
|
|
1411
|
+
observeTaskRecordId(aggregate, "publicationReference", stored.id);
|
|
1412
|
+
}
|
|
1413
|
+
aggregate.publicationReferences[stored.id] = stored;
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
listPublicationReferences(taskId) {
|
|
1417
|
+
return values(this.#requireTask(taskId).publicationReferences, "id");
|
|
1418
|
+
}
|
|
1419
|
+
getPublicationReference(taskId, referenceId) {
|
|
1420
|
+
return this.#requireTask(taskId).publicationReferences[referenceId] ?? null;
|
|
1421
|
+
}
|
|
1422
|
+
findPublicationReferenceByExternalKey(externalKey) {
|
|
1423
|
+
const matches = Object.values(this.#state().tasks)
|
|
1424
|
+
.flatMap((aggregate) => Object.values(aggregate.publicationReferences))
|
|
1425
|
+
.filter((reference) => publicationExternalKey(reference) === externalKey);
|
|
1426
|
+
return matches.length === 0 ? null : latestPublicationReference(matches);
|
|
1427
|
+
}
|
|
1320
1428
|
// -- Gate artifacts (Issue 08) ---------------------------------------------
|
|
1321
1429
|
// FileTaskStore delegates to the file-backed artifact namespace under
|
|
1322
1430
|
// `<home>/artifacts/gates/`. This is the transitional path for Homes that
|
|
@@ -1820,8 +1928,10 @@ function emptyStoredTask(task) {
|
|
|
1820
1928
|
decisions: {},
|
|
1821
1929
|
milestones: {},
|
|
1822
1930
|
events: {},
|
|
1931
|
+
wakes: {},
|
|
1823
1932
|
capabilityGrants: {},
|
|
1824
1933
|
releaseWorkflows: {},
|
|
1934
|
+
publicationReferences: {},
|
|
1825
1935
|
leaderFailure: null,
|
|
1826
1936
|
operatorNotification: null
|
|
1827
1937
|
};
|
|
@@ -1841,8 +1951,10 @@ function emptyTaskIdHighWaterMarks() {
|
|
|
1841
1951
|
decision: 0,
|
|
1842
1952
|
milestone: 0,
|
|
1843
1953
|
event: 0,
|
|
1954
|
+
taskWake: 0,
|
|
1844
1955
|
capabilityGrant: 0,
|
|
1845
|
-
releaseWorkflow: 0
|
|
1956
|
+
releaseWorkflow: 0,
|
|
1957
|
+
publicationReference: 0
|
|
1846
1958
|
};
|
|
1847
1959
|
}
|
|
1848
1960
|
function nextTaskRecordSequence(aggregate, taskId, kind) {
|
|
@@ -2033,8 +2145,10 @@ function parseStoredTask(value, taskId) {
|
|
|
2033
2145
|
"decisions",
|
|
2034
2146
|
"milestones",
|
|
2035
2147
|
"events",
|
|
2148
|
+
"wakes",
|
|
2036
2149
|
"capabilityGrants",
|
|
2037
2150
|
"releaseWorkflows",
|
|
2151
|
+
"publicationReferences",
|
|
2038
2152
|
"leaderFailure",
|
|
2039
2153
|
"operatorNotification"
|
|
2040
2154
|
], `Task aggregate ${taskId}`);
|
|
@@ -2190,6 +2304,14 @@ function parseStoredTask(value, taskId) {
|
|
|
2190
2304
|
}
|
|
2191
2305
|
return event;
|
|
2192
2306
|
}, "events");
|
|
2307
|
+
parseMap(aggregate.wakes, (record, key) => {
|
|
2308
|
+
const wake = identified(record, CURRENT_TASK_WAKE_SCHEMA_VERSION, "id", key, "Task wake");
|
|
2309
|
+
if (wake.taskId !== taskId) {
|
|
2310
|
+
throw new StorageRecordError(`Task wake belongs to another Task: ${wake.taskId}`);
|
|
2311
|
+
}
|
|
2312
|
+
validateTaskWake(wake);
|
|
2313
|
+
return wake;
|
|
2314
|
+
}, "wakes");
|
|
2193
2315
|
parseMap(aggregate.capabilityGrants, (record, key) => {
|
|
2194
2316
|
const grant = storedCapabilityGrant(record);
|
|
2195
2317
|
if (grant.id !== key) {
|
|
@@ -2210,6 +2332,16 @@ function parseStoredTask(value, taskId) {
|
|
|
2210
2332
|
}
|
|
2211
2333
|
return workflow;
|
|
2212
2334
|
}, "releaseWorkflows");
|
|
2335
|
+
parseMap(aggregate.publicationReferences, (record, key) => {
|
|
2336
|
+
const reference = storedPublicationReference(record);
|
|
2337
|
+
if (reference.id !== key) {
|
|
2338
|
+
throw new StorageRecordError(`Publication reference identity is inconsistent: ${key}.`);
|
|
2339
|
+
}
|
|
2340
|
+
if (reference.taskId !== taskId) {
|
|
2341
|
+
throw new StorageRecordError(`Publication reference belongs to another Task: ${reference.taskId}`);
|
|
2342
|
+
}
|
|
2343
|
+
return reference;
|
|
2344
|
+
}, "publicationReferences");
|
|
2213
2345
|
for (const [key, label] of [["leaderFailure", "Leader failure"], ["operatorNotification", "Operator notification"]]) {
|
|
2214
2346
|
const record = aggregate[key];
|
|
2215
2347
|
if (record !== null) {
|
|
@@ -2239,8 +2371,10 @@ function validateTaskIdHighWaterCoverage(aggregate, taskId) {
|
|
|
2239
2371
|
decision: aggregate.decisions,
|
|
2240
2372
|
milestone: aggregate.milestones,
|
|
2241
2373
|
event: aggregate.events,
|
|
2374
|
+
taskWake: aggregate.wakes,
|
|
2242
2375
|
capabilityGrant: aggregate.capabilityGrants,
|
|
2243
|
-
releaseWorkflow: aggregate.releaseWorkflows
|
|
2376
|
+
releaseWorkflow: aggregate.releaseWorkflows,
|
|
2377
|
+
publicationReference: aggregate.publicationReferences
|
|
2244
2378
|
};
|
|
2245
2379
|
for (const kind of Object.keys(TASK_RECORD_ID_PREFIXES)) {
|
|
2246
2380
|
const pattern = new RegExp(`^${TASK_RECORD_ID_PREFIXES[kind]}-(\\d+)$`);
|
|
@@ -2649,6 +2783,101 @@ export function storedReleaseWorkflow(value) {
|
|
|
2649
2783
|
throw new StorageRecordError(error instanceof Error ? error.message : String(error));
|
|
2650
2784
|
}
|
|
2651
2785
|
}
|
|
2786
|
+
export function storedPublicationReference(value) {
|
|
2787
|
+
const reference = versioned(value, CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION, "Publication reference");
|
|
2788
|
+
const fields = [
|
|
2789
|
+
"schemaVersion", "id", "taskId", "projectId", "provider", "repository",
|
|
2790
|
+
"externalKind", "externalId", "state", "verification", "recordedBy",
|
|
2791
|
+
"source", "createdAt"
|
|
2792
|
+
];
|
|
2793
|
+
if (reference.externalUrl !== undefined)
|
|
2794
|
+
fields.push("externalUrl");
|
|
2795
|
+
if (reference.title !== undefined)
|
|
2796
|
+
fields.push("title");
|
|
2797
|
+
if (reference.sourceBranch !== undefined)
|
|
2798
|
+
fields.push("sourceBranch");
|
|
2799
|
+
if (reference.targetBranch !== undefined)
|
|
2800
|
+
fields.push("targetBranch");
|
|
2801
|
+
if (reference.localCommit !== undefined)
|
|
2802
|
+
fields.push("localCommit");
|
|
2803
|
+
if (reference.remoteCommit !== undefined)
|
|
2804
|
+
fields.push("remoteCommit");
|
|
2805
|
+
if (reference.evidence !== undefined)
|
|
2806
|
+
fields.push("evidence");
|
|
2807
|
+
if (reference.supersedes !== undefined)
|
|
2808
|
+
fields.push("supersedes");
|
|
2809
|
+
if (reference.mergedAt !== undefined)
|
|
2810
|
+
fields.push("mergedAt");
|
|
2811
|
+
exact(reference, fields, "Publication reference");
|
|
2812
|
+
requireRecordIdentity(reference.id, "Publication reference id");
|
|
2813
|
+
requireRecordIdentity(reference.taskId, "Publication reference Task id");
|
|
2814
|
+
validateTaskRecordReference({ taskId: reference.taskId, localId: reference.id }, "publicationReference");
|
|
2815
|
+
requireRecordIdentity(reference.projectId, "Publication reference Project id");
|
|
2816
|
+
requireNormalizedText(reference.provider, "Publication reference provider");
|
|
2817
|
+
requireNormalizedText(reference.repository, "Publication reference repository");
|
|
2818
|
+
requireNormalizedText(reference.externalKind, "Publication reference external kind");
|
|
2819
|
+
requireNormalizedText(reference.externalId, "Publication reference external id");
|
|
2820
|
+
if (reference.externalUrl !== undefined) {
|
|
2821
|
+
requireNormalizedText(reference.externalUrl, "Publication reference external URL");
|
|
2822
|
+
}
|
|
2823
|
+
if (reference.title !== undefined) {
|
|
2824
|
+
requireNormalizedText(reference.title, "Publication reference title");
|
|
2825
|
+
}
|
|
2826
|
+
if (reference.sourceBranch !== undefined) {
|
|
2827
|
+
requireNormalizedText(reference.sourceBranch, "Publication reference source branch");
|
|
2828
|
+
}
|
|
2829
|
+
if (reference.targetBranch !== undefined) {
|
|
2830
|
+
requireNormalizedText(reference.targetBranch, "Publication reference target branch");
|
|
2831
|
+
}
|
|
2832
|
+
if (reference.localCommit !== undefined) {
|
|
2833
|
+
requireNormalizedText(reference.localCommit, "Publication reference local commit");
|
|
2834
|
+
}
|
|
2835
|
+
if (reference.remoteCommit !== undefined) {
|
|
2836
|
+
requireNormalizedText(reference.remoteCommit, "Publication reference remote commit");
|
|
2837
|
+
}
|
|
2838
|
+
requireNormalizedText(reference.state, "Publication reference state");
|
|
2839
|
+
requireNormalizedText(reference.verification, "Publication reference verification");
|
|
2840
|
+
if (reference.evidence !== undefined) {
|
|
2841
|
+
requireNormalizedText(reference.evidence, "Publication reference evidence");
|
|
2842
|
+
}
|
|
2843
|
+
if (reference.supersedes !== undefined) {
|
|
2844
|
+
requireNormalizedText(reference.supersedes, "Publication reference supersedes id");
|
|
2845
|
+
}
|
|
2846
|
+
requireNormalizedText(reference.recordedBy, "Publication reference recordedBy");
|
|
2847
|
+
requireNormalizedText(reference.source, "Publication reference source");
|
|
2848
|
+
if (reference.mergedAt !== undefined) {
|
|
2849
|
+
requireTimestamp(reference.mergedAt, "Publication reference mergedAt");
|
|
2850
|
+
}
|
|
2851
|
+
requireTimestamp(reference.createdAt, "Publication reference createdAt");
|
|
2852
|
+
try {
|
|
2853
|
+
return validatePublicationReference(reference);
|
|
2854
|
+
}
|
|
2855
|
+
catch (error) {
|
|
2856
|
+
throw new StorageRecordError(error instanceof Error ? error.message : String(error));
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
function latestPublicationReference(references) {
|
|
2860
|
+
const byId = new Map(references.map((reference) => [reference.id, reference]));
|
|
2861
|
+
const superseded = new Set(references
|
|
2862
|
+
.map((reference) => reference.supersedes)
|
|
2863
|
+
.filter((id) => id !== undefined));
|
|
2864
|
+
const roots = references.filter((reference) => !superseded.has(reference.id));
|
|
2865
|
+
const latest = roots.length === 1 ? roots[0] : [...byId.values()].sort((left, right) => (right.id.localeCompare(left.id, undefined, { numeric: true })))[0];
|
|
2866
|
+
let current = latest;
|
|
2867
|
+
const seen = new Set();
|
|
2868
|
+
while (current.supersedes !== undefined) {
|
|
2869
|
+
if (seen.has(current.id)) {
|
|
2870
|
+
throw new StorageRecordError(`Publication supersession cycle at ${current.id}.`);
|
|
2871
|
+
}
|
|
2872
|
+
seen.add(current.id);
|
|
2873
|
+
const previous = byId.get(current.supersedes);
|
|
2874
|
+
if (previous === undefined) {
|
|
2875
|
+
throw new StorageRecordError(`Publication supersession target not found: ${current.id}/${current.supersedes}.`);
|
|
2876
|
+
}
|
|
2877
|
+
current = previous;
|
|
2878
|
+
}
|
|
2879
|
+
return latest;
|
|
2880
|
+
}
|
|
2652
2881
|
function storedReleaseWorkflowSource(source) {
|
|
2653
2882
|
const value = object(source, "Release workflow source");
|
|
2654
2883
|
const sourceFields = ["repository", "commit"];
|
|
@@ -3097,10 +3326,38 @@ function validateCanonicalTaskReferences(state, aggregate) {
|
|
|
3097
3326
|
}
|
|
3098
3327
|
}
|
|
3099
3328
|
}
|
|
3329
|
+
for (const reference of Object.values(aggregate.publicationReferences)) {
|
|
3330
|
+
if (!boundProjects.has(reference.projectId)) {
|
|
3331
|
+
throw new StorageRecordError(`Publication reference Project does not match Task: ${reference.id}.`);
|
|
3332
|
+
}
|
|
3333
|
+
if (reference.supersedes !== undefined) {
|
|
3334
|
+
const target = aggregate.publicationReferences[reference.supersedes];
|
|
3335
|
+
if (target === undefined) {
|
|
3336
|
+
throw new StorageRecordError(`Publication reference supersedes target not found: ${reference.id}/${reference.supersedes}.`);
|
|
3337
|
+
}
|
|
3338
|
+
if (publicationExternalKey(target) !== publicationExternalKey(reference)) {
|
|
3339
|
+
throw new StorageRecordError(`Publication reference supersedes a different external identity: ${reference.id}/${reference.supersedes}.`);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
assertPublicationExternalIdentityUniqueness(state);
|
|
3100
3344
|
for (const workspace of Object.values(aggregate.managedWorkspaces)) {
|
|
3101
3345
|
assertManagedWorkspaceReferences(aggregate, workspace, "Managed workspace");
|
|
3102
3346
|
}
|
|
3103
3347
|
}
|
|
3348
|
+
function assertPublicationExternalIdentityUniqueness(state) {
|
|
3349
|
+
const owners = new Map();
|
|
3350
|
+
for (const aggregate of Object.values(state.tasks)) {
|
|
3351
|
+
for (const reference of Object.values(aggregate.publicationReferences)) {
|
|
3352
|
+
const key = publicationExternalKey(reference);
|
|
3353
|
+
const owner = owners.get(key);
|
|
3354
|
+
if (owner !== undefined && owner !== aggregate.task.id) {
|
|
3355
|
+
throw new StorageRecordError(`Publication external identity belongs to multiple Tasks: ${key}.`);
|
|
3356
|
+
}
|
|
3357
|
+
owners.set(key, aggregate.task.id);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3104
3361
|
/**
|
|
3105
3362
|
* Enforce the owner-specific scope at the storage boundary. The map key and
|
|
3106
3363
|
* foreign-record checks prevent dangling identities; these checks prevent a
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
* transform in the production migration graph.
|
|
17
17
|
*/
|
|
18
18
|
import { CURRENT_AGGREGATE_SCHEMA_VERSION, CURRENT_STORAGE_LAYOUT_VERSION } from "../storageVersions.js";
|
|
19
|
-
import { CURRENT_AGENT_RUN_SCHEMA_VERSION, CURRENT_ACTIVE_RUN_POINTER_SCHEMA_VERSION, CURRENT_AGENT_PROFILE_SCHEMA_VERSION, CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION, CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION, CURRENT_CHANGE_SET_SCHEMA_VERSION, CURRENT_DECISION_SCHEMA_VERSION, CURRENT_EVENT_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_INPUT_REQUEST_SCHEMA_VERSION, CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION, CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION, CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION, CURRENT_MESSAGE_SCHEMA_VERSION, CURRENT_MILESTONE_SCHEMA_VERSION, CURRENT_PROJECT_SCHEMA_VERSION, CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION, CURRENT_REVIEW_ROUND_SCHEMA_VERSION, CURRENT_STORED_TASK_SCHEMA_VERSION, CURRENT_TASK_BRIEF_SCHEMA_VERSION, CURRENT_TASK_ROLE_SCHEMA_VERSION, CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_TASK_SCHEMA_VERSION, CURRENT_WORK_ITEM_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION } from "../taskStore.js";
|
|
19
|
+
import { CURRENT_AGENT_RUN_SCHEMA_VERSION, CURRENT_ACTIVE_RUN_POINTER_SCHEMA_VERSION, CURRENT_AGENT_PROFILE_SCHEMA_VERSION, CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION, CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_CONFIGURED_AGENT_SCHEMA_VERSION, CURRENT_CHANGE_SET_SCHEMA_VERSION, CURRENT_DECISION_SCHEMA_VERSION, CURRENT_EVENT_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SCHEMA_VERSION, CURRENT_GLOBAL_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_INPUT_REQUEST_SCHEMA_VERSION, CURRENT_INTEGRATION_ATTEMPT_SCHEMA_VERSION, CURRENT_INTEGRATION_QUEUE_SCHEMA_VERSION, CURRENT_MANAGED_WORKSPACE_SCHEMA_VERSION, CURRENT_MESSAGE_SCHEMA_VERSION, CURRENT_MILESTONE_SCHEMA_VERSION, CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION, CURRENT_PROJECT_SCHEMA_VERSION, CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION, CURRENT_REVIEW_ROUND_SCHEMA_VERSION, CURRENT_STORED_TASK_SCHEMA_VERSION, CURRENT_TASK_BRIEF_SCHEMA_VERSION, CURRENT_TASK_ROLE_SCHEMA_VERSION, CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION, CURRENT_TASK_SCHEMA_VERSION, CURRENT_WORK_ITEM_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION } from "../taskStore.js";
|
|
20
20
|
import { CURRENT_LEADER_FAILURE_SCHEMA_VERSION } from "../../scheduler/leaderFailure.js";
|
|
21
21
|
import { CURRENT_OPERATOR_NOTIFICATION_SCHEMA_VERSION } from "../../scheduler/operatorNotification.js";
|
|
22
|
+
import { CURRENT_TASK_WAKE_SCHEMA_VERSION } from "../../scheduler/taskWake.js";
|
|
22
23
|
import { CURRENT_DURABLE_JOB_SCHEMA_VERSION } from "../../job/durableJob.js";
|
|
23
24
|
function descriptor(version, path) {
|
|
24
25
|
return Object.freeze({ version, path });
|
|
@@ -55,8 +56,10 @@ const EXPECTED_DIRECT_RECORD_LOCATORS = Object.freeze({
|
|
|
55
56
|
decision: "state.json#/tasks/*/decisions",
|
|
56
57
|
milestone: "state.json#/tasks/*/milestones",
|
|
57
58
|
event: "state.json#/tasks/*/events",
|
|
59
|
+
taskWake: "state.json#/tasks/*/wakes",
|
|
58
60
|
capabilityGrant: "state.json#/tasks/*/capabilityGrants",
|
|
59
61
|
releaseWorkflow: "state.json#/tasks/*/releaseWorkflows",
|
|
62
|
+
publicationReference: "state.json#/tasks/*/publicationReferences",
|
|
60
63
|
leaderFailure: "state.json#/tasks/*/leaderFailure",
|
|
61
64
|
operatorNotification: "state.json#/tasks/*/operatorNotification",
|
|
62
65
|
workMailbox: "state.json#/mailboxes"
|
|
@@ -108,8 +111,10 @@ function getCurrentRecordDescriptors() {
|
|
|
108
111
|
decision: descriptor(CURRENT_DECISION_SCHEMA_VERSION, "state.json#/tasks/*/decisions"),
|
|
109
112
|
milestone: descriptor(CURRENT_MILESTONE_SCHEMA_VERSION, "state.json#/tasks/*/milestones"),
|
|
110
113
|
event: descriptor(CURRENT_EVENT_SCHEMA_VERSION, "state.json#/tasks/*/events"),
|
|
114
|
+
taskWake: descriptor(CURRENT_TASK_WAKE_SCHEMA_VERSION, "state.json#/tasks/*/wakes"),
|
|
111
115
|
capabilityGrant: descriptor(CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION, "state.json#/tasks/*/capabilityGrants"),
|
|
112
116
|
releaseWorkflow: descriptor(CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION, "state.json#/tasks/*/releaseWorkflows"),
|
|
117
|
+
publicationReference: descriptor(CURRENT_PUBLICATION_REFERENCE_SCHEMA_VERSION, "state.json#/tasks/*/publicationReferences"),
|
|
113
118
|
leaderFailure: descriptor(CURRENT_LEADER_FAILURE_SCHEMA_VERSION, "state.json#/tasks/*/leaderFailure"),
|
|
114
119
|
operatorNotification: descriptor(CURRENT_OPERATOR_NOTIFICATION_SCHEMA_VERSION, "state.json#/tasks/*/operatorNotification"),
|
|
115
120
|
workMailbox: descriptor(CURRENT_WORK_MAILBOX_SCHEMA_VERSION, "state.json#/mailboxes")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, renameSync, rmSync } from "node:fs";
|
|
2
|
+
const SQLITE_FILE_SUFFIXES = ["", "-wal", "-shm"];
|
|
3
|
+
/**
|
|
4
|
+
* Move a SQLite database together with any live WAL/SHM sidecars.
|
|
5
|
+
*
|
|
6
|
+
* A WAL database is one logical file set. Moving only the main file can leave
|
|
7
|
+
* the old WAL under the promoted database name, causing SQLite to replay it
|
|
8
|
+
* against an unrelated database. The migration fence guarantees there are no
|
|
9
|
+
* writers while this bounded rename runs.
|
|
10
|
+
*/
|
|
11
|
+
export function moveSqliteFileSet(sourcePath, targetPath, rename = renameSync) {
|
|
12
|
+
const suffixes = SQLITE_FILE_SUFFIXES.filter((suffix) => existsSync(`${sourcePath}${suffix}`));
|
|
13
|
+
for (const suffix of suffixes) {
|
|
14
|
+
if (existsSync(`${targetPath}${suffix}`)) {
|
|
15
|
+
throw new Error(`Refusing to overwrite an existing SQLite switch target: ${targetPath}${suffix}.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const moved = [];
|
|
19
|
+
try {
|
|
20
|
+
for (const suffix of suffixes) {
|
|
21
|
+
rename(`${sourcePath}${suffix}`, `${targetPath}${suffix}`);
|
|
22
|
+
moved.push(suffix);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
let rollbackError;
|
|
27
|
+
for (const suffix of moved.reverse()) {
|
|
28
|
+
try {
|
|
29
|
+
rename(`${targetPath}${suffix}`, `${sourcePath}${suffix}`);
|
|
30
|
+
}
|
|
31
|
+
catch (candidate) {
|
|
32
|
+
rollbackError ??= candidate;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (rollbackError !== undefined) {
|
|
36
|
+
throw new Error(`SQLite file-set move failed and rollback was incomplete: ${messageOf(error)}; `
|
|
37
|
+
+ `rollback failed: ${messageOf(rollbackError)}`);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Remove a promoted SQLite file set before restoring its timestamped backup. */
|
|
43
|
+
export function removeSqliteFileSet(databasePath) {
|
|
44
|
+
for (const suffix of SQLITE_FILE_SUFFIXES) {
|
|
45
|
+
rmSync(`${databasePath}${suffix}`, { force: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function messageOf(error) {
|
|
49
|
+
return error instanceof Error ? error.message : String(error);
|
|
50
|
+
}
|
|
@@ -42,6 +42,7 @@ import { describeActiveRuntime, homeRuntimeIsActive, inspectHomeRuntime, inspect
|
|
|
42
42
|
import { COMMITTED_DATABASE_FILENAME, STAGED_DATABASE_FILENAME, computeDbFamilyChecksums, computeStateFamilyChecksums, populateSqliteFromState } from "./sqliteStateMigration.js";
|
|
43
43
|
import { latestStateBackupPath } from "./pseudoLayoutRepair.js";
|
|
44
44
|
import { migrationReceiptPath, writeMigrationReceipt } from "./migrationReceipt.js";
|
|
45
|
+
import { moveSqliteFileSet, removeSqliteFileSet } from "./sqliteFileSet.js";
|
|
45
46
|
/** Build the SQLite-backed migration target. */
|
|
46
47
|
export function createSqliteMigrationTarget(options) {
|
|
47
48
|
const home = options.home;
|
|
@@ -155,7 +156,7 @@ export function createSqliteMigrationTarget(options) {
|
|
|
155
156
|
if (existsSync(backupPath)) {
|
|
156
157
|
throw new Error(`Refusing to overwrite an existing database backup: ${backupPath}.`);
|
|
157
158
|
}
|
|
158
|
-
|
|
159
|
+
moveSqliteFileSet(committedDbPath, backupPath);
|
|
159
160
|
}
|
|
160
161
|
renameSync(stagedDbPath, committedDbPath);
|
|
161
162
|
// The staged connection may leave empty WAL/SHM sidecars behind
|
|
@@ -167,7 +168,7 @@ export function createSqliteMigrationTarget(options) {
|
|
|
167
168
|
// Pre-promotion failure: restore the original database if we moved it.
|
|
168
169
|
if (backupPath !== undefined && existsSync(backupPath)) {
|
|
169
170
|
try {
|
|
170
|
-
|
|
171
|
+
moveSqliteFileSet(backupPath, committedDbPath);
|
|
171
172
|
}
|
|
172
173
|
catch {
|
|
173
174
|
throw new AmbiguousSwitchError({
|
|
@@ -208,10 +209,11 @@ export function createSqliteMigrationTarget(options) {
|
|
|
208
209
|
// is ambiguous and must be recovered manually.
|
|
209
210
|
try {
|
|
210
211
|
if (backupPath !== undefined && existsSync(backupPath)) {
|
|
211
|
-
|
|
212
|
+
removeSqliteFileSet(committedDbPath);
|
|
213
|
+
moveSqliteFileSet(backupPath, committedDbPath);
|
|
212
214
|
}
|
|
213
215
|
else {
|
|
214
|
-
|
|
216
|
+
removeSqliteFileSet(committedDbPath);
|
|
215
217
|
}
|
|
216
218
|
}
|
|
217
219
|
catch {
|
|
@@ -37,6 +37,7 @@ import { planMigration } from "../migration/planner.js";
|
|
|
37
37
|
import { AmbiguousSwitchError } from "../migration/index.js";
|
|
38
38
|
import { describeActiveRuntime, homeRuntimeIsActive, inspectHomeRuntime, inspectSourceVersionState, inspectSnapshotVersionState } from "./homeMigrationTarget.js";
|
|
39
39
|
import { writeSwitchProgress } from "./switchProgress.js";
|
|
40
|
+
import { moveSqliteFileSet, removeSqliteFileSet } from "./sqliteFileSet.js";
|
|
40
41
|
import { COMMITTED_DATABASE_FILENAME, STAGED_DATABASE_FILENAME, copySqlitePassthroughState, computeDbFamilyChecksums, computeStateFamilyChecksums, populateSqliteFromState, readStateFromSqlite } from "./sqliteStateMigration.js";
|
|
41
42
|
/** Build the SQLite-backed record-migration target. */
|
|
42
43
|
export function createSqliteRecordMigrationTarget(options) {
|
|
@@ -135,7 +136,7 @@ export function createSqliteRecordMigrationTarget(options) {
|
|
|
135
136
|
if (existsSync(backupPath)) {
|
|
136
137
|
throw new Error(`Refusing to overwrite an existing database backup: ${backupPath}.`);
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
+
moveSqliteFileSet(committedDbPath, backupPath);
|
|
139
140
|
}
|
|
140
141
|
promoteRename(stagedDbPath, committedDbPath);
|
|
141
142
|
// The staged connection may leave empty WAL/SHM sidecars behind
|
|
@@ -147,7 +148,7 @@ export function createSqliteRecordMigrationTarget(options) {
|
|
|
147
148
|
// Pre-promotion failure: restore the original database if we moved it.
|
|
148
149
|
if (backupPath !== undefined && existsSync(backupPath)) {
|
|
149
150
|
try {
|
|
150
|
-
|
|
151
|
+
moveSqliteFileSet(backupPath, committedDbPath, promoteRename);
|
|
151
152
|
}
|
|
152
153
|
catch {
|
|
153
154
|
writeInterruptedMarker(home, backupPath, stagedDbPath, now);
|
|
@@ -175,10 +176,11 @@ export function createSqliteRecordMigrationTarget(options) {
|
|
|
175
176
|
// is ambiguous and must be recovered manually.
|
|
176
177
|
try {
|
|
177
178
|
if (backupPath !== undefined && existsSync(backupPath)) {
|
|
178
|
-
|
|
179
|
+
removeSqliteFileSet(committedDbPath);
|
|
180
|
+
moveSqliteFileSet(backupPath, committedDbPath, promoteRename);
|
|
179
181
|
}
|
|
180
182
|
else {
|
|
181
|
-
|
|
183
|
+
removeSqliteFileSet(committedDbPath);
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
catch {
|