refactor(porch): name the web framework porch, fix the wo.toml identifier claim
- docs/examples/writeonce-serve -> docs/examples/porch (git mv, history kept); `[deps]` key and import are now `porch` / `porch/http` / `porch/router` - name history preserved on the library README, not rewritten into dated records: writeonce-framework -> writeonce-serve (08-25) -> porch (08-26). Stories, specs, plans and the audit reports keep the older name by the repo's own convention; only live docs and every path link were rewritten - left alone deliberately: `internal/serve.wo`, `pub fn serve`, `serve_conn`, `app.serve(...)` — those are functions, not the module name - web-app/wo.toml comment corrected: it claimed hyphens are not identifier characters and named a key this file never used. lexer.ml's `is_ident_cont` DOES accept `-` (an internal dash is part of the identifier, which is why binary minus needs spaces), so a hyphenated key would be legal too - site now teaches the name: package card, the two-deps chapter and the handlers-are-classes chapter say `porch`; site-accept asserted the old /packages/serve route and caught the rename, as a gate should - gates: web-app 46/0, site 21/0, deps-accept 8/0, oop-e2e 116/0, linkcheck 0 broken / 0 anchors; porch typechecks entry-less as kind=library Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c0b0dbb846
commit
ffd791d05b
48 changed files with 97 additions and 84 deletions
10
README.md
10
README.md
|
|
@ -33,7 +33,7 @@ program ships as one file that depends only on the system C library.
|
|||
|
||||
writeonce is a systems language whose distinguishing feature is the embedded
|
||||
database. HTTP/1.1 and WebSockets **do** work today — but as `.wo` libraries you
|
||||
consume through `[deps]` (`writeonce-serve` for serving, `writeonce-view` for
|
||||
consume through `[deps]` (`porch` for serving, `writeonce-view` for
|
||||
HTML), never as runtime features: the runtime stays framework-agnostic on
|
||||
purpose. TLS is always terminated by a proxy in front. If you have seen an older
|
||||
"writeonce" that served REST from `cargo run`, that was a separate, earlier
|
||||
|
|
@ -282,13 +282,13 @@ dependencies, declared in the manifest:
|
|||
|
||||
```toml
|
||||
[deps]
|
||||
serve = { git = "https://github.com/shoneyj/writeonce-serve", rev = "v0.1.0" }
|
||||
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
|
||||
```
|
||||
|
||||
**The `[deps]` key IS the module name** `use` imports — the repository name
|
||||
never appears in your source. `woc` fetches each dep (via the `git` binary)
|
||||
into `.wo-deps/<name>/`, pins the resolved commit in `wo.lock`, and `use serve`
|
||||
(or `use serve/router`) imports its public names like any module. Builds never
|
||||
into `.wo-deps/<name>/`, pins the resolved commit in `wo.lock`, and `use porch`
|
||||
(or `use porch/router`) imports its public names like any module. Builds never
|
||||
touch the network once the lock is satisfied; a moved tag is reported, and
|
||||
`woc --update-deps myproject/` refreshes the lock deliberately. Flat
|
||||
dependencies only (a dep may not have its own `[deps]`) — honest and small,
|
||||
|
|
@ -318,7 +318,7 @@ reading first:
|
|||
just employee # compile + run every mode against a durable database
|
||||
```
|
||||
|
||||
- **`docs/examples/writeonce-serve/` + `docs/examples/web-app/`** — a web
|
||||
- **`docs/examples/porch/` + `docs/examples/web-app/`** — a web
|
||||
framework written in writeonce (HTTP/1.1 behind a TLS-terminating proxy,
|
||||
router with `:param` captures, interface-based handlers) and a storefront
|
||||
consuming it **as a `[deps]` dependency**, with `@table` persistence. Run:
|
||||
|
|
|
|||
|
|
@ -65,13 +65,13 @@ Three of its six entries have shipped:
|
|||
> WebSockets, or a UI."
|
||||
|
||||
Contradicted 30 lines later by its own §"Worked examples" (`:306-313`), which
|
||||
describes `docs/examples/writeonce-serve/` as "a web framework written in
|
||||
describes `docs/examples/porch/` as "a web framework written in
|
||||
writeonce (HTTP/1.1 …, router with `:param` captures, interface-based
|
||||
handlers)" and `just web-app` as its gate. Also contradicted by:
|
||||
|
||||
- iteration 16 (web framework) and 37 (wo-html components), both `done/`;
|
||||
- `docs/examples/site/` — server-rendered pages, gated by `just site`;
|
||||
- WebSockets: `docs/examples/writeonce-serve/http/ws.wo` (`ws_accept`, the 101
|
||||
- WebSockets: `docs/examples/porch/http/ws.wo` (`ws_accept`, the 101
|
||||
hijack sentinel) and `http/wsframe.wo` (a pure-`.wo` RFC 6455 frame codec),
|
||||
both landed per `docs/in-progress/2026-08-23-chat-ws-lifecycle.md` (T6, T7).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
# writeonce-serve
|
||||
> Renamed 2026-08-25: this library was `writeonce-framework`, imported as
|
||||
> `use framework`. Stories, specs and plans dated before that still say the
|
||||
> old name — they are dated records and were left as written.
|
||||
# porch — the writeonce web framework
|
||||
|
||||
> **Named `porch` on 2026-08-26.** Rename history: `writeonce-framework`
|
||||
> (`use framework`) → `writeonce-serve` (`use serve`, 2026-08-25) → **`porch`**
|
||||
> (`use porch`). Stories, specs, plans and the audit reports dated before each
|
||||
> change still say the older name — they are dated records and were left as
|
||||
> written, which is the repo's convention.
|
||||
>
|
||||
> Why `porch`: the structure in front of the house you actually enter through,
|
||||
> and in writeonce the house *is* the database. The name appears only in `use`
|
||||
> lines and the `[deps]` key — names resolve bare through `use` edges, so no
|
||||
> handler body mentions it.
|
||||
|
||||
A web framework **written in writeonce**, consumed as a `[deps]` dependency
|
||||
(iteration 15). Spec: `docs/superpowers/specs/2026-08-18-web-framework-design.md` §B.
|
||||
|
||||
```toml
|
||||
[deps]
|
||||
writeonce-serve = { git = "https://github.com/shoneyj/writeonce-serve", rev = "v0.1.0" }
|
||||
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
|
||||
```
|
||||
|
||||
## What it is
|
||||
|
|
@ -184,7 +192,7 @@ naming the kind, unless a demo `main` is added (lib+bin is allowed).
|
|||
- **`internal/` — not importable by a consumer.** The connection-level request
|
||||
parser and carry-state record (`parse.wo`) and the serve loop, status text,
|
||||
and response serializer (`serve.wo`) live here. A consuming app that writes
|
||||
`use writeonce-serve/internal` gets **WO-E108** at that `use`. The rule
|
||||
`use porch/internal` gets **WO-E108** at that `use`. The rule
|
||||
is Go's: a path segment named `internal` is refused across the `[deps]`
|
||||
boundary only — the framework's own modules import it freely.
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name = "writeonce-serve"
|
||||
name = "porch"
|
||||
kind = "library"
|
||||
version = "0.1.0"
|
||||
description = "A web framework written in writeonce: HTTP/1.1 keep-alive server core, router with :param captures, Handler/Middleware structural interfaces (iteration 16)"
|
||||
|
|
@ -9,4 +9,4 @@ wo = ">= 0.1"
|
|||
# A LIBRARY project (declared above since iteration 17): no `fn main` here —
|
||||
# the consuming app owns the entry. `woc <dir>` typechecks the whole project.
|
||||
# Apps import this repo through `wo.toml [deps]` (iteration 15) and
|
||||
# `use serve` / `use serve/http` / `use serve/router`.
|
||||
# `use porch` / `use porch/http` / `use porch/router`.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
-- wrapping every page with the shared header and footer. Styles are NOT
|
||||
-- inlined — pages link /assets/style.css (served by static_files), so
|
||||
-- markup and styling stay separate files.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use view
|
||||
|
||||
-- The app shell as a COMPONENT (writeonce-view's `Component`: fields in, Text
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
--
|
||||
-- WO_DATA=./data ./target/shop 8080 (run from the shop directory:
|
||||
-- /assets/* serves from ./assets)
|
||||
use serve
|
||||
use serve/http
|
||||
use serve/router
|
||||
use porch
|
||||
use porch/http
|
||||
use porch/router
|
||||
use product_list
|
||||
use product_page
|
||||
use orders
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
-- orders/controller.wo — the buying flow: stock-checked order creation
|
||||
-- (decrement + insert are each WAL-committed before they acknowledge)
|
||||
-- and the orders list (ref navigation: o.product.name).
|
||||
use serve/http
|
||||
use porch/http
|
||||
use layout
|
||||
use time
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
-- directory = one module, view and controller together. The @tables it
|
||||
-- queries live in the root module, which is fine: a CLASS is reachable
|
||||
-- across module lines (only free `fn`s are module-scoped).
|
||||
use serve/http
|
||||
use porch/http
|
||||
use layout
|
||||
|
||||
pub class ListProducts {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- product_page/controller.wo — the CONTROLLER for /p/:sku.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use layout
|
||||
|
||||
pub class ShowProduct {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ wo = ">= 0.1"
|
|||
# Two library dependencies, the site sample's proven shape. The [deps]
|
||||
# KEY is the module name `use` imports.
|
||||
[deps]
|
||||
serve = { git = "https://github.com/shoneyj/writeonce-serve", rev = "v0.1.0" }
|
||||
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
|
||||
view = { git = "https://github.com/shoneyj/writeonce-view", rev = "v0.1.0" }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ the whole thing is chapter 9's own example.
|
|||
|
||||
```
|
||||
[deps]
|
||||
serve = { git = "https://github.com/shoneyj/writeonce-serve", rev = "v0.1.0" }
|
||||
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
|
||||
view = { git = "https://github.com/shoneyj/writeonce-view", rev = "v0.1.0" }
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
-- form-encoded, bearer-gated. Mechanism (bearer_token, constant-time
|
||||
-- ct_eq) is the framework's; POLICY — which routes, which token — is
|
||||
-- this app's, right here. No rendering: the answer is a redirect.
|
||||
use serve/http
|
||||
use porch/http
|
||||
|
||||
pub class AdminEdit {
|
||||
token: Text
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
-- chapter/controller.wo — the CONTROLLER for `/ch/:slug`. It queries the
|
||||
-- model and fills the view components that sit beside it in this module;
|
||||
-- a view receives VALUES, never a cursor.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use layout
|
||||
|
||||
pub class ShowChapter {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ fn ch_containers() -> Text {
|
|||
|
||||
fn ch_classes() -> Text {
|
||||
let b = el("p", "leading-relaxed mb-4",
|
||||
"Classes hold fields and methods. There are NO function values and NO closures — a " .. "deliberate doctrine: behavior travels as a class satisfying an interface, and " .. "satisfaction is structural (same method name and shape, Go-style, no " .. "<code>implements</code>). This is how the web framework takes handlers.");
|
||||
"Classes hold fields and methods. There are NO function values and NO closures — a " .. "deliberate doctrine: behavior travels as a class satisfying an interface, and " .. "satisfaction is structural (same method name and shape, Go-style, no " .. "<code>implements</code>). This is how <code>porch</code>, the web framework, takes handlers.");
|
||||
b = b .. code_block("interface Handler {\n fn handle(req: Req) -> Resp\n}\n\nclass Hello {\n greeting: Text\n fn handle(req: Req) -> Resp {\n return ok_text(\"\${self.greeting}, \${req.path}\");\n }\n}\n\n-- any class with a matching handle() satisfies Handler\napp.get(\"/hello\", Hello { greeting: \"hi\" });");
|
||||
return b;
|
||||
}
|
||||
|
|
@ -61,14 +61,14 @@ fn ch_actors() -> Text {
|
|||
|
||||
fn ch_deps() -> Text {
|
||||
let b = el("p", "leading-relaxed mb-4",
|
||||
"Dependencies are git repositories pinned in <code>wo.toml</code>; <code>wo.lock</code> " .. "records the exact revision, and locked builds work offline. The [deps] KEY names the " .. "module you <code>use</code>. This site has two: the web framework, and the writeonce-view " .. "library that rendered the page you are reading.");
|
||||
"Dependencies are git repositories pinned in <code>wo.toml</code>; <code>wo.lock</code> " .. "records the exact revision, and locked builds work offline. The [deps] KEY names the " .. "module you <code>use</code>. This site has two: <code>porch</code>, the writeonce web framework, and the writeonce-view " .. "library that rendered the page you are reading.");
|
||||
b = b .. code_block(`
|
||||
[deps]
|
||||
serve = { git = "https://github.com/shoneyj/writeonce-serve", rev = "v0.1.0" }
|
||||
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
|
||||
view = { git = "https://github.com/shoneyj/writeonce-view", rev = "v0.1.0" }`);
|
||||
b = b .. code_block(`
|
||||
use serve
|
||||
use serve/http
|
||||
use porch
|
||||
use porch/http
|
||||
use view
|
||||
|
||||
-- html's builders + tailwind-style utilities, zero JS, no build step:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
-- favicon/controller.wo — GET /favicon.svg. The one route that answers
|
||||
-- something other than HTML or text, so it builds its own Resp.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use layout
|
||||
|
||||
pub class Favicon {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
-- health.controller.wo — GET /health: the liveness probe the accept
|
||||
-- script and any proxy poll. Text, not HTML, on purpose.
|
||||
use serve/http
|
||||
use porch/http
|
||||
|
||||
pub class Health {
|
||||
fn handle(req: Req) -> Resp {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
-- It reaches the chapter nav through `use chapter` and the query through
|
||||
-- the model's `Chapters.links()` static — a class crosses module lines,
|
||||
-- a free fn does not.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use layout
|
||||
use chapter
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
-- install/controller.wo — GET /install. Nothing to query: the page is
|
||||
-- static copy, so the controller only wraps it in the shell.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use layout
|
||||
|
||||
pub class ShowInstall {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
-- The only thing that varies between pages is the container width, so
|
||||
-- that is the one extra slot — and the two widths are named once, here,
|
||||
-- instead of as class strings scattered through the controllers.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use view
|
||||
|
||||
pub class AppShell {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-- site — writeonce.de: the language tutorial, served BY the language.
|
||||
-- Full stack in one binary: writeonce-serve ([deps]) for HTTP/routing/
|
||||
-- Full stack in one binary: porch ([deps]) for HTTP/routing/
|
||||
-- auth, writeonce-view ([deps]) for server-rendered pages with Tailwind-style
|
||||
-- utilities, @table + WAL for the chapters themselves. The site is its own
|
||||
-- final chapter: /ch/serving shows this file's shape.
|
||||
|
|
@ -15,9 +15,9 @@
|
|||
-- model is types.wo; every feature is a directory holding its view and
|
||||
-- its controller.
|
||||
use env
|
||||
use serve
|
||||
use serve/http
|
||||
use serve/router
|
||||
use porch
|
||||
use porch/http
|
||||
use porch/router
|
||||
use home
|
||||
use chapter
|
||||
use install
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
-- The catalogue is static data, not rows, so it lives here on the DATA
|
||||
-- side of the feature rather than in the view: the components are handed
|
||||
-- values exactly as they would be if this were a table one day.
|
||||
use serve/http
|
||||
use porch/http
|
||||
use view
|
||||
use layout
|
||||
|
||||
|
|
@ -54,9 +54,9 @@ fn catalogue() -> multi PackageInfo {
|
|||
let out: multi PackageInfo = [];
|
||||
|
||||
push(out, PackageInfo {
|
||||
name: "serve",
|
||||
summary: "The web framework: an HTTP/1.1 keep-alive server core, a router with :param captures, and Handler/Middleware structural interfaces.",
|
||||
git: "https://github.com/shoneyj/writeonce-serve",
|
||||
name: "porch",
|
||||
summary: "The writeonce web framework: an HTTP/1.1 keep-alive server core, a router with :param captures, and Handler/Middleware structural interfaces. Imported as <code>use porch</code>.",
|
||||
git: "https://github.com/shoneyj/porch",
|
||||
rev: "v0.1.0",
|
||||
what: `
|
||||
<ul class="list-disc pl-6 leading-relaxed">
|
||||
|
|
@ -67,7 +67,7 @@ fn catalogue() -> multi PackageInfo {
|
|||
<li class="mb-2">Auth MECHANISM only — <code>bearer_token</code>, <code>basic_credentials</code>, constant-time <code>ct_eq</code>. Which routes are gated stays your policy.</li>
|
||||
<li class="mb-2">Bodies: <code>form_values</code>, <code>multipart_parts</code>, content negotiation, ETags, security headers, CORS, WebSocket frames.</li>
|
||||
</ul>`,
|
||||
usage: code_block("use serve\nuse serve/http\nuse serve/router\n\nclass Hello {\n fn handle(req: Req) -> Resp {\n return ok_text(\"hello\");\n }\n}\n\nfn main(args: multi Text) -> Int {\n let app = App { middleware: [], routes: [] };\n app.use_mw(Mw { m: Logging {} });\n app.get(\"/\", Hello {});\n return app.serve(\"127.0.0.1\", 8080);\n}")
|
||||
usage: code_block("use porch\nuse porch/http\nuse porch/router\n\nclass Hello {\n fn handle(req: Req) -> Resp {\n return ok_text(\"hello\");\n }\n}\n\nfn main(args: multi Text) -> Int {\n let app = App { middleware: [], routes: [] };\n app.use_mw(Mw { m: Logging {} });\n app.get(\"/\", Hello {});\n return app.serve(\"127.0.0.1\", 8080);\n}")
|
||||
});
|
||||
|
||||
push(out, PackageInfo {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ description = "writeonce.de — the language tutorial served by the language: fr
|
|||
wo = ">= 0.1"
|
||||
|
||||
# Two real dependencies (the gate substitutes file:// remotes built from
|
||||
# docs/examples/writeonce-serve and docs/examples/writeonce-view, so CI never
|
||||
# docs/examples/porch and docs/examples/writeonce-view, so CI never
|
||||
# touches the network). The [deps] KEY is the module name `use` imports.
|
||||
[deps]
|
||||
serve = { git = "https://github.com/shoneyj/writeonce-serve", rev = "v0.1.0" }
|
||||
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
|
||||
view = { git = "https://github.com/shoneyj/writeonce-view", rev = "v0.1.0" }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# web-app — the storefront sample
|
||||
|
||||
A small store: `Product`/`Order` as `@table` classes, JSON routes, one auth
|
||||
middleware — built on [`writeonce-serve`](../writeonce-serve/), which
|
||||
middleware — built on [`porch`](../porch/), which
|
||||
it imports **through `[deps]`** (iteration 15). This app is iteration 16's
|
||||
acceptance workload: `just web-app` runs the whole chain — fetch → lock →
|
||||
build → serve → curl matrix → restart persistence → SIGTERM.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
-- web-app — the storefront: writeonce-serve (via [deps]) + @table
|
||||
-- web-app — the storefront: porch (via [deps]) + @table
|
||||
-- persistence. Every handler is a class satisfying Handler; the auth gate is
|
||||
-- a Middleware; the data layer is the language's own database — no ORM, no
|
||||
-- separate process, one binary.
|
||||
|
|
@ -6,9 +6,9 @@ use env
|
|||
use json
|
||||
use net
|
||||
use time
|
||||
use serve
|
||||
use serve/http
|
||||
use serve/router
|
||||
use porch
|
||||
use porch/http
|
||||
use porch/router
|
||||
|
||||
-- decode target for POST /products, encode shape for every product answer
|
||||
typedef ProductView = { name: Text, price: Float, stock: Int }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "web-app"
|
||||
version = "0.1.0"
|
||||
description = "Storefront sample: consumes writeonce-serve through [deps]; @table persistence; iteration 16's acceptance workload"
|
||||
description = "Storefront sample: consumes porch through [deps]; @table persistence; iteration 16's acceptance workload"
|
||||
|
||||
[runtime]
|
||||
wo = ">= 0.1"
|
||||
|
|
@ -11,9 +11,14 @@ wo = ">= 0.1"
|
|||
# The framework is a real dependency, never a relative path — extraction of
|
||||
# the framework to its own repository changes only this URL. The acceptance
|
||||
# gate (scripts/web-app-accept.sh) substitutes a run-time file:// remote
|
||||
# built from docs/examples/writeonce-serve, so CI never needs the
|
||||
# built from docs/examples/porch, so CI never needs the
|
||||
# network and this repo never carries .wo-deps/wo.lock artifacts.
|
||||
# The [deps] KEY is the module name `use` imports (hyphens are not identifier
|
||||
# characters, so the key is `framework` while the repository keeps its name).
|
||||
# The [deps] KEY is the module name `use` imports — here `porch`, so the source
|
||||
# says `use porch`. The key must be a legal identifier, which per
|
||||
# compiler/src/lexer.ml's `is_ident_cont` DOES include `-` (an internal dash is
|
||||
# part of the identifier: `a-b` is one name, which is why binary minus needs
|
||||
# spaces around it). So a hyphenated key like `wo-serve` would be legal too;
|
||||
# `porch` is simply shorter. An earlier version of this comment claimed hyphens
|
||||
# were illegal and named a key this file has never used — both wrong.
|
||||
[deps]
|
||||
serve = { git = "https://github.com/shoneyj/writeonce-serve", rev = "v0.1.0" }
|
||||
porch = { git = "https://github.com/shoneyj/porch", rev = "v0.1.0" }
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ goes to find what took each one's place.
|
|||
| `08-sendfile-static-assets.md` | Nothing. `sendfile` is not exposed; static assets are served as `Text` through `net.write` |
|
||||
| `15-mcp-streamable-http.md` | [`28-skillhost-host-workload.md`](../stories/language-runtime-database/28-skillhost-host-workload.md) — MCP transport is that story's Blocker B |
|
||||
| `16-postgres-mirror.md` | Nothing — the mirror-is-backup doctrine holds, but no iteration owns it and there are no outbound sockets to reach a mirror with ([`refine/38`](../stories/language-runtime-database/38-content-platform-capabilities.md)) |
|
||||
| `02-event-loop-epoll.md`, `03-hand-rolled-http.md` | `runtime/src/park.c` (io_uring with an epoll fallback) and the `.wo` framework `writeonce-serve` |
|
||||
| `02-event-loop-epoll.md`, `03-hand-rolled-http.md` | `runtime/src/park.c` (io_uring with an epoll fallback) and the `.wo` framework `porch` |
|
||||
| `04-cutover-remove-tokio-axum.md` | Completed by the Rust-track removal itself — nothing left to cut over |
|
||||
| `runtime/database/03-inmemory-engine.md` | [`database/src/CODE-LOGIC.md`](../../database/src/CODE-LOGIC.md) + [`plan/oop-vm/04-db-binding.md`](oop-vm/04-db-binding.md) |
|
||||
| `runtime/database/02-wo-language.md` | [`docs/guides/language-surface.md`](../guides/language-surface.md) |
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# Fiber parity study — what a mainstream Go web framework ships that `writeonce-serve` does not
|
||||
# Fiber parity study — what a mainstream Go web framework ships that `porch` does not
|
||||
|
||||
Reference: [gofiber/fiber](https://github.com/gofiber/fiber) **v3.5.0**, read
|
||||
2026-08-26 from a shallow clone at `.dev/reference/fiber` (gitignored — re-clone
|
||||
with `git clone --depth 1 https://github.com/gofiber/fiber .dev/reference/fiber`).
|
||||
Read against `docs/examples/writeonce-serve` as it stands the same day.
|
||||
Read against `docs/examples/porch` as it stands the same day.
|
||||
|
||||
Why Fiber and not Express or Axum: it is the closest structural analogue in the
|
||||
reference set. Compiled language, no runtime, one binary, an explicit
|
||||
`Ctx`-per-request, and middleware as an ordered chain — the same shape
|
||||
`writeonce-serve` already has. Where it differs, the difference is a feature
|
||||
`porch` already has. Where it differs, the difference is a feature
|
||||
decision rather than a paradigm gap, which is what makes the comparison useful.
|
||||
Express would have contributed mostly "you have no closures".
|
||||
|
||||
|
|
@ -17,8 +17,8 @@ What was actually read: `app.go` (routing methods, the 53-field `Config`),
|
|||
(binding), `hooks.go` (lifecycle), and the `Config` struct of every one of the
|
||||
**32** packages under `middleware/`.
|
||||
|
||||
**Headline: `writeonce-serve` is further along than its size suggests.** Of
|
||||
Fiber's 32 middleware packages, 9 already have a working `writeonce-serve`
|
||||
**Headline: `porch` is further along than its size suggests.** Of
|
||||
Fiber's 32 middleware packages, 9 already have a working `porch`
|
||||
counterpart (CORS, basic auth, key/bearer auth, helmet-style security headers,
|
||||
ETag, static files, logger, host authorization, recover-as-500). The gaps are
|
||||
real but they are mostly *breadth*, and they cluster around four things:
|
||||
|
|
@ -31,7 +31,7 @@ primitive nobody had noticed**.
|
|||
|
||||
## 0. The blocker the existing ledger gets wrong
|
||||
|
||||
`docs/examples/writeonce-serve/README.md`'s crypto row currently reads:
|
||||
`docs/examples/porch/README.md`'s crypto row currently reads:
|
||||
|
||||
> Unlocks (signed cookies, CSRF, session integrity, webhook verification, JWT
|
||||
> HS256) | ⬜ **UNBLOCKED** (the primitives exist since iteration 34)
|
||||
|
|
@ -64,7 +64,7 @@ This is the study's most valuable single finding and it is the reason iteration
|
|||
|
||||
## 1. Cookies — absent, and foundational
|
||||
|
||||
`writeonce-serve` has **no cookie support in either direction**. `Req` has
|
||||
`porch` has **no cookie support in either direction**. `Req` has
|
||||
`headers: map<Text, Text>` and nothing parses `Cookie:`; `Resp` has
|
||||
`headers: map<Text, Text>` and there is no `Set-Cookie` builder — and because
|
||||
`Resp.headers` is a *map*, it structurally cannot carry the two `Set-Cookie`
|
||||
|
|
@ -75,7 +75,7 @@ Fiber, for comparison: `Req.Cookies(key)`, `Res.Cookie(*Cookie)` with
|
|||
`ClearCookie`, and a `Cookie` struct carrying Path, Domain, MaxAge, Expires,
|
||||
Secure, HTTPOnly, SameSite, Partitioned and SessionOnly.
|
||||
|
||||
| Piece | Fiber | writeonce-serve |
|
||||
| Piece | Fiber | porch |
|
||||
| --- | --- | --- |
|
||||
| read request cookies | `Req.Cookies(key)` | — |
|
||||
| set a response cookie | `Res.Cookie(&Cookie{...})` | — |
|
||||
|
|
@ -156,7 +156,7 @@ stdlib doing its job, not a defect.
|
|||
|
||||
## 5. Routing and response ergonomics — mostly sugar, cheap to close
|
||||
|
||||
| Gap | Fiber | writeonce-serve |
|
||||
| Gap | Fiber | porch |
|
||||
| --- | --- | --- |
|
||||
| method helpers | Get/Post/Put/Delete/Patch/Head/Options/Trace/Connect/All/Add | `get`/`post`/`put`/`delete_` only — a `Route { method: "PATCH" }` literal works, so this is registration sugar, but its absence is felt |
|
||||
| route names + URL building | `Name()`, `GetRouteURL()` | — (no named routes, no reverse routing) |
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ sees through Interp; same corpus pin). Body-parsing hooks: all three ✅.
|
|||
|
||||
Scope split (2026-08-20): the surface above plus the remaining transport/
|
||||
routing/security gaps is **framework v1**, tracked item-by-item in the
|
||||
[framework README's status ledger](../examples/writeonce-serve/README.md)
|
||||
[framework README's status ledger](../examples/porch/README.md)
|
||||
(✅/🔶/⬜/⏸/🔧 per feature — timeouts and Unix sockets need `net` runtime
|
||||
seams, crypto hashes need C builtins since the language has no bitwise
|
||||
operators, streaming/cancellation park behind 8/11). The memory-rich
|
||||
|
|
@ -348,7 +348,7 @@ that sequences its tasks. Read one, approve, then the next starts.
|
|||
| 33 | [Single-file store](language-runtime-database/33-single-file-db.md) | ⬜ off-chain, small — `WO_DATA=<path>.db` file form; driver-only (story written 2026-08-22) |
|
||||
| 34 | [Crypto builtins](language-runtime-database/34-crypto-builtins.md) | 🔄 **code landed** as 24's T1 (`d14fa9f`): `sha1`/`sha256`/`hmac_sha256`, ids 85–87 in `wob.h`, `runtime/src/crypto.c`, RFC/FIPS vectors 18/0, corpus pin. The 24 gate that once needed it is cleared. Frontmatter keeps `status: refine` only until 24's T10 closeout sets it to `done` |
|
||||
| 38 | [Content platform capabilities](language-runtime-database/38-content-platform-capabilities.md) | ⬜ off-chain, needs a spec — the two capability families no iteration owns, confirmed against `runtime/src/wob.h`: `fs` mutation verbs (six fs builtins, ids 40–45; `append` creates-if-absent, so nothing is ever replaced, truncated, deleted or renamed) and `net.connect` (ids 51–55 + 91–95, no connect, and no `connect()` anywhere in `runtime/src/` — so no OIDC/SMTP/object-store/webhook/federation). Driven by a `docs/examples/vault` content-collaboration workload, in 28's mould. New builtins from 96 (89/90 reserved for 31); no `.wob` bump (`WOB_VERSION 6u`, last moved by 36). Story written 2026-08-26 from the "can it build a Nextcloud?" ask |
|
||||
| 39 | [Web framework parity](language-runtime-database/39-web-framework-parity.md) | ⬜ off-chain, needs a spec — from [the Fiber v3.5.0 study](../plan/exploration/fiber/00-fiber-parity.md) (all 32 of its middleware read against `writeonce-serve`; **nine already have a counterpart**). Leads with a **random-bytes builtin**: the framework ledger claimed CSRF/sessions were unblocked by iteration 34's HMAC, but HMAC authenticates a token and cannot mint one — there is no RNG anywhere in the runtime. Then cookies (absent both ways; `Resp.headers` being a map cannot carry two `Set-Cookie` lines), then limiter/idempotency (cheapest wins — `@table` + `time.ticks`, nothing new), sessions, CSRF, and the routing/response sugar. Streaming/SSE/compression, `@derive` binding, TTL cache, `proxy` and metrics all excluded with owners named |
|
||||
| 39 | [Web framework parity](language-runtime-database/39-web-framework-parity.md) | ⬜ off-chain, needs a spec — from [the Fiber v3.5.0 study](../plan/exploration/fiber/00-fiber-parity.md) (all 32 of its middleware read against `porch`; **nine already have a counterpart**). Leads with a **random-bytes builtin**: the framework ledger claimed CSRF/sessions were unblocked by iteration 34's HMAC, but HMAC authenticates a token and cannot mint one — there is no RNG anywhere in the runtime. Then cookies (absent both ways; `Resp.headers` being a map cannot carry two `Set-Cookie` lines), then limiter/idempotency (cheapest wins — `@table` + `time.ticks`, nothing new), sessions, CSRF, and the routing/response sugar. Streaming/SSE/compression, `@derive` binding, TTL cache, `proxy` and metrics all excluded with owners named |
|
||||
| 37 | [wo-html components](language-runtime-database/37-wo-html-components.md) | ✅ off-chain — LANDED 2026-08-25. Raw text literal (backtick, margin stripped at lex time, `{{ }}` auto-escapes) + the component layer: `Component`/`render_all`/`Layout` in wo-html, `ok_html` moved into the framework, site and shop both migrated |
|
||||
| 35 | [net runtime seams](language-runtime-database/35-net-runtime-seams.md) | ⬜ off-chain — fd deadlines on the park plane, Unix sockets, peer address; owns the ledger's three 🔧 rows (story written 2026-08-22) |
|
||||
| 20 | [Cross-program tables](language-runtime-database/20-cross-program-tables.md) | ⏸ hold (2026-08-21); channel done (branch ipc-attach keeps its manifest) |
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ still pending IS the runtime-concurrency chain; order:
|
|||
| 30 | 28 | [skillhost host workload](28-skillhost-host-workload.md) | host-shaped driving workload naming runtime gaps — demoted with the framework goal. *(was 14)* |
|
||||
| 31 | 29 | [Compile-time metaprogramming](29-compile-time-metaprogramming.md) | `@derive(...)` from class-table metadata; held with the parked drain by the 2026-08-08 scope directive. *(was 13)* |
|
||||
| 32 | 38 | [Content platform capabilities](38-content-platform-capabilities.md) | **NEW 2026-08-26** — the two capability families nothing owns, read off `wob.h`: `fs` mutation (`write`/`remove`/`rename`/`mkdir` — the table holds exactly six fs builtins, ids 40–45, where `append` creates-if-absent and grows, so a file is never replaced, truncated, deleted or renamed) and `net.connect` (ids 51–55 + 91–95, no connect; no `connect()` call in `runtime/src/` at all — which is every identity/notification/federation story at once; 35 deferred connect-side timeouts as "no workload asks yet" — this is the ask). Driven by a `docs/examples/vault` content-collaboration workload in 28's mould; WebDAV, CRDT editing, previews and FTS each get a written verdict instead of an implication. New builtins start at 96 (89/90 are 31's reserved holes); no `.wob` bump. Off-chain, wants a spec. |
|
||||
| 33 | 39 | [Web framework parity](39-web-framework-parity.md) | **NEW 2026-08-26** — gofiber/fiber v3.5.0 added as the web-framework reference and read end to end ([the study](../../plan/exploration/fiber/00-fiber-parity.md)); nine of its 32 middleware already have a `writeonce-serve` counterpart, so the gaps are breadth, not foundations — with one exception. The study's sharpest finding: the framework's own ledger said CSRF and sessions were UNBLOCKED because iteration 34 landed HMAC, but **there is no source of randomness in the runtime at all**, and an HMAC over a guessable session id is a signed guess. So 39 leads with a random-bytes builtin, then cookies (absent in both directions; `Resp.headers: map<Text,Text>` structurally cannot emit two `Set-Cookie` lines), then the store-backed chain — limiter and idempotency first since they need only `@table` + `time.ticks`. Off-chain, wants a spec. |
|
||||
| 33 | 39 | [Web framework parity](39-web-framework-parity.md) | **NEW 2026-08-26** — gofiber/fiber v3.5.0 added as the web-framework reference and read end to end ([the study](../../plan/exploration/fiber/00-fiber-parity.md)); nine of its 32 middleware already have a `porch` counterpart, so the gaps are breadth, not foundations — with one exception. The study's sharpest finding: the framework's own ledger said CSRF and sessions were UNBLOCKED because iteration 34 landed HMAC, but **there is no source of randomness in the runtime at all**, and an HMAC over a guessable session id is a signed guess. So 39 leads with a random-bytes builtin, then cookies (absent in both directions; `Resp.headers: map<Text,Text>` structurally cannot emit two `Set-Cookie` lines), then the store-backed chain — limiter and idempotency first since they need only `@table` + `time.ticks`. Off-chain, wants a spec. |
|
||||
| ✅ | 17 | [library projects + `internal/`](17-library-projects-internal.md) | **LANDED 2026-08-20** — `kind = "library"` + entry-less check mode (retires the `--emit` workaround) and Go's `internal/` rule as WO-E108 at the consumer's `use`; driver-only, VM/GC untouched. `just web-app` 26/0 |
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ status: hold
|
|||
|
||||
> **Scope label (2026-08-20): this iteration is FRAMEWORK V2.** Framework
|
||||
> v1 is the transport/routing/body/security surface tracked in the
|
||||
> [framework README's status ledger](../../examples/writeonce-serve/README.md);
|
||||
> [framework README's status ledger](../../examples/porch/README.md);
|
||||
> v2 is what the embedded store adds on top. v1 gaps land before or
|
||||
> alongside v2 as slices, per the ledger.
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ status: refine
|
|||
authenticated users, folders and documents as `@table` classes related by
|
||||
`ref`/`backlink`, multipart upload intake, content-addressed blobs, share
|
||||
grants with expiry, a version chain per document, a trash bucket, an
|
||||
activity feed, and a server-rendered UI over `writeonce-serve` +
|
||||
activity feed, and a server-rendered UI over `porch` +
|
||||
`writeonce-view`. Everything above the storage line is expressible on
|
||||
today's toolchain and is the part that ships.
|
||||
- **`fs` grows the mutation verbs.** The gap is not ergonomic. A blob store
|
||||
|
|
@ -150,7 +150,7 @@ iteration 36's opcodes 42–46; stdlib builtins are a table row, exactly as
|
|||
iteration 35's 91–95 were.
|
||||
|
||||
What already exists and needs no new capability, verified against the tree:
|
||||
`http/multipart` and `http/auth` in `writeonce-serve`; `sha1`/`sha256`/
|
||||
`http/multipart` and `http/auth` in `porch`; `sha1`/`sha256`/
|
||||
`hmac_sha256` (builtin ids 85–87) for content addressing, ETags and session
|
||||
tokens; `base64_encode`/`decode` and the `Bytes` carrier; `ws_accept` plus
|
||||
the pure-`.wo` RFC 6455 frame codec for the activity feed; `Component`/
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ status: refine
|
|||
> find the basic features the web framework lacks). Derived from
|
||||
> [the Fiber v3.5.0 parity study](../../plan/exploration/fiber/00-fiber-parity.md),
|
||||
> which read Fiber's routing surface, `Req`/`Res` API, binder and all 32 of its
|
||||
> `middleware/` packages against `docs/examples/writeonce-serve`. This iteration
|
||||
> `middleware/` packages against `docs/examples/porch`. This iteration
|
||||
> takes that study's §0–§2 plus the cheap half of §5; the study names an owner
|
||||
> for everything it leaves out.
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ excluded because someone else owns it — the study's §7 is the full map.
|
|||
|
||||
Fiber v3.5.0, `.dev/reference/fiber` (gitignored; the study carries the clone
|
||||
command). Of its 32 middleware packages, **nine already have a working
|
||||
`writeonce-serve` counterpart** — CORS, basic auth, key/bearer auth,
|
||||
`porch` counterpart** — CORS, basic auth, key/bearer auth,
|
||||
helmet-style security headers, ETag, static files, logger, host authorization,
|
||||
and recover-as-500. The framework is further along than its size suggests; the
|
||||
gaps are breadth, not foundations, with the single exception of randomness.
|
||||
|
|
|
|||
4
justfile
4
justfile
|
|
@ -41,7 +41,7 @@ deps-accept:
|
|||
./scripts/deps-accept.sh
|
||||
|
||||
# web-app: iteration 16's gate — the whole chain at run time, network-free:
|
||||
# a temp git remote from docs/examples/writeonce-serve, file:// URL
|
||||
# a temp git remote from docs/examples/porch, file:// URL
|
||||
# substituted into a temp copy of docs/examples/web-app, then fetch -> lock
|
||||
# -> build -> serve -> the storefront matrix (auth/CRUD/@unique/FK/404/400/
|
||||
# pipelining) -> SIGTERM -> WAL restart persistence.
|
||||
|
|
@ -49,7 +49,7 @@ web-app:
|
|||
./scripts/web-app-accept.sh
|
||||
|
||||
# site: the writeonce.de tutorial (docs/examples/site) — TWO deps
|
||||
# (serve + view), server-rendered pages, page matrix -> 401/edit
|
||||
# (porch + view), server-rendered pages, page matrix -> 401/edit
|
||||
# -> SIGTERM -> WAL restart persistence. `just site` runs it.
|
||||
site:
|
||||
./scripts/site-accept.sh
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ cleanup() {
|
|||
trap cleanup EXIT
|
||||
|
||||
# ---- both deps as git remotes; the app pointed at them ----
|
||||
cp -r "$ROOT/docs/examples/writeonce-serve" "$W/fw"
|
||||
cp -r "$ROOT/docs/examples/porch" "$W/fw"
|
||||
cp -r "$ROOT/docs/examples/writeonce-view" "$W/lib"
|
||||
for d in "$W/fw" "$W/lib"; do
|
||||
git -C "$d" init -q
|
||||
|
|
@ -36,7 +36,7 @@ for d in "$W/fw" "$W/lib"; do
|
|||
git -C "$d" tag v0.1.0
|
||||
done
|
||||
cp -r "$ROOT/docs/examples/site" "$W/app"
|
||||
sed -i "s|https://github.com/shoneyj/writeonce-serve|file://$W/fw|; s|https://github.com/shoneyj/writeonce-view|file://$W/lib|" "$W/app/wo.toml"
|
||||
sed -i "s|https://github.com/shoneyj/porch|file://$W/fw|; s|https://github.com/shoneyj/writeonce-view|file://$W/lib|" "$W/app/wo.toml"
|
||||
printf '[build]\nruntime = "%s"\n' "$WOVM" >> "$W/app/wo.toml"
|
||||
|
||||
# a stand-in release tarball so /dl can be exercised without running
|
||||
|
|
@ -114,7 +114,7 @@ expect "chapter renders a code sample" "$(hit /ch/hello)" 200 "fn main"
|
|||
expect "escaped interpolation visible" "$(hit /ch/values)" 200 '${port}'
|
||||
expect "unknown chapter is a 404 page" "$(hit /ch/nope)" 404 "No such chapter"
|
||||
expect "install guide renders" "$(hit /install)" 200 "tar -C /usr/local"
|
||||
expect "packages index lists both" "$(hit /packages)" 200 "/packages/serve"
|
||||
expect "packages index lists both" "$(hit /packages)" 200 "/packages/porch"
|
||||
expect "package detail shows its dep" "$(hit /packages/view)" 200 "writeonce-view"
|
||||
expect "unknown package is a 404 page" "$(hit /packages/nope)" 404 "No such package"
|
||||
expect "favicon is served as svg" "$(hit /favicon.svg)" 200 "<svg"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
# scripts/web-app-accept.sh — iteration 16's acceptance gate. Proves the whole
|
||||
# chain at run time, network-free: a temp git remote is built from
|
||||
# docs/examples/writeonce-serve, its file:// URL is substituted into a
|
||||
# docs/examples/porch, its file:// URL is substituted into a
|
||||
# temp copy of docs/examples/web-app, then: fetch -> lock -> build -> serve ->
|
||||
# the storefront matrix -> SIGTERM -> restart persistence, plus iteration 17's
|
||||
# library-kind and internal/-boundary checks. The repo itself
|
||||
|
|
@ -33,13 +33,13 @@ cleanup() {
|
|||
trap cleanup EXIT
|
||||
|
||||
# ---- the framework as a git remote; the app pointed at it ----
|
||||
cp -r "$ROOT/docs/examples/writeonce-serve" "$W/fw"
|
||||
cp -r "$ROOT/docs/examples/porch" "$W/fw"
|
||||
git -C "$W/fw" init -q
|
||||
git -C "$W/fw" add -A
|
||||
git -C "$W/fw" -c user.email=t@t -c user.name=t commit -qm v01
|
||||
git -C "$W/fw" tag v0.1.0
|
||||
cp -r "$ROOT/docs/examples/web-app" "$W/app"
|
||||
sed -i "s|https://github.com/shoneyj/writeonce-serve|file://$W/fw|" "$W/app/wo.toml"
|
||||
sed -i "s|https://github.com/shoneyj/porch|file://$W/fw|" "$W/app/wo.toml"
|
||||
printf '[build]\nruntime = "%s"\n' "$WOVM" >> "$W/app/wo.toml"
|
||||
|
||||
# ---- 1. fetch + lock + build ----
|
||||
|
|
@ -63,7 +63,7 @@ fi
|
|||
# A consumer reaching past the privacy line is WO-E108 at its own `use`.
|
||||
cp -r "$W/app" "$W/app-internal"
|
||||
rm -rf "$W/app-internal/target" "$W/app-internal/wo.lock" "$W/app-internal/.wo-deps"
|
||||
sed -i '1i use serve/internal' "$W/app-internal/main.wo"
|
||||
sed -i '1i use porch/internal' "$W/app-internal/main.wo"
|
||||
out="$("$WOC" "$W/app-internal" 2>&1)"; rc=$?
|
||||
if [[ "$rc" == "1" ]] && printf '%s' "$out" | grep -q 'WO-E108'; then
|
||||
ok "dep boundary: importing serve/internal is WO-E108"
|
||||
|
|
|
|||
Loading…
Reference in a new issue