@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.
Files changed (185) hide show
  1. package/ARCHITECTURE.md +91 -47
  2. package/README.md +275 -145
  3. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  4. package/dist/cli/commandCatalog.js +74 -109
  5. package/dist/cli/helpRenderer.js +1 -2
  6. package/dist/cli/interactionCandidates.js +10 -0
  7. package/dist/cli/interactionPolicy.js +76 -37
  8. package/dist/cli/roleWizard.js +5 -6
  9. package/dist/cli/updateCommand.js +17 -9
  10. package/dist/cli/updateOrchestrator.js +87 -19
  11. package/dist/cli/updatePorts.js +79 -13
  12. package/dist/cli/upgradeCommand.js +105 -23
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/controllerCommands.js +1 -1
  18. package/dist/commands/durableJobCommands.js +1 -1
  19. package/dist/commands/executionAuditCommands.js +4 -9
  20. package/dist/commands/globalRoleCommands.js +17 -0
  21. package/dist/commands/profileCommands.js +200 -30
  22. package/dist/commands/sessionCommands.js +1 -1
  23. package/dist/commands/taskActor.js +29 -127
  24. package/dist/commands/taskChangeSetCommands.js +1 -3
  25. package/dist/commands/taskCommands.js +1771 -2107
  26. package/dist/commands/taskCompletionGate.js +27 -103
  27. package/dist/commands/taskContextCommand.js +47 -80
  28. package/dist/commands/taskExecutionCommands.js +9 -27
  29. package/dist/commands/taskInputCommands.js +10 -10
  30. package/dist/commands/taskIntegrationCommands.js +69 -61
  31. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  32. package/dist/commands/taskNextActionCommand.js +8 -29
  33. package/dist/commands/taskOverviewCommand.js +25 -3
  34. package/dist/commands/taskPublicationCommands.js +151 -85
  35. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  36. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  37. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  38. package/dist/commands/taskUpstreamCommands.js +79 -95
  39. package/dist/config/configCatalog.js +1 -1
  40. package/dist/context/contextSnapshot.js +17 -2
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +6 -5
  63. package/dist/doctor/doctor.js +37 -14
  64. package/dist/domain/agentResultTransport.js +60 -0
  65. package/dist/execution/executionHealth.js +111 -118
  66. package/dist/execution/reviewMainTurn.js +161 -0
  67. package/dist/execution/workItemExecution.js +454 -0
  68. package/dist/execution/workItemExecutionProjection.js +309 -0
  69. package/dist/execution/workItemMainTurn.js +164 -0
  70. package/dist/executor/agentExecutor.js +19 -19
  71. package/dist/executor/executorRegistry.js +7 -28
  72. package/dist/executor/fileRoleLaunchPlanner.js +16 -82
  73. package/dist/external/githubPublicationVerifier.js +114 -0
  74. package/dist/external/gitlabPublicationVerifier.js +158 -0
  75. package/dist/external/pinnedCommandRunner.js +73 -0
  76. package/dist/integration/changeSetManifest.js +0 -3
  77. package/dist/integration/deliveryObligation.js +43 -6
  78. package/dist/integration/gitIntegrationService.js +284 -101
  79. package/dist/integration/integrationAttempt.js +64 -14
  80. package/dist/integration/integrationQueueEntry.js +5 -35
  81. package/dist/integration/integrationQueueService.js +65 -194
  82. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  83. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  84. package/dist/message/message.js +16 -0
  85. package/dist/observability/executionAudit.js +4 -25
  86. package/dist/observability/faultClassification.js +58 -70
  87. package/dist/observability/orchestrationMetrics.js +8 -50
  88. package/dist/observability/runtimeIdentity.js +48 -50
  89. package/dist/profile/agentProfile.js +60 -15
  90. package/dist/profile/agentProfileRuntime.js +100 -0
  91. package/dist/release/releaseWorkflowPorts.js +5 -68
  92. package/dist/release/runtimeRelease.js +9 -1
  93. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  94. package/dist/repository/gitWorkspace.js +9 -67
  95. package/dist/repository/taskBaseFreshness.js +3 -1
  96. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  97. package/dist/repository/taskWorkspacePreparer.js +252 -411
  98. package/dist/resources/liveReferences.js +6 -55
  99. package/dist/resources/resourceDiscovery.js +1 -30
  100. package/dist/resources/resourceRegistrar.js +0 -23
  101. package/dist/review/deltaRecheck.js +8 -65
  102. package/dist/review/reviewAcceptance.js +22 -19
  103. package/dist/review/reviewConfig.js +0 -10
  104. package/dist/review/reviewDecision.js +2 -2
  105. package/dist/review/reviewRound.js +147 -273
  106. package/dist/review/reviewerAvailability.js +13 -23
  107. package/dist/role/role.js +3 -0
  108. package/dist/runtime/agentDriverObservation.js +1 -1
  109. package/dist/runtime/agentHost.js +51 -51
  110. package/dist/runtime/builtinAgentDrivers.js +9 -2
  111. package/dist/runtime/codexAppServerRuntime.js +23 -5
  112. package/dist/runtime/exactControlPlane.js +22 -296
  113. package/dist/runtime/firstProgressAdvisory.js +1 -1
  114. package/dist/runtime/index.js +1 -1
  115. package/dist/runtime/launchBroker.js +11 -11
  116. package/dist/runtime/lifecycleReservation.js +2 -2
  117. package/dist/runtime/managedCaller.js +111 -0
  118. package/dist/runtime/ports.js +16 -3
  119. package/dist/runtime/preallocatedNativeSession.js +2 -2
  120. package/dist/runtime/processExitObservation.js +3 -2
  121. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  122. package/dist/runtime/runtimeBinding.js +1 -1
  123. package/dist/runtime/runtimeObservation.js +33 -16
  124. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  125. package/dist/runtime/runtimeStopReceipt.js +11 -11
  126. package/dist/runtime/sessionLaunchRequest.js +2 -2
  127. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  128. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  129. package/dist/runtime/sessionReconciliation.js +4 -4
  130. package/dist/runtime/sessionTerminationGuard.js +9 -9
  131. package/dist/runtime/sessionTokenMetrics.js +5 -7
  132. package/dist/runtime/structuredProviderHost.js +5 -5
  133. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  134. package/dist/runtime/tmuxAdapters.js +32 -30
  135. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  136. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  137. package/dist/scheduler/roleTurnLiveness.js +6 -6
  138. package/dist/scheduler/roleTurnStall.js +8 -9
  139. package/dist/scheduler/taskExecutionProjection.js +19 -41
  140. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  141. package/dist/storage/currentTaskStore.js +6 -4
  142. package/dist/storage/sqliteSchema.js +135 -80
  143. package/dist/storage/sqliteStore.js +69 -52
  144. package/dist/storage/storageSchema.js +92 -230
  145. package/dist/storage/storageVersions.js +12 -13
  146. package/dist/storage/taskStore.js +8 -8
  147. package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
  148. package/dist/task/completionReadiness.js +17 -98
  149. package/dist/task/deliveryGuard.js +9 -11
  150. package/dist/task/draftPlan.js +142 -0
  151. package/dist/task/nextAction.js +179 -477
  152. package/dist/task/publicationVerification.js +1 -0
  153. package/dist/task/remoteDelivery.js +233 -0
  154. package/dist/task/task.js +108 -4
  155. package/dist/task/taskRecordReference.js +0 -1
  156. package/dist/turn/turn.js +109 -16
  157. package/dist/verification/verificationGateService.js +0 -26
  158. package/dist/version.js +3 -3
  159. package/dist/web/assets/client/components.js +99 -121
  160. package/dist/web/assets/client/i18n.js +136 -58
  161. package/dist/web/assets/client/view.js +44 -0
  162. package/dist/web/assets/styles/cards.js +3 -15
  163. package/dist/web/assets/styles/widgets.js +4 -0
  164. package/dist/web/webSnapshot.js +29 -14
  165. package/dist/workItem/dependencyGate.js +34 -0
  166. package/dist/workItem/workItem.js +99 -266
  167. package/dist/workspace/workItemChangeSetManager.js +15 -59
  168. package/docs/task-local-identity.md +9 -9
  169. package/i18n/README.zh-CN.md +189 -74
  170. package/package.json +1 -1
  171. package/skills/yui-leader/SKILL.md +272 -782
  172. package/skills/yui-operator/SKILL.md +190 -365
  173. package/skills/yui-reviewer/SKILL.md +40 -33
  174. package/skills/yui-runtime/SKILL.md +22 -0
  175. package/skills/yui-worker/SKILL.md +19 -6
  176. package/dist/execution/candidateConvergence.js +0 -623
  177. package/dist/execution/executionGroup.js +0 -854
  178. package/dist/execution/resourceBroker.js +0 -426
  179. package/dist/executor/launchPlan.js +0 -16
  180. package/dist/review/reviewFinding.js +0 -240
  181. package/dist/review/reviewFindingLedger.js +0 -550
  182. package/dist/review/reviewOutcomeClassifier.js +0 -255
  183. package/dist/scheduler/resourceQueueProjection.js +0 -71
  184. package/dist/storage/upgrade/recordVersions.js +0 -82
  185. 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. Only the exact current storage contract is
