import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it } from "vitest";

const HELPER = resolve("scripts/docker/shared-image-artifact.sh");
const TARGET_SHA = "a".repeat(40);
const WORKFLOW_SHA = "b".repeat(40);
const PACKAGE_SHA256 = "c".repeat(64);
const ARTIFACT_DIGEST = "d".repeat(64);
const ARTIFACT_ID = "789";
const ARTIFACT_NAME = "docker-e2e-shared-images-release-aabbccddeeff-123456-2";
const ARTIFACT_RUN_ATTEMPT = "2";
const ARTIFACT_RUN_ID = "123456";
const IMAGE_REFS = ["openclaw-docker-e2e-bare:pkg-test", "openclaw-docker-e2e-functional:pkg-test"];

function imageId(ref: string): string {
  return `sha256:${createHash("sha256").update(ref).digest("hex")}`;
}

function writeExecutable(path: string, contents: string): void {
  writeFileSync(path, contents);
  chmodSync(path, 0o755);
}

function runHelper(params: {
  artifactDir: string;
  command: "load" | "pack";
  env: NodeJS.ProcessEnv;
  imageRefs?: string[];
}) {
  return spawnSync(
    "bash",
    [
      HELPER,
      params.command,
      params.artifactDir,
      "docker-e2e",
      TARGET_SHA,
      WORKFLOW_SHA,
      ...(params.imageRefs ?? IMAGE_REFS),
    ],
    {
      encoding: "utf8",
      env: params.env,
    },
  );
}

function verifyUploadedArtifact(
  fixture: ReturnType<typeof createFixture>,
  params: {
    artifactDigest?: string;
    artifactName?: string;
    env?: NodeJS.ProcessEnv;
    runAttempt?: string;
    runId?: string;
  } = {},
) {
  return spawnSync(
    "bash",
    [
      HELPER,
      "verify-upload",
      "Docker E2E image",
      ARTIFACT_ID,
      params.artifactName ?? ARTIFACT_NAME,
      params.artifactDigest ?? ARTIFACT_DIGEST,
      params.runId ?? ARTIFACT_RUN_ID,
      params.runAttempt ?? ARTIFACT_RUN_ATTEMPT,
    ],
    {
      encoding: "utf8",
      env: { ...fixture.env, ...params.env },
    },
  );
}

