mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +00:00
* refactor(daemon): group transport, provider, endpoint, and terminal owners * refactor(daemon): consolidate owner imports and preserve pinned baseline sources * refactor(daemon): import termination budgets from their defining owner * fix(ci): retain serve mode coverage for extracted orcad startup * fix daemon build smoke to load concrete argument owner * read daemon protocol facts from historical defining paths * route protocol revision owners through compatibility checks
110 lines
4.1 KiB
JavaScript
110 lines
4.1 KiB
JavaScript
// Newest release daemon must attach or upgrades strand live terminals; rollback crossing is advisory.
|
|
// Usage: node check-daemon-protocol-crossing.mjs [--release-ref <ref>]
|
|
import { execFileSync } from 'node:child_process'
|
|
import { readFileSync } from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import {
|
|
DAEMON_PROTOCOL_SOURCE_PATH,
|
|
canAttach,
|
|
crossingRequirements,
|
|
parseDaemonProtocolFacts
|
|
} from '../schema-facts/daemon-protocol-facts.mjs'
|
|
import { selectLatestStableReleaseTag } from '../../release/versioning/stable-release-tags.mjs'
|
|
import { readDaemonProtocolRevisionFacts } from '../schema-facts/daemon-protocol-revision-facts.mjs'
|
|
|
|
// Same override the cross-version-wire harness honors, so both pair against one ref.
|
|
const RELEASE_REF_ENV = 'ORCA_CROSS_VERSION_BASELINE_REF'
|
|
|
|
function git(repoRoot, args) {
|
|
return execFileSync('git', args, {
|
|
cwd: repoRoot,
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
maxBuffer: 16 * 1024 * 1024
|
|
}).trim()
|
|
}
|
|
|
|
export function resolveReleaseRef(repoRoot, explicitRef, env = process.env) {
|
|
const override = explicitRef?.trim() || env[RELEASE_REF_ENV]?.trim()
|
|
if (override) {
|
|
return override
|
|
}
|
|
const tags = git(repoRoot, ['tag', '--list', 'v[0-9]*']).split('\n').filter(Boolean)
|
|
const latest = selectLatestStableReleaseTag(tags)
|
|
if (!latest) {
|
|
// Why: a shallow clone has no tags; passing here would silently skip the gate.
|
|
throw new Error(
|
|
`no stable release tags matching vX.Y.Z (saw ${tags.length} tag(s)). ` +
|
|
`Check out with fetch-depth: 0, or pass --release-ref / ${RELEASE_REF_ENV}.`
|
|
)
|
|
}
|
|
return latest
|
|
}
|
|
|
|
/**
|
|
* @param {{ release: ReturnType<typeof parseDaemonProtocolFacts>, candidate: ReturnType<typeof parseDaemonProtocolFacts>, releaseRef: string }} input
|
|
*/
|
|
export function assessDaemonProtocolCrossing({ release, candidate, releaseRef }) {
|
|
const upgrade = canAttach(candidate, release)
|
|
const rollback = canAttach(release, candidate)
|
|
const requirements = crossingRequirements(release)
|
|
const lines = [
|
|
`release ${releaseRef}: daemon protocol ${release.protocolVersion}`,
|
|
`candidate (working tree): daemon protocol ${candidate.protocolVersion}`,
|
|
upgrade
|
|
? `upgrade ${releaseRef} -> candidate: OK, live terminals are adopted`
|
|
: `upgrade ${releaseRef} -> candidate: FAIL, ${requirements.upgrade}; add ${release.protocolVersion} to PREVIOUS_DAEMON_PROTOCOL_VERSIONS`,
|
|
rollback
|
|
? `rollback candidate -> ${releaseRef}: OK, live terminals survive (info)`
|
|
: `rollback candidate -> ${releaseRef}: terminals from the candidate's daemon are unreachable until re-upgrade (info, expected after a protocol bump)`
|
|
]
|
|
return { upgrade, rollback, lines }
|
|
}
|
|
|
|
export function checkDaemonProtocolCrossing({ repoRoot, releaseRef: explicitRef, env }) {
|
|
const releaseRef = resolveReleaseRef(repoRoot, explicitRef, env)
|
|
let release
|
|
try {
|
|
release = readDaemonProtocolRevisionFacts(repoRoot, releaseRef)
|
|
} catch (error) {
|
|
throw new Error(
|
|
`cannot read ${DAEMON_PROTOCOL_SOURCE_PATH} at ${releaseRef}: ${error.stderr?.trim() || String(error)}`
|
|
)
|
|
}
|
|
const candidate = parseDaemonProtocolFacts(
|
|
readFileSync(join(repoRoot, DAEMON_PROTOCOL_SOURCE_PATH), 'utf8'),
|
|
DAEMON_PROTOCOL_SOURCE_PATH
|
|
)
|
|
return assessDaemonProtocolCrossing({ release, candidate, releaseRef })
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const index = argv.indexOf('--release-ref')
|
|
if (index === -1) {
|
|
return {}
|
|
}
|
|
const value = argv[index + 1]
|
|
if (!value || value.startsWith('--')) {
|
|
throw new Error('--release-ref requires a value')
|
|
}
|
|
return { releaseRef: value }
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
const repoRoot = resolve(import.meta.dirname, '../../..', '..')
|
|
try {
|
|
const { releaseRef } = parseArgs(process.argv.slice(2))
|
|
const result = checkDaemonProtocolCrossing({ repoRoot, releaseRef })
|
|
for (const line of result.lines) {
|
|
console.log(line)
|
|
}
|
|
if (!result.upgrade) {
|
|
process.exitCode = 1
|
|
}
|
|
} catch (error) {
|
|
console.error(`daemon protocol crossing check failed: ${error.message}`)
|
|
process.exitCode = 1
|
|
}
|
|
}
|