From b1f2c78b4000d3d640208cf14c2c61467a1fd4fc Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 31 May 2026 15:37:22 -0400 Subject: [PATCH] refactor: extract runtime core contracts --- docs/architecture.md | 14 +- .../runtime-core/src/artifact-manifest.ts | 2 +- packages/runtime-core/src/index.ts | 548 +---------------- .../src/runtime-action-adapter.ts | 2 +- .../runtime-core/src/runtime-contracts.ts | 551 ++++++++++++++++++ .../src/runtime-episode-contracts.ts | 2 +- packages/runtime-core/src/runtime-episode.ts | 7 +- .../runtime-core/src/runtime-reference.ts | 2 +- 8 files changed, 568 insertions(+), 560 deletions(-) create mode 100644 packages/runtime-core/src/runtime-contracts.ts diff --git a/docs/architecture.md b/docs/architecture.md index fb46bca..bcd3ebd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -90,10 +90,12 @@ product-specific logic belongs in that product's adapter. `runtime-core` is the contract package. It should not import a concrete runtime backend or host adapter. The important modules are: -- [`src/index.ts`](../packages/runtime-core/src/index.ts): `Runtime`, - `RuntimeBackend`, `RuntimeCreateSpec`, `MountSpec`, `ExecutionSpec`, - `ObservationSpec`, `Snapshot`, `ArtifactBundle`, runtime episode contracts, - reference/replay indexes, `createRuntime()`, and artifact verification helpers. +- [`src/runtime-contracts.ts`](../packages/runtime-core/src/runtime-contracts.ts): + `Runtime`, `RuntimeBackend`, `RuntimeCreateSpec`, `MountSpec`, + `ExecutionSpec`, `ObservationSpec`, `Snapshot`, `ArtifactBundle`, runtime + episode contracts, and `createRuntime()`. +- [`src/index.ts`](../packages/runtime-core/src/index.ts): the public barrel for + focused core modules plus artifact verification helpers. - [`src/runtime-policy.ts`](../packages/runtime-core/src/runtime-policy.ts): `RuntimePolicy`, policy validation, and command allow-list enforcement. - [`src/command-registry.ts`](../packages/runtime-core/src/command-registry.ts): @@ -253,8 +255,8 @@ Anti-dumping-ground rules: ## Runtime Lifecycle The generic `Runtime` contract is defined in -[`runtime-core/src/index.ts`](../packages/runtime-core/src/index.ts). A backend -implements the same lifecycle regardless of how it boots the runtime. +[`runtime-core/src/runtime-contracts.ts`](../packages/runtime-core/src/runtime-contracts.ts). +A backend implements the same lifecycle regardless of how it boots the runtime. ```text create RuntimeCreateSpec diff --git a/packages/runtime-core/src/artifact-manifest.ts b/packages/runtime-core/src/artifact-manifest.ts index 7452ed5..97ec29c 100644 --- a/packages/runtime-core/src/artifact-manifest.ts +++ b/packages/runtime-core/src/artifact-manifest.ts @@ -2,7 +2,7 @@ import { createHash } from "node:crypto" import { readFile } from "node:fs/promises" import { join } from "node:path" -import type { RuntimeInfo } from "./index.js" +import type { RuntimeInfo } from "./runtime-contracts.js" import { stableJson } from "./object-utils.js" export interface ArtifactSpec { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 113f1cc..bbda0cd 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -1,10 +1,7 @@ -import type { ArtifactManifestFile, ArtifactSpec } from "./artifact-manifest.js" -import { RUNTIME_EPISODE_ACTION_SCHEMA, RUNTIME_EPISODE_OBSERVATION_SCHEMA, RUNTIME_EPISODE_SNAPSHOT_SCHEMA, RUNTIME_EPISODE_TRACE_SCHEMA } from "./runtime-episode.js" -import { assertRuntimePolicy } from "./runtime-policy.js" -import type { RuntimePolicy } from "./runtime-policy.js" -import { SANDBOX_WORKSPACE_ROOT } from "./runtime-action-adapter.js" +import type { ArtifactPreview, ArtifactProvenance } from "./runtime-contracts.js" export * from "./artifact-manifest.js" +export * from "./runtime-contracts.js" export * from "./runtime-policy.js" export * from "./workspace-policy.js" export * from "./sandbox-datamachine-tool-policy.js" @@ -18,408 +15,6 @@ export * from "./object-utils.js" export * from "./runtime-action-adapter.js" export * from "./artifact-bundle-verifier.js" -export type RuntimeBackendKind = "wordpress-playground" | (string & {}) - -export type SandboxWorkspaceMode = "repo-backed" | "site-backed" - -export interface EnvironmentSpec { - kind: string - name?: string - blueprint?: unknown - version?: string -} - -export interface RuntimeCreateSpec { - backend: RuntimeBackendKind - environment: EnvironmentSpec - policy: RuntimePolicy - artifactsDirectory?: string - secretEnv?: Record - metadata?: Record - preview?: RuntimePreviewSpec -} - -export interface RuntimePreviewSpec { - publicUrl?: string - siteUrl?: string - port?: number - bind?: string -} - -export interface WorkspaceRecipeMount { - type?: "directory" | "file" - source: string - target: string - mode?: "readonly" | "readwrite" - metadata?: Record -} - -export interface WorkspaceRecipeStagedFile { - source: string - target: string -} - -export interface WorkspaceRecipeStep { - command: string - args?: string[] -} - -export interface WorkspaceRecipePluginRuntimePhp { - memoryLimit?: string - maxExecutionTime?: number -} - -export interface WorkspaceRecipePluginRuntimeHealthProbe { - name: string - type: "plugin-active" | "php" | "wp-cli" - pluginFile?: string - code?: string - command?: string -} - -export interface WorkspaceRecipePluginRuntime { - label?: string - php?: WorkspaceRecipePluginRuntimePhp - wpConfigDefines?: Record - setup?: WorkspaceRecipeStep[] - healthProbes?: WorkspaceRecipePluginRuntimeHealthProbe[] -} - -export interface WorkspaceRecipeExtraPlugin { - source: string - slug?: string - pluginFile?: string - activate?: boolean - sha256?: string - loadAs?: "plugin" | "mu-plugin" -} - -export type WorkspaceRecipeSiteSeedType = "fixture" | "parent_site" -export type WorkspaceRecipeSiteSeedFormat = "json" | (string & {}) - -export interface WorkspaceRecipeSiteSeedScopeSelector { - ids?: number[] - slugs?: string[] - names?: string[] - postTypes?: string[] - taxonomies?: string[] - roles?: string[] - statuses?: string[] - includeFiles?: boolean - anonymize?: boolean - maxRecords?: number -} - -export interface WorkspaceRecipeSiteSeed { - type: WorkspaceRecipeSiteSeedType - name: string - source?: string - format?: WorkspaceRecipeSiteSeedFormat - scopes: { - posts?: WorkspaceRecipeSiteSeedScopeSelector - terms?: WorkspaceRecipeSiteSeedScopeSelector - options?: WorkspaceRecipeSiteSeedScopeSelector - users?: WorkspaceRecipeSiteSeedScopeSelector - media?: WorkspaceRecipeSiteSeedScopeSelector - activePlugins?: boolean - activeTheme?: boolean - } -} - -export type WorkspaceRecipeSeedType = "plugin_scaffold" | "theme_scaffold" | "directory" - -export interface WorkspaceRecipeWorkspaceSeed { - type: WorkspaceRecipeSeedType - slug?: string - name?: string - source?: string - excludePaths?: string[] -} - -export interface WorkspaceRecipeWorkspace { - target?: string - mode?: "readonly" | "readwrite" - sourceMode?: SandboxWorkspaceMode - seed: WorkspaceRecipeWorkspaceSeed -} - -export interface SandboxWorkspaceMountRef { - target: string - mode: "readonly" | "readwrite" - sourceMode: SandboxWorkspaceMode - workspaceRef?: string - mountRole?: string - component?: string - repo?: string - gitRef?: string - defaultBranch?: string - wpContentPath?: string -} - -export interface SandboxWorkspaceContract { - schema: "wp-codebox/sandbox-workspace/v1" - root: typeof SANDBOX_WORKSPACE_ROOT | (string & {}) - defaultMode: SandboxWorkspaceMode - mounts: SandboxWorkspaceMountRef[] - dmc: { - safeAbilities: string[] - parentOnlyAbilities: string[] - } -} - -export interface WorkspaceRecipe { - schema: "wp-codebox/workspace-recipe/v1" - runtime?: { - backend?: RuntimeBackendKind - name?: string - wp?: string - blueprint?: unknown - } - inputs?: { - workspaces?: WorkspaceRecipeWorkspace[] - mounts?: WorkspaceRecipeMount[] - extra_plugins?: WorkspaceRecipeExtraPlugin[] - extraPlugins?: WorkspaceRecipeExtraPlugin[] - secretEnv?: string[] - pluginRuntime?: WorkspaceRecipePluginRuntime - siteSeeds?: WorkspaceRecipeSiteSeed[] - stagedFiles?: WorkspaceRecipeStagedFile[] - inherit?: WorkspaceRecipeInheritanceRequest - inheritance?: WorkspaceRecipeInheritanceResolution - } - workflow: { - before?: WorkspaceRecipeStep[] - steps: WorkspaceRecipeStep[] - after?: WorkspaceRecipeStep[] - } - artifacts?: { - directory?: string - verify?: boolean | WorkspaceRecipeArtifactVerifier - workspacePolicy?: boolean | WorkspaceRecipeWorkspacePolicyArtifact - } -} - -export interface WorkspaceRecipeArtifactVerifier { - enabled?: boolean - strict?: boolean -} - -export interface WorkspaceRecipeWorkspacePolicyArtifact { - enabled?: boolean - strict?: boolean - writableRoots?: string[] - hiddenPaths?: string[] - gitBacked?: boolean -} - -export interface WorkspaceRecipeInheritanceRequest { - connectors?: string[] - settings?: string[] -} - -export interface WorkspaceRecipeInheritanceConnector { - name: string - status: "resolved" | "unresolved" | "skipped" | (string & {}) - provider?: string - model?: string - secretEnv?: string[] - credentials?: ConnectorCredentialEnvelope -} - -export type ConnectorCredentialStatus = "available" | "missing" | "denied" - -export interface ConnectorCredentialSecret { - name: string - status: ConnectorCredentialStatus - scope?: string - source?: "parent-env" | "connector" | (string & {}) - reason?: string -} - -export interface ConnectorCredentialEnvelope { - schema: "wp-codebox/connector-credentials/v1" - connector: string - scope: "connector" - status: ConnectorCredentialStatus - secrets: ConnectorCredentialSecret[] - reason?: string -} - -export interface WorkspaceRecipeInheritanceSetting { - name: string - status: "resolved" | "unresolved" | "skipped" | (string & {}) - scope?: string -} - -export interface WorkspaceRecipeInheritanceResolution { - connectors?: WorkspaceRecipeInheritanceConnector[] - settings?: WorkspaceRecipeInheritanceSetting[] -} - -export interface RuntimeInfo { - id: string - backend: RuntimeBackendKind - environment: EnvironmentSpec - createdAt: string - status: "created" | "destroyed" - previewUrl?: string -} - -export interface MountSpec { - type: "directory" | "file" | (string & {}) - source: string - target: string - mode: "readonly" | "readwrite" - metadata?: Record -} - -export interface ExecutionSpec { - command: string - args?: string[] - cwd?: string - timeoutMs?: number -} - -export type RuntimeEpisodeActionKind = "command" | "filesystem" | "http" | "browser" - -export interface RuntimeEpisodeActionSpec extends ExecutionSpec { - kind?: RuntimeEpisodeActionKind - method?: string - url?: string - path?: string - operation?: string - selector?: string - description?: string - metadata?: Record -} - -export interface RuntimeEpisodeContentDigest { - algorithm: "sha256" - value: string -} - -export interface RuntimeEpisodeTraceRef { - kind: "action" | "execution" | "observation" | "snapshot" | "artifact-bundle" | (string & {}) - id: string - digest?: RuntimeEpisodeContentDigest - artifactId?: string - path?: string -} - -export interface RuntimeEpisodeActionRecord { - schema: typeof RUNTIME_EPISODE_ACTION_SCHEMA - id: string - kind: RuntimeEpisodeActionKind - command: string - args: string[] - cwd?: string - timeoutMs?: number - method?: string - url?: string - path?: string - operation?: string - selector?: string - description?: string - metadata?: Record - digest: RuntimeEpisodeContentDigest -} - -export interface ExecutionResult { - id: string - command: string - args: string[] - exitCode: number - stdout: string - stderr: string - startedAt: string - finishedAt: string -} - -export interface ObservationSpec { - type: - | "runtime-info" - | "mounts" - | "files" - | "command-result" - | "wordpress-state" - | "http-response" - | "browser-result" - | "runtime-events" - | "runtime-logs" - | (string & {}) - path?: string - commandId?: string - url?: string - method?: string - headers?: Record - body?: string - includeBody?: boolean - sections?: string[] - redaction?: "safe" | "none" | (string & {}) - includeContent?: boolean - optionNames?: string[] - userFields?: string[] -} - -export interface ObservationResult { - schema?: typeof RUNTIME_EPISODE_OBSERVATION_SCHEMA - id?: string - type: string - data: unknown - observedAt: string - artifactRefs?: RuntimeEpisodeTraceRef[] - digest?: RuntimeEpisodeContentDigest -} - -export interface LifecycleEvent { - id: string - type: - | "runtime.created" - | "runtime.mounted" - | "runtime.command.started" - | "runtime.command.finished" - | "runtime.observed" - | "runtime.snapshot.created" - | "runtime.artifacts.collected" - | "runtime.destroyed" - | (string & {}) - timestamp: string - data?: Record -} - -export interface Snapshot { - schema?: typeof RUNTIME_EPISODE_SNAPSHOT_SCHEMA - id: string - createdAt: string - semantics?: "metadata-only" | "partial-replay" | "replayable-runtime-state" | "runtime-state-artifact" | (string & {}) - metadata: Record - artifactRefs?: RuntimeEpisodeTraceRef[] - digest?: RuntimeEpisodeContentDigest -} - -export interface RuntimeRestoreSpec { - runtime?: RuntimeCreateSpec - mounts?: MountSpec[] -} - -export interface ArtifactProvenance { - task?: Record - workspace?: SandboxWorkspaceContract - runtime: { - backend: RuntimeBackendKind - version?: string - wordpressVersion?: string - } - agent?: Record - mounts: Array<{ - type: MountSpec["type"] - source: string - target: string - mode: MountSpec["mode"] - metadata?: Record - }> -} - export type ArtifactReviewProgressEventType = | "boot" | "mount" @@ -525,19 +120,6 @@ export interface ArtifactReviewBrowserSummary { }> } -export interface ArtifactPreview { - url: string - localUrl?: string - publicUrl?: string - siteUrl?: string - status: "available" | "expired-on-completion" - lifecycle: "held-after-run" | "destroyed-on-completion" - source: "live-playground" | "public-url-override" - createdAt: string - expiresAt?: string - holdSeconds?: number -} - export interface ArtifactRedactionArtifactSummary { path: string count: number @@ -581,129 +163,3 @@ export interface ArtifactTestResults { suites: ArtifactTestResultsSuite[] rawLogReferences: ArtifactTestResultsRawLogReference[] } - -export interface ArtifactBundle { - id: string - directory: string - manifestPath: string - metadataPath: string - blueprintAfterPath: string - blueprintAfterNotesPath: string - eventsPath: string - commandsPath: string - observationsPath: string - runtimeLogPath: string - commandsLogPath: string - mountsPath: string - capturedMountsPath: string - diffsPath: string - changedFilesPath: string - patchPath: string - testResultsPath: string - reviewPath: string - runAttestationPath?: string - runtimeEpisodeTracePath?: string - runtimeEpisodeEventsPath?: string - artifactVerificationPath?: string - workspacePolicyPath?: string - runtimeReferenceManifestPath?: string - runtimeReferenceIndexPath?: string - runtimeReplayReferenceIndexPath?: string - preview?: ArtifactPreview - contentDigest: string - createdAt: string -} - -export interface Runtime { - info(): Promise - mount(spec: MountSpec): Promise - execute(spec: ExecutionSpec): Promise - observe(spec: ObservationSpec): Promise - snapshot(): Promise - collectArtifacts(spec?: ArtifactSpec): Promise - destroy(): Promise -} - -export interface RuntimeEpisodeSpec { - runtime: RuntimeCreateSpec - mounts?: MountSpec[] - resetObservations?: ObservationSpec[] - stepObservation?: ObservationSpec | false - artifactSpec?: ArtifactSpec -} - -export interface RuntimeEpisodeResetResult { - id: string - runtime: RuntimeInfo - observations: ObservationResult[] - observationRefs: RuntimeEpisodeTraceRef[] -} - -export interface RuntimeEpisodeStepResult { - id: string - index: number - action: RuntimeEpisodeActionRecord - actionRef: RuntimeEpisodeTraceRef - execution: ExecutionResult - executionRef: RuntimeEpisodeTraceRef - observation?: ObservationResult - observationRef?: RuntimeEpisodeTraceRef -} - -export interface RuntimeEpisodeTrace { - schema: typeof RUNTIME_EPISODE_TRACE_SCHEMA - version: 1 - id: string - createdAt: string - runtime: RuntimeInfo - reset: RuntimeEpisodeResetResult - steps: RuntimeEpisodeStepResult[] - snapshots: Snapshot[] - artifacts?: ArtifactBundle - artifactRef?: RuntimeEpisodeTraceRef -} - -export interface RuntimeEpisodeTraceValidationIssue { - path: string - message: string -} - -export interface RuntimeEpisodeTraceValidationResult { - valid: boolean - schema: typeof RUNTIME_EPISODE_TRACE_SCHEMA - issues: RuntimeEpisodeTraceValidationIssue[] -} - -export interface RuntimeEpisode { - reset(): Promise - step(action: RuntimeEpisodeActionSpec, observation?: ObservationSpec | false): Promise - observe(spec: ObservationSpec): Promise - snapshot(): Promise - collectArtifacts(spec?: ArtifactSpec): Promise - trace(): Promise - close(): Promise -} - -export interface RuntimeBackend { - readonly kind: RuntimeBackendKind - create(spec: RuntimeCreateSpec): Promise - restore?(snapshot: Snapshot, spec?: RuntimeRestoreSpec): Promise -} - -export async function createRuntime(spec: RuntimeCreateSpec, backend: RuntimeBackend): Promise { - assertRuntimePolicy(spec.policy) - - if (backend.kind !== spec.backend) { - throw new Error(`Backend ${backend.kind} cannot create runtime ${spec.backend}`) - } - - return backend.create(spec) -} - -export async function restoreRuntime(snapshot: Snapshot, backend: RuntimeBackend, spec?: RuntimeRestoreSpec): Promise { - if (!backend.restore) { - throw new Error(`Backend ${backend.kind} does not support runtime snapshot restore`) - } - - return backend.restore(snapshot, spec) -} diff --git a/packages/runtime-core/src/runtime-action-adapter.ts b/packages/runtime-core/src/runtime-action-adapter.ts index 00dd2ff..7568c7c 100644 --- a/packages/runtime-core/src/runtime-action-adapter.ts +++ b/packages/runtime-core/src/runtime-action-adapter.ts @@ -2,7 +2,7 @@ import { mkdir, readdir, readFile, realpath, rm, writeFile } from "node:fs/promi import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path" import { runtimeEpisodeDigest } from "./runtime-episode.js" import type { RuntimePolicy } from "./runtime-policy.js" -import type { MountSpec, RuntimeEpisode, RuntimeEpisodeContentDigest, RuntimeEpisodeStepResult, RuntimeEpisodeTraceRef } from "./index.js" +import type { MountSpec, RuntimeEpisode, RuntimeEpisodeContentDigest, RuntimeEpisodeStepResult, RuntimeEpisodeTraceRef } from "./runtime-contracts.js" export const RUNTIME_ACTION_OBSERVATION_SCHEMA = "wp-codebox/runtime-action-observation/v1" as const diff --git a/packages/runtime-core/src/runtime-contracts.ts b/packages/runtime-core/src/runtime-contracts.ts new file mode 100644 index 0000000..57149ab --- /dev/null +++ b/packages/runtime-core/src/runtime-contracts.ts @@ -0,0 +1,551 @@ +import { assertRuntimePolicy } from "./runtime-policy.js" +import type { RuntimePolicy } from "./runtime-policy.js" +import { SANDBOX_WORKSPACE_ROOT } from "./runtime-action-adapter.js" +import type { ArtifactSpec } from "./artifact-manifest.js" +import type { + RUNTIME_EPISODE_ACTION_SCHEMA, + RUNTIME_EPISODE_OBSERVATION_SCHEMA, + RUNTIME_EPISODE_SNAPSHOT_SCHEMA, + RUNTIME_EPISODE_TRACE_SCHEMA, +} from "./runtime-episode-contracts.js" + +export type RuntimeBackendKind = "wordpress-playground" | (string & {}) + +export type SandboxWorkspaceMode = "repo-backed" | "site-backed" + +export interface EnvironmentSpec { + kind: string + name?: string + blueprint?: unknown + version?: string +} + +export interface RuntimeCreateSpec { + backend: RuntimeBackendKind + environment: EnvironmentSpec + policy: RuntimePolicy + artifactsDirectory?: string + secretEnv?: Record + metadata?: Record + preview?: RuntimePreviewSpec +} + +export interface RuntimePreviewSpec { + publicUrl?: string + siteUrl?: string + port?: number + bind?: string +} + +export interface WorkspaceRecipeMount { + type?: "directory" | "file" + source: string + target: string + mode?: "readonly" | "readwrite" + metadata?: Record +} + +export interface WorkspaceRecipeStagedFile { + source: string + target: string +} + +export interface WorkspaceRecipeStep { + command: string + args?: string[] +} + +export interface WorkspaceRecipePluginRuntimePhp { + memoryLimit?: string + maxExecutionTime?: number +} + +export interface WorkspaceRecipePluginRuntimeHealthProbe { + name: string + type: "plugin-active" | "php" | "wp-cli" + pluginFile?: string + code?: string + command?: string +} + +export interface WorkspaceRecipePluginRuntime { + label?: string + php?: WorkspaceRecipePluginRuntimePhp + wpConfigDefines?: Record + setup?: WorkspaceRecipeStep[] + healthProbes?: WorkspaceRecipePluginRuntimeHealthProbe[] +} + +export interface WorkspaceRecipeExtraPlugin { + source: string + slug?: string + pluginFile?: string + activate?: boolean + sha256?: string + loadAs?: "plugin" | "mu-plugin" +} + +export type WorkspaceRecipeSiteSeedType = "fixture" | "parent_site" +export type WorkspaceRecipeSiteSeedFormat = "json" | (string & {}) + +export interface WorkspaceRecipeSiteSeedScopeSelector { + ids?: number[] + slugs?: string[] + names?: string[] + postTypes?: string[] + taxonomies?: string[] + roles?: string[] + statuses?: string[] + includeFiles?: boolean + anonymize?: boolean + maxRecords?: number +} + +export interface WorkspaceRecipeSiteSeed { + type: WorkspaceRecipeSiteSeedType + name: string + source?: string + format?: WorkspaceRecipeSiteSeedFormat + scopes: { + posts?: WorkspaceRecipeSiteSeedScopeSelector + terms?: WorkspaceRecipeSiteSeedScopeSelector + options?: WorkspaceRecipeSiteSeedScopeSelector + users?: WorkspaceRecipeSiteSeedScopeSelector + media?: WorkspaceRecipeSiteSeedScopeSelector + activePlugins?: boolean + activeTheme?: boolean + } +} + +export type WorkspaceRecipeSeedType = "plugin_scaffold" | "theme_scaffold" | "directory" + +export interface WorkspaceRecipeWorkspaceSeed { + type: WorkspaceRecipeSeedType + slug?: string + name?: string + source?: string + excludePaths?: string[] +} + +export interface WorkspaceRecipeWorkspace { + target?: string + mode?: "readonly" | "readwrite" + sourceMode?: SandboxWorkspaceMode + seed: WorkspaceRecipeWorkspaceSeed +} + +export interface SandboxWorkspaceMountRef { + target: string + mode: "readonly" | "readwrite" + sourceMode: SandboxWorkspaceMode + workspaceRef?: string + mountRole?: string + component?: string + repo?: string + gitRef?: string + defaultBranch?: string + wpContentPath?: string +} + +export interface SandboxWorkspaceContract { + schema: "wp-codebox/sandbox-workspace/v1" + root: typeof SANDBOX_WORKSPACE_ROOT | (string & {}) + defaultMode: SandboxWorkspaceMode + mounts: SandboxWorkspaceMountRef[] + dmc: { + safeAbilities: string[] + parentOnlyAbilities: string[] + } +} + +export interface WorkspaceRecipe { + schema: "wp-codebox/workspace-recipe/v1" + runtime?: { + backend?: RuntimeBackendKind + name?: string + wp?: string + blueprint?: unknown + } + inputs?: { + workspaces?: WorkspaceRecipeWorkspace[] + mounts?: WorkspaceRecipeMount[] + extra_plugins?: WorkspaceRecipeExtraPlugin[] + extraPlugins?: WorkspaceRecipeExtraPlugin[] + secretEnv?: string[] + pluginRuntime?: WorkspaceRecipePluginRuntime + siteSeeds?: WorkspaceRecipeSiteSeed[] + stagedFiles?: WorkspaceRecipeStagedFile[] + inherit?: WorkspaceRecipeInheritanceRequest + inheritance?: WorkspaceRecipeInheritanceResolution + } + workflow: { + before?: WorkspaceRecipeStep[] + steps: WorkspaceRecipeStep[] + after?: WorkspaceRecipeStep[] + } + artifacts?: { + directory?: string + verify?: boolean | WorkspaceRecipeArtifactVerifier + workspacePolicy?: boolean | WorkspaceRecipeWorkspacePolicyArtifact + } +} + +export interface WorkspaceRecipeArtifactVerifier { + enabled?: boolean + strict?: boolean +} + +export interface WorkspaceRecipeWorkspacePolicyArtifact { + enabled?: boolean + strict?: boolean + writableRoots?: string[] + hiddenPaths?: string[] + gitBacked?: boolean +} + +export interface WorkspaceRecipeInheritanceRequest { + connectors?: string[] + settings?: string[] +} + +export interface WorkspaceRecipeInheritanceConnector { + name: string + status: "resolved" | "unresolved" | "skipped" | (string & {}) + provider?: string + model?: string + secretEnv?: string[] + credentials?: ConnectorCredentialEnvelope +} + +export type ConnectorCredentialStatus = "available" | "missing" | "denied" + +export interface ConnectorCredentialSecret { + name: string + status: ConnectorCredentialStatus + scope?: string + source?: "parent-env" | "connector" | (string & {}) + reason?: string +} + +export interface ConnectorCredentialEnvelope { + schema: "wp-codebox/connector-credentials/v1" + connector: string + scope: "connector" + status: ConnectorCredentialStatus + secrets: ConnectorCredentialSecret[] + reason?: string +} + +export interface WorkspaceRecipeInheritanceSetting { + name: string + status: "resolved" | "unresolved" | "skipped" | (string & {}) + scope?: string +} + +export interface WorkspaceRecipeInheritanceResolution { + connectors?: WorkspaceRecipeInheritanceConnector[] + settings?: WorkspaceRecipeInheritanceSetting[] +} + +export interface RuntimeInfo { + id: string + backend: RuntimeBackendKind + environment: EnvironmentSpec + createdAt: string + status: "created" | "destroyed" + previewUrl?: string +} + +export interface MountSpec { + type: "directory" | "file" | (string & {}) + source: string + target: string + mode: "readonly" | "readwrite" + metadata?: Record +} + +export interface ExecutionSpec { + command: string + args?: string[] + cwd?: string + timeoutMs?: number +} + +export type RuntimeEpisodeActionKind = "command" | "filesystem" | "http" | "browser" + +export interface RuntimeEpisodeActionSpec extends ExecutionSpec { + kind?: RuntimeEpisodeActionKind + method?: string + url?: string + path?: string + operation?: string + selector?: string + description?: string + metadata?: Record +} + +export interface RuntimeEpisodeContentDigest { + algorithm: "sha256" + value: string +} + +export interface RuntimeEpisodeTraceRef { + kind: "action" | "execution" | "observation" | "snapshot" | "artifact-bundle" | (string & {}) + id: string + digest?: RuntimeEpisodeContentDigest + artifactId?: string + path?: string +} + +export interface RuntimeEpisodeActionRecord { + schema: typeof RUNTIME_EPISODE_ACTION_SCHEMA + id: string + kind: RuntimeEpisodeActionKind + command: string + args: string[] + cwd?: string + timeoutMs?: number + method?: string + url?: string + path?: string + operation?: string + selector?: string + description?: string + metadata?: Record + digest: RuntimeEpisodeContentDigest +} + +export interface ExecutionResult { + id: string + command: string + args: string[] + exitCode: number + stdout: string + stderr: string + startedAt: string + finishedAt: string +} + +export interface ObservationSpec { + type: + | "runtime-info" + | "mounts" + | "files" + | "command-result" + | "wordpress-state" + | "http-response" + | "browser-result" + | "runtime-events" + | "runtime-logs" + | (string & {}) + path?: string + commandId?: string + url?: string + method?: string + headers?: Record + body?: string + includeBody?: boolean + sections?: string[] + redaction?: "safe" | "none" | (string & {}) + includeContent?: boolean + optionNames?: string[] + userFields?: string[] +} + +export interface ObservationResult { + schema?: typeof RUNTIME_EPISODE_OBSERVATION_SCHEMA + id?: string + type: string + data: unknown + observedAt: string + artifactRefs?: RuntimeEpisodeTraceRef[] + digest?: RuntimeEpisodeContentDigest +} + +export interface LifecycleEvent { + id: string + type: + | "runtime.created" + | "runtime.mounted" + | "runtime.command.started" + | "runtime.command.finished" + | "runtime.observed" + | "runtime.snapshot.created" + | "runtime.artifacts.collected" + | "runtime.destroyed" + | (string & {}) + timestamp: string + data?: Record +} + +export interface Snapshot { + schema?: typeof RUNTIME_EPISODE_SNAPSHOT_SCHEMA + id: string + createdAt: string + semantics?: "metadata-only" | "partial-replay" | "replayable-runtime-state" | "runtime-state-artifact" | (string & {}) + metadata: Record + artifactRefs?: RuntimeEpisodeTraceRef[] + digest?: RuntimeEpisodeContentDigest +} + +export interface RuntimeRestoreSpec { + runtime?: RuntimeCreateSpec + mounts?: MountSpec[] +} + +export interface ArtifactProvenance { + task?: Record + workspace?: SandboxWorkspaceContract + runtime: { + backend: RuntimeBackendKind + version?: string + wordpressVersion?: string + } + agent?: Record + mounts: Array<{ + type: MountSpec["type"] + source: string + target: string + mode: MountSpec["mode"] + metadata?: Record + }> +} + +export interface ArtifactPreview { + url: string + localUrl?: string + publicUrl?: string + siteUrl?: string + status: "available" | "expired-on-completion" + lifecycle: "held-after-run" | "destroyed-on-completion" + source: "live-playground" | "public-url-override" + createdAt: string + expiresAt?: string + holdSeconds?: number +} + +export interface ArtifactBundle { + id: string + directory: string + manifestPath: string + metadataPath: string + blueprintAfterPath: string + blueprintAfterNotesPath: string + eventsPath: string + commandsPath: string + observationsPath: string + runtimeLogPath: string + commandsLogPath: string + mountsPath: string + capturedMountsPath: string + diffsPath: string + changedFilesPath: string + patchPath: string + testResultsPath: string + reviewPath: string + runAttestationPath?: string + runtimeEpisodeTracePath?: string + runtimeEpisodeEventsPath?: string + artifactVerificationPath?: string + workspacePolicyPath?: string + runtimeReferenceManifestPath?: string + runtimeReferenceIndexPath?: string + runtimeReplayReferenceIndexPath?: string + preview?: ArtifactPreview + contentDigest: string + createdAt: string +} + +export interface Runtime { + info(): Promise + mount(spec: MountSpec): Promise + execute(spec: ExecutionSpec): Promise + observe(spec: ObservationSpec): Promise + snapshot(): Promise + collectArtifacts(spec?: ArtifactSpec): Promise + destroy(): Promise +} + +export interface RuntimeEpisodeSpec { + runtime: RuntimeCreateSpec + mounts?: MountSpec[] + resetObservations?: ObservationSpec[] + stepObservation?: ObservationSpec | false + artifactSpec?: ArtifactSpec +} + +export interface RuntimeEpisodeResetResult { + id: string + runtime: RuntimeInfo + observations: ObservationResult[] + observationRefs: RuntimeEpisodeTraceRef[] +} + +export interface RuntimeEpisodeStepResult { + id: string + index: number + action: RuntimeEpisodeActionRecord + actionRef: RuntimeEpisodeTraceRef + execution: ExecutionResult + executionRef: RuntimeEpisodeTraceRef + observation?: ObservationResult + observationRef?: RuntimeEpisodeTraceRef +} + +export interface RuntimeEpisodeTrace { + schema: typeof RUNTIME_EPISODE_TRACE_SCHEMA + version: 1 + id: string + createdAt: string + runtime: RuntimeInfo + reset: RuntimeEpisodeResetResult + steps: RuntimeEpisodeStepResult[] + snapshots: Snapshot[] + artifacts?: ArtifactBundle + artifactRef?: RuntimeEpisodeTraceRef +} + +export interface RuntimeEpisodeTraceValidationIssue { + path: string + message: string +} + +export interface RuntimeEpisodeTraceValidationResult { + valid: boolean + schema: typeof RUNTIME_EPISODE_TRACE_SCHEMA + issues: RuntimeEpisodeTraceValidationIssue[] +} + +export interface RuntimeEpisode { + reset(): Promise + step(action: RuntimeEpisodeActionSpec, observation?: ObservationSpec | false): Promise + observe(spec: ObservationSpec): Promise + snapshot(): Promise + collectArtifacts(spec?: ArtifactSpec): Promise + trace(): Promise + close(): Promise +} + +export interface RuntimeBackend { + readonly kind: RuntimeBackendKind + create(spec: RuntimeCreateSpec): Promise + restore?(snapshot: Snapshot, spec?: RuntimeRestoreSpec): Promise +} + +export async function createRuntime(spec: RuntimeCreateSpec, backend: RuntimeBackend): Promise { + assertRuntimePolicy(spec.policy) + + if (backend.kind !== spec.backend) { + throw new Error(`Backend ${backend.kind} cannot create runtime ${spec.backend}`) + } + + return backend.create(spec) +} + +export async function restoreRuntime(snapshot: Snapshot, backend: RuntimeBackend, spec?: RuntimeRestoreSpec): Promise { + if (!backend.restore) { + throw new Error(`Backend ${backend.kind} does not support runtime snapshot restore`) + } + + return backend.restore(snapshot, spec) +} diff --git a/packages/runtime-core/src/runtime-episode-contracts.ts b/packages/runtime-core/src/runtime-episode-contracts.ts index 9ad1cdc..96214f3 100644 --- a/packages/runtime-core/src/runtime-episode-contracts.ts +++ b/packages/runtime-core/src/runtime-episode-contracts.ts @@ -12,7 +12,7 @@ import type { RuntimeEpisodeTraceValidationIssue, RuntimeEpisodeTraceValidationResult, Snapshot, -} from "./index.js" +} from "./runtime-contracts.js" export const RUNTIME_EPISODE_TRACE_SCHEMA = "wp-codebox/runtime-episode-trace/v1" as const export const RUNTIME_EPISODE_ACTION_SCHEMA = "wp-codebox/runtime-episode-action/v1" as const diff --git a/packages/runtime-core/src/runtime-episode.ts b/packages/runtime-core/src/runtime-episode.ts index a908fe7..d3eee72 100644 --- a/packages/runtime-core/src/runtime-episode.ts +++ b/packages/runtime-core/src/runtime-episode.ts @@ -3,7 +3,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises" import { join } from "node:path" import { artifactFileDigest, artifactManifestFile, refreshArtifactManifestFileSha256s, upsertArtifactManifestFiles } from "./artifact-manifest.js" -import type { ArtifactManifest } from "./artifact-manifest.js" +import type { ArtifactManifest, ArtifactSpec } from "./artifact-manifest.js" import { isPlainObject as isRecord } from "./object-utils.js" import { RUNTIME_EPISODE_ACTION_SCHEMA, @@ -20,8 +20,6 @@ import type { RuntimeReferenceManifestSnapshotRef } from "./runtime-reference.js import { assertRuntimePolicy } from "./runtime-policy.js" import type { ArtifactBundle, - ArtifactReview, - ArtifactSpec, ObservationResult, ObservationSpec, Runtime, @@ -34,7 +32,8 @@ import type { RuntimeEpisodeTrace, RuntimeEpisodeTraceRef, Snapshot, -} from "./index.js" +} from "./runtime-contracts.js" +import type { ArtifactReview } from "./index.js" export { RUNTIME_EPISODE_ACTION_SCHEMA, RUNTIME_EPISODE_OBSERVATION_SCHEMA, diff --git a/packages/runtime-core/src/runtime-reference.ts b/packages/runtime-core/src/runtime-reference.ts index 975fa69..119438e 100644 --- a/packages/runtime-core/src/runtime-reference.ts +++ b/packages/runtime-core/src/runtime-reference.ts @@ -9,7 +9,7 @@ import type { RuntimeEpisodeTraceRef, RuntimeInfo, Snapshot, -} from "./index.js" +} from "./runtime-contracts.js" export const RUNTIME_REFERENCE_MANIFEST_SCHEMA = "wp-codebox/runtime-reference-manifest/v1" as const export const RUNTIME_REPLAY_REFERENCE_INDEX_SCHEMA = "wp-codebox/runtime-replay-reference-index/v1" as const