docs: reprioritise to log-watcher-executable only; update stories and plans
Every remaining item is now traced to a measurement on the sample; anything the sample does not exercise is deferred by name with the measurement that says so. - new plan docs/plan/compiler/2026-08-14-logwatcher-executable.md — six tasks between "it runs" and "you can leave it running": the ownership pass learning stdlib return types (>1 MB leaked in 8s of `run` mode, one fs.read_all result), dropping a projected temporary (`for e in parse_dir(d).entries` leaks the shell per rescan), the runtime's own argv container (128 B every run), honouring the stop signal in blocking calls (a server in accept ignores SIGTERM), closing accepted connections (net.close exists, unused), and a soak that would have caught all of it. Opens with the measured starting point and closes with an explicit out-of-scope list - story 7 (log-watcher proof): status banner separating the met compile-and-run half from the executable half, plus a new Given/When/Then — clean SIGTERM exit, zero leaks, flat RSS and descriptors across a soak - story 5: grammar half landed, strictness half deliberately deferred - story 6: landed for the surface the workload uses, with the two lifetime defects it exposed pointed at the new plan - story 7b: recorded as off this workload's path, measured — the sample has no @gc class, 0 RC_INC/RC_DEC against 78 DROPs - 00-status.md: NEXT PLAN is the executable list; story table and in-progress row point at the new plan; deferrals carry their evidence - plan 8 (haxe-parity): banner now says on hold behind the executable plan, and its task states are corrected — Task 5 shipped, Tasks 6 and 7 are half done Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4dbbc48772
commit
7c10df67a3
7 changed files with 287 additions and 29 deletions
|
|
@ -19,29 +19,47 @@ Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **hold*
|
||||||
|
|
||||||
## ▶ NEXT PLAN
|
## ▶ NEXT PLAN
|
||||||
|
|
||||||
**Close the gaps the log-watcher milestone left open.** The acceptance target
|
**Make log-watcher executable — nothing else.** The compile-and-run half of the
|
||||||
of the whole language track — _compile and run log-watcher_ — is **met** as of
|
language track is met (2026-08-14): the sample compiles with zero diagnostics,
|
||||||
2026-08-14: `docs/examples/log-watcher` (1285 lines, 7 files) compiles with
|
`woc build` produces a 106 KB standalone binary, and all three modes work —
|
||||||
zero diagnostics, and the image runs (`wovm lw.wob watch app.log 2 1` tails a
|
`watch` alerts on a live file, `run` schedules a cron.d entry, `mcp` answers
|
||||||
live file, classifies levels and fires `ALERT … last entry is error, quiet for
|
JSON-RPC with all four tools returning `isError:false`. `just log-watcher`
|
||||||
2s`). What remains is the *strictness* half of iteration 5 plus one runtime
|
gates it: 6 checks, 0 failures.
|
||||||
gate, in this order:
|
|
||||||
|
|
||||||
1. **`?T` forced handling** (plan 8 Task 6's diagnostics half, `WO-E211`–`E213`
|
What is left is the difference between "it runs" and "you can leave it
|
||||||
still dead). Optionals are currently **lenient**: `nil` is the zero word, a
|
running", and every item below came from a measurement on the sample itself:
|
||||||
`?T` is usable where `T` is expected, and nothing narrows. The
|
|
||||||
representation and the comparisons are right; the refusals are missing.
|
|
||||||
2. **`pub(read)` write enforcement** — parsed and recorded on the field; the
|
|
||||||
typechecker does not yet refuse a write from outside the declaring class.
|
|
||||||
3. **`using` extensions and `#if` build flags + reject-row diagnostics** (plan 8
|
|
||||||
Tasks 7–8's remainder). Nothing in the workload needs them, so they are the
|
|
||||||
tail of the plan, not a blocker.
|
|
||||||
4. **ASan over the workload** — the corpus is ASan-clean, but log-watcher's own
|
|
||||||
run has never been under the sanitizer, and iteration 4's `gc/held-cycle`
|
|
||||||
leak is still open (see the known-gaps section).
|
|
||||||
|
|
||||||
Plan: [`plan/compiler/2026-08-01-haxe-parity-language.md`](plan/compiler/2026-08-01-haxe-parity-language.md) ·
|
1. **The ownership pass does not know what the stdlib returns** — so a binding
|
||||||
Story slice: [`docs/stories/language-runtime-database/05-language-surface.md`](stories/language-runtime-database/05-language-surface.md)
|
holding a fresh `fs.read_all`/`fs.list`/`net.read`/`json.encode` result is
|
||||||
|
classified Copy and never dropped. Measured: >1 MB leaked in eight seconds
|
||||||
|
of `run` mode, the largest single allocation being one `fs.read_all` result.
|
||||||
|
2. **A projected temporary is never dropped** — `for e in parse_dir(d).entries`
|
||||||
|
keeps the elements (correct) and leaks the record shell, once per rescan.
|
||||||
|
3. **The runtime leaks its own argv container** — 128 bytes in 2 allocations on
|
||||||
|
every run, `main.c`'s `multi Text` of arguments.
|
||||||
|
4. **A stopping program does not stop** — `env.stopping()` sets a flag, but
|
||||||
|
`net.accept`/`net.read` restart on `EINTR`, so a server parked in `accept`
|
||||||
|
ignores SIGTERM and needs `kill -9`.
|
||||||
|
5. **The MCP server never closes an accepted connection** — `net.close` exists
|
||||||
|
and is unused; every request costs a descriptor.
|
||||||
|
6. **Nothing soaks** — every check is seconds long, which is exactly the window
|
||||||
|
where a leak hides. The acceptance script needs a soak mode measuring RSS
|
||||||
|
and descriptors across a real duration.
|
||||||
|
|
||||||
|
Plan: [`plan/compiler/2026-08-14-logwatcher-executable.md`](plan/compiler/2026-08-14-logwatcher-executable.md) ·
|
||||||
|
Story slice: [`docs/stories/language-runtime-database/07-logwatcher-proof.md`](stories/language-runtime-database/07-logwatcher-proof.md)
|
||||||
|
|
||||||
|
**Deferred by name, with the measurement that says so:**
|
||||||
|
|
||||||
|
- Iteration 5's *strictness* half (`?T` forced handling, `pub(read)` write
|
||||||
|
enforcement, `using`, `#if`, reject rows) — it makes the language refuse
|
||||||
|
more; it does not make this program run. Plan 8 stays open for it.
|
||||||
|
- Everything `@gc`: iteration 7b, `set`'s `@gc` retention gap, iteration 4's
|
||||||
|
`gc/held-cycle` leak. The sample declares **no `@gc` class** — 35 classes,
|
||||||
|
none with the gc flag, 0 `RC_INC`/`RC_DEC` against 78 `DROP`s — so none of it
|
||||||
|
can affect this workload.
|
||||||
|
- Iterations 8–12 (shard-actor runtime, database engine, `@table`/query, HTTP
|
||||||
|
layer, fibers, blue-green): unchanged, and unblocked by this plan.
|
||||||
|
|
||||||
Two tracks run in this repo. The critical path is the **language track**:
|
Two tracks run in this repo. The critical path is the **language track**:
|
||||||
iterations 3 → 4 → 5 → 6 → 7, ending at _compile and run log-watcher_. The
|
iterations 3 → 4 → 5 → 6 → 7, ending at _compile and run log-watcher_. The
|
||||||
|
|
@ -62,10 +80,10 @@ that sequences its tasks. Read one, approve, then the next starts.
|
||||||
| 2 | [VM core (`wovm`)](stories/language-runtime-database/02-vm-core.md) | ✅ |
|
| 2 | [VM core (`wovm`)](stories/language-runtime-database/02-vm-core.md) | ✅ |
|
||||||
| 3 | [Compiler front (`woc`)](stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
|
| 3 | [Compiler front (`woc`)](stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
|
||||||
| 4 | [Single binary end-to-end](stories/language-runtime-database/04-single-binary-e2e.md) | ✅ (known gaps below) |
|
| 4 | [Single binary end-to-end](stories/language-runtime-database/04-single-binary-e2e.md) | ✅ (known gaps below) |
|
||||||
| 5 | [Language surface](stories/language-runtime-database/05-language-surface.md) | 🔄 grammar done, strictness open |
|
| 5 | [Language surface](stories/language-runtime-database/05-language-surface.md) | 🔄 grammar done, strictness ⏸ deferred |
|
||||||
| 6 | [Program mode + stdlib](stories/language-runtime-database/06-program-mode-stdlib.md) | ✅ (the surface log-watcher uses) |
|
| 6 | [Program mode + stdlib](stories/language-runtime-database/06-program-mode-stdlib.md) | ✅ (the surface log-watcher uses) |
|
||||||
| 7 | [log-watcher proof](stories/language-runtime-database/07-logwatcher-proof.md) | ✅ compiles and runs |
|
| 7 | [log-watcher proof](stories/language-runtime-database/07-logwatcher-proof.md) | 🔄 **runs; executable in progress** |
|
||||||
| 7b | [Inferred GC + mark-sweep](stories/language-runtime-database/07b-inferred-gc-mark-sweep.md) | ⬜ closes iteration 4's gate |
|
| 7b | [Inferred GC + mark-sweep](stories/language-runtime-database/07b-inferred-gc-mark-sweep.md) | ⏸ off the workload's path (no `@gc`) |
|
||||||
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
||||||
| 9 | [Database engine](stories/language-runtime-database/09-database-engine.md) | ⬜ |
|
| 9 | [Database engine](stories/language-runtime-database/09-database-engine.md) | ⬜ |
|
||||||
| 9b | [`@table`, relations, query](stories/language-runtime-database/09b-table-relations-query.md) | ⬜ needs a spec first |
|
| 9b | [`@table`, relations, query](stories/language-runtime-database/09b-table-relations-query.md) | ⬜ needs a spec first |
|
||||||
|
|
@ -79,7 +97,7 @@ that sequences its tasks. Read one, approve, then the next starts.
|
||||||
|
|
||||||
| Track | Item | Where |
|
| Track | Item | Where |
|
||||||
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||||
| Language | Iteration 5's strictness half — `?T` forced handling, `pub(read)` writes, `using`, `#if` | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
|
| Language | Iteration 7 — make log-watcher executable (leaks, stop signal, fd lifetime, soak) | [executable plan](plan/compiler/2026-08-14-logwatcher-executable.md) |
|
||||||
|
|
||||||
Off-critical-path work is parked by explicit scope directive (2026-08-08).
|
Off-critical-path work is parked by explicit scope directive (2026-08-08).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Haxe-Parity Language Adoptions Implementation Plan
|
# Haxe-Parity Language Adoptions Implementation Plan
|
||||||
|
|
||||||
> **Status: 🔄 in progress** (story iteration 5) — Tasks 1–4 ✅ shipped and review-verified: modules (`use`/`pub`), language surface (`and`/`or`, `${}` interpolation, `const`, break/continue, do-while), switch expressions, typedef records + enum payload variants. Task 5 (try/catch over the trap system) ⬜ started, not landed. Tasks 6 (`?T` forced handling), 7 (statics, `using`, `pub(read)`), 8 (`#if` + reject rows) ⬜. Board: [00-status.md](../../00-status.md)
|
> **Status: ⏸ on hold behind [the executable plan](2026-08-14-logwatcher-executable.md)** (story iteration 5) — the grammar half of this plan is what let the driving workload compile, and the rest of it makes the language *refuse* more rather than making that program *run*, so it waits. Original status follows. — Tasks 1–4 ✅ shipped and review-verified: modules (`use`/`pub`), language surface (`and`/`or`, `${}` interpolation, `const`, break/continue, do-while), switch expressions, typedef records + enum payload variants. Task 5 (try/catch over the trap system) ✅ shipped 2026-08-14 — VM catch frames, expression and block catch arms, the `{code, line, method, msg}` record. Task 6 (`?T`) 🔶 half: the representation, `nil`, comparisons and narrowing-free use all work — the forced-handling diagnostics (`WO-E211`–`E213`) do not exist. Task 7 🔶 half: `static` members and `pub(read)` syntax landed, `using` and the `pub(read)` write check did not. Task 8 (`#if` + reject rows) ⬜. Board: [00-status.md](../../00-status.md)
|
||||||
|
|
||||||
> **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.
|
> **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.
|
||||||
>
|
>
|
||||||
|
|
|
||||||
183
docs/plan/compiler/2026-08-14-logwatcher-executable.md
Normal file
183
docs/plan/compiler/2026-08-14-logwatcher-executable.md
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
# log-watcher Executable Implementation Plan
|
||||||
|
|
||||||
|
> **Status: 🔄 in progress** (story iteration 7) — the sample compiles and its
|
||||||
|
> three modes run; this plan is everything still between "it runs" and "you can
|
||||||
|
> leave it running". Board: [00-status.md](../../00-status.md)
|
||||||
|
|
||||||
|
> **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.
|
||||||
|
|
||||||
|
**Spec:** [`docs/superpowers/specs/2026-08-01-systems-track-design.md`](../../superpowers/specs/2026-08-01-systems-track-design.md) (Part 1 verdict table, normative), amended by [`docs/superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md`](../../superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md).
|
||||||
|
|
||||||
|
**Goal:** `docs/examples/log-watcher` is **executable** — not merely compilable.
|
||||||
|
Each of its three modes runs indefinitely without growing, stops when told to,
|
||||||
|
and ships as one self-contained binary. Nothing else is in scope: every task
|
||||||
|
below exists because a measurement on the sample demanded it, and anything the
|
||||||
|
sample does not exercise is deferred by name in "Out of scope".
|
||||||
|
|
||||||
|
**Architecture:** the compiler front (`compiler/src/`), the VM's ownership
|
||||||
|
tables (`owner.ml` ↔ `emit.ml`) and the runtime's process surface
|
||||||
|
(`runtime/src/main.c`, `sysio.c`). No new language features — the four
|
||||||
|
compiler-side tasks are missing *ownership knowledge*, not missing grammar.
|
||||||
|
|
||||||
|
**Tech Stack:** OCaml stdlib (compiler), C11 libc (runtime), the conformance
|
||||||
|
corpus as regression, `scripts/log-watcher-accept.sh` as acceptance.
|
||||||
|
|
||||||
|
## Where this plan starts (measured 2026-08-14)
|
||||||
|
|
||||||
|
- `woc --emit docs/examples/log-watcher` → **0 diagnostics**, 35 KB image.
|
||||||
|
- `woc build …` → a **106 KB standalone binary** that runs its three modes.
|
||||||
|
- `just log-watcher` → **6 checks, 0 failures** (compile, watch alert, cron
|
||||||
|
schedule, MCP initialize / tools/list / 401).
|
||||||
|
- All four MCP tools answer with `isError:false`.
|
||||||
|
- **Under ASan, both long-running modes leak**: `watch` 128 bytes in 2
|
||||||
|
allocations; `run` over 1 MB across 6 allocations in eight seconds — the
|
||||||
|
1 MiB one is a single `fs.read_all` result.
|
||||||
|
- The sample uses **zero `@gc`**: 35 classes, none with the gc flag, 0 `RC_INC`
|
||||||
|
/ 0 `RC_DEC`, 78 `DROP`s. Deterministic ownership is the whole memory story
|
||||||
|
here, which is why the leaks above are compiler bugs, not collector gaps.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- **The sample is the test.** No new corpus fixtures for this plan (user
|
||||||
|
direction, 2026-08-14); `just oop-e2e` must stay green as a regression, and
|
||||||
|
`just log-watcher` is the acceptance gate.
|
||||||
|
- **No new language surface.** A task that finds itself wanting one has found a
|
||||||
|
defect report against this plan, not a feature — stop and ask.
|
||||||
|
- Every task ends with the sample rebuilt and `just log-watcher` green, and
|
||||||
|
with the ASan measurement re-run so the number moves in writing.
|
||||||
|
- Commits are local only; never push.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
compiler/src/owner.ml stdlib return types; temporary-value drops (Tasks 1, 2)
|
||||||
|
compiler/src/emit.ml the drop sites those tables imply (Tasks 1, 2)
|
||||||
|
runtime/src/main.c argv container lifetime; stop-signal exit (Tasks 3, 4)
|
||||||
|
runtime/src/sysio.c blocking calls observing the stop flag (Task 4)
|
||||||
|
docs/examples/log-watcher/ mcp.wo: close what accept opened (Task 5)
|
||||||
|
scripts/log-watcher-accept.sh the soak check (Task 6)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 1: The owner pass must know what the stdlib returns
|
||||||
|
|
||||||
|
**Concept & reason:** `owner.ml`'s `expr_ty`/`resolve_callee` have no stdlib
|
||||||
|
table — `types.ml` and `emit.ml` each got one, the ownership pass did not. So a
|
||||||
|
binding whose value comes from `fs.read_all`, `fs.list`, `net.read`,
|
||||||
|
`json.encode`, `time.iso` or `proc.run` falls back to the `Scalar "Int"`
|
||||||
|
default, is classified **Copy**, and never gets a scope-end drop. That is the
|
||||||
|
1 MiB leak measured in `run` mode: `parse_file`'s `let content = try
|
||||||
|
fs.read_all(path, FILE_CAP) catch (e) nil` holds a fresh Text nobody frees.
|
||||||
|
The fix is to read the same `Types.stdlib_members` table the other two passes
|
||||||
|
read, including through a `try`'s arms, so the classification matches reality.
|
||||||
|
|
||||||
|
- [ ] Failing measurement first: record the current ASan totals for `watch` and
|
||||||
|
`run` (eight seconds each, clean exit via SIGTERM) so the drop is proven,
|
||||||
|
not assumed.
|
||||||
|
- [ ] Teach the ownership pass the stdlib return shapes; every stdlib member
|
||||||
|
that yields a fresh Text, `multi` or record is Owned at its binding.
|
||||||
|
- [ ] Re-measure: the `fs.read_all` and `fs.list` allocations disappear from
|
||||||
|
both modes' reports; `just oop-e2e` and `just woc-test` stay green.
|
||||||
|
|
||||||
|
### Task 2: A temporary whose field is projected must still be dropped
|
||||||
|
|
||||||
|
**Concept & reason:** `for e in parse_dir(self.cron_dir).entries` compiles to
|
||||||
|
"call, keep the record in a register, read its field, iterate" — and the record
|
||||||
|
itself is never dropped, because the drop tables only track *bindings*, not the
|
||||||
|
anonymous receiver a projection borrows from. The elements stay alive (the loop
|
||||||
|
is correct), the shell leaks, once per rescan. The same shape appears wherever a
|
||||||
|
call result is projected without a `let`. The temporary must be owned by the
|
||||||
|
statement that created it and dropped at that statement's end, after every use
|
||||||
|
of the projection.
|
||||||
|
|
||||||
|
- [ ] Failing measurement: the `run` mode's per-rescan growth over ~60 seconds,
|
||||||
|
with the rescan interval shortened, as the number to beat.
|
||||||
|
- [ ] Give a projected temporary a real owner and a drop at the end of its
|
||||||
|
statement, including when the projection feeds a loop that outlives the
|
||||||
|
expression.
|
||||||
|
- [ ] Re-measure: rescan no longer grows the process; corpus and unit gates
|
||||||
|
stay green.
|
||||||
|
|
||||||
|
### Task 3: The runtime's argv container has no owner
|
||||||
|
|
||||||
|
**Concept & reason:** program mode builds the `multi Text` of arguments in
|
||||||
|
`runtime/src/main.c` and hands it to the entry method, which borrows it. Nobody
|
||||||
|
frees it — ASan reports it on every run (128 bytes in 2 allocations). It is
|
||||||
|
bounded, so it is not the reason a daemon grows, but it is the runtime leaking
|
||||||
|
its own allocation, and it pollutes every future ASan reading of the sample.
|
||||||
|
The runtime owns that container and must release it after the entry returns,
|
||||||
|
before the heap is torn down.
|
||||||
|
|
||||||
|
- [ ] Drop the argument container once the entry method has returned (both the
|
||||||
|
plain `wovm image.wob …` path and the single-binary path).
|
||||||
|
- [ ] `watch` under ASan reports **zero** leaks for a clean exit.
|
||||||
|
|
||||||
|
### Task 4: A stopping program must actually stop
|
||||||
|
|
||||||
|
**Concept & reason:** `env.stopping()` installs SIGTERM/SIGINT handlers that set
|
||||||
|
a flag, and `net.accept`/`net.read` retry on `EINTR` — so a server parked in
|
||||||
|
`accept` never observes the flag and TERM does nothing; only `kill -9` ends it.
|
||||||
|
`watch` and `run` stop correctly today only because they sleep between polls.
|
||||||
|
A service that cannot be stopped is not executable in any operational sense
|
||||||
|
(no clean restart, no deploy, no supervisor integration). The decision to make
|
||||||
|
and record: when a blocking stdlib call is interrupted **and** the stop flag is
|
||||||
|
set, the runtime stops the program rather than restarting the syscall — the
|
||||||
|
exit is the entry's normal one, with the same status a clean `return 0` gives.
|
||||||
|
The alternative (surface the interruption to the source) is rejected here: it
|
||||||
|
would put a trap in the middle of every accept loop the language will ever
|
||||||
|
write, and the shard-actor runtime (iteration 8) replaces these blocking calls
|
||||||
|
with an event loop anyway.
|
||||||
|
|
||||||
|
- [ ] Failing measurement: `mcp` mode ignores SIGTERM and needs `kill -9`.
|
||||||
|
- [ ] Blocking stdlib calls observe the stop flag on interruption; the process
|
||||||
|
exits cleanly, flushing output.
|
||||||
|
- [ ] `just log-watcher` no longer needs `kill -9` in teardown, and the script's
|
||||||
|
hard-kill fallback becomes belt-and-braces rather than the mechanism.
|
||||||
|
|
||||||
|
### Task 5: The MCP server must close what it accepts
|
||||||
|
|
||||||
|
**Concept & reason:** `Mcp.serve` accepts a connection per request and never
|
||||||
|
calls `net.close` — the builtin exists, the sample does not use it. Every
|
||||||
|
request costs a descriptor; a long-lived server dies at the process limit. This
|
||||||
|
is the sample's own bug, and fixing it is in scope precisely because the sample
|
||||||
|
is the acceptance workload. The connection is a value the loop owns for one
|
||||||
|
iteration; it must be closed on every exit path from that iteration, including
|
||||||
|
the malformed-request path that answers 400.
|
||||||
|
|
||||||
|
- [ ] Failing measurement: descriptor count for the server process across a few
|
||||||
|
hundred requests.
|
||||||
|
- [ ] Close the connection on every path out of the serve loop's body.
|
||||||
|
- [ ] Re-measure: the descriptor count is flat.
|
||||||
|
|
||||||
|
### Task 6: Soak — the acceptance a daemon actually has to pass
|
||||||
|
|
||||||
|
**Concept & reason:** every check today is a few seconds long, which is exactly
|
||||||
|
the window in which a leak is invisible. The claim this plan exists to support
|
||||||
|
is "you can leave it running", and nothing verifies it. Add a soak mode to the
|
||||||
|
acceptance script: run each of the three modes under load for a fixed duration,
|
||||||
|
sample RSS and descriptor count at the start and the end, and fail when either
|
||||||
|
grows beyond a stated tolerance. Keep it opt-in (an environment variable or a
|
||||||
|
flag) so the default `just log-watcher` stays fast for the ordinary loop.
|
||||||
|
|
||||||
|
- [ ] Soak the three modes with a stated duration, load pattern and tolerance;
|
||||||
|
report the measured deltas whether it passes or fails.
|
||||||
|
- [ ] Run the soak against an ASan build once and record the result in the
|
||||||
|
status board's known-gaps section.
|
||||||
|
- [ ] `just log-watcher` (fast path) stays green and stays under a minute.
|
||||||
|
|
||||||
|
## Out of scope — deferred by name
|
||||||
|
|
||||||
|
- **`?T` forced handling, `pub(read)` write enforcement, `using`, `#if`,
|
||||||
|
reject-row diagnostics** (plan 8 Tasks 6–8's remainder). They make the
|
||||||
|
language *stricter*; they do not make this program run. Plan 8 stays open for
|
||||||
|
them behind this plan.
|
||||||
|
- **Anything `@gc`**: iteration 7b (inferred GC + incremental mark-sweep),
|
||||||
|
`set`'s `@gc` retention gap, iteration 4's `gc/held-cycle` leak. Measured:
|
||||||
|
the sample declares no `@gc` class and emits no `RC_INC`/`RC_DEC` at all, so
|
||||||
|
none of it can affect this workload.
|
||||||
|
- **json's `Bool` renders as `0`/`1`** and fractional numbers truncate — both
|
||||||
|
documented format consequences; the MCP client the sample targets reads them
|
||||||
|
fine.
|
||||||
|
- **Iterations 8–12** (shard-actor runtime, database engine, `@table`/query,
|
||||||
|
HTTP layer, fibers, blue-green) — unchanged, and unblocked by this plan.
|
||||||
|
|
@ -3,6 +3,18 @@
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
||||||
|
|
||||||
|
|
||||||
|
> **Status (2026-08-14):** the *grammar* half landed — modules, `and`/`or`,
|
||||||
|
> interpolation, `const`, loop control, switch expressions, typedef records,
|
||||||
|
> enum payloads, try/catch, `nil`/`?T`, statics, `pub(read)` syntax, container
|
||||||
|
> literals, `for k, v in m`, and `as` — which is what let the driving workload
|
||||||
|
> compile. The *strictness* half (`?T` forced handling `WO-E211`–`E213`,
|
||||||
|
> `pub(read)` write enforcement, `using`, `#if`, reject-row diagnostics) is
|
||||||
|
> **deliberately deferred** behind
|
||||||
|
> [`plan/compiler/2026-08-14-logwatcher-executable.md`](../../plan/compiler/2026-08-14-logwatcher-executable.md):
|
||||||
|
> it makes the language refuse more, not the program run. Plan 8 stays open
|
||||||
|
> for it.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- The language grows from milestone grammar to a daily-driver surface: the
|
- The language grows from milestone grammar to a daily-driver surface: the
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,18 @@
|
||||||
> Format: `product/story-iteration-template`. Part of
|
> Format: `product/story-iteration-template`. Part of
|
||||||
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
||||||
|
|
||||||
|
|
||||||
|
> **Status (2026-08-14):** ✅ landed for the surface the driving workload uses —
|
||||||
|
> program mode (`fn main(args: multi Text) -> Int`, argv from the runtime, the
|
||||||
|
> return value as the exit code), the text/container builtins, and the OS half
|
||||||
|
> (`fs`, `time`, `env`, `net`, `proc`) with predeclared `Stat`/`TimeParts`/
|
||||||
|
> `Proc` records, plus `json` encode/decode over `.wob` v2 class metadata.
|
||||||
|
> What the workload never calls was not written. Two lifetime defects found
|
||||||
|
> here are being fixed as part of
|
||||||
|
> [`plan/compiler/2026-08-14-logwatcher-executable.md`](../../plan/compiler/2026-08-14-logwatcher-executable.md):
|
||||||
|
> the runtime's own argv container is never freed, and blocking `accept`/`read`
|
||||||
|
> ignore the stop signal.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- writeonce stops being server-only: a project with a free
|
- writeonce stops being server-only: a project with a free
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,17 @@
|
||||||
systems track's acceptance bar: nothing in a real daemon exceeded the
|
systems track's acceptance bar: nothing in a real daemon exceeded the
|
||||||
language.
|
language.
|
||||||
|
|
||||||
|
> **Status (2026-08-14):** the compile-and-run half is **met** — the sample
|
||||||
|
> compiles with zero diagnostics, `woc build` produces a 106 KB standalone
|
||||||
|
> binary, and all three modes work (`watch` alerts, `run` schedules a cron.d
|
||||||
|
> entry, `mcp` answers JSON-RPC with all four tools returning `isError:false`).
|
||||||
|
> The remaining half is **executable**: under ASan both long-running modes leak
|
||||||
|
> (watch 128 B, run >1 MB in eight seconds), the MCP server never closes an
|
||||||
|
> accepted connection, and a server parked in `accept` ignores SIGTERM. That
|
||||||
|
> work is sequenced in
|
||||||
|
> [`plan/compiler/2026-08-14-logwatcher-executable.md`](../../plan/compiler/2026-08-14-logwatcher-executable.md)
|
||||||
|
> and nothing else blocks this iteration.
|
||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- What to achieve?
|
- What to achieve?
|
||||||
|
|
@ -33,6 +44,11 @@
|
||||||
- **when** the iteration closes,
|
- **when** the iteration closes,
|
||||||
- **then** every row names its `.hx` sibling and deliberate
|
- **then** every row names its `.hx` sibling and deliberate
|
||||||
divergences, and the could-not-express column is empty.
|
divergences, and the could-not-express column is empty.
|
||||||
|
- What to achieve? *(added 2026-08-14 — compiling is not running)*
|
||||||
|
- **Given** any of the three modes started under a sanitizer build,
|
||||||
|
- **when** it is signalled to stop after a soak,
|
||||||
|
- **then** it exits cleanly on SIGTERM alone, reports zero leaks, and
|
||||||
|
its resident size and descriptor count are flat across the soak.
|
||||||
|
|
||||||
## Out Of Scope
|
## Out Of Scope
|
||||||
|
|
||||||
|
|
@ -52,6 +68,14 @@
|
||||||
|
|
||||||
## Proposed Solution
|
## Proposed Solution
|
||||||
|
|
||||||
- Execute the existing plan: `docs/superpowers/plans/2026-08-01-log-watcher-sample.md`
|
- The authoring plan (`docs/superpowers/plans/2026-08-01-log-watcher-sample.md`)
|
||||||
(five tasks: tail state machine, cron, probes+supervisor, MCP subset,
|
is spent: the `.wo` files exist and compile.
|
||||||
main + README + live acceptance scenario in the `oop-accept` gate).
|
- What remains is
|
||||||
|
[`plan/compiler/2026-08-14-logwatcher-executable.md`](../../plan/compiler/2026-08-14-logwatcher-executable.md)
|
||||||
|
— six tasks, every one traced to a measurement on this sample: the ownership
|
||||||
|
pass learning stdlib return types, dropping a projected temporary, the
|
||||||
|
runtime's own argv container, honouring the stop signal in blocking calls,
|
||||||
|
closing accepted connections, and a soak that would have caught all of it.
|
||||||
|
- `just log-watcher` (`scripts/log-watcher-accept.sh`) is this iteration's gate:
|
||||||
|
compile, watch alert, cron schedule, and three MCP checks today; the soak
|
||||||
|
joins it in the last task.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,15 @@
|
||||||
> (iterations 3–7) must not be delayed, and because the collector should be
|
> (iterations 3–7) must not be delayed, and because the collector should be
|
||||||
> settled before iteration 8 multiplies shards.
|
> settled before iteration 8 multiplies shards.
|
||||||
|
|
||||||
|
|
||||||
|
> **Status (2026-08-14):** **not on the driving workload's path**, measured:
|
||||||
|
> `docs/examples/log-watcher` declares no `@gc` class — 35 classes in its image,
|
||||||
|
> none with the gc flag, and 0 `RC_INC` / 0 `RC_DEC` instructions against 78
|
||||||
|
> `DROP`s. Its memory story is arena + deterministic drops end to end, so this
|
||||||
|
> iteration (and iteration 4's open `gc/held-cycle` leak, and `set`'s `@gc`
|
||||||
|
> retention gap) cannot affect whether log-watcher runs. It stays queued for
|
||||||
|
> workloads that build cycles; the `gc/` corpus fixtures remain its only users.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- **The developer stops deciding which types are garbage collected.** `@gc`
|
- **The developer stops deciding which types are garbage collected.** `@gc`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue