writeonce/docs/superpowers/plans/2026-08-01-log-watcher-sample.md
shoney.arickathil d7304f23ec docs: status board at docs/00-status.md; gap-closure spec applied; recover lost doc
- Board renamed docs/plan/00-kanban.md -> docs/00-status.md and rebuilt: ▶ NEXT
  PLAN pointer (iteration 4 — emitter, corpus, `woc build`) then six buckets —
  stories, in progress, done, pending, discarded, learnings. It covered only the
  Rust runtime before, so the whole OOP track was invisible. All 16 inbound refs
  repointed; `Kanban:` banners renamed to `Status:`.
- New discarded.md (settled rejections with reasons: inheritance, `abstract`,
  Money/SKU/Float, Dynamic/cast/macro/extern, AOT-to-C, Menhir, shared engine
  state) and learnings.md (plumbed≠enforced, vacuous goldens, exit-0-wrong-
  output, malloc-path ASan trick, deferred checks that never reach the VM).
- RECOVERED docs/plan/exploration/blue-green-vm/00-vision.md — gone from disk,
  never committed (gitignored path), cited by five docs incl. principle 12.
  Root cause was broader: all seven forward-roadmap plans in
  docs/superpowers/plans/ were untracked and ignored, on one disk only. Dropped
  the docs ignore rules with a do-not-re-add note; added __pycache__/*.pyc.
- Repaired broken links across docs/, 270 -> 36: fixes a regression from the
  earlier reference/ -> .dev/reference/ move (relative paths at ../../ and
  deeper were skipped), plus depth and reorg drift. The 36 residual point at
  content that does not exist and need decisions, not paths.
- New spec docs/superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md,
  applied: `and`/`or` verdict row; Part 3 gains `env` (six modules), swaps
  time.mono for iso/local, adds 22 bare core builtins; throw/time.mono/is cut
  (0 uses in the sample). Plan 8: Task 2 gains and/or, Task 5 drops throw,
  abstract+`is` task deleted, 8/9 renumber to 7/8. Plan 9 gains core builtins.
  Plan 10 gains the 307 -> 0 diagnostic gate. WO-E205 re-filed unreachable-by-
  design. types.ml header drops its false satisfaction-set claim. 00-code-
  review.md reduced to a stub — its rival Phase 1-4 roadmap retired.
2026-08-10 23:42:26 +02:00

10 KiB
Raw Blame History

log-watcher .wo Sample Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Style rule (user convention): concept, reason, and required behavior in words only; the executor writes the code.

Goal: Plan 10 — re-express ~/projects/log-watcher in .wo at docs/examples/log-watcher/, file-for-file, ported test fixtures passing — the systems track's acceptance workload (spec criteria 4–5).

Architecture: Plan 10 of the roadmap, the proof plan. Depends on plans 8 (language) and 9 (stdlib) complete. No new language or runtime features may land here — a task that cannot express its file has found a defect in plans 8/9 and stops (that feedback loop is this plan's purpose; the repo pattern "samples force the grammar" runs in verification direction now). The Haxe original at ~/projects/log-watcher/src/ is the behavioral reference; its test suite's cases (test/TestMain.hx) are the fixture source. The port is behavior-faithful, not line-faithful — .wo idioms (optionals over sentinel values, records, switch expressions, RAII handles) where they read better, with the README mapping table recording every deliberate divergence.

Tech Stack: .wo only, plus corpus fixtures. The MCP scope is the original's hand-rolled subset: stateless Streamable-HTTP, tools-only, no SSE — matching docs/plan/15-mcp-streamable-http.md's neighborhood but implemented in-sample over net.

Global Constraints

  • All track constraints carry over (no commits — drafts to .dev/commit.md; docs under docs/; sample keeps only an orientation README beside code).
  • No new features in this plan — expressiveness gaps stop the task and report against plans 8/9.
  • Behavior parity is fixture-defined: every ported fixture states which Haxe test case it mirrors; divergences (improvements included) are README-tabled, never silent.
  • The pure-core discipline is preserved: tail state machine, cron math, and MCP handle stay socket-free and clock-injected, exactly like the original — testability was its best design decision.
  • Acceptance = spec criteria 4 and 5: empty "could not express" column; live silent-death detection on a real tempfile.

File Structure

docs/examples/log-watcher/
  README.md            mapping table: .wo file ↔ .hx sibling ↔ divergences ↔ could-not-express
  main.wo              subcommand dispatch, config decode (Task 5)
  logtail.wo           TailState record + bounded tail poll (Task 1)
  watcher.wo           quiet-period alert state machine (Task 1)
  cron.wo              cron.d parse + next-fire (Task 2)
  probes.wo            flock/pgrep probes as static fns (Task 3)
  supervisor.wo        tick loop, scheduled/active watches, detections sink (Task 3)
  mcp.wo               typed records, pure handle(), serve loop (Task 4)
  tools.wo             the MCP tool implementations over fs (Task 4)
tests/corpus/sample-logwatcher/   ported fixtures per task

Task 1: logtail.wo + watcher.wo — the tail state machine

Concept & reason: the heart of the original: TailState (offset, inode, last level, last-newline clock) as a typedef record; poll semantics ported exactly — first-sight starts a bounded chunk before EOF, inode change or shrink = rotation restart, burst jumps to tail, torn final line held back, complete lines classified by level prefix (the relaxed timestamp-aware rule the original converged on). watcher.wo layers the alert rule: last entry error + quiet period elapsed → alert transition. Both take injected clocks (now parameters) — the original's testability discipline. Divergence expected and tabled: ?TailState and ?stat optionals replace the -1-inode and exists-flag sentinels.

  • Port fixtures from the Haxe suite's tail/watcher groups: first-sight window, rotation by rename, truncate restart, torn-line holdback, level classification incl. timestamped lines, quiet-period alert timing.
  • Write the two files; fixtures green.
  • Record commit draft: docs(examples): log-watcher port — logtail/watcher (records, bounded read_at polls, rotation by inode, torn-line holdback, injected clocks); tail fixture group green.

Task 2: cron.wo — cron.d parsing + next-fire

Concept & reason: the original's Cron.hx: parse /etc/cron.d-format entries (five-field schedules, user column, command with >> logfile redirection extraction — the zero-config trick that derives what to watch), collapse same-log entries, compute next-fire from a schedule list. Unreadable directory reports as a skipped entry, never a throw (the production fix the original carries — preserved via ? returns). Switch expressions over field patterns replace the original's if-chains where clearer (tabled divergence).

  • Port fixtures: schedule parsing edges (steps, ranges, lists, weekday names), redirection extraction, same-log collapse, next-fire across day/week boundaries, unreadable-dir skip.
  • Write the file; green.
  • Record commit draft: docs(examples): log-watcher port — cron.d parse (redirection-derived watch list, unreadable-dir skip as data), next-fire math; cron fixture group green.

Task 3: probes.wo + supervisor.wo — the daemon loop

Concept & reason: probes as static fns over proc.run — flock's exit-1-means-held with exists-guard, pgrep's exit-0-means-alive, every unknown code falling in the safe direction (the original's comment-documented contract, now in the README table). The supervisor: the single-threaded tick loop verbatim — rescan on interval, pre-fire lock probes with the PROBE_LEAD constant, watch activation/completion, service watches with alert-transition detections appended as JSONL through fs.append, all clock-injected. The daemon run() wraps tick in the while !env.stopping() { tick; time.sleep } idiom — the original's while(true) improved by the shutdown flag (tabled).

  • Port fixtures: probe exit-code table; supervisor tick scenarios (activation at fire, skip-locked window, completion pruning, rescan on dir change, detection line shape).
  • Write both files; green.
  • Record commit draft: docs(examples): log-watcher port — flock/pgrep safe-direction probes, supervisor tick loop (lock-lead probes, JSONL detections, stopping-flag daemon idiom); supervisor fixture group green.

Task 4: mcp.wo + tools.wo — the MCP server

Concept & reason: the crown piece: hand-rolled MCP-over-HTTP in .wo. Typed request/response records; handle(req) -> resp stays a pure function — auth-first Bearer check, method/path/size gates, JSON-RPC envelope (initialize/ping/tools-list/tools-call, notifications answered 202), tool dispatch returning isError results for model-recoverable failures — all decoded/encoded through typed json records (the Dynamic-free rewrite is the port's most instructive diff). The serve loop: net.listen on 127.0.0.1, one request per connection, read with the body cap, write with byte-length framing (the original's UTF-8 lesson holds by construction — lengths are byte lengths in the stdlib). tools.wo implements the tool subset that needs only shipped capability: list_logs, tail_log, search_log (bounded windows over fs.read_at); the sqlite-backed minilog tools are OUT — tabled as "expressible when the DB track's in-RAM SQL lands", not a could-not-express row (the spec scoped embedded SQL out).

  • Port fixtures from the MCP test group: envelope cases (auth 401, wrong method 405, oversized 413, parse error -32700, unknown method -32601, notification 202), tool-call round-trips, socket-level smoke (scripted client, one connection).
  • Write both files; green.
  • Record commit draft: docs(examples): log-watcher port — MCP subset in .wo (pure handle() over typed json records, Bearer auth, tools list/tail/search over fs), net serve loop; envelope + socket fixtures green.

Task 5: main.wo + README + acceptance

Concept & reason: close the loop. main.wo: the three subcommands — watch (single-watcher poll loop), run (supervisor + optional config), mcp (config + env-fallback API key, required-field errors exit 1 with usage) — config decoded via json.decode as into the config record, usage text on anything else. The README mapping table: every .wo file, its .hx sibling, tabled divergences, and the could-not-express column — acceptance demands it empty (criterion 4). The live test (criterion 5): a scripted scenario starts the built sample in watch mode against a tempfile, feeds timestamped lines ending in an error, waits past the quiet period, asserts exactly one detection — the original's measured behavior, reproduced. Acceptance also gains a diagnostic-count gate: woc docs/examples/log-watcher emits 307 diagnostics today (167 WO-E101 + 140 WO-E207 across 7 files) — the pre-port baseline — and that count must fall monotonically from iteration 5 onward, reaching exactly 0 here. just oop-accept gains the sample build + fixture groups + the live scenario + the diagnostic-count check; kanban and the systems spec get their shipped-status notes.

  • Write main.wo + README table; port config-loading fixtures (defaults, partial config, missing-key mcp errors).
  • Wire the live scenario + gate; run acceptance: criteria 4 and 5 checked against the spec, plus the diagnostic-count gate (307 → 0).
  • Record commit draft: docs(examples): log-watcher port complete — main.wo subcommands + typed config, README mapping table (could-not-express: empty), live silent-death scenario in oop-accept, diagnostic count 307 to 0; systems-track criteria 4-5 checked.

Plan self-review notes

  • Spec coverage (Part 4, criteria 4–5): all five .hx→.wo mappings from the spec's table have tasks; the pure-core discipline, the README table, and both acceptance criteria are explicit task outputs. The minilog/sqlite tools exclusion matches the spec's out-of-scope list and is recorded as scoped-out, not inexpressible. Task 5 also carries the gap-closure amendment's diagnostic-count gate (307 → 0, recorded 2026-08-10).
  • Feedback-loop honesty: the no-new-features constraint plus stop-on-gap rule makes this plan the verification instrument for plans 8/9 — its failure mode is a defect report, not a workaround.
  • Order rationale: pure cores first (tail, cron) — testable without any daemon; probes/supervisor next (compose them); MCP after json/net are proven by earlier tasks; main last, wiring everything.