Slice 3 of dev-overlay-end-to-end. Adds a fourth DevOverlay tab that
tails every log record from every Jarvis subsystem in real time —
agent, voice, vision, memory, MCP, bus, replay, system — without
polling files or relying on Console.app.
How it works:
1. `LogBroadcaster` (new) — process-wide actor with a 500-line ring
buffer + subscriber set. Borrowed shape from
OrchestratorEventBroadcaster so the project's three broadcasters
share a mental model. New subscribers replay the buffer in order,
then go live.
2. `BroadcastLogHandler` (new) — swift-log LogHandler conformance.
Formats each log call into a `LogLine` and schedules an unstructured
`Task { await LogBroadcaster.shared.publish(...) }` so the logging
caller never blocks on actor isolation. Synchronous handler contract
preserved; ordering is best-effort under heavy load.
3. `LoggingBootstrap.make(label:)` now returns
`MultiplexLogHandler([file, os, broadcast])` — the new handler is
purely additive. File rotation + os.Logger streaming unchanged.
4. `DevOverlayLogsView` (new) — fourth tab in the overlay. Subscribes
to `LogBroadcaster.shared` on appear, caps at 1000 lines, renders
monospaced. Per-channel filter (Menu w/ checkboxes), per-level
filter (debug → critical), substring search, Pause/Resume button
(Cmd+P), auto-scroll-to-bottom while not paused. Color-coded by
level (warn=orange, error/critical=red).
Tests:
- 5 new LogBroadcasterTests covering publish/cap/replay/live/handler.
- swift test --package-path packages/Logging → 22/22 pass.
- swift test --package-path packages/DevOverlay → 14/14 pass.
- bash scripts/check-app-builds.sh → PASS.
- 18/18 boundary gates green.
Redaction note: file handler still routes through Redact.apply for
disk writes; the broadcaster sees raw messages because the DevOverlay
is a developer-only in-process surface. If this ever becomes
user-facing, route through Redact before publishing.
ConfirmingToolDispatcher needs to emit a WARNING log line on broker timeout
(AGENT-11). The plan specifies the log lands in JarvisLogChannel.mcp, but
that case did not yet exist in the enum.
Adds .mcp (rawValue 'mcp') to the channel enum and updates the two
case-count tests (ChannelTests + LoggingTests) to expect 8 cases.
- ReplayLog actor: opens replay.db with WAL pragmas + DDL + meta seed; beginSession/startTurn/record/endTurn/close. Writes batched in 50ms window OR 64-event chunks. endTurn appends a turn_end event, flushes, UPDATEs ended_at + stop_reason, then PRAGMA wal_checkpoint(TRUNCATE) for the per-turn fsync. Failures logged via JarvisLogChannel.replay; never propagated.
- OrphanDetector actor: opens its own connection, increments meta.crash_count, runs the OBS-07 query (ended_at IS NULL AND turn_id NOT IN (turn_end events)), stamps each orphan with stop_reason='orphan_recovered' + recovery_marker='detected_at_launch:crash_count=N'.
- 12 new tests: L1-L8 (session/turn/buffering/window/chunk/large-blob/nothing-masked) + O1-O4 (orphan vs non-orphan, race window, crash_count increment, end-to-end SIGKILL parity).
- Adds JarvisLogChannel.replay + .devoverlay (additive — Logging test updated to assert 7 cases). The full Task 3 channel-tests file lands next commit.
Phase 1 REVIEW.md WR-01 (OBS-06): `FileRotatingWriter` used `try?` on
both `FileHandle(forWritingTo:)` and `handle.write(contentsOf:)`, so a
disk-full, permission-revoked, or FD-evicted write silently dropped the
line with no signal. OBS-06 reads as "rotation must not lose data".
packages/Logging/Sources/JarvisLogging/FileRotatingWriter.swift
- Add `droppedLinesThisWindow` counter (serial-queue-local, no lock).
- Add `warnedAboutOpenFailureThisWindow` flag to emit a one-time
os.Logger fault per rotation window (prevents spam if the failure
persists for hours).
- `append()` now explicitly increments the counter on both the
nil-handle path and the `handle.write` throw path, then logs a
fault via `os.Logger` (subsystem `com.koftwentytwo.jarvis`,
category `logging.file`) so failures are visible in Console.app
even when the file log itself is the thing that's broken.
- `rotateIfNeeded()` emits an `.error`-level summary when the
just-closed window lost lines, then resets both flags for the new
window.
- Add `droppedLinesSnapshot()` test-only accessor that reads the
counter through the serial queue (no TSan race).
packages/Logging/Tests/JarvisLoggingTests/FileLogHandlerTests.swift
- New `test_dropsLinesWhenHandleUnavailable`: points the writer at a
path that is a regular file (so directory creation and file-open
both fail), appends three lines, asserts `droppedLinesSnapshot() == 3`.
Owner change: project will be owned by KofTwentyTwo. Bundle ID updates
propagate to three ${PRODUCT_BUNDLE_IDENTIFIER} targets, the Keychain
service name (SEC-01 / D-10), the OSLog subsystem identifier, and the
FileRotatingWriter DispatchQueue label. pbxproj regenerated via
xcodegen generate.
Forward-looking planning docs (STATE, REQUIREMENTS, PATTERNS, VALIDATION)
updated to match. Historical records (completed PLAN.md / SUMMARY.md /
RESEARCH.md / audits / CONTEXT / DISCUSSION-LOG) deliberately left as-is
to preserve the audit trail of the original naming decision.
Verified: 49 SPM tests pass (4 Keychain + 15 Config + 13 Logging + 17
Shell), xcodebuild Debug build succeeds with full codesign pipeline.
Decision logged in STATE.md Key Decisions (Locked 2026-04-22).
- 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>
- packages/Keychain — zero SPM deps (Security.framework at link time only)
- packages/Logging — depends on apple/swift-log 1.5.3+; library product is
named JarvisLogging to avoid collision with swift-log's own Logging module
- packages/Config — depends on Keychain + Logging (product JarvisLogging)
- packages/Shell — depends on Config + Logging; links AppKit,
ServiceManagement, IOKit, Carbon for hotkey/launch-at-login surfaces
Every target (main + test) opts into .swiftLanguageMode(.v6) per D-04 so
data-race errors are compile-time failures, not warnings.
Placeholder sources (one per package) plus one trivial XCTest per package
per D-03 so each package builds standalone and has a passing test. Real
sources land in Plan 02 (Keychain/Config/Logging) and Plan 04 (Shell).
Bus/LLM/MCP/Voice/Memory packages NOT pre-created per D-01 — they arrive
in their owning phases (P2/P4/P5/P6/P7) to keep rebuild scope tight.