@zq-silk/yui 0.13.9 → 0.14.0
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/ARCHITECTURE.md +69 -428
- package/README.md +108 -298
- package/dist/agent/managedRuntimeEnvironment.js +2 -2
- package/dist/cli/commandCatalog.js +39 -77
- package/dist/cli/interactionCandidates.js +4 -4
- package/dist/cli/interactionPolicy.js +7 -10
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/updateCommand.js +6 -27
- package/dist/cli/updateOrchestrator.js +63 -484
- package/dist/cli/updatePorts.js +15 -333
- package/dist/cli/upgradeCommand.js +8 -65
- package/dist/cli.js +57 -242
- package/dist/commands/configCommands.js +11 -62
- package/dist/commands/executionAuditCommands.js +23 -23
- package/dist/commands/globalRoleCommands.js +2 -7
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/projectCommands.js +5 -5
- package/dist/commands/releaseCommands.js +3 -10
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/sessionCommands.js +2 -2
- package/dist/commands/taskActor.js +31 -39
- package/dist/commands/taskChangeSetCommands.js +5 -12
- package/dist/commands/taskCommands.js +464 -1249
- package/dist/commands/taskContextCommand.js +35 -36
- package/dist/commands/taskExecutionCommands.js +33 -46
- package/dist/commands/taskInputCommands.js +20 -42
- package/dist/commands/taskNextActionCommand.js +9 -9
- package/dist/commands/taskOverviewCommand.js +56 -95
- package/dist/commands/taskRoleRuntimeStatus.js +77 -107
- package/dist/commands/taskUpstreamCommands.js +3 -3
- package/dist/commands/taskWorkspaceCommands.js +7 -79
- package/dist/commands/telemetryCommands.js +32 -124
- package/dist/commands/workflowCommands.js +3 -3
- package/dist/completion/fileCompletionManager.js +1 -1
- package/dist/config/configCatalog.js +4 -6
- package/dist/config/yuiConfig.js +6 -43
- package/dist/context/dispatchContext.js +14 -30
- package/dist/context/roleSessionContext.js +2 -2
- package/dist/context/sessionBootstrapManifest.js +2 -2
- package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
- package/dist/context/turnInputContract.js +204 -0
- package/dist/context/wakeNotification.js +2 -2
- package/dist/controller/agentRuntimeObserver.js +18 -18
- package/dist/controller/clientRuntime.js +13 -20
- package/dist/controller/controller.js +89 -116
- package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
- package/dist/controller/jobControl.js +17 -17
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +38 -59
- package/dist/controller/resourceInventoryRpc.js +3 -3
- package/dist/controller/resourceInventoryWorker.js +1 -1
- package/dist/controller/runtime.js +46 -53
- package/dist/controller/runtimeEventInbox.js +23 -31
- package/dist/controller/runtimeEventProcessor.js +15 -15
- package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
- package/dist/controller/runtimeLaunchCoordinator.js +3 -3
- package/dist/controller/runtimeObservationHook.js +11 -8
- package/dist/controller/sessionNotify.js +11 -11
- package/dist/controller/structuredProviderObservation.js +89 -16
- package/dist/coordination/workMailbox.js +309 -579
- package/dist/coordination/workMailboxQueue.js +5 -6
- package/dist/core/controllerClient.js +1 -91
- package/dist/core/controllerServer.js +3 -6
- package/dist/doctor/doctor.js +51 -314
- package/dist/execution/candidateConvergence.js +3 -3
- package/dist/execution/executionGroup.js +32 -33
- package/dist/execution/executionHealth.js +36 -38
- package/dist/execution/resourceBroker.js +12 -12
- package/dist/executor/agentExecutor.js +30 -265
- package/dist/executor/effectiveLaunch.js +9 -19
- package/dist/executor/executorRegistry.js +43 -17
- package/dist/executor/fileRoleLaunchPlanner.js +56 -56
- package/dist/executor/launchPlan.js +3 -3
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +20 -25
- package/dist/input/inputRequest.js +12 -12
- package/dist/integration/changeSet.js +9 -13
- package/dist/integration/changeSetManifest.js +1 -1
- package/dist/integration/gitIntegrationService.js +2 -2
- package/dist/integration/integrationQueueService.js +5 -21
- package/dist/integration/overlapDiagnostics.js +2 -2
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/job/jobRunner.js +1 -1
- package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
- package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
- package/dist/message/message.js +6 -6
- package/dist/observability/executionAudit.js +74 -86
- package/dist/observability/faultClassification.js +7 -24
- package/dist/observability/orchestrationMetrics.js +5 -5
- package/dist/observability/runtimeIdentity.js +34 -101
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +3 -3
- package/dist/release/releaseWorkflowEngine.js +6 -10
- package/dist/release/releaseWorkflowPorts.js +2 -2
- package/dist/release/workflowFileLock.js +1 -1
- package/dist/repository/checkoutSwap.js +1 -1
- package/dist/repository/gitWorkspace.js +0 -13
- package/dist/repository/project.js +1 -1
- package/dist/repository/projectMaintenanceLock.js +1 -2
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +21 -25
- package/dist/repository/taskWorkspaceIdentity.js +8 -21
- package/dist/repository/taskWorkspacePreparer.js +63 -620
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewFindingLedger.js +1 -1
- package/dist/review/reviewOutcomeClassifier.js +35 -50
- package/dist/review/reviewRound.js +14 -25
- package/dist/review/reviewerAvailability.js +4 -4
- package/dist/review/taskFinalReviewContractResolution.js +41 -0
- package/dist/role/role.js +6 -3
- package/dist/runtime/agentDriver.js +4 -0
- package/dist/runtime/agentHost.js +151 -36
- package/dist/runtime/builtinAgentDrivers.js +79 -2
- package/dist/runtime/codexAppServerRuntime.js +122 -0
- package/dist/runtime/continuationManager.js +3 -5
- package/dist/runtime/exactControlPlane.js +17 -42
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +4 -4
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/processExitOutbox.js +0 -32
- package/dist/runtime/promptEnvelope.js +9 -9
- package/dist/runtime/providerContinuation.js +2 -2
- package/dist/runtime/providerContinuationReconciliationService.js +2 -2
- package/dist/runtime/providerRuntimeIdentity.js +76 -24
- package/dist/runtime/recentTurnIds.js +38 -0
- package/dist/runtime/runtimeContinuationProjection.js +2 -2
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +58 -21
- package/dist/runtime/runtimeProjection.js +19 -27
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/structuredProviderHost.js +177 -12
- package/dist/runtime/tmuxAdapters.js +46 -11
- package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
- package/dist/scheduler/leaderWakeupProcessor.js +147 -445
- package/dist/scheduler/operatorEvent.js +4 -6
- package/dist/scheduler/resourceQueueProjection.js +71 -0
- package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
- package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
- package/dist/scheduler/taskExecutionProjection.js +99 -99
- package/dist/scheduler/taskObservabilityProjection.js +10 -10
- package/dist/scheduler/taskWake.js +4 -4
- package/dist/scheduler/wakeReason.js +6 -10
- package/dist/scheduler/wakeupQueue.js +0 -9
- package/dist/setup/setupCommand.js +2 -2
- package/dist/storage/currentTaskStore.js +46 -0
- package/dist/storage/persistenceWorker.js +1 -1
- package/dist/storage/sqliteSchema.js +81 -485
- package/dist/storage/sqliteStore.js +206 -365
- package/dist/storage/storageSchema.js +1 -15
- package/dist/storage/storageVersions.js +8 -9
- package/dist/storage/storeRpc.js +10 -34
- package/dist/storage/taskStore.js +63 -3415
- package/dist/storage/upgrade/recordVersions.js +55 -149
- package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
- package/dist/task/completionReadiness.js +5 -5
- package/dist/task/deliveryGuard.js +10 -10
- package/dist/task/nextAction.js +74 -77
- package/dist/task/task.js +2 -4
- package/dist/task/taskRecordReference.js +3 -3
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +53 -53
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +8 -14
- package/dist/turn/turn.js +356 -0
- package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
- package/dist/verification/gateArtifactStore.js +2 -2
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +29 -54
- package/dist/web/assets/client/i18n.js +17 -37
- package/dist/web/assets/client/view.js +37 -37
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +5 -5
- package/dist/web/assets/styles/widgets.js +1 -2
- package/dist/web/webSnapshot.js +65 -60
- package/dist/workItem/workItem.js +11 -11
- package/dist/workspace/workItemChangeSetManager.js +1 -1
- package/docs/task-local-identity.md +5 -5
- package/i18n/README.zh-CN.md +41 -54
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +85 -83
- package/skills/yui-operator/SKILL.md +16 -16
- package/skills/yui-reviewer/SKILL.md +10 -10
- package/skills/yui-runtime/SKILL.md +17 -18
- package/skills/yui-worker/SKILL.md +16 -18
- package/dist/context/runContextContract.js +0 -162
- package/dist/executor/turnCompletion.js +0 -126
- package/dist/review/taskFinalReviewContractEvent.js +0 -1
- package/dist/review/taskFinalReviewContractRebind.js +0 -367
- package/dist/run/agentRun.js +0 -356
- package/dist/run/rejectedYieldAttempt.js +0 -221
- package/dist/run/runControlRequest.js +0 -50
- package/dist/run/yieldReceipt.js +0 -65
- package/dist/runtime/turnCompletion.js +0 -3
- package/dist/scheduler/actionability.js +0 -323
- package/dist/scheduler/activeRoleRunDelivery.js +0 -868
- package/dist/scheduler/activeTaskProgress.js +0 -126
- package/dist/storage/compatibleTaskStore.js +0 -248
- package/dist/storage/migration/baseline.js +0 -78
- package/dist/storage/migration/classifier.js +0 -51
- package/dist/storage/migration/compatibleCodec.js +0 -53
- package/dist/storage/migration/engine.js +0 -147
- package/dist/storage/migration/index.js +0 -33
- package/dist/storage/migration/planner.js +0 -154
- package/dist/storage/migration/productionRegistry.js +0 -3931
- package/dist/storage/migration/registry.js +0 -169
- package/dist/storage/migration/report.js +0 -54
- package/dist/storage/migration/types.js +0 -31
- package/dist/storage/upgrade/homeClassification.js +0 -353
- package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
- package/dist/storage/upgrade/migrationReceipt.js +0 -67
- package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
- package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
- package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
- package/dist/storage/upgrade/recordVersionScan.js +0 -176
- package/dist/storage/upgrade/sqliteFileSet.js +0 -50
- package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
- package/dist/storage/upgrade/switchProgress.js +0 -80
- package/dist/storage/upgrade/upgradeReceipt.js +0 -161
- package/dist/storage/upgradeCoordination.js +0 -187
- package/dist/storage/upgradeFence.js +0 -366
- package/dist/telemetry/telemetryCompaction.js +0 -252
|
@@ -1,3931 +0,0 @@
|
|
|
1
|
-
/** The one production transition registry and its baseline-to-current delivery gate. */
|
|
2
|
-
import { generateHomeIdentity } from "../../repository/homeIdentity.js";
|
|
3
|
-
import { latestStorageVersionState } from "../upgrade/recordVersions.js";
|
|
4
|
-
import { assertBaselineConsistency, baselineStorageVersionState } from "./baseline.js";
|
|
5
|
-
import { planMigration } from "./planner.js";
|
|
6
|
-
import { MigrationRegistry } from "./registry.js";
|
|
7
|
-
const FINAL_REVIEW_AGGREGATE_FROM_VERSION = 16;
|
|
8
|
-
const FINAL_REVIEW_AGGREGATE_TO_VERSION = 17;
|
|
9
|
-
const HOME_IDENTITY_AGGREGATE_FROM_VERSION = 17;
|
|
10
|
-
const HOME_IDENTITY_AGGREGATE_TO_VERSION = 18;
|
|
11
|
-
const RUNTIME_OBSERVATION_AGGREGATE_FROM_VERSION = 18;
|
|
12
|
-
const RUNTIME_OBSERVATION_AGGREGATE_TO_VERSION = 19;
|
|
13
|
-
const MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION = 19;
|
|
14
|
-
const MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION = 20;
|
|
15
|
-
const SQLITE_LAYOUT_FROM_VERSION = 6;
|
|
16
|
-
const SQLITE_LAYOUT_TO_VERSION = 7;
|
|
17
|
-
const PROJECT_FROM_VERSION = 2;
|
|
18
|
-
const PROJECT_TO_VERSION = 3;
|
|
19
|
-
const PROJECT_KNOWLEDGE_PROPOSALS_FROM_VERSION = 3;
|
|
20
|
-
const PROJECT_KNOWLEDGE_PROPOSALS_TO_VERSION = 4;
|
|
21
|
-
const PROJECT_LIFECYCLE_FROM_VERSION = 4;
|
|
22
|
-
const PROJECT_LIFECYCLE_TO_VERSION = 5;
|
|
23
|
-
const TASK_FROM_VERSION = 3;
|
|
24
|
-
const TASK_TO_VERSION = 4;
|
|
25
|
-
const TASK_INTENT_FROM_VERSION = 4;
|
|
26
|
-
const TASK_INTENT_TO_VERSION = 5;
|
|
27
|
-
const TASK_EXECUTION_GATE_FROM_VERSION = 5;
|
|
28
|
-
const TASK_EXECUTION_GATE_TO_VERSION = 6;
|
|
29
|
-
const WORK_ITEM_FROM_VERSION = 6;
|
|
30
|
-
const WORK_ITEM_TO_VERSION = 7;
|
|
31
|
-
const WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION = 7;
|
|
32
|
-
const WORK_ITEM_GIT_SNAPSHOT_TO_VERSION = 8;
|
|
33
|
-
const WORK_ITEM_GROUP_HISTORY_FROM_VERSION = 8;
|
|
34
|
-
const WORK_ITEM_GROUP_HISTORY_TO_VERSION = 9;
|
|
35
|
-
const WORK_ITEM_EXPLORATION_STAGE_FROM_VERSION = 9;
|
|
36
|
-
const WORK_ITEM_EXPLORATION_STAGE_TO_VERSION = 10;
|
|
37
|
-
const WORK_ITEM_CANDIDATE_CONVERGENCE_FROM_VERSION = 10;
|
|
38
|
-
const WORK_ITEM_CANDIDATE_CONVERGENCE_TO_VERSION = 11;
|
|
39
|
-
const WORK_ITEM_RESOURCE_SCHEDULING_FROM_VERSION = 11;
|
|
40
|
-
const WORK_ITEM_RESOURCE_SCHEDULING_TO_VERSION = 12;
|
|
41
|
-
const AGENT_RUN_FROM_VERSION = 5;
|
|
42
|
-
const AGENT_RUN_TO_VERSION = 6;
|
|
43
|
-
/**
|
|
44
|
-
* v7 combines the optional Issue 04 `providerRetry`/`yieldReceipt` fields and
|
|
45
|
-
* Issue 05 Leader actionability fields. All are optional, so the transition is
|
|
46
|
-
* a version-only rewrite; legacy v6 records without them remain valid.
|
|
47
|
-
*/
|
|
48
|
-
const AGENT_RUN_OPTIONAL_FIELDS_FROM_VERSION = 6;
|
|
49
|
-
const AGENT_RUN_OPTIONAL_FIELDS_TO_VERSION = 7;
|
|
50
|
-
const AGENT_RUN_CONTEXT_PROTOCOL_FROM_VERSION = 7;
|
|
51
|
-
const AGENT_RUN_CONTEXT_PROTOCOL_TO_VERSION = 8;
|
|
52
|
-
const AGENT_RUN_RETRY_EPISODE_FROM_VERSION = 8;
|
|
53
|
-
const AGENT_RUN_RETRY_EPISODE_TO_VERSION = 9;
|
|
54
|
-
const AGENT_RUN_ERROR_FACTS_FROM_VERSION = 9;
|
|
55
|
-
const AGENT_RUN_ERROR_FACTS_TO_VERSION = 10;
|
|
56
|
-
const MESSAGE_WAKE_POLICY_FROM_VERSION = 2;
|
|
57
|
-
const MESSAGE_WAKE_POLICY_TO_VERSION = 3;
|
|
58
|
-
const REVIEW_ROUND_FROM_VERSION = 2;
|
|
59
|
-
const REVIEW_ROUND_TO_VERSION = 3;
|
|
60
|
-
const REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION = 3;
|
|
61
|
-
const REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION = 4;
|
|
62
|
-
const REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION = 4;
|
|
63
|
-
const REVIEW_ROUND_TASK_ANCHOR_TO_VERSION = 5;
|
|
64
|
-
const REVIEW_ROUND_ACTOR_FROM_VERSION = 5;
|
|
65
|
-
const REVIEW_ROUND_ACTOR_TO_VERSION = 6;
|
|
66
|
-
const ACTIVE_RUN_POINTER_FROM_VERSION = 1;
|
|
67
|
-
const ACTIVE_RUN_POINTER_TO_VERSION = 2;
|
|
68
|
-
const ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION = 2;
|
|
69
|
-
const ACTIVE_RUN_POINTER_NAMESPACE_TO_VERSION = 3;
|
|
70
|
-
const MANAGED_WORKSPACE_FROM_VERSION = 1;
|
|
71
|
-
const MANAGED_WORKSPACE_TO_VERSION = 2;
|
|
72
|
-
const CHANGE_SET_MANIFEST_FROM_VERSION = 2;
|
|
73
|
-
const CHANGE_SET_MANIFEST_TO_VERSION = 3;
|
|
74
|
-
const INTEGRATION_ATTEMPT_FROM_VERSION = 2;
|
|
75
|
-
const INTEGRATION_ATTEMPT_TO_VERSION = 3;
|
|
76
|
-
const INTEGRATION_ATTEMPT_GATE_IDENTITY_FROM_VERSION = 3;
|
|
77
|
-
const INTEGRATION_ATTEMPT_GATE_IDENTITY_TO_VERSION = 4;
|
|
78
|
-
const INTEGRATION_ATTEMPT_ACTOR_FROM_VERSION = 4;
|
|
79
|
-
const INTEGRATION_ATTEMPT_ACTOR_TO_VERSION = 5;
|
|
80
|
-
const MILESTONE_ACTOR_FROM_VERSION = 1;
|
|
81
|
-
const MILESTONE_ACTOR_TO_VERSION = 2;
|
|
82
|
-
const INTEGRATION_QUEUE_FROM_VERSION = 0;
|
|
83
|
-
const INTEGRATION_QUEUE_TO_VERSION = 1;
|
|
84
|
-
const CONTEXT_SNAPSHOT_FROM_VERSION = 0;
|
|
85
|
-
const CONTEXT_SNAPSHOT_TO_VERSION = 1;
|
|
86
|
-
const STORED_TASK_DURABLE_JOBS_FROM_VERSION = 14;
|
|
87
|
-
const STORED_TASK_DURABLE_JOBS_TO_VERSION = 15;
|
|
88
|
-
const STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION = 15;
|
|
89
|
-
const STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION = 16;
|
|
90
|
-
const STORED_TASK_WAKES_FROM_VERSION = 16;
|
|
91
|
-
const STORED_TASK_WAKES_TO_VERSION = 17;
|
|
92
|
-
const STORED_TASK_EVENT_NOTICES_FROM_VERSION = 17;
|
|
93
|
-
const STORED_TASK_EVENT_NOTICES_TO_VERSION = 18;
|
|
94
|
-
const TASK_WAKE_FROM_VERSION = 0;
|
|
95
|
-
const TASK_WAKE_TO_VERSION = 1;
|
|
96
|
-
const STORED_TASK_PUBLICATION_REFERENCES_FROM_VERSION = 16;
|
|
97
|
-
const STORED_TASK_PUBLICATION_REFERENCES_TO_VERSION = 17;
|
|
98
|
-
const CAPABILITY_GRANT_FROM_VERSION = 0;
|
|
99
|
-
const CAPABILITY_GRANT_TO_VERSION = 1;
|
|
100
|
-
const RELEASE_WORKFLOW_FROM_VERSION = 0;
|
|
101
|
-
const RELEASE_WORKFLOW_TO_VERSION = 1;
|
|
102
|
-
const WORK_MAILBOX_FROM_VERSION = 1;
|
|
103
|
-
const WORK_MAILBOX_TO_VERSION = 2;
|
|
104
|
-
const HOST_OWNED_WORK_MAILBOX_FROM_VERSION = 2;
|
|
105
|
-
const HOST_OWNED_WORK_MAILBOX_TO_VERSION = 3;
|
|
106
|
-
const TASK_ROLE_FROM_VERSION = 3;
|
|
107
|
-
const TASK_ROLE_TO_VERSION = 4;
|
|
108
|
-
const TASK_ROLE_SESSION_SET_FROM_VERSION = 4;
|
|
109
|
-
const TASK_ROLE_SESSION_SET_TO_VERSION = 5;
|
|
110
|
-
const STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION = 5;
|
|
111
|
-
const STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION = 6;
|
|
112
|
-
const AGENT_FIRST_SESSION_SET_FROM_VERSION = 6;
|
|
113
|
-
const AGENT_FIRST_SESSION_SET_TO_VERSION = 7;
|
|
114
|
-
const UNIFIED_TASK_SESSION_SET_FROM_VERSION = 7;
|
|
115
|
-
const UNIFIED_TASK_SESSION_SET_TO_VERSION = 8;
|
|
116
|
-
const UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION = 3;
|
|
117
|
-
const UNIFIED_GLOBAL_SESSION_SET_TO_VERSION = 4;
|
|
118
|
-
const PUBLICATION_REFERENCE_FROM_VERSION = 0;
|
|
119
|
-
const PUBLICATION_REFERENCE_TO_VERSION = 1;
|
|
120
|
-
const CONFIG_FROM_VERSION = 1;
|
|
121
|
-
const CONFIG_TO_VERSION = 2;
|
|
122
|
-
const CONFIG_REVIEW_CONTROLS_FROM_VERSION = 2;
|
|
123
|
-
const CONFIG_REVIEW_CONTROLS_TO_VERSION = 3;
|
|
124
|
-
const CONFIG_AGENT_RECOVERY_FROM_VERSION = 3;
|
|
125
|
-
const CONFIG_AGENT_RECOVERY_TO_VERSION = 4;
|
|
126
|
-
/**
|
|
127
|
-
* Build the authoritative production graph. Transition intent and executable
|
|
128
|
-
* transforms are registered together here; compatible loading and offline
|
|
129
|
-
* migration consume this same graph.
|
|
130
|
-
*/
|
|
131
|
-
export function createProductionStorageRegistry() {
|
|
132
|
-
assertBaselineConsistency();
|
|
133
|
-
const registry = new MigrationRegistry().registerOfflineMigration({
|
|
134
|
-
axis: "layout",
|
|
135
|
-
fromVersion: SQLITE_LAYOUT_FROM_VERSION,
|
|
136
|
-
toVersion: SQLITE_LAYOUT_TO_VERSION,
|
|
137
|
-
preconditions: requireLayoutV6Snapshot,
|
|
138
|
-
transform: migrateLayoutV6ToV7,
|
|
139
|
-
declaredEffects: []
|
|
140
|
-
})
|
|
141
|
-
.registerOfflineMigration({
|
|
142
|
-
axis: "aggregate",
|
|
143
|
-
fromVersion: FINAL_REVIEW_AGGREGATE_FROM_VERSION,
|
|
144
|
-
toVersion: FINAL_REVIEW_AGGREGATE_TO_VERSION,
|
|
145
|
-
preconditions: requireAggregateV16Snapshot,
|
|
146
|
-
transform: migrateAggregateV16ToV17,
|
|
147
|
-
declaredEffects: []
|
|
148
|
-
})
|
|
149
|
-
.registerOfflineMigration({
|
|
150
|
-
axis: "aggregate",
|
|
151
|
-
fromVersion: HOME_IDENTITY_AGGREGATE_FROM_VERSION,
|
|
152
|
-
toVersion: HOME_IDENTITY_AGGREGATE_TO_VERSION,
|
|
153
|
-
preconditions: requireAggregateV17Snapshot,
|
|
154
|
-
transform: migrateAggregateV17ToV18,
|
|
155
|
-
declaredEffects: []
|
|
156
|
-
})
|
|
157
|
-
.registerOfflineMigration({
|
|
158
|
-
axis: "aggregate",
|
|
159
|
-
fromVersion: MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION,
|
|
160
|
-
toVersion: MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION,
|
|
161
|
-
preconditions: requireAggregateV19Snapshot,
|
|
162
|
-
transform: migrateAggregateV19ToV20,
|
|
163
|
-
declaredEffects: []
|
|
164
|
-
})
|
|
165
|
-
.registerOfflineMigration({
|
|
166
|
-
axis: "aggregate",
|
|
167
|
-
fromVersion: RUNTIME_OBSERVATION_AGGREGATE_FROM_VERSION,
|
|
168
|
-
toVersion: RUNTIME_OBSERVATION_AGGREGATE_TO_VERSION,
|
|
169
|
-
preconditions: requireAggregateV18Snapshot,
|
|
170
|
-
transform: migrateAggregateV18ToV19,
|
|
171
|
-
declaredEffects: []
|
|
172
|
-
})
|
|
173
|
-
.registerOfflineMigration(projectOwnershipStep())
|
|
174
|
-
.registerOfflineMigration(configV2Step())
|
|
175
|
-
.registerOfflineMigration(configReviewControlsRemovalStep())
|
|
176
|
-
.registerOfflineMigration(configAgentRecoveryStep())
|
|
177
|
-
.registerCompatible(projectKnowledgeProposalsStep())
|
|
178
|
-
.registerCompatible(projectLifecycleStep())
|
|
179
|
-
.registerOfflineMigration(taskWorkspaceIdentityStep())
|
|
180
|
-
.registerOfflineMigration(taskIntentStep())
|
|
181
|
-
.registerOfflineMigration(taskExecutionGateStep())
|
|
182
|
-
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_FROM_VERSION, WORK_ITEM_TO_VERSION, "workItems"))
|
|
183
|
-
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_GIT_SNAPSHOT_FROM_VERSION, WORK_ITEM_GIT_SNAPSHOT_TO_VERSION, "workItems"))
|
|
184
|
-
.registerOfflineMigration(workItemExecutionGroupHistoryStep())
|
|
185
|
-
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_EXPLORATION_STAGE_FROM_VERSION, WORK_ITEM_EXPLORATION_STAGE_TO_VERSION, "workItems"))
|
|
186
|
-
// Candidate convergence is frozen only on newly created exploration
|
|
187
|
-
// histories. Existing valid T4 histories remain byte-for-byte evidence,
|
|
188
|
-
// so this adjacent persistent transition advances only the family version.
|
|
189
|
-
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_CANDIDATE_CONVERGENCE_FROM_VERSION, WORK_ITEM_CANDIDATE_CONVERGENCE_TO_VERSION, "workItems"))
|
|
190
|
-
// Existing v11 histories remain valid immutable evidence without a T6
|
|
191
|
-
// resource policy. Newly planned stages always freeze the full policy.
|
|
192
|
-
.registerOfflineMigration(recordFamilyStep("workItem", WORK_ITEM_RESOURCE_SCHEDULING_FROM_VERSION, WORK_ITEM_RESOURCE_SCHEDULING_TO_VERSION, "workItems"))
|
|
193
|
-
.registerOfflineMigration(recordFamilyStep("agentRun", AGENT_RUN_FROM_VERSION, AGENT_RUN_TO_VERSION, "agentRuns"))
|
|
194
|
-
.registerOfflineMigration(recordFamilyStep("agentRun", AGENT_RUN_OPTIONAL_FIELDS_FROM_VERSION, AGENT_RUN_OPTIONAL_FIELDS_TO_VERSION, "agentRuns"))
|
|
195
|
-
.registerOfflineMigration(agentRunContextProtocolStep())
|
|
196
|
-
.registerOfflineMigration(agentRunRetryEpisodeStep())
|
|
197
|
-
.registerOfflineMigration(agentRunErrorFactsStep())
|
|
198
|
-
.registerOfflineMigration(messageWakePolicyStep())
|
|
199
|
-
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_FROM_VERSION, REVIEW_ROUND_TO_VERSION, "reviewRounds"))
|
|
200
|
-
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_GIT_SNAPSHOT_FROM_VERSION, REVIEW_ROUND_GIT_SNAPSHOT_TO_VERSION, "reviewRounds"))
|
|
201
|
-
.registerOfflineMigration(reviewRoundTaskAnchorStep())
|
|
202
|
-
.registerOfflineMigration(recordFamilyStep("reviewRound", REVIEW_ROUND_ACTOR_FROM_VERSION, REVIEW_ROUND_ACTOR_TO_VERSION, "reviewRounds"))
|
|
203
|
-
.registerOfflineMigration(recordFamilyStep("activeRunPointer", ACTIVE_RUN_POINTER_FROM_VERSION, ACTIVE_RUN_POINTER_TO_VERSION, "activeRuns"))
|
|
204
|
-
.registerOfflineMigration(managedWorkspaceFamilyStep())
|
|
205
|
-
.registerOfflineMigration(activeRunPointerNamespaceStep())
|
|
206
|
-
.registerOfflineMigration(changeSetManifestStep())
|
|
207
|
-
.registerOfflineMigration(integrationAttemptSupersededStep())
|
|
208
|
-
.registerOfflineMigration(recordFamilyStep("integrationAttempt", INTEGRATION_ATTEMPT_GATE_IDENTITY_FROM_VERSION, INTEGRATION_ATTEMPT_GATE_IDENTITY_TO_VERSION, "integrationAttempts"))
|
|
209
|
-
.registerOfflineMigration(recordFamilyStep("integrationAttempt", INTEGRATION_ATTEMPT_ACTOR_FROM_VERSION, INTEGRATION_ATTEMPT_ACTOR_TO_VERSION, "integrationAttempts"))
|
|
210
|
-
.registerOfflineMigration(recordFamilyStep("milestone", MILESTONE_ACTOR_FROM_VERSION, MILESTONE_ACTOR_TO_VERSION, "milestones"))
|
|
211
|
-
.registerOfflineMigration(integrationQueueIntroductionStep())
|
|
212
|
-
.registerOfflineMigration(contextSnapshotIntroductionStep())
|
|
213
|
-
.registerCompatible(storedTaskDurableJobsStep())
|
|
214
|
-
.registerCompatible(storedTaskJobCallerKeyHashesStep())
|
|
215
|
-
.registerCompatible(storedTaskWakesAndPublicationReferencesStep())
|
|
216
|
-
.registerOfflineMigration(storedTaskEventNoticesStep())
|
|
217
|
-
.registerCompatible(taskWakeIntroductionStep())
|
|
218
|
-
.registerCompatible(durableJobIntroductionStep())
|
|
219
|
-
.registerOfflineMigration(capabilityGrantIntroductionStep())
|
|
220
|
-
.registerOfflineMigration(releaseWorkflowIntroductionStep())
|
|
221
|
-
.registerOfflineMigration(workMailboxV2Step())
|
|
222
|
-
.registerOfflineMigration(hostOwnedWorkMailboxStep())
|
|
223
|
-
.registerOfflineMigration(agentRunOwnedTaskRoleStep())
|
|
224
|
-
.registerOfflineMigration(taskRoleSessionSetV5Step())
|
|
225
|
-
.registerOfflineMigration(structuredProviderSessionSetV6Step())
|
|
226
|
-
.registerOfflineMigration(agentFirstSessionSetV7Step())
|
|
227
|
-
.registerOfflineMigration(unifiedTaskSessionSetStep())
|
|
228
|
-
.registerOfflineMigration(unifiedGlobalSessionSetStep())
|
|
229
|
-
.registerOfflineMigration(publicationReferenceIntroductionStep());
|
|
230
|
-
assertRegistryCoversBaselineToCurrent(registry);
|
|
231
|
-
return registry;
|
|
232
|
-
}
|
|
233
|
-
/** Removes the independently writable Role runtime projection; AgentRun owns workflow activity. */
|
|
234
|
-
function agentRunOwnedTaskRoleStep() {
|
|
235
|
-
return {
|
|
236
|
-
axis: "record",
|
|
237
|
-
recordKind: "taskRole",
|
|
238
|
-
fromVersion: TASK_ROLE_FROM_VERSION,
|
|
239
|
-
toVersion: TASK_ROLE_TO_VERSION,
|
|
240
|
-
preconditions: requireTaskRoleV3Family,
|
|
241
|
-
transform: migrateTaskRoleV3ToV4,
|
|
242
|
-
declaredEffects: []
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
function requireTaskRoleV3Family(snapshot) {
|
|
246
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
247
|
-
if (versions.taskRole !== TASK_ROLE_FROM_VERSION) {
|
|
248
|
-
throw new Error(`Record taskRole migration requires manifest version ${TASK_ROLE_FROM_VERSION}.`);
|
|
249
|
-
}
|
|
250
|
-
if (snapshot.state === null)
|
|
251
|
-
return;
|
|
252
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
253
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
254
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
255
|
-
const roles = asObject(aggregate.roles, `Task Roles ${taskId}`);
|
|
256
|
-
for (const [roleName, rawRole] of Object.entries(roles)) {
|
|
257
|
-
const role = asObject(rawRole, `Task Role ${taskId}/${roleName}`);
|
|
258
|
-
if (role.schemaVersion !== TASK_ROLE_FROM_VERSION) {
|
|
259
|
-
throw new Error(`Task Role ${taskId}/${roleName} must use schemaVersion ${TASK_ROLE_FROM_VERSION}.`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
function migrateTaskRoleV3ToV4(snapshot) {
|
|
265
|
-
requireTaskRoleV3Family(snapshot);
|
|
266
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
267
|
-
const schemaManifest = {
|
|
268
|
-
...snapshot.schemaManifest,
|
|
269
|
-
recordVersions: { ...versions, taskRole: TASK_ROLE_TO_VERSION }
|
|
270
|
-
};
|
|
271
|
-
if (snapshot.state === null)
|
|
272
|
-
return { schemaManifest, state: null };
|
|
273
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
274
|
-
const nextTasks = Object.fromEntries(Object.entries(tasks).map(([taskId, rawAggregate]) => {
|
|
275
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
276
|
-
const roles = asObject(aggregate.roles, `Task Roles ${taskId}`);
|
|
277
|
-
const nextRoles = Object.fromEntries(Object.entries(roles).map(([roleName, rawRole]) => {
|
|
278
|
-
const role = asObject(rawRole, `Task Role ${taskId}/${roleName}`);
|
|
279
|
-
const { status: _status, ...retained } = role;
|
|
280
|
-
void _status;
|
|
281
|
-
return [roleName, { ...retained, schemaVersion: TASK_ROLE_TO_VERSION }];
|
|
282
|
-
}));
|
|
283
|
-
return [taskId, { ...aggregate, roles: nextRoles }];
|
|
284
|
-
}));
|
|
285
|
-
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
286
|
-
}
|
|
287
|
-
function configV2Step() {
|
|
288
|
-
return {
|
|
289
|
-
axis: "record",
|
|
290
|
-
recordKind: "config",
|
|
291
|
-
fromVersion: CONFIG_FROM_VERSION,
|
|
292
|
-
toVersion: CONFIG_TO_VERSION,
|
|
293
|
-
preconditions: requireConfigV1,
|
|
294
|
-
transform: migrateConfigV1ToV2,
|
|
295
|
-
declaredEffects: []
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
function requireConfigV1(snapshot) {
|
|
299
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
300
|
-
if (versions.config !== CONFIG_FROM_VERSION) {
|
|
301
|
-
throw new Error(`Record config migration requires manifest version ${CONFIG_FROM_VERSION}.`);
|
|
302
|
-
}
|
|
303
|
-
if (snapshot.state === null)
|
|
304
|
-
return;
|
|
305
|
-
const config = asObject(snapshot.state.config, "Yui config");
|
|
306
|
-
if (config.schemaVersion !== CONFIG_FROM_VERSION) {
|
|
307
|
-
throw new Error(`Yui config must use schemaVersion ${CONFIG_FROM_VERSION} before migration.`);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
function migrateConfigV1ToV2(snapshot) {
|
|
311
|
-
requireConfigV1(snapshot);
|
|
312
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
313
|
-
const schemaManifest = {
|
|
314
|
-
...snapshot.schemaManifest,
|
|
315
|
-
recordVersions: { ...versions, config: CONFIG_TO_VERSION }
|
|
316
|
-
};
|
|
317
|
-
if (snapshot.state === null)
|
|
318
|
-
return { schemaManifest, state: null };
|
|
319
|
-
const config = asObject(snapshot.state.config, "Yui config");
|
|
320
|
-
const { providerRetryMaxWindowMs, yieldReceiptReplay: _yieldReceiptReplay, gitBin: _gitBin, telemetryMode, schemaVersion: _schemaVersion, ...retained } = config;
|
|
321
|
-
const providerRetryMaxWindowSeconds = typeof providerRetryMaxWindowMs === "number"
|
|
322
|
-
? Math.ceil(providerRetryMaxWindowMs / 1_000)
|
|
323
|
-
: undefined;
|
|
324
|
-
const telemetryEnabled = telemetryMode === undefined
|
|
325
|
-
? undefined
|
|
326
|
-
: telemetryMode === "dual" || telemetryMode === "bounded";
|
|
327
|
-
return {
|
|
328
|
-
schemaManifest,
|
|
329
|
-
state: {
|
|
330
|
-
...snapshot.state,
|
|
331
|
-
config: {
|
|
332
|
-
...retained,
|
|
333
|
-
schemaVersion: CONFIG_TO_VERSION,
|
|
334
|
-
...(providerRetryMaxWindowSeconds === undefined
|
|
335
|
-
? {}
|
|
336
|
-
: { providerRetryMaxWindowSeconds }),
|
|
337
|
-
...(telemetryEnabled === undefined ? {} : { telemetryEnabled })
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
/** Removes retired Core controls for the Leader-owned Delta decision. */
|
|
343
|
-
function configReviewControlsRemovalStep() {
|
|
344
|
-
return {
|
|
345
|
-
axis: "record",
|
|
346
|
-
recordKind: "config",
|
|
347
|
-
fromVersion: CONFIG_REVIEW_CONTROLS_FROM_VERSION,
|
|
348
|
-
toVersion: CONFIG_REVIEW_CONTROLS_TO_VERSION,
|
|
349
|
-
preconditions: requireConfigV2,
|
|
350
|
-
transform: migrateConfigV2ToV3,
|
|
351
|
-
declaredEffects: []
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
function requireConfigV2(snapshot) {
|
|
355
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
356
|
-
if (versions.config !== CONFIG_REVIEW_CONTROLS_FROM_VERSION) {
|
|
357
|
-
throw new Error(`Record config migration requires manifest version ${CONFIG_REVIEW_CONTROLS_FROM_VERSION}.`);
|
|
358
|
-
}
|
|
359
|
-
if (snapshot.state === null)
|
|
360
|
-
return;
|
|
361
|
-
const config = asObject(snapshot.state.config, "Yui config");
|
|
362
|
-
if (config.schemaVersion !== CONFIG_REVIEW_CONTROLS_FROM_VERSION) {
|
|
363
|
-
throw new Error(`Yui config must use schemaVersion ${CONFIG_REVIEW_CONTROLS_FROM_VERSION} before migration.`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
function migrateConfigV2ToV3(snapshot) {
|
|
367
|
-
requireConfigV2(snapshot);
|
|
368
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
369
|
-
const schemaManifest = {
|
|
370
|
-
...snapshot.schemaManifest,
|
|
371
|
-
recordVersions: { ...versions, config: CONFIG_REVIEW_CONTROLS_TO_VERSION }
|
|
372
|
-
};
|
|
373
|
-
if (snapshot.state === null)
|
|
374
|
-
return { schemaManifest, state: null };
|
|
375
|
-
const config = asObject(snapshot.state.config, "Yui config");
|
|
376
|
-
const review = config.review === undefined
|
|
377
|
-
? undefined
|
|
378
|
-
: asObject(config.review, "Yui review config");
|
|
379
|
-
const migratedReview = review === undefined
|
|
380
|
-
? undefined
|
|
381
|
-
: (() => {
|
|
382
|
-
const { deltaRecheck: _retiredSwitch, deltaRecheckMaxChangedLines: _retiredLineThreshold, deltaRecheckMaxChangedFiles: _retiredFileThreshold, ...retained } = review;
|
|
383
|
-
return retained;
|
|
384
|
-
})();
|
|
385
|
-
return {
|
|
386
|
-
schemaManifest,
|
|
387
|
-
state: {
|
|
388
|
-
...snapshot.state,
|
|
389
|
-
config: {
|
|
390
|
-
...config,
|
|
391
|
-
schemaVersion: CONFIG_REVIEW_CONTROLS_TO_VERSION,
|
|
392
|
-
...(migratedReview === undefined ? {} : { review: migratedReview })
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
/** Removes Core-owned Provider retry policy; recovery is chosen from AgentError facts. */
|
|
398
|
-
function configAgentRecoveryStep() {
|
|
399
|
-
return {
|
|
400
|
-
axis: "record",
|
|
401
|
-
recordKind: "config",
|
|
402
|
-
fromVersion: CONFIG_AGENT_RECOVERY_FROM_VERSION,
|
|
403
|
-
toVersion: CONFIG_AGENT_RECOVERY_TO_VERSION,
|
|
404
|
-
preconditions: requireConfigV3,
|
|
405
|
-
transform: migrateConfigV3ToV4,
|
|
406
|
-
declaredEffects: []
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
function requireConfigV3(snapshot) {
|
|
410
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
411
|
-
if (versions.config !== CONFIG_AGENT_RECOVERY_FROM_VERSION) {
|
|
412
|
-
throw new Error(`Record config migration requires manifest version ${CONFIG_AGENT_RECOVERY_FROM_VERSION}.`);
|
|
413
|
-
}
|
|
414
|
-
if (snapshot.state === null)
|
|
415
|
-
return;
|
|
416
|
-
const config = asObject(snapshot.state.config, "Yui config");
|
|
417
|
-
if (config.schemaVersion !== CONFIG_AGENT_RECOVERY_FROM_VERSION) {
|
|
418
|
-
throw new Error(`Yui config must use schemaVersion ${CONFIG_AGENT_RECOVERY_FROM_VERSION} before migration.`);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
function migrateConfigV3ToV4(snapshot) {
|
|
422
|
-
requireConfigV3(snapshot);
|
|
423
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
424
|
-
const schemaManifest = {
|
|
425
|
-
...snapshot.schemaManifest,
|
|
426
|
-
recordVersions: { ...versions, config: CONFIG_AGENT_RECOVERY_TO_VERSION }
|
|
427
|
-
};
|
|
428
|
-
if (snapshot.state === null)
|
|
429
|
-
return { schemaManifest, state: null };
|
|
430
|
-
const config = asObject(snapshot.state.config, "Yui config");
|
|
431
|
-
const { providerRetryMode: _mode, providerRetryAdapters: _adapters, providerRetryDelaysSeconds: _delays, providerRetryMaxWindowSeconds: _window, ...retained } = config;
|
|
432
|
-
void _mode;
|
|
433
|
-
void _adapters;
|
|
434
|
-
void _delays;
|
|
435
|
-
void _window;
|
|
436
|
-
return {
|
|
437
|
-
schemaManifest,
|
|
438
|
-
state: {
|
|
439
|
-
...snapshot.state,
|
|
440
|
-
config: { ...retained, schemaVersion: CONFIG_AGENT_RECOVERY_TO_VERSION }
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
function workItemExecutionGroupHistoryStep() {
|
|
445
|
-
return {
|
|
446
|
-
axis: "record",
|
|
447
|
-
recordKind: "workItem",
|
|
448
|
-
fromVersion: WORK_ITEM_GROUP_HISTORY_FROM_VERSION,
|
|
449
|
-
toVersion: WORK_ITEM_GROUP_HISTORY_TO_VERSION,
|
|
450
|
-
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, "workItem", WORK_ITEM_GROUP_HISTORY_FROM_VERSION, "workItems"),
|
|
451
|
-
transform: migrateWorkItemExecutionGroupHistory,
|
|
452
|
-
declaredEffects: []
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Task Messages gain an optional `wakePolicy` field (Issue 05). The field is
|
|
457
|
-
* absent on every older record, so this adjacent step only advances the
|
|
458
|
-
* family version; the strict current parser treats the omitted field as
|
|
459
|
-
* "no explicit policy" and preserves the existing routing.
|
|
460
|
-
*/
|
|
461
|
-
function messageWakePolicyStep() {
|
|
462
|
-
return {
|
|
463
|
-
axis: "record",
|
|
464
|
-
recordKind: "message",
|
|
465
|
-
fromVersion: MESSAGE_WAKE_POLICY_FROM_VERSION,
|
|
466
|
-
toVersion: MESSAGE_WAKE_POLICY_TO_VERSION,
|
|
467
|
-
preconditions: (snapshot) => requireMessageFamilyVersion(snapshot, MESSAGE_WAKE_POLICY_FROM_VERSION),
|
|
468
|
-
transform: migrateMessageWakePolicy,
|
|
469
|
-
declaredEffects: []
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
function requireMessageFamilyVersion(snapshot, version) {
|
|
473
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
474
|
-
if (manifestVersions.message !== version) {
|
|
475
|
-
throw new Error(`Message migration requires manifest version ${version}.`);
|
|
476
|
-
}
|
|
477
|
-
if (snapshot.state === null)
|
|
478
|
-
return;
|
|
479
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
480
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
481
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
482
|
-
const messages = task.messages;
|
|
483
|
-
if (messages === undefined)
|
|
484
|
-
continue;
|
|
485
|
-
const map = asObject(messages, `message map ${taskId}`);
|
|
486
|
-
for (const [messageId, rawMessage] of Object.entries(map)) {
|
|
487
|
-
const message = asObject(rawMessage, `message ${taskId}/${messageId}`);
|
|
488
|
-
if (message.schemaVersion !== version) {
|
|
489
|
-
throw new Error(`Message ${taskId}/${messageId} must use schemaVersion ${version}.`);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
function migrateMessageWakePolicy(snapshot) {
|
|
495
|
-
requireMessageFamilyVersion(snapshot, MESSAGE_WAKE_POLICY_FROM_VERSION);
|
|
496
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
497
|
-
const schemaManifest = {
|
|
498
|
-
...snapshot.schemaManifest,
|
|
499
|
-
recordVersions: {
|
|
500
|
-
...manifestVersions,
|
|
501
|
-
message: MESSAGE_WAKE_POLICY_TO_VERSION
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
if (snapshot.state === null)
|
|
505
|
-
return { schemaManifest, state: null };
|
|
506
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
507
|
-
const nextTasks = {};
|
|
508
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
509
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
510
|
-
if (task.messages === undefined) {
|
|
511
|
-
nextTasks[taskId] = { ...task };
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
const messages = asObject(task.messages, `message map ${taskId}`);
|
|
515
|
-
const nextMessages = {};
|
|
516
|
-
for (const [messageId, rawMessage] of Object.entries(messages)) {
|
|
517
|
-
nextMessages[messageId] = {
|
|
518
|
-
...asObject(rawMessage, `message ${taskId}/${messageId}`),
|
|
519
|
-
schemaVersion: MESSAGE_WAKE_POLICY_TO_VERSION
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
nextTasks[taskId] = { ...task, messages: nextMessages };
|
|
523
|
-
}
|
|
524
|
-
return {
|
|
525
|
-
schemaManifest,
|
|
526
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* ManagedWorkspace is stored once in the owner-keyed workspace map and copied
|
|
531
|
-
* into Candidates, AgentRuns, and ReviewRounds as immutable lifecycle
|
|
532
|
-
* evidence. Those copies are the same record family, not independent parent
|
|
533
|
-
* records: advancing only the map leaves an otherwise legal historical Home
|
|
534
|
-
* unreadable by the strict current parser.
|
|
535
|
-
*/
|
|
536
|
-
function managedWorkspaceFamilyStep() {
|
|
537
|
-
return {
|
|
538
|
-
axis: "record",
|
|
539
|
-
recordKind: "managedWorkspace",
|
|
540
|
-
fromVersion: MANAGED_WORKSPACE_FROM_VERSION,
|
|
541
|
-
toVersion: MANAGED_WORKSPACE_TO_VERSION,
|
|
542
|
-
preconditions: requireLegacyManagedWorkspaceFamily,
|
|
543
|
-
transform: migrateManagedWorkspaceFamily,
|
|
544
|
-
declaredEffects: []
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
function requireLegacyManagedWorkspaceFamily(snapshot) {
|
|
548
|
-
requireRecordFamilyVersion(snapshot, "managedWorkspace", MANAGED_WORKSPACE_FROM_VERSION, "managedWorkspaces");
|
|
549
|
-
visitEmbeddedManagedWorkspaces(snapshot, (workspace, label) => {
|
|
550
|
-
requireManagedWorkspaceVersion(workspace, MANAGED_WORKSPACE_FROM_VERSION, label);
|
|
551
|
-
return workspace;
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
function migrateManagedWorkspaceFamily(snapshot) {
|
|
555
|
-
requireLegacyManagedWorkspaceFamily(snapshot);
|
|
556
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
557
|
-
const schemaManifest = {
|
|
558
|
-
...snapshot.schemaManifest,
|
|
559
|
-
recordVersions: {
|
|
560
|
-
...manifestVersions,
|
|
561
|
-
managedWorkspace: MANAGED_WORKSPACE_TO_VERSION
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
|
-
if (snapshot.state === null)
|
|
565
|
-
return { schemaManifest, state: null };
|
|
566
|
-
const migrated = visitEmbeddedManagedWorkspaces(snapshot, (workspace) => ({ ...workspace, schemaVersion: MANAGED_WORKSPACE_TO_VERSION }));
|
|
567
|
-
return { schemaManifest, state: migrated.state };
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Rewrite every persisted ManagedWorkspace occurrence while preserving the
|
|
571
|
-
* surrounding record bytes. The direct map is handled by the ordinary
|
|
572
|
-
* record-family migration; this traversal covers the immutable embedded
|
|
573
|
-
* snapshots which the strict validators also treat as ManagedWorkspace.
|
|
574
|
-
*/
|
|
575
|
-
function visitEmbeddedManagedWorkspaces(snapshot, visit) {
|
|
576
|
-
if (snapshot.state === null)
|
|
577
|
-
return snapshot;
|
|
578
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
579
|
-
const nextTasks = {};
|
|
580
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
581
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
582
|
-
const nextTask = { ...task };
|
|
583
|
-
if (task.managedWorkspaces !== undefined) {
|
|
584
|
-
const workspaces = asObject(task.managedWorkspaces, `managedWorkspace map ${taskId}`);
|
|
585
|
-
nextTask.managedWorkspaces = Object.fromEntries(Object.entries(workspaces).map(([recordId, rawWorkspace]) => [
|
|
586
|
-
recordId,
|
|
587
|
-
visit(asObject(rawWorkspace, `managedWorkspace ${taskId}/${recordId}`), `managedWorkspace ${taskId}/${recordId}`)
|
|
588
|
-
]));
|
|
589
|
-
}
|
|
590
|
-
if (task.workItems !== undefined) {
|
|
591
|
-
const workItems = asObject(task.workItems, `workItem map ${taskId}`);
|
|
592
|
-
nextTask.workItems = Object.fromEntries(Object.entries(workItems).map(([workItemId, rawWorkItem]) => {
|
|
593
|
-
const workItem = asObject(rawWorkItem, `workItem ${taskId}/${workItemId}`);
|
|
594
|
-
if (workItem.candidates === undefined)
|
|
595
|
-
return [workItemId, { ...workItem }];
|
|
596
|
-
if (!Array.isArray(workItem.candidates)) {
|
|
597
|
-
throw new Error(`workItem ${taskId}/${workItemId} candidates must be an array.`);
|
|
598
|
-
}
|
|
599
|
-
return [workItemId, {
|
|
600
|
-
...workItem,
|
|
601
|
-
candidates: workItem.candidates.map((rawCandidate, index) => {
|
|
602
|
-
const candidate = asObject(rawCandidate, `Candidate ${taskId}/${workItemId}/${index}`);
|
|
603
|
-
return migrateOptionalManagedWorkspace(candidate, `Candidate ${taskId}/${workItemId}/${index}`, visit);
|
|
604
|
-
})
|
|
605
|
-
}];
|
|
606
|
-
}));
|
|
607
|
-
}
|
|
608
|
-
for (const mapKey of ["agentRuns", "reviewRounds"]) {
|
|
609
|
-
if (task[mapKey] === undefined)
|
|
610
|
-
continue;
|
|
611
|
-
const records = asObject(task[mapKey], `${mapKey} map ${taskId}`);
|
|
612
|
-
nextTask[mapKey] = Object.fromEntries(Object.entries(records).map(([recordId, rawRecord]) => [
|
|
613
|
-
recordId,
|
|
614
|
-
migrateOptionalManagedWorkspace(asObject(rawRecord, `${mapKey} ${taskId}/${recordId}`), `${mapKey} ${taskId}/${recordId}`, visit)
|
|
615
|
-
]));
|
|
616
|
-
}
|
|
617
|
-
nextTasks[taskId] = nextTask;
|
|
618
|
-
}
|
|
619
|
-
return {
|
|
620
|
-
schemaManifest: snapshot.schemaManifest,
|
|
621
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
function migrateOptionalManagedWorkspace(record, label, visit) {
|
|
625
|
-
if (record.workspace === undefined)
|
|
626
|
-
return { ...record };
|
|
627
|
-
return {
|
|
628
|
-
...record,
|
|
629
|
-
workspace: visit(asObject(record.workspace, `${label} workspace`), `${label} workspace`)
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
function requireManagedWorkspaceVersion(workspace, version, label) {
|
|
633
|
-
if (workspace.schemaVersion !== version) {
|
|
634
|
-
throw new Error(`${label} must use schemaVersion ${version}.`);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
function migrateWorkItemExecutionGroupHistory(snapshot) {
|
|
638
|
-
requireRecordFamilyVersion(snapshot, "workItem", WORK_ITEM_GROUP_HISTORY_FROM_VERSION, "workItems");
|
|
639
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
640
|
-
const schemaManifest = {
|
|
641
|
-
...snapshot.schemaManifest,
|
|
642
|
-
recordVersions: { ...manifestVersions, workItem: WORK_ITEM_GROUP_HISTORY_TO_VERSION }
|
|
643
|
-
};
|
|
644
|
-
if (snapshot.state === null)
|
|
645
|
-
return { schemaManifest, state: null };
|
|
646
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
647
|
-
const nextTasks = {};
|
|
648
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
649
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
650
|
-
if (task.workItems === undefined) {
|
|
651
|
-
nextTasks[taskId] = { ...task };
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
const workItems = asObject(task.workItems, `workItem map ${taskId}`);
|
|
655
|
-
const nextWorkItems = {};
|
|
656
|
-
for (const [recordId, rawRecord] of Object.entries(workItems)) {
|
|
657
|
-
const record = asObject(rawRecord, `workItem ${taskId}/${recordId}`);
|
|
658
|
-
const { executionGroup, ...rest } = record;
|
|
659
|
-
if (executionGroup !== undefined
|
|
660
|
-
&& (executionGroup === null || typeof executionGroup !== "object" || Array.isArray(executionGroup))) {
|
|
661
|
-
throw new Error(`workItem ${taskId}/${recordId} executionGroup must be an object.`);
|
|
662
|
-
}
|
|
663
|
-
const groupId = executionGroup === undefined
|
|
664
|
-
? undefined
|
|
665
|
-
: asObject(executionGroup, `ExecutionGroup ${taskId}/${recordId}`).id;
|
|
666
|
-
if (groupId !== undefined && (typeof groupId !== "string" || groupId.trim().length === 0)) {
|
|
667
|
-
throw new Error(`workItem ${taskId}/${recordId} executionGroup id is invalid.`);
|
|
668
|
-
}
|
|
669
|
-
nextWorkItems[recordId] = {
|
|
670
|
-
...rest,
|
|
671
|
-
schemaVersion: WORK_ITEM_GROUP_HISTORY_TO_VERSION,
|
|
672
|
-
executionGroups: executionGroup === undefined ? [] : [executionGroup],
|
|
673
|
-
...(groupId === undefined ? {} : { currentExecutionGroupId: groupId })
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
nextTasks[taskId] = { ...task, workItems: nextWorkItems };
|
|
677
|
-
}
|
|
678
|
-
return {
|
|
679
|
-
schemaManifest,
|
|
680
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Move the legacy `lane:<group>:<lane>` keys into a namespace that cannot be
|
|
685
|
-
* mistaken for a legal Role identity. A legacy key is only rewritten when
|
|
686
|
-
* its pointed Run proves the lane lineage; otherwise a legal Role with that
|
|
687
|
-
* exact name is retained. Ambiguous or malformed lane-looking records fail
|
|
688
|
-
* closed instead of guessing which active Run the old bytes meant.
|
|
689
|
-
*/
|
|
690
|
-
function activeRunPointerNamespaceStep() {
|
|
691
|
-
return {
|
|
692
|
-
axis: "record",
|
|
693
|
-
recordKind: "activeRunPointer",
|
|
694
|
-
fromVersion: ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION,
|
|
695
|
-
toVersion: ACTIVE_RUN_POINTER_NAMESPACE_TO_VERSION,
|
|
696
|
-
preconditions: (snapshot) => requireActiveRunPointerNamespaceVersion(snapshot),
|
|
697
|
-
transform: (snapshot) => migrateActiveRunPointerNamespace(snapshot),
|
|
698
|
-
declaredEffects: []
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
function requireActiveRunPointerNamespaceVersion(snapshot) {
|
|
702
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
703
|
-
if (manifestVersions.activeRunPointer !== ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION) {
|
|
704
|
-
throw new Error(`Record activeRunPointer migration requires manifest version ${ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION}.`);
|
|
705
|
-
}
|
|
706
|
-
if (snapshot.state === null)
|
|
707
|
-
return;
|
|
708
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
709
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
710
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
711
|
-
const activeRuns = task.activeRuns;
|
|
712
|
-
if (activeRuns === undefined)
|
|
713
|
-
continue;
|
|
714
|
-
const pointers = asObject(activeRuns, `activeRunPointer map ${taskId}`);
|
|
715
|
-
for (const [key, rawPointer] of Object.entries(pointers)) {
|
|
716
|
-
const pointer = asObject(rawPointer, `Active run ${taskId}/${key}`);
|
|
717
|
-
if (pointer.schemaVersion !== ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION) {
|
|
718
|
-
throw new Error(`Active run ${taskId}/${key} must use schemaVersion ${ACTIVE_RUN_POINTER_NAMESPACE_FROM_VERSION}.`);
|
|
719
|
-
}
|
|
720
|
-
if (typeof pointer.runId !== "string" || pointer.runId.trim().length === 0) {
|
|
721
|
-
throw new Error(`Active run ${taskId}/${key} has an invalid runId.`);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
function migrateActiveRunPointerNamespace(snapshot) {
|
|
727
|
-
requireActiveRunPointerNamespaceVersion(snapshot);
|
|
728
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
729
|
-
const schemaManifest = {
|
|
730
|
-
...snapshot.schemaManifest,
|
|
731
|
-
recordVersions: {
|
|
732
|
-
...manifestVersions,
|
|
733
|
-
activeRunPointer: ACTIVE_RUN_POINTER_NAMESPACE_TO_VERSION
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
|
-
if (snapshot.state === null)
|
|
737
|
-
return { schemaManifest, state: null };
|
|
738
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
739
|
-
const nextTasks = {};
|
|
740
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
741
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
742
|
-
const rawActiveRuns = task.activeRuns;
|
|
743
|
-
if (rawActiveRuns === undefined) {
|
|
744
|
-
nextTasks[taskId] = { ...task };
|
|
745
|
-
continue;
|
|
746
|
-
}
|
|
747
|
-
const activeRuns = asObject(rawActiveRuns, `activeRunPointer map ${taskId}`);
|
|
748
|
-
const rawAgentRuns = asObject(task.agentRuns, `agentRun map ${taskId}`);
|
|
749
|
-
const nextActiveRuns = {};
|
|
750
|
-
for (const [key, rawPointer] of Object.entries(activeRuns)) {
|
|
751
|
-
const pointer = asObject(rawPointer, `Active run ${taskId}/${key}`);
|
|
752
|
-
const runId = typeof pointer.runId === "string" ? pointer.runId : "";
|
|
753
|
-
const run = asObject(rawAgentRuns[runId], `Agent run ${taskId}/${runId}`);
|
|
754
|
-
const lane = legacyLaneKeyParts(key);
|
|
755
|
-
const migratedPointer = {
|
|
756
|
-
...pointer,
|
|
757
|
-
schemaVersion: ACTIVE_RUN_POINTER_NAMESPACE_TO_VERSION
|
|
758
|
-
};
|
|
759
|
-
const addPointer = (nextKey) => {
|
|
760
|
-
if (nextActiveRuns[nextKey] !== undefined) {
|
|
761
|
-
throw new Error(`Active run pointer key collides after migration: ${taskId}/${nextKey}.`);
|
|
762
|
-
}
|
|
763
|
-
nextActiveRuns[nextKey] = migratedPointer;
|
|
764
|
-
};
|
|
765
|
-
if (lane !== null) {
|
|
766
|
-
const laneBacked = run.executionGroupId === lane.executionGroupId
|
|
767
|
-
&& run.executionLaneId === lane.executionLaneId;
|
|
768
|
-
// The prior writer used the same map key for a legal Role and a Lane
|
|
769
|
-
// when the Role itself happened to contain the `lane:g:l` shape. The
|
|
770
|
-
// v3 namespace must retain both identities instead of rejecting the
|
|
771
|
-
// record or silently dropping the Role pointer.
|
|
772
|
-
const roleBacked = run.roleName === key;
|
|
773
|
-
if (!laneBacked && !roleBacked) {
|
|
774
|
-
throw new Error(`Active run pointer lineage is invalid: ${taskId}/${key}.`);
|
|
775
|
-
}
|
|
776
|
-
if (roleBacked)
|
|
777
|
-
addPointer(key);
|
|
778
|
-
if (laneBacked) {
|
|
779
|
-
addPointer(executionLaneNamespaceKey(lane.executionGroupId, lane.executionLaneId));
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
else if (key.startsWith("lane:")) {
|
|
783
|
-
// A malformed lane-looking key is allowed only when it is a legal
|
|
784
|
-
// legacy Role pointer. It must not be silently reinterpreted.
|
|
785
|
-
if (run.roleName !== key) {
|
|
786
|
-
throw new Error(`Active run pointer key is malformed: ${taskId}/${key}.`);
|
|
787
|
-
}
|
|
788
|
-
addPointer(key);
|
|
789
|
-
}
|
|
790
|
-
else {
|
|
791
|
-
// Role pointers remain keyed by the Role even when the pointed Run
|
|
792
|
-
// also carries execution lineage (a shape emitted by the prior
|
|
793
|
-
// writer). The lane namespace above preserves that second identity.
|
|
794
|
-
if (run.roleName !== key) {
|
|
795
|
-
throw new Error(`Active run Role pointer is invalid: ${taskId}/${key}.`);
|
|
796
|
-
}
|
|
797
|
-
addPointer(key);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
nextTasks[taskId] = { ...task, activeRuns: nextActiveRuns };
|
|
801
|
-
}
|
|
802
|
-
return {
|
|
803
|
-
schemaManifest,
|
|
804
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* ChangeSet v3 adds the optional integration manifest (tags, deleted paths,
|
|
809
|
-
* target ref, evidence references). The manifest is optional, so the
|
|
810
|
-
* transition only rewrites the record version; legacy records without a
|
|
811
|
-
* manifest remain valid and keep integrating.
|
|
812
|
-
*/
|
|
813
|
-
function changeSetManifestStep() {
|
|
814
|
-
return {
|
|
815
|
-
axis: "record",
|
|
816
|
-
recordKind: "changeSet",
|
|
817
|
-
fromVersion: CHANGE_SET_MANIFEST_FROM_VERSION,
|
|
818
|
-
toVersion: CHANGE_SET_MANIFEST_TO_VERSION,
|
|
819
|
-
preconditions: requireChangeSetManifestVersion,
|
|
820
|
-
transform: migrateChangeSetManifest,
|
|
821
|
-
declaredEffects: []
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
function requireChangeSetManifestVersion(snapshot) {
|
|
825
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
826
|
-
if (manifestVersions.changeSet !== CHANGE_SET_MANIFEST_FROM_VERSION) {
|
|
827
|
-
throw new Error(`Record changeSet migration requires manifest version ${CHANGE_SET_MANIFEST_FROM_VERSION}.`);
|
|
828
|
-
}
|
|
829
|
-
if (snapshot.state === null)
|
|
830
|
-
return;
|
|
831
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
832
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
833
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
834
|
-
if (task.changeSets === undefined)
|
|
835
|
-
continue;
|
|
836
|
-
const changeSets = asObject(task.changeSets, `changeSet map ${taskId}`);
|
|
837
|
-
for (const [changeSetId, rawRecord] of Object.entries(changeSets)) {
|
|
838
|
-
const record = asObject(rawRecord, `changeSet ${taskId}/${changeSetId}`);
|
|
839
|
-
if (record.schemaVersion !== CHANGE_SET_MANIFEST_FROM_VERSION) {
|
|
840
|
-
throw new Error(`changeSet ${taskId}/${changeSetId} must use schemaVersion ${CHANGE_SET_MANIFEST_FROM_VERSION}.`);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
function migrateChangeSetManifest(snapshot) {
|
|
846
|
-
requireChangeSetManifestVersion(snapshot);
|
|
847
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
848
|
-
const schemaManifest = {
|
|
849
|
-
...snapshot.schemaManifest,
|
|
850
|
-
recordVersions: {
|
|
851
|
-
...manifestVersions,
|
|
852
|
-
changeSet: CHANGE_SET_MANIFEST_TO_VERSION
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
if (snapshot.state === null)
|
|
856
|
-
return { schemaManifest, state: null };
|
|
857
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
858
|
-
const nextTasks = {};
|
|
859
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
860
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
861
|
-
if (task.changeSets === undefined) {
|
|
862
|
-
nextTasks[taskId] = { ...task };
|
|
863
|
-
continue;
|
|
864
|
-
}
|
|
865
|
-
const changeSets = asObject(task.changeSets, `changeSet map ${taskId}`);
|
|
866
|
-
const nextChangeSets = {};
|
|
867
|
-
for (const [changeSetId, rawRecord] of Object.entries(changeSets)) {
|
|
868
|
-
const record = asObject(rawRecord, `changeSet ${taskId}/${changeSetId}`);
|
|
869
|
-
nextChangeSets[changeSetId] = {
|
|
870
|
-
...record,
|
|
871
|
-
schemaVersion: CHANGE_SET_MANIFEST_TO_VERSION
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
nextTasks[taskId] = { ...task, changeSets: nextChangeSets };
|
|
875
|
-
}
|
|
876
|
-
return {
|
|
877
|
-
schemaManifest,
|
|
878
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* IntegrationAttempt v2->v3 adds the "superseded" terminal status for
|
|
883
|
-
* committed Integrations that are obsolete. The migration preserves all
|
|
884
|
-
* existing fields and advances the schema version; the new status is
|
|
885
|
-
* opt-in, so old records remain valid until explicitly superseded.
|
|
886
|
-
*/
|
|
887
|
-
function integrationAttemptSupersededStep() {
|
|
888
|
-
return {
|
|
889
|
-
axis: "record",
|
|
890
|
-
recordKind: "integrationAttempt",
|
|
891
|
-
fromVersion: INTEGRATION_ATTEMPT_FROM_VERSION,
|
|
892
|
-
toVersion: INTEGRATION_ATTEMPT_TO_VERSION,
|
|
893
|
-
preconditions: requireIntegrationAttemptV2,
|
|
894
|
-
transform: migrateIntegrationAttemptV2ToV3,
|
|
895
|
-
declaredEffects: []
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
function requireIntegrationAttemptV2(snapshot) {
|
|
899
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
900
|
-
if (manifestVersions.integrationAttempt !== INTEGRATION_ATTEMPT_FROM_VERSION) {
|
|
901
|
-
throw new Error(`Record integrationAttempt migration requires manifest version ${INTEGRATION_ATTEMPT_FROM_VERSION}.`);
|
|
902
|
-
}
|
|
903
|
-
if (snapshot.state === null)
|
|
904
|
-
return;
|
|
905
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
906
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
907
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
908
|
-
if (task.integrationAttempts === undefined)
|
|
909
|
-
continue;
|
|
910
|
-
const attempts = asObject(task.integrationAttempts, `integrationAttempt map ${taskId}`);
|
|
911
|
-
for (const [attemptId, rawRecord] of Object.entries(attempts)) {
|
|
912
|
-
const record = asObject(rawRecord, `integrationAttempt ${taskId}/${attemptId}`);
|
|
913
|
-
if (record.schemaVersion !== INTEGRATION_ATTEMPT_FROM_VERSION) {
|
|
914
|
-
throw new Error(`integrationAttempt ${taskId}/${attemptId} must use schemaVersion ${INTEGRATION_ATTEMPT_FROM_VERSION}.`);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
function migrateIntegrationAttemptV2ToV3(snapshot) {
|
|
920
|
-
requireIntegrationAttemptV2(snapshot);
|
|
921
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
922
|
-
const schemaManifest = {
|
|
923
|
-
...snapshot.schemaManifest,
|
|
924
|
-
recordVersions: {
|
|
925
|
-
...manifestVersions,
|
|
926
|
-
integrationAttempt: INTEGRATION_ATTEMPT_TO_VERSION
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
if (snapshot.state === null)
|
|
930
|
-
return { schemaManifest, state: null };
|
|
931
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
932
|
-
const nextTasks = {};
|
|
933
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
934
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
935
|
-
if (task.integrationAttempts === undefined) {
|
|
936
|
-
nextTasks[taskId] = { ...task };
|
|
937
|
-
continue;
|
|
938
|
-
}
|
|
939
|
-
const attempts = asObject(task.integrationAttempts, `integrationAttempt map ${taskId}`);
|
|
940
|
-
const nextAttempts = {};
|
|
941
|
-
for (const [attemptId, rawRecord] of Object.entries(attempts)) {
|
|
942
|
-
const record = asObject(rawRecord, `integrationAttempt ${taskId}/${attemptId}`);
|
|
943
|
-
nextAttempts[attemptId] = {
|
|
944
|
-
...record,
|
|
945
|
-
schemaVersion: INTEGRATION_ATTEMPT_TO_VERSION
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
nextTasks[taskId] = { ...task, integrationAttempts: nextAttempts };
|
|
949
|
-
}
|
|
950
|
-
return {
|
|
951
|
-
schemaManifest,
|
|
952
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
/**
|
|
956
|
-
* The integration queue is a post-baseline per-Task record family. Its
|
|
957
|
-
* explicit 0->1 introduction adds the empty map and its id high-water mark to
|
|
958
|
-
* every Task aggregate and the family to the record manifest; old Homes keep
|
|
959
|
-
* integrating without it until the queue is first used.
|
|
960
|
-
*/
|
|
961
|
-
function integrationQueueIntroductionStep() {
|
|
962
|
-
return {
|
|
963
|
-
axis: "record",
|
|
964
|
-
recordKind: "integrationQueue",
|
|
965
|
-
fromVersion: INTEGRATION_QUEUE_FROM_VERSION,
|
|
966
|
-
toVersion: INTEGRATION_QUEUE_TO_VERSION,
|
|
967
|
-
introduction: true,
|
|
968
|
-
preconditions: requireIntegrationQueueIntroduction,
|
|
969
|
-
transform: introduceIntegrationQueue,
|
|
970
|
-
declaredEffects: []
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
function requireIntegrationQueueIntroduction(snapshot) {
|
|
974
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
975
|
-
if (manifestVersions.integrationQueue !== undefined) {
|
|
976
|
-
throw new Error("integrationQueue is already introduced in the record manifest.");
|
|
977
|
-
}
|
|
978
|
-
if (snapshot.state === null)
|
|
979
|
-
return;
|
|
980
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
981
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
982
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
983
|
-
if (task.integrationQueue !== undefined) {
|
|
984
|
-
throw new Error(`Task aggregate already carries an integrationQueue map: ${taskId}.`);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
function introduceIntegrationQueue(snapshot) {
|
|
989
|
-
requireIntegrationQueueIntroduction(snapshot);
|
|
990
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
991
|
-
const schemaManifest = {
|
|
992
|
-
...snapshot.schemaManifest,
|
|
993
|
-
recordVersions: {
|
|
994
|
-
...manifestVersions,
|
|
995
|
-
integrationQueue: INTEGRATION_QUEUE_TO_VERSION
|
|
996
|
-
}
|
|
997
|
-
};
|
|
998
|
-
if (snapshot.state === null)
|
|
999
|
-
return { schemaManifest, state: null };
|
|
1000
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1001
|
-
const nextTasks = {};
|
|
1002
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1003
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1004
|
-
const highWaterMarks = asObject(task.idHighWaterMarks ?? {}, `Task id high-water marks ${taskId}`);
|
|
1005
|
-
nextTasks[taskId] = {
|
|
1006
|
-
...task,
|
|
1007
|
-
idHighWaterMarks: { ...highWaterMarks, integrationQueue: 0 },
|
|
1008
|
-
integrationQueue: {}
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
return {
|
|
1012
|
-
schemaManifest,
|
|
1013
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
/** Introduce the immutable Task-scoped ContextSnapshot record family. */
|
|
1017
|
-
function contextSnapshotIntroductionStep() {
|
|
1018
|
-
return {
|
|
1019
|
-
axis: "record",
|
|
1020
|
-
recordKind: "contextSnapshot",
|
|
1021
|
-
fromVersion: CONTEXT_SNAPSHOT_FROM_VERSION,
|
|
1022
|
-
toVersion: CONTEXT_SNAPSHOT_TO_VERSION,
|
|
1023
|
-
introduction: true,
|
|
1024
|
-
preconditions: requireContextSnapshotIntroduction,
|
|
1025
|
-
transform: introduceContextSnapshots,
|
|
1026
|
-
declaredEffects: []
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
function requireContextSnapshotIntroduction(snapshot) {
|
|
1030
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1031
|
-
const namedVersion = manifestVersions.contextSnapshot;
|
|
1032
|
-
if (namedVersion !== undefined && namedVersion !== CONTEXT_SNAPSHOT_FROM_VERSION) {
|
|
1033
|
-
throw new Error("Record contextSnapshot introduction requires an absent manifest version or 0.");
|
|
1034
|
-
}
|
|
1035
|
-
if (snapshot.state === null)
|
|
1036
|
-
return;
|
|
1037
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1038
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1039
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1040
|
-
if (task.contextSnapshots !== undefined) {
|
|
1041
|
-
const records = asObject(task.contextSnapshots, `contextSnapshot map ${taskId}`);
|
|
1042
|
-
if (Object.keys(records).length > 0) {
|
|
1043
|
-
throw new Error(`Context Snapshot introduction found existing records: ${taskId}.`);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
const marks = asObject(task.idHighWaterMarks ?? {}, `Task id high-water marks ${taskId}`);
|
|
1047
|
-
const mark = marks.contextSnapshot;
|
|
1048
|
-
if (mark !== undefined && mark !== CONTEXT_SNAPSHOT_FROM_VERSION) {
|
|
1049
|
-
throw new Error(`Context Snapshot introduction found a high-water mark: ${taskId}.`);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
function introduceContextSnapshots(snapshot) {
|
|
1054
|
-
requireContextSnapshotIntroduction(snapshot);
|
|
1055
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1056
|
-
const schemaManifest = {
|
|
1057
|
-
...snapshot.schemaManifest,
|
|
1058
|
-
recordVersions: {
|
|
1059
|
-
...manifestVersions,
|
|
1060
|
-
contextSnapshot: CONTEXT_SNAPSHOT_TO_VERSION
|
|
1061
|
-
}
|
|
1062
|
-
};
|
|
1063
|
-
if (snapshot.state === null)
|
|
1064
|
-
return { schemaManifest, state: null };
|
|
1065
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1066
|
-
const nextTasks = {};
|
|
1067
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1068
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1069
|
-
const marks = asObject(task.idHighWaterMarks ?? {}, `Task id high-water marks ${taskId}`);
|
|
1070
|
-
nextTasks[taskId] = {
|
|
1071
|
-
...task,
|
|
1072
|
-
idHighWaterMarks: { ...marks, contextSnapshot: 0 },
|
|
1073
|
-
contextSnapshots: {}
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
return {
|
|
1077
|
-
schemaManifest,
|
|
1078
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
function legacyLaneKeyParts(key) {
|
|
1082
|
-
const match = /^lane:([^:]+):([^:]+)$/u.exec(key);
|
|
1083
|
-
if (match === null)
|
|
1084
|
-
return null;
|
|
1085
|
-
return { executionGroupId: match[1], executionLaneId: match[2] };
|
|
1086
|
-
}
|
|
1087
|
-
function executionLaneNamespaceKey(executionGroupId, executionLaneId) {
|
|
1088
|
-
return `/execution-lane/${encodeLaneKeyPart(executionGroupId)}:${encodeLaneKeyPart(executionLaneId)}`;
|
|
1089
|
-
}
|
|
1090
|
-
function encodeLaneKeyPart(value) {
|
|
1091
|
-
return encodeURIComponent(value).replace(/:/gu, "%3A");
|
|
1092
|
-
}
|
|
1093
|
-
/**
|
|
1094
|
-
* The StoredTask aggregate gains the `durableJobs` record family. Every v14
|
|
1095
|
-
* task defaults to an empty map; the compatible normalizer adds it in place
|
|
1096
|
-
* without rewriting any other field. The strict source validator rejects
|
|
1097
|
-
* unknown fields so a pre-v15 Home cannot smuggle an unrecognized shape.
|
|
1098
|
-
*/
|
|
1099
|
-
function storedTaskDurableJobsStep() {
|
|
1100
|
-
return {
|
|
1101
|
-
kind: "compatible",
|
|
1102
|
-
axis: "record",
|
|
1103
|
-
recordKind: "storedTask",
|
|
1104
|
-
fromVersion: STORED_TASK_DURABLE_JOBS_FROM_VERSION,
|
|
1105
|
-
toVersion: STORED_TASK_DURABLE_JOBS_TO_VERSION,
|
|
1106
|
-
defaults: [
|
|
1107
|
-
"durableJobs defaults to an empty map on every Task aggregate",
|
|
1108
|
-
"idHighWaterMarks.durableJob defaults to 0 on every Task aggregate"
|
|
1109
|
-
],
|
|
1110
|
-
validateSource: (snapshot) => requireStoredTaskV14Shape(snapshot),
|
|
1111
|
-
normalize: (snapshot) => normalizeStoredTaskV14ToV15(snapshot)
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
const STORED_TASK_V14_FIELDS = [
|
|
1115
|
-
"schemaVersion",
|
|
1116
|
-
"task",
|
|
1117
|
-
"idHighWaterMarks",
|
|
1118
|
-
"brief",
|
|
1119
|
-
"changeSets",
|
|
1120
|
-
"integrationAttempts",
|
|
1121
|
-
"integrationQueue",
|
|
1122
|
-
// durableJobs may already be present on a v14 task when the Home was
|
|
1123
|
-
// written by a build that had the family before the storedTask version
|
|
1124
|
-
// advanced (or after a state.json→SQLite repair round-trip, which
|
|
1125
|
-
// reconstructs every current family map). The 14→15 normalizer preserves
|
|
1126
|
-
// existing records instead of overwriting them with an empty map.
|
|
1127
|
-
"durableJobs",
|
|
1128
|
-
// jobCallerKeyHashes is introduced at v16 but the state.json→SQLite repair
|
|
1129
|
-
// round-trip reconstructs every current family map, so a v14 task may carry
|
|
1130
|
-
// an empty map after the repair. The 14→15 normalizer preserves it.
|
|
1131
|
-
"jobCallerKeyHashes",
|
|
1132
|
-
"capabilityGrants",
|
|
1133
|
-
"releaseWorkflows",
|
|
1134
|
-
"roles",
|
|
1135
|
-
"managedWorkspaces",
|
|
1136
|
-
"roleSessionSets",
|
|
1137
|
-
"workItems",
|
|
1138
|
-
"agentRuns",
|
|
1139
|
-
"reviewRounds",
|
|
1140
|
-
"activeRuns",
|
|
1141
|
-
"messages",
|
|
1142
|
-
"inputRequests",
|
|
1143
|
-
"decisions",
|
|
1144
|
-
"milestones",
|
|
1145
|
-
"events",
|
|
1146
|
-
"leaderFailure",
|
|
1147
|
-
"operatorNotification"
|
|
1148
|
-
];
|
|
1149
|
-
function requireStoredTaskV14Shape(snapshot) {
|
|
1150
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1151
|
-
if (manifestVersions.storedTask !== STORED_TASK_DURABLE_JOBS_FROM_VERSION) {
|
|
1152
|
-
throw new Error(`Record storedTask compatible step requires manifest version ${STORED_TASK_DURABLE_JOBS_FROM_VERSION}.`);
|
|
1153
|
-
}
|
|
1154
|
-
if (snapshot.state === null)
|
|
1155
|
-
return;
|
|
1156
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1157
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1158
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1159
|
-
if (task.schemaVersion !== STORED_TASK_DURABLE_JOBS_FROM_VERSION) {
|
|
1160
|
-
throw new Error(`Task aggregate ${taskId} must use schemaVersion ${STORED_TASK_DURABLE_JOBS_FROM_VERSION}.`);
|
|
1161
|
-
}
|
|
1162
|
-
const allowed = new Set(STORED_TASK_V14_FIELDS);
|
|
1163
|
-
const unknown = Object.keys(task).find((key) => !allowed.has(key));
|
|
1164
|
-
if (unknown !== undefined) {
|
|
1165
|
-
throw new Error(`Task aggregate ${taskId} has an unknown v14 field: ${unknown}.`);
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
function normalizeStoredTaskV14ToV15(snapshot) {
|
|
1170
|
-
requireStoredTaskV14Shape(snapshot);
|
|
1171
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1172
|
-
const schemaManifest = {
|
|
1173
|
-
...snapshot.schemaManifest,
|
|
1174
|
-
recordVersions: {
|
|
1175
|
-
...manifestVersions,
|
|
1176
|
-
storedTask: STORED_TASK_DURABLE_JOBS_TO_VERSION
|
|
1177
|
-
}
|
|
1178
|
-
};
|
|
1179
|
-
if (snapshot.state === null)
|
|
1180
|
-
return { schemaManifest, state: null };
|
|
1181
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1182
|
-
const nextTasks = {};
|
|
1183
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1184
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1185
|
-
// The v14 high-water marks have no durableJob family; the strict current
|
|
1186
|
-
// parser requires every family key, so the normalizer supplies the zero
|
|
1187
|
-
// default in the same pass as the empty durableJobs map.
|
|
1188
|
-
const highWaterMarks = asObject(task.idHighWaterMarks, `Task aggregate ${taskId} idHighWaterMarks`);
|
|
1189
|
-
const existingJobs = task.durableJobs === undefined
|
|
1190
|
-
? {}
|
|
1191
|
-
: asObject(task.durableJobs, `Task aggregate ${taskId} durableJobs`);
|
|
1192
|
-
const existingJobMark = highWaterMarks.durableJob;
|
|
1193
|
-
nextTasks[taskId] = {
|
|
1194
|
-
...task,
|
|
1195
|
-
schemaVersion: STORED_TASK_DURABLE_JOBS_TO_VERSION,
|
|
1196
|
-
idHighWaterMarks: {
|
|
1197
|
-
...highWaterMarks,
|
|
1198
|
-
durableJob: typeof existingJobMark === "number" ? existingJobMark : 0
|
|
1199
|
-
},
|
|
1200
|
-
durableJobs: existingJobs
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
return {
|
|
1204
|
-
schemaManifest,
|
|
1205
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* rr13: The StoredTask aggregate gains the `jobCallerKeyHashes` map. Every v15
|
|
1210
|
-
* task defaults to an empty map; the compatible normalizer adds it in place
|
|
1211
|
-
* without rewriting any other field. A v15 task must not already carry the
|
|
1212
|
-
* field. No legacy fallback: a Session whose hash is absent is rejected at the
|
|
1213
|
-
* Controller boundary (fail-closed).
|
|
1214
|
-
*/
|
|
1215
|
-
function storedTaskJobCallerKeyHashesStep() {
|
|
1216
|
-
return {
|
|
1217
|
-
kind: "compatible",
|
|
1218
|
-
axis: "record",
|
|
1219
|
-
recordKind: "storedTask",
|
|
1220
|
-
fromVersion: STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION,
|
|
1221
|
-
toVersion: STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION,
|
|
1222
|
-
defaults: [
|
|
1223
|
-
"jobCallerKeyHashes defaults to an empty map on every Task aggregate"
|
|
1224
|
-
],
|
|
1225
|
-
validateSource: (snapshot) => requireStoredTaskV15Shape(snapshot),
|
|
1226
|
-
normalize: (snapshot) => normalizeStoredTaskV15ToV16(snapshot)
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
const STORED_TASK_V15_FIELDS = [
|
|
1230
|
-
"schemaVersion",
|
|
1231
|
-
"task",
|
|
1232
|
-
"idHighWaterMarks",
|
|
1233
|
-
"brief",
|
|
1234
|
-
"changeSets",
|
|
1235
|
-
"integrationAttempts",
|
|
1236
|
-
"integrationQueue",
|
|
1237
|
-
"durableJobs",
|
|
1238
|
-
// May already be present after a state.json→SQLite repair round-trip (the
|
|
1239
|
-
// reverse reader reconstructs every current family map). The 15→16
|
|
1240
|
-
// normalizer preserves existing hashes instead of overwriting them.
|
|
1241
|
-
"jobCallerKeyHashes",
|
|
1242
|
-
"capabilityGrants",
|
|
1243
|
-
"releaseWorkflows",
|
|
1244
|
-
"roles",
|
|
1245
|
-
"managedWorkspaces",
|
|
1246
|
-
"roleSessionSets",
|
|
1247
|
-
"workItems",
|
|
1248
|
-
"agentRuns",
|
|
1249
|
-
"reviewRounds",
|
|
1250
|
-
"activeRuns",
|
|
1251
|
-
"messages",
|
|
1252
|
-
"inputRequests",
|
|
1253
|
-
"decisions",
|
|
1254
|
-
"milestones",
|
|
1255
|
-
"events",
|
|
1256
|
-
"leaderFailure",
|
|
1257
|
-
"operatorNotification"
|
|
1258
|
-
];
|
|
1259
|
-
function requireStoredTaskV15Shape(snapshot) {
|
|
1260
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1261
|
-
if (manifestVersions.storedTask !== STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION) {
|
|
1262
|
-
throw new Error(`Record storedTask compatible step requires manifest version ${STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION}.`);
|
|
1263
|
-
}
|
|
1264
|
-
if (snapshot.state === null)
|
|
1265
|
-
return;
|
|
1266
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1267
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1268
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1269
|
-
if (task.schemaVersion !== STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION) {
|
|
1270
|
-
throw new Error(`Task aggregate ${taskId} must use schemaVersion ${STORED_TASK_JOB_CALLER_KEY_HASHES_FROM_VERSION}.`);
|
|
1271
|
-
}
|
|
1272
|
-
const allowed = new Set(STORED_TASK_V15_FIELDS);
|
|
1273
|
-
const unknown = Object.keys(task).find((key) => !allowed.has(key));
|
|
1274
|
-
if (unknown !== undefined) {
|
|
1275
|
-
throw new Error(`Task aggregate ${taskId} has an unknown v15 field: ${unknown}.`);
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
function normalizeStoredTaskV15ToV16(snapshot) {
|
|
1280
|
-
requireStoredTaskV15Shape(snapshot);
|
|
1281
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1282
|
-
const schemaManifest = {
|
|
1283
|
-
...snapshot.schemaManifest,
|
|
1284
|
-
recordVersions: {
|
|
1285
|
-
...manifestVersions,
|
|
1286
|
-
storedTask: STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION
|
|
1287
|
-
}
|
|
1288
|
-
};
|
|
1289
|
-
if (snapshot.state === null)
|
|
1290
|
-
return { schemaManifest, state: null };
|
|
1291
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1292
|
-
const nextTasks = {};
|
|
1293
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1294
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1295
|
-
const existingHashes = task.jobCallerKeyHashes === undefined
|
|
1296
|
-
? {}
|
|
1297
|
-
: asObject(task.jobCallerKeyHashes, `Task aggregate ${taskId} jobCallerKeyHashes`);
|
|
1298
|
-
nextTasks[taskId] = {
|
|
1299
|
-
...task,
|
|
1300
|
-
schemaVersion: STORED_TASK_JOB_CALLER_KEY_HASHES_TO_VERSION,
|
|
1301
|
-
jobCallerKeyHashes: existingHashes
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
return {
|
|
1305
|
-
schemaManifest,
|
|
1306
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
1307
|
-
};
|
|
1308
|
-
}
|
|
1309
|
-
/**
|
|
1310
|
-
* StoredTask v17 introduces both the durable TaskWake ledger (Issue 04
|
|
1311
|
-
* long-term design) and the publicationReferences map (Issue 11). Every
|
|
1312
|
-
* aggregate gains empty `wakes` and `publicationReferences` maps and zero
|
|
1313
|
-
* high-water marks.
|
|
1314
|
-
*/
|
|
1315
|
-
function storedTaskWakesAndPublicationReferencesStep() {
|
|
1316
|
-
return {
|
|
1317
|
-
kind: "compatible",
|
|
1318
|
-
axis: "record",
|
|
1319
|
-
recordKind: "storedTask",
|
|
1320
|
-
fromVersion: STORED_TASK_WAKES_FROM_VERSION,
|
|
1321
|
-
toVersion: STORED_TASK_WAKES_TO_VERSION,
|
|
1322
|
-
defaults: [
|
|
1323
|
-
"wakes defaults to an empty map on every Task aggregate",
|
|
1324
|
-
"publicationReferences defaults to an empty map on every Task aggregate"
|
|
1325
|
-
],
|
|
1326
|
-
validateSource: (snapshot) => requireStoredTaskV16Shape(snapshot),
|
|
1327
|
-
normalize: (snapshot) => normalizeStoredTaskV16ToV17(snapshot)
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
|
-
const STORED_TASK_V16_FIELDS = [
|
|
1331
|
-
"schemaVersion",
|
|
1332
|
-
"task",
|
|
1333
|
-
"idHighWaterMarks",
|
|
1334
|
-
"brief",
|
|
1335
|
-
"changeSets",
|
|
1336
|
-
"integrationAttempts",
|
|
1337
|
-
"integrationQueue",
|
|
1338
|
-
"durableJobs",
|
|
1339
|
-
"jobCallerKeyHashes",
|
|
1340
|
-
// The SQLite reverse reader reconstructs every physical family map even
|
|
1341
|
-
// when the older manifest has not introduced that family yet. A v16
|
|
1342
|
-
// aggregate may therefore contain an empty wakes map; preserve that exact
|
|
1343
|
-
// repair shape, but reject records because taskWake has no v16 version.
|
|
1344
|
-
"wakes",
|
|
1345
|
-
// May already be present after the publicationReference introduction runs
|
|
1346
|
-
// first; the 16->17 normalizer preserves the empty introduced map.
|
|
1347
|
-
"publicationReferences",
|
|
1348
|
-
"capabilityGrants",
|
|
1349
|
-
"releaseWorkflows",
|
|
1350
|
-
"roles",
|
|
1351
|
-
"managedWorkspaces",
|
|
1352
|
-
"roleSessionSets",
|
|
1353
|
-
"workItems",
|
|
1354
|
-
"agentRuns",
|
|
1355
|
-
"reviewRounds",
|
|
1356
|
-
"activeRuns",
|
|
1357
|
-
"messages",
|
|
1358
|
-
"inputRequests",
|
|
1359
|
-
"decisions",
|
|
1360
|
-
"milestones",
|
|
1361
|
-
"events",
|
|
1362
|
-
"leaderFailure",
|
|
1363
|
-
"operatorNotification"
|
|
1364
|
-
];
|
|
1365
|
-
function requireStoredTaskV16Shape(snapshot) {
|
|
1366
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1367
|
-
if (manifestVersions.storedTask !== STORED_TASK_WAKES_FROM_VERSION) {
|
|
1368
|
-
throw new Error(`Record storedTask compatible step requires manifest version ${STORED_TASK_WAKES_FROM_VERSION}.`);
|
|
1369
|
-
}
|
|
1370
|
-
if (snapshot.state === null)
|
|
1371
|
-
return;
|
|
1372
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1373
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1374
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1375
|
-
if (task.schemaVersion !== STORED_TASK_WAKES_FROM_VERSION) {
|
|
1376
|
-
throw new Error(`Task aggregate ${taskId} must use schemaVersion ${STORED_TASK_WAKES_FROM_VERSION}.`);
|
|
1377
|
-
}
|
|
1378
|
-
const allowed = new Set(STORED_TASK_V16_FIELDS);
|
|
1379
|
-
const unknown = Object.keys(task).find((key) => !allowed.has(key));
|
|
1380
|
-
if (unknown !== undefined) {
|
|
1381
|
-
throw new Error(`Task aggregate ${taskId} has an unknown v16 field: ${unknown}.`);
|
|
1382
|
-
}
|
|
1383
|
-
if (task.wakes !== undefined) {
|
|
1384
|
-
const wakes = asObject(task.wakes, `Task aggregate ${taskId} wakes`);
|
|
1385
|
-
if (Object.keys(wakes).length > 0) {
|
|
1386
|
-
throw new Error(`Task aggregate ${taskId} already has Task wakes before v17.`);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
if (task.publicationReferences !== undefined) {
|
|
1390
|
-
const references = asObject(task.publicationReferences, `Task aggregate ${taskId} publicationReferences`);
|
|
1391
|
-
if (Object.keys(references).length > 0) {
|
|
1392
|
-
throw new Error(`Task aggregate ${taskId} already has publication references before v17.`);
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
function normalizeStoredTaskV16ToV17(snapshot) {
|
|
1398
|
-
requireStoredTaskV16Shape(snapshot);
|
|
1399
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1400
|
-
const schemaManifest = {
|
|
1401
|
-
...snapshot.schemaManifest,
|
|
1402
|
-
recordVersions: {
|
|
1403
|
-
...manifestVersions,
|
|
1404
|
-
storedTask: STORED_TASK_WAKES_TO_VERSION
|
|
1405
|
-
}
|
|
1406
|
-
};
|
|
1407
|
-
if (snapshot.state === null)
|
|
1408
|
-
return { schemaManifest, state: null };
|
|
1409
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1410
|
-
const nextTasks = {};
|
|
1411
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1412
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1413
|
-
const existingReferences = task.publicationReferences === undefined
|
|
1414
|
-
? {}
|
|
1415
|
-
: asObject(task.publicationReferences, `Task aggregate ${taskId} publicationReferences`);
|
|
1416
|
-
const existingWakes = task.wakes === undefined
|
|
1417
|
-
? {}
|
|
1418
|
-
: asObject(task.wakes, `Task aggregate ${taskId} wakes`);
|
|
1419
|
-
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
1420
|
-
const existingMark = marks.publicationReference;
|
|
1421
|
-
const existingWakeMark = marks.taskWake;
|
|
1422
|
-
nextTasks[taskId] = {
|
|
1423
|
-
...task,
|
|
1424
|
-
schemaVersion: STORED_TASK_WAKES_TO_VERSION,
|
|
1425
|
-
idHighWaterMarks: {
|
|
1426
|
-
...marks,
|
|
1427
|
-
publicationReference: typeof existingMark === "number" ? existingMark : 0,
|
|
1428
|
-
taskWake: typeof existingWakeMark === "number" ? existingWakeMark : TASK_WAKE_FROM_VERSION
|
|
1429
|
-
},
|
|
1430
|
-
publicationReferences: existingReferences,
|
|
1431
|
-
wakes: existingWakes
|
|
1432
|
-
};
|
|
1433
|
-
}
|
|
1434
|
-
return {
|
|
1435
|
-
schemaManifest,
|
|
1436
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
/** StoredTask v18 retires the mutable OperatorNotification projection. */
|
|
1440
|
-
function storedTaskEventNoticesStep() {
|
|
1441
|
-
return {
|
|
1442
|
-
axis: "record",
|
|
1443
|
-
recordKind: "storedTask",
|
|
1444
|
-
fromVersion: STORED_TASK_EVENT_NOTICES_FROM_VERSION,
|
|
1445
|
-
toVersion: STORED_TASK_EVENT_NOTICES_TO_VERSION,
|
|
1446
|
-
preconditions: requireStoredTaskV17OperatorNoticeShape,
|
|
1447
|
-
transform: normalizeStoredTaskV17ToV18,
|
|
1448
|
-
declaredEffects: ["operator-mailbox-event-references"]
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
const STORED_TASK_V17_FIELDS = [
|
|
1452
|
-
"schemaVersion",
|
|
1453
|
-
"task",
|
|
1454
|
-
"idHighWaterMarks",
|
|
1455
|
-
"brief",
|
|
1456
|
-
"changeSets",
|
|
1457
|
-
"integrationAttempts",
|
|
1458
|
-
"integrationQueue",
|
|
1459
|
-
"durableJobs",
|
|
1460
|
-
"roles",
|
|
1461
|
-
"managedWorkspaces",
|
|
1462
|
-
"roleSessionSets",
|
|
1463
|
-
"jobCallerKeyHashes",
|
|
1464
|
-
"workItems",
|
|
1465
|
-
"contextSnapshots",
|
|
1466
|
-
"agentRuns",
|
|
1467
|
-
"reviewRounds",
|
|
1468
|
-
"activeRuns",
|
|
1469
|
-
"messages",
|
|
1470
|
-
"inputRequests",
|
|
1471
|
-
"decisions",
|
|
1472
|
-
"milestones",
|
|
1473
|
-
"events",
|
|
1474
|
-
"wakes",
|
|
1475
|
-
"capabilityGrants",
|
|
1476
|
-
"releaseWorkflows",
|
|
1477
|
-
"publicationReferences",
|
|
1478
|
-
"leaderFailure",
|
|
1479
|
-
"operatorNotification"
|
|
1480
|
-
];
|
|
1481
|
-
function requireStoredTaskV17OperatorNoticeShape(snapshot) {
|
|
1482
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1483
|
-
if (versions.storedTask !== STORED_TASK_EVENT_NOTICES_FROM_VERSION) {
|
|
1484
|
-
throw new Error(`Record storedTask compatible step requires manifest version ${STORED_TASK_EVENT_NOTICES_FROM_VERSION}.`);
|
|
1485
|
-
}
|
|
1486
|
-
if (snapshot.state === null)
|
|
1487
|
-
return;
|
|
1488
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1489
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1490
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1491
|
-
if (task.schemaVersion !== STORED_TASK_EVENT_NOTICES_FROM_VERSION) {
|
|
1492
|
-
throw new Error(`Task aggregate ${taskId} must use schemaVersion ${STORED_TASK_EVENT_NOTICES_FROM_VERSION}.`);
|
|
1493
|
-
}
|
|
1494
|
-
const allowed = new Set(STORED_TASK_V17_FIELDS);
|
|
1495
|
-
const unknown = Object.keys(task).find((key) => !allowed.has(key));
|
|
1496
|
-
if (unknown !== undefined) {
|
|
1497
|
-
throw new Error(`Task aggregate ${taskId} has an unknown v17 field: ${unknown}.`);
|
|
1498
|
-
}
|
|
1499
|
-
const missing = STORED_TASK_V17_FIELDS.find((key) => !Object.hasOwn(task, key));
|
|
1500
|
-
if (missing !== undefined) {
|
|
1501
|
-
throw new Error(`Task aggregate ${taskId} is missing v17 field: ${missing}.`);
|
|
1502
|
-
}
|
|
1503
|
-
if (task.operatorNotification !== null) {
|
|
1504
|
-
requireLegacyOperatorNotice(task.operatorNotification, taskId);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
function requireLegacyOperatorNotice(value, taskId) {
|
|
1509
|
-
const notice = asObject(value, `Operator notification ${taskId}`);
|
|
1510
|
-
if (notice.schemaVersion !== 1 || notice.taskId !== taskId) {
|
|
1511
|
-
throw new Error(`Operator notification identity is invalid: ${taskId}.`);
|
|
1512
|
-
}
|
|
1513
|
-
if (notice.type === "leader-recovery-failed") {
|
|
1514
|
-
requireLegacyNoticeText(notice.message, `Operator notification message ${taskId}`);
|
|
1515
|
-
}
|
|
1516
|
-
else if (notice.type === "leader-stalled") {
|
|
1517
|
-
requireLegacyNoticeText(notice.message, `Operator notification message ${taskId}`);
|
|
1518
|
-
requireLegacyNoticeText(notice.runId, `Operator notification Run ${taskId}`);
|
|
1519
|
-
requireLegacyNoticeTimestamp(notice.progressAt, `Operator notification progress ${taskId}`);
|
|
1520
|
-
requireLegacyNoticeText(notice.evidenceKey, `Operator notification evidence ${taskId}`);
|
|
1521
|
-
if (notice.classification !== "truly-stalled") {
|
|
1522
|
-
throw new Error(`Operator notification classification is invalid: ${taskId}.`);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
else if (notice.type === "task-terminal") {
|
|
1526
|
-
if (notice.status !== "completed" && notice.status !== "retired") {
|
|
1527
|
-
throw new Error(`Operator notification Task status is invalid: ${taskId}.`);
|
|
1528
|
-
}
|
|
1529
|
-
if (!["user", "operator", "leader"].includes(String(notice.by))) {
|
|
1530
|
-
throw new Error(`Operator notification Task actor is invalid: ${taskId}.`);
|
|
1531
|
-
}
|
|
1532
|
-
requireLegacyNoticeText(notice.summary, `Operator notification summary ${taskId}`);
|
|
1533
|
-
}
|
|
1534
|
-
else {
|
|
1535
|
-
throw new Error(`Operator notification type is invalid: ${taskId}.`);
|
|
1536
|
-
}
|
|
1537
|
-
requireLegacyNoticeTimestamp(notice.createdAt, `Operator notification createdAt ${taskId}`);
|
|
1538
|
-
requireLegacyNoticeTimestamp(notice.updatedAt, `Operator notification updatedAt ${taskId}`);
|
|
1539
|
-
}
|
|
1540
|
-
function requireLegacyNoticeText(value, label) {
|
|
1541
|
-
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
1542
|
-
throw new Error(`${label} is invalid.`);
|
|
1543
|
-
}
|
|
1544
|
-
return value;
|
|
1545
|
-
}
|
|
1546
|
-
function requireLegacyNoticeTimestamp(value, label) {
|
|
1547
|
-
const timestamp = requireLegacyNoticeText(value, label);
|
|
1548
|
-
if (!Number.isFinite(Date.parse(timestamp)))
|
|
1549
|
-
throw new Error(`${label} is invalid.`);
|
|
1550
|
-
return timestamp;
|
|
1551
|
-
}
|
|
1552
|
-
function normalizeStoredTaskV17ToV18(snapshot) {
|
|
1553
|
-
requireStoredTaskV17OperatorNoticeShape(snapshot);
|
|
1554
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1555
|
-
const schemaManifest = {
|
|
1556
|
-
...snapshot.schemaManifest,
|
|
1557
|
-
recordVersions: {
|
|
1558
|
-
...versions,
|
|
1559
|
-
storedTask: STORED_TASK_EVENT_NOTICES_TO_VERSION
|
|
1560
|
-
}
|
|
1561
|
-
};
|
|
1562
|
-
if (snapshot.state === null)
|
|
1563
|
-
return { schemaManifest, state: null };
|
|
1564
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1565
|
-
const nextTasks = {};
|
|
1566
|
-
const legacyNotices = new Map();
|
|
1567
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1568
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1569
|
-
legacyNotices.set(taskId, task.operatorNotification === null
|
|
1570
|
-
? null
|
|
1571
|
-
: asObject(task.operatorNotification, `Operator notification ${taskId}`));
|
|
1572
|
-
const nextTask = {
|
|
1573
|
-
...task,
|
|
1574
|
-
schemaVersion: STORED_TASK_EVENT_NOTICES_TO_VERSION,
|
|
1575
|
-
events: { ...asObject(task.events, `Task events ${taskId}`) },
|
|
1576
|
-
idHighWaterMarks: {
|
|
1577
|
-
...asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`)
|
|
1578
|
-
}
|
|
1579
|
-
};
|
|
1580
|
-
delete nextTask.operatorNotification;
|
|
1581
|
-
nextTasks[taskId] = nextTask;
|
|
1582
|
-
}
|
|
1583
|
-
const eventByTask = new Map();
|
|
1584
|
-
const eventRefForTask = (taskId) => {
|
|
1585
|
-
const existing = eventByTask.get(taskId);
|
|
1586
|
-
if (existing !== undefined)
|
|
1587
|
-
return { type: "event", taskId, id: existing };
|
|
1588
|
-
const task = asObject(nextTasks[taskId], `Task aggregate ${taskId}`);
|
|
1589
|
-
const events = asObject(task.events, `Task events ${taskId}`);
|
|
1590
|
-
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
1591
|
-
let sequence = Number(marks.event ?? 0) + 1;
|
|
1592
|
-
if (!Number.isSafeInteger(sequence) || sequence < 1) {
|
|
1593
|
-
throw new Error(`Task event high-water mark is invalid: ${taskId}.`);
|
|
1594
|
-
}
|
|
1595
|
-
while (events[`event-${sequence}`] !== undefined)
|
|
1596
|
-
sequence += 1;
|
|
1597
|
-
const eventId = `event-${sequence}`;
|
|
1598
|
-
const notice = legacyNotices.get(taskId) ?? null;
|
|
1599
|
-
const migrated = migratedOperatorNoticeEvent(taskId, eventId, notice, task);
|
|
1600
|
-
events[eventId] = migrated;
|
|
1601
|
-
marks.event = sequence;
|
|
1602
|
-
eventByTask.set(taskId, eventId);
|
|
1603
|
-
return { type: "event", taskId, id: eventId };
|
|
1604
|
-
};
|
|
1605
|
-
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
1606
|
-
const nextMailboxes = {};
|
|
1607
|
-
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
1608
|
-
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
1609
|
-
const target = asObject(mailbox.target, `WorkMailbox target ${key}`);
|
|
1610
|
-
nextMailboxes[key] = target.kind === "operator"
|
|
1611
|
-
? rewriteLegacyOperatorMailbox(mailbox, key, eventRefForTask)
|
|
1612
|
-
: { ...mailbox };
|
|
1613
|
-
}
|
|
1614
|
-
for (const [taskId, notice] of legacyNotices) {
|
|
1615
|
-
if (notice !== null)
|
|
1616
|
-
eventRefForTask(taskId);
|
|
1617
|
-
}
|
|
1618
|
-
return {
|
|
1619
|
-
schemaManifest,
|
|
1620
|
-
state: { ...snapshot.state, tasks: nextTasks, mailboxes: nextMailboxes }
|
|
1621
|
-
};
|
|
1622
|
-
}
|
|
1623
|
-
function migratedOperatorNoticeEvent(taskId, eventId, notice, taskAggregate) {
|
|
1624
|
-
const task = asObject(taskAggregate.task, `Task ${taskId}`);
|
|
1625
|
-
const timestamp = String(notice?.updatedAt ?? task.updatedAt ?? task.createdAt ?? "");
|
|
1626
|
-
if (!Number.isFinite(Date.parse(timestamp))) {
|
|
1627
|
-
throw new Error(`Legacy Operator notice timestamp is invalid: ${taskId}.`);
|
|
1628
|
-
}
|
|
1629
|
-
const type = notice?.type;
|
|
1630
|
-
if (notice !== null && type === "task-terminal") {
|
|
1631
|
-
const status = String(notice.status ?? "");
|
|
1632
|
-
if (status !== "completed" && status !== "retired") {
|
|
1633
|
-
throw new Error(`Legacy terminal Operator notice status is invalid: ${taskId}.`);
|
|
1634
|
-
}
|
|
1635
|
-
return {
|
|
1636
|
-
schemaVersion: 2,
|
|
1637
|
-
id: eventId,
|
|
1638
|
-
taskId,
|
|
1639
|
-
type: `task.${status}`,
|
|
1640
|
-
payload: {
|
|
1641
|
-
status,
|
|
1642
|
-
by: String(notice.by ?? "operator"),
|
|
1643
|
-
summary: String(notice.summary ?? "Task reached a terminal state."),
|
|
1644
|
-
migratedFrom: "operator-notification"
|
|
1645
|
-
},
|
|
1646
|
-
createdAt: timestamp
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
if (notice !== null && type === "leader-stalled") {
|
|
1650
|
-
return {
|
|
1651
|
-
schemaVersion: 2,
|
|
1652
|
-
id: eventId,
|
|
1653
|
-
taskId,
|
|
1654
|
-
type: "run.stalled",
|
|
1655
|
-
payload: {
|
|
1656
|
-
runId: String(notice.runId ?? "unknown"),
|
|
1657
|
-
roleName: "leader",
|
|
1658
|
-
progressAt: String(notice.progressAt ?? timestamp),
|
|
1659
|
-
classification: "truly-stalled",
|
|
1660
|
-
evidenceKey: String(notice.evidenceKey ?? "legacy-operator-notification"),
|
|
1661
|
-
migratedFrom: "operator-notification"
|
|
1662
|
-
},
|
|
1663
|
-
createdAt: timestamp
|
|
1664
|
-
};
|
|
1665
|
-
}
|
|
1666
|
-
return {
|
|
1667
|
-
schemaVersion: 2,
|
|
1668
|
-
id: eventId,
|
|
1669
|
-
taskId,
|
|
1670
|
-
type: "leader.attention-required",
|
|
1671
|
-
payload: {
|
|
1672
|
-
reason: type === "leader-recovery-failed"
|
|
1673
|
-
? "leader-recovery-failed"
|
|
1674
|
-
: "legacy-operator-mailbox",
|
|
1675
|
-
message: String(notice?.message ?? "Inspect the migrated Operator notice."),
|
|
1676
|
-
migratedFrom: notice === null ? "operator-mailbox" : "operator-notification"
|
|
1677
|
-
},
|
|
1678
|
-
createdAt: timestamp
|
|
1679
|
-
};
|
|
1680
|
-
}
|
|
1681
|
-
function rewriteLegacyOperatorMailbox(mailbox, key, eventRefForTask) {
|
|
1682
|
-
const rewriteRef = (rawRef) => {
|
|
1683
|
-
const ref = asObject(rawRef, `WorkMailbox ${key} entity ref`);
|
|
1684
|
-
if (ref.type === "input" || ref.type === "event")
|
|
1685
|
-
return { ...ref };
|
|
1686
|
-
const taskId = ref.type === "task" ? ref.id : ref.taskId;
|
|
1687
|
-
if (typeof taskId !== "string" || taskId.trim().length === 0) {
|
|
1688
|
-
throw new Error(`Legacy Operator mailbox ref has no Task identity: ${key}.`);
|
|
1689
|
-
}
|
|
1690
|
-
return eventRefForTask(taskId);
|
|
1691
|
-
};
|
|
1692
|
-
const rewriteBatch = (rawBatch, label) => {
|
|
1693
|
-
const batch = asObject(rawBatch, label);
|
|
1694
|
-
if (!Array.isArray(batch.refs))
|
|
1695
|
-
throw new Error(`${label} refs must be an array.`);
|
|
1696
|
-
const sourceRefs = batch.refs.map((rawRef) => asObject(rawRef, `${label} entity ref`));
|
|
1697
|
-
const semanticTaskIds = new Set(sourceRefs.flatMap((ref) => ((ref.type === "input" || ref.type === "event") && typeof ref.taskId === "string"
|
|
1698
|
-
? [ref.taskId]
|
|
1699
|
-
: [])));
|
|
1700
|
-
const refs = [];
|
|
1701
|
-
const seen = new Set();
|
|
1702
|
-
for (const sourceRef of sourceRefs) {
|
|
1703
|
-
const referencedTaskId = sourceRef.type === "task" ? sourceRef.id : sourceRef.taskId;
|
|
1704
|
-
if (sourceRef.type !== "input"
|
|
1705
|
-
&& sourceRef.type !== "event"
|
|
1706
|
-
&& typeof referencedTaskId === "string"
|
|
1707
|
-
&& semanticTaskIds.has(referencedTaskId)) {
|
|
1708
|
-
continue;
|
|
1709
|
-
}
|
|
1710
|
-
const ref = rewriteRef(sourceRef);
|
|
1711
|
-
const identity = `${String(ref.type)}\0${String(ref.taskId ?? "")}\0${String(ref.id)}`;
|
|
1712
|
-
if (seen.has(identity))
|
|
1713
|
-
continue;
|
|
1714
|
-
seen.add(identity);
|
|
1715
|
-
refs.push(ref);
|
|
1716
|
-
}
|
|
1717
|
-
return { ...batch, refs };
|
|
1718
|
-
};
|
|
1719
|
-
const rewriteProcessing = (raw) => {
|
|
1720
|
-
if (raw === null)
|
|
1721
|
-
return null;
|
|
1722
|
-
const processing = asObject(raw, `WorkMailbox ${key} processing`);
|
|
1723
|
-
return {
|
|
1724
|
-
...processing,
|
|
1725
|
-
batch: rewriteBatch(processing.batch, `WorkMailbox ${key} processing batch`),
|
|
1726
|
-
...(processing.executionRef === undefined
|
|
1727
|
-
? {}
|
|
1728
|
-
: { executionRef: rewriteRef(processing.executionRef) })
|
|
1729
|
-
};
|
|
1730
|
-
};
|
|
1731
|
-
const rewriteDelivery = (raw) => {
|
|
1732
|
-
if (raw === null)
|
|
1733
|
-
return null;
|
|
1734
|
-
const delivery = asObject(raw, `WorkMailbox ${key} input delivery`);
|
|
1735
|
-
return {
|
|
1736
|
-
...delivery,
|
|
1737
|
-
batch: rewriteBatch(delivery.batch, `WorkMailbox ${key} input delivery batch`),
|
|
1738
|
-
executionRef: rewriteRef(delivery.executionRef)
|
|
1739
|
-
};
|
|
1740
|
-
};
|
|
1741
|
-
const pending = asObject(mailbox.pending, `WorkMailbox ${key} pending lanes`);
|
|
1742
|
-
return {
|
|
1743
|
-
...mailbox,
|
|
1744
|
-
processing: rewriteProcessing(mailbox.processing),
|
|
1745
|
-
pending: {
|
|
1746
|
-
...pending,
|
|
1747
|
-
normal: pending.normal === null
|
|
1748
|
-
? null
|
|
1749
|
-
: rewriteBatch(pending.normal, `WorkMailbox ${key} normal batch`),
|
|
1750
|
-
userCorrection: pending.userCorrection === null
|
|
1751
|
-
? null
|
|
1752
|
-
: rewriteBatch(pending.userCorrection, `WorkMailbox ${key} correction batch`)
|
|
1753
|
-
},
|
|
1754
|
-
inputDelivery: rewriteDelivery(mailbox.inputDelivery)
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
|
-
/**
|
|
1758
|
-
* The taskWake record family is introduced alongside StoredTask v17. The
|
|
1759
|
-
* compatible step supplies the empty map and high-water mark; this step only
|
|
1760
|
-
* advances the manifest record version so the planner can resolve the 0->1
|
|
1761
|
-
* boundary.
|
|
1762
|
-
*/
|
|
1763
|
-
function taskWakeIntroductionStep() {
|
|
1764
|
-
return {
|
|
1765
|
-
kind: "compatible",
|
|
1766
|
-
axis: "record",
|
|
1767
|
-
recordKind: "taskWake",
|
|
1768
|
-
fromVersion: TASK_WAKE_FROM_VERSION,
|
|
1769
|
-
toVersion: TASK_WAKE_TO_VERSION,
|
|
1770
|
-
introduction: true,
|
|
1771
|
-
defaults: [
|
|
1772
|
-
"taskWake family starts at version 1 with an empty ledger"
|
|
1773
|
-
],
|
|
1774
|
-
validateSource: (snapshot) => {
|
|
1775
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1776
|
-
const namedVersion = manifestVersions.taskWake;
|
|
1777
|
-
if (namedVersion !== undefined && namedVersion !== TASK_WAKE_FROM_VERSION) {
|
|
1778
|
-
throw new Error(`Record taskWake introduction requires an absent manifest version or ${TASK_WAKE_FROM_VERSION}.`);
|
|
1779
|
-
}
|
|
1780
|
-
},
|
|
1781
|
-
normalize: (snapshot) => ({
|
|
1782
|
-
schemaManifest: {
|
|
1783
|
-
...snapshot.schemaManifest,
|
|
1784
|
-
recordVersions: {
|
|
1785
|
-
...asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions"),
|
|
1786
|
-
taskWake: TASK_WAKE_TO_VERSION
|
|
1787
|
-
}
|
|
1788
|
-
},
|
|
1789
|
-
state: snapshot.state
|
|
1790
|
-
})
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
1793
|
-
/**
|
|
1794
|
-
* The durableJob record family is introduced alongside the StoredTask v15
|
|
1795
|
-
* aggregate. A pre-baseline Home has no durableJobs map and no manifest entry;
|
|
1796
|
-
* the introduction declares the family at version 1. The StoredTask 14->15
|
|
1797
|
-
* compatible step supplies the empty map; this step only advances the manifest
|
|
1798
|
-
* record version so the planner can resolve the 0->1 boundary.
|
|
1799
|
-
*/
|
|
1800
|
-
function durableJobIntroductionStep() {
|
|
1801
|
-
return {
|
|
1802
|
-
kind: "compatible",
|
|
1803
|
-
axis: "record",
|
|
1804
|
-
recordKind: "durableJob",
|
|
1805
|
-
fromVersion: 0,
|
|
1806
|
-
toVersion: 1,
|
|
1807
|
-
introduction: true,
|
|
1808
|
-
defaults: ["durableJob family is introduced as an empty map on every Task"],
|
|
1809
|
-
validateSource: (snapshot) => {
|
|
1810
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1811
|
-
const declared = manifestVersions.durableJob;
|
|
1812
|
-
if (declared !== undefined && declared !== 0) {
|
|
1813
|
-
throw new Error(`Record durableJob introduction requires manifest version 0 or absent, found ${String(declared)}.`);
|
|
1814
|
-
}
|
|
1815
|
-
if (snapshot.state === null)
|
|
1816
|
-
return;
|
|
1817
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1818
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1819
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1820
|
-
if (task.durableJobs !== undefined) {
|
|
1821
|
-
const jobs = asObject(task.durableJobs, `durableJobs map ${taskId}`);
|
|
1822
|
-
if (Object.keys(jobs).length > 0) {
|
|
1823
|
-
throw new Error(`Task aggregate ${taskId} must not carry durableJobs before introduction.`);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
},
|
|
1828
|
-
normalize: (snapshot) => {
|
|
1829
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1830
|
-
return {
|
|
1831
|
-
schemaManifest: {
|
|
1832
|
-
...snapshot.schemaManifest,
|
|
1833
|
-
recordVersions: {
|
|
1834
|
-
...manifestVersions,
|
|
1835
|
-
durableJob: 1
|
|
1836
|
-
}
|
|
1837
|
-
},
|
|
1838
|
-
state: snapshot.state === null
|
|
1839
|
-
? null
|
|
1840
|
-
: { ...snapshot.state }
|
|
1841
|
-
};
|
|
1842
|
-
}
|
|
1843
|
-
};
|
|
1844
|
-
}
|
|
1845
|
-
/**
|
|
1846
|
-
* Upgrade one nested Task record family without guessing fields or repairing
|
|
1847
|
-
* malformed state. The new execution lineage fields are optional, so legal
|
|
1848
|
-
* old single-lane records retain their direct shape; the next dispatch creates
|
|
1849
|
-
* the unified one-lane Group explicitly. The version transition is still
|
|
1850
|
-
* durable and centralized, which keeps old Homes out of the strict current
|
|
1851
|
-
* parser until this step has run.
|
|
1852
|
-
*/
|
|
1853
|
-
/**
|
|
1854
|
-
* The post-baseline introduction of the task-scoped CapabilityGrant family.
|
|
1855
|
-
* A pre-introduction Home names no capabilityGrant manifest version and its
|
|
1856
|
-
* Task aggregates carry no `capabilityGrants` map; this explicit 0->1 step
|
|
1857
|
-
* adds the empty family to every Task (plus the matching high-water mark) so
|
|
1858
|
-
* the strict current parser can open the migrated state.
|
|
1859
|
-
*/
|
|
1860
|
-
function capabilityGrantIntroductionStep() {
|
|
1861
|
-
return {
|
|
1862
|
-
axis: "record",
|
|
1863
|
-
recordKind: "capabilityGrant",
|
|
1864
|
-
fromVersion: CAPABILITY_GRANT_FROM_VERSION,
|
|
1865
|
-
toVersion: CAPABILITY_GRANT_TO_VERSION,
|
|
1866
|
-
introduction: true,
|
|
1867
|
-
preconditions: requireCapabilityGrantIntroduction,
|
|
1868
|
-
transform: introduceCapabilityGrantFamily,
|
|
1869
|
-
declaredEffects: []
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
function requireCapabilityGrantIntroduction(snapshot) {
|
|
1873
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1874
|
-
const namedVersion = manifestVersions.capabilityGrant;
|
|
1875
|
-
if (namedVersion !== undefined && namedVersion !== CAPABILITY_GRANT_FROM_VERSION) {
|
|
1876
|
-
throw new Error(`Record capabilityGrant introduction requires an absent manifest version or ${CAPABILITY_GRANT_FROM_VERSION}.`);
|
|
1877
|
-
}
|
|
1878
|
-
if (snapshot.state === null)
|
|
1879
|
-
return;
|
|
1880
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1881
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1882
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1883
|
-
if (task.capabilityGrants !== undefined) {
|
|
1884
|
-
const grants = asObject(task.capabilityGrants, `capabilityGrant map ${taskId}`);
|
|
1885
|
-
if (Object.keys(grants).length > 0) {
|
|
1886
|
-
throw new Error(`Capability grant introduction found existing records: ${taskId}.`);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
const marks = task.idHighWaterMarks;
|
|
1890
|
-
if (marks !== undefined) {
|
|
1891
|
-
const highWaterMarks = asObject(marks, `Task id high-water marks ${taskId}`);
|
|
1892
|
-
const mark = highWaterMarks.capabilityGrant;
|
|
1893
|
-
if (mark !== undefined && mark !== CAPABILITY_GRANT_FROM_VERSION) {
|
|
1894
|
-
throw new Error(`Capability grant introduction found a high-water mark: ${taskId}.`);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
function introduceCapabilityGrantFamily(snapshot) {
|
|
1900
|
-
requireCapabilityGrantIntroduction(snapshot);
|
|
1901
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1902
|
-
const schemaManifest = {
|
|
1903
|
-
...snapshot.schemaManifest,
|
|
1904
|
-
recordVersions: {
|
|
1905
|
-
...manifestVersions,
|
|
1906
|
-
capabilityGrant: CAPABILITY_GRANT_TO_VERSION
|
|
1907
|
-
}
|
|
1908
|
-
};
|
|
1909
|
-
if (snapshot.state === null)
|
|
1910
|
-
return { schemaManifest, state: null };
|
|
1911
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1912
|
-
const nextTasks = {};
|
|
1913
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1914
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1915
|
-
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
1916
|
-
nextTasks[taskId] = {
|
|
1917
|
-
...task,
|
|
1918
|
-
idHighWaterMarks: { ...marks, capabilityGrant: CAPABILITY_GRANT_FROM_VERSION },
|
|
1919
|
-
capabilityGrants: {}
|
|
1920
|
-
};
|
|
1921
|
-
}
|
|
1922
|
-
return {
|
|
1923
|
-
schemaManifest,
|
|
1924
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
1925
|
-
};
|
|
1926
|
-
}
|
|
1927
|
-
/**
|
|
1928
|
-
* The post-baseline introduction of the task-scoped ReleaseWorkflow family.
|
|
1929
|
-
* A pre-introduction Home names no releaseWorkflow manifest version and its
|
|
1930
|
-
* Task aggregates carry no `releaseWorkflows` map; this explicit 0->1 step
|
|
1931
|
-
* adds the empty family to every Task (plus the matching high-water mark) so
|
|
1932
|
-
* the strict current parser can open the migrated state.
|
|
1933
|
-
*/
|
|
1934
|
-
function releaseWorkflowIntroductionStep() {
|
|
1935
|
-
return {
|
|
1936
|
-
axis: "record",
|
|
1937
|
-
recordKind: "releaseWorkflow",
|
|
1938
|
-
fromVersion: RELEASE_WORKFLOW_FROM_VERSION,
|
|
1939
|
-
toVersion: RELEASE_WORKFLOW_TO_VERSION,
|
|
1940
|
-
introduction: true,
|
|
1941
|
-
preconditions: requireReleaseWorkflowIntroduction,
|
|
1942
|
-
transform: introduceReleaseWorkflowFamily,
|
|
1943
|
-
declaredEffects: []
|
|
1944
|
-
};
|
|
1945
|
-
}
|
|
1946
|
-
function requireReleaseWorkflowIntroduction(snapshot) {
|
|
1947
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1948
|
-
const namedVersion = manifestVersions.releaseWorkflow;
|
|
1949
|
-
if (namedVersion !== undefined && namedVersion !== RELEASE_WORKFLOW_FROM_VERSION) {
|
|
1950
|
-
throw new Error(`Record releaseWorkflow introduction requires an absent manifest version or ${RELEASE_WORKFLOW_FROM_VERSION}.`);
|
|
1951
|
-
}
|
|
1952
|
-
if (snapshot.state === null)
|
|
1953
|
-
return;
|
|
1954
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1955
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1956
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1957
|
-
if (task.releaseWorkflows !== undefined) {
|
|
1958
|
-
const workflows = asObject(task.releaseWorkflows, `releaseWorkflow map ${taskId}`);
|
|
1959
|
-
if (Object.keys(workflows).length > 0) {
|
|
1960
|
-
throw new Error(`Release workflow introduction found existing records: ${taskId}.`);
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
const marks = task.idHighWaterMarks;
|
|
1964
|
-
if (marks !== undefined) {
|
|
1965
|
-
const highWaterMarks = asObject(marks, `Task id high-water marks ${taskId}`);
|
|
1966
|
-
const mark = highWaterMarks.releaseWorkflow;
|
|
1967
|
-
if (mark !== undefined && mark !== RELEASE_WORKFLOW_FROM_VERSION) {
|
|
1968
|
-
throw new Error(`Release workflow introduction found a high-water mark: ${taskId}.`);
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
function introduceReleaseWorkflowFamily(snapshot) {
|
|
1974
|
-
requireReleaseWorkflowIntroduction(snapshot);
|
|
1975
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
1976
|
-
const schemaManifest = {
|
|
1977
|
-
...snapshot.schemaManifest,
|
|
1978
|
-
recordVersions: {
|
|
1979
|
-
...manifestVersions,
|
|
1980
|
-
releaseWorkflow: RELEASE_WORKFLOW_TO_VERSION
|
|
1981
|
-
}
|
|
1982
|
-
};
|
|
1983
|
-
if (snapshot.state === null)
|
|
1984
|
-
return { schemaManifest, state: null };
|
|
1985
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
1986
|
-
const nextTasks = {};
|
|
1987
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
1988
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
1989
|
-
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
1990
|
-
nextTasks[taskId] = {
|
|
1991
|
-
...task,
|
|
1992
|
-
idHighWaterMarks: { ...marks, releaseWorkflow: RELEASE_WORKFLOW_FROM_VERSION },
|
|
1993
|
-
releaseWorkflows: {}
|
|
1994
|
-
};
|
|
1995
|
-
}
|
|
1996
|
-
return {
|
|
1997
|
-
schemaManifest,
|
|
1998
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
1999
|
-
};
|
|
2000
|
-
}
|
|
2001
|
-
/**
|
|
2002
|
-
* Issue 11: introduce the task-scoped PublicationReference family. A
|
|
2003
|
-
* pre-introduction Home names no publicationReference manifest version and its
|
|
2004
|
-
* Task aggregates carry no `publicationReferences` map; this explicit 0->1
|
|
2005
|
-
* step adds the empty family to every Task (plus the matching high-water mark).
|
|
2006
|
-
*/
|
|
2007
|
-
function publicationReferenceIntroductionStep() {
|
|
2008
|
-
return {
|
|
2009
|
-
axis: "record",
|
|
2010
|
-
recordKind: "publicationReference",
|
|
2011
|
-
fromVersion: PUBLICATION_REFERENCE_FROM_VERSION,
|
|
2012
|
-
toVersion: PUBLICATION_REFERENCE_TO_VERSION,
|
|
2013
|
-
introduction: true,
|
|
2014
|
-
preconditions: requirePublicationReferenceIntroduction,
|
|
2015
|
-
transform: introducePublicationReferenceFamily,
|
|
2016
|
-
declaredEffects: []
|
|
2017
|
-
};
|
|
2018
|
-
}
|
|
2019
|
-
function requirePublicationReferenceIntroduction(snapshot) {
|
|
2020
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2021
|
-
const namedVersion = manifestVersions.publicationReference;
|
|
2022
|
-
if (namedVersion !== undefined && namedVersion !== PUBLICATION_REFERENCE_FROM_VERSION) {
|
|
2023
|
-
throw new Error(`Record publicationReference introduction requires an absent manifest version or ${PUBLICATION_REFERENCE_FROM_VERSION}.`);
|
|
2024
|
-
}
|
|
2025
|
-
if (snapshot.state === null)
|
|
2026
|
-
return;
|
|
2027
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2028
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2029
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2030
|
-
if (task.publicationReferences !== undefined) {
|
|
2031
|
-
const references = asObject(task.publicationReferences, `publicationReference map ${taskId}`);
|
|
2032
|
-
if (Object.keys(references).length > 0) {
|
|
2033
|
-
throw new Error(`Publication reference introduction found existing records: ${taskId}.`);
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
const marks = task.idHighWaterMarks;
|
|
2037
|
-
if (marks !== undefined) {
|
|
2038
|
-
const highWaterMarks = asObject(marks, `Task id high-water marks ${taskId}`);
|
|
2039
|
-
const mark = highWaterMarks.publicationReference;
|
|
2040
|
-
if (mark !== undefined && mark !== PUBLICATION_REFERENCE_FROM_VERSION) {
|
|
2041
|
-
throw new Error(`Publication reference introduction found a high-water mark: ${taskId}.`);
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
function introducePublicationReferenceFamily(snapshot) {
|
|
2047
|
-
requirePublicationReferenceIntroduction(snapshot);
|
|
2048
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2049
|
-
const schemaManifest = {
|
|
2050
|
-
...snapshot.schemaManifest,
|
|
2051
|
-
recordVersions: {
|
|
2052
|
-
...manifestVersions,
|
|
2053
|
-
publicationReference: PUBLICATION_REFERENCE_TO_VERSION
|
|
2054
|
-
}
|
|
2055
|
-
};
|
|
2056
|
-
if (snapshot.state === null)
|
|
2057
|
-
return { schemaManifest, state: null };
|
|
2058
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2059
|
-
const nextTasks = {};
|
|
2060
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2061
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2062
|
-
const marks = asObject(task.idHighWaterMarks, `Task id high-water marks ${taskId}`);
|
|
2063
|
-
nextTasks[taskId] = {
|
|
2064
|
-
...task,
|
|
2065
|
-
idHighWaterMarks: { ...marks, publicationReference: PUBLICATION_REFERENCE_FROM_VERSION },
|
|
2066
|
-
publicationReferences: {}
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
return {
|
|
2070
|
-
schemaManifest,
|
|
2071
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
2072
|
-
};
|
|
2073
|
-
}
|
|
2074
|
-
function recordFamilyStep(recordKind, fromVersion, toVersion, taskMapKey) {
|
|
2075
|
-
return {
|
|
2076
|
-
axis: "record",
|
|
2077
|
-
recordKind,
|
|
2078
|
-
fromVersion,
|
|
2079
|
-
toVersion,
|
|
2080
|
-
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, recordKind, fromVersion, taskMapKey),
|
|
2081
|
-
transform: (snapshot) => migrateRecordFamily(snapshot, recordKind, fromVersion, toVersion, taskMapKey),
|
|
2082
|
-
declaredEffects: []
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
function reviewRoundTaskAnchorStep() {
|
|
2086
|
-
return {
|
|
2087
|
-
axis: "record",
|
|
2088
|
-
recordKind: "reviewRound",
|
|
2089
|
-
fromVersion: REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION,
|
|
2090
|
-
toVersion: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION,
|
|
2091
|
-
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, "reviewRound", REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION, "reviewRounds"),
|
|
2092
|
-
transform: migrateReviewRoundTaskAnchors,
|
|
2093
|
-
declaredEffects: []
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
function migrateReviewRoundTaskAnchors(snapshot) {
|
|
2097
|
-
requireRecordFamilyVersion(snapshot, "reviewRound", REVIEW_ROUND_TASK_ANCHOR_FROM_VERSION, "reviewRounds");
|
|
2098
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2099
|
-
const schemaManifest = {
|
|
2100
|
-
...snapshot.schemaManifest,
|
|
2101
|
-
recordVersions: {
|
|
2102
|
-
...manifestVersions,
|
|
2103
|
-
reviewRound: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION
|
|
2104
|
-
}
|
|
2105
|
-
};
|
|
2106
|
-
if (snapshot.state === null)
|
|
2107
|
-
return { schemaManifest, state: null };
|
|
2108
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2109
|
-
const nextTasks = {};
|
|
2110
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2111
|
-
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2112
|
-
const rawRounds = aggregate.reviewRounds;
|
|
2113
|
-
if (rawRounds === undefined) {
|
|
2114
|
-
nextTasks[taskId] = { ...aggregate };
|
|
2115
|
-
continue;
|
|
2116
|
-
}
|
|
2117
|
-
const rounds = asObject(rawRounds, `reviewRound map ${taskId}`);
|
|
2118
|
-
const nextRounds = {};
|
|
2119
|
-
for (const [roundId, rawRound] of Object.entries(rounds)) {
|
|
2120
|
-
const round = asObject(rawRound, `reviewRound ${taskId}/${roundId}`);
|
|
2121
|
-
if (round.scope !== "task") {
|
|
2122
|
-
nextRounds[roundId] = {
|
|
2123
|
-
...round,
|
|
2124
|
-
schemaVersion: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION
|
|
2125
|
-
};
|
|
2126
|
-
continue;
|
|
2127
|
-
}
|
|
2128
|
-
const workItemId = requiredMigrationText(round.workItemId, `reviewRound ${taskId}/${roundId} WorkItem id`);
|
|
2129
|
-
const candidateId = requiredMigrationText(round.candidateId, `reviewRound ${taskId}/${roundId} Candidate id`);
|
|
2130
|
-
const { workItemId: _workItemId, candidateId: _candidateId, schemaVersion: _schemaVersion, ...retained } = round;
|
|
2131
|
-
nextRounds[roundId] = {
|
|
2132
|
-
...retained,
|
|
2133
|
-
schemaVersion: REVIEW_ROUND_TASK_ANCHOR_TO_VERSION,
|
|
2134
|
-
legacyAnchor: { workItemId, candidateId }
|
|
2135
|
-
};
|
|
2136
|
-
}
|
|
2137
|
-
nextTasks[taskId] = { ...aggregate, reviewRounds: nextRounds };
|
|
2138
|
-
}
|
|
2139
|
-
return {
|
|
2140
|
-
schemaManifest,
|
|
2141
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
2142
|
-
};
|
|
2143
|
-
}
|
|
2144
|
-
function agentRunContextProtocolStep() {
|
|
2145
|
-
return {
|
|
2146
|
-
axis: "record",
|
|
2147
|
-
recordKind: "agentRun",
|
|
2148
|
-
fromVersion: AGENT_RUN_CONTEXT_PROTOCOL_FROM_VERSION,
|
|
2149
|
-
toVersion: AGENT_RUN_CONTEXT_PROTOCOL_TO_VERSION,
|
|
2150
|
-
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_CONTEXT_PROTOCOL_FROM_VERSION, "agentRuns"),
|
|
2151
|
-
transform: migrateAgentRunsToContextProtocol,
|
|
2152
|
-
declaredEffects: []
|
|
2153
|
-
};
|
|
2154
|
-
}
|
|
2155
|
-
function migrateAgentRunsToContextProtocol(snapshot) {
|
|
2156
|
-
requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_CONTEXT_PROTOCOL_FROM_VERSION, "agentRuns");
|
|
2157
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2158
|
-
const schemaManifest = {
|
|
2159
|
-
...snapshot.schemaManifest,
|
|
2160
|
-
recordVersions: {
|
|
2161
|
-
...manifestVersions,
|
|
2162
|
-
agentRun: AGENT_RUN_CONTEXT_PROTOCOL_TO_VERSION
|
|
2163
|
-
}
|
|
2164
|
-
};
|
|
2165
|
-
if (snapshot.state === null)
|
|
2166
|
-
return { schemaManifest, state: null };
|
|
2167
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2168
|
-
const nextTasks = {};
|
|
2169
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2170
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2171
|
-
const records = asObject(task.agentRuns ?? {}, `agentRun map ${taskId}`);
|
|
2172
|
-
const nextRecords = {};
|
|
2173
|
-
for (const [runId, rawRecord] of Object.entries(records)) {
|
|
2174
|
-
const record = asObject(rawRecord, `agentRun ${taskId}/${runId}`);
|
|
2175
|
-
const { input: rawInput, schemaVersion: _schemaVersion, ...rest } = record;
|
|
2176
|
-
const roleName = requiredMigrationText(record.roleName, "Agent Run roleName");
|
|
2177
|
-
const purpose = record.purpose === "review" ? "review" : "execution";
|
|
2178
|
-
const workItemId = optionalMigrationText(record.workItemId, "Agent Run workItemId");
|
|
2179
|
-
const reviewRoundId = optionalMigrationText(record.reviewRoundId, "Agent Run reviewRoundId");
|
|
2180
|
-
const executionGroupId = optionalMigrationText(record.executionGroupId, "Agent Run executionGroupId");
|
|
2181
|
-
const executionLaneId = optionalMigrationText(record.executionLaneId, "Agent Run executionLaneId");
|
|
2182
|
-
const action = purpose === "review"
|
|
2183
|
-
? "review-round"
|
|
2184
|
-
: roleName === "leader"
|
|
2185
|
-
? "leader-wake"
|
|
2186
|
-
: workItemId === undefined
|
|
2187
|
-
? "lead-task"
|
|
2188
|
-
: "execute-work-item";
|
|
2189
|
-
const subject = {
|
|
2190
|
-
taskId,
|
|
2191
|
-
...(workItemId === undefined ? {} : { workItemId }),
|
|
2192
|
-
...(reviewRoundId === undefined ? {} : { reviewRoundId }),
|
|
2193
|
-
...(executionGroupId === undefined ? {} : { executionGroupId }),
|
|
2194
|
-
...(executionLaneId === undefined ? {} : { executionLaneId })
|
|
2195
|
-
};
|
|
2196
|
-
const assignment = {
|
|
2197
|
-
schemaVersion: 1,
|
|
2198
|
-
runId,
|
|
2199
|
-
roleName,
|
|
2200
|
-
purpose,
|
|
2201
|
-
action,
|
|
2202
|
-
subject,
|
|
2203
|
-
...(typeof rawInput === "string" && rawInput.trim().length > 0
|
|
2204
|
-
? { directive: rawInput }
|
|
2205
|
-
: {}),
|
|
2206
|
-
deltaRefIds: []
|
|
2207
|
-
};
|
|
2208
|
-
nextRecords[runId] = {
|
|
2209
|
-
...rest,
|
|
2210
|
-
schemaVersion: AGENT_RUN_CONTEXT_PROTOCOL_TO_VERSION,
|
|
2211
|
-
assignment,
|
|
2212
|
-
bootstrapEnvelope: {
|
|
2213
|
-
protocol: "yui-run/v1",
|
|
2214
|
-
runId,
|
|
2215
|
-
roleName,
|
|
2216
|
-
purpose,
|
|
2217
|
-
action,
|
|
2218
|
-
subject,
|
|
2219
|
-
deltaRefIds: []
|
|
2220
|
-
}
|
|
2221
|
-
};
|
|
2222
|
-
}
|
|
2223
|
-
nextTasks[taskId] = { ...task, agentRuns: nextRecords };
|
|
2224
|
-
}
|
|
2225
|
-
return {
|
|
2226
|
-
schemaManifest,
|
|
2227
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
2228
|
-
};
|
|
2229
|
-
}
|
|
2230
|
-
function agentRunRetryEpisodeStep() {
|
|
2231
|
-
return {
|
|
2232
|
-
axis: "record",
|
|
2233
|
-
recordKind: "agentRun",
|
|
2234
|
-
fromVersion: AGENT_RUN_RETRY_EPISODE_FROM_VERSION,
|
|
2235
|
-
toVersion: AGENT_RUN_RETRY_EPISODE_TO_VERSION,
|
|
2236
|
-
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_RETRY_EPISODE_FROM_VERSION, "agentRuns"),
|
|
2237
|
-
transform: migrateAgentRunsToRetryEpisodes,
|
|
2238
|
-
declaredEffects: []
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
function migrateAgentRunsToRetryEpisodes(snapshot) {
|
|
2242
|
-
requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_RETRY_EPISODE_FROM_VERSION, "agentRuns");
|
|
2243
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2244
|
-
const schemaManifest = {
|
|
2245
|
-
...snapshot.schemaManifest,
|
|
2246
|
-
recordVersions: { ...manifestVersions, agentRun: AGENT_RUN_RETRY_EPISODE_TO_VERSION }
|
|
2247
|
-
};
|
|
2248
|
-
if (snapshot.state === null)
|
|
2249
|
-
return { schemaManifest, state: null };
|
|
2250
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2251
|
-
const nextTasks = {};
|
|
2252
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2253
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2254
|
-
const records = asObject(task.agentRuns ?? {}, `agentRun map ${taskId}`);
|
|
2255
|
-
const nextRecords = {};
|
|
2256
|
-
for (const [runId, rawRecord] of Object.entries(records)) {
|
|
2257
|
-
const record = asObject(rawRecord, `agentRun ${taskId}/${runId}`);
|
|
2258
|
-
const legacyRetry = record.providerRetry === undefined
|
|
2259
|
-
? undefined
|
|
2260
|
-
: asObject(record.providerRetry, `providerRetry ${taskId}/${runId}`);
|
|
2261
|
-
let providerRetry = undefined;
|
|
2262
|
-
let deliveryReceiptId;
|
|
2263
|
-
if (legacyRetry !== undefined) {
|
|
2264
|
-
const attempt = requiredMigrationPositiveInteger(legacyRetry.attempt, `providerRetry attempt ${taskId}/${runId}`);
|
|
2265
|
-
const firstFailureAt = requiredMigrationTimestamp(legacyRetry.firstFailureAt, `providerRetry firstFailureAt ${taskId}/${runId}`);
|
|
2266
|
-
const lastFailureAt = requiredMigrationTimestamp(legacyRetry.lastFailureAt, `providerRetry lastFailureAt ${taskId}/${runId}`);
|
|
2267
|
-
const nextAttemptAt = optionalMigrationTimestamp(legacyRetry.nextAttemptAt, `providerRetry nextAttemptAt ${taskId}/${runId}`);
|
|
2268
|
-
const policyBlocked = legacyRetry.errorClass === "policy-denied";
|
|
2269
|
-
const deadlineMs = Math.max(Date.parse(firstFailureAt) + 600_000, nextAttemptAt === undefined ? 0 : Date.parse(nextAttemptAt) + 1);
|
|
2270
|
-
deliveryReceiptId = nextAttemptAt !== undefined || policyBlocked
|
|
2271
|
-
? undefined
|
|
2272
|
-
: `legacy-retry-receipt-${runId}-${attempt}`;
|
|
2273
|
-
providerRetry = {
|
|
2274
|
-
schemaVersion: 2,
|
|
2275
|
-
episodeId: `legacy-retry-${runId}`,
|
|
2276
|
-
failureEventId: `legacy-provider-failure-${runId}-${attempt}`,
|
|
2277
|
-
policyVersion: 1,
|
|
2278
|
-
state: nextAttemptAt !== undefined
|
|
2279
|
-
? "scheduled"
|
|
2280
|
-
: policyBlocked ? "blocked" : "awaiting-progress",
|
|
2281
|
-
errorClass: legacyRetry.errorClass,
|
|
2282
|
-
consecutiveFailures: attempt,
|
|
2283
|
-
dispatchedRetries: Math.min(3, nextAttemptAt === undefined ? attempt : attempt - 1),
|
|
2284
|
-
maxRetries: 3,
|
|
2285
|
-
firstFailureAt,
|
|
2286
|
-
lastFailureAt,
|
|
2287
|
-
episodeDeadlineAt: new Date(deadlineMs).toISOString(),
|
|
2288
|
-
...(nextAttemptAt === undefined ? {} : { nextAttemptAt }),
|
|
2289
|
-
...(deliveryReceiptId === undefined
|
|
2290
|
-
? {}
|
|
2291
|
-
: { lastRetryReceiptId: deliveryReceiptId }),
|
|
2292
|
-
...(legacyRetry.launchId === undefined ? {} : { launchId: legacyRetry.launchId }),
|
|
2293
|
-
...(legacyRetry.nativeSessionId === undefined
|
|
2294
|
-
? {}
|
|
2295
|
-
: { nativeSessionId: legacyRetry.nativeSessionId }),
|
|
2296
|
-
lastErrorSummary: legacyRetry.lastErrorSummary
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
|
-
nextRecords[runId] = {
|
|
2300
|
-
...record,
|
|
2301
|
-
schemaVersion: AGENT_RUN_RETRY_EPISODE_TO_VERSION,
|
|
2302
|
-
...(deliveryReceiptId === undefined ? {} : { deliveryReceiptId }),
|
|
2303
|
-
...(providerRetry === undefined ? {} : { providerRetry })
|
|
2304
|
-
};
|
|
2305
|
-
}
|
|
2306
|
-
nextTasks[taskId] = { ...task, agentRuns: nextRecords };
|
|
2307
|
-
}
|
|
2308
|
-
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
2309
|
-
}
|
|
2310
|
-
/** Deletes the retired retry episode; an AgentRun now records scheduling only. */
|
|
2311
|
-
function agentRunErrorFactsStep() {
|
|
2312
|
-
return {
|
|
2313
|
-
axis: "record",
|
|
2314
|
-
recordKind: "agentRun",
|
|
2315
|
-
fromVersion: AGENT_RUN_ERROR_FACTS_FROM_VERSION,
|
|
2316
|
-
toVersion: AGENT_RUN_ERROR_FACTS_TO_VERSION,
|
|
2317
|
-
preconditions: (snapshot) => requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_ERROR_FACTS_FROM_VERSION, "agentRuns"),
|
|
2318
|
-
transform: migrateAgentRunsToErrorFacts,
|
|
2319
|
-
declaredEffects: []
|
|
2320
|
-
};
|
|
2321
|
-
}
|
|
2322
|
-
function migrateAgentRunsToErrorFacts(snapshot) {
|
|
2323
|
-
requireRecordFamilyVersion(snapshot, "agentRun", AGENT_RUN_ERROR_FACTS_FROM_VERSION, "agentRuns");
|
|
2324
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2325
|
-
const schemaManifest = {
|
|
2326
|
-
...snapshot.schemaManifest,
|
|
2327
|
-
recordVersions: { ...versions, agentRun: AGENT_RUN_ERROR_FACTS_TO_VERSION }
|
|
2328
|
-
};
|
|
2329
|
-
if (snapshot.state === null)
|
|
2330
|
-
return { schemaManifest, state: null };
|
|
2331
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2332
|
-
const nextTasks = {};
|
|
2333
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2334
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2335
|
-
const records = asObject(task.agentRuns ?? {}, `agentRun map ${taskId}`);
|
|
2336
|
-
const nextRecords = {};
|
|
2337
|
-
for (const [runId, rawRecord] of Object.entries(records)) {
|
|
2338
|
-
const record = asObject(rawRecord, `agentRun ${taskId}/${runId}`);
|
|
2339
|
-
const { providerRetry: _retry, ...retained } = record;
|
|
2340
|
-
void _retry;
|
|
2341
|
-
nextRecords[runId] = {
|
|
2342
|
-
...retained,
|
|
2343
|
-
schemaVersion: AGENT_RUN_ERROR_FACTS_TO_VERSION
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2346
|
-
nextTasks[taskId] = { ...task, agentRuns: nextRecords };
|
|
2347
|
-
}
|
|
2348
|
-
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
2349
|
-
}
|
|
2350
|
-
function requiredMigrationPositiveInteger(value, label) {
|
|
2351
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
2352
|
-
throw new Error(`${label} must be a positive integer.`);
|
|
2353
|
-
}
|
|
2354
|
-
return value;
|
|
2355
|
-
}
|
|
2356
|
-
function requiredMigrationTimestamp(value, label) {
|
|
2357
|
-
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
|
|
2358
|
-
throw new Error(`${label} must be a timestamp.`);
|
|
2359
|
-
}
|
|
2360
|
-
return value;
|
|
2361
|
-
}
|
|
2362
|
-
function optionalMigrationTimestamp(value, label) {
|
|
2363
|
-
return value === undefined ? undefined : requiredMigrationTimestamp(value, label);
|
|
2364
|
-
}
|
|
2365
|
-
function requiredMigrationText(value, label) {
|
|
2366
|
-
if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
|
|
2367
|
-
throw new Error(`${label} is invalid.`);
|
|
2368
|
-
}
|
|
2369
|
-
return value.trim();
|
|
2370
|
-
}
|
|
2371
|
-
function optionalMigrationText(value, label) {
|
|
2372
|
-
return value === undefined ? undefined : requiredMigrationText(value, label);
|
|
2373
|
-
}
|
|
2374
|
-
/**
|
|
2375
|
-
* The snapshot boundary is nested inside WorkItem/ReviewRound ExecutionGroup
|
|
2376
|
-
* results. The parent record versions make that persisted shape explicit;
|
|
2377
|
-
* this adjacent step intentionally performs no field rewrite, preserving old
|
|
2378
|
-
* records while preventing a pre-v8/pre-v4 Home from entering the strict
|
|
2379
|
-
* current parser without the declared transition.
|
|
2380
|
-
*/
|
|
2381
|
-
function requireRecordFamilyVersion(snapshot, recordKind, fromVersion, taskMapKey) {
|
|
2382
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2383
|
-
if (manifestVersions[recordKind] !== fromVersion) {
|
|
2384
|
-
throw new Error(`Record ${recordKind} migration requires manifest version ${fromVersion}.`);
|
|
2385
|
-
}
|
|
2386
|
-
if (snapshot.state === null)
|
|
2387
|
-
return;
|
|
2388
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2389
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2390
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2391
|
-
const records = task[taskMapKey];
|
|
2392
|
-
if (records === undefined)
|
|
2393
|
-
continue;
|
|
2394
|
-
const map = asObject(records, `${recordKind} map ${taskId}`);
|
|
2395
|
-
for (const [recordId, rawRecord] of Object.entries(map)) {
|
|
2396
|
-
const record = asObject(rawRecord, `${recordKind} ${taskId}/${recordId}`);
|
|
2397
|
-
if (record.schemaVersion !== fromVersion) {
|
|
2398
|
-
throw new Error(`Record ${recordKind} ${taskId}/${recordId} must use schemaVersion ${fromVersion}.`);
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
function migrateRecordFamily(snapshot, recordKind, fromVersion, toVersion, taskMapKey) {
|
|
2404
|
-
// Keep the same source-shape checks in the transform so a direct caller
|
|
2405
|
-
// cannot bypass the migration's precondition contract.
|
|
2406
|
-
requireRecordFamilyVersion(snapshot, recordKind, fromVersion, taskMapKey);
|
|
2407
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2408
|
-
const schemaManifest = {
|
|
2409
|
-
...snapshot.schemaManifest,
|
|
2410
|
-
recordVersions: {
|
|
2411
|
-
...manifestVersions,
|
|
2412
|
-
[recordKind]: toVersion
|
|
2413
|
-
}
|
|
2414
|
-
};
|
|
2415
|
-
if (snapshot.state === null)
|
|
2416
|
-
return { schemaManifest, state: null };
|
|
2417
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2418
|
-
const nextTasks = {};
|
|
2419
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
2420
|
-
const task = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
2421
|
-
const records = task[taskMapKey];
|
|
2422
|
-
if (records === undefined) {
|
|
2423
|
-
nextTasks[taskId] = { ...task };
|
|
2424
|
-
continue;
|
|
2425
|
-
}
|
|
2426
|
-
const map = asObject(records, `${recordKind} map ${taskId}`);
|
|
2427
|
-
const nextMap = {};
|
|
2428
|
-
for (const [recordId, rawRecord] of Object.entries(map)) {
|
|
2429
|
-
nextMap[recordId] = {
|
|
2430
|
-
...asObject(rawRecord, `${recordKind} ${taskId}/${recordId}`),
|
|
2431
|
-
schemaVersion: toVersion
|
|
2432
|
-
};
|
|
2433
|
-
}
|
|
2434
|
-
nextTasks[taskId] = { ...task, [taskMapKey]: nextMap };
|
|
2435
|
-
}
|
|
2436
|
-
return {
|
|
2437
|
-
schemaManifest,
|
|
2438
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
2439
|
-
};
|
|
2440
|
-
}
|
|
2441
|
-
function asObject(value, label) {
|
|
2442
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2443
|
-
throw new Error(`${label} must be an object.`);
|
|
2444
|
-
}
|
|
2445
|
-
return value;
|
|
2446
|
-
}
|
|
2447
|
-
/** Historical public spelling retained as an alias to the single graph. */
|
|
2448
|
-
export const createProductionRegistry = createProductionStorageRegistry;
|
|
2449
|
-
/**
|
|
2450
|
-
* Layout 6 -> 7 is the state.json -> SQLite WAL transition (task-21 §8). The
|
|
2451
|
-
* transform only advances the manifest's layout version; the staged SQLite
|
|
2452
|
-
* database is populated by the migration target's `writeFreshOutput`, and the
|
|
2453
|
-
* `state.json` document is retained read-only for rollback. The precondition
|
|
2454
|
-
* requires a layout-6 manifest so a future layout cannot be silently regressed.
|
|
2455
|
-
*/
|
|
2456
|
-
function requireLayoutV6Snapshot(snapshot) {
|
|
2457
|
-
if (snapshot.schemaManifest.storageVersion !== SQLITE_LAYOUT_FROM_VERSION) {
|
|
2458
|
-
throw new Error(`Layout ${SQLITE_LAYOUT_FROM_VERSION}->${SQLITE_LAYOUT_TO_VERSION} migration requires ` +
|
|
2459
|
-
`schema.json storageVersion ${SQLITE_LAYOUT_FROM_VERSION}.`);
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
function migrateLayoutV6ToV7(snapshot) {
|
|
2463
|
-
requireLayoutV6Snapshot(snapshot);
|
|
2464
|
-
return {
|
|
2465
|
-
schemaManifest: {
|
|
2466
|
-
...snapshot.schemaManifest,
|
|
2467
|
-
storageVersion: SQLITE_LAYOUT_TO_VERSION
|
|
2468
|
-
},
|
|
2469
|
-
state: snapshot.state
|
|
2470
|
-
};
|
|
2471
|
-
}
|
|
2472
|
-
/** Advance the aggregate identity after proving manifest/root agreement at v16. */
|
|
2473
|
-
function migrateAggregateV16ToV17(snapshot) {
|
|
2474
|
-
return {
|
|
2475
|
-
schemaManifest: {
|
|
2476
|
-
...snapshot.schemaManifest,
|
|
2477
|
-
aggregateSchemaVersion: FINAL_REVIEW_AGGREGATE_TO_VERSION
|
|
2478
|
-
},
|
|
2479
|
-
state: snapshot.state === null
|
|
2480
|
-
? null
|
|
2481
|
-
: {
|
|
2482
|
-
...snapshot.state,
|
|
2483
|
-
schemaVersion: FINAL_REVIEW_AGGREGATE_TO_VERSION
|
|
2484
|
-
}
|
|
2485
|
-
};
|
|
2486
|
-
}
|
|
2487
|
-
function requireAggregateV16Snapshot(snapshot) {
|
|
2488
|
-
if (snapshot.schemaManifest.aggregateSchemaVersion
|
|
2489
|
-
!== FINAL_REVIEW_AGGREGATE_FROM_VERSION) {
|
|
2490
|
-
throw new Error("Aggregate 16->17 migration requires schema.json aggregateSchemaVersion 16.");
|
|
2491
|
-
}
|
|
2492
|
-
if (snapshot.state !== null
|
|
2493
|
-
&& snapshot.state.schemaVersion !== FINAL_REVIEW_AGGREGATE_FROM_VERSION) {
|
|
2494
|
-
throw new Error("Aggregate 16->17 migration requires state.json schemaVersion 16 to match schema.json.");
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
/**
|
|
2498
|
-
* Introduce the durable Home identity. A v18 aggregate always carries one; a
|
|
2499
|
-
* Home with no state.json yet gets its identity when its first state is
|
|
2500
|
-
* written, so this step only mints one when state.json already exists.
|
|
2501
|
-
*/
|
|
2502
|
-
function migrateAggregateV17ToV18(snapshot) {
|
|
2503
|
-
const schemaManifest = {
|
|
2504
|
-
...snapshot.schemaManifest,
|
|
2505
|
-
aggregateSchemaVersion: HOME_IDENTITY_AGGREGATE_TO_VERSION
|
|
2506
|
-
};
|
|
2507
|
-
if (snapshot.state === null)
|
|
2508
|
-
return { schemaManifest, state: null };
|
|
2509
|
-
const state = snapshot.state;
|
|
2510
|
-
if (state.homeIdentity !== undefined) {
|
|
2511
|
-
throw new Error("Aggregate 17->18 migration found an unexpected homeIdentity.");
|
|
2512
|
-
}
|
|
2513
|
-
return {
|
|
2514
|
-
schemaManifest,
|
|
2515
|
-
state: {
|
|
2516
|
-
...state,
|
|
2517
|
-
schemaVersion: HOME_IDENTITY_AGGREGATE_TO_VERSION,
|
|
2518
|
-
homeIdentity: generateHomeIdentity(new Date())
|
|
2519
|
-
}
|
|
2520
|
-
};
|
|
2521
|
-
}
|
|
2522
|
-
function requireAggregateV17Snapshot(snapshot) {
|
|
2523
|
-
if (snapshot.schemaManifest.aggregateSchemaVersion
|
|
2524
|
-
!== HOME_IDENTITY_AGGREGATE_FROM_VERSION) {
|
|
2525
|
-
throw new Error("Aggregate 17->18 migration requires schema.json aggregateSchemaVersion 17.");
|
|
2526
|
-
}
|
|
2527
|
-
if (snapshot.state !== null
|
|
2528
|
-
&& snapshot.state.schemaVersion !== HOME_IDENTITY_AGGREGATE_FROM_VERSION) {
|
|
2529
|
-
throw new Error("Aggregate 17->18 migration requires state.json schemaVersion 17 to match schema.json.");
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
/**
|
|
2533
|
-
* Runtime state is now projected exclusively from canonical
|
|
2534
|
-
* `runtime.observation` events. Offline upgrade inventory proves there are no
|
|
2535
|
-
* active Runs or live Sessions. A retired Task has also explicitly abandoned
|
|
2536
|
-
* its runtime, so its stored runtime inconsistencies no longer block the Home;
|
|
2537
|
-
* the anomalous records themselves remain available as history. Non-retired
|
|
2538
|
-
* Tasks still fail closed and receive the supported retirement command.
|
|
2539
|
-
*/
|
|
2540
|
-
function migrateAggregateV18ToV19(snapshot) {
|
|
2541
|
-
requireAggregateV18Snapshot(snapshot);
|
|
2542
|
-
const schemaManifest = {
|
|
2543
|
-
...snapshot.schemaManifest,
|
|
2544
|
-
aggregateSchemaVersion: RUNTIME_OBSERVATION_AGGREGATE_TO_VERSION
|
|
2545
|
-
};
|
|
2546
|
-
if (snapshot.state === null)
|
|
2547
|
-
return { schemaManifest, state: null };
|
|
2548
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2549
|
-
const nextTasks = {};
|
|
2550
|
-
for (const [taskId, rawStoredTask] of Object.entries(tasks)) {
|
|
2551
|
-
const storedTask = asObject(rawStoredTask, `Task aggregate ${taskId}`);
|
|
2552
|
-
const rawTask = storedTask.task;
|
|
2553
|
-
const task = rawTask !== null && typeof rawTask === "object" && !Array.isArray(rawTask)
|
|
2554
|
-
? rawTask
|
|
2555
|
-
: undefined;
|
|
2556
|
-
if (task?.status === "retired") {
|
|
2557
|
-
nextTasks[taskId] = { ...storedTask };
|
|
2558
|
-
continue;
|
|
2559
|
-
}
|
|
2560
|
-
requireResolvableActiveRunPointers(taskId, storedTask);
|
|
2561
|
-
nextTasks[taskId] = { ...storedTask };
|
|
2562
|
-
}
|
|
2563
|
-
return {
|
|
2564
|
-
schemaManifest,
|
|
2565
|
-
state: {
|
|
2566
|
-
...snapshot.state,
|
|
2567
|
-
schemaVersion: RUNTIME_OBSERVATION_AGGREGATE_TO_VERSION,
|
|
2568
|
-
tasks: nextTasks
|
|
2569
|
-
}
|
|
2570
|
-
};
|
|
2571
|
-
}
|
|
2572
|
-
function requireResolvableActiveRunPointers(taskId, storedTask) {
|
|
2573
|
-
if (storedTask.activeRuns === undefined)
|
|
2574
|
-
return;
|
|
2575
|
-
const activeRuns = asObject(storedTask.activeRuns, `activeRunPointer map ${taskId}`);
|
|
2576
|
-
if (Object.keys(activeRuns).length === 0)
|
|
2577
|
-
return;
|
|
2578
|
-
const agentRuns = asObject(storedTask.agentRuns, `agentRun map ${taskId}`);
|
|
2579
|
-
for (const [pointer, rawActiveRun] of Object.entries(activeRuns)) {
|
|
2580
|
-
const activeRun = asObject(rawActiveRun, `Active run ${taskId}/${pointer}`);
|
|
2581
|
-
const runId = typeof activeRun.runId === "string" ? activeRun.runId.trim() : "";
|
|
2582
|
-
if (runId.length === 0) {
|
|
2583
|
-
throw new Error(`Active run pointer ${taskId}/${pointer} has an invalid runId. `
|
|
2584
|
-
+ taskRetirementUpgradeHint(taskId));
|
|
2585
|
-
}
|
|
2586
|
-
if (agentRuns[runId] === undefined) {
|
|
2587
|
-
throw new Error(`Active run pointer ${taskId}/${pointer} references missing agent run ${runId}. `
|
|
2588
|
-
+ taskRetirementUpgradeHint(taskId));
|
|
2589
|
-
}
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
function taskRetirementUpgradeHint(taskId) {
|
|
2593
|
-
return `Retire Task ${taskId} with `
|
|
2594
|
-
+ `\`yui task retire ${taskId} --summary "abandon inconsistent runtime state"\`, `
|
|
2595
|
-
+ "then retry `yui update`.";
|
|
2596
|
-
}
|
|
2597
|
-
function requireAggregateV18Snapshot(snapshot) {
|
|
2598
|
-
if (snapshot.schemaManifest.aggregateSchemaVersion
|
|
2599
|
-
!== RUNTIME_OBSERVATION_AGGREGATE_FROM_VERSION) {
|
|
2600
|
-
throw new Error("Aggregate 18->19 migration requires schema.json aggregateSchemaVersion 18.");
|
|
2601
|
-
}
|
|
2602
|
-
if (snapshot.state !== null
|
|
2603
|
-
&& snapshot.state.schemaVersion !== RUNTIME_OBSERVATION_AGGREGATE_FROM_VERSION) {
|
|
2604
|
-
throw new Error("Aggregate 18->19 migration requires state.json schemaVersion 18 to match schema.json.");
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
function requireAggregateV19Snapshot(snapshot) {
|
|
2608
|
-
if (snapshot.schemaManifest.aggregateSchemaVersion
|
|
2609
|
-
!== MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION) {
|
|
2610
|
-
throw new Error("Aggregate 19->20 migration requires schema.json aggregateSchemaVersion 19.");
|
|
2611
|
-
}
|
|
2612
|
-
if (snapshot.state !== null
|
|
2613
|
-
&& snapshot.state.schemaVersion !== MULTI_AGENT_RUNTIME_AGGREGATE_FROM_VERSION) {
|
|
2614
|
-
throw new Error("Aggregate 19->20 migration requires state.json schemaVersion 19 to match schema.json.");
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
function migrateAggregateV19ToV20(snapshot) {
|
|
2618
|
-
requireAggregateV19Snapshot(snapshot);
|
|
2619
|
-
const schemaManifest = {
|
|
2620
|
-
...snapshot.schemaManifest,
|
|
2621
|
-
aggregateSchemaVersion: MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION
|
|
2622
|
-
};
|
|
2623
|
-
if (snapshot.state === null)
|
|
2624
|
-
return { schemaManifest, state: null };
|
|
2625
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2626
|
-
const nextTasks = {};
|
|
2627
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
2628
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
2629
|
-
const events = asObject(aggregate.events, `Task events ${taskId}`);
|
|
2630
|
-
const nextEvents = {};
|
|
2631
|
-
for (const [eventId, rawEvent] of Object.entries(events)) {
|
|
2632
|
-
const event = asObject(rawEvent, `Task event ${taskId}/${eventId}`);
|
|
2633
|
-
nextEvents[eventId] = event.type === "runtime.observation"
|
|
2634
|
-
? migrateRuntimeObservationEventV1ToV2(event, taskId, eventId)
|
|
2635
|
-
: { ...event };
|
|
2636
|
-
}
|
|
2637
|
-
nextTasks[taskId] = { ...aggregate, events: nextEvents };
|
|
2638
|
-
}
|
|
2639
|
-
return {
|
|
2640
|
-
schemaManifest,
|
|
2641
|
-
state: {
|
|
2642
|
-
...snapshot.state,
|
|
2643
|
-
schemaVersion: MULTI_AGENT_RUNTIME_AGGREGATE_TO_VERSION,
|
|
2644
|
-
tasks: nextTasks
|
|
2645
|
-
}
|
|
2646
|
-
};
|
|
2647
|
-
}
|
|
2648
|
-
function migrateRuntimeObservationEventV1ToV2(event, taskId, eventId) {
|
|
2649
|
-
const payload = asObject(event.payload, `Runtime observation event payload ${taskId}/${eventId}`);
|
|
2650
|
-
if (typeof payload.observation !== "string") {
|
|
2651
|
-
throw new Error(`Runtime observation event has no canonical observation: ${taskId}/${eventId}.`);
|
|
2652
|
-
}
|
|
2653
|
-
let raw;
|
|
2654
|
-
try {
|
|
2655
|
-
raw = JSON.parse(payload.observation);
|
|
2656
|
-
}
|
|
2657
|
-
catch {
|
|
2658
|
-
throw new Error(`Runtime observation event JSON is invalid: ${taskId}/${eventId}.`);
|
|
2659
|
-
}
|
|
2660
|
-
const observation = asObject(raw, `Runtime observation ${taskId}/${eventId}`);
|
|
2661
|
-
if (observation.schemaVersion !== 1) {
|
|
2662
|
-
throw new Error(`Runtime observation ${taskId}/${eventId} must use schemaVersion 1.`);
|
|
2663
|
-
}
|
|
2664
|
-
const fence = asObject(observation.fence, `Runtime observation fence ${taskId}/${eventId}`);
|
|
2665
|
-
const kind = String(observation.kind);
|
|
2666
|
-
const semanticKey = migratedRuntimeObservationSemanticKey(observation, fence);
|
|
2667
|
-
const nextObservation = {
|
|
2668
|
-
...observation,
|
|
2669
|
-
schemaVersion: 2,
|
|
2670
|
-
semanticKey,
|
|
2671
|
-
fence: {
|
|
2672
|
-
...fence,
|
|
2673
|
-
...(fence.nativeSessionId === undefined
|
|
2674
|
-
? {}
|
|
2675
|
-
: { conversationId: String(fence.nativeSessionId) }),
|
|
2676
|
-
activationId: String(fence.launchId)
|
|
2677
|
-
}
|
|
2678
|
-
};
|
|
2679
|
-
return {
|
|
2680
|
-
...event,
|
|
2681
|
-
payload: {
|
|
2682
|
-
...payload,
|
|
2683
|
-
semanticKey,
|
|
2684
|
-
kind,
|
|
2685
|
-
observation: JSON.stringify(nextObservation)
|
|
2686
|
-
}
|
|
2687
|
-
};
|
|
2688
|
-
}
|
|
2689
|
-
function migratedRuntimeObservationSemanticKey(observation, fence) {
|
|
2690
|
-
const kind = String(observation.kind);
|
|
2691
|
-
if (Number.isSafeInteger(observation.sequence)) {
|
|
2692
|
-
return `provider-sequence:${String(fence.driverId)}:${String(fence.nativeSessionId ?? fence.launchId)}:${String(observation.sequence)}:${kind}`;
|
|
2693
|
-
}
|
|
2694
|
-
const terminal = [
|
|
2695
|
-
"session.ended",
|
|
2696
|
-
"session.failed",
|
|
2697
|
-
"turn.completed",
|
|
2698
|
-
"turn.failed",
|
|
2699
|
-
"turn.cancelled"
|
|
2700
|
-
].includes(kind);
|
|
2701
|
-
if (terminal) {
|
|
2702
|
-
const payload = asObject(observation.payload, "Runtime observation payload");
|
|
2703
|
-
const failure = payload.failure === undefined
|
|
2704
|
-
? undefined
|
|
2705
|
-
: asObject(payload.failure, "Runtime observation failure");
|
|
2706
|
-
return [
|
|
2707
|
-
"terminal",
|
|
2708
|
-
String(fence.driverId),
|
|
2709
|
-
String(fence.nativeSessionId ?? fence.launchId),
|
|
2710
|
-
String(fence.launchId),
|
|
2711
|
-
String(fence.nativeTurnId ?? "none"),
|
|
2712
|
-
kind,
|
|
2713
|
-
String(payload.outcome ?? failure?.code ?? "terminal")
|
|
2714
|
-
].join(":");
|
|
2715
|
-
}
|
|
2716
|
-
return `provider-event:${String(observation.eventId)}`;
|
|
2717
|
-
}
|
|
2718
|
-
function workMailboxV2Step() {
|
|
2719
|
-
return {
|
|
2720
|
-
axis: "record",
|
|
2721
|
-
recordKind: "workMailbox",
|
|
2722
|
-
fromVersion: WORK_MAILBOX_FROM_VERSION,
|
|
2723
|
-
toVersion: WORK_MAILBOX_TO_VERSION,
|
|
2724
|
-
preconditions: requireWorkMailboxV1Family,
|
|
2725
|
-
transform: migrateWorkMailboxV1ToV2,
|
|
2726
|
-
declaredEffects: []
|
|
2727
|
-
};
|
|
2728
|
-
}
|
|
2729
|
-
function requireWorkMailboxV1Family(snapshot) {
|
|
2730
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2731
|
-
if (versions.workMailbox !== WORK_MAILBOX_FROM_VERSION) {
|
|
2732
|
-
throw new Error(`Record workMailbox migration requires manifest version ${WORK_MAILBOX_FROM_VERSION}.`);
|
|
2733
|
-
}
|
|
2734
|
-
if (snapshot.state === null)
|
|
2735
|
-
return;
|
|
2736
|
-
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
2737
|
-
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
2738
|
-
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
2739
|
-
if (mailbox.schemaVersion !== WORK_MAILBOX_FROM_VERSION) {
|
|
2740
|
-
throw new Error(`WorkMailbox ${key} must use schemaVersion ${WORK_MAILBOX_FROM_VERSION}.`);
|
|
2741
|
-
}
|
|
2742
|
-
if (mailbox.pending !== null)
|
|
2743
|
-
asObject(mailbox.pending, `WorkMailbox ${key} pending`);
|
|
2744
|
-
if (mailbox.processing !== null)
|
|
2745
|
-
asObject(mailbox.processing, `WorkMailbox ${key} processing`);
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
function migrateWorkMailboxV1ToV2(snapshot) {
|
|
2749
|
-
requireWorkMailboxV1Family(snapshot);
|
|
2750
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2751
|
-
const schemaManifest = {
|
|
2752
|
-
...snapshot.schemaManifest,
|
|
2753
|
-
recordVersions: { ...versions, workMailbox: WORK_MAILBOX_TO_VERSION }
|
|
2754
|
-
};
|
|
2755
|
-
if (snapshot.state === null)
|
|
2756
|
-
return { schemaManifest, state: null };
|
|
2757
|
-
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
2758
|
-
const nextMailboxes = {};
|
|
2759
|
-
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
2760
|
-
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
2761
|
-
const pending = mailbox.pending === null
|
|
2762
|
-
? null
|
|
2763
|
-
: migrateMailboxBatchV1(mailbox.pending, key, "pending");
|
|
2764
|
-
const migratedProcessing = mailbox.processing === null
|
|
2765
|
-
? null
|
|
2766
|
-
: migrateMailboxProcessingV1(mailbox.processing, key);
|
|
2767
|
-
const continuationDelivery = migratedProcessing === null
|
|
2768
|
-
? null
|
|
2769
|
-
: migrateMailboxContinuationDeliveryV1(snapshot, mailbox, migratedProcessing, key);
|
|
2770
|
-
nextMailboxes[key] = {
|
|
2771
|
-
...mailbox,
|
|
2772
|
-
schemaVersion: WORK_MAILBOX_TO_VERSION,
|
|
2773
|
-
processing: continuationDelivery === null ? migratedProcessing : null,
|
|
2774
|
-
pending: {
|
|
2775
|
-
normal: pending,
|
|
2776
|
-
userCorrection: null,
|
|
2777
|
-
cursors: { normal: 0, userCorrection: 0 },
|
|
2778
|
-
recentDedupeKeys: []
|
|
2779
|
-
},
|
|
2780
|
-
inputDelivery: continuationDelivery
|
|
2781
|
-
};
|
|
2782
|
-
}
|
|
2783
|
-
return {
|
|
2784
|
-
schemaManifest,
|
|
2785
|
-
state: { ...snapshot.state, mailboxes: nextMailboxes }
|
|
2786
|
-
};
|
|
2787
|
-
}
|
|
2788
|
-
/** A Host launch reservation belongs to the Role owner and launchId, never a Run. */
|
|
2789
|
-
function hostOwnedWorkMailboxStep() {
|
|
2790
|
-
return {
|
|
2791
|
-
axis: "record",
|
|
2792
|
-
recordKind: "workMailbox",
|
|
2793
|
-
fromVersion: HOST_OWNED_WORK_MAILBOX_FROM_VERSION,
|
|
2794
|
-
toVersion: HOST_OWNED_WORK_MAILBOX_TO_VERSION,
|
|
2795
|
-
preconditions: requireWorkMailboxV2Family,
|
|
2796
|
-
transform: migrateWorkMailboxV2ToV3,
|
|
2797
|
-
declaredEffects: []
|
|
2798
|
-
};
|
|
2799
|
-
}
|
|
2800
|
-
function requireWorkMailboxV2Family(snapshot) {
|
|
2801
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2802
|
-
if (versions.workMailbox !== HOST_OWNED_WORK_MAILBOX_FROM_VERSION) {
|
|
2803
|
-
throw new Error(`Record workMailbox migration requires manifest version ${HOST_OWNED_WORK_MAILBOX_FROM_VERSION}.`);
|
|
2804
|
-
}
|
|
2805
|
-
if (snapshot.state === null)
|
|
2806
|
-
return;
|
|
2807
|
-
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
2808
|
-
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
2809
|
-
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
2810
|
-
if (mailbox.schemaVersion !== HOST_OWNED_WORK_MAILBOX_FROM_VERSION) {
|
|
2811
|
-
throw new Error(`WorkMailbox ${key} must use schemaVersion ${HOST_OWNED_WORK_MAILBOX_FROM_VERSION}.`);
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
function migrateWorkMailboxV2ToV3(snapshot) {
|
|
2816
|
-
requireWorkMailboxV2Family(snapshot);
|
|
2817
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2818
|
-
const schemaManifest = {
|
|
2819
|
-
...snapshot.schemaManifest,
|
|
2820
|
-
recordVersions: { ...versions, workMailbox: HOST_OWNED_WORK_MAILBOX_TO_VERSION }
|
|
2821
|
-
};
|
|
2822
|
-
if (snapshot.state === null)
|
|
2823
|
-
return { schemaManifest, state: null };
|
|
2824
|
-
const mailboxes = asObject(snapshot.state.mailboxes, "state mailboxes");
|
|
2825
|
-
const nextMailboxes = {};
|
|
2826
|
-
for (const [key, rawMailbox] of Object.entries(mailboxes)) {
|
|
2827
|
-
const mailbox = asObject(rawMailbox, `WorkMailbox ${key}`);
|
|
2828
|
-
const target = asObject(mailbox.target, `WorkMailbox ${key} target`);
|
|
2829
|
-
const processing = mailbox.processing === null
|
|
2830
|
-
? null
|
|
2831
|
-
: asObject(mailbox.processing, `WorkMailbox ${key} processing`);
|
|
2832
|
-
const batch = processing === null
|
|
2833
|
-
? null
|
|
2834
|
-
: asObject(processing.batch, `WorkMailbox ${key} processing batch`);
|
|
2835
|
-
const lifecycleReservation = (target.kind === "role-runtime"
|
|
2836
|
-
|| target.kind === "global-role-runtime")
|
|
2837
|
-
&& Array.isArray(batch?.reasons)
|
|
2838
|
-
&& batch.reasons.includes("runtime-launch-reserved");
|
|
2839
|
-
const nextProcessing = processing === null
|
|
2840
|
-
? null
|
|
2841
|
-
: lifecycleReservation
|
|
2842
|
-
? (() => {
|
|
2843
|
-
const { executionRef: _run, ...retained } = processing;
|
|
2844
|
-
void _run;
|
|
2845
|
-
return retained;
|
|
2846
|
-
})()
|
|
2847
|
-
: processing;
|
|
2848
|
-
nextMailboxes[key] = {
|
|
2849
|
-
...mailbox,
|
|
2850
|
-
schemaVersion: HOST_OWNED_WORK_MAILBOX_TO_VERSION,
|
|
2851
|
-
processing: nextProcessing
|
|
2852
|
-
};
|
|
2853
|
-
}
|
|
2854
|
-
return {
|
|
2855
|
-
schemaManifest,
|
|
2856
|
-
state: { ...snapshot.state, mailboxes: nextMailboxes }
|
|
2857
|
-
};
|
|
2858
|
-
}
|
|
2859
|
-
/**
|
|
2860
|
-
* v1 used `processing` for both internal/initial dispatch ownership and later
|
|
2861
|
-
* model input. A crashed later input may already have reached the provider;
|
|
2862
|
-
* migrate it to an explicit delivery-unknown fence instead of making it
|
|
2863
|
-
* eligible for a blind resend. Other processing batches retain their old
|
|
2864
|
-
* controller ownership semantics.
|
|
2865
|
-
*/
|
|
2866
|
-
function migrateMailboxContinuationDeliveryV1(snapshot, mailbox, processing, mailboxKey) {
|
|
2867
|
-
const batchId = String(processing.batchId ?? "");
|
|
2868
|
-
if (!batchId.startsWith("agent-input:"))
|
|
2869
|
-
return null;
|
|
2870
|
-
const target = asObject(mailbox.target, `WorkMailbox ${mailboxKey} target`);
|
|
2871
|
-
if (target.kind !== "role") {
|
|
2872
|
-
throw new Error(`WorkMailbox ${mailboxKey} continuation input requires a Role target.`);
|
|
2873
|
-
}
|
|
2874
|
-
const executionRef = asObject(processing.executionRef, `WorkMailbox ${mailboxKey} continuation executionRef`);
|
|
2875
|
-
if (executionRef.type !== "run") {
|
|
2876
|
-
throw new Error(`WorkMailbox ${mailboxKey} continuation input requires a Run reference.`);
|
|
2877
|
-
}
|
|
2878
|
-
const taskId = String(target.taskId);
|
|
2879
|
-
const roleName = String(target.roleName);
|
|
2880
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2881
|
-
const aggregate = asObject(tasks[taskId], `Task aggregate ${taskId}`);
|
|
2882
|
-
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
2883
|
-
const set = asObject(sets[roleName], `Task Role session set ${taskId}/${roleName}`);
|
|
2884
|
-
const sessions = asObject(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
2885
|
-
const active = asObject(sessions[String(set.activeAgentId)], `Task Role active Session ${taskId}/${roleName}`);
|
|
2886
|
-
const activationId = String(active.launchId ?? asObject(set.inFlight, `Task Role in-flight Run ${taskId}/${roleName}`).receiptId);
|
|
2887
|
-
return {
|
|
2888
|
-
attemptId: batchId,
|
|
2889
|
-
lane: "normal",
|
|
2890
|
-
mode: "followup",
|
|
2891
|
-
batch: processing.batch,
|
|
2892
|
-
owner: processing.owner,
|
|
2893
|
-
status: "delivery-unknown",
|
|
2894
|
-
startedAt: processing.startedAt,
|
|
2895
|
-
pushedAt: processing.startedAt,
|
|
2896
|
-
unknownReason: "migrated-unconfirmed-provider-acceptance",
|
|
2897
|
-
executionRef,
|
|
2898
|
-
providerFence: {
|
|
2899
|
-
conversationId: String(active.nativeSessionId),
|
|
2900
|
-
activationId
|
|
2901
|
-
}
|
|
2902
|
-
};
|
|
2903
|
-
}
|
|
2904
|
-
function migrateMailboxProcessingV1(value, mailboxKey) {
|
|
2905
|
-
const processing = asObject(value, `WorkMailbox ${mailboxKey} processing`);
|
|
2906
|
-
return {
|
|
2907
|
-
...processing,
|
|
2908
|
-
lane: "normal",
|
|
2909
|
-
batch: migrateMailboxBatchV1(processing.batch, mailboxKey, "processing")
|
|
2910
|
-
};
|
|
2911
|
-
}
|
|
2912
|
-
function migrateMailboxBatchV1(value, mailboxKey, location) {
|
|
2913
|
-
const batch = asObject(value, `WorkMailbox ${mailboxKey} ${location} batch`);
|
|
2914
|
-
const from = batch.fromSequence;
|
|
2915
|
-
const to = batch.toSequence;
|
|
2916
|
-
if (!Number.isInteger(from) || !Number.isInteger(to)) {
|
|
2917
|
-
throw new Error(`WorkMailbox ${mailboxKey} ${location} sequence range is invalid.`);
|
|
2918
|
-
}
|
|
2919
|
-
return {
|
|
2920
|
-
...batch,
|
|
2921
|
-
sources: ["migration-v1"],
|
|
2922
|
-
dedupeKeys: [`mailbox-v1:${mailboxKey}:${String(from)}-${String(to)}`],
|
|
2923
|
-
deliveryModes: ["followup"]
|
|
2924
|
-
};
|
|
2925
|
-
}
|
|
2926
|
-
function taskRoleSessionSetV5Step() {
|
|
2927
|
-
return {
|
|
2928
|
-
axis: "record",
|
|
2929
|
-
recordKind: "taskRoleSessionSet",
|
|
2930
|
-
fromVersion: TASK_ROLE_SESSION_SET_FROM_VERSION,
|
|
2931
|
-
toVersion: TASK_ROLE_SESSION_SET_TO_VERSION,
|
|
2932
|
-
preconditions: requireTaskRoleSessionSetV4Family,
|
|
2933
|
-
transform: migrateTaskRoleSessionSetV4ToV5,
|
|
2934
|
-
declaredEffects: []
|
|
2935
|
-
};
|
|
2936
|
-
}
|
|
2937
|
-
/**
|
|
2938
|
-
* v6 is a deliberate runtime cutover, not an emulation layer. Existing Task,
|
|
2939
|
-
* Run, Conversation, and Session identities remain as audit evidence, while
|
|
2940
|
-
* every pre-v6 managed process is terminalized locally. A new Agent Host must
|
|
2941
|
-
* establish structured Provider evidence before any further write.
|
|
2942
|
-
*/
|
|
2943
|
-
function structuredProviderSessionSetV6Step() {
|
|
2944
|
-
return {
|
|
2945
|
-
axis: "record",
|
|
2946
|
-
recordKind: "taskRoleSessionSet",
|
|
2947
|
-
fromVersion: STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION,
|
|
2948
|
-
toVersion: STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION,
|
|
2949
|
-
preconditions: requireTaskRoleSessionSetV5Family,
|
|
2950
|
-
transform: migrateTaskRoleSessionSetV5ToV6,
|
|
2951
|
-
declaredEffects: []
|
|
2952
|
-
};
|
|
2953
|
-
}
|
|
2954
|
-
/**
|
|
2955
|
-
* A Provider binding describes one reusable Conversation. Run correlation now
|
|
2956
|
-
* belongs to the concrete Provider Turn instead of making the Conversation a
|
|
2957
|
-
* second writable projection of the current AgentRun.
|
|
2958
|
-
*/
|
|
2959
|
-
function agentFirstSessionSetV7Step() {
|
|
2960
|
-
return {
|
|
2961
|
-
axis: "record",
|
|
2962
|
-
recordKind: "taskRoleSessionSet",
|
|
2963
|
-
fromVersion: AGENT_FIRST_SESSION_SET_FROM_VERSION,
|
|
2964
|
-
toVersion: AGENT_FIRST_SESSION_SET_TO_VERSION,
|
|
2965
|
-
preconditions: requireTaskRoleSessionSetV6Family,
|
|
2966
|
-
transform: migrateTaskRoleSessionSetV6ToV7,
|
|
2967
|
-
declaredEffects: []
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
function requireTaskRoleSessionSetV6Family(snapshot) {
|
|
2971
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2972
|
-
if (versions.taskRoleSessionSet !== AGENT_FIRST_SESSION_SET_FROM_VERSION) {
|
|
2973
|
-
throw new Error(`Record taskRoleSessionSet migration requires manifest version ${AGENT_FIRST_SESSION_SET_FROM_VERSION}.`);
|
|
2974
|
-
}
|
|
2975
|
-
if (snapshot.state === null)
|
|
2976
|
-
return;
|
|
2977
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
2978
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
2979
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
2980
|
-
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
2981
|
-
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
2982
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
2983
|
-
if (set.schemaVersion !== AGENT_FIRST_SESSION_SET_FROM_VERSION) {
|
|
2984
|
-
throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${AGENT_FIRST_SESSION_SET_FROM_VERSION}.`);
|
|
2985
|
-
}
|
|
2986
|
-
if (set.providerBinding !== null) {
|
|
2987
|
-
const binding = asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`);
|
|
2988
|
-
if (binding.schemaVersion !== 2 || typeof binding.runId !== "string") {
|
|
2989
|
-
throw new Error(`Provider Binding ${taskId}/${roleName} must use schemaVersion 2.`);
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
function migrateTaskRoleSessionSetV6ToV7(snapshot) {
|
|
2996
|
-
requireTaskRoleSessionSetV6Family(snapshot);
|
|
2997
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
2998
|
-
const schemaManifest = {
|
|
2999
|
-
...snapshot.schemaManifest,
|
|
3000
|
-
recordVersions: {
|
|
3001
|
-
...versions,
|
|
3002
|
-
taskRoleSessionSet: AGENT_FIRST_SESSION_SET_TO_VERSION
|
|
3003
|
-
}
|
|
3004
|
-
};
|
|
3005
|
-
if (snapshot.state === null)
|
|
3006
|
-
return { schemaManifest, state: null };
|
|
3007
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3008
|
-
const nextTasks = {};
|
|
3009
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3010
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3011
|
-
const rawSets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3012
|
-
const nextSets = {};
|
|
3013
|
-
for (const [roleName, rawSet] of Object.entries(rawSets)) {
|
|
3014
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3015
|
-
let providerBinding = null;
|
|
3016
|
-
if (set.providerBinding !== null) {
|
|
3017
|
-
const binding = asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`);
|
|
3018
|
-
const { runId, ...conversation } = binding;
|
|
3019
|
-
providerBinding = {
|
|
3020
|
-
...conversation,
|
|
3021
|
-
schemaVersion: 3,
|
|
3022
|
-
turn: binding.turn === null
|
|
3023
|
-
? null
|
|
3024
|
-
: { ...asObject(binding.turn, `Provider Turn ${taskId}/${roleName}`), runId }
|
|
3025
|
-
};
|
|
3026
|
-
}
|
|
3027
|
-
nextSets[roleName] = {
|
|
3028
|
-
...set,
|
|
3029
|
-
schemaVersion: AGENT_FIRST_SESSION_SET_TO_VERSION,
|
|
3030
|
-
providerBinding
|
|
3031
|
-
};
|
|
3032
|
-
}
|
|
3033
|
-
nextTasks[taskId] = { ...aggregate, roleSessionSets: nextSets };
|
|
3034
|
-
}
|
|
3035
|
-
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
3036
|
-
}
|
|
3037
|
-
/** Session stores existence only; Host readiness and Turn activity are runtime facts. */
|
|
3038
|
-
function unifiedTaskSessionSetStep() {
|
|
3039
|
-
return {
|
|
3040
|
-
axis: "record",
|
|
3041
|
-
recordKind: "taskRoleSessionSet",
|
|
3042
|
-
fromVersion: UNIFIED_TASK_SESSION_SET_FROM_VERSION,
|
|
3043
|
-
toVersion: UNIFIED_TASK_SESSION_SET_TO_VERSION,
|
|
3044
|
-
preconditions: requireTaskRoleSessionSetV7Family,
|
|
3045
|
-
transform: migrateTaskRoleSessionSetV7ToV8,
|
|
3046
|
-
declaredEffects: []
|
|
3047
|
-
};
|
|
3048
|
-
}
|
|
3049
|
-
function requireTaskRoleSessionSetV7Family(snapshot) {
|
|
3050
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3051
|
-
if (versions.taskRoleSessionSet !== UNIFIED_TASK_SESSION_SET_FROM_VERSION) {
|
|
3052
|
-
throw new Error(`Record taskRoleSessionSet migration requires manifest version ${UNIFIED_TASK_SESSION_SET_FROM_VERSION}.`);
|
|
3053
|
-
}
|
|
3054
|
-
if (snapshot.state === null)
|
|
3055
|
-
return;
|
|
3056
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3057
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3058
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3059
|
-
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3060
|
-
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3061
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3062
|
-
if (set.schemaVersion !== UNIFIED_TASK_SESSION_SET_FROM_VERSION) {
|
|
3063
|
-
throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${UNIFIED_TASK_SESSION_SET_FROM_VERSION}.`);
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
|
-
function migrateTaskRoleSessionSetV7ToV8(snapshot) {
|
|
3069
|
-
requireTaskRoleSessionSetV7Family(snapshot);
|
|
3070
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3071
|
-
const schemaManifest = {
|
|
3072
|
-
...snapshot.schemaManifest,
|
|
3073
|
-
recordVersions: {
|
|
3074
|
-
...versions,
|
|
3075
|
-
taskRoleSessionSet: UNIFIED_TASK_SESSION_SET_TO_VERSION
|
|
3076
|
-
}
|
|
3077
|
-
};
|
|
3078
|
-
if (snapshot.state === null)
|
|
3079
|
-
return { schemaManifest, state: null };
|
|
3080
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3081
|
-
const nextTasks = {};
|
|
3082
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3083
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3084
|
-
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3085
|
-
const nextSets = {};
|
|
3086
|
-
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3087
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3088
|
-
const sessions = migrateRoleAgentSessionMap(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
3089
|
-
const history = set.history === undefined
|
|
3090
|
-
? undefined
|
|
3091
|
-
: migrateRoleAgentSessionArray(set.history, `Task Role session history ${taskId}/${roleName}`);
|
|
3092
|
-
nextSets[roleName] = {
|
|
3093
|
-
...set,
|
|
3094
|
-
schemaVersion: UNIFIED_TASK_SESSION_SET_TO_VERSION,
|
|
3095
|
-
sessions,
|
|
3096
|
-
...(history === undefined ? {} : { history })
|
|
3097
|
-
};
|
|
3098
|
-
}
|
|
3099
|
-
nextTasks[taskId] = { ...aggregate, roleSessionSets: nextSets };
|
|
3100
|
-
}
|
|
3101
|
-
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
3102
|
-
}
|
|
3103
|
-
function unifiedGlobalSessionSetStep() {
|
|
3104
|
-
return {
|
|
3105
|
-
axis: "record",
|
|
3106
|
-
recordKind: "globalRoleSessionSet",
|
|
3107
|
-
fromVersion: UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION,
|
|
3108
|
-
toVersion: UNIFIED_GLOBAL_SESSION_SET_TO_VERSION,
|
|
3109
|
-
preconditions: requireGlobalRoleSessionSetV3Family,
|
|
3110
|
-
transform: migrateGlobalRoleSessionSetV3ToV4,
|
|
3111
|
-
declaredEffects: []
|
|
3112
|
-
};
|
|
3113
|
-
}
|
|
3114
|
-
function requireGlobalRoleSessionSetV3Family(snapshot) {
|
|
3115
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3116
|
-
if (versions.globalRoleSessionSet !== UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION) {
|
|
3117
|
-
throw new Error(`Record globalRoleSessionSet migration requires manifest version ${UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION}.`);
|
|
3118
|
-
}
|
|
3119
|
-
if (snapshot.state === null)
|
|
3120
|
-
return;
|
|
3121
|
-
const sets = asObject(snapshot.state.globalRoleSessionSets, "Global Role session sets");
|
|
3122
|
-
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3123
|
-
const set = asObject(rawSet, `Global Role session set ${roleName}`);
|
|
3124
|
-
if (set.schemaVersion !== UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION) {
|
|
3125
|
-
throw new Error(`Global Role session set ${roleName} must use schemaVersion ${UNIFIED_GLOBAL_SESSION_SET_FROM_VERSION}.`);
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
}
|
|
3129
|
-
function migrateGlobalRoleSessionSetV3ToV4(snapshot) {
|
|
3130
|
-
requireGlobalRoleSessionSetV3Family(snapshot);
|
|
3131
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3132
|
-
const schemaManifest = {
|
|
3133
|
-
...snapshot.schemaManifest,
|
|
3134
|
-
recordVersions: {
|
|
3135
|
-
...versions,
|
|
3136
|
-
globalRoleSessionSet: UNIFIED_GLOBAL_SESSION_SET_TO_VERSION
|
|
3137
|
-
}
|
|
3138
|
-
};
|
|
3139
|
-
if (snapshot.state === null)
|
|
3140
|
-
return { schemaManifest, state: null };
|
|
3141
|
-
const sets = asObject(snapshot.state.globalRoleSessionSets, "Global Role session sets");
|
|
3142
|
-
const nextSets = {};
|
|
3143
|
-
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3144
|
-
const set = asObject(rawSet, `Global Role session set ${roleName}`);
|
|
3145
|
-
const history = set.history === undefined
|
|
3146
|
-
? undefined
|
|
3147
|
-
: migrateRoleAgentSessionMap(set.history, `Global Role session history ${roleName}`);
|
|
3148
|
-
nextSets[roleName] = {
|
|
3149
|
-
...set,
|
|
3150
|
-
schemaVersion: UNIFIED_GLOBAL_SESSION_SET_TO_VERSION,
|
|
3151
|
-
sessions: migrateRoleAgentSessionMap(set.sessions, `Global Role sessions ${roleName}`),
|
|
3152
|
-
...(history === undefined ? {} : { history })
|
|
3153
|
-
};
|
|
3154
|
-
}
|
|
3155
|
-
return {
|
|
3156
|
-
schemaManifest,
|
|
3157
|
-
state: { ...snapshot.state, globalRoleSessionSets: nextSets }
|
|
3158
|
-
};
|
|
3159
|
-
}
|
|
3160
|
-
function migrateRoleAgentSessionMap(value, label) {
|
|
3161
|
-
const sessions = asObject(value, label);
|
|
3162
|
-
return Object.fromEntries(Object.entries(sessions).map(([key, session]) => [
|
|
3163
|
-
key,
|
|
3164
|
-
migrateRoleAgentSessionV3(session, `${label}/${key}`)
|
|
3165
|
-
]));
|
|
3166
|
-
}
|
|
3167
|
-
function migrateRoleAgentSessionArray(value, label) {
|
|
3168
|
-
if (!Array.isArray(value))
|
|
3169
|
-
throw new Error(`${label} must be an array.`);
|
|
3170
|
-
return value.map((session, index) => migrateRoleAgentSessionV3(session, `${label}/${String(index)}`));
|
|
3171
|
-
}
|
|
3172
|
-
function migrateRoleAgentSessionV3(value, label) {
|
|
3173
|
-
const session = asObject(value, label);
|
|
3174
|
-
if (session.schemaVersion !== 3) {
|
|
3175
|
-
throw new Error(`${label} must use schemaVersion 3.`);
|
|
3176
|
-
}
|
|
3177
|
-
const status = session.status;
|
|
3178
|
-
if (status !== "reserved"
|
|
3179
|
-
&& status !== "ready"
|
|
3180
|
-
&& status !== "running"
|
|
3181
|
-
&& status !== "stopped"
|
|
3182
|
-
&& status !== "broken") {
|
|
3183
|
-
throw new Error(`${label} has an invalid Session status.`);
|
|
3184
|
-
}
|
|
3185
|
-
const { status: _status, ...retained } = session;
|
|
3186
|
-
void _status;
|
|
3187
|
-
return {
|
|
3188
|
-
...retained,
|
|
3189
|
-
schemaVersion: 4,
|
|
3190
|
-
status: status === "stopped" || status === "broken" ? "ended" : "active",
|
|
3191
|
-
...(status === "stopped"
|
|
3192
|
-
? { endReason: "stopped" }
|
|
3193
|
-
: status === "broken" ? { endReason: "failed" } : {})
|
|
3194
|
-
};
|
|
3195
|
-
}
|
|
3196
|
-
function requireTaskRoleSessionSetV5Family(snapshot) {
|
|
3197
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3198
|
-
if (versions.taskRoleSessionSet !== STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION) {
|
|
3199
|
-
throw new Error(`Record taskRoleSessionSet migration requires manifest version ${STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION}.`);
|
|
3200
|
-
}
|
|
3201
|
-
if (snapshot.state === null)
|
|
3202
|
-
return;
|
|
3203
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3204
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3205
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3206
|
-
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3207
|
-
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3208
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3209
|
-
if (set.schemaVersion !== STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION) {
|
|
3210
|
-
throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${STRUCTURED_PROVIDER_SESSION_SET_FROM_VERSION}.`);
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
}
|
|
3215
|
-
function migrateTaskRoleSessionSetV5ToV6(snapshot) {
|
|
3216
|
-
requireTaskRoleSessionSetV5Family(snapshot);
|
|
3217
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3218
|
-
const schemaManifest = {
|
|
3219
|
-
...snapshot.schemaManifest,
|
|
3220
|
-
recordVersions: {
|
|
3221
|
-
...versions,
|
|
3222
|
-
taskRoleSessionSet: STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION
|
|
3223
|
-
}
|
|
3224
|
-
};
|
|
3225
|
-
if (snapshot.state === null)
|
|
3226
|
-
return { schemaManifest, state: null };
|
|
3227
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3228
|
-
const nextTasks = {};
|
|
3229
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3230
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3231
|
-
const rawSets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3232
|
-
const nextSets = {};
|
|
3233
|
-
for (const [roleName, rawSet] of Object.entries(rawSets)) {
|
|
3234
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3235
|
-
const invalidatedAt = String(set.updatedAt);
|
|
3236
|
-
const sessions = asObject(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
3237
|
-
const nextSessions = Object.fromEntries(Object.entries(sessions).map(([agentId, rawSession]) => {
|
|
3238
|
-
const session = asObject(rawSession, `Task Role Session ${taskId}/${roleName}/${agentId}`);
|
|
3239
|
-
return [agentId, session.status === "stopped" || session.status === "broken"
|
|
3240
|
-
? session
|
|
3241
|
-
: { ...session, status: "broken", updatedAt: invalidatedAt }];
|
|
3242
|
-
}));
|
|
3243
|
-
nextSets[roleName] = {
|
|
3244
|
-
...set,
|
|
3245
|
-
schemaVersion: STRUCTURED_PROVIDER_SESSION_SET_TO_VERSION,
|
|
3246
|
-
sessions: nextSessions,
|
|
3247
|
-
providerBinding: set.providerBinding === null
|
|
3248
|
-
? null
|
|
3249
|
-
: invalidateLegacyProviderBinding(asObject(set.providerBinding, `Provider Binding ${taskId}/${roleName}`), invalidatedAt)
|
|
3250
|
-
};
|
|
3251
|
-
}
|
|
3252
|
-
nextTasks[taskId] = { ...aggregate, roleSessionSets: nextSets };
|
|
3253
|
-
}
|
|
3254
|
-
return { schemaManifest, state: { ...snapshot.state, tasks: nextTasks } };
|
|
3255
|
-
}
|
|
3256
|
-
function invalidateLegacyProviderBinding(binding, invalidatedAt) {
|
|
3257
|
-
if (binding.schemaVersion !== 1) {
|
|
3258
|
-
throw new Error("Pre-v6 Provider Runtime Binding must use schemaVersion 1.");
|
|
3259
|
-
}
|
|
3260
|
-
const activations = Array.isArray(binding.activations) ? binding.activations : [];
|
|
3261
|
-
return {
|
|
3262
|
-
...binding,
|
|
3263
|
-
schemaVersion: 2,
|
|
3264
|
-
conversations: (Array.isArray(binding.conversations) ? binding.conversations : []).map((rawConversation) => {
|
|
3265
|
-
const conversation = asObject(rawConversation, "Provider Conversation");
|
|
3266
|
-
return conversation.status === "current"
|
|
3267
|
-
? { ...conversation, recoverability: "unknown" }
|
|
3268
|
-
: conversation;
|
|
3269
|
-
}),
|
|
3270
|
-
activations: activations.map((rawActivation) => {
|
|
3271
|
-
const activation = asObject(rawActivation, "Provider Activation");
|
|
3272
|
-
const { writerLease: _removed, ...withoutLease } = activation;
|
|
3273
|
-
void _removed;
|
|
3274
|
-
return activation.status === "active"
|
|
3275
|
-
? {
|
|
3276
|
-
...withoutLease,
|
|
3277
|
-
status: "failed",
|
|
3278
|
-
endedAt: invalidatedAt,
|
|
3279
|
-
terminalReason: "legacy-managed-runtime-invalidated"
|
|
3280
|
-
}
|
|
3281
|
-
: withoutLease;
|
|
3282
|
-
}),
|
|
3283
|
-
authority: { epoch: 1, owner: "none", changedAt: invalidatedAt },
|
|
3284
|
-
turn: null
|
|
3285
|
-
};
|
|
3286
|
-
}
|
|
3287
|
-
function requireTaskRoleSessionSetV4Family(snapshot) {
|
|
3288
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3289
|
-
if (versions.taskRoleSessionSet !== TASK_ROLE_SESSION_SET_FROM_VERSION) {
|
|
3290
|
-
throw new Error(`Record taskRoleSessionSet migration requires manifest version ${TASK_ROLE_SESSION_SET_FROM_VERSION}.`);
|
|
3291
|
-
}
|
|
3292
|
-
if (snapshot.state === null)
|
|
3293
|
-
return;
|
|
3294
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3295
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3296
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3297
|
-
const sets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3298
|
-
for (const [roleName, rawSet] of Object.entries(sets)) {
|
|
3299
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3300
|
-
if (set.schemaVersion !== TASK_ROLE_SESSION_SET_FROM_VERSION) {
|
|
3301
|
-
throw new Error(`Task Role session set ${taskId}/${roleName} must use schemaVersion ${TASK_ROLE_SESSION_SET_FROM_VERSION}.`);
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
}
|
|
3305
|
-
}
|
|
3306
|
-
function migrateTaskRoleSessionSetV4ToV5(snapshot) {
|
|
3307
|
-
requireTaskRoleSessionSetV4Family(snapshot);
|
|
3308
|
-
const versions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3309
|
-
const schemaManifest = {
|
|
3310
|
-
...snapshot.schemaManifest,
|
|
3311
|
-
recordVersions: {
|
|
3312
|
-
...versions,
|
|
3313
|
-
taskRoleSessionSet: TASK_ROLE_SESSION_SET_TO_VERSION
|
|
3314
|
-
}
|
|
3315
|
-
};
|
|
3316
|
-
if (snapshot.state === null)
|
|
3317
|
-
return { schemaManifest, state: null };
|
|
3318
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3319
|
-
const nextTasks = {};
|
|
3320
|
-
for (const [taskId, rawAggregate] of Object.entries(tasks)) {
|
|
3321
|
-
const aggregate = asObject(rawAggregate, `Task aggregate ${taskId}`);
|
|
3322
|
-
const rawSets = asObject(aggregate.roleSessionSets, `Task Role session sets ${taskId}`);
|
|
3323
|
-
const nextSets = {};
|
|
3324
|
-
let events = { ...asObject(aggregate.events, `Task events ${taskId}`) };
|
|
3325
|
-
const highWater = { ...asObject(aggregate.idHighWaterMarks, `Task id high-water marks ${taskId}`) };
|
|
3326
|
-
let eventHighWater = Number(highWater.event ?? 0);
|
|
3327
|
-
if (!Number.isSafeInteger(eventHighWater) || eventHighWater < 0) {
|
|
3328
|
-
throw new Error(`Task event high-water mark is invalid: ${taskId}.`);
|
|
3329
|
-
}
|
|
3330
|
-
for (const [roleName, rawSet] of Object.entries(rawSets)) {
|
|
3331
|
-
const set = asObject(rawSet, `Task Role session set ${taskId}/${roleName}`);
|
|
3332
|
-
const sessions = asObject(set.sessions, `Task Role sessions ${taskId}/${roleName}`);
|
|
3333
|
-
const inFlight = set.inFlight === null
|
|
3334
|
-
? null
|
|
3335
|
-
: asObject(set.inFlight, `Task Role in-flight Run ${taskId}/${roleName}`);
|
|
3336
|
-
const pending = set.pendingTurnCompletion === null
|
|
3337
|
-
? null
|
|
3338
|
-
: asObject(set.pendingTurnCompletion, `Task Role Turn completion ${taskId}/${roleName}`);
|
|
3339
|
-
const activeAgentId = String(set.activeAgentId ?? "");
|
|
3340
|
-
const activeSession = sessions[activeAgentId] === undefined
|
|
3341
|
-
? null
|
|
3342
|
-
: asObject(sessions[activeAgentId], `Task Role active Session ${taskId}/${roleName}`);
|
|
3343
|
-
if (inFlight !== null && activeSession === null) {
|
|
3344
|
-
throw new Error(`Task Role in-flight Run has no active Session: ${taskId}/${roleName}.`);
|
|
3345
|
-
}
|
|
3346
|
-
const nextSessions = { ...sessions };
|
|
3347
|
-
if (pending !== null) {
|
|
3348
|
-
if (activeSession === null
|
|
3349
|
-
|| pending.agentId !== activeAgentId
|
|
3350
|
-
|| pending.runId !== inFlight?.runId
|
|
3351
|
-
|| pending.nativeSessionId !== activeSession.nativeSessionId) {
|
|
3352
|
-
throw new Error(`Task Role Turn completion identity is inconsistent: ${taskId}/${roleName}.`);
|
|
3353
|
-
}
|
|
3354
|
-
const recent = Array.isArray(activeSession.recentCompletedTurnIds)
|
|
3355
|
-
? activeSession.recentCompletedTurnIds.map(String)
|
|
3356
|
-
: [];
|
|
3357
|
-
const turnId = String(pending.turnId);
|
|
3358
|
-
nextSessions[activeAgentId] = {
|
|
3359
|
-
...activeSession,
|
|
3360
|
-
recentCompletedTurnIds: recent.includes(turnId) ? recent : [...recent, turnId],
|
|
3361
|
-
updatedAt: String(pending.observedAt)
|
|
3362
|
-
};
|
|
3363
|
-
eventHighWater += 1;
|
|
3364
|
-
const eventId = `event-${eventHighWater}`;
|
|
3365
|
-
const driverId = migratedDriverId(String(activeSession.adapterId));
|
|
3366
|
-
const launchId = String(activeSession.launchId ?? inFlight?.receiptId ?? `legacy-${eventId}`);
|
|
3367
|
-
const observation = {
|
|
3368
|
-
schemaVersion: 2,
|
|
3369
|
-
eventId: `migrated-turn-completed:${taskId}:${roleName}:${turnId}`,
|
|
3370
|
-
semanticKey: [
|
|
3371
|
-
"terminal",
|
|
3372
|
-
driverId,
|
|
3373
|
-
activeAgentId,
|
|
3374
|
-
String(activeSession.nativeSessionId),
|
|
3375
|
-
launchId,
|
|
3376
|
-
"none",
|
|
3377
|
-
"none",
|
|
3378
|
-
turnId,
|
|
3379
|
-
"turn.completed",
|
|
3380
|
-
"terminal",
|
|
3381
|
-
"none",
|
|
3382
|
-
"turn-terminal"
|
|
3383
|
-
].join(":"),
|
|
3384
|
-
kind: "turn.completed",
|
|
3385
|
-
authority: "controller",
|
|
3386
|
-
receivedAt: String(pending.observedAt),
|
|
3387
|
-
observedAt: String(pending.observedAt),
|
|
3388
|
-
fence: {
|
|
3389
|
-
taskId,
|
|
3390
|
-
roleName,
|
|
3391
|
-
runId: String(pending.runId),
|
|
3392
|
-
agentId: activeAgentId,
|
|
3393
|
-
driverId,
|
|
3394
|
-
launchId,
|
|
3395
|
-
sessionGenerationId: launchId,
|
|
3396
|
-
conversationId: String(activeSession.nativeSessionId),
|
|
3397
|
-
activationId: launchId,
|
|
3398
|
-
nativeSessionId: String(activeSession.nativeSessionId),
|
|
3399
|
-
nativeTurnId: turnId
|
|
3400
|
-
},
|
|
3401
|
-
payload: { summary: String(pending.summary) }
|
|
3402
|
-
};
|
|
3403
|
-
events[eventId] = {
|
|
3404
|
-
schemaVersion: 2,
|
|
3405
|
-
id: eventId,
|
|
3406
|
-
taskId,
|
|
3407
|
-
type: "runtime.observation",
|
|
3408
|
-
payload: {
|
|
3409
|
-
eventId: observation.eventId,
|
|
3410
|
-
roleName,
|
|
3411
|
-
agentId: activeAgentId,
|
|
3412
|
-
driverId,
|
|
3413
|
-
launchId,
|
|
3414
|
-
taskId,
|
|
3415
|
-
runId: String(pending.runId),
|
|
3416
|
-
nativeSessionId: String(activeSession.nativeSessionId),
|
|
3417
|
-
kind: "turn.completed",
|
|
3418
|
-
receivedAt: String(pending.observedAt),
|
|
3419
|
-
observation: JSON.stringify(observation)
|
|
3420
|
-
},
|
|
3421
|
-
createdAt: String(pending.observedAt)
|
|
3422
|
-
};
|
|
3423
|
-
}
|
|
3424
|
-
const providerBinding = inFlight === null || activeSession === null
|
|
3425
|
-
? null
|
|
3426
|
-
: migratedProviderBinding(activeSession, inFlight);
|
|
3427
|
-
const { pendingTurnCompletion: _removed, ...withoutPending } = set;
|
|
3428
|
-
void _removed;
|
|
3429
|
-
nextSets[roleName] = {
|
|
3430
|
-
...withoutPending,
|
|
3431
|
-
schemaVersion: TASK_ROLE_SESSION_SET_TO_VERSION,
|
|
3432
|
-
sessions: nextSessions,
|
|
3433
|
-
providerBinding
|
|
3434
|
-
};
|
|
3435
|
-
}
|
|
3436
|
-
highWater.event = eventHighWater;
|
|
3437
|
-
nextTasks[taskId] = {
|
|
3438
|
-
...aggregate,
|
|
3439
|
-
roleSessionSets: nextSets,
|
|
3440
|
-
events,
|
|
3441
|
-
idHighWaterMarks: highWater
|
|
3442
|
-
};
|
|
3443
|
-
}
|
|
3444
|
-
return {
|
|
3445
|
-
schemaManifest,
|
|
3446
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
3447
|
-
};
|
|
3448
|
-
}
|
|
3449
|
-
function migratedProviderBinding(session, inFlight) {
|
|
3450
|
-
const conversationId = String(session.nativeSessionId);
|
|
3451
|
-
const activationId = String(session.launchId ?? inFlight.receiptId);
|
|
3452
|
-
const startedAt = String(inFlight.preparedAt);
|
|
3453
|
-
const live = session.status !== "stopped" && session.status !== "broken";
|
|
3454
|
-
return {
|
|
3455
|
-
schemaVersion: 1,
|
|
3456
|
-
providerNamespace: migratedDriverId(String(session.adapterId)),
|
|
3457
|
-
accountScope: String(session.agentId),
|
|
3458
|
-
runId: String(inFlight.runId),
|
|
3459
|
-
currentConversationEpoch: 1,
|
|
3460
|
-
conversations: [{
|
|
3461
|
-
conversationId,
|
|
3462
|
-
epoch: 1,
|
|
3463
|
-
status: "current",
|
|
3464
|
-
recoverability: "unknown",
|
|
3465
|
-
createdAt: String(session.createdAt ?? startedAt)
|
|
3466
|
-
}],
|
|
3467
|
-
activations: [{
|
|
3468
|
-
activationId,
|
|
3469
|
-
conversationId,
|
|
3470
|
-
generation: 1,
|
|
3471
|
-
status: live ? "active" : session.status === "broken" ? "failed" : "ended",
|
|
3472
|
-
writerLease: live,
|
|
3473
|
-
startedAt,
|
|
3474
|
-
...(live ? {} : { endedAt: String(session.updatedAt) })
|
|
3475
|
-
}]
|
|
3476
|
-
};
|
|
3477
|
-
}
|
|
3478
|
-
function migratedDriverId(adapterId) {
|
|
3479
|
-
if (adapterId === "claude")
|
|
3480
|
-
return "anthropic/claude-code";
|
|
3481
|
-
if (adapterId === "codex")
|
|
3482
|
-
return "openai/codex";
|
|
3483
|
-
return adapterId.includes("/") ? adapterId : `legacy/${adapterId}`;
|
|
3484
|
-
}
|
|
3485
|
-
/**
|
|
3486
|
-
* Project ownership is a new required field. Every pre-v3 Project is a
|
|
3487
|
-
* user-registered checkout, so the historical binding is `external`; a managed
|
|
3488
|
-
* Home-owned repository is only ever created explicitly by the new binding
|
|
3489
|
-
* path. The version transition is durable and centralized.
|
|
3490
|
-
*/
|
|
3491
|
-
function projectOwnershipStep() {
|
|
3492
|
-
return {
|
|
3493
|
-
axis: "record",
|
|
3494
|
-
recordKind: "project",
|
|
3495
|
-
fromVersion: PROJECT_FROM_VERSION,
|
|
3496
|
-
toVersion: PROJECT_TO_VERSION,
|
|
3497
|
-
preconditions: requireProjectV2Family,
|
|
3498
|
-
transform: migrateProjectV2ToV3,
|
|
3499
|
-
declaredEffects: []
|
|
3500
|
-
};
|
|
3501
|
-
}
|
|
3502
|
-
function requireProjectV2Family(snapshot) {
|
|
3503
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3504
|
-
if (manifestVersions.project !== PROJECT_FROM_VERSION) {
|
|
3505
|
-
throw new Error(`Record project migration requires manifest version ${PROJECT_FROM_VERSION}.`);
|
|
3506
|
-
}
|
|
3507
|
-
if (snapshot.state === null)
|
|
3508
|
-
return;
|
|
3509
|
-
const projects = snapshot.state.projects;
|
|
3510
|
-
if (projects === undefined)
|
|
3511
|
-
return;
|
|
3512
|
-
const map = asObject(projects, "project map");
|
|
3513
|
-
for (const [projectId, rawProject] of Object.entries(map)) {
|
|
3514
|
-
const project = asObject(rawProject, `Project ${projectId}`);
|
|
3515
|
-
if (project.schemaVersion !== PROJECT_FROM_VERSION) {
|
|
3516
|
-
throw new Error(`Project ${projectId} must use schemaVersion ${PROJECT_FROM_VERSION}.`);
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
function migrateProjectV2ToV3(snapshot) {
|
|
3521
|
-
requireProjectV2Family(snapshot);
|
|
3522
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3523
|
-
const schemaManifest = {
|
|
3524
|
-
...snapshot.schemaManifest,
|
|
3525
|
-
recordVersions: { ...manifestVersions, project: PROJECT_TO_VERSION }
|
|
3526
|
-
};
|
|
3527
|
-
if (snapshot.state === null)
|
|
3528
|
-
return { schemaManifest, state: null };
|
|
3529
|
-
const projects = snapshot.state.projects;
|
|
3530
|
-
if (projects === undefined) {
|
|
3531
|
-
return { schemaManifest, state: { ...snapshot.state } };
|
|
3532
|
-
}
|
|
3533
|
-
const map = asObject(projects, "project map");
|
|
3534
|
-
const nextProjects = {};
|
|
3535
|
-
for (const [projectId, rawProject] of Object.entries(map)) {
|
|
3536
|
-
const project = asObject(rawProject, `Project ${projectId}`);
|
|
3537
|
-
nextProjects[projectId] = {
|
|
3538
|
-
...project,
|
|
3539
|
-
schemaVersion: PROJECT_TO_VERSION,
|
|
3540
|
-
ownership: "external"
|
|
3541
|
-
};
|
|
3542
|
-
}
|
|
3543
|
-
return {
|
|
3544
|
-
schemaManifest,
|
|
3545
|
-
state: { ...snapshot.state, projects: nextProjects }
|
|
3546
|
-
};
|
|
3547
|
-
}
|
|
3548
|
-
/**
|
|
3549
|
-
* Project v4 adds the `knowledgeProposals` workflow list (Leader-proposed
|
|
3550
|
-
* Knowledge candidates awaiting an Operator decision). Every v3 Project
|
|
3551
|
-
* defaults to an empty list; the compatible normalizer adds it in place
|
|
3552
|
-
* without rewriting any other field.
|
|
3553
|
-
*/
|
|
3554
|
-
function projectKnowledgeProposalsStep() {
|
|
3555
|
-
return {
|
|
3556
|
-
kind: "compatible",
|
|
3557
|
-
axis: "record",
|
|
3558
|
-
recordKind: "project",
|
|
3559
|
-
fromVersion: PROJECT_KNOWLEDGE_PROPOSALS_FROM_VERSION,
|
|
3560
|
-
toVersion: PROJECT_KNOWLEDGE_PROPOSALS_TO_VERSION,
|
|
3561
|
-
defaults: [
|
|
3562
|
-
"knowledgeProposals defaults to an empty list on every Project"
|
|
3563
|
-
],
|
|
3564
|
-
validateSource: (snapshot) => requireProjectV3Shape(snapshot),
|
|
3565
|
-
normalize: (snapshot) => normalizeProjectV3ToV4(snapshot)
|
|
3566
|
-
};
|
|
3567
|
-
}
|
|
3568
|
-
function requireProjectV3Shape(snapshot) {
|
|
3569
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3570
|
-
if (manifestVersions.project !== PROJECT_KNOWLEDGE_PROPOSALS_FROM_VERSION) {
|
|
3571
|
-
throw new Error(`Record project compatible step requires manifest version ${PROJECT_KNOWLEDGE_PROPOSALS_FROM_VERSION}.`);
|
|
3572
|
-
}
|
|
3573
|
-
if (snapshot.state === null)
|
|
3574
|
-
return;
|
|
3575
|
-
const projects = snapshot.state.projects;
|
|
3576
|
-
if (projects === undefined)
|
|
3577
|
-
return;
|
|
3578
|
-
const map = asObject(projects, "project map");
|
|
3579
|
-
const allowed = new Set(PROJECT_V3_FIELDS);
|
|
3580
|
-
for (const [projectId, rawProject] of Object.entries(map)) {
|
|
3581
|
-
const project = asObject(rawProject, `Project ${projectId}`);
|
|
3582
|
-
if (project.schemaVersion !== PROJECT_KNOWLEDGE_PROPOSALS_FROM_VERSION) {
|
|
3583
|
-
throw new Error(`Project ${projectId} must use schemaVersion ${PROJECT_KNOWLEDGE_PROPOSALS_FROM_VERSION}.`);
|
|
3584
|
-
}
|
|
3585
|
-
const unknown = Object.keys(project).find((key) => !allowed.has(key));
|
|
3586
|
-
if (unknown !== undefined) {
|
|
3587
|
-
throw new Error(`Project ${projectId} has an unknown v3 field: ${unknown}.`);
|
|
3588
|
-
}
|
|
3589
|
-
}
|
|
3590
|
-
}
|
|
3591
|
-
const PROJECT_V3_FIELDS = [
|
|
3592
|
-
"schemaVersion",
|
|
3593
|
-
"id",
|
|
3594
|
-
"name",
|
|
3595
|
-
"aliases",
|
|
3596
|
-
"path",
|
|
3597
|
-
"ownership",
|
|
3598
|
-
"remoteUrl",
|
|
3599
|
-
"stableBranch",
|
|
3600
|
-
"developmentBranch",
|
|
3601
|
-
"knowledge",
|
|
3602
|
-
"createdAt",
|
|
3603
|
-
"updatedAt"
|
|
3604
|
-
];
|
|
3605
|
-
function normalizeProjectV3ToV4(snapshot) {
|
|
3606
|
-
requireProjectV3Shape(snapshot);
|
|
3607
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3608
|
-
const schemaManifest = {
|
|
3609
|
-
...snapshot.schemaManifest,
|
|
3610
|
-
recordVersions: { ...manifestVersions, project: PROJECT_KNOWLEDGE_PROPOSALS_TO_VERSION }
|
|
3611
|
-
};
|
|
3612
|
-
if (snapshot.state === null)
|
|
3613
|
-
return { schemaManifest, state: null };
|
|
3614
|
-
const projects = snapshot.state.projects;
|
|
3615
|
-
if (projects === undefined) {
|
|
3616
|
-
return { schemaManifest, state: { ...snapshot.state } };
|
|
3617
|
-
}
|
|
3618
|
-
const map = asObject(projects, "project map");
|
|
3619
|
-
const nextProjects = {};
|
|
3620
|
-
for (const [projectId, rawProject] of Object.entries(map)) {
|
|
3621
|
-
const project = asObject(rawProject, `Project ${projectId}`);
|
|
3622
|
-
nextProjects[projectId] = {
|
|
3623
|
-
...project,
|
|
3624
|
-
schemaVersion: PROJECT_KNOWLEDGE_PROPOSALS_TO_VERSION,
|
|
3625
|
-
knowledgeProposals: []
|
|
3626
|
-
};
|
|
3627
|
-
}
|
|
3628
|
-
return {
|
|
3629
|
-
schemaManifest,
|
|
3630
|
-
state: { ...snapshot.state, projects: nextProjects }
|
|
3631
|
-
};
|
|
3632
|
-
}
|
|
3633
|
-
/**
|
|
3634
|
-
* Project v5 adds the lifecycle status (`active`/`retired`) plus the
|
|
3635
|
-
* retirement audit record. Historical Projects are all active; the step only
|
|
3636
|
-
* defaults the new field, so it is a compatible adjacent migration.
|
|
3637
|
-
*/
|
|
3638
|
-
function projectLifecycleStep() {
|
|
3639
|
-
return {
|
|
3640
|
-
kind: "compatible",
|
|
3641
|
-
axis: "record",
|
|
3642
|
-
recordKind: "project",
|
|
3643
|
-
fromVersion: PROJECT_LIFECYCLE_FROM_VERSION,
|
|
3644
|
-
toVersion: PROJECT_LIFECYCLE_TO_VERSION,
|
|
3645
|
-
defaults: [
|
|
3646
|
-
"status defaults to active on every Project"
|
|
3647
|
-
],
|
|
3648
|
-
validateSource: (snapshot) => requireProjectV4Shape(snapshot),
|
|
3649
|
-
normalize: (snapshot) => normalizeProjectV4ToV5(snapshot)
|
|
3650
|
-
};
|
|
3651
|
-
}
|
|
3652
|
-
function requireProjectV4Shape(snapshot) {
|
|
3653
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3654
|
-
if (manifestVersions.project !== PROJECT_LIFECYCLE_FROM_VERSION) {
|
|
3655
|
-
throw new Error(`Record project compatible step requires manifest version ${PROJECT_LIFECYCLE_FROM_VERSION}.`);
|
|
3656
|
-
}
|
|
3657
|
-
if (snapshot.state === null)
|
|
3658
|
-
return;
|
|
3659
|
-
const projects = snapshot.state.projects;
|
|
3660
|
-
if (projects === undefined)
|
|
3661
|
-
return;
|
|
3662
|
-
const map = asObject(projects, "project map");
|
|
3663
|
-
const allowed = new Set(PROJECT_V4_FIELDS);
|
|
3664
|
-
for (const [projectId, rawProject] of Object.entries(map)) {
|
|
3665
|
-
const project = asObject(rawProject, `Project ${projectId}`);
|
|
3666
|
-
if (project.schemaVersion !== PROJECT_LIFECYCLE_FROM_VERSION) {
|
|
3667
|
-
throw new Error(`Project ${projectId} must use schemaVersion ${PROJECT_LIFECYCLE_FROM_VERSION}.`);
|
|
3668
|
-
}
|
|
3669
|
-
const unknown = Object.keys(project).find((key) => !allowed.has(key));
|
|
3670
|
-
if (unknown !== undefined) {
|
|
3671
|
-
throw new Error(`Project ${projectId} has an unknown v4 field: ${unknown}.`);
|
|
3672
|
-
}
|
|
3673
|
-
}
|
|
3674
|
-
}
|
|
3675
|
-
function normalizeProjectV4ToV5(snapshot) {
|
|
3676
|
-
requireProjectV4Shape(snapshot);
|
|
3677
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3678
|
-
const schemaManifest = {
|
|
3679
|
-
...snapshot.schemaManifest,
|
|
3680
|
-
recordVersions: { ...manifestVersions, project: PROJECT_LIFECYCLE_TO_VERSION }
|
|
3681
|
-
};
|
|
3682
|
-
if (snapshot.state === null)
|
|
3683
|
-
return { schemaManifest, state: null };
|
|
3684
|
-
const projects = snapshot.state.projects;
|
|
3685
|
-
if (projects === undefined) {
|
|
3686
|
-
return { schemaManifest, state: { ...snapshot.state } };
|
|
3687
|
-
}
|
|
3688
|
-
const map = asObject(projects, "project map");
|
|
3689
|
-
const nextProjects = {};
|
|
3690
|
-
for (const [projectId, rawProject] of Object.entries(map)) {
|
|
3691
|
-
const project = asObject(rawProject, `Project ${projectId}`);
|
|
3692
|
-
nextProjects[projectId] = {
|
|
3693
|
-
...project,
|
|
3694
|
-
schemaVersion: PROJECT_LIFECYCLE_TO_VERSION,
|
|
3695
|
-
status: "active"
|
|
3696
|
-
};
|
|
3697
|
-
}
|
|
3698
|
-
return {
|
|
3699
|
-
schemaManifest,
|
|
3700
|
-
state: { ...snapshot.state, projects: nextProjects }
|
|
3701
|
-
};
|
|
3702
|
-
}
|
|
3703
|
-
const PROJECT_V4_FIELDS = [
|
|
3704
|
-
"schemaVersion",
|
|
3705
|
-
"id",
|
|
3706
|
-
"name",
|
|
3707
|
-
"aliases",
|
|
3708
|
-
"path",
|
|
3709
|
-
"ownership",
|
|
3710
|
-
"remoteUrl",
|
|
3711
|
-
"stableBranch",
|
|
3712
|
-
"developmentBranch",
|
|
3713
|
-
"knowledge",
|
|
3714
|
-
"knowledgeProposals",
|
|
3715
|
-
"createdAt",
|
|
3716
|
-
"updatedAt"
|
|
3717
|
-
];
|
|
3718
|
-
/**
|
|
3719
|
-
* Task v4 adds the optional durable workspace identity. Historical Tasks have
|
|
3720
|
-
* no identity; they keep working against their existing (legacy) refs until the
|
|
3721
|
-
* controlled rebuild mints one. This adjacent step performs no field rewrite,
|
|
3722
|
-
* preserving old records while keeping a pre-v4 Task out of the strict current
|
|
3723
|
-
* parser.
|
|
3724
|
-
*/
|
|
3725
|
-
function taskWorkspaceIdentityStep() {
|
|
3726
|
-
return {
|
|
3727
|
-
axis: "record",
|
|
3728
|
-
recordKind: "task",
|
|
3729
|
-
fromVersion: TASK_FROM_VERSION,
|
|
3730
|
-
toVersion: TASK_TO_VERSION,
|
|
3731
|
-
preconditions: requireTaskV3Family,
|
|
3732
|
-
transform: migrateTaskV3ToV4,
|
|
3733
|
-
declaredEffects: []
|
|
3734
|
-
};
|
|
3735
|
-
}
|
|
3736
|
-
function requireTaskV3Family(snapshot) {
|
|
3737
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3738
|
-
if (manifestVersions.task !== TASK_FROM_VERSION) {
|
|
3739
|
-
throw new Error(`Record task migration requires manifest version ${TASK_FROM_VERSION}.`);
|
|
3740
|
-
}
|
|
3741
|
-
if (snapshot.state === null)
|
|
3742
|
-
return;
|
|
3743
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3744
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3745
|
-
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3746
|
-
const task = aggregate.task;
|
|
3747
|
-
if (task === undefined)
|
|
3748
|
-
continue;
|
|
3749
|
-
const record = asObject(task, `Task ${taskId}`);
|
|
3750
|
-
if (record.schemaVersion !== TASK_FROM_VERSION) {
|
|
3751
|
-
throw new Error(`Task ${taskId} must use schemaVersion ${TASK_FROM_VERSION}.`);
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
}
|
|
3755
|
-
function migrateTaskV3ToV4(snapshot) {
|
|
3756
|
-
requireTaskV3Family(snapshot);
|
|
3757
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3758
|
-
const schemaManifest = {
|
|
3759
|
-
...snapshot.schemaManifest,
|
|
3760
|
-
recordVersions: { ...manifestVersions, task: TASK_TO_VERSION }
|
|
3761
|
-
};
|
|
3762
|
-
if (snapshot.state === null)
|
|
3763
|
-
return { schemaManifest, state: null };
|
|
3764
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3765
|
-
const nextTasks = {};
|
|
3766
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3767
|
-
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3768
|
-
if (aggregate.task === undefined) {
|
|
3769
|
-
nextTasks[taskId] = { ...aggregate };
|
|
3770
|
-
continue;
|
|
3771
|
-
}
|
|
3772
|
-
const task = asObject(aggregate.task, `Task ${taskId}`);
|
|
3773
|
-
nextTasks[taskId] = {
|
|
3774
|
-
...aggregate,
|
|
3775
|
-
task: { ...task, schemaVersion: TASK_TO_VERSION }
|
|
3776
|
-
};
|
|
3777
|
-
}
|
|
3778
|
-
return {
|
|
3779
|
-
schemaManifest,
|
|
3780
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
3781
|
-
};
|
|
3782
|
-
}
|
|
3783
|
-
/**
|
|
3784
|
-
* Task v5 replaces the stored delivery topology switch with an optional,
|
|
3785
|
-
* Project-defined intent. Historical topology is retained as audit context,
|
|
3786
|
-
* but no current workflow decision reads it.
|
|
3787
|
-
*/
|
|
3788
|
-
function taskIntentStep() {
|
|
3789
|
-
return {
|
|
3790
|
-
axis: "record",
|
|
3791
|
-
recordKind: "task",
|
|
3792
|
-
fromVersion: TASK_INTENT_FROM_VERSION,
|
|
3793
|
-
toVersion: TASK_INTENT_TO_VERSION,
|
|
3794
|
-
preconditions: requireTaskV4Family,
|
|
3795
|
-
transform: migrateTaskV4ToV5,
|
|
3796
|
-
declaredEffects: []
|
|
3797
|
-
};
|
|
3798
|
-
}
|
|
3799
|
-
function requireTaskV4Family(snapshot) {
|
|
3800
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3801
|
-
if (manifestVersions.task !== TASK_INTENT_FROM_VERSION) {
|
|
3802
|
-
throw new Error(`Record task migration requires manifest version ${TASK_INTENT_FROM_VERSION}.`);
|
|
3803
|
-
}
|
|
3804
|
-
if (snapshot.state === null)
|
|
3805
|
-
return;
|
|
3806
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3807
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3808
|
-
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3809
|
-
if (aggregate.task === undefined)
|
|
3810
|
-
continue;
|
|
3811
|
-
const record = asObject(aggregate.task, `Task ${taskId}`);
|
|
3812
|
-
if (record.schemaVersion !== TASK_INTENT_FROM_VERSION) {
|
|
3813
|
-
throw new Error(`Task ${taskId} must use schemaVersion ${TASK_INTENT_FROM_VERSION}.`);
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
}
|
|
3817
|
-
function migrateTaskV4ToV5(snapshot) {
|
|
3818
|
-
requireTaskV4Family(snapshot);
|
|
3819
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3820
|
-
const schemaManifest = {
|
|
3821
|
-
...snapshot.schemaManifest,
|
|
3822
|
-
recordVersions: { ...manifestVersions, task: TASK_INTENT_TO_VERSION }
|
|
3823
|
-
};
|
|
3824
|
-
if (snapshot.state === null)
|
|
3825
|
-
return { schemaManifest, state: null };
|
|
3826
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3827
|
-
const nextTasks = {};
|
|
3828
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3829
|
-
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3830
|
-
if (aggregate.task === undefined) {
|
|
3831
|
-
nextTasks[taskId] = { ...aggregate };
|
|
3832
|
-
continue;
|
|
3833
|
-
}
|
|
3834
|
-
const task = asObject(aggregate.task, `Task ${taskId}`);
|
|
3835
|
-
const { requireIntegration, schemaVersion: _schemaVersion, ...retained } = task;
|
|
3836
|
-
const projectBindings = Array.isArray(task.projectBindings)
|
|
3837
|
-
? task.projectBindings
|
|
3838
|
-
: [];
|
|
3839
|
-
const legacyDeliveryPath = projectBindings.length === 0
|
|
3840
|
-
? undefined
|
|
3841
|
-
: requireIntegration === true ? "integrated" : "direct";
|
|
3842
|
-
nextTasks[taskId] = {
|
|
3843
|
-
...aggregate,
|
|
3844
|
-
task: {
|
|
3845
|
-
...retained,
|
|
3846
|
-
schemaVersion: TASK_INTENT_TO_VERSION,
|
|
3847
|
-
...(legacyDeliveryPath === undefined ? {} : { legacyDeliveryPath })
|
|
3848
|
-
}
|
|
3849
|
-
};
|
|
3850
|
-
}
|
|
3851
|
-
return {
|
|
3852
|
-
schemaManifest,
|
|
3853
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
3854
|
-
};
|
|
3855
|
-
}
|
|
3856
|
-
/** Task v6 separates semantic lifecycle from the current execution admission gate. */
|
|
3857
|
-
function taskExecutionGateStep() {
|
|
3858
|
-
return {
|
|
3859
|
-
axis: "record",
|
|
3860
|
-
recordKind: "task",
|
|
3861
|
-
fromVersion: TASK_EXECUTION_GATE_FROM_VERSION,
|
|
3862
|
-
toVersion: TASK_EXECUTION_GATE_TO_VERSION,
|
|
3863
|
-
preconditions: requireTaskV5Family,
|
|
3864
|
-
transform: migrateTaskV5ToV6,
|
|
3865
|
-
declaredEffects: []
|
|
3866
|
-
};
|
|
3867
|
-
}
|
|
3868
|
-
function requireTaskV5Family(snapshot) {
|
|
3869
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3870
|
-
if (manifestVersions.task !== TASK_EXECUTION_GATE_FROM_VERSION) {
|
|
3871
|
-
throw new Error(`Record task migration requires manifest version ${TASK_EXECUTION_GATE_FROM_VERSION}.`);
|
|
3872
|
-
}
|
|
3873
|
-
if (snapshot.state === null)
|
|
3874
|
-
return;
|
|
3875
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3876
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3877
|
-
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3878
|
-
if (aggregate.task === undefined)
|
|
3879
|
-
continue;
|
|
3880
|
-
const record = asObject(aggregate.task, `Task ${taskId}`);
|
|
3881
|
-
if (record.schemaVersion !== TASK_EXECUTION_GATE_FROM_VERSION) {
|
|
3882
|
-
throw new Error(`Task ${taskId} must use schemaVersion ${TASK_EXECUTION_GATE_FROM_VERSION}.`);
|
|
3883
|
-
}
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
function migrateTaskV5ToV6(snapshot) {
|
|
3887
|
-
requireTaskV5Family(snapshot);
|
|
3888
|
-
const manifestVersions = asObject(snapshot.schemaManifest.recordVersions, "schema manifest recordVersions");
|
|
3889
|
-
const schemaManifest = {
|
|
3890
|
-
...snapshot.schemaManifest,
|
|
3891
|
-
recordVersions: { ...manifestVersions, task: TASK_EXECUTION_GATE_TO_VERSION }
|
|
3892
|
-
};
|
|
3893
|
-
if (snapshot.state === null)
|
|
3894
|
-
return { schemaManifest, state: null };
|
|
3895
|
-
const tasks = asObject(snapshot.state.tasks, "state tasks");
|
|
3896
|
-
const nextTasks = {};
|
|
3897
|
-
for (const [taskId, rawTask] of Object.entries(tasks)) {
|
|
3898
|
-
const aggregate = asObject(rawTask, `Task aggregate ${taskId}`);
|
|
3899
|
-
if (aggregate.task === undefined) {
|
|
3900
|
-
nextTasks[taskId] = { ...aggregate };
|
|
3901
|
-
continue;
|
|
3902
|
-
}
|
|
3903
|
-
const task = asObject(aggregate.task, `Task ${taskId}`);
|
|
3904
|
-
nextTasks[taskId] = {
|
|
3905
|
-
...aggregate,
|
|
3906
|
-
task: {
|
|
3907
|
-
...task,
|
|
3908
|
-
schemaVersion: TASK_EXECUTION_GATE_TO_VERSION,
|
|
3909
|
-
executionGate: { state: "enabled" }
|
|
3910
|
-
}
|
|
3911
|
-
};
|
|
3912
|
-
}
|
|
3913
|
-
return {
|
|
3914
|
-
schemaManifest,
|
|
3915
|
-
state: { ...snapshot.state, tasks: nextTasks }
|
|
3916
|
-
};
|
|
3917
|
-
}
|
|
3918
|
-
/**
|
|
3919
|
-
* A version bump is deliverable only when the shared planner resolves the full
|
|
3920
|
-
* adjacent path. This also covers target-only record families as explicit 0->1
|
|
3921
|
-
* introductions and rejects offline declarations without executable steps.
|
|
3922
|
-
*/
|
|
3923
|
-
export function assertRegistryCoversBaselineToCurrent(registry, baseline = baselineStorageVersionState(), current = latestStorageVersionState()) {
|
|
3924
|
-
const plan = planMigration(registry, baseline, current);
|
|
3925
|
-
if (plan.kind !== "blocked")
|
|
3926
|
-
return;
|
|
3927
|
-
const coordinate = plan.blocker.axis === "record"
|
|
3928
|
-
? `record/${plan.blocker.recordKind ?? "?"}`
|
|
3929
|
-
: plan.blocker.axis;
|
|
3930
|
-
throw new Error(`Storage migration delivery gate failed for ${coordinate}: ${plan.blocker.message}`);
|
|
3931
|
-
}
|