mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-09 17:32:41 -10:00
fix(cli): rig up respects daemon.host source (forward-fix #2)
Guard catch onf841612: the previous fix only covered `rig daemon start`. `rig up` (auto-start path) also materialized `resolvedConfig.daemon.host` into `startDaemon` regardless of whether the value came from `env`/`file` (explicit) or `default` — so the daemon's multi-bind path never ran on a default `rig up` launch either. Same class of bug, second product launch path. Applies the same `resolveWithSource("daemon.host")` discriminator to `packages/cli/src/commands/up.ts`: when `source === "default"`, omit `host` from the startDaemon call so `buildDaemonEnv` does not export `OPENRIG_HOST` and the daemon falls through to loopback + tailscale auto-detect. When `source === "env"` or `"file"`, pass the resolved host through (operator opt-in path preserved). Expanded forensic trace per banked feedback_forensic_trace and the guard's expanded-audit directive: grep'd every `startDaemon` caller in `packages/cli/src` — `daemon.ts` (fixed inf841612) and `up.ts` (this commit). No other product launch paths call `startDaemon`. Tests (TDD red→green with distinct-value discriminator per banked feedback_poc_regression_must_discriminate): - env-default scenario → `spawnedEnv["OPENRIG_HOST"]` undefined - env-explicit scenario (`OPENRIG_HOST=100.64.55.66`) → preserved through spawn with the distinct tailscale-shape value, proving the preservation branch fired vs vacuous loopback-default match - up.test.ts: 32/32 green (+2 new) - daemon-lifecycle.test.ts: 52/52 green (unchanged) - Full CLI+daemon sweep with env-var pollution cleaned: 4223/4225 (same one environmental pre-existing fail: cli/dist/index.js missing in worktree) - CLI tsc clean Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
f84161289c
commit
0fb4e4e832
2 files changed
+107
-1
No files matched your search
@@ -43,6 +43,14 @@ Examples:
|
||||
transcripts: { enabled: boolean; path: string; lines: number; pollIntervalSeconds: number };
|
||||
workspace: { root: string };
|
||||
} | null = null;
|
||||
// bug-fix slice auth-bearer-tailscale-trust: track whether
|
||||
// daemon.host was operator-explicit (env or config file) vs
|
||||
// default-fallback. The daemon's multi-bind path (loopback +
|
||||
// tailscale auto-detect) only runs when OPENRIG_HOST is NOT
|
||||
// exported to the child, so we omit it on the default path.
|
||||
// Hoisted to function scope so the startDaemon block below can
|
||||
// read it after the preflight try-catch.
|
||||
let hostForDaemon: string | undefined;
|
||||
try {
|
||||
const { ConfigStore } = await import("../config-store.js");
|
||||
const { SystemPreflight } = await import("../system-preflight.js");
|
||||
@@ -50,6 +58,10 @@ Examples:
|
||||
const { OPENRIG_DIR } = await import("../daemon-lifecycle.js");
|
||||
const configStore = new ConfigStore();
|
||||
resolvedConfig = configStore.resolve();
|
||||
const hostResolution = configStore.resolveWithSource("daemon.host");
|
||||
hostForDaemon = hostResolution.source === "default"
|
||||
? undefined
|
||||
: resolvedConfig.daemon.host;
|
||||
const preflightExec = depsOverride?.preflightExec ?? (async (cmd: string) =>
|
||||
execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }));
|
||||
const preflight = new SystemPreflight({
|
||||
@@ -77,7 +89,7 @@ Examples:
|
||||
try {
|
||||
await startDaemon({
|
||||
port: resolvedConfig?.daemon.port,
|
||||
host: resolvedConfig?.daemon.host,
|
||||
host: hostForDaemon,
|
||||
db: resolvedConfig?.db.path,
|
||||
transcriptsEnabled: resolvedConfig?.transcripts.enabled,
|
||||
transcriptsPath: resolvedConfig?.transcripts.path,
|
||||
|
||||
@@ -618,6 +618,100 @@ describe("Up CLI", () => {
|
||||
expect(clientBaseUrl).toBe("http://127.0.0.1:7461");
|
||||
});
|
||||
|
||||
// bug-fix slice auth-bearer-tailscale-trust forward-fix #2: `rig up`
|
||||
// auto-start path was the second product launch path materializing
|
||||
// the default daemon.host into OPENRIG_HOST. The discriminator below
|
||||
// proves source=default → omit (so daemon multi-binds); source=env
|
||||
// → preserve (operator opt-in).
|
||||
describe("auth-bearer-tailscale-trust: up auto-start respects daemon.host source", () => {
|
||||
function makeAutoStartDeps(captureSpawn: (env: Record<string, string>) => void): StatusDeps {
|
||||
let daemonState: DaemonState | null = null;
|
||||
let daemonStarted = false;
|
||||
return {
|
||||
lifecycleDeps: {
|
||||
...mockLifecycleDeps(),
|
||||
exists: vi.fn((p: string) => p === STATE_FILE ? daemonState !== null : false),
|
||||
readFile: vi.fn((p: string) => p === STATE_FILE && daemonState ? JSON.stringify(daemonState) : null),
|
||||
writeFile: vi.fn((p: string, content: string) => {
|
||||
if (p === STATE_FILE) daemonState = JSON.parse(content) as DaemonState;
|
||||
}),
|
||||
openForAppend: vi.fn(() => 3),
|
||||
mkdirp: vi.fn(),
|
||||
spawn: vi.fn((cmd, args, opts) => {
|
||||
daemonStarted = true;
|
||||
captureSpawn(opts.env as Record<string, string>);
|
||||
return { pid: 999, unref: vi.fn() } as never;
|
||||
}),
|
||||
fetch: vi.fn(async (url: string) => {
|
||||
if (!daemonStarted) throw new Error(`refused:${url}`);
|
||||
return { ok: url.includes("/healthz") };
|
||||
}),
|
||||
},
|
||||
clientFactory: () => ({
|
||||
post: vi.fn(async () => ({
|
||||
status: 201,
|
||||
data: { status: "completed", rigId: "rig-1", stages: [], errors: [], warnings: [] },
|
||||
})),
|
||||
} as unknown as DaemonClient),
|
||||
};
|
||||
}
|
||||
|
||||
it("default (no env, no config) does NOT export OPENRIG_HOST so daemon multi-binds", async () => {
|
||||
const savedHost = process.env["OPENRIG_HOST"];
|
||||
const savedPort = process.env["OPENRIG_PORT"];
|
||||
delete process.env["OPENRIG_HOST"];
|
||||
process.env["OPENRIG_PORT"] = "7471";
|
||||
|
||||
let spawnedEnv: Record<string, string> = {};
|
||||
const deps = makeAutoStartDeps((env) => { spawnedEnv = env; });
|
||||
|
||||
const prog = new Command();
|
||||
prog.exitOverride();
|
||||
prog.addCommand(upCommand({ ...deps, preflightExec: healthyPreflightExec }));
|
||||
|
||||
await captureLogs(async () => {
|
||||
await prog.parseAsync(["node", "rig", "up", "/tmp/test.yaml"]);
|
||||
});
|
||||
|
||||
if (savedHost === undefined) delete process.env["OPENRIG_HOST"];
|
||||
else process.env["OPENRIG_HOST"] = savedHost;
|
||||
if (savedPort === undefined) delete process.env["OPENRIG_PORT"];
|
||||
else process.env["OPENRIG_PORT"] = savedPort;
|
||||
|
||||
expect(spawnedEnv["OPENRIG_HOST"]).toBeUndefined();
|
||||
expect(spawnedEnv["OPENRIG_PORT"]).toBe("7471");
|
||||
});
|
||||
|
||||
it("env-explicit OPENRIG_HOST is preserved into spawn env (distinct value discriminator)", async () => {
|
||||
const savedHost = process.env["OPENRIG_HOST"];
|
||||
const savedPort = process.env["OPENRIG_PORT"];
|
||||
// Use a distinct value so the discriminator can't pass vacuously:
|
||||
// 100.64.55.66 is a tailscale CGNAT address — operator-explicit
|
||||
// bind would normally be honored as-is.
|
||||
process.env["OPENRIG_HOST"] = "100.64.55.66";
|
||||
process.env["OPENRIG_PORT"] = "7472";
|
||||
|
||||
let spawnedEnv: Record<string, string> = {};
|
||||
const deps = makeAutoStartDeps((env) => { spawnedEnv = env; });
|
||||
|
||||
const prog = new Command();
|
||||
prog.exitOverride();
|
||||
prog.addCommand(upCommand({ ...deps, preflightExec: healthyPreflightExec }));
|
||||
|
||||
await captureLogs(async () => {
|
||||
await prog.parseAsync(["node", "rig", "up", "/tmp/test.yaml"]);
|
||||
});
|
||||
|
||||
if (savedHost === undefined) delete process.env["OPENRIG_HOST"];
|
||||
else process.env["OPENRIG_HOST"] = savedHost;
|
||||
if (savedPort === undefined) delete process.env["OPENRIG_PORT"];
|
||||
else process.env["OPENRIG_PORT"] = savedPort;
|
||||
|
||||
expect(spawnedEnv["OPENRIG_HOST"]).toBe("100.64.55.66");
|
||||
expect(spawnedEnv["OPENRIG_PORT"]).toBe("7472");
|
||||
});
|
||||
});
|
||||
|
||||
it("up surfaces the real daemon auto-start failure instead of a generic hint", async () => {
|
||||
const savedPort = process.env["OPENRIG_PORT"];
|
||||
process.env["OPENRIG_PORT"] = "7463";
|
||||
|
||||
Reference in new issue
Block a user