Commit graph

10 commits

Author SHA1 Message Date
c0b0dbb846 docs: audit all markdown against the code, fix findings, flatten status folders
- README: shipped concurrency/HTTP/WebSockets sat in the roadmap as "not yet
  available"; "no package manager" contradicted [deps]; the deps example
  would not have compiled (the key IS the module name)
- runtime/README: leads with wovm, wo-rt.c demoted to a historical section;
  dropped 2 nonexistent recipes, crates/rt, @gc refcounting, 13 suites -> 18
- employee + log-watcher READMEs claimed "does not compile"; both are gates
- error catalog: +10 emitted codes incl WO-E250, the only diagnostic the
  shipped query surface raises; recorded why the sweep rotted
- language-surface: group-by parses, then the typechecker refuses it
- 00-code-review + 00-link-audit re-run; history kept, not rewritten
- 48 dead Rust-era exploration links de-linked rather than re-pointed (their
  prose names the retired plan by number); successor map -> discarded.md
- 08-project-structure: compiler/plan/ never existed; corpus has 9 dirs, 5 empty
- releasing.md: dropped a --draft step the workflow never had
- new docs/00-doc-audit.md: findings + disposition, incl one row where the
  audit was wrong and the doc it accused was right
- status folders removed: 34 stories flat, status only in frontmatter; 252
  links recomputed from resolved paths; board/board-views/structure retaught
- story 24 -> in-progress, since frontmatter is now the only truth
- new iteration 38: fs mutation verbs + net.connect, the two capability
  families no iteration owned
- new iteration 39: gofiber/fiber v3.5.0 parity study. The ledger called
  CSRF/sessions unblocked by iteration 34's HMAC, but the runtime has no
  source of randomness at all
- linkcheck skips .dev/.superpowers: 0 broken paths, 0 bad anchors

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:20:22 +02:00
8f6ff1e865 feat(compiler): WO-E205 structural interface satisfaction + call-arg checks
The hybrid-boundary inversion is closed: a statically provable interface
violation now fails at COMPILE time instead of reaching wovm as an ICALL
that traps WO_T_BOUNDS at runtime.

- types.ml class_satisfies: the same rule emit.ml's `satisfies` builds
  vtable rows from (instance method with matching name + parameter count
  for every interface method; `static fn` never satisfies) — one rule, two
  consumers, so the check and the vtable can never disagree.
- check_iface_boundary fires wherever a confidently class-typed value flows
  into an interface-typed slot: call arguments against the callee's declared
  parameters (free fns, methods off confident receivers, interface-method
  sigs, statics — resolved exactly as confident_typ resolves returns),
  annotated `let`s, and `return`s. Silent when underivable.
- The same per-argument pass extends the ?T boundary to CALL ARGUMENTS
  (the previous slice covered stores/returns/operands): nil into a
  non-nullable parameter is WO-E212, an unnarrowed ?T argument is WO-E211.
- tests/corpus/trap/unsatisfied-interface -> compile-fail/ with
  fixture.code WO-E205, per the fixture's own standing instruction; its
  header comment rewritten to the wired reality.
- The new arg checks caught a real mistyped signature in the sample:
  log-watcher's rpc_error/rpc_result/call_tool declared `id: json.Value`
  while every caller legitimately passes nil (JSON-RPC id-absent) — now
  `?json.Value`; dispatch/call_tool/cron-row sites moved to the
  bind-then-narrow idiom (including an `or`-guard narrowing:
  `if spath == nil or spat == nil { return }`).
- Catalog: E205 gains its main-table row; the "owed gap" section is
  rewritten as closed. Board known-gap struck through.

Verified: woc-test 540/0 + test_diag 14/0; oop-e2e 83/0 (fixture now
compile-fail, satisfying-class negative probe compiles clean); oop-accept
ALL MET; log-watcher 7/0; employee 8/0; gc-cycle clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 17:53:40 +02:00
934780c54c feat(compiler): ?T forced handling — WO-E211/E212/E213 + narrowing (iter 5)
The type system now keeps its nullability promise: a `?T` value cannot be
used, stored, or dereferenced as a plain `T` without narrowing. The canonical
evidence probe (return b.v where v: ?Int, fn -> Int) that compiled clean for
months now fails with WO-E211.

- WO-E211 (un-narrowed use): arithmetic and </<=/>/>= operands, and/or
  operands (?Bool), interpolation segments, for-iterables, and returns whose
  declared type is not nullable.
- WO-E212 (boundary): nil or ?T stored into a non-nullable slot — annotated
  let, assignment to a confidently-typed local (cenv, never the placeholder
  env — a placeholder target must stay silent) or a resolvable class field.
