OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / release-evidence-verify.test.js

3141 lines · javascript ref:
import test from 'brittle'
import { execFile } from 'node:child_process'
import crypto from 'node:crypto'
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'

const TAG_SHA = 'a'.repeat(40)
const IMAGE_DIGEST = 'sha256:' + 'b'.repeat(64)
const IMAGE_AMD64_DIGEST = 'sha256:' + 'a'.repeat(64)
const IMAGE_ARM64_DIGEST = 'sha256:' + 'c'.repeat(64)
const EXPECTED_SMOKE_SERVICE_PLUGINS = ['poker', 'vrf', 'arbitration', 'zk', 'ai']
const EXPECTED_IMAGE_NAME = 'ghcr.io/bigdestiny2/p2p-hiverelay'
const EXPECTED_REPOSITORY = 'bigdestiny2/P2P-Hiverelay'
const STARTOS_REGISTRY_URL = 'https://registry.start9.com/startos'
const STARTOS_REGISTRY_PACKAGE_URL = `${STARTOS_REGISTRY_URL}/blindspark`
const FLEET_INVENTORY_SHA = crypto.createHash('sha256').update(readFileSync('fleet/relays.json')).digest('hex')
const FLEET_CHANNEL_CONFIG_SHA = '3'.repeat(64)
const VERIFY_RELEASE_EVIDENCE_SCRIPT = path.join(process.cwd(), 'scripts/verify-release-evidence.mjs')
const FLEET_RELAYS = Object.freeze([
  ['utah', 'canary', '42%'],
  ['utah-us', 'stable', '51%'],
  ['utah-2gb-a', 'stable', '50%'],
  ['utah-0.5gb', 'canary', '49%'],
  ['utah-8gb', 'stable', '48%'],
  ['sing-1', 'stable', '52%'],
  ['sing-2', 'stable', '53%'],
  ['bern', 'canary', '54%'],
  ['dubai', 'stable', '47%']
])

function runVerify (argv) {
  ensureImageManifestSidecar(argv)
  ensureStartosRegistrySidecar(argv)
  return new Promise((resolve, reject) => {
    execFile(process.execPath, ['scripts/verify-release-evidence.mjs', ...argv], {
      cwd: process.cwd(),
      timeout: 10000
    }, (err, stdout, stderr) => {
      if (err) {
        err.stdout = stdout
        err.stderr = stderr
        reject(err)
      } else {
        resolve({ stdout, stderr })
      }
    })
  })
}

async function writeJson (file, body) {
  await writeFile(file, JSON.stringify(body, null, 2) + '\n')
}

function writeJsonSync (file, body) {
  writeFileSync(file, JSON.stringify(body, null, 2) + '\n')
}

function sha256Json (body) {
  return crypto.createHash('sha256').update(JSON.stringify(body, null, 2) + '\n').digest('hex')
}

async function sha256File (file) {
  return crypto.createHash('sha256').update(await readFile(file)).digest('hex')
}

