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:
shoney.arickathil 2026-08-14 22:16:14 +02:00
parent b6c9b9e379
commit f8d3a8861c
7 changed files with 287 additions and 29 deletions

View file

@ -19,29 +19,47 @@ Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **hold*
## ▶ NEXT PLAN
**Close the gaps the log-watcher milestone left open.** The acceptance target
of the whole language track — _compile and run log-watcher_ — is **met** as of
2026-08-14: `docs/examples/log-watcher` (1285 lines, 7 files) compiles with
zero diagnostics, and the image runs (`wovm lw.wob watch app.log 2 1` tails a
live file, classifies levels and fires `ALERT … last entry is error, quiet for
2s`). What remains is the *strictness* half of iteration 5 plus one runtime
gate, in this order:
**Make log-watcher executable — nothing else.** The compile-and-run half of the
language track is met (2026-08-14): the sample compiles with zero diagnostics,
`woc build` produces a 106 KB standalone binary, and all three modes work —
`watch` alerts on a live file, `run` schedules a cron.d entry, `mcp` answers
JSON-RPC with all four tools returning `isError:false`. `just log-watcher`
gates it: 6 checks, 0 failures.
1. **`?T` forced handling** (plan 8 Task 6's diagnostics half, `WO-E211`–`E213`
still dead). Optionals are currently **lenient**: `nil` is the zero word, a
`?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).
What is left is the difference between "it runs" and "you can leave it
running", and every item below came from a measurement on the sample itself:
Plan: [`plan/compiler/2026-08-01-haxe-parity-language.md`](plan/compiler/2026-08-01-haxe-parity-language.md) ·
Story slice: [`docs/stories/language-runtime-database/05-language-surface.md`](stories/language-runtime-database/05-language-surface.md)
1. **The ownership pass does not know what the stdlib returns** — so a binding
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**:
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) | ✅ |
| 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) |
| 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) |
| 7 | [log-watcher proof](stories/language-runtime-database/07-logwatcher-proof.md) | ✅ compiles and runs |
| 7b | [Inferred GC + mark-sweep](stories/language-runtime-database/07b-inferred-gc-mark-sweep.md) | ⬜ closes iteration 4's gate |
| 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) | ⏸ off the workload's path (no `@gc`) |
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.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 |
@ -79,7 +97,7 @@ that sequences its tasks. Read one, approve, then the next starts.
| 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).

View file

@ -1,6 +1,6 @@
# 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.
>

View 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.

View file

@ -3,6 +3,18 @@
> Format: fiberloom `product/story-iteration-template`. Part of
> [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
- The language grows from milestone grammar to a daily-driver surface: the

View file

@ -3,6 +3,18 @@
> Format: fiberloom `product/story-iteration-template`. Part of
> [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
- writeonce stops being server-only: a project with a free

View file

@ -13,6 +13,17 @@
systems track's acceptance bar: nothing in a real daemon exceeded the
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
- What to achieve?
@ -33,6 +44,11 @@
- **when** the iteration closes,
- **then** every row names its `.hx` sibling and deliberate
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
@ -52,6 +68,14 @@
## Proposed Solution
- Execute the existing plan: `docs/superpowers/plans/2026-08-01-log-watcher-sample.md`
(five tasks: tail state machine, cron, probes+supervisor, MCP subset,
main + README + live acceptance scenario in the `oop-accept` gate).
- The authoring plan (`docs/superpowers/plans/2026-08-01-log-watcher-sample.md`)
is spent: the `.wo` files exist and compile.
- 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.

View file

@ -8,6 +8,15 @@
> (iterations 3–7) must not be delayed, and because the collector should be
> 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
- **The developer stops deciding which types are garbage collected.** `@gc`