fix: accept FK reviews by candidate digest across same-content runs

This commit is contained in:
2026-08-11 19:46:16 +02:00
parent ff75742be9
commit 0986125327
@@ -297,8 +297,10 @@ export class WorkspacePreprocessingService {
} }
const candidate = this.state(scope.runtime.workspaceId).readFkCandidates(scope.job.runId); const candidate = this.state(scope.runtime.workspaceId).readFkCandidates(scope.job.runId);
if (candidate && !scope.job.completedStages.includes("fk_review")) { if (candidate && !scope.job.completedStages.includes("fk_review")) {
const review = this.state(scope.runtime.workspaceId).readFkReview(scope.job.runId); // The candidate content digest is authoritative: a human review accepted for ANY run
if (!review || review.reviewedCandidatesDigest !== candidate.digest) { // carrying the exact same candidate digest counts as the review checkpoint for this run.
const accepted = this.findAcceptedReviewForDigest(scope.runtime.workspaceId, candidate.digest);
if (!accepted) {
return baseResult(scope.runtime, "preprocess run", "blocked", "manual_review_required", { return baseResult(scope.runtime, "preprocess run", "blocked", "manual_review_required", {
runId: scope.job.runId, runId: scope.job.runId,
childRuns: { ...scope.job.childRuns }, childRuns: { ...scope.job.childRuns },
@@ -306,6 +308,7 @@ export class WorkspacePreprocessingService {
artifactIdentities: [{ kind: "fk_candidates", digest: candidate.digest }], artifactIdentities: [{ kind: "fk_candidates", digest: candidate.digest }],
}); });
} }
scope.job.reviewDigest = accepted.reviewedCandidatesDigest;
scope.job.completedStages.push("fk_review"); scope.job.completedStages.push("fk_review");
this.state(scope.runtime.workspaceId).writeJob(scope.job); this.state(scope.runtime.workspaceId).writeJob(scope.job);
} }
@@ -470,6 +473,20 @@ export class WorkspacePreprocessingService {
} }
} }
private findAcceptedReviewForDigest(
workspaceId: string,
digestValue: string,
): FkReviewRecord | undefined {
const state = this.state(workspaceId);
for (const entry of readdirSync(state.fkReviewsDirectory(), { withFileTypes: true })) {
if (!entry.isFile() || entry.isSymbolicLink() || !/^[0-9a-f]{32}\.json$/.test(entry.name)) continue;
const runId = entry.name.slice(0, -".json".length);
const review = state.readFkReview(runId);
if (review && review.reviewedCandidatesDigest === digestValue) return review;
}
return undefined;
}
private findRunIdByCandidateDigest(state: PreprocessingStateStore, digestValue: string): string | undefined { private findRunIdByCandidateDigest(state: PreprocessingStateStore, digestValue: string): string | undefined {
for (const entry of readdirSync(state.fkCandidatesDirectory(), { withFileTypes: true })) { for (const entry of readdirSync(state.fkCandidatesDirectory(), { withFileTypes: true })) {
if (!entry.isFile() || entry.isSymbolicLink() || !/^[0-9a-f]{32}\.yaml$/.test(entry.name)) continue; if (!entry.isFile() || entry.isSymbolicLink() || !/^[0-9a-f]{32}\.yaml$/.test(entry.name)) continue;