import b4a from 'b4a'
import {
arrayOf,
boundedBytes,
canonicalAsciiBytes,
canonicalHttpsUrlBytes,
canonicalUtf8Bytes,
constant,
encodeCanonical,
fixedBytes,
optional,
ranged,
struct,
u8,
u16be,
u32be,
u64be
} from './codec.js'
import { protocolError } from './errors.js'
const KiB = 1024
const MiB = 1024 * KiB
const DAY_MILLIS = 86400000n
const bytes32 = fixedBytes(32)
const bytes64 = fixedBytes(64)
const version1 = constant(u8, 1, 'version')
const utf8Path = canonicalUtf8Bytes(1, 512, 'path')
const absolutePath = canonicalUtf8Bytes(1, 512, 'absolute path')
const httpsUrl = canonicalHttpsUrlBytes('canonical HTTPS URL')
const environmentName = canonicalAsciiBytes(1, 128, 'environmentName')
function fail (message) {
protocolError('BAD_ENCODING', message)
}
function isZero (value) {
for (const byte of value) if (byte !== 0) return false
return true
}
function nonzero (value, field) {
if (isZero(value)) fail(`${field} must be nonzero`)
}
function asU64 (value, field) {
if (typeof value === 'number') value = BigInt(value)
if (typeof value !== 'bigint' || value < 0n || value > ((1n << 64n) - 1n)) fail(`${field} is outside u64`)
return value
}
function compareBytes (left, right) {
return b4a.compare(left, right)
}
function compareCanonical (encoding, left, right) {
return compareBytes(encodeCanonical(encoding, left), encodeCanonical(encoding, right))
}
function assertSorted (values, compare, name) {
for (let index = 1; index < values.length; index++) {
if (compare(values[index - 1], values[index]) >= 0) fail(`${name} must be strictly sorted and duplicate-free`)
}
}
function assertOrderedIds (values, field, first, name) {
for (let index = 0; index < values.length; index++) {
if (values[index][field] !== first + index) fail(`${name} must use the frozen ordered identifiers`)
}
}
function assertUnique (values, equal, name) {
for (let left = 0; left < values.length; left++) {
for (let right = left + 1; right < values.length; right++) {
if (equal(values[left], values[right])) fail(`${name} must be duplicate-free`)
}
}
}
function assertSameBytes (left, right, message) {
if (!b4a.equals(left, right)) fail(message)
}
function validateWindow (value, from, through, name) {
if (asU64(value[from], from) > asU64(value[through], through)) fail(`${name} observation window is reversed`)
}
function validateAbsolutePath (value, name = 'absolute path') {
const text = b4a.toString(value, 'utf8')
if (!text.startsWith('/') || text.length === 1 || text.endsWith('/') || text.includes('\\')) fail(`${name} must be an absolute canonical path`)
for (const component of text.slice(1).split('/')) {
if (component === '' || component === '.' || component === '..') fail(`${name} contains a forbidden path component`)
}
}
function validateRelativePath (value, name = 'relative path') {
const text = b4a.toString(value, 'utf8')
if (text.startsWith('/') || text.endsWith('/') || text.includes('\\')) fail(`${name} must be a canonical relative path`)
for (const component of text.split('/')) {
if (component === '' || component === '.' || component === '..') fail(`${name} contains a forbidden path component`)
}
}
function validateBoundaryWindow (value, name) {
const observedFrom = asU64(value.observedFromUnixMillis, 'observedFromUnixMillis')
const observedThrough = asU64(value.observedThroughUnixMillis, 'observedThroughUnixMillis')
const issued = asU64(value.issuedUnixMillis, 'issuedUnixMillis')
const expires = asU64(value.expiresUnixMillis, 'expiresUnixMillis')
if (observedFrom > observedThrough || observedThrough > issued || issued >= expires) fail(`${name} times are invalid`)
if (expires - issued > DAY_MILLIS) fail(`${name} validity exceeds 24 hours`)
}
function pathsOverlap (left, right) {
const leftText = b4a.toString(left, 'utf8')
const rightText = b4a.toString(right, 'utf8')
return leftText === rightText || leftText.startsWith(`${rightText}/`) || rightText.startsWith(`${leftText}/`)
}
function capped (encoding, maximum, name) {
return {
preencode (state, value) {
const start = state.end
encoding.preencode(state, value)
if (state.end - start > maximum) fail(`${name} exceeds ${maximum} bytes`)
},
encode (state, value) { encoding.encode(state, value) },
decode (state) {
const start = state.start
const value = encoding.decode(state)
if (state.start - start > maximum) fail(`${name} exceeds ${maximum} bytes`)
return value
}
}
}
export const buildInputV1 = struct([
['path', utf8Path],
['byteLength', u64be],
['contentHash', bytes32]
], {
name: 'BuildInputV1',
validate (value) { validateRelativePath(value.path, 'build input path') }
})
export const toolchainEntryV1 = struct([
['name', canonicalAsciiBytes(1, 64, 'toolchain name')],
['version', canonicalAsciiBytes(1, 128, 'toolchain version')],
['distributionHash', bytes32]
], {
name: 'ToolchainEntryV1'
})
export const toolchainManifestV1 = struct([
['version', version1],
['entries', arrayOf(toolchainEntryV1, 1, 256, 'entries')]
], {
name: 'ToolchainManifestV1',
validate (value) {
assertSorted(value.entries, (left, right) => compareCanonical(toolchainEntryV1, left, right), 'toolchain entries')
assertUnique(value.entries, (left, right) => b4a.equals(left.name, right.name), 'toolchain names')
}
})
const reproductionVariableV1 = struct([
['name', canonicalAsciiBytes(1, 128, 'variable name')],
['valueHash', bytes32]
], { name: 'ReproductionVariableV1' })
export const reproductionEnvironmentV1 = struct([
['version', version1],
['os', canonicalAsciiBytes(1, 64, 'os')],
['architecture', canonicalAsciiBytes(1, 64, 'architecture')],
['containerOrVmHash', bytes32],
['sourceDateEpoch', u64be],
['locale', canonicalAsciiBytes(1, 64, 'locale')],
['timezone', canonicalAsciiBytes(1, 64, 'timezone')],
['variables', arrayOf(reproductionVariableV1, 0, 256, 'variables')]
], {
name: 'ReproductionEnvironmentV1',
validate (value) {
assertSorted(value.variables, (left, right) => compareBytes(left.name, right.name), 'environment variables')
}
})
export const buildReproductionAttestationV1 = struct([
['version', version1],
['builderPublicKey', bytes32],
['environmentHash', bytes32],
['unsignedBuildCommitment', bytes32],
['reproducedArtifactHash', bytes32]
], {
name: 'BuildReproductionAttestationV1',
validate (value) { nonzero(value.builderPublicKey, 'builderPublicKey') }
})
const packagingFileV1 = struct([
['path', utf8Path],
['mode', u16be],
['fileBytes', boundedBytes(1, 16 * MiB, 'fileBytes')]
], { name: 'BlindPackagingFileV1' })
export const blindProductDistributionBundleV1 = struct([
['version', version1],
['artifactFormat', ranged(u8, 1, 2, 'artifactFormat')],
['edgeComponentDistributionBytes', boundedBytes(1, 0xffffffff, 'edgeComponentDistributionBytes')],
['daemonComponentDistributionBytes', boundedBytes(1, 0xffffffff, 'daemonComponentDistributionBytes')],
['packagingFiles', arrayOf(packagingFileV1, 2, 256, 'packagingFiles')]
], {
name: 'BlindProductDistributionBundleV1',
validate (value) {
assertSorted(value.packagingFiles, (left, right) => compareCanonical(packagingFileV1, left, right), 'packaging files')
assertUnique(value.packagingFiles, (left, right) => b4a.equals(left.path, right.path), 'packaging file paths')
}
})
const buildReproductionV1 = struct([
['builderPublicKey', bytes32],
['environmentHash', bytes32],
['reproducedArtifactHash', bytes32],
['signature', bytes64]
], { name: 'BuildManifestReproductionV1' })
export const buildManifestV1 = struct([
['version', version1],
['productMode', constant(u8, 1, 'productMode')],
['implementationId', canonicalAsciiBytes(1, 64, 'implementationId')],
['implementationVersion', canonicalAsciiBytes(1, 64, 'implementationVersion')],
['sourceRevision', canonicalUtf8Bytes(1, 256, 'sourceRevision')],
['sourceTreeHash', bytes32],
['implementationSpecHash', bytes32],
['specHash', bytes32],
['abiHash', bytes32],
['vectorSetHash', bytes32],
['evidenceFormatHash', bytes32],
['evidenceVectorSetHash', bytes32],
['storeFormatHash', bytes32],
['storeVectorSetHash', bytes32],
['privateIpcFormatHash', bytes32],
['privateIpcVectorSetHash', bytes32],
['toolchainManifestHash', bytes32],
['dependencyLockHash', bytes32],
['sbomHash', optional(bytes32, 'sbomHash')],
['artifactFormat', ranged(u8, 1, 2, 'artifactFormat')],
['inputs', arrayOf(buildInputV1, 1, 0xffff, 'inputs')],
['buildArtifactHash', bytes32],
['launchTopologyHash', bytes32],
['releaseSupportHorizonHash', bytes32],
['productIsolationEvidenceHash', bytes32],
['reproductionPolicyId', constant(u8, 1, 'reproductionPolicyId')],
['reproductions', arrayOf(buildReproductionV1, 1, 16, 'reproductions')],
['releaseSignerPublicKey', bytes32],
['releaseSignature', bytes64]
], {
name: 'BuildManifestV1',
validate (value) {
assertSorted(value.inputs, (left, right) => compareBytes(left.path, right.path), 'build inputs')
assertSorted(value.reproductions, (left, right) => compareCanonical(buildReproductionV1, left, right), 'build reproductions')
assertUnique(value.reproductions, (left, right) => b4a.equals(left.builderPublicKey, right.builderPublicKey) &&
b4a.equals(left.environmentHash, right.environmentHash), 'builder/environment pairs')
for (const reproduction of value.reproductions) {
nonzero(reproduction.builderPublicKey, 'builderPublicKey')
assertSameBytes(reproduction.reproducedArtifactHash, value.buildArtifactHash, 'reproducedArtifactHash must equal buildArtifactHash')
}
nonzero(value.releaseSignerPublicKey, 'releaseSignerPublicKey')
if (!value.reproductions.some(reproduction => !b4a.equals(reproduction.builderPublicKey, value.releaseSignerPublicKey))) {
fail('reproduction policy 1 requires a builder key distinct from the release signer')
}
}
})
const launchComponentV1 = struct([
['componentId', ranged(u8, 1, 2, 'componentId')],
['componentArtifactHash', bytes32],
['entrypointPath', utf8Path],
['entrypointContentHash', bytes32],
['serviceUnitPath', utf8Path],
['serviceUnitContentHash', bytes32],
['uid', u32be],
['gid', u32be],
['readOnlyMounts', arrayOf(utf8Path, 0, 32, 'readOnlyMounts')],
['writableMounts', arrayOf(utf8Path, 0, 8, 'writableMounts')],
['publicListenerFamilyBits', u8],
['allowedChildEntrypointHashes', arrayOf(bytes32, 0, 8, 'allowedChildEntrypointHashes')]
], {
name: 'BlindLaunchComponentV1',
validate (value) {
assertSorted(value.readOnlyMounts, (left, right) => compareCanonical(utf8Path, left, right), 'read-only mounts')
assertSorted(value.writableMounts, (left, right) => compareCanonical(utf8Path, left, right), 'writable mounts')
assertSorted(value.allowedChildEntrypointHashes, (left, right) => compareCanonical(bytes32, left, right), 'allowed child entrypoint hashes')
}
})
const launchInitializerTargetV1 = struct([
['targetKind', ranged(u8, 1, 2, 'targetKind')],
['path', absolutePath],
['finalUid', u32be],
['finalGid', u32be],
['finalMode', u16be]
], {
name: 'BlindLaunchInitializerTargetV1',
validate (value) { validateAbsolutePath(value.path) }
})
const launchInitializerV1 = struct([
['initializerId', constant(u8, 1, 'initializerId')],
['componentArtifactHash', bytes32],
['argv', arrayOf(canonicalUtf8Bytes(1, 512, 'argv'), 1, 32, 'argv')],
['uid', u32be],
['gid', u32be],
['capabilityBits', u64be],
['networkDisabled', constant(u8, 1, 'networkDisabled')],
['rootFilesystemReadOnly', constant(u8, 1, 'rootFilesystemReadOnly')],
['noNewPrivileges', constant(u8, 1, 'noNewPrivileges')],
['maxPids', ranged(u16be, 1, 32, 'maxPids')],
['writableMounts', arrayOf(utf8Path, 1, 8, 'writableMounts')],
['targets', arrayOf(launchInitializerTargetV1, 2, 2, 'targets')],
['maxRuntimeMillis', ranged(u32be, 1, 60000, 'maxRuntimeMillis')]
], {
name: 'BlindLaunchInitializerV1',
validate (value) {
assertSorted(value.writableMounts, (left, right) => compareCanonical(utf8Path, left, right), 'initializer writable mounts')
assertOrderedIds(value.targets, 'targetKind', 1, 'initializer targets')
if (value.uid !== 0 || value.gid !== 0 || asU64(value.capabilityBits, 'capabilityBits') !== 7n || value.maxPids !== 32) fail('initializer privilege profile does not match VOLUME_OWNERSHIP_V1')
if (value.targets[0].finalMode !== 0x01e8 || value.targets[1].finalMode !== 0x01c0) fail('initializer target modes must be POSIX 0750 then 0700')
}
})
export const blindLaunchTopologyV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['privateIpcFormatHash', bytes32],
['privateIpcVectorSetHash', bytes32],
['components', arrayOf(launchComponentV1, 2, 2, 'components')],
['ipcUnarySocketPath', absolutePath],
['ipcStreamSocketPath', absolutePath],
['ipcOwnerUid', u32be],
['ipcPeerUid', u32be],
['ipcGroupGid', u32be],
['ipcMode', constant(u16be, 0x01b0, 'ipcMode')],
['launcherKind', ranged(u8, 1, 2, 'launcherKind')],
['defaultCommand', arrayOf(canonicalUtf8Bytes(1, 512, 'defaultCommand'), 1, 32, 'defaultCommand')],
['initializers', arrayOf(launchInitializerV1, 1, 1, 'initializers')],
['releaseSignerPublicKey', bytes32],
['signature', bytes64]
], {
name: 'BlindLaunchTopologyV1',
validate (value) {
assertOrderedIds(value.components, 'componentId', 1, 'launch components')
const [edge, daemon] = value.components
const initializer = value.initializers[0]
if (edge.uid === 0 || daemon.uid === 0 || edge.uid === daemon.uid) fail('edge and daemon UIDs must be nonzero and distinct')
if (edge.publicListenerFamilyBits !== 0x1f || daemon.publicListenerFamilyBits !== 0) fail('public listener family bits must be edge=0x1f and daemon=0')
if (edge.writableMounts.length !== 0 || daemon.writableMounts.length !== 2) fail('only daemon may own the two writable runtime/data roots')
if (edge.allowedChildEntrypointHashes.length !== 0 || daemon.allowedChildEntrypointHashes.length > 1) fail('only daemon may allowlist the single blind-peer child')
const writableMounts = value.components.flatMap(component => component.writableMounts)
for (let left = 0; left < writableMounts.length; left++) {
for (let right = left + 1; right < writableMounts.length; right++) {
if (pathsOverlap(writableMounts[left], writableMounts[right])) fail('component writable mounts must be disjoint')
}
}
validateAbsolutePath(value.ipcUnarySocketPath, 'ipcUnarySocketPath')
validateAbsolutePath(value.ipcStreamSocketPath, 'ipcStreamSocketPath')
if (b4a.equals(value.ipcUnarySocketPath, value.ipcStreamSocketPath)) fail('IPC socket paths must be distinct')
if (value.ipcOwnerUid !== daemon.uid || value.ipcPeerUid !== edge.uid) fail('IPC owner/peer UIDs must equal daemon/edge UIDs')
assertSameBytes(initializer.componentArtifactHash, daemon.componentArtifactHash, 'initializer artifact must equal daemon component artifact')
if (initializer.writableMounts.length !== daemon.writableMounts.length ||
initializer.writableMounts.some((path, index) => !b4a.equals(path, daemon.writableMounts[index]))) {
fail('initializer writable mounts must equal daemon writable mounts')
}
for (const target of initializer.targets) {
if (target.finalUid !== daemon.uid || target.finalGid !== daemon.gid) fail('initializer target ownership must equal daemon ownership')
if (!daemon.writableMounts.some(path => b4a.equals(path, target.path))) fail('initializer target must name a daemon writable mount')
}
assertUnique(initializer.targets, (left, right) => b4a.equals(left.path, right.path), 'initializer target paths')
nonzero(value.releaseSignerPublicKey, 'releaseSignerPublicKey')
}
})
const artifactFileV1 = struct([
['componentId', ranged(u8, 0, 2, 'componentId')],
['path', utf8Path],
['mode', u16be],
['byteLength', u64be],
['contentHash', bytes32]
], { name: 'BlindArtifactFileV1' })
export const blindArtifactFileInventoryV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['files', arrayOf(artifactFileV1, 1, 0xffff, 'files')]
], {
name: 'BlindArtifactFileInventoryV1',
validate (value) {
assertSorted(value.files, (left, right) => compareCanonical(artifactFileV1, left, right), 'artifact files')
assertUnique(value.files, (left, right) => left.componentId === right.componentId && b4a.equals(left.path, right.path), 'artifact component/path pairs')
}
})
const executableEntrypointV1 = struct([
['componentId', ranged(u8, 1, 2, 'componentId')],
['componentArtifactHash', bytes32],
['entrypointPath', utf8Path],
['entrypointContentHash', bytes32],
['argvPrefix', arrayOf(canonicalUtf8Bytes(1, 512, 'argvPrefix'), 1, 16, 'argvPrefix')]
], { name: 'BlindExecutableEntrypointV1' })
export const blindExecutableEntrypointCatalogV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['launchTopologyHash', bytes32],
['entries', arrayOf(executableEntrypointV1, 2, 2, 'entries')]
], {
name: 'BlindExecutableEntrypointCatalogV1',
validate (value) { assertOrderedIds(value.entries, 'componentId', 1, 'executable entrypoints') }
})
const runtimeImportNodeV1 = struct([
['nodeId', bytes32],
['componentId', ranged(u8, 1, 2, 'componentId')],
['path', utf8Path],
['contentHash', bytes32],
['importedNodeIds', arrayOf(bytes32, 0, 4096, 'importedNodeIds')]
], {
name: 'BlindRuntimeImportNodeV1',
validate (value) {
assertSorted(value.importedNodeIds, (left, right) => compareCanonical(bytes32, left, right), 'imported node IDs')
}
})
export const blindRuntimeImportGraphV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['entrypointNodeIds', arrayOf(bytes32, 2, 2, 'entrypointNodeIds')],
['nodes', arrayOf(runtimeImportNodeV1, 2, 0xffff, 'nodes')]
], {
name: 'BlindRuntimeImportGraphV1',
validate (value) {
assertSorted(value.nodes, (left, right) => compareCanonical(runtimeImportNodeV1, left, right), 'runtime import nodes')
assertUnique(value.nodes, (left, right) => b4a.equals(left.nodeId, right.nodeId), 'runtime import node IDs')
if (b4a.equals(value.entrypointNodeIds[0], value.entrypointNodeIds[1])) fail('entrypoint node IDs must be distinct')
const nodeById = new Map(value.nodes.map(node => [b4a.toString(node.nodeId, 'hex'), node]))
const roots = value.entrypointNodeIds.map(nodeId => nodeById.get(b4a.toString(nodeId, 'hex')))
if (!roots[0] || !roots[1] || roots[0].componentId !== 1 || roots[1].componentId !== 2) fail('ordered entrypoint nodes must exist for edge then daemon')
for (const node of value.nodes) {
for (const importedNodeId of node.importedNodeIds) {
const imported = nodeById.get(b4a.toString(importedNodeId, 'hex'))
if (!imported) fail('runtime import graph references an unknown node')
if (imported.componentId !== node.componentId) fail('runtime import graph crosses component boundaries')
}
}
for (let componentId = 1; componentId <= 2; componentId++) {
const reachable = new Set()
const pending = [roots[componentId - 1]]
while (pending.length > 0) {
const node = pending.pop()
const key = b4a.toString(node.nodeId, 'hex')
if (reachable.has(key)) continue
reachable.add(key)
for (const importedNodeId of node.importedNodeIds) pending.push(nodeById.get(b4a.toString(importedNodeId, 'hex')))
}
for (const node of value.nodes) {
if (node.componentId === componentId && !reachable.has(b4a.toString(node.nodeId, 'hex'))) fail('runtime import graph contains an unreachable node')
}
}
}
})
export const blindListenerEntryV1 = struct([
['componentId', ranged(u8, 1, 3, 'componentId')],
['listenerClass', ranged(u8, 1, 2, 'listenerClass')],
['transportId', ranged(u8, 0, 9, 'transportId')],
['endpointId', u8],
['addressOrSocket', canonicalUtf8Bytes(1, 512, 'addressOrSocket')],
['port', u16be],
['ownerUid', u32be]
], {
name: 'BlindListenerEntryV1',
validate (value) {
if (value.ownerUid === 0) fail('listener ownerUid must be nonzero')
if (value.listenerClass === 1 && (value.transportId === 0 || value.endpointId === 0 || value.port === 0)) {
fail('public listener requires public transport, endpoint, and port')
}
if (value.listenerClass === 1 && value.componentId !== 1 && value.componentId !== 3) fail('public listener must belong to blind edge or legacy compatibility')
if (value.listenerClass === 2 && (value.transportId !== 0 || value.endpointId !== 0 || value.port !== 0)) {
fail('private Unix listener requires zero transport, endpoint, and port')
}
if (value.listenerClass === 2 && value.componentId !== 2) fail('private Unix listener must belong to blind daemon')
if (value.listenerClass === 2) validateAbsolutePath(value.addressOrSocket, 'private Unix listener socket')
}
})
export const blindListenerCatalogV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['launchTopologyHash', bytes32],
['observedFromUnixMillis', u64be],
['observedThroughUnixMillis', u64be],
['listeners', arrayOf(blindListenerEntryV1, 1, 64, 'listeners')]
], {
name: 'BlindListenerCatalogV1',
validate (value) {
validateWindow(value, 'observedFromUnixMillis', 'observedThroughUnixMillis', 'listener catalog')
assertSorted(value.listeners, (left, right) => compareCanonical(blindListenerEntryV1, left, right), 'listeners')
if (value.listeners.some(listener => listener.componentId === 3)) fail('blind listener catalog cannot contain legacy compatibility listeners')
}
})
const allowedRouteV1 = struct([
['method', canonicalAsciiBytes(1, 16, 'method')],
['path', canonicalAsciiBytes(1, 256, 'route path')],
['familyId', ranged(u8, 1, 5, 'familyId')]
], { name: 'BlindAllowedRouteV1' })
const negativeProbeV1 = struct([
['method', canonicalAsciiBytes(1, 16, 'method')],
['path', canonicalAsciiBytes(1, 256, 'probe path')],
['expectedStatus', u16be],
['observedStatus', u16be],
['responseBodyBytes', boundedBytes(0, 4096, 'responseBodyBytes')]
], { name: 'BlindNegativeRouteProbeV1' })
export const blindRouteAbsenceEvidenceV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['abiHash', bytes32],
['evidenceVectorSetHash', bytes32],
['allowedRoutes', arrayOf(allowedRouteV1, 1, 16, 'allowedRoutes')],
['negativeProbes', arrayOf(negativeProbeV1, 1, 256, 'negativeProbes')]
], {
name: 'BlindRouteAbsenceEvidenceV1',
validate (value) {
assertSorted(value.allowedRoutes, (left, right) => compareCanonical(allowedRouteV1, left, right), 'allowed routes')
assertSorted(value.negativeProbes, (left, right) => compareCanonical(negativeProbeV1, left, right), 'negative probes')
assertUnique(value.allowedRoutes, (left, right) => b4a.equals(left.method, right.method) && b4a.equals(left.path, right.path), 'allowed method/path pairs')
assertUnique(value.negativeProbes, (left, right) => b4a.equals(left.method, right.method) && b4a.equals(left.path, right.path), 'negative-probe method/path pairs')
}
})
const inspectionTargetV1 = struct([
['targetKind', ranged(u8, 1, 2, 'targetKind')],
['path', absolutePath],
['finalUid', u32be],
['finalGid', u32be],
['finalMode', u16be],
['inodeKind', constant(u8, 1, 'inodeKind')],
['symlinkFree', constant(u8, 1, 'symlinkFree')]
], { name: 'BlindInspectionTargetV1', validate: value => validateAbsolutePath(value.path) })
const completedInitializerV1 = struct([
['initializerId', constant(u8, 1, 'initializerId')],
['componentArtifactHash', bytes32],
['argv', arrayOf(canonicalUtf8Bytes(1, 512, 'argv'), 1, 32, 'argv')],
['uid', u32be],
['gid', u32be],
['startedUnixMillis', u64be],
['endedUnixMillis', u64be],
['exitCode', u8],
['observedCapabilityBits', u64be],
['networkDisabled', u8],
['rootFilesystemReadOnly', u8],
['noNewPrivileges', u8],
['pidsLimit', u16be],
['observedPeakPids', u16be],
['writableMounts', arrayOf(utf8Path, 1, 8, 'writableMounts')],
['targetsAfter', arrayOf(inspectionTargetV1, 2, 2, 'targetsAfter')]
], {
name: 'BlindCompletedInitializerV1',
validate (value) {
if (asU64(value.startedUnixMillis, 'startedUnixMillis') >= asU64(value.endedUnixMillis, 'endedUnixMillis')) fail('initializer completion must follow its start')
assertSorted(value.writableMounts, (left, right) => compareCanonical(utf8Path, left, right), 'initializer writable mounts')
assertOrderedIds(value.targetsAfter, 'targetKind', 1, 'initializer targets')
if (value.uid !== 0 || value.gid !== 0 || value.exitCode !== 0 || asU64(value.observedCapabilityBits, 'observedCapabilityBits') !== 7n ||
value.networkDisabled !== 1 || value.rootFilesystemReadOnly !== 1 || value.noNewPrivileges !== 1) {
fail('completed initializer does not match VOLUME_OWNERSHIP_V1')
}
if (value.pidsLimit !== 32 || value.observedPeakPids < 1 || value.observedPeakPids > value.pidsLimit) fail('initializer PID evidence is outside its signed limit')
if (asU64(value.endedUnixMillis, 'endedUnixMillis') - asU64(value.startedUnixMillis, 'startedUnixMillis') > 60000n) fail('initializer runtime exceeds the signed v1 maximum')
if (value.targetsAfter[0].finalMode !== 0x01e8 || value.targetsAfter[1].finalMode !== 0x01c0) fail('initializer target modes must be POSIX 0750 then 0700')
if (value.writableMounts.length !== value.targetsAfter.length ||
value.targetsAfter.some(target => !value.writableMounts.some(path => b4a.equals(path, target.path)))) fail('completed initializer targets must equal its writable mounts')
}
})
const inspectionMountV1 = struct([
['path', utf8Path],
['accessMode', ranged(u8, 1, 2, 'accessMode')]
], { name: 'BlindInspectionMountV1' })
const inspectionProcessV1 = struct([
['processOrdinal', ranged(u16be, 1, 0xffff, 'processOrdinal')],
['componentId', ranged(u8, 1, 2, 'componentId')],
['parentProcessOrdinal', optional(u16be, 'parentProcessOrdinal')],
['uid', u32be],
['gid', u32be],
['executablePath', utf8Path],
['executableContentHash', bytes32],
['argv', arrayOf(canonicalUtf8Bytes(1, 512, 'argv'), 1, 32, 'argv')],
['environmentNames', arrayOf(environmentName, 0, 256, 'environmentNames')],
['mounts', arrayOf(inspectionMountV1, 0, 64, 'mounts')]
], {
name: 'BlindInspectionProcessV1',
validate (value) {
assertSorted(value.environmentNames, (left, right) => compareCanonical(environmentName, left, right), 'environment names')
assertSorted(value.mounts, (left, right) => compareCanonical(inspectionMountV1, left, right), 'process mounts')
assertUnique(value.mounts, (left, right) => b4a.equals(left.path, right.path), 'process mount paths')
}
})
export const blindProcessInspectionEvidenceV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['launchTopologyHash', bytes32],
['observedFromUnixMillis', u64be],
['observedThroughUnixMillis', u64be],
['completedInitializers', arrayOf(completedInitializerV1, 1, 1, 'completedInitializers')],
['processes', arrayOf(inspectionProcessV1, 2, 16, 'processes')]
], {
name: 'BlindProcessInspectionEvidenceV1',
validate (value) {
validateWindow(value, 'observedFromUnixMillis', 'observedThroughUnixMillis', 'process inspection')
const observedFrom = asU64(value.observedFromUnixMillis, 'observedFromUnixMillis')
const observedThrough = asU64(value.observedThroughUnixMillis, 'observedThroughUnixMillis')
const initializer = value.completedInitializers[0]
if (observedFrom > asU64(initializer.startedUnixMillis, 'startedUnixMillis') ||
asU64(initializer.endedUnixMillis, 'endedUnixMillis') > observedThrough) fail('initializer execution must fit inside the inspection window')
assertSorted(value.processes, (left, right) => compareCanonical(inspectionProcessV1, left, right), 'processes')
assertUnique(value.processes, (left, right) => left.processOrdinal === right.processOrdinal, 'process ordinals')
const prior = new Map()
const rootCount = new Map([[1, 0], [2, 0]])
for (const process of value.processes) {
if (process.parentProcessOrdinal == null) {
rootCount.set(process.componentId, rootCount.get(process.componentId) + 1)
} else {
const parent = prior.get(process.parentProcessOrdinal)
if (!parent || parent.componentId !== process.componentId) fail('process parent must be an earlier process in the same component')
if (process.componentId !== 2) fail('only daemon may own an allowlisted child process')
}
prior.set(process.processOrdinal, process)
}
if (rootCount.get(1) !== 1 || rootCount.get(2) !== 1) fail('process inspection requires one edge root and one daemon root')
}
})
export const blindProductIsolationReportBundleV1 = struct([
['version', version1],
['artifactFileInventoryBytes', boundedBytes(1, 48 * MiB, 'artifactFileInventoryBytes')],
['executableEntrypointBytes', boundedBytes(1, 0x10000, 'executableEntrypointBytes')],
['runtimeImportGraphBytes', boundedBytes(1, 48 * MiB, 'runtimeImportGraphBytes')],
['listenerCatalogBytes', boundedBytes(1, MiB, 'listenerCatalogBytes')],
['routeAbsenceEvidenceBytes', boundedBytes(1, MiB, 'routeAbsenceEvidenceBytes')],
['processInspectionBytes', boundedBytes(1, 4 * MiB, 'processInspectionBytes')]
], { name: 'BlindProductIsolationReportBundleV1' })
export const blindProductIsolationEvidenceV1 = struct([
['version', version1],
['productMode', constant(u8, 1, 'productMode')],
['buildArtifactHash', bytes32],
['launchTopologyHash', bytes32],
['artifactFileInventoryHash', bytes32],
['executableEntryPointHash', bytes32],
['runtimeImportGraphHash', bytes32],
['listenerCatalogHash', bytes32],
['forbiddenComponentPresenceBits', constant(u16be, 0, 'forbiddenComponentPresenceBits')],
['routeAbsenceEvidenceHash', bytes32],
['processInspectionEvidenceHash', bytes32],
['isolationReportBundleHash', bytes32],
['issuedUnixMillis', u64be],
['evidenceSignerPublicKey', bytes32],
['signature', bytes64]
], {
name: 'BlindProductIsolationEvidenceV1',
validate (value) { nonzero(value.evidenceSignerPublicKey, 'evidenceSignerPublicKey') }
})
const boundaryComponentV1 = struct([
['componentId', ranged(u8, 1, 2, 'componentId')],
['entrypointPath', utf8Path],
['entrypointBytes', boundedBytes(1, 16 * MiB, 'entrypointBytes')],
['serviceUnitPath', utf8Path],
['serviceUnitBytes', boundedBytes(1, MiB, 'serviceUnitBytes')],
['uid', u32be],
['gid', u32be]
], { name: 'BlindBoundaryComponentV1' })
const publicCredentialV1 = struct([
['credentialClass', ranged(u8, 1, 4, 'credentialClass')],
['canonicalPublicBytes', boundedBytes(1, 0xffff, 'canonicalPublicBytes')]
], { name: 'BlindPublicCredentialV1' })
const storageRootV1 = struct([
['componentId', ranged(u8, 1, 2, 'componentId')],
['path', utf8Path],
['rootClass', canonicalAsciiBytes(1, 64, 'rootClass')],
['encryptionPublicKey', optional(bytes32, 'encryptionPublicKey')]
], { name: 'BlindStorageRootV1' })
const observabilityV1 = struct([
['componentId', ranged(u8, 1, 2, 'componentId')],
['logSinkId', canonicalAsciiBytes(1, 128, 'logSinkId')],
['logNamespace', canonicalAsciiBytes(1, 128, 'logNamespace')],
['metricSinkId', canonicalAsciiBytes(1, 128, 'metricSinkId')],
['metricNamespace', canonicalAsciiBytes(1, 128, 'metricNamespace')]
], { name: 'BlindObservabilityBindingV1' })
export const blindRuntimeBoundaryEvidenceV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['buildManifestHash', bytes32],
['launchTopologyHash', bytes32],
['componentProcesses', arrayOf(boundaryComponentV1, 2, 2, 'componentProcesses')],
['listeners', arrayOf(blindListenerEntryV1, 1, 64, 'listeners')],
['descriptorProtocolId', canonicalAsciiBytes(1, 64, 'descriptorProtocolId')],
['descriptorSigningPublicKey', bytes32],
['discoveryTopic', bytes32],
['publicCredentials', arrayOf(publicCredentialV1, 1, 32, 'publicCredentials')],
['storageRoots', arrayOf(storageRootV1, 1, 16, 'storageRoots')],
['releaseChannelUrl', httpsUrl],
['releaseChannelPublicKey', bytes32],
['observability', arrayOf(observabilityV1, 2, 2, 'observability')],
['deploymentId', bytes32],
['observedFromUnixMillis', u64be],
['observedThroughUnixMillis', u64be],
['issuedUnixMillis', u64be],
['expiresUnixMillis', u64be],
['evidenceSignerPublicKey', bytes32],
['signature', bytes64]
], {
name: 'BlindRuntimeBoundaryEvidenceV1',
validate (value) {
assertOrderedIds(value.componentProcesses, 'componentId', 1, 'boundary component processes')
if (value.componentProcesses[0].uid === 0 || value.componentProcesses[1].uid === 0 || value.componentProcesses[0].uid === value.componentProcesses[1].uid) fail('boundary component UIDs must be nonzero and distinct')
assertSorted(value.listeners, (left, right) => compareCanonical(blindListenerEntryV1, left, right), 'boundary listeners')
if (value.listeners.some(listener => listener.componentId === 3)) fail('blind runtime boundary cannot contain legacy compatibility listeners')
assertOrderedIds(value.observability, 'componentId', 1, 'observability bindings')
assertSorted(value.publicCredentials, (left, right) => compareCanonical(publicCredentialV1, left, right), 'public credentials')
assertUnique(value.publicCredentials, (left, right) => left.credentialClass === right.credentialClass, 'public credential classes')
assertSorted(value.storageRoots, (left, right) => compareCanonical(storageRootV1, left, right), 'storage roots')
assertUnique(value.storageRoots, (left, right) => left.componentId === right.componentId && b4a.equals(left.path, right.path), 'storage component/path pairs')
validateBoundaryWindow(value, 'runtime boundary')
nonzero(value.deploymentId, 'deploymentId')
nonzero(value.descriptorSigningPublicKey, 'descriptorSigningPublicKey')
nonzero(value.releaseChannelPublicKey, 'releaseChannelPublicKey')
nonzero(value.evidenceSignerPublicKey, 'evidenceSignerPublicKey')
}
})
const predecessorV1 = struct([
['buildArtifactHash', bytes32],
['buildManifestHash', bytes32],
['abiHash', bytes32],
['storeFormatHash', bytes32],
['privateIpcFormatHash', bytes32],
['oldAbiServeThroughUnixMillis', u64be],
['rollbackThroughUnixMillis', u64be],
['compatibilityVectorSetHash', bytes32]
], { name: 'BlindReleasePredecessorV1' })
export const blindReleaseSupportHorizonV1 = struct([
['version', version1],
['buildArtifactHash', bytes32],
['specHash', bytes32],
['abiHash', bytes32],
['vectorSetHash', bytes32],
['evidenceFormatHash', bytes32],
['evidenceVectorSetHash', bytes32],
['storeFormatHash', bytes32],
['storeVectorSetHash', bytes32],
['privateIpcFormatHash', bytes32],
['privateIpcVectorSetHash', bytes32],
['issuedUnixMillis', u64be],
['activationNotBeforeUnixMillis', u64be],
['fullSupportThroughUnixMillis', u64be],
['upgradeMode', ranged(u8, 1, 2, 'upgradeMode')],
['predecessors', arrayOf(predecessorV1, 0, 4, 'predecessors')],
['releaseSignerPublicKey', bytes32],
['signature', bytes64]
], {
name: 'BlindReleaseSupportHorizonV1',
validate (value) {
assertSorted(value.predecessors, (left, right) => compareCanonical(predecessorV1, left, right), 'release predecessors')
assertUnique(value.predecessors, (left, right) => b4a.equals(left.buildArtifactHash, right.buildArtifactHash), 'predecessor build artifacts')
const issued = asU64(value.issuedUnixMillis, 'issuedUnixMillis')
const activation = asU64(value.activationNotBeforeUnixMillis, 'activationNotBeforeUnixMillis')
const through = asU64(value.fullSupportThroughUnixMillis, 'fullSupportThroughUnixMillis')
if (issued > activation || activation >= through) fail('release support horizon times are invalid')
for (const predecessor of value.predecessors) {
const oldAbiThrough = asU64(predecessor.oldAbiServeThroughUnixMillis, 'oldAbiServeThroughUnixMillis')
const rollbackThrough = asU64(predecessor.rollbackThroughUnixMillis, 'rollbackThroughUnixMillis')
if (oldAbiThrough < activation || oldAbiThrough > through || rollbackThrough < activation || rollbackThrough > through) fail('predecessor deadlines must lie within the support horizon')
}
nonzero(value.releaseSignerPublicKey, 'releaseSignerPublicKey')
}
})
const releaseCompatibilityVectorManifestBytesV1 = boundedBytes(1, MiB, 'compatibilityVectorManifest')
const reproductionEnvironmentBytesV1 = boundedBytes(1, MiB, 'reproductionEnvironment')
export const blindReleaseEvidenceBundleV1 = capped(struct([
['version', version1],
['buildManifestBytes', boundedBytes(1, 48 * MiB, 'buildManifestBytes')],
['launchTopologyBytes', boundedBytes(1, MiB, 'launchTopologyBytes')],
['isolationEvidenceBytes', boundedBytes(1, 0x10000, 'isolationEvidenceBytes')],
['isolationReportBundleBytes', boundedBytes(1, 128 * MiB, 'isolationReportBundleBytes')],
['releaseSupportHorizonBytes', boundedBytes(1, MiB, 'releaseSupportHorizonBytes')],
['privateIpcRegistryBytes', boundedBytes(1, MiB, 'privateIpcRegistryBytes')],
['privateIpcVectorManifestBytes', boundedBytes(1, MiB, 'privateIpcVectorManifestBytes')],
['releaseCompatibilityVectorManifestBytes', arrayOf(releaseCompatibilityVectorManifestBytesV1, 0, 4, 'releaseCompatibilityVectorManifestBytes')],
['toolchainManifestBytes', boundedBytes(1, MiB, 'toolchainManifestBytes')],
['reproductionEnvironmentBytes', arrayOf(reproductionEnvironmentBytesV1, 1, 16, 'reproductionEnvironmentBytes')],
['dependencyLockBytes', boundedBytes(1, 16 * MiB, 'dependencyLockBytes')],
['sbomBytes', optional(boundedBytes(1, 32 * MiB, 'sbomBytes'), 'sbomBytes')]
], {
name: 'BlindReleaseEvidenceBundleV1',
validate (value) {
assertSorted(value.releaseCompatibilityVectorManifestBytes, (left, right) => compareCanonical(releaseCompatibilityVectorManifestBytesV1, left, right), 'release compatibility vector manifests')
assertSorted(value.reproductionEnvironmentBytes, (left, right) => compareCanonical(reproductionEnvironmentBytesV1, left, right), 'reproduction environments')
}
}), 512 * MiB, 'BlindReleaseEvidenceBundleV1')
export const hiveRelayCompatibilityBuildManifestV1 = struct([
['version', version1],
['productMode', constant(u8, 2, 'productMode')],
['compatibilityProductId', canonicalAsciiBytes(1, 128, 'compatibilityProductId')],
['implementationVersion', canonicalAsciiBytes(1, 64, 'implementationVersion')],
['sourceRevision', canonicalUtf8Bytes(1, 256, 'sourceRevision')],
['sourceTreeHash', bytes32],
['inputs', arrayOf(buildInputV1, 1, 0xffff, 'inputs')],
['toolchainManifestHash', bytes32],
['dependencyLockHash', bytes32],
['compatibilityArtifactFormat', ranged(u8, 1, 2, 'compatibilityArtifactFormat')],
['compatibilityArtifactUrl', httpsUrl],
['compatibilityArtifactHash', bytes32],
['sunsetChainGenesisHash', bytes32],
['sunsetGenesisUrl', httpsUrl],
['sunsetLatestUrl', httpsUrl],
['releaseSignerPublicKey', bytes32],
['releaseSignature', bytes64]
], {
name: 'HiveRelayCompatibilityBuildManifestV1',
validate (value) {
assertSorted(value.inputs, (left, right) => compareBytes(left.path, right.path), 'compatibility build inputs')
nonzero(value.releaseSignerPublicKey, 'releaseSignerPublicKey')
}
})
export const hiveRelayCompatibilitySunsetGenesisV1 = struct([
['version', version1],
['compatibilityProductId', canonicalAsciiBytes(1, 128, 'compatibilityProductId')],
['sunsetChainId', bytes32],
['sunsetSequence', constant(u64be, 0n, 'sunsetSequence')],
['successorSpecHash', bytes32],
['successorAbiHash', bytes32],
['successorVectorSetHash', bytes32],
['genesisAuthoritySequence', u64be],
['genesisAuthorityPublicKey', bytes32],
['genesisAuthorityKeyId', bytes32],
['sunsetHistoryBaseUrl', httpsUrl],
['issuedUnixMillis', u64be],
['lastWriteUnixMillis', u64be],
['lastReadUnixMillis', u64be],
['releaseChannelPublicKey', bytes32],
['signature', bytes64]
], {
name: 'HiveRelayCompatibilitySunsetGenesisV1',
validate (value) {
nonzero(value.sunsetChainId, 'sunsetChainId')
const issued = asU64(value.issuedUnixMillis, 'issuedUnixMillis')
const lastWrite = asU64(value.lastWriteUnixMillis, 'lastWriteUnixMillis')
const lastRead = asU64(value.lastReadUnixMillis, 'lastReadUnixMillis')
if (lastWrite === 0n || lastRead === 0n || issued > lastWrite || lastWrite > lastRead) fail('sunset deadlines are invalid')
nonzero(value.genesisAuthorityPublicKey, 'genesisAuthorityPublicKey')
nonzero(value.releaseChannelPublicKey, 'releaseChannelPublicKey')
}
})
export const hiveRelayLegacyCompatibilitySunsetV1 = struct([
['version', version1],
['compatibilityProductId', canonicalAsciiBytes(1, 128, 'compatibilityProductId')],
['sunsetChainGenesisHash', bytes32],
['sunsetChainId', bytes32],
['sunsetSequence', u64be],
['compatibilityArtifactUrl', httpsUrl],
['compatibilityArtifactHash', bytes32],
['compatibilityBuildManifestUrl', httpsUrl],
['compatibilityBuildManifestHash', bytes32],
['compatibilityRuntimeBoundaryEvidenceUrl', httpsUrl],
['compatibilityRuntimeBoundaryEvidenceHash', bytes32],
['successorSpecHash', bytes32],
['successorAbiHash', bytes32],
['successorVectorSetHash', bytes32],
['successorBuildArtifactHash', bytes32],
['successorBuildManifestHash', bytes32],
['successorLaunchTopologyHash', bytes32],
['successorIsolationEvidenceHash', bytes32],
['successorRuntimeBoundaryEvidenceHash', bytes32],
['issuedUnixMillis', u64be],
['lastWriteUnixMillis', u64be],
['lastReadUnixMillis', u64be],
['previousSunsetHash', bytes32],
['releaseAuthoritySequence', u64be],
['releaseAuthorityPublicKey', bytes32],
['releaseAuthorityKeyId', bytes32],
['authorityTransitionHash', optional(bytes32, 'authorityTransitionHash')],
['signature', bytes64]
], {
name: 'HiveRelayLegacyCompatibilitySunsetV1',
validate (value) {
const sequence = asU64(value.sunsetSequence, 'sunsetSequence')
const lastWrite = asU64(value.lastWriteUnixMillis, 'lastWriteUnixMillis')
const lastRead = asU64(value.lastReadUnixMillis, 'lastReadUnixMillis')
if (sequence === 0n) fail('sunsetSequence must be nonzero after genesis')
if (lastWrite === 0n || lastRead === 0n || lastWrite > lastRead) fail('sunset deadlines are invalid')
nonzero(value.sunsetChainId, 'sunsetChainId')
nonzero(value.releaseAuthorityPublicKey, 'releaseAuthorityPublicKey')
if (sequence === 1n) {
if (value.authorityTransitionHash != null) fail('sequence-one sunset must omit authority transition evidence')
assertSameBytes(value.previousSunsetHash, value.sunsetChainGenesisHash, 'sequence-one previousSunsetHash must equal sunsetChainGenesisHash')
}
}
})
export const hiveRelayCompatibilitySunsetHeadV1 = struct([
['version', version1],
['compatibilityProductId', canonicalAsciiBytes(1, 128, 'compatibilityProductId')],
['sunsetChainGenesisHash', bytes32],
['sunsetChainId', bytes32],
['sunsetSequence', u64be],
['sunsetHash', bytes32],
['compatibilityBuildManifestHash', bytes32],
['headLeaseSlot', u64be],
['issuedUnixMillis', u64be],
['notBeforeUnixMillis', u64be],
['expiresUnixMillis', u64be],
['releaseAuthoritySequence', u64be],
['releaseAuthorityKeyId', bytes32],
['signature', bytes64]
], {
name: 'HiveRelayCompatibilitySunsetHeadV1',
validate (value) {
const notBefore = asU64(value.notBeforeUnixMillis, 'notBeforeUnixMillis')
const expires = asU64(value.expiresUnixMillis, 'expiresUnixMillis')
if (asU64(value.sunsetSequence, 'sunsetSequence') === 0n) fail('sunset head must bind a nonzero sunset sequence')
if (asU64(value.headLeaseSlot, 'headLeaseSlot') !== notBefore / 300000n) fail('headLeaseSlot does not match notBeforeUnixMillis')
if (asU64(value.issuedUnixMillis, 'issuedUnixMillis') > notBefore || notBefore >= expires || expires - notBefore > 900000n) fail('sunset head times are invalid')
nonzero(value.sunsetChainId, 'sunsetChainId')
}
})
export const hiveRelayCompatibilityAuthorityTransitionV1 = struct([
['version', version1],
['compatibilityProductId', canonicalAsciiBytes(1, 128, 'compatibilityProductId')],
['sunsetChainGenesisHash', bytes32],
['sunsetChainId', bytes32],
['successorSpecHash', bytes32],
['successorAbiHash', bytes32],
['successorVectorSetHash', bytes32],
['previousSunsetHash', bytes32],
['previousSunsetSequence', u64be],
['nextSunsetSequence', u64be],
['previousAuthoritySequence', u64be],
['nextAuthoritySequence', u64be],
['previousPublicKey', bytes32],
['nextPublicKey', bytes32],
['previousKeyId', bytes32],
['nextKeyId', bytes32],
['validFromSunsetSequence', u64be],
['previousKeySignature', bytes64],
['nextKeySignature', bytes64]
], {
name: 'HiveRelayCompatibilityAuthorityTransitionV1',
validate (value) {
if (asU64(value.previousSunsetSequence, 'previousSunsetSequence') === 0n ||
asU64(value.nextSunsetSequence, 'nextSunsetSequence') !== asU64(value.previousSunsetSequence, 'previousSunsetSequence') + 1n ||
asU64(value.nextAuthoritySequence, 'nextAuthoritySequence') !== asU64(value.previousAuthoritySequence, 'previousAuthoritySequence') + 1n ||
asU64(value.validFromSunsetSequence, 'validFromSunsetSequence') !== asU64(value.nextSunsetSequence, 'nextSunsetSequence')) {
fail('compatibility authority transition sequences are not contiguous')
}
if (b4a.equals(value.previousPublicKey, value.nextPublicKey) || b4a.equals(value.previousKeyId, value.nextKeyId)) fail('authority transition must change key and key ID')
nonzero(value.sunsetChainId, 'sunsetChainId')
nonzero(value.previousPublicKey, 'previousPublicKey')
nonzero(value.nextPublicKey, 'nextPublicKey')
}
})
const compatibilityStorageRootV1 = struct([
['path', utf8Path],
['rootClass', canonicalAsciiBytes(1, 64, 'rootClass')],
['encryptionPublicKey', optional(bytes32, 'encryptionPublicKey')]
], { name: 'HiveRelayCompatibilityStorageRootV1' })
export const hiveRelayCompatibilityRuntimeBoundaryEvidenceV1 = struct([
['version', version1],
['compatibilityProductId', canonicalAsciiBytes(1, 128, 'compatibilityProductId')],
['compatibilityArtifactHash', bytes32],
['compatibilityBuildManifestHash', bytes32],
['entrypointPath', utf8Path],
['entrypointBytes', boundedBytes(1, 16 * MiB, 'entrypointBytes')],
['serviceUnitPath', utf8Path],
['serviceUnitBytes', boundedBytes(1, MiB, 'serviceUnitBytes')],
['processUid', u32be],
['processGid', u32be],
['processArgv', arrayOf(canonicalUtf8Bytes(1, 512, 'processArgv'), 1, 64, 'processArgv')],
['listeners', arrayOf(blindListenerEntryV1, 1, 64, 'listeners')],
['descriptorProtocolId', canonicalAsciiBytes(1, 64, 'descriptorProtocolId')],
['descriptorSigningPublicKey', bytes32],
['discoveryTopic', bytes32],
['publicCredentials', arrayOf(publicCredentialV1, 1, 32, 'publicCredentials')],
['storageRoots', arrayOf(compatibilityStorageRootV1, 1, 16, 'storageRoots')],
['releaseChannelUrl', httpsUrl],
['releaseChannelPublicKey', bytes32],
['logSinkId', canonicalAsciiBytes(1, 128, 'logSinkId')],
['logNamespace', canonicalAsciiBytes(1, 128, 'logNamespace')],
['metricSinkId', canonicalAsciiBytes(1, 128, 'metricSinkId')],
['metricNamespace', canonicalAsciiBytes(1, 128, 'metricNamespace')],
['deploymentId', bytes32],
['observedFromUnixMillis', u64be],
['observedThroughUnixMillis', u64be],
['successorBuildArtifactHash', bytes32],
['successorBuildManifestHash', bytes32],
['successorBuildManifestBytes', boundedBytes(1, 48 * MiB, 'successorBuildManifestBytes')],
['successorBlindRuntimeBoundaryEvidenceBytes', boundedBytes(1, 48 * MiB, 'successorBlindRuntimeBoundaryEvidenceBytes')],
['disjointBoundaryBits', constant(u16be, 0x07ff, 'disjointBoundaryBits')],
['issuedUnixMillis', u64be],
['expiresUnixMillis', u64be],
['evidenceSignerPublicKey', bytes32],
['signature', bytes64]
], {
name: 'HiveRelayCompatibilityRuntimeBoundaryEvidenceV1',
validate (value) {
assertSorted(value.listeners, (left, right) => compareCanonical(blindListenerEntryV1, left, right), 'compatibility boundary listeners')
if (value.listeners.some(listener => listener.componentId !== 3 || listener.listenerClass !== 1)) fail('compatibility boundary listeners must be public component 3 listeners')
assertSorted(value.publicCredentials, (left, right) => compareCanonical(publicCredentialV1, left, right), 'public credentials')
assertUnique(value.publicCredentials, (left, right) => left.credentialClass === right.credentialClass, 'public credential classes')
assertSorted(value.storageRoots, (left, right) => compareCanonical(compatibilityStorageRootV1, left, right), 'storage roots')
assertUnique(value.storageRoots, (left, right) => b4a.equals(left.path, right.path), 'compatibility storage root paths')
validateBoundaryWindow(value, 'compatibility runtime boundary')
nonzero(value.deploymentId, 'deploymentId')
nonzero(value.descriptorSigningPublicKey, 'descriptorSigningPublicKey')
nonzero(value.releaseChannelPublicKey, 'releaseChannelPublicKey')
nonzero(value.evidenceSignerPublicKey, 'evidenceSignerPublicKey')
}
})