refactor workspace registry addressed API

This commit is contained in:
2026-08-11 21:46:01 +02:00
parent 1cc0b50446
commit e2fecf9953
10 changed files with 582 additions and 432 deletions
+14 -17
View File
@@ -13,7 +13,7 @@ import { buildApp } from "../src/app.js";
import { loadConfig } from "../src/config.js";
import { createProductionWorkspaceDiagnoser } from "../src/workspaces/diagnostics.js";
import { WorkspaceRegistryError } from "../src/workspaces/git-repository.js";
import { WorkspaceRegistry, type WorkspaceRevision } from "../src/workspaces/registry.js";
import { createWorkspaceRegistry, workspaceRegistryRecoveryIdentity, type WorkspaceRevision } from "../src/workspaces/registry.js";
import {
parseWorkspaceYaml, renderWorkspaceDocs, serializeWorkspaceYaml, validateWorkspaceDescriptor,
type CanonicalWorkspace,
@@ -116,22 +116,17 @@ const revision: WorkspaceRevision = {
snapshotPath: "/registry/snapshots/psd-clinical.yaml",
};
type RegistryFake = Pick<WorkspaceRegistry, "read" | "recoveryIdentity"> & { ensureBootstrapAddressed: ReturnType<typeof vi.fn>; publishAddressed: ReturnType<typeof vi.fn>; snapshotPath: ReturnType<typeof vi.fn> };
type RegistryFake = Pick<WorkspaceRegistry, "read" | "readPinned"> & {
ensureBootstrapAddressed: ReturnType<typeof vi.fn>;
publishAddressed: ReturnType<typeof vi.fn>;
};
function registryFake(overrides: Partial<RegistryFake> = {}): RegistryFake {
return {
bootstrap: vi.fn(async () => ({
branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: false,
})),
pull: vi.fn(async () => ({
branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: false,
})),
list: vi.fn(async () => [revision]),
read: vi.fn(async () => ({ workspace, revision })),
recoveryIdentity: vi.fn(() => ({ operation: "registry_bootstrap", requestSha256: "c".repeat(64), installationIdentitySha256: "d".repeat(64), repositoryIdentitySha256: "e".repeat(64), remoteRefIdentitySha256: "f".repeat(64) })),
readPinned: vi.fn(async () => ({ workspace, workspaceConfigPath: revision.snapshotPath })),
ensureBootstrapAddressed: vi.fn(async () => ({ kind: "already_active", snapshot: { schemaVersion: 1, commit: revision.commit, manifestSha256: "a".repeat(64), workspaces: [{ workspaceId: revision.id, revision: revision.commit, descriptorBlob: revision.blob, manifestSha256: "b".repeat(64) }] } })),
publishAddressed: vi.fn(async () => ({ plan: { targetCommit: revision.commit, targetManifestSha256: "a".repeat(64), targetWorkspaces: [{ workspaceId: revision.id, revision: revision.commit, descriptorBlob: revision.blob, manifestSha256: "b".repeat(64) }] } })),
snapshotPath: vi.fn(() => revision.snapshotPath),
...overrides,
};
}
@@ -143,6 +138,8 @@ function appFor(registry: RegistryFake, diagnose = vi.fn(async () => ({ activata
}), {
thtRunner: {} as any,
workspaceRegistry: registry as WorkspaceRegistry,
workspaceRegistryRecoveryIdentity: () => ({ operation: "registry_bootstrap", requestSha256: "c".repeat(64), installationIdentitySha256: "d".repeat(64), repositoryIdentitySha256: "e".repeat(64), remoteRefIdentitySha256: "f".repeat(64) }),
workspaceRegistrySnapshotPath: () => revision.snapshotPath,
workspaceAuthorService: { publish: vi.fn(async () => ({ id: workspace.workspace.id, commit: revision.commit, blob: revision.blob })) } as any,
workspaceDiagnoser: diagnose,
} as any);
@@ -517,7 +514,7 @@ async function createRealRouteFixture(
THT_WORKSPACE_GIT_AUTHOR_NAME: "Workspace Route Publisher",
THT_WORKSPACE_GIT_AUTHOR_EMAIL: "workspace-route-publisher@example.invalid",
});
const registry = new WorkspaceRegistry(config.workspaceRegistry);
const registry = createWorkspaceRegistry(config.workspaceRegistry);
const app = buildApp(config, {
thtRunner: {} as any,
workspaceRegistry: registry,
@@ -665,7 +662,7 @@ test("real publish create/update, pull, list, and read preserve a complete Evide
test("real route reports a safe field for an Evidence-only concurrent edit", async () => {
const fixture = await createRealRouteFixture(httpEvidenceWorkspace);
await fixture.registry.ensureBootstrapAddressed(fixture.registry.recoveryIdentity());
await fixture.registry.ensureBootstrapAddressed(workspaceRegistryRecoveryIdentity(fixture.registry));
const base = await fixture.registry.read("psd-clinical");
const remote = withEvidence(
{ ...httpEvidenceWorkspace.evidence!.source },
@@ -701,7 +698,7 @@ test.each([
["cross-workspace", "workspace-content/research/evidence"],
])("real publish rejects %s filesystem Evidence paths without changing HEAD", async (_label, uri) => {
const fixture = await createRealRouteFixture();
await fixture.registry.ensureBootstrapAddressed(fixture.registry.recoveryIdentity());
await fixture.registry.ensureBootstrapAddressed(workspaceRegistryRecoveryIdentity(fixture.registry));
const base = await fixture.registry.read("psd-clinical");
const invalid = structuredClone(filesystemEvidenceWorkspace) as any;
invalid.evidence.source.uri = uri;
@@ -733,7 +730,7 @@ test.each([
},
])("real publish rejects $label without echoing it or changing HEAD", async ({ source }) => {
const fixture = await createRealRouteFixture();
await fixture.registry.ensureBootstrapAddressed(fixture.registry.recoveryIdentity());
await fixture.registry.ensureBootstrapAddressed(workspaceRegistryRecoveryIdentity(fixture.registry));
const base = await fixture.registry.read("psd-clinical");
const invalid = structuredClone(base.workspace) as any;
invalid.evidence = { source };
@@ -754,7 +751,7 @@ test.each([
test("real publish and pull fail safely when the contextual Evidence Git tree is missing", async () => {
const fixture = await createRealRouteFixture();
await fixture.registry.ensureBootstrapAddressed(fixture.registry.recoveryIdentity());
await fixture.registry.ensureBootstrapAddressed(workspaceRegistryRecoveryIdentity(fixture.registry));
const current = await fixture.registry.read("psd-clinical");
const missing = validateWorkspaceDescriptor({
...workspace,
@@ -792,7 +789,7 @@ test("real export and import preserve stable public Evidence artifacts without E
const secretDirectory = join(fixture.root, "fixture-secrets");
mkdirSync(secretDirectory);
writeFileSync(join(secretDirectory, "credential"), SECRET_CANARY);
await fixture.registry.ensureBootstrapAddressed(fixture.registry.recoveryIdentity());
await fixture.registry.ensureBootstrapAddressed(workspaceRegistryRecoveryIdentity(fixture.registry));
const firstResponse = await fixture.app.inject({ method: "GET", url: "/workspaces/psd-clinical/export" });
const secondResponse = await fixture.app.inject({ method: "GET", url: "/workspaces/psd-clinical/export" });