hephaestus/docs/changelog.d/v1-prototype.feature.md
Erich Blume ee865e5635
Some checks failed
Build / validate (pull_request) Failing after 4s
heph.nvim: RPC client + buffer editing + wiki-links + journal (slice 11a)
The primary surface begins (tech-spec §8): a Neovim plugin that is a thin
client of the local hephd over its unix-socket JSON-RPC.

- node.resolve {title} → Node|null (heph-core Store + dispatch): exact,
  owner-scoped, non-tombstoned alias-then-title match — the same mapping that
  materializes wiki links, so follow-link jumps to the node the stored link
  points at (never fuzzy search). Unit + rpc_socket integration tests.
- heph.nvim/: vim.uv unix-socket JSON-RPC client (blocking call via vim.wait,
  id-demuxed, partial-line buffered, luanil so JSON null → Lua nil; isolated
  Sessions for tests). Buffer-backed nodes (heph://node/<id>, acwrite;
  BufReadCmd→node.get / BufWriteCmd→node.update, whole-buffer body round-trips
  exactly through the CRDT). [[wiki-link]] follow on <CR>. Daily journal.
  :Heph command surface + completion.
- Headless e2e (§9): a self-contained busted-style runner (tests/e2e/runner.lua)
  — no external plugins, no network, deterministic CI exit codes. Specs: journal
  round-trip, follow-link (+ unresolved no-op), link-two-docs/backlink.
  `make -C heph.nvim test` builds hephd and runs it.

Docs: heph-nvim reference card, §14 tracker (11a done; 11b/11c/11d queued),
changelog fragment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 20:33:29 -07:00

5.5 KiB

Begin the v1 prototype (Phase 1, tech-spec §11.1), built in TDD slices:

  • Cargo workspace + heph-core crate; migration-run SQLite schema (§4.5); clock-injected Store trait + LocalStore node create/get; single local-user bootstrap.
  • Markdown extraction (§5): [[wiki-links]] and GFM - [ ] checkbox context-items derived purely and idempotently from a body, skipping code blocks.
  • Committed tasks (§4.3, §6): task.create auto-creates the canonical context doc + canonical-context link; attention/do-date/late-on/state/recurrence columns; set-state/set-attention. Links CRUD (outgoing/backlinks). A body update reconciles wiki links (diff-based, resolved by alias/title, idempotent).
  • "What is next?" ranking (§7): pure, clock-injected, two-stage engine — candidacy filter (do-date as a boolean gate only) then a reorderable list of named dimensions (past-late-on → overdue-amount → attention band → FIFO). late_on is the sole urgency signal; blue hidden; red always shown. Proptest-checked total order. Store::next surfaces it over SQLite.
  • Recurrence — roll-forward in place (§4.4): completing a recurring task resets its checklist to all-unchecked, logs the occurrence, and advances the do-date to the next RRULE instance after now (skipping misses) — completion never carries forward (proptest-checked). Per-task append-only logs (log-of) with log.append/log.tail; skip advances without logging.
  • hephd daemon, local mode (§3, §6): exclusive file lock (handoff-ready), line-delimited JSON-RPC over a unix socket exposing the node/task/next/links/log methods, with DB work on tokio's blocking pool. Synchronous client for surfaces/CLI. Model types are serde-serializable.
  • 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.
  • Client mode (§3.1, slice 9b): hephd --mode client --server-url <url> runs with no local replica, proxying every store call to a server's POST /rpc endpoint (the full daemon API over HTTP). The daemon is now backend-agnostic (local/server front a LocalStore, client a RemoteStore), so surfaces see the same unix-socket API in every mode.
  • Hub authentication (§13, slice 10a): the sync hub now verifies an OIDC bearer token on /sync/* and /rpc — RS256-pinned JWT validation with exact issuer/audience, expiry, and a required subject; JWKS discovered and cached, refetched on key rotation (jsonwebtoken). Enabled with hephd --mode server --oidc-issuer <url> --oidc-audience <client-id> (open when unset, for local dev). A single-tenant owner gate binds the hub to the first authenticated identity and rejects any other. Verification sits behind a TokenVerifier trait, so it's tested entirely offline (stub middleware + an adversarial battery against an in-process mock IdP).
  • Client authentication (§13, slice 10b): heph auth login --hub-url <url> --issuer <url> --client-id <id> runs the OAuth 2.0 device-code flow and caches the token in the OS keyring; spokes and client mode attach it to hub requests, refreshing on expiry (--oidc-issuer/--oidc-client-id). Offline-tested against a mock OAuth server and a full spoke-to-authenticated-hub loop. (Auth/proxy HTTP uses the runtime-free ureq, since reqwest::blocking is unsafe inside the async daemon.)
  • CI runs the Rust suite (fmt/clippy/test) via the project build hook.
  • heph.nvim slice 11a (§8) — the primary surface begins: a Neovim plugin that is a thin client of the local hephd over its unix socket. A vim.uv JSON-RPC client (blocking call via vim.wait, id-demuxed, partial-line buffered, JSON null→Lua nil); buffer-backed nodes (heph://node/<id> with BufReadCmdnode.get / BufWriteCmdnode.update, whole-buffer body round-tripping exactly through the CRDT); [[wiki-link]] follow on <CR> via a new exact node.resolve {title} RPC (alias-then-title, the same mapping that materializes wiki links — unresolved links allowed); the daily journal (:Heph today); and the :Heph command surface. Headless e2e (§9) drives the plugin against a real daemon over a temp socket with a self-contained busted-style runner (no external plugins, no network): journal round-trip, follow-link, and link-two-docs/backlink.