OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / public-hive-gateway-release-workflow.test.js

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

const RESOLVER = path.join(process.cwd(), 'scripts/resolve-public-hive-gateway-release.mjs')
const MANIFEST_PATH = 'fleet/public-hive-gateway-release.json'
const APP_KEY = 'aa'.repeat(32)
const SUFFIX = 'hive-canary.operator.example'
const LABEL = z32(Buffer.from(APP_KEY, 'hex'))
const FINGERPRINT = Array(32).fill('AA').join(':')

test('tagged enabled public gateway manifests force every requested release channel to none', async (t) => {
  const repo = await gitRepo(t)
  const raw = JSON.stringify(manifest('v1.2.3'), null, 2) + '\n'
  await commitManifest(repo, raw, 'enabled gateway')
  await git(repo, ['tag', 'v1.2.3'])

  // The tag, not a later mutable worktree value, is authoritative.
  await writeFile(path.join(repo, MANIFEST_PATH), JSON.stringify({
    schema: 'hiverelay-public-gateway-release-v1',
    enabled: false
  }) + '\n')

  for (const requested of ['canary', 'stable', 'both', 'none']) {
    const result = await resolve(repo, 'v1.2.3', 'v1.2.3', requested)
    t.is(result.status, 0)
    const body = JSON.parse(result.stdout)
    t.is(body.status, 'enabled')
    t.is(body.enabled, true)
    t.is(body.requestedChannel, requested)
    t.is(body.effectiveChannel, 'none')
    t.is(body.manifestPath, MANIFEST_PATH)
    t.is(body.manifestSha256, createHash('sha256').update(raw).digest('hex'))
    t.is(body.releaseTarget, 'v1.2.3')
    t.is(body.admissionProfile, 'blind-substrate-public-v1')
    t.is(body.cohortSize, 1)
    t.ok(/^[a-f0-9]{40}$/.test(body.commitSha))
  }
})

test('missing and explicitly disabled tagged manifests retain ordinary channel behavior', async (t) => {
  const repo = await gitRepo(t)
  await writeFile(path.join(repo, 'README.md'), 'ordinary release\n')
  await git(repo, ['add', 'README.md'])
  await git(repo, ['commit', '-m', 'ordinary release'])
  await git(repo, ['tag', 'v1.2.2'])

  const missing = await resolve(repo, 'v1.2.2', 'v1.2.2', 'both')
  t.is(missing.status, 0)
  t.alike(pickResolution(JSON.parse(missing.stdout)), {
    status: 'missing',
    enabled: false,
    requestedChannel: 'both',
    effectiveChannel: 'both',
    manifestSha256: ''
  })

  const disabledRaw = JSON.stringify({
    schema: 'hiverelay-public-gateway-release-v1',
    enabled: false
  }, null, 2) + '\n'
  await commitManifest(repo, disabledRaw, 'disabled gateway')
  await git(repo, ['tag', 'v1.2.3'])
  const disabled = await resolve(repo, 'v1.2.3', 'v1.2.3', 'stable')
  t.is(disabled.status, 0)
  t.alike(pickResolution(JSON.parse(disabled.stdout)), {
    status: 'disabled',
    enabled: false,
    requestedChannel: 'stable',
    effectiveChannel: 'stable',
    manifestSha256: createHash('sha256').update(disabledRaw).digest('hex')
  })
})

test('tagged public gateway resolver fails closed on hostile or mismatched enabled manifests', async (t) => {
  const repo = await gitRepo(t)
  const cases = [
    ['v2.0.0', '{not json\n', 'valid JSON'],
    ['v2.0.1', JSON.stringify(manifest('v9.9.9')) + '\n', 'must target v2.0.1'],
    ['v2.0.2', JSON.stringify({ ...manifest('v2.0.2'), admissionProfile: 'transitional-allowlist' }) + '\n', 'frozen admissionProfile'],
    ['v2.0.3', JSON.stringify({ ...manifest('v2.0.3'), unexpected: true }) + '\n', 'unsupported fields'],
    ['v2.0.4', JSON.stringify({
      ...manifest('v2.0.4'),
      cohort: [{ ...manifest('v2.0.4').cohort[0], channel: 'stable' }]
    }) + '\n', 'at least one canary'],
    ['v2.0.5', JSON.stringify({
      ...manifest('v2.0.5'),
      admissionProfile: 'blind-$(untrusted-shell)'
    }) + '\n', 'frozen admissionProfile']
  ]

  for (const [tag, raw, message] of cases) {
    await commitManifest(repo, raw, `hostile ${tag}`)
    await git(repo, ['tag', tag])
    const result = await resolve(repo, tag, tag, 'both')
    t.is(result.status, 1)
    t.is(result.stdout, '')
    t.ok(result.stderr.includes(message), `${tag} reports ${message}`)
  }

  const unknown = await run(process.execPath, [
    RESOLVER,
    '--repo', repo,
    '--ref', 'v2.0.4',
    '--release-target', 'v2.0.4',
    '--requested-channel', 'both',
    '--surprise', 'true'
  ])
  t.is(unknown.status, 1)
  t.ok(unknown.stderr.includes('unknown argument'))
})