function createFixture() {
  const root = mkdtempSync(join(tmpdir(), "openclaw-shared-image-artifact-"));
  const bin = join(root, "bin");
  const artifactDir = join(root, "artifact");
  const dockerLog = join(root, "docker.log");
  const ghLog = join(root, "gh.log");
  const ghState = join(root, "gh-state");
  const sleepLog = join(root, "sleep.log");
  mkdirSync(bin);
  mkdirSync(ghState);
  writeFileSync(dockerLog, "");
  writeFileSync(ghLog, "");
  writeFileSync(sleepLog, "");

  writeExecutable(
    join(bin, "docker"),
    `#!/usr/bin/env bash
set -euo pipefail
printf '%s\\n' "$*" >> "$FAKE_DOCKER_LOG"

image_id() {
  printf '%s' "$1" | sha256sum | awk '{print "sha256:" $1}'
}

if [[ "$1" == "image" && "$2" == "inspect" ]]; then
  ref="\${5:?image ref required}"
  id="$(image_id "$ref")"
  if [[ "\${FAKE_DOCKER_FORCE_ID_MISMATCH:-0}" == "1" ]]; then
    id="sha256:$(printf 'mismatch:%s' "$ref" | sha256sum | awk '{print $1}')"
  fi
  printf '%s\\n' "$id"
  exit 0
fi

if [[ "$1" == "image" && "$2" == "save" ]]; then
  shift 2
  output=""
  refs=()
  while [[ "$#" -gt 0 ]]; do
    case "$1" in
      --output)
        output="\${2:?output path required}"
        shift 2
        ;;
      *)
        refs+=("$1")
        shift
        ;;
    esac
  done
  : > "$output"
  for ref in "\${refs[@]}"; do
    printf '%s\\t%s\\n' "$ref" "$(image_id "$ref")" >> "$output"
  done
  exit 0
fi

if [[ "$1" == "image" && "$2" == "load" ]]; then
  cat >/dev/null
  exit 0
fi

echo "unexpected docker invocation: $*" >&2
exit 2
`,
  );

  writeExecutable(
    join(bin, "zstd"),
    `#!/usr/bin/env bash
set -euo pipefail
if [[ "$1" == "-t" ]]; then
  test -s "$2"
  exit 0
fi
if [[ "$1" == "-d" && "$2" == "--stdout" ]]; then
  cat "$3"
  exit 0
fi

source_path=""
output_path=""
while [[ "$#" -gt 0 ]]; do
  case "$1" in
    -f)
      source_path="\${2:?source path required}"
      shift 2
      ;;
    -o)
      output_path="\${2:?output path required}"
      shift 2
      ;;
    *)
      shift
      ;;
  esac
done
cp "$source_path" "$output_path"
`,
  );

  writeExecutable(
    join(bin, "gh"),
    `#!/usr/bin/env bash
set -euo pipefail
printf '%s\\n' "$*" >> "$FAKE_GH_LOG"
[[ "$1" == "api" ]] || {
  echo "unexpected gh invocation: $*" >&2
  exit 2
}
shift
method=""
path=""
while [[ "$#" -gt 0 ]]; do
  case "$1" in
    --method)
      method="\${2:?method required}"
      shift 2
      ;;
    *)
      [[ -z "$path" ]] || {
        echo "unexpected gh api argument: $1" >&2
        exit 2
      }
      path="$1"
      shift
      ;;
  esac
done
[[ "$method" == "GET" ]] || {
  echo "unexpected gh api method: $method" >&2
  exit 2
}

case "$path" in
  "repos/\${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}")
    count_file="$FAKE_GH_STATE/artifact"
    count="$(( $(cat "$count_file" 2>/dev/null || printf 0) + 1 ))"
    printf '%s\\n' "$count" > "$count_file"
    if [[ "$count" -le "\${FAKE_GH_ARTIFACT_FAILURES:-0}" ]]; then
      printf 'partial artifact response\\n'
      printf '%s\\n' "\${FAKE_GH_ARTIFACT_ERROR:-gh: request failed}" >&2
      exit 1
    fi
    if [[ -n "\${FAKE_ARTIFACT_JSON:-}" ]]; then
      printf '%s\\n' "$FAKE_ARTIFACT_JSON"
      exit 0
    fi
    printf '{"id":%s,"name":"%s","expired":%s,"digest":"sha256:%s","workflow_run":{"id":%s}}\\n' \
      "$FAKE_ARTIFACT_ID" "$FAKE_ARTIFACT_NAME" "$FAKE_ARTIFACT_EXPIRED" \
      "$FAKE_ARTIFACT_DIGEST" "$FAKE_ARTIFACT_RUN_ID"
    ;;
  "repos/\${GITHUB_REPOSITORY}/actions/runs/${ARTIFACT_RUN_ID}/attempts/${ARTIFACT_RUN_ATTEMPT}")
    count_file="$FAKE_GH_STATE/attempt"
    count="$(( $(cat "$count_file" 2>/dev/null || printf 0) + 1 ))"
    printf '%s\\n' "$count" > "$count_file"
    if [[ "$count" -le "\${FAKE_GH_ATTEMPT_FAILURES:-0}" ]]; then
      printf 'partial attempt response\\n'
      printf '%s\\n' "\${FAKE_GH_ATTEMPT_ERROR:-gh: request failed}" >&2
      exit 1
    fi
    printf '{"id":%s,"run_attempt":%s}\\n' \
      "$FAKE_ATTEMPT_RUN_ID" "$FAKE_ARTIFACT_RUN_ATTEMPT"
    ;;
  *)
    echo "unexpected gh api path: $path" >&2
    exit 2
    ;;
esac
`,
  );

  writeExecutable(
    join(bin, "sleep"),
    `#!/usr/bin/env bash
set -euo pipefail
printf '%s\\n' "$*" >> "$FAKE_SLEEP_LOG"
`,
  );

  const env = {
    ...process.env,
    FAKE_ARTIFACT_DIGEST: ARTIFACT_DIGEST,
    FAKE_ARTIFACT_EXPIRED: "false",
    FAKE_ARTIFACT_ID: ARTIFACT_ID,
    FAKE_ARTIFACT_NAME: ARTIFACT_NAME,
    FAKE_ARTIFACT_RUN_ATTEMPT: ARTIFACT_RUN_ATTEMPT,
    FAKE_ARTIFACT_RUN_ID: ARTIFACT_RUN_ID,
    FAKE_ATTEMPT_RUN_ID: ARTIFACT_RUN_ID,
    FAKE_DOCKER_LOG: dockerLog,
    FAKE_GH_LOG: ghLog,
    FAKE_GH_STATE: ghState,
    FAKE_SLEEP_LOG: sleepLog,
    GH_TOKEN: "test-token",
    GITHUB_REPOSITORY: "openclaw/openclaw",
    GITHUB_RUN_ATTEMPT: "2",
    GITHUB_RUN_ID: "123456",
    PATH: `${bin}:${process.env.PATH ?? ""}`,
    RUNNER_TEMP: root,
    OPENCLAW_SHARED_IMAGE_PACKAGE_SHA256: PACKAGE_SHA256,
  };
  return { artifactDir, dockerLog, env, ghLog, root, sleepLog };
}

