writeonce/docs/stories/language-runtime-database/17-library-projects-internal.md
shoney.arickathil 0370727d69 docs: iteration 17 — library projects + dependency privacy (needs refinement)
Brainstorm outcome, deliberately NOT implemented (developer decision: keep
as an iteration needing further refinement). Records:
- the two gaps iterations 15/16 exposed: library-ness is implicit (a
  no-main project fails woc <dir> build mode — the framework is verified
  via an --emit workaround) and the dep boundary leaks internals (pub has
  no dep-private tier: parse_request is as importable as Handler).
- the conventions corpus: Go (package decides program-ness; cmd/;
  internal/ = directory-shaped privacy, zero keywords) vs Rust ([lib]/
  [[bin]] manifest targets; pub(crate)-family keyword visibility). Doctrine
  fit points at Go's shape with an explicit manifest key (writeonce HAS a
  manifest; explicit beats inference in errors).
- four open forks for the spec: kind declaration form; internal/ vs
  pub(lib) vs export-allowlist; dep-boundary-only vs Go's subtree rule;
  lib+bin duality. Draft acceptance criteria; web-app 14/0 as the
  regression gate. Roadmap + board rows added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 01:23:10 +02:00

5.1 KiB

Iteration 17 — library projects and dependency privacy (kind, internal/)

Format: fiberloom product/story-iteration-template. Part of Story — one language, one runtime, one database, one binary.

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.

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

Open forks (each a real decision for the spec)

  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.

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 uses 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

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.