test('release evidence verifier hashes StartOS package artifacts as a stream', async (t) => {
  const script = await readFile(VERIFY_RELEASE_EVIDENCE_SCRIPT, 'utf8')
  const packageVerifier = script.match(/if \(opts\.startosPackageFile\) \{[\s\S]*?requireEqual\('StartOS package hash'/)
  t.ok(packageVerifier, 'StartOS package verifier is present')
  t.ok(script.includes("await sha256LargeFile(opts.startosPackageFile, 'StartOS package')"), 'release package verifier uses the streaming helper')
  t.ok(script.includes('fs.createReadStream(file)'), 'release package hash check streams the .s9pk')
  t.absent((packageVerifier?.[0] || '').includes('sha256File(opts.startosPackageFile'), 'release package verifier does not use the bounded JSON file hash helper')
})

function ensureImageManifestSidecar (argv) {
  const releaseFlag = argv.indexOf('--release')
  const bundleFlag = argv.indexOf('--bundle-dir')
  const explicitFlag = argv.indexOf('--release-image-manifest')
  let releaseFile = ''
  if (releaseFlag !== -1) {
    releaseFile = argv[releaseFlag + 1]
  } else if (bundleFlag !== -1) {
    releaseFile = path.join(argv[bundleFlag + 1], 'release-evidence.json')
  }
  if (!releaseFile || explicitFlag !== -1 || !existsSync(releaseFile)) return
  const release = JSON.parse(readFileSync(releaseFile, 'utf8'))
  if (release.release?.workflow?.status !== 'success') return
  const sidecarPath = path.join(path.dirname(releaseFile), release.gates?.imageManifestEvidence?.path || 'release-image-manifest-evidence.json')
  if (existsSync(sidecarPath)) return
  writeJsonSync(sidecarPath, releaseImageManifestEvidence({ release }))
}

function ensureStartosRegistrySidecar (argv) {
  const releaseFlag = argv.indexOf('--release')
  const bundleFlag = argv.indexOf('--bundle-dir')
  const explicitFlag = argv.indexOf('--startos-registry')
  let releaseFile = ''
  if (releaseFlag !== -1) {
    releaseFile = argv[releaseFlag + 1]
  } else if (bundleFlag !== -1) {
    releaseFile = path.join(argv[bundleFlag + 1], 'release-evidence.json')
  }
  if (!releaseFile || explicitFlag !== -1 || !existsSync(releaseFile)) return
  const release = JSON.parse(readFileSync(releaseFile, 'utf8'))
  if (release.release?.prerelease || release.release?.workflow?.status !== 'success') return
  const sidecarPath = path.join(path.dirname(releaseFile), release.surfaces?.startosRegistryEvidence?.path || 'startos-registry-evidence.json')
  if (existsSync(sidecarPath)) return
  writeJsonSync(sidecarPath, startosRegistryEvidence({ release }))
}

function releaseEvidence (opts) {
  const prerelease = Boolean(opts.prerelease)
  const candidate = Boolean(opts.candidate)
  const version = prerelease ? 'v9.9.9-beta.1' : 'v9.9.9'
  const semver = version.slice(1)
  let startosRegistryEvidenceSha = ''
  if (!prerelease) {
    startosRegistryEvidenceSha = opts.startosRegistrySha || sha256Json(startosRegistryEvidence({
      release: {
        release: { version, semver, workflow: { repository: EXPECTED_REPOSITORY, runId: '123', runAttempt: '1', runUrl: `https://github.com/${EXPECTED_REPOSITORY}/actions/runs/123` } },
        artifacts: { startosPackage: { path: 'startos/blindspark.s9pk', sha256: opts.startosSha } },
        surfaces: {
          startosPackageId: 'blindspark',
          startosRegistryUrl: STARTOS_REGISTRY_URL,
          startosRegistryPackageUrl: STARTOS_REGISTRY_PACKAGE_URL
        }
      }
    }))
  }

  return {
    schemaVersion: 1,
    generatedAt: '2026-06-22T00:00:00.000Z',
    release: {
      version,
      semver,
      channel: prerelease ? 'none' : 'both',
      prerelease,
      candidate,
      tagSha: TAG_SHA,
      metadataSha: TAG_SHA,
      workflow: {
        status: 'success',
        repository: EXPECTED_REPOSITORY,
        runId: '123',
        runAttempt: '1',
        runUrl: `https://github.com/${EXPECTED_REPOSITORY}/actions/runs/123`
      }
    },
    image: {
      name: EXPECTED_IMAGE_NAME,
      digest: IMAGE_DIGEST,
      ref: `${EXPECTED_IMAGE_NAME}:${semver}@${IMAGE_DIGEST}`
    },
    artifacts: {
      startosPackage: {
        path: 'startos/blindspark.s9pk',
        sha256: opts.startosSha
      }
    },
    gates: {
      auditAndUnit: 'passed',
      distributionPreflight: prerelease ? 'skipped' : 'passed',
      imageManifest: 'passed',
      imageManifestEvidence: {
        path: 'release-image-manifest-evidence.json',
        sha256: opts.imageManifestSha || sha256Json(releaseImageManifestEvidence({
          release: {
            release: { semver },
            image: {
              name: EXPECTED_IMAGE_NAME,
              digest: IMAGE_DIGEST,
              ref: `${EXPECTED_IMAGE_NAME}:${semver}@${IMAGE_DIGEST}`
            }
          }
        }))
      },
      pushedImageSmoke: 'passed',
      pushedImageSmokeEvidence: {
        path: 'release-image-smoke-evidence.json',
        sha256: opts.imageSmokeSha
      },
      umbrelPackageSmoke: 'passed',
      umbrelPackageSmokeEvidence: {
        path: 'umbrel-package-smoke-evidence.json',
        sha256: opts.umbrelSmokeSha
      },
      startosVerify: 'passed'
    },
    surfaces: {
      metadataCommit: prerelease ? 'skipped' : 'committed',
      npmPackages: prerelease ? 'skipped' : (opts.npmPackages || 'published'),
      startosReleaseAsset: candidate ? 'skipped' : 'uploaded',
      fleetRollout: prerelease ? 'skipped' : 'verified',
      fleetRolloutChannel: prerelease ? '' : 'both',
      fleetRolloutEvidence: {
        path: prerelease ? '' : 'fleet-rollout-evidence.json',
        sha256: prerelease ? '' : opts.rolloutSha
      },
      fleetChannelConfig: {
        path: prerelease ? '' : 'fleet/channels.json',
        sha256: prerelease ? '' : FLEET_CHANNEL_CONFIG_SHA
      },
      startosRegistry: prerelease ? 'skipped' : 'published',
      startosRegistryUrl: prerelease ? '' : STARTOS_REGISTRY_URL,
      startosRegistryPackageUrl: prerelease ? '' : STARTOS_REGISTRY_PACKAGE_URL,
      startosPackageId: prerelease ? '' : 'blindspark',
      startosRegistryEvidence: {
        path: prerelease ? '' : 'startos-registry-evidence.json',
        sha256: startosRegistryEvidenceSha
      },
      umbrelOfficial: {
        status: prerelease ? 'skipped' : 'draft-pr-ready',
        prUrl: prerelease ? '' : 'https://github.com/getumbrel/umbrel-apps/pull/123',
        head: prerelease ? '' : 'bigdestiny2:blindspark-v9.9.9',
        headSha: prerelease ? '' : TAG_SHA,
        state: prerelease ? '' : 'OPEN',
        isDraft: prerelease ? null : true,
        base: prerelease ? '' : 'master',
        headOwner: prerelease ? '' : 'bigdestiny2',
        headRef: prerelease ? '' : 'blindspark-v9.9.9',
        headOid: prerelease ? '' : TAG_SHA
      },
      umbrelCommunityStore: {
        validation: prerelease ? 'skipped' : 'passed',
        publish: prerelease ? 'skipped' : 'pushed',
        commit: prerelease ? '' : TAG_SHA,
        commitUrl: prerelease ? '' : `https://github.com/bigdestiny2/blindspark-umbrel-store/commit/${TAG_SHA}`
      }
    }
  }
}

function releaseImageManifestEvidence ({ release }) {
  return {
    schemaVersion: 1,
    generatedAt: '2026-06-22T00:00:00.000Z',
    kind: 'release-image-manifest',
    status: 'verified',
    image: {
      name: release.image.name,
      tag: release.release.semver,
      digest: release.image.digest,
      ref: release.image.ref
    },
    requiredPlatforms: ['linux/amd64', 'linux/arm64'],
    platforms: [
      { os: 'linux', architecture: 'amd64', variant: '', digest: IMAGE_AMD64_DIGEST },
      { os: 'linux', architecture: 'arm64', variant: 'v8', digest: IMAGE_ARM64_DIGEST }
    ],
    manifest: {
      mediaType: 'application/vnd.oci.image.index.v1+json',
      manifestCount: 2
    }
  }
}

function startosRegistryEvidence ({ release }) {
  const releaseBase = `https://github.com/${release.release.workflow.repository}/releases/download/${release.release.version}`
  return {
    schemaVersion: 1,
    generatedAt: '2026-06-22T00:00:00.000Z',
    kind: 'startos-registry-publication',
    status: 'published',
    release: {
      version: release.release.version,
      semver: release.release.semver
    },
    package: {
      id: release.surfaces.startosPackageId,
      path: release.artifacts.startosPackage.path,
      sha256: release.artifacts.startosPackage.sha256,
      url: release.surfaces.startosRegistryPackageUrl
    },
    registry: {
      url: release.surfaces.startosRegistryUrl
    },
    workflow: {
      repository: release.release.workflow.repository,
      runId: release.release.workflow.runId,
      runAttempt: release.release.workflow.runAttempt,
      runUrl: release.release.workflow.runUrl
    },
    evidenceLinks: {
      releaseEvidence: `${releaseBase}/release-evidence.json`,
      releaseImageManifest: `${releaseBase}/release-image-manifest-evidence.json`,
      releaseImageSmoke: `${releaseBase}/release-image-smoke-evidence.json`,
      startosPackage: `${releaseBase}/blindspark.s9pk`,
      registryPackage: release.surfaces.startosRegistryPackageUrl,
      workflow: release.release.workflow.runUrl
    }
  }
}

function releaseImageSmokeEvidence (opts = {}) {
  const prerelease = Boolean(opts.prerelease)
  const semver = prerelease ? '9.9.9-beta.1' : '9.9.9'
  return {
    schemaVersion: 1,
    generatedAt: '2026-06-22T00:00:00.000Z',
    kind: 'release-image-smoke',
    imageRef: `${EXPECTED_IMAGE_NAME}:${semver}@${IMAGE_DIGEST}`,
    imageName: EXPECTED_IMAGE_NAME,
    imageTag: semver,
    imageDigest: IMAGE_DIGEST,
    checks: [
      { name: 'health', status: 'passed', version: semver },
      { name: 'dashboard', status: 'passed', serviceManager: true, walletControls: true, tokenMeta: true, walletBusyState: true, dynamicPayoutControls: true, serviceActionState: true, serviceInlinePlanState: true, aiModelAddState: true, appProxyWrites: true, leasePollingBounded: true, staticMarkupSafe: true },
      { name: 'setupWizard', status: 'passed', editMode: true, statusRegion: true, actionLock: true, dashboardLinkAppPath: true, staticMarkupSafe: true },
      { name: 'dashboardToken', status: 'passed', exposedViaMeta: true },
      { name: 'dashboardWebSocket', status: 'passed', queryTokenRejected: true, inBandAuth: true, updateReceived: true },
      { name: 'usageTelemetry', status: 'passed', bandwidth: { enabled: true, count: 0, bytes: 0, bandwidthBytes: 0 }, poker: { enabled: false, tables: 0, appends: 0, seats: 0 } },
      { name: 'acceptModeDefault', status: 'passed', mode: 'review' },
      { name: 'serviceCatalog', status: 'passed', builtIns: ['poker', 'vrf'], bundles: ['poker'] },
      { name: 'walletWrite', status: 'passed', destinationSaved: true },
      { name: 'servicesSave', status: 'passed', plugins: EXPECTED_SMOKE_SERVICE_PLUGINS, restartRequired: true }
    ]
  }
}

function umbrelSmokeEvidence (opts = {}) {
  const prerelease = Boolean(opts.prerelease)
  const semver = prerelease ? '9.9.9-beta.1' : '9.9.9'
  return {
    schemaVersion: 1,
    generatedAt: '2026-06-22T00:00:00.000Z',
    kind: 'umbrel-package-smoke',
    imageRef: `${EXPECTED_IMAGE_NAME}:${semver}@${IMAGE_DIGEST}`,
    imageName: EXPECTED_IMAGE_NAME,
    imageTag: semver,
    imageDigest: IMAGE_DIGEST,
    composePath: 'umbrel-app/docker-compose.yml',
    checks: [
      { name: 'composeSafety', status: 'passed', composePath: 'umbrel-app/docker-compose.yml' },
      { name: 'firstBoot', status: 'passed', dashboard: true, setup: true, serviceCatalog: true, dashboardUiHardening: true, dynamicPayoutControls: true, serviceInlinePlanState: true, appProxyWrites: true, leasePollingBounded: true, dashboardStaticMarkupSafe: true, setupUiHardening: true, dashboardLinkAppPath: true, setupStaticMarkupSafe: true, acceptMode: 'review', healthVersion: semver },
      { name: 'dashboardWebSocket', status: 'passed', queryTokenRejected: true, inBandAuth: true, updateReceived: true },
      { name: 'acceptModeDefault', status: 'passed', mode: 'review' },
      { name: 'usageTelemetry', status: 'passed', bandwidth: { enabled: true, count: 0, bytes: 0, bandwidthBytes: 0 }, poker: { enabled: false, tables: 0, appends: 0, seats: 0 } },
      { name: 'walletWrite', status: 'passed', destinationSaved: true },
      { name: 'servicesSave', status: 'passed', plugins: EXPECTED_SMOKE_SERVICE_PLUGINS, restartRequired: true },
      { name: 'secondBoot', status: 'passed', dashboard: true, setup: true, serviceCatalog: true, dashboardUiHardening: true, dynamicPayoutControls: true, serviceInlinePlanState: true, appProxyWrites: true, leasePollingBounded: true, dashboardStaticMarkupSafe: true, setupUiHardening: true, dashboardLinkAppPath: true, setupStaticMarkupSafe: true, acceptMode: 'review', healthVersion: semver },
      { name: 'identityPersistence', status: 'passed', publicKeyStable: true },
      { name: 'walletPersistence', status: 'passed', destinationPersisted: true },
      {
        name: 'servicesPersistence',
        status: 'passed',
        selectedServicesActive: true,
        plugins: EXPECTED_SMOKE_SERVICE_PLUGINS,
        active: EXPECTED_SMOKE_SERVICE_PLUGINS
      }
    ]
  }
}

function fleetEvidence (overrides = {}) {
  const relays = FLEET_RELAYS.map(([name, channel, disk]) => fleetRelay(name, channel, disk))
  return {
    schemaVersion: 1,
    generatedAt: '2026-06-22T00:00:00.000Z',
    status: 'verified',
    target: {
      tag: 'v9.9.9',
      version: '9.9.9',
      sha: TAG_SHA,
      channel: 'both'
    },
    inventory: {
      path: 'fleet/relays.json',
      sha256: FLEET_INVENTORY_SHA,
      relayNames: FLEET_RELAYS.map(([name]) => name)
    },
    channelConfig: {
      path: 'fleet/channels.json',
      sha256: FLEET_CHANNEL_CONFIG_SHA,
      targets: {
        canary: 'v9.9.9',
        stable: 'v9.9.9'
      }
    },
    probes: {
      timeoutMs: 1800000,
      intervalMs: 30000,
      sshTimeoutMs: 25000,
      service: 'hiverelay',
      api: 'http://127.0.0.1:9100'
    },
    summary: {
      total: relays.length,
      updated: relays.length,
      packageVersionMatches: relays.length,
      healthy: relays.length,
      runtimeVersionMatches: relays.length
    },
    relays,
    ...overrides
  }
}

function fleetRelay (name, channel, disk) {
  return {
    name,
    channel,
    packageVersion: 'v9.9.9',
    healthVersion: '9.9.9',
    observedAt: '2026-06-22T00:00:00.000Z',
    headSha: TAG_SHA,
    targetSha: TAG_SHA,
    updated: true,
    packageVersionMatches: true,
    healthy: true,
    runtimeVersionMatches: true,
    disk,
    note: 'ok',
    error: ''
  }
}

async function fixtureDir (t) {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-verify-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })
  return dir
}

