fix(retention): add UTC calendar-day diversity gate to reinforceMemory

Implement OQ-2 decision: allow at most one reinforcement per memory
identity per UTC calendar day. Same-day reinforcement is blocked
regardless of session or interval. This prevents repetitive-task
gaming where a daily recurring task could reach MAX_COUNT=6 in hours.

Guard order: same-session → calendar-day → 1-hour → max-count
(existing guards kept as defense-in-depth)

1 hour guard is redundant within same day but preserved for
sub-hour edge cases.
This commit is contained in:
Ralph Chang
2026-04-30 18:38:29 +08:00
parent 84245c783d
commit f25a235b93
2 changed files with 40 additions and 0 deletions
+27
View File
@@ -505,6 +505,33 @@ test("reinforceMemory enforces session interval and max guards", () => {
assert.equal(reinforceMemory(atMax, "session-c", now), atMax);
});
test("reinforceMemory requires distinct UTC calendar days between reinforcements", () => {
const firstReinforcedAt = Date.UTC(2026, 3, 29, 0, 15);
const sameUtcDayMuchLater = Date.UTC(2026, 3, 29, 23, 30);
const nextUtcDayAfterInterval = Date.UTC(2026, 3, 30, 1, 30);
const base: LongTermMemoryEntry = {
...entry("calendar-day-gated", "Reinforcement requires distinct UTC calendar days", "decision"),
reinforcementCount: 1,
lastReinforcedAt: firstReinforcedAt,
lastReinforcedSessionID: "session-a",
};
assert.equal(reinforceMemory(base, "session-b", sameUtcDayMuchLater), base);
const reinforcedNextDay = reinforceMemory(base, "session-b", nextUtcDayAfterInterval);
assert.notEqual(reinforcedNextDay, base);
assert.equal(reinforcedNextDay.reinforcementCount, 2);
assert.equal(reinforcedNextDay.lastReinforcedAt, nextUtcDayAfterInterval);
assert.equal(reinforcedNextDay.lastReinforcedSessionID, "session-b");
assert.equal(reinforcedNextDay.retentionClock, nextUtcDayAfterInterval);
const atMax: LongTermMemoryEntry = {
...base,
reinforcementCount: 6,
};
assert.equal(reinforceMemory(atMax, "session-c", nextUtcDayAfterInterval), atMax);
});
test("dedupeLongTermEntriesWithAccounting reinforces absorbed exact duplicates", () => {
const now = Date.now();
const retained: LongTermMemoryEntry = {