writeonce/docs/superpowers/plans/2026-08-20-library-kind-internal.md
shoney.arickathil 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

14 KiB
Raw Permalink Blame History

Iteration 17 — library kind + internal/: implementation plan

Status: ✅ LANDED 2026-08-20 — executed in full, all six tasks. Was parked the same day (developer directive: framework v1 work first), then unparked and run. Two disclosed deviations, both because the framework grew after this plan was written: http/parse.wo was SPLIT rather than moved whole (its media_type/form_values are public surface the web-app calls), and the gate reads 26/0 rather than 17/0 (just web-app was already at 23 before this iteration). Board: docs/00-status.md.

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Style rule (user convention): this plan carries concept, reason, and required behavior in words plus verification commands only — no implementation or test code blocks; the executor writes the code.

Goal: kind = "library" in wo.toml makes a project checkable without an entry, the internal/ rule keeps a dependency's plumbing private (WO-E108), and the framework adopts both — just web-app proves the public surface unmoved.

Architecture: every change is compile-time and lives in the driver (compiler/bin/main.ml): the manifest reader learns one key, the manifest build path grows a check branch, and the dep-use resolution walk in compile_image grows the boundary rule. No lexer, parser, typechecker, VM, .wob, or GC change — the spec's impact analysis is normative.

Tech Stack: OCaml stdlib only (woc), bash acceptance scripts, just.

Spec: ../specs/2026-08-20-library-kind-internal-design.md (normative). Story: 17-library-projects-internal.md.

Global Constraints

  • Branch library-internal; commits local only, never push.
  • OCaml stdlib only; no new executables, no new build steps.
  • Absent kind means program — every existing project's behavior is byte-identical; the standing gates prove it each task.
  • Exit-code doctrine stays: 0 clean, 1 diagnostics, 2 usage/manifest/IO. WO-E108 is a diagnostic (exit 1, printed by the normal collector path); WO-E109 is a manifest error (exit 2, woc: <file>: error WO-E109: ..., the WO-E106 shape).
  • internal matches a whole path SEGMENT of a dep-relative module path — never a substring.
  • Gates that must stay green after every task: just woc-test, just oop-e2e, just deps-accept, just web-app, just log-watcher, just employee.

