Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/lib/onboard/station-express-resume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ function portReceiptText(
);
}

// The current installer's ten-field receipt: the nine port-bound fields plus a
// trailing `mode=` field (scripts/install.sh save_station_express_resume).
function modeReceiptText(mode = "express"): string {
return `${portReceiptText().trimEnd()}\nmode=${mode}\n`;
}

function retirementClaims(home: string): string[] {
const stateDir = path.join(home, ".nemoclaw");
return fs
Expand Down Expand Up @@ -645,6 +651,40 @@ describe("DGX Station Express resume (#7048)", () => {
}
});

it("accepts the current ten-field mode-bound installer receipt (#8205)", () => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-station-mode-receipt-"));
const stateDir = path.join(home, ".nemoclaw");
fs.mkdirSync(stateDir, { mode: 0o700 });
fs.writeFileSync(path.join(stateDir, "station-express-resume"), modeReceiptText(), {
mode: 0o600,
});

try {
expect(() =>
assertStationExpressInstallerResumeMatches(receiptGeneration, { HOME: home }),
).not.toThrow();
} finally {
fs.rmSync(home, { recursive: true, force: true });
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

it("rejects a mode-bound receipt with an unknown mode value (#8205)", () => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-station-mode-bad-"));
const stateDir = path.join(home, ".nemoclaw");
fs.mkdirSync(stateDir, { mode: 0o700 });
fs.writeFileSync(path.join(stateDir, "station-express-resume"), modeReceiptText("bogus"), {
mode: 0o600,
});

try {
expect(() =>
assertStationExpressInstallerResumeMatches(receiptGeneration, { HOME: home }),
).toThrow("malformed");
} finally {
fs.rmSync(home, { recursive: true, force: true });
}
});

it("retires only the exact matching installer receipt generation", () => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-station-receipt-match-"));
const stateDir = path.join(home, ".nemoclaw");
Expand Down
27 changes: 26 additions & 1 deletion src/lib/onboard/station-express-resume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ const STATION_EXPRESS_RETIREMENT_CLAIM_SUFFIX_PATTERN = /^[A-Za-z0-9]+$/;
const STATION_EXPRESS_RECEIPT_PORT_PATTERN = /^\d+$/;
const STATION_EXPRESS_RECEIPT_AGENTS = new Set(["openclaw", "hermes", "langchain-deepagents-code"]);
const STATION_EXPRESS_RECEIPT_POLICY_TIERS = new Set(["restricted", "balanced", "open"]);
// Mirrors validate_station_install_mode() in scripts/install.sh.
const STATION_EXPRESS_RECEIPT_MODES = new Set(["express", "provider"]);

function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
Expand Down Expand Up @@ -341,7 +343,30 @@ function readStationExpressInstallerResumeGeneration(stateFile: string): string
lines[7].slice("dashboard_port=".length),
lines[8].slice("vllm_port=".length),
);
if (!legacyFormat && !currentFormat && !portFormat) {
// The current installer appends a tenth `mode=` field after the nine
// port-bound fields (scripts/install.sh save_station_express_resume). Accept
// that exact shape so completeSession() can validate and retire the receipt
// the same installer wrote, while still rejecting an unknown/expanded mode.
const modeFormat =
lines.length === 11 &&
lines[10] === "" &&
lines[3]?.startsWith("agent=") &&
STATION_EXPRESS_RECEIPT_AGENTS.has(lines[3].slice("agent=".length)) &&
lines[4]?.startsWith("sandbox=") &&
validSandboxName(lines[4].slice("sandbox=".length)) &&
lines[5]?.startsWith("policy_tier=") &&
STATION_EXPRESS_RECEIPT_POLICY_TIERS.has(lines[5].slice("policy_tier=".length)) &&
lines[6]?.startsWith("gateway_port=") &&
lines[7]?.startsWith("dashboard_port=") &&
lines[8]?.startsWith("vllm_port=") &&
validReceiptPorts(
lines[6].slice("gateway_port=".length),
lines[7].slice("dashboard_port=".length),
lines[8].slice("vllm_port=".length),
) &&
lines[9]?.startsWith("mode=") &&
STATION_EXPRESS_RECEIPT_MODES.has(lines[9].slice("mode=".length));
if (!legacyFormat && !currentFormat && !portFormat && !modeFormat) {
throw new Error("DGX Station Express installer resume state is malformed.");
}
const revision = lines[0]?.startsWith("revision=") ? lines[0].slice("revision=".length) : "";
Expand Down
Loading