Phase 1 REVIEW.md WR-08: `FileRotatingWriter` relied solely on wall-
clock `Date()` for day-boundary rotation and ran retention GC only as
a side effect of rotation. Two failure modes:
1. Clock jumping ±N days (NTP correction, manual change) would
rotate without signal — operators would see log files that
interleave pre/post-adjustment timestamps with no warning.
2. An idle app (no log write crossing midnight for >7 days) would
never GC stale files.
packages/Logging/Sources/JarvisLogging/FileRotatingWriter.swift
- `rotateIfNeeded()` computes the day-delta between the previous
window and the new day. When `|delta| > 2` emit an `.notice`-level
fault via os.Logger naming the skew magnitude. Rotation still
happens normally — the log just flags the unusual trigger.
- New `runRetentionGC()` dispatches `gcOldFiles` on the serial
queue, independent of rotation. Callers can invoke at shutdown or
on a periodic heartbeat.
packages/Logging/Sources/JarvisLogging/FileLogHandler.swift
- Public `runRetentionGC()` forwards to the writer so App-layer
callers can trigger GC without reaching into internals.
All 14 logging tests still pass.
- JarvisLogChannel: four cases (agent, tools, ui, system)
- JarvisLogHandlerFactory.bootstrap wires MultiplexLogHandler that
fans out to FileLogHandler + OSLogHandler (D-17)
- FileLogHandler applies Redact.apply once (S-4 single call site)
- OSLogHandler does NOT redact (os.Logger handles %{private}@ at
system layer; callers redact untrusted input before logging)
- Redact covers exactly 5 patterns: sk-ant-, Authorization:Bearer,
sk- (OpenAI), AKIA, ghp_ (D-20, OBS-06); Anthropic branch ordered
before OpenAI so sk-ant- consumes the full prefix
- FileRotatingWriter: lazy midnight rotation via TestDateProvider-
injectable DateProvider; serial DispatchQueue; retain-7 GC on
rotate (D-18)
- LogPaths.channelDirectory = ~/Library/Logs/Jarvis/
- Swift 6 strict: ISO8601DateFormatter.jarvisShared uses
nonisolated(unsafe) since the formatter is documented thread-safe
after configuration but not Sendable-annotated by Apple
- OSLogHandler uses fully-qualified Logging.Logger to disambiguate
from os.Logger (both imported)
- Tests: 13/13 pass (7 Redact + 2 FileLogHandler rotate/retain +
4 bootstrap/factory/channels)
- Deletes Plan 01 placeholders
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>