dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x

This commit is contained in:
hermes committed 2026-10-01 19:21:19 +00:00
commit e6dcef9bf5
302 files changed
+75178

No files matched your search

@@ -0,0 +1,12 @@
export async function consumeInternalCapability(access, { token, audience, method, target }) {
try {
const result = await access.request('POST', '/v1/capabilities/consume', {
token, audience, method, target,
})
return result.authorized === true
} catch (error) {
if (error?.statusCode === 401) return false
if (!Number.isInteger(error?.statusCode) || error.statusCode >= 500) error.statusCode = 503
throw error
}
}
@@ -0,0 +1,63 @@
import { lstat, readdir } from 'node:fs/promises'
import { join } from 'node:path'
const EVIDENCE_KEYS = Object.freeze([
'dshSettings',
'webProfile',
'dshSessions',
'userSkills',
'deploymentState',
'managementState',
'updaterState',
'legacyAuthenticationConfigured',
])
async function exists(path) {
return lstat(path).then(() => true, error => error?.code === 'ENOENT' ? false : Promise.reject(error))
}
async function nonEmptyDirectory(path) {
try { return (await readdir(path)).length > 0 }
catch (error) { return error?.code === 'ENOENT' ? false : Promise.reject(error) }
}
export function parseAccessEvidence(value) {
let parsed
try { parsed = JSON.parse(value) } catch { throw new Error('legacy installation evidence is invalid') }
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)
|| Object.keys(parsed).length !== EVIDENCE_KEYS.length
|| EVIDENCE_KEYS.some(key => typeof parsed[key] !== 'boolean')) {
throw new Error('legacy installation evidence is invalid')
}
return Object.freeze(Object.fromEntries(EVIDENCE_KEYS.map(key => [key, parsed[key]])))
}
export async function collectAccessEvidence({ dshHome, paths, legacyAuthenticationConfigured = false }) {
const [
dshSettings,
webProfile,
dshSessions,
userSkills,
deploymentState,
managementState,
updaterState,
] = await Promise.all([
exists(join(dshHome, 'settings.yaml')),
exists(join(dshHome, 'profiles', 'web', 'package.json')),
nonEmptyDirectory(join(dshHome, 'sessions')),
nonEmptyDirectory(join(dshHome, 'skills')),
exists(join(paths.deploymentStateRoot, 'slots.json')),
nonEmptyDirectory(paths.managementStateRoot),
nonEmptyDirectory(paths.updaterStateRoot),
])
return Object.freeze({
dshSettings,
webProfile,
dshSessions,
userSkills,
deploymentState,
managementState,
updaterState,
legacyAuthenticationConfigured: legacyAuthenticationConfigured === true,
})
}
@@ -0,0 +1,76 @@
import { exactKeys, isoTimestamp, parseJsonDocument, plainObject, positiveSafeInteger, TrustError } from './validation.mjs'
export const MANIFEST_MEDIA_TYPE = 'application/vnd.dsh-platform.manifest.v1+json'
export const SIGNATURE_MEDIA_TYPE = 'application/vnd.dsh-platform.signature.v1+json'
function nonNegativeSafeInteger(value, label) {
if (!Number.isSafeInteger(value) || value < 0) {
throw new TrustError(`${label} must be a non-negative safe integer`)
}
return value
}
export function parseArtifactDescriptor(value, label = 'artifact') {
const object = plainObject(value, label)
exactKeys(object, ['id', 'mediaType', 'sha256', 'size', 'url'], label)
if (typeof object.id !== 'string' || !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(object.id)) {
throw new TrustError(`${label}.id is invalid`)
}
if (typeof object.mediaType !== 'string' || !/^[\x21-\x7e]{1,127}$/.test(object.mediaType)) {
throw new TrustError(`${label}.mediaType is invalid`)
}
if (typeof object.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(object.sha256)) {
throw new TrustError(`${label}.sha256 must be a SHA-256 hex digest`)
}
const size = nonNegativeSafeInteger(object.size, `${label}.size`)
let url
try {
url = new URL(object.url)
} catch {
throw new TrustError(`${label}.url must be an HTTPS URL`)
}
if (url.protocol !== 'https:' || url.username !== '' || url.password !== '') {
throw new TrustError(`${label}.url must be an HTTPS URL without credentials`)
}
return Object.freeze({
id: object.id,
mediaType: object.mediaType,
sha256: object.sha256,
size,
url: url.href,
})
}
export function parseArtifactList(value, label) {
if (!Array.isArray(value)) throw new TrustError(`${label} must be an array`)
const artifacts = value.map((entry, index) => parseArtifactDescriptor(entry, `${label}[${String(index)}]`))
if (new Set(artifacts.map(artifact => artifact.id)).size !== artifacts.length) {
throw new TrustError(`${label} must not contain duplicate IDs`)
}
return Object.freeze(artifacts)
}
export function parseReleaseManifest(bytes) {
const object = parseJsonDocument(bytes, 'release manifest')
const common = ['artifacts', 'issuedAt', 'keyringGeneration', 'manifestType', 'schema', 'targetSequence', 'version']
const specific = object.manifestType === 'bootstrap'
? ['bootstrapApi', 'entrypoint']
: object.manifestType === 'environment'
? ['bootstrapApi', 'components', 'patches', 'systemPlugins']
: []
exactKeys(object, [...common, ...specific], 'release manifest')
if (object.schema !== 1) throw new TrustError('release manifest schema must be 1')
if (typeof object.manifestType !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(object.manifestType)) {
throw new TrustError('release manifest manifestType is invalid')
}
if (typeof object.version !== 'string' || object.version === '') {
throw new TrustError('release manifest version is invalid')
}
return Object.freeze({
document: object,
keyringGeneration: positiveSafeInteger(object.keyringGeneration, 'release manifest keyringGeneration'),
targetSequence: positiveSafeInteger(object.targetSequence, 'release manifest targetSequence'),
issuedAt: isoTimestamp(object.issuedAt, 'release manifest issuedAt'),
artifacts: parseArtifactList(object.artifacts, 'release manifest artifacts'),
})
}
@@ -0,0 +1,46 @@
import { link, mkdir, open, rename, rm } from 'node:fs/promises'
import { randomUUID } from 'node:crypto'
import { dirname } from 'node:path'
async function syncDirectory(path) {
const handle = await open(path, 'r')
try {
await handle.sync()
} finally {
await handle.close()
}
}
async function temporaryFile(path, bytes, mode) {
await mkdir(dirname(path), { recursive: true })
const temporary = `${path}.${randomUUID()}.tmp`
const handle = await open(temporary, 'wx', mode)
try {
await handle.writeFile(bytes)
await handle.sync()
} finally {
await handle.close()
}
return temporary
}
export async function durableReplace(path, bytes, mode = 0o600) {
const temporary = await temporaryFile(path, bytes, mode)
try {
await rename(temporary, path)
await syncDirectory(dirname(path))
} catch (error) {
await rm(temporary, { force: true })
throw error
}
}
export async function durableCreate(path, bytes, mode = 0o600) {
const temporary = await temporaryFile(path, bytes, mode)
try {
await link(temporary, path)
await syncDirectory(dirname(path))
} finally {
await rm(temporary, { force: true })
}
}
@@ -0,0 +1,31 @@
export function parseBootstrapVersion(value, label = 'Bootstrap VERSION') {
if (typeof value !== 'string') throw new Error(`${label} is invalid`)
const normalized = value.endsWith('\r\n')
? value.slice(0, -2)
: value.endsWith('\n') ? value.slice(0, -1) : value
if (!/^\d+\.\d+\.\d+$/.test(normalized)) {
throw new Error(`${label} is invalid`)
}
if (normalized.split('.').some(part => !Number.isSafeInteger(Number(part)))) {
throw new Error(`${label} is invalid`)
}
return normalized
}
function compareBootstrapVersions(left, right) {
const leftParts = left.split('.').map(Number)
const rightParts = right.split('.').map(Number)
for (let index = 0; index < leftParts.length; index += 1) {
if (leftParts[index] !== rightParts[index]) return leftParts[index] - rightParts[index]
}
return 0
}
export function validateBootstrapContentTransition({ previousVersion, previousSha256, nextVersion, nextSha256 }) {
parseBootstrapVersion(previousVersion, 'previous Bootstrap version')
parseBootstrapVersion(nextVersion, 'next Bootstrap version')
if (previousSha256 === nextSha256) return
if (compareBootstrapVersions(nextVersion, previousVersion) <= 0) {
throw new Error('Bootstrap content changed without increasing Bootstrap VERSION')
}
}
@@ -0,0 +1,23 @@
import { TrustError } from './validation.mjs'
function normalize(value) {
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value
if (typeof value === 'number') {
if (!Number.isSafeInteger(value)) throw new TrustError('canonical JSON numbers must be safe integers')
return value
}
if (Array.isArray(value)) return value.map(normalize)
if (typeof value === 'object') {
const result = {}
for (const key of Object.keys(value).sort()) {
if (value[key] === undefined) throw new TrustError('canonical JSON cannot contain undefined')
result[key] = normalize(value[key])
}
return result
}
throw new TrustError(`canonical JSON cannot contain ${typeof value}`)
}
export function canonicalJson(value) {
return Buffer.from(`${JSON.stringify(normalize(value))}\n`)
}
@@ -0,0 +1,265 @@
import { exactKeys, isoTimestamp, parseJsonDocument, plainObject, positiveSafeInteger, TrustError } from './validation.mjs'
import { parseArtifactList } from './artifact-contracts.mjs'
function identifier(value, label) {
if (typeof value !== 'string' || !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(value)) {
throw new TrustError(`${label} is invalid`)
}
return value
}
function version(value, label) {
if (typeof value !== 'string' || value === '' || value.length > 128 || /[\0/\\]/.test(value)) {
throw new TrustError(`${label} is invalid`)
}
return value
}
function exactReference(value, label) {
const object = plainObject(value, label)
exactKeys(object, ['manifestArtifactId', 'signatureArtifactId', 'version'], label)
const manifestArtifactId = identifier(object.manifestArtifactId, `${label}.manifestArtifactId`)
const signatureArtifactId = identifier(object.signatureArtifactId, `${label}.signatureArtifactId`)
if (manifestArtifactId === signatureArtifactId) throw new TrustError(`${label} manifest and signature Artifacts must differ`)
return Object.freeze({
version: version(object.version, `${label}.version`),
manifestArtifactId,
signatureArtifactId,
})
}
function dshReference(value) {
const object = plainObject(value, 'stable.desired.dsh')
exactKeys(object, ['integrity', 'version'], 'stable.desired.dsh')
if (typeof object.integrity !== 'string' || !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(object.integrity)) {
throw new TrustError('stable.desired.dsh.integrity must be npm SHA-512 integrity')
}
return Object.freeze({
version: version(object.version, 'stable.desired.dsh.version'),
integrity: object.integrity,
})
}
function registryKey(value, label) {
const object = plainObject(value, label)
exactKeys(object, ['algorithm', 'expires', 'keyId', 'publicKey'], label)
if (object.algorithm !== 'ECDSA-P256-SHA256') throw new TrustError(`${label}.algorithm is unsupported`)
if (typeof object.keyId !== 'string' || !/^SHA256:[A-Za-z0-9+/]{43}$/.test(object.keyId)) {
throw new TrustError(`${label}.keyId is invalid`)
}
if (typeof object.publicKey !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(object.publicKey)) {
throw new TrustError(`${label}.publicKey is invalid`)
}
if (object.expires !== null) isoTimestamp(object.expires, `${label}.expires`)
return Object.freeze({ ...object })
}
export function parseOfficialDshPolicy(value, label = 'official DSH policy') {
const object = plainObject(value, label)
exactKeys(object, ['keys', 'packageName', 'registry'], label)
if (object.registry !== 'https://registry.npmjs.org/') {
throw new TrustError(`${label}.registry must be the official npm registry`)
}
if (object.packageName !== '@deepseek-ai/dsh') {
throw new TrustError(`${label}.packageName must be @deepseek-ai/dsh`)
}
if (!Array.isArray(object.keys) || object.keys.length === 0) throw new TrustError(`${label}.keys must not be empty`)
const keys = object.keys.map((entry, index) => registryKey(entry, `${label}.keys[${String(index)}]`))
if (new Set(keys.map(key => key.keyId)).size !== keys.length) throw new TrustError(`${label}.keys must be unique`)
return Object.freeze({ registry: object.registry, packageName: object.packageName, keys: Object.freeze(keys) })
}
export function parseStable(bytes) {
const object = parseJsonDocument(bytes, 'stable')
exactKeys(
object,
['artifacts', 'desired', 'issuedAt', 'keyringGeneration', 'officialDshPolicy', 'schema', 'targetSequence', 'updateApi'],
'stable',
)
if (object.schema !== 1) throw new TrustError('stable.schema must be 1')
if (object.updateApi !== 1) throw new TrustError('stable.updateApi must be 1')
const desired = plainObject(object.desired, 'stable.desired')
exactKeys(desired, ['bootstrap', 'dsh', 'environment'], 'stable.desired')
const artifacts = parseArtifactList(object.artifacts, 'stable.artifacts')
const parsedDesired = Object.freeze({
bootstrap: exactReference(desired.bootstrap, 'stable.desired.bootstrap'),
environment: exactReference(desired.environment, 'stable.desired.environment'),
dsh: dshReference(desired.dsh),
})
const artifactIds = new Set(artifacts.map(artifact => artifact.id))
for (const artifactId of [
parsedDesired.bootstrap.manifestArtifactId,
parsedDesired.bootstrap.signatureArtifactId,
parsedDesired.environment.manifestArtifactId,
parsedDesired.environment.signatureArtifactId,
]) {
if (!artifactIds.has(artifactId)) throw new TrustError(`stable desired Artifact ${JSON.stringify(artifactId)} is missing`)
}
return Object.freeze({
document: object,
keyringGeneration: positiveSafeInteger(object.keyringGeneration, 'stable.keyringGeneration'),
targetSequence: positiveSafeInteger(object.targetSequence, 'stable.targetSequence'),
issuedAt: isoTimestamp(object.issuedAt, 'stable.issuedAt'),
artifacts,
desired: parsedDesired,
officialDshPolicy: parseOfficialDshPolicy(object.officialDshPolicy),
})
}
function stringArray(value, label) {
if (!Array.isArray(value) || value.some(entry => typeof entry !== 'string' || entry.includes('\0'))) {
throw new TrustError(`${label} must be a string array`)
}
return Object.freeze([...value])
}
function stringMap(value, label) {
const object = plainObject(value, label)
const result = {}
for (const [key, entry] of Object.entries(object)) {
if (!/^[A-Z_][A-Z0-9_]*$/.test(key) || typeof entry !== 'string' || entry.includes('\0')) {
throw new TrustError(`${label} contains an invalid environment entry`)
}
result[key] = entry
}
return Object.freeze(result)
}
function command(value, label) {
const object = plainObject(value, label)
exactKeys(object, ['args', 'executable', 'timeoutSeconds'], label)
if (typeof object.executable !== 'string' || !object.executable.startsWith('/')) {
throw new TrustError(`${label}.executable must be an absolute path`)
}
return Object.freeze({
executable: object.executable,
args: stringArray(object.args, `${label}.args`),
timeoutSeconds: positiveSafeInteger(object.timeoutSeconds, `${label}.timeoutSeconds`),
})
}
function optionalCommand(value, label) {
return value === null ? null : command(value, label)
}
function parseHealth(value) {
if (value === null) return null
const object = plainObject(value, 'component.health')
if (object.type === 'http') {
exactKeys(object, ['host', 'intervalSeconds', 'path', 'port', 'timeoutSeconds', 'type'], 'component.health')
if (object.host !== '127.0.0.1') throw new TrustError('HTTP health host must be 127.0.0.1')
if (!Number.isInteger(object.port) || object.port < 1 || object.port > 65535) throw new TrustError('HTTP health port is invalid')
if (typeof object.path !== 'string' || !object.path.startsWith('/')) throw new TrustError('HTTP health path is invalid')
} else if (object.type === 'exec') {
exactKeys(object, ['command', 'intervalSeconds', 'timeoutSeconds', 'type'], 'component.health')
command(object.command, 'component.health.command')
} else throw new TrustError('component.health.type must be http or exec')
positiveSafeInteger(object.intervalSeconds, 'component.health.intervalSeconds')
positiveSafeInteger(object.timeoutSeconds, 'component.health.timeoutSeconds')
return Object.freeze(object)
}
export function parseComponentManifest(bytes) {
const object = parseJsonDocument(bytes, 'component')
exactKeys(
object,
['command', 'environment', 'health', 'id', 'lifecycle', 'logging', 'schema', 'type'],
'component',
)
if (object.schema !== 1) throw new TrustError('component.schema must be 1')
if (!['service', 'oneshot', 'hook'].includes(object.type)) throw new TrustError('component.type is invalid')
const lifecycle = plainObject(object.lifecycle, 'component.lifecycle')
const phases = ['prepare', 'postStart', 'postStop', 'preStart', 'preStop', 'stop']
exactKeys(lifecycle, phases, 'component.lifecycle')
const logging = plainObject(object.logging, 'component.logging')
exactKeys(logging, ['stderr', 'stdout'], 'component.logging')
if (typeof logging.stdout !== 'boolean' || typeof logging.stderr !== 'boolean') {
throw new TrustError('component logging flags must be booleans')
}
return Object.freeze({
schema: object.schema,
id: identifier(object.id, 'component.id'),
type: object.type,
command: command(object.command, 'component.command'),
environment: stringMap(object.environment, 'component.environment'),
lifecycle: Object.freeze(Object.fromEntries(phases.map(phase => [
phase,
optionalCommand(lifecycle[phase], `component.lifecycle.${phase}`),
]))),
health: parseHealth(object.health),
logging: Object.freeze({ stdout: logging.stdout, stderr: logging.stderr }),
})
}
function orderedReferences(value, artifacts, label) {
if (!Array.isArray(value)) throw new TrustError(`${label} must be an array`)
const result = value.map((entry, index) => {
const object = plainObject(entry, `${label}[${String(index)}]`)
exactKeys(object, ['id', 'sha256'], `${label}[${String(index)}]`)
if (typeof object.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(object.sha256)) {
throw new TrustError(`${label}[${String(index)}].sha256 must be a SHA-256 hex digest`)
}
if (!artifacts.some(artifact => artifact.sha256 === object.sha256)) {
throw new TrustError(`${label}[${String(index)}] references a missing Artifact hash`)
}
return Object.freeze({
id: identifier(object.id, `${label}[${String(index)}].id`),
sha256: object.sha256,
})
})
if (new Set(result.map(item => item.id)).size !== result.length) throw new TrustError(`${label} IDs must be unique`)
return Object.freeze(result)
}
export function parseBootstrapManifest(bytes) {
const manifest = parseCommonManifest(bytes, 'bootstrap')
exactKeys(manifest.object, [...manifest.commonKeys, 'bootstrapApi', 'entrypoint'], 'bootstrap manifest')
if (manifest.object.bootstrapApi !== 1) throw new TrustError('bootstrapApi must be 1')
if (typeof manifest.object.entrypoint !== 'string' || !manifest.object.entrypoint.startsWith('/')) {
throw new TrustError('bootstrap entrypoint must be absolute')
}
return Object.freeze({ ...manifest.common, bootstrapApi: 1, entrypoint: manifest.object.entrypoint })
}
export function parseEnvironmentManifest(bytes) {
const manifest = parseCommonManifest(bytes, 'environment')
exactKeys(
manifest.object,
[...manifest.commonKeys, 'bootstrapApi', 'components', 'patches', 'systemPlugins'],
'environment manifest',
)
if (manifest.object.bootstrapApi !== 1) throw new TrustError('environment bootstrapApi must be 1')
return Object.freeze({
...manifest.common,
bootstrapApi: 1,
components: orderedReferences(manifest.object.components, manifest.common.artifacts, 'environment components'),
patches: orderedReferences(manifest.object.patches, manifest.common.artifacts, 'environment patches'),
systemPlugins: orderedReferences(manifest.object.systemPlugins, manifest.common.artifacts, 'environment systemPlugins'),
})
}
export function artifactForReference(manifest, reference) {
const artifact = manifest.artifacts.find(candidate => candidate.sha256 === reference.sha256)
if (artifact === undefined) throw new TrustError(`resource ${JSON.stringify(reference.id)} references a missing Artifact hash`)
return artifact
}
function parseCommonManifest(bytes, expectedType) {
const object = parseJsonDocument(bytes, `${expectedType} manifest`)
const commonKeys = ['artifacts', 'issuedAt', 'keyringGeneration', 'manifestType', 'schema', 'targetSequence', 'version']
if (object.schema !== 1) throw new TrustError(`${expectedType} manifest schema must be 1`)
if (object.manifestType !== expectedType) throw new TrustError(`manifestType must be ${expectedType}`)
return {
object,
commonKeys,
common: {
schema: 1,
manifestType: expectedType,
version: version(object.version, `${expectedType} manifest version`),
keyringGeneration: positiveSafeInteger(object.keyringGeneration, `${expectedType} keyringGeneration`),
targetSequence: positiveSafeInteger(object.targetSequence, `${expectedType} targetSequence`),
issuedAt: isoTimestamp(object.issuedAt, `${expectedType} issuedAt`),
artifacts: parseArtifactList(object.artifacts, `${expectedType} artifacts`),
},
}
}
@@ -0,0 +1,286 @@
import { createHash } from 'node:crypto'
import { canonicalJson } from './canonical-json.mjs'
import { exactKeys, parseJsonDocument, plainObject, TrustError } from './validation.mjs'
export const PLATFORM_LAYOUT = 1
export const BOOTSTRAP_API = 1
export const UPDATE_API = 1
const REFERENCE_KINDS = new Set([
'bootstrap',
'environment',
'pristine',
'runtime',
'system-plugins',
])
const AUTHORITIES = new Set(['stable', 'experimental', 'development'])
const SHA256 = /^[a-f0-9]{64}$/
const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
const IMAGE_BUILD_ID = /^sha256:[a-f0-9]{64}$/
function identifier(value, label) {
if (typeof value !== 'string' || !IDENTIFIER.test(value)) throw new TrustError(`${label} is invalid`)
return value
}
function sha256(value, label) {
if (typeof value !== 'string' || !SHA256.test(value)) throw new TrustError(`${label} must be a SHA-256 hex digest`)
return value
}
function imageBuildId(value, label = 'imageBuildId') {
if (typeof value !== 'string' || !IMAGE_BUILD_ID.test(value)) throw new TrustError(`${label} is invalid`)
return value
}
function version(value, label) {
if (typeof value !== 'string' || value === '' || value.length > 128 || /[\0/\\]/.test(value)) {
throw new TrustError(`${label} is invalid`)
}
return value
}
function nonNegativeInteger(value, label) {
if (!Number.isSafeInteger(value) || value < 0) throw new TrustError(`${label} must be a non-negative safe integer`)
return value
}
function positiveInteger(value, label) {
if (!Number.isSafeInteger(value) || value < 1) throw new TrustError(`${label} must be a positive safe integer`)
return value
}
function authority(value, sequence, label = 'authority') {
if (!AUTHORITIES.has(value)) throw new TrustError(`${label} is invalid`)
if (value === 'development' && sequence !== 0) throw new TrustError('development authority requires targetSequence 0')
if (value !== 'development' && sequence === 0) throw new TrustError(`${value} authority requires a positive targetSequence`)
return value
}
function parseBytesOrObject(value, label) {
return Buffer.isBuffer(value) ? parseJsonDocument(value, label) : plainObject(value, label)
}
function contentHash(value) {
return createHash('sha256').update(canonicalJson(value)).digest('hex')
}
export function deriveRecordId(prefix, recordWithoutId) {
identifier(prefix, 'record prefix')
return `${prefix}-${contentHash(recordWithoutId)}`
}
export function deriveImageBuildId(inventoryWithoutId) {
return `sha256:${contentHash(inventoryWithoutId)}`
}
function parseInventoryAsset(value, kind, label) {
const object = plainObject(value, label)
exactKeys(object, ['id', 'sha256'], label)
return Object.freeze({
id: identifier(object.id, `${label}.id`),
sha256: sha256(object.sha256, `${label}.sha256`),
kind,
})
}
export function parseArtifactReference(value, label = 'artifact reference') {
const object = plainObject(value, label)
if (object.storage === 'image') {
exactKeys(object, ['id', 'imageBuildId', 'kind', 'sha256', 'storage'], label)
imageBuildId(object.imageBuildId, `${label}.imageBuildId`)
} else if (object.storage === 'store') {
exactKeys(object, ['id', 'kind', 'sha256', 'storage'], label)
} else {
throw new TrustError(`${label}.storage must be image or store`)
}
if (!REFERENCE_KINDS.has(object.kind)) throw new TrustError(`${label}.kind is invalid`)
return Object.freeze({
storage: object.storage,
...(object.storage === 'image' ? { imageBuildId: object.imageBuildId } : {}),
kind: object.kind,
id: identifier(object.id, `${label}.id`),
sha256: sha256(object.sha256, `${label}.sha256`),
})
}
export function parseImageInventory(value) {
const object = parseBytesOrObject(value, 'image inventory')
exactKeys(object, [
'authority', 'bootstrap', 'bootstrapApi', 'deployment', 'imageBuildId', 'platformRevision',
'schema', 'targetSequence', 'updateApi',
], 'image inventory')
if (object.schema !== 1) throw new TrustError('image inventory schema must be 1')
if (object.bootstrapApi !== BOOTSTRAP_API) throw new TrustError('image inventory bootstrapApi must be 1')
if (object.updateApi !== UPDATE_API) throw new TrustError('image inventory updateApi must be 1')
const targetSequence = nonNegativeInteger(object.targetSequence, 'image inventory targetSequence')
const parsedAuthority = authority(object.authority, targetSequence, 'image inventory authority')
identifier(object.platformRevision, 'image inventory platformRevision')
const bootstrap = plainObject(object.bootstrap, 'image inventory bootstrap')
exactKeys(bootstrap, ['id', 'sha256', 'version'], 'image inventory bootstrap')
const parsedBootstrap = Object.freeze({
version: version(bootstrap.version, 'image inventory bootstrap.version'),
...parseInventoryAsset({ id: bootstrap.id, sha256: bootstrap.sha256 }, 'bootstrap', 'image inventory bootstrap artifact'),
})
const deployment = plainObject(object.deployment, 'image inventory deployment')
exactKeys(deployment, [
'dshVersion', 'environment', 'environmentVersion', 'id', 'pristine', 'runtime', 'systemPlugins',
], 'image inventory deployment')
const parsedDeployment = Object.freeze({
id: identifier(deployment.id, 'image inventory deployment.id'),
dshVersion: version(deployment.dshVersion, 'image inventory deployment.dshVersion'),
environmentVersion: version(deployment.environmentVersion, 'image inventory deployment.environmentVersion'),
environment: parseInventoryAsset(deployment.environment, 'environment', 'image inventory deployment.environment'),
pristine: parseInventoryAsset(deployment.pristine, 'pristine', 'image inventory deployment.pristine'),
runtime: parseInventoryAsset(deployment.runtime, 'runtime', 'image inventory deployment.runtime'),
systemPlugins: parseInventoryAsset(deployment.systemPlugins, 'system-plugins', 'image inventory deployment.systemPlugins'),
})
const withoutId = { ...object }
delete withoutId.imageBuildId
const expected = deriveImageBuildId(withoutId)
imageBuildId(object.imageBuildId)
if (object.imageBuildId !== expected) throw new TrustError('image inventory imageBuildId does not match its canonical content')
return Object.freeze({
schema: 1,
imageBuildId: object.imageBuildId,
authority: parsedAuthority,
platformRevision: object.platformRevision,
targetSequence,
bootstrapApi: BOOTSTRAP_API,
updateApi: UPDATE_API,
bootstrap: parsedBootstrap,
deployment: parsedDeployment,
document: Object.freeze(object),
})
}
function parseRecord(value, label, fields) {
const object = parseBytesOrObject(value, label)
exactKeys(object, ['id', 'schema', ...fields], label)
if (object.schema !== 1) throw new TrustError(`${label} schema must be 1`)
return object
}
function verifyRecordId(object, prefix, label) {
const withoutId = { ...object }
delete withoutId.id
const expected = deriveRecordId(prefix, withoutId)
if (object.id !== expected) throw new TrustError(`${label} id does not match its canonical content`)
return object.id
}
export function parseBootstrapRecord(value) {
const object = parseRecord(value, 'Bootstrap Record', [
'artifact', 'bootstrapApi', 'targetSequence', 'version',
])
if (object.bootstrapApi !== BOOTSTRAP_API) throw new TrustError('Bootstrap Record bootstrapApi must be 1')
const parsed = {
schema: 1,
id: object.id,
version: version(object.version, 'Bootstrap Record version'),
bootstrapApi: BOOTSTRAP_API,
targetSequence: nonNegativeInteger(object.targetSequence, 'Bootstrap Record targetSequence'),
artifact: parseArtifactReference(object.artifact, 'Bootstrap Record artifact'),
}
verifyRecordId(object, 'bootstrap-record', 'Bootstrap Record')
return Object.freeze(parsed)
}
function stringList(value, label) {
if (!Array.isArray(value) || value.some(entry => typeof entry !== 'string' || entry === '' || entry.includes('\0'))) {
throw new TrustError(`${label} must be a string array`)
}
if (new Set(value).size !== value.length) throw new TrustError(`${label} must not contain duplicates`)
return Object.freeze([...value])
}
export function parseDeploymentRecord(value) {
const object = parseRecord(value, 'Deployment Record', [
'authority', 'dshVersion', 'environment', 'environmentVersion', 'pristine', 'receiptTokens',
'runtime', 'snapshotId', 'systemPlugins', 'targetSequence',
])
const targetSequence = nonNegativeInteger(object.targetSequence, 'Deployment Record targetSequence')
const parsed = {
schema: 1,
id: object.id,
authority: authority(object.authority, targetSequence, 'Deployment Record authority'),
targetSequence,
dshVersion: version(object.dshVersion, 'Deployment Record dshVersion'),
environmentVersion: version(object.environmentVersion, 'Deployment Record environmentVersion'),
environment: parseArtifactReference(object.environment, 'Deployment Record environment'),
pristine: parseArtifactReference(object.pristine, 'Deployment Record pristine'),
runtime: parseArtifactReference(object.runtime, 'Deployment Record runtime'),
systemPlugins: parseArtifactReference(object.systemPlugins, 'Deployment Record systemPlugins'),
receiptTokens: stringList(object.receiptTokens, 'Deployment Record receiptTokens'),
snapshotId: object.snapshotId === null ? null : identifier(object.snapshotId, 'Deployment Record snapshotId'),
}
if (parsed.environment.kind !== 'environment' || parsed.pristine.kind !== 'pristine'
|| parsed.runtime.kind !== 'runtime' || parsed.systemPlugins.kind !== 'system-plugins') {
throw new TrustError('Deployment Record references use the wrong kind')
}
const imageIds = [parsed.environment, parsed.pristine, parsed.runtime, parsed.systemPlugins]
.filter(reference => reference.storage === 'image')
.map(reference => reference.imageBuildId)
if (new Set(imageIds).size > 1) throw new TrustError('Deployment Record image references must use one imageBuildId')
verifyRecordId(object, 'deployment-record', 'Deployment Record')
return Object.freeze(parsed)
}
export function parseSlots(value, recordPrefix, label = 'slots') {
const object = parseBytesOrObject(value, label)
exactKeys(object, ['current', 'generation', 'previous', 'schema'], label)
if (object.schema !== 1) throw new TrustError(`${label} schema must be 1`)
const parseId = (entry, field) => entry === null ? null : identifier(entry, `${label}.${field}`)
const current = parseId(object.current, 'current')
const previous = parseId(object.previous, 'previous')
if (current === null) throw new TrustError(`${label}.current must not be null`)
if (!current.startsWith(`${recordPrefix}-`) || (previous !== null && !previous.startsWith(`${recordPrefix}-`))) {
throw new TrustError(`${label} contains an unexpected Record ID`)
}
if (current === previous) throw new TrustError(`${label} current and previous must differ`)
return Object.freeze({ schema: 1, generation: positiveInteger(object.generation, `${label}.generation`), current, previous })
}
function withRecordId(prefix, content) {
const document = { ...content }
return Object.freeze({ ...document, id: deriveRecordId(prefix, document) })
}
export function recordsFromImageInventory(value) {
const inventory = value?.document === undefined ? parseImageInventory(value) : value
const imageReference = asset => ({
storage: 'image',
imageBuildId: inventory.imageBuildId,
kind: asset.kind,
id: asset.id,
sha256: asset.sha256,
})
const bootstrap = withRecordId('bootstrap-record', {
schema: 1,
version: inventory.bootstrap.version,
bootstrapApi: BOOTSTRAP_API,
targetSequence: inventory.targetSequence,
artifact: imageReference(inventory.bootstrap),
})
const deployment = withRecordId('deployment-record', {
schema: 1,
authority: inventory.authority,
targetSequence: inventory.targetSequence,
dshVersion: inventory.deployment.dshVersion,
environmentVersion: inventory.deployment.environmentVersion,
environment: imageReference(inventory.deployment.environment),
pristine: imageReference(inventory.deployment.pristine),
runtime: imageReference(inventory.deployment.runtime),
systemPlugins: imageReference(inventory.deployment.systemPlugins),
receiptTokens: [],
snapshotId: null,
})
return Object.freeze({
bootstrap: parseBootstrapRecord(bootstrap),
deployment: parseDeploymentRecord(deployment),
})
}
@@ -0,0 +1,22 @@
import { readFile } from 'node:fs/promises'
import { durableReplace } from './atomic.mjs'
import { canonicalJson } from './canonical-json.mjs'
export async function readDeploymentStatus(path) {
try {
const value = JSON.parse(await readFile(path, 'utf8'))
return value !== null && typeof value === 'object' && !Array.isArray(value)
? value
: { platformLayout: 1, imageBaseline: null, current: null, imageBehindCurrent: false, recoveryMode: 'status-invalid' }
} catch (error) {
if (error?.code === 'ENOENT') {
return { platformLayout: 1, imageBaseline: null, current: null, imageBehindCurrent: false, recoveryMode: 'status-unavailable' }
}
throw error
}
}
export async function writeDeploymentStatus(path, value) {
await durableReplace(path, canonicalJson(value))
return value
}
@@ -0,0 +1,143 @@
import { execFile } from 'node:child_process'
import { readFile, readdir } from 'node:fs/promises'
import { promisify } from 'node:util'
import { join } from 'node:path'
const execFileAsync = promisify(execFile)
export const DSH_COMPATIBILITY_PACKAGES = Object.freeze({
'@deepseek-ai/dsh-api-remotes': Object.freeze(['.', './client']),
'@deepseek-ai/dsh-app-boot': Object.freeze(['.']),
'@deepseek-ai/dsh-client-connection': Object.freeze(['.', './client']),
'@deepseek-ai/dsh-client-locale': Object.freeze(['.', './client']),
'@deepseek-ai/dsh-client-ui-settings': Object.freeze(['.', './client']),
'@deepseek-ai/dsh-client-ui-theme': Object.freeze(['.', './client']),
'@deepseek-ai/dsh-host-directory-picker-browse': Object.freeze(['.']),
'@deepseek-ai/dsh-llm': Object.freeze(['.', './remote']),
'@deepseek-ai/dsh-settings': Object.freeze(['.']),
'@deepseek-ai/dsh-web-app': Object.freeze(['.', './startup', './cordis.patch.yml']),
})
const DSH_DIRECT_COMPATIBILITY_PACKAGES = Object.freeze([
'@deepseek-ai/dsh-app-boot',
'@deepseek-ai/dsh-web-app',
])
export const DSH_WEB_ROWS = Object.freeze({
connection: '@deepseek-ai/dsh-client-connection',
'plugin-inventory': '@deepseek-ai/dsh-host-plugin-inventory',
'settings-controller': '@deepseek-ai/dsh-api-settings-controller',
webserver: '@deepseek-ai/dsh-host-webserver',
'web-runtime': '@deepseek-ai/dsh-web-app',
})
export const DSH_REMOTE_METHODS = Object.freeze([
'@deepseek-ai/dsh-api-settings-controller#settings/describe',
'@deepseek-ai/dsh-llm#llm/listConfigurableProviders',
'@deepseek-ai/dsh-llm#llm/listProviders',
])
export const DSH_STATIC_CLIENT_MODULES = Object.freeze([
'@deepseek-ai/dsh-client-ui-primitives',
'react',
'react-dom',
])
function packagePath(root, packageName, file = 'package.json') {
return join(root, 'node_modules', ...packageName.split('/'), file)
}
async function json(path) {
return JSON.parse(await readFile(path, 'utf8'))
}
function requireObject(value, description) {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${description} is invalid`)
}
return value
}
function requireText(source, expected, description) {
if (!source.includes(expected)) throw new Error(`${description} is missing ${JSON.stringify(expected)}`)
}
function requireStaticModule(source, name) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const pattern = name === 'react'
? /(?:^|[{,])["']?react["']?:/
: new RegExp(`["']${escaped}["']:`)
if (!pattern.test(source)) throw new Error(`DSH Web static module table is missing ${JSON.stringify(name)}`)
}
function exported(manifest, subpath) {
const exports = requireObject(manifest.exports, `${manifest.name} exports`)
return Object.hasOwn(exports, subpath)
}
function webRowPattern(id, packageName) {
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const escapedPackage = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
return new RegExp(`(?:^|\\n)- id: ${escapedId}\\n(?: {2}[^\\n]*\\n)*? {2}name: ['\"]?${escapedPackage}['\"]?(?:\\n|$)`)
}
export async function verifyDshCompatibility({ packageRoot, expectedVersion, home }) {
if (typeof packageRoot !== 'string' || packageRoot === '') throw new TypeError('DSH package root is required')
if (typeof expectedVersion !== 'string' || expectedVersion === '') throw new TypeError('Expected DSH version is required')
if (typeof home !== 'string' || home === '') throw new TypeError('Temporary DSH home is required')
const manifest = await json(join(packageRoot, 'package.json'))
if (manifest.name !== '@deepseek-ai/dsh') throw new Error('DSH package name is invalid')
if (manifest.version !== expectedVersion) {
throw new Error(`DSH package version ${JSON.stringify(manifest.version)} does not match ${JSON.stringify(expectedVersion)}`)
}
if (manifest.bin?.dsh !== 'lib/bin.js') throw new Error('DSH command entry is not lib/bin.js')
const dependencies = requireObject(manifest.dependencies, 'DSH dependencies')
for (const packageName of DSH_DIRECT_COMPATIBILITY_PACKAGES) {
if (typeof dependencies[packageName] !== 'string') throw new Error(`DSH does not declare ${packageName}`)
}
for (const [packageName, requiredExports] of Object.entries(DSH_COMPATIBILITY_PACKAGES)) {
const dependency = await json(packagePath(packageRoot, packageName))
if (dependency.name !== packageName || dependency.version !== expectedVersion) {
throw new Error(`${packageName} is not installed at DSH version ${expectedVersion}`)
}
for (const subpath of requiredExports) {
if (!exported(dependency, subpath)) throw new Error(`${packageName} does not export ${subpath}`)
}
}
const remotes = await readFile(packagePath(packageRoot, '@deepseek-ai/dsh-api-remotes', 'lib/client.js'), 'utf8')
for (const method of DSH_REMOTE_METHODS) requireText(remotes, method, 'DSH Remote registry')
const frontendRoot = packagePath(packageRoot, '@deepseek-ai/dsh-web-frontend', 'dist/assets')
const frontend = (await Promise.all((await readdir(frontendRoot))
.filter(name => name.endsWith('.js'))
.map(name => readFile(join(frontendRoot, name), 'utf8')))).join('\n')
for (const moduleName of DSH_STATIC_CLIENT_MODULES) {
requireStaticModule(frontend, moduleName)
}
const { stdout, stderr } = await execFileAsync(
process.execPath,
[join(packageRoot, 'lib/bin.js'), '--profile', 'web', '--dump-default-config'],
{
env: { ...process.env, DSH_HOME: home },
maxBuffer: 8 * 1024 * 1024,
timeout: 30_000,
},
)
if (stderr.trim() !== '') throw new Error(`DSH config dump wrote to stderr: ${stderr.trim()}`)
for (const [id, packageName] of Object.entries(DSH_WEB_ROWS)) {
if (!webRowPattern(id, packageName).test(stdout)) {
throw new Error(`DSH Web profile does not contain ${id} (${packageName})`)
}
}
return Object.freeze({
name: manifest.name,
version: manifest.version,
packages: Object.keys(DSH_COMPATIBILITY_PACKAGES).length,
remoteMethods: DSH_REMOTE_METHODS.length,
webRows: Object.keys(DSH_WEB_ROWS).length,
})
}
@@ -0,0 +1,15 @@
import { compareDshVersions } from './supported-target.mjs'
export const OFFICIAL_DSH_PACKAGE = '@deepseek-ai/dsh'
export const OFFICIAL_NPM_REGISTRY = 'https://registry.npmjs.org/'
export const OFFICIAL_DSH_METADATA_LIMIT = 20 * 1024 * 1024
export const OFFICIAL_DSH_TARBALL_LIMIT = 512 * 1024 * 1024
export function officialDshMetadataUrl() {
return new URL(encodeURIComponent(OFFICIAL_DSH_PACKAGE), OFFICIAL_NPM_REGISTRY)
}
export function officialDshTarballUrl(version) {
compareDshVersions(version, version)
return new URL(`${OFFICIAL_DSH_PACKAGE}/-/dsh-${version}.tgz`, OFFICIAL_NPM_REGISTRY)
}
@@ -0,0 +1,98 @@
const LOOPBACK = '127.0.0.1'
export const OUTBOUND_PROXY_PORTS = Object.freeze({
updates: 17891,
platform: 17892,
dshCore: 17893,
dshPlugins: 17894,
agentNetwork: 17895,
managementTerminal: 17896,
modelApi: 17897,
sharedDsh: 17898,
})
export const OUTBOUND_PROXY_SCOPES = Object.freeze(Object.keys(OUTBOUND_PROXY_PORTS))
export const PLATFORM_NO_PROXY = Object.freeze(['localhost', '127.0.0.1', '::1'])
export const OUTBOUND_PROXY_ENVIRONMENT_KEYS = Object.freeze([
'HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy',
'ALL_PROXY', 'all_proxy',
])
export function outboundProxyUrl(scope) {
const port = OUTBOUND_PROXY_PORTS[scope]
if (port === undefined) throw new Error(`outbound proxy scope ${String(scope)} is invalid`)
return `http://${LOOPBACK}:${String(port)}`
}
export function outboundProxyScopeEnabled(configuration, scope, providerId) {
if (configuration?.enabled !== true) return false
if (scope === 'sharedDsh') return configuration.scopes?.dshCore === true || configuration.scopes?.dshPlugins === true
if (scope === 'modelApi') {
if (providerId === undefined) return false
const policy = configuration.modelApi?.providers?.[providerId] ?? configuration.modelApi?.default
return policy?.proxyEnabled === true
}
return configuration.scopes?.[scope] === true
}
export function outboundProxyEnvironment(scope, {
noProxy = PLATFORM_NO_PROXY,
allProxy = false,
enabled = true,
} = {}) {
if (!Array.isArray(noProxy) || noProxy.some(value => typeof value !== 'string' || value.length === 0)) {
throw new Error('outbound proxy NO_PROXY entries are invalid')
}
if (typeof allProxy !== 'boolean') throw new Error('outbound proxy ALL_PROXY setting is invalid')
const proxy = outboundProxyUrl(scope)
const bypass = [...new Set(noProxy)].join(',')
if (typeof enabled !== 'boolean') throw new Error('outbound proxy enabled setting is invalid')
return Object.freeze({
...Object.fromEntries(OUTBOUND_PROXY_ENVIRONMENT_KEYS.map(key => [key, undefined])),
NO_PROXY: bypass,
no_proxy: bypass,
...(enabled ? {
HTTP_PROXY: proxy,
HTTPS_PROXY: proxy,
http_proxy: proxy,
https_proxy: proxy,
...(allProxy ? { ALL_PROXY: proxy, all_proxy: proxy } : {}),
} : {}),
})
}
export function parseOutboundProxyEnvironment(value, scope) {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
throw new Error('outbound proxy environment response is invalid')
}
const expectedUrl = outboundProxyUrl(scope)
const allowed = new Set([
'HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy',
'NO_PROXY', 'no_proxy', 'ALL_PROXY', 'all_proxy',
])
if (Object.keys(value).some(key => !allowed.has(key))) {
throw new Error('outbound proxy environment response contains unsupported fields')
}
const proxyKeys = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy']
const proxied = proxyKeys.some(key => value[key] !== undefined)
for (const key of proxyKeys) {
if (proxied ? value[key] !== expectedUrl : value[key] !== undefined) {
throw new Error(`outbound proxy environment ${key} is invalid`)
}
}
if (typeof value.NO_PROXY !== 'string' || value.no_proxy !== value.NO_PROXY) {
throw new Error('outbound proxy environment NO_PROXY is invalid')
}
const hasAllProxy = value.ALL_PROXY !== undefined || value.all_proxy !== undefined
if (hasAllProxy && (value.ALL_PROXY !== expectedUrl || value.all_proxy !== expectedUrl)) {
throw new Error('outbound proxy environment ALL_PROXY is invalid')
}
return Object.freeze(Object.fromEntries(Object.entries(value)))
}
export function clearOutboundProxyEnvironment(value = {}) {
return Object.freeze({
...Object.fromEntries(OUTBOUND_PROXY_ENVIRONMENT_KEYS.map(key => [key, undefined])),
...value,
})
}
+155
View File
@@ -0,0 +1,155 @@
import { randomUUID } from 'node:crypto'
import { chmod, chown, lstat, mkdir, rename, rm, symlink } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
const LEGACY_NAMES = Object.freeze([
'bootstrap', 'downloads', 'dsh', 'environments', 'run', 'runtime', 'snapshots', 'system-plugins', 'trust',
])
async function exists(path) {
return lstat(path).then(() => true, error => error?.code === 'ENOENT' ? false : Promise.reject(error))
}
export class PlatformPaths {
constructor(dataRoot = '/data/platform', runRoot = '/run/dsh-platform') {
this.dataRoot = resolve(dataRoot)
this.runRoot = resolve(runRoot)
this.stateRoot = join(this.dataRoot, 'state')
this.storeRoot = join(this.dataRoot, 'store')
this.cacheRoot = join(this.dataRoot, 'cache')
this.logsRoot = join(this.dataRoot, 'logs')
this.trustStateRoot = join(this.stateRoot, 'trust')
this.developmentTrustStateRoot = join(this.trustStateRoot, 'development')
this.bootstrapStateRoot = join(this.stateRoot, 'bootstrap')
this.deploymentStateRoot = join(this.stateRoot, 'deployments')
this.deploymentStatusPath = join(this.deploymentStateRoot, 'status.json')
this.systemSkillStatePath = join(this.deploymentStateRoot, 'skills.json')
this.updaterStateRoot = join(this.stateRoot, 'updater')
this.proxyStateRoot = join(this.stateRoot, 'proxy')
this.accessStateRoot = join(this.stateRoot, 'access')
this.proxyTestStatePath = join(this.proxyStateRoot, 'test-tasks.json')
this.managementStateRoot = join(this.stateRoot, 'management')
this.userPluginStatePath = join(this.managementStateRoot, 'user-plugins.json')
this.userPluginJournalPath = join(this.managementStateRoot, 'user-plugin-transaction.json')
this.proxyProviderInventoryPath = join(this.managementStateRoot, 'proxy-provider-inventory.json')
this.fileTasksRoot = join(this.managementStateRoot, 'file-tasks')
this.maintenanceLeasePath = join(this.managementStateRoot, 'maintenance-mutation.lock')
this.objectsRoot = join(this.storeRoot, 'objects')
this.bootstrapStoreRoot = join(this.storeRoot, 'bootstrap')
this.environmentsRoot = join(this.storeRoot, 'environments')
this.systemSkillCatalogsRoot = join(this.environmentsRoot, 'versions', '.system-skill-catalogs')
this.pristineRoot = join(this.storeRoot, 'pristine')
this.runtimesRoot = join(this.storeRoot, 'runtimes')
this.systemPluginsRoot = join(this.storeRoot, 'system-plugins')
this.snapshotsRoot = join(this.storeRoot, 'snapshots')
this.userPluginSnapshotsRoot = join(this.snapshotsRoot, 'user-plugins')
this.downloadsRoot = join(this.cacheRoot, 'downloads')
this.npmCacheRoot = join(this.cacheRoot, 'npm')
this.viewsRoot = join(this.runRoot, 'views')
this.deploymentViewsRoot = join(this.runRoot, 'deployments')
this.systemPluginViewsRoot = join(this.runRoot, 'system-plugin-views')
this.systemSkillsView = join(this.viewsRoot, 'skills')
this.deploymentView = join(this.runRoot, 'deployment')
this.trustSocket = join(this.runRoot, 'stage0-trust.sock')
this.bootstrapSocket = join(this.runRoot, 'bootstrap.sock')
this.dshLifecycleSocket = join(this.runRoot, 'dsh-lifecycle.sock')
this.managementSocket = join(this.runRoot, 'management.sock')
this.managementCliSocket = join(this.runRoot, 'management-cli.sock')
this.maintenanceSocket = join(this.runRoot, 'maintenance.sock')
this.userSkillSocket = join(this.runRoot, 'user-skills.sock')
this.snapshotSocket = join(this.runRoot, 'snapshot.sock')
this.gatewayAccessSocket = join(this.runRoot, 'gateway-access.sock')
this.accessRunRoot = join(this.runRoot, 'access')
this.accessSocket = join(this.accessRunRoot, 'access.sock')
this.accessRecoverySocket = join(this.accessRunRoot, 'recovery.sock')
this.accessLaunchSocket = join(this.runRoot, 'access-launch.sock')
this.proxyControlSocket = join(this.runRoot, 'outbound-proxy.sock')
this.proxyRoutingStatePath = join(this.runRoot, 'outbound-proxy-routing.json')
this.proxyLaunchSocket = join(this.runRoot, 'proxy-launch.sock')
this.recoverySocket = join(this.runRoot, 'recovery.sock')
}
}
export function trustStateRootForAuthority(paths, authority) {
return authority === 'development' ? paths.developmentTrustStateRoot : paths.trustStateRoot
}
export async function rejectLegacyLayout(paths) {
const found = []
for (const name of LEGACY_NAMES) {
if (await exists(join(paths.dataRoot, name))) found.push(name)
}
if (found.length > 0) {
throw new Error(
`legacy DSH platform volume layout detected (${found.join(', ')}); stop the container and clear only ${paths.dataRoot}, then restart. Do not delete /data/dsh.`,
)
}
}
export async function preparePersistentLayout(paths) {
await mkdir(paths.dataRoot, { recursive: true })
await rejectLegacyLayout(paths)
const directories = [
paths.trustStateRoot, paths.bootstrapStateRoot, paths.deploymentStateRoot, paths.updaterStateRoot,
paths.managementStateRoot, paths.fileTasksRoot,
paths.objectsRoot, paths.bootstrapStoreRoot, paths.environmentsRoot, paths.pristineRoot,
paths.runtimesRoot, paths.systemPluginsRoot, paths.snapshotsRoot, paths.userPluginSnapshotsRoot,
paths.downloadsRoot, paths.npmCacheRoot, paths.logsRoot,
]
await Promise.all(directories.map(path => mkdir(path, { recursive: true })))
return Object.freeze([...directories])
}
export async function resetRuntimeLayout(paths) {
if (paths.runRoot === '/' || paths.runRoot === paths.dataRoot) throw new Error('platform run root is unsafe')
await rm(paths.runRoot, { recursive: true, force: true })
await mkdir(paths.viewsRoot, { recursive: true })
await mkdir(paths.deploymentViewsRoot, { recursive: true })
await mkdir(paths.systemPluginViewsRoot, { recursive: true })
await mkdir(paths.systemSkillsView, { recursive: true })
for (const name of ['environment', 'runtime']) {
await symlink(join('..', 'deployment', name), join(paths.viewsRoot, name), 'dir')
}
await symlink(join('..', 'system-plugin-views', 'current'), join(paths.viewsRoot, 'system-plugins'), 'dir')
await symlink(join('..', 'deployment', 'system-plugins'), join(paths.systemPluginViewsRoot, 'current'), 'dir')
if (process.getuid?.() === 0) {
await chown(paths.runRoot, 0, 1000)
// Isolated service identities must traverse to their dedicated subtrees and
// immutable Bootstrap entrypoints without gaining the node group's socket access.
await chmod(paths.runRoot, 0o1771)
await chown(paths.viewsRoot, 0, 0)
await chmod(paths.viewsRoot, 0o755)
await chown(paths.deploymentViewsRoot, 1000, 1000)
await chmod(paths.deploymentViewsRoot, 0o755)
await chown(paths.systemPluginViewsRoot, 1000, 1000)
await chmod(paths.systemPluginViewsRoot, 0o755)
await chown(paths.systemSkillsView, 1000, 1000)
await chmod(paths.systemSkillsView, 0o755)
}
}
export async function replaceRuntimeView(paths, name, target) {
if (name !== 'bootstrap') {
throw new Error(`runtime view ${name} is invalid`)
}
const path = join(paths.viewsRoot, name)
const temporary = join(dirname(path), `.${name}.${randomUUID()}.tmp`)
await symlink(resolve(target), temporary, 'dir')
await rename(temporary, path)
return path
}
export async function replaceDeploymentView(paths, target) {
const temporary = join(paths.runRoot, `.deployment.${randomUUID()}.tmp`)
await symlink(resolve(target), temporary, 'dir')
await rename(temporary, paths.deploymentView)
return paths.deploymentView
}
export async function replaceSystemPluginView(paths, target = join(paths.deploymentView, 'system-plugins')) {
const path = join(paths.systemPluginViewsRoot, 'current')
const temporary = join(paths.systemPluginViewsRoot, `.current.${randomUUID()}.tmp`)
await symlink(resolve(target), temporary, 'dir')
await rename(temporary, path)
return path
}
@@ -0,0 +1,92 @@
import { exactKeys, parseJsonDocument, TrustError } from './validation.mjs'
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/
function parseSemver(value, label) {
if (typeof value !== 'string') throw new TrustError(`${label} must be a semantic version`)
const match = SEMVER.exec(value)
if (match === null) throw new TrustError(`${label} must be a semantic version`)
const prerelease = match[4] === undefined ? [] : match[4].split('.')
for (const identifier of prerelease) {
if (/^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith('0')) {
throw new TrustError(`${label} has an invalid numeric prerelease identifier`)
}
}
return {
core: match.slice(1, 4),
prerelease,
}
}
function compareNumeric(left, right) {
if (left.length !== right.length) return left.length < right.length ? -1 : 1
if (left === right) return 0
return left < right ? -1 : 1
}
function compareIdentifier(left, right) {
const leftNumeric = /^\d+$/.test(left)
const rightNumeric = /^\d+$/.test(right)
if (leftNumeric && rightNumeric) return compareNumeric(left, right)
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1
if (left === right) return 0
return left < right ? -1 : 1
}
export function compareSemanticVersions(leftValue, rightValue, leftLabel = 'left version', rightLabel = 'right version') {
const left = parseSemver(leftValue, leftLabel)
const right = parseSemver(rightValue, rightLabel)
for (let index = 0; index < left.core.length; index += 1) {
const compared = compareNumeric(left.core[index], right.core[index])
if (compared !== 0) return compared
}
if (left.prerelease.length === 0 || right.prerelease.length === 0) {
return left.prerelease.length === right.prerelease.length ? 0 : left.prerelease.length === 0 ? 1 : -1
}
const length = Math.max(left.prerelease.length, right.prerelease.length)
for (let index = 0; index < length; index += 1) {
if (left.prerelease[index] === undefined) return -1
if (right.prerelease[index] === undefined) return 1
const compared = compareIdentifier(left.prerelease[index], right.prerelease[index])
if (compared !== 0) return Math.sign(compared)
}
return 0
}
export function compareDshVersions(leftValue, rightValue) {
return compareSemanticVersions(leftValue, rightValue, 'left DSH version', 'right DSH version')
}
export function parseSupportedTarget(bytes) {
const object = parseJsonDocument(bytes, 'supported target')
exactKeys(object, ['environment', 'latestSupportedDsh', 'schema'], 'supported target')
if (object.schema !== 1) throw new TrustError('supported target schema must be 1')
parseSemver(object.latestSupportedDsh, 'latestSupportedDsh')
if (typeof object.environment !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(object.environment)) {
throw new TrustError('supported target environment is invalid')
}
return Object.freeze({
schema: 1,
latestSupportedDsh: object.latestSupportedDsh,
environment: object.environment,
})
}
export function validateSupportedTarget(targetBytes, environmentDefinitionBytes) {
const target = parseSupportedTarget(targetBytes)
const definition = parseJsonDocument(environmentDefinitionBytes, 'environment definition')
if (definition.version !== target.environment) {
throw new TrustError(`supported Environment ${JSON.stringify(target.environment)} does not match definition ${JSON.stringify(definition.version)}`)
}
return target
}
export function advanceSupportedDsh(target, upstreamVersion) {
const comparison = compareDshVersions(upstreamVersion, target.latestSupportedDsh)
if (comparison < 0) throw new TrustError('upstream DSH version would roll back Latest Supported')
if (comparison === 0) return Object.freeze({ changed: false, target })
return Object.freeze({
changed: true,
target: Object.freeze({ ...target, latestSupportedDsh: upstreamVersion }),
})
}
@@ -0,0 +1,45 @@
import { createHash } from 'node:crypto'
import { lstat, readFile, readdir, readlink } from 'node:fs/promises'
import { join } from 'node:path'
import { canonicalJson } from './canonical-json.mjs'
import { TrustError } from './validation.mjs'
async function entries(root, relative = '', includeMode = true) {
const names = await readdir(join(root, relative))
const result = []
for (const name of names.sort()) {
const path = relative === '' ? name : `${relative}/${name}`
const absolute = join(root, path)
const details = await lstat(absolute)
const mode = details.mode & 0o777
if (details.isDirectory()) {
result.push({ path, type: 'directory', ...(includeMode ? { mode } : {}) })
result.push(...await entries(root, path, includeMode))
} else if (details.isFile()) {
result.push({
path,
type: 'file',
...(includeMode ? { mode } : {}),
sha256: createHash('sha256').update(await readFile(absolute)).digest('hex'),
size: details.size,
})
} else if (details.isSymbolicLink()) {
const target = await readlink(absolute)
if (target.includes('\0')) throw new TrustError(`tree symlink ${path} is invalid`)
result.push({ path, type: 'symlink', ...(includeMode ? { mode } : {}), target })
} else {
throw new TrustError(`tree entry ${path} has an unsupported type`)
}
}
return result
}
export async function hashTree(root) {
const manifest = { schema: 1, entries: await entries(root) }
return createHash('sha256').update(canonicalJson(manifest)).digest('hex')
}
export async function hashTreeContent(root) {
const manifest = { schema: 1, entries: await entries(root, '', false) }
return createHash('sha256').update(canonicalJson(manifest)).digest('hex')
}
@@ -0,0 +1,49 @@
export class TrustError extends Error {
constructor(message, code = 'TRUST_INVALID') {
super(message)
this.name = 'TrustError'
this.code = code
}
}
export function plainObject(value, label) {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
throw new TrustError(`${label} must be an object`)
}
return value
}
export function exactKeys(value, expected, label) {
const actual = Object.keys(value).sort()
const wanted = [...expected].sort()
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
throw new TrustError(`${label} fields must be exactly: ${wanted.join(', ')}`)
}
}
export function positiveSafeInteger(value, label) {
if (!Number.isSafeInteger(value) || value < 1) {
throw new TrustError(`${label} must be a positive safe integer`)
}
return value
}
export function isoTimestamp(value, label) {
if (typeof value !== 'string') throw new TrustError(`${label} must be an ISO timestamp`)
const parsed = new Date(value)
if (!Number.isFinite(parsed.valueOf()) || parsed.toISOString() !== value) {
throw new TrustError(`${label} must be a canonical ISO timestamp`)
}
return value
}
export function parseJsonDocument(bytes, label) {
if (!Buffer.isBuffer(bytes)) throw new TrustError(`${label} must be bytes`)
let value
try {
value = JSON.parse(bytes.toString('utf8'))
} catch {
throw new TrustError(`${label} must contain valid JSON`)
}
return plainObject(value, label)
}