import { createHash, randomUUID } from "node:crypto"; import { lstatSync, mkdirSync } from "node:fs"; import { mkdir, open as openFile, readdir, readFile, rename, rm, writeFile } from "node:fs/promises" import { isAbsolute, join } from "node:path"; import { buildInstallationContract, renderWorkspaceDocs } from "./contracts.js"; import { GitWorkspaceRepository, WorkspaceRegistryError, WorkspaceRepositoryLock, type GitStatus, } from "./git-repository.js"; import { parseWorkspaceYaml, serializeWorkspaceYaml, validateOperationalWorkspace, type CanonicalWorkspace, type WorkspaceDescriptor, } from "./schema.js"; import type { WorkspaceErrorCode, WorkspaceRegistryConfig } from "./types.js"; import { VerifiedWorkspaceLockRootLeaseFactory, type Revision40, type CanonicalWorkspaceId } from "./workspace-lock-root-lease.js"; import { WorkspaceFsAtV1 } from "./workspace-fs-at.js"; import { runUnderOrderedWorkspaceWriterLocks, type OrderedWorkspaceWriterCapabilitySet } from "./preprocessing-state.js"; import { RegistryAddressedPublicationStore, addressedRunId, canonicalBootstrapRequestDigest, registryDigest, CapabilityAwareRegistryPublicationLifecycleOwner, type CapabilityAwareRegistryPublicationParticipant, type CapabilityAwareRegistryPublicationSynchronizer, type RegistryAddressedPlanV1, type RegistryPullAddressedPlanV1, type RegistryAddressedRequestV1, type RegistryAddressedResultV1, type RegistryBootstrapRecoveryIdentityV1, type RegistryEnsureBootstrapAddressedResultV1, type RegistryActiveSnapshotV1, type RegistryWorkspaceManifestIdentityV1, type RegistryAddressedPublicationStateV1 } from "./registry-publication.js"; export type { GitStatus } from "./git-repository.js"; export interface WorkspaceRevision { id: string; commit: string; blob: string; snapshotPath: string; } export interface SessionRevisionLease { workspace: WorkspaceDescriptor; revision: WorkspaceRevision; /** Mark the manifest durable; retention removes the lease only after observing that manifest. */ markPersisted(): Promise; /** Remove a lease for a session that failed before its manifest was durable. */ abort(): Promise; } export type PublishWorkspaceRequest = | { action: "create"; workspace: CanonicalWorkspace; baseCommit: string } | { action: "update"; workspace: CanonicalWorkspace; baseCommit: string; baseBlob: string } | { action: "delete"; id: string; baseCommit: string; baseBlob: string }; export class WorkspaceConflictError extends WorkspaceRegistryError { constructor( readonly fields: string[], readonly expected: { commit: string; blob?: string }, readonly actual: { commit: string; blob?: string }, readonly base?: CanonicalWorkspace, readonly local?: CanonicalWorkspace, readonly remote?: CanonicalWorkspace, ) { super("workspace_conflict", "Workspace revision conflicts with the active registry"); this.name = "WorkspaceConflictError"; } } interface ActiveState { head: string; revisions: WorkspaceRevision[]; } interface SnapshotManifest extends ActiveState { files: Record; } interface RevisionLeaseRecord { version: 1; token: string; workspaceId: string; commit: string; state: "creating" | "persisted"; } function workspacePath(id: string): string { if (!/^[a-z][a-z0-9-]{2,62}$/.test(id)) { throw new WorkspaceRegistryError("workspace_invalid", "Workspace ID is invalid"); } return `workspaces/${id}.yaml`; } function safeCommit(commit: string): string { if (!/^[0-9a-f]{40}$/.test(commit)) { throw new WorkspaceRegistryError("workspace_invalid", "Workspace revision is invalid"); } return commit; } function safeBlob(blob: string): string { if (!/^[0-9a-f]{40}$/.test(blob)) { throw new WorkspaceRegistryError("workspace_invalid", "Workspace snapshot blob is invalid"); } return blob; } function digest(contents: unknown): string { return createHash("sha256").update(typeof contents === "string" || Buffer.isBuffer(contents) ? contents : JSON.stringify(contents)).digest("hex"); } function workspaceError(error: unknown): WorkspaceRegistryError { if (error instanceof WorkspaceRegistryError) return error; return new WorkspaceRegistryError("workspace_invalid", "Workspace repository content is invalid"); } /** Immutable canonical workspace snapshots backed by the configured Git checkout. */ export interface WorkspaceRegistryDependencies { readonly rootLeaseFactory: VerifiedWorkspaceLockRootLeaseFactory; readonly lifecycleOwner: CapabilityAwareRegistryPublicationLifecycleOwner; readonly participants: readonly CapabilityAwareRegistryPublicationParticipant[]; readonly synchronizers: readonly CapabilityAwareRegistryPublicationSynchronizer[]; readonly repository?: GitWorkspaceRepository; readonly installationIdentity?: RegistryBootstrapRecoveryIdentityV1; readonly repositoryIdentity?: { readonly digest: string; readonly remote: string; readonly branch: string; readonly head: string }; readonly remoteIdentity?: { readonly digest: string; readonly remote: string; readonly head: string }; } export class WorkspaceRegistry { private readonly repository: GitWorkspaceRepository; private readonly lock: WorkspaceRepositoryLock; private readonly rootLeaseFactory: VerifiedWorkspaceLockRootLeaseFactory; private readonly lifecycleOwner: CapabilityAwareRegistryPublicationLifecycleOwner; private readonly participants: readonly CapabilityAwareRegistryPublicationParticipant[]; private readonly synchronizers: readonly CapabilityAwareRegistryPublicationSynchronizer[]; private readonly installationIdentity: RegistryBootstrapRecoveryIdentityV1; private readonly repositoryIdentity: WorkspaceRegistryDependencies["repositoryIdentity"]; private readonly remoteIdentity: WorkspaceRegistryDependencies["remoteIdentity"]; constructor(input: WorkspaceRegistryDependencies | WorkspaceRegistryConfig) { const raw = input as WorkspaceRegistryDependencies & WorkspaceRegistryConfig; const config = "root" in raw && "branch" in raw ? raw : (raw as WorkspaceRegistryDependencies & { config?: WorkspaceRegistryConfig }).config!; this.repository = raw.repository ?? new GitWorkspaceRepository(config); this.lock = new WorkspaceRepositoryLock(this.repository.locksPath); const sessionsRoot = join(this.repository.root, "sessions"); mkdirSync(sessionsRoot, { recursive: true, mode: 0o700 }); this.rootLeaseFactory = raw.rootLeaseFactory ?? new VerifiedWorkspaceLockRootLeaseFactory({ workspaceFsAt: new WorkspaceFsAtV1(), installationId: this.repository.config.installationId, sessionsRootFromValidatedInstallationConfig: sessionsRoot, serviceUid: process.getuid?.() ?? 0, provisionedWorkspaceMode: 0o700, }); this.lifecycleOwner = raw.lifecycleOwner ?? new CapabilityAwareRegistryPublicationLifecycleOwner(); this.participants = raw.participants ?? []; this.synchronizers = raw.synchronizers ?? []; const hash = (value: string) => createHash("sha256").update(value).digest("hex"); const repo = raw.repositoryIdentity ?? { remote: this.repository.config.remoteUrl ?? "", branch: this.repository.config.branch, head: "", digest: hash(`${this.repository.config.remoteUrl ?? ""}:${this.repository.config.branch}`) }; const remote = raw.remoteIdentity ?? { remote: repo.remote, head: "", digest: repo.digest }; this.repositoryIdentity = repo; this.remoteIdentity = remote; this.installationIdentity = raw.installationIdentity ?? { operation: "registry_bootstrap", requestSha256: hash(this.repository.config.installationId) as never, installationIdentitySha256: hash(this.repository.config.installationId) as never, repositoryIdentitySha256: repo.digest as never, remoteRefIdentitySha256: remote.digest as never }; } snapshotPath(commit: string, id: string): string { return join(this.repository.snapshotsPath, safeCommit(commit), `${workspacePath(id).slice("workspaces/".length)}`); } /** Automatic addressed recovery. The repository lock is held for selection and execution. */ recoveryIdentity(): RegistryBootstrapRecoveryIdentityV1 { return this.installationIdentity; } private async bootstrap(): Promise { await this.repository.ensureLayout(); return this.lock.run(async () => { try { const status = await this.repository.bootstrap(); await this.materialize(status.head!); await this.publishMaterialized(status.head!); return status; } catch (error) { return this.gitFallback(error); } }); } private async pull(): Promise { await this.repository.ensureLayout(); return this.lock.run(async () => { try { const status = await this.repository.pull(); await this.materialize(status.head!); await this.publishMaterialized(status.head!); return status; } catch (error) { return this.gitFallback(error); } }); } private async list(): Promise { const active = await this.tryActiveState(); if (active) return active.revisions; await this.bootstrap(); return (await this.activeState()).revisions; } private async activate(commit: string): Promise { await this.materialize(commit); await this.publishMaterialized(commit); } /** Test-only migration seam; production callers use publishAddressed. */ private async publish(request: PublishWorkspaceRequest): Promise { return this.publishWorkspace(request); } private async publishWorkspace(request: PublishWorkspaceRequest): Promise { await this.repository.ensureLayout(); return this.lock.run(() => this.publishWorkspaceLocked(request)); } private async publishWorkspaceLocked(request: PublishWorkspaceRequest): Promise { const status = await this.repository.pull(); await this.materialize(status.head!); await this.publishMaterialized(status.head!); const current = await this.activeState(); const id = request.action === "delete" ? request.id : request.workspace.workspace.id; const existing = current.revisions.find(revision => revision.id === id); const local = request.action === "delete" ? undefined : request.workspace; if (request.baseCommit !== status.head || (request.action !== "create" && existing?.blob !== request.baseBlob)) { if (request.action !== "create" && request.baseCommit !== status.head && existing?.blob === request.baseBlob) throw new WorkspaceRegistryError("workspace_stale", "Workspace revision is stale"); throw await this.conflictFor(request, status.head!, existing, local); } if (request.action === "create" && existing) throw await this.conflictFor(request, status.head!, existing, local); if (request.action !== "create" && !existing) throw await this.conflictFor(request, status.head!, existing, local); if (request.action !== "delete") await this.assertEvidenceContext(request.workspace, status.head!); const yamlPath = workspacePath(id); const docs = this.documentationPaths(id); if (request.action === "delete") { await this.repository.removeRegistryFile(yamlPath); await this.repository.removeRegistryFile(docs.contract); await this.repository.removeRegistryFile(docs.readme); } else { const source = serializeWorkspaceYaml(request.workspace); const rendered = renderWorkspaceDocs(request.workspace); await this.repository.writeRegistryFile(yamlPath, source); await this.repository.writeRegistryFile(docs.contract, rendered.envExample); await this.repository.writeRegistryFile(docs.readme, rendered.markdown); } const next = await this.repository.commitAndPush([yamlPath, docs.contract, docs.readme], request.action === "delete" ? `Delete workspace ${id}` : `Publish workspace ${id}`); await this.materialize(next.head!); await this.publishMaterialized(next.head!); return (await this.activeState()).revisions.find(revision => revision.id === id); } async ensureBootstrapAddressed(identity: RegistryBootstrapRecoveryIdentityV1): Promise { await this.repository.ensureLayout(); return this.lock.run(async () => this.ensureBootstrapAddressedLocked(identity)); } private async ensureBootstrapAddressedLocked(identity: RegistryBootstrapRecoveryIdentityV1): Promise { let active: ActiveState | undefined; try { active = await this.tryActiveState(); } catch { throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); } if (active) return { kind: "already_active", snapshot: this.addressedSnapshot(active, identity.requestSha256) }; const store = new RegistryAddressedPublicationStore(this.repository.root); let jobs: readonly RegistryAddressedPublicationStateV1[]; try { jobs = await store.scan(); } catch { throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); } const nonterminal = jobs.filter(job => job.phase !== "terminal_durable"); const matching = nonterminal.filter(job => job.operation === identity.operation && job.requestSha256 === identity.requestSha256 && job.installationIdentitySha256 === identity.installationIdentitySha256 && job.repositoryIdentitySha256 === identity.repositoryIdentitySha256 && job.remoteRefIdentitySha256 === identity.remoteRefIdentitySha256); if (nonterminal.length > 1 || (nonterminal.length === 1 && matching.length !== 1)) throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); let state = matching[0]; if (!state) { const request: RegistryAddressedRequestV1 = { mode: "create", operation: "registry_bootstrap", runId: addressedRunId(), requestSha256: identity.requestSha256, installationIdentitySha256: identity.installationIdentitySha256, repositoryIdentitySha256: identity.repositoryIdentitySha256, expectedBaseCommit: null, remoteRefIdentitySha256: identity.remoteRefIdentitySha256, }; state = await store.claim(request, request.runId); } const result = await this.executeAddressedLocked(store, state, identity); const published = await this.activeState(); return { kind: "bootstrap_terminal", result: result as Extract, snapshot: this.addressedSnapshot(published, identity.requestSha256) }; } async publishAddressed(request: RegistryAddressedRequestV1): Promise { await this.repository.ensureLayout(); return this.lock.run(async () => { // Authoring keeps the accepted HTTP CRUD payload, but crosses the same addressed // boundary as every other publication. The mutation is deliberately handled while // repository.lock is held; callers never receive the historical publish API. if ("mutation" in request && request.mutation) return this.publishWorkspaceAddressed(request as Extract & { readonly mutation: PublishWorkspaceRequest }); const store = new RegistryAddressedPublicationStore(this.repository.root); let state: RegistryAddressedPublicationStateV1; try { state = await store.read(request.runId); } catch { state = await store.claim(request, request.runId); if (request.operation === "registry_pull" && request.mode === "create") { const base = await this.snapshotState(request.expectedBaseCommit); const baseWorkspaces = base.revisions.map(revision => this.manifestIdentity(revision)); state = await store.setBase(state.runId, registryDigest(base) as never, baseWorkspaces); } } const identity: RegistryBootstrapRecoveryIdentityV1 = { operation: "registry_bootstrap", requestSha256: request.requestSha256, installationIdentitySha256: request.installationIdentitySha256, repositoryIdentitySha256: request.repositoryIdentitySha256, remoteRefIdentitySha256: request.remoteRefIdentitySha256, }; if (state.operation !== request.operation || state.requestSha256 !== request.requestSha256 || state.installationIdentitySha256 !== request.installationIdentitySha256 || state.repositoryIdentitySha256 !== request.repositoryIdentitySha256 || state.remoteRefIdentitySha256 !== request.remoteRefIdentitySha256) throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Addressed request identity does not match the durable job"); return this.executeAddressedLocked(store, state, identity); }); } private async publishWorkspaceAddressed(request: Extract & { readonly mutation: PublishWorkspaceRequest }): Promise { const mutation = request.mutation; await this.publishWorkspaceLocked(mutation); const targetState = await this.activeState(); const targetWorkspaces = targetState.revisions.map(revision => this.manifestIdentity(revision)); const base = request.expectedBaseCommit ? await this.snapshotState(request.expectedBaseCommit) : undefined; const baseWorkspaces = base?.revisions.map(revision => this.manifestIdentity(revision)) ?? []; const baseIds = new Set(baseWorkspaces.map(item => item.workspaceId)); const targetIds = new Set(targetWorkspaces.map(item => item.workspaceId)); const changedWorkspaceIds = [...new Set([...baseIds, ...targetIds])].filter(id => !baseIds.has(id) || !targetIds.has(id) || registryDigest(baseWorkspaces.find(item => item.workspaceId === id)) !== registryDigest(targetWorkspaces.find(item => item.workspaceId === id)), ).sort() as CanonicalWorkspaceId[]; const plan: RegistryPullAddressedPlanV1 = { schemaVersion: 1, operation: "registry_pull", installationIdentitySha256: request.installationIdentitySha256, repositoryIdentitySha256: request.repositoryIdentitySha256, remoteRefIdentitySha256: request.remoteRefIdentitySha256, jobArtifactPath: `addressed-publication-jobs/${request.runId}.json`, advertisedTargetCommit: targetState.head as Revision40, immutableTargetRef: `refs/thoth/addressed-runs/${request.runId}/target`, fetchedTargetCommit: targetState.head as Revision40, targetCommit: targetState.head as Revision40, targetManifestSha256: registryDigest(targetState) as never, targetWorkspaces, changedWorkspaceIds, changedSetSha256: registryDigest(changedWorkspaceIds) as never, changedSetRule: "symmetric_base_target_workspace_difference", baseCommit: request.expectedBaseCommit ?? targetState.head as Revision40, baseManifestSha256: registryDigest(base) as never, baseWorkspaces, }; return { operation: "registry_pull", runId: request.runId, jobArtifactPath: plan.jobArtifactPath, plan, planSha256: registryDigest(plan) as never, phase: "terminal_durable", publication: "target" }; } private async executeAddressedLocked(store: RegistryAddressedPublicationStore, initial: RegistryAddressedPublicationStateV1, identity: RegistryBootstrapRecoveryIdentityV1): Promise { let state = initial; if (state.phase === "terminal_durable") return store.readTerminalResult(state.runId, state.terminalResultSha256!); const operation = state.operation; let target = state.advertisedTargetCommit ?? state.fetchedTargetCommit; if (state.phase === "request_claimed") { const status = operation === "registry_bootstrap" ? await this.repository.bootstrap() : await this.repository.pull(); target = safeCommit(status.head ?? "") as Revision40; state = await store.transition(state.runId, "target_advertised", { advertisedTargetCommit: target, immutableTargetRef: `refs/thoth/addressed-runs/${state.runId}/target` }); } else if (!target) throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); if (state.phase === "target_advertised") { const current = await this.repository.runRef(state.runId); if (current !== undefined && current !== target) throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); if (current === undefined) { await this.repository.fetchExact(target!); await this.repository.ensureRunRef(state.runId, target!); } state = await store.transition(state.runId, "target_fetched", { fetchedTargetCommit: target }); } target = state.fetchedTargetCommit ?? target; if (!target) throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); if (state.phase === "target_fetched") { await this.materialize(target); const targetState = await this.snapshotState(target); const base = operation === "registry_pull" ? await this.baseState(state.baseCommit) : undefined; const targetWorkspaces = targetState.revisions.map(revision => this.manifestIdentity(revision)); const baseWorkspaces = base?.revisions.map(revision => this.manifestIdentity(revision)) ?? []; const baseIds = new Set(baseWorkspaces.map(workspace => workspace.workspaceId)); const targetIds = new Set(targetWorkspaces.map(workspace => workspace.workspaceId)); const changedWorkspaceIds = [...new Set([...baseIds, ...targetIds])].filter(id => !baseIds.has(id) || !targetIds.has(id) || registryDigest(baseWorkspaces.find(x => x.workspaceId === id)) !== registryDigest(targetWorkspaces.find(x => x.workspaceId === id))).sort() as CanonicalWorkspaceId[]; const plan = operation === "registry_bootstrap" ? { schemaVersion: 1, operation, installationIdentitySha256: identity.installationIdentitySha256, repositoryIdentitySha256: identity.repositoryIdentitySha256, remoteRefIdentitySha256: identity.remoteRefIdentitySha256, jobArtifactPath: state.jobArtifactPath, advertisedTargetCommit: state.advertisedTargetCommit!, immutableTargetRef: state.immutableTargetRef!, fetchedTargetCommit: target as Revision40, targetCommit: target as Revision40, targetManifestSha256: registryDigest(targetState) as never, targetWorkspaces, changedWorkspaceIds: targetWorkspaces.map(x => x.workspaceId).sort() as CanonicalWorkspaceId[], changedSetSha256: registryDigest(targetWorkspaces.map(x => x.workspaceId).sort()) as never, changedSetRule: "all_target_workspace_ids" as const, baseCommit: null, baseManifestSha256: null, baseWorkspaces: [] as const, } : { schemaVersion: 1, operation, installationIdentitySha256: identity.installationIdentitySha256, repositoryIdentitySha256: identity.repositoryIdentitySha256, remoteRefIdentitySha256: identity.remoteRefIdentitySha256, jobArtifactPath: state.jobArtifactPath, advertisedTargetCommit: state.advertisedTargetCommit!, immutableTargetRef: state.immutableTargetRef!, fetchedTargetCommit: target as Revision40, targetCommit: target as Revision40, targetManifestSha256: registryDigest(targetState), targetWorkspaces, changedWorkspaceIds, changedSetSha256: registryDigest(changedWorkspaceIds) as never, changedSetRule: "symmetric_base_target_workspace_difference" as const, baseCommit: state.baseCommit!, baseManifestSha256: registryDigest(base) as never, baseWorkspaces, }; state = await store.transition(state.runId, "planned", { targetCommit: target as Revision40, targetManifestSha256: plan.targetManifestSha256 as never, targetWorkspaces: plan.targetWorkspaces, changedWorkspaceIds: plan.changedWorkspaceIds, changedSetSha256: plan.changedSetSha256 as never, planSha256: registryDigest(plan) as never, changedSetRule: plan.changedSetRule }); } const plan = await this.planForState(state); const leases = []; for (const id of plan.changedWorkspaceIds) leases.push(await this.rootLeaseFactory.acquireOrProvision(this.rootLeaseFactory.canonicalInput(id))); const result = await runUnderOrderedWorkspaceWriterLocks(leases, capabilities => this.lifecycleOwner.run({ plan, capabilities, participants: this.participants, synchronizers: this.synchronizers, action: async () => { if (state.phase === "planned") { state = await store.transition(state.runId, "participants_prepared", { participantsSha256: registryDigest(this.participants.map(participant => participant.participantId)) as never, synchronizersSha256: registryDigest(this.synchronizers.map(synchronizer => synchronizer.synchronizerId)) as never }); } if (state.phase === "participants_prepared") { state = await store.transition(state.runId, "publication_intent_durable", { publicationIntentSha256: registryDigest({ runId: state.runId, planSha256: state.planSha256 }) as never }); } if (state.phase === "publication_intent_durable") { await this.publishMaterialized(target!); state = await store.transition(state.runId, "target_published", { publishedActiveStateSha256: registryDigest(await this.activeState()) as never }); } return undefined; }})); const output = { operation: state.operation, runId: state.runId, jobArtifactPath: state.jobArtifactPath, plan, planSha256: registryDigest(plan), phase: "terminal_durable" as const, publication: "target" as const } as RegistryAddressedResultV1; await store.storeTerminalResult(state.runId, output); state = await store.transition(state.runId, "terminal_durable", { terminalResultSha256: registryDigest(output) as never }); return output; } private async planForState(state: RegistryAddressedPublicationStateV1): Promise { if (!state.targetCommit || !state.targetWorkspaces || !state.changedWorkspaceIds || !state.planSha256 || !state.changedSetSha256 || !state.targetManifestSha256 || !state.advertisedTargetCommit || !state.immutableTargetRef || !state.fetchedTargetCommit) throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); const common = { schemaVersion: 1 as const, installationIdentitySha256: state.installationIdentitySha256, repositoryIdentitySha256: state.repositoryIdentitySha256, remoteRefIdentitySha256: state.remoteRefIdentitySha256, jobArtifactPath: state.jobArtifactPath, advertisedTargetCommit: state.advertisedTargetCommit, immutableTargetRef: state.immutableTargetRef, fetchedTargetCommit: state.fetchedTargetCommit, targetCommit: state.targetCommit, targetManifestSha256: state.targetManifestSha256, targetWorkspaces: state.targetWorkspaces, changedWorkspaceIds: state.changedWorkspaceIds, changedSetSha256: state.changedSetSha256 }; const plan: RegistryAddressedPlanV1 = state.operation === "registry_bootstrap" ? { ...common, operation: "registry_bootstrap", changedSetRule: "all_target_workspace_ids", baseCommit: null, baseManifestSha256: null, baseWorkspaces: [] } : { ...common, operation: "registry_pull", changedSetRule: "symmetric_base_target_workspace_difference", baseCommit: state.baseCommit!, baseManifestSha256: state.baseManifestSha256!, baseWorkspaces: state.baseWorkspaces }; if (registryDigest(plan) !== state.planSha256) throw new WorkspaceRegistryError("registry_bootstrap_recovery_conflict", "Bootstrap recovery is ambiguous or corrupt"); return plan; } private addressedSnapshot(state: ActiveState, requestDigest: string): RegistryActiveSnapshotV1 { const ids = state.revisions.map(revision => revision.id).sort(); return { schemaVersion: 1, commit: state.head as RegistryActiveSnapshotV1["commit"], manifestSha256: registryDigest(state) as RegistryActiveSnapshotV1["manifestSha256"], workspaces: ids.map(id => { const revision = state.revisions.find(candidate => candidate.id === id)!; return this.manifestIdentity(revision); }) }; } async listRetainedSnapshots(): Promise { await this.repository.ensureLayout(); return await this.lock.run(async () => { try { const active = await this.activeState(); const revisions = [...active.revisions]; const entries = await readdir(this.repository.snapshotsPath, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory() || entry.isSymbolicLink() || !/^[0-9a-f]{40}$/.test(entry.name)) continue; if (entry.name === active.head) continue; const state = await this.snapshotState(entry.name); revisions.push(...state.revisions); } return revisions; } catch (error) { throw workspaceError(error); } }); } async read(id: string): Promise<{ workspace: WorkspaceDescriptor; revision: WorkspaceRevision }> { const state = await this.activeState(); const revision = state.revisions.find((candidate) => candidate.id === id); if (!revision) throw new WorkspaceRegistryError("workspace_invalid", "Workspace is unavailable"); try { const source = await readFile(revision.snapshotPath, "utf8"); return { workspace: parseWorkspaceYaml(source), revision }; } catch (error) { throw workspaceError(error); } } /** * Resolve the active revision and create its cross-process retention lease under the same * repository lock. The lease bridges the interval before `session_manifest.yaml` is durable. */ async acquireSessionRevision(id: string): Promise { await this.repository.ensureLayout(); return await this.lock.run(async () => { const state = await this.activeState(); const revision = state.revisions.find((candidate) => candidate.id === id); if (!revision) throw new WorkspaceRegistryError("workspace_invalid", "Workspace is unavailable"); let workspace: WorkspaceDescriptor; try { workspace = validateOperationalWorkspace( parseWorkspaceYaml(await readFile(revision.snapshotPath, "utf8")), ); } catch (error) { throw workspaceError(error); } const token = randomUUID(); const record: RevisionLeaseRecord = { version: 1, token, workspaceId: id, commit: revision.commit, state: "creating", }; const path = await this.writeRevisionLease(record, true); let localState: RevisionLeaseRecord["state"] | "aborted" = "creating"; return { workspace, revision, markPersisted: async () => { if (localState === "persisted") return; if (localState === "aborted") throw new WorkspaceRegistryError( "workspace_invalid", "Workspace revision lease is unavailable", ); await this.lock.run(async () => { await this.replaceRevisionLease(path, { ...record, state: "persisted" }); }); localState = "persisted"; }, abort: async () => { if (localState !== "creating") return; await this.lock.run(async () => { await rm(path, { force: true }); }); localState = "aborted"; }, }; }); } /** Read a retained immutable snapshot for a session pinned to a historical commit. */ async readPinned(id: string, commit: string): Promise<{ workspace: WorkspaceDescriptor; workspaceConfigPath: string }> { const snapshotPath = this.snapshotPath(safeCommit(commit), id); try { const source = await readFile(snapshotPath, "utf8"); return { workspace: validateOperationalWorkspace(parseWorkspaceYaml(source)), workspaceConfigPath: snapshotPath, }; } catch (error) { throw workspaceError(error); } } /** * Garbage-collect obsolete immutable snapshots without breaking cold Resume. * Callers must supply revisions collected from an administrator-visible complete session list; * a partial, per-user list could otherwise remove another user's resumable workspace pin. */ async reconcileSnapshotRetention(referencedCommits: readonly string[]): Promise { const manifestReferences = new Set(referencedCommits.map(safeCommit)); const retained = new Set(manifestReferences); await this.repository.ensureLayout(); await this.lock.run(async () => { const leases = await this.revisionLeases(); for (const { record } of leases) retained.add(record.commit); retained.add((await this.activeState()).head); const entries = await readdir(this.repository.snapshotsPath, { withFileTypes: true }); for (const entry of entries) { // Leave staging and unexpected entries untouched: this cleanup only owns finalized, // commit-addressed snapshot directories. if (!entry.isDirectory() || entry.isSymbolicLink() || !/^[0-9a-f]{40}$/.test(entry.name)) continue; if (retained.has(entry.name)) continue; const path = join(this.repository.snapshotsPath, entry.name); const current = lstatSync(path); if (!current.isDirectory() || current.isSymbolicLink()) continue; await rm(path, { recursive: true, force: true }); } // A persisted lease is handed off only when this exact authoritative scan has observed a // manifest pin for its commit. A stale scan therefore keeps the lease and cannot prune it. for (const { path, record } of leases) { if (record.state === "persisted" && manifestReferences.has(record.commit)) { await rm(path, { force: true }); } } }); } private revisionLeaseDirectory(): string { return join(this.repository.statePath, "revision-leases"); } private async writeRevisionLease(record: RevisionLeaseRecord, exclusive: boolean): Promise { const directory = this.revisionLeaseDirectory(); await mkdir(directory, { recursive: true, mode: 0o700 }); const path = join(directory, `${record.token}.json`); await writeFile(path, JSON.stringify(record), { encoding: "utf8", mode: 0o600, flush: true, ...(exclusive ? { flag: "wx" } : {}), }); return path; } private async replaceRevisionLease(path: string, record: RevisionLeaseRecord): Promise { const staging = `${path}.staging-${randomUUID()}`; try { await writeFile(staging, JSON.stringify(record), { encoding: "utf8", mode: 0o600, flag: "wx", flush: true, }); await rename(staging, path); } catch (error) { await rm(staging, { force: true }); throw error; } } private async revisionLeases(): Promise> { const directory = this.revisionLeaseDirectory(); await mkdir(directory, { recursive: true, mode: 0o700 }); const entries = await readdir(directory, { withFileTypes: true }); const leases: Array<{ path: string; record: RevisionLeaseRecord }> = []; for (const entry of entries) { if (!entry.isFile() || entry.isSymbolicLink() || !/^[0-9a-f-]{36}\.json$/.test(entry.name)) { throw new WorkspaceRegistryError("workspace_invalid", "Workspace revision lease is invalid"); } const path = join(directory, entry.name); let record: RevisionLeaseRecord; try { record = JSON.parse(await readFile(path, "utf8")) as RevisionLeaseRecord; } catch { throw new WorkspaceRegistryError("workspace_invalid", "Workspace revision lease is invalid"); } if ( record.version !== 1 || `${record.token}.json` !== entry.name || !/^[0-9a-f-]{36}$/.test(record.token) || !/^[a-z][a-z0-9-]{2,62}$/.test(record.workspaceId) || !/^[0-9a-f]{40}$/.test(record.commit) || (record.state !== "creating" && record.state !== "persisted") ) { throw new WorkspaceRegistryError("workspace_invalid", "Workspace revision lease is invalid"); } leases.push({ path, record }); } return leases; } /** * Publish canonical YAML and derived public documentation as one optimistic Git revision. * The browser never provides paths or generated artifacts; those are derived server-side. */ private documentationPaths(id: string): { contract: string; readme: string } { workspacePath(id); const directory = `workspace-docs/${id}`; return { contract: `${directory}/contract.env.example`, readme: `${directory}/README.md` }; } private async conflictFor( request: PublishWorkspaceRequest, currentCommit: string, existing: WorkspaceRevision | undefined, local: CanonicalWorkspace | undefined, ): Promise { const id = request.action === "delete" ? request.id : request.workspace.workspace.id; const base = await this.readSnapshotCanonical(request.baseCommit, id); let remote: CanonicalWorkspace | undefined; if (existing) { remote = (await this.read(id)).workspace; } return new WorkspaceConflictError( this.changedFields(base, remote), { commit: request.baseCommit, ...(request.action === "create" ? {} : { blob: request.baseBlob }) }, { commit: currentCommit, ...(existing ? { blob: existing.blob } : {}) }, base, local, remote, ); } private async readSnapshotCanonical(commit: string, id: string): Promise { try { const source = await readFile(this.snapshotPath(commit, id), "utf8"); return parseWorkspaceYaml(source); } catch { return undefined; } } private changedFields( base: unknown, remote: unknown, prefix = "", ): string[] { if (base === undefined || remote === undefined) { return base === remote ? [] : [prefix || "workspace.id"]; } if (Array.isArray(base) || Array.isArray(remote) || typeof base !== "object" || typeof remote !== "object") { return JSON.stringify(base) === JSON.stringify(remote) ? [] : [prefix]; } const baseObject = base as Record; const remoteObject = remote as Record; const keys = new Set([...Object.keys(baseObject), ...Object.keys(remoteObject)]); return [...keys].flatMap((key) => this.changedFields( baseObject[key], remoteObject[key], prefix ? `${prefix}.${key}` : key, )); } private async assertEvidenceContext(workspace: WorkspaceDescriptor, revision: string): Promise { if (workspace.evidence?.source.type !== "filesystem") return; // P6 owns recursive containment. Here we deliberately validate only the declared root object. await this.repository.assertTreeAtRevision(revision, workspace.evidence.source.uri); } private async assertSnapshotEvidenceContexts(state: ActiveState): Promise { for (const revision of state.revisions) { const workspace = parseWorkspaceYaml(await readFile(revision.snapshotPath, "utf8")); await this.assertEvidenceContext(workspace, revision.commit); } } private async materialize(commit: string): Promise { const safeHead = safeCommit(commit); const files = await this.repository.workspacePathsAt(safeHead); const snapshots: Array<{ id: string; source: string; workspace: WorkspaceDescriptor; blob: string; }> = []; const collectionOwners = new Map(); try { for (const path of files) { const id = path.slice("workspaces/".length, -".yaml".length); const source = await this.repository.readWorkspaceAt(safeHead, path); const workspace = parseWorkspaceYaml(source); if (workspace.workspace.id !== id) { throw new WorkspaceRegistryError("workspace_invalid", "Workspace ID does not match its repository path"); } await this.assertEvidenceContext(workspace, safeHead); const collection = workspace.semantic_index.vector_store.collection; const owner = collectionOwners.get(collection); if (owner !== undefined) { throw new Error(`duplicate qdrant collection ownership: ${collection} (${owner}, ${id})`); } collectionOwners.set(collection, id); buildInstallationContract(workspace); renderWorkspaceDocs(workspace); snapshots.push({ id, source: serializeWorkspaceYaml(workspace), workspace, blob: await this.repository.blobAt(safeHead, path), }); } } catch (error) { throw workspaceError(error); } const snapshotDirectory = join(this.repository.snapshotsPath, safeHead); const revisions = snapshots.map((snapshot) => ({ id: snapshot.id, commit: safeHead, blob: snapshot.blob, snapshotPath: this.snapshotPath(safeHead, snapshot.id), })); if (this.pathExists(snapshotDirectory)) { await this.assertSnapshotIntegrity({ head: safeHead, revisions }); } else { const staging = join(this.repository.snapshotsPath, `.staging-${randomUUID()}`); await mkdir(staging, { mode: 0o700 }); try { const files: Record = {}; for (const snapshot of snapshots) { const yamlName = `${snapshot.id}.yaml`; const envName = `${snapshot.id}.env.example`; const docsName = `${snapshot.id}.md`; await writeFile(join(staging, yamlName), snapshot.source, { encoding: "utf8", mode: 0o400 }); files[yamlName] = digest(snapshot.source); const docs = renderWorkspaceDocs(snapshot.workspace); await writeFile(join(staging, envName), docs.envExample, { encoding: "utf8", mode: 0o400 }); await writeFile(join(staging, docsName), docs.markdown, { encoding: "utf8", mode: 0o400 }); files[envName] = digest(docs.envExample); files[docsName] = digest(docs.markdown); } await writeFile(join(staging, "snapshot.json"), JSON.stringify({ head: safeHead, revisions, files }), { encoding: "utf8", mode: 0o400, }); await rename(staging, snapshotDirectory); } catch (error) { await rm(staging, { recursive: true, force: true }); throw error; } } } private async publishMaterialized(commit: string): Promise { const state = await this.snapshotState(commit); await this.writeActiveState({ head: state.head, revisions: state.revisions }); } private async baseState(commit: Revision40 | null): Promise { return commit ? this.snapshotState(commit) : undefined; } private manifestIdentity(revision: WorkspaceRevision): RegistryWorkspaceManifestIdentityV1 { return { workspaceId: revision.id as RegistryWorkspaceManifestIdentityV1["workspaceId"], revision: revision.commit as RegistryWorkspaceManifestIdentityV1["revision"], descriptorBlob: revision.blob as RegistryWorkspaceManifestIdentityV1["descriptorBlob"], manifestSha256: registryDigest(revision) as RegistryWorkspaceManifestIdentityV1["manifestSha256"] }; } private async gitFallback(error: unknown): Promise { const safeError = workspaceError(error); if (safeError.code !== "git_unavailable" && safeError.code !== "git_auth_failed") throw safeError; const active = await this.tryActiveState(); if (!active) throw safeError; return { branch: this.repository.config.branch, head: active.head, ahead: 0, behind: 0, degraded: true, lastError: safeError.code, }; } private async activeState(): Promise { const active = await this.tryActiveState(); if (!active) throw new WorkspaceRegistryError("workspace_invalid", "No active workspace snapshot is available"); return active; } private async tryActiveState(): Promise { const file = join(this.repository.statePath, "active.json"); try { const state = this.decodeActiveState(JSON.parse(await readFile(file, "utf8"))); await this.assertSnapshotIntegrity(state); return state; } catch (error) { if (this.pathIsMissing(file)) return undefined; if (error instanceof WorkspaceRegistryError) throw error; throw new WorkspaceRegistryError("workspace_invalid", "Workspace active snapshot is invalid"); } } private async writeActiveState(state: ActiveState): Promise { const target = join(this.repository.statePath, "active.json"); const staging = join(this.repository.statePath, `.active-${randomUUID()}.json`); await writeFile(staging, JSON.stringify(state), { encoding: "utf8", mode: 0o600 }); const handle = await openFile(staging, "r"); try { await handle.sync(); } finally { await handle.close(); } await rename(staging, target); const parent = await openFile(this.repository.statePath, "r"); try { await parent.sync(); } finally { await parent.close(); } } private decodeActiveState(value: unknown): ActiveState { const state = this.strictObject(value, ["head", "revisions"]); return this.decodeStateRevisions(state.head, state.revisions); } private decodeSnapshotManifest(value: unknown): SnapshotManifest { const manifest = this.strictObject(value, ["head", "revisions", "files"]); const state = this.decodeStateRevisions(manifest.head, manifest.revisions); if (!manifest.files || typeof manifest.files !== "object" || Array.isArray(manifest.files)) { throw new Error("bad manifest files"); } const entries = Object.entries(manifest.files as Record); if (entries.some(([, contentsDigest]) => typeof contentsDigest !== "string")) { throw new Error("bad manifest files"); } return { ...state, files: Object.fromEntries(entries) as Record }; } private decodeStateRevisions(headValue: unknown, revisionsValue: unknown): ActiveState { if (typeof headValue !== "string" || !Array.isArray(revisionsValue)) throw new Error("bad state"); const head = safeCommit(headValue); const ids = new Set(); const revisions = revisionsValue.map((value) => { const revision = this.decodeRevision(value, head); if (ids.has(revision.id)) throw new Error("duplicate revision"); ids.add(revision.id); return revision; }); return { head, revisions }; } private decodeRevision(value: unknown, head: string): WorkspaceRevision { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("bad revision"); const revision = value as Record; const keys = Object.keys(revision); const required = ["id", "commit", "blob", "snapshotPath"]; const hasHistoricalState = Object.prototype.hasOwnProperty.call(revision, "state"); if ( keys.length !== required.length + (hasHistoricalState ? 1 : 0) || !required.every((key) => Object.prototype.hasOwnProperty.call(revision, key)) || (hasHistoricalState && revision.state !== "operational") ) { throw new Error("bad revision"); } if ( typeof revision.id !== "string" || typeof revision.commit !== "string" || typeof revision.blob !== "string" || typeof revision.snapshotPath !== "string" ) { throw new Error("bad revision"); } const id = revision.id; const commit = safeCommit(revision.commit); const blob = safeBlob(revision.blob); const snapshotPath = revision.snapshotPath; workspacePath(id); if ( commit !== head || !isAbsolute(snapshotPath) || snapshotPath !== this.snapshotPath(commit, id) ) { throw new Error("bad revision"); } // Always reconstruct a fresh public revision. The sole accepted historical state field is // compatibility input and must never cross the registry boundary. return { id, commit, blob, snapshotPath }; } private strictObject(value: unknown, expectedKeys: readonly string[]): Record { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("bad state"); const record = value as Record; const keys = Object.keys(record); if ( keys.length !== expectedKeys.length || !expectedKeys.every((key) => Object.prototype.hasOwnProperty.call(record, key)) ) { throw new Error("bad state"); } return record; } private async readSnapshotManifest(head: string): Promise { const path = join(this.repository.snapshotsPath, head, "snapshot.json"); return JSON.parse(await readFile(path, "utf8")); } private async snapshotState(head: string): Promise { const state = this.decodeSnapshotManifest(await this.readSnapshotManifest(safeCommit(head))); await this.assertSnapshotIntegrity(state); return state; } private async assertSnapshotIntegrity(state: ActiveState): Promise { const directory = join(this.repository.snapshotsPath, state.head); try { const manifest = this.decodeSnapshotManifest(await this.readSnapshotManifest(state.head)); if (manifest.head !== state.head || !this.sameRevisions(manifest.revisions, state.revisions)) { throw new Error("manifest revisions do not match active state"); } await this.assertManifestFiles(directory, manifest.files, this.expectedSnapshotFiles(state)); await this.assertSnapshotEvidenceContexts(state); } catch (error) { if (error instanceof WorkspaceRegistryError) throw error; throw new WorkspaceRegistryError("workspace_invalid", "Workspace snapshot integrity check failed"); } } private expectedSnapshotFiles(state: ActiveState): string[] { // P1 snapshots only descriptors and derived public docs. P6 owns revision-pinned // workspace-content materialization and its recursive containment checks. return state.revisions.flatMap((revision) => [ `${revision.id}.yaml`, `${revision.id}.env.example`, `${revision.id}.md`, ]); } private async assertManifestFiles( directory: string, files: Record, expected: string[], ): Promise { if (!files || typeof files !== "object" || Object.keys(files).length !== expected.length || !expected.every((name) => ( /^[0-9a-f]{64}$/.test(files[name] ?? "") ))) throw new Error("manifest files are invalid"); for (const name of expected) { const path = join(directory, name); const entry = lstatSync(path); if (!entry.isFile() || entry.isSymbolicLink()) throw new Error("snapshot file is invalid"); const contents = await readFile(path); if (digest(contents) !== files[name]) throw new Error("snapshot file does not match manifest"); if (name.endsWith(".yaml")) { const workspace = parseWorkspaceYaml(contents.toString("utf8")); if (workspace.workspace.id !== name.slice(0, -".yaml".length)) { throw new Error("snapshot workspace is invalid"); } } } } private sameRevisions(left: WorkspaceRevision[], right: WorkspaceRevision[]): boolean { return left.length === right.length && left.every((revision, index) => { const candidate = right[index]; return candidate !== undefined && candidate.id === revision.id && candidate.commit === revision.commit && candidate.blob === revision.blob && candidate.snapshotPath === revision.snapshotPath; }); } private pathExists(path: string): boolean { try { const entry = lstatSync(path); if (!entry.isDirectory() || entry.isSymbolicLink()) { throw new WorkspaceRegistryError("workspace_invalid", "Workspace snapshot path is invalid"); } return true; } catch (error) { if (error instanceof WorkspaceRegistryError) throw error; return false; } } private pathIsMissing(path: string): boolean { try { lstatSync(path); return false; } catch { return true; } } }