- story 02: `status: done`, `review_pending` (forks 1–7 auto-approved for
autonomy); progress rows 6a ✅, 6b ➡ databasev2 5 Phase A, 7 `a310496`;
5c/5d rows cite the `dev` hashes (the pre-merge ones were unreachable);
task 6a's Given/When/Then met; Info records the seven forks (sentinel over
`:memory:`, its rules, the refusal contract, startup-only, the budget
leaves for 5, library-owned tables bind consumers, the v8 table bit);
History keeps the first cut that refused every class-bearing program
- database/src/CODE-LOGIC.md: "Startup refusal + WO_EPHEMERAL" — contract,
hatch, table bit, measured blast radius, deferred items, proof; the
dispatcher paragraph no longer says a failed commit un-applies the row
(fatal since databasev2 4 part A; WO_T_IO unreachable from a write path)
- residency spec + plan: task 6 items annotated with the 2026-09-09
decisions; the byte budget marked moved to databasev2 5
- README, seven example READMEs and four guides carry the one-line rule
(durable default refuses without WO_DATA; WO_EPHEMERAL=1; durable:
false); shop's RAM-only command sets the sentinel
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 2c3531998124042fe736388e8b926abda3841194)
5.5 KiB
Updating the writeonce.de application
Status: current as of 2026-08-31. This is the DEVELOPER loop — changing the site's code, content, or schema. Shipping the result to the host is
deploying-site.md; this guide ends where that one begins. Layout and environment live in the site's own README.
Where the code actually lives
docs/examples/site is a submodule of
github.com/shoneyJ/writeonce-site. The monorepo tracks it by revision, not
by content, which makes one mistake easy and expensive:
Editing files under docs/examples/site and committing in the monorepo
records nothing. The change lives in a directory the monorepo only points
at. The working order is:
cd docs/examples/site
git checkout master # the submodule checks out detached by default
# ...edit, test (below)...
git add <files> && git commit
git push # writeonce-site must have it BEFORE the bump
cd ../../..
git add docs/examples/site # the pointer, nothing else
git commit # "bump site to <short-hash>: <what changed>"
Push the submodule first: a monorepo pointer naming a commit that exists only
on one laptop breaks every other clone's git submodule update.
The site's gate lives in the monorepo (scripts/site-accept.sh), not the
submodule — a change that needs a new gate leg is two coordinated commits by
design: behaviour in writeonce-site, its proof in the monorepo.
The loop
- Edit in
docs/examples/site/. just site— builds againstdocs/examples/porchanddocs/examples/writeonce-viewas localfile://remotes and runs the whole matrix (23 checks: pages, escaping, 404, 401, authed edit, SIGTERM, WAL persistence). This is the only signal worth trusting; the site cannot even build standalone (its[deps]point at unpublished repos).- New behaviour gets a leg in
site-accept.shin the same change — the storage chapter's two legs are the pattern to copy. - Commit the pair as above.
Framework work is different: porch and writeonce-view are ordinary
monorepo directories (docs/examples/porch, docs/examples/writeonce-view),
so a site change that needs a framework change is a normal monorepo commit
plus the submodule pair. The rev = "v0.1.0" pins in wo.toml are
aspirational until those two repos are published.
Content updates — chapter text, new chapters
Chapter bodies are code (content.wo builders), so a reworded chapter is an
ordinary edit: the compiler checks it, code_block() escapes it, just site
proves it renders.
What content edits do NOT do is reach a host that already has a WO_DATA
directory: seed_if_empty() fills an empty table only, and the admin route
cannot create a chapter. (a program with any durable table (the default) refuses to start without WO_DATA; WO_EPHEMERAL=1 opts into a RAM-only run, @table(durable: false) opts a table out.) A new chapter (or renumbered ords) therefore ships
with the data-refresh step in
deploying-site.md
— measured there, silent otherwise.
Schema updates — changing the Chapter class
Two facts, both proven against the real app on 2026-08-31:
The compiler makes you finish the job at compile time. There is no field-default syntax, so adding a field breaks every insert literal until the seeds carry it:
content.wo:105:3: error WO-E206: missing field `views` in insert of `Chapter`
All ten seed inserts (and any other insert Chapter { ... }) must name the
new field. This is a feature: the seed can never silently drift from the
schema.
The live database migrates itself at boot (schema migrations, databasev2
12). Booting the new binary against the existing WO_DATA:
wovm: .../shard-0.wal: migrating `Chapter`: +views
wovm: .../shard-0.wal: schema migrated
- All ten chapters render; the new field reads the kind's zero.
- Live admin edits survive — unlike the content-refresh wipe, a schema
migration rewrites rows in place. (An edited title stayed edited through
the
+viewsmigration.) - Deleting a field drops its stored values at the swap, permanently.
- A retype refuses to start, by name, with the old log intact:
wovm: ...: refusing to start — class `Chapter`: field `views` changed its
type — v1 has no conversions; add a new field and backfill instead
- A rename reads as delete+add of the same type and refuses the same way: deploy the delete and the add as two separate releases when both are meant.
- Rolling back a deployed schema change is a migration in the other
direction; the old binary refuses the migrated log — see the rollback
section of
deploying-site.md.
So a schema change ships as: edit types.wo, satisfy WO-E206 everywhere,
just site, the two-repo commit pair, deploy the binary — no data step,
unlike content. The one combination that still needs the wipe is a schema
change WITH new seed content in the same release: the migration handles the
shape, the seeds still cannot reach a non-empty table.
Evidence
The schema claims above were exercised against the built site, not inferred:
seeded a WO_DATA under v1, edited a chapter through /admin, rebuilt with
views: Int added (ten seed inserts updated after WO-E206 stopped the
build), booted against the same data — migration lines printed, 10 chapters
rendered, the edit survived, /ch/storage intact — then retyped views to
Float and got the refusal, exit 2, data untouched.