feat: bounded Evidence materializer with manifest and atomic publication (P6)

This commit is contained in:
2026-08-13 12:31:12 +02:00
parent 0c1033889a
commit 0c9e614100
3 changed files with 315 additions and 0 deletions
+26
View File
@@ -332,6 +332,32 @@ export class GitWorkspaceRepository {
return this.gitBlobBytes(objectId, maxBytes, "Evidence");
}
/** Return the 40-hex tree id of a canonical Evidence root at an exact commit. */
async evidenceTreeId(revision: string, id: string): Promise<string> {
if (!/^[0-9a-f]{40}$/.test(revision) || !/^[a-z][a-z0-9-]{2,62}$/.test(id)) {
throw new WorkspaceRegistryError("workspace_invalid", "Workspace Evidence revision is invalid");
}
const objectId = (await this.git(["rev-parse", `${revision}:${id}/evidence`])).trim();
const type = (await this.git(["cat-file", "-t", objectId])).trim();
if (!/^[0-9a-f]{40}$/.test(objectId) || type !== "tree") {
throw new WorkspaceRegistryError("workspace_invalid", "Workspace Evidence root is invalid");
}
return objectId;
}
/** Return the byte size of one Git object without reading its contents. */
async gitObjectSize(objectId: string): Promise<number> {
if (!/^[0-9a-f]{40}$/.test(objectId)) {
throw new WorkspaceRegistryError("workspace_invalid", "Workspace Evidence object is invalid");
}
const raw = (await this.git(["cat-file", "-s", objectId])).trim();
const size = Number(raw);
if (!Number.isSafeInteger(size) || size < 0) {
throw new WorkspaceRegistryError("workspace_invalid", "Workspace Evidence object size is invalid");
}
return size;
}
private async gitBlobBytes(objectId: string, maxBytes: number, label: string): Promise<Buffer> {
if (!/^[0-9a-f]{40}$/.test(objectId)) {
throw new WorkspaceRegistryError("workspace_invalid", `Workspace ${label} object is invalid`);