460 lines
36 KiB
TypeScript
460 lines
36 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { sshHelp } from "./src/help";
|
|
import { providerTriageRecommendedCrossChecks } from "./src/provider-triage";
|
|
import { extractRemoteCliOptions, remoteSshFrontendPlanForTest } from "./src/remote";
|
|
import { formatSshFailureHint, formatSshRuntimeTimingHint, parseSshArgs, parseSshInvocation, remoteApplyPatchSource, sshFailureHint, sshRuntimeTimingHint } from "./src/ssh";
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
|
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
|
}
|
|
|
|
function assertThrows(fn: () => unknown, pattern: RegExp, message: string): void {
|
|
try {
|
|
fn();
|
|
} catch (error) {
|
|
const text = error instanceof Error ? error.message : String(error);
|
|
assertCondition(pattern.test(text), message, { error: text });
|
|
return;
|
|
}
|
|
throw new Error(`${message}: expected throw`);
|
|
}
|
|
|
|
function applyPatchFixture(args: string[], patch: string, files: Record<string, string>): { status: number | null; stdout: string; stderr: string; files: Record<string, string> } {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), "unidesk-apply-patch-contract-"));
|
|
try {
|
|
const helperPath = path.join(root, "apply_patch");
|
|
writeFileSync(helperPath, remoteApplyPatchSource, "utf8");
|
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
const target = path.join(root, relativePath);
|
|
writeFileSync(target, content, "utf8");
|
|
}
|
|
const run = spawnSync("sh", [helperPath, ...args], {
|
|
cwd: root,
|
|
input: patch,
|
|
encoding: "utf8",
|
|
});
|
|
const outputFiles: Record<string, string> = {};
|
|
for (const relativePath of Object.keys(files)) {
|
|
outputFiles[relativePath] = readFileSync(path.join(root, relativePath), "utf8");
|
|
}
|
|
return {
|
|
status: run.status,
|
|
stdout: run.stdout,
|
|
stderr: run.stderr,
|
|
files: outputFiles,
|
|
};
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function tranConcurrentLockFixture(): { status: number | null; stdout: string; stderr: string } {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), "unidesk-tran-lock-contract-"));
|
|
try {
|
|
const fakeRepo = path.join(root, "repo");
|
|
const fakeScripts = path.join(fakeRepo, "scripts");
|
|
const fakeBin = path.join(root, "bin");
|
|
mkdirSync(fakeScripts, { recursive: true });
|
|
mkdirSync(fakeBin, { recursive: true });
|
|
writeFileSync(path.join(fakeScripts, "cli.ts"), "// fake cli entry for tran wrapper contract\n", "utf8");
|
|
const fakeBun = path.join(fakeBin, "bun");
|
|
writeFileSync(fakeBun, [
|
|
"#!/bin/sh",
|
|
"if mkdir \"$FAKE_BUN_RUN_LOCK\" 2>/dev/null; then",
|
|
" sleep 1",
|
|
" rmdir \"$FAKE_BUN_RUN_LOCK\"",
|
|
" exit 0",
|
|
"fi",
|
|
"echo fake bun observed overlapping tran execution >&2",
|
|
"exit 42",
|
|
"",
|
|
].join("\n"), "utf8");
|
|
chmodSync(fakeBun, 0o755);
|
|
const tranPath = path.resolve("scripts/tran");
|
|
return spawnSync("sh", ["-c", [
|
|
`"${tranPath}" D601:/tmp pwd >/tmp/unidesk-tran-lock-one.out 2>/tmp/unidesk-tran-lock-one.err &`,
|
|
"p1=$!",
|
|
`"${tranPath}" D601:/tmp pwd >/tmp/unidesk-tran-lock-two.out 2>/tmp/unidesk-tran-lock-two.err &`,
|
|
"p2=$!",
|
|
"wait $p1; s1=$?",
|
|
"wait $p2; s2=$?",
|
|
"cat /tmp/unidesk-tran-lock-one.err /tmp/unidesk-tran-lock-two.err >&2",
|
|
"printf '%s %s\\n' \"$s1\" \"$s2\"",
|
|
].join("\n")], {
|
|
cwd: path.resolve("."),
|
|
env: {
|
|
...process.env,
|
|
PATH: `${fakeBin}${path.delimiter}${process.env.PATH ?? ""}`,
|
|
UNIDESK_TRAN_REPO_ROOT: fakeRepo,
|
|
UNIDESK_TRAN_LOCK_DIR: path.join(root, "locks"),
|
|
UNIDESK_TRAN_LOCK_NOTICE_SECONDS: "0",
|
|
UNIDESK_TRAN_LOCK_TIMEOUT_SECONDS: "10",
|
|
FAKE_BUN_RUN_LOCK: path.join(root, "fake-bun-running"),
|
|
},
|
|
encoding: "utf8",
|
|
timeout: 10_000,
|
|
});
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
export function runSshArgvGuidanceContract(): JsonRecord {
|
|
const argv = parseSshArgs(["argv", "true"]);
|
|
assertCondition(argv.invocationKind === "argv", "argv subcommand must be classified as argv", argv);
|
|
assertCondition(argv.remoteCommand === "'true'", "argv command must shell-quote each token", argv);
|
|
assertCondition(argv.requiresStdin === false, "argv command must not require stdin", argv);
|
|
assertCondition(sshFailureHint("D601", argv, 255, "kex_exchange_identification: Connection closed by remote host") === null, "argv failures must not produce ssh-like friction hint", argv);
|
|
|
|
const shortcut = parseSshArgs(["pwd"]);
|
|
assertCondition(shortcut.invocationKind === "argv", "safe command shortcuts must use argv quoting", shortcut);
|
|
assertCondition(shortcut.remoteCommand === "'pwd'", "safe command shortcut should be shell-quoted", shortcut);
|
|
|
|
const hostWorkspace = parseSshInvocation("D601:/home/ubuntu/workspace/hwlab-dev", ["pwd"]);
|
|
assertCondition(hostWorkspace.route.plane === "host" && hostWorkspace.route.workspace === "/home/ubuntu/workspace/hwlab-dev", "host workspace route must parse provider:/absolute/path", hostWorkspace);
|
|
assertCondition(hostWorkspace.parsed.remoteCommand === "'pwd'", "host workspace route must leave operation argv independent from location", hostWorkspace);
|
|
|
|
const hostWorkspaceLongForm = parseSshInvocation("D601:host:/home/ubuntu/workspace/hwlab-dev", ["argv", "git", "status", "--short"]);
|
|
assertCondition(hostWorkspaceLongForm.route.workspace === "/home/ubuntu/workspace/hwlab-dev", "host: workspace route must parse as the same location model", hostWorkspaceLongForm);
|
|
assertCondition(hostWorkspaceLongForm.parsed.remoteCommand === "'git' 'status' '--short'", "host workspace argv operation must stay argv-quoted", hostWorkspaceLongForm);
|
|
|
|
const script = parseSshArgs(["script", "--shell", "bash", "--", "alpha beta"]);
|
|
assertCondition(script.invocationKind === "helper", "script stdin helper must be classified as helper", script);
|
|
assertCondition(script.remoteCommand === "'bash' '-s' '--' 'alpha beta'", "script helper must pass stdin to shell directly", script);
|
|
assertCondition(script.requiresStdin === true, "script helper must require stdin", script);
|
|
|
|
const directScriptCommand = parseSshArgs(["script", "--", "sed", "-n", "1,2p", "file.txt"]);
|
|
assertCondition(directScriptCommand.invocationKind === "argv", "script -- command form must run as direct argv without stdin", directScriptCommand);
|
|
assertCondition(directScriptCommand.remoteCommand === "'sed' '-n' '1,2p' 'file.txt'", "script -- command form must preserve dash-prefixed command args", directScriptCommand);
|
|
assertCondition(directScriptCommand.requiresStdin === false, "script -- command form must not wait for stdin", directScriptCommand);
|
|
|
|
const k3sGuard = parseSshInvocation("D601:k3s", ["guard"]).parsed;
|
|
assertCondition(k3sGuard.invocationKind === "helper", "k3s guard must be classified as helper", k3sGuard);
|
|
assertCondition(k3sGuard.remoteCommand?.includes("KUBECONFIG") && k3sGuard.remoteCommand.includes("/etc/rancher/k3s/k3s.yaml"), "k3s guard must force native k3s kubeconfig", k3sGuard);
|
|
|
|
const k3sExec = parseSshInvocation("D601:k3s", ["exec", "--namespace", "hwlab-dev", "--deployment", "hwlab-cloud-api", "--", "node", "-e", "console.log(process.version)"]).parsed;
|
|
assertCondition(k3sExec.invocationKind === "helper", "k3s exec must be classified as helper", k3sExec);
|
|
assertCondition(k3sExec.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'node' '-e' 'console.log(process.version)'", "k3s exec must assemble kubectl argv without nested shell quoting", k3sExec);
|
|
|
|
const routeKubectl = parseSshInvocation("D601:k3s", ["kubectl", "get", "pods", "-n", "hwlab-dev"]);
|
|
assertCondition(routeKubectl.providerId === "D601", "route must preserve provider id", routeKubectl);
|
|
assertCondition(routeKubectl.route.plane === "k3s" && routeKubectl.route.entry === null, "route must keep kubectl as an operation, not as a route entry", routeKubectl);
|
|
assertCondition(routeKubectl.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'get' 'pods' '-n' 'hwlab-dev'", "D601:k3s kubectl must map to kubectl argv", routeKubectl);
|
|
|
|
const g14Guard = parseSshInvocation("G14:k3s", []);
|
|
assertCondition(g14Guard.providerId === "G14" && g14Guard.route.plane === "k3s", "G14:k3s must parse as a native k3s route", g14Guard);
|
|
assertCondition(g14Guard.parsed.remoteCommand?.includes("UNIDESK_K3S_PROVIDER_ID") && g14Guard.parsed.remoteCommand.includes("ubuntu-rog-zephyrus-g14-ga401iv-ga401iv"), "G14:k3s guard must use the G14 node profile", g14Guard);
|
|
|
|
assertThrows(
|
|
() => parseSshInvocation("G14", ["k3s", "kubectl", "get", "nodes"]),
|
|
/unsupported.*ssh G14:k3s/u,
|
|
"k3s must be a route plane, not a post-provider shorthand",
|
|
);
|
|
|
|
const routeTarget = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api", ["node", "-e", "console.log(process.version)"]);
|
|
assertCondition(routeTarget.route.namespace === "hwlab-dev" && routeTarget.route.resource === "hwlab-cloud-api", "route target must parse namespace and workload", routeTarget);
|
|
assertCondition(routeTarget.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'node' '-e' 'console.log(process.version)'", "D601:k3s:<namespace>:<workload> must default to deployment exec", routeTarget);
|
|
|
|
const routeTargetArgv = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api", ["argv", "sh", "-c", "printf ok"]);
|
|
assertCondition(routeTargetArgv.parsed.invocationKind === "argv", "k3s target argv operation must stay explicit argv", routeTargetArgv);
|
|
assertCondition(routeTargetArgv.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'sh' '-c' 'printf ok'", "D601:k3s:<namespace>:<workload> argv must exec the argv payload instead of treating argv as a pod command", routeTargetArgv);
|
|
|
|
const routeScript = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api", ["script", "--shell", "bash", "--", "arg"]);
|
|
assertCondition(routeScript.parsed.requiresStdin === true, "k3s script operation must stream local stdin", routeScript);
|
|
assertCondition(routeScript.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-i' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'bash' '-s' '--' 'arg'", "D601:k3s:<namespace>:<workload> script must map stdin to shell -s", routeScript);
|
|
|
|
const routeControlScript = parseSshInvocation("D601:k3s", ["script", "--shell", "bash", "--", "arg"]);
|
|
assertCondition(routeControlScript.parsed.requiresStdin === true, "k3s control-plane script operation must stream local stdin", routeControlScript);
|
|
assertCondition(routeControlScript.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'bash' '-s' '--' 'arg'", "D601:k3s script must inject native kubeconfig without manual export", routeControlScript);
|
|
|
|
const topLevelScriptSeparator = extractRemoteCliOptions(["ssh", "D601:/tmp", "script", "--", "sed", "-n", "1,2p", "file.txt"]);
|
|
assertCondition(JSON.stringify(topLevelScriptSeparator.args) === JSON.stringify(["ssh", "D601:/tmp", "script", "--", "sed", "-n", "1,2p", "file.txt"]), "top-level remote option parser must preserve command-local -- after the command starts", topLevelScriptSeparator);
|
|
const topLevelScriptInvocation = parseSshInvocation(topLevelScriptSeparator.args[1] as string, topLevelScriptSeparator.args.slice(2));
|
|
assertCondition(topLevelScriptInvocation.parsed.remoteCommand === "'sed' '-n' '1,2p' 'file.txt'", "script -- must allow argv such as sed -n to execute without being parsed as script options", topLevelScriptInvocation);
|
|
assertCondition(topLevelScriptInvocation.parsed.requiresStdin === false, "script -- direct command must not require stdin after top-level parsing", topLevelScriptInvocation);
|
|
|
|
const remoteOptionSeparator = extractRemoteCliOptions(["--main-server-ip", "74.48.78.17", "--", "ssh", "D601:/tmp", "script", "--", "sed", "-n", "1,2p", "file.txt"]);
|
|
assertCondition(remoteOptionSeparator.host === "74.48.78.17", "global remote options before -- must still be parsed", remoteOptionSeparator);
|
|
assertCondition(JSON.stringify(remoteOptionSeparator.args) === JSON.stringify(["ssh", "D601:/tmp", "script", "--", "sed", "-n", "1,2p", "file.txt"]), "global -- must not strip nested command separators", remoteOptionSeparator);
|
|
|
|
const routeApplyPatch = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api", ["apply-patch"]);
|
|
assertCondition(routeApplyPatch.parsed.requiresStdin === true, "k3s apply-patch operation must stream local patch stdin", routeApplyPatch);
|
|
assertCondition(routeApplyPatch.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-i' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'sh' '-s' '--'", "D601:k3s:<namespace>:<workload> apply-patch must enter pod with stdin", routeApplyPatch);
|
|
assertCondition(routeApplyPatch.parsed.stdinPrefix?.includes("apply_patch") && routeApplyPatch.parsed.stdinPrefix.includes("__UNIDESK_APPLY_PATCH_PAYLOAD__"), "k3s apply-patch operation must inject pod helper before patch stdin", routeApplyPatch);
|
|
assertCondition(routeApplyPatch.parsed.stdinPrefix?.includes("#!/bin/sh"), "k3s apply-patch operation must inject the same sh helper used by host apply-patch", routeApplyPatch);
|
|
assertCondition(!routeApplyPatch.parsed.stdinPrefix?.includes("python3") && !routeApplyPatch.parsed.stdinPrefix?.includes("node "), "k3s apply-patch operation must use the sh-only pod helper", routeApplyPatch);
|
|
assertCondition(routeApplyPatch.parsed.stdinSuffix === "\n__UNIDESK_APPLY_PATCH_PAYLOAD__\n", "k3s apply-patch operation must terminate patch heredoc", routeApplyPatch);
|
|
|
|
const hostApplyPatchLoose = parseSshArgs(["apply-patch", "--allow-loose"]);
|
|
assertCondition(hostApplyPatchLoose.remoteCommand === "'apply_patch' '--allow-loose'", "host apply-patch must pass --allow-loose as an explicit helper argument", hostApplyPatchLoose);
|
|
assertCondition(hostApplyPatchLoose.requiredHelpers?.length === 1 && hostApplyPatchLoose.requiredHelpers.includes("apply_patch"), "host apply-patch must request only the apply_patch helper bootstrap", hostApplyPatchLoose);
|
|
assertCondition(remoteApplyPatchSource.includes("replace_once_with_perl") && remoteApplyPatchSource.includes("perl -0777"), "apply_patch helper must keep a fast path for large files", {});
|
|
|
|
const safePatch = applyPatchFixture([], [
|
|
"*** Begin Patch",
|
|
"*** Update File: sample.txt",
|
|
"@@",
|
|
" function alpha() {",
|
|
" return 1;",
|
|
" }",
|
|
"+function inserted() {",
|
|
"+ return 3;",
|
|
"+}",
|
|
" function beta() {",
|
|
" return 2;",
|
|
" }",
|
|
"*** End Patch",
|
|
"",
|
|
].join("\n"), {
|
|
"sample.txt": [
|
|
"function alpha() {",
|
|
" return 1;",
|
|
"}",
|
|
"function beta() {",
|
|
" return 2;",
|
|
"}",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
assertCondition(safePatch.status === 0, "apply_patch sh helper should accept anchored insertions", safePatch);
|
|
assertCondition(safePatch.stderr.includes("apply_patch: hunk 1 matched sample.txt:1"), "apply_patch sh helper must report matched file:line", safePatch);
|
|
assertCondition(safePatch.files["sample.txt"]?.includes("function inserted()"), "apply_patch sh helper should apply anchored insertion", safePatch);
|
|
|
|
const lowContextPatch = applyPatchFixture([], [
|
|
"*** Begin Patch",
|
|
"*** Update File: sample.txt",
|
|
"@@",
|
|
" function alpha() {",
|
|
" return 1;",
|
|
" }",
|
|
"+function misplaced() {",
|
|
"+ return 9;",
|
|
"+}",
|
|
"*** End Patch",
|
|
"",
|
|
].join("\n"), {
|
|
"sample.txt": [
|
|
"function alpha() {",
|
|
" return 1;",
|
|
"}",
|
|
"function beta() {",
|
|
" return 2;",
|
|
"}",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
assertCondition(lowContextPatch.status !== 0, "apply_patch sh helper should reject insert-only hunks without trailing context", lowContextPatch);
|
|
assertCondition(lowContextPatch.stderr.includes("insert-only without both leading and trailing context"), "low-context rejection must explain the risk", lowContextPatch);
|
|
|
|
const duplicateContextPatch = applyPatchFixture([], [
|
|
"*** Begin Patch",
|
|
"*** Update File: sample.txt",
|
|
"@@",
|
|
" marker",
|
|
"+inserted",
|
|
" x",
|
|
"*** End Patch",
|
|
"",
|
|
].join("\n"), {
|
|
"sample.txt": "marker\nx\nmarker\nx\n",
|
|
});
|
|
assertCondition(duplicateContextPatch.status !== 0, "apply_patch sh helper should reject hunks whose context matches multiple locations", duplicateContextPatch);
|
|
assertCondition(duplicateContextPatch.stderr.includes("matched multiple locations"), "duplicate context rejection must explain the risk", duplicateContextPatch);
|
|
|
|
const reviewedLoosePatch = applyPatchFixture(["--allow-loose"], [
|
|
"*** Begin Patch",
|
|
"*** Update File: sample.txt",
|
|
"@@",
|
|
" marker",
|
|
"+reviewed",
|
|
" x",
|
|
"*** End Patch",
|
|
"",
|
|
].join("\n"), {
|
|
"sample.txt": "marker\nx\nmarker\nx\n",
|
|
});
|
|
assertCondition(reviewedLoosePatch.status === 0, "--allow-loose should allow an explicitly reviewed ambiguous hunk", reviewedLoosePatch);
|
|
assertCondition(reviewedLoosePatch.files["sample.txt"] === "marker\nreviewed\nx\nmarker\nx\n", "--allow-loose should still apply only one hunk", reviewedLoosePatch);
|
|
|
|
assertThrows(
|
|
() => parseSshInvocation("D601:k3s:kubectl", ["get", "pods"]),
|
|
/route must locate a target only.*ssh D601:k3s kubectl/u,
|
|
"operation names must not be accepted as k3s route segments",
|
|
);
|
|
assertThrows(
|
|
() => parseSshInvocation("D601:k3s:hwlab-ci:kubectl", ["get", "pods"]),
|
|
/route must locate a target only.*operation "kubectl" after the route/u,
|
|
"operation names must not be accepted as nested k3s route segments",
|
|
);
|
|
assertThrows(
|
|
() => parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api:logs", []),
|
|
/route must locate a target only.*operation "logs" after the route/u,
|
|
"operation names must not be accepted as k3s container route segments",
|
|
);
|
|
assertThrows(
|
|
() => parseSshInvocation("D601:k3s:apply-patch:hwlab-dev:hwlab-cloud-api", []),
|
|
/route must locate a target only.*apply-patch/u,
|
|
"pod apply-patch must be an operation after the route",
|
|
);
|
|
|
|
const routePodTarget = parseSshInvocation("D601:k3s:hwlab-dev:pod/hwlab-cloud-api-abc:api", ["printenv", "HOSTNAME"]);
|
|
assertCondition(routePodTarget.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'hwlab-dev' 'pod/hwlab-cloud-api-abc' '-c' 'api' '--' 'printenv' 'HOSTNAME'", "pod route with container must preserve explicit pod kind", routePodTarget);
|
|
|
|
const routePodWorkspace = parseSshInvocation("D601:k3s:hwlab-dev:pod/hwlab-cloud-api-abc/workspace/app:api", ["pwd"]);
|
|
assertCondition(routePodWorkspace.route.resource === "pod/hwlab-cloud-api-abc" && routePodWorkspace.route.container === "api" && routePodWorkspace.route.workspace === "/workspace/app", "pod route must support a workspace suffix after the pod id", routePodWorkspace);
|
|
assertCondition(routePodWorkspace.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'hwlab-dev' 'pod/hwlab-cloud-api-abc' '-c' 'api' '--' 'sh' '-c' 'cd \"$1\" || exit; shift; exec \"$@\"' 'unidesk-cwd' '/workspace/app' 'pwd'", "pod workspace route must run commands through a fixed cwd wrapper", routePodWorkspace);
|
|
|
|
const routeDeploymentWorkspaceScript = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api/app", ["script"]);
|
|
assertCondition(routeDeploymentWorkspaceScript.route.resource === "hwlab-cloud-api" && routeDeploymentWorkspaceScript.route.workspace === "/app", "deployment shorthand route must support workspace suffix", routeDeploymentWorkspaceScript);
|
|
assertCondition(routeDeploymentWorkspaceScript.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-i' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'sh' '-c' 'cd \"$1\" || exit; shift; exec \"$@\"' 'unidesk-cwd' '/app' 'sh' '-s' '--'", "pod workspace script must set cwd before shell -s consumes stdin", routeDeploymentWorkspaceScript);
|
|
|
|
const routeApplyPatchWorkspace = parseSshInvocation("D601:k3s:hwlab-dev:hwlab-cloud-api/app", ["apply-patch"]);
|
|
assertCondition(routeApplyPatchWorkspace.parsed.requiresStdin === true, "pod workspace apply-patch must still stream patch stdin", routeApplyPatchWorkspace);
|
|
assertCondition(routeApplyPatchWorkspace.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-i' '-n' 'hwlab-dev' 'deployment/hwlab-cloud-api' '--' 'sh' '-c' 'cd \"$1\" || exit; shift; exec \"$@\"' 'unidesk-cwd' '/app' 'sh' '-s' '--'", "pod workspace apply-patch must set cwd before injecting the sh helper", routeApplyPatchWorkspace);
|
|
|
|
const routeExecStdin = parseSshInvocation("D601:k3s:unidesk:code-queue/root/unidesk", ["exec", "--stdin", "--", "tar", "-xf", "-", "-C", "/root/unidesk"]);
|
|
assertCondition(routeExecStdin.parsed.requiresStdin === true, "pod route exec --stdin must stream local stdin into kubectl exec", routeExecStdin);
|
|
assertCondition(routeExecStdin.parsed.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'unidesk' 'deployment/code-queue' '-i' '--' 'sh' '-c' 'cd \"$1\" || exit; shift; exec \"$@\"' 'unidesk-cwd' '/root/unidesk' 'tar' '-xf' '-' '-C' '/root/unidesk'", "pod route exec --stdin must keep exec flags before -- and command argv after --", routeExecStdin);
|
|
|
|
const sshLike = parseSshArgs(["echo hello"]);
|
|
const hint = sshFailureHint("D601", sshLike, 255, "kex_exchange_identification: Connection closed by remote host");
|
|
assertCondition(hint !== null, "ssh-like kex failure must produce a hint", sshLike);
|
|
assertCondition(hint?.try === "bun scripts/cli.ts ssh D601 script <<'SCRIPT'", "hint must provide canonical stdin script retry", hint);
|
|
assertCondition(hint?.triage.includes("provider triage D601"), "hint must provide provider triage command", hint);
|
|
const formatted = formatSshFailureHint(hint!);
|
|
assertCondition(formatted.startsWith("UNIDESK_SSH_HINT "), "formatted hint must have structured prefix", formatted);
|
|
assertCondition(!formatted.includes("echo hello"), "formatted hint must not echo the original remote command", formatted);
|
|
|
|
const timingInfo = sshRuntimeTimingHint({
|
|
invocation: parseSshInvocation("D601:/home/ubuntu/workspace/hwlab-dev", ["argv", "true"]),
|
|
transport: "backend-core-broker",
|
|
exitCode: 0,
|
|
startedAtMs: 1000,
|
|
finishedAtMs: 5200,
|
|
thresholdMs: 10_000,
|
|
});
|
|
assertCondition(timingInfo.level === "info" && timingInfo.slow === false, "short ssh operation should stay below the timing warning threshold", timingInfo);
|
|
assertCondition(timingInfo.elapsedMs === 4200 && timingInfo.elapsedSeconds === 4.2, "timing hint must include elapsed ms and seconds", timingInfo);
|
|
assertCondition(formatSshRuntimeTimingHint(timingInfo) === "", "short ssh operation must not write routine timing noise to stderr", timingInfo);
|
|
const slowTiming = sshRuntimeTimingHint({
|
|
invocation: parseSshInvocation("D601", ["apply-patch"]),
|
|
transport: "frontend-websocket",
|
|
exitCode: 0,
|
|
startedAtMs: 0,
|
|
finishedAtMs: 12_345,
|
|
thresholdMs: 10_000,
|
|
});
|
|
assertCondition(slowTiming.level === "warning" && slowTiming.slow === true, "slow ssh operation should emit a warning timing hint", slowTiming);
|
|
assertCondition(slowTiming.message.includes("above the 10s warning threshold"), "slow timing warning must explain the threshold", slowTiming);
|
|
const formattedTiming = formatSshRuntimeTimingHint(slowTiming);
|
|
assertCondition(formattedTiming.startsWith("UNIDESK_SSH_TIMING "), "formatted timing hint must have structured prefix", formattedTiming);
|
|
assertCondition(formattedTiming.includes("\"exitCode\":0"), "slow successful ssh operation must still emit timing warning as a performance signal", formattedTiming);
|
|
assertCondition(!formattedTiming.includes("apply_patch"), "timing hint must not echo the original remote command", formattedTiming);
|
|
|
|
const timeoutHint = sshFailureHint("D601", sshLike, 255, "unidesk ssh bridge timed out waiting for provider session");
|
|
assertCondition(timeoutHint?.trigger === "timeout-or-kex", "provider session timeout must map to timeout-or-kex", timeoutHint);
|
|
|
|
const helpText = JSON.stringify(sshHelp());
|
|
assertCondition(helpText.includes("ssh <providerId> script [--shell sh|bash] [script-args...] <<'SCRIPT'"), "ssh help must recommend stdin script passthrough for shell scripts", helpText);
|
|
assertCondition(helpText.includes("inherits provider proxy variables"), "ssh help must state default script inherits provider proxy env", helpText);
|
|
assertCondition(helpText.includes("not as a proxy workaround"), "ssh help must reserve --shell bash for bash syntax instead of proxy workarounds", helpText);
|
|
assertCondition(helpText.includes("ssh D601:/home/ubuntu/workspace/hwlab-dev git status --short --branch"), "ssh help must document host workspace routes", helpText);
|
|
assertCondition(helpText.includes("ssh D601:k3s kubectl get pods -n hwlab-dev"), "ssh help must document k3s kubectl operation", helpText);
|
|
assertCondition(helpText.includes("ssh G14:k3s kubectl get pipelineruns -n hwlab-ci"), "ssh help must document G14 k3s route operation", helpText);
|
|
assertCondition(helpText.includes("ssh D601:k3s:hwlab-dev:hwlab-cloud-api/app pwd"), "ssh help must document k3s pod workspace route", helpText);
|
|
assertCondition(helpText.includes("ssh D601:k3s script <<'SCRIPT'"), "ssh help must document k3s control-plane script operation", helpText);
|
|
assertCondition(helpText.includes("ssh D601:k3s:hwlab-dev:hwlab-cloud-api apply-patch <<'PATCH'"), "ssh help must document k3s pod apply-patch operation", helpText);
|
|
assertCondition(helpText.includes("ssh D601:k3s:unidesk:code-queue/root/unidesk exec --stdin -- tar -xf - -C /root/unidesk"), "ssh help must document one-step stdin file streaming into pod exec", helpText);
|
|
assertCondition(helpText.includes("apply-patch [--allow-loose]") && helpText.includes("low-context update hunks"), "ssh help must document apply-patch loose-context guard", helpText);
|
|
assertCondition(helpText.includes("ssh D601:k3s:hwlab-dev:hwlab-cloud-api script <<'SCRIPT'"), "ssh help must document k3s script operation", helpText);
|
|
assertCondition(helpText.includes("UNIDESK_SSH_HINT"), "ssh help must document structured failure hint", helpText);
|
|
assertCondition(helpText.includes("UNIDESK_SSH_TIMING") && helpText.includes("10s") && helpText.includes("slow successful calls are a distributed performance monitoring signal") && helpText.includes("Routine short calls do not emit timing noise"), "ssh help must document slow-only runtime timing hints", helpText);
|
|
assertCondition(helpText.includes("UNIDESK_TRAN_SESSION_LOCK=0") && helpText.includes("provider session allocator"), "ssh help must document tran provider session serialization", helpText);
|
|
|
|
const crossChecks = providerTriageRecommendedCrossChecks("D601");
|
|
assertCondition(crossChecks.includes("bun scripts/cli.ts ssh D601 argv true"), "provider triage cross-checks must keep argv true", crossChecks);
|
|
|
|
const frontendRemoteK3sPlan = remoteSshFrontendPlanForTest("D601:k3s", ["kubectl", "get", "nodes", "-o", "name"]);
|
|
assertCondition(frontendRemoteK3sPlan.transport === "frontend-websocket", "remote frontend ssh must use the streaming websocket bridge", frontendRemoteK3sPlan);
|
|
assertCondition(frontendRemoteK3sPlan.providerId === "D601", "remote frontend ssh must dispatch route target to the provider id", frontendRemoteK3sPlan);
|
|
assertCondition(frontendRemoteK3sPlan.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'get' 'nodes' '-o' 'name'", "remote frontend ssh must preserve k3s route command construction", frontendRemoteK3sPlan);
|
|
assertCondition(!String(frontendRemoteK3sPlan.wrappedRemoteCommand ?? "").includes("UNIDESK_SSH_TOOL_DIR=/tmp/unidesk-ssh-tools"), "remote frontend ssh must not bootstrap helper tools for plain kubectl argv", frontendRemoteK3sPlan);
|
|
|
|
const frontendRemoteHostPatchPlan = remoteSshFrontendPlanForTest("D601", ["apply-patch"]);
|
|
assertCondition(String(frontendRemoteHostPatchPlan.wrappedRemoteCommand ?? "").includes("UNIDESK_SSH_TOOL_DIR=/tmp/unidesk-ssh-tools"), "host apply-patch must bootstrap the remote apply_patch helper", frontendRemoteHostPatchPlan);
|
|
assertCondition(String(frontendRemoteHostPatchPlan.wrappedRemoteCommand ?? "").includes("/apply_patch") && !String(frontendRemoteHostPatchPlan.wrappedRemoteCommand ?? "").includes("/glob") && !String(frontendRemoteHostPatchPlan.wrappedRemoteCommand ?? "").includes("/skill-discover"), "host apply-patch must not bootstrap unrelated helper tools", frontendRemoteHostPatchPlan);
|
|
|
|
const frontendRemotePodArgvPlan = remoteSshFrontendPlanForTest("G14:k3s:unidesk:code-queue", ["argv", "sh", "-c", "command -v tran"]);
|
|
assertCondition(frontendRemotePodArgvPlan.providerId === "G14", "remote frontend pod route must dispatch through G14 provider", frontendRemotePodArgvPlan);
|
|
assertCondition(frontendRemotePodArgvPlan.remoteCommand === "'env' 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml' 'kubectl' 'exec' '-n' 'unidesk' 'deployment/code-queue' '--' 'sh' '-c' 'command -v tran'", "remote frontend pod argv route must be fully assembled before dispatch", frontendRemotePodArgvPlan);
|
|
|
|
const frontendRemoteWorkspacePlan = remoteSshFrontendPlanForTest("D601:/home/ubuntu/workspace/hwlab-dev", ["git", "status", "--short"]);
|
|
assertCondition(frontendRemoteWorkspacePlan.payloadCwd === "/home/ubuntu/workspace/hwlab-dev", "remote frontend host workspace route must pass cwd to host.ssh payload", frontendRemoteWorkspacePlan);
|
|
assertCondition(frontendRemoteWorkspacePlan.remoteCommand === "'git' 'status' '--short'", "remote frontend host workspace route must keep command argv-quoted", frontendRemoteWorkspacePlan);
|
|
|
|
const tranScript = readFileSync(new URL("./tran", import.meta.url), "utf8");
|
|
assertCondition(tranScript.includes("CODE_QUEUE_DEV_CONTAINER_MASTER_HOST") && tranScript.includes("--main-server-ip"), "tran wrapper must auto-select frontend transport inside Code Queue runner pods", tranScript);
|
|
assertCondition(tranScript.includes("UNIDESK_TRAN_LOCAL"), "tran wrapper must keep an explicit local override for diagnostics", tranScript);
|
|
assertCondition(tranScript.includes("tran_lock_scope") && tranScript.includes("UNIDESK_TRAN_LOCK_DIR"), "tran wrapper must serialize concurrent provider session opens with a local sh lock", tranScript);
|
|
const tranLock = tranConcurrentLockFixture();
|
|
assertCondition(tranLock.status === 0, "tran lock fixture shell should complete", tranLock);
|
|
assertCondition(tranLock.stdout.trim() === "0 0", "parallel tran invocations for one provider must serialize instead of overlapping fake bun", tranLock);
|
|
assertCondition(!tranLock.stderr.includes("overlapping tran execution"), "tran provider lock must prevent overlapping provider session allocation", tranLock);
|
|
|
|
const remoteSource = readFileSync(new URL("./src/remote.ts", import.meta.url), "utf8");
|
|
assertCondition(remoteSource.includes("UNIDESK_REMOTE_HTTP_CLIENT") && remoteSource.includes("isCodeQueueRunnerEnv(env) ? \"curl\" : \"fetch\""), "remote frontend transport must default to curl HTTP in Code Queue runner environments", remoteSource);
|
|
assertCondition(remoteSource.includes("frontendSshWebSocketUrl") && remoteSource.includes("runRemoteSshWebSocket"), "remote frontend ssh must go through the streaming websocket implementation", remoteSource);
|
|
assertCondition(!remoteSource.includes("remote frontend transport does not stream stdin"), "remote frontend ssh must not reject stdin-backed helpers", remoteSource);
|
|
assertCondition(!remoteSource.includes("source: \"cli-remote-ssh\""), "remote frontend ssh must not use host.ssh dispatch task polling", remoteSource);
|
|
|
|
const frontendSource = readFileSync(new URL("../src/components/frontend/src/index.ts", import.meta.url), "utf8");
|
|
assertCondition(frontendSource.includes('url.pathname === "/ws/ssh"') && frontendSource.includes("proxySshWebSocket"), "frontend must expose an authenticated /ws/ssh proxy", frontendSource);
|
|
assertCondition(frontendSource.includes("coreSshWebSocketUrl") && frontendSource.includes('url.searchParams.set("token"'), "frontend /ws/ssh proxy must connect to backend-core ssh bridge with the provider token", frontendSource);
|
|
assertCondition(frontendSource.includes("PROVIDER_TOKEN_FILE") && frontendSource.includes("/run/secrets/unidesk_provider_token"), "frontend ssh proxy must support file-based provider token injection for runtime hotfix and secret mounts", frontendSource);
|
|
|
|
const composeSource = readFileSync(new URL("../docker-compose.yml", import.meta.url), "utf8");
|
|
assertCondition(composeSource.includes('PROVIDER_TOKEN: "${UNIDESK_PROVIDER_TOKEN}"'), "frontend compose service must receive provider token for the ssh proxy", composeSource);
|
|
|
|
const devCoreManifest = readFileSync(new URL("../src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml", import.meta.url), "utf8");
|
|
assertCondition(devCoreManifest.includes("name: frontend-dev") && devCoreManifest.includes("name: PROVIDER_TOKEN"), "dev frontend manifest must receive provider token for the ssh proxy", devCoreManifest);
|
|
|
|
const codeQueueDockerfile = readFileSync(new URL("../src/components/microservices/code-queue/Dockerfile", import.meta.url), "utf8");
|
|
assertCondition(codeQueueDockerfile.includes("COPY scripts/tran /usr/local/bin/tran") && codeQueueDockerfile.includes("chmod 755 /usr/local/bin/tran"), "Code Queue runner image must install tran on PATH", codeQueueDockerfile);
|
|
|
|
return {
|
|
ok: true,
|
|
checks: [
|
|
"argv form is classified and quoted as the success path for non-interactive commands",
|
|
"stdin script form removes shell-command strings for host and k3s workload scripts",
|
|
"script -- command form executes dash-prefixed argv without waiting for stdin",
|
|
"pod apply-patch operation injects helper and forwards patch stdin",
|
|
"pod exec --stdin streams arbitrary local stdin through workload routes without shell wrapping",
|
|
"apply-patch uses one sh helper for host and pod paths and rejects low-context hunks unless --allow-loose is explicit",
|
|
"legacy operation-in-route forms are rejected in any k3s route segment with canonical route-plus-operation guidance",
|
|
"post-provider k3s shorthand is rejected so location and operation stay separated",
|
|
"k3s route stays location-only while operations fix native kubeconfig and assemble kubectl exec as argv",
|
|
"top-level remote option parsing preserves command-local -- separators for script -- sed -n style commands",
|
|
"ssh-like timeout/kex failures emit one structured argv retry hint",
|
|
"ssh runtime emits structured timing for slow operations over 10 seconds, including successful slow calls",
|
|
"help text documents stdin script passthrough and UNIDESK_SSH_HINT",
|
|
"provider triage recommendedCrossChecks keeps ssh D601 argv true",
|
|
"remote frontend ssh uses the same structured route parser for host, k3s and pod argv routes",
|
|
"ssh helper bootstrap is lazy so plain argv/script commands do not transfer helper sources",
|
|
"host apply-patch bootstraps only the apply_patch helper and uses a Perl fast path for large files",
|
|
"remote frontend ssh uses authenticated /ws/ssh streaming instead of host.ssh dispatch task polling",
|
|
"Code Queue runner image installs the tran wrapper and runner tran auto-selects remote frontend transport",
|
|
"tran serializes concurrent non-interactive calls per provider/plane before opening provider SSH sessions",
|
|
"Code Queue runner remote frontend HTTP uses curl by default for non-ssh API calls to avoid Bun response-body native crashes",
|
|
],
|
|
};
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
process.stdout.write(`${JSON.stringify(runSshArgvGuidanceContract(), null, 2)}\n`);
|
|
}
|