feat: add VedAstro rectification post-validation
This commit is contained in:
@@ -188,6 +188,38 @@ export const candidateSplitSchema = z.object({
|
||||
eventIds: z.array(uuid).max(100),
|
||||
}).strict();
|
||||
|
||||
export const vedAstroCandidateMetricSchema = z.object({
|
||||
role: z.enum(["primary", "runner_up"]),
|
||||
requestedEventCount: z.number().int().nonnegative().max(20),
|
||||
successfulEventCount: z.number().int().nonnegative().max(20),
|
||||
matchedEventCount: z.number().int().nonnegative().max(20),
|
||||
eventHitCount: z.number().int().nonnegative(),
|
||||
signalLift: z.number().finite(),
|
||||
}).strict();
|
||||
|
||||
export const vedAstroPostValidationSchema = z.object({
|
||||
contractVersion: z.literal("vedastro-post-validation-v1"),
|
||||
provider: z.literal("vedastro_official"),
|
||||
status: z.enum(["pass", "blocked", "not_validated"]),
|
||||
providerStatus: nonblank(80),
|
||||
blockers: z.array(nonblank(120)).max(20),
|
||||
primaryCandidateTime: clockTimeSchema.nullable(),
|
||||
runnerUpCandidateTime: clockTimeSchema.nullable(),
|
||||
eligibleEventCount: z.number().int().nonnegative().max(100),
|
||||
selectedEventCount: z.number().int().nonnegative().max(20),
|
||||
unsupportedEventCount: z.number().int().nonnegative().max(100),
|
||||
candidateMetrics: z.array(vedAstroCandidateMetricSchema).max(2),
|
||||
minuteSensitiveValidation: z.object({
|
||||
comparisonReady: z.boolean(),
|
||||
discriminated: z.boolean(),
|
||||
discriminatedLayers: z.array(nonblank(80)).max(10),
|
||||
}).strict(),
|
||||
validationHash: hash,
|
||||
validatedAt: z.string().datetime({ offset: true }),
|
||||
canConfirmExactMinute: z.literal(false),
|
||||
}).strict();
|
||||
export type VedAstroPostValidation = z.infer<typeof vedAstroPostValidationSchema>;
|
||||
|
||||
export const diagnosticsSummarySchema = z.object({
|
||||
id: uuid,
|
||||
caseId: uuid,
|
||||
@@ -203,6 +235,7 @@ export const diagnosticsSummarySchema = z.object({
|
||||
mostDiscriminatingLayers: z.array(nonblank(80)).max(40),
|
||||
eventDateSensitivity: z.array(eventDateSensitivitySchema).max(100),
|
||||
candidateSplits: z.array(candidateSplitSchema).max(20),
|
||||
externalValidation: vedAstroPostValidationSchema.optional(),
|
||||
calculationHash: hash,
|
||||
createdAt: z.string().datetime({ offset: true }),
|
||||
}).strict();
|
||||
|
||||
@@ -17,18 +17,51 @@ import { recordRectificationAgentTelemetry } from "./telemetry.ts";
|
||||
import {
|
||||
candidateFeatureSnapshotSchema,
|
||||
diagnosticsSummarySchema,
|
||||
vedAstroPostValidationSchema,
|
||||
validateRectificationDecision,
|
||||
type AgentRun,
|
||||
type CandidateFeatureSnapshot,
|
||||
type DiagnosticsSummary,
|
||||
type StoredPublicMessage,
|
||||
type ValidatedDecision,
|
||||
type VedAstroPostValidation,
|
||||
} from "./contracts.ts";
|
||||
|
||||
function hash(value: unknown): string {
|
||||
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
||||
}
|
||||
|
||||
function vedAstroCandidateTimes(snapshot: CandidateSnapshot): readonly [string, string] | null {
|
||||
const primary = snapshot.clusters[0]?.representativeTime;
|
||||
if (!primary) return null;
|
||||
const runnerUp = snapshot.clusters[1]?.representativeTime
|
||||
?? [...snapshot.candidates].sort((left, right) => right.score - left.score).find((candidate) => candidate.time !== primary)?.time;
|
||||
return runnerUp && runnerUp !== primary ? [primary, runnerUp] : null;
|
||||
}
|
||||
|
||||
function blockedVedAstroValidation(now: Date, blocker: string, candidateTimes: readonly [string, string] | null): VedAstroPostValidation {
|
||||
const safe = {
|
||||
contractVersion: "vedastro-post-validation-v1" as const,
|
||||
provider: "vedastro_official" as const,
|
||||
status: "blocked" as const,
|
||||
providerStatus: "unavailable",
|
||||
blockers: [blocker],
|
||||
primaryCandidateTime: candidateTimes?.[0] ?? null,
|
||||
runnerUpCandidateTime: candidateTimes?.[1] ?? null,
|
||||
eligibleEventCount: 0,
|
||||
selectedEventCount: 0,
|
||||
unsupportedEventCount: 0,
|
||||
candidateMetrics: [],
|
||||
minuteSensitiveValidation: { comparisonReady: false, discriminated: false, discriminatedLayers: [] },
|
||||
canConfirmExactMinute: false as const,
|
||||
};
|
||||
return vedAstroPostValidationSchema.parse({
|
||||
...safe,
|
||||
validationHash: hash(safe),
|
||||
validatedAt: now.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
const analysisPhaseLabels = {
|
||||
extracting_evidence: "整理用户经历",
|
||||
scoring_candidates: "扫描候选分钟",
|
||||
@@ -240,6 +273,50 @@ export async function processRectificationAgentTurn(input: Readonly<{
|
||||
});
|
||||
}
|
||||
|
||||
if (snapshot?.canAcceptRange && diagnostics && claimed.case.deploymentMode === "v5_agent") {
|
||||
const candidateTimes = vedAstroCandidateTimes(snapshot);
|
||||
const validationStarted = Date.now();
|
||||
let externalValidation: VedAstroPostValidation;
|
||||
let outcome: "succeeded" | "failed" | "rejected";
|
||||
if (!candidateTimes) {
|
||||
externalValidation = blockedVedAstroValidation(now, "vedastro_runner_up_candidate_missing", null);
|
||||
outcome = "rejected";
|
||||
} else if (!input.engine.validateWithVedAstro) {
|
||||
externalValidation = blockedVedAstroValidation(now, "vedastro_validator_unavailable", candidateTimes);
|
||||
outcome = "failed";
|
||||
} else {
|
||||
try {
|
||||
externalValidation = await input.engine.validateWithVedAstro({
|
||||
calculationSpec: claimed.case.calculationSpec,
|
||||
events: scoreable,
|
||||
candidateTimes,
|
||||
});
|
||||
outcome = externalValidation.status === "pass" ? "succeeded" : "rejected";
|
||||
} catch {
|
||||
externalValidation = blockedVedAstroValidation(now, "vedastro_validation_failed", candidateTimes);
|
||||
outcome = "failed";
|
||||
}
|
||||
}
|
||||
diagnostics = diagnosticsSummarySchema.parse({ ...diagnostics, externalValidation });
|
||||
analysisToolCalls.push({
|
||||
category: "diagnostic",
|
||||
label: "VedAstro 事后校验",
|
||||
outcome,
|
||||
durationMs: Date.now() - validationStarted,
|
||||
});
|
||||
if (externalValidation.status !== "pass") {
|
||||
snapshot = {
|
||||
...snapshot,
|
||||
canAcceptRange: false,
|
||||
gateReasons: [...new Set([
|
||||
...snapshot.gateReasons,
|
||||
"vedastro_validation_not_passed",
|
||||
...externalValidation.blockers,
|
||||
])].slice(0, 20),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const safeDiagnostics = diagnostics ?? diagnosticsSummarySchema.parse({
|
||||
id: randomUUID(),
|
||||
caseId: claimed.case.id,
|
||||
|
||||
Reference in New Issue
Block a user