ADR-0006: Phase 1 Concurrency Resilience, Storage Layer Isolation & Session Duration Tracking
Status
Accepted
Context
As PALEE CLI evolves into a daily driver for intense study workflows, real-world usage patterns introduced several concurrency and data integrity challenges:
- Storage Leaks in CLI Handlers (#86, #90): Direct invocations of
fs.unlinkSyncandfs.mkdirSyncinside command handlers (src/cli/session.ts,src/cli/roadmap.ts) bypassed storage boundary checks, creating path traversal risks and potential uncaught exceptions. - Review TOCTOU Race Windows (#87): Spaced repetition reviews calculate new SM-2 intervals based on in-memory topic frontmatter read when the command starts. If an external editor or synchronization daemon modified the target topic file while the user was recalling the answer, writing back the result risked overwriting those changes without conflict detection.
- Session Timing Inaccuracies (#88): Learning sessions recorded placeholder or unverified elapsed times rather than tracking true study durations calculated between actual start timestamps and session completion.
- All-or-Nothing Roadmap Ingestion (#89): Ingesting multi-topic roadmaps failed entirely if a single topic note contained corrupt frontmatter or suffered an I/O glitch, preventing valid topics in the same batch from importing.
- Non-Deterministic Cache Leaks & UI Inconsistencies (#90, #91): The
FileCachecontainedNODE_ENV !== 'test'bypasses that caused divergence between test runs and production behavior. Mastery outputs varied between floating-point decimals (0.45) and formatted percentages.
Decision
We implemented a comprehensive Phase 1 concurrency resilience and storage isolation architecture:
1. Storage Boundary Isolation & Public Facade (src/storage/index.ts)
- Re-exported all persistence functions and types through
src/storage/index.ts. - Replaced all raw filesystem calls in CLI commands with dedicated storage boundary helpers:
ensureVaultDirectory(vaultPath, targetPath): Validates vault path boundaries and prevents symlink escape attacks before creating directories.resetHotMemory(vaultPath): Safely resets.palee/hot.mdduring reinitialization.deleteTopicDrafts(vaultPath, topicId)&deleteSessionNote(vaultPath, targetPath): Encapsulates note unlinks with directory boundary validation preventing file deletions outside.palee/sessions/.getTopicDrafts(vaultPath, topicId): Discovers topic-associated active draft checkpoints.
2. Elimination of Review TOCTOU Race Windows (src/cli/review.ts)
- In
reviewCommand, re-read the topic note from disk immediately prior to invokingatomicWrite(). - Compute a fresh SHA-256 content fingerprint (
computeFingerprint(freshContent)) and verify thatfreshFingerprint === initialFingerprint. - If modified concurrently during the recall prompt, immediately trigger Optimistic Concurrency Control (OCC) conflict handling, cleanly setting
process.exitCode = 4.
3. Resilient Multi-Topic Roadmap Batch Processing (src/cli/roadmap.ts)
- Enclosed per-topic note reading, parsing, and atomic writes inside a per-topic
try/catchloop withindoImport(). - Corrupt notes or file errors increment
failed++and emit clear diagnostic errors while allowing valid topics in the batch to continue importing. - Established deterministic exit code semantics:
0: All topics imported successfully (failed === 0).1: Partial batch failure (failed > 0).4: OCC conflict during atomic write.
- Refactored helper function declaration order above call sites for linear control flow.
4. True Session Duration & 3-Tier Timestamp Recovery (src/cli/session.ts, src/storage/memory.ts)
- Persist
started_atin.palee/hot.mdfrontmatter and draft checkpoint files (.palee/sessions/DRAFT-S-*.md). - In
palee session end, recover initialstarted_atvia a 3-tier algorithm:- Tier 1: Earliest
started_atfrom matching draft checkpoints for the topic. - Tier 2:
started_atfrom.palee/hot.mdifactive_topicmatches. - Tier 3: Current instant
ended_atas fallback.
- Tier 1: Earliest
- Calculate actual elapsed study time:
- Persist
started_at,ended_at, andduration_minutesinto final session notes.
5. Deterministic Cache Invalidation & UI Hygiene (src/storage/cache.ts, src/cli/*.ts)
- Removed
NODE_ENV !== 'test'bypasses fromFileCache, guaranteeing deterministic cache invalidation and 2,000 ms unsettled horizon checks across all environments. - Standardized mastery display across all CLI commands (
next,plan,progress,dashboard,review) to percentage format with 1 decimal place (XX.X%). - Aligned dashboard ASCII boxes to 62-character width.
Consequences
Positive
- Fault-Tolerant Batch Operations: Vault curriculum imports do not fail completely due to an isolated corrupted note.
- Zero Race Window on Active Recall: External edits made during SM-2 reviews are protected from overwrite collisions with exit code
4. - Accurate Study Analytics: Session notes record real elapsed study duration rather than placeholder timestamps.
- Strict Storage Layer Encapsulation: CLI handlers are insulated from raw filesystem mutations and path traversal vulnerabilities.
- Deterministic Testability: Cache behavior in unit and integration test suites matches production runtime identically.
Negative / Tradeoffs
- Re-reading topic notes immediately prior to review writes adds a minimal microsecond disk I/O step before atomic commit.
Alternatives Considered
- Global Process Mutex for Reviews:
- Why Rejected: Serializes unrelated vault operations and fails to protect against edits made by external tools (Obsidian, sync daemons) that do not share the Node.js mutex.
- Direct CLI
fs.unlinkSyncCalls:- Why Rejected: Bypasses path boundary validation and leaks raw filesystem error handling into user-facing CLI code.