test('release workflow resolves the tagged gateway policy before every irreversible publication', async (t) => {
  const workflow = await readFile('.github/workflows/release-surfaces.yml', 'utf8')
  const release = await readFile('scripts/release.sh', 'utf8')
  const resolver = workflow.indexOf('node hiverelay/scripts/resolve-public-hive-gateway-release.mjs')

  t.ok(resolver > 0)
  t.ok(resolver < workflow.indexOf('- name: Ensure GitHub Release exists'))
  t.ok(resolver < workflow.indexOf('docker buildx build'))
  t.ok(resolver < workflow.indexOf('npm publish "$pkg"'))
  t.ok(workflow.includes('channel="$resolved_channel"'))
  t.ok(workflow.includes('--public-gateway-release "$' + '{{ steps.rel.outputs.public_gateway_enabled }}"'))
  t.ok(workflow.includes('HIVERELAY_FLEET_ROLLOUT_STATUS=$fleet_rollout_status'))
  t.ok(workflow.includes('fleet_rollout_status=deferred-gateway-canary-gated'))
  t.ok(workflow.includes('Release tag moved after public gateway policy resolution.'))

  const localResolver = release.indexOf('scripts/resolve-public-hive-gateway-release.mjs')
  const tagPush = release.indexOf('push origin "$version"')
  const githubRelease = release.indexOf('gh release create "$version"')
  t.ok(localResolver > 0)
  t.ok(localResolver < tagPush)
  t.ok(tagPush < githubRelease)
  t.ok(release.includes('GitHub Release creation is delegated to the manifest-gated release workflow'))
  t.ok(release.includes('--promote-canary is disabled for public gateway releases'))
})

async function gitRepo (t) {
  const repo = await mkdtemp(path.join(tmpdir(), 'hiverelay-gateway-release-'))
  t.teardown(async () => {
    await rm(repo, { recursive: true, force: true })
  })
  await git(repo, ['init', '-q'])
  await git(repo, ['config', 'user.name', 'Release Test'])
  await git(repo, ['config', 'user.email', 'release-test@example.test'])
  return repo
}

async function commitManifest (repo, raw, message) {
  await mkdir(path.join(repo, 'fleet'), { recursive: true })
  await writeFile(path.join(repo, MANIFEST_PATH), raw)
  await git(repo, ['add', MANIFEST_PATH])
  await git(repo, ['commit', '-m', message])
}

function resolve (repo, ref, releaseTarget, requestedChannel) {
  return run(process.execPath, [
    RESOLVER,
    '--repo', repo,
    '--ref', ref,
    '--release-target', releaseTarget,
    '--requested-channel', requestedChannel
  ])
}

function run (file, args) {
  return new Promise((resolve) => {
    execFile(file, args, {
      cwd: process.cwd(),
      env: { PATH: process.env.PATH || '' },
      timeout: 10000
    }, (err, stdout, stderr) => {
      resolve({
        status: err && typeof err.code === 'number' ? err.code : 0,
        stdout,
        stderr
      })
    })
  })
}

function git (repo, args) {
  return new Promise((resolve, reject) => {
    execFile('git', args, {
      cwd: repo,
      env: { PATH: process.env.PATH || '' },
      timeout: 10000
    }, (err, stdout, stderr) => {
      if (err) {
        err.stdout = stdout
        err.stderr = stderr
        reject(err)
      } else {
        resolve(stdout)
      }
    })
  })
}

function pickResolution (body) {
  return {
    status: body.status,
    enabled: body.enabled,
    requestedChannel: body.requestedChannel,
    effectiveChannel: body.effectiveChannel,
    manifestSha256: body.manifestSha256
  }
}

function manifest (releaseTarget) {
  return {
    schema: 'hiverelay-public-gateway-release-v1',
    enabled: true,
    releaseTarget,
    admissionProfile: 'blind-substrate-public-v1',
    observationWindowMs: 24 * 60 * 60 * 1000,
    maxProbeGapMs: 20 * 60 * 1000,
    cohort: [{
      relay: 'canary-1',
      channel: 'canary',
      suffix: SUFFIX,
      origin: `https://${LABEL}.${SUFFIX}/`,
      connectAddress: '127.0.0.1',
      appKey: APP_KEY,
      path: '/index.html',
      contentSha256: 'bb'.repeat(32),
      driveVersion: '7',
      peerFingerprint256: FINGERPRINT,
      nginxConfigSha256: 'cc'.repeat(32),
      deploymentProfile: 'public-t1-gateway',
      operatorContractSha256: 'dd'.repeat(32)
    }]
  }
}

function z32 (bytes) {
  const alphabet = 'ybndrfg8ejkmcpqxot1uwisza345h769'
  let output = ''
  let accumulator = 0
  let bits = 0
  for (const byte of bytes) {
    accumulator = (accumulator << 8) | byte
    bits += 8
    while (bits >= 5) {
      bits -= 5
      output += alphabet[(accumulator >>> bits) & 31]
      accumulator &= bits === 0 ? 0 : (1 << bits) - 1
    }
  }
  if (bits > 0) output += alphabet[(accumulator << (5 - bits)) & 31]
  return output
}