@zq-silk/yui 0.6.0 → 0.6.2
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/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 +30 -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 +618 -202
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +33 -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 +100 -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 +62 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +402 -56
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- 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/runtime.js +165 -15
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +297 -58
- package/dist/controller/sessionOwnerReconciliation.js +321 -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/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +736 -1
- package/dist/storage/sqliteSchema.js +264 -3
- package/dist/storage/sqliteStore.js +487 -13
- package/dist/storage/storeRpc.js +21 -0
- package/dist/storage/taskStore.js +974 -21
- package/dist/storage/upgrade/homeClassification.js +120 -2
- 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 +58 -6
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
- package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +738 -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 +1 -1
- package/skills/yui-leader/SKILL.md +8 -0
|
@@ -22,6 +22,15 @@ const WORK_ITEM_GROUP_HISTORY_FROM_VERSION = 8;
|
|
|
22
22
|
const WORK_ITEM_GROUP_HISTORY_TO_VERSION = 9;
|
|
23
23
|
const AGENT_RUN_FROM_VERSION = 5;
|
|
24
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;
|
|
25
34
|
const REVIEW_ROUND_FROM_VERSION = 2;
|
|
26
35
|
const REVIEW_ROUND_TO_VERSION = 3;
|
|
27
36
|
const REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION = 3;
|
|
@@ -32,6 +41,22 @@ const ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION = 2;
|
|
|
32
41
|
const ACTIVE_RUN_POINTER_NAMESPACE_TO_VERSION = 3;
|
|
33
42
|
const MANAGED_WORKSPACE_FROM_VERSION = 1;
|
|
34
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;
|
|
35
60
|
/**
|
|
36
61
|
* Build the authoritative production graph. Transition intent and executable
|
|
37
62
|
* transforms are registered together here; compatible loading and offline
|
|
@@ -69,11 +94,22 @@ export function createProductionStorageRegistry() {
|
|
|
69
94
|
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION, WORK_ITEM_GIT_SNAPSHOT_TO_VERSION, "workItems"))
|
|
70
95
|
.registerOfflineMigration(workItemExecutionGroupHistoryStep())
|
|
71
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())
|
|
72
99
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_FROM_VERSION, REVIEW_ROUND_TO_VERSION, "reviewRounds"))
|
|
73
100
|
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION, REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION, "reviewRounds"))
|
|
74
101
|
.registerOfflineMigration(recordFamilyStep("activeRunPointer", ACTIVE_RUN_POINTER_FROM_VERSION, ACTIVE_RUN_POINTER_TO_VERSION, "activeRuns"))
|
|
75
102
|
.registerOfflineMigration(managedWorkspaceFamilyStep())
|
|
76
|
-
.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());
|
|
77
113
|
assertRegistryCoversBaselineToCurrent(registry);
|
|
78
114
|
return registry;
|
|
79
115
|
}
|
|
@@ -88,6 +124,80 @@ function workItemExecutionGroupHistoryStep() {
|
|
|
88
124
|
declaredEffects: []
|
|
89
125
|
};
|
|
90
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
|
+
}
|
|
91
201
|
/**
|
|
92
202
|
* ManagedWorkspace is stored once in the owner-keyed workspace map and copied
|
|
93
203
|
* into Candidates, AgentRuns, and ReviewRounds as immutable lifecycle
|
|
@@ -366,6 +476,215 @@ function migrateActiveRunPointerNamespace(snapshot) {
|
|
|
366
476
|
state: { ...snapshot.state, tasks: nextTasks }
|
|
367
477
|
};
|
|
368
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
|
+
}
|
|
369
688
|
function legacyLaneKeyParts(key) {
|
|
370
689
|
const match = /^lane:([^:]+):([^:]+)$/u.exec(key);
|
|
371
690
|
if (match === null)
|
|
@@ -378,6 +697,274 @@ function executionLaneNamespaceKey(executionGroupId, executionLaneId) {
|
|
|
378
697
|
function encodeLaneKeyPart(value) {
|
|
379
698
|
return encodeURIComponent(value).replace(/:/gu, "%3A");
|
|
380
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
|
+
}
|
|
381
968
|
/**
|
|
382
969
|
* Upgrade one nested Task record family without guessing fields or repairing
|
|
383
970
|
* malformed state. The new execution lineage fields are optional, so legal
|
|
@@ -386,6 +973,154 @@ function encodeLaneKeyPart(value) {
|
|
|
386
973
|
* durable and centralized, which keeps old Homes out of the strict current
|
|
387
974
|
* parser until this step has run.
|
|
388
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
|
+
}
|
|
389
1124
|
function recordFamilyStep(recordKind, fromVersion, toVersion, taskMapKey) {
|
|
390
1125
|
return {
|
|
391
1126
|
axis: "record",
|