test('release evidence verifier validates full-release sidecars and hashes', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  const res = await runVerify([
    '--release', releaseFile,
    '--release-image-smoke', releaseImageSmokeFile,
    '--umbrel-package-smoke', umbrelSmokeFile,
    '--fleet-rollout', fleetFile,
    '--startos-package', s9pkFile
  ])

  t.ok(res.stdout.includes('Release evidence verified: v9.9.9'))
})

test('release evidence verifier accepts only a bound public gateway release for deferred fleet rollout', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: ''
  })
  release.release.channel = 'none'
  release.release.publicGateway = {
    enabled: true,
    manifestStatus: 'enabled',
    manifestPath: 'fleet/public-hive-gateway-release.json',
    manifestSha256: '4'.repeat(64),
    releaseTarget: release.release.version,
    commitSha: release.release.tagSha,
    admissionProfile: 'blind-substrate-public-v1',
    cohortSize: 3
  }
  release.surfaces.fleetRollout = 'deferred-gateway-canary-gated'
  release.surfaces.fleetRolloutChannel = ''
  release.surfaces.fleetRolloutEvidence = { path: '', sha256: '' }
  release.surfaces.fleetChannelConfig = { path: '', sha256: '' }
  await writeJson(releaseFile, release)

  const res = await runVerify([
    '--release', releaseFile,
    '--release-image-smoke', releaseImageSmokeFile,
    '--umbrel-package-smoke', umbrelSmokeFile,
    '--startos-package', s9pkFile
  ])
  t.ok(res.stdout.includes('Release evidence verified: v9.9.9'))

  delete release.release.publicGateway
  await writeJson(releaseFile, release)
  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }
  t.ok(err)
  t.ok(err.stderr.includes('release.channel'))
})

test('release evidence verifier rejects unsupported release certificate fields', async (t) => {
  const cases = [
    ['top-level', release => { release.marketplacePublished = true }, 'release evidence has unsupported fields: marketplacePublished'],
    ['release', release => { release.release.marketplaceReady = true }, 'release evidence release has unsupported fields: marketplaceReady'],
    ['gates', release => { release.gates.appStoreReview = 'passed' }, 'release evidence gates has unsupported fields: appStoreReview'],
    ['surfaces', release => { release.surfaces.umbrelRuntimeReview = 'passed' }, 'release evidence surfaces has unsupported fields: umbrelRuntimeReview'],
    ['official Umbrel PR', release => { release.surfaces.umbrelOfficial.reviewReady = true }, 'release evidence official Umbrel PR has unsupported fields: reviewReady']
  ]

  for (const [name, mutate, message] of cases) {
    const dir = await fixtureDir(t)
    const releaseFile = path.join(dir, 'release-evidence.json')
    const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
    const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
    const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
    const s9pkFile = path.join(dir, 'blindspark.s9pk')

    await writeFile(s9pkFile, 'fake-s9pk')
    await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
    await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
    await writeJson(fleetFile, fleetEvidence())
    const release = releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    })
    mutate(release)
    await writeJson(releaseFile, release)

    let err = null
    try {
      await runVerify(['--bundle-dir', dir])
    } catch (e) {
      err = e
    }

    t.ok(err, name)
    t.ok(err.stderr.includes(message), name)
  }
})

test('release evidence verifier auto-discovers a downloaded release bundle directory', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosDir = path.join(dir, 'startos')
  const s9pkFile = path.join(startosDir, 'blindspark.s9pk')

  await mkdir(startosDir)
  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  const res = await runVerify(['--bundle-dir', dir])

  t.ok(res.stdout.includes('Release evidence verified: v9.9.9'))
})

test('release evidence verifier rejects symlinked evidence sidecars', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const realSmokeFile = path.join(dir, 'real-release-image-smoke-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosRegistryFile = path.join(dir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(realSmokeFile, releaseImageSmokeEvidence())
  await symlink(realSmokeFile, releaseImageSmokeFile)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(realSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  const sidecar = startosRegistryEvidence({ release })
  release.surfaces.startosRegistryEvidence.sha256 = sha256Json(sidecar)
  await writeJson(startosRegistryFile, sidecar)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-registry', startosRegistryFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release-image-smoke evidence file must not be a symlink'))
})

