OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / startos-registry-evidence.test.js

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

const REPOSITORY = 'bigdestiny2/P2P-Hiverelay'
const RELEASE_BASE = `https://github.com/${REPOSITORY}/releases/download/v9.9.9`
const REGISTRY_URL = 'https://registry.start9.com/startos'
const REGISTRY_PACKAGE_URL = `${REGISTRY_URL}/blindspark`
const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
const WRITE_STARTOS_REGISTRY_EVIDENCE_SCRIPT = path.join(process.cwd(), 'scripts/write-startos-registry-evidence.mjs')
const PACKAGE_BYTES = 'startos package\n'
const IMAGE_MANIFEST_BYTES = '{"kind":"release-image-manifest"}\n'
const IMAGE_SMOKE_BYTES = '{"kind":"release-image-smoke"}\n'
const PACKAGE_SHA256 = sha256(PACKAGE_BYTES)
const IMAGE_MANIFEST_SHA256 = sha256(IMAGE_MANIFEST_BYTES)
const IMAGE_SMOKE_SHA256 = sha256(IMAGE_SMOKE_BYTES)
const LINKED_EVIDENCE_FILES = [
  'release-image-manifest-evidence.json',
  'release-image-smoke-evidence.json'
]

async function runWriter (outFile, env, opts = {}) {
  const cwd = opts.cwd || path.dirname(path.resolve(outFile))
  if (opts.linkedEvidence !== false) await writeLinkedEvidence(cwd, opts.linkedEvidence || {})
  if (opts.packageBytes !== false) {
    const packageBytes = opts.packageBytes || PACKAGE_BYTES
    await mkdir(path.join(cwd, 'startos'), { recursive: true })
    await writeFile(path.join(cwd, 'startos/blindspark.s9pk'), packageBytes)
  }
  return new Promise((resolve, reject) => {
    execFile(process.execPath, [WRITE_STARTOS_REGISTRY_EVIDENCE_SCRIPT, '--out', outFile], {
      cwd,
      env: { ...process.env, ...env },
      timeout: 10000
    }, (err, stdout, stderr) => {
      if (err) {
        err.stdout = stdout
        err.stderr = stderr
        reject(err)
      } else {
        resolve({ stdout, stderr })
      }
    })
  })
}

async function writeLinkedEvidence (dir, overrides = {}) {
  const evidence = {
    'release-image-manifest-evidence.json': IMAGE_MANIFEST_BYTES,
    'release-image-smoke-evidence.json': IMAGE_SMOKE_BYTES
  }
  await mkdir(dir, { recursive: true })
  for (const file of LINKED_EVIDENCE_FILES) {
    if (overrides[file] === false) continue
    await writeFile(path.join(dir, file), Object.prototype.hasOwnProperty.call(overrides, file) ? overrides[file] : evidence[file])
  }
}

function sha256 (value) {
  return createHash('sha256').update(value).digest('hex')
}

function env (overrides = {}) {
  return {
    GITHUB_SERVER_URL: 'https://github.com',
    GITHUB_REPOSITORY: REPOSITORY,
    GITHUB_RUN_ID: '12345',
    GITHUB_RUN_ATTEMPT: '2',
    HIVERELAY_RELEASE_VERSION: 'v9.9.9',
    HIVERELAY_RELEASE_SEMVER: '9.9.9',
    HIVERELAY_RELEASE_IMAGE_MANIFEST_EVIDENCE: 'release-image-manifest-evidence.json',
    HIVERELAY_RELEASE_IMAGE_MANIFEST_EVIDENCE_SHA256: IMAGE_MANIFEST_SHA256,
    HIVERELAY_RELEASE_IMAGE_SMOKE_EVIDENCE: 'release-image-smoke-evidence.json',
    HIVERELAY_RELEASE_IMAGE_SMOKE_EVIDENCE_SHA256: IMAGE_SMOKE_SHA256,
    HIVERELAY_STARTOS_PACKAGE_ID: 'blindspark',
    HIVERELAY_STARTOS_PACKAGE_SHA256: PACKAGE_SHA256,
    HIVERELAY_STARTOS_REGISTRY_URL: REGISTRY_URL,
    HIVERELAY_STARTOS_REGISTRY_PACKAGE_URL: REGISTRY_PACKAGE_URL,
    ...overrides
  }
}

test('StartOS registry evidence writer keeps a closed public schema', async (t) => {
  const script = await readFile(WRITE_STARTOS_REGISTRY_EVIDENCE_SCRIPT, 'utf8')

  t.ok(script.includes('assertStartosRegistryEvidenceSchema(body)'))
  t.ok(script.includes('function assertStartosRegistryEvidenceSchema'))
  t.ok(script.includes("requireOnlyKeys('StartOS registry evidence'"))
  t.ok(script.includes('has unsupported fields'))
})

