import { buildDockerCleanupPlan, type DockerCleanupInventory } from "./src/server-cleanup"; type JsonRecord = Record; function assertCondition(condition: unknown, message: string, detail: unknown = {}): void { if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`); } const observedAt = "2026-05-21T14:00:00.000Z"; const fixture: DockerCleanupInventory = { observedAt, images: [ { id: "sha256:1111111111111111111111111111111111111111111111111111111111111111", repoTags: ["unidesk-backend-core:latest"], repoDigests: [], sizeBytes: 110 * 1024 * 1024, createdAt: "2026-05-21T10:00:00.000Z", labels: { "unidesk.ai/service-id": "backend-core", "unidesk.ai/source-commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, }, { id: "sha256:2222222222222222222222222222222222222222222222222222222222222222", repoTags: ["127.0.0.1:5000/unidesk/frontend:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"], repoDigests: [], sizeBytes: 120 * 1024 * 1024, createdAt: "2026-05-21T08:00:00.000Z", labels: { "unidesk.ai/service-id": "frontend", "unidesk.ai/source-commit": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, }, { id: "sha256:3333333333333333333333333333333333333333333333333333333333333333", repoTags: ["old-test-image:local"], repoDigests: ["old-test-image@sha256:3333333333333333333333333333333333333333333333333333333333333333"], sizeBytes: 500 * 1024 * 1024, createdAt: "2026-05-19T14:00:00.000Z", labels: {}, }, { id: "sha256:4444444444444444444444444444444444444444444444444444444444444444", repoTags: [], repoDigests: [], sizeBytes: 1024 * 1024 * 1024, createdAt: "2026-05-18T14:00:00.000Z", labels: {}, }, ], containers: [ { id: "container-running-backend-core", name: "unidesk-backend-core", imageRef: "unidesk-backend-core:latest", imageId: "sha256:1111111111111111111111111111111111111111111111111111111111111111", state: "running", status: "running", labels: {}, }, ], desiredImageRefs: [ { ref: "127.0.0.1:5000/unidesk/frontend:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", source: "CI.json + deploy.json", serviceId: "frontend", reason: "current commit-pinned registry artifact", }, ], desiredCommitsByService: { "backend-core": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], frontend: ["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"], }, protectedStorage: [ { kind: "docker-volume", ref: "unidesk_pgdata_10gb", risk: "blocked", reason: "database named volume", }, { kind: "path", ref: "/workspace/unidesk/.state/baidu-netdisk", risk: "blocked", reason: "Baidu Netdisk state", }, { kind: "path", ref: "/home/ubuntu/.unidesk/registry-storage", risk: "blocked", reason: "registry storage", }, ], collection: { dockerAvailable: true, readOnlyCommands: [["docker", "image", "ls", "-q", "--no-trunc"]], errors: [], }, }; export function runServerCleanupPlanContract(): JsonRecord { const plan = buildDockerCleanupPlan(fixture, { minAgeHours: 24, limit: 20 }); const candidateIds = plan.candidateStaleImages.map((image) => image.id); const protectedIds = plan.protectedImages.map((image) => image.id); assertCondition(plan.ok === true, "plan should be ok", plan); assertCondition(plan.dryRun === true && plan.mutation === false, "plan must be dry-run and non-mutating", plan.policy); assertCondition(plan.policy.deletionExecuted === false, "plan must not execute deletion", plan.policy); assertCondition(plan.policy.dockerPruneUsed === false, "plan must not use docker prune", plan.policy); assertCondition(plan.policy.dockerVolumesTouched === false, "plan must not touch docker volumes", plan.policy); assertCondition(plan.policy.databaseCleanupIncluded === false, "plan must not include database cleanup", plan.policy); assertCondition(candidateIds.includes("sha256:3333333333333333333333333333333333333333333333333333333333333333"), "tagged stale image should be a candidate", plan.candidateStaleImages); assertCondition(candidateIds.includes("sha256:4444444444444444444444444444444444444444444444444444444444444444"), "dangling stale image should be a candidate", plan.candidateStaleImages); assertCondition(protectedIds.includes("sha256:1111111111111111111111111111111111111111111111111111111111111111"), "running image should be protected", plan.protectedImages); assertCondition(protectedIds.includes("sha256:2222222222222222222222222222222222222222222222222222222222222222"), "desired deploy image should be protected", plan.protectedImages); assertCondition(plan.candidateStaleImages.length === 2, "only stale non-protected images should be candidates", plan.candidateStaleImages); assertCondition(plan.risk.medium === 1, "tagged stale candidate should be medium risk", plan.risk); assertCondition(plan.risk.low === 1, "dangling stale candidate should be low risk", plan.risk); assertCondition(plan.commandsToReview.length === 2, "commandsToReview should include candidate commands", plan.commandsToReview); assertCondition(plan.commandsToReview.every((command) => command.requiresManualApproval === true), "commands must require manual approval", plan.commandsToReview); const taggedCommand = plan.commandsToReview.find((command) => command.imageId === "sha256:3333333333333333333333333333333333333333333333333333333333333333"); assertCondition(taggedCommand?.command.includes("old-test-image:local"), "tagged candidate command should include reviewed tag", plan.commandsToReview); assertCondition(taggedCommand?.command.includes("old-test-image@sha256:3333333333333333333333333333333333333333333333333333333333333333"), "tagged candidate command should include reviewed digest", plan.commandsToReview); assertCondition(plan.commandsToReview.some((command) => command.command.includes("sha256:4444444444444444444444444444444444444444444444444444444444444444")), "dangling candidate command should use image id", plan.commandsToReview); assertCondition(!JSON.stringify(plan.commandsToReview).includes("docker image prune"), "plan must not recommend image prune", plan.commandsToReview); assertCondition(!JSON.stringify(plan.commandsToReview).includes("docker system prune"), "plan must not recommend system prune", plan.commandsToReview); assertCondition(plan.prohibitedCommands.includes("docker image prune"), "image prune should be explicitly prohibited", plan.prohibitedCommands); assertCondition(plan.prohibitedCommands.includes("docker system prune"), "system prune should be explicitly prohibited", plan.prohibitedCommands); assertCondition(plan.protectedStorage.some((item) => item.ref === "unidesk_pgdata_10gb"), "database volume must be protected", plan.protectedStorage); assertCondition(plan.protectedStorage.some((item) => String(item.ref).includes("baidu-netdisk")), "Baidu Netdisk state must be protected", plan.protectedStorage); assertCondition(plan.protectedStorage.some((item) => String(item.ref).includes("registry-storage")), "registry storage must be protected", plan.protectedStorage); assertCondition(plan.estimatedReclaimBytes === (500 * 1024 * 1024) + (1024 * 1024 * 1024), "estimated reclaim should sum candidate image sizes", plan.estimates); return { ok: true, checks: [ "dry-run non-mutating policy", "active image protected", "deploy/CI desired image protected", "stale candidates emitted", "risk levels emitted", "manual commandsToReview emitted", "database/registry/baidu storage protected", "prune commands absent", ], }; } if (import.meta.main) { process.stdout.write(`${JSON.stringify(runServerCleanupPlanContract(), null, 2)}\n`); }