test('release evidence verifier rejects oversized evidence sidecars before hashing', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosRegistryFile = path.join(dir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeFile(releaseImageSmokeFile, 'x'.repeat(2 * 1024 * 1024 + 1))
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: '0'.repeat(64),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  const sidecar = startosRegistryEvidence({ release })
  release.surfaces.startosRegistryEvidence.sha256 = sha256Json(sidecar)
  await writeJson(startosRegistryFile, sidecar)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-registry', startosRegistryFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release-image-smoke evidence file must be 2097152 bytes or smaller'))
})

test('release evidence verifier rejects mismatched fleet sidecar hash', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: 'd'.repeat(64)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('fleet rollout evidence hash'))
})

test('release evidence verifier rejects unsafe fleet rollout probe timing', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const fleet = fleetEvidence()
  fleet.probes.timeoutMs = 1

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleet)
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('fleet rollout timeoutMs must be an integer between 600000 and 14400000'))
})

test('release evidence verifier rejects non-loopback fleet rollout probe API URLs', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())

  for (const api of ['https://relay.example.com:9100', 'http://128.0.0.1:9100']) {
    const fleet = fleetEvidence()
    fleet.probes.api = api

    await writeJson(fleetFile, fleet)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, api)
    t.ok(err.stderr.includes('fleet rollout API URL must be a loopback http(s) base URL'), api)
  }
})

test('release evidence verifier rejects unsupported fleet rollout sidecar fields', async (t) => {
  const cases = [
    ['top-level', fleet => { fleet.marketplacePublished = true }, 'fleet rollout evidence has unsupported fields: marketplacePublished'],
    ['target', fleet => { fleet.target.reviewReady = true }, 'fleet rollout target has unsupported fields: reviewReady'],
    ['inventory', fleet => { fleet.inventory.hosts = ['10.0.0.1'] }, 'fleet rollout inventory has unsupported fields: hosts'],
    ['channel config', fleet => { fleet.channelConfig.notes = 'ok' }, 'fleet rollout channel config has unsupported fields: notes'],
    ['targets', fleet => { fleet.channelConfig.targets.beta = 'v9.9.9' }, 'fleet rollout channel config targets has unsupported fields: beta'],
    ['probes', fleet => { fleet.probes.gateway = 'https://relay.example.com' }, 'fleet rollout probes has unsupported fields: gateway'],
    ['summary', fleet => { fleet.summary.marketplaceReady = fleet.relays.length }, 'fleet rollout summary has unsupported fields: marketplaceReady'],
    ['relay', fleet => { fleet.relays[0].region = 'utah' }, 'fleet rollout relay utah has unsupported fields: region']
  ]

  for (const [name, mutate, message] of cases) {
    const dir = await fixtureDir(t)
    const releaseFile = path.join(dir, 'release-evidence.json')
    const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
    const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
    const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
    const s9pkFile = path.join(dir, 'blindspark.s9pk')
    const fleet = fleetEvidence()

    mutate(fleet)

    await writeFile(s9pkFile, 'fake-s9pk')
    await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
    await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
    await writeJson(fleetFile, fleet)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, name)
    t.ok(err.stderr.includes(message), name)
  }
})

test('release evidence verifier rejects credentialed registry URLs', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const release = releaseEvidence({})
  release.surfaces.startosRegistryUrl = 'https://user:pass@registry.start9.com/startos'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  release.artifacts.startosPackage.sha256 = await sha256File(s9pkFile)
  release.gates.pushedImageSmokeEvidence.sha256 = await sha256File(releaseImageSmokeFile)
  release.gates.umbrelPackageSmokeEvidence.sha256 = await sha256File(umbrelSmokeFile)
  release.surfaces.fleetRolloutEvidence.sha256 = await sha256File(fleetFile)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('URL credentials') || err.stderr.includes('without embedded credentials'))
})

test('release evidence verifier rejects placeholder registry hosts', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const release = releaseEvidence({})
  release.surfaces.startosRegistryUrl = 'https://registry.example/startos'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  release.artifacts.startosPackage.sha256 = await sha256File(s9pkFile)
  release.gates.pushedImageSmokeEvidence.sha256 = await sha256File(releaseImageSmokeFile)
  release.gates.umbrelPackageSmokeEvidence.sha256 = await sha256File(umbrelSmokeFile)
  release.surfaces.fleetRolloutEvidence.sha256 = await sha256File(fleetFile)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('reserved/local hostnames'))
})

test('release evidence verifier rejects mismatched registry package URLs', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const release = releaseEvidence({})
  release.surfaces.startosRegistryPackageUrl = 'https://registry.start9.com/startos/other'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  release.artifacts.startosPackage.sha256 = await sha256File(s9pkFile)
  release.gates.pushedImageSmokeEvidence.sha256 = await sha256File(releaseImageSmokeFile)
  release.gates.umbrelPackageSmokeEvidence.sha256 = await sha256File(umbrelSmokeFile)
  release.surfaces.fleetRolloutEvidence.sha256 = await sha256File(fleetFile)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS registry package URL'))
})

test('release evidence verifier rejects StartOS registry sidecar hash drift', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosRegistryFile = path.join(dir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile),
    startosRegistrySha: '0'.repeat(64)
  })
  await writeJson(startosRegistryFile, startosRegistryEvidence({ release }))
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-registry', startosRegistryFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS registry evidence hash'))
})

test('release evidence verifier honors explicit StartOS registry sidecar paths', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const sidecarDir = path.join(dir, 'sidecars')
  const startosRegistryFile = path.join(sidecarDir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await mkdir(sidecarDir)
  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  const sidecar = startosRegistryEvidence({ release })
  release.surfaces.startosRegistryEvidence.sha256 = sha256Json(sidecar)
  await writeJson(startosRegistryFile, sidecar)
  await writeJson(releaseFile, release)

  const res = await runVerify([
    '--release', releaseFile,
    '--release-image-smoke', releaseImageSmokeFile,
    '--umbrel-package-smoke', umbrelSmokeFile,
    '--fleet-rollout', fleetFile,
    '--startos-registry', startosRegistryFile,
    '--startos-package', s9pkFile
  ])

  t.ok(res.stdout.includes('Release evidence verified: v9.9.9'))
})

test('release evidence verifier rejects unsupported StartOS registry sidecar fields', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosRegistryFile = path.join(dir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())

  const cases = [
    ['top-level', sidecar => { sidecar.marketplacePublished = true }, 'StartOS registry evidence has unsupported fields: marketplacePublished'],
    ['package', sidecar => { sidecar.package.extraClaim = 'published' }, 'StartOS registry evidence package has unsupported fields: extraClaim'],
    ['evidence links', sidecar => { sidecar.evidenceLinks.marketplace = `${STARTOS_REGISTRY_URL}/blindspark` }, 'StartOS registry evidence evidenceLinks has unsupported fields: marketplace']
  ]

  for (const [name, mutate, expected] of cases) {
    const release = releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    })
    const sidecar = startosRegistryEvidence({ release })
    mutate(sidecar)
    release.surfaces.startosRegistryEvidence.sha256 = sha256Json(sidecar)
    await writeJson(startosRegistryFile, sidecar)
    await writeJson(releaseFile, release)

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-registry', startosRegistryFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, name)
    t.ok(err.stderr.includes(expected), name)
  }
})

test('release evidence verifier rejects StartOS registry sidecar package drift', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosRegistryFile = path.join(dir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  const sidecar = startosRegistryEvidence({ release })
  sidecar.package.url = `${STARTOS_REGISTRY_URL}/other`
  release.surfaces.startosRegistryEvidence.sha256 = sha256Json(sidecar)
  await writeJson(startosRegistryFile, sidecar)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-registry', startosRegistryFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS registry evidence package URL'))
})

