@zq-silk/yui 0.16.1 → 1.0.0-alpha
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 +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +9 -4
- package/dist/agent/agent.js +4 -9
- package/dist/agent/executionComponents.js +4 -4
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +13 -23
- package/dist/artifacts/managedGit.js +3 -56
- package/dist/brief/taskBrief.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -44
- package/dist/cli/updatePorts.js +76 -86
- package/dist/cli/upgradeCommand.js +4 -8
- package/dist/cli.js +39 -33
- package/dist/commands/controllerCommands.js +2 -2
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +91 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +2 -2
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/agentRuntimeObserver.js +4 -4
- package/dist/controller/clientRuntime.js +19 -44
- package/dist/controller/controller.js +24 -10
- package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
- package/dist/controller/globalInputDelivery.js +31 -8
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/jobSupervisor.js +3 -3
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +136 -119
- package/dist/controller/runtime.js +2 -28
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +28 -1
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/core/controllerIdentity.js +25 -0
- package/dist/core/controllerProcessIdentity.js +2 -2
- package/dist/core/controllerServer.js +4 -2
- package/dist/core/protocol.js +1 -1
- package/dist/doctor/doctor.js +2 -1
- package/dist/domain/validation.js +6 -0
- package/dist/event/taskEvent.js +3 -3
- package/dist/execution/workItemExecution.js +2 -2
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +11 -8
- package/dist/executor/effectiveLaunch.js +3 -3
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/grant/capabilityGrant.js +2 -2
- package/dist/input/inputRequest.js +4 -4
- package/dist/integration/changeSet.js +3 -3
- package/dist/integration/integrationAttempt.js +3 -3
- package/dist/integration/integrationSourceApplication.js +1 -1
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/job/durableJob.js +1 -1
- package/dist/job/jobRunner.js +2 -2
- package/dist/message/message.js +8 -6
- package/dist/message/messageContinuation.js +8 -9
- package/dist/milestone/milestone.js +3 -3
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/profile/agentProfile.js +3 -3
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/release/runtimeRelease.js +9 -7
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/project.js +3 -3
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +5 -7
- package/dist/review/reviewRound.js +5 -5
- package/dist/role/role.js +7 -8
- package/dist/runtime/acpProtocol.js +2 -3
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +25 -65
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +3 -3
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/codexInteractiveHost.js +1 -1
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/launchBroker.js +1 -1
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/providerContinuationReconciliationService.js +86 -38
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeIdentity.js +3 -3
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/runtimeCoherence.js +7 -3
- package/dist/runtime/runtimeObservation.js +3 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/runtime/taskRuntimeIsolation.js +3 -3
- package/dist/runtime/tmuxAdapters.js +3 -3
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/scheduler/taskWake.js +1 -1
- package/dist/storage/baselineSchema.js +606 -0
- package/dist/storage/homeLayout.js +5 -16
- package/dist/storage/recordValidation.js +16 -4
- package/dist/storage/sqliteSchema.js +106 -1741
- package/dist/storage/sqliteStore.js +16 -14
- package/dist/storage/storageSchema.js +8 -7
- package/dist/storage/storageVersions.js +23 -16
- package/dist/storage/taskStore.js +3 -29
- package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +17 -8
- package/dist/task/taskActivation.js +5 -4
- package/dist/telemetry/sqliteTelemetryStore.js +2 -2
- package/dist/verification/gateArtifact.js +5 -3
- package/dist/verification/verificationPlan.js +6 -7
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/dist/workItem/workItem.js +6 -6
- package/dist/workspace/cleanupInspection.js +1 -9
- package/dist/worktree/managedWorkspace.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +50 -7
- package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +70 -214
- package/docs/release-workflow.zh-CN.md +53 -157
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/sqlite-control-plane-design.md +48 -289
- package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
- package/docs/storage-baseline.md +132 -0
- package/docs/storage-baseline.zh-CN.md +106 -0
- package/docs/task-delivery.md +110 -2
- package/docs/task-delivery.zh-CN.md +82 -2
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +42 -164
- package/docs/testing/verification-levels.zh-CN.md +29 -115
- package/i18n/README.zh-CN.md +7 -4
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
- package/dist/storage/migrations/agentFailureContext.js +0 -22
- package/dist/storage/migrations/agentRunContract.js +0 -159
- package/dist/storage/migrations/artifactsToGit.js +0 -338
- package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
- package/dist/storage/migrations/currentInputContract.js +0 -86
- package/dist/storage/migrations/currentRuntimeContract.js +0 -228
- package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
- package/dist/storage/migrations/integrationContinuation.js +0 -105
- package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
- package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
- package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
- package/dist/storage/migrations/submitIntent.js +0 -126
- package/dist/storage/migrations/unifyHomeLayout.js +0 -925
- package/dist/storage/migrations/verificationPlanV1.js +0 -162
- package/dist/storage/migrations/verificationPolicy.js +0 -74
- package/dist/storage/migrations/workItemHistory.js +0 -46
package/dist/cli/updatePorts.js
CHANGED
|
@@ -25,15 +25,18 @@
|
|
|
25
25
|
* mismatch fails closed.
|
|
26
26
|
*/
|
|
27
27
|
import { spawnSync } from "node:child_process";
|
|
28
|
-
import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
28
|
+
import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
29
29
|
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
import { runtimeError } from "../errors/cliError.js";
|
|
32
32
|
import { isConcreteVersion } from "../domain/validation.js";
|
|
33
|
+
import { parseControllerIdentity } from "../core/controllerIdentity.js";
|
|
34
|
+
import { isMinorStorageUpgrade, isStorageVersion, storageVersionParts } from "../storage/storageVersions.js";
|
|
33
35
|
import { STORAGE_DOCTOR_CHECK_NAMES } from "../doctor/doctor.js";
|
|
34
36
|
import { acquireHandoverLock } from "../release/runtimeRelease.js";
|
|
35
37
|
import { updateStagingRoot } from "../storage/homeLayout.js";
|
|
36
38
|
import { resolveYuiHome } from "../storage/taskStore.js";
|
|
39
|
+
import { UpdateControllerReconciliationError } from "../controller/updateReconciliation.js";
|
|
37
40
|
const PACKAGE_NAME = "@zq-silk/yui";
|
|
38
41
|
const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
|
|
39
42
|
function resolveExecutable(command, environmentPath) {
|
|
@@ -90,20 +93,20 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
90
93
|
beginControllerHandover(home) {
|
|
91
94
|
return acquireHandoverLock(home).release;
|
|
92
95
|
},
|
|
93
|
-
stage(
|
|
96
|
+
stage(requestedVersion) {
|
|
94
97
|
// A caller that names a version (the release workflow, which freezes the
|
|
95
98
|
// exact version in its plan) installs THAT version — never a moving
|
|
96
99
|
// `latest` that could resolve to a different build than the one the
|
|
97
100
|
// plan authorized. A non-concrete value fails closed rather than being
|
|
98
101
|
// interpolated into an install spec. An omitted version keeps the
|
|
99
102
|
// interactive `yui update` behavior of staging latest.
|
|
100
|
-
const spec =
|
|
103
|
+
const spec = requestedVersion === undefined
|
|
101
104
|
? PACKAGE_SPEC
|
|
102
|
-
: isConcreteVersion(
|
|
103
|
-
? `${PACKAGE_NAME}@${
|
|
105
|
+
: isConcreteVersion(requestedVersion)
|
|
106
|
+
? `${PACKAGE_NAME}@${requestedVersion.trim()}`
|
|
104
107
|
: null;
|
|
105
108
|
if (spec === null) {
|
|
106
|
-
throw runtimeError(`Refusing to stage a non-concrete version (${String(
|
|
109
|
+
throw runtimeError(`Refusing to stage a non-concrete version (${String(requestedVersion)}): only an exact `
|
|
107
110
|
+ "major.minor.patch version can be pinned for an update.");
|
|
108
111
|
}
|
|
109
112
|
// The Home staging parent may not exist yet on a Home that has never
|
|
@@ -126,6 +129,10 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
126
129
|
+ "nor `yui --json version` returned a concrete version). Refusing to proceed with a "
|
|
127
130
|
+ "`@latest` fallback that could promote a different build than the one preflighted.");
|
|
128
131
|
}
|
|
132
|
+
if (requestedVersion !== undefined && version !== requestedVersion.trim()) {
|
|
133
|
+
throw runtimeError(`Requested Yui ${requestedVersion.trim()}, but staged Yui ${version}. `
|
|
134
|
+
+ "Refusing to activate a different release; the install and storage are unchanged.");
|
|
135
|
+
}
|
|
129
136
|
// Successful staging transfers cleanup ownership to runUpdate's finally
|
|
130
137
|
// block. Every assertion, spawn, npm/network, or version-resolution
|
|
131
138
|
// failure before that handoff removes the throwaway prefix here.
|
|
@@ -196,10 +203,9 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
196
203
|
+ `but the staged/verified artifact was ${staged.version}. Refusing to trust a `
|
|
197
204
|
+ "different build than the one that passed preflight.");
|
|
198
205
|
}
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// the update cannot strand a live Session.
|
|
206
|
+
// Retarget current Manifest-referenced wrappers to the activated
|
|
207
|
+
// installation before the replacement Controller starts. This relocates
|
|
208
|
+
// the current shell form; it does not convert historical Session wrappers.
|
|
203
209
|
const sessionCliRefresh = run(activeBinary, ["--json", "internal", "session-cli-refresh"], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
|
|
204
210
|
assertSpawnOk(sessionCliRefresh, "refresh managed Session CLI wrappers");
|
|
205
211
|
// Retain the exact path used by both doctor and version verification. The
|
|
@@ -214,13 +220,15 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
|
|
|
214
220
|
},
|
|
215
221
|
// `runUpdate` is intentionally synchronous because the npm/staged-binary
|
|
216
222
|
// ports use spawnSync. A short-lived child owns the async, exactly fenced
|
|
217
|
-
// reconciliation
|
|
223
|
+
// reconciliation explicitly; lifecycle capture uses only structured live
|
|
218
224
|
// Controller commands. Tests can replace these seams with deterministic
|
|
219
225
|
// fakes without touching a real Controller.
|
|
220
226
|
controllerStatus(home) {
|
|
221
|
-
reconcileControllerResourcesForUpdate(home, environment, spawn);
|
|
222
227
|
return readControllerLifecycle(home, environment, spawn);
|
|
223
228
|
},
|
|
229
|
+
reconcileController(home) {
|
|
230
|
+
return reconcileControllerResourcesForUpdate(home, environment, spawn);
|
|
231
|
+
},
|
|
224
232
|
stopController(home, expectedPid) {
|
|
225
233
|
return stopControllerForUpdate(home, expectedPid, environment, spawn);
|
|
226
234
|
},
|
|
@@ -501,18 +509,16 @@ function restoreControllerIdentity(home, identity, environment, spawn) {
|
|
|
501
509
|
"const { spawn } = require('node:child_process');",
|
|
502
510
|
"const values = process.argv.slice(1);",
|
|
503
511
|
"const handoverOwnerPid = Number(values.pop());",
|
|
504
|
-
"const
|
|
505
|
-
"const args = JSON.parse(values.pop());",
|
|
506
|
-
"const executable = values.pop();",
|
|
512
|
+
"const identity = JSON.parse(values.pop());",
|
|
507
513
|
"const home = values.pop();",
|
|
508
514
|
"const runtimeModule = values.pop();",
|
|
509
515
|
"(async () => {",
|
|
510
516
|
" const { ensureFileTaskControllerIdentity } = await import(runtimeModule);",
|
|
511
|
-
" await ensureFileTaskControllerIdentity(home,
|
|
517
|
+
" await ensureFileTaskControllerIdentity(home, identity, {",
|
|
512
518
|
" environment: process.env,",
|
|
513
519
|
" handoverOwnerPid,",
|
|
514
520
|
" spawnController: (_home, launchEnv) => {",
|
|
515
|
-
" const child = spawn(
|
|
521
|
+
" const child = spawn(identity.executablePath, identity.args, { detached: true, stdio: 'ignore', env: launchEnv });",
|
|
516
522
|
" child.unref();",
|
|
517
523
|
" }",
|
|
518
524
|
" });",
|
|
@@ -523,9 +529,7 @@ function restoreControllerIdentity(home, identity, environment, spawn) {
|
|
|
523
529
|
helper,
|
|
524
530
|
UPDATE_CLIENT_RUNTIME_PATH,
|
|
525
531
|
home,
|
|
526
|
-
identity
|
|
527
|
-
JSON.stringify(identity.args),
|
|
528
|
-
identity.version,
|
|
532
|
+
JSON.stringify(parseControllerIdentity(identity)),
|
|
529
533
|
String(process.pid)
|
|
530
534
|
], { cwd: process.cwd(), env: launchEnvironment, shell: false, stdio: "pipe" });
|
|
531
535
|
assertSpawnOk(result, "restore the previously running Controller identity");
|
|
@@ -593,14 +597,23 @@ function reconcileControllerResourcesForUpdate(home, environment, spawn) {
|
|
|
593
597
|
" process.stdout.write(JSON.stringify({ ok: true, data }));",
|
|
594
598
|
"})().catch((error) => {",
|
|
595
599
|
" const message = error instanceof Error ? error.message : String(error);",
|
|
596
|
-
" process.stderr.write(JSON.stringify({ ok: false, code: 'RUNTIME_ERROR', message }));",
|
|
600
|
+
" process.stderr.write(JSON.stringify({ ok: false, code: 'RUNTIME_ERROR', message, result: error.result }));",
|
|
597
601
|
" process.exitCode = 5;",
|
|
598
602
|
"});"
|
|
599
603
|
].join(" ");
|
|
600
604
|
const result = spawn(process.execPath, ["-e", helper, UPDATE_CONTROLLER_RECONCILIATION_PATH, home], { cwd: process.cwd(), env: { ...environment, YUI_HOME: home }, shell: false });
|
|
601
605
|
if (result.error !== undefined || result.status !== 0) {
|
|
602
606
|
const detail = structuredErrorMessage(result) ?? result.stderr.toString("utf8").trim();
|
|
603
|
-
|
|
607
|
+
const message = `Controller reconciliation failed (exit ${result.status ?? "null"})${detail.length === 0 ? "." : `: ${detail}`}`;
|
|
608
|
+
let evidence;
|
|
609
|
+
try {
|
|
610
|
+
evidence = JSON.parse(result.stderr.toString("utf8")).result;
|
|
611
|
+
}
|
|
612
|
+
catch { /* Unknown child effects are reported below, never retried here. */ }
|
|
613
|
+
if (isReconciliationResult(evidence, home)) {
|
|
614
|
+
throw new UpdateControllerReconciliationError(message, evidence);
|
|
615
|
+
}
|
|
616
|
+
throw new Error(`${message} Cleanup effects are unknown; inspect the exact Home ${home} before retrying.`);
|
|
604
617
|
}
|
|
605
618
|
let parsed;
|
|
606
619
|
try {
|
|
@@ -613,11 +626,19 @@ function reconcileControllerResourcesForUpdate(home, environment, spawn) {
|
|
|
613
626
|
}
|
|
614
627
|
if (!isRecord(parsed)
|
|
615
628
|
|| parsed.ok !== true
|
|
616
|
-
|| !
|
|
617
|
-
|| !Array.isArray(parsed.data.cleaned)
|
|
618
|
-
|| parsed.data.cleaned.some((id) => typeof id !== "string")) {
|
|
629
|
+
|| !isReconciliationResult(parsed.data, home)) {
|
|
619
630
|
throw new Error("Controller reconciliation returned an invalid structured result.");
|
|
620
631
|
}
|
|
632
|
+
return parsed.data;
|
|
633
|
+
}
|
|
634
|
+
function isReconciliationResult(value, home) {
|
|
635
|
+
return isRecord(value) && value.home === resolve(home)
|
|
636
|
+
&& Array.isArray(value.cleaned) && value.cleaned.every(id => typeof id === "string")
|
|
637
|
+
&& Array.isArray(value.attempts) && value.attempts.every(attempt => (isRecord(attempt) && isRecord(attempt.resource) && typeof attempt.resource.id === "string"
|
|
638
|
+
&& ["cleaned", "absent", "unknown"].includes(String(attempt.outcome))
|
|
639
|
+
&& (attempt.error === undefined || typeof attempt.error === "string")))
|
|
640
|
+
&& Array.isArray(value.remaining) && value.remaining.every(resource => (isRecord(resource) && typeof resource.id === "string"))
|
|
641
|
+
&& ["observedAt", "observationError", "lockReleaseError"].every(key => (value[key] === undefined || typeof value[key] === "string"));
|
|
621
642
|
}
|
|
622
643
|
function structuredErrorMessage(result) {
|
|
623
644
|
for (const buffer of [result.stderr, result.stdout]) {
|
|
@@ -633,21 +654,6 @@ function structuredErrorMessage(result) {
|
|
|
633
654
|
}
|
|
634
655
|
return undefined;
|
|
635
656
|
}
|
|
636
|
-
function parseControllerIdentity(value) {
|
|
637
|
-
if (typeof value.executablePath !== "string"
|
|
638
|
-
|| value.executablePath.length === 0
|
|
639
|
-
|| !Array.isArray(value.args)
|
|
640
|
-
|| value.args.some((arg) => typeof arg !== "string")
|
|
641
|
-
|| typeof value.version !== "string"
|
|
642
|
-
|| value.version.length === 0) {
|
|
643
|
-
throw new Error("Authenticated Controller identity is malformed; treating ownership as unknown-active.");
|
|
644
|
-
}
|
|
645
|
-
return {
|
|
646
|
-
executablePath: value.executablePath,
|
|
647
|
-
args: value.args,
|
|
648
|
-
version: value.version
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
657
|
function controllerErrorCodeFromResult(result) {
|
|
652
658
|
for (const buffer of [result.stdout, result.stderr]) {
|
|
653
659
|
try {
|
|
@@ -752,23 +758,22 @@ function assertActivatedControllerIdentity(identity, activatedBinary, activatedV
|
|
|
752
758
|
* entrypoint derivation as the production startup identity check (P1-1, rr23).
|
|
753
759
|
*/
|
|
754
760
|
export function activatedControllerEntrypoint(activatedBinary) {
|
|
755
|
-
|
|
756
|
-
try {
|
|
757
|
-
resolvedBinary = realpathSync(activatedBinary);
|
|
758
|
-
}
|
|
759
|
-
catch {
|
|
760
|
-
// `verify` already checked existsSync. Keep the fallback deterministic for
|
|
761
|
-
// test seams and fail closed later if the identity does not match it.
|
|
762
|
-
resolvedBinary = resolve(activatedBinary);
|
|
763
|
-
}
|
|
761
|
+
const resolvedBinary = realpathSync(activatedBinary);
|
|
764
762
|
const direct = join(dirname(resolvedBinary), "controller", "controllerMain.js");
|
|
765
|
-
if (existsSync(direct))
|
|
766
|
-
return direct;
|
|
767
763
|
// npm may expose a non-symlink launcher under <prefix>/bin. Resolve the
|
|
768
764
|
// package's canonical global layout when it is present.
|
|
769
765
|
const prefix = resolve(dirname(resolvedBinary), "..");
|
|
770
766
|
const packageEntrypoint = join(prefix, "lib", "node_modules", PACKAGE_NAME, "dist", "controller", "controllerMain.js");
|
|
771
|
-
|
|
767
|
+
for (const candidate of [direct, packageEntrypoint]) {
|
|
768
|
+
if (!existsSync(candidate))
|
|
769
|
+
continue;
|
|
770
|
+
const entrypoint = realpathSync(candidate);
|
|
771
|
+
if (!statSync(entrypoint).isFile()) {
|
|
772
|
+
throw runtimeError(`Controller entrypoint is not a regular file: ${entrypoint}.`);
|
|
773
|
+
}
|
|
774
|
+
return entrypoint;
|
|
775
|
+
}
|
|
776
|
+
throw runtimeError(`Controller entrypoint is missing for the activated binary: ${resolvedBinary}.`);
|
|
772
777
|
}
|
|
773
778
|
function interpretPreflight(result) {
|
|
774
779
|
// Require a valid `{ ok:true, data }` success envelope before trusting any
|
|
@@ -787,7 +792,7 @@ function interpretPreflight(result) {
|
|
|
787
792
|
}
|
|
788
793
|
const outcome = typeof data.outcome === "string" ? data.outcome : undefined;
|
|
789
794
|
// EXIT/OUTCOME CONSISTENCY (P1-2): the one success-class internal preflight
|
|
790
|
-
// outcome must exit 0. A user dry-run,
|
|
795
|
+
// outcome must exit 0. A user dry-run, a direct classification outcome,
|
|
791
796
|
// or any other spelling is not this contract and is never promoted to green.
|
|
792
797
|
if (outcome === "update-preflight" && result.status !== 0) {
|
|
793
798
|
return {
|
|
@@ -818,7 +823,6 @@ function interpretPreflight(result) {
|
|
|
818
823
|
action: "Use a staged binary that supports the update-preflight contract; do not force the update."
|
|
819
824
|
};
|
|
820
825
|
}
|
|
821
|
-
const blockers = parseUpdateBlockers(data.blockers);
|
|
822
826
|
return {
|
|
823
827
|
status: "blocked",
|
|
824
828
|
stage: typeof data.stage === "string" ? data.stage : "preflight",
|
|
@@ -826,8 +830,6 @@ function interpretPreflight(result) {
|
|
|
826
830
|
action: typeof data.action === "string"
|
|
827
831
|
? data.action
|
|
828
832
|
: "Resolve the reported condition and retry.",
|
|
829
|
-
...(blockers === undefined ? {} : { blockers }),
|
|
830
|
-
...(typeof data.retryCommand === "string" ? { retryCommand: data.retryCommand } : {}),
|
|
831
833
|
...(data.sceneUnchanged === true ? { sceneUnchanged: true } : {})
|
|
832
834
|
};
|
|
833
835
|
}
|
|
@@ -841,9 +843,13 @@ function parseUpdatePreflightResult(data) {
|
|
|
841
843
|
const homeClassification = data.classification;
|
|
842
844
|
if (!isRecord(homeClassification) || !isRecord(homeClassification.classification))
|
|
843
845
|
return null;
|
|
846
|
+
const from = homeClassification.storageVersion;
|
|
847
|
+
const to = homeClassification.currentStorageVersion;
|
|
848
|
+
if (!isStorageVersion(from) || !isStorageVersion(to))
|
|
849
|
+
return null;
|
|
844
850
|
const classification = homeClassification.classification;
|
|
845
851
|
if (data.status === "already-current") {
|
|
846
|
-
if (data.stepCount !== 0
|
|
852
|
+
if (data.stepCount !== 0 || from !== to
|
|
847
853
|
|| classification.verdict !== "USABLE"
|
|
848
854
|
|| classification.status !== "current")
|
|
849
855
|
return null;
|
|
@@ -852,8 +858,17 @@ function parseUpdatePreflightResult(data) {
|
|
|
852
858
|
if (data.stepCount <= 0
|
|
853
859
|
|| classification.verdict !== "MIGRATABLE"
|
|
854
860
|
|| classification.status !== "migration-ready"
|
|
861
|
+
|| !isMinorStorageUpgrade(from, to)
|
|
855
862
|
|| !data.steps.every(isStorageMigrationStep))
|
|
856
863
|
return null;
|
|
864
|
+
let previous = from;
|
|
865
|
+
for (const step of data.steps) {
|
|
866
|
+
if (step.fromVersion !== previous)
|
|
867
|
+
return null;
|
|
868
|
+
previous = step.toVersion;
|
|
869
|
+
}
|
|
870
|
+
if (previous !== to)
|
|
871
|
+
return null;
|
|
857
872
|
return { status: "migration-ready", stepCount: data.stepCount };
|
|
858
873
|
}
|
|
859
874
|
function interpretStorageMigration(result) {
|
|
@@ -889,38 +904,13 @@ function interpretStorageMigration(result) {
|
|
|
889
904
|
}
|
|
890
905
|
function isStorageMigrationStep(value) {
|
|
891
906
|
return isRecord(value)
|
|
892
|
-
&&
|
|
893
|
-
&&
|
|
894
|
-
&& value.
|
|
907
|
+
&& isStorageVersion(value.fromVersion)
|
|
908
|
+
&& isStorageVersion(value.toVersion)
|
|
909
|
+
&& isMinorStorageUpgrade(value.fromVersion, value.toVersion)
|
|
910
|
+
&& storageVersionParts(value.toVersion).minor === storageVersionParts(value.fromVersion).minor + 1
|
|
895
911
|
&& typeof value.name === "string"
|
|
896
912
|
&& value.name.length > 0;
|
|
897
913
|
}
|
|
898
|
-
function parseUpdateBlockers(value) {
|
|
899
|
-
if (value === undefined)
|
|
900
|
-
return undefined;
|
|
901
|
-
if (!Array.isArray(value))
|
|
902
|
-
return undefined;
|
|
903
|
-
const parsed = [];
|
|
904
|
-
for (const item of value) {
|
|
905
|
-
if (!isRecord(item) || typeof item.reason !== "string" || item.reason.length === 0) {
|
|
906
|
-
return undefined;
|
|
907
|
-
}
|
|
908
|
-
const optional = ["taskId", "roleName", "runId", "nativeSessionId"];
|
|
909
|
-
if (optional.some((key) => item[key] !== undefined && typeof item[key] !== "string")) {
|
|
910
|
-
return undefined;
|
|
911
|
-
}
|
|
912
|
-
parsed.push({
|
|
913
|
-
...(typeof item.taskId === "string" ? { taskId: item.taskId } : {}),
|
|
914
|
-
...(typeof item.roleName === "string" ? { roleName: item.roleName } : {}),
|
|
915
|
-
...(typeof item.runId === "string" ? { runId: item.runId } : {}),
|
|
916
|
-
...(typeof item.nativeSessionId === "string"
|
|
917
|
-
? { nativeSessionId: item.nativeSessionId }
|
|
918
|
-
: {}),
|
|
919
|
-
reason: item.reason
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
return parsed;
|
|
923
|
-
}
|
|
924
914
|
/**
|
|
925
915
|
* Extract the validated `data` object from a spawn result's `{ ok: true, data }`
|
|
926
916
|
* JSON envelope, or `null` when the result is not a trustworthy success envelope.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** `yui upgrade` plans or applies the supported linear storage migration chain. */
|
|
2
2
|
import { runStorageUpgrade } from "../storage/upgrade/upgradeOrchestrator.js";
|
|
3
3
|
import { ensureFileTaskController, ensureFileTaskControllerIdentity, stopFileTaskController } from "../controller/clientRuntime.js";
|
|
4
|
+
import { parseControllerIdentity } from "../core/controllerIdentity.js";
|
|
4
5
|
import { callController, ControllerClientError } from "../core/controllerClient.js";
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
6
7
|
import { runtimeError, usageError } from "../errors/cliError.js";
|
|
@@ -99,16 +100,11 @@ async function captureUpgradeController(home) {
|
|
|
99
100
|
return undefined;
|
|
100
101
|
throw error;
|
|
101
102
|
}
|
|
102
|
-
const
|
|
103
|
-
if (!Number.isSafeInteger(
|
|
104
|
-
|| typeof identity.executablePath !== "string" || !identity.executablePath
|
|
105
|
-
|| !Array.isArray(identity.args) || identity.args.some(arg => typeof arg !== "string")
|
|
106
|
-
|| typeof identity.version !== "string" || !identity.version) {
|
|
103
|
+
const pid = value?.pid;
|
|
104
|
+
if (!Number.isSafeInteger(pid) || pid < 1) {
|
|
107
105
|
throw runtimeError("Cannot capture the exact Controller for a reversible upgrade preflight.");
|
|
108
106
|
}
|
|
109
|
-
return { pid:
|
|
110
|
-
executablePath: identity.executablePath, args: identity.args, version: identity.version
|
|
111
|
-
} };
|
|
107
|
+
return { pid: pid, identity: parseControllerIdentity(value) };
|
|
112
108
|
}
|
|
113
109
|
async function runUpdateOwnedUpgrade(home, environment) {
|
|
114
110
|
const ownerText = environment.YUI_UPDATE_HANDOVER_OWNER_PID;
|
package/dist/cli.js
CHANGED
|
@@ -45,7 +45,7 @@ import { assertTaskDeliveryAuthority, taskLocalActor } from "./task/taskAuthorit
|
|
|
45
45
|
import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
|
|
46
46
|
import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
|
|
47
47
|
import { assertWorkItemDependenciesCompletedForCommand, dispatchPreparedReviewRound, failPendingReviewRound, parseTaskArchiveArguments, parseTaskCompletionRequest, planReplicatedWorkItemLanes, preflightTaskCompletion, preserveReviewRoundWorkspace, previewTaskRoleAgentConfigurationMutation, requireWorkItemAssignee, runTaskCommand, validateTaskArchiveRequest } from "./commands/taskCommands.js";
|
|
48
|
-
import {
|
|
48
|
+
import { verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
|
|
49
49
|
import { finalizeStoppedTaskExecution, parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
|
|
50
50
|
import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
|
|
51
51
|
import { runTaskOverlapCommand } from "./commands/taskOverlapCommands.js";
|
|
@@ -100,7 +100,7 @@ import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/ma
|
|
|
100
100
|
import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
|
|
101
101
|
import { runSetupCommand, validateSetupInvocation } from "./setup/setupCommand.js";
|
|
102
102
|
import { openCurrentTaskStore, validateCurrentTaskStore } from "./storage/currentTaskStore.js";
|
|
103
|
-
import {
|
|
103
|
+
import { SqliteSchemaError } from "./storage/sqliteSchema.js";
|
|
104
104
|
import { inspectStorageSchema } from "./storage/storageSchema.js";
|
|
105
105
|
import { resolveYuiHome } from "./storage/taskStore.js";
|
|
106
106
|
import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
|
|
@@ -141,7 +141,7 @@ void main().catch((error) => {
|
|
|
141
141
|
});
|
|
142
142
|
function runtimeFailureMessage(error) {
|
|
143
143
|
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
-
if (!(error instanceof
|
|
144
|
+
if (!(error instanceof SqliteSchemaError))
|
|
145
145
|
return message;
|
|
146
146
|
try {
|
|
147
147
|
return `${message}\n${describeCliHomeInvocation({
|
|
@@ -225,9 +225,7 @@ export async function main() {
|
|
|
225
225
|
if (args[0] === "update") {
|
|
226
226
|
if (jsonOutput)
|
|
227
227
|
throw usageError("Update does not support --json.");
|
|
228
|
-
|
|
229
|
-
throw usageError("Update usage: yui update");
|
|
230
|
-
process.exitCode = runUpdateCommand();
|
|
228
|
+
process.exitCode = runUpdateCommand(args.slice(1));
|
|
231
229
|
return;
|
|
232
230
|
}
|
|
233
231
|
if (args[0] === "release") {
|
|
@@ -544,12 +542,9 @@ export async function main() {
|
|
|
544
542
|
validateCurrentTaskStore(home);
|
|
545
543
|
const controllerMethod = method;
|
|
546
544
|
const updateHandoverOwner = process.env.YUI_UPDATE_HANDOVER_OWNER_PID;
|
|
547
|
-
//
|
|
548
|
-
// activated restart child, but it remains that child's direct parent while
|
|
549
|
-
// holding the exact handover lock. Inherit only that OS-backed relationship;
|
|
550
|
-
// every unrelated live lock still compares foreign and fails closed.
|
|
545
|
+
// Maintenance children must explicitly name their exact lock owner.
|
|
551
546
|
const updateHandoverOwnerPid = updateHandoverOwner === undefined
|
|
552
|
-
?
|
|
547
|
+
? undefined
|
|
553
548
|
: Number(updateHandoverOwner);
|
|
554
549
|
if (updateHandoverOwnerPid !== undefined
|
|
555
550
|
&& (!Number.isSafeInteger(updateHandoverOwnerPid) || updateHandoverOwnerPid < 1)) {
|
|
@@ -1384,6 +1379,8 @@ export async function main() {
|
|
|
1384
1379
|
}
|
|
1385
1380
|
let completionSummary;
|
|
1386
1381
|
let completionPublishedTreeProof;
|
|
1382
|
+
let completionBaseFreshness;
|
|
1383
|
+
let completionWarnings = [];
|
|
1387
1384
|
if (resolved[1] === "base" && resolved[2] === "status") {
|
|
1388
1385
|
const result = await runTaskBaseStatusCommand(resolved.slice(3), store);
|
|
1389
1386
|
emit(result.output, false, result.data);
|
|
@@ -1400,8 +1397,8 @@ export async function main() {
|
|
|
1400
1397
|
if (resolved[1] === "complete" && resolved[2] !== undefined) {
|
|
1401
1398
|
const completionRequest = parseTaskCompletionRequest(resolved.slice(2));
|
|
1402
1399
|
completionSummary = completionRequest.summary;
|
|
1403
|
-
const refreshRemote =
|
|
1404
|
-
const completion = preflightTaskCompletion(
|
|
1400
|
+
const refreshRemote = completionRequest.refreshRemote;
|
|
1401
|
+
const completion = preflightTaskCompletion(completionRequest.taskId, store, {
|
|
1405
1402
|
environment: process.env,
|
|
1406
1403
|
...(taskFinalReviewContract === undefined
|
|
1407
1404
|
? {}
|
|
@@ -1412,34 +1409,25 @@ export async function main() {
|
|
|
1412
1409
|
// Publication proof resolves its exact commit. Without the flag the
|
|
1413
1410
|
// command remains offline and preserves the existing proof-first path.
|
|
1414
1411
|
const refreshedFreshness = refreshRemote
|
|
1415
|
-
? await inspectTaskBaseFreshness(
|
|
1412
|
+
? await inspectTaskBaseFreshness(completionRequest.taskId, store, { refresh: true })
|
|
1416
1413
|
: undefined;
|
|
1417
1414
|
if (completionRequest.acceptedPublishedTreePublicationId !== undefined) {
|
|
1418
1415
|
completionPublishedTreeProof = await verifyTaskCompletionPublishedTree(completionRequest.taskId, completionRequest.acceptedPublishedTreePublicationId, store);
|
|
1419
1416
|
}
|
|
1420
|
-
|
|
1421
|
-
?? await inspectTaskBaseFreshness(
|
|
1422
|
-
|
|
1417
|
+
completionBaseFreshness = refreshedFreshness
|
|
1418
|
+
?? await inspectTaskBaseFreshness(completionRequest.taskId, store);
|
|
1419
|
+
completionWarnings = assertTaskBaseFreshnessForCompletion(completionBaseFreshness, {
|
|
1423
1420
|
...(completionPublishedTreeProof === undefined
|
|
1424
1421
|
? {}
|
|
1425
1422
|
: {
|
|
1426
1423
|
acceptedPublishedTreeProjectId: completionPublishedTreeProof.projectId
|
|
1427
1424
|
})
|
|
1428
|
-
})
|
|
1425
|
+
});
|
|
1426
|
+
for (const warning of completionWarnings) {
|
|
1429
1427
|
process.stderr.write(`Warning: ${warning}\n`);
|
|
1430
1428
|
}
|
|
1431
|
-
//
|
|
1432
|
-
//
|
|
1433
|
-
if (refreshRemote) {
|
|
1434
|
-
const reconciled = await reconcileTaskRemoteBaselines(resolved[2], store, home, { environment: process.env, jobPort: createControllerIntegrationJobPort(home, { environment: process.env }) });
|
|
1435
|
-
if (reconciled.length > 0) {
|
|
1436
|
-
const updates = reconciled.map((entry) => (`${entry.projectId}: ${entry.fromCommit} -> ${entry.toCommit} `
|
|
1437
|
-
+ `(Integration ${entry.integrationId})`)).join("; ");
|
|
1438
|
-
throw usageError(`Remote baseline reconciliation advanced Task ${resolved[2]} (${updates}). `
|
|
1439
|
-
+ "The Task remains active so the Leader can inspect the new authoritative head, "
|
|
1440
|
-
+ "decide how prior Review evidence applies, and retry task complete.");
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1429
|
+
// Refresh observes remote objects only. Choosing and executing an
|
|
1430
|
+
// upstream Integration belongs to its explicit command, never completion.
|
|
1443
1431
|
}
|
|
1444
1432
|
}
|
|
1445
1433
|
let releaseReviewHandoverLock;
|
|
@@ -1628,9 +1616,24 @@ export async function main() {
|
|
|
1628
1616
|
emitControlFailure(result.output, failureCode, result.data);
|
|
1629
1617
|
return;
|
|
1630
1618
|
}
|
|
1631
|
-
|
|
1619
|
+
const commandData = reviewData === undefined
|
|
1632
1620
|
? result.data
|
|
1633
|
-
: { command: result.data, ...reviewData }
|
|
1621
|
+
: { command: result.data, ...reviewData };
|
|
1622
|
+
const completionData = resolved[1] === "complete" ? {
|
|
1623
|
+
// Review dispatch may have advanced or failed after the command
|
|
1624
|
+
// prepared its Round. Report that observed stage, not preparation.
|
|
1625
|
+
...(reviewData === undefined ? {} : {
|
|
1626
|
+
stage: reviewRoundFromCommandData(reviewData)?.status === "running"
|
|
1627
|
+
? "review-running"
|
|
1628
|
+
: "review-blocked",
|
|
1629
|
+
projectHeads: result.data.projectHeads
|
|
1630
|
+
}),
|
|
1631
|
+
baseFreshness: completionBaseFreshness ?? null,
|
|
1632
|
+
warnings: completionWarnings
|
|
1633
|
+
} : undefined;
|
|
1634
|
+
emit(`${result.output}${reviewOutput}`, false, completionData === undefined
|
|
1635
|
+
? commandData
|
|
1636
|
+
: { ...commandData, ...completionData });
|
|
1634
1637
|
return;
|
|
1635
1638
|
}
|
|
1636
1639
|
if (jsonOutput && result.kind !== "session-stop"
|
|
@@ -2431,7 +2434,10 @@ async function executeOperatorSessionControl(control, home, store, runtime, tmux
|
|
|
2431
2434
|
await ensureFileTaskController(home, { environment: process.env });
|
|
2432
2435
|
if (paneRunning
|
|
2433
2436
|
|| (active !== undefined
|
|
2434
|
-
&& active.status === "active")
|
|
2437
|
+
&& active.status === "active")
|
|
2438
|
+
|| (sessionSet?.providerBinding?.run != null
|
|
2439
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(sessionSet.providerBinding.run.status))
|
|
2440
|
+
|| (sessionSet?.providerBinding?.goal != null && sessionSet.providerBinding.goal.status !== "complete")) {
|
|
2435
2441
|
await runtime.stopGlobalRoleSession(role.name);
|
|
2436
2442
|
}
|
|
2437
2443
|
applyOperatorSessionControl(control, store);
|
|
@@ -49,7 +49,7 @@ export function renderControllerResourceStatus(snapshot, verbose, width = defaul
|
|
|
49
49
|
{ header: "Reason", minWidth: 12, maxWidth: 30 }
|
|
50
50
|
], visibleDomains.map((domain) => [
|
|
51
51
|
domain.yuiHome,
|
|
52
|
-
domain.domainKind ?? "
|
|
52
|
+
domain.domainKind ?? "unmarked",
|
|
53
53
|
domain.liveness === undefined
|
|
54
54
|
? "—"
|
|
55
55
|
: `${domain.liveness}/${domain.disposition ?? "review"}`,
|
|
@@ -202,7 +202,7 @@ export function renderRuntimeIdentitySection(input) {
|
|
|
202
202
|
` Source commit ${build.sourceCommit}`,
|
|
203
203
|
` Node ${build.nodeVersion} (${build.platform})`,
|
|
204
204
|
` Storage version ${storage.storageVersion} (status ${storage.storageStatus}, minimum ${storage.minimumStorageVersion}) · backend ${storage.configuredBackend} · worker ${storage.workerEnabled ? "on" : "off"}`,
|
|
205
|
-
` Store files
|
|
205
|
+
` Store files yui.db ${storage.physicalDatabase.present ? "present" : "absent"}${storage.physicalDatabase.wal ? " +WAL" : ""}${storage.physicalDatabase.present && storage.physicalDatabase.health !== UNSUPPORTED ? ` (${storage.physicalDatabase.health})` : ""}`
|
|
206
206
|
];
|
|
207
207
|
for (const finding of storage.findings) {
|
|
208
208
|
lines.push(` ! ${finding.severity === "contradiction" ? "CONTRADICTION" : finding.severity === "needs-repair" ? "NEEDS-REPAIR" : "warning"} ${finding.code}: ${finding.message}`, ` remediation: ${finding.remediation}`);
|
|
@@ -258,8 +258,8 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
258
258
|
}
|
|
259
259
|
if (report.storage.status === "ok" && report.storage.data !== undefined) {
|
|
260
260
|
const storage = report.storage.data;
|
|
261
|
-
lines.push("", `Storage: backend ${storage.backend} ·
|
|
262
|
-
+ ` · runtime/ ${formatBytes(storage.runtimeDirBytes)}
|
|
261
|
+
lines.push("", `Storage: backend ${storage.backend} · yui.db ${formatBytes(storage.databaseBytes)}`
|
|
262
|
+
+ ` · runtime/ ${formatBytes(storage.runtimeDirBytes)}`);
|
|
263
263
|
}
|
|
264
264
|
else {
|
|
265
265
|
lines.push("", ...sectionError("storage", report));
|
|
@@ -238,7 +238,7 @@ async function cloneProject(args, store, options) {
|
|
|
238
238
|
// A remote-URL binding is Home-managed by default: its canonical repository
|
|
239
239
|
// lives below the persistent Home, so the runtime never depends on a
|
|
240
240
|
// user-controlled checkout path. --external is an explicit opt-in to the
|
|
241
|
-
//
|
|
241
|
+
// user-owned clone-inside-the-workspace mode.
|
|
242
242
|
let destination;
|
|
243
243
|
let ownership;
|
|
244
244
|
if (parsed.external) {
|
|
@@ -341,55 +341,26 @@ export function createReleaseActivatePorts(overrides = {}) {
|
|
|
341
341
|
}),
|
|
342
342
|
runPreflight: overrides.runPreflight ?? ((releaseDir, home) => {
|
|
343
343
|
const cli = join(releaseDir, "dist", "cli.js");
|
|
344
|
-
|
|
344
|
+
// The target owns current-record and database integrity validation.
|
|
345
|
+
// Activation never migrates; a valid but older Home still blocks here.
|
|
346
|
+
const result = spawnSync(process.execPath, [cli, "--json", "upgrade", "--update-preflight"], {
|
|
345
347
|
env: { ...process.env, YUI_HOME: home, NO_COLOR: "1" },
|
|
346
348
|
encoding: "utf8",
|
|
347
349
|
timeout: 60_000
|
|
348
350
|
});
|
|
349
351
|
let report;
|
|
350
352
|
try {
|
|
351
|
-
|
|
352
|
-
report = envelope.data;
|
|
353
|
+
report = JSON.parse(result.stdout);
|
|
353
354
|
}
|
|
354
355
|
catch (error) {
|
|
355
356
|
throw new Error(`Release preflight produced no JSON (exit ${result.status}): `
|
|
356
357
|
+ `${messageOf(error)} ${result.stderr.trim()}`);
|
|
357
358
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
throw new Error("Release preflight report has no storage checks.");
|
|
364
|
-
}
|
|
365
|
-
const required = ["storage compatibility"];
|
|
366
|
-
const failed = required.filter((name) => {
|
|
367
|
-
const check = checks.find((candidate) => candidate !== null
|
|
368
|
-
&& typeof candidate === "object"
|
|
369
|
-
&& candidate.name === name);
|
|
370
|
-
return check === undefined
|
|
371
|
-
|| check.status !== "ok";
|
|
372
|
-
});
|
|
373
|
-
if (failed.length > 0) {
|
|
374
|
-
throw new Error(`Release preflight storage compatibility checks failed: ${failed.join(", ")}.`);
|
|
375
|
-
}
|
|
376
|
-
// Doctor proves the target's storage, not the pinned code in existing
|
|
377
|
-
// Hosts. Ask the target's upgrade boundary for its independent Host
|
|
378
|
-
// protocol proof as well; release activation itself still never migrates.
|
|
379
|
-
const hostCheck = spawnSync(process.execPath, [cli, "--json", "upgrade", "--update-preflight"], {
|
|
380
|
-
env: { ...process.env, YUI_HOME: home, NO_COLOR: "1" },
|
|
381
|
-
encoding: "utf8", timeout: 60_000
|
|
382
|
-
});
|
|
383
|
-
let preflight;
|
|
384
|
-
try {
|
|
385
|
-
preflight = JSON.parse(hostCheck.stdout).data;
|
|
386
|
-
}
|
|
387
|
-
catch {
|
|
388
|
-
throw new Error(`Release Host preflight produced no JSON: ${hostCheck.stderr.trim()}`);
|
|
389
|
-
}
|
|
390
|
-
if (hostCheck.status !== 0 || preflight?.outcome !== "update-preflight"
|
|
391
|
-
|| preflight.status !== "already-current") {
|
|
392
|
-
throw new Error(`Release Host compatibility preflight failed: ${preflight?.message ?? preflight?.status ?? "unknown"}. ${preflight?.action ?? ""}`);
|
|
359
|
+
if (result.status !== 0 || report?.ok !== true
|
|
360
|
+
|| report.data?.outcome !== "update-preflight"
|
|
361
|
+
|| report.data.status !== "already-current") {
|
|
362
|
+
throw new Error(`Release storage preflight failed: ${report?.data?.message ?? report?.data?.status ?? "invalid response"}. `
|
|
363
|
+
+ `${report?.data?.action ?? result.stderr.trim()}`);
|
|
393
364
|
}
|
|
394
365
|
}),
|
|
395
366
|
killOwnedProcess: overrides.killOwnedProcess ?? ((owner) => {
|