const fs = require('node:fs') const path = require('node:path') const { execFileSync } = require('node:child_process') const { parseEnvironment } = require('./workbench_acceptance_runtime.cjs') const { preserveFrontendAssets } = require('./preserve_frontend_assets.cjs') const { stagePublicAPIContext } = require('./stage_public_api_context.cjs') const root = path.resolve(__dirname, '../..') const database = 'yawan_native_acceptance_20260915' const apiName = 'yawan-native-acceptance' const network = 'weknora_WeKnora-network' const publicVerificationReports = ['public-release-smoke', 'public-navigation', 'public-access-control', 'public-resource-isolation', 'public-resource-files', 'public-tenant-isolation', 'public-indexed-retrieval-isolation'] const fixtureReconciliationCounts = ['active_users', 'active_memberships', 'roles', 'assignments', 'grants', 'unrevoked_tokens', 'active_agents', 'active_skills', 'active_digital_humans', 'workbench_resources', 'active_sessions', 'synthetic_messages', 'file_resources', 'file_bindings', 'file_access_grants', 'active_knowledge_bases', 'active_documents', 'active_chunks', 'tags', 'tag_relations', 'publications', 'secondary_tenant', 'secondary_memberships', 'memory_item_embeddings', 'memory_items', 'memory_tombstones', 'memory_topic_stats', 'memory_doc_affinity', 'memory_subjects'] const docker = (...args) => execFileSync('docker', ['--context', 'colima-yawan', ...args], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'], }).trim() const writeJSON = (file, value) => fs.writeFileSync(file, JSON.stringify(value, null, 2) + '\n', { mode: 0o600 }) // Include every native queue, plus queues discovered in Redis. Read all states // in one read-only Lua snapshot: an active task can otherwise move to retry // between separate commands and appear idle. Archived/completed history does // not block publication; waiting aggregation work does. const nativeQueues = ['default', 'chat_attachment', 'postprocess', 'summary', 'multimodal', 'graph', 'question', 'sync', 'low', 'wiki', 'memory'] const queueStates = ['active', 'pending', 'scheduled', 'retry', 'aggregating', 'aggregation_sets'] const queueSnapshotScript = ` local names = {} for _, name in ipairs(ARGV) do names[name] = true end for _, name in ipairs(redis.call('SMEMBERS', KEYS[1])) do names[name] = true end local result = {} for name, _ in pairs(names) do local prefix = 'asynq:{' .. name .. '}:' local aggregating = 0 for _, group in ipairs(redis.call('SMEMBERS', prefix .. 'groups')) do aggregating = aggregating + redis.call('ZCARD', prefix .. 'g:' .. group) end result[name] = { active = redis.call('LLEN', prefix .. 'active'), pending = redis.call('LLEN', prefix .. 'pending'), scheduled = redis.call('ZCARD', prefix .. 'scheduled'), retry = redis.call('ZCARD', prefix .. 'retry'), aggregating = aggregating, aggregation_sets = redis.call('ZCARD', prefix .. 'aggregation_sets') } end return cjson.encode(result) ` function idleCount(value) { if (!Number.isSafeInteger(value) || value < 0) throw new Error('Cannot verify background task counts') return value } function assertIdleSnapshot(snapshot) { if (!snapshot || idleCount(snapshot.workbench) !== 0) throw new Error('Background jobs, uploads or reserved balances are still active') if (idleCount(snapshot.pending_ops) !== 0) throw new Error('Wiki or other persisted background operations are still pending') const queues = snapshot.queues if (!queues || typeof queues !== 'object' || Array.isArray(queues) || nativeQueues.some(name => !Object.hasOwn(queues, name))) throw new Error('Cannot verify native background queues') for (const state of Object.values(queues)) { if (!state || typeof state !== 'object' || queueStates.some(name => idleCount(state[name]) !== 0)) { throw new Error('Native background queues still have active or waiting tasks') } } } function idle(run = execFileSync) { const invoke = (args, env = process.env) => run('docker', ['--context', 'colima-yawan', ...args], { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, timeout: 15000, env, stdio: ['ignore', 'pipe', 'pipe'], }).trim() let snapshot try { snapshot = JSON.parse(invoke(['exec', 'WeKnora-postgres', 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-U', 'postgres', '-d', database, '-c', "SELECT json_build_object('workbench', (SELECT count(*) FROM workbench_jobs WHERE status IN ('queued','running')) + (SELECT count(*) FROM digital_human_jobs WHERE status IN ('queued','running')) + (SELECT count(*) FROM workbench_knowledge_version_jobs WHERE status IN ('queued','running')) + (SELECT count(*) FROM knowledge_classification_jobs WHERE status='running') + (SELECT count(*) FROM resumable_uploads WHERE state NOT IN ('completed','cancelled','failed','expired')) + (SELECT count(*) FROM workbench_balance_holds WHERE status='reserved'), 'pending_ops', (SELECT count(*) FROM task_pending_ops));"])) const environment = JSON.parse(invoke(['inspect', apiName, '--format', '{{json .Config.Env}}'])) if (!Array.isArray(environment) || environment.some(value => typeof value !== 'string')) throw new Error() const runtime = parseEnvironment(environment.join('\n')) // Use the running API's Redis host, ACL identity and database, not the old // deployment or the redis-cli defaults. Secrets are passed only in env. const address = /^(?:\[([^\]]+)\]|([^:]+)):(\d+)$/.exec(runtime.REDIS_ADDR || '') if (!address || !/^\d+$/.test(runtime.REDIS_DB || '') || runtime.DB_NAME !== database) throw new Error() const redisArgs = ['redis-cli', '--raw', '--no-auth-warning', '-h', address[1] || address[2], '-p', address[3], '-n', runtime.REDIS_DB] if (runtime.REDIS_USERNAME) redisArgs.push('--user', runtime.REDIS_USERNAME) if (/^true$/i.test((runtime.REDIS_USE_TLS || '').trim())) { redisArgs.push('--tls') if (runtime.REDIS_TLS_SERVER_NAME) redisArgs.push('--sni', runtime.REDIS_TLS_SERVER_NAME) if (/^true$/i.test((runtime.REDIS_TLS_INSECURE_SKIP_VERIFY || '').trim())) redisArgs.push('--insecure') } redisArgs.push('EVAL_RO', queueSnapshotScript, '1', 'asynq:queues', ...nativeQueues) snapshot.queues = JSON.parse(invoke(['exec', '-e', 'REDISCLI_AUTH', 'WeKnora-redis', ...redisArgs], { ...process.env, REDISCLI_AUTH: runtime.REDIS_PASSWORD || '' })) } catch { // Docker, Redis and JSON errors may embed credentials or server output. // An unavailable or unparseable queue is never considered empty. throw new Error('Cannot verify background task idleness; publication stopped') } assertIdleSnapshot(snapshot) return snapshot } function prepare(release) { if (!/^public-\d{8}-\d{6}$/.test(release || '')) throw new Error('Expected public-YYYYMMDD-HHMMSS release identifier') const directory = path.join(root, 'releases', release) if (fs.existsSync(directory)) throw new Error('Release already exists; do not overwrite immutable artifacts') fs.mkdirSync(directory, { recursive: true, mode: 0o700 }) const env = parseEnvironment(fs.readFileSync(path.join(root, 'acceptance/runtime.env'), 'utf8')) if (env.DB_NAME !== database) throw new Error('Unexpected source database') Object.assign(env, { GIN_MODE: 'release', FRONTEND_BASE_URL: 'https://agent.pducv.cn', DISABLE_REGISTRATION: 'true', WEKNORA_AUTH_REGISTRATION_MODE: 'invite_only', WEKNORA_TENANT_ENABLE_RBAC: 'true', WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS: 'false', WEKNORA_TENANT_SELF_SERVICE_CREATION_ENABLED: 'false', WEKNORA_TRUSTED_PROXIES: '127.0.0.1,::1,172.19.0.0/16', }) fs.writeFileSync(path.join(directory, 'runtime.env'), Object.entries(env).map(([key, value]) => `${key}=${value}`).join('\n') + '\n', { mode: 0o600 }) fs.copyFileSync(path.join(root, 'acceptance/runtime.env'), path.join(directory, 'previous-runtime.env')) fs.copyFileSync('/Users/macclaw/.cloudflared/config.yml', path.join(directory, 'previous-tunnel.yml')) for (const db of [database, 'WeKnora']) { const fd = fs.openSync(path.join(directory, `${db}.dump`), 'wx', 0o600) try { execFileSync('docker', ['--context', 'colima-yawan', 'exec', 'WeKnora-postgres', 'pg_dump', '-U', 'postgres', '-Fc', db], { stdio: ['ignore', fd, 'pipe'] }) } finally { fs.closeSync(fd) } } const manifest = { release, prepared_at: new Date().toISOString(), database, api_image: `yawan-api:${release}`, frontend_image: `yawan-frontend:${release}`, previous_api: `yawan-native-before-${release}`, public_origin: 'http://127.0.0.1:18088', url: 'https://agent.pducv.cn', stage: 'prepared' } writeJSON(path.join(directory, 'manifest.json'), manifest) manifest.retained_assets = preserveFrontendAssets(root, release) fs.writeFileSync(path.join(root, 'src/frontend/dist/release.json'), JSON.stringify({ release, built_at: manifest.prepared_at })) const apiContext = stagePublicAPIContext(root, release) docker('build', '-t', manifest.api_image, apiContext) docker('build', '-t', manifest.frontend_image, path.join(root, 'src/frontend')) for (const [key, image] of [['api_image_id', manifest.api_image], ['frontend_image_id', manifest.frontend_image]]) { manifest[key] = docker('image', 'inspect', image, '--format', '{{.Id}}') } writeJSON(path.join(directory, 'manifest.json'), manifest) console.log(JSON.stringify({ release, stage: 'prepared', backups: 2, immutable_images: true })) } function isActivatedRelease(runtime, manifest) { return !!manifest.release && !!manifest.api_image_id && runtime.image === manifest.api_image_id && runtime.release === manifest.release } async function activate(release) { if (!/^public-\d{8}-\d{6}$/.test(release || '')) throw new Error('Invalid release identifier') const directory = path.join(root, 'releases', release) const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'manifest.json'), 'utf8')) if (manifest.stage !== 'prepared') throw new Error('Release has already been activated') idle() const frontendName = 'yawan-public-frontend' const previousFrontend = `${frontendName}-before-${release}` const hasFrontend = docker('ps', '-a', '--filter', `name=^/${frontendName}$`, '--format', '{{.Names}}') === frontendName const runtime = JSON.parse(docker('inspect', apiName, '--format', '{"image":{{json .Image}},"release":{{json (index .Config.Labels "yawan.release")}}}')) const apiAlreadyStarted = isActivatedRelease(runtime, manifest) if (apiAlreadyStarted && docker('ps', '-a', '--filter', `name=^/${manifest.previous_api}$`, '--format', '{{.Names}}') !== manifest.previous_api) { throw new Error('Cannot resume activation without the previous API rollback container') } let frontendRenamed = false let frontendCreated = false if (!apiAlreadyStarted) { docker('stop', '--time', '40', apiName) docker('rename', apiName, manifest.previous_api) } try { if (!apiAlreadyStarted) docker('run', '-d', '--name', apiName, '--restart', 'unless-stopped', '--network', network, '--env-file', path.join(directory, 'runtime.env'), '-p', '127.0.0.1:18080:8080', '-e', `WEKNORA_REVIEWED_RELEASE=${release}`, '-e', `WEKNORA_REVIEWED_API_IMAGE=${manifest.api_image_id}`, '--label', `yawan.release=${release}`, '-v', `${path.join(root, 'acceptance/files')}:/data/files`, '-v', `${path.join(root, 'acceptance/resumable-uploads')}:/data/uploads`, manifest.api_image_id) let healthy = false for (let attempt = 0; attempt < 45; attempt++) { try { healthy = (await fetch('http://127.0.0.1:18080/health', { signal: AbortSignal.timeout(1500) })).ok } catch {} if (healthy) break await new Promise(resolve => setTimeout(resolve, 1000)) } if (!healthy) throw new Error('New API did not become healthy') if (hasFrontend) { docker('stop', '--time', '20', frontendName) docker('rename', frontendName, previousFrontend) frontendRenamed = true } docker('run', '-d', '--name', 'yawan-public-frontend', '--restart', 'unless-stopped', '--network', network, '-p', '127.0.0.1:18088:80', '-e', `APP_HOST=${apiName}`, '-e', 'APP_PORT=8080', '-e', 'DEFAULT_LOCALE=zh-CN', '-e', 'MAX_FILE_SIZE_MB=50', '--label', `yawan.release=${release}`, manifest.frontend_image_id) frontendCreated = true docker('exec', 'yawan-public-frontend', 'nginx', '-t') const frontendHealth = await fetch('http://127.0.0.1:18088/api/v1/access/me', { signal: AbortSignal.timeout(10000) }) if (frontendHealth.status !== 401) throw new Error('Frontend proxy health failed') } catch (error) { if (frontendCreated || frontendRenamed) { try { docker('rm', '-f', frontendName) } catch {} } try { docker('rm', '-f', apiName) } catch {} docker('rename', manifest.previous_api, apiName) docker('start', apiName) if (frontendRenamed) { docker('rename', previousFrontend, frontendName) docker('start', frontendName) } else if (hasFrontend) { docker('restart', frontendName) } throw error } if (frontendRenamed) manifest.previous_frontend = previousFrontend manifest.stage = 'origin-ready' manifest.activated_at = new Date().toISOString() writeJSON(path.join(directory, 'manifest.json'), manifest) writeJSON(path.join(root, 'public-release.json'), manifest) console.log(JSON.stringify({ release, stage: manifest.stage, origin: manifest.public_origin, tunnel_changed: false })) } async function refreshFrontend(release) { if (!/^public-\d{8}-\d{6}$/.test(release || '')) throw new Error('Invalid release identifier') const directory = path.join(root, 'releases', release) const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'manifest.json'), 'utf8')) if (manifest.stage !== 'origin-ready') throw new Error('Only refresh an unpublished origin') const builtAt = new Date().toISOString() const revision = builtAt.replace(/[^0-9]/g, '') const image = `yawan-frontend:${release}-ui-${revision}` manifest.retained_assets = preserveFrontendAssets(root, `${release}-ui-${revision}`) fs.writeFileSync(path.join(root, 'src/frontend/dist/release.json'), JSON.stringify({ release, built_at: builtAt, frontend_revision: revision })) docker('build', '-t', image, path.join(root, 'src/frontend')) const imageID = docker('image', 'inspect', image, '--format', '{{.Id}}') const previous = `yawan-public-frontend-before-${revision}` docker('stop', '--time', '20', 'yawan-public-frontend') docker('rename', 'yawan-public-frontend', previous) try { docker('run', '-d', '--name', 'yawan-public-frontend', '--restart', 'unless-stopped', '--network', network, '-p', '127.0.0.1:18088:80', '-e', `APP_HOST=${apiName}`, '-e', 'APP_PORT=8080', '-e', 'DEFAULT_LOCALE=zh-CN', '-e', 'MAX_FILE_SIZE_MB=50', '--label', `yawan.release=${release}`, imageID) docker('exec', 'yawan-public-frontend', 'nginx', '-t') if (!(await fetch('http://127.0.0.1:18088/login', { signal: AbortSignal.timeout(10000) })).ok) throw new Error('Frontend health failed') } catch (error) { try { docker('rm', '-f', 'yawan-public-frontend') } catch {} docker('rename', previous, 'yawan-public-frontend') docker('start', 'yawan-public-frontend') throw error } manifest.previous_frontends = [...(manifest.previous_frontends || []), { image: manifest.frontend_image, image_id: manifest.frontend_image_id, container: previous }] manifest.frontend_image = image manifest.frontend_image_id = imageID manifest.frontend_built_at = builtAt manifest.frontend_activated_at = new Date().toISOString() writeJSON(path.join(directory, 'manifest.json'), manifest) writeJSON(path.join(root, 'public-release.json'), manifest) console.log(JSON.stringify({ release, stage: manifest.stage, frontend_revision: revision, tunnel_changed: false })) } async function refreshAPI(release) { if (!/^public-\d{8}-\d{6}$/.test(release || '')) throw new Error('Invalid release identifier') const directory = path.join(root, 'releases', release) const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'manifest.json'), 'utf8')) if (manifest.stage !== 'origin-ready') throw new Error('Only refresh an unverified origin') idle() const revision = new Date().toISOString().replace(/[^0-9]/g, '') const image = `yawan-api:${release}-api-${revision}` const apiContext = stagePublicAPIContext(root, release, revision) docker('build', '-t', image, apiContext) const imageID = docker('image', 'inspect', image, '--format', '{{.Id}}') idle() const previous = `${apiName}-before-${revision}` docker('stop', '--time', '40', apiName) docker('rename', apiName, previous) try { docker('run', '-d', '--name', apiName, '--restart', 'unless-stopped', '--network', network, '--env-file', path.join(directory, 'runtime.env'), '-p', '127.0.0.1:18080:8080', '-e', `WEKNORA_REVIEWED_RELEASE=${release}`, '-e', `WEKNORA_REVIEWED_API_IMAGE=${imageID}`, '--label', `yawan.release=${release}`, '-v', `${path.join(root, 'acceptance/files')}:/data/files`, '-v', `${path.join(root, 'acceptance/resumable-uploads')}:/data/uploads`, imageID) let healthy = false for (let attempt = 0; attempt < 45; attempt++) { try { healthy = (await fetch('http://127.0.0.1:18080/health', { signal: AbortSignal.timeout(1500) })).ok } catch {} if (healthy) break await new Promise(resolve => setTimeout(resolve, 1000)) } if (!healthy) throw new Error('Refreshed API health failed') docker('exec', 'yawan-public-frontend', 'nginx', '-s', 'reload') } catch (error) { try { docker('rm', '-f', apiName) } catch {} docker('rename', previous, apiName) docker('start', apiName) docker('exec', 'yawan-public-frontend', 'nginx', '-s', 'reload') throw error } manifest.previous_apis = [...(manifest.previous_apis || []), {image: manifest.api_image, image_id: manifest.api_image_id, container: previous}] manifest.api_image = image manifest.api_image_id = imageID manifest.api_activated_at = new Date().toISOString() writeJSON(path.join(directory, 'manifest.json'), manifest) writeJSON(path.join(root, 'public-release.json'), manifest) console.log(JSON.stringify({release, stage: manifest.stage, api_revision: revision})) } function publicationTimestamp(value, field, now) { const parsed = typeof value === 'string' ? Date.parse(value) : NaN if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value || parsed > now) { throw new Error(`Invalid publication timestamp: ${field}`) } return parsed } function assertSuccessfulVerification(result, report) { if (!result || result.passed !== true || !Array.isArray(result.checks) || !result.checks.length || result.checks.some(check => check?.passed !== true) || !Array.isArray(result.failures) || result.failures.length) { throw new Error(`Public verification did not pass: ${report}`) } if (Object.hasOwn(result, 'cleanup') && (!Array.isArray(result.cleanup) || !result.cleanup.length || result.cleanup.some(item => !item || typeof item !== 'object' || Array.isArray(item) || typeof item.name !== 'string' || !item.name.trim() || item.passed !== true || (Object.hasOwn(item, 'error') && item.error !== undefined && item.error !== null && item.error !== '')))) { throw new Error(`Public verification cleanup did not pass: ${report}`) } } function assertPublicVerification(manifest, result, report, now = Date.now()) { const activated = publicationTimestamp(manifest.activated_at, 'activated_at', now) const changes = [activated] for (const field of ['api_activated_at', 'frontend_built_at', 'frontend_activated_at']) { if (Object.hasOwn(manifest, field)) { const changed = publicationTimestamp(manifest[field], field, now) if (changed < activated) throw new Error(`Publication timestamp predates activation: ${field}`) changes.push(changed) } } if (manifest.previous_apis?.length && !Object.hasOwn(manifest, 'api_activated_at')) { throw new Error('Missing API activation timestamp') } const frontendRefreshed = Object.hasOwn(manifest, 'frontend_built_at') || Object.hasOwn(manifest, 'frontend_activated_at') || manifest.previous_frontends?.length if (frontendRefreshed) { const built = publicationTimestamp(manifest.frontend_built_at, 'frontend_built_at', now) const enabled = publicationTimestamp(manifest.frontend_activated_at, 'frontend_activated_at', now) if (enabled < built) throw new Error('Frontend activation predates its build') } assertSuccessfulVerification(result, report) if (report === 'public-indexed-retrieval-isolation') { const knowledgeIDs = result.fixtures?.kbs if (!Array.isArray(knowledgeIDs) || !knowledgeIDs.length || knowledgeIDs.some(id => typeof id !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) || new Set(knowledgeIDs).size !== knowledgeIDs.length) { throw new Error(`Invalid indexed knowledge fixtures: ${report}`) } // The final SQL reconciliation relies on these entries to attest Milvus cleanup. const knowledgeCleanup = result.cleanup?.filter(item => item.name.startsWith('knowledge ')) if (!knowledgeCleanup || knowledgeCleanup.length !== knowledgeIDs.length || knowledgeIDs.some(id => knowledgeCleanup.filter(item => item.name === `knowledge ${id}`).length !== 1)) { throw new Error(`Incomplete indexed knowledge cleanup: ${report}`) } } const started = publicationTimestamp(result.started_at, `${report}.started_at`, now) const completedField = Object.hasOwn(result, 'checked_at') ? 'checked_at' : 'time' const completed = publicationTimestamp(result[completedField], `${report}.${completedField}`, now) if (completed < started) throw new Error(`Public verification finished before it started: ${report}`) if (started < Math.max(...changes)) throw new Error(`Public verification started before this runtime: ${report}`) if (new URL(result.origin || result.base).origin !== manifest.url) throw new Error(`Wrong verification origin: ${report}`) if (['public-navigation', 'public-access-control'].includes(report)) { let apiOrigin try { apiOrigin = new URL(result.api_origin).origin } catch {} if (apiOrigin !== manifest.url) throw new Error(`Wrong verification API origin: ${report}`) } } function assertFixtureReconciliation(manifest, result, reports, now = Date.now()) { const name = 'public-fixture-reconciliation' assertSuccessfulVerification(result, name) if (result.release !== manifest.release || result.origin !== manifest.url || result.read_only !== true) { throw new Error('Fixture reconciliation identity does not match this release') } const checked = publicationTimestamp(result.checked_at, `${name}.checked_at`, now) if (!result.reports || Array.isArray(result.reports) || Object.keys(result.reports).length !== publicVerificationReports.length) { throw new Error('Fixture reconciliation must cover all seven source reports') } for (const sourceName of publicVerificationReports) { const source = reports?.[sourceName] assertPublicVerification(manifest, source, sourceName, now) const completed = Object.hasOwn(source, 'checked_at') ? source.checked_at : source.time const reconciled = result.reports[sourceName] if (!reconciled || reconciled.started_at !== source.started_at || reconciled.completed_at !== completed) { throw new Error(`Fixture reconciliation does not match current report: ${sourceName}`) } if (checked < Date.parse(completed)) throw new Error(`Fixture reconciliation predates source report: ${sourceName}`) } const expected = [...fixtureReconciliationCounts, 'file_fixture_directory_removed'] const byName = new Map(result.checks.map(check => [check.name, check])) if (result.checks.length !== expected.length || byName.size !== expected.length || expected.some(check => !byName.has(check))) { throw new Error('Fixture reconciliation is missing required residual checks') } for (const check of fixtureReconciliationCounts) { if (byName.get(check).count !== 0) throw new Error(`Fixture reconciliation has residual rows: ${check}`) } const prefix = reports['public-resource-files'].prefix if (!/^file-isolation-[0-9]+$/.test(prefix || '') || byName.get('file_fixture_directory_removed').path !== path.join(root, 'acceptance', 'files', '10059', 'exports', prefix)) { throw new Error('Fixture reconciliation checked the wrong attachment directory') } } async function markPublished(release) { if (!/^public-\d{8}-\d{6}$/.test(release || '')) throw new Error('Invalid release identifier') const directory = path.join(root, 'releases', release) const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'manifest.json'), 'utf8')) if (manifest.release !== release) throw new Error('Manifest release does not match the requested release') if (manifest.stage !== 'origin-ready') throw new Error('Expected verified origin') const response = await fetch(`${manifest.url}/release.json`, { signal: AbortSignal.timeout(30000) }) if (!response.ok || (await response.json()).release !== release) throw new Error('Public origin does not serve this release') const reports = Object.fromEntries(publicVerificationReports.map(name => [name, JSON.parse(fs.readFileSync(path.join(root, 'acceptance', `${name}.json`), 'utf8'))])) const reconciliation = JSON.parse(fs.readFileSync(path.join(root, 'acceptance', 'public-fixture-reconciliation.json'), 'utf8')) assertFixtureReconciliation(manifest, reconciliation, reports) manifest.stage = 'published' manifest.published_at = new Date().toISOString() manifest.public_verification_reports = publicVerificationReports manifest.fixture_reconciliation_checked_at = reconciliation.checked_at writeJSON(path.join(directory, 'manifest.json'), manifest) writeJSON(path.join(root, 'public-release.json'), manifest) console.log(JSON.stringify({ release, stage: manifest.stage, url: manifest.url })) } module.exports = { idle, assertIdleSnapshot, nativeQueues, queueStates, queueSnapshotScript, isActivatedRelease, assertPublicVerification, assertFixtureReconciliation, publicVerificationReports, fixtureReconciliationCounts } if (require.main === module) { const [action, release] = process.argv.slice(2) Promise.resolve().then(() => { if (action === 'prepare') return prepare(release) if (action === 'activate') return activate(release) if (action === 'refresh-frontend') return refreshFrontend(release) if (action === 'refresh-api') return refreshAPI(release) if (action === 'mark-published') return markPublished(release) throw new Error('Use prepare, activate, refresh-api, refresh-frontend or mark-published, followed by release identifier') }).catch(() => { // Docker diagnostics may contain environment secrets; keep console output bounded. console.error('Public release command failed; inspect the private release directory and container health. This command does not change the tunnel.') process.exitCode = 1 }) }