- `readiness: ready | refine` is a SECOND axis, orthogonal to status.
`ready` = the brainstorm is complete and the decisions are LOCKED (a spec
approved, or the forks explicitly confirmed). `refine` = open forks remain
and it cannot be planned yet
- `status: refine` RETIRED because it carried both meanings at once, so a held
iteration with an approved spec (language 18, 26) was indistinguishable from
one nobody had thought about. status is now purely where the WORK is:
done | in-progress | pending | hold — `pending` was already the board's own
rendering word, so nothing new was invented
- all 47 iterations classified from EVIDENCE in their own text, not by guess:
"the four forks are SETTLED" / "spec + plan approved" / "Approved spec:" for
ready; "Forks the spec must settle" / "no spec exists yet" for refine. Every
shipped iteration is ready by definition. 19 done, 5 in-progress, 15
pending, 8 hold; 27 ready, 20 refine
- two iterations moved refine -> in-progress rather than -> pending: language
31 and 34 are absorbed into 24 and work on them is literally happening, which
the board already showed as 🔄 while their frontmatter said otherwise. That
disagreement is now gone
- board legend, board-views' frontmatter contract, and two new Dataview
queries updated — the useful one being `readiness: ready AND status:
pending`, the startable set
WHAT THE NEW AXIS IMMEDIATELY SURFACED: of 15 pending iterations, exactly ONE
is startable — databasev2 4, io_uring group-commit, whose forks were confirmed
settled 2026-08-20. Everything else pending needs a brainstorm first. That was
invisible while one key carried both meanings, and it is now on the board.
Also caught by the sweep, unrelated to readiness but found by cross-checking
frontmatter against the board: SIX duplicate rows. Every iteration moved into
databasev2 was still listed in the LANGUAGE pending table under its retired id
(23, 32, 33, 20, 21, 27) as well as its new one. Stale copies removed. And two
databasev2 rows made claims the sweep contradicts — iteration 1 was billed
"startable today" while its forks are open, and 6 still called itself the
ceiling-raiser after 2 took that role.
Docs only. linkcheck 0 broken / 0 anchors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
205 lines
11 KiB
Markdown
205 lines
11 KiB
Markdown
---
|
|
iteration: "17"
|
|
status: done
|
|
readiness: ready
|
|
---
|
|
|
|
# Iteration 17 — library projects and dependency privacy (`kind`, `internal/`)
|
|
|
|
> Format: `product/story-iteration-template`. Part of
|
|
> [Story — one language, one runtime, one database, one binary](00-story.md).
|
|
>
|
|
> **Inserted 2026-08-20, needs further refinement** (developer decision: keep
|
|
> as an iteration, do not implement yet).
|
|
>
|
|
> **Refined 2026-08-20: all four forks SETTLED** (developer decisions, no code
|
|
> changed). See "Settled decisions" and "Impact analysis" below.
|
|
>
|
|
> **⏸ PARKED 2026-08-20** (developer directive: framework v1 work first).
|
|
> The spec and plan were written and approved before parking
|
|
> (`docs/superpowers/specs/2026-08-20-library-kind-internal-design.md`,
|
|
> `docs/superpowers/plans/2026-08-20-library-kind-internal.md`).
|
|
>
|
|
> **LANDED 2026-08-20** — unparked and executed against that plan, all six
|
|
> tasks. Every change is in the driver (`compiler/bin/main.ml`); the VM,
|
|
> `.wob`, and GC are untouched exactly as the impact analysis predicted.
|
|
> Reasoning-under-the-code in `compiler/src/CODE-LOGIC.md`.
|
|
>
|
|
> Gates: `just web-app` **26/0** (three new checks — library check mode,
|
|
> WO-E108 at the boundary, WO-E109 on a bad kind), and `just woc-test`,
|
|
> `just oop-e2e` (103/0), `just deps-accept`, `just log-watcher`,
|
|
> `just employee` all unchanged.
|
|
>
|
|
> Two deviations from the plan, both because the framework grew after the plan
|
|
> was written:
|
|
>
|
|
> 1. **`http/parse.wo` was SPLIT, not moved whole.** The plan said move it
|
|
> under `internal/`, but the framework-v1 slices had since added
|
|
> `media_type` and `form_values` to that file and the web-app calls both —
|
|
> moving the file whole would have put public surface behind the privacy
|
|
> line and broken the consumer. The parsing plumbing (`Parsed`,
|
|
> `parse_request`, `url_decode`, `parse_query`) is now `internal/parse.wo`;
|
|
> the two public functions are `http/form.wo`, which does `use internal`
|
|
> (legal inside the library).
|
|
> 2. **The gate is 26/0, not the plan's 17/0.** `just web-app` had grown from
|
|
> 14 to 23 checks (framework v1 plus iteration 19's Float price) before this
|
|
> iteration started; the three new checks make 26. The plan's numbers were
|
|
> written against a 14-check gate. Acceptance criterion 3 below still holds
|
|
> in substance: no pre-existing check changed.
|
|
|
|
## Why this iteration exists
|
|
|
|
Iterations 15/16 made cross-repo libraries real — and exposed two gaps the
|
|
established ecosystems solved long ago:
|
|
|
|
1. **Library-ness is implicit and broken.** A project without `fn main` (the
|
|
framework) cannot be checked by `woc <dir>`: manifest presence forces
|
|
build mode, which errors "no `main` entry point found". Iteration 16
|
|
verified the framework through a `woc --emit` workaround — a wart, not a
|
|
design.
|
|
2. **The dependency boundary leaks internals.** `pub` is module-public with
|
|
no dep-private tier: the framework's plumbing (`parse_request`, the hex
|
|
decoder, `route_match`) is exactly as importable by the consuming app as
|
|
its intended surface (`Handler`, `App`, the builders). Nothing marks "this
|
|
module is the library's own business".
|
|
|
|
## The conventions, analyzed (the corpus for the spec)
|
|
|
|
**Go:** no manifest marker — the *package* decides (`package main` +
|
|
`func main` = executable; anything else = library), `cmd/<name>/` hosts
|
|
multiple binaries, and **`internal/`** carries all encapsulation: the
|
|
compiler refuses any import of a path containing `internal/` from outside
|
|
the subtree rooted at `internal/`'s parent. Directory-shaped privacy, zero
|
|
keywords — public repo, private API.
|
|
|
|
**Rust:** manifest-declared targets (`[lib]` / `[[bin]]`, `src/lib.rs` /
|
|
`src/main.rs` conventions; a crate can be both) and *keyword-grained*
|
|
visibility: `pub`, `pub(crate)`, `pub(super)`, `pub(in path)`. A dependency
|
|
sees only what is `pub`-reachable from the crate root. No `internal/`
|
|
convention — `pub(crate)` does that job.
|
|
|
|
**Fit to writeonce doctrine:** directory-as-module and keyword frugality
|
|
point at Go's shape — but writeonce *has* a manifest (Go does not), so the
|
|
library marker can be explicit where Go infers, giving clearer errors.
|
|
|
|
## Goals (draft — the spec refines)
|
|
|
|
- A library project is first-class: declared in `wo.toml`, `woc <dir>`
|
|
typechecks it whole (no entry required), `woc build` on it refuses with a
|
|
message that says what it is. The framework adopts it and loses the
|
|
`--emit` workaround.
|
|
- A dependency has a private interior: some modules are importable inside
|
|
the dep but not across the `[deps]` boundary; violations are a named
|
|
compile diagnostic at the offending `use`.
|
|
- The framework reorganizes to demonstrate both (its parser plumbing moves
|
|
behind the privacy line; `Handler`/`App`/builders stay public).
|
|
|
|
## Settled decisions (2026-08-20 — the former open forks)
|
|
|
|
1. **Library-ness is manifest-declared: `kind = "library"`**, a top-level
|
|
`wo.toml` key, default `"program"`. Explicit beats inference in error
|
|
messages both ways: a library refuses the default build with a message
|
|
naming its kind, and a program missing `main` stays a loud error instead
|
|
of silently becoming a library. (Rejected: a `[lib]` section — ceremony
|
|
with nothing to hold yet; Go-style inference — makes forgot-`main` and
|
|
is-a-library indistinguishable.)
|
|
2. **Privacy mechanism: Go's `internal/` directory rule.** A dependency
|
|
module whose path contains the segment `internal` is not importable
|
|
across the `[deps]` boundary; the violation is a named diagnostic at the
|
|
offending `use`, naming the dependency. Zero new syntax — a pure
|
|
use-resolution rule, matching the directory-as-module doctrine exactly.
|
|
(Rejected: `pub(lib)` keyword — parser + typechecker + catalog surface
|
|
for granularity nothing needs; manifest export allowlist — a second
|
|
place that drifts from the code.)
|
|
3. **Scope: dep-boundary only.** `internal/` modules stay importable
|
|
anywhere INSIDE the dependency; only the consumer is refused. The
|
|
smaller honest cut; Go's full subtree rule (parent-of-`internal/` scope,
|
|
enforced even within one project) is recorded as a possible later
|
|
tightening — adopting it later only ever rejects more, never breaks a
|
|
consumer.
|
|
4. **Lib+bin duality: allowed.** A library MAY carry an entry-shaped
|
|
`main` (demo/self-test binary). `kind = "library"` changes the DEFAULT
|
|
action of `woc <dir>` to whole-project typecheck; an explicit build
|
|
invocation still produces the binary when a `main` exists. As a
|
|
dependency its `main` is never the entry — iteration 15 already ships
|
|
that rule.
|
|
|
|
Follow-on rule inherited from Go, recorded so the spec doesn't relitigate
|
|
it: an internal type MAY appear in a public signature (Go permits exported
|
|
functions returning internal types — the consumer can hold and pass the
|
|
value but cannot `use` the module to name the type). No extra check in v1;
|
|
it is the library author's own smell to avoid.
|
|
|
|
## Impact analysis (what each layer feels)
|
|
|
|
**Framework (`docs/examples/writeonce-framework/`).** Gains one manifest
|
|
line (`kind = "library"`); `woc <dir>` then typechecks the whole project
|
|
with no entry required — the iteration-16 `--emit` verification workaround
|
|
dies. Reorg: the plumbing the web-app never imports — the request parser
|
|
module (parse_request, url_decode, the carry-state record) and the serve
|
|
loop internals — moves under `internal/`; the public surface (`Handler`,
|
|
`Middleware`, `App`, `Req`/`Resp`, the response builders) stays where it
|
|
is. The framework's own `use` of its internal modules stays legal
|
|
(decision 3). The web-app changes nothing: it already imports only the
|
|
public modules, so `just web-app` staying 14/0 is the regression gate, not
|
|
a migration.
|
|
|
|
**Compiler (`woc`) — the only place code would change.** Two seams, both in
|
|
existing passes: the driver reads the `kind` key and picks
|
|
check-mode-by-default for libraries (build mode already exists; check mode
|
|
must still run the FULL pipeline — parse, typecheck, borrow check, GC
|
|
inference — so a green check means what a green build means); and the
|
|
dep-use resolution (the iteration-15 prefixing step) refuses a consumer
|
|
`use` whose dep-relative path contains `internal`, with a new WO-E1xx.
|
|
No lexer, parser, or type-system syntax changes — `internal` is a path
|
|
shape, not a keyword.
|
|
|
|
**VM (`wovm`): zero impact, by construction.** Visibility is name
|
|
resolution at compile time; the `.wob` format carries no module or
|
|
visibility metadata to extend — no new opcodes, no version bump, no loader
|
|
change. A library never yields its own `.wob` at all: iteration 15's model
|
|
compiles dependencies whole-program into the consumer's single image, and
|
|
that stands. The VM never learns "library" exists. A dual-built demo
|
|
binary is an ordinary program image. One honest disclosure: `internal/`
|
|
modules still compile INTO the consumer's image (there is no dead-code
|
|
elimination) — privacy restricts naming, it strips nothing.
|
|
|
|
**GC: zero mechanism impact, one semantic note worth pinning.** GC-ness is
|
|
whole-program inferred (iteration 7b): structural SCC plus demand
|
|
promotion run over the app's AND every dep's classes together, AFTER use
|
|
resolution — privacy is invisible to inference. So a framework class can
|
|
still be promoted to traced by how the APP uses it (an escape in app code
|
|
promotes the escaping projection's class, wherever that class lives), and
|
|
`internal/` does not fence that off. This is correct and intended:
|
|
GC-ness stays a per-consumer, whole-program property, not a library
|
|
promise — a library author cannot pin "my class is untraced" any more
|
|
than before. Library check mode runs the same inference (over the library
|
|
alone), so a library-standalone check and an app-embedded compile may
|
|
legitimately disagree about traced-ness — that is the design, restated
|
|
here so nobody files it as a bug. Runtime GC (header, barrier, safepoints,
|
|
budgets) untouched.
|
|
|
|
## Acceptance Criteria (draft)
|
|
|
|
- **Given** the framework marked as a library, **when** `woc <dir>` runs,
|
|
**then** it typechecks the whole project with no entry required, and
|
|
`woc build` refuses with a message naming the kind.
|
|
- **Given** a framework module behind the privacy line, **when** the web-app
|
|
`use`s it, **then** a named diagnostic points at the `use` and names the
|
|
dependency; inside the framework the same import stays legal.
|
|
- **Given** the public surface (`Handler`, `App`, builders), **when** the
|
|
web-app builds, **then** nothing changed — `just web-app` stays 14/0.
|
|
|
|
## Out Of Scope
|
|
|
|
Registries/semver (still future), transitive deps (15's flat-only stands),
|
|
`pub(super)`-style fine grains beyond the chosen mechanism, multiple named
|
|
binaries per project (`cmd/` convention — record, don't build).
|
|
|
|
## Proposed Solution
|
|
|
|
Forks settled above (2026-08-20). Remaining path: spec + small plan —
|
|
manifest `kind` key + driver check-mode for libraries, the use-resolution
|
|
privacy rule + WO-E1xx diagnostic, the framework reorg (plumbing under
|
|
`internal/`), and `just web-app` as the regression gate. VM and GC are
|
|
untouched by design (see Impact analysis).
|