- WO-E213 (deref): field/index access through a possibly-nil base.
- Narrowing (locals only — a field place can be re-assigned between check
  and use, so chains bind to a local first): `if x != nil { }` narrows the
  branch; a DIVERGING then-branch (`if x == nil { return }`) narrows after
  the if; `x != nil and x.n > 3` narrows and/or right operands
  (short-circuit); `while x != nil` narrows the body. The narrow is
  un-applied when an else-less then-env leaks out un-diverged (the existing
  env-leak convention must not leak the narrow).
- No false positives by construction: env/cenv types are declared or
  confidently inferred; the placeholder fallbacks are plain scalars, never
  ?T. The whole golden suite passed untouched (540/0).
- Samples updated to the bind-then-narrow idiom (log-watcher config decode +
  supervisor lock/next_fire, gc-cycle ring print) — 22 genuine unnarrowed-nil
  sites; employee needed zero changes. All acceptances green.
- Corpus: compile-fail/{nullable-unnarrowed-use,nullable-nil-into-plain,
  nullable-deref-unchecked} + run/nullable-narrowing (all four forms) — 83/0.
- Catalog: E211/E212/E213 move from "Reserved, not yet emitted" to the main
  table; nullable-types-implementation.md status flipped to ENFORCED
  (historical record kept); plan 8 Task 6 ticked (boxed scalar cells
  superseded by WO_NIL_SCALAR); board updated.

Verified: woc-test 540/0 + test_diag 14/0; oop-e2e 83/0; oop-accept ALL MET;
log-watcher 7/0; employee 8/0; gc-cycle ring prints + reclaims.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 17:47:13 +02:00
464b61ba65 fix(log-watcher): portable wo.toml — no pinned runtime path
The manifest pinned `[build] runtime = "../../../runtime/wovm"`, a repo-only
relative path that overrides woc's runtime resolution — so `woc <copied-dir>`
failed off-repo (e.g. an installed toolchain on a test server) with "runtime
binary not found".

- Drop the [build] section: the project no longer hardcodes a machine path, so
  an installed `woc` self-locates `wovm` beside its own binary.
- The module justfile's `build` recipe now sets WO_RUNTIME=<repo>/runtime/wovm
  so the in-repo build still uses the freshly built VM.
- Acceptance is unaffected (it compiles via `woc --emit` + an explicit $WOVM,
  never the manifest [build] key).

Verified: just log-watcher::build OK; just log-watcher 7/0; and building a
copied tree with the installed-layout woc from an unrelated cwd self-locates
the sibling wovm and produces a runnable binary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 01:19:20 +02:00
c4d2a588ff chore: move the sample's recipes beside it (just module)
- docs/examples/log-watcher/justfile carries build/accept/soak; the
  root mounts it with `mod log-watcher`, so `just log-watcher` still
  runs the acceptance (default recipe) and every doc reference stays
  valid; `just log-watcher::build` / `::soak 60` reach the rest, and
  plain `just build` works from inside the directory
- ROOT is source_directory()-based: inside a `mod`,
  justfile_directory() names the ROOT justfile's directory and every
  path would miss

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 01:35:56 +02:00
5d780d8669 feat: woc <dir> builds from wo.toml
- a directory carrying wo.toml is a PROJECT: `woc .` inside it (or
  `woc path/to/project` from anywhere) reads the manifest and produces
  <target>/<name>, exactly what `woc build <dir> -o ...` produces
- schema is the one the sample already carried -- top-level name/
  version/description, [runtime] wo (accepted, not yet enforced) --
  plus a new [build] section: runtime (wovm to prepend) and target
  (output dir, default "target"), both relative to the manifest's own
  directory so the build is invocation-point independent
- unknown keys and sections are hard errors: a typo'd key silently
  ignored would build the wrong thing
- directories WITHOUT wo.toml keep check-only semantics -- the corpus
  is full of those; oop-e2e 71/0, woc-test 565/0, log-watcher 7/0
- sample's wo.toml gains the [build] section; target/ gitignored

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 00:55:29 +02:00
3c53d27347 fix: close per request, soak the daemon, kill five soak-found leaks (Tasks 5+6)
- Task 5: net.close on every path out of a serve iteration (400
  included) and the listener on stop; measured 4 -> 54 fds over 50
  requests before, 4 -> 4 over 200 after. The loop's comment claimed
  the iteration-end drop IS the close -- wrong twice (net.Conn is a
  scalar, and a drop would not close an fd); it now says what is true
- Task 6: LW_SOAK=<seconds> in the acceptance script -- each mode under
  load, resident+descriptor deltas against a WARMED baseline (warm-up
  includes load: cold-to-high-water is not growth), 256 KiB / zero
  tolerance; LW_ACCEPT_WOVM soaks another build
