fix(memory): fall back to default retention on negative keep_days - #8246
Conversation
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of All checks done — the shipped code is sound, but the description describes a different implementation than the diff contains. Design-Verdict: CONCERNS Diff and description diverge: the PR describes a sink guard in Watch
Suggestions
[DESIGN-REVIEWED] 0fc7496 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All checks complete. Here is the review. First-Principles-Verdict: CONCERNS The diff fixes the wipe at config load — correctly — but the description narrates a different fix (a sink guard in What this change shipsIntent: stop a hand-edited negative
Watch
No subtractions: the change reuses the existing [FIRST-PRINCIPLES-REVIEWED] 0fc7496 |
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
|
Good catch on |
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
A hand-edited negative history_max_days reached prune_history raw via the heartbeat and wiped all daily memory. Coerce it at the loader with _safe_nonnegative_int, the existing mechanism for exactly this class. Includes a loader-level regression test. Fixes kirodotdev#8245
7c79844 to
0fc7496
Compare
|
Addressing the First Principles BLOCK — verified each claim and reworked accordingly:
|
|
On the Design CONCERNS (reviewed the old commit; the PR is now loader-only, 1 commit):
|
|
Noted on the relationship audit — no overlapping change on my side, nothing to coordinate. |
iamwhatever
left a comment
There was a problem hiding this comment.
Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: routes memory.history_max_days through the existing _safe_nonnegative_int sanitizer at load so a hand-edited negative value falls back to the 365-day default instead of reaching prune_history raw (#8245); one production line plus a parametrized regression test. CodeQL is not applicable on this fork PR (default-setup emits no check-run); SAST coverage is Semgrep only, latest run success with 0 annotations.
Problem / Motivation
A negative
history_max_daysinconfig.json(hand edit, bad import, corruption) makes the periodic heartbeat wipe the ENTIRE daily memory history, silently. Verified: seeding 3 daily files then callingprune_history(-1)deletes all 3.cutoff = today - timedelta(days=-1)lands tomorrow, so every file counts as "older" and gets unlinked with only an info log.Why it matters
Daily history is unrecoverable user memory. The dashboard path already clamps retention to min 7, but the file-config path passes the raw value through (
config/loader.py.getpassthrough, no clamp inconfig/sections.py) straight into a periodic, unattended deleter. One bad number = total silent memory loss.What changed (motivation → approach → change)
The destructive sink is the right place for the guard:
prune_historynow treats a negative (or None)keep_daysas invalid, logs a warning, and falls back to the 365-day default instead of deleting everything. Zero keeps its current "drop everything before today" meaning — only the can-never-be-intentional negatives are caught.Tests
test_negative_keep_days_falls_back_to_default: seeds an old + recent file, asserts-1deletes only the old one (red without the fix — it wiped both, green with it).test_memory_cov80.pygreen (21 passed).Manual verification
N/A — unit coverage sufficient: the wipe and the fallback are both locked by automated tests against a tmp workspace.
Fixes #8245
Pattern harvest
Rule candidate: numeric config values flowing into destructive retention/deletion paths need range validation at the sink, since file-config paths bypass dashboard clamps.