@cotal-ai/cli 0.153.1 → 0.155.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/dist/command.d.ts.map +1 -1
- package/dist/command.js +7 -6
- package/dist/command.js.map +1 -1
- package/dist/commands/mint.js +1 -1
- package/dist/commands/mint.js.map +1 -1
- package/dist/commands/reset-space-apply.d.ts.map +1 -1
- package/dist/commands/reset-space-apply.js +5 -2
- package/dist/commands/reset-space-apply.js.map +1 -1
- package/dist/commands/space-registration.d.ts.map +1 -1
- package/dist/commands/space-registration.js +2 -3
- package/dist/commands/space-registration.js.map +1 -1
- package/dist/commands/space.d.ts +5 -3
- package/dist/commands/space.d.ts.map +1 -1
- package/dist/commands/space.js +16 -9
- package/dist/commands/space.js.map +1 -1
- package/dist/commands/spawn-preview.d.ts.map +1 -1
- package/dist/commands/spawn-preview.js +4 -8
- package/dist/commands/spawn-preview.js.map +1 -1
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +11 -1
- package/dist/commands/spawn.js.map +1 -1
- package/dist/commands/stack-down.d.ts.map +1 -1
- package/dist/commands/stack-down.js +13 -8
- package/dist/commands/stack-down.js.map +1 -1
- package/dist/commands/stack-lost-records.d.ts +7 -0
- package/dist/commands/stack-lost-records.d.ts.map +1 -0
- package/dist/commands/stack-lost-records.js +143 -0
- package/dist/commands/stack-lost-records.js.map +1 -0
- package/dist/commands/stack-up.d.ts +3 -3
- package/dist/commands/stack-up.d.ts.map +1 -1
- package/dist/commands/stack-up.js +20 -12
- package/dist/commands/stack-up.js.map +1 -1
- package/dist/commands/status.d.ts +2 -2
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +90 -44
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +8 -6
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/team-boot-controller.d.ts.map +1 -1
- package/dist/commands/team-boot-controller.js +3 -1
- package/dist/commands/team-boot-controller.js.map +1 -1
- package/dist/commands/team.d.ts.map +1 -1
- package/dist/commands/team.js +6 -2
- package/dist/commands/team.js.map +1 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +3 -3
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +7 -11
- package/dist/commands/whoami.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +11 -2
- package/dist/help.js.map +1 -1
- package/dist/lib/controller-resume.d.ts.map +1 -1
- package/dist/lib/controller-resume.js +2 -1
- package/dist/lib/controller-resume.js.map +1 -1
- package/dist/lib/manifest/prepare.d.ts.map +1 -1
- package/dist/lib/manifest/prepare.js +4 -0
- package/dist/lib/manifest/prepare.js.map +1 -1
- package/dist/lib/persona-files.js +4 -4
- package/dist/lib/persona-files.js.map +1 -1
- package/dist/lib/restore.d.ts.map +1 -1
- package/dist/lib/restore.js +3 -1
- package/dist/lib/restore.js.map +1 -1
- package/dist/lib/service-presets.d.ts.map +1 -1
- package/dist/lib/service-presets.js +7 -1
- package/dist/lib/service-presets.js.map +1 -1
- package/dist/lib/stack-journal.d.ts +16 -0
- package/dist/lib/stack-journal.d.ts.map +1 -1
- package/dist/lib/stack-journal.js +39 -8
- package/dist/lib/stack-journal.js.map +1 -1
- package/dist/lib/store-identity.d.ts +15 -0
- package/dist/lib/store-identity.d.ts.map +1 -0
- package/dist/lib/store-identity.js +30 -0
- package/dist/lib/store-identity.js.map +1 -0
- package/package.json +16 -4
package/dist/commands/status.js
CHANGED
|
@@ -2,7 +2,7 @@ import { accessSync, constants, existsSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { CotalEndpoint, agentFilePath, dialerFor, EpEnvelopeError, formatAge, isReachable, mintCreds, mintLifecycleUid, resolveService, realDirNoSymlink, invokeRepairingSplit, standaloneConnectOpts, newIdentity, idFromCreds, principalKey, DEV_OWNER, unansweredRequest, resolveAuthProvider, personaDir, presenceText, } from "@cotal-ai/core";
|
|
5
|
-
import { stoppedLoopbackListener, accountInventory, assessMaintenanceClaim, authDir, isCommandFailure, readMaintenanceJournal, recordsElsewhereError, recordsElsewhereText, restoreRolledBackRuns, targetErrorSpace, brokerAddressForDisplay, canonicalRoot, CLI_USER_ACTOR, codeSpan, CommandFailure, deliveryCredsKey, DELIVERY_PIDFILE, extensionsDir, findCotalRoot, folderSpaceOverSelection, meshesForRoot, resolveReadsLiveness, stoppedStack, unsettledStackOperation, withdrawOperationNote, hasUserAuthState, homeCotalDir, cachedPreferences, loadLaunchPersonaFile, resolvePreference, isMeshRegistryError, isWorkspaceTargetError, jsonFlag, loadManagerInstanceIdentity, loadSoleSpaceAuth, localProcessPath, localProcessVisible, MANAGER_PIDFILE, oneLine, PREFERENCE_KEYS, parsePid, plainFlag, preflightTarget, presentationOf, principalFromBearer, probeLiveness, probeWebReadiness, readCurrent, readMeshRegistry, readPidfile, preparedRecordSkipped, readPreparedSpaces, readRenewalRecord, renderWorkspaceError, resolveMeshTarget, serverFlag, spaceFlag, userAuthStateDir, WEB_PIDFILE, WEB_READINESS_HEADER, workspaceSecretStore, writeResult, workspaceErrorParts, preferencesPath, preferencesFolderWarning, cotalDirLink, movedProject, spaceStreamsAbsent, spaceStreamsExpected, resolveControlTarget, } from "@cotal-ai/workspace";
|
|
5
|
+
import { stoppedLoopbackListener, accountInventory, assessMaintenanceClaim, authDir, isCommandFailure, readMaintenanceJournal, recordsElsewhereError, recordsElsewhereText, restoreRolledBackRuns, targetErrorSpace, brokerAddressForDisplay, brokerCheck, canonicalRoot, CLI_USER_ACTOR, codeSpan, CommandFailure, deliveryCredsKey, DELIVERY_PIDFILE, extensionsDir, findCotalRoot, folderSpaceOverSelection, meshesForRoot, resolveReadsLiveness, stoppedStack, unsettledStackOperation, withdrawOperationNote, hasUserAuthState, homeCotalDir, cachedPreferences, loadLaunchPersonaFile, resolvePreference, isMeshRegistryError, isWorkspaceTargetError, jsonFlag, loadManagerInstanceIdentity, loadSoleSpaceAuth, localProcessPath, localProcessVisible, MANAGER_PIDFILE, oneLine, PREFERENCE_KEYS, parsePid, plainFlag, preflightTarget, presentationOf, principalFromBearer, probeLiveness, probeWebReadiness, readCurrent, readMeshRegistry, readPidfile, preparedRecordSkipped, readPreparedSpaces, readRenewalRecord, renderWorkspaceError, resolveMeshTarget, serverFlag, spaceFlag, userAuthStateDir, WEB_PIDFILE, WEB_READINESS_HEADER, workspaceSecretStore, writeResult, workspaceErrorParts, preferencesPath, preferencesFolderWarning, cotalDirLink, movedProject, spaceStreamsAbsent, spaceStreamsExpected, resolveControlTarget, } from "@cotal-ai/workspace";
|
|
6
6
|
import { localProcessSurface } from "../ext-loader.js";
|
|
7
7
|
import { cliVersion, cliProvenance, readExtensionVersions } from "../lib/version.js";
|
|
8
8
|
import { agentSkillsSkew } from "../lib/agent-skills.js";
|
|
@@ -30,9 +30,10 @@ import { freshTeamAttemptSettled, listFreshTeamAttempts, listFreshTeamSummaries
|
|
|
30
30
|
import { additiveTeamRecordedState, listAdditiveTeamAttempts } from "../lib/team-additive-attempt.js";
|
|
31
31
|
import { maintenanceNextStep, preservedSourceState, restoreNextStep, rolledBackNotes } from "../lib/restore.js";
|
|
32
32
|
import { attemptProcessesGone } from "../lib/isolated-broker.js";
|
|
33
|
-
import { journalRecordedElsewhere, readStackJournal, serviceHeldRecords, STACK_DEFINITION, stackStartedWithoutManager, stackOperationBlock,
|
|
33
|
+
import { journalRecordedElsewhere, readStackJournal, serviceHeldRecords, STACK_DEFINITION, stackStartedWithoutManager, stackOperationBlock, stackRootGone, unfinishedStackOperation, } from "../lib/stack-journal.js";
|
|
34
34
|
import { cotalLinkHere, homeHoldsStack, linkedCotalRemedy } from "../lib/paths.js";
|
|
35
35
|
import { storeWriteProblem } from "../lib/store-dir.js";
|
|
36
|
+
import { stackStoreWarnings } from "../lib/store-identity.js";
|
|
36
37
|
import { natsServerLabel } from "../lib/nats-bin.js";
|
|
37
38
|
import { askManager } from "@cotal-ai/workspace/control";
|
|
38
39
|
/** `--components` is the fail-loud health pass with a machine-readable EXIT DISPOSITION. Bare
|
|
@@ -59,21 +60,20 @@ export const statusFlags = [
|
|
|
59
60
|
jsonFlag,
|
|
60
61
|
plainFlag,
|
|
61
62
|
];
|
|
62
|
-
/** A project root with no registration or prepared space
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
/** A project root with an empty `.cotal/` and no registration or prepared space. The resolver
|
|
64
|
+
* gives it the default broker address, which any broker may hold, so status selects no space for
|
|
65
|
+
* it and dials nothing. A root whose records name no space is refused by the resolver instead, and
|
|
66
|
+
* status uses that refusal. */
|
|
65
67
|
class UnregisteredRoot {
|
|
66
68
|
root;
|
|
67
|
-
|
|
68
|
-
constructor(root, namesNoSpace) {
|
|
69
|
+
constructor(root) {
|
|
69
70
|
this.root = root;
|
|
70
|
-
this.namesNoSpace = namesNoSpace;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
function resolveSelected(cwd, values, offline) {
|
|
74
74
|
try {
|
|
75
75
|
const target = resolveMeshTarget(cwd, { server: values.server, space: values.space, offline });
|
|
76
|
-
return target.source === "local-space" ? { ok: false, error: new UnregisteredRoot(target.root
|
|
76
|
+
return target.source === "local-space" ? { ok: false, error: new UnregisteredRoot(target.root) } : { ok: true, target };
|
|
77
77
|
}
|
|
78
78
|
catch (e) {
|
|
79
79
|
return { ok: false, error: e };
|
|
@@ -162,10 +162,24 @@ export async function status(args) {
|
|
|
162
162
|
// THE RESPONDER AXIS (#1576), read ONCE and threaded into the rows that would otherwise claim
|
|
163
163
|
// health they never checked. It is read before the folder section because that section renders
|
|
164
164
|
// the delivery process row, and a live pid means nothing without it.
|
|
165
|
-
const
|
|
165
|
+
const preflight = selected.ok && selected.target.mode !== "user" ? await preflightTarget(selected.target) : undefined;
|
|
166
|
+
const responder = await readResponderAxis(selected, preflight);
|
|
166
167
|
const folder = blocked ? unreadFolder(root, blocked) : await readFolder(root, selected, values, responder.state, gaps, hints);
|
|
167
|
-
const meshes = await readMeshes(cwd, selected, Boolean(values.all), gaps, offline);
|
|
168
|
-
const
|
|
168
|
+
const meshes = await readMeshes(cwd, selected, Boolean(values.all), gaps, offline, preflight);
|
|
169
|
+
const maintenance = blocked ? { root, space: null, state: "unreadable", attemptId: null, next: null } : readMaintenance(root, gaps);
|
|
170
|
+
const recovery = backupRecovery(maintenance);
|
|
171
|
+
const selectedHints = [];
|
|
172
|
+
const target = await readSelected(selected, responder, folder, gaps, selectedHints, preflight);
|
|
173
|
+
// A held backup must be released before this root can start. Keep the selection's diagnostics,
|
|
174
|
+
// but replace its ordinary restart advice with the journal's recovery step.
|
|
175
|
+
if (recovery)
|
|
176
|
+
hints.unshift(recovery);
|
|
177
|
+
else
|
|
178
|
+
hints.push(...selectedHints);
|
|
179
|
+
if (!blocked)
|
|
180
|
+
readStackStoreWarnings(root, gaps, hints);
|
|
181
|
+
if (selected.ok && selected.target.origin === "up" && !sameDir(root, selected.target.root))
|
|
182
|
+
readStackStoreWarnings(selected.target.root, gaps, hints, selected.target.space);
|
|
169
183
|
// The note under the heading sends here for this folder's own stack cut off with no recorded
|
|
170
184
|
// outcome under another selection. A blocked one is named with its block under `This folder's
|
|
171
185
|
// space` and its step first in Next, so that note is left out here, with its --json warning. One
|
|
@@ -173,11 +187,10 @@ export async function status(args) {
|
|
|
173
187
|
const held = folderStack(folder, target);
|
|
174
188
|
if (held && folder.stackBlock)
|
|
175
189
|
withdrawOperationNote("FOLDER_SPACE_NOT_RUNNING", held.space);
|
|
176
|
-
if (held && !folder.stackBlock && !folder.stackUnfinished && unsettledStackOperation(held.root, held.space))
|
|
190
|
+
if (!recovery && held && !folder.stackBlock && !folder.stackUnfinished && unsettledStackOperation(held.root, held.space))
|
|
177
191
|
hints.push(`start it with \`cotal up\` in ${held.root}`);
|
|
178
192
|
const services = readOwnedServices(selected, gaps);
|
|
179
193
|
const bootAttempts = blocked ? { root, state: "partial", observedAt: new Date().toISOString(), unsettled: [], additive: [], unreadable: [], elsewhere: [] } : readBootAttempts(root, gaps);
|
|
180
|
-
const maintenance = blocked ? { root, space: null, state: "unreadable", attemptId: null, next: null } : readMaintenance(root, gaps);
|
|
181
194
|
const logged = selected.ok ? brokerStoreWrite(selected.target.root, selected.target.space) : undefined;
|
|
182
195
|
// A store write failure holds until the broker restarts, so it is named before a full store.
|
|
183
196
|
const full = selected.ok && target.resolved && target.storedBytes !== null && logged?.state !== "write-failed" ? storeFull(selected.target.root, selected.target.space, target.storedBytes) : undefined;
|
|
@@ -476,7 +489,7 @@ async function expectedDeliveryHolder(target) {
|
|
|
476
489
|
*
|
|
477
490
|
* `leased` says whether the read found a delivery lease record. An open mesh runs no delivery
|
|
478
491
|
* daemon, so there a missing lease is the normal state rather than an unbound responder. */
|
|
479
|
-
async function readResponderAxis(selected) {
|
|
492
|
+
async function readResponderAxis(selected, preflight) {
|
|
480
493
|
let leased = false;
|
|
481
494
|
if (!selected.ok)
|
|
482
495
|
return { state: "unknown", leased };
|
|
@@ -484,8 +497,7 @@ async function readResponderAxis(selected) {
|
|
|
484
497
|
if (target.mode === "user")
|
|
485
498
|
return { state: "unknown", leased };
|
|
486
499
|
try {
|
|
487
|
-
|
|
488
|
-
if (!preflight.ok)
|
|
500
|
+
if (!preflight?.ok)
|
|
489
501
|
return { state: "unknown", leased };
|
|
490
502
|
const id = newIdentity();
|
|
491
503
|
const creds = target.auth ? await mintCreds(target.auth, id, "observer") : undefined;
|
|
@@ -646,9 +658,9 @@ async function readFolder(root, selected, values, responder, gaps, hints) {
|
|
|
646
658
|
controllerStack = false;
|
|
647
659
|
// A journal no command can use is named with its way out, in the warnings and under Next; the
|
|
648
660
|
// rest of the report still stands.
|
|
649
|
-
if (isCommandFailure(error) && error.code === "SCHEMA_INVALID") {
|
|
661
|
+
if (isCommandFailure(error) && (error.code === "SCHEMA_INVALID" || error.code === "PERMISSION_DENIED")) {
|
|
650
662
|
unusable = true;
|
|
651
|
-
gaps.add("STACK_JOURNAL_INVALID", error.remedy ? `${error.message}; ${error.remedy}` : error.message, { root });
|
|
663
|
+
gaps.add(error.code === "PERMISSION_DENIED" ? "STACK_JOURNAL_UNREADABLE" : "STACK_JOURNAL_INVALID", error.remedy ? `${error.message}; ${error.remedy}` : error.message, { root });
|
|
652
664
|
if (error.remedy)
|
|
653
665
|
hints.push(error.remedy);
|
|
654
666
|
}
|
|
@@ -929,7 +941,7 @@ function deliveryDetail(live, responder) {
|
|
|
929
941
|
* with its own recorded TLS requirement. A bare TCP/INFO probe would green-light a plaintext broker
|
|
930
942
|
* that has substituted for a TLS-required mesh — the FAIL1 attack — so a list that only asks "is
|
|
931
943
|
* anything listening" cannot report on the one property the record claims. */
|
|
932
|
-
async function readMeshes(cwd, selected, all, gaps, offline) {
|
|
944
|
+
async function readMeshes(cwd, selected, all, gaps, offline, preflight) {
|
|
933
945
|
// A registry file that cannot be read is named, and the readable records are still listed and
|
|
934
946
|
// checked. Only an absent directory or pointer is an empty registry or no current mesh.
|
|
935
947
|
const unreadable = [];
|
|
@@ -971,6 +983,18 @@ async function readMeshes(cwd, selected, all, gaps, offline) {
|
|
|
971
983
|
const chosen = selected.ok ? selected.target : undefined;
|
|
972
984
|
// A `--server` that names another address is the only broker dialed for the selected space.
|
|
973
985
|
const checked = (m) => all || (m.space === chosen?.space && m.server === chosen.server);
|
|
986
|
+
const check = async (m) => {
|
|
987
|
+
if (!checked(m))
|
|
988
|
+
return "not-checked";
|
|
989
|
+
if (preflight && m.space === chosen?.space && m.server === chosen.server) {
|
|
990
|
+
if (preflight.ok)
|
|
991
|
+
return "reachable";
|
|
992
|
+
if (preflight.kind === "slow-link" || preflight.kind === "broker-stopped" || preflight.kind === "unreachable")
|
|
993
|
+
return preflight.kind;
|
|
994
|
+
return preflight.kind === "tls-trust" || preflight.kind === "tls-unsupported" ? "unreachable" : "reachable";
|
|
995
|
+
}
|
|
996
|
+
return brokerCheck(m.server, m.tlsRequired);
|
|
997
|
+
};
|
|
974
998
|
const items = await Promise.all(entries.map(async (m) => ({
|
|
975
999
|
space: m.space,
|
|
976
1000
|
server: displayStatusBroker(m.server, m.space, gaps),
|
|
@@ -981,7 +1005,7 @@ async function readMeshes(cwd, selected, all, gaps, offline) {
|
|
|
981
1005
|
eventsRequired: m.policy?.events === "required",
|
|
982
1006
|
modeChecked: m.unchecked !== true,
|
|
983
1007
|
current: m.space === current,
|
|
984
|
-
check:
|
|
1008
|
+
check: await check(m),
|
|
985
1009
|
catalog: m.origin === "catalog" ? { name: m.catalogName && m.catalogName !== m.space ? m.catalogName : null, fetchedAt: m.catalogFetchedAt ?? null, error: m.catalogError ?? null } : null,
|
|
986
1010
|
})));
|
|
987
1011
|
// The prepared spaces folder, or a record in it, that cannot be read is named the same way.
|
|
@@ -1014,6 +1038,25 @@ async function readMeshes(cwd, selected, all, gaps, offline) {
|
|
|
1014
1038
|
catalogs: preparedCatalogDiagnostics().map((r) => ({ idp: r.account.idpUrl, account: r.account.sub, state: r.state, error: r.state === "failed" ? (r.error ?? "refresh failed") : null })),
|
|
1015
1039
|
};
|
|
1016
1040
|
}
|
|
1041
|
+
/** Diagnose the current folder's store and a selected local stack independently of folder trust. */
|
|
1042
|
+
function readStackStoreWarnings(root, gaps, hints, space) {
|
|
1043
|
+
try {
|
|
1044
|
+
const journal = readStackJournal(root)?.journal;
|
|
1045
|
+
if (journal && (space === undefined || journal.space === space))
|
|
1046
|
+
for (const warning of stackStoreWarnings(journal))
|
|
1047
|
+
gaps.add(warning.code, warning.message, warning.target);
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
if (isCommandFailure(error) && (error.code === "SCHEMA_INVALID" || error.code === "PERMISSION_DENIED")) {
|
|
1051
|
+
gaps.add(error.code === "PERMISSION_DENIED" ? "STACK_JOURNAL_UNREADABLE" : "STACK_JOURNAL_INVALID", error.remedy ? `${error.message}; ${error.remedy}` : error.message, { root });
|
|
1052
|
+
if (error.remedy)
|
|
1053
|
+
hints.push(error.remedy);
|
|
1054
|
+
}
|
|
1055
|
+
else if (!isCommandFailure(error) || error.code !== "CONFLICT")
|
|
1056
|
+
throw error;
|
|
1057
|
+
// A copied or moved journal is diagnosed by the folder and selection readers.
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1017
1060
|
/** The refusal of the stack journal at `root` when it is one no command can use, as `up` and `down`
|
|
1018
1061
|
* refuse it. Undefined when it reads or is absent. */
|
|
1019
1062
|
function unusableStackJournal(root) {
|
|
@@ -1022,7 +1065,7 @@ function unusableStackJournal(root) {
|
|
|
1022
1065
|
return undefined;
|
|
1023
1066
|
}
|
|
1024
1067
|
catch (error) {
|
|
1025
|
-
return isCommandFailure(error) && error.code === "SCHEMA_INVALID" ? error : undefined;
|
|
1068
|
+
return isCommandFailure(error) && (error.code === "SCHEMA_INVALID" || error.code === "PERMISSION_DENIED") ? error : undefined;
|
|
1026
1069
|
}
|
|
1027
1070
|
}
|
|
1028
1071
|
/** The refusal of `space`'s service instance records when one is a record no command can use, as
|
|
@@ -1053,7 +1096,7 @@ function rootStoreProblem(root) {
|
|
|
1053
1096
|
return undefined;
|
|
1054
1097
|
}
|
|
1055
1098
|
}
|
|
1056
|
-
async function readSelected(selected, responder, folder, gaps, hints) {
|
|
1099
|
+
async function readSelected(selected, responder, folder, gaps, hints, preflight) {
|
|
1057
1100
|
const cmd = displayCmd();
|
|
1058
1101
|
if (!selected.ok) {
|
|
1059
1102
|
const e = selected.error;
|
|
@@ -1074,11 +1117,7 @@ async function readSelected(selected, responder, folder, gaps, hints) {
|
|
|
1074
1117
|
}
|
|
1075
1118
|
if (e instanceof UnregisteredRoot) {
|
|
1076
1119
|
const message = `no space is registered or prepared here for ${e.root}`;
|
|
1077
|
-
|
|
1078
|
-
// which then holds it, so the step that brings a forgotten space back is to prepare it again.
|
|
1079
|
-
hints.push(e.namesNoSpace
|
|
1080
|
-
? `prepare a space with \`${cmd} space create <name> --root ${shellWord(e.root)}\`, or select one with \`${cmd} use <name>\``
|
|
1081
|
-
: `run \`${cmd} up\` in ${e.root} to start one, or select a space with \`${cmd} use <name>\``);
|
|
1120
|
+
hints.push(`run \`${cmd} up\` in ${e.root} to start one, or select a space with \`${cmd} use <name>\``);
|
|
1082
1121
|
return { resolved: false, code: "unregistered-root", message };
|
|
1083
1122
|
}
|
|
1084
1123
|
if (!isWorkspaceTargetError(e))
|
|
@@ -1145,6 +1184,16 @@ async function readSelected(selected, responder, folder, gaps, hints) {
|
|
|
1145
1184
|
spaceStreams: { state: "not-checked", root: null, stack: null },
|
|
1146
1185
|
storedBytes: null,
|
|
1147
1186
|
};
|
|
1187
|
+
// A moved project keeps its trust under its new path. Prefer that specific diagnosis when
|
|
1188
|
+
// this folder holds the records, otherwise report the missing stack root before any auth probe.
|
|
1189
|
+
const moved = target.mode === "auth" && !target.auth ? movedProject(target.space, target.root) : undefined;
|
|
1190
|
+
if (moved) {
|
|
1191
|
+
hints.push(moved.remedy);
|
|
1192
|
+
return { resolved: false, code: "moved-folder", message: moved.message };
|
|
1193
|
+
}
|
|
1194
|
+
const overridden = (target.source === "flag-server" || target.source === "flag-space-override") && target.recordedServer !== target.server;
|
|
1195
|
+
if (base.rootGone && !overridden)
|
|
1196
|
+
return { ...base, connection: "root-gone", snapshot: { state: "skipped", reason: "the stack's project folder was moved or deleted" } };
|
|
1148
1197
|
if (target.mode === "user") {
|
|
1149
1198
|
// Status never static-mints on a user-auth mesh (the flip). Everything here is offline
|
|
1150
1199
|
// introspection — the login cache + the locally-readable ledger — plus, only when this
|
|
@@ -1177,14 +1226,8 @@ async function readSelected(selected, responder, folder, gaps, hints) {
|
|
|
1177
1226
|
return { ...result, snapshot: { state: "skipped", reason: "needs a signed-in, granted login" } };
|
|
1178
1227
|
return { ...result, ...(await readSnapshot(() => userSnapshotEndpoint(target, stateDir), target.space, true, gaps)) };
|
|
1179
1228
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
const moved = target.mode === "auth" && !target.auth ? movedProject(target.space, target.root) : undefined;
|
|
1183
|
-
if (moved) {
|
|
1184
|
-
hints.push(moved.remedy);
|
|
1185
|
-
return { resolved: false, code: "moved-folder", message: moved.message };
|
|
1186
|
-
}
|
|
1187
|
-
const preflight = await preflightTarget(target);
|
|
1229
|
+
if (preflight === undefined)
|
|
1230
|
+
throw new Error("status: selected non-user target has no preflight observation");
|
|
1188
1231
|
if (!preflight.ok) {
|
|
1189
1232
|
// A `--server` address that is not the one recorded for the space says nothing about whether its
|
|
1190
1233
|
// stack runs, so Next names that address as the refusal of other commands does. A stopped stack
|
|
@@ -1409,7 +1452,7 @@ function folderSection(f, selected) {
|
|
|
1409
1452
|
return { title: "This Folder", blocks: [{ kind: "fields", rows }] };
|
|
1410
1453
|
}
|
|
1411
1454
|
function meshesSections(m) {
|
|
1412
|
-
const check = { reachable: cell("reachable", "ok"), unreachable: cell("down", "failed"), "not-checked": cell("not checked", "hint") };
|
|
1455
|
+
const check = { reachable: cell("reachable", "ok"), unreachable: cell("down", "failed"), "slow-link": cell("not answering", "failed"), "broker-stopped": cell("broker process is stopped", "failed"), "not-checked": cell("not checked", "hint") };
|
|
1413
1456
|
const blocks = [];
|
|
1414
1457
|
if (!m.items.length && !m.unreadable.length)
|
|
1415
1458
|
blocks.push({ kind: "lines", lines: [cell("none", "hint")] });
|
|
@@ -1608,17 +1651,13 @@ function machineOverview(r, wide) {
|
|
|
1608
1651
|
],
|
|
1609
1652
|
};
|
|
1610
1653
|
}
|
|
1611
|
-
/** Whether `root` is the gone folder of the stack this machine still records for `space`. */
|
|
1612
|
-
function stackRootGone(space, root) {
|
|
1613
|
-
const gone = goneStackRoot(space);
|
|
1614
|
-
return gone !== undefined && canonicalRoot(gone) === canonicalRoot(root);
|
|
1615
|
-
}
|
|
1616
1654
|
function connectionCell(s) {
|
|
1617
1655
|
if (s.connection === "ok" || s.connection === "reachable")
|
|
1618
1656
|
return stateCell("reachable", "ok");
|
|
1619
1657
|
const states = {
|
|
1658
|
+
"root-gone": "project folder was moved or deleted",
|
|
1620
1659
|
unreachable: "unreachable",
|
|
1621
|
-
"slow-link": "
|
|
1660
|
+
"slow-link": "not answering",
|
|
1622
1661
|
"broker-stopped": `broker process ${s.brokerPid} is stopped`,
|
|
1623
1662
|
"tls-trust": "TLS could not be verified",
|
|
1624
1663
|
"tls-unsupported": "listener does not offer required TLS",
|
|
@@ -1655,7 +1694,7 @@ function spaceStateCell(r, entry) {
|
|
|
1655
1694
|
return connectionCell(r.selected);
|
|
1656
1695
|
if (entry.check === "not-checked")
|
|
1657
1696
|
return "";
|
|
1658
|
-
return stateCell(entry.check === "
|
|
1697
|
+
return stateCell(entry.check === "slow-link" ? "not answering" : entry.check === "broker-stopped" ? "broker process is stopped" : entry.check, entry.check === "reachable" ? "ok" : "failed");
|
|
1659
1698
|
}
|
|
1660
1699
|
/**
|
|
1661
1700
|
* The spaces this machine knows: the selected one first with its connection state, then one line
|
|
@@ -1818,7 +1857,14 @@ function stackLines(folder) {
|
|
|
1818
1857
|
function roleList(roles) {
|
|
1819
1858
|
return roles.length > 1 ? `${roles.slice(0, -1).join(", ")} and ${roles.at(-1)}` : roles[0] ?? "";
|
|
1820
1859
|
}
|
|
1860
|
+
/** Pre-commit backup claims block ordinary startup; ready cuts and committed restores do not. */
|
|
1861
|
+
function backupRecovery(maintenance) {
|
|
1862
|
+
return maintenance?.state === "claimed" || maintenance?.state === "restore-ready" ? maintenance.next : null;
|
|
1863
|
+
}
|
|
1821
1864
|
function nextActions(r) {
|
|
1865
|
+
const recovery = backupRecovery(r.maintenance);
|
|
1866
|
+
if (recovery)
|
|
1867
|
+
return { title: "Next", blocks: [...new Set([recovery, ...r.hints])].map((hint) => ({ kind: "lines", lines: [cell(hint, "hint")] })) };
|
|
1822
1868
|
const cmd = displayCmd();
|
|
1823
1869
|
const items = [];
|
|
1824
1870
|
const extra = [];
|