- the soak caught ~1.6 MiB/min of in-arena leaks ASan cannot see (the
  arena is one allocation to LeakSanitizer); an arena size-class
  census + pointer trace attributed five bugs:
  - jparse_string sized every decoded string at "rest of the input"
    and relabeled len after -- blocks filed on free lists their next
    allocation never reads (fs.read_all's mis-size, again); copy out
    exact, free at the taken size
  - `!=` never dropped fresh operands (headers["authorization"] !=
    "Bearer ${key}" leaked both sides per request); Ne now reaps as Eq
  - an Int interpolation segment is a fresh int_to_text, not a borrow;
    is_borrowed_value_t asks the segment's type
  - json.encode(Ctor{...}) had no owner -- record + both field copies
    leaked per tool call; its bespoke lowering now drops the argument
  - a discarded expression statement owns its result: `pop(lines);`
    leaked the popped element; reader builtins excluded
- after: arena live bytes flat per request on every handler; release
  soak 30 s per mode watch 0 / run 0 / mcp +20 KiB, descriptors flat;
  ASan build flat at 14600 KiB across 601686 requests in 90 s past its
  ~1200-request quarantine warm-up
- gates: oop-accept ALL CRITERIA MET, oop-e2e 71/0, woc-test 565/0,
  wovm-test green, log-watcher 7/0 (soak opt-in, fast path <1 min)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 00:32:49 +02:00
0acb6be559 fix: net.Conn is a scalar, \r escape, map[k] is optional, interp node ids
Found by driving the compiled log-watcher's third path — the MCP server. It now
answers real JSON-RPC over HTTP: initialize returns protocolVersion/serverInfo,
tools/list returns the full tool list (1049 bytes of generated JSON), and an
unauthorized request gets 401 {"error":"unauthorized"}. corpus 71/0, woc 565/0,
wovm gates green.

- lexer: `\r` and `\0` escapes. Without `\r` a program cannot write CRLF at
  all — the server's `index_of(buf, "\r\n\r\n")` was searching for a literal
  backslash-r, so it never found a header terminator and hung on every request
- parser: an interpolated sub-expression now mints node ids from the OUTER id
  space. A fresh sub-parser started at 1, so `${...}` nodes collided with the
  file's own nodes — and every side table (drops, moves, rc, masks, f_decl) is
  keyed by node id. Surfaced as WO-E404 "ownership table names `headers`,
  which has no register"; silent misattribution otherwise
- types.ml: `net.Conn` is a reserved SCALAR type (a file descriptor). It was
  falling through as "some user class", i.e. WO_K_OWNED, so the frame would
  DROP an integer at scope end
- types.ml: confident_typ knows `..` yields Text. Interpolation desugars to a
  Concat chain, so without it every interpolated value looked underivable —
  which is why the `+`-on-Text check missed two live sites in the workload
- `m[k]` on a map is now the OPTIONAL read (nil for a missing key), while
  `get(m, k)` stays the asserting one that traps KEY. That is what makes
  `let v = m[k]; if v != nil` — the workload's header lookup — work.
  trap/missing-map-key now pins `get(...)`, and the surface doc records the
  split
- json.encode of a `json.Value` emits it verbatim (kind 255): an echoed id was
  coming back as "1" instead of 1
- disasm: TRY/ENDTRY render instead of ?OP32/?OP33
- status board: the push-of-a-borrowed-Text gap is now recorded with the
  concrete failure it produces (tools/call tail_log), plus the leaked
  temporary-record shell found in the same disassembly

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 17:47:45 +02:00
b973ea107e feat: program mode (argv + exit code); reject + on Text; nil compares by word
docs/examples/log-watcher now COMPILES AND RUNS: `wovm lw.wob watch app.log 2 1`
tails a live file, classifies levels and fires its alert
("last entry is error, quiet for 2s"). corpus 71/0, woc 565/0, wovm gates green.

- program mode: the entry is `fn main` taking nothing or one `multi Text`;
  runtime/src/main.c builds that list from the program's own arguments (not
  the program name, not the image path) and the entry's return value is the
  process exit code (low byte); the loader accepts a 0- or 1-arg free-fn entry
- `+` on Text is now WO-E201 pointing at `..`. This was a memory-safety hole,
  not a style nit: the emitter lowered it to ADD on two heap pointers, and the
  workload's own `out = out + char_of(c)` produced a wild pointer that
  segfaulted the VM inside starts_with. Reported off confident types only
- `x == nil` / `x != nil` lower to EQ (a word compare), never EQS: nil is the
  zero word and EQS dereferences its operands, so a nil guard would trap
  instead of answering
- docs/examples/log-watcher: seven `+`-on-Text sites corrected to `..`
  (logtail sanitize, mcp header/body/carry assembly, supervisor detections
  line) — the sample was carrying the Haxe habit, and the language reserves
  `+` for arithmetic by doctrine
- wovm CLI takes arguments after the image path (`wovm <file.wob> [args...]`)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 17:18:01 +02:00
2171b2d94b docs: log-watcher program 2026-08-10 09:26:07 +02:00