feat(hololake): add explicit JD release activation operator
This commit is contained in:
parent
4dc5b3b658
commit
4357d25ea8
5 changed files with 608 additions and 0 deletions
|
|
@ -0,0 +1,167 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import crypto from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { activateRelease, verifyReleaseBundle } from '../server/release-broadcast/operator.mjs'
|
||||
import { loadRuntimeState } from '../server/release-broadcast/server.mjs'
|
||||
|
||||
const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex')
|
||||
const writeJson = (file, value) => fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 })
|
||||
|
||||
function buildOperatorFixture() {
|
||||
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-release-operator-fixture-'))
|
||||
const source = path.join(fixtureRoot, 'source')
|
||||
const approvals = path.join(fixtureRoot, 'approvals')
|
||||
const stateRoot = path.join(fixtureRoot, 'state')
|
||||
for (const directory of [source, approvals, stateRoot]) fs.mkdirSync(directory, { mode: 0o700 })
|
||||
const packageName = 'HoloLake.app.tar.gz'
|
||||
const packageBytes = Buffer.from('signed-notarized-updater-package')
|
||||
const sourceCommit = '2'.repeat(40)
|
||||
fs.writeFileSync(path.join(source, packageName), packageBytes, { mode: 0o600 })
|
||||
writeJson(path.join(source, 'HOLOLAKE-CODESIGN.json'), {
|
||||
schema: 'hololake.platform-code-signature-receipt/v1',
|
||||
state: 'DEVELOPER_ID_SIGNATURE_STRICT_AND_GATEKEEPER_ACCEPTED',
|
||||
sourceCommit,
|
||||
})
|
||||
writeJson(path.join(source, 'HOLOLAKE-NOTARIZATION.json'), {
|
||||
schema: 'hololake.apple-notarization-receipt/v1',
|
||||
state: 'APPLE_NOTARIZATION_ACCEPTED_AND_STAPLED',
|
||||
sourceCommit,
|
||||
})
|
||||
const broadcast = {
|
||||
schema: 'hololake.release-broadcast/v1',
|
||||
releaseId: 'GH-HOLOLAKE-RELEASE-0.2.0',
|
||||
version: '0.2.0',
|
||||
pub_date: '2026-08-13T10:00:00.000Z',
|
||||
notes: 'Signed release',
|
||||
platforms: {
|
||||
'darwin-aarch64': {
|
||||
url: `https://guanghulab.com/hololake/releases/0.2.0/${packageName}`,
|
||||
signature: 'trusted-updater-signature',
|
||||
size: packageBytes.length,
|
||||
sha256: sha256(packageBytes),
|
||||
platformCodeSignatureReceipt: 'HOLOLAKE-CODESIGN',
|
||||
notarizationReceipt: 'HOLOLAKE-NOTARIZATION',
|
||||
},
|
||||
},
|
||||
hololake: {
|
||||
features: ['Stage one'],
|
||||
fixes: [],
|
||||
compatibility: { minimumVersion: '0.1.0', dataMigrationRequired: false },
|
||||
restart: { required: true, automaticAllowed: false },
|
||||
rollback: { supported: true, healthReceiptRequired: true, previousVersion: '0.1.0' },
|
||||
},
|
||||
}
|
||||
const broadcastPath = path.join(source, 'latest.json')
|
||||
writeJson(broadcastPath, broadcast)
|
||||
const broadcastSha256 = sha256(fs.readFileSync(broadcastPath))
|
||||
writeJson(path.join(source, 'pipeline-receipt.json'), {
|
||||
schema: 'hololake.signed-release-pipeline-receipt/v1',
|
||||
state: 'SIGNED_NOTARIZED_RELEASE_BROADCAST_READY_FOR_JD_CONTROLLER_UPLOAD',
|
||||
sourceCommit,
|
||||
broadcastSha256,
|
||||
automaticUpload: false,
|
||||
automaticActivation: false,
|
||||
})
|
||||
const approval = path.join(approvals, 'approval.json')
|
||||
writeJson(approval, {
|
||||
schema: 'hololake.release-broadcast-human-approval/v1',
|
||||
state: 'HUMAN_APPROVED_EXACT_SIGNED_NOTARIZED_RELEASE',
|
||||
approvalId: 'GH-HUMAN-RELEASE-APPROVAL-002',
|
||||
releaseId: broadcast.releaseId,
|
||||
version: broadcast.version,
|
||||
broadcastSha256,
|
||||
})
|
||||
return {
|
||||
fixtureRoot,
|
||||
source,
|
||||
approval,
|
||||
stateRoot,
|
||||
expected: { releaseId: broadcast.releaseId, version: broadcast.version, sourceCommit, broadcastSha256 },
|
||||
}
|
||||
}
|
||||
|
||||
test('verify reconstructs and validates an exact private candidate without mutating state', () => {
|
||||
const fixture = buildOperatorFixture()
|
||||
try {
|
||||
const result = verifyReleaseBundle(fixture.source, fixture.approval)
|
||||
assert.equal(result.state, 'EXACT_SIGNED_NOTARIZED_HUMAN_APPROVED_BUNDLE_VERIFIED')
|
||||
assert.equal(result.broadcastSha256, fixture.expected.broadcastSha256)
|
||||
assert.equal(result.artifacts.some((artifact) => artifact.name === 'HoloLake.app.tar.gz'), true)
|
||||
assert.deepEqual(fs.readdirSync(fixture.stateRoot), [])
|
||||
} finally {
|
||||
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('activation repeats expected facts, commits atomically, and still requires explicit restart', () => {
|
||||
const fixture = buildOperatorFixture()
|
||||
try {
|
||||
const receipt = activateRelease({
|
||||
sourceDirectory: fixture.source,
|
||||
humanApprovalPath: fixture.approval,
|
||||
stateRoot: fixture.stateRoot,
|
||||
expected: fixture.expected,
|
||||
enforceRootOwner: false,
|
||||
})
|
||||
assert.equal(receipt.state, 'ACTIVATED_EXPLICIT_SERVICE_RESTART_REQUIRED')
|
||||
assert.equal(receipt.automaticUpload, false)
|
||||
assert.equal(receipt.automaticActivation, false)
|
||||
assert.equal(receipt.automaticRestart, false)
|
||||
assert.equal(loadRuntimeState(fixture.stateRoot).state, 'READY_SIGNED_NOTARIZED_BROADCAST')
|
||||
assert.equal(fs.existsSync(path.join(fixture.stateRoot, '.operator-lock')), false)
|
||||
} finally {
|
||||
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('expected-fact mismatch performs no activation or release staging', () => {
|
||||
const fixture = buildOperatorFixture()
|
||||
try {
|
||||
assert.throws(() => activateRelease({
|
||||
sourceDirectory: fixture.source,
|
||||
humanApprovalPath: fixture.approval,
|
||||
stateRoot: fixture.stateRoot,
|
||||
expected: { ...fixture.expected, broadcastSha256: '3'.repeat(64) },
|
||||
enforceRootOwner: false,
|
||||
}), /HOLOLAKE_RELEASE_OPERATOR_EXPECTED_BROADCAST_SHA256_MISMATCH/)
|
||||
assert.deepEqual(fs.readdirSync(fixture.stateRoot), [])
|
||||
} finally {
|
||||
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('human approval cannot be reused after broadcast bytes change', () => {
|
||||
const fixture = buildOperatorFixture()
|
||||
try {
|
||||
const latestPath = path.join(fixture.source, 'latest.json')
|
||||
const latest = JSON.parse(fs.readFileSync(latestPath, 'utf8'))
|
||||
latest.notes = 'Changed after approval'
|
||||
writeJson(latestPath, latest)
|
||||
assert.throws(
|
||||
() => verifyReleaseBundle(fixture.source, fixture.approval),
|
||||
/HOLOLAKE_RELEASE_OPERATOR_PIPELINE_RECEIPT_INVALID/,
|
||||
)
|
||||
} finally {
|
||||
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('symlinked package inputs are rejected instead of followed', () => {
|
||||
const fixture = buildOperatorFixture()
|
||||
try {
|
||||
const packagePath = path.join(fixture.source, 'HoloLake.app.tar.gz')
|
||||
const moved = path.join(fixture.fixtureRoot, 'moved-package')
|
||||
fs.renameSync(packagePath, moved)
|
||||
fs.symlinkSync(moved, packagePath)
|
||||
assert.throws(
|
||||
() => verifyReleaseBundle(fixture.source, fixture.approval),
|
||||
/HOLOLAKE_RELEASE_OPERATOR_FILE_INVALID/,
|
||||
)
|
||||
} finally {
|
||||
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
Loading…
Reference in a new issue