mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(memory-wiki): keep claim freshness tied to evidence timestamps (#97465)
* fix(memory-wiki): keep claim freshness tied to evidence * fix(memory-wiki): preserve page freshness fallback only for untimestamped claims --------- Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// Memory Wiki tests cover claim health plugin behavior.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildPageContradictionClusters } from "./claim-health.js";
|
||||
import type { WikiPageSummary } from "./markdown.js";
|
||||
import { assessClaimFreshness, buildPageContradictionClusters } from "./claim-health.js";
|
||||
import type { WikiClaim, WikiPageSummary } from "./markdown.js";
|
||||
|
||||
function createPage(params: {
|
||||
relativePath: string;
|
||||
@@ -64,3 +64,103 @@ describe("buildPageContradictionClusters", () => {
|
||||
expect(clusters.every((cluster) => cluster.entries)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("assessClaimFreshness", () => {
|
||||
it("uses the latest claim evidence timestamp without relying on page freshness", () => {
|
||||
const page = createPage({
|
||||
relativePath: "entities/alpha.md",
|
||||
title: "Alpha",
|
||||
contradictions: [],
|
||||
});
|
||||
page.updatedAt = "2026-01-01T00:00:00.000Z";
|
||||
const claim: WikiClaim = {
|
||||
text: "Alpha prefers current evidence.",
|
||||
updatedAt: "2026-01-05T00:00:00.000Z",
|
||||
evidence: [
|
||||
{ updatedAt: "2026-01-03T00:00:00.000Z" },
|
||||
{ updatedAt: "2026-01-20T00:00:00.000Z" },
|
||||
],
|
||||
};
|
||||
|
||||
const freshness = assessClaimFreshness({
|
||||
page,
|
||||
claim,
|
||||
now: new Date("2026-01-25T00:00:00.000Z"),
|
||||
});
|
||||
|
||||
expect(freshness.level).toBe("fresh");
|
||||
expect(freshness.lastTouchedAt).toBe("2026-01-20T00:00:00.000Z");
|
||||
expect(freshness.daysSinceTouch).toBe(5);
|
||||
});
|
||||
|
||||
it("does not let a newer page timestamp make stale claim evidence fresh", () => {
|
||||
const page = createPage({
|
||||
relativePath: "entities/beta.md",
|
||||
title: "Beta",
|
||||
contradictions: [],
|
||||
});
|
||||
page.updatedAt = "2026-04-20T00:00:00.000Z";
|
||||
const claim: WikiClaim = {
|
||||
text: "Beta still needs old evidence checked.",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
evidence: [{ updatedAt: "2026-01-05T00:00:00.000Z" }],
|
||||
};
|
||||
|
||||
const freshness = assessClaimFreshness({
|
||||
page,
|
||||
claim,
|
||||
now: new Date("2026-04-20T00:00:00.000Z"),
|
||||
});
|
||||
|
||||
expect(freshness.level).toBe("stale");
|
||||
expect(freshness.lastTouchedAt).toBe("2026-01-05T00:00:00.000Z");
|
||||
expect(freshness.daysSinceTouch).toBe(105);
|
||||
});
|
||||
|
||||
it("falls back to page freshness when claim and evidence timestamps are absent", () => {
|
||||
const page = createPage({
|
||||
relativePath: "entities/gamma.md",
|
||||
title: "Gamma",
|
||||
contradictions: [],
|
||||
});
|
||||
page.updatedAt = "2026-04-20T00:00:00.000Z";
|
||||
const claim: WikiClaim = {
|
||||
text: "Gamma was written through wiki_apply without per-claim timestamps.",
|
||||
evidence: [{ kind: "session", sourceId: "session-1" }],
|
||||
};
|
||||
|
||||
const freshness = assessClaimFreshness({
|
||||
page,
|
||||
claim,
|
||||
now: new Date("2026-04-25T00:00:00.000Z"),
|
||||
});
|
||||
|
||||
expect(freshness.level).toBe("fresh");
|
||||
expect(freshness.lastTouchedAt).toBe("2026-04-20T00:00:00.000Z");
|
||||
expect(freshness.daysSinceTouch).toBe(5);
|
||||
});
|
||||
|
||||
it("keeps malformed claim timestamps unknown instead of using page freshness", () => {
|
||||
const page = createPage({
|
||||
relativePath: "entities/delta.md",
|
||||
title: "Delta",
|
||||
contradictions: [],
|
||||
});
|
||||
page.updatedAt = "2026-04-20T00:00:00.000Z";
|
||||
const claim: WikiClaim = {
|
||||
text: "Delta has malformed claim freshness metadata.",
|
||||
updatedAt: "not-a-date",
|
||||
evidence: [],
|
||||
};
|
||||
|
||||
const freshness = assessClaimFreshness({
|
||||
page,
|
||||
claim,
|
||||
now: new Date("2026-04-25T00:00:00.000Z"),
|
||||
});
|
||||
|
||||
expect(freshness.level).toBe("unknown");
|
||||
expect(freshness.lastTouchedAt).toBeUndefined();
|
||||
expect(freshness.reason).toBe("missing updatedAt");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,12 +136,25 @@ export function assessClaimFreshness(params: {
|
||||
claim: WikiClaim;
|
||||
now?: Date;
|
||||
}): WikiFreshness {
|
||||
const latestTimestamp = resolveLatestTimestamp([
|
||||
params.claim.updatedAt,
|
||||
params.page.updatedAt,
|
||||
...params.claim.evidence.map((evidence) => evidence.updatedAt),
|
||||
]);
|
||||
return buildFreshnessFromTimestamp({ timestamp: latestTimestamp, now: params.now });
|
||||
let hasClaimTimestamp = typeof params.claim.updatedAt === "string" &&
|
||||
params.claim.updatedAt.trim().length > 0;
|
||||
let latestTimestamp = resolveLatestTimestamp([params.claim.updatedAt]);
|
||||
let latestMs = parseTimestamp(latestTimestamp) ?? -1;
|
||||
for (const evidence of params.claim.evidence) {
|
||||
if (typeof evidence.updatedAt === "string" && evidence.updatedAt.trim().length > 0) {
|
||||
hasClaimTimestamp = true;
|
||||
}
|
||||
const evidenceMs = parseTimestamp(evidence.updatedAt);
|
||||
if (evidenceMs === null || !evidence.updatedAt || evidenceMs <= latestMs) {
|
||||
continue;
|
||||
}
|
||||
latestMs = evidenceMs;
|
||||
latestTimestamp = evidence.updatedAt;
|
||||
}
|
||||
return buildFreshnessFromTimestamp({
|
||||
timestamp: latestTimestamp ?? (hasClaimTimestamp ? undefined : params.page.updatedAt),
|
||||
now: params.now,
|
||||
});
|
||||
}
|
||||
|
||||
function buildWikiClaimHealth(params: {
|
||||
|
||||
Reference in New Issue
Block a user