Skip to content

PRD-234 — Cross-OS journal source

Status: ✅ done (2026-05-08) · completion_pct: 100 · latest_log_id: 15 · 5 phases shipped across 2 repos · daemon tags v0.35.0 / v0.36.0 / v0.37.0. The full PRD lives at n1-cloud/docs/prd/PRD-234-2026-05-08-rename-windows-tab-journal-cross-os/PRD-234.md .

The box-detail page in n1-cloud/packages/web/src/features/boxes/pages/BoxPage.tsx previously exposed ?tab=windows rendering a WindowsEventsTab component that read the PRD-136 windows_event Cloudflare Analytics Engine dataset — populated exclusively by the n1-cloud-daemon’s n1-cloud-daemon/internal/wintelemetry/ ETW listener.

Two surface-level consequences justified the rename:

  1. Operator-visible asymmetry. The “Windows” tab label was OS-specific even though most of the surrounding chrome (composite-health badge, capability install, oracle drift) is OS-agnostic. Linux operators didn’t see the tab; Windows operators did.
  2. Pipeline-internal naming drift. The PRD-136 daemon → AE pipeline (windows_event dataset, WINDOWS_EVENT_AE binding, subscribeWindowsEvents() ETW listener, api-worker route /api/realtime/windows-events) was structurally identical to what a Linux journalctl follower / macOS log stream follower / FreeBSD tail /var/log/messages follower would need — but the names anchored the write-path to the Windows-only origin.

Hard-cut rename — no ?tab=windows redirect — landed across five PRs (Phase 0 + A/B/C/D), each running the greploop pre-merge gate per PRD-0/DO-23 .

The TypeScript zod schema lives at n1-cloud/packages/types/src/journal-event.types.ts . The Go struct in the daemon at n1-cloud-daemon/internal/wintelemetry/types.go mirrors the JSON tags byte-for-byte; its name WindowsEventFrame is retained on purpose to avoid churn in dependents — only the TS module was renamed in PRD-234. New cross-OS callers use JournalEventFrame in TypeScript and wintelemetry.WindowsEventFrame{EventClass: ClassJournal, …} in Go.

Per-OS dispatch (build-tag, not runtime probe)

Section titled “Per-OS dispatch (build-tag, not runtime probe)”

The daemon’s internal/journal/ package uses Go build tags so each binary ships with exactly one source path linked in:

Every per-OS source runner uses exec.CommandContext(ctx, …) (which installs an internal kill-on-cancel goroutine) and defer cmd.Wait() to reap zombies. No manual go func() { <-ctx.Done(); cmd.Process.Kill() }() — that was Greptile’s P1 finding on Phase B’s PR #2 (the manual goroutine leaked one entry per restart cycle when the child exited naturally before ctx cancelled). The fix landed at commit n1-cloud-daemon/internal/journal/journalctl_linux.go:26 with the rationale comment future agents will hit when they re-add the redundant goroutine.

PhasePRMergeDaemon tag
0 (FreeBSD type-system widening + raise) n1-cloud#128 f61ff19d—
A (web tab rename) n1-cloud#134 f861fa87—
B (control plane + Linux journal) n1-cloud#138 + n1-cloud-daemon#2 0d26575a + 1267511v0.35.0
C (macOS Apple Unified Logging) n1-cloud-daemon#3 5d64250v0.36.0
D (FreeBSD MultiSource: tail + dmesg) n1-cloud-daemon#5 b032b31v0.37.0

Every PR cleared greploop with Confidence Score: 5/5 + zero unresolved review threads before squash-merge. No --admin. Notable findings fixed inside the loop:

  • Phase B (n1-cloud-daemon): kill-goroutine leak in runJournalctl + runSyslogTail (P1) + UTF-8-safe truncation on the raw-line cap.
  • Phase B (n1-cloud): buildJournalEventsSql was outside the try block in journal.lib.ts — a malformed ULID boxId raised RangeError and leaked as HTTP 500 instead of returning a structured warning. Moved inside the try (P1).
  • Phase C: Apple’s NDJSON timestamp layout uses variable-precision fractional seconds (.999999, not .000000) — initial parse failure rate was ~30% on test data. Fixed at n1-cloud-daemon/internal/journal/decode_darwin.go . Also added defer cmd.Wait() to avoid zombie log stream processes on shutdown (P1).
  • Phase D: dmesg -w on FreeBSD replays the entire kernel ring buffer on every restart (no -n 0 equivalent). Mitigated via a 500 ms quiet-period drain at n1-cloud-daemon/internal/journal/dmesg_freebsd.go matching the from-now semantics of Linux journalctl -f -n 0 + macOS log stream.

Verbatim from n1-cloud/docs/prd/PRD-234-2026-05-08-rename-windows-tab-journal-cross-os/PRD-234.md .

  • README description blocks for PRD-234 and PRD-235 were re-authored from the conversation summary + worktree README content after the original raise files were lost (transcript-loss recovery; see L-5 in the implementation log if filed). The substantive content (path-fork answers, locked DOs/DONTs, phased plans) is anchored verbatim from the summary.
  • AE dataset rename windows_event → journal_event chosen over additive journal_event + retain-windows_event. Bootstrap-mode + No deprecations tolerated; PRD-136 has no consumer outside the renamed JournalTab surface in this PRD.
  • Linux non-systemd fallback to tail -F /var/log/messages chosen over rejecting non-systemd distros. Alpine + Gentoo OpenRC remain bookable Linux variants; failing-loud here would be friction without justification.
  • No metric / event-volume estimate in this PRD’s Costs lens — AE pricing is per-row + per-byte, and the current windows_event dataset is empty in production (verified via wrangler analytics-engine sql --query "select count(*) from windows_event"); will be confirmed during Phase B rollout.

The completion gate at n1-cloud/docs/prd/PRD-234-2026-05-08-rename-windows-tab-journal-cross-os/PRD-234.completion-gate.md tracks five rows still ⏳ (audit-trail follow-ups, not blockers per AGENTS.md “When a PRD is Done”):

  • R2 / R3 / R4 — per-OS production smoke (Linux + macOS + FreeBSD) once live boxes are accessible. The acceptance query is wrangler analytics-engine sql --query "select count(*) from journal_event where blob3='<os>' and timestamp > now()-INTERVAL '5' MINUTE" returning ≥1 within 60s of n1d start.
  • E2 — Worker-side AE-shape regression test on JOURNAL_EVENT. Wire shape today is locked by the zod schema in journal-event.types.ts which the api-worker route validates inbound, but a dedicated regression test would catch silent schema drift.
  • F2 — cross-OS unfiltered-log volume (Greptile flagged unfiltered log stream ingestion on macOS as a future cost concern; journalctl -f has the same property on Linux). Raise a separate PRD if AE row volume becomes operationally expensive.

PRD-235 ships native FreeBSD pkg(8) install paths for the 69 platform-seeded capability_catalog rows. Until PRD-235 lands, FreeBSD boxes can be booked / provisioned / SSH’d / rendered through every UI surface, but capability deploys return the dispatcher’s fail-loud HTTP 422 — that’s the right posture per PRD-234/DO-8.