test('release evidence verifier rejects StartOS registry sidecar image evidence link drift', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosRegistryFile = path.join(dir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  const cases = [
    ['manifest', { releaseImageManifest: `https://github.com/${EXPECTED_REPOSITORY}/releases/download/v9.9.9/wrong-manifest.json` }, 'StartOS registry evidence image manifest link'],
    ['smoke', { releaseImageSmoke: `https://github.com/${EXPECTED_REPOSITORY}/releases/download/v9.9.9/wrong-smoke.json` }, 'StartOS registry evidence image smoke link']
  ]

  for (const [name, links, expected] of cases) {
    const sidecar = startosRegistryEvidence({ release })
    Object.assign(sidecar.evidenceLinks, links)
    release.surfaces.startosRegistryEvidence.sha256 = sha256Json(sidecar)
    await writeJson(startosRegistryFile, sidecar)
    await writeJson(releaseFile, release)

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-registry', startosRegistryFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, name)
    t.ok(err.stderr.includes(expected), name)
  }
})

test('release evidence verifier rejects future image-manifest sidecar timestamps', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageManifestFile = path.join(dir, 'release-image-manifest-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  const imageManifest = releaseImageManifestEvidence({
    release: {
      release: { semver: '9.9.9' },
      image: {
        name: EXPECTED_IMAGE_NAME,
        digest: IMAGE_DIGEST,
        ref: `${EXPECTED_IMAGE_NAME}:9.9.9@${IMAGE_DIGEST}`
      }
    }
  })
  imageManifest.generatedAt = '2026-06-22T00:00:00.001Z'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageManifestFile, imageManifest)
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageManifestSha: sha256Json(imageManifest),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-manifest', releaseImageManifestFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release image manifest generatedAt must not be after release generatedAt'))
})

test('release evidence verifier rejects duplicate image-manifest platform entries', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageManifestFile = path.join(dir, 'release-image-manifest-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  const imageManifest = releaseImageManifestEvidence({
    release: {
      release: { semver: '9.9.9' },
      image: {
        name: EXPECTED_IMAGE_NAME,
        digest: IMAGE_DIGEST,
        ref: `${EXPECTED_IMAGE_NAME}:9.9.9@${IMAGE_DIGEST}`
      }
    }
  })
  imageManifest.platforms.push({
    os: 'linux',
    architecture: 'amd64',
    variant: '',
    digest: 'sha256:' + 'd'.repeat(64)
  })
  imageManifest.manifest.manifestCount = imageManifest.platforms.length

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageManifestFile, imageManifest)
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageManifestSha: sha256Json(imageManifest),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-manifest', releaseImageManifestFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release image manifest evidence has duplicate platform linux/amd64'))
})

test('release evidence verifier rejects unsupported image-manifest sidecar fields', async (t) => {
  const cases = [
    ['top-level', sidecar => { sidecar.registryPublished = true }, 'release image manifest evidence has unsupported fields: registryPublished'],
    ['image', sidecar => { sidecar.image.repoDigest = sidecar.image.ref }, 'release image manifest image has unsupported fields: repoDigest'],
    ['platform', sidecar => { sidecar.platforms[0].annotations = { reviewer: 'ok' } }, 'release image manifest platform linux/amd64 has unsupported fields: annotations'],
    ['manifest', sidecar => { sidecar.manifest.attestations = 2 }, 'release image manifest manifest has unsupported fields: attestations']
  ]

  for (const [name, mutate, message] of cases) {
    const dir = await fixtureDir(t)
    const releaseFile = path.join(dir, 'release-evidence.json')
    const releaseImageManifestFile = path.join(dir, 'release-image-manifest-evidence.json')
    const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
    const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
    const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
    const s9pkFile = path.join(dir, 'blindspark.s9pk')
    const imageManifest = releaseImageManifestEvidence({
      release: {
        release: { semver: '9.9.9' },
        image: {
          name: EXPECTED_IMAGE_NAME,
          digest: IMAGE_DIGEST,
          ref: `${EXPECTED_IMAGE_NAME}:9.9.9@${IMAGE_DIGEST}`
        }
      }
    })
    mutate(imageManifest)

    await writeFile(s9pkFile, 'fake-s9pk')
    await writeJson(releaseImageManifestFile, imageManifest)
    await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
    await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
    await writeJson(fleetFile, fleetEvidence())
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageManifestSha: sha256Json(imageManifest),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-manifest', releaseImageManifestFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, name)
    t.ok(err.stderr.includes(message), name)
  }
})

test('release evidence verifier rejects future StartOS registry sidecar timestamps', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const startosRegistryFile = path.join(dir, 'startos-registry-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  const sidecar = startosRegistryEvidence({ release })
  sidecar.generatedAt = '2026-06-22T00:00:00.001Z'
  release.surfaces.startosRegistryEvidence.sha256 = sha256Json(sidecar)
  await writeJson(startosRegistryFile, sidecar)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-registry', startosRegistryFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS registry evidence generatedAt must not be after release generatedAt'))
})

test('release evidence verifier rejects stale official Umbrel PR state facts', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const baseRelease = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  baseRelease.image.name = 'ghcr.io/attacker/p2p-hiverelay'
  baseRelease.image.ref = `ghcr.io/attacker/p2p-hiverelay:9.9.9@${IMAGE_DIGEST}`
  await writeJson(releaseFile, baseRelease)

  let imageErr = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    imageErr = e
  }

  t.ok(imageErr)
  t.ok(imageErr.stderr.includes(`image.name must be "${EXPECTED_IMAGE_NAME}"`))
  await rm(path.join(dir, 'release-image-manifest-evidence.json'), { force: true })

  const zeroPrRelease = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  zeroPrRelease.surfaces.umbrelOfficial.prUrl = 'https://github.com/getumbrel/umbrel-apps/pull/0'
  await writeJson(releaseFile, zeroPrRelease)

  let zeroPrErr = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    zeroPrErr = e
  }

  t.ok(zeroPrErr)
  t.ok(zeroPrErr.stderr.includes('official Umbrel PR URL'))

  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  release.surfaces.umbrelOfficial.isDraft = false
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('official Umbrel PR draft'))
})

test('release evidence verifier rejects official Umbrel PR head owner and OID drift', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const wrongRepoRelease = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  wrongRepoRelease.release.workflow.repository = 'attacker/P2P-Hiverelay'
  wrongRepoRelease.release.workflow.runUrl = 'https://github.com/attacker/P2P-Hiverelay/actions/runs/123'
  await writeJson(releaseFile, wrongRepoRelease)

  let repoErr = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    repoErr = e
  }

  t.ok(repoErr)
  t.ok(repoErr.stderr.includes(`successful release workflow repository must be "${EXPECTED_REPOSITORY}"`))

  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  release.surfaces.umbrelOfficial.headOwner = 'attacker'
  await writeJson(releaseFile, release)

  let ownerErr = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    ownerErr = e
  }

  t.ok(ownerErr)
  t.ok(ownerErr.stderr.includes('official Umbrel PR head owner matches head owner'))

  release.surfaces.umbrelOfficial.headOwner = 'bigdestiny2'
  release.surfaces.umbrelOfficial.headOid = 'b'.repeat(40)
  await writeJson(releaseFile, release)

  let oidErr = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    oidErr = e
  }

  t.ok(oidErr)
  t.ok(oidErr.stderr.includes('official Umbrel PR head OID matches head SHA'))
})

test('release evidence verifier rejects malformed official Umbrel PR GitHub owner names', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())

  for (const owner of ['bad_owner', 'bad.owner', '-bad', 'getumbrel', 'GetUmbrel']) {
    const release = releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    })
    release.surfaces.umbrelOfficial.head = `${owner}:blindspark-v9.9.9`
    release.surfaces.umbrelOfficial.headOwner = owner
    await writeJson(releaseFile, release)

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, owner)
    t.ok(err.stderr.includes('official Umbrel PR head owner must be a normal GitHub owner name'), owner)
  }
})

