@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,55 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Side-by-side `yui update` orchestration for the current storage contract.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* stage (side-by-side, never touches the live install)
|
|
10
|
-
* -> preflight (the STAGED new binary classifies the target Home read-only)
|
|
11
|
-
* -> current/compatible: exact Controller stop -> activate the same binary
|
|
12
|
-
* -> migration-required: authoritative offline inventory -> recoverable
|
|
13
|
-
* storage switch -> activate the same binary
|
|
14
|
-
* -> post-verify through the activated binary, then replacement Controller
|
|
15
|
-
*
|
|
16
|
-
* ## Rollback boundary (NARROWED — no versioned binary pointer is introduced)
|
|
17
|
-
*
|
|
18
|
-
* The managed Session launcher is an in-place forwarder, not a versioned package
|
|
19
|
-
* pointer, so this release does NOT claim binary+Home dual-resource atomicity.
|
|
20
|
-
* The guarantees it DOES make, precisely:
|
|
21
|
-
*
|
|
22
|
-
* - **Staging is isolated.** `stage` installs side-by-side and never mutates the
|
|
23
|
-
* live global install, so a stage/preflight failure leaves the old binary and
|
|
24
|
-
* the Home byte-for-byte unchanged.
|
|
25
|
-
* - **Compatible activation does not switch the Home.** The fast path stops the
|
|
26
|
-
* exact Controller, promotes the staged artifact, and verifies a compatible
|
|
27
|
-
* current-model loader without copy, backup, rename, or Session wait.
|
|
28
|
-
* - **Storage activation is recoverable until writes resume.** The Home switch is
|
|
29
|
-
* atomic with a timestamped backup (see the migration engine). Before the new
|
|
30
|
-
* version resumes writes, recovery is a single restore of that backup.
|
|
31
|
-
* - **No auto-downgrade after writes.** Once the new version has resumed writes
|
|
32
|
-
* (a new Controller/first write against the migrated Home), the tool never
|
|
33
|
-
* auto-reverts; the migrated Home is authoritative.
|
|
34
|
-
*
|
|
35
|
-
* The one window it CANNOT make atomic is: storage already switched to the new
|
|
36
|
-
* schema, but binary promotion then fails. Because the axes are version-gated,
|
|
37
|
-
* the old binary fail-closes on the new Home rather than misreading it; recovery
|
|
38
|
-
* is to restore the timestamped Home backup (the exact command is printed), after
|
|
39
|
-
* which the old binary works again. This is stated in the failure output, not
|
|
40
|
-
* hidden behind a false atomicity claim.
|
|
41
|
-
*/
|
|
42
|
-
import { isAbsolute } from "node:path";
|
|
43
|
-
import { switchProgressPath } from "../storage/upgrade/switchProgress.js";
|
|
44
|
-
import { upgradeReceiptPath } from "../storage/upgrade/upgradeReceipt.js";
|
|
45
|
-
/**
|
|
46
|
-
* Run the update orchestration. Never performs an irreversible step before the
|
|
47
|
-
* read-only preflight has proven the Home is safe, and reports the precise phase
|
|
48
|
-
* plus a recovery action on any failure.
|
|
4
|
+
* The staged binary may only prove that the target Home already implements the
|
|
5
|
+
* exact current contract. Updating never rewrites, normalizes, migrates, or
|
|
6
|
+
* repairs storage. An older Home stays untouched so an Operator can inspect it
|
|
7
|
+
* with its original Yui version and create a new current Task explicitly.
|
|
49
8
|
*/
|
|
50
9
|
export function runUpdate(ports, options) {
|
|
51
|
-
const home = options.home;
|
|
52
|
-
// 1) Stage side-by-side. A failure here leaves the live install untouched.
|
|
53
10
|
let staged;
|
|
54
11
|
try {
|
|
55
12
|
staged = ports.stage();
|
|
@@ -66,27 +23,19 @@ export function runUpdate(ports, options) {
|
|
|
66
23
|
let result;
|
|
67
24
|
let cleanupWarning;
|
|
68
25
|
try {
|
|
69
|
-
result = runStagedUpdate(ports, staged, home);
|
|
26
|
+
result = runStagedUpdate(ports, staged, options.home);
|
|
70
27
|
}
|
|
71
28
|
finally {
|
|
72
29
|
try {
|
|
73
30
|
ports.cleanup(staged);
|
|
74
31
|
}
|
|
75
32
|
catch (error) {
|
|
76
|
-
// Cleanup is deliberately best-effort. Once the orchestrator has
|
|
77
|
-
// determined an update/abort/ambiguous result, a staging I/O failure must
|
|
78
|
-
// not replace that authoritative outcome or turn it into a generic error.
|
|
79
33
|
cleanupWarning = `Staging cleanup could not be completed: ${messageOf(error)}`;
|
|
80
34
|
}
|
|
81
35
|
}
|
|
82
36
|
return cleanupWarning === undefined ? result : { ...result, cleanupWarning };
|
|
83
37
|
}
|
|
84
|
-
/** Run the staged flow; cleanup is owned by the caller so warnings are retained. */
|
|
85
38
|
function runStagedUpdate(ports, staged, home) {
|
|
86
|
-
// 2) Preflight — the staged binary inspects the Home read-only; no switch.
|
|
87
|
-
// A preflight port that throws unexpectedly (e.g. an I/O fault) is not a safe
|
|
88
|
-
// green light: treat it as a blocked preflight (recoverable, no switch) rather
|
|
89
|
-
// than letting the exception escape (R4-F1).
|
|
90
39
|
let preflight;
|
|
91
40
|
try {
|
|
92
41
|
preflight = ports.preflight(staged, home);
|
|
@@ -96,7 +45,7 @@ function runStagedUpdate(ports, staged, home) {
|
|
|
96
45
|
outcome: "aborted",
|
|
97
46
|
phase: "preflight",
|
|
98
47
|
message: `Preflight failed unexpectedly: ${messageOf(error)}`,
|
|
99
|
-
action: "The current install and Home are unchanged.
|
|
48
|
+
action: "The current install and Home are unchanged. Inspect the staged binary and retry.",
|
|
100
49
|
recoverable: true,
|
|
101
50
|
version: staged.version
|
|
102
51
|
};
|
|
@@ -110,9 +59,7 @@ function runStagedUpdate(ports, staged, home) {
|
|
|
110
59
|
recoverable: true,
|
|
111
60
|
version: staged.version,
|
|
112
61
|
...(preflight.blockers === undefined ? {} : { blockers: preflight.blockers }),
|
|
113
|
-
...(preflight.retryCommand === undefined
|
|
114
|
-
? {}
|
|
115
|
-
: { retryCommand: preflight.retryCommand }),
|
|
62
|
+
...(preflight.retryCommand === undefined ? {} : { retryCommand: preflight.retryCommand }),
|
|
116
63
|
...(preflight.sceneUnchanged === true ? { sceneUnchanged: true } : {})
|
|
117
64
|
};
|
|
118
65
|
}
|
|
@@ -125,234 +72,76 @@ function runStagedUpdate(ports, staged, home) {
|
|
|
125
72
|
outcome: "aborted",
|
|
126
73
|
phase: "coordination",
|
|
127
74
|
message: `Controller handover could not be acquired: ${messageOf(error)}`,
|
|
128
|
-
action: "
|
|
75
|
+
action: "Wait for the other maintenance operation to finish, then retry; nothing was changed.",
|
|
129
76
|
recoverable: true,
|
|
130
77
|
version: staged.version
|
|
131
78
|
};
|
|
132
79
|
}
|
|
133
|
-
let releaseStorageWriteFence;
|
|
134
|
-
const migrationPath = preflight.status === "in-place-migration"
|
|
135
|
-
|| preflight.status === "migration-required"
|
|
136
|
-
|| preflight.status === "migratable";
|
|
137
|
-
if (migrationPath && ports.beginControllerHandover !== undefined) {
|
|
138
|
-
if (ports.beginStorageWriteFence === undefined) {
|
|
139
|
-
releaseHandover?.();
|
|
140
|
-
return {
|
|
141
|
-
outcome: "aborted",
|
|
142
|
-
phase: "coordination",
|
|
143
|
-
message: "The update lifecycle does not provide a storage write-admission fence.",
|
|
144
|
-
action: "Use an updater that holds write admission through storage migration, binary activation, and loader verification; no Controller, binary, or storage change was made.",
|
|
145
|
-
recoverable: true,
|
|
146
|
-
version: staged.version
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
try {
|
|
150
|
-
releaseStorageWriteFence = ports.beginStorageWriteFence(home);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
releaseHandover?.();
|
|
154
|
-
return {
|
|
155
|
-
outcome: "aborted",
|
|
156
|
-
phase: "coordination",
|
|
157
|
-
message: `Storage write admission could not be acquired: ${messageOf(error)}`,
|
|
158
|
-
action: "Another maintenance operation owns this Home. Wait for it to finish, then retry; no Controller, binary, or storage change was made.",
|
|
159
|
-
recoverable: true,
|
|
160
|
-
version: staged.version
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
const resumeWrites = () => {
|
|
165
|
-
releaseStorageWriteFence?.();
|
|
166
|
-
releaseStorageWriteFence = undefined;
|
|
167
|
-
};
|
|
168
80
|
try {
|
|
169
|
-
|
|
81
|
+
const captured = captureControllerLifecycle(ports, staged.version, home);
|
|
82
|
+
if ("outcome" in captured)
|
|
83
|
+
return captured;
|
|
84
|
+
return activateAndVerify(ports, staged, home, captured.lifecycle);
|
|
170
85
|
}
|
|
171
86
|
finally {
|
|
172
|
-
resumeWrites();
|
|
173
87
|
releaseHandover?.();
|
|
174
88
|
}
|
|
175
89
|
}
|
|
176
|
-
function
|
|
177
|
-
// Capture and stop the old Controller exactly once after preflight but
|
|
178
|
-
// before either storage activation or binary promotion. This parent update
|
|
179
|
-
// process remains the sole lifecycle owner for both binary-only and
|
|
180
|
-
// migratable updates.
|
|
181
|
-
const lifecycle = captureControllerLifecycle(ports, staged.version, home);
|
|
182
|
-
if ("outcome" in lifecycle)
|
|
183
|
-
return lifecycle;
|
|
184
|
-
if (preflight.status === "already-current" || preflight.status === "compatible") {
|
|
185
|
-
// Current and all-compatible chains share the lean path: no Home copy,
|
|
186
|
-
// backup, rename, or command replay. The exact Controller handoff prevents
|
|
187
|
-
// old/new writers from overlapping while the same staged artifact activates.
|
|
188
|
-
return activateAndVerify(ports, staged, home, undefined, lifecycle.lifecycle, preflight.status === "compatible" ? "compatible-fast" : "current-fast", resumeWrites);
|
|
189
|
-
}
|
|
190
|
-
// 3) Activate storage — recoverable: atomic switch + timestamped backup. An
|
|
191
|
-
// activation port that throws unexpectedly may have committed the switch
|
|
192
|
-
// before failing, so its state is UNKNOWN — resolve it as ambiguous from the
|
|
193
|
-
// durable on-disk evidence, never let the exception escape as a false clean
|
|
194
|
-
// failure (R4-F1 / P1-2).
|
|
195
|
-
let activation;
|
|
196
|
-
try {
|
|
197
|
-
activation = ports.activateStorage(staged, home);
|
|
198
|
-
}
|
|
199
|
-
catch (error) {
|
|
200
|
-
return resolveAmbiguousActivation(ports, staged, home, `the activation step threw unexpectedly: ${messageOf(error)}`, preflight.status === "in-place-migration"
|
|
201
|
-
? { kind: "in-place", migration: preflight.sqliteMigration }
|
|
202
|
-
: { kind: "switch" }, lifecycle.lifecycle, resumeWrites);
|
|
203
|
-
}
|
|
204
|
-
if (activation.status === "blocked") {
|
|
205
|
-
const failure = {
|
|
206
|
-
outcome: "aborted",
|
|
207
|
-
phase: "activate-storage",
|
|
208
|
-
message: activation.message,
|
|
209
|
-
action: activation.action,
|
|
210
|
-
// The engine guarantees the source Home is unchanged on a blocked/failed
|
|
211
|
-
// migration, and the binary has not been promoted, so this is recoverable.
|
|
212
|
-
recoverable: true,
|
|
213
|
-
version: staged.version,
|
|
214
|
-
...(activation.blockers === undefined ? {} : { blockers: activation.blockers }),
|
|
215
|
-
...(activation.retryCommand === undefined
|
|
216
|
-
? {}
|
|
217
|
-
: { retryCommand: activation.retryCommand }),
|
|
218
|
-
...(activation.sceneUnchanged === true ? { sceneUnchanged: true } : {})
|
|
219
|
-
};
|
|
220
|
-
// The child was externally quiesced by this parent. A clean pre-switch
|
|
221
|
-
// refusal therefore restores the exact captured identity; an ambiguous
|
|
222
|
-
// activation is handled separately and never restores blindly.
|
|
223
|
-
resumeWrites();
|
|
224
|
-
return restoreBeforeSwitchOrReport(ports, home, lifecycle.lifecycle, undefined, false, failure);
|
|
225
|
-
}
|
|
226
|
-
if (activation.status === "ambiguous") {
|
|
227
|
-
// The activation child left no parseable receipt: the switch may or may not
|
|
228
|
-
// have committed. Resolve the true state from the durable on-disk evidence
|
|
229
|
-
// and report an explicit manual recovery — never a false "recoverable".
|
|
230
|
-
return resolveAmbiguousActivation(ports, staged, home, activation.detail, preflight.status === "in-place-migration"
|
|
231
|
-
? { kind: "in-place", migration: preflight.sqliteMigration }
|
|
232
|
-
: { kind: "switch" }, lifecycle.lifecycle, resumeWrites);
|
|
233
|
-
}
|
|
234
|
-
if (activation.status === "migrated" && !isValidBackupPath(activation.backupPath)) {
|
|
235
|
-
// A migrated/upgraded success without a concrete backup path violates
|
|
236
|
-
// the recoverable storage-activation contract. Resolve it through the
|
|
237
|
-
// existing durable receipt/schema probe instead of inferring that the
|
|
238
|
-
// Home was untouched.
|
|
239
|
-
return resolveAmbiguousActivation(ports, staged, home, "the activation reported migrated without a non-empty absolute backupPath", { kind: "switch" }, lifecycle.lifecycle, resumeWrites);
|
|
240
|
-
}
|
|
241
|
-
const backupPath = activation.status === "migrated" ? activation.backupPath : undefined;
|
|
242
|
-
const path = activation.status === "migrated-in-place"
|
|
243
|
-
? "in-place-migration"
|
|
244
|
-
: "offline-migration";
|
|
245
|
-
// 4/5) Promote the binary, then post-verify with the new binary's loader.
|
|
246
|
-
return activateAndVerify(ports, staged, home, backupPath, lifecycle.lifecycle, path, resumeWrites);
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Promote the staged binary and run the new-binary health check. This is the
|
|
250
|
-
* last, non-atomic step: after storage has switched, binary promotion cannot be
|
|
251
|
-
* made atomic with it, so a failure here is reported with the exact backup-based
|
|
252
|
-
* recovery and is NOT auto-reverted once the new version would resume writes.
|
|
253
|
-
*/
|
|
254
|
-
function activateAndVerify(ports, staged, home, storageBackupPath, lifecycle, path, resumeWrites) {
|
|
255
|
-
const inPlaceCommitted = path === "in-place-migration";
|
|
90
|
+
function activateAndVerify(ports, staged, home, lifecycle) {
|
|
256
91
|
try {
|
|
257
92
|
ports.activateBinary(staged);
|
|
258
93
|
}
|
|
259
94
|
catch (error) {
|
|
260
|
-
|
|
95
|
+
return restoreControllerOrReport(ports, home, lifecycle, {
|
|
261
96
|
outcome: "aborted",
|
|
262
97
|
phase: "activate-binary",
|
|
263
98
|
message: `Failed to activate the new binary: ${messageOf(error)}`,
|
|
264
|
-
action:
|
|
265
|
-
? inPlaceMigrationRecoveryAction(home)
|
|
266
|
-
: storageBackupPath === undefined
|
|
267
|
-
? binaryActivationUncertainAction()
|
|
268
|
-
: postSwitchRecoveryAction(home, storageBackupPath),
|
|
269
|
-
// Once binary activation begins, its outcome is not knowable from a
|
|
270
|
-
// failed npm process. Home-not-switched is useful evidence, but it does
|
|
271
|
-
// not prove the current installation remains usable.
|
|
99
|
+
action: binaryActivationUncertainAction(),
|
|
272
100
|
recoverable: false,
|
|
273
|
-
version: staged.version
|
|
274
|
-
|
|
275
|
-
};
|
|
276
|
-
if (!inPlaceCommitted && storageBackupPath === undefined)
|
|
277
|
-
resumeWrites();
|
|
278
|
-
return restoreBeforeSwitchOrReport(ports, home, lifecycle, storageBackupPath, inPlaceCommitted || storageBackupPath !== undefined, failure);
|
|
101
|
+
version: staged.version
|
|
102
|
+
});
|
|
279
103
|
}
|
|
280
104
|
try {
|
|
281
105
|
ports.verify(staged, home);
|
|
282
106
|
}
|
|
283
107
|
catch (error) {
|
|
284
|
-
|
|
108
|
+
return restoreControllerOrReport(ports, home, lifecycle, {
|
|
285
109
|
outcome: "aborted",
|
|
286
110
|
phase: "post-verify",
|
|
287
111
|
message: `Post-update health check failed: ${messageOf(error)}`,
|
|
288
|
-
action:
|
|
289
|
-
? inPlaceMigrationRecoveryAction(home)
|
|
290
|
-
: storageBackupPath === undefined
|
|
291
|
-
? binaryHealthUncertainAction()
|
|
292
|
-
: postSwitchRecoveryAction(home, storageBackupPath),
|
|
112
|
+
action: binaryHealthUncertainAction(),
|
|
293
113
|
recoverable: false,
|
|
294
|
-
version: staged.version
|
|
295
|
-
|
|
296
|
-
};
|
|
297
|
-
if (!inPlaceCommitted && storageBackupPath === undefined)
|
|
298
|
-
resumeWrites();
|
|
299
|
-
return restoreBeforeSwitchOrReport(ports, home, lifecycle, storageBackupPath, inPlaceCommitted || storageBackupPath !== undefined, failure);
|
|
114
|
+
version: staged.version
|
|
115
|
+
});
|
|
300
116
|
}
|
|
301
|
-
|
|
302
|
-
// binary. Release direct-write admission before starting the replacement
|
|
303
|
-
// Controller, whose startup scheduler is itself an authoritative writer.
|
|
304
|
-
resumeWrites();
|
|
305
|
-
if (lifecycle?.ensureRunning === true) {
|
|
117
|
+
if (lifecycle.ensureRunning) {
|
|
306
118
|
try {
|
|
307
119
|
ports.startController(home);
|
|
308
120
|
}
|
|
309
121
|
catch (error) {
|
|
310
122
|
const unknownActive = isUnknownActiveControllerFailure(error);
|
|
311
|
-
const startFailureAction = inPlaceCommitted
|
|
312
|
-
? inPlaceMigrationRecoveryAction(home)
|
|
313
|
-
: lifecycle.wasRunning
|
|
314
|
-
? "The Home was not migrated. Keep writes quiesced and restore the previously running Controller identity before retrying."
|
|
315
|
-
: "The Home was not migrated. Keep writes quiesced and start the replacement Controller after verifying the activated binary.";
|
|
316
123
|
const failure = {
|
|
317
124
|
outcome: "aborted",
|
|
318
125
|
phase: "post-verify",
|
|
319
|
-
message: `${unknownActive
|
|
320
|
-
|
|
126
|
+
message: `${unknownActive
|
|
127
|
+
? "Replacement Controller ownership could not be authenticated safely"
|
|
128
|
+
: "The replacement Controller could not start after activation and verification"}: ${messageOf(error)}.`,
|
|
321
129
|
action: unknownActive
|
|
322
|
-
? unknownActiveControllerAction(home
|
|
323
|
-
:
|
|
324
|
-
?
|
|
325
|
-
:
|
|
130
|
+
? unknownActiveControllerAction(home)
|
|
131
|
+
: lifecycle.wasRunning
|
|
132
|
+
? "Keep the Home quiesced and restore the captured Controller identity before retrying."
|
|
133
|
+
: "Verify the activated binary, then start the Controller explicitly.",
|
|
326
134
|
recoverable: false,
|
|
327
135
|
version: staged.version,
|
|
328
|
-
|
|
329
|
-
// Controller leaves the lifecycle handoff unresolved — not just
|
|
330
|
-
// UPDATE_CONTROLLER_UNKNOWN_ACTIVE. Even when the old identity is
|
|
331
|
-
// restored below, the running Controller is not the replacement the
|
|
332
|
-
// step promised, so the adapter must persist the step as unknown
|
|
333
|
-
// rather than letting a controller-home query confirm it on binary
|
|
334
|
-
// health alone.
|
|
335
|
-
controllerOwnershipUnknown: true,
|
|
336
|
-
...(storageBackupPath === undefined ? {} : { storageBackupPath })
|
|
136
|
+
controllerOwnershipUnknown: true
|
|
337
137
|
};
|
|
338
|
-
// An ownership-unknown mismatch is a live-process blocker, not a
|
|
339
|
-
// recoverable pre-switch failure. Restoring the captured identity here
|
|
340
|
-
// could overwrite or race a foreign process, so leave the Home quiesced
|
|
341
|
-
// and preserve the explicit manual blocker.
|
|
342
138
|
return unknownActive
|
|
343
139
|
? failure
|
|
344
|
-
:
|
|
140
|
+
: restoreControllerOrReport(ports, home, lifecycle, failure);
|
|
345
141
|
}
|
|
346
142
|
}
|
|
347
|
-
return
|
|
348
|
-
? { outcome: "updated", version: staged.version, path }
|
|
349
|
-
: { outcome: "updated", version: staged.version, path, storageBackupPath };
|
|
143
|
+
return { outcome: "updated", version: staged.version, path: "current-fast" };
|
|
350
144
|
}
|
|
351
|
-
/**
|
|
352
|
-
* Capture the old Controller before a binary-only update mutates the install.
|
|
353
|
-
* A partial lifecycle port set is rejected rather than silently selecting a
|
|
354
|
-
* staged/new `ensureFileTaskController` for restoration.
|
|
355
|
-
*/
|
|
356
145
|
function captureControllerLifecycle(ports, version, home) {
|
|
357
146
|
const supplied = [
|
|
358
147
|
ports.controllerStatus,
|
|
@@ -361,7 +150,7 @@ function captureControllerLifecycle(ports, version, home) {
|
|
|
361
150
|
ports.restoreController
|
|
362
151
|
].some((port) => port !== undefined);
|
|
363
152
|
if (!supplied) {
|
|
364
|
-
return { lifecycle: { ensureRunning: false, wasRunning: false
|
|
153
|
+
return { lifecycle: { ensureRunning: false, wasRunning: false } };
|
|
365
154
|
}
|
|
366
155
|
if (ports.controllerStatus === undefined
|
|
367
156
|
|| ports.stopController === undefined
|
|
@@ -371,7 +160,7 @@ function captureControllerLifecycle(ports, version, home) {
|
|
|
371
160
|
outcome: "aborted",
|
|
372
161
|
phase: "preflight",
|
|
373
162
|
message: "Controller lifecycle ownership is incomplete for this update.",
|
|
374
|
-
action: "Provide status, stop, replacement
|
|
163
|
+
action: "Provide status, exact stop, replacement start, and exact restore operations.",
|
|
375
164
|
recoverable: true,
|
|
376
165
|
version
|
|
377
166
|
};
|
|
@@ -385,43 +174,19 @@ function captureControllerLifecycle(ports, version, home) {
|
|
|
385
174
|
outcome: "aborted",
|
|
386
175
|
phase: "preflight",
|
|
387
176
|
message: `Controller status could not be verified: ${messageOf(error)}`,
|
|
388
|
-
action: "
|
|
177
|
+
action: "Inspect Controller ownership and retry after its status is known.",
|
|
389
178
|
recoverable: true,
|
|
390
179
|
version
|
|
391
180
|
};
|
|
392
181
|
}
|
|
393
182
|
if (!isControllerLifecycleStatus(status)) {
|
|
394
|
-
return
|
|
395
|
-
outcome: "aborted",
|
|
396
|
-
phase: "preflight",
|
|
397
|
-
message: "Controller status was malformed; expected a boolean running field.",
|
|
398
|
-
action: "Inspect the Controller status provider and retry; no binary activation was attempted.",
|
|
399
|
-
recoverable: true,
|
|
400
|
-
version
|
|
401
|
-
};
|
|
183
|
+
return malformedControllerResult(version, "Controller status was malformed.");
|
|
402
184
|
}
|
|
403
185
|
if (!status.running) {
|
|
404
|
-
return { lifecycle: { ensureRunning: true, wasRunning: false
|
|
405
|
-
}
|
|
406
|
-
if (!isPositivePid(status.pid)) {
|
|
407
|
-
return {
|
|
408
|
-
outcome: "aborted",
|
|
409
|
-
phase: "preflight",
|
|
410
|
-
message: "The Controller is running but its exact PID could not be captured.",
|
|
411
|
-
action: "Refusing an unfenced stop; inspect Controller ownership and retry only after its PID is authenticated.",
|
|
412
|
-
recoverable: true,
|
|
413
|
-
version
|
|
414
|
-
};
|
|
186
|
+
return { lifecycle: { ensureRunning: true, wasRunning: false } };
|
|
415
187
|
}
|
|
416
|
-
if (!isControllerIdentity(status.identity)) {
|
|
417
|
-
return
|
|
418
|
-
outcome: "aborted",
|
|
419
|
-
phase: "preflight",
|
|
420
|
-
message: "The Controller is running but its executable/version identity could not be captured.",
|
|
421
|
-
action: "Refusing to stop a Controller that cannot be restored exactly; provide its executable path, arguments, and version, then retry.",
|
|
422
|
-
recoverable: true,
|
|
423
|
-
version
|
|
424
|
-
};
|
|
188
|
+
if (!isPositivePid(status.pid) || !isControllerIdentity(status.identity)) {
|
|
189
|
+
return malformedControllerResult(version, "The running Controller did not expose an exact PID and launch identity.");
|
|
425
190
|
}
|
|
426
191
|
let stopped;
|
|
427
192
|
try {
|
|
@@ -432,85 +197,56 @@ function captureControllerLifecycle(ports, version, home) {
|
|
|
432
197
|
outcome: "aborted",
|
|
433
198
|
phase: "preflight",
|
|
434
199
|
message: `Controller stop/drain failed: ${messageOf(error)}`,
|
|
435
|
-
action: "
|
|
200
|
+
action: "Inspect the captured Controller before retrying; binary activation was not attempted.",
|
|
436
201
|
recoverable: true,
|
|
437
202
|
version
|
|
438
203
|
};
|
|
439
204
|
}
|
|
440
|
-
if (!isControllerStopResult(stopped)
|
|
441
|
-
|
|
442
|
-
|| stopped.pid !== status.pid) {
|
|
443
|
-
return {
|
|
444
|
-
outcome: "aborted",
|
|
445
|
-
phase: "preflight",
|
|
446
|
-
message: `Controller stop did not confirm the captured PID ${status.pid} with stopped:true.`,
|
|
447
|
-
action: "Keep the Home quiesced and inspect the Controller; no binary activation was attempted and no stop retry was issued.",
|
|
448
|
-
recoverable: true,
|
|
449
|
-
version
|
|
450
|
-
};
|
|
205
|
+
if (!isControllerStopResult(stopped) || stopped.stopped !== true || stopped.pid !== status.pid) {
|
|
206
|
+
return malformedControllerResult(version, `Controller stop did not confirm captured PID ${status.pid}.`);
|
|
451
207
|
}
|
|
452
208
|
return {
|
|
453
209
|
lifecycle: {
|
|
454
210
|
ensureRunning: true,
|
|
455
211
|
wasRunning: true,
|
|
456
|
-
stopped: true,
|
|
457
212
|
identity: status.identity
|
|
458
213
|
}
|
|
459
214
|
};
|
|
460
215
|
}
|
|
461
|
-
function
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
216
|
+
function malformedControllerResult(version, message) {
|
|
217
|
+
return {
|
|
218
|
+
outcome: "aborted",
|
|
219
|
+
phase: "preflight",
|
|
220
|
+
message,
|
|
221
|
+
action: "Refusing an unfenced Controller handoff; inspect ownership and retry.",
|
|
222
|
+
recoverable: true,
|
|
223
|
+
version
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function restoreControllerOrReport(ports, home, lifecycle, failure) {
|
|
227
|
+
if (!lifecycle.wasRunning)
|
|
465
228
|
return failure;
|
|
466
229
|
try {
|
|
467
230
|
ports.restoreController(home, lifecycle.identity);
|
|
468
231
|
return failure;
|
|
469
232
|
}
|
|
470
233
|
catch (error) {
|
|
471
|
-
// Preserve the original phase, Home-switch evidence, and binary-health
|
|
472
|
-
// uncertainty guidance. A restore failure is an appended blocker, not a
|
|
473
|
-
// replacement generic post-verify result.
|
|
474
234
|
return {
|
|
475
235
|
...failure,
|
|
476
|
-
message: `${failure.message}
|
|
477
|
-
|
|
478
|
-
action: `${failure.action} The old Controller restore also failed: ${messageOf(error)}. `
|
|
479
|
-
+ "Keep the Home quiesced; inspect the restore blocker and resolve it before any bounded retry. ",
|
|
236
|
+
message: `${failure.message} Captured Controller restore failed: ${messageOf(error)}.`,
|
|
237
|
+
action: `${failure.action} Keep the Home quiesced and resolve the restore failure.`,
|
|
480
238
|
recoverable: false
|
|
481
239
|
};
|
|
482
240
|
}
|
|
483
241
|
}
|
|
484
|
-
function postSwitchRecoveryAction(home, backupPath) {
|
|
485
|
-
return `The storage switch is committed (backup at ${backupPath}); do not restore the old Controller. `
|
|
486
|
-
+ `Inspect the backup, receipt marker "${upgradeReceiptPath(home)}", and switch-progress marker `
|
|
487
|
-
+ `"${switchProgressPath(home)}". Verify the migrated Home, then either finish the update or `
|
|
488
|
-
+ `restore the backup explicitly with mv "${backupPath}" "${home}".`;
|
|
489
|
-
}
|
|
490
242
|
function binaryActivationUncertainAction() {
|
|
491
|
-
return "
|
|
492
|
-
+ "do not assume the current install is usable. Reinstall Yui, verify `yui version` and "
|
|
493
|
-
+ "`yui doctor`, then retry `yui update` before resuming writes.";
|
|
243
|
+
return "Binary activation began and its result is unknown. Reinstall Yui, then verify `yui version` and `yui doctor` before resuming the Controller.";
|
|
494
244
|
}
|
|
495
245
|
function binaryHealthUncertainAction() {
|
|
496
|
-
return "The
|
|
497
|
-
+ "assume the current install is usable. Reinstall Yui, verify `yui version` and `yui doctor`, "
|
|
498
|
-
+ "then retry `yui update` before resuming writes.";
|
|
499
|
-
}
|
|
500
|
-
function inPlaceMigrationRecoveryAction(home) {
|
|
501
|
-
return `The SQLite migration for ${home} committed in place and has no rollback backup. `
|
|
502
|
-
+ "Do not restore the old Controller; finish installing this or a newer Yui version, run `yui doctor`, then start the Controller.";
|
|
246
|
+
return "The activated binary failed health verification. Reinstall Yui and verify `yui version` and `yui doctor` before resuming the Controller.";
|
|
503
247
|
}
|
|
504
|
-
function unknownActiveControllerAction(home
|
|
505
|
-
|
|
506
|
-
? "The SQLite migration committed in place; do not restore the old Controller. "
|
|
507
|
-
: backupPath === undefined
|
|
508
|
-
? "The Home was not migrated."
|
|
509
|
-
: `The storage switch is committed (backup at ${backupPath}); do not restore the old Controller. `;
|
|
510
|
-
return `${storageEvidence} A replacement Controller may still be active under unknown ownership. `
|
|
511
|
-
+ `Keep writes quiesced and do not claim recovery or resume writes. Inspect the authenticated `
|
|
512
|
-
+ `Controller status for ${home}, stop only the PID proven to belong to this update, then `
|
|
513
|
-
+ "verify `yui version` and `yui doctor` before a bounded retry.";
|
|
248
|
+
function unknownActiveControllerAction(home) {
|
|
249
|
+
return `A replacement Controller may be active under unknown ownership for ${home}. Keep it quiesced, authenticate its PID, and stop only that proven owner before retrying.`;
|
|
514
250
|
}
|
|
515
251
|
function isUnknownActiveControllerFailure(error) {
|
|
516
252
|
return isRecord(error) && error.code === "UPDATE_CONTROLLER_UNKNOWN_ACTIVE";
|
|
@@ -533,166 +269,9 @@ function isControllerIdentity(value) {
|
|
|
533
269
|
&& typeof value.version === "string"
|
|
534
270
|
&& value.version.length > 0;
|
|
535
271
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
* parseable receipt, so the switch may or may not have committed. Probe the
|
|
539
|
-
* durable on-disk evidence and report an explicit manual recovery — this is
|
|
540
|
-
* NEVER reported as recoverable-and-unchanged.
|
|
541
|
-
*
|
|
542
|
-
* The verdict is driven by the completion receipt first (the switch writes it the
|
|
543
|
-
* instant it commits), then corroborated by the on-disk schema:
|
|
544
|
-
* - receipt present -> the switch committed; point at the backup and require
|
|
545
|
-
* the operator to verify the migrated Home before resuming (the binary was
|
|
546
|
-
* never promoted, so the old binary fail-closes on the new schema).
|
|
547
|
-
* - no receipt, schema already current -> most likely the switch never ran
|
|
548
|
-
* (or fully reverted); still require an explicit re-run rather than asserting
|
|
549
|
-
* "unchanged", because stdout was lost.
|
|
550
|
-
* - no receipt, schema not current -> genuinely indeterminate; give the
|
|
551
|
-
* operator the exact files to inspect.
|
|
552
|
-
*/
|
|
553
|
-
function resolveAmbiguousActivation(ports, staged, home, detail, expected, lifecycle, resumeWrites) {
|
|
554
|
-
let probe;
|
|
555
|
-
try {
|
|
556
|
-
probe = ports.probeStorage(home);
|
|
557
|
-
}
|
|
558
|
-
catch (error) {
|
|
559
|
-
// Even the probe failed: report maximum uncertainty with the raw evidence.
|
|
560
|
-
if (expected.kind === "in-place") {
|
|
561
|
-
return {
|
|
562
|
-
outcome: "ambiguous",
|
|
563
|
-
phase: "activate-storage",
|
|
564
|
-
message: `SQLite activation result is unknown (${detail}); reading its migration ledger also failed: ${messageOf(error)}.`,
|
|
565
|
-
action: `Do not restore the old Controller or assume the migration committed. Inspect the `
|
|
566
|
-
+ `schema_migrations ledger in "${home}/yui.db" with the staged/current Yui version, `
|
|
567
|
-
+ `then re-run "yui update" only after the ledger state is known.`,
|
|
568
|
-
version: staged.version,
|
|
569
|
-
schemaCurrent: false,
|
|
570
|
-
switched: false
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
return {
|
|
574
|
-
outcome: "ambiguous",
|
|
575
|
-
phase: "activate-storage",
|
|
576
|
-
message: `Storage activation result is unknown (${detail}); probing the Home also failed: ${messageOf(error)}.`,
|
|
577
|
-
action: `Do NOT assume the update succeeded or was a no-op. Manually inspect ${home} and any `
|
|
578
|
-
+ `"${home}.backup-*" sibling, "${upgradeReceiptPath(home)}", and `
|
|
579
|
-
+ `"${switchProgressPath(home)}"; if a receipt exists the `
|
|
580
|
-
+ `switch committed — verify the migrated Home with "yui doctor" before resuming, otherwise `
|
|
581
|
-
+ `restore the newest backup with mv before re-running "yui update".`,
|
|
582
|
-
version: staged.version,
|
|
583
|
-
schemaCurrent: false,
|
|
584
|
-
switched: false
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
if (expected.kind === "in-place"
|
|
588
|
-
&& sameSqliteLedgerHead(probe.sqliteSchemaHead, expected.migration.target)) {
|
|
589
|
-
return {
|
|
590
|
-
outcome: "ambiguous",
|
|
591
|
-
phase: "activate-storage",
|
|
592
|
-
message: `The SQLite transaction committed according to its migration ledger, but the activation process did not confirm success (${detail}). The new binary was NOT promoted.`,
|
|
593
|
-
action: "Do not restore the old Controller. Re-run `yui update` with this or a newer version; the current ledger will make the storage step a no-op, then the binary and Controller handoff can finish.",
|
|
594
|
-
version: staged.version,
|
|
595
|
-
schemaCurrent: probe.schemaCurrent,
|
|
596
|
-
switched: false
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
if (expected.kind === "in-place"
|
|
600
|
-
&& sameSqliteLedgerHead(probe.sqliteSchemaHead, expected.migration.current)) {
|
|
601
|
-
const failure = {
|
|
602
|
-
outcome: "aborted",
|
|
603
|
-
phase: "activate-storage",
|
|
604
|
-
message: `The SQLite activation process did not confirm success (${detail}), but the atomic migration ledger proves that no schema transaction committed.`,
|
|
605
|
-
action: "The database remains at its previous schema. The captured Controller identity was restored when possible; retry the update after diagnosing the activation child.",
|
|
606
|
-
recoverable: true,
|
|
607
|
-
version: staged.version
|
|
608
|
-
};
|
|
609
|
-
resumeWrites();
|
|
610
|
-
return restoreBeforeSwitchOrReport(ports, home, lifecycle, undefined, false, failure);
|
|
611
|
-
}
|
|
612
|
-
if (expected.kind === "in-place") {
|
|
613
|
-
const actual = probe.sqliteSchemaHead === undefined
|
|
614
|
-
? "unavailable"
|
|
615
|
-
: `${probe.sqliteSchemaHead.version}:${probe.sqliteSchemaHead.checksum}`;
|
|
616
|
-
return {
|
|
617
|
-
outcome: "ambiguous",
|
|
618
|
-
phase: "activate-storage",
|
|
619
|
-
message: `SQLite activation did not confirm a result (${detail}), and its migration ledger head `
|
|
620
|
-
+ `(${actual}) matches neither the validated source nor target boundary.`,
|
|
621
|
-
action: `Do not restore the old Controller or resume writes. Inspect the schema_migrations ledger `
|
|
622
|
-
+ `in "${home}/yui.db" with the staged/current Yui version and resolve the unexpected `
|
|
623
|
-
+ `ledger state before re-running "yui update".`,
|
|
624
|
-
version: staged.version,
|
|
625
|
-
schemaCurrent: probe.schemaCurrent,
|
|
626
|
-
switched: false
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
if (probe.interrupted === true) {
|
|
630
|
-
// A partially-applied, interrupted switch: the original was moved to the
|
|
631
|
-
// backup and neither promotion nor rollback completed, so the Home path may
|
|
632
|
-
// be missing. This is the strongest "restore the backup now" signal — never a
|
|
633
|
-
// "verify the migrated Home" or a "recoverable no-op".
|
|
634
|
-
return {
|
|
635
|
-
outcome: "ambiguous",
|
|
636
|
-
phase: "activate-storage",
|
|
637
|
-
message: `Storage switch was INTERRUPTED mid-rename (${detail}); the Home may be missing and was `
|
|
638
|
-
+ `NOT left intact. The new binary was NOT promoted.`,
|
|
639
|
-
action: `Restore the timestamped backup to recover the original Home: `
|
|
640
|
-
+ `mv "${probe.backupPath ?? "<home>.backup-*"}" "${home}". Do NOT resume writes until it is `
|
|
641
|
-
+ `restored; inspect "${switchProgressPath(home)}" for the interrupted phase, then re-run "yui update".`,
|
|
642
|
-
version: staged.version,
|
|
643
|
-
schemaCurrent: probe.schemaCurrent,
|
|
644
|
-
switched: false,
|
|
645
|
-
...(probe.backupPath === undefined ? {} : { storageBackupPath: probe.backupPath })
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
if (probe.switched) {
|
|
649
|
-
// The switch provably committed; the binary was not promoted.
|
|
650
|
-
return {
|
|
651
|
-
outcome: "ambiguous",
|
|
652
|
-
phase: "activate-storage",
|
|
653
|
-
message: `Storage was switched (a completion receipt is present) but the activation process `
|
|
654
|
-
+ `did not confirm success (${detail}). The new binary was NOT promoted.`,
|
|
655
|
-
action: `Verify the migrated Home before resuming writes: run "yui doctor"`
|
|
656
|
-
+ `${probe.backupPath === undefined ? "" : ` (its timestamped backup is ${probe.backupPath})`}. `
|
|
657
|
-
+ `Inspect "${upgradeReceiptPath(home)}" and "${switchProgressPath(home)}" before deciding. `
|
|
658
|
-
+ `If it is healthy, finish by re-running "yui update" to promote the binary; if not, restore `
|
|
659
|
-
+ `the backup with mv "${probe.backupPath ?? "<home>.backup-*"}" "${home}". Do NOT resume `
|
|
660
|
-
+ `writes with the old binary against the migrated Home.`,
|
|
661
|
-
version: staged.version,
|
|
662
|
-
schemaCurrent: probe.schemaCurrent,
|
|
663
|
-
switched: true,
|
|
664
|
-
...(probe.backupPath === undefined ? {} : { storageBackupPath: probe.backupPath })
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
// No receipt: the switch most likely never committed, but stdout was lost so we
|
|
668
|
-
// cannot assert "unchanged". Require an explicit, verified re-run.
|
|
669
|
-
return {
|
|
670
|
-
outcome: "ambiguous",
|
|
671
|
-
phase: "activate-storage",
|
|
672
|
-
message: `Storage activation did not confirm a result (${detail}) and no completion receipt was found`
|
|
673
|
-
+ `${probe.schemaCurrent ? "; the Home currently loads at the current schema" : ""}.`,
|
|
674
|
-
action: `The switch most likely did not commit, but this was not confirmed. Verify with "yui doctor" `
|
|
675
|
-
+ `and check for any "${home}.backup-*" sibling, "${upgradeReceiptPath(home)}", and `
|
|
676
|
-
+ `"${switchProgressPath(home)}" before retrying; then re-run "yui update". Do `
|
|
677
|
-
+ `NOT assume the update completed.`,
|
|
678
|
-
version: staged.version,
|
|
679
|
-
schemaCurrent: probe.schemaCurrent,
|
|
680
|
-
switched: false
|
|
681
|
-
};
|
|
272
|
+
function isRecord(value) {
|
|
273
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
682
274
|
}
|
|
683
275
|
function messageOf(error) {
|
|
684
276
|
return error instanceof Error ? error.message : String(error);
|
|
685
277
|
}
|
|
686
|
-
function sameSqliteLedgerHead(actual, expected) {
|
|
687
|
-
return actual?.version === expected.version && actual.checksum === expected.checksum;
|
|
688
|
-
}
|
|
689
|
-
function isValidBackupPath(value) {
|
|
690
|
-
return typeof value === "string"
|
|
691
|
-
&& value.length > 0
|
|
692
|
-
&& value.trim() === value
|
|
693
|
-
&& !value.includes("\0")
|
|
694
|
-
&& isAbsolute(value);
|
|
695
|
-
}
|
|
696
|
-
function isRecord(value) {
|
|
697
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
698
|
-
}
|