test: verify portable workspace registry end to end

This commit is contained in:
2026-08-04 08:42:33 +02:00
parent 3d5d26c20c
commit 8b046f9fb2
9 changed files with 143 additions and 2 deletions
+25 -1
View File
@@ -1,6 +1,6 @@
import { createHash, randomUUID } from "node:crypto";
import { lstatSync } from "node:fs";
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { isAbsolute, join } from "node:path";
import { buildInstallationContract, renderWorkspaceDocs } from "./contracts.js";
import {
@@ -164,6 +164,30 @@ export class WorkspaceRegistry {
}
}
/**
* 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<void> {
const retained = new Set(referencedCommits.map(safeCommit));
await this.repository.ensureLayout();
await this.lock.run(async () => {
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 });
}
});
}
/**
* 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.