test('release evidence verifier rejects malformed official Umbrel PR GitHub head refs', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())

  for (const ref of ['bad..ref', '.bad-ref', 'bad/ref.lock']) {
    const release = releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    })
    release.surfaces.umbrelOfficial.head = `bigdestiny2:${ref}`
    release.surfaces.umbrelOfficial.headRef = ref
    await writeJson(releaseFile, release)

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, ref)
    t.ok(err.stderr.includes('official Umbrel PR head ref must be a normal GitHub branch name'), ref)
  }
})

test('release evidence verifier rejects image digest and ref drift', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  release.image.digest = 'sha256:' + 'c'.repeat(64)
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('image.ref matches image name, release semver, and digest'))
})

test('release evidence verifier rejects smoke image provenance drift', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(fleetFile, fleetEvidence())

  const cases = [
    {
      label: 'release-image-smoke imageDigest',
      mutate: (imageSmoke) => { imageSmoke.imageDigest = 'sha256:' + 'c'.repeat(64) }
    },
    {
      label: 'umbrel-package-smoke imageTag',
      mutate: (_imageSmoke, umbrelSmoke) => { umbrelSmoke.imageTag = '9.9.8' }
    }
  ]

  for (const { label, mutate } of cases) {
    const imageSmoke = releaseImageSmokeEvidence()
    const umbrelSmoke = umbrelSmokeEvidence()
    mutate(imageSmoke, umbrelSmoke)
    await writeJson(releaseImageSmokeFile, imageSmoke)
    await writeJson(umbrelSmokeFile, umbrelSmoke)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, label)
    t.ok(err.stderr.includes(label), label)
  }
})

test('release evidence verifier rejects stale smoke evidence timestamps', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(fleetFile, fleetEvidence())

  const cases = [
    {
      label: 'release-image-smoke generatedAt',
      mutate: (imageSmoke) => { delete imageSmoke.generatedAt }
    },
    {
      label: 'release-image-smoke generatedAt must not be before release image manifest generatedAt',
      mutate: (imageSmoke) => { imageSmoke.generatedAt = '2026-06-21T23:59:59.999Z' }
    },
    {
      label: 'umbrel-package-smoke generatedAt must not be after release generatedAt',
      mutate: (_imageSmoke, umbrelSmoke) => { umbrelSmoke.generatedAt = '2026-06-22T00:00:00.001Z' }
    },
    {
      label: 'umbrel-package-smoke generatedAt must not be before release image manifest generatedAt',
      mutate: (_imageSmoke, umbrelSmoke) => { umbrelSmoke.generatedAt = '2026-06-21T23:59:59.999Z' }
    }
  ]

  for (const { label, mutate } of cases) {
    const imageSmoke = releaseImageSmokeEvidence()
    const umbrelSmoke = umbrelSmokeEvidence()
    mutate(imageSmoke, umbrelSmoke)
    await writeJson(releaseImageSmokeFile, imageSmoke)
    await writeJson(umbrelSmokeFile, umbrelSmoke)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, label)
    t.ok(err.stderr.includes(label), label)
  }
})

test('release evidence verifier rejects malformed successful workflow identity', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  release.release.workflow.runUrl = 'https://github.com/attacker/hiverelay/actions/runs/123'
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('successful release workflow URL'))

  const zeroRunIdRelease = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  zeroRunIdRelease.release.workflow.runId = '0'
  zeroRunIdRelease.release.workflow.runUrl = `https://github.com/${zeroRunIdRelease.release.workflow.repository}/actions/runs/0`
  await writeJson(releaseFile, zeroRunIdRelease)

  let zeroRunIdErr = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    zeroRunIdErr = e
  }

  t.ok(zeroRunIdErr)
  t.ok(zeroRunIdErr.stderr.includes('successful release workflow run id'))

  const zeroAttemptRelease = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  zeroAttemptRelease.release.workflow.runAttempt = '0'
  await writeJson(releaseFile, zeroAttemptRelease)

  let zeroAttemptErr = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    zeroAttemptErr = e
  }

  t.ok(zeroAttemptErr)
  t.ok(zeroAttemptErr.stderr.includes('successful release workflow run attempt'))
})

test('release evidence verifier rejects successful evidence without metadata SHA', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  release.release.metadataSha = ''
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release.metadataSha'))
})

test('release evidence verifier rejects StartOS package path drift', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  const release = releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  })
  release.artifacts.startosPackage.path = 'wrong/blindspark.s9pk'
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS package path'))
})

test('release evidence verifier rejects failed workflow evidence by default', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const release = releaseEvidence({})
  release.release.workflow.status = 'failure'

  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify(['--release', releaseFile])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release workflow status must be "success"'))
})

test('release evidence verifier accepts failed workflow evidence only as an explicit diagnostic', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const release = releaseEvidence({})
  release.release.workflow.status = 'failure'
  release.image.digest = ''
  release.image.ref = ''
  release.artifacts.startosPackage.sha256 = ''
  release.surfaces.fleetRolloutEvidence = { path: '', sha256: '' }

  await writeJson(releaseFile, release)

  const res = await runVerify(['--release', releaseFile, '--allow-failed-diagnostic'])

  t.ok(res.stdout.includes('Diagnostic release evidence verified: v9.9.9'))
})

test('release evidence verifier rejects unsafe diagnostic fleet evidence sidecars', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const body = fleetEvidence()
  body.relays[0].error = 'Authorization: Bearer ghp_abcdefghijklmnopqrstuvwxyz123456'

  await writeJson(fleetFile, body)
  const release = releaseEvidence({
    rolloutSha: await sha256File(fleetFile)
  })
  release.release.workflow.status = 'failure'
  release.image.digest = ''
  release.image.ref = ''
  release.artifacts.startosPackage.sha256 = ''
  release.surfaces.fleetRolloutEvidence = {
    path: 'fleet-rollout-evidence.json',
    sha256: await sha256File(fleetFile)
  }
  await writeJson(releaseFile, release)

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--fleet-rollout', fleetFile,
      '--allow-failed-diagnostic'
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('fleet rollout evidence must not expose authorization header') ||
    err.stderr.includes('fleet rollout evidence must not expose GitHub token'))
})

test('release evidence verifier rejects fleet evidence that exposes host data', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const body = fleetEvidence()
  body.relays[0].host = '10.0.0.1'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, body)
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('must not expose host'))
})

test('release evidence verifier rejects stale fleet package versions', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const body = fleetEvidence()
  body.relays[0].packageVersion = 'v9.9.8'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, body)
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('package version'))
})

test('release evidence verifier rejects incomplete fleet package-version convergence proof', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())

  const cases = [
    {
      label: 'fleet rollout summary packageVersionMatches',
      mutate: (body) => { body.summary.packageVersionMatches = body.relays.length - 1 }
    },
    {
      label: 'fleet rollout relay utah packageVersionMatches',
      mutate: (body) => { body.relays[0].packageVersionMatches = false }
    }
  ]

  for (const { label, mutate } of cases) {
    const body = fleetEvidence()
    mutate(body)
    await writeJson(fleetFile, body)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, label)
    t.ok(err.stderr.includes(label), label)
  }
})

test('release evidence verifier rejects stale fleet relay observation timestamps', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())

  const cases = [
    {
      label: 'fleet rollout relay utah observedAt',
      mutate: (body) => { delete body.relays[0].observedAt }
    },
    {
      label: 'observedAt must not be after fleet rollout generatedAt',
      mutate: (body) => { body.relays[0].observedAt = '2026-06-22T00:00:00.001Z' }
    }
  ]

  for (const { label, mutate } of cases) {
    const body = fleetEvidence()
    mutate(body)
    await writeJson(fleetFile, body)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, label)
    t.ok(err.stderr.includes(label), label)
  }
})

test('release evidence verifier rejects incomplete fleet evidence for the authoritative inventory', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const body = fleetEvidence()
  body.relays = body.relays.filter((relay) => relay.channel === 'canary')
  body.summary.total = body.relays.length
  body.summary.updated = body.relays.length
  body.summary.packageVersionMatches = body.relays.length
  body.summary.healthy = body.relays.length
  body.summary.runtimeVersionMatches = body.relays.length

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, body)
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('fleet rollout relay names'))
})