9
- * accepted; the exact Controller handoff then promotes and verifies the binary
10
- * without changing the Home. Older Homes remain untouched.
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 `Updated Yui to ${result.version}; the current Home was not modified.`;
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.recoverable
35
- ? "The current install and Home remain usable."
36
- : result.phase === "activate-binary"
37
- ? "The Home was unchanged, but binary health is unknown; do not assume the current install is usable."
38
- : "Manual recovery is required (see below).",
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 current storage contract.
2
+ * Side-by-side `yui update` orchestration for the supported storage range.
3
3
  *
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.
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
- return restoreControllerOrReport(ports, home, lifecycle, {
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: false,
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
- : 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.",
134
- recoverable: false,
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
- controllerOwnershipUnknown: true
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 { outcome: "updated", version: staged.version, path: "current-fast" };
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 launch identity.");
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
  }
@@ -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 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.
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 unchanged current Home through the activated binary.
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 launch identity does not match the activated global binary "
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
- /** Only the exact current storage contract is a green update preflight. */
801
+ /** Accept either an exact current Home or a complete supported migration path. */
786
802
  function parseUpdatePreflightResult(data) {
787
- if (data.status !== "already-current" || data.stepCount !== 0)
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 (classification.verdict !== "USABLE"
794
- || classification.status !== "current") {
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
- return { status: "already-current" };
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", "launchId"];
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.launchId === "string" ? { launchId: item.launchId } : {}),
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 { usageError } from "../errors/cliError.js";
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 = await runStorageUpgrade({
16
- home,
17
- latest: latestStorageVersionState(),
18
- mode
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-turn.
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: already-current (0 steps). Storage was not modified.`;
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" ? "Dry run" : mode === "update-preflight" ? "Update preflight" : "Upgrade"} blocked at ${result.stage}: ${result.message}`,
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 layout = classification.layoutVersion ?? classification.latestLayoutVersion;
58
- const aggregate = classification.aggregateVersion ?? classification.latestAggregateVersion;
131
+ const storage = classification.storageVersion === undefined
132
+ ? "unknown"
133
+ : String(classification.storageVersion);
59
134
  const verdict = classification.classification.verdict;
60
- const incompatible = classification.incompatibleComponent === undefined
61
- ? ""
62
- : ` incompatibleComponent=${classification.incompatibleComponent}`;
63
- return `Storage: ${verdict} layout=${layout}/${classification.latestLayoutVersion} `
64
- + `aggregate=${aggregate}/${classification.latestAggregateVersion}${incompatible}`;
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
  }