@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
package/dist/cli/updatePorts.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* `npm install --global --prefix <tmp>`, so the live global install is never
|
|
8
8
|
* touched until the binary-activation step. Preflight invokes the STAGED binary's
|
|
9
9
|
* internal `yui upgrade --update-preflight` contract so the target version
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* proves that the Home already implements the exact current contract. After
|
|
11
|
+
* the parent stops the exact old Controller, only the binary is promoted;
|
|
12
|
+
* update never rewrites or switches storage. Post-verify invokes the actually
|
|
13
|
+
* activated global binary.
|
|
14
14
|
*
|
|
15
15
|
* Two hardening guarantees this module enforces:
|
|
16
16
|
*
|
|
@@ -23,26 +23,16 @@
|
|
|
23
23
|
* global `yui` (via `npm prefix -g`), runs its `--json doctor`, AND confirms
|
|
24
24
|
* the promoted binary's reported version matches the staged version. A
|
|
25
25
|
* mismatch fails closed.
|
|
26
|
-
*
|
|
27
|
-
* And the ambiguity guarantee (P1-2): `activateStorage` returns `ambiguous`
|
|
28
|
-
* (never a false "unchanged") when the child leaves no parseable receipt, and
|
|
29
|
-
* `probeStorage` reads the durable receipt + backup + schema so the orchestrator
|
|
30
|
-
* can resolve the true state.
|
|
31
26
|
*/
|
|
32
27
|
import { spawnSync } from "node:child_process";
|
|
33
28
|
import { accessSync, constants, existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
34
29
|
import { tmpdir } from "node:os";
|
|
35
30
|
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
36
31
|
import { fileURLToPath } from "node:url";
|
|
37
|
-
import Database from "better-sqlite3";
|
|
38
32
|
import { runtimeError } from "../errors/cliError.js";
|
|
39
33
|
import { isConcreteVersion } from "../domain/validation.js";
|
|
40
34
|
import { STORAGE_DOCTOR_CHECK_NAMES } from "../doctor/doctor.js";
|
|
41
35
|
import { acquireHandoverLock } from "../release/runtimeRelease.js";
|
|
42
|
-
import { inspectStorageSchema } from "../storage/storageSchema.js";
|
|
43
|
-
import { placeUpgradeFence } from "../storage/upgradeFence.js";
|
|
44
|
-
import { correlateUpgradeReceipt } from "../storage/upgrade/upgradeOrchestrator.js";
|
|
45
|
-
import { readSwitchProgress } from "../storage/upgrade/switchProgress.js";
|
|
46
36
|
const PACKAGE_NAME = "@zq-silk/yui";
|
|
47
37
|
const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
|
|
48
38
|
function resolveExecutable(command, environmentPath) {
|
|
@@ -94,29 +84,11 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
94
84
|
// deliberately not persisted as a retry or recovery protocol.
|
|
95
85
|
let verifiedActivatedBinary;
|
|
96
86
|
let verifiedActivatedVersion;
|
|
97
|
-
let storageFenceOwnerPid;
|
|
98
87
|
const stopReplacementController = (home, pid) => (stopReplacementControllerForUpdate(home, pid, environment, run));
|
|
99
88
|
return {
|
|
100
89
|
beginControllerHandover(home) {
|
|
101
90
|
return acquireHandoverLock(home).release;
|
|
102
91
|
},
|
|
103
|
-
beginStorageWriteFence(home) {
|
|
104
|
-
const release = placeUpgradeFence(home, {
|
|
105
|
-
reason: "update storage activation in progress",
|
|
106
|
-
createdAt: new Date().toISOString(),
|
|
107
|
-
ownerPid: process.pid
|
|
108
|
-
});
|
|
109
|
-
storageFenceOwnerPid = process.pid;
|
|
110
|
-
let released = false;
|
|
111
|
-
return () => {
|
|
112
|
-
if (released)
|
|
113
|
-
return;
|
|
114
|
-
release();
|
|
115
|
-
released = true;
|
|
116
|
-
if (storageFenceOwnerPid === process.pid)
|
|
117
|
-
storageFenceOwnerPid = undefined;
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
92
|
stage(version) {
|
|
121
93
|
// A caller that names a version (the release workflow, which freezes the
|
|
122
94
|
// exact version in its plan) installs THAT version — never a moving
|
|
@@ -165,25 +137,6 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
165
137
|
const result = run(staged.binaryPath, ["--json", "upgrade", "--update-preflight"], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
|
|
166
138
|
return interpretPreflight(result);
|
|
167
139
|
},
|
|
168
|
-
activateStorage(staged, home) {
|
|
169
|
-
const result = run(staged.binaryPath, ["--json", "upgrade"], {
|
|
170
|
-
cwd: process.cwd(),
|
|
171
|
-
// The parent update process captures/stops/drains the old Controller
|
|
172
|
-
// before invoking the staged child. Mark this internal call so the
|
|
173
|
-
// child performs storage migration only and never starts a Controller
|
|
174
|
-
// from the temporary staging installation.
|
|
175
|
-
env: {
|
|
176
|
-
...environment,
|
|
177
|
-
YUI_HOME: home,
|
|
178
|
-
YUI_UPDATE_EXTERNALLY_QUIESCED: "1",
|
|
179
|
-
...(storageFenceOwnerPid === undefined
|
|
180
|
-
? {}
|
|
181
|
-
: { YUI_UPDATE_HANDOVER_OWNER_PID: String(storageFenceOwnerPid) })
|
|
182
|
-
},
|
|
183
|
-
shell: false
|
|
184
|
-
});
|
|
185
|
-
return interpretActivation(result);
|
|
186
|
-
},
|
|
187
140
|
activateBinary(staged) {
|
|
188
141
|
verifiedActivatedBinary = undefined;
|
|
189
142
|
verifiedActivatedVersion = undefined;
|
|
@@ -200,7 +153,7 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
200
153
|
if (activeBinary === null || !existsSync(activeBinary)) {
|
|
201
154
|
throw runtimeError("Post-update health check failed: could not locate the activated global `yui` binary.");
|
|
202
155
|
}
|
|
203
|
-
// 1) Health check the
|
|
156
|
+
// 1) Health check the unchanged current Home through the activated binary.
|
|
204
157
|
// POST-VERIFY PARSES THE MACHINE-READABLE RESULT FIRST, THEN THE EXIT STATUS
|
|
205
158
|
// (R2-F2). `yui --json doctor` deliberately sets a non-zero exit when storage
|
|
206
159
|
// is unhealthy, so interpreting the exit status before the envelope would
|
|
@@ -237,64 +190,6 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
237
190
|
verifiedActivatedBinary = activeBinary;
|
|
238
191
|
verifiedActivatedVersion = activeVersion;
|
|
239
192
|
},
|
|
240
|
-
probeStorage(home) {
|
|
241
|
-
// Resolve an ambiguous activation from durable on-disk evidence (P1-2),
|
|
242
|
-
// but only trust a receipt that CORRESPONDS to the current Home/backup
|
|
243
|
-
// (P2-6): a leftover receipt from a prior attempt, a different Home, or one
|
|
244
|
-
// whose backup was already restored/cleaned is NOT evidence that THIS
|
|
245
|
-
// attempt's switch committed. When it does not correspond, fall back to the
|
|
246
|
-
// on-disk schema and report `switched: false` so the caller re-probes the
|
|
247
|
-
// real state instead of giving a recovery instruction from a stale receipt.
|
|
248
|
-
const schema = inspectStorageSchema(home);
|
|
249
|
-
const sqliteSchemaHead = inspectSqliteSchemaHead(home);
|
|
250
|
-
// A crash mid-switch leaves a durable progress marker. A marker of ANY phase
|
|
251
|
-
// — `backing-up`, `promoting`, or `interrupted` — is only actionable as an
|
|
252
|
-
// interrupted switch when the FILESYSTEM still corroborates it: the backup
|
|
253
|
-
// exists AND the Home is not in place (missing/uninitialized), so the
|
|
254
|
-
// authoritative data lives only at the backup and the recovery is a precise
|
|
255
|
-
// restore. This gate now applies to `interrupted` too (R2-F3): a leftover
|
|
256
|
-
// `interrupted` marker after a manual recovery — Home already restored, or
|
|
257
|
-
// the backup already gone — must NOT be trusted to emit a restore path; we
|
|
258
|
-
// ignore the stale marker and re-probe the real state below.
|
|
259
|
-
const progress = readSwitchProgress(home);
|
|
260
|
-
if (progress !== null) {
|
|
261
|
-
const backupPresent = progress.backupPath !== undefined
|
|
262
|
-
&& existsSync(progress.backupPath);
|
|
263
|
-
// The Home is "in place" only when storage is actually initialized there;
|
|
264
|
-
// a missing/uninitialized Home after a mid-switch crash means the data is
|
|
265
|
-
// at the backup.
|
|
266
|
-
const homeInitialized = schema.status !== "uninitialized";
|
|
267
|
-
if (backupPresent && !homeInitialized) {
|
|
268
|
-
// Original at the backup, Home missing/uninitialized: recover by restore.
|
|
269
|
-
// The Home is uninitialized here, so it is definitively not current.
|
|
270
|
-
return {
|
|
271
|
-
switched: false,
|
|
272
|
-
interrupted: true,
|
|
273
|
-
schemaCurrent: false,
|
|
274
|
-
...(sqliteSchemaHead === undefined ? {} : { sqliteSchemaHead }),
|
|
275
|
-
...(progress.backupPath === undefined ? {} : { backupPath: progress.backupPath })
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
// Otherwise the marker is stale relative to the current filesystem (Home
|
|
279
|
-
// intact, or no backup to restore). Do not emit a restore path from it —
|
|
280
|
-
// fall through and reconcile against the receipt/schema below.
|
|
281
|
-
}
|
|
282
|
-
const correlation = correlateUpgradeReceipt(home);
|
|
283
|
-
if (!correlation.corresponds) {
|
|
284
|
-
return {
|
|
285
|
-
switched: false,
|
|
286
|
-
schemaCurrent: schema.status === "current",
|
|
287
|
-
...(sqliteSchemaHead === undefined ? {} : { sqliteSchemaHead })
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
const receipt = correlation.receipt;
|
|
291
|
-
return {
|
|
292
|
-
switched: true,
|
|
293
|
-
schemaCurrent: schema.status === "current",
|
|
294
|
-
...(sqliteSchemaHead === undefined ? {} : { sqliteSchemaHead }),
|
|
295
|
-
...(receipt.backupPath === undefined ? {} : { backupPath: receipt.backupPath })
|
|
296
|
-
};
|
|
297
|
-
},
|
|
298
193
|
cleanup(staged) {
|
|
299
194
|
if (staged.stagingPath !== undefined) {
|
|
300
195
|
rmSync(staged.stagingPath, { recursive: true, force: true });
|
|
@@ -312,7 +207,6 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
312
207
|
stopController(home, expectedPid) {
|
|
313
208
|
return stopControllerForUpdate(home, expectedPid, environment, spawn);
|
|
314
209
|
},
|
|
315
|
-
stopReplacementController,
|
|
316
210
|
startController(home) {
|
|
317
211
|
if (verifiedActivatedBinary === undefined || verifiedActivatedVersion === undefined) {
|
|
318
212
|
throw runtimeError("Replacement Controller cannot start before the activated global binary has "
|
|
@@ -325,39 +219,6 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
325
219
|
}
|
|
326
220
|
};
|
|
327
221
|
}
|
|
328
|
-
/**
|
|
329
|
-
* Read the generic durable SQLite ledger without asking the old parent binary
|
|
330
|
-
* to understand the staged release's migration registry. The staged preflight
|
|
331
|
-
* supplies the validated source and target heads used to interpret this value.
|
|
332
|
-
*/
|
|
333
|
-
function inspectSqliteSchemaHead(home) {
|
|
334
|
-
const path = join(home, "yui.db");
|
|
335
|
-
if (!existsSync(path))
|
|
336
|
-
return undefined;
|
|
337
|
-
const db = new Database(path, { readonly: true, fileMustExist: true });
|
|
338
|
-
try {
|
|
339
|
-
db.pragma("query_only = ON");
|
|
340
|
-
const rows = db.prepare("SELECT version, checksum FROM schema_migrations ORDER BY version").all();
|
|
341
|
-
if (rows.length === 0)
|
|
342
|
-
throw new Error("SQLite migration ledger is empty.");
|
|
343
|
-
for (let index = 0; index < rows.length; index += 1) {
|
|
344
|
-
const row = rows[index];
|
|
345
|
-
if (row.version !== index + 1
|
|
346
|
-
|| typeof row.checksum !== "string"
|
|
347
|
-
|| row.checksum.length === 0) {
|
|
348
|
-
throw new Error("SQLite migration ledger is not a contiguous checksummed prefix.");
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
const head = rows.at(-1);
|
|
352
|
-
return {
|
|
353
|
-
version: head.version,
|
|
354
|
-
checksum: head.checksum
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
finally {
|
|
358
|
-
db.close();
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
222
|
const UPDATE_CLI_PATH = fileURLToPath(new URL("../cli.js", import.meta.url));
|
|
362
223
|
const UPDATE_CLIENT_RUNTIME_PATH = fileURLToPath(new URL("../controller/clientRuntime.js", import.meta.url));
|
|
363
224
|
const UPDATE_CONTROLLER_RECONCILIATION_PATH = fileURLToPath(new URL("../controller/updateReconciliation.js", import.meta.url));
|
|
@@ -921,90 +782,19 @@ function interpretPreflight(result) {
|
|
|
921
782
|
...(data.sceneUnchanged === true ? { sceneUnchanged: true } : {})
|
|
922
783
|
};
|
|
923
784
|
}
|
|
924
|
-
/**
|
|
785
|
+
/** Only the exact current storage contract is a green update preflight. */
|
|
925
786
|
function parseUpdatePreflightResult(data) {
|
|
926
|
-
|
|
927
|
-
const stepCount = data.stepCount;
|
|
928
|
-
if ((status !== "already-current"
|
|
929
|
-
&& status !== "compatible"
|
|
930
|
-
&& status !== "in-place-migration"
|
|
931
|
-
&& status !== "migration-required")
|
|
932
|
-
|| !Number.isSafeInteger(stepCount)
|
|
933
|
-
|| stepCount < 0) {
|
|
787
|
+
if (data.status !== "already-current" || data.stepCount !== 0)
|
|
934
788
|
return null;
|
|
935
|
-
}
|
|
936
789
|
const homeClassification = data.classification;
|
|
937
790
|
if (!isRecord(homeClassification) || !isRecord(homeClassification.classification))
|
|
938
791
|
return null;
|
|
939
792
|
const classification = homeClassification.classification;
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
: status === "compatible"
|
|
943
|
-
? { verdict: "COMPATIBLE", classificationStatus: "compatible-old" }
|
|
944
|
-
: { verdict: "MIGRATABLE", classificationStatus: "migration-required" };
|
|
945
|
-
if (classification.verdict !== expected.verdict
|
|
946
|
-
|| classification.status !== expected.classificationStatus
|
|
947
|
-
|| (status === "already-current"
|
|
948
|
-
? stepCount !== 0
|
|
949
|
-
: classification.stepCount !== stepCount || stepCount < 1)) {
|
|
793
|
+
if (classification.verdict !== "USABLE"
|
|
794
|
+
|| classification.status !== "current") {
|
|
950
795
|
return null;
|
|
951
796
|
}
|
|
952
|
-
|
|
953
|
-
return { status };
|
|
954
|
-
const evidence = status === "compatible"
|
|
955
|
-
? `${stepCount} compatible step(s) classified and the compatible source validated in memory`
|
|
956
|
-
: status === "in-place-migration"
|
|
957
|
-
? `${stepCount} SQLite migration step(s) classified for one in-place transaction and the offline runtime inventory confirmed clear`
|
|
958
|
-
: `${stepCount} offline migration step(s) classified and the offline runtime inventory confirmed clear`;
|
|
959
|
-
const summary = `${evidence}. `
|
|
960
|
-
+ "No staged Home or staged-output loader validation was performed during update preflight.";
|
|
961
|
-
if (status === "in-place-migration") {
|
|
962
|
-
const sqliteMigration = parseSqliteMigrationBoundary(data.sqliteMigration, stepCount);
|
|
963
|
-
if (sqliteMigration === null)
|
|
964
|
-
return null;
|
|
965
|
-
return {
|
|
966
|
-
status,
|
|
967
|
-
summary,
|
|
968
|
-
sqliteMigration
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
return {
|
|
972
|
-
status,
|
|
973
|
-
summary
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
function parseSqliteMigrationBoundary(value, stepCount) {
|
|
977
|
-
if (!isRecord(value))
|
|
978
|
-
return null;
|
|
979
|
-
const currentVersion = value.currentVersion;
|
|
980
|
-
const currentChecksum = value.currentChecksum;
|
|
981
|
-
const targetVersion = value.targetVersion;
|
|
982
|
-
const targetChecksum = value.targetChecksum;
|
|
983
|
-
const pendingVersions = value.pendingVersions;
|
|
984
|
-
if (!Number.isSafeInteger(currentVersion)
|
|
985
|
-
|| currentVersion < 1
|
|
986
|
-
|| typeof currentChecksum !== "string"
|
|
987
|
-
|| currentChecksum.length === 0
|
|
988
|
-
|| !Number.isSafeInteger(targetVersion)
|
|
989
|
-
|| targetVersion <= currentVersion
|
|
990
|
-
|| typeof targetChecksum !== "string"
|
|
991
|
-
|| targetChecksum.length === 0
|
|
992
|
-
|| !Array.isArray(pendingVersions)
|
|
993
|
-
|| pendingVersions.length !== stepCount
|
|
994
|
-
|| targetVersion !== currentVersion + stepCount
|
|
995
|
-
|| pendingVersions.some((version, index) => !Number.isSafeInteger(version) || version !== currentVersion + index + 1)) {
|
|
996
|
-
return null;
|
|
997
|
-
}
|
|
998
|
-
return {
|
|
999
|
-
current: {
|
|
1000
|
-
version: currentVersion,
|
|
1001
|
-
checksum: currentChecksum
|
|
1002
|
-
},
|
|
1003
|
-
target: {
|
|
1004
|
-
version: targetVersion,
|
|
1005
|
-
checksum: targetChecksum
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
797
|
+
return { status: "already-current" };
|
|
1008
798
|
}
|
|
1009
799
|
function parseUpdateBlockers(value) {
|
|
1010
800
|
if (value === undefined)
|
|
@@ -1016,14 +806,14 @@ function parseUpdateBlockers(value) {
|
|
|
1016
806
|
if (!isRecord(item) || typeof item.reason !== "string" || item.reason.length === 0) {
|
|
1017
807
|
return undefined;
|
|
1018
808
|
}
|
|
1019
|
-
const optional = ["taskId", "roleName", "
|
|
809
|
+
const optional = ["taskId", "roleName", "turnId", "nativeSessionId", "launchId"];
|
|
1020
810
|
if (optional.some((key) => item[key] !== undefined && typeof item[key] !== "string")) {
|
|
1021
811
|
return undefined;
|
|
1022
812
|
}
|
|
1023
813
|
parsed.push({
|
|
1024
814
|
...(typeof item.taskId === "string" ? { taskId: item.taskId } : {}),
|
|
1025
815
|
...(typeof item.roleName === "string" ? { roleName: item.roleName } : {}),
|
|
1026
|
-
...(typeof item.
|
|
816
|
+
...(typeof item.turnId === "string" ? { turnId: item.turnId } : {}),
|
|
1027
817
|
...(typeof item.nativeSessionId === "string"
|
|
1028
818
|
? { nativeSessionId: item.nativeSessionId }
|
|
1029
819
|
: {}),
|
|
@@ -1033,114 +823,6 @@ function parseUpdateBlockers(value) {
|
|
|
1033
823
|
}
|
|
1034
824
|
return parsed;
|
|
1035
825
|
}
|
|
1036
|
-
function interpretActivation(result) {
|
|
1037
|
-
// A spawn transport error (could not even run) is a clean pre-switch failure.
|
|
1038
|
-
if (result.error !== undefined) {
|
|
1039
|
-
return {
|
|
1040
|
-
status: "ambiguous",
|
|
1041
|
-
detail: `the activation process could not be run: ${result.error.message}`
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
// Require a valid `{ ok:true, data }` success envelope (R3-F3). Killed by a
|
|
1045
|
-
// signal, no parseable JSON, or an `ok:false`/malformed envelope: the child may
|
|
1046
|
-
// have died after the atomic switch but before printing a valid result. This is
|
|
1047
|
-
// AMBIGUOUS, never a false "recoverable/unchanged" (P1-2).
|
|
1048
|
-
const data = parseSuccessEnvelopeData(result);
|
|
1049
|
-
if (data === null) {
|
|
1050
|
-
const how = result.signal !== null
|
|
1051
|
-
? `terminated by ${result.signal}`
|
|
1052
|
-
: result.status === null
|
|
1053
|
-
? "terminated without an exit code"
|
|
1054
|
-
: `exited with status ${result.status} and no valid success envelope`;
|
|
1055
|
-
return {
|
|
1056
|
-
status: "ambiguous",
|
|
1057
|
-
detail: `the activation process ${how}`
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
const outcome = typeof data.outcome === "string" ? data.outcome : undefined;
|
|
1061
|
-
// EXIT STATUS / OUTCOME CONSISTENCY (P1-2)
|
|
1062
|
-
// A success-class outcome is only trustworthy when the child ALSO exited 0. A
|
|
1063
|
-
// contradiction (e.g. stdout says `upgraded` but the process exited non-zero)
|
|
1064
|
-
// means the child's own contract was violated mid-flight — the switch may or
|
|
1065
|
-
// may not have committed — so it is AMBIGUOUS, never a false success. Blocker-
|
|
1066
|
-
// class outcomes are exempt: `yui upgrade` deliberately exits non-zero (5) for
|
|
1067
|
-
// a clean `blocked`, so a non-zero exit there is expected and consistent.
|
|
1068
|
-
if ((outcome === "already-current" || outcome === "upgraded") && result.status !== 0) {
|
|
1069
|
-
return {
|
|
1070
|
-
status: "ambiguous",
|
|
1071
|
-
detail: `the activation process reported outcome=${outcome} but exited with status `
|
|
1072
|
-
+ `${result.status ?? "null"} (a success outcome must exit 0); the switch state is unknown`
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
if (outcome === "already-current")
|
|
1076
|
-
return { status: "already-current" };
|
|
1077
|
-
if (outcome === "upgraded") {
|
|
1078
|
-
if (data.migrationMode === "in-place" && data.backupPath === undefined) {
|
|
1079
|
-
return { status: "migrated-in-place" };
|
|
1080
|
-
}
|
|
1081
|
-
const backupPath = data.backupPath;
|
|
1082
|
-
if (typeof backupPath !== "string"
|
|
1083
|
-
|| backupPath.length === 0
|
|
1084
|
-
|| backupPath.trim() !== backupPath
|
|
1085
|
-
|| backupPath.includes("\0")
|
|
1086
|
-
|| !isAbsolute(backupPath)) {
|
|
1087
|
-
return {
|
|
1088
|
-
status: "ambiguous",
|
|
1089
|
-
detail: "the activation process reported outcome=upgraded without a non-empty absolute backupPath"
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
return {
|
|
1093
|
-
status: "migrated",
|
|
1094
|
-
backupPath
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
if (outcome === "blocked") {
|
|
1098
|
-
const stage = typeof data?.stage === "string" ? data.stage : "activate-storage";
|
|
1099
|
-
// A `switch-ambiguous` blocker is NOT a clean, recoverable refusal (P1-4):
|
|
1100
|
-
// the upgrade's atomic switch was left partially applied (original moved to
|
|
1101
|
-
// backup, promotion + rollback both failed). Route it to AMBIGUOUS so the
|
|
1102
|
-
// orchestrator probes the interrupted marker and reports a restore, never a
|
|
1103
|
-
// false "the current install and Home remain usable".
|
|
1104
|
-
if (stage === "switch-ambiguous") {
|
|
1105
|
-
return {
|
|
1106
|
-
status: "ambiguous",
|
|
1107
|
-
detail: typeof data?.message === "string"
|
|
1108
|
-
? data.message
|
|
1109
|
-
: "the storage switch was left partially applied"
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
// A post-verify blocker is emitted only after the atomic Home switch has
|
|
1113
|
-
// committed. Treat it as AMBIGUOUS in the parent update flow so a stopped
|
|
1114
|
-
// old Controller is never restored against the migrated Home; the receipt,
|
|
1115
|
-
// backup, and switch-progress marker are the recovery evidence.
|
|
1116
|
-
if (stage === "post-verify") {
|
|
1117
|
-
return {
|
|
1118
|
-
status: "ambiguous",
|
|
1119
|
-
detail: typeof data?.message === "string"
|
|
1120
|
-
? data.message
|
|
1121
|
-
: "storage switched but post-switch verification did not complete"
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
const blockers = parseUpdateBlockers(data.blockers);
|
|
1125
|
-
return {
|
|
1126
|
-
status: "blocked",
|
|
1127
|
-
stage,
|
|
1128
|
-
message: typeof data?.message === "string" ? data.message : "Storage activation was refused.",
|
|
1129
|
-
action: typeof data?.action === "string"
|
|
1130
|
-
? data.action
|
|
1131
|
-
: "Resolve the reported condition and retry.",
|
|
1132
|
-
...(blockers === undefined ? {} : { blockers }),
|
|
1133
|
-
...(typeof data.retryCommand === "string" ? { retryCommand: data.retryCommand } : {}),
|
|
1134
|
-
...(data.sceneUnchanged === true ? { sceneUnchanged: true } : {})
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
// Parseable JSON but an unrecognized outcome: we cannot classify it, so it is
|
|
1138
|
-
// ambiguous rather than silently treated as a clean refusal.
|
|
1139
|
-
return {
|
|
1140
|
-
status: "ambiguous",
|
|
1141
|
-
detail: `the activation process returned an unrecognized outcome (${String(outcome)})`
|
|
1142
|
-
};
|
|
1143
|
-
}
|
|
1144
826
|
/**
|
|
1145
827
|
* Extract the validated `data` object from a spawn result's `{ ok: true, data }`
|
|
1146
828
|
* JSON envelope, or `null` when the result is not a trustworthy success envelope.
|
|
@@ -1243,7 +925,7 @@ function resolveGlobalBinary(environment, spawn) {
|
|
|
1243
925
|
return join(prefix, "bin", "yui");
|
|
1244
926
|
}
|
|
1245
927
|
/**
|
|
1246
|
-
* Fail closed unless the doctor result
|
|
928
|
+
* Fail closed unless the doctor result proves the current Home's storage is
|
|
1247
929
|
* healthy (P1-3 / R2-F2). The envelope and storage checks are validated BEFORE
|
|
1248
930
|
* the exit status is interpreted, because `yui --json doctor` deliberately exits
|
|
1249
931
|
* non-zero on unhealthy storage — so keying off the exit first would reduce a
|
|
@@ -1344,7 +1026,7 @@ function assertDoctorStorageHealthy(result) {
|
|
|
1344
1026
|
const detail = [...blockingChecks, ...declaredBlockingChecks]
|
|
1345
1027
|
.map((c) => `${String(c.name)}=${String(c.status)} (${String(c.detail)})`)
|
|
1346
1028
|
.join("; ");
|
|
1347
|
-
throw runtimeError("Post-update health check failed: the
|
|
1029
|
+
throw runtimeError("Post-update health check failed: the current Home is not healthy per the activated "
|
|
1348
1030
|
+ `binary's doctor: ${detail || "(no detail)"}. The storage did not come up cleanly on `
|
|
1349
1031
|
+ "the new version. Restore the timestamped backup to recover the original Home before "
|
|
1350
1032
|
+ "resuming writes.");
|
|
@@ -1358,7 +1040,7 @@ function assertDoctorStorageHealthy(result) {
|
|
|
1358
1040
|
}
|
|
1359
1041
|
/** A fail-closed post-update health-check error for an unverifiable doctor result. */
|
|
1360
1042
|
function doctorUnverifiable(reason) {
|
|
1361
|
-
return runtimeError(`Post-update health check failed: ${reason}, so the
|
|
1043
|
+
return runtimeError(`Post-update health check failed: ${reason}, so the current Home cannot be confirmed `
|
|
1362
1044
|
+ "healthy. Investigate with `yui doctor` before resuming; if storage was migrated, restore "
|
|
1363
1045
|
+ "the timestamped backup to recover.");
|
|
1364
1046
|
}
|
|
@@ -1,22 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `yui upgrade`
|
|
3
|
-
*
|
|
4
|
-
* `yui upgrade --dry-run` first classifies read-only. Compatible-old validates
|
|
5
|
-
* through the in-memory current-model gate and returns without staging a Home;
|
|
6
|
-
* migration-required also proves the offline runtime inventory before it stages
|
|
7
|
-
* and validates a fresh copy. The hidden `--update-preflight` contract stops at
|
|
8
|
-
* classification, compatible-source validation, or offline inventory as required
|
|
9
|
-
* by the classified path, so a staged updater can run it while the old Controller
|
|
10
|
-
* remains live without a false staged-output validation claim. Execute enters
|
|
11
|
-
* fence/quiesce/switch only for the offline path. Only explicitly declared
|
|
12
|
-
* production paths can run; the aggregate `16→17` and execution record-family
|
|
13
|
-
* transitions remain offline migrations.
|
|
14
|
-
*
|
|
15
|
-
* Like `doctor`/`controller`, this command needs a Home but manages its own
|
|
16
|
-
* schema check (it must run against a non-current Home), so it is dispatched
|
|
17
|
-
* before the unconditional `requireStorageSchema` gate in `cli.ts`.
|
|
2
|
+
* `yui upgrade` is now a read-only storage admission diagnostic. This release
|
|
3
|
+
* opens only its exact current contract and never rewrites an historical Home.
|
|
18
4
|
*/
|
|
19
|
-
import { createProductionStorageRegistry } from "../storage/compatibleTaskStore.js";
|
|
20
5
|
import { latestStorageVersionState } from "../storage/upgrade/recordVersions.js";
|
|
21
6
|
import { runStorageUpgrade } from "../storage/upgrade/upgradeOrchestrator.js";
|
|
22
7
|
import { usageError } from "../errors/cliError.js";
|
|
@@ -25,20 +10,12 @@ import { usageError } from "../errors/cliError.js";
|
|
|
25
10
|
* updater's internal `--update-preflight` form, then returns rendered text,
|
|
26
11
|
* structured data, and an exit code (0 for a safe result, 5 for a blocker).
|
|
27
12
|
*/
|
|
28
|
-
export async function runUpgradeCommand(args, home
|
|
13
|
+
export async function runUpgradeCommand(args, home) {
|
|
29
14
|
const mode = parseUpgradeArgs(args);
|
|
30
15
|
const result = await runStorageUpgrade({
|
|
31
16
|
home,
|
|
32
|
-
registry: createProductionStorageRegistry(),
|
|
33
17
|
latest: latestStorageVersionState(),
|
|
34
|
-
mode
|
|
35
|
-
...(options.controllerLifecycle === undefined
|
|
36
|
-
? {}
|
|
37
|
-
: { controllerLifecycle: options.controllerLifecycle }),
|
|
38
|
-
...(options.externalUpgradeFenceOwnerPid === undefined
|
|
39
|
-
? {}
|
|
40
|
-
: { externalUpgradeFenceOwnerPid: options.externalUpgradeFenceOwnerPid }),
|
|
41
|
-
...(options.now === undefined ? {} : { now: options.now })
|
|
18
|
+
mode
|
|
42
19
|
});
|
|
43
20
|
return {
|
|
44
21
|
output: renderUpgradeResult(result, mode),
|
|
@@ -52,7 +29,7 @@ function parseUpgradeArgs(args) {
|
|
|
52
29
|
if (args.length === 1 && args[0] === "--dry-run")
|
|
53
30
|
return "dry-run";
|
|
54
31
|
// Intentionally omitted from public command help: this is the machine contract
|
|
55
|
-
// used by a staged `yui update`, not a replacement for user-facing dry-
|
|
32
|
+
// used by a staged `yui update`, not a replacement for user-facing dry-turn.
|
|
56
33
|
if (args.length === 1 && args[0] === "--update-preflight")
|
|
57
34
|
return "update-preflight";
|
|
58
35
|
throw usageError("Upgrade usage: yui upgrade [--dry-run]");
|
|
@@ -63,48 +40,14 @@ export function renderUpgradeResult(result, mode) {
|
|
|
63
40
|
switch (result.outcome) {
|
|
64
41
|
case "already-current":
|
|
65
42
|
return `${header}\nStorage is already at the current version; nothing to upgrade.`;
|
|
66
|
-
case "
|
|
67
|
-
return `${header}\
|
|
68
|
-
+ "No Home migration, backup, or switch is required.";
|
|
69
|
-
case "update-preflight": {
|
|
70
|
-
const evidence = result.status === "already-current"
|
|
71
|
-
? "four-state classification"
|
|
72
|
-
: result.status === "compatible"
|
|
73
|
-
? "four-state classification plus compatible-source validation"
|
|
74
|
-
: result.status === "in-place-migration"
|
|
75
|
-
? "SQLite ledger classification plus a clear offline runtime inventory"
|
|
76
|
-
: "four-state classification plus a clear offline runtime inventory";
|
|
77
|
-
return `${header}\nUpdate preflight: ${result.status} (${result.stepCount} step(s)); `
|
|
78
|
-
+ `${evidence}. No staged Home was created, `
|
|
79
|
-
+ "no staged-output loader validation was performed, and storage was not switched.";
|
|
80
|
-
}
|
|
81
|
-
case "dry-run": {
|
|
82
|
-
const steps = result.classification.sqliteMigration?.pendingVersions.length
|
|
83
|
-
?? (result.report.outcome === "dry-run" ? result.report.steps.length : 0);
|
|
84
|
-
return `${header}\nDry run: validated ${steps} migration step(s) through the loader gate. `
|
|
85
|
-
+ "Staged output discarded; storage was not switched.";
|
|
86
|
-
}
|
|
87
|
-
case "upgraded":
|
|
88
|
-
return result.migrationMode === "in-place"
|
|
89
|
-
? `${header}\nUpgraded SQLite in place in one transaction; no database rebuild or backup copy was created.`
|
|
90
|
-
: `${header}\nUpgraded storage. Original Home backed up at `
|
|
91
|
-
+ `${result.backupPath ?? "(unspecified)"}.`;
|
|
43
|
+
case "update-preflight":
|
|
44
|
+
return `${header}\nUpdate preflight: already-current (0 steps). Storage was not modified.`;
|
|
92
45
|
case "blocked": {
|
|
93
|
-
// Most blockers guarantee the source is untouched. A partial switch or a
|
|
94
|
-
// post-switch ambiguity explicitly carries the committed boundary and
|
|
95
|
-
// named recovery evidence; never print a false unchanged claim there.
|
|
96
|
-
const unchangedNote = result.storageCommitted === true
|
|
97
|
-
? "The SQLite transaction committed in place; the old Controller was not restored (see Action)."
|
|
98
|
-
: result.switchCommitted === true
|
|
99
|
-
? "The storage switch committed, but post-switch completion is ambiguous; the old Controller was not restored (see Action and recovery evidence)."
|
|
100
|
-
: result.stage === "switch-ambiguous"
|
|
101
|
-
? "The switch did not complete and could not be rolled back; the authoritative Home is NOT intact (see Action)."
|
|
102
|
-
: "Storage was not switched; the authoritative Home is unchanged.";
|
|
103
46
|
return [
|
|
104
47
|
header,
|
|
105
48
|
`${mode === "dry-run" ? "Dry run" : mode === "update-preflight" ? "Update preflight" : "Upgrade"} blocked at ${result.stage}: ${result.message}`,
|
|
106
49
|
`Action: ${result.action}`,
|
|
107
|
-
|
|
50
|
+
"The authoritative Home is unchanged."
|
|
108
51
|
].join("\n");
|
|
109
52
|
}
|
|
110
53
|
}
|