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>
99 lines
5.1 KiB
Markdown
99 lines
5.1 KiB
Markdown
# 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](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.
|
|
|
|
## 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
|
|
`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
|
|
|
|
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.
|