diff --git a/docs/00-git-commit-history.md b/docs/00-git-commit-history.md index 2a57d7d..e3b35a2 100644 --- a/docs/00-git-commit-history.md +++ b/docs/00-git-commit-history.md @@ -37,6 +37,9 @@ features cannot collide. | `db2-delta` | databasev2 2 — keys-resident updates as WAL delta records | ✅ on `master` 2026-08-30 | | `db2-chains` / `db2-chain` | databasev2 11 — bounding a keys-resident row's delta chain | ✅ on `master` 2026-08-30 | | `site` | the writeonce.de tutorial site | ✅ on `master` 2026-08-30 | +| `db2-migrate` | databasev2 12 — schema migrations (add/delete, declarative, auto on boot) | ✅ on `master` 2026-08-31 | +| `site-deploy` | the writeonce.de redeploy runbook (`docs/guides/deploying-site.md`) | ✅ on `master` 2026-08-31, picked as iteration 12's docs dependency | +| `site-update` | the developer loop for changing the site app (`docs/guides/updating-site.md`) | on `dev` 2026-08-31 | | `site-submodule` | `docs/examples/site` extracted to github.com/shoneyJ/writeonce-site and consumed as a submodule | ✅ on `master` 2026-08-30. Both branches now track the site by revision; an edit to it is a commit in that repo plus a pointer bump here | | `lang41` | runtime: unadopted shard must not impersonate shard 0 | on `dev` (`9dca0b4`); independent of the residency stack, not picked | | `porch-store` | porch store tables, Limiter and Idempotent middleware (Phases A, B, C) | on `dev` (`519d411`, `5b1e82a`, `aee7926`). **In progress**: Phase C was uncommitted work from a parallel session, committed as-is, and calls `json.decode`/`json.encode` with no `use json` import | @@ -49,6 +52,7 @@ produced. | Date | Prefix | Feature | `dev` → `master` | | --- | --- | --- | --- | +| 2026-08-31 | `db2-migrate` + `site-deploy` | **databasev2 12 — schema migrations v1**: WO_WAL_SCHEMA head record, name-keyed boot diff, record-level transcode for add/delete, poisons that bite only with records; plus the redeploy runbook the close-out edits (dev-only until now). Zero conflicts. Verified on `master`: 36 suites 0 fail (`test_wal` 5966/0), woc-test clean, residency-accept 14/0, site-accept 23/0 | `930a715` → `b594717`, `072e007` → `8d9207d`, `ba8519f` → `570e0d6`, `63a063b` → `b1b7984`, `b69092a` → `4a70fc7`, `b21943a` → `ace5699`, `4bb6ece` → `4f1fda1` | | 2026-08-30 | `site-submodule` | **`docs/examples/site` becomes a submodule** — extracted to github.com/shoneyJ/writeonce-site with `git subtree split` (its own 9 commits of history, not a snapshot) | `4b56348` → `a5497a3`, `4eead89` → `565b894` | | 2026-08-30 | `db2-keys` + `db2-delta` + `db2-chains` + `site` | **databasev2 `resident: keys`, end to end** — storage, readers, deletes, updates as delta records, bounded delta chains, and the tutorial chapter documenting them | 37 commits, mapped one-to-one below | diff --git a/docs/guides/deploying-site.md b/docs/guides/deploying-site.md index 963a863..3a6a0e8 100644 --- a/docs/guides/deploying-site.md +++ b/docs/guides/deploying-site.md @@ -5,7 +5,8 @@ > this covers shipping the *site* that advertises it. Layout, nginx and the > environment variables are documented once, in the site's own > [README](../examples/site/README.md) — this is the update runbook, and the -> three things that go wrong. +> three things that go wrong. Changing the application itself (code, +> content, schema) is [`updating-site.md`](updating-site.md). ## Read this first — three traps, in the order they bite diff --git a/docs/guides/updating-site.md b/docs/guides/updating-site.md new file mode 100644 index 0000000..c9b3092 --- /dev/null +++ b/docs/guides/updating-site.md @@ -0,0 +1,123 @@ +# 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`](deploying-site.md); this guide ends where that one +> begins. Layout and environment live in the site's own +> [README](../examples/site/README.md). + +## 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 && 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 : " +``` + +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 + +1. Edit in `docs/examples/site/`. +2. `just site` — builds against `docs/examples/porch` and + `docs/examples/writeonce-view` as local `file://` 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). +3. New behaviour gets a leg in `site-accept.sh` in the same change — the + storage chapter's two legs are the pattern to copy. +4. 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 new chapter (or renumbered `ord`s) therefore ships +with the data-refresh step in +[`deploying-site.md`](deploying-site.md#refreshing-content--trap-1-in-practice) +— 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 `+views` migration.) +- 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.