From d9f58490ae839e85e621d0703ff8c1e0d824632e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E5=8F=8A?= <522caiji@gmail.com> Date: Sat, 29 Aug 2026 19:21:53 +0800 Subject: [PATCH] docs: treat shipped SQLite migration SQL as immutable v0.2.19 retabbed an applied migration and existing databases panic on boot. Record the rule in AGENTS.md and DESIGN.md so whitespace-only rewrites of shipped SQL do not ship again. --- AGENTS.md | 2 ++ CONTRIBUTING.md | 1 + docs/DESIGN.md | 25 ++++++++++++++++++++++++- docs/DEVELOPMENT.md | 3 ++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7595dd77..89352cec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,7 @@ Do not add new `TODO.md`, `NOTES.md`, or extra plan files. Extra design docs are - Follow `docs/DESIGN.md` (taste v1 adapted for this console) - Keep iterating Qoder login, usage, and account routing. Borrow scheduling ideas from [sub2api](https://github.com/Wei-Shaw/sub2api), not its commercial gateway - Multi-account = one worker process per Qoder HOME; do not share WASM context +- Schema changes go in a new numbered SQLite migration. Never rewrite a shipped file ## Don't @@ -36,3 +37,4 @@ Do not add new `TODO.md`, `NOTES.md`, or extra plan files. Extra design docs are - Copy sub2api billing, Redis slots, multi-tenant API keys, or session-hash-for-profit - Add a new component library, purple AI chrome, centered generic login cards, or emoji in UI copy - Start Cursor / Anthropic until the current Qoder milestone in `docs/PLAN.md` is done. Qoder CN is that milestone (`provider=qoder` + `region=cn`); do not spawn a full `qoderclicn` per request +- Change the SQL bytes of a shipped SQLite migration in `internal/accounts/migrations.go`. Tabs, spaces, and comments inside the raw string count. `gofmt` on the Go around it is fine; indenting the SQL is not. Existing databases panic on boot with `checksum mismatch` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c05a06c..f7cc0009 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,6 +67,7 @@ from `docs/DEVELOPMENT.md`; do not create version tags by hand. - Preserve the proven WASM encode and HTTP/SSE request path - Use HeroUI for console components - Add tests for account, routing, API, or translation behavior changes +- Treat shipped SQLite migration SQL as immutable. Add a new numbered file instead of editing an applied one; pin the canonical checksum in tests. See `docs/DESIGN.md` - Do not commit `.env`, `.qoder`, auth blobs, tokens, raw captures, host IPs, or `docs/PRIVATE_DEPLOYMENT.md` diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 2701853d..b5a1a6d3 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -424,6 +424,29 @@ on state change (`power2.out`, 180–220ms); they do not loop. Distinguish this account-level quota from the request-level `insufficient_quota` error kind: that error means a per-request token/model limit, not a zero account balance. +## SQLite migrations + +Go embeds numbered SQL in `internal/accounts/migrations.go`. On open, each filename is recorded in `schema_migrations` with the SHA-256 of the **raw Go string**, including the leading newline and every tab or space. Applied files are never re-run. A checksum mismatch panics in `api.New` and the process exits, so Docker `restart: unless-stopped` loops and `/health` never comes up. + +This is the rule that `v0.2.19` broke: it retabbed `006_request_log_provider.sql` while adding `007`. Databases that had already applied 006 on `v0.2.18` refused to boot. The host updater rolled us1 back because health never reported `v0.2.19`. + +Hard constraints: + +- Never edit a shipped migration's SQL bytes. Whitespace, comments, quote style, and statement order all change the checksum. +- `gofmt` / indent of the surrounding Go is fine. Indenting the text inside the raw string is not. +- New columns, indexes, or tables go in the next numbered file (`008_…`). Do not append to an applied file. +- Do not `UPDATE schema_migrations` on a live database to make a rewritten file pass. That hides the mismatch and can leave hosts on different schemas. +- Do not delete, rename, or reorder applied filenames. + +If a bad rewrite already shipped (as with 006 in `v0.2.19`): + +1. Restore the original SQL bytes so new databases record the first checksum. +2. Put the mistaken checksum on that file's `legacyChecksums` so databases that recorded the rewrite can still open. +3. Pin both checksums in `internal/accounts` tests. `TestRequestLogProviderMigrationKeepsV0218Bytes` is the pattern. +4. Ship that as a new patch. Do not ask operators to patch SQLite by hand. + +`legacyChecksums` is only for a checksum that already landed in published images. It is not a way to keep editing old SQL. + ## Console IA Keep the menu short. Login is a gate, not a nav item. @@ -484,7 +507,7 @@ Useful ideas borrowed from sub2api: - system operations are serialized and expose durable status; - release checks use a fixed trusted repository and stable releases only; - host-only privileged work crosses a narrow authenticated boundary; -- applied database migrations are immutable and checksum-verified. +- applied database migrations are immutable and checksum-verified. Editing the SQL bytes of a shipped file, including whitespace, panics existing databases on boot; add a new numbered file instead. `legacyChecksums` is only for a checksum that already shipped by mistake. - release artifacts are built per OS/architecture and verified by checksum. Ideas intentionally not copied: diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 2e3dd99a..2e841622 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,6 +1,6 @@ # Development -last-updated: 2026-08-28 +last-updated: 2026-08-29 Repository rules, layering constraints, and validation checklists live in [CONTRIBUTING.md](../CONTRIBUTING.md). This page covers the local build loop @@ -53,6 +53,7 @@ Do not create or push version tags by hand. The tag, GitHub Release, updater ass 1. `main` is the commit you want to ship. The workflow waits for CI on that exact SHA. 2. `CHANGELOG.md` `## Unreleased` has matching `### English` and `### 中文` bullets for every user-facing change on `main` since the last published tag. The workflow copies that section into the GitHub Release and the console System page. `validate` allows an empty Unreleased section; `extract-for-release` fails if Unreleased is empty and the new version heading does not already exist. 3. Do not freeze Unreleased yourself before the run. The workflow reads Unreleased first; freeze only after the tag is public. +4. Do not ship a change that edits the SQL bytes of an already-applied SQLite migration. Existing databases panic on boot with `checksum mismatch`, and the host updater rolls back. Schema changes go in a new numbered file; details are in `docs/DESIGN.md`. ```bash gh workflow run release.yml --ref main