feat(rectification): drive questions from candidate contrast

This commit is contained in:
Jesse_Chen
2026-07-31 13:17:00 +08:00
parent fdf791953e
commit a7a11a1a7a
21 changed files with 703 additions and 42 deletions
@@ -13,7 +13,7 @@ import { generateOpeningQuestion, regenerateQuestionRealization } from "../recti
import { calculationSpecHash, evidenceSetHash } from "./fingerprints.ts";
import { hasPolicyInvalidScoreableEvents } from "./evidence-ledger.ts";
import { openingQuestion } from "./opening-question.ts";
import type { RectificationV4Store } from "./store.ts";
import { canResumeRectificationCase, type RectificationV4Store } from "./store.ts";
const regenerationInFlight = new Map<string, Promise<RectificationV4Case | null>>();
@@ -58,7 +58,10 @@ export function createRectificationV4CaseService(
const specHash = calculationSpecHash(input.calculationSpec);
const active = await store.findActiveCase(input.userId);
if (active?.calculationSpecHash === specHash) {
const activeJob = active?.status === "processing" ? await store.loadActiveJob(input.userId, active.id) : null;
if (active?.calculationSpecHash === specHash
&& active.algorithmVersion === rectificationV4AlgorithmVersion
&& canResumeRectificationCase(active, Boolean(activeJob))) {
return response(input.userId, await store.createCase({ case: active, actionId: input.actionId }));
}
@@ -4,7 +4,11 @@ export const rectificationV4Protocol = "rectification-evidence-v4" as const;
export const rectificationAgentV5Protocol = "rectification-evidence-v5" as const;
export const rectificationDeploymentModeSchema = z.enum(["v4_legacy", "v5_shadow", "v5_agent"]);
export type RectificationDeploymentMode = z.infer<typeof rectificationDeploymentModeSchema>;
export const rectificationV4AlgorithmVersion = "rectification-v5-matrix-scoring-1" as const;
export const rectificationV4AlgorithmVersion = "rectification-v5-matrix-scoring-2" as const;
const rectificationLegacyAlgorithmVersions = [
"rectification-v4-range-scoring-1",
"rectification-v5-matrix-scoring-1",
] as const;
export const rectificationV4CaseStatusSchema = z.enum([
"awaiting_answer",
@@ -182,7 +186,7 @@ const candidateSnapshotBaseSchema = z.object({
caseVersion: z.number().int().nonnegative(),
evidenceSetHash: z.string().regex(/^[a-f0-9]{64}$/),
calculationSpecHash: z.string().regex(/^[a-f0-9]{64}$/),
algorithmVersion: z.literal(rectificationV4AlgorithmVersion),
algorithmVersion: z.enum([...rectificationLegacyAlgorithmVersions, rectificationV4AlgorithmVersion]),
candidates: z.array(candidateMinuteSchema).min(1).max(1_440),
clusters: z.array(candidateClusterSchema).max(20),
robustness: robustnessSchema,
@@ -11,7 +11,7 @@ import type {
RectificationV4Store,
RectificationV4Turn,
} from "./store.ts";
import { RectificationV4StoreError } from "./store.ts";
import { canResumeRectificationCase, RectificationV4StoreError } from "./store.ts";
import { evidenceSetHash } from "./fingerprints.ts";
export function createRectificationV4MemoryStore(): RectificationV4Store & {
@@ -93,7 +93,10 @@ export function createRectificationV4MemoryStore(): RectificationV4Store & {
if (replay) return owned(input.case.userId, replay.caseId);
const active = [...cases.values()].find((value) => value.userId === input.case.userId
&& value.status !== "abandoned" && value.acceptedRange === null);
if (active?.calculationSpecHash === input.case.calculationSpecHash) {
const hasActiveJob = active ? [...jobs.values()].some((job) => job.caseId === active.id && ["pending", "processing"].includes(job.status)) : false;
if (active?.calculationSpecHash === input.case.calculationSpecHash
&& active.algorithmVersion === input.case.algorithmVersion
&& canResumeRectificationCase(active, hasActiveJob)) {
actionResults.set(`${input.case.userId}:${input.actionId}`, { caseId: active.id, jobId: null });
return active;
}
@@ -12,6 +12,11 @@ import type {
} from "./contracts.ts";
export type { RectificationV4Turn } from "./contracts.ts";
export function canResumeRectificationCase(caseValue: RectificationV4Case, hasActiveJob: boolean): boolean {
return (caseValue.status === "awaiting_answer" && caseValue.currentQuestion !== null)
|| (caseValue.status === "processing" && hasActiveJob);
}
export type ClaimedRectificationV4Job = Readonly<{
job: RectificationV4Job;
case: RectificationV4Case;
@@ -97,7 +97,7 @@ function caseValue(row: Row, latestSnapshot: CandidateSnapshot | null): Rectific
narrationModelId: row.narration_model_id ? String(row.narration_model_id) : null,
skillVersion: row.skill_version ? String(row.skill_version) : "birth-time-rectification-v5",
promptVersion: row.prompt_version ? String(row.prompt_version) : "rectification-agent-v5-1",
algorithmVersion: row.algorithm_version ? String(row.algorithm_version) : "rectification-v5-matrix-scoring-1",
algorithmVersion: row.algorithm_version ? String(row.algorithm_version) : "rectification-v5-matrix-scoring-2",
deploymentMode: row.deployment_mode === "v5_agent" || row.deployment_mode === "v5_shadow" ? row.deployment_mode : "v4_legacy",
agentMode: row.agent_mode === "agent" ? "agent" : "deterministic_fallback",
featureSnapshotId: row.feature_snapshot_id ? String(row.feature_snapshot_id) : null,