218 lines
8.9 KiB
TypeScript
218 lines
8.9 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
|
|
import { parseSecretSyncOptions } from "./agentrun/control-plane";
|
|
import type { SecretSyncOptions } from "./agentrun/options";
|
|
import { compactAgentRunSecretSyncPayload, renderAgentRunSecretSyncResult } from "./agentrun/secret-sync-output";
|
|
import { confirmedSecretSyncPlan } from "./agentrun/trigger";
|
|
|
|
const options = (output: SecretSyncOptions["output"], overrides: Partial<SecretSyncOptions> = {}): SecretSyncOptions => ({
|
|
confirm: false,
|
|
dryRun: true,
|
|
node: "NC01",
|
|
lane: "nc01-v02",
|
|
secretIds: [],
|
|
full: false,
|
|
raw: false,
|
|
output,
|
|
...overrides,
|
|
});
|
|
|
|
function blockedFixture(): Record<string, unknown> {
|
|
const planItems = Array.from({ length: 13 }, (_, index) => {
|
|
const id = index === 11 ? "tool-github-ssh-private-key" : index === 12 ? "tool-github-ssh-known-hosts" : `fixture-secret-${index}`;
|
|
return {
|
|
id,
|
|
namespace: "agentrun-v02",
|
|
secret: index >= 11 ? "agentrun-v01-tool-github-ssh" : `fixture-target-${index}`,
|
|
key: index === 11 ? "id_ed25519" : index === 12 ? "known_hosts" : `key-${index}`,
|
|
sourceMode: "file",
|
|
sourcePath: "/root/.unidesk/.state/secrets/<redacted>",
|
|
present: true,
|
|
sourceFilePresent: true,
|
|
fingerprint: `sha256:${String(index).padStart(64, "a")}`,
|
|
valueBytes: 120 + index,
|
|
degradedReason: null,
|
|
valuesPrinted: false,
|
|
};
|
|
});
|
|
return {
|
|
ok: false,
|
|
command: "agentrun control-plane secret-sync",
|
|
mode: "dry-run-blocked-before-mutation",
|
|
status: "blocked",
|
|
mutation: false,
|
|
blockedBeforeMutation: true,
|
|
secretSyncStarted: false,
|
|
target: { node: "NC01", lane: "nc01-v02", namespace: "agentrun-v02", valuesPrinted: false },
|
|
degradedReason: "provider-secret-activation-fence-unavailable",
|
|
plan: { secretCount: planItems.length, items: planItems, omittedCount: 0, valuesPrinted: false },
|
|
activationPreflight: {
|
|
ok: false,
|
|
status: "blocked",
|
|
reason: "provider-secret-activation-fence-unavailable",
|
|
targetSecretCount: 1,
|
|
targetSecrets: [{
|
|
namespace: "agentrun-v02",
|
|
name: "agentrun-v01-provider-codex",
|
|
desiredKeys: ["auth.json", "config.toml"],
|
|
queryOk: true,
|
|
present: true,
|
|
mutationCount: 1,
|
|
noOp: false,
|
|
mutations: [{
|
|
id: "provider-codex-config",
|
|
key: "config.toml",
|
|
desiredFingerprintSuffix: "aaaaaaaaaaaa",
|
|
runtimeFingerprintSuffix: "bbbbbbbbbbbb",
|
|
changeKind: "change-key",
|
|
valuesPrinted: false,
|
|
}],
|
|
valuesPrinted: false,
|
|
}],
|
|
activationRiskCount: 1,
|
|
activationRisks: [{
|
|
pod: "agentrun-runner-rjob_fixture-pod",
|
|
jobName: "agentrun-runner-rjob_fixture",
|
|
phase: "Pending",
|
|
runId: "run_fixture",
|
|
commandId: "cmd_fixture",
|
|
runnerJobId: "rjob_fixture",
|
|
waitingReasons: ["CreateContainerConfigError"],
|
|
classification: "nonterminal-runner-required-provider-secret-missing",
|
|
dependencies: [{
|
|
source: "volume-secret",
|
|
name: "agentrun-v01-provider-codex",
|
|
affectedDesiredKeys: ["config.toml"],
|
|
mutationKinds: ["change-key"],
|
|
reason: "nonterminal-runner-provider-secret-mutation",
|
|
valuesPrinted: false,
|
|
}],
|
|
valuesPrinted: false,
|
|
}],
|
|
requiresManagerFence: true,
|
|
targetSecretObservationComplete: true,
|
|
runnerObservationComplete: true,
|
|
allObservationsComplete: true,
|
|
providerSecretMutationCount: 1,
|
|
providerSecretDataNoOp: false,
|
|
providerSecretMutationAuthorized: false,
|
|
managerAuthority: {
|
|
owner: "agentrun-manager-provider-activation-fence",
|
|
atomicFenceAvailable: false,
|
|
mutationAuthorized: false,
|
|
noOpOnly: true,
|
|
trackingIssue: "https://github.com/pikasTech/agentrun/issues/284",
|
|
valuesPrinted: false,
|
|
},
|
|
capture: {
|
|
stdoutTail: "FULL_PROBE_BODY_ONLY_VISIBLE_WITH_EXPLICIT_FULL",
|
|
valuesPrinted: false,
|
|
},
|
|
valuesPrinted: false,
|
|
},
|
|
execution: {
|
|
providerSecretClassification: "exact-target-ref",
|
|
providerSecretWriteBlockedCount: 2,
|
|
secretSyncStarted: false,
|
|
valuesPrinted: false,
|
|
},
|
|
next: {
|
|
observeRunners: "bun scripts/cli.ts agentrun control-plane cleanup-runners --node NC01 --lane nc01-v02 --dry-run",
|
|
retryAfterManagerFence: "bun scripts/cli.ts agentrun control-plane secret-sync --node NC01 --lane nc01-v02 --confirm",
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
describe("AgentRun secret-sync bounded output", () => {
|
|
test("confirmed sync reports all written secrets without stale provider classification", () => {
|
|
const items = [{ id: "provider-gpt-pika-auth-json" }, { id: "provider-gpt-pika-config" }];
|
|
expect(confirmedSecretSyncPlan(items, 2)).toEqual({
|
|
secretCount: 2,
|
|
items,
|
|
writableSecretCount: 2,
|
|
valuesPrinted: false,
|
|
});
|
|
});
|
|
|
|
test("parses compact machine output and explicit disclosure options", () => {
|
|
expect(parseSecretSyncOptions(["--node", "NC01", "--lane", "nc01-v02", "--dry-run", "-o", "yaml"])).toEqual({
|
|
confirm: false,
|
|
dryRun: true,
|
|
node: "NC01",
|
|
lane: "nc01-v02",
|
|
secretIds: [],
|
|
full: false,
|
|
raw: false,
|
|
output: "yaml",
|
|
});
|
|
expect(parseSecretSyncOptions(["--node", "NC01", "--lane", "nc01-v02", "--secret-id", "tool-github-ssh-private-key", "--full", "--output=json"])).toMatchObject({
|
|
secretIds: ["tool-github-ssh-private-key"],
|
|
full: true,
|
|
raw: false,
|
|
output: "json",
|
|
});
|
|
expect(parseSecretSyncOptions(["--node", "NC01", "--lane", "nc01-v02", "--raw"])).toMatchObject({ full: true, raw: true });
|
|
expect(() => parseSecretSyncOptions(["--node", "NC01", "--lane", "nc01-v02", "-o", "wide"])).toThrow("must be one of human,json,yaml");
|
|
});
|
|
|
|
test("uses one bounded Secret-safe projection for human, JSON, and YAML", () => {
|
|
const fixture = blockedFixture();
|
|
const compact = compactAgentRunSecretSyncPayload(fixture);
|
|
const human = renderAgentRunSecretSyncResult(fixture, options("human")).renderedText;
|
|
const jsonText = renderAgentRunSecretSyncResult(fixture, options("json")).renderedText;
|
|
const yamlText = renderAgentRunSecretSyncResult(fixture, options("yaml")).renderedText;
|
|
const json = JSON.parse(jsonText) as Record<string, any>;
|
|
const yaml = Bun.YAML.parse(yamlText) as Record<string, any>;
|
|
|
|
expect(json).toEqual(compact);
|
|
expect(yaml).toEqual(json);
|
|
expect(json.kind).toBe("AgentRunSecretSyncPlan");
|
|
expect(json.status).toMatchObject({ status: "blocked", reason: "provider-secret-activation-fence-unavailable", mutation: false, valuesPrinted: false });
|
|
expect(json.plan.secretCount).toBe(13);
|
|
expect(json.plan.omittedCount).toBe(0);
|
|
expect(json.plan.items.some((item: Record<string, unknown>) => item.id === "tool-github-ssh-private-key")).toBe(true);
|
|
expect(json.plan.items.some((item: Record<string, unknown>) => item.id === "tool-github-ssh-known-hosts")).toBe(true);
|
|
expect(json.activationFence).toMatchObject({
|
|
kind: "ProviderSecretActivationFence",
|
|
identity: "providersecretactivationfence/NC01/nc01-v02",
|
|
status: "blocked",
|
|
reason: "provider-secret-activation-fence-unavailable",
|
|
resourceCount: 1,
|
|
valuesPrinted: false,
|
|
});
|
|
expect(json.activationFence.resources[0]).toMatchObject({
|
|
identity: "runnerjob/rjob_fixture",
|
|
status: "Pending",
|
|
runId: "run_fixture",
|
|
commandId: "cmd_fixture",
|
|
runnerJobId: "rjob_fixture",
|
|
});
|
|
expect(json.activationFence.resources[0].drillDown.map((item: Record<string, unknown>) => item.identity)).toEqual([
|
|
"runnerjob/rjob_fixture",
|
|
"command/cmd_fixture",
|
|
"run/run_fixture",
|
|
]);
|
|
expect(json.next.secretById).toContain("--secret-id <secret-id> --dry-run -o json");
|
|
expect(human).toContain("ACTIVATION FENCE");
|
|
expect(human).toContain("runnerjob/rjob_fixture");
|
|
expect(human).toContain("tool-github-ssh-private-key");
|
|
expect(human).toContain("VALUES PRINTED false");
|
|
for (const output of [human, jsonText, yamlText]) {
|
|
expect(Buffer.byteLength(output, "utf8")).toBeLessThan(10_240);
|
|
expect(output).not.toContain("FULL_PROBE_BODY_ONLY_VISIBLE_WITH_EXPLICIT_FULL");
|
|
expect(output).not.toContain("/root/.unidesk/.state/secrets/<redacted>");
|
|
}
|
|
});
|
|
|
|
test("keeps exhaustive probe details behind explicit full or raw", () => {
|
|
const fixture = blockedFixture();
|
|
const full = renderAgentRunSecretSyncResult(fixture, options("json", { full: true })).renderedText;
|
|
const raw = renderAgentRunSecretSyncResult(fixture, options("human", { full: true, raw: true })).renderedText;
|
|
expect(JSON.parse(full)).toEqual(fixture);
|
|
expect(JSON.parse(raw)).toEqual(fixture);
|
|
expect(full).toContain("FULL_PROBE_BODY_ONLY_VISIBLE_WITH_EXPLICIT_FULL");
|
|
expect(raw).toContain("FULL_PROBE_BODY_ONLY_VISIBLE_WITH_EXPLICIT_FULL");
|
|
});
|
|
});
|