Spec deviations, disclosed up front

  1. The spec put WO-E108/E109 fixtures in the compile-fail corpus. The corpus harness (scripts/oop-e2e.sh) compiles a fixture DIRECTORY with no manifest and no network — a wo.toml in a fixture dir would flip woc into manifest-build mode, and WO-E108 additionally needs a fetched dep. Both diagnostics are therefore gated in scripts/web-app-accept.sh (which already builds a file:// dep chain), not the corpus. Task 5.
  2. The spec says check mode "stops before image emission". The cheapest correct implementation reuses compile_image whole — emission happens in memory and the image is discarded; no artifact is written, no entry is required (an entry-less image is already legal there, the --emit precedent). Behavior matches the spec; the pipeline boundary is one step later than the spec's wording.
  3. woc build <dir> -o <out> never reads the manifest, so it resolves no [deps] (pre-existing, iteration 15). The dual lib+bin case therefore holds for libraries without [deps] — the framework qualifies. Recorded here, not fixed; a dep-aware explicit build is future work.

Task 1 — the manifest kind key + WO-E109

Files:

  • Modify: compiler/bin/main.ml (manifest_parse known-key table ~line 808; manifest_build ~line 1023).

Interfaces:

  • Produces: manifest_parse accepts top-level kind; manifest_build exposes the validated kind ("program" when absent) to Task 2's branch.

  • manifest_parse: add kind to the known TOP-LEVEL keys (the match arm that today allows name, version, description). Nothing else in the parser changes — the value is a quoted string like every other key.

  • manifest_build: after the name check, read kind. Absent or "program" continues to build. "library" is Task 2's branch (for this task, temporarily fall through to build — the framework does not carry the key until Task 4, so nothing observable changes). Any OTHER value fails as a manifest error in the WO-E106 print shape with code WO-E109, naming the given value and the two legal ones, exit 2.

  • Verify by hand: a scratch project under the scratchpad with kind = "junk" refuses with WO-E109 and exit 2; the same project with kind = "program", and with no kind line, builds as before.

  • Verify nothing moved: just woc-test && just deps-accept && just employee.

  • Commit.

Task 2 — library check mode + the build-error hint

Files:

  • Modify: compiler/bin/main.ml (manifest_build; build_mode no-entry error ~line 639; usage_msg ~lines 42–52 and the long help's build paragraph ~line 96).

Interfaces:

  • Consumes: Task 1's validated kind.

  • Produces: woc <dir> on a kind = "library" manifest runs the full check; the behavior Tasks 4–5 verify against.

  • manifest_build, kind "library": resolve [deps] and enforce the [runtime] constraint exactly as build does (a library must be checkable offline once locked), then run compile_image ~deps dir, print diagnostics through the normal finish path on error (exit 1), and exit 0 silently on success. No target/ directory is created, no file is written, no entry is required. The full pipeline runs — parse, typecheck, interface satisfaction, ownership, GC inference — because compile_image already runs it; the in-memory image is discarded (deviation 2).

  • build_mode, the no-entry error: when <dir>/wo.toml exists and names kind = "library", append the library hint to the existing message — the project is a library; add a main for a demo binary or check it with woc <dir>. Read the manifest only if the file exists; malformed manifests keep failing as they do today.

  • usage_msg: the woc <dir> line notes "builds a program / checks a library, per the manifest's kind"; the long help gains two sentences on kind and check mode. No new flags.

  • Verify by hand: scratch library project (kind = "library", one class, no main) — woc <dir> exits 0 with no output; plant a type error, woc <dir> exits 1 with the normal diagnostic; woc build <dir> -o x fails with the no-entry message plus the library hint; add a main, woc build produces a runnable binary (dual case) while woc <dir> still only checks.

  • Verify nothing moved: just woc-test && just oop-e2e && just deps-accept.

  • Commit.

Task 3 — the internal/ boundary rule (WO-E108)

Files:

  • Modify: compiler/bin/main.ml (compile_image's dep-use walk, ~lines 487–518).

Interfaces:

  • Consumes: the existing walk that already knows, per file, whether a dep owns it (owner) and prefixes dep-internal use paths.

  • Produces: WO-E108 diagnostics in the collector; Task 5 gates on the code string.

  • In the same List.map over parsed files: for a file NOT owned by any dep (the owner = None branch, today untouched), inspect each Use whose FIRST segment names a dep in deps — when any LATER segment is exactly internal, add a diagnostic to the collector: code WO-E108, the use's file and pos, message naming the internal module path and the dependency it belongs to (the Diag.error + Collector.add pattern at ~line 277). Do not drop the use — the collector's has-error path already prevents emission, and later resolution errors on the same use are harmless duplicates suppressed by exit-on-first-report ordering as today.

  • Dep-owned files stay untouched on this path — the boundary is consumer-only (spec §3): the dep's own use internal (prefixed to <dep>/internal by the existing arm) must keep compiling.

  • The root project's own internal/ directories are NOT matched: the rule keys on the first segment being a DEP name, so a root-project use internal never fires it. No code needed — assert it in the Task 5 gate instead.

  • Verify by hand: temp dir pair — a dep with an internal/ module and a consumer importing <dep>/internal — compile fails exit 1 printing WO-E108 at the use's line; the dep's own file importing internal compiles.

  • Verify nothing moved: just woc-test && just deps-accept && just web-app.

  • Commit.

Task 4 — framework reorg: adopt kind + internal/

Files:

  • Modify: docs/examples/writeonce-framework/wo.toml (add kind = "library"), app.wo (imports), README.md (verification note).
  • Move: http/parse.wo → internal/parse.wo, http/serve.wo → internal/serve.wo (git mv; module becomes framework/internal under a consumer, internal standalone).
  • Not touched: http/types.wo, router/router.wo, the web-app.

Interfaces:

  • Consumes: Task 2's check mode (standalone verification), Task 3's rule (what the move protects).

  • Produces: the reorganized layout Task 5's gate checks against.

  • Move the two plumbing files. Same-module access dies with the move: internal/serve.wo and internal/parse.wo now need use http for Req/Resp (they shared the http module with types.wo before); app.wo adds use internal for the serve loop and Parsed seam. No declaration changes — imports only.

  • wo.toml gains kind = "library" (top-level, beside name).

  • README: replace the --emit verification workaround sentence with the check-mode invocation (woc <dir> — full pipeline, no entry), and one sentence on internal/ being unimportable by consumers.

  • Verify: compiler/_build/default/bin/woc docs/examples/writeonce-framework exits 0 silently — the workaround is dead.

  • Verify the consumer: just web-app — all 14 standing checks pass unchanged (the app imports only framework, framework/http, framework/router, so NOTHING in it changes).

  • Commit.

Task 5 — gate: three new checks in web-app-accept

Files:

  • Modify: scripts/web-app-accept.sh (after the build check, before the serve matrix).

Interfaces:

  • Consumes: the $W/fw framework copy and $W/app consumer the script already builds; Tasks 1–4's behavior.

  • Produces: just web-app at 17 checks, the iteration's single gate.

  • Check 15 — library check mode: woc "$W/fw" (the framework copy, which now carries kind = "library" and no main) exits 0 with empty output.

  • Check 16 — the boundary: copy the app to a second temp dir, append a use framework/internal line to its main.wo, compile; assert exit 1 and WO-E108 in stderr.

  • Check 17 — kind validation: copy the app again, set kind = "junk" in its manifest, compile; assert exit 2 and WO-E109 in stderr.

  • Renumber nothing — the script counts dynamically (pass/fail); update only the header comment's check inventory.

  • Run the full battery: just web-app (17/0) and the standing gates — just woc-test, just oop-e2e, just deps-accept, just log-watcher, just employee.

  • Commit.

Task 6 — docs closeout

Files:

  • Modify: docs/00-status.md (NEXT PLAN advances; board row 17 → done with what landed; in-progress row moves to the next order item), story 17-library-projects-internal.md (landing banner), compiler/src/CODE-LOGIC.md (driver section: kind, check mode, the boundary rule), docs/08-project-structure.md (framework layout gains internal/), framework story 16-web-framework.md only if it names the --emit workaround.
  • Apply; every claim carries its measured gate result (17/0 etc.); no forward-looking "will".
  • just web-app once more after the doc edits (nothing should move — honesty check).
  • Commit.

Success criteria (spec, restated as the gate reads them)

  1. Framework checks entry-less (woc <dir> exit 0) and woc build on it fails naming the library kind — gate check 15 + Task 2's hand check.
  2. A consumer import of framework/internal is WO-E108 at the use; the framework's own import stays legal — gate check 16 + Task 3.
  3. just web-app's original 14 checks pass unchanged after the reorg — Task 4/5.
  4. A manifest without kind behaves byte-identically — every standing gate, every task.

Self-review notes

  • Spec coverage: §1 kind key → Task 1; §2 driver modes → Task 2; §3 rule → Task 3; §4 diagnostics → Tasks 1–3; §5 reorg → Task 4; §6 gate → Task 5; out-of-scope list untouched by any task. Corpus-fixture clause replaced by deviation 1; emission-boundary wording by deviation 2; dual-with-deps limit by deviation 3.
  • Type consistency: the only cross-task names are the two code strings (WO-E108, WO-E109), the manifest key kind, and the module path framework/internal — spelled identically in every task.
  • Risk, disclosed: moving serve/parse breaks same-module visibility they silently enjoyed beside types.wo; Task 4 names the exact import each file must gain, and the standalone check catches any miss before the gate runs.