test('StartOS registry evidence writer records public package and registry facts', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  await runWriter(outFile, env())

  const body = JSON.parse(await readFile(outFile, 'utf8'))
  t.is(body.schemaVersion, 1)
  t.ok(ISO_TIMESTAMP_PATTERN.test(body.generatedAt), 'StartOS registry evidence generatedAt is an ISO timestamp')
  t.is(body.kind, 'startos-registry-publication')
  t.is(body.status, 'published')
  t.is(body.release.version, 'v9.9.9')
  t.is(body.package.id, 'blindspark')
  t.is(body.package.sha256, PACKAGE_SHA256)
  t.is(body.package.url, REGISTRY_PACKAGE_URL)
  t.is(body.registry.url, REGISTRY_URL)
  t.is(body.workflow.runUrl, `https://github.com/${REPOSITORY}/actions/runs/12345`)
  t.is(body.evidenceLinks.releaseEvidence, `${RELEASE_BASE}/release-evidence.json`)
  t.is(body.evidenceLinks.releaseImageManifest, `${RELEASE_BASE}/release-image-manifest-evidence.json`)
  t.is(body.evidenceLinks.releaseImageSmoke, `${RELEASE_BASE}/release-image-smoke-evidence.json`)
  t.is(body.evidenceLinks.startosPackage, `${RELEASE_BASE}/blindspark.s9pk`)
  t.is(body.evidenceLinks.registryPackage, REGISTRY_PACKAGE_URL)
})

test('StartOS registry evidence writer verifies present package artifacts before writing evidence', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const packageBytes = 'startos package\n'
  await mkdir(path.join(dir, 'startos'), { recursive: true })
  await writeFile(path.join(dir, 'startos/blindspark.s9pk'), packageBytes)

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  await runWriter(outFile, env({
    HIVERELAY_STARTOS_PACKAGE_SHA256: sha256(packageBytes)
  }), { cwd: dir })

  const body = JSON.parse(await readFile(outFile, 'utf8'))
  t.is(body.package.path, 'startos/blindspark.s9pk')
  t.is(body.package.sha256, sha256(packageBytes))
})

test('StartOS registry evidence writer hashes present package artifacts as a stream', async (t) => {
  const script = await readFile(WRITE_STARTOS_REGISTRY_EVIDENCE_SCRIPT, 'utf8')
  t.ok(script.includes('fs.createReadStream(file)'), 'package hash check streams the .s9pk')
  t.absent(script.includes('fs.readFileSync(file)'), 'package hash check does not load the full .s9pk into memory')
})

test('StartOS registry evidence writer requires the local package artifact before writing evidence', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env(), { cwd: dir, packageBytes: false })
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS package file is required before writing registry evidence'))
})

test('StartOS registry evidence writer requires linked release image evidence before writing registry evidence', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const missingFile = path.join(dir, 'missing-linked-image-proof.json')
  let missingErr = null
  try {
    await runWriter(missingFile, env(), {
      cwd: dir,
      linkedEvidence: { 'release-image-manifest-evidence.json': false }
    })
  } catch (e) {
    missingErr = e
  }
  t.ok(missingErr)
  t.ok(missingErr.stderr.includes('release image manifest evidence file is required before writing registry evidence: release-image-manifest-evidence.json'))

  const symlinkDir = path.join(dir, 'symlink')
  await mkdir(symlinkDir, { recursive: true })
  await writeFile(path.join(symlinkDir, 'manifest-target.json'), '{}\n')
  await symlink('manifest-target.json', path.join(symlinkDir, 'release-image-manifest-evidence.json'))
  const symlinkFile = path.join(symlinkDir, 'symlink-linked-image-proof.json')
  let symlinkErr = null
  try {
    await runWriter(symlinkFile, env(), { cwd: symlinkDir, linkedEvidence: false })
  } catch (e) {
    symlinkErr = e
  }
  t.ok(symlinkErr)
  t.ok(symlinkErr.stderr.includes('release image manifest evidence file must not be a symlink: release-image-manifest-evidence.json'))

  const oversizedDir = path.join(dir, 'oversized')
  await mkdir(oversizedDir, { recursive: true })
  await writeFile(path.join(oversizedDir, 'release-image-manifest-evidence.json'), IMAGE_MANIFEST_BYTES)
  await writeFile(path.join(oversizedDir, 'release-image-smoke-evidence.json'), Buffer.alloc(2 * 1024 * 1024 + 1, 'x'))
  const oversizedFile = path.join(oversizedDir, 'oversized-linked-image-proof.json')
  let oversizedErr = null
  try {
    await runWriter(oversizedFile, env(), { cwd: oversizedDir, linkedEvidence: false })
  } catch (e) {
    oversizedErr = e
  }
  t.ok(oversizedErr)
  t.ok(oversizedErr.stderr.includes('release image smoke evidence file must be 2097152 bytes or smaller'))
})

