#!/usr/bin/env node
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import {
  mkdir,
  mkdtemp,
  readFile,
  rm,
  writeFile
} from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

import { activeReportLifecycle } from "../internal/dev-report-lifecycle.mjs";
import { ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";
import { createCiPlan } from "./src/ci-plan-lib.mjs";
import {
  BUILDABLE_IMPLEMENTATION_STATES,
  resolveDevArtifactServices,
  serviceInventoryFromServices
} from "./src/dev-artifact-services.mjs";
import { probeRegistryCapabilities } from "./src/registry-capabilities.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs";
import { readStructuredFile } from "./src/structured-config.mjs";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const cliEntrypoint = process.env.HWLAB_ARTIFACT_PUBLISH_ENTRYPOINT || "scripts/artifact-publish.mjs";
const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "node";
const defaultRegistryPrefix =
  process.env.HWLAB_DEV_REGISTRY_PREFIX || process.env.HWLAB_DEV_REGISTRY || "127.0.0.1:5000/hwlab";
const defaultBaseImage = process.env.HWLAB_DEV_BASE_IMAGE || null;
const defaultReportPath = tempReportPath("dev-artifacts.json");
const buildBackend = "buildkit";
const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout";
const defaultBuildkitCommand = process.env.HWLAB_BUILDKIT_BUILDCTL || "buildctl-daemonless.sh";
const defaultBuildkitAddr = process.env.HWLAB_BUILDKIT_ADDR || null;
const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json";
const defaultDeployPath = process.env.HWLAB_DEPLOY_MANIFEST_PATH || "deploy/deploy.yaml";

const mutableTags = new Set(["latest", "dev", "main", "master", "prod", "production"]);
const shaDigestPattern = /^sha256:[a-f0-9]{64}$/u;
const fatalBlockerTypes = new Set([
  "contract_blocker",
  "environment_blocker",
  "network_blocker",
  "safety_blocker"
]);
const buildableImplementationStates = new Set(BUILDABLE_IMPLEMENTATION_STATES);
const sourceStates = new Set([
  "source-present",
  "intentionally-disabled"
]);
const runtimeArtifactLanePattern = /^v\d{2,}$/u;

const ciArtifactIdentityEnvNames = [
  "HWLAB_CI_ARTIFACT_RUN_ID",
  "GITHUB_RUN_ID",
  "CI_PIPELINE_ID",
  "BUILDKITE_BUILD_ID"
];

const servicePorts = new Map([
  ["hwlab-cloud-api", 6667],
  ["hwlab-workbench-runtime", 6671],
  ["hwlab-user-billing", 6670],
  ["hwlab-cloud-web", 8080],
  ["hwlab-gateway", 7001],
  ["hwlab-edge-proxy", 6667],
  ["hwlab-agent-skills", 7430]
]);

function parseArgs(argv) {
  const args = {
    mode: "preflight",
    lane: defaultLane,
    catalogPath: defaultCatalogPath,
    deployPath: defaultDeployPath,
    imageTagMode: process.env.HWLAB_ARTIFACT_IMAGE_TAG_MODE || "short",
    registryPrefix: defaultRegistryPrefix,
    baseImage: defaultBaseImage,
    reportPath: defaultReportPath,
    services: [...SERVICE_IDS],
    servicesExplicit: false,
    affectedOnly: true,
    buildBackend,
    buildkitCommand: defaultBuildkitCommand,
    buildkitAddr: defaultBuildkitAddr,
    externalServiceReportDir: null,
    externalServiceResultsEnv: false,
    tektonResultsDir: null,
    emitReport: true,
    quietBuild: false,
    concurrency: parseConcurrency(process.env.HWLAB_DEV_ARTIFACT_CONCURRENCY, 4)
  };

  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === "--preflight") args.mode = "preflight";
    else if (arg === "--build") args.mode = "build";
    else if (arg === "--publish") args.mode = "publish";
    else if (arg === "--lane") args.lane = readOption(argv, ++index, arg);
    else if (arg === "--catalog-path") args.catalogPath = readOption(argv, ++index, arg);
    else if (arg === "--deploy-config") args.deployPath = readOption(argv, ++index, arg);
    else if (arg === "--image-tag-mode") args.imageTagMode = readOption(argv, ++index, arg);
    else if (arg === "--registry-prefix") args.registryPrefix = readOption(argv, ++index, arg);
    else if (arg === "--base-image") args.baseImage = readOption(argv, ++index, arg);
    else if (arg === "--report") args.reportPath = readOption(argv, ++index, arg);
    else if (arg === "--services") {
      args.services = readOption(argv, ++index, arg).split(",").map((item) => item.trim()).filter(Boolean);
      args.servicesExplicit = true;
    }
    else if (arg === "--affected-only") args.affectedOnly = true;
    else if (arg === "--buildkit-command") args.buildkitCommand = readOption(argv, ++index, arg);
    else if (arg === "--buildkit-addr") args.buildkitAddr = readOption(argv, ++index, arg);
    else if (arg === "--external-service-report-dir") args.externalServiceReportDir = readOption(argv, ++index, arg);
    else if (arg === "--external-service-results-env") args.externalServiceResultsEnv = true;
    else if (arg === "--tekton-results-dir") args.tektonResultsDir = readOption(argv, ++index, arg);
    else if (arg === "--no-report") args.emitReport = false;
    else if (arg === "--quiet-build") args.quietBuild = true;
    else if (arg === "--concurrency") args.concurrency = parseConcurrency(readOption(argv, ++index, arg), 4);
    else if (arg === "--help" || arg === "-h") args.help = true;
    else throw new Error(`unknown argument ${arg}`);
  }

  assert.ok(args.lane === "node" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`);
  assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`);

  return args;
}

function readOption(argv, index, name) {
  const value = argv[index];
  if (!value || value.startsWith("--")) {
    throw new Error(`${name} requires a value`);
  }
  return value;
}

function parseConcurrency(value, fallback) {
  const parsed = Number.parseInt(String(value ?? ""), 10);
  if (!Number.isInteger(parsed)) return fallback;
  return Math.min(Math.max(parsed, 1), 8);
}

function artifactIdentityValue(env, name) {
  const value = env?.[name];
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}

function artifactIdentityName(env = process.env) {
  return ciArtifactIdentityEnvNames.find((name) => artifactIdentityValue(env, name));
}

function artifactProducer(env = process.env) {
  const idName = artifactIdentityName(env);
  if (!idName) {
    return {
      kind: "missing",
      runId: null,
      owner: null
    };
  }
  return {
    kind: "node-ci-artifact",
    runId: artifactIdentityValue(env, idName),
    owner: artifactIdentityValue(env, "HWLAB_CI_ARTIFACT_RUN_OWNER") ?? idName
  };
}

function artifactPublishIdentityGuardFailure({ script, mode }) {
  return {
    ok: false,
    status: "blocked",
    error: "artifact-publish-identity-required",
    code: "artifact-publish-identity-required",
    script,
    mode,
    requiredEnv: "HWLAB_CI_ARTIFACT_RUN_ID or standard CI run identity",
    acceptedEnv: ciArtifactIdentityEnvNames,
    entrypoint: `HWLAB_CI_ARTIFACT_RUN_ID=<run-id> HWLAB_CI_ARTIFACT_RUN_OWNER=<owner> node scripts/artifact-publish.mjs --publish --report ${tempReportPath("dev-artifacts.json")}`,
    summary: `${script} ${mode} is a node artifact publish side-effect step and must run under CI artifact identity. Legacy D601 CD transaction identity is not accepted.`,
    devOnly: true,
    prodTouched: false,
    cdLockRequired: false,
    rolloutAttempted: false,
    mutationAttempted: false
  };
}

function requireArtifactPublishIdentity({ env = process.env, script, mode }) {
  return artifactIdentityName(env) ? null : artifactPublishIdentityGuardFailure({ script, mode });
}

function printHelp() {
  console.log([
    `usage: node ${cliEntrypoint} [--preflight|--build|--publish]`,
    "",
    "Commit-pinned artifact build/publish helper for controlled CI execution.",
    "",
    "options:",
    "  --lane LANE              node or runtime lane vNN; default: node",
    `  --catalog-path PATH      default: ${defaultCatalogPath}`,
    `  --deploy-config PATH     default: ${defaultDeployPath}`,
    "  --image-tag-mode MODE    short or full; v02 uses full source commit IDs",
    "  --registry-prefix PREFIX  default: 127.0.0.1:5000/hwlab",
    "  --base-image IMAGE        override HWLAB_DEV_BASE_IMAGE for this run; must already be local",
    "  --services LIST           comma-separated service IDs; runtime lane default comes from deploy.lanes.<lane>.envReuseServices",
    "  --affected-only           default and only supported scope: build/publish affected services and reuse unchanged catalog digests",
    "  --buildkit-command PATH   buildctl-daemonless.sh/buildctl command for buildkit backend",
    "  --buildkit-addr ADDR      optional buildctl --addr endpoint for a sidecar BuildKit daemon",
    "  --external-service-report-dir DIR  collect affected service artifacts from per-service publish reports",
    "  --external-service-results-env     collect service artifacts from HWLAB_SERVICE_RESULT_* env vars",
    "  --tekton-results-dir DIR  write single-service build result files for Tekton results",
    `  --report PATH             default: ${defaultReportPath}`,
    "  --no-report               print JSON without updating the temporary artifact JSON",
    "  --quiet-build             keep BuildKit progress quiet; default keeps build output visible",
    "  --concurrency N           parallel service build/push workers; default: 4, max: 8"
  ].join("\n"));
}

async function readConfig(relativePath) {
  return readStructuredFile(repoRoot, relativePath);
}

async function readConfigIfPresent(relativePath, fallback = null) {
  try {
    return await readConfig(relativePath);
  } catch (error) {
    if (error?.code === "ENOENT") return fallback;
    throw error;
  }
}

function emit(event, payload = {}) {
  process.stderr.write(`${JSON.stringify({ event, at: new Date().toISOString(), ...payload })}\n`);
}

function ciTimingEnabled(env = process.env) {
  return env.HWLAB_NODE_CICD_TIMING === "1" || env.HWLAB_CI_TIMING === "1";
}

function emitCiTiming(payload) {
  emit("node-cicd-timing", {
    schemaVersion: "v1",
    source: "scripts/artifact-publish.mjs",
    pipelineRun: process.env.HWLAB_TEKTON_PIPELINERUN ?? null,
    taskRun: process.env.HWLAB_TEKTON_TASKRUN ?? null,
    task: process.env.HWLAB_TEKTON_TASK ?? null,
    revision: process.env.HWLAB_SOURCE_REVISION ?? null,
    ...payload
  });
}

function parseDurationMs(value) {
  const text = String(value ?? "").trim();
  if (!text) return null;
  let total = 0;
  let matched = false;
  for (const match of text.matchAll(/(\d+(?:\.\d+)?)(ms|s|m|h)/gu)) {
    matched = true;
    const amount = Number(match[1]);
    const unit = match[2];
    if (unit === "ms") total += amount;
    else if (unit === "s") total += amount * 1000;
    else if (unit === "m") total += amount * 60 * 1000;
    else if (unit === "h") total += amount * 60 * 60 * 1000;
  }
  return matched ? Math.round(total) : null;
}

