ADR-0007: Hot-Memory Read-State Contract
Status
Accepted
Context
planning/memory_design.md defines .palee/hot.md as a derived working-memory view that must be tolerant of external edits (Obsidian, sync daemons, manual text editing). ADR-0006 established the write side of that contract — resetHotMemory, rebuildHotAndIndex, updateHotMemory in src/storage/memory.ts, all behind the src/storage/index.ts facade.
The read side was never owned. Until #130, src/cli/session.ts hand-rolled its own parseFrontmatter on hot.md content at six call sites, and each site re-implemented its own notion of what a "bad" read looked like. When those sites were consolidated behind a single readHotMemory() accessor (#130), the classification itself became an exported contract (HotMemoryReadState) without ever being formally decided — first flagged in #145 review.
One semantic question was left implicit: which palee_schema values count as supported. The pre-accessor code checked truthiness (!frontmatter.palee_schema), so palee_schema: 2, true, and "1" all classified as ok. That is inconsistent with the rest of the codebase: palee migrate rejects non-v1 schemas as unrecognized, ADR-0005's migration path converges notes on palee_schema: 1, and every hot-memory writer pins version 1 (HotMemoryData).
Decision
readHotMemory(vaultPath) classifies .palee/hot.md into exactly five states and enforces the accessor boundary:
Five-state classification (
HotMemoryReadState):ok— frontmatter parses and carriespalee_schema: 1.schema-invalid— frontmatter parses butpalee_schemais absent or not the supported version. Strict equality (=== 1) rejects2,true, and"1"; tolerant fields are still exposed for policy-free inspection.no-frontmatter— no parsable fences (absent, or---\n---empty fence).corrupt— fences present, YAML parse failed.missing— file does not exist (ENOENT).
Accessor boundary (unchanged from #130):
ENOENTmaps tomissing; all other filesystem errors are thrown, so callers keep deciding whether to swallow; unknown frontmatter keys are ignored, never rejected; no rebuild, no mutation, no output. The read joins.palee/hot.mddirectly rather than viagetPaleeDir()(which creates the directory) — a read must never have that side effect.Session policy stays in the CLI layer, deliberately un-normalized because the flows are intentionally different:
- topic resolution swallows every read failure → null
session startrebuildscorrupt/schema-invalid, toleratesno-frontmatterwithout rebuilding, propagates non-ENOENTerrors to the command catch (exit 5)session draft/session endkeep their own age/skew rules (24h/no-future; no age limit + 60s future-skew clamp)
Compatibility boundary: only
palee_schema: 1is supported. A v2 (or foreign) hot.md isschema-invalid, sosession startrebuilds it from the newest confirmed session rather than trusting derived state it cannot interpret — matchingmemory_design.md's rule that a missing or invalid hot.md is rebuilt, and ADR-0005's stance that unknown schema versions are not silently consumed.
Consequences
Positive
- The derived file's schema finally has a single read-side owner; adding a hot.md field touches
memory.tsandtypes.ts, not six scattered parsers. session startno longer trusts a v2 hot.md whose fields it may misinterpret; it self-heals via the documented rebuild path.- Callers keep their deliberately different policies — the accessor is classification, not a hidden session-policy object.
Negative / Tradeoffs
session startrebuilds a hot.md that a hypothetical future schema version wrote. That is intended (rebuild is lossless: hot.md is derived from confirmed sessions), but the rebuild discards body text of the invalid file. Characterization tests pin the rebuild path so the tradeoff stays visible.
Alternatives Considered
- Keep the pre-accessor truthiness check (behavior-preservation only):
- Why Rejected:
palee_schema: 2classified asok, lettingsession startskip the rebuild the design doc mandates for invalid files. Preserving a bug through a refactor was worse than fixing it; the fix is pinned by its own tests.
- Why Rejected:
- Consume-and-upgrade unknown schema versions on read (ADR-0005's
migrate --fixpattern, applied to hot.md):- Why Rejected: read-path mutation violates the accessor boundary and the read-only idempotency invariant ADR-0005 established for read commands. hot.md is derived and rebuildable; self-healing via rebuild needs no upgrade path.
- Move session policy into the accessor (return e.g. a "shouldRebuild" verdict):
- Why Rejected: collapses deliberately different caller policies into one, exactly what #130 flagged as the original smell.
