ci: run the sync round-trip against a real Postgres, and document it

CI starts postgres:16-alpine and the sync server before npm test, so the
round-trip runs for real instead of skipping. A fake would not exercise
the change_seq trigger, the last-write-wins upsert or cursor paging, which
is exactly where sync goes wrong.

Also corrects the stale advisory count in the validate.mjs comment: the
updated export bundle carries data/gloss-extra.json, and the baseline has
been 0 blocking, 0 advisory since it landed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-09-08 19:58:09 +02:00
parent 1c666cdf3b
commit 58e454411f
2 changed files with 58 additions and 7 deletions

View File

@@ -18,11 +18,13 @@ types/ TypeScript declarations for lib/ and shar
tools/dict/ the dictionary build pipeline
app/ Vite + React + TypeScript, and the Capacitor shell
src/db/ one storage interface, two SQLite drivers
src/domain/ the gate, the lexicon, SRS, the stub tutor
src/domain/ the gate, the lexicon, SRS, the tutor clients
src/sync/ push/pull against the Pi
src/ui/ six tabs, the review overlay, the 한글 keyboard
public/dict/ generated dictionary — committed, shipped
test/ golden tests for lib/, driver conformance, domain logic
server/ placeholder; PORT.md steps 4-6
server/ sync + the tutor endpoint (optional)
tools/icons/ the 한칸 mark, generated
test/ lib goldens, driver conformance, domain, sync, SSE
```
`data/`, `lib/`, `prompt/` and `validate.mjs` are byte-identical to the export
@@ -144,11 +146,32 @@ that file.
CI runs them in that order, `validate.mjs` first.
## Sync and the tutor — optional, and genuinely optional
The app is complete without a server: its own SQLite, the shipped dictionary,
and a local stand-in tutor. Pointing it at a Pi adds two things — the real
선생님, and syncing between devices. Everything degrades to the offline
behaviour when the server is unreachable, and a sync failure is recorded in
settings rather than surfaced as an interruption.
Setup, the Caddy config, and how sync resolves conflicts: [server/README.md](server/README.md).
Three details worth knowing here:
- **Seeded rows carry `updated_at = 0`**, so a fresh device can neither push
its empty defaults nor win a conflict with them. The artifact's clobbering
bug is unrepresentable rather than merely avoided, and
`test/sync/roundtrip.test.ts` asserts it against a real Postgres.
- **An allowlist decides what leaves the device.** `meta` mixes preferences
with per-install bookkeeping; `dict.loadedBands` crossing the wire would
tell a phone it holds rows it never downloaded.
- **The system prompt is cached.** It is ~12k characters of gate, identical
for as long as the learner stays in one unit, so every turn after the first
reads it at a fraction of the input price.
## Not in this pass
No sync layer, no tutor server endpoint, no Play Store packaging — PORT.md steps
46. The schema carries `updated_at` on every syncable row and the dictionary is
fetched by URL, so both land without rework.
Android beyond the existing Capacitor scaffold, and Play Store packaging.
## Known limitation