function parseBuildkitPlainTimings(text) {
  const idToStage = new Map();
  const timings = new Map();
  const stageMatchers = [
    { stage: "buildkit-context-transfer", pattern: /load build context|transferring context/iu },
    { stage: "cache-import", pattern: /importing cache manifest/iu },
    { stage: "cache-export", pattern: /exporting cache/iu }
  ];
  for (const rawLine of String(text ?? "").split("\n")) {
    const line = rawLine.replace(/\u001b\[[0-9;]*m/gu, "").trim();
    const match = line.match(/^#(\d+)\s+(.*)$/u);
    if (!match) continue;
    const [, id, body] = match;
    const identified = stageMatchers.find((item) => item.pattern.test(body));
    if (identified) idToStage.set(id, identified.stage);
    const stage = idToStage.get(id);
    if (!stage || timings.has(stage)) continue;
    const durationMatch = body.match(/(?:DONE|done)\s+([0-9.]+(?:ms|s|m|h)(?:[0-9.]+(?:ms|s|m|h))*)/iu) ??
      body.match(/([0-9.]+(?:ms|s|m|h)(?:[0-9.]+(?:ms|s|m|h))*)\s+done/iu);
    const durationMs = parseDurationMs(durationMatch?.[1]);
    if (durationMs !== null) timings.set(stage, { stage, status: "succeeded", durationMs, source: "buildkit-plain-progress" });
  }
  return timings;
}

function parseDockerfileStepTimings(text) {
  const timings = [];
  for (const rawLine of String(text ?? "").split("\n")) {
    const line = rawLine.replace(/\u001b\[[0-9;]*m/gu, "");
    const start = line.indexOf("{");
    const end = line.lastIndexOf("}");
    if (start < 0 || end <= start) continue;
    let parsed;
    try {
      parsed = JSON.parse(line.slice(start, end + 1));
    } catch {
      continue;
    }
    if (parsed?.event !== "node-cicd-build-step-timing") continue;
    timings.push({
      stage: parsed.stage,
      status: parsed.status ?? "unknown",
      durationMs: Number.isFinite(parsed.durationMs) ? parsed.durationMs : null,
      source: parsed.source ?? "dockerfile-run"
    });
  }
  return timings;
}

function emitBuildkitTimingEvents({ service, result, timingEnabled }) {
  if (!timingEnabled) return;
  const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
  const buildkitTimings = parseBuildkitPlainTimings(output);
  for (const stage of ["buildkit-context-transfer", "cache-import", "cache-export"]) {
    const timing = buildkitTimings.get(stage) ?? { stage, status: "not-observed", durationMs: null, source: "buildkit-plain-progress" };
    emitCiTiming({
      ...timing,
      serviceId: service.serviceId,
      buildkitDurationMs: result.durationMs
    });
  }
  for (const timing of parseDockerfileStepTimings(output)) {
    emitCiTiming({
      ...timing,
      serviceId: service.serviceId,
      buildkitDurationMs: result.durationMs
    });
  }
}

function blocker({ type, scope, summary, next }) {
  return {
    type,
    scope,
    status: "open",
    summary,
    next
  };
}

function buildkitBaseImagePreflight(baseImage) {
  const requestedImage = baseImage || defaultBaseImage || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim";
  return {
    preflight: "hwlab-dev-base-image",
    environment: ENVIRONMENT_DEV,
    status: "pass",
    publishUsable: true,
    artifactPublish: {
      issue: "#35",
      usable: true,
      reason: "BuildKit pulls the approved builder base image from the registry during k8s-native publish."
    },
    imageSource: "buildkit-registry-pull",
    requestedImage,
    localTag: requestedImage,
    imageId: null,
    repoDigests: [],
    candidates: [],
    rejectedCandidates: [],
    blockers: [],
    nextSteps: [],
    recommendation: {
      source: "node k8s registry image used by rootless BuildKit.",
      envVar: "HWLAB_DEV_BASE_IMAGE",
      recommendedEnvValue: requestedImage
    },
    provision: {
      required: false,
      exactBlocker: null,
      recommendedEnv: {
        name: "HWLAB_DEV_BASE_IMAGE",
        value: requestedImage,
        assignment: `HWLAB_DEV_BASE_IMAGE=${requestedImage}`
      },
      commands: {}
    },
    blockedReport: null,
    containerEngine: "buildkit-rootless"
  };
}

function summarizeBaseImagePreflight(result) {
  return {
    status: result.status,
    imageSource: result.imageSource,
    requestedImage: result.requestedImage,
    localTag: result.localTag,
    imageId: result.imageId,
    repoDigests: result.repoDigests,
    candidates: result.candidates,
    rejectedCandidates: result.rejectedCandidates,
    publishUsable: result.publishUsable,
    blockers: result.blockers,
    nextSteps: result.nextSteps,
    recommendation: result.recommendation,
    provision: result.provision,
    blockedReport: result.blockedReport,
    containerEngine: result.containerEngine
  };
}

function summarizeRegistryCapabilities(result) {
  return {
    version: result.version,
    registryPrefix: result.registryPrefix,
    generatedAt: result.generatedAt,
    classification: result.classification,
    dimensions: result.dimensions,
    interpretation: result.interpretation
  };
}

function baseImagePreflightBlocker(result) {
  return blocker({
    type: "environment_blocker",
    scope: "base-image",
    summary: result.blockers.length
      ? result.blockers.join("; ")
      : "DEV builder base image preflight did not return ready.",
    next: result.nextSteps.length
      ? result.nextSteps.join(" ")
      : "Provide an approved local Node 20 or HWLAB DEV builder base image before artifact publish."
  });
}

function commandLine(command, args) {
  return [command, ...args].map((part) => (/\s/u.test(part) ? JSON.stringify(part) : part)).join(" ");
}

function shellQuote(value) {
  return `'${String(value).replaceAll("'", "'\\''")}'`;
}

function dockerfileQuote(value) {
  return JSON.stringify(String(value));
}

function buildkitCacheRef(registryPrefix, serviceId) {
  return `${registryPrefix}/cache/${serviceId}`;
}

function envReuseImageRef(registryPrefix, serviceId, environmentInputHash) {
  const tag = String(environmentInputHash || "unknown").slice(0, 12);
  return `${registryPrefix}/${serviceId}-env:env-${tag}`;
}

function envReuseCacheRef(registryPrefix, serviceId) {
  return `${registryPrefix}/cache/${serviceId}-env`;
}

function buildkitRegistryOption(registryPrefix) {
  const parsed = parseRegistryPrefix(registryPrefix);
  return parsed.host === "127.0.0.1" || parsed.host === "localhost"
    ? ",registry.insecure=true"
    : "";
}

function buildkitDigestFromMetadata(value) {
  const keys = [
    "containerimage.digest",
    "containerimage.descriptor.digest",
    "image.digest",
    "digest"
  ];
  for (const key of keys) {
    const direct = value?.[key];
    if (shaDigestPattern.test(direct ?? "")) return direct;
  }
  const nested = value?.containerimage?.digest ?? value?.containerimage?.descriptor?.digest;
  return shaDigestPattern.test(nested ?? "") ? nested : null;
}

async function buildkitCommandPreflight(command) {
  const result = await run("sh", ["-c", `command -v ${shellQuote(command)}`]);
  return {
    ok: result.code === 0,
    command,
    result
  };
}

async function run(command, args, options = {}) {
  const startedAt = Date.now();
  const child = spawn(command, args, {
    cwd: options.cwd ?? repoRoot,
    env: options.env ?? process.env,
    stdio: ["ignore", "pipe", "pipe"]
  });

  let stdout = "";
  let stderr = "";
  child.stdout.on("data", (chunk) => {
    stdout += chunk;
  });
  child.stderr.on("data", (chunk) => {
    stderr += chunk;
  });

  const code = await new Promise((resolve) => {
    child.on("error", () => resolve(127));
    child.on("close", resolve);
  });

  return {
    command: commandLine(command, args),
    code,
    stdout,
    stderr,
    durationMs: Date.now() - startedAt
  };
}

function bunExecutable() {
  return process.env.HWLAB_BUN_BINARY || process.env.BUN_BINARY || "bun";
}

async function inspectCloudWebDistFreshness(webRoot) {
  const result = await run(bunExecutable(), ["web/hwlab-cloud-web/scripts/dist-contract.ts", "--inspect", "--root", webRoot]);
  if (result.code !== 0) {
    return {
      status: "blocked",
      distPath: "web/hwlab-cloud-web/dist",
      buildCommand: "cd web/hwlab-cloud-web && bun run build",
      freshnessCommand: "cd web/hwlab-cloud-web && bun run build",
      files: [],
      mismatches: ["dist-contract-inspect"],
      error: tailText(`${result.stdout}\n${result.stderr}`)
    };
  }
  return JSON.parse(result.stdout);
}

function assertDevOnlyContracts(catalog, deployManifest) {
  assert.equal(catalog.environment, ENVIRONMENT_DEV, "catalog.environment must be dev");
  assert.equal(catalog.profile, ENVIRONMENT_DEV, "catalog.profile must be dev");
  assert.equal(catalog.namespace, "hwlab-dev", "catalog.namespace must be hwlab-dev");
  assert.deepEqual(catalog.allowedProfiles, [ENVIRONMENT_DEV], "catalog.allowedProfiles must only allow dev");
  assert.deepEqual(catalog.forbiddenProfiles, ["prod"], "catalog.forbiddenProfiles must forbid prod");
  assert.equal(deployManifest.environment, ENVIRONMENT_DEV, "deploy.environment must be dev");
  assert.equal(deployManifest.namespace, "hwlab-dev", "deploy.namespace must be hwlab-dev");
  assert.equal(deployManifest.profiles?.dev?.enabled, true, "deploy dev profile must be enabled");
  assert.equal(deployManifest.profiles?.prod?.enabled, false, "deploy prod profile must stay disabled");

  const catalogIds = catalog.services.map((service) => service.serviceId);
  const deployIds = deployManifest.services.map((service) => service.serviceId);
  assert.deepEqual(catalogIds, SERVICE_IDS, "catalog services must match frozen DEV service IDs");
  assert.deepEqual(deployIds, SERVICE_IDS, "deploy services must match frozen DEV service IDs");

  for (const service of [...catalog.services, ...deployManifest.services]) {
    assert.equal(service.profile, ENVIRONMENT_DEV, `${service.serviceId}.profile must be dev`);
    assert.equal(service.namespace, "hwlab-dev", `${service.serviceId}.namespace must be hwlab-dev`);
    assert.ok(!String(service.image).includes("prod"), `${service.serviceId}.image must not target prod`);
  }
}

function laneDeployConfig(deployManifest, lane) {
  return deployManifest?.lanes?.[lane] ?? null;
}

function isRuntimeArtifactLane(lane) {
  return runtimeArtifactLanePattern.test(String(lane ?? ""));
}

function isHttpEndpoint(value) {
  if (typeof value !== "string" || value.trim().length === 0) return false;
  try {
    const url = new URL(value);
    return url.protocol === "http:" || url.protocol === "https:";
  } catch {
    return false;
  }
}

function assertRuntimeLaneContracts(catalog, deployManifest, laneName) {
  const lane = laneDeployConfig(deployManifest, laneName);
  assert.ok(lane && typeof lane === "object", `deploy.lanes.${laneName} must exist for ${laneName} artifact publish`);
  assert.ok(String(lane.namespace ?? "").trim(), `deploy.lanes.${laneName}.namespace must not be empty`);
  assert.ok(isHttpEndpoint(lane.endpoint), `deploy.lanes.${laneName}.endpoint must be a non-empty http(s) URL`);
  const declaredServiceIds = new Set(runtimeLaneServiceIdsFromDeclarations(deployManifest, laneName));
  assert.ok(
    runtimeLaneServiceIdsFromDeploy(deployManifest, laneName).every((serviceId) => declaredServiceIds.has(serviceId)),
    `deploy.lanes.${laneName}.serviceDeclarations must cover envReuseServices`
  );
  assert.equal(catalog.environment, laneName, `catalog.environment must be ${laneName}`);
  assert.equal(catalog.profile, laneName, `catalog.profile must be ${laneName}`);
  assert.equal(catalog.namespace, lane.namespace, `catalog.namespace must be ${lane.namespace}`);
  if (catalog.endpoint != null) assert.ok(isHttpEndpoint(catalog.endpoint), "catalog.endpoint must be a non-empty http(s) URL");
  assert.deepEqual(catalog.allowedProfiles, [laneName], `catalog.allowedProfiles must only allow ${laneName}`);
  assert.deepEqual(catalog.forbiddenProfiles, ["dev", "prod"], "catalog.forbiddenProfiles must forbid dev/prod");
  const catalogIds = (catalog.services ?? []).map((service) => service.serviceId);
  assert.deepEqual(catalogIds, runtimeLaneServiceIdsFromDeploy(deployManifest, laneName), `${laneName} catalog services must match deploy.lanes.${laneName}.envReuseServices`);
  for (const service of catalog.services ?? []) {
    assert.equal(service.profile, laneName, `${service.serviceId}.profile must be ${laneName}`);
    assert.equal(service.namespace, lane.namespace, `${service.serviceId}.namespace must be ${lane.namespace}`);
    assert.ok(!String(service.image).includes("prod"), `${service.serviceId}.image must not target prod`);
  }
}

function assertArtifactContracts(args, catalog, deployManifest) {
  if (isRuntimeArtifactLane(args.lane)) return assertRuntimeLaneContracts(catalog, deployManifest, args.lane);
  return assertDevOnlyContracts(catalog, deployManifest);
}

function imageTagForCommit(args, commitId) {
  return args.imageTagMode === "full" ? commitId : commitId.slice(0, 7);
}

function artifactEnvironment(args) {
  return isRuntimeArtifactLane(args.lane) ? args.lane : ENVIRONMENT_DEV;
}

function artifactCatalogSkeleton({ args, deployManifest, commitId }) {
  const tag = imageTagForCommit(args, commitId);
  const laneConfig = laneDeployConfig(deployManifest, args.lane);
  const laneEndpoint = isRuntimeArtifactLane(args.lane) ? laneConfig?.endpoint : "http://74.48.78.17:16667";
  const namespace = isRuntimeArtifactLane(args.lane) ? laneConfig?.namespace : "hwlab-dev";
  assert.ok(isHttpEndpoint(laneEndpoint), "deploy lane endpoint must be a non-empty http(s) URL");
  assert.ok(String(namespace ?? "").trim(), "deploy lane namespace must not be empty");
  return {
    catalogVersion: "v1",
    kind: "hwlab-artifact-catalog",
    environment: artifactEnvironment(args),
    profile: artifactEnvironment(args),
    namespace,
    endpoint: laneEndpoint,
    commitId: tag,
    artifactState: "contract-skeleton",
    publish: {
      ciPublished: false,
      registryVerified: false,
      provenance: "not_available_until_publish",
      note: `${artifactEnvironment(args)} artifact catalog skeleton initialized without DEV/node fallback.`
    },
    allowedProfiles: [artifactEnvironment(args)],
    forbiddenProfiles: isRuntimeArtifactLane(args.lane) ? ["dev", "prod"] : ["prod"],
    services: serviceIdsForArtifactLane(args, deployManifest).map((serviceId) => artifactCatalogSkeletonService({ args, deployManifest, serviceId, tag, commitId }))
  };
}

function artifactCatalogSkeletonService({ args, deployManifest, serviceId, tag, commitId }) {
  const laneConfig = laneDeployConfig(deployManifest, args.lane);
  const namespace = isRuntimeArtifactLane(args.lane) ? laneConfig?.namespace : "hwlab-dev";
  const base = {
    serviceId,
    commitId: tag,
    sourceCommitId: commitId,
    image: imageRef(args.registryPrefix, serviceId, tag),
    imageTag: tag,
    digest: "not_published",
    publishState: "skeleton-only",
    profile: artifactEnvironment(args),
    namespace,
    healthPath: "/health/live",
    sourceState: "source-present",
    publishEnabled: true,
    artifactRequired: true,
    artifactScope: "required",
    notPublishedReason: "publish_not_run"
  };
  const envReuseServiceIds = envReuseServiceIdsForLane(deployManifest, args.lane);
  if (!envReuseServiceIds.has(serviceId)) return base;
  const bootSh = bootShForService(deployManifest, serviceId, args.lane);
  const environmentImage = envReuseImageRef(args.registryPrefix, serviceId, commitId);
  return {
    ...base,
    runtimeMode: v02EnvReuseRuntimeMode,
    envReuse: true,
    image: environmentImage,
    imageTag: imageTagFromReference(environmentImage),
    environmentImage,
    environmentDigest: "not_published",
    environmentInputHash: null,
    codeInputHash: null,
    bootRepo: "git@github.com:pikasTech/HWLAB.git",
    bootCommit: commitId,
    bootSh,
    bootEnvEvidence: bootEnvEvidence({ service: { serviceId, bootCommit: commitId, bootSh }, commitId, environmentImage, environmentDigest: "not_published" })
  };
}

function envReuseServiceIdsForLane(deployManifest, lane) {
  if (!isRuntimeArtifactLane(lane)) return new Set();
  const configured = deployManifest?.lanes?.[lane]?.envReuseServices;
  return new Set(normalizeServiceIdList(configured));
}

function serviceIdsForArtifactLane(args, deployManifest) {
  if (!isRuntimeArtifactLane(args.lane)) return [...SERVICE_IDS];
  return runtimeLaneServiceIdsFromDeploy(deployManifest, args.lane);
}

function applyDeployDefaultServices(args, deployManifest) {
  if (args.servicesExplicit) return;
  args.services = serviceIdsForArtifactLane(args, deployManifest);
}

function runtimeLaneServiceIdsFromDeploy(deployManifest, lane) {
  const serviceIds = normalizeServiceIdList(deployManifest?.lanes?.[lane]?.envReuseServices);
  assert.ok(serviceIds.length > 0, `deploy.lanes.${lane}.envReuseServices must not be empty`);
  return serviceIds;
}

function runtimeLaneServiceIdsFromDeclarations(deployManifest, lane) {
  return normalizeServiceIdList(Object.keys(deployManifest?.lanes?.[lane]?.serviceDeclarations ?? {}));
}

function normalizeServiceIdList(value) {
  return uniquePreserveOrder((Array.isArray(value) ? value : []).map((item) => String(item ?? "").trim()).filter(Boolean));
}

function uniquePreserveOrder(values) {
  const seen = new Set();
  const result = [];
  for (const value of values) {
    if (seen.has(value)) continue;
    seen.add(value);
    result.push(value);
  }
  return result;
}

function bootShForService(deployManifest, serviceId, lane) {
  const value = deployManifest?.lanes?.[lane]?.bootScripts?.[serviceId];
  assert.ok(value, `deploy.lanes.${lane}.bootScripts.${serviceId} is required`);
  const text = String(value).replaceAll("\\", "/").replace(/^\.\//u, "");
  assert.ok(text && !text.startsWith("/") && !text.split("/").includes(".."), `${serviceId} boot script must be repo-relative`);
  return text;
}

function normalizeCatalogForLane(catalog, args, deployManifest, commitId) {
  const lane = args?.lane;
  if (!catalog || !isRuntimeArtifactLane(lane)) return catalog;
  const serviceIds = runtimeLaneServiceIdsFromDeploy(deployManifest, lane);
  const allowed = new Set(serviceIds);
  const existingById = new Map((catalog.services ?? [])
    .filter((service) => allowed.has(service.serviceId))
    .map((service) => [service.serviceId, service]));
  const tag = imageTagForCommit(args, commitId);
  return {
    ...catalog,
    services: serviceIds.map((serviceId) => existingById.get(serviceId)
      ?? artifactCatalogSkeletonService({ args, deployManifest, serviceId, tag, commitId }))
  };
}

function parseRegistryPrefix(prefix) {
  const normalized = prefix.replace(/\/+$/u, "");
  const [hostPort, ...pathParts] = normalized.split("/");
  const host = hostPort.split(":")[0].toLowerCase();
  return {
    normalized,
    hostPort,
    host,
    namespace: pathParts.join("/")
  };
}

function isPrivateHost(host) {
  if (host === "localhost" || host === "127.0.0.1" || host === "::1") return true;
  if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/u.test(host)) return true;
  if (/^192\.168\.\d{1,3}\.\d{1,3}$/u.test(host)) return true;
  if (/^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/u.test(host)) return true;
  if (host.endsWith(".local") || host.endsWith(".svc") || host.endsWith(".cluster.local")) return true;
  return false;
}

function validateRegistryPrefix(prefix) {
  const parsed = parseRegistryPrefix(prefix);
  assert.ok(parsed.hostPort, "registry prefix must include a registry host");
  assert.ok(parsed.namespace, "registry prefix must include an image namespace, for example 127.0.0.1:5000/hwlab");
  assert.ok(!/prod|production/iu.test(parsed.normalized), "registry prefix must not include prod");
  assert.ok(
    !/(^|\/)(ghcr\.io|docker\.io|index\.docker\.io|quay\.io|gcr\.io|registry-1\.docker\.io)(\/|$)/iu.test(parsed.normalized),
    "registry prefix must not target a third-party registry"
  );
  assert.ok(isPrivateHost(parsed.host), "registry prefix must target localhost or a private/internal host");
  return parsed.normalized;
}

async function gitValue(args) {
  const result = await run("git", args);
  if (result.code !== 0) {
    throw new Error(`${result.command} failed: ${result.stderr.trim() || result.stdout.trim()}`);
  }
  return result.stdout.trim();
}

function repoLabelFromRemote(remoteUrl) {
  const githubMatch = remoteUrl.match(/github\.com[:/](.+?)(?:\.git)?$/iu);
  if (githubMatch) {
    return githubMatch[1];
  }

  try {
    const url = new URL(remoteUrl);
    url.username = "";
    url.password = "";
    return `${url.host}${url.pathname.replace(/\.git$/u, "")}`;
  } catch {
    return "pikasTech/HWLAB";
  }
}

async function resolveServices(serviceIds, catalog, allowedServiceIds = SERVICE_IDS) {
  return resolveDevArtifactServices(repoRoot, serviceIds, allowedServiceIds, catalog);
}

function dockerfile(baseImage, port, labels = []) {
  const dependencyTimingScript = `started_at=$(node -e "console.log(Date.now())"); status=succeeded; if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then bun_pkg=""; case "$(uname -m)" in x86_64|amd64) bun_pkg="@oven/bun-linux-x64@1.3.13" ;; aarch64|arm64) bun_pkg="@oven/bun-linux-aarch64@1.3.13" ;; *) echo "unsupported bun-command architecture: $(uname -m)" >&2; false ;; esac && npm install --no-save --omit=dev --include=optional --ignore-scripts "$bun_pkg" && test -x "node_modules/\${bun_pkg%@*}/bin/bun" && ln -sf "/app/node_modules/\${bun_pkg%@*}/bin/bun" /usr/local/bin/bun; fi && codex_version="$(node -p "require('./node_modules/@openai/codex/package.json').version")" && case "$(uname -m)" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-x64@npm:@openai/codex@\${codex_version}-linux-x64" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-arm64@npm:@openai/codex@\${codex_version}-linux-arm64" ;; esac || status=failed; finished_at=$(node -e "console.log(Date.now())"); node -e "console.log(JSON.stringify({event:'node-cicd-build-step-timing',stage:'dependency-install',status:process.argv[1],durationMs:Number(process.argv[3])-Number(process.argv[2]),source:'dockerfile-run'}))" "$status" "$started_at" "$finished_at"; test "$status" = succeeded`;
  const runtimeReadinessScript = `set -eu; fail() { echo "hwlab-artifact-runtime-check-failed:$1" >&2; exit 1; }; mkdir -p /workspace /codex-home; rm -rf /workspace/hwlab; ln -s /app /workspace/hwlab; if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then test -x /usr/local/bin/bun || fail bun-missing; /usr/local/bin/bun --version >/tmp/hwlab-bun-version.txt || fail bun-version; fi; chmod -R a+rwX /app /workspace /codex-home || fail chmod; test -x /app/node_modules/.bin/codex || fail codex-bin-missing; /app/node_modules/.bin/codex --version >/tmp/hwlab-codex-version.txt || fail codex-version; test -x /app/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex/codex || test -x /app/node_modules/@openai/codex-linux-arm64/vendor/aarch64-unknown-linux-musl/codex/codex || fail codex-native-missing`;
  return [
    `FROM ${baseImage}`,
    "WORKDIR /app",
    ...labels.map(([name, value]) => `LABEL ${name}=${dockerfileQuote(value)}`),
    "ARG HWLAB_ARTIFACT_KIND",
    "ENV HWLAB_CODE_AGENT_SKILLS_DIRS=/app/skills",
    "COPY package.json ./package.json",
    "COPY package-lock.json* ./",
    `RUN ${dependencyTimingScript}`,
    "COPY internal ./internal",
    "COPY cmd ./cmd",
    "COPY scripts ./scripts",
    "COPY web ./web",
    "COPY tools ./tools",
    "COPY skills ./skills",
    "COPY .hwlab ./.hwlab",
    "COPY deploy ./deploy",
    "RUN printf '%s\\n' '#!/usr/bin/env sh' 'exec node /app/scripts/run-bun.mjs /app/tools/hwpod-cli.ts \"$@\"' > /usr/local/bin/hwpod && chmod 755 /usr/local/bin/hwpod && printf '%s\\n' '#!/usr/bin/env sh' 'exec node /app/scripts/run-bun.mjs /app/tools/hwpod-ctl.ts \"$@\"' > /usr/local/bin/hwpod-ctl && chmod 755 /usr/local/bin/hwpod-ctl && printf '%s\\n' '#!/usr/bin/env sh' 'exec node /app/scripts/run-bun.mjs /app/tools/hwpod-compiler-cli.ts \"$@\"' > /usr/local/bin/hwpod-compiler && chmod 755 /usr/local/bin/hwpod-compiler",
    `RUN ${runtimeReadinessScript}`,
    "RUN cp /app/internal/dev-entrypoint/artifact-runtime.mjs /usr/local/bin/hwlab-dev-artifact-runtime.mjs && chmod 755 /usr/local/bin/hwlab-dev-artifact-runtime.mjs",
    "ARG HWLAB_ENVIRONMENT",
    "ARG HWLAB_SERVICE_ID",
    "ARG HWLAB_SERVICE_ENTRYPOINT",
    "ARG HWLAB_COMMIT_ID",
    "ARG HWLAB_REVISION",
    "ARG HWLAB_IMAGE",
    "ARG HWLAB_IMAGE_TAG",
    "ARG HWLAB_IMAGE_DIGEST",
    "ARG HWLAB_BUILD_CREATED_AT",
    "ARG HWLAB_BUILD_SOURCE",
    "ARG HWLAB_BUILD_PROVENANCE",
    "ARG PORT",
    "ARG HWLAB_PORT",
    "ENV HWLAB_ENVIRONMENT=$HWLAB_ENVIRONMENT",
    "ENV HWLAB_SERVICE_ID=$HWLAB_SERVICE_ID",
    "ENV HWLAB_ARTIFACT_KIND=$HWLAB_ARTIFACT_KIND",
    "ENV HWLAB_SERVICE_ENTRYPOINT=$HWLAB_SERVICE_ENTRYPOINT",
    "ENV HWLAB_COMMIT_ID=$HWLAB_COMMIT_ID",
    "ENV HWLAB_REVISION=$HWLAB_REVISION",
    "ENV HWLAB_IMAGE=$HWLAB_IMAGE",
    "ENV HWLAB_IMAGE_TAG=$HWLAB_IMAGE_TAG",
    "ENV HWLAB_IMAGE_DIGEST=$HWLAB_IMAGE_DIGEST",
    "ENV HWLAB_BUILD_CREATED_AT=$HWLAB_BUILD_CREATED_AT",
    "ENV HWLAB_BUILD_SOURCE=$HWLAB_BUILD_SOURCE",
    "ENV HWLAB_BUILD_PROVENANCE=$HWLAB_BUILD_PROVENANCE",
    "ENV PORT=$PORT",
    "ENV HWLAB_PORT=$HWLAB_PORT",
    `EXPOSE ${port}`,
    "CMD [\"node\", \"/usr/local/bin/hwlab-dev-artifact-runtime.mjs\"]",
    ""
  ].join("\n");
}

function envReuseDockerfile(baseImage, labels = [], recipe, service = null) {
  const normalizedRecipe = normalizeEnvReuseRecipe(recipe);
  const runtimeRoot = normalizedRecipe.runtimeNodeModulesPath.replace(/\/node_modules$/u, "");
  const downloadEnvPrefix = shellExportPrefix(downloadEnvPairs(normalizedRecipe.downloadStack));
  const npmConfigScript = npmDownloadConfigScript(normalizedRecipe.downloadStack);
  const goService = service?.runtimeKind === "go-service" || service?.artifactKind === "go-service";
  const goCacheEnabled = Boolean(goService && normalizedRecipe.runtimeGoModCachePath && normalizedRecipe.runtimeGoBuildCachePath);
  const goProxy = normalizedRecipe.downloadStack.goProxy || "https://proxy.golang.org,direct";
  const goEnv = goCacheEnabled ? [
    `ENV GOMODCACHE=${normalizedRecipe.runtimeGoModCachePath}`,
    `ENV GOCACHE=${normalizedRecipe.runtimeGoBuildCachePath}`,
    `ENV GOPROXY=${goProxy}`,
    `ENV GOTOOLCHAIN=${normalizedRecipe.goToolchain || "local"}`
  ] : [];
  const goBuildEnvPrefix = goCacheEnabled ? shellExportPrefix([
    ["GOMODCACHE", normalizedRecipe.runtimeGoModCachePath],
    ["GOCACHE", normalizedRecipe.runtimeGoBuildCachePath],
    ["GOPROXY", goProxy],
    ["GOTOOLCHAIN", normalizedRecipe.goToolchain || "local"]
  ]) : "";
  const goDownloadRetryScript = `retry_go_mod_download() { attempts=6; n=1; while ! go mod download; do if [ "$n" -ge "$attempts" ]; then return 1; fi; echo "go mod download attempt $n/$attempts failed; retrying" >&2; n=$((n + 1)); sleep 5; done; }`;
  const goDownloadScript = goCacheEnabled
    ? `RUN ${downloadEnvPrefix}${goBuildEnvPrefix}set -eu; mkdir -p ${shellQuote(normalizedRecipe.runtimeGoModCachePath)} ${shellQuote(normalizedRecipe.runtimeGoBuildCachePath)}; ${goDownloadRetryScript}; retry_go_mod_download`
    : null;
  const goReadiness = goCacheEnabled
    ? `; mkdir -p ${shellQuote(normalizedRecipe.runtimeGoModCachePath)} ${shellQuote(normalizedRecipe.runtimeGoBuildCachePath)}; command -v go >/dev/null; go version >/tmp/hwlab-env-go-version.txt`
    : "";
  const osPackages = uniquePreserveOrder([
    ...normalizedRecipe.osPackages,
    ...(goCacheEnabled ? normalizedRecipe.goOsPackages : [])
  ]);
  const downloadDependencyScript = `set -eu; ${npmConfigScript}; if [ -f package-lock.json ]; then npm ci --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi; if command -v bun >/dev/null 2>&1; then bun_path="$(command -v bun)"; if [ "$bun_path" != "/usr/local/bin/bun" ]; then ln -sf "$bun_path" /usr/local/bin/bun; fi; fi; if [ ! -x /usr/local/bin/bun ]; then bun_pkg=""; case "$(uname -m)" in x86_64|amd64) bun_pkg="${normalizedRecipe.downloadStack.bunPackages.x64}@${normalizedRecipe.bunVersion}" ;; aarch64|arm64) bun_pkg="${normalizedRecipe.downloadStack.bunPackages.arm64}@${normalizedRecipe.bunVersion}" ;; *) echo "unsupported bun architecture: $(uname -m)" >&2; false ;; esac; npm install --no-save --omit=dev --include=optional --ignore-scripts "$bun_pkg"; bun_bin="node_modules/\${bun_pkg%@*}/bin/bun"; test -x "$bun_bin"; ln -sf "${runtimeRoot}/$bun_bin" /usr/local/bin/bun; fi`;
  const aptRetryHelpers = `retry_apt_update() { attempts=6; n=1; while ! apt-get update -o Acquire::Retries=3; do if [ "$n" -ge "$attempts" ]; then return 1; fi; echo "apt-get update attempt $n/$attempts failed; retrying" >&2; n=$((n + 1)); rm -rf /var/lib/apt/lists/*; sleep 5; done; }; apt_packages_ready() { for pkg in "$@"; do dpkg-query -W -f='\${Status}' "$pkg" 2>/dev/null | grep -q 'install ok installed' || return 1; done; }; retry_apt_install() { attempts=8; n=1; while true; do apt-get install -o Acquire::Retries=3 -y --no-install-recommends --fix-missing "$@" || true; if apt_packages_ready "$@"; then return 0; fi; if [ "$n" -ge "$attempts" ]; then return 1; fi; echo "apt-get install attempt $n/$attempts incomplete; retrying" >&2; n=$((n + 1)); sleep 5; apt-get update -o Acquire::Retries=3 || true; done; }`;
  const aptUpdateScript = `retry_apt_update || (for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.sources; do [ -f "$f" ] || continue; sed -i -e '/^[[:space:]]*deb .* bookworm-updates /d' -e 's/[[:space:]]bookworm-updates//g' -e 's/bookworm-updates[[:space:]]//g' "$f"; done; retry_apt_update)`;
  const osPackageScript = `set -eu; export DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC; ${aptRetryHelpers}; ${aptUpdateScript}; retry_apt_install ${osPackages.map(shellQuote).join(" ")}; rm -rf /var/lib/apt/lists/*`;
  const aliasScript = normalizedRecipe.hwpodAliases.map((alias) => `printf '%s\\n' '#!/usr/bin/env sh' 'exec node /workspace/hwlab-boot/repo/scripts/run-bun.mjs /workspace/hwlab-boot/repo/${alias.script} \"$@\"' > /usr/local/bin/${alias.command}; chmod 755 /usr/local/bin/${alias.command}`).join("; ");
  const readinessScript = `set -eu; command -v node >/dev/null; command -v npm >/dev/null; command -v git >/dev/null; command -v sh >/dev/null; test -d ${shellQuote(normalizedRecipe.runtimeNodeModulesPath)}; test -x /usr/local/bin/bun; /usr/local/bin/bun --version >/tmp/hwlab-env-bun-version.txt${goReadiness}; ${aliasScript}`;
  return [
    `FROM ${baseImage}`,
    `WORKDIR ${runtimeRoot}`,
    ...labels.map(([name, value]) => `LABEL ${name}=${dockerfileQuote(value)}`),
    "COPY package.json ./package.json",
    "COPY package-lock.json* ./",
    ...(goCacheEnabled ? ["COPY go.mod ./go.mod", "COPY go.sum* ./"] : []),
    `RUN ${downloadEnvPrefix}${osPackageScript}`,
    `RUN ${downloadEnvPrefix}${downloadDependencyScript}`,
    ...(goDownloadScript ? [goDownloadScript] : []),
    `COPY ${normalizedRecipe.launcherPath} /usr/local/bin/hwlab-env-reuse-launcher.ts`,
    `RUN ${readinessScript}`,
    "ENV HWLAB_RUNTIME_MODE=env-reuse-git-mirror-checkout",
    `ENV HWLAB_RUNTIME_NODE_MODULES=${normalizedRecipe.runtimeNodeModulesPath}`,
    ...goEnv,
    "ENV PATH=/usr/local/bin:$PATH",
    "CMD [\"/usr/local/bin/bun\", \"/usr/local/bin/hwlab-env-reuse-launcher.ts\"]",
    ""
  ].join("\n");
}

function normalizeEnvReuseRecipe(recipe) {
  assert.ok(recipe && typeof recipe === "object", "envRecipe must be provided by deploy.lanes.<lane>.envRecipe");
  const normalized = {
    osPackages: normalizeStringList(recipe.osPackages),
    goOsPackages: normalizeStringList(recipe.goOsPackages),
    bunVersion: String(recipe.bunVersion ?? "").trim(),
    launcherPath: normalizeRepoPath(recipe.launcherPath),
    runtimeNodeModulesPath: normalizeAbsolutePath(recipe.runtimeNodeModulesPath),
    runtimeGoModCachePath: normalizeAbsolutePath(recipe.runtimeGoModCachePath),
    runtimeGoBuildCachePath: normalizeAbsolutePath(recipe.runtimeGoBuildCachePath),
    goToolchain: String(recipe.goToolchain ?? "").trim(),
    downloadStack: normalizeDownloadStack(recipe.downloadStack),
    hwpodAliases: normalizeHwpodAliases(recipe.hwpodAliases)
  };
  assert.ok(normalized.osPackages.length > 0, "envRecipe.osPackages must not be empty");
  if (normalized.runtimeGoModCachePath || normalized.runtimeGoBuildCachePath) {
    assert.ok(normalized.goOsPackages.length > 0, "envRecipe.goOsPackages must not be empty when Go cache paths are configured");
  }
  assert.match(normalized.bunVersion, /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u, "envRecipe.bunVersion must be a semver-like version");
  assert.ok(normalized.launcherPath && !normalized.launcherPath.split("/").includes(".."), "envRecipe.launcherPath must be repo-relative");
  assert.ok(normalized.runtimeNodeModulesPath, "envRecipe.runtimeNodeModulesPath must be absolute");
  assert.ok(normalized.hwpodAliases.length > 0, "envRecipe.hwpodAliases must not be empty");
  return normalized;
}

function normalizeDownloadStack(value) {
  value = effectiveDownloadStackConfig(value, process.env);
  assert.ok(value && typeof value === "object", "envRecipe.downloadStack must be configured in deploy/deploy.yaml");
  const normalized = {
    httpProxy: normalizeOptionalHttpUrl(value.httpProxy),
    httpsProxy: normalizeOptionalHttpUrl(value.httpsProxy),
    noProxy: normalizeStringList(value.noProxy),
    npmRegistry: normalizeOptionalHttpUrl(value.npmRegistry),
    npmFetchTimeoutMs: Number(value.npmFetchTimeoutMs),
    goProxy: String(value.goProxy ?? "").trim(),
    bunPackages: {
      x64: normalizePackageName(value.bunPackages?.x64),
      arm64: normalizePackageName(value.bunPackages?.arm64)
    }
  };
  assert.ok(normalized.npmRegistry, "envRecipe.downloadStack.npmRegistry must be an http(s) URL");
  assert.ok(Number.isInteger(normalized.npmFetchTimeoutMs) && normalized.npmFetchTimeoutMs > 0, "envRecipe.downloadStack.npmFetchTimeoutMs must be a positive integer");
  assert.ok(normalized.bunPackages.x64 && normalized.bunPackages.arm64, "envRecipe.downloadStack.bunPackages.x64/arm64 are required");
  return normalized;
}

function effectiveDownloadStackConfig(value, env = process.env) {
  if (!value || typeof value !== "object" || !usesK8sNativeDownloadProxy(env)) return value;
  const httpProxy = textEnv(env.HTTP_PROXY) || textEnv(env.http_proxy) || value.httpProxy;
  const httpsProxy = textEnv(env.HTTPS_PROXY) || textEnv(env.https_proxy) || httpProxy || value.httpsProxy;
  const noProxy = noProxyFromEnv(env) || value.noProxy;
  return {
    ...value,
    httpProxy,
    httpsProxy,
    noProxy
  };
}

function usesK8sNativeDownloadProxy(env = process.env) {
  return env.HWLAB_DEV_REGISTRY_K8S_NATIVE === "1" || Boolean(textEnv(env.HWLAB_TEKTON_PIPELINERUN));
}

function noProxyFromEnv(env = process.env) {
  const text = textEnv(env.NO_PROXY) || textEnv(env.no_proxy);
  if (!text) return null;
  return text.split(",").map((item) => item.trim()).filter(Boolean);
}

function textEnv(value) {
  const text = String(value ?? "").trim();
  return text || null;
}

function normalizeOptionalHttpUrl(value) {
  const text = String(value ?? "").trim();
  if (!text) return null;
  try {
    const url = new URL(text);
    if (url.protocol === "http:" || url.protocol === "https:") return text;
  } catch {}
  return null;
}

function normalizePackageName(value) {
  const text = String(value ?? "").trim();
  return text && !text.includes(" ") ? text : null;
}

function downloadEnvPairs(stack) {
  return [
    ["HTTP_PROXY", stack.httpProxy],
    ["http_proxy", stack.httpProxy],
    ["HTTPS_PROXY", stack.httpsProxy],
    ["https_proxy", stack.httpsProxy],
    ["NO_PROXY", stack.noProxy.join(",")],
    ["no_proxy", stack.noProxy.join(",")]
  ].filter(([, value]) => value);
}

function shellExportPrefix(pairs) {
  return pairs.length === 0 ? "" : `export ${pairs.map(([name, value]) => `${name}=${shellQuote(value)}`).join(" ")}; `;
}

function npmDownloadConfigScript(stack) {
  const commands = [
    `npm config set registry ${shellQuote(stack.npmRegistry)}`,
    `npm config set fetch-timeout ${Number(stack.npmFetchTimeoutMs)}`
  ];
  if (stack.httpProxy) commands.push(`npm config set proxy ${shellQuote(stack.httpProxy)}`);
  if (stack.httpsProxy) commands.push(`npm config set https-proxy ${shellQuote(stack.httpsProxy)}`);
  if (stack.noProxy.length > 0) commands.push(`npm config set noproxy ${shellQuote(stack.noProxy.join(","))}`);
  return commands.join("; ");
}

function normalizeStringList(value) {
  const source = Array.isArray(value) ? value : [];
  return source.map((item) => String(item ?? "").trim()).filter(Boolean);
}

function normalizeRepoPath(value) {
  return String(value ?? "").replaceAll("\\", "/").replace(/^\.\//u, "").replace(/^\/+/, "").trim();
}

function normalizeAbsolutePath(value) {
  const text = String(value ?? "").trim();
  return text.startsWith("/") && !text.includes("..") ? text : null;
}

function normalizeHwpodAliases(value) {
  const source = Array.isArray(value) ? value : [];
  return source.map((item) => ({
    command: String(item?.command ?? "").trim(),
    script: normalizeRepoPath(item?.script)
  })).filter((item) => item.command && item.script);
}

function imageRef(registryPrefix, serviceId, tag) {
  return `${registryPrefix}/${serviceId}:${tag}`;
}

function imageRepository(registryPrefix, serviceId) {
  return `${registryPrefix}/${serviceId}`;
}

function tailText(value, maxLength = 2500) {
  const text = value.trim();
  if (text.length <= maxLength) return text;
  return text.slice(text.length - maxLength);
}

function sourceStateBlocker(service) {
  return blocker({
    type: "contract_blocker",
    scope: service.serviceId,
    summary: `${service.serviceId} has invalid sourceState ${service.sourceState ?? "unset"}.`,
    next: `Set ${service.serviceId} sourceState to source-present or intentionally-disabled.`
  });
}

async function preflight({ args, services, catalog, deployManifest, baseImagePreflight, registryCapabilities, buildServiceIds = new Set() }) {
  const blockers = [];
  let registryPrefix = args.registryPrefix;
  const buildRequired = (args.mode === "build" || args.mode === "publish") && buildServiceIds.size > 0;

  if (baseImagePreflight.publishUsable) {
    args.baseImage = baseImagePreflight.localTag;
  } else if (buildRequired) {
    blockers.push(baseImagePreflightBlocker(baseImagePreflight));
  }

  try {
    registryPrefix = validateRegistryPrefix(args.registryPrefix);
  } catch (error) {
    blockers.push(
      blocker({
        type: "safety_blocker",
        scope: "registry",
        summary: error.message,
        next: "Use the node local/internal registry prefix, for example 127.0.0.1:5000/hwlab."
      })
    );
  }

  try {
    assertArtifactContracts(args, catalog, deployManifest);
  } catch (error) {
    blockers.push(
      blocker({
        type: "contract_blocker",
        scope: `${args.lane}-contract`,
        summary: error.message,
        next: `Fix ${args.catalogPath} and ${args.deployPath} so the selected artifact lane covers the frozen service IDs without crossing namespaces.`
      })
    );
  }

  const baseImageTag = args.baseImage ? args.baseImage.split(":").at(-1) : "";
  for (const tag of [baseImageTag, args.services.length === 1 ? args.services[0] : ""]) {
    if (mutableTags.has(tag)) {
      blockers.push(
        blocker({
          type: "safety_blocker",
          scope: "tag-policy",
          summary: `mutable tag ${tag} is not allowed for DEV artifact identity`,
          next: "Use the immutable git commit tag generated by this script."
        })
      );
    }
  }

  if (buildRequired) {
    const buildkitPreflight = await buildkitCommandPreflight(args.buildkitCommand);
    if (!buildkitPreflight.ok) {
      blockers.push(
        blocker({
          type: "environment_blocker",
          scope: "buildkit-command",
          summary: `BuildKit command ${args.buildkitCommand} is not available in the publish task image.`,
          next: "Use the node Tekton BuildKit path that prepares buildctl/buildkitd before running --publish."
        })
      );
    }
  }

  for (const service of services) {
    if (!sourceStates.has(service.sourceState)) {
      blockers.push(sourceStateBlocker(service));
    } else if (service.sourceState === "intentionally-disabled") {
      blockers.push(
        blocker({
          type: "runtime_blocker",
          scope: service.serviceId,
          summary: `${service.serviceId} is intentionally disabled for MVP artifact publication.`,
          next: `Keep ${service.serviceId} out of the MVP artifact catalog, or add a source entrypoint before publishing it.`
        })
      );
    } else if (service.artifactRequired && service.implementationState === "missing-runtime-entrypoint") {
      blockers.push(
        blocker({
          type: "runtime_blocker",
          scope: service.serviceId,
          summary: `${service.serviceId} has no runtime entrypoint; a health-only placeholder image was not built as a real implementation.`,
          next: `Add cmd/${service.serviceId}/main.mjs, cmd/${service.serviceId}/main.ts, or a dedicated Dockerfile for ${service.serviceId}.`
        })
      );
    }
  }

  return blockers;
}

function hasFatalBlocker(blockers) {
  return blockers.some((item) => fatalBlockerTypes.has(item.type));
}

async function buildService({ args, repo, commitId, shortCommit, service, buildCreatedAt }) {
  if (service.envReuse) return buildEnvReuseService({ args, repo, commitId, service, buildCreatedAt });
  const tag = shortCommit;
  const ref = imageRef(args.registryPrefix, service.serviceId, tag);
  const buildSource = `${repo}@${commitId}`;
  if (!buildableImplementationStates.has(service.implementationState)) {
    return {
      ...service,
      image: ref,
      imageTag: tag,
      buildCreatedAt,
      buildSource,
      status: "blocked_missing_runtime",
      digest: "not_published"
    };
  }

  if (service.runtimeKind === "cloud-web") {
    const check = await run(bunExecutable(), ["run", "--cwd", "web/hwlab-cloud-web", "check"]);
    if (check.code !== 0) {
      return {
        ...service,
        image: ref,
        imageTag: tag,
        buildCreatedAt,
        buildSource,
        status: "build_failed",
        digest: "not_published",
        blocker: blocker({
          type: "environment_blocker",
          scope: service.serviceId,
          summary: "cloud web source check failed before BuildKit image build",
          next: "Run bun run --cwd web/hwlab-cloud-web check, fix TypeScript/unit/build failures, then rerun --publish."
        }),
        logTail: tailText(`${check.stdout}\n${check.stderr}`),
        cloudWebCheckDurationMs: check.durationMs
      };
    }

    service = {
      ...service,
      cloudWebCheckDurationMs: check.durationMs
    };
  }

  const port = servicePorts.get(service.serviceId) ?? 8080;
  const labels = [
    ["org.opencontainers.image.source", "https://github.com/pikasTech/HWLAB"],
    ["org.opencontainers.image.revision", commitId],
    ["org.opencontainers.image.created", buildCreatedAt],
    ["org.opencontainers.image.title", service.serviceId],
    ["hwlab.pikastech.local/repo", repo],
    ["hwlab.pikastech.local/commit", commitId],
    ["hwlab.pikastech.local/build-created-at", buildCreatedAt],
    ["hwlab.pikastech.local/build-source", buildSource],
    ["hwlab.pikastech.local/service-id", service.serviceId],
    ["hwlab.pikastech.local/environment", ENVIRONMENT_DEV]
  ];
  const envs = [
    ["HWLAB_ENVIRONMENT", ENVIRONMENT_DEV],
    ["HWLAB_SERVICE_ID", service.serviceId],
    ["HWLAB_ARTIFACT_KIND", service.runtimeKind],
    ["HWLAB_SERVICE_ENTRYPOINT", service.entrypoint ?? ""],
    ["HWLAB_COMMIT_ID", commitId],
    ["HWLAB_REVISION", commitId],
    ["HWLAB_IMAGE", ref],
    ["HWLAB_IMAGE_TAG", tag],
    ["HWLAB_IMAGE_DIGEST", "unknown"],
    ["HWLAB_BUILD_CREATED_AT", buildCreatedAt],
    ["HWLAB_BUILD_SOURCE", buildSource],
    ["HWLAB_BUILD_PROVENANCE", cliEntrypoint],
    ["PORT", String(port)],
    ["HWLAB_PORT", String(port)]
  ];
  assert.equal(args.buildBackend, "buildkit", "artifact publish must use BuildKit only");
  return buildServiceWithBuildkit({ args, service, ref, tag, buildCreatedAt, buildSource, port, labels, envs });
}

async function buildEnvReuseService({ args, repo, commitId, service, buildCreatedAt }) {
  const ref = envReuseImageRef(args.registryPrefix, service.serviceId, service.environmentInputHash);
  const buildSource = `${repo}@env:${service.environmentInputHash}`;
  const labels = [
    ["org.opencontainers.image.source", "https://github.com/pikasTech/HWLAB"],
    ["org.opencontainers.image.revision", commitId],
    ["org.opencontainers.image.created", buildCreatedAt],
    ["org.opencontainers.image.title", `${service.serviceId}-env`],
    ["hwlab.pikastech.local/runtime-mode", v02EnvReuseRuntimeMode],
    ["hwlab.pikastech.local/service-id", service.serviceId],
    ["hwlab.pikastech.local/environment-input-hash", service.environmentInputHash],
    ["hwlab.pikastech.local/build-created-at", buildCreatedAt]
  ];
  const artifact = await buildEnvReuseServiceWithBuildkit({ args, service, ref, buildCreatedAt, buildSource, labels });
  return withEnvReuseArtifactFields({ artifact, service, commitId, buildCreatedAt, buildSource });
}

async function buildEnvReuseServiceWithBuildkit({ args, service, ref, buildCreatedAt, buildSource, labels }) {
  const tmpDir = await mkdtemp(path.join(os.tmpdir(), `hwlab-env-reuse-${service.serviceId}-`));
  const metadataPath = path.join(tmpDir, "metadata.json");
  const dockerfilePath = path.join(tmpDir, "Dockerfile");
  const cacheRef = envReuseCacheRef(args.registryPrefix, service.serviceId);
  const registryOption = buildkitRegistryOption(args.registryPrefix);
  const buildkitAddrArgs = args.buildkitAddr ? ["--addr", args.buildkitAddr] : [];
  const startedAt = Date.now();
  try {
    await writeFile(dockerfilePath, envReuseDockerfile(args.baseImage, labels, service.envReuseRecipe, service));
    const argsList = [
      ...buildkitAddrArgs,
      "build",
      "--frontend", "dockerfile.v0",
      "--local", `context=${repoRoot}`,
      "--local", `dockerfile=${tmpDir}`,
      "--opt", "filename=Dockerfile",
      "--metadata-file", metadataPath,
      "--import-cache", `type=registry,ref=${cacheRef}${registryOption}`,
      "--export-cache", `type=registry,ref=${cacheRef},mode=max${registryOption}`,
      "--output", `type=image,name=${ref},push=true${registryOption}`
    ];
    if (!args.quietBuild || ciTimingEnabled()) argsList.push("--progress", "plain");
    const buildEnv = { ...process.env };
    if (!args.buildkitAddr) {
      buildEnv.BUILDKITD_FLAGS = process.env.BUILDKITD_FLAGS || "--oci-worker-no-process-sandbox";
      buildEnv.XDG_RUNTIME_DIR = process.env.XDG_RUNTIME_DIR || path.join(os.tmpdir(), "hwlab-buildkit-runtime");
      await mkdir(buildEnv.XDG_RUNTIME_DIR, { recursive: true });
    }
    const result = await run(args.buildkitCommand, argsList, { env: buildEnv });
    emitBuildkitTimingEvents({ service, result, timingEnabled: ciTimingEnabled(buildEnv) });
    if (result.code !== 0) {
      return {
        ...service,
        image: ref,
        imageTag: imageTagFromReference(ref),
        buildCreatedAt,
        buildSource,
        status: "build_failed",
        digest: "not_published",
        blocker: blocker({
          type: "environment_blocker",
          scope: service.serviceId,
          summary: `BuildKit env image build failed for ${service.serviceId}`,
          next: "Inspect the env image BuildKit output and base image git/bun availability."
        }),
        logTail: tailText(`${result.stdout}\n${result.stderr}`, 12000),
        buildBackend: "buildkit-env-reuse",
        buildkitCacheRef: cacheRef,
        buildkitBuildDurationMs: result.durationMs
      };
    }
    let metadata = {};
    try {
      metadata = JSON.parse(await readFile(metadataPath, "utf8"));
    } catch {}
    const digest = buildkitDigestFromMetadata(metadata);
    if (!shaDigestPattern.test(digest ?? "")) {
      return {
        ...service,
        image: ref,
        imageTag: imageTagFromReference(ref),
        buildCreatedAt,
        buildSource,
        status: "published_unverified_digest",
        digest: "not_published",
        blocker: blocker({
          type: "environment_blocker",
          scope: service.serviceId,
          summary: `BuildKit completed env image for ${service.serviceId} but did not expose an immutable digest`,
          next: "Inspect BuildKit metadata output before updating the v02 env catalog."
        }),
        pushLogTail: tailText(`${result.stdout}\n${result.stderr}`, 1200),
        buildBackend: "buildkit-env-reuse",
        buildkitCacheRef: cacheRef,
        buildkitBuildDurationMs: Date.now() - startedAt
      };
    }
    return {
      ...service,
      image: ref,
      imageTag: imageTagFromReference(ref),
      buildCreatedAt,
      buildSource,
      status: args.mode === "publish" ? "published" : "built",
      digest,
      repositoryDigest: `${repositoryFromImageRef(ref)}@${digest}`,
      buildBackend: "buildkit-env-reuse",
      buildkitCacheRef: cacheRef,
      buildkitBuildDurationMs: Date.now() - startedAt,
      publishDurationMs: args.mode === "publish" ? Date.now() - startedAt : null,
      pushLogTail: tailText(`${result.stdout}\n${result.stderr}`, 1200)
    };
  } finally {
    await rm(tmpDir, { recursive: true, force: true });
  }
}

function withEnvReuseArtifactFields({ artifact, service, commitId, buildCreatedAt = null, buildSource = null }) {
  const environmentImage = artifact.image ?? service.environmentImage ?? null;
  const environmentDigest = artifact.digest ?? service.environmentDigest ?? "not_published";
  return {
    ...artifact,
    runtimeMode: v02EnvReuseRuntimeMode,
    envReuse: true,
    envChanged: service.envChanged ?? null,
    codeChanged: service.codeChanged ?? null,
    commitId,
    sourceCommitId: commitId,
    environmentImage,
    environmentDigest,
    environmentInputHash: service.environmentInputHash ?? null,
    codeInputHash: service.codeInputHash ?? null,
    bootRepo: service.bootRepo ?? "git@github.com:pikasTech/HWLAB.git",
    bootCommit: service.bootCommit ?? commitId,
    bootSh: service.bootSh ?? `deploy/runtime/boot/${service.serviceId}.sh`,
    image: environmentImage,
    imageTag: imageTagFromReference(environmentImage),
    digest: environmentDigest,
    buildCreatedAt: artifact.buildCreatedAt ?? buildCreatedAt,
    buildSource: artifact.buildSource ?? buildSource,
    bootEnvEvidence: bootEnvEvidence({ service, commitId, environmentImage, environmentDigest })
  };
}

function bootEnvEvidence({ service, commitId, environmentImage, environmentDigest }) {
  return {
    runtimeMode: v02EnvReuseRuntimeMode,
    environmentDigest: environmentDigest ?? null,
    environmentImage: environmentImage ?? null,
    bootRepo: service.bootRepo ?? "git@github.com:pikasTech/HWLAB.git",
    bootCommit: service.bootCommit ?? commitId,
    bootSh: service.bootSh ?? `deploy/runtime/boot/${service.serviceId}.sh`,
    env: {
      HWLAB_BOOT_REPO: service.bootRepo ?? "git@github.com:pikasTech/HWLAB.git",
      HWLAB_BOOT_COMMIT: service.bootCommit ?? commitId,
      HWLAB_BOOT_SH: service.bootSh ?? `deploy/runtime/boot/${service.serviceId}.sh`,
      HWLAB_ENVIRONMENT_IMAGE: environmentImage ?? "",
      HWLAB_ENVIRONMENT_DIGEST: environmentDigest ?? ""
    }
  };
}

async function buildServiceWithBuildkit({ args, service, ref, tag, buildCreatedAt, buildSource, port, labels, envs }) {
  const tmpDir = await mkdtemp(path.join(os.tmpdir(), `hwlab-buildkit-${service.serviceId}-`));
  const metadataPath = path.join(tmpDir, "metadata.json");
  const dockerfilePath = path.join(tmpDir, "Dockerfile");
  const cacheRef = buildkitCacheRef(args.registryPrefix, service.serviceId);
  const registryOption = buildkitRegistryOption(args.registryPrefix);
  const buildkitAddrArgs = args.buildkitAddr ? ["--addr", args.buildkitAddr] : [];
  const startedAt = Date.now();
  try {
    await writeFile(dockerfilePath, dockerfile(args.baseImage, port, labels));
    const argsList = [
      ...buildkitAddrArgs,
      "build",
      "--frontend", "dockerfile.v0",
      "--local", `context=${repoRoot}`,
      "--local", `dockerfile=${tmpDir}`,
      "--opt", "filename=Dockerfile",
      "--metadata-file", metadataPath,
      "--import-cache", `type=registry,ref=${cacheRef}${registryOption}`,
      "--export-cache", `type=registry,ref=${cacheRef},mode=max${registryOption}`,
      "--output", `type=image,name=${ref},push=true${registryOption}`
    ];
    if (!args.quietBuild || ciTimingEnabled()) argsList.push("--progress", "plain");
    for (const [name, value] of envs) argsList.push("--opt", `build-arg:${name}=${value}`);

    const buildEnv = { ...process.env };
    if (!args.buildkitAddr) {
      buildEnv.BUILDKITD_FLAGS = process.env.BUILDKITD_FLAGS || "--oci-worker-no-process-sandbox";
      buildEnv.XDG_RUNTIME_DIR = process.env.XDG_RUNTIME_DIR || path.join(os.tmpdir(), "hwlab-buildkit-runtime");
      await mkdir(buildEnv.XDG_RUNTIME_DIR, { recursive: true });
    }
    const result = await run(args.buildkitCommand, argsList, { env: buildEnv });
    emitBuildkitTimingEvents({ service, result, timingEnabled: ciTimingEnabled(buildEnv) });
    if (result.code !== 0) {
      return {
        ...service,
        image: ref,
        imageTag: tag,
        buildCreatedAt,
        buildSource,
        status: "build_failed",
        digest: "not_published",
        blocker: blocker({
          type: "environment_blocker",
          scope: service.serviceId,
          summary: `BuildKit build failed for ${service.serviceId}`,
          next: `Inspect the BuildKit output for ${service.serviceId}, then fix its copied source, base image, or registry cache settings.`
        }),
        logTail: tailText(`${result.stdout}\n${result.stderr}`, 12000),
        buildBackend: "buildkit",
        buildkitCacheRef: cacheRef,
        buildkitBuildDurationMs: result.durationMs
      };
    }

    let metadata = {};
    try {
      metadata = JSON.parse(await readFile(metadataPath, "utf8"));
    } catch {}
    const digest = buildkitDigestFromMetadata(metadata);
    if (!shaDigestPattern.test(digest ?? "")) {
      return {
        ...service,
        image: ref,
        imageTag: tag,
        buildCreatedAt,
        buildSource,
        status: "published_unverified_digest",
        digest: "not_published",
        blocker: blocker({
          type: "environment_blocker",
          scope: service.serviceId,
          summary: `BuildKit completed for ${service.serviceId} but did not expose an immutable digest`,
          next: "Inspect BuildKit metadata output before updating the artifact catalog."
        }),
        pushLogTail: tailText(`${result.stdout}\n${result.stderr}`, 1200),
        buildBackend: "buildkit",
        buildkitCacheRef: cacheRef,
        buildkitBuildDurationMs: Date.now() - startedAt
      };
    }

    return {
      ...service,
      image: ref,
      imageTag: tag,
      buildCreatedAt,
      buildSource,
      status: args.mode === "publish" ? "published" : "built",
      digest,
      repositoryDigest: `${repositoryFromImageRef(ref)}@${digest}`,
      buildBackend: "buildkit",
      buildkitCacheRef: cacheRef,
      buildkitBuildDurationMs: Date.now() - startedAt,
      publishDurationMs: args.mode === "publish" ? Date.now() - startedAt : null,
      pushLogTail: tailText(`${result.stdout}\n${result.stderr}`, 1200)
    };
  } finally {
    await rm(tmpDir, { recursive: true, force: true });
  }
}

async function runWithInput(command, args, input) {
  const startedAt = Date.now();
  const child = spawn(command, args, {
    cwd: repoRoot,
    env: process.env,
    stdio: ["pipe", "pipe", "pipe"]
  });
  child.stdin.end(input);

  let stdout = "";
  let stderr = "";
  child.stdout.on("data", (chunk) => {
    stdout += chunk;
  });
  child.stderr.on("data", (chunk) => {
    stderr += chunk;
  });

  const code = await new Promise((resolve) => {
    child.on("error", () => resolve(127));
    child.on("close", resolve);
  });

  return {
    command: commandLine(command, args),
    code,
    stdout,
    stderr,
    durationMs: Date.now() - startedAt
  };
}

function repositoryFromImageRef(image) {
  const lastColon = image.lastIndexOf(":");
  return lastColon === -1 ? image : image.slice(0, lastColon);
}

const publishReadyStatuses = new Set(["published", "reused"]);
const buildReadyStatuses = new Set(["built", "published", "published_unverified_digest", "reused"]);

function reportStatus(mode, artifacts, blockers) {
  const requiredArtifacts = artifacts.filter((artifact) => artifact.artifactRequired);
  if (mode === "preflight") return blockers.length ? "blocked" : "pass";
  if (requiredArtifacts.some((artifact) => artifact.status === "publish_failed" || artifact.status === "build_failed")) return "failed";
  if (requiredArtifacts.some((artifact) => artifact.status.startsWith("blocked_"))) return "blocked";
  if (mode === "publish" && requiredArtifacts.length > 0 && requiredArtifacts.every((artifact) => publishReadyStatuses.has(artifact.status))) return "published";
  if (mode === "build" && requiredArtifacts.length > 0 && requiredArtifacts.every((artifact) => buildReadyStatuses.has(artifact.status))) return "built";
  return blockers.length ? "blocked" : "pass";
}

function devGateBlockers(blockers) {
  const seen = new Set();
  const result = [];
  for (const item of blockers) {
    const key = `${item.type}::${item.scope}`;
    if (seen.has(key)) continue;
    seen.add(key);
    result.push({
      type: item.type,
      scope: item.scope,
      status: item.status,
      summary: item.summary
    });
  }
  return result;
}

function artifactNotPublishedReason(artifact, mode, fatalBlocked) {
  if (/^sha256:[a-f0-9]{64}$/u.test(artifact.digest ?? "")) return null;
  if (!artifact.artifactRequired) return artifact.notPublishedReason;
  if (artifact.status === "preflight_only") return fatalBlocked ? "blocked_by_preflight" : "preflight_only";
  if (artifact.status === "built") return "build_only_not_published";
  if (artifact.status === "build_failed") return "build_failed";
  if (artifact.status === "publish_failed") return "publish_failed";
  if (artifact.status === "published_unverified_digest") return "digest_unverified";
  return artifact.notPublishedReason ?? "publish_not_run";
}

function imageTagFromReference(image) {
  const value = String(image ?? "");
  const slashIndex = value.lastIndexOf("/");
  const colonIndex = value.lastIndexOf(":");
  return colonIndex > slashIndex ? value.slice(colonIndex + 1) : null;
}

function repositoryDigestFor(image, digest) {
  return image && shaDigestPattern.test(digest ?? "") ? `${repositoryFromImageRef(image)}@${digest}` : null;
}

function serviceEnvKey(serviceId) {
  return String(serviceId).toUpperCase().replace(/[^A-Z0-9]+/gu, "_");
}

function serviceResultEnvName(serviceId, field) {
  return `HWLAB_SERVICE_RESULT_${serviceEnvKey(serviceId)}_${field}`;
}

function serviceResultValue(env, serviceId, field) {
  const value = env[serviceResultEnvName(serviceId, field)];
  return typeof value === "string" && value.length > 0 ? value : null;
}

async function writeTektonResultFiles(dir, report) {
  const service = report?.artifactPublish?.services?.[0];
  if (!service) return;
  const values = {
    "service-id": service.serviceId,
    status: service.status,
    image: service.image,
    "image-tag": service.imageTag,
    digest: service.digest,
    "repository-digest": service.repositoryDigest ?? repositoryDigestFor(service.image, service.digest) ?? "",
    "source-commit-id": service.sourceCommitId,
    "component-input-hash": service.componentInputHash ?? "",
    "environment-input-hash": service.environmentInputHash ?? "",
    "code-input-hash": service.codeInputHash ?? "",
    "runtime-mode": service.runtimeMode ?? "service-image",
    "boot-repo": service.bootRepo ?? "",
    "boot-commit": service.bootCommit ?? "",
    "boot-sh": service.bootSh ?? "",
    "build-created-at": service.buildCreatedAt ?? "",
    "build-backend": report.artifactPublish.buildBackend ?? "unknown",
    "reused-from": service.reusedFrom ?? ""
  };
  await mkdir(dir, { recursive: true });
  await Promise.all(Object.entries(values).map(([name, value]) => writeFile(path.join(dir, name), String(value ?? ""))));
}

function artifactRecord({ args, service, commitId, shortCommit, buildCreatedAt = null, buildSource = null, status, digest = "not_published" }) {
  if (service.envReuse) {
    const environmentImage = service.environmentImage ?? envReuseImageRef(args.registryPrefix, service.serviceId, service.environmentInputHash ?? commitId);
    const environmentDigest = service.environmentDigest ?? digest;
    return withEnvReuseArtifactFields({
      artifact: {
        ...service,
        commitId,
        sourceCommitId: commitId,
        status,
        image: environmentImage,
        imageTag: imageTagFromReference(environmentImage),
        buildCreatedAt,
        buildSource,
        digest: environmentDigest,
        repositoryDigest: repositoryDigestFor(environmentImage, environmentDigest)
      },
      service,
      commitId,
      buildCreatedAt,
      buildSource
    });
  }
  return {
    ...service,
    commitId: shortCommit,
    sourceCommitId: commitId,
    status,
    image: imageRef(args.registryPrefix, service.serviceId, shortCommit),
    imageTag: shortCommit,
    buildCreatedAt,
    buildSource,
    digest,
    repositoryDigest: null
  };
}

function artifactRecordForCatalogReuse({ service, catalogService, commitId }) {
  if (service.envReuse) {
    return artifactRecordForEnvReuseCatalogReuse({ service, catalogService, commitId });
  }
  const image = catalogService?.image ?? null;
  const digest = catalogService?.digest ?? "not_published";
  const imageTag = catalogService?.imageTag ?? imageTagFromReference(image);
  const catalogCommitId = catalogService?.commitId ?? imageTag ?? null;
  const catalogProvenance = serviceImageCatalogProvenance(catalogService);
  if (!image || !imageTag || !shaDigestPattern.test(digest)) {
    return {
      ...service,
      commitId: catalogCommitId,
      sourceCommitId: catalogService?.sourceCommitId ?? catalogCommitId ?? commitId,
      status: "blocked_reuse_unavailable",
      image: image ?? null,
      imageTag,
      buildCreatedAt: catalogService?.buildCreatedAt ?? null,
      buildSource: catalogService?.buildSource ?? null,
      digest,
      repositoryDigest: repositoryDigestFor(image, digest),
      ...catalogProvenance,
      buildBackend: "reused-catalog",
      reusedFrom: null,
      notPublishedReason: "reuse_catalog_digest_unavailable"
    };
  }
  return {
    ...service,
    commitId: catalogCommitId,
    sourceCommitId: catalogService?.sourceCommitId ?? catalogCommitId ?? commitId,
    status: "reused",
    image,
    imageTag,
    buildCreatedAt: catalogService?.buildCreatedAt ?? null,
    buildSource: catalogService?.buildSource ?? null,
    digest,
    repositoryDigest: catalogService?.repositoryDigest ?? repositoryDigestFor(image, digest),
    ...catalogProvenance,
    buildBackend: "reused-catalog",
    reusedFrom: service.reuse?.reusedFrom ?? catalogService?.componentInputHash ?? catalogCommitId,
    notPublishedReason: null
  };
}

function serviceImageCatalogProvenance(catalogService) {
  return {
    componentCommitId: catalogService?.componentCommitId ?? null,
    componentInputHash: catalogService?.componentInputHash ?? null,
    dockerfileHash: catalogService?.dockerfileHash ?? null,
    baseImageReference: catalogService?.baseImageReference ?? null,
    baseImageDigest: catalogService?.baseImageDigest ?? null,
    buildArgsHash: catalogService?.buildArgsHash ?? null
  };
}

function artifactRecordForEnvReuseCatalogReuse({ service, catalogService, commitId }) {
  const environmentImage = catalogService?.environmentImage ?? service.environmentImage ?? null;
  const environmentDigest = catalogService?.environmentDigest ?? service.environmentDigest ?? "not_published";
  const imageTag = imageTagFromReference(environmentImage);
  const bootCommit = service.codeChanged === true || service.envChanged === true
    ? service.bootCommit ?? commitId
    : catalogService?.bootCommit ?? service.bootCommit ?? commitId;
  const base = {
    ...service,
    runtimeMode: v02EnvReuseRuntimeMode,
    envReuse: true,
    envChanged: service.envChanged ?? null,
    codeChanged: service.codeChanged ?? null,
    commitId: bootCommit,
    sourceCommitId: bootCommit,
    image: environmentImage,
    imageTag,
    digest: environmentDigest,
    environmentImage,
    environmentDigest,
    environmentInputHash: service.environmentInputHash ?? catalogService?.environmentInputHash ?? null,
    codeInputHash: service.codeInputHash ?? null,
    bootRepo: service.bootRepo ?? catalogService?.bootRepo ?? "git@github.com:pikasTech/HWLAB.git",
    bootCommit,
    bootSh: service.bootSh ?? catalogService?.bootSh ?? `deploy/runtime/boot/${service.serviceId}.sh`,
    buildCreatedAt: catalogService?.buildCreatedAt ?? null,
    buildSource: catalogService?.buildSource ?? null,
    buildBackend: "reused-env-catalog",
    repositoryDigest: catalogService?.repositoryDigest ?? repositoryDigestFor(environmentImage, environmentDigest),
    bootEnvEvidence: bootEnvEvidence({ service: { ...service, bootCommit }, commitId: bootCommit, environmentImage, environmentDigest })
  };
  if (!environmentImage || !imageTag || !shaDigestPattern.test(environmentDigest)) {
    return {
      ...base,
      status: "blocked_reuse_unavailable",
      reusedFrom: null,
      notPublishedReason: "environment_reuse_digest_unavailable"
    };
  }
  return {
    ...base,
    status: "reused",
    reusedFrom: service.reuse?.reusedFrom ?? catalogService?.environmentInputHash ?? catalogService?.componentInputHash ?? imageTag,
    notPublishedReason: null
  };
}

function externalServiceReportPath(dir, serviceId) {
  const base = path.isAbsolute(dir) ? dir : path.join(repoRoot, dir);
  return path.join(base, `${serviceId}.json`);
}

function artifactRecordFromExternalReport({ service, record, reportPath }) {
  const image = record?.image ?? null;
  const digest = record?.digest ?? "not_published";
  const imageTag = record?.imageTag ?? imageTagFromReference(image);
  return {
    ...service,
    ...record,
    serviceId: service.serviceId,
    runtimeKind: record?.runtimeKind ?? service.runtimeKind,
    implementationState: record?.implementationState ?? service.implementationState,
    sourceState: record?.sourceState ?? service.sourceState,
    entrypoint: record?.entrypoint ?? service.entrypoint,
    publishEnabled: record?.publishEnabled ?? service.publishEnabled,
    artifactRequired: record?.artifactRequired ?? service.artifactRequired,
    artifactScope: record?.artifactScope ?? service.artifactScope,
    image,
    imageTag,
    digest,
    repositoryDigest: record?.repositoryDigest ?? repositoryDigestFor(image, digest),
    externalReportPath: reportPath,
    notPublishedReason: record?.notPublishedReason ?? null
  };
}

async function readExternalServiceArtifacts({ dir, services, commitId }) {
  const artifacts = new Map();
  const blockers = [];
  for (const service of services) {
    const buildRequired = service.buildRequired === true || service.ciBuildRequired === true;
    const reportPath = externalServiceReportPath(dir, service.serviceId);
    let report;
    try {
      report = JSON.parse(await readFile(reportPath, "utf8"));
    } catch (error) {
      if (!buildRequired) {
        continue;
      }
      blockers.push(blocker({
        type: "environment_blocker",
        scope: service.serviceId,
        summary: `missing per-service artifact report for ${service.serviceId}: ${error.message}`,
        next: `Ensure the Tekton build-${service.serviceId} TaskRun writes ${reportPath} before collect-artifacts runs.`
      }));
      continue;
    }

    const record = report?.artifactPublish?.services?.find((item) => item?.serviceId === service.serviceId);
    if (!record) {
      blockers.push(blocker({
        type: "contract_blocker",
        scope: service.serviceId,
        summary: `per-service artifact report ${reportPath} does not contain ${service.serviceId}`,
        next: "Regenerate the service artifact report with --services set to the same service ID."
      }));
      continue;
    }

    const artifact = artifactRecordFromExternalReport({ service, record, reportPath });
    artifacts.set(service.serviceId, artifact);
    if (!publishReadyStatuses.has(artifact.status) || !shaDigestPattern.test(artifact.digest ?? "")) {
      blockers.push(blocker({
        type: "environment_blocker",
        scope: service.serviceId,
        summary: `per-service artifact report ${reportPath} is not a verified published/reused image for ${service.serviceId}`,
        next: "Inspect the corresponding service TaskRun logs and rerun the failed service build."
      }));
    }
    if (artifact.sourceCommitId && artifact.sourceCommitId !== commitId) {
      blockers.push(blocker({
        type: "contract_blocker",
        scope: service.serviceId,
        summary: `per-service artifact report ${reportPath} was produced for ${artifact.sourceCommitId}, expected ${commitId}`,
        next: "Discard stale service reports and rerun the current PipelineRun."
      }));
    }
  }
  return { artifacts, blockers };
}

function artifactRecordFromServiceResultsEnv({ service, env }) {
  const status = serviceResultValue(env, service.serviceId, "STATUS");
  if (!status) return null;
  const image = serviceResultValue(env, service.serviceId, "IMAGE");
  const digest = serviceResultValue(env, service.serviceId, "DIGEST") ?? "not_published";
  const imageTag = serviceResultValue(env, service.serviceId, "IMAGE_TAG") ?? imageTagFromReference(image);
  const reusedServiceImage = status === "reused" && service.envReuse !== true;
  const artifact = {
    ...service,
    serviceId: service.serviceId,
    status,
    image,
    imageTag,
    digest,
    repositoryDigest: serviceResultValue(env, service.serviceId, "REPOSITORY_DIGEST") ?? repositoryDigestFor(image, digest),
    sourceCommitId: serviceResultValue(env, service.serviceId, "SOURCE_COMMIT_ID"),
    commitId: imageTag,
    buildCreatedAt: serviceResultValue(env, service.serviceId, "BUILD_CREATED_AT"),
    componentInputHash: serviceResultValue(env, service.serviceId, "COMPONENT_INPUT_HASH") ?? (reusedServiceImage ? null : service.componentInputHash ?? null),
    environmentInputHash: serviceResultValue(env, service.serviceId, "ENVIRONMENT_INPUT_HASH") ?? service.environmentInputHash ?? null,
    codeInputHash: serviceResultValue(env, service.serviceId, "CODE_INPUT_HASH") ?? service.codeInputHash ?? null,
    runtimeMode: serviceResultValue(env, service.serviceId, "RUNTIME_MODE") ?? service.runtimeMode ?? "service-image",
    bootRepo: serviceResultValue(env, service.serviceId, "BOOT_REPO") ?? service.bootRepo ?? null,
    bootCommit: serviceResultValue(env, service.serviceId, "BOOT_COMMIT") ?? service.bootCommit ?? null,
    bootSh: serviceResultValue(env, service.serviceId, "BOOT_SH") ?? service.bootSh ?? null,
    reusedFrom: serviceResultValue(env, service.serviceId, "REUSED_FROM"),
    buildBackend: serviceResultValue(env, service.serviceId, "BUILD_BACKEND"),
    ...(reusedServiceImage ? {
      componentCommitId: null,
      dockerfileHash: null,
      baseImageReference: null,
      baseImageDigest: null,
      buildArgsHash: null
    } : {}),
    notPublishedReason: null
  };
  if (service.envReuse) {
    return withEnvReuseArtifactFields({ artifact, service, commitId: artifact.sourceCommitId || service.bootCommit || "" });
  }
  return artifact;
}

async function readExternalServiceArtifactsFromEnv({ services, commitId, env = process.env }) {
  const artifacts = new Map();
  const blockers = [];
  for (const service of services) {
    const artifact = artifactRecordFromServiceResultsEnv({ service, env });
    if (!artifact) {
      blockers.push(blocker({
        type: "environment_blocker",
        scope: service.serviceId,
        summary: `missing Tekton result env for ${service.serviceId}`,
        next: `Ensure build-${service.serviceId} writes Tekton results and collect-artifacts passes them as HWLAB_SERVICE_RESULT_${serviceEnvKey(service.serviceId)}_* env vars.`
      }));
      continue;
    }
    artifacts.set(service.serviceId, artifact);
    if (!publishReadyStatuses.has(artifact.status) || !shaDigestPattern.test(artifact.digest ?? "")) {
      blockers.push(blocker({
        type: "environment_blocker",
        scope: service.serviceId,
        summary: `Tekton result for ${service.serviceId} is not a verified published/reused image`,
        next: "Inspect the corresponding build task results and registry digest before collecting artifacts."
      }));
    }
    if (artifact.status === "published" && artifact.sourceCommitId && artifact.sourceCommitId !== commitId) {
      blockers.push(blocker({
        type: "contract_blocker",
        scope: service.serviceId,
        summary: `Tekton result for ${service.serviceId} was produced for ${artifact.sourceCommitId}, expected ${commitId}`,
        next: "Discard stale PipelineRun results and rerun the current source commit."
      }));
    }
  }
  return { artifacts, blockers };
}

function publishPlanFor({ args, commitId, shortCommit, mode, artifacts, fatalBlocked, registryCapabilities }) {
  return {
    version: "v2",
    mode,
    sourceCommitId: commitId,
    imageTag: shortCommit,
    registryTarget: args.registryPrefix,
    registryCapabilities: summarizeRegistryCapabilities(registryCapabilities),
    digestPlaceholder: "not_published",
    services: artifacts.map((artifact) => {
      const image = artifact.image ?? imageRef(args.registryPrefix, artifact.serviceId, shortCommit);
      const digest = artifact.digest ?? "not_published";
      return {
        serviceId: artifact.serviceId,
        enabled: artifact.publishEnabled,
        required: artifact.artifactRequired,
        artifactScope: artifact.artifactScope,
        commitId: artifact.commitId ?? shortCommit,
        sourceCommitId: artifact.sourceCommitId ?? commitId,
        registryTarget: imageRepository(args.registryPrefix, artifact.serviceId),
        image,
        imageTag: artifact.imageTag ?? shortCommit,
        buildCreatedAt: artifact.buildCreatedAt ?? null,
        buildSource: artifact.buildSource ?? null,
        digest,
        repositoryDigest: artifact.repositoryDigest ?? null,
        digestStatus: /^sha256:[a-f0-9]{64}$/u.test(digest) ? "real" : "placeholder",
        status: artifact.status,
        cloudWebBuildDurationMs: artifact.cloudWebBuildDurationMs ?? null,
        publishDurationMs: artifact.publishDurationMs ?? null,
        componentCommitId: artifact.componentCommitId ?? null,
        componentInputHash: artifact.componentInputHash ?? null,
        dockerfileHash: artifact.dockerfileHash ?? null,
        baseImageReference: artifact.baseImageReference ?? null,
        baseImageDigest: artifact.baseImageDigest ?? null,
        buildArgsHash: artifact.buildArgsHash ?? null,
        ciAffected: artifact.ciAffected ?? null,
        ciReason: artifact.ciReason ?? [],
        runtimeMode: artifact.runtimeMode ?? "service-image",
        envReuse: artifact.envReuse === true,
        envChanged: artifact.envChanged ?? null,
        codeChanged: artifact.codeChanged ?? null,
        environmentImage: artifact.environmentImage ?? null,
        environmentDigest: artifact.environmentDigest ?? null,
        environmentInputHash: artifact.environmentInputHash ?? null,
        codeInputHash: artifact.codeInputHash ?? null,
        bootRepo: artifact.bootRepo ?? null,
        bootCommit: artifact.bootCommit ?? null,
        bootSh: artifact.bootSh ?? null,
        bootEnvEvidence: artifact.bootEnvEvidence ?? null,
        reuse: artifact.reuse ?? null,
        reusedFrom: artifact.reusedFrom ?? null,
        notPublishedReason: artifactNotPublishedReason(artifact, mode, fatalBlocked)
      };
    })
  };
}

function plannerFieldsFor(servicePlan) {
  if (!servicePlan) return {};
  return {
    componentCommitId: servicePlan.componentCommitId ?? null,
    componentInputHash: servicePlan.componentInputHash ?? null,
    runtimeMode: servicePlan.runtimeMode ?? "service-image",
    envReuse: servicePlan.envReuse === true,
    envReuseRecipe: servicePlan.envReuseRecipe ?? null,
    envChanged: servicePlan.envChanged ?? null,
    codeChanged: servicePlan.codeChanged ?? null,
    environmentInputHash: servicePlan.environmentInputHash ?? null,
    codeInputHash: servicePlan.codeInputHash ?? null,
    bootRepo: servicePlan.bootRepo ?? null,
    bootCommit: servicePlan.bootCommit ?? null,
    bootSh: servicePlan.bootSh ?? null,
    buildRequired: servicePlan.buildRequired ?? servicePlan.affected ?? false,
    environmentImage: servicePlan.environmentImage ?? null,
    environmentDigest: servicePlan.environmentDigest ?? null,
    dockerfileHash: servicePlan.dockerfileHash ?? null,
    baseImageReference: servicePlan.baseImageReference ?? null,
    baseImageDigest: servicePlan.baseImageDigest ?? null,
    buildArgsHash: servicePlan.buildArgsHash ?? null,
    ciAffected: servicePlan.affected ?? false,
    ciReason: servicePlan.reason ?? [],
    reuse: servicePlan.reuse ?? null
  };
}

function enrichServicesWithCiPlan(services, ciPlan) {
  const byServiceId = new Map((ciPlan?.services ?? []).map((service) => [service.serviceId, service]));
  return services.map((service) => ({
    ...service,
    ...plannerFieldsFor(byServiceId.get(service.serviceId))
  }));
}

function ciPlanReportSummary(ciPlan) {
  if (!ciPlan) return null;
  return {
    planVersion: ciPlan.planVersion,
    sourceCommitId: ciPlan.sourceCommitId,
    shortCommitId: ciPlan.shortCommitId,
    baseRef: ciPlan.baseRef,
    targetRef: ciPlan.targetRef,
    compatibility: ciPlan.compatibility,
    changedPathSummary: ciPlan.changedPathSummary,
    imageBuildRequired: ciPlan.imageBuildRequired,
    affectedServices: ciPlan.affectedServices,
    rolloutServices: ciPlan.rolloutServices ?? ciPlan.affectedServices,
    buildServices: ciPlan.buildServices ?? ciPlan.affectedServices,
    reusedServices: ciPlan.reusedServices,
    buildSkippedCount: ciPlan.buildSkippedCount,
    ciCdPlan: ciPlan.ciCdPlan
  };
}

function sumDurations(artifacts, field) {
  return artifacts.reduce((sum, artifact) => sum + (Number.isFinite(artifact[field]) ? artifact[field] : 0), 0);
}

async function mapWithConcurrency(items, concurrency, worker) {
  const results = new Array(items.length);
  let nextIndex = 0;
  const workerCount = Math.min(Math.max(concurrency, 1), items.length);
  await Promise.all(Array.from({ length: workerCount }, async () => {
    while (nextIndex < items.length) {
      const index = nextIndex;
      nextIndex += 1;
      results[index] = await worker(items[index], index);
    }
  }));
  return results;
}

function createReport({ args, repo, commitId, shortCommit, mode, services, artifacts, blockers, baseImagePreflight, registryCapabilities, producer, ciPlan }) {
  const status = reportStatus(mode, artifacts, blockers);
  const fatalBlocked = hasFatalBlocker(blockers);
  const requiredArtifacts = artifacts.filter((artifact) => artifact.artifactRequired);
  const serviceInventory = serviceInventoryFromServices(services);
  const publishPlan = publishPlanFor({ args, commitId, shortCommit, mode, artifacts, fatalBlocked, registryCapabilities });
  const publishedCount = requiredArtifacts.filter((artifact) => artifact.status === "published").length;
  const reusedCount = requiredArtifacts.filter((artifact) => artifact.status === "reused").length;
  const builtCount = requiredArtifacts.filter((artifact) => buildReadyStatuses.has(artifact.status)).length;
  const environment = artifactEnvironment(args);

  return {
    $schema: "https://hwlab.pikastech.local/schemas/artifact-publish.schema.json",
    $id: `https://hwlab.pikastech.local/${args.lane}-cicd/artifacts.json`,
    reportVersion: "v1",
    issue: args.lane === "v02" ? "pikasTech/HWLAB#530" : "pikasTech/HWLAB#35",
    taskId: `${args.lane}-artifact-publish`,
    commitId: shortCommit,
    acceptanceLevel: `${environment}_artifact_publish`,
    lane: args.lane,
    devOnly: args.lane !== "v02",
    prodDisabled: true,
    reportLifecycle: activeReportLifecycle(`Current ${environment} artifact publish report; it is not runtime acceptance evidence.`),
    sourceContract: {
      status: fatalBlocked ? "blocked" : "pass",
      documents: [
        "docs/reference/MVP-e2e-acceptance.md",
        "docs/reference/spec-v02-documentation-governance.md",
        "docs/reference/node-gitops-cicd.md",
        "docs/reference/spec-v02-cicd.md"
      ],
      summary: `${environment} artifact publish evidence; runtime acceptance stays separate from artifact publication.`
    },
    validationCommands: [
      "node --check scripts/artifact-publish.mjs",
      "node --check scripts/artifact-publish.mjs",
      "node --check scripts/src/dev-artifact-services.mjs",
      "node --check scripts/src/registry-capabilities.mjs",
      "node --check scripts/preflight-dev-base-image.mjs",
      "node --check scripts/src/dev-base-image-preflight.mjs",
      "node --check scripts/dev-runtime-base-image.mjs",
      "node --test scripts/refresh-artifact-catalog.test.mjs",
      "node scripts/preflight-dev-base-image.mjs",
      "node scripts/artifact-publish.mjs --preflight --no-report",
      "node --check scripts/validate-dev-gate-report.mjs",
      "node scripts/validate-dev-gate-report.mjs"
    ],
    localSmoke: {
      status: "not_run",
      commands: [],
      evidence: [
        "This report records artifact build/publish state only; local smoke remains a separate gate."
      ],
      summary: "Local smoke is not implied by artifact publication."
    },
    dryRun: {
      status: "not_run",
      commands: [],
      evidence: [
        "This report records DEV artifact build/publish state only; hwlab-cli is not an artifact service."
      ],
      summary: "DEV deploy dry-run is not implied by artifact publication."
    },
    devPreconditions: {
      status: fatalBlocked ? "blocked" : "pass",
      requirements: [
        "Registry prefix is localhost or private/internal only.",
        "DEV builder base image preflight returns ready before build or publish.",
        `Environment remains ${environment} and namespace remains ${args.lane === "v02" ? "hwlab-v02" : "hwlab-dev"}.`,
        "PROD profile remains disabled.",
        "No secret or token material is required by this workflow."
      ],
      summary: fatalBlocked
        ? `Fatal preconditions blocked a full ${environment} artifact publish.`
        : `Fatal ${environment} artifact preconditions passed.`
    },
    blockers: devGateBlockers(blockers),
    artifactPublish: {
      issue: args.lane === "v02" ? "pikasTech/HWLAB#530" : "pikasTech/HWLAB#35",
      status,
      mode,
      lane: args.lane,
      catalogPath: args.catalogPath,
      imageTagMode: args.imageTagMode,
      repo,
      sourceCommitId: commitId,
      registryPrefix: args.registryPrefix,
      buildBackend: args.buildBackend,
      buildkitCommand: args.buildkitCommand,
      buildkitAddr: args.buildkitAddr ?? null,
      producer,
      registryCapabilities: summarizeRegistryCapabilities(registryCapabilities),
      baseImage: args.baseImage ?? null,
      selectedServiceIds: args.services,
      affectedOnly: args.affectedOnly,
      externalServiceReportDir: args.externalServiceReportDir,
      externalServiceResultsEnv: args.externalServiceResultsEnv,
      quietBuild: args.quietBuild,
      concurrency: args.concurrency,
      baseImagePreflight: summarizeBaseImagePreflight(baseImagePreflight),
      environment,
      buildCreatedAt: artifacts.find((artifact) => artifact.buildCreatedAt)?.buildCreatedAt ?? null,
      serviceInventory,
      ciPlan: ciPlanReportSummary(ciPlan),
      publishPlan,
      serviceCount: artifacts.length,
      requiredServiceCount: serviceInventory.requiredServiceCount,
      disabledServiceCount: serviceInventory.disabledServiceCount,
      builtCount,
      publishedCount,
      reusedCount,
      timings: {
        buildkitBuildDurationMs: sumDurations(artifacts, "buildkitBuildDurationMs"),
        cloudWebBuildDurationMs: sumDurations(artifacts, "cloudWebBuildDurationMs"),
        publishDurationMs: sumDurations(artifacts, "publishDurationMs")
      },
      generatedAt: new Date().toISOString(),
      services: artifacts.map((artifact) => ({
        serviceId: artifact.serviceId,
        status: artifact.status,
        commitId: artifact.commitId ?? shortCommit,
        sourceCommitId: artifact.sourceCommitId ?? commitId,
        image: artifact.image ?? imageRef(args.registryPrefix, artifact.serviceId, shortCommit),
        imageTag: artifact.imageTag ?? shortCommit,
        buildCreatedAt: artifact.buildCreatedAt ?? null,
        buildSource: artifact.buildSource ?? null,
        digest: artifact.digest ?? "not_published",
        repositoryDigest: artifact.repositoryDigest ?? null,
        runtimeKind: artifact.runtimeKind,
        distFreshness: artifact.distFreshness,
        implementationState: artifact.implementationState,
        sourceState: artifact.sourceState,
        entrypoint: artifact.entrypoint,
        publishEnabled: artifact.publishEnabled,
        artifactRequired: artifact.artifactRequired,
        artifactScope: artifact.artifactScope,
        notPublishedReason: artifactNotPublishedReason(artifact, mode, fatalBlocked),
        localImageId: artifact.localImageId,
        buildkitBuildDurationMs: artifact.buildkitBuildDurationMs ?? null,
        buildBackend: artifact.buildBackend ?? args.buildBackend,
        buildkitCacheRef: artifact.buildkitCacheRef ?? null,
        cloudWebBuildDurationMs: artifact.cloudWebBuildDurationMs ?? null,
        publishDurationMs: artifact.publishDurationMs ?? null,
        componentCommitId: artifact.componentCommitId ?? null,
        componentInputHash: artifact.componentInputHash ?? null,
        dockerfileHash: artifact.dockerfileHash ?? null,
        baseImageReference: artifact.baseImageReference ?? null,
        baseImageDigest: artifact.baseImageDigest ?? null,
        buildArgsHash: artifact.buildArgsHash ?? null,
        ciAffected: artifact.ciAffected ?? null,
        ciReason: artifact.ciReason ?? [],
        runtimeMode: artifact.runtimeMode ?? "service-image",
        envReuse: artifact.envReuse === true,
        envChanged: artifact.envChanged ?? null,
        codeChanged: artifact.codeChanged ?? null,
        environmentImage: artifact.environmentImage ?? null,
        environmentDigest: artifact.environmentDigest ?? null,
        environmentInputHash: artifact.environmentInputHash ?? null,
        codeInputHash: artifact.codeInputHash ?? null,
        bootRepo: artifact.bootRepo ?? null,
        bootCommit: artifact.bootCommit ?? null,
        bootSh: artifact.bootSh ?? null,
        bootEnvEvidence: artifact.bootEnvEvidence ?? null,
        reuse: artifact.reuse ?? null,
        reusedFrom: artifact.reusedFrom ?? null,
        logTail: artifact.logTail ?? null,
        pushLogTail: artifact.pushLogTail ?? null
      })),
      blockers
    },
    notes: `${environment} artifact report. Do not treat runtime placeholder images as real service implementations.`
  };
}

async function writeReport(relativePath, report) {
  const absolutePath = ensureNotRepoReportsPath(repoRoot, relativePath, "--report");
  await mkdir(path.dirname(absolutePath), { recursive: true });
  await writeFile(absolutePath, `${JSON.stringify(report, null, 2)}\n`);
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help) {
    printHelp();
    return;
  }
  if (args.mode === "publish") {
    const identityGuard = requireArtifactPublishIdentity({
      script: cliEntrypoint,
      mode: "--publish"
    });
    if (identityGuard) {
      console.log(JSON.stringify(identityGuard, null, 2));
      process.exitCode = 2;
      return;
    }
  }

  args.registryPrefix = validateRegistryPrefix(args.registryPrefix);
  const [baseImagePreflight, registryCapabilities] = await Promise.all([
    buildkitBaseImagePreflight(args.baseImage),
    probeRegistryCapabilities({
      registryPrefix: args.registryPrefix
    })
  ]);
  if (baseImagePreflight.publishUsable) {
    args.baseImage = baseImagePreflight.localTag;
  }
  const [deployManifest, commitId, remoteUrl] = await Promise.all([
    readConfig(args.deployPath),
    gitValue(["rev-parse", "HEAD"]),
    gitValue(["remote", "get-url", "origin"]).catch(() => "git@github.com:pikasTech/HWLAB.git")
  ]);
  applyDeployDefaultServices(args, deployManifest);
  let catalog = await readConfigIfPresent(args.catalogPath, null);
  const catalogWasMissing = !catalog;
  if (!catalog && isRuntimeArtifactLane(args.lane)) catalog = artifactCatalogSkeleton({ args, deployManifest, commitId });
  catalog = normalizeCatalogForLane(catalog, args, deployManifest, commitId);
  assert.ok(catalog, `${args.catalogPath} is required for ${args.lane} artifact publish`);
  const shortCommit = imageTagForCommit(args, commitId);
  let effectiveCatalogPath = args.catalogPath;
  if (catalogWasMissing && isRuntimeArtifactLane(args.lane)) {
    effectiveCatalogPath = path.join(os.tmpdir(), `hwlab-${args.lane}-artifact-catalog-${process.pid}.json`);
    await writeFile(effectiveCatalogPath, `${JSON.stringify(catalog, null, 2)}\n`);
  }
  const buildCreatedAt = new Date().toISOString();
  const repo = repoLabelFromRemote(remoteUrl);
  const producer = artifactProducer(process.env);
  const ciPlan = await createCiPlan({
    repoRoot,
    lane: args.lane,
    targetRef: "HEAD",
    deployConfigPath: args.deployPath,
    artifactCatalogPath: effectiveCatalogPath,
    registryPrefix: args.registryPrefix,
    baseImage: args.baseImage ?? undefined,
    ...(args.servicesExplicit ? { services: args.services } : {})
  });
  const allowedServiceIds = isRuntimeArtifactLane(args.lane) ? runtimeLaneServiceIdsFromDeploy(deployManifest, args.lane) : SERVICE_IDS;
  const services = enrichServicesWithCiPlan(await resolveServices(args.services, catalog, allowedServiceIds), ciPlan);
  const catalogByServiceId = new Map((catalog?.services ?? []).map((service) => [service.serviceId, service]));
  const affectedServiceIds = new Set(ciPlan?.affectedServices ?? []);
  const plannedBuildServices = Array.isArray(ciPlan?.buildServices) ? ciPlan.buildServices : null;
  const buildServiceIds = new Set(plannedBuildServices ?? services
    .filter((service) => service.artifactRequired && (service.envReuse ? service.envChanged === true : affectedServiceIds.has(service.serviceId)))
    .map((service) => service.serviceId));

  const preflightBuildServiceIds = args.externalServiceReportDir || args.externalServiceResultsEnv ? new Set() : buildServiceIds;
  let blockers = await preflight({ args, services, catalog, deployManifest, baseImagePreflight, registryCapabilities, buildServiceIds: preflightBuildServiceIds });
  let artifacts = services.map((service) => artifactRecord({
    args,
    service,
    commitId,
    shortCommit,
    status: service.artifactRequired ? "preflight_only" : "not_required"
  }));
  let externalArtifacts = new Map();
  if (args.mode === "build" || args.mode === "publish") {
    if (args.externalServiceReportDir && buildServiceIds.size > 0) {
      const externalResult = await readExternalServiceArtifacts({
        dir: args.externalServiceReportDir,
        services: services.filter((service) => buildServiceIds.has(service.serviceId)),
        commitId
      });
      externalArtifacts = externalResult.artifacts;
      blockers.push(...externalResult.blockers);
    }
    if (args.externalServiceResultsEnv) {
      const externalResult = await readExternalServiceArtifactsFromEnv({
        services: services.filter((service) => service.artifactRequired),
        commitId
      });
      externalArtifacts = new Map([...externalArtifacts, ...externalResult.artifacts]);
      blockers.push(...externalResult.blockers);
    }
    artifacts = services.map((service) => {
      if (externalArtifacts.has(service.serviceId)) {
        return externalArtifacts.get(service.serviceId);
      }
      if (!service.artifactRequired || buildServiceIds.has(service.serviceId)) {
        return artifactRecord({
          args,
          service,
          commitId,
          shortCommit,
          status: service.artifactRequired ? "preflight_only" : "not_required"
        });
      }
      const reused = artifactRecordForCatalogReuse({
        service,
        catalogService: catalogByServiceId.get(service.serviceId),
        commitId
      });
      emit("artifact_reuse", {
        serviceId: service.serviceId,
        status: reused.status,
        image: reused.image,
        digest: reused.digest,
        reusedFrom: reused.reusedFrom ?? null
      });
      if (reused.status.startsWith("blocked_")) {
        blockers.push(blocker({
          type: "environment_blocker",
          scope: service.serviceId,
          summary: `${service.serviceId} cannot be reused because ${args.catalogPath} has no verified digest`,
          next: "Refresh the service catalog with a verified sha256 digest for this service, or create a service-scoped source change so the planner publishes it through the per-service BuildKit task."
        }));
      }
      return reused;
    });
  }

  if ((args.mode === "build" || args.mode === "publish") && hasFatalBlocker(blockers)) {
    emit("publish_blocked", {
      fatalBlockers: blockers.filter((item) => fatalBlockerTypes.has(item.type)).map((item) => item.scope)
    });
  } else if (args.mode === "build" || args.mode === "publish") {
    const artifactByServiceId = new Map(artifacts.map((artifact) => [artifact.serviceId, artifact]));
    await mapWithConcurrency(services.filter((item) => buildServiceIds.has(item.serviceId) && !externalArtifacts.has(item.serviceId)), args.concurrency, async (service) => {
      emit("build_start", { serviceId: service.serviceId, image: imageRef(args.registryPrefix, service.serviceId, shortCommit) });
      const artifact = await buildService({ args, repo, commitId, shortCommit, service, buildCreatedAt });
      artifactByServiceId.set(service.serviceId, artifact);
      if (artifact.blocker) blockers.push(artifact.blocker);
      emit("build_done", {
        serviceId: service.serviceId,
        status: artifact.status,
        image: artifact.image,
        buildkitBuildDurationMs: artifact.buildkitBuildDurationMs ?? null,
        buildBackend: artifact.buildBackend ?? args.buildBackend,
        cloudWebBuildDurationMs: artifact.cloudWebBuildDurationMs ?? null
      });
    });
    artifacts = services.map((service) => artifactByServiceId.get(service.serviceId));
  }

  const report = createReport({ args, repo, commitId, shortCommit, mode: args.mode, services, artifacts, blockers, baseImagePreflight, registryCapabilities, producer, ciPlan });
  if (args.tektonResultsDir) {
    await writeTektonResultFiles(args.tektonResultsDir, report);
  }
  if (args.emitReport) {
    await writeReport(args.reportPath, report);
  }

  console.log(JSON.stringify({
    status: report.artifactPublish.status,
    mode: args.mode,
    reportPath: args.emitReport ? args.reportPath : null,
    sourceCommitId: commitId,
    registryPrefix: args.registryPrefix,
    registryCapabilities: summarizeRegistryCapabilities(registryCapabilities),
    baseImagePreflight: summarizeBaseImagePreflight(baseImagePreflight),
    selectedServiceIds: report.artifactPublish.selectedServiceIds,
    affectedOnly: report.artifactPublish.affectedOnly,
    ciPlan: report.artifactPublish.ciPlan,
    concurrency: report.artifactPublish.concurrency,
    builtCount: report.artifactPublish.builtCount,
    publishedCount: report.artifactPublish.publishedCount,
    reusedCount: report.artifactPublish.reusedCount,
    timings: report.artifactPublish.timings,
    publishPlan: report.artifactPublish.publishPlan,
    services: report.artifactPublish.services.map((service) => ({
      serviceId: service.serviceId,
      status: service.status,
      commitId: service.commitId,
      sourceCommitId: service.sourceCommitId,
      image: service.image,
      imageTag: service.imageTag,
      buildCreatedAt: service.buildCreatedAt,
      digest: service.digest,
      distFreshness: service.distFreshness,
      buildkitBuildDurationMs: service.buildkitBuildDurationMs,
      buildBackend: service.buildBackend,
      buildkitCacheRef: service.buildkitCacheRef,
      cloudWebBuildDurationMs: service.cloudWebBuildDurationMs,
      publishDurationMs: service.publishDurationMs,
      reusedFrom: service.reusedFrom,
      logTail: service.logTail,
      pushLogTail: service.pushLogTail,
      notPublishedReason: service.notPublishedReason
    })),
    blockers
  }, null, 2));

  if (hasFatalBlocker(blockers)) {
    process.exitCode = 2;
  } else if ((args.mode === "build" || args.mode === "publish") && artifacts.some((artifact) => artifact.status.endsWith("_failed"))) {
    process.exitCode = 1;
  }
}

main().catch((error) => {
  console.error(JSON.stringify({
    status: "failed",
    error: error instanceof Error ? error.message : String(error)
  }, null, 2));
  process.exitCode = 1;
});
