mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 08:02:38 +00:00
264 lines
8.3 KiB
TypeScript
264 lines
8.3 KiB
TypeScript
import { existsSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { z } from 'zod'
|
|
import { readNodeFileSyncWithinLimit } from './node-bounded-file-reader'
|
|
import {
|
|
JsonStringifyByteLimitError,
|
|
stringifyJsonWithinByteLimit
|
|
} from './node-bounded-json-stringify'
|
|
import { writeSecureJsonFileWithinLimit } from './bounded-secure-json-file'
|
|
import { hardenExistingSecureFile } from './secure-file'
|
|
import {
|
|
EphemeralVmRuntimeRecordSchema,
|
|
type EphemeralVmRuntimeRecord
|
|
} from './ephemeral-vm-runtimes'
|
|
|
|
const EPHEMERAL_VM_RUNTIME_FEATURES_FILE = 'orca-ephemeral-vm-runtime-features.json'
|
|
export const MAX_EPHEMERAL_VM_RUNTIME_FEATURE_STORE_FILE_BYTES = 1024 * 1024
|
|
|
|
const EphemeralVmRuntimeFeatureEntrySchema = z
|
|
.object({
|
|
id: z.string().min(1),
|
|
recipeId: z.string().min(1),
|
|
createdAt: z.number().finite(),
|
|
recipeCheckoutMode: z.enum(['orca-worktree', 'provisioned-root']).optional(),
|
|
resultCheckoutMode: z.literal('provisioned-root').optional()
|
|
})
|
|
.strict()
|
|
|
|
export type EphemeralVmRuntimeFeatureEntry = z.infer<typeof EphemeralVmRuntimeFeatureEntrySchema>
|
|
|
|
const EphemeralVmRuntimeFeatureStoreSchema = z
|
|
.object({
|
|
version: z.literal(1),
|
|
records: z.array(z.unknown())
|
|
})
|
|
.strict()
|
|
|
|
export type EphemeralVmRuntimeFeatureStoreSnapshot =
|
|
| {
|
|
writable: true
|
|
features: EphemeralVmRuntimeFeatureEntry[]
|
|
retainedRecords: unknown[]
|
|
}
|
|
| {
|
|
writable: false
|
|
features: []
|
|
retainedRecords: []
|
|
}
|
|
|
|
export function getEphemeralVmRuntimeFeatureStorePath(userDataPath: string): string {
|
|
return join(userDataPath, EPHEMERAL_VM_RUNTIME_FEATURES_FILE)
|
|
}
|
|
|
|
export function assertEphemeralVmRuntimeCheckoutModeCanPersist(
|
|
userDataPath: string,
|
|
args: {
|
|
id: string
|
|
recipeId: string
|
|
createdAt: number
|
|
checkoutMode: NonNullable<NonNullable<EphemeralVmRuntimeRecord['recipe']>['checkoutMode']>
|
|
}
|
|
): void {
|
|
const snapshot = readEphemeralVmRuntimeFeatureStore(userDataPath)
|
|
if (!snapshot.writable) {
|
|
throw new Error('Could not preserve ephemeral VM runtime compatibility metadata.')
|
|
}
|
|
const required: EphemeralVmRuntimeFeatureEntry = {
|
|
id: args.id,
|
|
recipeId: args.recipeId,
|
|
createdAt: args.createdAt,
|
|
recipeCheckoutMode: args.checkoutMode,
|
|
...(args.checkoutMode === 'provisioned-root' ? { resultCheckoutMode: 'provisioned-root' } : {})
|
|
}
|
|
try {
|
|
assertFeatureStoreCanPersist(snapshot, mergeFeatureEntries(snapshot.features, [required]))
|
|
} catch (error) {
|
|
if (error instanceof JsonStringifyByteLimitError) {
|
|
throw new Error(
|
|
'Could not preserve ephemeral VM runtime compatibility metadata; the feature store exceeds its durable capacity.'
|
|
)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export function readEphemeralVmRuntimeFeatureStore(
|
|
userDataPath: string
|
|
): EphemeralVmRuntimeFeatureStoreSnapshot {
|
|
const path = getEphemeralVmRuntimeFeatureStorePath(userDataPath)
|
|
if (!existsSync(path)) {
|
|
return { writable: true, features: [], retainedRecords: [] }
|
|
}
|
|
try {
|
|
hardenExistingSecureFile(path)
|
|
const parsed = EphemeralVmRuntimeFeatureStoreSchema.parse(
|
|
JSON.parse(
|
|
readNodeFileSyncWithinLimit(
|
|
path,
|
|
MAX_EPHEMERAL_VM_RUNTIME_FEATURE_STORE_FILE_BYTES
|
|
).buffer.toString('utf8')
|
|
)
|
|
)
|
|
return parseFeatureRecords(parsed.records)
|
|
} catch {
|
|
return { writable: false, features: [], retainedRecords: [] }
|
|
}
|
|
}
|
|
|
|
export function writeEphemeralVmRuntimeFeatureStore(
|
|
userDataPath: string,
|
|
snapshot: EphemeralVmRuntimeFeatureStoreSnapshot,
|
|
features: EphemeralVmRuntimeFeatureEntry[]
|
|
): void {
|
|
if (!snapshot.writable) {
|
|
throw new Error('The ephemeral VM runtime feature store is not writable.')
|
|
}
|
|
writeSecureJsonFileWithinLimit(
|
|
getEphemeralVmRuntimeFeatureStorePath(userDataPath),
|
|
runtimeFeatureStoreValue(snapshot, features),
|
|
MAX_EPHEMERAL_VM_RUNTIME_FEATURE_STORE_FILE_BYTES,
|
|
{ durable: true }
|
|
)
|
|
}
|
|
|
|
function assertFeatureStoreCanPersist(
|
|
snapshot: EphemeralVmRuntimeFeatureStoreSnapshot,
|
|
features: EphemeralVmRuntimeFeatureEntry[]
|
|
): void {
|
|
if (!snapshot.writable) {
|
|
throw new Error('The ephemeral VM runtime feature store is not writable.')
|
|
}
|
|
stringifyJsonWithinByteLimit(
|
|
runtimeFeatureStoreValue(snapshot, features),
|
|
MAX_EPHEMERAL_VM_RUNTIME_FEATURE_STORE_FILE_BYTES
|
|
)
|
|
}
|
|
|
|
function mergeFeatureEntries(
|
|
existing: readonly EphemeralVmRuntimeFeatureEntry[],
|
|
required: readonly EphemeralVmRuntimeFeatureEntry[]
|
|
): EphemeralVmRuntimeFeatureEntry[] {
|
|
const merged = new Map(existing.map((entry) => [featureIdentity(entry), entry]))
|
|
for (const entry of required) {
|
|
merged.set(featureIdentity(entry), entry)
|
|
}
|
|
return sortRuntimeFeatures([...merged.values()])
|
|
}
|
|
|
|
export function featureEntryFromRuntime(
|
|
runtime: EphemeralVmRuntimeRecord
|
|
): EphemeralVmRuntimeFeatureEntry | null {
|
|
const recipeCheckoutMode = runtime.recipe?.checkoutMode
|
|
const resultCheckoutMode =
|
|
runtime.recipeResult.schemaVersion === 2 ? runtime.recipeResult.checkoutMode : undefined
|
|
if (!recipeCheckoutMode && !resultCheckoutMode) {
|
|
return null
|
|
}
|
|
return {
|
|
id: runtime.id,
|
|
recipeId: runtime.recipeId,
|
|
createdAt: runtime.createdAt,
|
|
...(recipeCheckoutMode ? { recipeCheckoutMode } : {}),
|
|
...(resultCheckoutMode ? { resultCheckoutMode } : {})
|
|
}
|
|
}
|
|
|
|
export function restoreRuntimeFeatureList(
|
|
runtimes: readonly EphemeralVmRuntimeRecord[],
|
|
features: readonly EphemeralVmRuntimeFeatureEntry[]
|
|
): EphemeralVmRuntimeRecord[] {
|
|
const byIdentity = new Map<string, EphemeralVmRuntimeFeatureEntry>()
|
|
for (const feature of features) {
|
|
const identity = featureIdentity(feature)
|
|
if (!byIdentity.has(identity)) {
|
|
byIdentity.set(identity, feature)
|
|
}
|
|
}
|
|
return runtimes.map((runtime) =>
|
|
restoreRuntimeFeatures(runtime, byIdentity.get(featureIdentity(runtime)))
|
|
)
|
|
}
|
|
|
|
function restoreRuntimeFeatures(
|
|
runtime: EphemeralVmRuntimeRecord,
|
|
feature: EphemeralVmRuntimeFeatureEntry | undefined
|
|
): EphemeralVmRuntimeRecord {
|
|
if (!feature) {
|
|
return runtime
|
|
}
|
|
return EphemeralVmRuntimeRecordSchema.parse({
|
|
...runtime,
|
|
...(runtime.recipe && feature.recipeCheckoutMode
|
|
? { recipe: { ...runtime.recipe, checkoutMode: feature.recipeCheckoutMode } }
|
|
: {}),
|
|
...(feature.resultCheckoutMode
|
|
? {
|
|
recipeResult: {
|
|
...runtime.recipeResult,
|
|
schemaVersion: 2,
|
|
checkoutMode: feature.resultCheckoutMode
|
|
}
|
|
}
|
|
: {})
|
|
})
|
|
}
|
|
|
|
export function runtimeFeaturesEqual(
|
|
left: EphemeralVmRuntimeRecord,
|
|
right: EphemeralVmRuntimeRecord
|
|
): boolean {
|
|
return (
|
|
JSON.stringify(featureEntryFromRuntime(left)) === JSON.stringify(featureEntryFromRuntime(right))
|
|
)
|
|
}
|
|
|
|
export function featureIdentity(
|
|
value: Pick<EphemeralVmRuntimeRecord, 'id' | 'recipeId' | 'createdAt'>
|
|
): string {
|
|
return `${value.id}\0${value.recipeId}\0${value.createdAt}`
|
|
}
|
|
|
|
function parseFeatureRecords(records: unknown[]): EphemeralVmRuntimeFeatureStoreSnapshot {
|
|
const features: EphemeralVmRuntimeFeatureEntry[] = []
|
|
const retainedRecords: unknown[] = []
|
|
const identities = new Map<string, string>()
|
|
for (const record of records) {
|
|
const parsed = EphemeralVmRuntimeFeatureEntrySchema.safeParse(record)
|
|
if (!parsed.success) {
|
|
retainedRecords.push(record)
|
|
continue
|
|
}
|
|
const identity = featureIdentity(parsed.data)
|
|
const serialized = JSON.stringify(parsed.data)
|
|
const existing = identities.get(identity)
|
|
if (existing && existing !== serialized) {
|
|
return { writable: false, features: [], retainedRecords: [] }
|
|
}
|
|
if (!existing) {
|
|
identities.set(identity, serialized)
|
|
features.push(parsed.data)
|
|
}
|
|
}
|
|
return { writable: true, features: sortRuntimeFeatures(features), retainedRecords }
|
|
}
|
|
|
|
export function sortRuntimeFeatures<T extends { id: string; recipeId: string; createdAt: number }>(
|
|
features: readonly T[]
|
|
): T[] {
|
|
return features
|
|
.map((feature) => ({ feature, identity: featureIdentity(feature) }))
|
|
.sort((left, right) => left.identity.localeCompare(right.identity))
|
|
.map(({ feature }) => feature)
|
|
}
|
|
|
|
function runtimeFeatureStoreValue(
|
|
snapshot: Extract<EphemeralVmRuntimeFeatureStoreSnapshot, { writable: true }>,
|
|
features: EphemeralVmRuntimeFeatureEntry[]
|
|
): { version: 1; records: unknown[] } {
|
|
return {
|
|
version: 1,
|
|
records: [...sortRuntimeFeatures(features), ...snapshot.retainedRecords]
|
|
}
|
|
}
|