From e7f963849e85756783f7b880506e4545c6ff8c3f Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Thu, 20 Aug 2026 01:45:06 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20iteration=2017=20forks=20settled=20?= =?UTF-8?q?=E2=80=94=20kind=20key,=20internal/=20rule=20(no=20code)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fork 1: library-ness manifest-declared, kind = "library", default program - fork 2: privacy = Go internal/ directory rule, named diagnostic at use - fork 3: dep-boundary-only scope; Go subtree rule recorded as later tightening - fork 4: lib+bin dual — library default action is check, explicit build works - Go-inherited rule pinned: internal type in public signature allowed, no check - impact analysis added: framework loses --emit workaround, plumbing under internal/; compiler = two seams (driver kind + dep-use refusal WO-E1xx) - VM zero impact by construction: no .wob change, libs compile whole-program into consumer image, internal modules still emitted (privacy strips nothing) - GC zero mechanism impact; pinned: inference stays whole-program, app usage can promote dep classes, internal/ invisible to gcinfer — intended, not bug - board + roadmap rows: needs-refinement -> forks settled, spec/plan next Co-Authored-By: Claude Opus 5 (1M context) --- docs/00-status.md | 4 +- .../language-runtime-database/00-story.md | 2 +- .../17-library-projects-internal.md | 117 ++++++++++++++---- 3 files changed, 96 insertions(+), 27 deletions(-) diff --git a/docs/00-status.md b/docs/00-status.md index af377ff..62601b5 100644 --- a/docs/00-status.md +++ b/docs/00-status.md @@ -132,7 +132,7 @@ that sequences its tasks. Read one, approve, then the next starts. | 14 | [skillhost host workload](stories/language-runtime-database/14-skillhost-host-workload.md) | ⬜ gaps recorded (branch query-grammar found skillhost needs no new query grammar); each gap a candidate iteration | | 15 | [deps: `wo.toml [deps]`](stories/language-runtime-database/15-deps-package-manager.md) | ✅ **landed 2026-08-18** (branch web-framework): [deps] inline tables, git-binary fetch, wo.lock pinning, offline-when-locked, --update-deps, WO-E106/E107; `just deps-accept` 8/0 | | 16 | [web framework](stories/language-runtime-database/16-web-framework.md) | ✅ **landed 2026-08-19** — writeonce-framework (HTTP/1.1 + router + Handler/Middleware) consumed by web-app through [deps]; `just web-app` 14/0; h2c parked (§C) behind 8/9f/11 | -| 17 | [library projects + `internal/`](stories/language-runtime-database/17-library-projects-internal.md) | ⬜ **needs refinement** (2026-08-20): Go internal/ vs Rust pub(crate) analyzed; four forks open — library kind marker, privacy mechanism, rule scope, lib+bin duality | +| 17 | [library projects + `internal/`](stories/language-runtime-database/17-library-projects-internal.md) | ⬜ **forks settled 2026-08-20, awaiting spec/plan**: kind = "library" manifest key; Go internal/ rule, dep-boundary-only; lib+bin dual; VM/GC untouched by design (impact analysis in the iteration) | --- @@ -321,7 +321,7 @@ The C proving-ground work (`exploration/c-runtime/`, phases A–F: 859k reads/s, | 9f | io_uring group-commit write path — batched durability overlapped on shard threads, fsync fallback | **no spec yet** — brainstorm after iterations 8 + 9e | | 9g | Query grammar from real embedded-DB corpora — whole-query count + correlated exists, driven by the skillhost SQL catalogue; add only what a corpus uses | **no spec yet** — three forks; may collapse to "confirm len(query) + add exists" | | 14 | skillhost host workload — port skillhost (MCP host + confined script runner) to writeonce; drives the missing host capabilities into the open (bounded subprocess, stdin/stdout transport, fs metadata, FFI-vs-out-of-process) | **no spec yet** — gaps recorded in the iteration; each gap brainstormed on demand, bounded-subprocess first | -| 17 | library projects + dependency privacy — `wo.toml` library kind (checkable, unbuildable) + a dep-internal module tier; framework reorg demonstrates both | **needs refinement** — four forks recorded in the iteration; brainstorm before spec/plan | +| 17 | library projects + dependency privacy — `wo.toml` kind = "library" (checkable without entry, dual lib+bin) + Go-style `internal/` at the [deps] boundary; framework reorg demonstrates both | **forks settled 2026-08-20** — decisions + framework/compiler/VM/GC impact in the iteration; spec/plan next | | 10 | HTTP service layer | [plan 6](superpowers/plans/2026-08-01-http-service-layer.md) | | 11 | Fibers | vision §3, [blue-green exploration](plan/exploration/blue-green-vm/00-vision.md) | | 12 | Blue-green deploy | [spec](superpowers/specs/2026-08-03-blue-green-vm-design.md) — plan authored after iterations 9–10 | diff --git a/docs/stories/language-runtime-database/00-story.md b/docs/stories/language-runtime-database/00-story.md index 8f359b8..e214415 100644 --- a/docs/stories/language-runtime-database/00-story.md +++ b/docs/stories/language-runtime-database/00-story.md @@ -65,7 +65,7 @@ iterations); no commits by agents — drafts go to `.dev/commit.md`. | 14 | [skillhost host workload](14-skillhost-host-workload.md) | a host-shaped driving workload (writeonce port of skillhost) that names the runtime gaps it exposes: bounded/killable subprocess, stdin/stdout transport, fs metadata, and FFI-vs-out-of-process model driver — each a candidate iteration | | 15 | [deps: `wo.toml [deps]`](15-deps-package-manager.md) | exact-rev git dependencies + `wo.lock` + `.wo-deps` cache; `use ` resolves a fetched project as a module root; flat-only, network-free when locked | | 16 | [web framework](16-web-framework.md) | a `.wo`-library framework (HTTP/1.1 keep-alive behind a TLS-terminating proxy): router, `Handler`/`Middleware` structural interfaces, `@table` data layer; `docs/examples/web-app` consumes it via `[deps]`; h2c parked behind 8/9f/11 | -| 17 | [library projects + `internal/`](17-library-projects-internal.md) | first-class library projects (`wo.toml` kind, checkable without an entry) and dependency privacy (a dep-internal module tier, Go's `internal/` shape vs Rust's `pub(crate)` analyzed) — four open forks, needs refinement | +| 17 | [library projects + `internal/`](17-library-projects-internal.md) | first-class library projects (`wo.toml` kind = "library", checkable without an entry, dual lib+bin) and dependency privacy (Go's `internal/` rule at the [deps] boundary) — forks settled 2026-08-20, spec/plan next | Review protocol: the developer reads one iteration, approves or amends; the next starts only after approval. Each iteration is an unsplittable diff --git a/docs/stories/language-runtime-database/17-library-projects-internal.md b/docs/stories/language-runtime-database/17-library-projects-internal.md index c2ec0d3..f15d55f 100644 --- a/docs/stories/language-runtime-database/17-library-projects-internal.md +++ b/docs/stories/language-runtime-database/17-library-projects-internal.md @@ -4,8 +4,11 @@ > [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). The forks below are genuinely open; -> a brainstorm/spec settles them before any plan. +> 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. Next step is +> the spec/plan; implementation stays parked until asked. ## Why this iteration exists @@ -54,26 +57,90 @@ library marker can be explicit where Go infers, giving clearer errors. - The framework reorganizes to demonstrate both (its parser plumbing moves behind the privacy line; `Handler`/`App`/builders stay public). -## Open forks (each a real decision for the spec) +## Settled decisions (2026-08-20 — the former open forks) -1. **How library-ness is declared.** `kind = "library"` top-level key vs a - `[lib]` section vs pure inference from "no entry-shaped main". Leaning: - the explicit key — the manifest exists, and explicit beats inference in - error messages — with "program" the default. -2. **Privacy mechanism.** Go's `internal/` directory rule (pure - use-resolution change, zero new syntax, coarse) vs Rust's `pub(lib)` - visibility keyword (fine-grained, touches parser + typechecker + the - error catalog) vs a manifest `export = [...]` module allowlist (explicit - surface, but a second place to maintain). Leaning: `internal/` — it - matches directory-as-module exactly and costs a resolution rule. -3. **Scope of the internal rule.** Dep-boundary-only (importable anywhere - inside the dep, refused from the consumer) vs Go's full subtree rule - (importable only under `internal/`'s parent, even within one project). - Dep-boundary-only is the smaller honest cut; Go's rule also disciplines - large single projects. -4. **Can one project be both** (Rust's lib+bin)? A framework shipping a demo - binary wants it; the entry-selection rule (iteration 15's "a dep's main is - never the entry") already half-answers it. Decide explicitly. +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 ` 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 ` 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) @@ -94,6 +161,8 @@ binaries per project (`cmd/` convention — record, don't build). ## Proposed Solution -Brainstorm → spec settling the four forks, then a small plan: manifest key + -driver check-mode for libraries, the use-resolution privacy rule + WO-E1xx -diagnostic, the framework reorg, and `just web-app` as the regression gate. +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).