/*
storage repository — dossier repository over the storage adapter port.
- Do not access storage directly — the repository is the seam.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
*/
import type { CertificationDossierEventV1, DossierRootReferenceV1 } from "../contracts/dossier.ts";
import type { Sha256Digest } from "@warpgogol/werkstatt-shared/fingerprint";
import { computeDossierEventHash, computeDossierRoot } from "../dossier-hash.ts";
export interface DossierRepositoryV1 {
readonly candidateId: string;
readonly events: readonly CertificationDossierEventV1[];
readonly eventHashes: readonly Sha256Digest[];
readonly rootHash: Sha256Digest | null;
}
export interface DossierAppendInputV1 {
event: CertificationDossierEventV1;
}
export interface DossierAppendResultV1 {
ok: true;
repository: DossierRepositoryV1;
eventHash: Sha256Digest;
newRootHash: Sha256Digest;
}
export interface DossierAppendFailureV1 {
ok: false;
ruleId: "CERT-DOSSIER-01" | "CERT-DOSSIER-02" | "CERT-DOSSIER-03";
message: string;
}
export type DossierAppendOutcomeV1 = DossierAppendResultV1 | DossierAppendFailureV1;
export function createDossierRepository(candidateId: string): DossierRepositoryV1 {
return {
candidateId,
events: [],
eventHashes: [],
rootHash: null,
};
}
export function appendDossierEvent(
repo: DossierRepositoryV1,
input: DossierAppendInputV1,
): DossierAppendOutcomeV1 {
if (input.event.candidateId !== repo.candidateId) {
return {
ok: false,
ruleId: "CERT-DOSSIER-01",
message: `event candidateId "${input.event.candidateId}" does not match repository candidateId "${repo.candidateId}"`,
};
}
const expectedPrevHash =
repo.eventHashes.length === 0
? null
: repo.eventHashes[repo.eventHashes.length - 1];
if (input.event.previousEventHash !== expectedPrevHash) {
return {
ok: false,
ruleId: "CERT-DOSSIER-02",
message: `previousEventHash mismatch: expected ${expectedPrevHash}, got ${input.event.previousEventHash}`,
};
}
if (repo.events.length >= 100000) {
return {
ok: false,
ruleId: "CERT-DOSSIER-03",
message: "dossier event limit (100000) exceeded",
};
}
const hashResult = computeDossierEventHash(input.event);
if (!hashResult.ok) {
return {
ok: false,
ruleId: "CERT-DOSSIER-02",
message: `failed to compute event hash: ${hashResult.code} — ${hashResult.message}`,
};
}
const newEvents = [...repo.events, input.event];
const newEventHashes = [...repo.eventHashes, hashResult.eventHash];
const newRootHash = computeDossierRoot(repo.candidateId, newEventHashes);
return {
ok: true,
repository: {
candidateId: repo.candidateId,
events: newEvents,
eventHashes: newEventHashes,
rootHash: newRootHash,
},
eventHash: hashResult.eventHash,
newRootHash,
};
}
export function rebuildRootHash(repo: DossierRepositoryV1): Sha256Digest {
return computeDossierRoot(repo.candidateId, repo.eventHashes);
}
export function buildRootReference(
repo: DossierRepositoryV1,
): DossierRootReferenceV1 {
return {
schema: "werkstatt/dossier-root-reference@1",
rootHash: repo.rootHash ?? computeDossierRoot(repo.candidateId, []),
candidateId: repo.candidateId,
eventCount: repo.events.length,
};
}
export interface DossierIntegrityVerifyResultV1 {
ok: true;
valid: boolean;
recomputedRootHash: Sha256Digest;
}
export interface DossierIntegrityVerifyFailureV1 {
ok: false;
ruleId: "CERT-DOSSIER-04";
message: string;
}
export type DossierIntegrityVerifyOutcomeV1 =
| DossierIntegrityVerifyResultV1
| DossierIntegrityVerifyFailureV1;
export function verifyDossierIntegrity(
repo: DossierRepositoryV1,
): DossierIntegrityVerifyOutcomeV1 {
const recomputedHashes: Sha256Digest[] = [];
let prevHash: Sha256Digest | null = null;
for (const event of repo.events) {
if (event.previousEventHash !== prevHash) {
return {
ok: false,
ruleId: "CERT-DOSSIER-04",
message: `chain break at event ${event.eventId}: expected previousEventHash ${prevHash}, got ${event.previousEventHash}`,
};
}
const hashResult = computeDossierEventHash(event);
if (!hashResult.ok) {
return {
ok: false,
ruleId: "CERT-DOSSIER-04",
message: `failed to hash event ${event.eventId}: ${hashResult.code}`,
};
}
recomputedHashes.push(hashResult.eventHash);
prevHash = hashResult.eventHash;
}
const recomputedRoot = computeDossierRoot(repo.candidateId, recomputedHashes);
const valid = repo.rootHash === null || repo.rootHash === recomputedRoot;
return {
ok: true,
valid,
recomputedRootHash: recomputedRoot,
};
}