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:
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
# The curriculum gate comes first, before anything else can run.
|
# The curriculum gate comes first, before anything else can run.
|
||||||
# validate.mjs reads only data/ and lib/ and exits non-zero on a
|
# validate.mjs reads only data/ and lib/ and exits non-zero on a
|
||||||
# blocking failure. Baseline: PASS — 0 blocking, 167 advisory.
|
# blocking failure. Baseline: PASS — 0 blocking, 0 advisory.
|
||||||
- name: Curriculum validation
|
- name: Curriculum validation
|
||||||
run: node validate.mjs
|
run: node validate.mjs
|
||||||
|
|
||||||
@@ -28,7 +28,35 @@ jobs:
|
|||||||
- name: Typecheck
|
- name: Typecheck
|
||||||
run: npm run typecheck
|
run: npm run typecheck
|
||||||
|
|
||||||
|
# The sync round-trip needs a real Postgres and a running server, so a
|
||||||
|
# fake would not exercise the change_seq trigger, the last-write-wins
|
||||||
|
# upsert, or cursor paging — which is where sync actually goes wrong.
|
||||||
|
- name: Start Postgres
|
||||||
|
run: |
|
||||||
|
docker run -d --name hankan-pg \
|
||||||
|
-e POSTGRES_PASSWORD=test -e POSTGRES_DB=hankan \
|
||||||
|
-p 55432:5432 postgres:16-alpine
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
docker exec hankan-pg pg_isready -U postgres -d hankan && break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Start the sync server
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgres://postgres:test@localhost:55432/hankan
|
||||||
|
HANKAN_TOKEN: test-token
|
||||||
|
HANKAN_TEST_MODE: "1"
|
||||||
|
PORT: "8788"
|
||||||
|
run: |
|
||||||
|
node --experimental-strip-types server/src/main.ts &
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
curl -sf http://localhost:8788/health && break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
|
env:
|
||||||
|
HANKAN_TEST_SERVER: http://localhost:8788
|
||||||
run: npm test
|
run: npm test
|
||||||
|
|
||||||
# Every word in every unit's words[] must resolve in lemma or surface.
|
# Every word in every unit's words[] must resolve in lemma or surface.
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -18,11 +18,13 @@ types/ TypeScript declarations for lib/ and shar
|
|||||||
tools/dict/ the dictionary build pipeline
|
tools/dict/ the dictionary build pipeline
|
||||||
app/ Vite + React + TypeScript, and the Capacitor shell
|
app/ Vite + React + TypeScript, and the Capacitor shell
|
||||||
src/db/ one storage interface, two SQLite drivers
|
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
|
src/ui/ six tabs, the review overlay, the 한글 keyboard
|
||||||
public/dict/ generated dictionary — committed, shipped
|
public/dict/ generated dictionary — committed, shipped
|
||||||
test/ golden tests for lib/, driver conformance, domain logic
|
server/ sync + the tutor endpoint (optional)
|
||||||
server/ placeholder; PORT.md steps 4-6
|
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
|
`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.
|
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
|
## Not in this pass
|
||||||
|
|
||||||
No sync layer, no tutor server endpoint, no Play Store packaging — PORT.md steps
|
Android beyond the existing Capacitor scaffold, and Play Store packaging.
|
||||||
4–6. The schema carries `updated_at` on every syncable row and the dictionary is
|
|
||||||
fetched by URL, so both land without rework.
|
|
||||||
|
|
||||||
## Known limitation
|
## Known limitation
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user