import { readConfig } from "./src/config"; import { runCiPublishBackendCoreDryRunPreflight, type PublishPreflightTransport } from "./src/ci"; import { autoRemoteCiPublishUserServiceDryRunPlan } from "./src/remote"; type JsonRecord = Record; function assertCondition(condition: unknown, message: string, detail: unknown = {}): void { if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`); } function asRecord(value: unknown, label: string): JsonRecord { assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, { value }); return value as JsonRecord; } const commit = "0123456789abcdef0123456789abcdef01234567"; const config = readConfig(); function healthyRegistryStdout(): string { return [ "systemctl_available=true", "docker_available=true", "curl_available=true", "unit_exists=true", "unit_active=active", "unit_enabled=enabled", "compose_exists=true", "config_exists=true", "storage_exists=true", "container_running=true", "container_status=running", "container_image=registry:2.8.3", "container_restart_policy=always", "listener_count=1", "bad_listener_count=0", "loopback_only=true", "v2_http_code=200", "image_matches=true", "config_hash_matches=true", "compose_hash_matches=true", "unit_hash_matches=true", ].join("\n"); } function readyTransport(kind: "remote-frontend" | "local-docker" = "remote-frontend"): PublishPreflightTransport { return { kind, remoteHost: kind === "remote-frontend" ? "https://example.invalid" : null, coreFetch: async () => ({ ok: true, status: 200, body: { ok: true, dbReady: true, }, }), dispatchHostSsh: async (command) => { if (command.includes("kubectl get namespace unidesk-ci")) { return { ok: true, taskId: "task-ci-runner", status: "succeeded", stdout: [ "provider_host_ssh=ok", "kubectl=ok", "docker=ok", "docker_socket=true", "namespace=true", "tekton_pipeline=true", "tekton_task=true", "service_account=true", "pvc=true", "source_parent_directory=true", ].join("\n"), stderr: "", exitCode: 0, raw: {}, }; } if (command.includes("/v2/")) { return { ok: true, taskId: "task-registry", status: "succeeded", stdout: healthyRegistryStdout(), stderr: "", exitCode: 0, raw: {}, }; } return { ok: true, taskId: "task-host-ssh", status: "succeeded", stdout: "provider_host_ssh=ok\n", stderr: "", exitCode: 0, raw: {}, }; }, commandCwd: "/workspace/unidesk", artifactRegistryCommand: (probe) => ["mock", probe.action, probe.remoteCommandShape], }; } const localMissingTransport: PublishPreflightTransport = { kind: "local-docker", coreFetch: async () => ({ ok: false, status: 503, body: { ok: false, failureKind: "target-stack-not-running", runnerDisposition: "infra-blocked", degradedReason: "backend-core-container-missing", message: "backend-core unavailable from runner-local Docker", }, }), dispatchHostSsh: async (command, waitMs, remoteTimeoutMs) => ({ ok: false, taskId: null, status: "infra-blocked", stdout: "", stderr: `backend-core bridge unavailable while dispatching readonly SSH task (${waitMs}/${remoteTimeoutMs})`, exitCode: null, raw: { ok: false, failureKind: "target-stack-not-running", runnerDisposition: "infra-blocked", command, }, }), commandCwd: "/workspace/unidesk", artifactRegistryCommand: (probe) => ["mock", probe.action, probe.remoteCommandShape], }; const remoteInfraBlockedTransport: PublishPreflightTransport = { kind: "remote-frontend", remoteHost: "https://example.invalid", coreFetch: async () => ({ ok: true, status: 200, body: { ok: true, dbReady: true, }, }), dispatchHostSsh: async (command) => { if (command.includes("/v2/")) { return { ok: true, taskId: "task-registry", status: "succeeded", stdout: [ "systemctl_available=true", "docker_available=true", "curl_available=true", "unit_exists=true", "unit_active=active", "unit_enabled=enabled", "compose_exists=true", "config_exists=true", "storage_exists=true", "container_running=false", "container_status=exited", "container_image=registry:2.8.3", "container_restart_policy=always", "listener_count=0", "bad_listener_count=0", "loopback_only=false", "v2_http_code=000", "image_matches=true", "config_hash_matches=true", "compose_hash_matches=true", "unit_hash_matches=true", ].join("\n"), stderr: "", exitCode: 0, raw: {}, }; } return { ok: true, taskId: "task-ci-runner", status: "succeeded", stdout: [ "provider_host_ssh=ok", "kubectl=ok", "docker=ok", "docker_socket=true", "namespace=true", "tekton_pipeline=true", "tekton_task=true", "service_account=true", "pvc=true", "source_parent_directory=true", ].join("\n"), stderr: "", exitCode: 0, raw: {}, }; }, commandCwd: "/workspace/unidesk", artifactRegistryCommand: (probe) => ["mock", probe.action, probe.remoteCommandShape], }; async function main(): Promise { const autoRemote = autoRemoteCiPublishUserServiceDryRunPlan(config, [ "ci", "publish-backend-core", "--commit", commit, "--dry-run", ], { CODE_QUEUE_SERVICE_ROLE: "scheduler", CODE_QUEUE_DEV_CONTAINER_MASTER_HOST: "74.48.78.17", }); assertCondition(autoRemote.enabled === true, "Code Queue runner backend-core dry-run should auto-select remote frontend transport", autoRemote); assertCondition(autoRemote.host === "74.48.78.17", "auto remote backend-core plan should use CODE_QUEUE_DEV_CONTAINER_MASTER_HOST", autoRemote); assertCondition(String(autoRemote.command ?? "").includes("ci publish-backend-core"), "auto remote command should preserve backend-core publish dry-run", autoRemote); const localMissing = await runCiPublishBackendCoreDryRunPreflight(config, [ "publish-backend-core", "--commit", commit, "--dry-run", ], localMissingTransport); const localMissingRecord = asRecord(localMissing, "local missing backend-core preflight"); const localMissingControlPlane = asRecord(localMissingRecord.remoteControlPlaneCandidate, "local missing control plane"); const localBlockedScopes = Array.isArray(localMissingRecord.blockedScopes) ? localMissingRecord.blockedScopes as string[] : []; assertCondition(localMissingRecord.ok === false, "local missing backend-core preflight should fail", localMissingRecord); assertCondition(localMissingRecord.failureClassification === "local-docker-required", "local Docker absence should classify local-docker-required", localMissingRecord); assertCondition(localMissingControlPlane.transport === "local-docker", "local missing control plane should state local transport", localMissingControlPlane); assertCondition(localMissingControlPlane.remoteCapable === false, "local missing control plane should not claim remote capable", localMissingControlPlane); assertCondition(String(localMissingControlPlane.remoteCommandShape ?? "").includes("publish-backend-core"), "fallback should expose backend-core remote command shape", localMissingControlPlane); assertCondition(localBlockedScopes.includes("local-docker-control-plane"), "local missing blockedScopes should expose local-docker-control-plane", localMissingRecord); assertCondition(asRecord(localMissingRecord.d601Ci, "local missing d601Ci").dryRunWillCompileRust === false, "local dry-run must not compile Rust", localMissingRecord); const result = await runCiPublishBackendCoreDryRunPreflight(config, [ "publish-backend-core", "--commit", commit, "--dry-run", ], readyTransport()); const record = asRecord(result, "backend-core preflight"); const source = asRecord(record.source, "source"); const sourceAuth = asRecord(record.sourceAuth, "sourceAuth"); const d601Ci = asRecord(record.d601Ci, "d601Ci"); const ciRunner = asRecord(record.ciRunner, "ciRunner"); const artifactSummary = asRecord(record.artifactSummary, "artifactSummary"); const artifactRequirements = asRecord(record.artifactRequirements, "artifactRequirements"); const requiredLabels = asRecord(artifactRequirements.requiredLabels, "requiredLabels"); const devApplyPath = asRecord(record.devApplyPath, "devApplyPath"); const controlledPublish = asRecord(record.controlledPublish, "controlledPublish"); const controlPlane = asRecord(record.remoteControlPlaneCandidate, "remoteControlPlaneCandidate"); const blockedScopes = Array.isArray(record.blockedScopes) ? record.blockedScopes as string[] : []; assertCondition(record.ok === true, "ready backend-core preflight should pass", record); assertCondition(record.mode === "dry-run-preflight", "backend-core dry-run must be preflight mode", record); assertCondition(record.targetCommit === commit, "target commit should be surfaced", record); assertCondition(record.sourceRepo === "https://github.com/pikasTech/unidesk", "source repo should come from CI.json", record); assertCondition(record.registryTarget === "127.0.0.1:5000/unidesk/backend-core", "registry target should be D601 backend-core repository", record); assertCondition(record.wouldBuildOnD601 === true, "preflight should state real publish would build on D601", record); assertCondition(record.dryRunBuildStarted === false, "dry-run must not start a build", record); assertCondition(blockedScopes.length === 0, "ready preflight should have no blocked scopes", record); assertCondition(record.failureClassification === null, "ready preflight should not classify a failure", record); assertCondition(ciRunner.environment === "D601", "ciRunner should name D601", ciRunner); assertCondition(controlPlane.transport === "remote-frontend", "ready remote preflight should expose remote frontend transport", controlPlane); assertCondition(String(controlPlane.remoteCommandShape ?? "").includes("publish-backend-core"), "controlPlane command shape should be backend-core-specific", controlPlane); assertCondition(source.repoFetchUrl === "git@github.com:pikasTech/unidesk.git", "source auth should use GitHub SSH fetch URL", source); assertCondition(sourceAuth.egressProxy === "http://127.0.0.1:18789", "source auth should name provider egress proxy", sourceAuth); assertCondition(d601Ci.wouldBuildOnD601 === true, "D601 CI runner preflight should expose D601 build boundary", d601Ci); assertCondition(artifactSummary.imageRef === `127.0.0.1:5000/unidesk/backend-core:${commit}`, "artifact should be commit-pinned", artifactSummary); assertCondition(artifactSummary.digest === null, "dry-run must not fake digest", artifactSummary); assertCondition(requiredLabels["unidesk.ai/service-id"] === "backend-core", "service id label should be required", requiredLabels); assertCondition(requiredLabels["unidesk.ai/source-commit"] === commit, "source commit label should be required", requiredLabels); assertCondition(devApplyPath.pullOnly === true, "dev apply path should be pull-only", devApplyPath); assertCondition(String(devApplyPath.apply ?? "").includes("deploy apply --env dev --service backend-core"), "dev apply command should be surfaced", devApplyPath); assertCondition(!String(devApplyPath.apply ?? "").includes("--env prod"), "dev apply path must not point to prod", devApplyPath); assertCondition(controlledPublish.environment === "D601", "controlled publish should name D601", controlledPublish); assertCondition(String(record.boundary ?? "").includes("no Rust compile"), "boundary should explicitly forbid Rust compile during dry-run", record); assertCondition(String(record.recommendedAction ?? "").includes("ci publish-backend-core"), "recommended action should name real publish command", record); const remoteBlocked = await runCiPublishBackendCoreDryRunPreflight(config, [ "publish-backend-core", "--commit", commit, "--dry-run", ], remoteInfraBlockedTransport); const remoteBlockedRecord = asRecord(remoteBlocked, "remote infra-blocked backend-core preflight"); const remoteBlockedControlPlane = asRecord(remoteBlockedRecord.remoteControlPlaneCandidate, "remote infra-blocked control plane"); const remoteBlockedScopes = Array.isArray(remoteBlockedRecord.blockedScopes) ? remoteBlockedRecord.blockedScopes as string[] : []; assertCondition(remoteBlockedRecord.ok === false, "remote infra-blocked preflight should fail", remoteBlockedRecord); assertCondition(remoteBlockedRecord.failureClassification === "registry-unhealthy" || remoteBlockedRecord.failureClassification === "registry-not-installed", "remote infra-blocked should classify registry failure, not local docker", remoteBlockedRecord); assertCondition(remoteBlockedControlPlane.transport === "remote-frontend", "remote infra-blocked should keep remote frontend transport", remoteBlockedControlPlane); assertCondition(!remoteBlockedScopes.includes("local-docker-control-plane"), "remote infra-blocked must not blame runner local Docker", remoteBlockedRecord); assertCondition(remoteBlockedScopes.includes("registry-container") || remoteBlockedScopes.includes("registry-api") || remoteBlockedScopes.includes("provider-ssh-command"), "remote infra-blocked should expose registry/provider scope", remoteBlockedRecord); assertCondition(String(remoteBlockedRecord.recommendedAction ?? "").includes("registry"), "remote infra-blocked should recommend registry recovery", remoteBlockedRecord); process.stdout.write(`${JSON.stringify({ ok: true, checks: [ "runner backend-core dry-run auto-selects the remote frontend control plane", "local-docker-required fallback is explicit and exposes a backend-core remote command shape", "remote ready preflight reports D601/registry/artifact requirements without starting CI", "remote infra-blocked preflight does not misclassify runner-local Docker absence", "backend-core dry-run exposes target commit, source repo, D601 runner and registry target", "backend-core dry-run reports wouldBuildOnD601 without starting a build", "artifact labels and digest requirements are visible without faking a digest", "standard path is publish artifact, verify labels/digest, then dev pull-only apply", ], localBlockedScopes, readyBlockedScopes: record.blockedScopes, remoteBlockedScopes, }, null, 2)}\n`); } if (import.meta.main) { await main(); }