omp-conductor 0.19.0 → 0.19.2
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/package.json +2 -2
- package/src/check-browser-js.ts +72 -0
- package/src/dashboard/app.js +4 -4
- package/src/doctor.ts +2 -2
- package/src/fleet.ts +14 -86
- package/src/setup-host.ts +4 -4
- package/src/upgrade.ts +28 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-conductor",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"LICENSE"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
|
-
"check": "tsc --noEmit && bun run src/check-trailing-newlines.ts",
|
|
32
|
+
"check": "tsc --noEmit && bun run src/check-trailing-newlines.ts && bun run src/check-browser-js.ts",
|
|
33
33
|
"test": "bun test",
|
|
34
34
|
"schema": "bun run src/generate-schema.ts"
|
|
35
35
|
},
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate: every browser script this package serves must parse.
|
|
3
|
+
*
|
|
4
|
+
* Run via `bun run check` from the package root, beside the trailing-newline
|
|
5
|
+
* gate. `tsc` never sees `src/dashboard/app.js` — it is plain browser JS,
|
|
6
|
+
* shipped as an asset and loaded by a `<script>` tag — and no test executes
|
|
7
|
+
* it, so nothing in the pipeline had an opinion about whether it was even
|
|
8
|
+
* syntactically valid.
|
|
9
|
+
*
|
|
10
|
+
* It was not. 0.19.0 shipped an `app.js` carrying four `__omp_shell("…")`
|
|
11
|
+
* fragments where `!answer.ok` and `!confirmDestructive(` should have been:
|
|
12
|
+
* the authoring session wrote the file through a Python eval path, whose
|
|
13
|
+
* IPython-style `!cmd` shell escape rewrote every line that began with `!`.
|
|
14
|
+
* A single SyntaxError takes the whole script with it, so the dashboard
|
|
15
|
+
* rendered its static `<h1>` and nothing else — no token prompt, no fleet —
|
|
16
|
+
* and looked, from the outside, like an auth or data problem (#981).
|
|
17
|
+
*
|
|
18
|
+
* A parse is the whole check. It is not a linter and has no opinion about
|
|
19
|
+
* style: it asks the one question a served script must answer yes to.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { execFileSync } from "node:child_process";
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
|
|
26
|
+
/** Tracked browser scripts, repo-root-relative. */
|
|
27
|
+
export const BROWSER_SCRIPTS: readonly string[] = ["omp/src/dashboard/app.js"];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The parse error for one script, or null when it parses. Uses Bun's own
|
|
31
|
+
* transpiler: a full parse of the source, without executing a line of it (the
|
|
32
|
+
* script drives a DOM this process does not have).
|
|
33
|
+
*/
|
|
34
|
+
export function parseFailure(source: string, path: string): string | null {
|
|
35
|
+
try {
|
|
36
|
+
new Bun.Transpiler({ loader: "js", target: "browser" }).transformSync(source);
|
|
37
|
+
return null;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
40
|
+
return `${path}: ${message.split("\n")[0]}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One violation line per unparseable script, so a run names every offender. */
|
|
45
|
+
export function findViolations(
|
|
46
|
+
paths: readonly string[],
|
|
47
|
+
read: (path: string) => string,
|
|
48
|
+
): string[] {
|
|
49
|
+
const violations: string[] = [];
|
|
50
|
+
for (const path of paths) {
|
|
51
|
+
const failure = parseFailure(read(path), path);
|
|
52
|
+
if (failure !== null) violations.push(failure);
|
|
53
|
+
}
|
|
54
|
+
return violations;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function repoRoot(): string {
|
|
58
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (import.meta.main) {
|
|
62
|
+
const root = repoRoot();
|
|
63
|
+
const violations = findViolations(BROWSER_SCRIPTS, (path) =>
|
|
64
|
+
readFileSync(join(root, path), "utf8"),
|
|
65
|
+
);
|
|
66
|
+
if (violations.length > 0) {
|
|
67
|
+
for (const violation of violations) console.error(violation);
|
|
68
|
+
console.error(`browser script gate: ${violations.length} script(s) do not parse`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
console.log("browser script gate: ok");
|
|
72
|
+
}
|
package/src/dashboard/app.js
CHANGED
|
@@ -140,7 +140,7 @@ async function runControl(path, body, describe) {
|
|
|
140
140
|
: JSON.stringify(answer.body);
|
|
141
141
|
showControlResult(
|
|
142
142
|
answer.ok ? `${describe}: ok` : `${describe} refused (${answer.status}): ${detail}`,
|
|
143
|
-
|
|
143
|
+
!answer.ok,
|
|
144
144
|
);
|
|
145
145
|
} catch (err) {
|
|
146
146
|
showControlResult(`${describe} failed: ${String(err.message ?? err)}`, true);
|
|
@@ -188,7 +188,7 @@ controls.addEventListener("click", (event) => {
|
|
|
188
188
|
if (reason === null || reason.trim() === "") return;
|
|
189
189
|
if (
|
|
190
190
|
action === "hold" &&
|
|
191
|
-
|
|
191
|
+
!confirmDestructive(
|
|
192
192
|
"Hold stops new claims AND disarms ticks. Re-arming needs a Telegram challenge answered in the chat. Continue?",
|
|
193
193
|
)
|
|
194
194
|
) {
|
|
@@ -447,7 +447,7 @@ function renderRunControls(issue) {
|
|
|
447
447
|
const reason = window.prompt(`Reason for stopping #${issue} (recorded in the run's report):`);
|
|
448
448
|
if (reason === null || reason.trim() === "") return;
|
|
449
449
|
if (
|
|
450
|
-
|
|
450
|
+
!confirmDestructive(
|
|
451
451
|
`Stop #${issue}? This settles the run terminally, salvages its tree and frees the slot. It cannot be resumed.`,
|
|
452
452
|
)
|
|
453
453
|
) {
|
|
@@ -474,7 +474,7 @@ function renderRunControls(issue) {
|
|
|
474
474
|
);
|
|
475
475
|
button("Unblock --force", "destructive", () => {
|
|
476
476
|
if (
|
|
477
|
-
|
|
477
|
+
!confirmDestructive(
|
|
478
478
|
`Force-unblock #${issue}? This accepts the loss of uncommitted work in its worktree, which may be the only copy.`,
|
|
479
479
|
)
|
|
480
480
|
) {
|
package/src/doctor.ts
CHANGED
|
@@ -55,7 +55,7 @@ import { ompSettingsOverlay, sessionRootDir } from "./omp-settings.ts";
|
|
|
55
55
|
import {
|
|
56
56
|
DEFAULT_HERDR_UNIT,
|
|
57
57
|
probeTelegramHealth,
|
|
58
|
-
|
|
58
|
+
resolveHerdrSession,
|
|
59
59
|
resolveTickConfig,
|
|
60
60
|
sessionsRoot,
|
|
61
61
|
sessionDirForCwd,
|
|
@@ -2003,7 +2003,7 @@ export function defaultProbes(): Probes {
|
|
|
2003
2003
|
workerHarness: defaultWorkerHarness,
|
|
2004
2004
|
pidLive: (pid) => pidAlive(pid),
|
|
2005
2005
|
herdrAgents: defaultHerdrAgents,
|
|
2006
|
-
herdrSession: () =>
|
|
2006
|
+
herdrSession: () => resolveHerdrSession(),
|
|
2007
2007
|
herdrConfig: defaultHerdrConfig,
|
|
2008
2008
|
herdrEnv: defaultHerdrEnv,
|
|
2009
2009
|
claimedTopics: () => claimedTelegramTopics(),
|
package/src/fleet.ts
CHANGED
|
@@ -105,10 +105,11 @@ export const PANE_HALT_FILE = ".conductor-pane-halted";
|
|
|
105
105
|
export const DEFAULT_HERDR_UNIT = "herdr-fleet.service";
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
108
|
+
* The Herdr session conductor fleets run in. `HERDR_SESSION` wins when set;
|
|
109
|
+
* this is the only other name any surface may resolve. #320 renamed it from
|
|
110
|
+
* `fleet` and shipped a one-release probe that preferred a populated legacy
|
|
111
|
+
* session; #976 removed that probe, because a name resolved two ways is a
|
|
112
|
+
* name two surfaces can disagree about — and they did, in `upgrade`.
|
|
112
113
|
*/
|
|
113
114
|
export const DEFAULT_HERDR_SESSION = "conductor";
|
|
114
115
|
|
|
@@ -121,32 +122,13 @@ export const DEFAULT_HERDR_SESSION = "conductor";
|
|
|
121
122
|
export const DEFAULT_FLEET_AGENT_NAME = "fleet";
|
|
122
123
|
export const ARM_CHALLENGE_TIMEOUT_MS = 300_000;
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
/** Pre-#320 session name. Bridged for one release when `conductor` is empty. */
|
|
126
|
-
const LEGACY_HERDR_SESSION = "fleet";
|
|
127
|
-
|
|
128
|
-
export const LEGACY_HERDR_SESSION_HINT =
|
|
129
|
-
'herdr session "fleet" found — rename it to "conductor" or run omp-conductor setup host to pin HERDR_SESSION=fleet (remove this bridge next minor)';
|
|
130
|
-
|
|
131
|
-
let legacyHerdrSessionHintPrinted = false;
|
|
132
|
-
|
|
133
|
-
function noteLegacyHerdrSession(log?: (message: string) => void): void {
|
|
134
|
-
if (legacyHerdrSessionHintPrinted) return;
|
|
135
|
-
legacyHerdrSessionHintPrinted = true;
|
|
136
|
-
if (log) log(LEGACY_HERDR_SESSION_HINT);
|
|
137
|
-
else console.warn(LEGACY_HERDR_SESSION_HINT);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Reset the once-per-process rename hint (tests). */
|
|
141
|
-
export function resetLegacyHerdrSessionHintForTests(): void {
|
|
142
|
-
legacyHerdrSessionHintPrinted = false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
125
|
/**
|
|
146
|
-
* Resolve the Herdr session name
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
126
|
+
* Resolve the Herdr session name — the single resolution every surface uses.
|
|
127
|
+
* `HERDR_SESSION` is authoritative; otherwise {@link DEFAULT_HERDR_SESSION}.
|
|
128
|
+
* Never probe for an alternative name here: a session that answers is not
|
|
129
|
+
* evidence of which session this fleet is, and the probe that assumed it was
|
|
130
|
+
* is what made `upgrade` install against `fleet` and verify against
|
|
131
|
+
* `conductor` (#976).
|
|
150
132
|
*/
|
|
151
133
|
export function resolveHerdrSession(env: NodeJS.ProcessEnv = process.env): string {
|
|
152
134
|
const explicit = env["HERDR_SESSION"];
|
|
@@ -154,56 +136,6 @@ export function resolveHerdrSession(env: NodeJS.ProcessEnv = process.env): strin
|
|
|
154
136
|
return DEFAULT_HERDR_SESSION;
|
|
155
137
|
}
|
|
156
138
|
|
|
157
|
-
function herdrAgentListRaw(
|
|
158
|
-
session: string,
|
|
159
|
-
bin: string,
|
|
160
|
-
env: NodeJS.ProcessEnv,
|
|
161
|
-
): { ok: true; agents: HerdrAgent[] } | { ok: false } {
|
|
162
|
-
const res = spawnSync(bin, ["--session", session, "agent", "list"], {
|
|
163
|
-
encoding: "utf8",
|
|
164
|
-
timeout: 8_000,
|
|
165
|
-
env,
|
|
166
|
-
});
|
|
167
|
-
if (res.error || res.status !== 0) return { ok: false };
|
|
168
|
-
try {
|
|
169
|
-
return { ok: true, agents: parseHerdrAgentList(res.stdout ?? "") };
|
|
170
|
-
} catch {
|
|
171
|
-
return { ok: false };
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* One-release bridge (#320): when `HERDR_SESSION` is unset, a `conductor`
|
|
177
|
-
* session with no agents while a `fleet` session answers → use `fleet` and
|
|
178
|
-
* print the rename hint once. Remove next minor.
|
|
179
|
-
*/
|
|
180
|
-
export function resolveHerdrSessionWithBridge(opts: {
|
|
181
|
-
env?: NodeJS.ProcessEnv;
|
|
182
|
-
herdrBin?: string;
|
|
183
|
-
log?: (message: string) => void;
|
|
184
|
-
} = {}): string {
|
|
185
|
-
const env = opts.env ?? process.env;
|
|
186
|
-
const explicit = env["HERDR_SESSION"];
|
|
187
|
-
if (explicit !== undefined && explicit.length > 0) return explicit;
|
|
188
|
-
|
|
189
|
-
const bin = opts.herdrBin ?? "herdr";
|
|
190
|
-
const primary = herdrAgentListRaw(DEFAULT_HERDR_SESSION, bin, env);
|
|
191
|
-
if (primary.ok && primary.agents.length > 0) return DEFAULT_HERDR_SESSION;
|
|
192
|
-
|
|
193
|
-
const legacy = herdrAgentListRaw(LEGACY_HERDR_SESSION, bin, env);
|
|
194
|
-
if (legacy.ok && legacy.agents.length > 0) {
|
|
195
|
-
noteLegacyHerdrSession(opts.log);
|
|
196
|
-
return LEGACY_HERDR_SESSION;
|
|
197
|
-
}
|
|
198
|
-
// conductor answered (even empty) wins over a dead legacy session.
|
|
199
|
-
if (primary.ok) return DEFAULT_HERDR_SESSION;
|
|
200
|
-
if (legacy.ok) {
|
|
201
|
-
noteLegacyHerdrSession(opts.log);
|
|
202
|
-
return LEGACY_HERDR_SESSION;
|
|
203
|
-
}
|
|
204
|
-
return DEFAULT_HERDR_SESSION;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
139
|
export function telegramStateDir(): string {
|
|
208
140
|
const override = process.env["OMP_TELEGRAM_STATE_DIR"];
|
|
209
141
|
if (override !== undefined && override.length > 0) return override;
|
|
@@ -1382,9 +1314,7 @@ export async function stopConductorPane(
|
|
|
1382
1314
|
|
|
1383
1315
|
async function herdrAgentList(deps: PaneStopDeps): Promise<HerdrAgent[]> {
|
|
1384
1316
|
const bin = deps.herdrBin ?? "herdr";
|
|
1385
|
-
const session =
|
|
1386
|
-
deps.herdrSession ??
|
|
1387
|
-
resolveHerdrSessionWithBridge({ env: process.env, herdrBin: bin });
|
|
1317
|
+
const session = deps.herdrSession ?? resolveHerdrSession(process.env);
|
|
1388
1318
|
const res = spawnSync(bin, ["--session", session, "agent", "list"], {
|
|
1389
1319
|
encoding: "utf8",
|
|
1390
1320
|
timeout: 8_000,
|
|
@@ -1485,9 +1415,7 @@ export function parseHerdrProcessInfo(stdout: string, paneId: string): ProcessIn
|
|
|
1485
1415
|
|
|
1486
1416
|
async function herdrOmpForegroundPids(paneId: string, deps: PaneStopDeps): Promise<number[]> {
|
|
1487
1417
|
const bin = deps.herdrBin ?? "herdr";
|
|
1488
|
-
const session =
|
|
1489
|
-
deps.herdrSession ??
|
|
1490
|
-
resolveHerdrSessionWithBridge({ env: process.env, herdrBin: bin });
|
|
1418
|
+
const session = deps.herdrSession ?? resolveHerdrSession(process.env);
|
|
1491
1419
|
const res = spawnSync(bin, ["--session", session, "pane", "process-info", "--pane", paneId], {
|
|
1492
1420
|
encoding: "utf8",
|
|
1493
1421
|
timeout: 8_000,
|
|
@@ -2539,7 +2467,7 @@ function probeOmpPane(
|
|
|
2539
2467
|
}
|
|
2540
2468
|
const agentName =
|
|
2541
2469
|
tick.kind === "ok" ? (tick.config.agentName ?? DEFAULT_FLEET_AGENT_NAME) : DEFAULT_FLEET_AGENT_NAME;
|
|
2542
|
-
const session =
|
|
2470
|
+
const session = resolveHerdrSession(process.env);
|
|
2543
2471
|
try {
|
|
2544
2472
|
const res = spawnSync("herdr", ["--session", session, "agent", "list"], {
|
|
2545
2473
|
encoding: "utf8",
|
package/src/setup-host.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
DEFAULT_FLEET_AGENT_NAME,
|
|
10
10
|
DEFAULT_HERDR_SESSION,
|
|
11
11
|
DEFAULT_HERDR_UNIT,
|
|
12
|
-
|
|
12
|
+
resolveHerdrSession,
|
|
13
13
|
} from "./fleet.ts";
|
|
14
14
|
import {
|
|
15
15
|
WORKER_HARNESS_DIR,
|
|
@@ -612,9 +612,9 @@ export function canonicalBinSearchPath(home: string = homedir()): string {
|
|
|
612
612
|
|
|
613
613
|
export function defaultServiceRuntime(
|
|
614
614
|
telegramStateDir: string,
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
herdrSession: string =
|
|
615
|
+
// The one resolved session name (#976). Passed in so tests stay hermetic and
|
|
616
|
+
// the renderer never shells out — same threading as telegramStateDir.
|
|
617
|
+
herdrSession: string = resolveHerdrSession(),
|
|
618
618
|
// The canonical search path (#890), injectable for the same reason: a test
|
|
619
619
|
// must be able to prove the render does not move when the CALLER's PATH does,
|
|
620
620
|
// which means controlling what "installed here" means without touching the
|
package/src/upgrade.ts
CHANGED
|
@@ -4,9 +4,7 @@ import { backupTimestamp, copyToUniqueBackup } from "./backups.ts";
|
|
|
4
4
|
import { inspectBriefLayout, type BriefLayout } from "./brief-upgrade.ts";
|
|
5
5
|
import { pauseInstance, setPaused, statusSnapshot, type AdmissionAckRecord } from "./daemon.ts";
|
|
6
6
|
import {
|
|
7
|
-
DEFAULT_HERDR_SESSION,
|
|
8
7
|
fleetLayers,
|
|
9
|
-
LEGACY_HERDR_SESSION_HINT,
|
|
10
8
|
resolveHerdrSession,
|
|
11
9
|
telegramStateDir,
|
|
12
10
|
} from "./fleet.ts";
|
|
@@ -133,6 +131,14 @@ export interface UpgradeDeps {
|
|
|
133
131
|
setPaused(value: boolean, project?: string): void;
|
|
134
132
|
restartDaemon(): Promise<void>;
|
|
135
133
|
sleep(ms: number): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* The environment every surface of this transaction resolves from — the
|
|
136
|
+
* herdr session included, through {@link resolveHerdrSession}. One source,
|
|
137
|
+
* because the `herdr plugin list` read and the pane probe MUST agree on
|
|
138
|
+
* which session this fleet is: when they did not, `upgrade` installed
|
|
139
|
+
* against one session and failed verification against another, then rolled
|
|
140
|
+
* a good release back (#976).
|
|
141
|
+
*/
|
|
136
142
|
env: NodeJS.ProcessEnv;
|
|
137
143
|
log(message: string): void;
|
|
138
144
|
/**
|
|
@@ -142,11 +148,6 @@ export interface UpgradeDeps {
|
|
|
142
148
|
* session process actually loaded.
|
|
143
149
|
*/
|
|
144
150
|
health(port: number): Promise<{ ok: boolean; body?: string }>;
|
|
145
|
-
/**
|
|
146
|
-
* The herdr session the fleet pane lives in, for the external-orchestrator
|
|
147
|
-
* pane probe (#832).
|
|
148
|
-
*/
|
|
149
|
-
herdrSession(): string;
|
|
150
151
|
/**
|
|
151
152
|
* Every omp process the fleet pane currently claims by herdr, resolved to
|
|
152
153
|
* its start time — the evidence an external (pane-owned) orchestrator
|
|
@@ -208,7 +209,6 @@ export const DEFAULT_DEPS: UpgradeDeps = {
|
|
|
208
209
|
await restartDaemon({});
|
|
209
210
|
},
|
|
210
211
|
health: async (port) => healthCheck(port),
|
|
211
|
-
herdrSession: () => resolveHerdrSession(process.env),
|
|
212
212
|
probePaneOmp: (session) => herdrPaneOmpStarts(runCommand, session, processStartTimeMs),
|
|
213
213
|
// The bare host-global plan — the same render the advisory's `setup host`
|
|
214
214
|
// command would install: no per-project tail, the recovery unit encoding no
|
|
@@ -430,32 +430,7 @@ export async function inspectSurfaces(
|
|
|
430
430
|
if (version.length === 0) throw new Error("installed omp-conductor CLI has no version");
|
|
431
431
|
return { cliVersion: version, ompVersion: ompPluginVersion(ompOnly.stdout) };
|
|
432
432
|
}
|
|
433
|
-
|
|
434
|
-
// #320's one-release bridge to a populated legacy "fleet" session.
|
|
435
|
-
let session = resolveHerdrSession(deps.env);
|
|
436
|
-
if (deps.env["HERDR_SESSION"] === undefined || deps.env["HERDR_SESSION"] === "") {
|
|
437
|
-
// Prefer a session that answers plugin list; fall back to conductor.
|
|
438
|
-
let conductorOk = false;
|
|
439
|
-
let fleetOk = false;
|
|
440
|
-
try {
|
|
441
|
-
const r = await deps.run("herdr", ["--session", DEFAULT_HERDR_SESSION, "plugin", "list"]);
|
|
442
|
-
conductorOk = r.code === 0;
|
|
443
|
-
} catch {
|
|
444
|
-
conductorOk = false;
|
|
445
|
-
}
|
|
446
|
-
try {
|
|
447
|
-
const r = await deps.run("herdr", ["--session", "fleet", "plugin", "list"]);
|
|
448
|
-
fleetOk = r.code === 0;
|
|
449
|
-
} catch {
|
|
450
|
-
fleetOk = false;
|
|
451
|
-
}
|
|
452
|
-
if (!conductorOk && fleetOk) {
|
|
453
|
-
session = "fleet";
|
|
454
|
-
deps.log(LEGACY_HERDR_SESSION_HINT);
|
|
455
|
-
} else {
|
|
456
|
-
session = DEFAULT_HERDR_SESSION;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
433
|
+
const session = resolveHerdrSession(deps.env);
|
|
459
434
|
const [cli, omp, herdr] = await Promise.all([
|
|
460
435
|
mustRun(deps, "omp-conductor", ["--version"]),
|
|
461
436
|
mustRun(deps, "omp", ["plugin", "list", "--json"]),
|
|
@@ -908,7 +883,7 @@ async function sessionVerifyProblem(
|
|
|
908
883
|
// No daemon to attest: only the pane could have reloaded anything, so the
|
|
909
884
|
// pane probe decides alone, exactly as the external leg of the detached
|
|
910
885
|
// verifier does.
|
|
911
|
-
const pane = await deps.probePaneOmp(deps.
|
|
886
|
+
const pane = await deps.probePaneOmp(resolveHerdrSession(deps.env));
|
|
912
887
|
if ("problem" in pane) return `the fleet pane could not be probed: ${pane.problem}`;
|
|
913
888
|
return paneRestartProblem(pane.starts, reloadAfterMs) ?? undefined;
|
|
914
889
|
}
|
|
@@ -934,7 +909,7 @@ async function sessionVerifyProblem(
|
|
|
934
909
|
if (names.length === 0) {
|
|
935
910
|
return `the restarted daemon's /healthz on :${port} named no project — the live session cannot be attested`;
|
|
936
911
|
}
|
|
937
|
-
const pane = await deps.probePaneOmp(deps.
|
|
912
|
+
const pane = await deps.probePaneOmp(resolveHerdrSession(deps.env));
|
|
938
913
|
for (const project of names) {
|
|
939
914
|
const problem = sessionReloadProblem({
|
|
940
915
|
facts: orchestratorFactsFromHealth(health.body, project),
|
|
@@ -1388,6 +1363,23 @@ export async function upgradeConductor(
|
|
|
1388
1363
|
}
|
|
1389
1364
|
if (initial.herdr === "unknown") throw new Error("cannot determine whether herdr-fleet.service is active");
|
|
1390
1365
|
|
|
1366
|
+
// The one verification input that can be read before anything moves (#976).
|
|
1367
|
+
// Verification probes the fleet pane in `resolveHerdrSession(deps.env)`; a
|
|
1368
|
+
// host whose session answers to another name only discovers that after three
|
|
1369
|
+
// installs and two restarts, and a good release is then rolled back. Read it
|
|
1370
|
+
// first: the same probe, at zero cost, as a refusal.
|
|
1371
|
+
if (!detached && (initial.dispatch !== "stopped" || initial.herdr === "active")) {
|
|
1372
|
+
const session = resolveHerdrSession(deps.env);
|
|
1373
|
+
const pane = await deps.probePaneOmp(session);
|
|
1374
|
+
if ("problem" in pane) {
|
|
1375
|
+
throw new Error(
|
|
1376
|
+
`the fleet pane in herdr session "${session}" cannot be probed, so this upgrade could not be ` +
|
|
1377
|
+
`verified: ${pane.problem}. Set HERDR_SESSION to the session that owns the fleet pane and ` +
|
|
1378
|
+
`re-run, or bring that session up first — nothing has been installed.`,
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1391
1383
|
if (detached) {
|
|
1392
1384
|
// The durable record the returning process rolls back or verifies against:
|
|
1393
1385
|
// what was installed, what dispatch state the fleet began in, which pause
|