
























@@ -36,6 +36,30 @@ export type DreamingShadowTrialReport = {
3636markdown: string;
3737};
383839+export type DreamingShadowTrialScoreOptions = {
40+helpfulBoost?: number;
41+neutralDelta?: number;
42+harmfulPenalty?: number;
43+};
44+45+export type DreamingShadowTrialCandidateInput = {
46+key: string;
47+score: number;
48+};
49+50+export type DreamingShadowTrialCandidateScore<T extends DreamingShadowTrialCandidateInput> = {
51+candidate: T;
52+scoreBeforeShadowTrial: number;
53+scoreAfterShadowTrial: number;
54+shadowTrialScoreDelta: number;
55+shadowTrialVerdict: DreamingShadowTrialVerdict;
56+shadowTrialRecommendation: DreamingShadowTrialRecommendation;
57+shadowTrialRiskFlags: string[];
58+shadowTrialEvidenceRefs: string[];
59+rejectedByShadowTrial: boolean;
60+scoringAction: "report-only";
61+};
62+3963function normalizeRequiredText(value: string, label: string): string {
4064const normalized = value.trim().replace(/\s+/g, " ");
4165if (!normalized) {
@@ -49,6 +73,24 @@ function normalizeList(values: string[] | undefined, fallback: string): string[]
4973return normalized.length > 0 ? normalized : [fallback];
5074}
517576+function normalizeDataList(values: string[] | undefined): string[] {
77+return (values ?? []).map((value) => value.trim()).filter(Boolean);
78+}
79+80+function clampScore(value: number): number {
81+if (!Number.isFinite(value)) {
82+return 0;
83+}
84+return Math.max(0, Math.min(1, value));
85+}
86+87+function normalizeScoreDelta(value: number, fallback: number): number {
88+if (!Number.isFinite(value)) {
89+return fallback;
90+}
91+return Math.max(-1, Math.min(1, value));
92+}
93+5294export function resolveDreamingShadowTrialRecommendation(
5395verdict: DreamingShadowTrialVerdict,
5496): DreamingShadowTrialRecommendation {
@@ -88,6 +130,19 @@ function resolveReportContentHash(params: {
88130return crypto.createHash("sha256").update(seed).digest("hex").slice(0, 12);
89131}
90132133+function resolveDreamingShadowTrialScoreDelta(
134+verdict: DreamingShadowTrialVerdict,
135+options?: DreamingShadowTrialScoreOptions,
136+): number {
137+if (verdict === "helpful") {
138+return normalizeScoreDelta(options?.helpfulBoost ?? 0.04, 0.04);
139+}
140+if (verdict === "harmful") {
141+return normalizeScoreDelta(options?.harmfulPenalty ?? -1, -1);
142+}
143+return normalizeScoreDelta(options?.neutralDelta ?? 0, 0);
144+}
145+91146export function defaultDreamingShadowTrialReportPath(params: {
92147workspaceDir: string;
93148candidate: string;
@@ -110,8 +165,8 @@ export function defaultDreamingShadowTrialReportPath(params: {
110165candidateOutcome: normalizeRequiredText(params.candidateOutcome, "candidateOutcome"),
111166verdict: params.verdict,
112167reason: normalizeRequiredText(params.reason, "reason"),
113-riskFlags: normalizeList(params.riskFlags, "none recorded"),
114-evidenceRefs: normalizeList(params.evidenceRefs, "none supplied"),
168+riskFlags: normalizeDataList(params.riskFlags),
169+evidenceRefs: normalizeDataList(params.evidenceRefs),
115170});
116171return path.join(
117172params.workspaceDir,
@@ -172,8 +227,8 @@ export function buildDreamingShadowTrialReport(
172227const baselineOutcome = normalizeRequiredText(input.baselineOutcome, "baselineOutcome");
173228const candidateOutcome = normalizeRequiredText(input.candidateOutcome, "candidateOutcome");
174229const reason = normalizeRequiredText(input.reason, "reason");
175-const riskFlags = normalizeList(input.riskFlags, "none recorded");
176-const evidenceRefs = normalizeList(input.evidenceRefs, "none supplied");
230+const riskFlags = normalizeDataList(input.riskFlags);
231+const evidenceRefs = normalizeDataList(input.evidenceRefs);
177232const recommendation = resolveDreamingShadowTrialRecommendation(input.verdict);
178233const reportPath = resolveReportPath({
179234workspaceDir: input.workspaceDir,
@@ -201,9 +256,9 @@ export function buildDreamingShadowTrialReport(
201256`recommendation: ${recommendation}`,
202257`reason: ${reason}`,
203258"risk flags:",
204-formatList(riskFlags),
259+formatList(normalizeList(riskFlags, "none recorded")),
205260"evidence refs:",
206-formatList(evidenceRefs),
261+formatList(normalizeList(evidenceRefs, "none supplied")),
207262"promotion action: report-only",
208263"",
209264].join("\n");
@@ -224,6 +279,68 @@ export function buildDreamingShadowTrialReport(
224279};
225280}
226281282+export function scoreDreamingShadowTrialCandidate<T extends DreamingShadowTrialCandidateInput>(
283+candidate: T,
284+report: Pick<
285+DreamingShadowTrialReport,
286+"verdict" | "recommendation" | "riskFlags" | "evidenceRefs"
287+>,
288+options?: DreamingShadowTrialScoreOptions,
289+): DreamingShadowTrialCandidateScore<T> {
290+const scoreBeforeShadowTrial = clampScore(candidate.score);
291+const shadowTrialScoreDelta = resolveDreamingShadowTrialScoreDelta(report.verdict, options);
292+const rejectedByShadowTrial = report.verdict === "harmful" || report.recommendation === "reject";
293+const scoreAfterShadowTrial = rejectedByShadowTrial
294+ ? 0
295+ : clampScore(scoreBeforeShadowTrial + shadowTrialScoreDelta);
296+297+return {
298+ candidate,
299+ scoreBeforeShadowTrial,
300+ scoreAfterShadowTrial,
301+ shadowTrialScoreDelta,
302+shadowTrialVerdict: report.verdict,
303+shadowTrialRecommendation: report.recommendation,
304+shadowTrialRiskFlags: normalizeDataList(report.riskFlags),
305+shadowTrialEvidenceRefs: normalizeDataList(report.evidenceRefs),
306+ rejectedByShadowTrial,
307+scoringAction: "report-only",
308+};
309+}
310+311+export function rankDreamingShadowTrialCandidates<T extends DreamingShadowTrialCandidateInput>(
312+candidates: readonly T[],
313+reportsByCandidateKey: ReadonlyMap<string, DreamingShadowTrialReport>,
314+options?: DreamingShadowTrialScoreOptions,
315+): DreamingShadowTrialCandidateScore<T>[] {
316+return candidates
317+.map((candidate) => {
318+const report = reportsByCandidateKey.get(candidate.key);
319+if (!report) {
320+const score = clampScore(candidate.score);
321+return {
322+ candidate,
323+scoreBeforeShadowTrial: score,
324+scoreAfterShadowTrial: score,
325+shadowTrialScoreDelta: 0,
326+shadowTrialVerdict: "neutral" as const,
327+shadowTrialRecommendation: "defer" as const,
328+shadowTrialRiskFlags: ["not shadow-trialed"],
329+shadowTrialEvidenceRefs: [],
330+rejectedByShadowTrial: false,
331+scoringAction: "report-only" as const,
332+};
333+}
334+return scoreDreamingShadowTrialCandidate(candidate, report, options);
335+})
336+.toSorted((left, right) => {
337+if (left.rejectedByShadowTrial !== right.rejectedByShadowTrial) {
338+return left.rejectedByShadowTrial ? 1 : -1;
339+}
340+return right.scoreAfterShadowTrial - left.scoreAfterShadowTrial;
341+});
342+}
343+227344export async function writeDreamingShadowTrialReport(
228345input: DreamingShadowTrialInput & { workspaceDir: string },
229346): Promise<DreamingShadowTrialReport> {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。