test('release evidence verifier rejects fleet inventory proof drift', async (t) => {
  const cases = [
    {
      label: 'fleet rollout inventory path',
      mutate: (body) => { body.inventory.path = 'other-relays.json' }
    },
    {
      label: 'fleet rollout inventory SHA-256',
      mutate: (body) => { body.inventory.sha256 = 'f'.repeat(64) }
    },
    {
      label: 'fleet rollout inventory relay names',
      mutate: (body) => { body.inventory.relayNames = body.inventory.relayNames.slice(0, 2) }
    }
  ]

  for (const { label, mutate } of cases) {
    const dir = await fixtureDir(t)
    const releaseFile = path.join(dir, 'release-evidence.json')
    const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
    const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
    const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
    const s9pkFile = path.join(dir, 'blindspark.s9pk')
    const body = fleetEvidence()
    mutate(body)

    await writeFile(s9pkFile, 'fake-s9pk')
    await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
    await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
    await writeJson(fleetFile, body)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, label)
    t.ok(err.stderr.includes(label), label)
  }
})

test('release evidence verifier rejects fleet channel config proof drift', async (t) => {
  const cases = [
    {
      label: 'fleet rollout channel config path',
      mutate: (body) => { body.channelConfig.path = 'other-channels.json' }
    },
    {
      label: 'fleet rollout channel config SHA-256',
      mutate: (body) => { body.channelConfig.sha256 = '4'.repeat(64) }
    },
    {
      label: 'fleet rollout channel canary target',
      mutate: (body) => { body.channelConfig.targets.canary = 'v9.9.8' }
    }
  ]

  for (const { label, mutate } of cases) {
    const dir = await fixtureDir(t)
    const releaseFile = path.join(dir, 'release-evidence.json')
    const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
    const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
    const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
    const s9pkFile = path.join(dir, 'blindspark.s9pk')
    const body = fleetEvidence()
    mutate(body)

    await writeFile(s9pkFile, 'fake-s9pk')
    await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
    await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
    await writeJson(fleetFile, body)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, label)
    t.ok(err.stderr.includes(label), label)
  }
})

test('release evidence verifier rejects duplicate fleet relay names', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const body = fleetEvidence()
  body.relays[1].name = body.relays[0].name

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, body)
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('duplicate relay name'))
})

test('release evidence verifier rejects secret-looking smoke evidence values', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const imageSmoke = releaseImageSmokeEvidence()
  imageSmoke.checks[0].note = 'Authorization: Bearer ghp_abcdefghijklmnopqrstuvwxyz123456'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, imageSmoke)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('must not expose'))
  t.ok(err.stderr.includes('authorization header') || err.stderr.includes('GitHub token'))
})

test('release evidence verifier rejects hyphenated API-key smoke evidence values', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const imageSmoke = releaseImageSmokeEvidence()
  imageSmoke.checks[0].note = 'sk-test_key-with-dashes_1234567890'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, imageSmoke)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('must not expose API key'))
})

test('release evidence verifier rejects env-style secret smoke evidence values', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const imageSmoke = releaseImageSmokeEvidence()
  imageSmoke.checks[0].note = 'APP_SEED=super-secret-seed'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, imageSmoke)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('must not expose APP_SEED'))
})

test('release evidence verifier rejects control-character public evidence values', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const imageSmoke = releaseImageSmokeEvidence()
  imageSmoke.checks[0].note = 'line-one\nline-two'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, imageSmoke)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('must not contain control characters'))
})

test('release evidence verifier rejects secret-looking fleet evidence values', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const body = fleetEvidence()
  body.relays[0].error = 'Bearer ghp_abcdefghijklmnopqrstuvwxyz123456'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, body)
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('fleet rollout evidence must not expose'))
  t.ok(err.stderr.includes('bearer token') || err.stderr.includes('GitHub token'))
})

test('release evidence verifier rejects env-style secret fleet evidence values', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const body = fleetEvidence()
  body.relays[0].error = 'HIVERELAY_API_KEY=super-secret-key'

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, body)
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('fleet rollout evidence must not expose API key'))
})

test('release evidence verifier accepts successful prereleases without fleet sidecar', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence({ prerelease: true }))
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence({ prerelease: true }))
  await writeJson(releaseFile, releaseEvidence({
    prerelease: true,
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: ''
  }))

  const res = await runVerify([
    '--release', releaseFile,
    '--release-image-smoke', releaseImageSmokeFile,
    '--umbrel-package-smoke', umbrelSmokeFile,
    '--startos-package', s9pkFile
  ])

  t.ok(res.stdout.includes('Release evidence verified: v9.9.9-beta.1'))
})

test('release evidence verifier accepts successful branch candidates without release assets', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence({ prerelease: true }))
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence({ prerelease: true }))
  await writeJson(releaseFile, releaseEvidence({
    prerelease: true,
    candidate: true,
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: ''
  }))

  const res = await runVerify([
    '--release', releaseFile,
    '--release-image-smoke', releaseImageSmokeFile,
    '--umbrel-package-smoke', umbrelSmokeFile,
    '--startos-package', s9pkFile
  ])

  t.ok(res.stdout.includes('Release evidence verified: v9.9.9-beta.1'))
})

test('release evidence verifier rejects malformed prerelease boundary facts', async (t) => {
  const cases = [
    ['non-boolean prerelease flag', release => { release.release.prerelease = 'true' }, 'release.prerelease must be a boolean'],
    ['non-prerelease candidate flag', release => { release.release.prerelease = false; release.release.candidate = true }, 'release.candidate requires release.prerelease=true'],
    ['npm packages', release => { release.surfaces.npmPackages = 'published' }, 'prerelease npm packages'],
    ['registry URL', release => { release.surfaces.startosRegistryUrl = STARTOS_REGISTRY_URL }, 'prerelease StartOS registry URL'],
    ['package id', release => { release.surfaces.startosPackageId = 'blindspark' }, 'prerelease StartOS package id'],
    ['official PR URL', release => { release.surfaces.umbrelOfficial.prUrl = 'https://github.com/getumbrel/umbrel-apps/pull/123' }, 'prerelease official Umbrel PR URL'],
    ['official PR draft', release => { release.surfaces.umbrelOfficial.isDraft = false }, 'prerelease official Umbrel PR draft'],
    ['community commit', release => { release.surfaces.umbrelCommunityStore.commit = TAG_SHA }, 'prerelease Umbrel community commit']
  ]

  for (const [name, mutate, expectedError] of cases) {
    const dir = await fixtureDir(t)
    const releaseFile = path.join(dir, `${name.replaceAll(' ', '-')}.json`)
    const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
    const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
    const s9pkFile = path.join(dir, 'blindspark.s9pk')

    await writeFile(s9pkFile, 'fake-s9pk')
    await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence({ prerelease: true }))
    await writeJson(umbrelSmokeFile, umbrelSmokeEvidence({ prerelease: true }))
    const release = releaseEvidence({
      prerelease: true,
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: ''
    })
    mutate(release)
    await writeJson(releaseFile, release)

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, name)
    t.ok(err.stderr.includes(expectedError), name)
  }
})

test('release evidence verifier rejects stale service smoke plugin lists', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const imageSmoke = releaseImageSmokeEvidence()
  imageSmoke.checks.find(check => check.name === 'servicesSave').plugins = ['vrf', 'arbitration', 'zk', 'ai']

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, imageSmoke)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release-image-smoke servicesSave plugins must be ["poker","vrf","arbitration","zk","ai"]'))
})

test('release evidence verifier rejects stale Umbrel restart service proof', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const umbrelSmoke = umbrelSmokeEvidence()
  umbrelSmoke.checks.find(check => check.name === 'servicesPersistence').active = ['vrf', 'arbitration', 'zk', 'ai']

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmoke)
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('umbrel-package-smoke servicesPersistence active must be ["poker","vrf","arbitration","zk","ai"]'))
})