function expectedArchiveEnv(fixture: ReturnType<typeof createFixture>): NodeJS.ProcessEnv {
  const manifest = JSON.parse(
    readFileSync(join(fixture.artifactDir, "shared-image-artifact.json"), "utf8"),
  );
  return {
    ...fixture.env,
    OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256: manifest.archive.sha256,
    OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: String(manifest.runAttempt),
    OPENCLAW_SHARED_IMAGE_RUN_ID: String(manifest.runId),
  };
}

describe("shared Docker image artifacts", () => {
  it("binds uploaded artifacts to the exact service tuple and producer attempt", () => {
    const fixture = createFixture();
    try {
      const verified = verifyUploadedArtifact(fixture);
      expect(verified.status, `${verified.stdout}\n${verified.stderr}`).toBe(0);
      expect(readFileSync(fixture.ghLog, "utf8")).toContain(
        `api --method GET repos/openclaw/openclaw/actions/artifacts/${ARTIFACT_ID}`,
      );
      expect(readFileSync(fixture.ghLog, "utf8")).toContain(
        `api --method GET repos/openclaw/openclaw/actions/runs/${ARTIFACT_RUN_ID}/attempts/${ARTIFACT_RUN_ATTEMPT}`,
      );

      writeFileSync(fixture.ghLog, "");
      const digestMismatch = verifyUploadedArtifact(fixture, {
        artifactDigest: "e".repeat(64),
      });
      expect(digestMismatch.status).not.toBe(0);
      expect(digestMismatch.stderr).toContain(
        "artifact identity does not match the immutable producer tuple",
      );
      expect(readFileSync(fixture.ghLog, "utf8").trim().split("\n")).toHaveLength(1);
      expect(digestMismatch.stderr).not.toContain("retrying");

      writeFileSync(fixture.ghLog, "");
      const invalidJson = verifyUploadedArtifact(fixture, {
        env: { FAKE_ARTIFACT_JSON: "not-json" },
      });
      expect(invalidJson.status).not.toBe(0);
      expect(invalidJson.stderr).toContain(
        "artifact identity does not match the immutable producer tuple",
      );
      expect(readFileSync(fixture.ghLog, "utf8").trim().split("\n")).toHaveLength(1);
      expect(invalidJson.stderr).not.toContain("retrying");

      writeFileSync(fixture.ghLog, "");
      const attemptMismatch = verifyUploadedArtifact(fixture, {
        env: { FAKE_ARTIFACT_RUN_ATTEMPT: "3" },
      });
      expect(attemptMismatch.status).not.toBe(0);
      expect(attemptMismatch.stderr).toContain(
        "producer run attempt does not match the immutable tuple",
      );
      expect(readFileSync(fixture.ghLog, "utf8").trim().split("\n")).toHaveLength(2);
      expect(attemptMismatch.stderr).not.toContain("retrying");
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("retries an exact i/o timeout without leaking partial output", () => {
    const fixture = createFixture();
    try {
      const verified = verifyUploadedArtifact(fixture, {
        env: {
          FAKE_GH_ARTIFACT_ERROR: "Get https://api.github.com: dial tcp: i/o timeout",
          FAKE_GH_ARTIFACT_FAILURES: "1",
        },
      });
      expect(verified.status, `${verified.stdout}\n${verified.stderr}`).toBe(0);
      expect(verified.stdout).not.toContain("partial artifact response");
      expect(verified.stderr).toContain(
        "artifact metadata GitHub API GET failed transiently on attempt 1/3; retrying in 2s",
      );
      expect(readFileSync(fixture.sleepLog, "utf8")).toBe("2\n");
      const calls = readFileSync(fixture.ghLog, "utf8");
      expect(calls.match(/actions\/artifacts/g)).toHaveLength(2);
      expect(calls.match(/actions\/runs/g)).toHaveLength(1);
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("retries a fresh artifact metadata 404 and then succeeds", () => {
    const fixture = createFixture();
    try {
      const verified = verifyUploadedArtifact(fixture, {
        env: {
          FAKE_GH_ARTIFACT_ERROR: "gh: Not Found (HTTP 404)",
          FAKE_GH_ARTIFACT_FAILURES: "1",
        },
      });
      expect(verified.status, `${verified.stdout}\n${verified.stderr}`).toBe(0);
      expect(verified.stderr).toContain(
        "artifact metadata GitHub API GET failed transiently on attempt 1/3; retrying in 2s",
      );
      expect(readFileSync(fixture.sleepLog, "utf8")).toBe("2\n");
      const calls = readFileSync(fixture.ghLog, "utf8");
      expect(calls.match(/actions\/artifacts/g)).toHaveLength(2);
      expect(calls.match(/actions\/runs/g)).toHaveLength(1);
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("fails after three fresh artifact metadata 404 responses", () => {
    const fixture = createFixture();
    try {
      const failed = verifyUploadedArtifact(fixture, {
        env: {
          FAKE_GH_ARTIFACT_ERROR: "gh: Not Found (HTTP 404)",
          FAKE_GH_ARTIFACT_FAILURES: "3",
        },
      });
      expect(failed.status).not.toBe(0);
      expect(failed.stderr).toContain("GitHub API GET failed after 3 attempt(s)");
      expect(readFileSync(fixture.sleepLog, "utf8")).toBe("2\n4\n");
      const calls = readFileSync(fixture.ghLog, "utf8");
      expect(calls.match(/actions\/artifacts/g)).toHaveLength(3);
      expect(calls).not.toContain("actions/runs");
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("fails immediately when producer run-attempt metadata returns 404", () => {
    const fixture = createFixture();
    try {
      const failed = verifyUploadedArtifact(fixture, {
        env: {
          FAKE_GH_ATTEMPT_ERROR: "gh: Not Found (HTTP 404)",
          FAKE_GH_ATTEMPT_FAILURES: "3",
        },
      });
      expect(failed.status).not.toBe(0);
      expect(failed.stderr).toContain("GitHub API GET failed after 1 attempt(s)");
      expect(failed.stderr).not.toContain("retrying");
      expect(readFileSync(fixture.sleepLog, "utf8")).toBe("");
      const calls = readFileSync(fixture.ghLog, "utf8");
      expect(calls.match(/actions\/artifacts/g)).toHaveLength(1);
      expect(calls.match(/actions\/runs/g)).toHaveLength(1);
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("recovers on the third attempt and fails cleanly when all attempts are exhausted", () => {
    const recoveredFixture = createFixture();
    try {
      const recovered = verifyUploadedArtifact(recoveredFixture, {
        env: {
          FAKE_GH_ARTIFACT_ERROR: "gh: upstream unavailable (HTTP 503)",
          FAKE_GH_ARTIFACT_FAILURES: "2",
        },
      });
      expect(recovered.status, `${recovered.stdout}\n${recovered.stderr}`).toBe(0);
      expect(readFileSync(recoveredFixture.sleepLog, "utf8")).toBe("2\n4\n");
      expect(
        readFileSync(recoveredFixture.ghLog, "utf8").match(/actions\/artifacts/g),
      ).toHaveLength(3);
    } finally {
      rmSync(recoveredFixture.root, { force: true, recursive: true });
    }

    const exhaustedFixture = createFixture();
    try {
      const exhausted = verifyUploadedArtifact(exhaustedFixture, {
        env: {
          FAKE_GH_ARTIFACT_ERROR: "Get https://api.github.com: dial tcp: i/o timeout",
          FAKE_GH_ARTIFACT_FAILURES: "3",
        },
      });
      expect(exhausted.status).not.toBe(0);
      expect(exhausted.stdout).not.toContain("partial artifact response");
      expect(exhausted.stderr).toContain("i/o timeout");
      expect(exhausted.stderr).toContain("GitHub API GET failed after 3 attempt(s)");
      expect(readFileSync(exhaustedFixture.sleepLog, "utf8")).toBe("2\n4\n");
      const calls = readFileSync(exhaustedFixture.ghLog, "utf8");
      expect(calls.match(/actions\/artifacts/g)).toHaveLength(3);
      expect(calls).not.toContain("actions/runs");
    } finally {
      rmSync(exhaustedFixture.root, { force: true, recursive: true });
    }
  });

  it("retries explicit rate limiting but fails fast on permanent HTTP and credential errors", () => {
    const rateLimitFixture = createFixture();
    try {
      const rateLimited = verifyUploadedArtifact(rateLimitFixture, {
        env: {
          FAKE_GH_ARTIFACT_ERROR: "gh: API rate limit exceeded (HTTP 429)",
          FAKE_GH_ARTIFACT_FAILURES: "1",
        },
      });
      expect(rateLimited.status, `${rateLimited.stdout}\n${rateLimited.stderr}`).toBe(0);
      expect(readFileSync(rateLimitFixture.sleepLog, "utf8")).toBe("2\n");
    } finally {
      rmSync(rateLimitFixture.root, { force: true, recursive: true });
    }

    for (const error of [
      "gh: Unauthorized (HTTP 401)",
      "gh: Forbidden (HTTP 403)",
      "gh: Unprocessable Entity (HTTP 422)",
      "gh: Bad credentials (HTTP 500)",
      "gh: authentication failed (HTTP 500)",
    ]) {
      const fixture = createFixture();
      try {
        const failed = verifyUploadedArtifact(fixture, {
          env: {
            FAKE_GH_ARTIFACT_ERROR: error,
            FAKE_GH_ARTIFACT_FAILURES: "3",
          },
        });
        expect(failed.status, error).not.toBe(0);
        expect(failed.stderr).toContain(error);
        expect(failed.stderr).toContain("GitHub API GET failed after 1 attempt(s)");
        expect(failed.stderr).not.toContain("retrying");
        expect(readFileSync(fixture.ghLog, "utf8").trim().split("\n")).toHaveLength(1);
        expect(readFileSync(fixture.sleepLog, "utf8")).toBe("");
      } finally {
        rmSync(fixture.root, { force: true, recursive: true });
      }
    }
  });

  it("packs provenance-bound images and verifies them before loading", () => {
    const fixture = createFixture();
    try {
      const packed = runHelper({
        artifactDir: fixture.artifactDir,
        command: "pack",
        env: fixture.env,
      });
      expect(packed.status, `${packed.stdout}\n${packed.stderr}`).toBe(0);

      const archive = readFileSync(join(fixture.artifactDir, "shared-images.tar.zst"));
      const manifest = JSON.parse(
        readFileSync(join(fixture.artifactDir, "shared-image-artifact.json"), "utf8"),
      );
      expect(manifest).toEqual({
        archive: {
          filename: "shared-images.tar.zst",
          format: "docker-tar+zstd",
          sha256: createHash("sha256").update(archive).digest("hex"),
          sizeBytes: archive.length,
        },
        conclusion: "success",
        images: IMAGE_REFS.map((ref) => ({ id: imageId(ref), ref })),
        kind: "docker-e2e",
        packageSha256: PACKAGE_SHA256,
        packageSourceSha: TARGET_SHA,
        runAttempt: 2,
        runId: 123456,
        schema: "openclaw.shared-docker-image-artifact/v1",
        schemaVersion: 1,
        targetSha: TARGET_SHA,
        workflowSha: WORKFLOW_SHA,
      });

      writeFileSync(fixture.dockerLog, "");
      const loaded = runHelper({
        artifactDir: fixture.artifactDir,
        command: "load",
        env: { ...expectedArchiveEnv(fixture), GITHUB_RUN_ATTEMPT: "3" },
      });
      expect(loaded.status, `${loaded.stdout}\n${loaded.stderr}`).toBe(0);
      const calls = readFileSync(fixture.dockerLog, "utf8");
      expect(calls).toContain("image load");
      for (const ref of IMAGE_REFS) {
        expect(calls).toContain(`image inspect --format {{.Id}} ${ref}`);
      }
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("fails before loading when provenance or archive bytes differ", () => {
    const fixture = createFixture();
    try {
      const packed = runHelper({
        artifactDir: fixture.artifactDir,
        command: "pack",
        env: fixture.env,
      });
      expect(packed.status, packed.stderr).toBe(0);

      for (const variant of [
        {
          env: {
            ...expectedArchiveEnv(fixture),
            OPENCLAW_SHARED_IMAGE_RUN_ID: "654321",
          },
          imageRefs: IMAGE_REFS,
          expected: "run ID",
        },
        {
          env: expectedArchiveEnv(fixture),
          imageRefs: [
            expectDefined(IMAGE_REFS[0], "first shared Docker image reference"),
            "openclaw-docker-e2e-functional:wrong",
          ],
          expected: "image ref 1",
        },
        {
          env: {
            ...expectedArchiveEnv(fixture),
            OPENCLAW_SHARED_IMAGE_PACKAGE_SHA256: "d".repeat(64),
          },
          imageRefs: IMAGE_REFS,
          expected: "package SHA-256",
        },
      ]) {
        writeFileSync(fixture.dockerLog, "");
        const result = runHelper({
          artifactDir: fixture.artifactDir,
          command: "load",
          env: variant.env,
          imageRefs: variant.imageRefs,
        });
        expect(result.status).not.toBe(0);
        expect(result.stderr).toContain(variant.expected);
        expect(readFileSync(fixture.dockerLog, "utf8")).not.toContain("image load");
      }

      const archivePath = join(fixture.artifactDir, "shared-images.tar.zst");
      writeFileSync(archivePath, `${readFileSync(archivePath, "utf8")}tampered`);
      writeFileSync(fixture.dockerLog, "");
      const tampered = runHelper({
        artifactDir: fixture.artifactDir,
        command: "load",
        env: expectedArchiveEnv(fixture),
      });
      expect(tampered.status).not.toBe(0);
      expect(tampered.stderr).toContain("archive SHA-256 mismatch");
      expect(readFileSync(fixture.dockerLog, "utf8")).not.toContain("image load");
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("rejects unsafe pack destinations and loaded image ID drift", () => {
    const fixture = createFixture();
    try {
      const unsafe = runHelper({
        artifactDir: fixture.root,
        command: "pack",
        env: fixture.env,
      });
      expect(unsafe.status).not.toBe(0);
      expect(unsafe.stderr).toContain("artifact directory must be a child of RUNNER_TEMP");

      const packed = runHelper({
        artifactDir: fixture.artifactDir,
        command: "pack",
        env: fixture.env,
      });
      expect(packed.status, packed.stderr).toBe(0);

      writeFileSync(fixture.dockerLog, "");
      const mismatch = runHelper({
        artifactDir: fixture.artifactDir,
        command: "load",
        env: { ...expectedArchiveEnv(fixture), FAKE_DOCKER_FORCE_ID_MISMATCH: "1" },
      });
      expect(mismatch.status).not.toBe(0);
      expect(mismatch.stderr).toContain("loaded ID mismatch");
      expect(readFileSync(fixture.dockerLog, "utf8")).toContain("image load");
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });

  it("requires an external expected archive digest before loading", () => {
    const fixture = createFixture();
    try {
      const packed = runHelper({
        artifactDir: fixture.artifactDir,
        command: "pack",
        env: fixture.env,
      });
      expect(packed.status, packed.stderr).toBe(0);

      const missingRunEnv = expectedArchiveEnv(fixture);
      delete missingRunEnv.OPENCLAW_SHARED_IMAGE_RUN_ID;
      delete missingRunEnv.OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT;
      const missingRun = runHelper({
        artifactDir: fixture.artifactDir,
        command: "load",
        env: missingRunEnv,
      });
      expect(missingRun.status).not.toBe(0);
      expect(missingRun.stderr).toContain("OPENCLAW_SHARED_IMAGE_RUN_ID");
      expect(readFileSync(fixture.dockerLog, "utf8")).not.toContain("image load");

      const missing = runHelper({
        artifactDir: fixture.artifactDir,
        command: "load",
        env: {
          ...fixture.env,
          OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: "2",
          OPENCLAW_SHARED_IMAGE_RUN_ID: "123456",
        },
      });
      expect(missing.status).not.toBe(0);
      expect(missing.stderr).toContain("expected shared image archive SHA-256");
      expect(readFileSync(fixture.dockerLog, "utf8")).not.toContain("image load");

      const mismatched = runHelper({
        artifactDir: fixture.artifactDir,
        command: "load",
        env: {
          ...expectedArchiveEnv(fixture),
          OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256: "d".repeat(64),
        },
      });
      expect(mismatched.status).not.toBe(0);
      expect(mismatched.stderr).toContain("expected archive sha256");
      expect(readFileSync(fixture.dockerLog, "utf8")).not.toContain("image load");
    } finally {
      rmSync(fixture.root, { force: true, recursive: true });
    }
  });
});
