Repository navigation
PRD P4-W1: the DR-02 sync decision and the hub server skeleton - #44
Merged
Merged
Conversation
Decide the P4 sync engine on measured evidence: the canonical repository on SQLite (device) with SQLite or Postgres (server) against PouchDB-CouchDB. The spike runs the real repository, schema, codec and guards on both engines and counts engine round trips through the shared adapter driver. Bootstrap, first pull, one accepted change and a 20-change push cost 998 / 138 / 28 / 560 statements on both SQLite and Postgres, so the domain layer does not fork; what sync must add is an apply verb, server-allocated sequences, a ChangeEvent on pull and an FK-safe apply order, all recorded in DR-02. PouchDB forces the fork at the storage unit: the conflict is the whole document, different-field edits still collide, the winner comes from the revision tree and agreed with the HLC only 50/100, bulkDocs is not atomic, and the per-field clock still has to travel inside every document (a one-field edit costs 98.6% of a whole-row create). CouchDB itself is unmeasured: Docker is not running, so candidate B is PouchDB local replication. The gap is stated in DR-02, not papered over.
…4-B01] LANServer becomes the composition root of topology B: one node:http server over the canonical SQLite database, applying Repository.setup() and serving two APIs. /api/entities exposes the typed repository (list, read, create, patch/put, remove, audit); /api/services runs the browser's own AdmissionService, AttendanceService and BillingRunService, constructed with a repository and nothing else, so the domain layer has no server-side fork. node:http replaces express/ws/bonjour here; real-time sync is P4-A02, discovery/TLS is P4-D02, and x-actor is a trusted placeholder until P4-C01. The acceptance test starts the hub against a seeded SQLite file, admits a learner over HTTP, reads the rows back, and compares the result with the same service run in-process over a copy of the same file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P4-W1: the sync decision and the hub skeleton. Two tasks, both done, each in its own worktree and integrated here as one wave.
P4-A01 — the DR-02 sync engine spike, with a decision
Deliverable:
docs/prd/workflow-log/decisions/DR-02.mdandspikes/sync/**(25 files, ~4,900 lines).Recommendation: adopt A — the canonical repository on SQLite (device) with SQLite or Postgres (server), the backend as a driver rather than a dialect fork. PouchDB does not remove the work A does (the HLC, per-field clocks, guards, envelope); it only puts a whole-document, revision-tree conflict model underneath it, which cannot express cell-level marks or field-level LWW.
Measured, not asserted. Postgres was measured for real (PostgreSQL 18.1) through the same
createSqliteAdapter(driver)seam the device uses: 998 statements to bootstrap, 138 to serve the first pull, 28 per accepted change, 560 for 20 changes in one push, 42/82 for a pulled batch; a 2.84 µs pure merge policy; one logged conflict for two devices editing one field. Raw numbers inspikes/sync/results/*.json, regenerated by the final green run.The hole in its own evidence, stated plainly: candidate B was measured PouchDB↔PouchDB, not against a real CouchDB — Docker is not available here, so the HTTP wire,
_changesand filtered replication are assumptions. The spike calls that "a real limitation of the evidence, not a detail". It would not change the decision; it would close the evidence.Verified by me:
npx vitest run spikes/sync→ 43/43 pass;tsc -p spikes/sync/tsconfig.jsonclean; the commit touches only the two owned paths. The Postgres suite gates onpostgresAvailable()and becomesdescribe.skipwithout a server, so merging it cannot redden CI.P4-B01 — the hub skeleton that runs domain services unchanged
Deliverable:
server/**— one Node HTTP server over the canonical SQLite database, serving the typed repository under/api/entitiesand the domain services under/api/services(admissions, attendance registers and marks, billing previews and posts) through the browser's own services. The hub adds a transport, not a second domain layer.src/server/LANServer.tsbecomes the composition root. It was a 478-line stub that importedexpresstypes while express is not installed and that nothing imported, so it could not run; the class, its constructor andstart/stop/addressshape are kept, and what the stub only described is deferred to the tasks that own it by name: authentication to P4-C01, the WebSocket hub to P4-A02, mDNS and TLS to P4-D02.tsconfig.jsonnow includesserver/**/*.ts, so the server is typechecked like the rest of the tree.The acceptance is demonstrated rather than asserted:
server/__tests__/hub.test.tsstarts the hub against a seeded SQLite file, admits a learner through HTTP, reads the rows back over HTTP, and then runs the sameAdmissionServicein-process against an identical copy of the seeded file and compares the results. It also exercises the repository API, an attendance register and a billing preview, and confirms the admission reached the file on disk. 6 tests, 148 s — run by me, the integrator, because the implementer ended its turn with the work uncommitted.Integration notes
consolidation; the two tasks branched from the P3 tip and merge cleanly, so the diff againstconsolidationis exactlyspikes/**,server/**,src/server/LANServer.ts,tsconfig.jsonanddocs/prd/workflow-log/decisions/DR-02.md— 36 files, +6,252/−490.x-actorheader is a trusted placeholder), no sync transport, no packaging, no health page. Each is named to its task.