test('release evidence verifier rejects incomplete smoke evidence', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const imageSmoke = releaseImageSmokeEvidence()
  imageSmoke.checks = imageSmoke.checks.filter(check => check.name !== 'walletWrite')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, imageSmoke)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release-image-smoke evidence is missing required check walletWrite'))
})

test('release evidence verifier rejects duplicate smoke evidence checks', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const imageSmoke = releaseImageSmokeEvidence()
  imageSmoke.checks.push({ ...imageSmoke.checks.find(check => check.name === 'walletWrite') })

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, imageSmoke)
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release-image-smoke evidence has duplicate check walletWrite'))
})

test('release evidence verifier rejects unsupported smoke evidence fields', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(umbrelSmokeFile, umbrelSmokeEvidence())
  await writeJson(fleetFile, fleetEvidence())

  const cases = [
    ['top-level', smoke => { smoke.marketplacePublished = true }, 'release-image-smoke evidence has unsupported fields: marketplacePublished'],
    ['check field', smoke => { smoke.checks.find(check => check.name === 'walletWrite').note = 'reviewed' }, 'release-image-smoke check walletWrite has unsupported fields: note'],
    ['unsupported check', smoke => { smoke.checks.push({ name: 'marketplacePublished', status: 'passed' }) }, 'release-image-smoke evidence has unsupported check marketplacePublished'],
    ['nested telemetry', smoke => { smoke.checks.find(check => check.name === 'usageTelemetry').bandwidth.extraCount = 1 }, 'release-image-smoke usageTelemetry bandwidth has unsupported fields: extraCount']
  ]

  for (const [name, mutate, expected] of cases) {
    const imageSmoke = releaseImageSmokeEvidence()
    mutate(imageSmoke)
    await writeJson(releaseImageSmokeFile, imageSmoke)
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, name)
    t.ok(err.stderr.includes(expected), name)
  }
})

test('release evidence verifier rejects stale critical smoke proof details', async (t) => {
  const cases = [
    {
      label: 'release-image-smoke dashboardWebSocket inBandAuth',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'dashboardWebSocket').inBandAuth = false
      }
    },
    {
      label: 'release-image-smoke health version',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'health').version = '9.9.8'
      }
    },
    {
      label: 'release-image-smoke walletWrite destinationSaved',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'walletWrite').destinationSaved = false
      }
    },
    {
      label: 'release-image-smoke dashboard walletBusyState',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'dashboard').walletBusyState = false
      }
    },
    {
      label: 'release-image-smoke dashboard dynamicPayoutControls',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'dashboard').dynamicPayoutControls = false
      }
    },
    {
      label: 'release-image-smoke dashboard appProxyWrites',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'dashboard').appProxyWrites = false
      }
    },
    {
      label: 'release-image-smoke dashboard serviceInlinePlanState',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'dashboard').serviceInlinePlanState = false
      }
    },
    {
      label: 'release-image-smoke setupWizard actionLock',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'setupWizard').actionLock = false
      }
    },
    {
      label: 'release-image-smoke setupWizard dashboardLinkAppPath',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'setupWizard').dashboardLinkAppPath = false
      }
    },
    {
      label: 'release-image-smoke usageTelemetry bandwidth bandwidthBytes',
      mutate: (imageSmoke) => {
        imageSmoke.checks.find(check => check.name === 'usageTelemetry').bandwidth.bandwidthBytes = -1
      }
    },
    {
      label: 'umbrel-package-smoke firstBoot acceptMode',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'firstBoot').acceptMode = 'open'
      }
    },
    {
      label: 'umbrel-package-smoke secondBoot healthVersion',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'secondBoot').healthVersion = '9.9.8'
      }
    },
    {
      label: 'umbrel-package-smoke firstBoot dashboardUiHardening',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'firstBoot').dashboardUiHardening = false
      }
    },
    {
      label: 'umbrel-package-smoke firstBoot appProxyWrites',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'firstBoot').appProxyWrites = false
      }
    },
    {
      label: 'umbrel-package-smoke firstBoot serviceInlinePlanState',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'firstBoot').serviceInlinePlanState = false
      }
    },
    {
      label: 'umbrel-package-smoke firstBoot dynamicPayoutControls',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'firstBoot').dynamicPayoutControls = false
      }
    },
    {
      label: 'umbrel-package-smoke secondBoot setupUiHardening',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'secondBoot').setupUiHardening = false
      }
    },
    {
      label: 'umbrel-package-smoke secondBoot setupStaticMarkupSafe',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'secondBoot').setupStaticMarkupSafe = false
      }
    },
    {
      label: 'umbrel-package-smoke walletPersistence destinationPersisted',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'walletPersistence').destinationPersisted = false
      }
    },
    {
      label: 'umbrel-package-smoke usageTelemetry poker enabled',
      mutate: (_imageSmoke, umbrelSmoke) => {
        umbrelSmoke.checks.find(check => check.name === 'usageTelemetry').poker.enabled = 'no'
      }
    }
  ]

  for (const { label, mutate } of cases) {
    const dir = await fixtureDir(t)
    const releaseFile = path.join(dir, 'release-evidence.json')
    const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
    const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
    const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
    const s9pkFile = path.join(dir, 'blindspark.s9pk')
    const imageSmoke = releaseImageSmokeEvidence()
    const umbrelSmoke = umbrelSmokeEvidence()
    mutate(imageSmoke, umbrelSmoke)

    await writeFile(s9pkFile, 'fake-s9pk')
    await writeJson(releaseImageSmokeFile, imageSmoke)
    await writeJson(umbrelSmokeFile, umbrelSmoke)
    await writeJson(fleetFile, fleetEvidence())
    await writeJson(releaseFile, releaseEvidence({
      startosSha: await sha256File(s9pkFile),
      imageSmokeSha: await sha256File(releaseImageSmokeFile),
      umbrelSmokeSha: await sha256File(umbrelSmokeFile),
      rolloutSha: await sha256File(fleetFile)
    }))

    let err = null
    try {
      await runVerify([
        '--release', releaseFile,
        '--release-image-smoke', releaseImageSmokeFile,
        '--umbrel-package-smoke', umbrelSmokeFile,
        '--fleet-rollout', fleetFile,
        '--startos-package', s9pkFile
      ])
    } catch (e) {
      err = e
    }

    t.ok(err, label)
    t.ok(err.stderr.includes(label), label)
  }
})

test('release evidence verifier rejects smoke evidence without dashboard WebSocket proof', async (t) => {
  const dir = await fixtureDir(t)
  const releaseFile = path.join(dir, 'release-evidence.json')
  const releaseImageSmokeFile = path.join(dir, 'release-image-smoke-evidence.json')
  const umbrelSmokeFile = path.join(dir, 'umbrel-package-smoke-evidence.json')
  const fleetFile = path.join(dir, 'fleet-rollout-evidence.json')
  const s9pkFile = path.join(dir, 'blindspark.s9pk')
  const umbrelSmoke = umbrelSmokeEvidence()
  umbrelSmoke.checks = umbrelSmoke.checks.filter(check => check.name !== 'dashboardWebSocket')

  await writeFile(s9pkFile, 'fake-s9pk')
  await writeJson(releaseImageSmokeFile, releaseImageSmokeEvidence())
  await writeJson(umbrelSmokeFile, umbrelSmoke)
  await writeJson(fleetFile, fleetEvidence())
  await writeJson(releaseFile, releaseEvidence({
    startosSha: await sha256File(s9pkFile),
    imageSmokeSha: await sha256File(releaseImageSmokeFile),
    umbrelSmokeSha: await sha256File(umbrelSmokeFile),
    rolloutSha: await sha256File(fleetFile)
  }))

  let err = null
  try {
    await runVerify([
      '--release', releaseFile,
      '--release-image-smoke', releaseImageSmokeFile,
      '--umbrel-package-smoke', umbrelSmokeFile,
      '--fleet-rollout', fleetFile,
      '--startos-package', s9pkFile
    ])
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('umbrel-package-smoke evidence is missing required check dashboardWebSocket'))
})