test('StartOS registry evidence writer rejects linked release image evidence hash drift', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'stale-linked-image-proof.json')
  let err = null
  try {
    await runWriter(outFile, env(), {
      cwd: dir,
      linkedEvidence: {
        'release-image-smoke-evidence.json': '{"kind":"release-image-smoke","stale":true}\n'
      }
    })
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release image smoke evidence SHA-256 does not match release-image-smoke-evidence.json'))
})

test('StartOS registry evidence writer rejects miswired release image evidence env before writing registry evidence', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const wrongPathFile = path.join(dir, 'wrong-linked-image-path.json')
  let wrongPathErr = null
  try {
    await runWriter(wrongPathFile, env({
      HIVERELAY_RELEASE_IMAGE_MANIFEST_EVIDENCE: 'manifest.json'
    }), { cwd: dir })
  } catch (e) {
    wrongPathErr = e
  }
  t.ok(wrongPathErr)
  t.ok(wrongPathErr.stderr.includes('release image manifest evidence path'))

  const badShaFile = path.join(dir, 'bad-linked-image-sha.json')
  let badShaErr = null
  try {
    await runWriter(badShaFile, env({
      HIVERELAY_RELEASE_IMAGE_SMOKE_EVIDENCE_SHA256: 'not-a-sha'
    }), { cwd: dir })
  } catch (e) {
    badShaErr = e
  }
  t.ok(badShaErr)
  t.ok(badShaErr.stderr.includes('release image smoke evidence SHA-256'))
})

test('StartOS registry evidence writer rejects unsafe linked release image evidence before write', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const unsafe = '{"kind":"release-image-smoke","token":"Bearer abcdefghijklmnopqrstuvwxyz"}\n'
  const outFile = path.join(dir, 'unsafe-linked-image-proof.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_RELEASE_IMAGE_SMOKE_EVIDENCE_SHA256: sha256(unsafe)
    }), {
      cwd: dir,
      linkedEvidence: {
        'release-image-smoke-evidence.json': unsafe
      }
    })
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release image smoke evidence must not contain bearer token'))
})

test('StartOS registry evidence writer rejects linked release image evidence kind drift', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const wrongManifest = '{"kind":"release-image-smoke"}\n'
  const outFile = path.join(dir, 'wrong-linked-image-kind.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_RELEASE_IMAGE_MANIFEST_EVIDENCE_SHA256: sha256(wrongManifest)
    }), {
      cwd: dir,
      linkedEvidence: {
        'release-image-manifest-evidence.json': wrongManifest
      }
    })
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('release image manifest evidence kind must be "release-image-manifest"'))
})

test('StartOS registry evidence writer rejects symlinked present package artifacts', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  await mkdir(path.join(dir, 'startos'), { recursive: true })
  await writeFile(path.join(dir, 'startos/package-target.s9pk'), 'startos package\n')
  await symlink('package-target.s9pk', path.join(dir, 'startos/blindspark.s9pk'))

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env(), { cwd: dir, packageBytes: false })
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS package file must not be a symlink'))
})

test('StartOS registry evidence writer rejects non-regular present package artifacts', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  await mkdir(path.join(dir, 'startos/blindspark.s9pk'), { recursive: true })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env(), { cwd: dir, packageBytes: false })
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS package file must be a regular file'))
})

test('StartOS registry evidence writer rejects present package artifact hash drift', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const packageBytes = 'stale startos package\n'
  await mkdir(path.join(dir, 'startos'), { recursive: true })
  await writeFile(path.join(dir, 'startos/blindspark.s9pk'), packageBytes)

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_STARTOS_PACKAGE_SHA256: '0'.repeat(64)
    }), { cwd: dir, packageBytes: false })
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('StartOS package SHA-256 does not match startos/blindspark.s9pk'))
  t.ok(err.stderr.includes(sha256(packageBytes)), 'reports the actual present package hash')
})

test('StartOS registry evidence writer derives package URL from registry and package id', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  await runWriter(outFile, env({
    HIVERELAY_STARTOS_REGISTRY_PACKAGE_URL: ''
  }))

  const body = JSON.parse(await readFile(outFile, 'utf8'))
  t.is(body.package.url, REGISTRY_PACKAGE_URL)
  t.is(body.evidenceLinks.registryPackage, REGISTRY_PACKAGE_URL)
})

