@zq-silk/yui 0.14.1 → 0.15.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 +91 -47
- package/README.md +275 -145
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateCommand.js +17 -9
- package/dist/cli/updateOrchestrator.js +87 -19
- package/dist/cli/updatePorts.js +79 -13
- package/dist/cli/upgradeCommand.js +105 -23
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +6 -5
- package/dist/doctor/doctor.js +37 -14
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +16 -82
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/observability/runtimeIdentity.js +48 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/release/runtimeRelease.js +9 -1
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +22 -296
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/currentTaskStore.js +6 -4
- package/dist/storage/sqliteSchema.js +135 -80
- package/dist/storage/sqliteStore.js +69 -52
- package/dist/storage/storageSchema.js +92 -230
- package/dist/storage/storageVersions.js +12 -13
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/version.js +3 -3
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +9 -9
- package/i18n/README.zh-CN.md +189 -74
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +22 -0
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/storage/upgrade/recordVersions.js +0 -82
- package/dist/task/repairWave.js +0 -150
|
@@ -5,9 +5,9 @@ import { runUpdate } from "./updateOrchestrator.js";
|
|
|
5
5
|
* Run `yui update` as a side-by-side, recoverable orchestration.
|
|
6
6
|
*
|
|
7
7
|
* The new package is staged beside the live install and used to run a read-only
|
|
8
|
-
* preflight against the target Home.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* preflight against the target Home. A current Home proceeds directly; a Home
|
|
9
|
+
* inside the staged release's supported range is migrated after the exact
|
|
10
|
+
* Controller handoff and before post-update verification.
|
|
11
11
|
*
|
|
12
12
|
* Returns a process exit code: 0 on success or already-current, 5 on abort.
|
|
13
13
|
*/
|
|
@@ -27,15 +27,23 @@ export function renderUpdateResult(result) {
|
|
|
27
27
|
case "already-current":
|
|
28
28
|
return "Yui is already up to date; nothing to install.";
|
|
29
29
|
case "updated":
|
|
30
|
-
return
|
|
30
|
+
return result.backupPath === undefined
|
|
31
|
+
? `Updated Yui to ${result.version}; storage was already current.`
|
|
32
|
+
: `Updated Yui to ${result.version} and migrated storage. Backup: ${result.backupPath}`;
|
|
31
33
|
case "aborted":
|
|
32
34
|
return [
|
|
33
35
|
`Update aborted during ${result.phase}: ${result.message}`,
|
|
34
|
-
result.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
result.phase === "migrate-storage"
|
|
37
|
+
|| (result.phase === "post-verify" && result.recoverable)
|
|
38
|
+
? "The target binary is installed; the Home remains quiesced pending successful verification."
|
|
39
|
+
: result.recoverable
|
|
40
|
+
? "The current install and Home remain usable."
|
|
41
|
+
: result.phase === "activate-binary"
|
|
42
|
+
? "The Home was unchanged, but binary health is unknown; do not assume the current install is usable."
|
|
43
|
+
: "Manual recovery is required (see below).",
|
|
44
|
+
...(result.backupPath === undefined
|
|
45
|
+
? []
|
|
46
|
+
: [`Storage backup: ${result.backupPath}`]),
|
|
39
47
|
`Action: ${result.action}`
|
|
40
48
|
].join("\n");
|
|
41
49
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Side-by-side `yui update` orchestration for the
|
|
2
|
+
* Side-by-side `yui update` orchestration for the supported storage range.
|
|
3
3
|
*
|
|
4
|
-
* The staged binary
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The staged binary proves either an exact-current Home or a complete
|
|
5
|
+
* migration path before the exact Controller is stopped. The replacement is
|
|
6
|
+
* then activated, any required storage migration is applied, and the result is
|
|
7
|
+
* verified before the Controller is restarted.
|
|
8
8
|
*/
|
|
9
9
|
export function runUpdate(ports, options) {
|
|
10
10
|
let staged;
|
|
@@ -63,6 +63,26 @@ function runStagedUpdate(ports, staged, home) {
|
|
|
63
63
|
...(preflight.sceneUnchanged === true ? { sceneUnchanged: true } : {})
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
if (preflight.status === "migration-ready" && ports.migrateStorage === undefined) {
|
|
67
|
+
return {
|
|
68
|
+
outcome: "aborted",
|
|
69
|
+
phase: "preflight",
|
|
70
|
+
message: "The staged binary requires a storage migration, but no migration operation is available.",
|
|
71
|
+
action: "Use the complete Yui updater or run the staged release's `yui upgrade` explicitly.",
|
|
72
|
+
recoverable: true,
|
|
73
|
+
version: staged.version
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (preflight.status === "migration-ready" && !hasCompleteControllerLifecycle(ports)) {
|
|
77
|
+
return {
|
|
78
|
+
outcome: "aborted",
|
|
79
|
+
phase: "preflight",
|
|
80
|
+
message: "Storage migration requires a complete, fenced Controller handoff.",
|
|
81
|
+
action: "Provide status, exact stop, replacement start, and exact restore operations.",
|
|
82
|
+
recoverable: true,
|
|
83
|
+
version: staged.version
|
|
84
|
+
};
|
|
85
|
+
}
|
|
66
86
|
let releaseHandover;
|
|
67
87
|
try {
|
|
68
88
|
releaseHandover = ports.beginControllerHandover?.(home);
|
|
@@ -81,13 +101,13 @@ function runStagedUpdate(ports, staged, home) {
|
|
|
81
101
|
const captured = captureControllerLifecycle(ports, staged.version, home);
|
|
82
102
|
if ("outcome" in captured)
|
|
83
103
|
return captured;
|
|
84
|
-
return activateAndVerify(ports, staged, home, captured.lifecycle);
|
|
104
|
+
return activateAndVerify(ports, staged, home, captured.lifecycle, preflight);
|
|
85
105
|
}
|
|
86
106
|
finally {
|
|
87
107
|
releaseHandover?.();
|
|
88
108
|
}
|
|
89
109
|
}
|
|
90
|
-
function activateAndVerify(ports, staged, home, lifecycle) {
|
|
110
|
+
function activateAndVerify(ports, staged, home, lifecycle, preflight) {
|
|
91
111
|
try {
|
|
92
112
|
ports.activateBinary(staged);
|
|
93
113
|
}
|
|
@@ -101,18 +121,46 @@ function activateAndVerify(ports, staged, home, lifecycle) {
|
|
|
101
121
|
version: staged.version
|
|
102
122
|
});
|
|
103
123
|
}
|
|
124
|
+
let migration;
|
|
125
|
+
if (preflight.status === "migration-ready") {
|
|
126
|
+
try {
|
|
127
|
+
migration = ports.migrateStorage(staged, home);
|
|
128
|
+
if (!isStorageMigrationResult(migration)) {
|
|
129
|
+
throw new Error("Storage migration returned a malformed result.");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
return {
|
|
134
|
+
outcome: "aborted",
|
|
135
|
+
phase: "migrate-storage",
|
|
136
|
+
message: `Storage migration failed: ${messageOf(error)}`,
|
|
137
|
+
action: "The target Yui binary is installed and the Home remains quiesced. "
|
|
138
|
+
+ "Resolve the reported problem, rerun `yui upgrade`, then verify "
|
|
139
|
+
+ "`yui doctor` before starting the Controller.",
|
|
140
|
+
recoverable: true,
|
|
141
|
+
version: staged.version
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
104
145
|
try {
|
|
105
146
|
ports.verify(staged, home);
|
|
106
147
|
}
|
|
107
148
|
catch (error) {
|
|
108
|
-
|
|
149
|
+
const storageMigrated = preflight.status === "migration-ready";
|
|
150
|
+
const failure = {
|
|
109
151
|
outcome: "aborted",
|
|
110
152
|
phase: "post-verify",
|
|
111
153
|
message: `Post-update health check failed: ${messageOf(error)}`,
|
|
112
154
|
action: binaryHealthUncertainAction(),
|
|
113
|
-
recoverable:
|
|
114
|
-
version: staged.version
|
|
115
|
-
|
|
155
|
+
recoverable: storageMigrated,
|
|
156
|
+
version: staged.version,
|
|
157
|
+
...(migration?.backupPath === undefined
|
|
158
|
+
? {}
|
|
159
|
+
: { backupPath: migration.backupPath })
|
|
160
|
+
};
|
|
161
|
+
return storageMigrated
|
|
162
|
+
? failure
|
|
163
|
+
: restoreControllerOrReport(ports, home, lifecycle, failure);
|
|
116
164
|
}
|
|
117
165
|
if (lifecycle.ensureRunning) {
|
|
118
166
|
try {
|
|
@@ -120,6 +168,7 @@ function activateAndVerify(ports, staged, home, lifecycle) {
|
|
|
120
168
|
}
|
|
121
169
|
catch (error) {
|
|
122
170
|
const unknownActive = isUnknownActiveControllerFailure(error);
|
|
171
|
+
const storageMigrated = preflight.status === "migration-ready";
|
|
123
172
|
const failure = {
|
|
124
173
|
outcome: "aborted",
|
|
125
174
|
phase: "post-verify",
|
|
@@ -128,19 +177,28 @@ function activateAndVerify(ports, staged, home, lifecycle) {
|
|
|
128
177
|
: "The replacement Controller could not start after activation and verification"}: ${messageOf(error)}.`,
|
|
129
178
|
action: unknownActive
|
|
130
179
|
? unknownActiveControllerAction(home)
|
|
131
|
-
:
|
|
132
|
-
? "Keep the Home quiesced
|
|
133
|
-
:
|
|
134
|
-
|
|
180
|
+
: storageMigrated
|
|
181
|
+
? "Keep the Home quiesced, verify the activated binary, then start the current Controller explicitly."
|
|
182
|
+
: lifecycle.wasRunning
|
|
183
|
+
? "Keep the Home quiesced and restore the captured Controller identity before retrying."
|
|
184
|
+
: "Verify the activated binary, then start the Controller explicitly.",
|
|
185
|
+
recoverable: storageMigrated && !unknownActive,
|
|
135
186
|
version: staged.version,
|
|
136
|
-
|
|
187
|
+
...(migration?.backupPath === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { backupPath: migration.backupPath }),
|
|
190
|
+
...(unknownActive ? { controllerOwnershipUnknown: true } : {})
|
|
137
191
|
};
|
|
138
|
-
return unknownActive
|
|
192
|
+
return unknownActive || storageMigrated
|
|
139
193
|
? failure
|
|
140
194
|
: restoreControllerOrReport(ports, home, lifecycle, failure);
|
|
141
195
|
}
|
|
142
196
|
}
|
|
143
|
-
return {
|
|
197
|
+
return {
|
|
198
|
+
outcome: "updated",
|
|
199
|
+
version: staged.version,
|
|
200
|
+
...(migration?.backupPath === undefined ? {} : { backupPath: migration.backupPath })
|
|
201
|
+
};
|
|
144
202
|
}
|
|
145
203
|
function captureControllerLifecycle(ports, version, home) {
|
|
146
204
|
const supplied = [
|
|
@@ -186,7 +244,7 @@ function captureControllerLifecycle(ports, version, home) {
|
|
|
186
244
|
return { lifecycle: { ensureRunning: true, wasRunning: false } };
|
|
187
245
|
}
|
|
188
246
|
if (!isPositivePid(status.pid) || !isControllerIdentity(status.identity)) {
|
|
189
|
-
return malformedControllerResult(version, "The running Controller did not expose an exact PID and
|
|
247
|
+
return malformedControllerResult(version, "The running Controller did not expose an exact PID and runtime generation identity.");
|
|
190
248
|
}
|
|
191
249
|
let stopped;
|
|
192
250
|
try {
|
|
@@ -257,6 +315,16 @@ function isControllerLifecycleStatus(value) {
|
|
|
257
315
|
function isControllerStopResult(value) {
|
|
258
316
|
return isRecord(value) && typeof value.stopped === "boolean";
|
|
259
317
|
}
|
|
318
|
+
function hasCompleteControllerLifecycle(ports) {
|
|
319
|
+
return ports.controllerStatus !== undefined
|
|
320
|
+
&& ports.stopController !== undefined
|
|
321
|
+
&& ports.startController !== undefined
|
|
322
|
+
&& ports.restoreController !== undefined;
|
|
323
|
+
}
|
|
324
|
+
function isStorageMigrationResult(value) {
|
|
325
|
+
return isRecord(value)
|
|
326
|
+
&& (value.backupPath === undefined || typeof value.backupPath === "string");
|
|
327
|
+
}
|
|
260
328
|
function isPositivePid(value) {
|
|
261
329
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
262
330
|
}
|
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
|
-
* proves that the Home
|
|
11
|
-
* the parent stops the exact old Controller,
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* proves that the Home is current or has a complete supported migration path.
|
|
11
|
+
* After the parent stops the exact old Controller, the binary is promoted,
|
|
12
|
+
* required migrations run through that same staged artifact, and the activated
|
|
13
|
+
* binary verifies the resulting current Home.
|
|
14
14
|
*
|
|
15
15
|
* Two hardening guarantees this module enforces:
|
|
16
16
|
*
|
|
@@ -147,13 +147,26 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
147
147
|
const result = run("npm", ["install", "--global", spec], { cwd: process.cwd(), env: environment, shell: false, stdio: "inherit" });
|
|
148
148
|
assertSpawnOk(result, "activate the new binary");
|
|
149
149
|
},
|
|
150
|
+
migrateStorage(staged, home) {
|
|
151
|
+
const result = run(staged.binaryPath, ["--json", "upgrade", "--update-apply"], {
|
|
152
|
+
cwd: process.cwd(),
|
|
153
|
+
env: {
|
|
154
|
+
...environment,
|
|
155
|
+
YUI_HOME: home,
|
|
156
|
+
YUI_UPDATE_HANDOVER_OWNER_PID: String(process.pid)
|
|
157
|
+
},
|
|
158
|
+
shell: false
|
|
159
|
+
});
|
|
160
|
+
return interpretStorageMigration(result);
|
|
161
|
+
},
|
|
150
162
|
verify(staged, home) {
|
|
151
163
|
// Verify the ACTUALLY-ACTIVATED global binary, not the staging path (P1-3).
|
|
152
164
|
const activeBinary = resolveGlobalBinary(environment, run);
|
|
153
165
|
if (activeBinary === null || !existsSync(activeBinary)) {
|
|
154
166
|
throw runtimeError("Post-update health check failed: could not locate the activated global `yui` binary.");
|
|
155
167
|
}
|
|
156
|
-
// 1) Health check the
|
|
168
|
+
// 1) Health check the current (possibly just migrated) Home through the
|
|
169
|
+
// activated binary.
|
|
157
170
|
// POST-VERIFY PARSES THE MACHINE-READABLE RESULT FIRST, THEN THE EXIT STATUS
|
|
158
171
|
// (R2-F2). `yui --json doctor` deliberately sets a non-zero exit when storage
|
|
159
172
|
// is unhealthy, so interpreting the exit status before the envelope would
|
|
@@ -514,6 +527,9 @@ function runControllerCommand(home, environment, spawn, method, cliBinary) {
|
|
|
514
527
|
env: {
|
|
515
528
|
...environment,
|
|
516
529
|
YUI_HOME: home,
|
|
530
|
+
// Lifecycle capture needs the schema-independent resource inventory,
|
|
531
|
+
// not the optional current-schema identity health verdict.
|
|
532
|
+
YUI_STATUS_IDENTITY: "0",
|
|
517
533
|
// This exact lifecycle child is part of the update process that owns
|
|
518
534
|
// the handover lock. Managed Sessions never receive this bypass.
|
|
519
535
|
YUI_UPDATE_HANDOVER_OWNER_PID: String(process.pid)
|
|
@@ -693,7 +709,7 @@ function assertActivatedControllerIdentity(identity, activatedBinary, activatedV
|
|
|
693
709
|
if (identity.executablePath !== process.execPath
|
|
694
710
|
|| identity.args.length !== 1
|
|
695
711
|
|| identity.args[0] !== expectedEntrypoint) {
|
|
696
|
-
throw new Error("Replacement Controller
|
|
712
|
+
throw new Error("Replacement Controller runtime generation identity does not match the activated global binary "
|
|
697
713
|
+ "runtime/entrypoint; refusing readiness.");
|
|
698
714
|
}
|
|
699
715
|
}
|
|
@@ -782,19 +798,69 @@ function interpretPreflight(result) {
|
|
|
782
798
|
...(data.sceneUnchanged === true ? { sceneUnchanged: true } : {})
|
|
783
799
|
};
|
|
784
800
|
}
|
|
785
|
-
/**
|
|
801
|
+
/** Accept either an exact current Home or a complete supported migration path. */
|
|
786
802
|
function parseUpdatePreflightResult(data) {
|
|
787
|
-
if (data.status !== "already-current"
|
|
803
|
+
if ((data.status !== "already-current" && data.status !== "migration-ready")
|
|
804
|
+
|| !Number.isSafeInteger(data.stepCount)
|
|
805
|
+
|| !Array.isArray(data.steps)
|
|
806
|
+
|| data.steps.length !== data.stepCount)
|
|
788
807
|
return null;
|
|
789
808
|
const homeClassification = data.classification;
|
|
790
809
|
if (!isRecord(homeClassification) || !isRecord(homeClassification.classification))
|
|
791
810
|
return null;
|
|
792
811
|
const classification = homeClassification.classification;
|
|
793
|
-
if (
|
|
794
|
-
|
|
812
|
+
if (data.status === "already-current") {
|
|
813
|
+
if (data.stepCount !== 0
|
|
814
|
+
|| classification.verdict !== "USABLE"
|
|
815
|
+
|| classification.status !== "current")
|
|
816
|
+
return null;
|
|
817
|
+
return { status: "already-current", stepCount: 0 };
|
|
818
|
+
}
|
|
819
|
+
if (data.stepCount <= 0
|
|
820
|
+
|| classification.verdict !== "MIGRATABLE"
|
|
821
|
+
|| classification.status !== "migration-ready"
|
|
822
|
+
|| !data.steps.every(isStorageMigrationStep))
|
|
795
823
|
return null;
|
|
824
|
+
return { status: "migration-ready", stepCount: data.stepCount };
|
|
825
|
+
}
|
|
826
|
+
function interpretStorageMigration(result) {
|
|
827
|
+
const data = parseSuccessEnvelopeData(result);
|
|
828
|
+
if (data === null) {
|
|
829
|
+
throw runtimeError("The staged binary did not return a successful storage migration result "
|
|
830
|
+
+ `(exit ${result.status ?? "null"}${result.signal === null ? "" : `, signal ${result.signal}`}).`);
|
|
831
|
+
}
|
|
832
|
+
if (data.outcome === "blocked" || data.outcome === "failed") {
|
|
833
|
+
const message = typeof data.message === "string"
|
|
834
|
+
? data.message
|
|
835
|
+
: "The staged binary refused the storage migration.";
|
|
836
|
+
const action = typeof data.action === "string" ? ` Action: ${data.action}` : "";
|
|
837
|
+
const backup = typeof data.backupPath === "string"
|
|
838
|
+
? ` Backup: ${data.backupPath}`
|
|
839
|
+
: "";
|
|
840
|
+
throw runtimeError(`${message}${action}${backup}`);
|
|
796
841
|
}
|
|
797
|
-
|
|
842
|
+
if (result.status !== 0) {
|
|
843
|
+
throw runtimeError(`The staged binary returned outcome=${String(data.outcome)} but exited `
|
|
844
|
+
+ `with status ${result.status ?? "null"}.`);
|
|
845
|
+
}
|
|
846
|
+
if (data.outcome === "already-current")
|
|
847
|
+
return {};
|
|
848
|
+
if (data.outcome !== "upgraded" || !isRecord(data.report)) {
|
|
849
|
+
throw runtimeError(`The staged binary returned unexpected storage migration outcome=${String(data.outcome)}.`);
|
|
850
|
+
}
|
|
851
|
+
const backupPath = data.report.backupPath;
|
|
852
|
+
if (typeof backupPath !== "string" || backupPath.length === 0) {
|
|
853
|
+
throw runtimeError("The staged binary did not report the required storage backup path.");
|
|
854
|
+
}
|
|
855
|
+
return { backupPath };
|
|
856
|
+
}
|
|
857
|
+
function isStorageMigrationStep(value) {
|
|
858
|
+
return isRecord(value)
|
|
859
|
+
&& Number.isSafeInteger(value.fromVersion)
|
|
860
|
+
&& Number.isSafeInteger(value.toVersion)
|
|
861
|
+
&& value.toVersion === value.fromVersion + 1
|
|
862
|
+
&& typeof value.name === "string"
|
|
863
|
+
&& value.name.length > 0;
|
|
798
864
|
}
|
|
799
865
|
function parseUpdateBlockers(value) {
|
|
800
866
|
if (value === undefined)
|
|
@@ -806,7 +872,7 @@ function parseUpdateBlockers(value) {
|
|
|
806
872
|
if (!isRecord(item) || typeof item.reason !== "string" || item.reason.length === 0) {
|
|
807
873
|
return undefined;
|
|
808
874
|
}
|
|
809
|
-
const optional = ["taskId", "roleName", "turnId", "nativeSessionId", "
|
|
875
|
+
const optional = ["taskId", "roleName", "turnId", "nativeSessionId", "runtimeGenerationId"];
|
|
810
876
|
if (optional.some((key) => item[key] !== undefined && typeof item[key] !== "string")) {
|
|
811
877
|
return undefined;
|
|
812
878
|
}
|
|
@@ -817,7 +883,7 @@ function parseUpdateBlockers(value) {
|
|
|
817
883
|
...(typeof item.nativeSessionId === "string"
|
|
818
884
|
? { nativeSessionId: item.nativeSessionId }
|
|
819
885
|
: {}),
|
|
820
|
-
...(typeof item.
|
|
886
|
+
...(typeof item.runtimeGenerationId === "string" ? { runtimeGenerationId: item.runtimeGenerationId } : {}),
|
|
821
887
|
reason: item.reason
|
|
822
888
|
});
|
|
823
889
|
}
|
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
/**
|
|
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.
|
|
4
|
-
*/
|
|
5
|
-
import { latestStorageVersionState } from "../storage/upgrade/recordVersions.js";
|
|
1
|
+
/** `yui upgrade` plans or applies the supported linear storage migration chain. */
|
|
6
2
|
import { runStorageUpgrade } from "../storage/upgrade/upgradeOrchestrator.js";
|
|
7
|
-
import {
|
|
3
|
+
import { ensureFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
|
|
4
|
+
import { runtimeError, usageError } from "../errors/cliError.js";
|
|
5
|
+
import { acquireHandoverLock, isForeignHandoverLockHeld, isHandoverLockHeld } from "../release/runtimeRelease.js";
|
|
8
6
|
/**
|
|
9
7
|
* Run the upgrade command. Parses the public `[--dry-run]` form plus the staged
|
|
10
8
|
* updater's internal `--update-preflight` form, then returns rendered text,
|
|
11
9
|
* structured data, and an exit code (0 for a safe result, 5 for a blocker).
|
|
12
10
|
*/
|
|
13
|
-
export async function runUpgradeCommand(args, home) {
|
|
11
|
+
export async function runUpgradeCommand(args, home, environment = process.env) {
|
|
14
12
|
const mode = parseUpgradeArgs(args);
|
|
15
|
-
const result =
|
|
16
|
-
home,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const result = mode === "execute"
|
|
14
|
+
? await runInteractiveUpgrade(home, environment)
|
|
15
|
+
: mode === "update-apply"
|
|
16
|
+
? await runUpdateOwnedUpgrade(home, environment)
|
|
17
|
+
: await runStorageUpgrade({ home, mode });
|
|
20
18
|
return {
|
|
21
19
|
output: renderUpgradeResult(result, mode),
|
|
22
20
|
data: result,
|
|
23
|
-
exitCode: result.outcome === "blocked" ? 5 : 0
|
|
21
|
+
exitCode: result.outcome === "blocked" || result.outcome === "failed" ? 5 : 0
|
|
24
22
|
};
|
|
25
23
|
}
|
|
26
24
|
function parseUpgradeArgs(args) {
|
|
@@ -29,37 +27,121 @@ function parseUpgradeArgs(args) {
|
|
|
29
27
|
if (args.length === 1 && args[0] === "--dry-run")
|
|
30
28
|
return "dry-run";
|
|
31
29
|
// Intentionally omitted from public command help: this is the machine contract
|
|
32
|
-
// used by a staged `yui update`, not a replacement for user-facing dry-
|
|
30
|
+
// used by a staged `yui update`, not a replacement for user-facing dry-run.
|
|
33
31
|
if (args.length === 1 && args[0] === "--update-preflight")
|
|
34
32
|
return "update-preflight";
|
|
33
|
+
if (args.length === 1 && args[0] === "--update-apply")
|
|
34
|
+
return "update-apply";
|
|
35
35
|
throw usageError("Upgrade usage: yui upgrade [--dry-run]");
|
|
36
36
|
}
|
|
37
|
+
async function runInteractiveUpgrade(home, environment) {
|
|
38
|
+
const preflight = await runStorageUpgrade({ home, mode: "update-preflight" });
|
|
39
|
+
if (preflight.outcome !== "update-preflight"
|
|
40
|
+
|| preflight.status === "already-current") {
|
|
41
|
+
return runStorageUpgrade({ home, mode: "execute" });
|
|
42
|
+
}
|
|
43
|
+
const handover = acquireHandoverLock(home);
|
|
44
|
+
let controllerWasRunning = false;
|
|
45
|
+
try {
|
|
46
|
+
const stopped = await stopFileTaskController(home, {
|
|
47
|
+
environment,
|
|
48
|
+
handoverOwnerPid: process.pid
|
|
49
|
+
});
|
|
50
|
+
controllerWasRunning = stopped.stopped;
|
|
51
|
+
const result = await runStorageUpgrade({ home, mode: "execute" });
|
|
52
|
+
if (controllerWasRunning
|
|
53
|
+
&& (result.outcome === "upgraded" || result.outcome === "already-current")) {
|
|
54
|
+
try {
|
|
55
|
+
await ensureFileTaskController(home, {
|
|
56
|
+
environment,
|
|
57
|
+
handoverOwnerPid: process.pid
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw runtimeError(`Storage reached version ${result.report.targetVersion}, but the current `
|
|
62
|
+
+ `Controller could not restart: ${messageOf(error)} Backup: `
|
|
63
|
+
+ `${result.report.backupPath ?? "none"}. Keep the Home quiesced, inspect `
|
|
64
|
+
+ "Controller ownership, and start only the current Yui Controller.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
handover.release();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function runUpdateOwnedUpgrade(home, environment) {
|
|
74
|
+
const ownerText = environment.YUI_UPDATE_HANDOVER_OWNER_PID;
|
|
75
|
+
const ownerPid = ownerText === undefined ? Number.NaN : Number(ownerText);
|
|
76
|
+
if (!Number.isSafeInteger(ownerPid)
|
|
77
|
+
|| ownerPid < 1
|
|
78
|
+
|| process.ppid !== ownerPid
|
|
79
|
+
|| !isHandoverLockHeld(home)
|
|
80
|
+
|| isForeignHandoverLockHeld(home, ownerPid)) {
|
|
81
|
+
throw runtimeError("The internal update migration requires its direct parent to own the live "
|
|
82
|
+
+ "Controller handover lock.");
|
|
83
|
+
}
|
|
84
|
+
return runStorageUpgrade({ home, mode: "execute" });
|
|
85
|
+
}
|
|
37
86
|
/** Render an {@link UpgradeResult} as concise, CLI-style text. */
|
|
38
87
|
export function renderUpgradeResult(result, mode) {
|
|
39
88
|
const header = versionHeader(result);
|
|
40
89
|
switch (result.outcome) {
|
|
41
90
|
case "already-current":
|
|
42
91
|
return `${header}\nStorage is already at the current version; nothing to upgrade.`;
|
|
92
|
+
case "upgrade-plan":
|
|
93
|
+
return [
|
|
94
|
+
header,
|
|
95
|
+
`Upgrade plan: ${renderSteps(result.report.steps)}. Storage was not modified.`
|
|
96
|
+
].join("\n");
|
|
97
|
+
case "upgraded":
|
|
98
|
+
return [
|
|
99
|
+
header,
|
|
100
|
+
`Storage upgraded through ${renderSteps(result.report.steps)}.`,
|
|
101
|
+
`Backup: ${result.report.backupPath ?? "none"}`
|
|
102
|
+
].join("\n");
|
|
43
103
|
case "update-preflight":
|
|
44
|
-
return `${header}\nUpdate preflight:
|
|
104
|
+
return `${header}\nUpdate preflight: ${result.status} (${result.stepCount} steps). Storage was not modified.`;
|
|
45
105
|
case "blocked": {
|
|
46
106
|
return [
|
|
47
107
|
header,
|
|
48
|
-
`${mode === "dry-run"
|
|
108
|
+
`${mode === "dry-run"
|
|
109
|
+
? "Dry run"
|
|
110
|
+
: mode === "update-preflight"
|
|
111
|
+
? "Update preflight"
|
|
112
|
+
: "Upgrade"} blocked at ${result.stage}: ${result.message}`,
|
|
49
113
|
`Action: ${result.action}`,
|
|
50
114
|
"The authoritative Home is unchanged."
|
|
51
115
|
].join("\n");
|
|
52
116
|
}
|
|
117
|
+
case "failed":
|
|
118
|
+
return [
|
|
119
|
+
header,
|
|
120
|
+
`Upgrade failed at ${result.stage}: ${result.message}`,
|
|
121
|
+
`Action: ${result.action}`,
|
|
122
|
+
`Backup: ${result.backupPath ?? "none"}`,
|
|
123
|
+
result.sceneUnchanged
|
|
124
|
+
? "The authoritative Home was restored."
|
|
125
|
+
: "The authoritative Home may have changed; keep it quiesced."
|
|
126
|
+
].join("\n");
|
|
53
127
|
}
|
|
54
128
|
}
|
|
55
129
|
function versionHeader(result) {
|
|
56
130
|
const classification = result.classification;
|
|
57
|
-
const
|
|
58
|
-
|
|
131
|
+
const storage = classification.storageVersion === undefined
|
|
132
|
+
? "unknown"
|
|
133
|
+
: String(classification.storageVersion);
|
|
59
134
|
const verdict = classification.classification.verdict;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
135
|
+
return `Storage: ${verdict} version=${storage}/${classification.currentStorageVersion} `
|
|
136
|
+
+ `minimum=${classification.minimumSupportedStorageVersion}`;
|
|
137
|
+
}
|
|
138
|
+
function renderSteps(steps) {
|
|
139
|
+
if (steps.length === 0)
|
|
140
|
+
return "no migrations";
|
|
141
|
+
return steps
|
|
142
|
+
.map(({ fromVersion, toVersion, name }) => `${fromVersion}->${toVersion} ${name}`)
|
|
143
|
+
.join(", ");
|
|
144
|
+
}
|
|
145
|
+
function messageOf(error) {
|
|
146
|
+
return error instanceof Error ? error.message : String(error);
|
|
65
147
|
}
|