Skip to content

PRD P4-W1: the DR-02 sync decision and the hub server skeleton - #44

Merged
compgeniuses merged 4 commits into
consolidationfrom
prd/P4-W1
Oct 23, 2026
Merged

compgeniuses merged 4 commits into
consolidationfrom
prd/P4-W1

Conversation

@compgeniuses

Copy link
Copy Markdown
Contributor

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.md and spikes/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 in spikes/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, _changes and 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.json clean; the commit touches only the two owned paths. The Postgres suite gates on postgresAvailable() and becomes describe.skip without 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/entities and 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.ts becomes the composition root. It was a 478-line stub that imported express types while express is not installed and that nothing imported, so it could not run; the class, its constructor and start/stop/address shape 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.json now includes server/**/*.ts, so the server is typechecked like the rest of the tree.

The acceptance is demonstrated rather than asserted: server/__tests__/hub.test.ts starts the hub against a seeded SQLite file, admits a learner through HTTP, reads the rows back over HTTP, and then runs the same AdmissionService in-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

  • The wave branch is built from consolidation; the two tasks branched from the P3 tip and merge cleanly, so the diff against consolidation is exactly spikes/**, server/**, src/server/LANServer.ts, tsconfig.json and docs/prd/workflow-log/decisions/DR-02.md — 36 files, +6,252/−490.
  • What this wave deliberately does not do: no authentication (the x-actor header is a trusted placeholder), no sync transport, no packaging, no health page. Each is named to its task.

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.
@compgeniuses
compgeniuses merged commit 7720e53 into consolidation Oct 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant