generated from eblume/project-template
hephd: network sync over HTTP — hub + spoke (sync 9a)
Some checks failed
Build / validate (pull_request) Failing after 2s
Some checks failed
Build / validate (pull_request) Failing after 2s
Wire the existing merge engine over the network so the everyday config (local + hub_url) syncs through a hub. Transport ratified = axum HTTP/JSON (tech-spec §6.1, §12). - heph-core: SyncCursors model + Store::sync_state/record_sync over the sync_state table (per-peer push/pull HLC cursors). Incremental, so each exchange transfers only the tail. - hephd::sync: the hub router (POST /sync/push, GET /sync/pull?after=<hlc>) served from the shared LocalStore, and sync_once — a spoke's pull-then- merge, then push-tail exchange, advancing the cursors. Idempotent: a re-pushed op the hub already has is a no-op. - Daemon carries optional hub config; sync.now/sync.status handled at the daemon (they need the hub transport the store can't reach). conflicts. list/resolve now reachable over the unix socket too. - main: --mode local|server, --hub-url, --http-addr. server mode binds the hub HTTP endpoint on the same store; a local+hub_url spoke background- syncs on a 30s interval. - tests/sync_http.rs: two spokes converge through a real-HTTP hub on an ephemeral port — node propagation and a divergent-scalar conflict. Unauthenticated/single-owner for now; OIDC + per-user scoping is slice 10, client mode + RemoteStore is 9b. 100 tests green; clippy -D warnings + fmt + prek clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
455f172a54
commit
8c25d114c4
18 changed files with 1239 additions and 42 deletions
|
|
@ -9,4 +9,5 @@ Begin the v1 prototype (Phase 1, tech-spec §11.1), built in TDD slices:
|
|||
- `heph` CLI (§1) — a thin client of the daemon: `next`, `task`, `doc`, `get`, `export`. Export materializes the store to a `<kind>/<id>.md` tree with YAML frontmatter + body (§5), one-way, tombstones excluded.
|
||||
- Sync engine, local-only (§12): real hybrid logical clock + persistent device `origin`; an append-only op-log per mutation; an idempotent, order-independent merge/apply engine — last-writer-wins task scalars (discards surfaced in a `conflicts` queue), OR-set links, monotonic tombstones. Two-replica convergence proven.
|
||||
- Body text CRDT (§5, §12, slice 8d): node bodies now merge through the `yrs` text CRDT (`body_crdt`) instead of last-writer-wins — whole-buffer writes are diffed into the doc and the yrs delta rides the op, so concurrent edits to different regions both survive and never enqueue a conflict.
|
||||
- Network sync over HTTP (§6.1, §12, slice 9a): `hephd --mode server` exposes a sync hub (`POST /sync/push`, `GET /sync/pull?after=<hlc>`, axum) over the same store; `hephd --mode local --hub-url <url>` becomes a spoke that background-syncs its op-log with that hub (and on demand via the `sync.now`/`sync.status` RPC). Exchange is incremental by HLC cursor (`sync_state`) and idempotent. The merge engine is `heph-core`'s, unchanged. Unauthenticated/single-owner for now (auth lands with OIDC). `conflicts.list`/`conflicts.resolve` are now reachable over the daemon socket.
|
||||
- CI runs the Rust suite (fmt/clippy/test) via the project build hook.
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ See [[design]] §5–§7 for the constraints later phases impose on present choi
|
|||
|
||||
## 14. Implementation status (Phase 1 tracker)
|
||||
|
||||
> Cross-session resume tracker for the Phase 1 C1 (branch `feature/v1-prototype`, PR #1). Updated 2026-06-01 — **97 tests green** (`cargo test --all`), `clippy -D warnings` + `fmt` + `prek` clean. Workspace: `crates/heph-core`, `crates/hephd`, `crates/heph` (no `heph.nvim/` yet).
|
||||
> Cross-session resume tracker for the Phase 1 C1 (branch `feature/v1-prototype`, PR #1). Updated 2026-06-01 — **100 tests green** (`cargo test --all`), `clippy -D warnings` + `fmt` + `prek` clean. Workspace: `crates/heph-core`, `crates/hephd`, `crates/heph` (no `heph.nvim/` yet).
|
||||
|
||||
**Done**
|
||||
|
||||
|
|
@ -334,16 +334,17 @@ See [[design]] §5–§7 for the constraints later phases impose on present choi
|
|||
- ✅ **Markdown handling (§5):** wiki-link + checkbox extraction (pure, idempotent, code-aware); `update_node` materializes/reconciles `wiki` links; `export` to a `<kind>/<id>.md` tree.
|
||||
- ✅ **Recurrence (§4.4):** roll-forward in place — fresh checklist, logged occurrence, advance-skipping-misses; completion never carries forward (proptest). Per-task logs; `skip`.
|
||||
- ✅ **Ranking (§7):** pure two-stage filter + reorderable named dimensions; proptest total order.
|
||||
- ✅ **Daemon RPC (§6) — local subset:** node.get/create/update/tombstone, task.create/set_state/set_attention/skip, next, list, health, journal.open_or_create, search, links.outgoing/backlinks, log.append/tail, export, conflicts.list/resolve, sync (ops_since/apply_op). Line-delimited JSON-RPC over a unix socket; sync `Client`.
|
||||
- ✅ **Runtime modes (§3.1) — `local` only:** exclusive file-lock handoff via `LockGuard`.
|
||||
- ✅ **Daemon RPC (§6):** node.get/create/update/tombstone, task.create/set_state/set_attention/skip, next, list, health, journal.open_or_create, search, links.outgoing/backlinks, log.append/tail, export, conflicts.list/resolve, sync.now/sync.status. Line-delimited JSON-RPC over a unix socket; sync `Client`. (`ops_since`/`apply_op` are `Store` methods exchanged over the hub HTTP endpoint, not the unix socket.)
|
||||
- ✅ **Runtime modes (§3.1) — `local` + `server`:** exclusive file-lock handoff via `LockGuard`; `--mode local|server`, `--hub-url`, `--http-addr`. `client` (no replica) is a later slice.
|
||||
- ✅ **Sync engine (§12) minus network:** HLC (clock-injected, monotonic) + persistent device `origin`; op-log per mutation; `apply_op` merge — **LWW** task scalars + titles with a **conflict queue**, **OR-set** links, monotonic tombstones, idempotent; two-replica convergence proven. `adopt_owner` = basic §13 canonical-owner adoption.
|
||||
- ✅ **Body text CRDT (§5, §12, slice 8d):** node bodies merge through the **`yrs`** text CRDT (`body_crdt` BLOB) instead of LWW. A device authors under a stable `client_id` derived from its `origin`; whole-buffer writes are diffed (common prefix/suffix, char-boundary safe) into the doc; the yrs delta rides the `node.create`/`node.set` op (`body_crdt` field) and `apply` merges it — concurrent disjoint edits both survive and never enqueue a conflict.
|
||||
- ✅ **Network sync (§6.1, §12, slice 9a):** **transport ratified = `axum` HTTP/JSON.** The hub (`server` mode) exposes `POST /sync/push` + `GET /sync/pull?after=<hlc>` over the same store; a spoke (`local` + `hub_url`) runs `sync::sync_once` (pull→merge, then push) and background-syncs on a 30s interval. Incremental by HLC cursor (`sync_state`/`SyncCursors`); idempotent re-push is a no-op. Two spokes converge through a real-HTTP hub (incl. scalar conflict) in `tests/sync_http.rs`. **Unauthenticated for now, single-owner** (auth + per-user scoping is slice 10).
|
||||
- ✅ **CLI (§1):** `heph` next/task/doc/get/export/search/journal.
|
||||
- ✅ **CI (§9):** `.forgejo/scripts/build` runs fmt/clippy/test (self-bootstrapping rustup).
|
||||
|
||||
**Not yet done (resume order)**
|
||||
|
||||
1. ⏳ **`server`/`client` modes + network sync (§3.1, §6.1, §12):** `RemoteStore`; hub network endpoint; push/pull by HLC cursor (the merge logic already exists); background sync; `sync.now`/`sync.status` RPC; multi-replica-over-real-sockets tests. Open: transport (`axum` HTTP/JSON vs gRPC), propagation cadence, device-id/hub registration.
|
||||
1. ⏳ **`client` mode + `RemoteStore` (§3.1, slice 9b):** a no-replica spoke that proxies every `Store` method to a `server` over HTTP — the online-only escape hatch (borrowed box, CI, future web backend). Open: how the proxy maps the mutating `Store` surface; `sync.now` is N/A for `client`.
|
||||
2. ⏳ **OIDC/Authentik auth (§13):** device-code flow, bearer token on the hub endpoint, full per-user isolation, adoption-with-deterministic-ids.
|
||||
3. ⏳ **`heph.nvim` (§8):** obsidian.nvim parity + task views; headless-nvim e2e (needs `neovim` + `plenary.nvim` on the CI runner).
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue