@zq-silk/yui 0.5.3 → 0.6.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/README.md +5 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +35 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +610 -201
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +24 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +85 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +60 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +413 -61
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/ephemeralResourceReaper.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/resourceInventoryRpc.js +85 -0
- package/dist/controller/resourceInventoryWorker.js +50 -0
- package/dist/controller/runtime.js +238 -22
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +549 -42
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/boundedRpc.js +475 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/setup/setupCommand.js +27 -4
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +769 -1
- package/dist/storage/persistenceWorker.js +194 -0
- package/dist/storage/sqliteSchema.js +705 -0
- package/dist/storage/sqliteStore.js +1695 -0
- package/dist/storage/storageVersions.js +9 -2
- package/dist/storage/storeRpc.js +298 -0
- package/dist/storage/taskStore.js +982 -21
- package/dist/storage/upgrade/homeClassification.js +157 -12
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +351 -0
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +713 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +510 -18
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +343 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +2 -1
|
@@ -8,6 +8,8 @@ const FINAL_REVIEW_AGGREGATE_FROM_VERSION = 16;
|
|
|
8
8
|
const FINAL_REVIEW_AGGREGATE_TO_VERSION = 17;
|
|
9
9
|
const HOME_IDENTITY_AGGREGATE_FROM_VERSION = 17;
|
|
10
10
|
const HOME_IDENTITY_AGGREGATE_TO_VERSION = 18;
|
|
11
|
+
const SQLITE_LAYOUT_FROM_VERSION = 6;
|
|
12
|
+
const SQLITE_LAYOUT_TO_VERSION = 7;
|
|
11
13
|
const PROJECT_FROM_VERSION = 2;
|
|
12
14
|
const PROJECT_TO_VERSION = 3;
|
|
13
15
|
const TASK_FROM_VERSION = 3;
|
|
@@ -20,6 +22,15 @@ const WORK_ITEM_GROUP_HISTORY_FROM_VERSION = 8;
|
|
|
20
22
|
const WORK_ITEM_GROUP_HISTORY_TO_VERSION = 9;
|
|
21
23
|
const AGENT_RUN_FROM_VERSION = 5;
|
|
22
24
|
const AGENT_RUN_TO_VERSION = 6;
|
|
25
|
+
/**
|
|
26
|
+
* v7 combines the optional Issue 04 `providerRetry`/`yieldReceipt` fields and
|
|
27
|
+
* Issue 05 Leader actionability fields. All are optional, so the transition is
|
|
28
|
+
* a version-only rewrite; legacy v6 records without them remain valid.
|
|
29
|
+
*/
|
|
30
|
+
const AGENT_RUN_OPTIONAL_FIELDS_FROM_VERSION = 6;
|
|
31
|
+
const AGENT_RUN_OPTIONAL_FIELDS_TO_VERSION = 7;
|
|
32
|
+
const MESSAGE_WAKE_POLICY_FROM_VERSION = 2;
|
|
33
|
+
const MESSAGE_WAKE_POLICY_TO_VERSION = 3;
|
|
23
34
|
const REVIEW_ROUND_FROM_VERSION = 2;
|
|
24
35
|
const REVIEW_ROUND_TO_VERSION = 3;
|
|
25
36
|
const REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION = 3;
|
|
@@ -30,6 +41,22 @@ const ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION = 2;
|
|
|
30
41
|
const ACTIVE_RUN_POINTER_NAMESPACE_TO_VERSION = 3;
|
|
31
42
|
const MANAGED_WORKSPACE_FROM_VERSION = 1;
|
|
32
43
|
const MANAGED_WORKSPACE_TO_VERSION = 2;
|
|
44
|
+
const CHANGE_SET_MANIFEST_FROM_VERSION = 2;
|
|
45
|
+
const CHANGE_SET_MANIFEST_TO_VERSION = 3;
|
|
46
|
+
const INTEGRATION_ATTEMPT_FROM_VERSION = 2;
|
|
47
|
+
const INTEGRATION_ATTEMPT_TO_VERSION = 3;
|
|
48
|
+
const INTEGRATION_ATTEMPT_GATE_IDENTITY_FROM_VERSION = 3;
|
|
49
|
+
const INTEGRATION_ATTEMPT_GATE_IDENTITY_TO_VERSION = 4;
|
|
50
|
+
const INTEGRATION_QUEUE_FROM_VERSION = 0;
|
|
51
|
+
const INTEGRATION_QUEUE_TO_VERSION = 1;
|
|
52
|
+
const STORED_TASK_DURABLE_JOBS_FROM_VERSION = 14;
|
|
53
|
+
const STORED_TASK_DURABLE_JOBS_TO_VERSION = 15;
|
|
54
|
+
const STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION = 15;
|
|
55
|
+
const STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION = 16;
|
|
56
|
+
const CAPABILITY_GRANT_FROM_VERSION = 0;
|
|
57
|
+
const CAPABILITY_GRANT_TO_VERSION = 1;
|
|
58
|
+
const RELEASE_WORKFLOW_FROM_VERSION = 0;
|
|
59
|
+
const RELEASE_WORKFLOW_TO_VERSION = 1;
|
|
33
60
|
/**
|
|
34
61
|
* Build the authoritative production graph. Transition intent and executable
|
|
35
62
|
* transforms are registered together here; compatible loading and offline
|
|
@@ -38,6 +65,14 @@ const MANAGED_WORKSPACE_TO_VERSION = 2;
|
|
|
38
65
|
export function createProductionStorageRegistry() {
|
|
39
66
|
assertBaselineConsistency();
|
|
40
67
|
const registry = new MigrationRegistry().registerOfflineMigration({
|
|
68
|
+
axis: "layout",
|
|
69
|
+
fromVersion: SQLITE_LAYOUT_FROM_VERSION,
|
|
70
|
+
toVersion: SQLITE_LAYOUT_TO_VERSION,
|
|
71
|
+
preconditions: requireLayoutV6Snapshot,
|
|
72
|
+
transform: migrateLayoutV6ToV7,
|
|
73
|
+
declaredEffects: []
|
|
74
|
+
})
|
|
75
|
+
.registerOfflineMigration({
|
|
41
76
|
axis: "aggregate",
|
|
42
77
|
fromVersion: FINAL_REVIEW_AGGREGATE_FROM_VERSION,
|
|
43
78
|
toVersion: FINAL_REVIEW_AGGREGATE_TO_VERSION,
|
|
@@ -59,11 +94,22 @@ export function createProductionStorageRegistry() {
|
|
|
59
94
|
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION, WORK_ITEM_GIT_SNAPSHOT_TO_VERSION, "workItems"))
|
|
60
95
|
.registerOfflineMigration(workItemExecutionGroupHistoryStep())
|
|
61
96
|
.registerOfflineMigration(recordFamilyStep("agentRun", AGENT_RUN_FROM_VERSION, AGENT_RUN_TO_VERSION, "agentRuns"))
|
|
97
|
+
.registerOfflineMigration(recordFamilyStep("agentRun", AGENT_RUN_OPTIONAL_FIELDS_FROM_VERSION, AGENT_RUN_OPTIONAL_FIELDS_TO_VERSION, "agentRuns"))
|
|
98
|
+
.registerOfflineMigration(messageWakePolicyStep())
|
|
62
99
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_FROM_VERSION, REVIEW_ROUND_TO_VERSION, "reviewRounds"))
|
|
63
100
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION, REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION, "reviewRounds"))
|
|
64
101
|
.registerOfflineMigration(recordFamilyStep("activeRunPointer", ACTIVE_RUN_POINTER_FROM_VERSION, ACTIVE_RUN_POINTER_TO_VERSION, "activeRuns"))
|
|
65
102
|
.registerOfflineMigration(managedWorkspaceFamilyStep())
|
|
66
|
-
.registerOfflineMigration(activeRunPointerNamespaceStep())
|
|
103
|
+
.registerOfflineMigration(activeRunPointerNamespaceStep())
|
|
104
|
+
.registerOfflineMigration(changeSetManifestStep())
|
|
105
|
+
.registerOfflineMigration(integrationAttemptSupersededStep())
|
|
106
|
+
.registerOfflineMigration(recordFamilyStep("integrationAttempt", INTEGRATION_ATTEMPT_GATE_IDENTITY_FROM_VERSION, INTEGRATION_ATTEMPT_GATE_IDENTITY_TO_VERSION, "integrationAttempts"))
|
|
107
|
+
.registerOfflineMigration(integrationQueueIntroductionStep())
|
|
108
|
+
.registerCompatible(storedTaskDurableJobsStep())
|
|
109
|
+
.registerCompatible(storedTaskJobCallerKeyHashesStep())
|
|
110
|
+
.registerCompatible(durableJobIntroductionStep())
|
|
111
|
+
.registerOfflineMigration(capabilityGrantIntroductionStep())
|
|
112
|
+
.registerOfflineMigration(releaseWorkflowIntroductionStep());
|
|
67
113
|
assertRegistryCoversBaselineToCurrent(registry);
|
|
68
114
|
return registry;
|
|
69
115
|
}
|
|
@@ -78,6 +124,80 @@ function workItemExecutionGroupHistoryStep() {
|
|
|
78
124
|
declaredEffects: []
|
|
79
125
|
};
|
|
80
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Task Messages gain an optional `wakePolicy` field (Issue 05). The field is
|
|
129
|
+
* absent on every older record, so this adjacent step only advances the
|
|
130
|
+
* family version; the strict current parser treats the omitted field as
|
|
131
|
+
* "no explicit policy" and preserves the existing routing.
|
|
132
|
+
*/
|
|
133
|
+
function messageWakePolicyStep() {
|
|
134
|
+
return {
|
|
135
|
+
axis: "record",
|
|
136
|
+
recordKind: "message",
|
|
137
|
+
fromVersion: MESSAGE_WAKE_POLICY_FROM_VERSION,
|
|
138
|
+
toVersion: MESSAGE_WAKE_POLICY_TO_VERSION,
|
|
139
|
+
preconditions: (snapshot) => requireMessageFamilyVersion(snapshot, MESSAGE_WAKE_POLICY_FROM_VERSION),
|
|
140
|
+
transform: migrateMessageWakePolicy,
|
|
141
|
+
declaredEffects: []
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function requireMessageFamilyVersion(snapshot, version) {
|
|
145
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
146
|
+
if (manifestVersions.message !== version) {
|
|
147
|
+
throw new Error(`Message migration requires manifest version ${version}.`);
|
|
148
|
+
}
|
|
149
|
+
if (snapshot.state === null)
|
|
150
|
+
return;
|
|
151
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
152
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
153
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
154
|
+
const messages = task.messages;
|
|
155
|
+
if (messages === undefined)
|
|
156
|
+
continue;
|
|
157
|
+
const map = asObject(messages, `message map ${taskId}`);
|
|
158
|
+
for (const [messageId, rawMessage] of Object.entries(map)) {
|
|
159
|
+
const message = asObject(rawMessage, `message ${taskId}/${messageId}`);
|
|
160
|
+
if (message.schemaVersion !== version) {
|
|
161
|
+
throw new Error(`Message ${taskId}/${messageId} must use schemaVersion ${version}.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function migrateMessageWakePolicy(snapshot) {
|
|
167
|
+
requireMessageFamilyVersion(snapshot, MESSAGE_WAKE_POLICY_FROM_VERSION);
|
|
168
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
169
|
+
const schemaManifest = {
|
|
170
|
+
...snapshot.schemaManifest,
|
|
171
|
+
recordVersions: {
|
|
172
|
+
...manifestVersions,
|
|
173
|
+
message: MESSAGE_WAKE_POLICY_TO_VERSION
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
if (snapshot.state === null)
|
|
177
|
+
return { schemaManifest, state: null };
|
|
178
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
179
|
+
const nextTasks = {};
|
|
180
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
181
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
182
|
+
if (task.messages === undefined) {
|
|
183
|
+
nextTasks[taskId] = { ...task };
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const messages = asObject(task.messages, `message map ${taskId}`);
|
|
187
|
+
const nextMessages = {};
|
|
188
|
+
for (const [messageId, rawMessage] of Object.entries(messages)) {
|
|
189
|
+
nextMessages[messageId] = {
|
|
190
|
+
...asObject(rawMessage, `message ${taskId}/${messageId}`),
|
|
191
|
+
schemaVersion: MESSAGE_WAKE_POLICY_TO_VERSION
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
nextTasks[taskId] = { ...task, messages: nextMessages };
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
schemaManifest,
|
|
198
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
199
|
+
};
|
|
200
|
+
}
|
|
81
201
|
/**
|
|
82
202
|
* ManagedWorkspace is stored once in the owner-keyed workspace map and copied
|
|
83
203
|
* into Candidates, AgentRuns, and ReviewRounds as immutable lifecycle
|
|
@@ -356,6 +476,215 @@ function migrateActiveRunPointerNamespace(snapshot) {
|
|
|
356
476
|
state: { ...snapshot.state, tasks: nextTasks }
|
|
357
477
|
};
|
|
358
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* ChangeSet v3 adds the optional integration manifest (tags, deleted paths,
|
|
481
|
+
* target ref, evidence references). The manifest is optional, so the
|
|
482
|
+
* transition only rewrites the record version; legacy records without a
|
|
483
|
+
* manifest remain valid and keep integrating.
|
|
484
|
+
*/
|
|
485
|
+
function changeSetManifestStep() {
|
|
486
|
+
return {
|
|
487
|
+
axis: "record",
|
|
488
|
+
recordKind: "changeSet",
|
|
489
|
+
fromVersion: CHANGE_SET_MANIFEST_FROM_VERSION,
|
|
490
|
+
toVersion: CHANGE_SET_MANIFEST_TO_VERSION,
|
|
491
|
+
preconditions: requireChangeSetManifestVersion,
|
|
492
|
+
transform: migrateChangeSetManifest,
|
|
493
|
+
declaredEffects: []
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function requireChangeSetManifestVersion(snapshot) {
|
|
497
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
498
|
+
if (manifestVersions.changeSet !== CHANGE_SET_MANIFEST_FROM_VERSION) {
|
|
499
|
+
throw new Error(`Record changeSet migration requires manifest version ${CHANGE_SET_MANIFEST_FROM_VERSION}.`);
|
|
500
|
+
}
|
|
501
|
+
if (snapshot.state === null)
|
|
502
|
+
return;
|
|
503
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
504
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
505
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
506
|
+
if (task.changeSets === undefined)
|
|
507
|
+
continue;
|
|
508
|
+
const changeSets = asObject(task.changeSets, `changeSet map ${taskId}`);
|
|
509
|
+
for (const [changeSetId, rawRecord] of Object.entries(changeSets)) {
|
|
510
|
+
const record = asObject(rawRecord, `changeSet ${taskId}/${changeSetId}`);
|
|
511
|
+
if (record.schemaVersion !== CHANGE_SET_MANIFEST_FROM_VERSION) {
|
|
512
|
+
throw new Error(`changeSet ${taskId}/${changeSetId} must use schemaVersion ${CHANGE_SET_MANIFEST_FROM_VERSION}.`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function migrateChangeSetManifest(snapshot) {
|
|
518
|
+
requireChangeSetManifestVersion(snapshot);
|
|
519
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
520
|
+
const schemaManifest = {
|
|
521
|
+
...snapshot.schemaManifest,
|
|
522
|
+
recordVersions: {
|
|
523
|
+
...manifestVersions,
|
|
524
|
+
changeSet: CHANGE_SET_MANIFEST_TO_VERSION
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
if (snapshot.state === null)
|
|
528
|
+
return { schemaManifest, state: null };
|
|
529
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
530
|
+
const nextTasks = {};
|
|
531
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
532
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
533
|
+
if (task.changeSets === undefined) {
|
|
534
|
+
nextTasks[taskId] = { ...task };
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const changeSets = asObject(task.changeSets, `changeSet map ${taskId}`);
|
|
538
|
+
const nextChangeSets = {};
|
|
539
|
+
for (const [changeSetId, rawRecord] of Object.entries(changeSets)) {
|
|
540
|
+
const record = asObject(rawRecord, `changeSet ${taskId}/${changeSetId}`);
|
|
541
|
+
nextChangeSets[changeSetId] = {
|
|
542
|
+
...record,
|
|
543
|
+
schemaVersion: CHANGE_SET_MANIFEST_TO_VERSION
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
nextTasks[taskId] = { ...task, changeSets: nextChangeSets };
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
schemaManifest,
|
|
550
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* IntegrationAttempt v2->v3 adds the "superseded" terminal status for
|
|
555
|
+
* committed Integrations that are obsolete. The migration preserves all
|
|
556
|
+
* existing fields and advances the schema version; the new status is
|
|
557
|
+
* opt-in, so old records remain valid until explicitly superseded.
|
|
558
|
+
*/
|
|
559
|
+
function integrationAttemptSupersededStep() {
|
|
560
|
+
return {
|
|
561
|
+
axis: "record",
|
|
562
|
+
recordKind: "integrationAttempt",
|
|
563
|
+
fromVersion: INTEGRATION_ATTEMPT_FROM_VERSION,
|
|
564
|
+
toVersion: INTEGRATION_ATTEMPT_TO_VERSION,
|
|
565
|
+
preconditions: requireIntegrationAttemptV2,
|
|
566
|
+
transform: migrateIntegrationAttemptV2ToV3,
|
|
567
|
+
declaredEffects: []
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function requireIntegrationAttemptV2(snapshot) {
|
|
571
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
572
|
+
if (manifestVersions.integrationAttempt !== INTEGRATION_ATTEMPT_FROM_VERSION) {
|
|
573
|
+
throw new Error(`Record integrationAttempt migration requires manifest version ${INTEGRATION_ATTEMPT_FROM_VERSION}.`);
|
|
574
|
+
}
|
|
575
|
+
if (snapshot.state === null)
|
|
576
|
+
return;
|
|
577
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
578
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
579
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
580
|
+
if (task.integrationAttempts === undefined)
|
|
581
|
+
continue;
|
|
582
|
+
const attempts = asObject(task.integrationAttempts, `integrationAttempt map ${taskId}`);
|
|
583
|
+
for (const [attemptId, rawRecord] of Object.entries(attempts)) {
|
|
584
|
+
const record = asObject(rawRecord, `integrationAttempt ${taskId}/${attemptId}`);
|
|
585
|
+
if (record.schemaVersion !== INTEGRATION_ATTEMPT_FROM_VERSION) {
|
|
586
|
+
throw new Error(`integrationAttempt ${taskId}/${attemptId} must use schemaVersion ${INTEGRATION_ATTEMPT_FROM_VERSION}.`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function migrateIntegrationAttemptV2ToV3(snapshot) {
|
|
592
|
+
requireIntegrationAttemptV2(snapshot);
|
|
593
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
594
|
+
const schemaManifest = {
|
|
595
|
+
...snapshot.schemaManifest,
|
|
596
|
+
recordVersions: {
|
|
597
|
+
...manifestVersions,
|
|
598
|
+
integrationAttempt: INTEGRATION_ATTEMPT_TO_VERSION
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
if (snapshot.state === null)
|
|
602
|
+
return { schemaManifest, state: null };
|
|
603
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
604
|
+
const nextTasks = {};
|
|
605
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
606
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
607
|
+
if (task.integrationAttempts === undefined) {
|
|
608
|
+
nextTasks[taskId] = { ...task };
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const attempts = asObject(task.integrationAttempts, `integrationAttempt map ${taskId}`);
|
|
612
|
+
const nextAttempts = {};
|
|
613
|
+
for (const [attemptId, rawRecord] of Object.entries(attempts)) {
|
|
614
|
+
const record = asObject(rawRecord, `integrationAttempt ${taskId}/${attemptId}`);
|
|
615
|
+
nextAttempts[attemptId] = {
|
|
616
|
+
...record,
|
|
617
|
+
schemaVersion: INTEGRATION_ATTEMPT_TO_VERSION
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
nextTasks[taskId] = { ...task, integrationAttempts: nextAttempts };
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
schemaManifest,
|
|
624
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* The integration queue is a post-baseline per-Task record family. Its
|
|
629
|
+
* explicit 0->1 introduction adds the empty map and its id high-water mark to
|
|
630
|
+
* every Task aggregate and the family to the record manifest; old Homes keep
|
|
631
|
+
* integrating without it until the queue is first used.
|
|
632
|
+
*/
|
|
633
|
+
function integrationQueueIntroductionStep() {
|
|
634
|
+
return {
|
|
635
|
+
axis: "record",
|
|
636
|
+
recordKind: "integrationQueue",
|
|
637
|
+
fromVersion: INTEGRATION_QUEUE_FROM_VERSION,
|
|
638
|
+
toVersion: INTEGRATION_QUEUE_TO_VERSION,
|
|
639
|
+
introduction: true,
|
|
640
|
+
preconditions: requireIntegrationQueueIntroduction,
|
|
641
|
+
transform: introduceIntegrationQueue,
|
|
642
|
+
declaredEffects: []
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function requireIntegrationQueueIntroduction(snapshot) {
|
|
646
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
647
|
+
if (manifestVersions.integrationQueue !== undefined) {
|
|
648
|
+
throw new Error("integrationQueue is already introduced in the record manifest.");
|
|
649
|
+
}
|
|
650
|
+
if (snapshot.state === null)
|
|
651
|
+
return;
|
|
652
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
653
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
654
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
655
|
+
if (task.integrationQueue !== undefined) {
|
|
656
|
+
throw new Error(`Task aggregate already carries an integrationQueue map: ${taskId}.`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function introduceIntegrationQueue(snapshot) {
|
|
661
|
+
requireIntegrationQueueIntroduction(snapshot);
|
|
662
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
663
|
+
const schemaManifest = {
|
|
664
|
+
...snapshot.schemaManifest,
|
|
665
|
+
recordVersions: {
|
|
666
|
+
...manifestVersions,
|
|
667
|
+
integrationQueue: INTEGRATION_QUEUE_TO_VERSION
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
if (snapshot.state === null)
|
|
671
|
+
return { schemaManifest, state: null };
|
|
672
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
673
|
+
const nextTasks = {};
|
|
674
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
675
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
676
|
+
const highWaterMarks = asObject(task.idHighWaterMarks ?? {}, `Task id high-water marks ${taskId}`);
|
|
677
|
+
nextTasks[taskId] = {
|
|
678
|
+
...task,
|
|
679
|
+
idHighWaterMarks: { ...highWaterMarks, integrationQueue: 0 },
|
|
680
|
+
integrationQueue: {}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
schemaManifest,
|
|
685
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
686
|
+
};
|
|
687
|
+
}
|
|
359
688
|
function legacyLaneKeyParts(key) {
|
|
360
689
|
const match = /^lane:([^:]+):([^:]+)$/u.exec(key);
|
|
361
690
|
if (match === null)
|
|
@@ -368,6 +697,274 @@ function executionLaneNamespaceKey(executionGroupId, executionLaneId) {
|
|
|
368
697
|
function encodeLaneKeyPart(value) {
|
|
369
698
|
return encodeURIComponent(value).replace(/:/gu, "%3A");
|
|
370
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* The StoredTask aggregate gains the `durableJobs` record family. Every v14
|
|
702
|
+
* task defaults to an empty map; the compatible normalizer adds it in place
|
|
703
|
+
* without rewriting any other field. The strict source validator rejects
|
|
704
|
+
* unknown fields so a pre-v15 Home cannot smuggle an unrecognized shape.
|
|
705
|
+
*/
|
|
706
|
+
function storedTaskDurableJobsStep() {
|
|
707
|
+
return {
|
|
708
|
+
kind: "compatible",
|
|
709
|
+
axis: "record",
|
|
710
|
+
recordKind: "storedTask",
|
|
711
|
+
fromVersion: STORED_TASK_DURABLE_JOBS_FROM_VERSION,
|
|
712
|
+
toVersion: STORED_TASK_DURABLE_JOBS_TO_VERSION,
|
|
713
|
+
defaults: [
|
|
714
|
+
"durableJobs defaults to an empty map on every Task aggregate",
|
|
715
|
+
"idHighWaterMarks.durableJob defaults to 0 on every Task aggregate"
|
|
716
|
+
],
|
|
717
|
+
validateSource: (snapshot) => requireStoredTaskV14Shape(snapshot),
|
|
718
|
+
normalize: (snapshot) => normalizeStoredTaskV14ToV15(snapshot)
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
const STORED_TASK_V14_FIELDS = [
|
|
722
|
+
"schemaVersion",
|
|
723
|
+
"task",
|
|
724
|
+
"idHighWaterMarks",
|
|
725
|
+
"brief",
|
|
726
|
+
"changeSets",
|
|
727
|
+
"integrationAttempts",
|
|
728
|
+
"integrationQueue",
|
|
729
|
+
// durableJobs may already be present on a v14 task when the Home was
|
|
730
|
+
// written by a build that had the family before the storedTask version
|
|
731
|
+
// advanced (or after a state.json→SQLite repair round-trip, which
|
|
732
|
+
// reconstructs every current family map). The 14→15 normalizer preserves
|
|
733
|
+
// existing records instead of overwriting them with an empty map.
|
|
734
|
+
"durableJobs",
|
|
735
|
+
// jobCallerKeyHashes is introduced at v16 but the state.json→SQLite repair
|
|
736
|
+
// round-trip reconstructs every current family map, so a v14 task may carry
|
|
737
|
+
// an empty map after the repair. The 14→15 normalizer preserves it.
|
|
738
|
+
"jobCallerKeyHashes",
|
|
739
|
+
"capabilityGrants",
|
|
740
|
+
"releaseWorkflows",
|
|
741
|
+
"roles",
|
|
742
|
+
"managedWorkspaces",
|
|
743
|
+
"roleSessionSets",
|
|
744
|
+
"workItems",
|
|
745
|
+
"agentRuns",
|
|
746
|
+
"reviewRounds",
|
|
747
|
+
"activeRuns",
|
|
748
|
+
"messages",
|
|
749
|
+
"inputRequests",
|
|
750
|
+
"decisions",
|
|
751
|
+
"milestones",
|
|
752
|
+
"events",
|
|
753
|
+
"leaderFailure",
|
|
754
|
+
"operatorNotification"
|
|
755
|
+
];
|
|
756
|
+
function requireStoredTaskV14Shape(snapshot) {
|
|
757
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
758
|
+
if (manifestVersions.storedTask !== STORED_TASK_DURABLE_JOBS_FROM_VERSION) {
|
|
759
|
+
throw new Error(`Record storedTask compatible step requires manifest version ${STORED_TASK_DURABLE_JOBS_FROM_VERSION}.`);
|
|
760
|
+
}
|
|
761
|
+
if (snapshot.state === null)
|
|
762
|
+
return;
|
|
763
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
764
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
765
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
766
|
+
if (task.schemaVersion !== STORED_TASK_DURABLE_JOBS_FROM_VERSION) {
|
|
767
|
+
throw new Error(`Task aggregate ${taskId} must use schemaVersion ${STORED_TASK_DURABLE_JOBS_FROM_VERSION}.`);
|
|
768
|
+
}
|
|
769
|
+
const allowed = new Set(STORED_TASK_V14_FIELDS);
|
|
770
|
+
const unknown = Object.keys(task).find((key) => !allowed.has(key));
|
|
771
|
+
if (unknown !== undefined) {
|
|
772
|
+
throw new Error(`Task aggregate ${taskId} has an unknown v14 field: ${unknown}.`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function normalizeStoredTaskV14ToV15(snapshot) {
|
|
777
|
+
requireStoredTaskV14Shape(snapshot);
|
|
778
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
779
|
+
const schemaManifest = {
|
|
780
|
+
...snapshot.schemaManifest,
|
|
781
|
+
recordVersions: {
|
|
782
|
+
...manifestVersions,
|
|
783
|
+
storedTask: STORED_TASK_DURABLE_JOBS_TO_VERSION
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
if (snapshot.state === null)
|
|
787
|
+
return { schemaManifest, state: null };
|
|
788
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
789
|
+
const nextTasks = {};
|
|
790
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
791
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
792
|
+
// The v14 high-water marks have no durableJob family; the strict current
|
|
793
|
+
// parser requires every family key, so the normalizer supplies the zero
|
|
794
|
+
// default in the same pass as the empty durableJobs map.
|
|
795
|
+
const highWaterMarks = asObject(task.idHighWaterMarks, `Task aggregate ${taskId} idHighWaterMarks`);
|
|
796
|
+
const existingJobs = task.durableJobs === undefined
|
|
797
|
+
? {}
|
|
798
|
+
: asObject(task.durableJobs, `Task aggregate ${taskId} durableJobs`);
|
|
799
|
+
const existingJobMark = highWaterMarks.durableJob;
|
|
800
|
+
nextTasks[taskId] = {
|
|
801
|
+
...task,
|
|
802
|
+
schemaVersion: STORED_TASK_DURABLE_JOBS_TO_VERSION,
|
|
803
|
+
idHighWaterMarks: {
|
|
804
|
+
...highWaterMarks,
|
|
805
|
+
durableJob: typeof existingJobMark === "number" ? existingJobMark : 0
|
|
806
|
+
},
|
|
807
|
+
durableJobs: existingJobs
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
schemaManifest,
|
|
812
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* rr13: The StoredTask aggregate gains the `jobCallerKeyHashes` map. Every v15
|
|
817
|
+
* task defaults to an empty map; the compatible normalizer adds it in place
|
|
818
|
+
* without rewriting any other field. A v15 task must not already carry the
|
|
819
|
+
* field. No legacy fallback: a Session whose hash is absent is rejected at the
|
|
820
|
+
* Controller boundary (fail-closed).
|
|
821
|
+
*/
|
|
822
|
+
function storedTaskJobCallerKeyHashesStep() {
|
|
823
|
+
return {
|
|
824
|
+
kind: "compatible",
|
|
825
|
+
axis: "record",
|
|
826
|
+
recordKind: "storedTask",
|
|
827
|
+
fromVersion: STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION,
|
|
828
|
+
toVersion: STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION,
|
|
829
|
+
defaults: [
|
|
830
|
+
"jobCallerKeyHashes defaults to an empty map on every Task aggregate"
|
|
831
|
+
],
|
|
832
|
+
validateSource: (snapshot) => requireStoredTaskV15Shape(snapshot),
|
|
833
|
+
normalize: (snapshot) => normalizeStoredTaskV15ToV16(snapshot)
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
const STORED_TASK_V15_FIELDS = [
|
|
837
|
+
"schemaVersion",
|
|
838
|
+
"task",
|
|
839
|
+
"idHighWaterMarks",
|
|
840
|
+
"brief",
|
|
841
|
+
"changeSets",
|
|
842
|
+
"integrationAttempts",
|
|
843
|
+
"integrationQueue",
|
|
844
|
+
"durableJobs",
|
|
845
|
+
// May already be present after a state.json→SQLite repair round-trip (the
|
|
846
|
+
// reverse reader reconstructs every current family map). The 15→16
|
|
847
|
+
// normalizer preserves existing hashes instead of overwriting them.
|
|
848
|
+
"jobCallerKeyHashes",
|
|
849
|
+
"capabilityGrants",
|
|
850
|
+
"releaseWorkflows",
|
|
851
|
+
"roles",
|
|
852
|
+
"managedWorkspaces",
|
|
853
|
+
"roleSessionSets",
|
|
854
|
+
"workItems",
|
|
855
|
+
"agentRuns",
|
|
856
|
+
"reviewRounds",
|
|
857
|
+
"activeRuns",
|
|
858
|
+
"messages",
|
|
859
|
+
"inputRequests",
|
|
860
|
+
"decisions",
|
|
861
|
+
"milestones",
|
|
862
|
+
"events",
|
|
863
|
+
"leaderFailure",
|
|
864
|
+
"operatorNotification"
|
|
865
|
+
];
|
|
866
|
+
function requireStoredTaskV15Shape(snapshot) {
|
|
867
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
868
|
+
if (manifestVersions.storedTask !== STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION) {
|
|
869
|
+
throw new Error(`Record storedTask compatible step requires manifest version ${STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION}.`);
|
|
870
|
+
}
|
|
871
|
+
if (snapshot.state === null)
|
|
872
|
+
return;
|
|
873
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
874
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
875
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
876
|
+
if (task.schemaVersion !== STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION) {
|
|
877
|
+
throw new Error(`Task aggregate ${taskId} must use schemaVersion ${STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION}.`);
|
|
878
|
+
}
|
|
879
|
+
const allowed = new Set(STORED_TASK_V15_FIELDS);
|
|
880
|
+
const unknown = Object.keys(task).find((key) => !allowed.has(key));
|
|
881
|
+
if (unknown !== undefined) {
|
|
882
|
+
throw new Error(`Task aggregate ${taskId} has an unknown v15 field: ${unknown}.`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function normalizeStoredTaskV15ToV16(snapshot) {
|
|
887
|
+
requireStoredTaskV15Shape(snapshot);
|
|
888
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
889
|
+
const schemaManifest = {
|
|
890
|
+
...snapshot.schemaManifest,
|
|
891
|
+
recordVersions: {
|
|
892
|
+
...manifestVersions,
|
|
893
|
+
storedTask: STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
if (snapshot.state === null)
|
|
897
|
+
return { schemaManifest, state: null };
|
|
898
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
899
|
+
const nextTasks = {};
|
|
900
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
901
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
902
|
+
const existingHashes = task.jobCallerKeyHashes === undefined
|
|
903
|
+
? {}
|
|
904
|
+
: asObject(task.jobCallerKeyHashes, `Task aggregate ${taskId} jobCallerKeyHashes`);
|
|
905
|
+
nextTasks[taskId] = {
|
|
906
|
+
...task,
|
|
907
|
+
schemaVersion: STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION,
|
|
908
|
+
jobCallerKeyHashes: existingHashes
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
schemaManifest,
|
|
913
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* The durableJob record family is introduced alongside the StoredTask v15
|
|
918
|
+
* aggregate. A pre-baseline Home has no durableJobs map and no manifest entry;
|
|
919
|
+
* the introduction declares the family at version 1. The StoredTask 14->15
|
|
920
|
+
* compatible step supplies the empty map; this step only advances the manifest
|
|
921
|
+
* record version so the planner can resolve the 0->1 boundary.
|
|
922
|
+
*/
|
|
923
|
+
function durableJobIntroductionStep() {
|
|
924
|
+
return {
|
|
925
|
+
kind: "compatible",
|
|
926
|
+
axis: "record",
|
|
927
|
+
recordKind: "durableJob",
|
|
928
|
+
fromVersion: 0,
|
|
929
|
+
toVersion: 1,
|
|
930
|
+
introduction: true,
|
|
931
|
+
defaults: ["durableJob family is introduced as an empty map on every Task"],
|
|
932
|
+
validateSource: (snapshot) => {
|
|
933
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
934
|
+
const declared = manifestVersions.durableJob;
|
|
935
|
+
if (declared !== undefined && declared !== 0) {
|
|
936
|
+
throw new Error(`Record durableJob introduction requires manifest version 0 or absent, found ${String(declared)}.`);
|
|
937
|
+
}
|
|
938
|
+
if (snapshot.state === null)
|
|
939
|
+
return;
|
|
940
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
941
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
942
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
943
|
+
if (task.durableJobs !== undefined) {
|
|
944
|
+
const jobs = asObject(task.durableJobs, `durableJobs map ${taskId}`);
|
|
945
|
+
if (Object.keys(jobs).length > 0) {
|
|
946
|
+
throw new Error(`Task aggregate ${taskId} must not carry durableJobs before introduction.`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
normalize: (snapshot) => {
|
|
952
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
953
|
+
return {
|
|
954
|
+
schemaManifest: {
|
|
955
|
+
...snapshot.schemaManifest,
|
|
956
|
+
recordVersions: {
|
|
957
|
+
...manifestVersions,
|
|
958
|
+
durableJob: 1
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
state: snapshot.state === null
|
|
962
|
+
? null
|
|
963
|
+
: { ...snapshot.state }
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
371
968
|
/**
|
|
372
969
|
* Upgrade one nested Task record family without guessing fields or repairing
|
|
373
970
|
* malformed state. The new execution lineage fields are optional, so legal
|
|
@@ -376,6 +973,154 @@ function encodeLaneKeyPart(value) {
|
|
|
376
973
|
* durable and centralized, which keeps old Homes out of the strict current
|
|
377
974
|
* parser until this step has run.
|
|
378
975
|
*/
|
|
976
|
+
/**
|
|
977
|
+
* The post-baseline introduction of the task-scoped CapabilityGrant family.
|
|
978
|
+
* A pre-introduction Home names no capabilityGrant manifest version and its
|
|
979
|
+
* Task aggregates carry no `capabilityGrants` map; this explicit 0->1 step
|
|
980
|
+
* adds the empty family to every Task (plus the matching high-water mark) so
|
|
981
|
+
* the strict current parser can open the migrated state.
|
|
982
|
+
*/
|
|
983
|
+
function capabilityGrantIntroductionStep() {
|
|
984
|
+
return {
|
|
985
|
+
axis: "record",
|
|
986
|
+
recordKind: "capabilityGrant",
|
|
987
|
+
fromVersion: CAPABILITY_GRANT_FROM_VERSION,
|
|
988
|
+
toVersion: CAPABILITY_GRANT_TO_VERSION,
|
|
989
|
+
introduction: true,
|
|
990
|
+
preconditions: requireCapabilityGrantIntroduction,
|
|
991
|
+
transform: introduceCapabilityGrantFamily,
|
|
992
|
+
declaredEffects: []
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function requireCapabilityGrantIntroduction(snapshot) {
|
|
996
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
997
|
+
const namedVersion = manifestVersions.capabilityGrant;
|
|
998
|
+
if (namedVersion !== undefined && namedVersion !== CAPABILITY_GRANT_FROM_VERSION) {
|
|
999
|
+
throw new Error(`Record capabilityGrant introduction requires an absent manifest version or ${CAPABILITY_GRANT_FROM_VERSION}.`);
|
|
1000
|
+
}
|
|
1001
|
+
if (snapshot.state === null)
|
|
1002
|
+
return;
|
|
1003
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1004
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1005
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1006
|
+
if (task.capabilityGrants !== undefined) {
|
|
1007
|
+
const grants = asObject(task.capabilityGrants, `capabilityGrant map ${taskId}`);
|
|
1008
|
+
if (Object.keys(grants).length > 0) {
|
|
1009
|
+
throw new Error(`Capability grant introduction found existing records: ${taskId}.`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const marks = task.idHighWaterMarks;
|
|
1013
|
+
if (marks !== undefined) {
|
|
1014
|
+
const highWaterMarks = asObject(marks, `Task id high-water marks ${taskId}`);
|
|
1015
|
+
const mark = highWaterMarks.capabilityGrant;
|
|
1016
|
+
if (mark !== undefined && mark !== CAPABILITY_GRANT_FROM_VERSION) {
|
|
1017
|
+
throw new Error(`Capability grant introduction found a high-water mark: ${taskId}.`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function introduceCapabilityGrantFamily(snapshot) {
|
|
1023
|
+
requireCapabilityGrantIntroduction(snapshot);
|
|
1024
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1025
|
+
const schemaManifest = {
|
|
1026
|
+
...snapshot.schemaManifest,
|
|
1027
|
+
recordVersions: {
|
|
1028
|
+
...manifestVersions,
|
|
1029
|
+
capabilityGrant: CAPABILITY_GRANT_TO_VERSION
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
if (snapshot.state === null)
|
|
1033
|
+
return { schemaManifest, state: null };
|
|
1034
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1035
|
+
const nextTasks = {};
|
|
1036
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1037
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1038
|
+
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
1039
|
+
nextTasks[taskId] = {
|
|
1040
|
+
...task,
|
|
1041
|
+
idHighWaterMarks: { ...marks, capabilityGrant: CAPABILITY_GRANT_FROM_VERSION },
|
|
1042
|
+
capabilityGrants: {}
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
return {
|
|
1046
|
+
schemaManifest,
|
|
1047
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* The post-baseline introduction of the task-scoped ReleaseWorkflow family.
|
|
1052
|
+
* A pre-introduction Home names no releaseWorkflow manifest version and its
|
|
1053
|
+
* Task aggregates carry no `releaseWorkflows` map; this explicit 0->1 step
|
|
1054
|
+
* adds the empty family to every Task (plus the matching high-water mark) so
|
|
1055
|
+
* the strict current parser can open the migrated state.
|
|
1056
|
+
*/
|
|
1057
|
+
function releaseWorkflowIntroductionStep() {
|
|
1058
|
+
return {
|
|
1059
|
+
axis: "record",
|
|
1060
|
+
recordKind: "releaseWorkflow",
|
|
1061
|
+
fromVersion: RELEASE_WORKFLOW_FROM_VERSION,
|
|
1062
|
+
toVersion: RELEASE_WORKFLOW_TO_VERSION,
|
|
1063
|
+
introduction: true,
|
|
1064
|
+
preconditions: requireReleaseWorkflowIntroduction,
|
|
1065
|
+
transform: introduceReleaseWorkflowFamily,
|
|
1066
|
+
declaredEffects: []
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function requireReleaseWorkflowIntroduction(snapshot) {
|
|
1070
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1071
|
+
const namedVersion = manifestVersions.releaseWorkflow;
|
|
1072
|
+
if (namedVersion !== undefined && namedVersion !== RELEASE_WORKFLOW_FROM_VERSION) {
|
|
1073
|
+
throw new Error(`Record releaseWorkflow introduction requires an absent manifest version or ${RELEASE_WORKFLOW_FROM_VERSION}.`);
|
|
1074
|
+
}
|
|
1075
|
+
if (snapshot.state === null)
|
|
1076
|
+
return;
|
|
1077
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1078
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1079
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1080
|
+
if (task.releaseWorkflows !== undefined) {
|
|
1081
|
+
const workflows = asObject(task.releaseWorkflows, `releaseWorkflow map ${taskId}`);
|
|
1082
|
+
if (Object.keys(workflows).length > 0) {
|
|
1083
|
+
throw new Error(`Release workflow introduction found existing records: ${taskId}.`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const marks = task.idHighWaterMarks;
|
|
1087
|
+
if (marks !== undefined) {
|
|
1088
|
+
const highWaterMarks = asObject(marks, `Task id high-water marks ${taskId}`);
|
|
1089
|
+
const mark = highWaterMarks.releaseWorkflow;
|
|
1090
|
+
if (mark !== undefined && mark !== RELEASE_WORKFLOW_FROM_VERSION) {
|
|
1091
|
+
throw new Error(`Release workflow introduction found a high-water mark: ${taskId}.`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
function introduceReleaseWorkflowFamily(snapshot) {
|
|
1097
|
+
requireReleaseWorkflowIntroduction(snapshot);
|
|
1098
|
+
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1099
|
+
const schemaManifest = {
|
|
1100
|
+
...snapshot.schemaManifest,
|
|
1101
|
+
recordVersions: {
|
|
1102
|
+
...manifestVersions,
|
|
1103
|
+
releaseWorkflow: RELEASE_WORKFLOW_TO_VERSION
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
if (snapshot.state === null)
|
|
1107
|
+
return { schemaManifest, state: null };
|
|
1108
|
+
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1109
|
+
const nextTasks = {};
|
|
1110
|
+
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1111
|
+
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1112
|
+
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
1113
|
+
nextTasks[taskId] = {
|
|
1114
|
+
...task,
|
|
1115
|
+
idHighWaterMarks: { ...marks, releaseWorkflow: RELEASE_WORKFLOW_FROM_VERSION },
|
|
1116
|
+
releaseWorkflows: {}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
return {
|
|
1120
|
+
schemaManifest,
|
|
1121
|
+
state: { ...snapshot.state, tasks: nextTasks }
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
379
1124
|
function recordFamilyStep(recordKind, fromVersion, toVersion, taskMapKey) {
|
|
380
1125
|
return {
|
|
381
1126
|
axis: "record",
|
|
@@ -462,6 +1207,29 @@ function asObject(value, label) {
|
|
|
462
1207
|
}
|
|
463
1208
|
/** Historical public spelling retained as an alias to the single graph. */
|
|
464
1209
|
export const createProductionRegistry = createProductionStorageRegistry;
|
|
1210
|
+
/**
|
|
1211
|
+
* Layout 6 -> 7 is the state.json -> SQLite WAL transition (task-21 §8). The
|
|
1212
|
+
* transform only advances the manifest's layout version; the staged SQLite
|
|
1213
|
+
* database is populated by the migration target's `writeFreshOutput`, and the
|
|
1214
|
+
* `state.json` document is retained read-only for rollback. The precondition
|
|
1215
|
+
* requires a layout-6 manifest so a future layout cannot be silently regressed.
|
|
1216
|
+
*/
|
|
1217
|
+
function requireLayoutV6Snapshot(snapshot) {
|
|
1218
|
+
if (snapshot.schemaManifest.storageVersion !== SQLITE_LAYOUT_FROM_VERSION) {
|
|
1219
|
+
throw new Error(`Layout ${SQLITE_LAYOUT_FROM_VERSION}->${SQLITE_LAYOUT_TO_VERSION} migration requires ` +
|
|
1220
|
+
`schema.json storageVersion ${SQLITE_LAYOUT_FROM_VERSION}.`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function migrateLayoutV6ToV7(snapshot) {
|
|
1224
|
+
requireLayoutV6Snapshot(snapshot);
|
|
1225
|
+
return {
|
|
1226
|
+
schemaManifest: {
|
|
1227
|
+
...snapshot.schemaManifest,
|
|
1228
|
+
storageVersion: SQLITE_LAYOUT_TO_VERSION
|
|
1229
|
+
},
|
|
1230
|
+
state: snapshot.state
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
465
1233
|
/** Advance the aggregate identity after proving manifest/root agreement at v16. */
|
|
466
1234
|
function migrateAggregateV16ToV17(snapshot) {
|
|
467
1235
|
return {
|