





















1+import fs from "node:fs/promises";
2+import path from "node:path";
3+import { describe, expect, it } from "vitest";
4+import {
5+buildDreamingShadowTrialReport,
6+defaultDreamingShadowTrialReportPath,
7+resolveDreamingShadowTrialRecommendation,
8+writeDreamingShadowTrialReport,
9+} from "./dreaming-shadow-trial.js";
10+import { createMemoryCoreTestHarness } from "./test-helpers.js";
11+12+const { createTempWorkspace } = createMemoryCoreTestHarness();
13+14+const baseInput = {
15+candidate: "The user prefers release notes with exact verification commands.",
16+trialPrompt: "Prepare a release readiness note.",
17+baselineOutcome: "Mentions tests passed without the exact command.",
18+candidateOutcome: "Includes the exact verification command and remaining risk.",
19+reason: "The candidate improves the release reply without exposing private data.",
20+riskFlags: ["no secret exposure", "no outdated preference conflict"],
21+evidenceRefs: ["memory/2026-05-18.md#L30-L49"],
22+};
23+24+describe("dreaming shadow trial runner", () => {
25+it("maps verdicts to report-only recommendations", () => {
26+expect(resolveDreamingShadowTrialRecommendation("helpful")).toBe("promote");
27+expect(resolveDreamingShadowTrialRecommendation("neutral")).toBe("defer");
28+expect(resolveDreamingShadowTrialRecommendation("harmful")).toBe("reject");
29+});
30+31+it("builds the stable shadow-trial report contract", () => {
32+const report = buildDreamingShadowTrialReport({
33+ ...baseInput,
34+verdict: "helpful",
35+nowMs: Date.parse("2026-05-18T18:00:00.000Z"),
36+});
37+38+expect(report.recommendation).toBe("promote");
39+expect(report.promotionAction).toBe("report-only");
40+expect(report.markdown).toContain("candidate: The user prefers release notes");
41+expect(report.markdown).toContain("baseline outcome: Mentions tests passed");
42+expect(report.markdown).toContain("candidate outcome: Includes the exact verification command");
43+expect(report.markdown).toContain("verdict: helpful");
44+expect(report.markdown).toContain("recommendation: promote");
45+expect(report.markdown).toContain("risk flags:");
46+expect(report.markdown).toContain("- no secret exposure");
47+expect(report.markdown).toContain("evidence refs:");
48+expect(report.markdown).toContain("promotion action: report-only");
49+expect(report.markdown).not.toContain("promoted to MEMORY.md");
50+});
51+52+it("writes only the shadow-trial report and leaves MEMORY.md unchanged", async () => {
53+const workspaceDir = await createTempWorkspace("openclaw-shadow-trial-");
54+const memoryPath = path.join(workspaceDir, "MEMORY.md");
55+await fs.writeFile(memoryPath, "# Memory\n\nExisting durable memory.\n", "utf-8");
56+57+const report = await writeDreamingShadowTrialReport({
58+ ...baseInput,
59+verdict: "neutral",
60+ workspaceDir,
61+nowMs: Date.parse("2026-05-18T18:00:00.000Z"),
62+});
63+64+expect(report.recommendation).toBe("defer");
65+expect(path.dirname(report.reportPath!)).toBe(
66+path.join(workspaceDir, "memory", "dreaming", "shadow-trials", "2026-05-18"),
67+);
68+expect(path.basename(report.reportPath!)).toMatch(/^[a-f0-9]{12}\.md$/);
69+await expect(fs.readFile(memoryPath, "utf-8")).resolves.toBe(
70+"# Memory\n\nExisting durable memory.\n",
71+);
72+expect(report.reportPath).toBeTruthy();
73+await expect(fs.readFile(report.reportPath!, "utf-8")).resolves.toContain(
74+"promotion action: report-only",
75+);
76+});
77+78+it("uses the configured dreaming timezone for the default report day", async () => {
79+const workspaceDir = await createTempWorkspace("openclaw-shadow-trial-timezone-");
80+81+const report = await writeDreamingShadowTrialReport({
82+ ...baseInput,
83+verdict: "helpful",
84+ workspaceDir,
85+nowMs: Date.parse("2026-05-18T21:30:00.000Z"),
86+timezone: "Asia/Riyadh",
87+});
88+89+expect(path.dirname(report.reportPath!)).toBe(
90+path.join(workspaceDir, "memory", "dreaming", "shadow-trials", "2026-05-19"),
91+);
92+expect(path.basename(report.reportPath!)).toMatch(/^[a-f0-9]{12}\.md$/);
93+await expect(fs.readFile(report.reportPath!, "utf-8")).resolves.toContain(
94+"recommendation: promote",
95+);
96+});
97+98+it("keeps distinct same-day trials in separate default report files", async () => {
99+const workspaceDir = await createTempWorkspace("openclaw-shadow-trial-collisions-");
100+const nowMs = Date.parse("2026-05-18T18:00:00.000Z");
101+102+const first = await writeDreamingShadowTrialReport({
103+ ...baseInput,
104+verdict: "helpful",
105+ workspaceDir,
106+ nowMs,
107+});
108+const second = await writeDreamingShadowTrialReport({
109+ ...baseInput,
110+candidate: "The user prefers terse release notes with exact verification commands.",
111+verdict: "helpful",
112+ workspaceDir,
113+ nowMs,
114+});
115+116+expect(first.reportPath).not.toBe(second.reportPath);
117+expect(path.dirname(first.reportPath!)).toBe(path.dirname(second.reportPath!));
118+await expect(fs.readFile(first.reportPath!, "utf-8")).resolves.toContain(
119+"candidate: The user prefers release notes",
120+);
121+await expect(fs.readFile(second.reportPath!, "utf-8")).resolves.toContain(
122+"candidate: The user prefers terse release notes",
123+);
124+});
125+126+it("keeps risky candidates reject-only without promoting durable memory", async () => {
127+const workspaceDir = await createTempWorkspace("openclaw-shadow-trial-risk-");
128+const reportPath = defaultDreamingShadowTrialReportPath({
129+ ...baseInput,
130+candidate: "The user always wants private tokens pasted into status reports.",
131+candidateOutcome: "Includes a private token in the release reply.",
132+verdict: "harmful",
133+reason: "The candidate creates secret exposure risk.",
134+riskFlags: ["secret exposure"],
135+ workspaceDir,
136+nowMs: Date.parse("2026-05-19T01:00:00.000Z"),
137+});
138+139+const report = await writeDreamingShadowTrialReport({
140+ ...baseInput,
141+candidate: "The user always wants private tokens pasted into status reports.",
142+candidateOutcome: "Includes a private token in the release reply.",
143+verdict: "harmful",
144+reason: "The candidate creates secret exposure risk.",
145+riskFlags: ["secret exposure"],
146+ workspaceDir,
147+ reportPath,
148+});
149+150+expect(report.recommendation).toBe("reject");
151+expect(report.markdown).toContain("verdict: harmful");
152+expect(report.markdown).toContain("recommendation: reject");
153+expect(report.markdown).toContain("promotion action: report-only");
154+await expect(fs.readFile(path.join(workspaceDir, "MEMORY.md"), "utf-8")).rejects.toMatchObject({
155+code: "ENOENT",
156+});
157+});
158+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。