test('StartOS registry evidence writer rejects non-canonical release repositories', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      GITHUB_REPOSITORY: 'attacker/P2P-Hiverelay'
    }))
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes(`workflow repository must be "${REPOSITORY}"`))
})

test('StartOS registry evidence writer rejects non-https registry URLs', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_STARTOS_REGISTRY_URL: 'http://registry.start9.com/startos'
    }))
  } catch (e) {
    err = e
  }

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

test('StartOS registry evidence writer rejects credentialed registry URLs', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_STARTOS_REGISTRY_URL: 'https://user:pass@registry.start9.com/startos'
    }))
  } catch (e) {
    err = e
  }

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

test('StartOS registry evidence writer rejects mismatched package URLs', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_STARTOS_REGISTRY_PACKAGE_URL: 'https://registry.start9.com/startos/other'
    }))
  } catch (e) {
    err = e
  }

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

test('StartOS registry evidence writer rejects non-numeric run attempts', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      GITHUB_RUN_ATTEMPT: 'not-a-number'
    }))
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('workflow run attempt'))

  const zeroAttemptFile = path.join(dir, 'zero-attempt.json')
  let zeroAttemptErr = null
  try {
    await runWriter(zeroAttemptFile, env({
      GITHUB_RUN_ATTEMPT: '0'
    }))
  } catch (e) {
    zeroAttemptErr = e
  }

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

  const zeroRunIdFile = path.join(dir, 'zero-run-id.json')
  let zeroRunIdErr = null
  try {
    await runWriter(zeroRunIdFile, env({
      GITHUB_RUN_ID: '0'
    }))
  } catch (e) {
    zeroRunIdErr = e
  }

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

  const badServerFile = path.join(dir, 'bad-workflow-server.json')
  let badServerErr = null
  try {
    await runWriter(badServerFile, env({
      GITHUB_SERVER_URL: 'https://github.com/attacker'
    }))
  } catch (e) {
    badServerErr = e
  }

  t.ok(badServerErr)
  t.ok(badServerErr.stderr.includes('workflow URL'))
})

test('StartOS registry evidence writer rejects whitespace-normalized metadata before write', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const cases = [
    ['run-id', { GITHUB_RUN_ID: ' 12345' }, 'workflow run id'],
    ['run-attempt', { GITHUB_RUN_ATTEMPT: '2 ' }, 'workflow run attempt'],
    ['server-url', { GITHUB_SERVER_URL: 'https://github.com ' }, 'workflow URL'],
    ['package-id', { HIVERELAY_STARTOS_PACKAGE_ID: 'blindspark ' }, 'StartOS package id'],
    ['package-sha', { HIVERELAY_STARTOS_PACKAGE_SHA256: `${PACKAGE_SHA256} ` }, 'StartOS package SHA-256'],
    ['registry-url', { HIVERELAY_STARTOS_REGISTRY_URL: `${REGISTRY_URL} ` }, 'StartOS registry URL'],
    ['image-manifest-path', { HIVERELAY_RELEASE_IMAGE_MANIFEST_EVIDENCE: ' release-image-manifest-evidence.json' }, 'release image manifest evidence path']
  ]

  for (const [name, overrides, message] of cases) {
    const outFile = path.join(dir, `${name}.json`)
    let err = null
    try {
      await runWriter(outFile, env(overrides))
    } catch (e) {
      err = e
    }

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

    let evidenceErr = null
    try {
      await readFile(outFile, 'utf8')
    } catch (e) {
      evidenceErr = e
    }
    t.ok(evidenceErr, `${name} does not write public evidence`)
  }
})

test('StartOS registry evidence writer rejects secret-looking public values before write', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      GITHUB_RUN_ATTEMPT: 'Bearer abcdefghijklmnopqrstuvwxyz'
    }))
  } catch (e) {
    err = e
  }

  t.ok(err)
  t.ok(err.stderr.includes('bearer token') || err.stderr.includes('workflow run attempt'))
})

test('StartOS registry evidence writer rejects control-character public values before write', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_STARTOS_REGISTRY_URL: 'https://registry.start9.com/startos\nnext'
    }))
  } catch (e) {
    err = e
  }

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

test('StartOS registry evidence writer rejects placeholder registry hosts', async (t) => {
  const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-startos-registry-evidence-'))
  t.teardown(async () => {
    await rm(dir, { recursive: true, force: true })
  })

  const outFile = path.join(dir, 'startos-registry-evidence.json')
  let err = null
  try {
    await runWriter(outFile, env({
      HIVERELAY_STARTOS_REGISTRY_URL: 'https://registry.example/startos'
    }))
  } catch (e) {
    err = e
  }

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