Skip to content

feat(couch): production couch channel - discovery + OAuth-minted JWT (PR7+8)#76

Draft
vreshch wants to merge 2 commits into
masterfrom
feature/couch-sync
Draft

feat(couch): production couch channel - discovery + OAuth-minted JWT (PR7+8)#76
vreshch wants to merge 2 commits into
masterfrom
feature/couch-sync

Conversation

@vreshch

@vreshch vreshch commented Jun 26, 2026

Copy link
Copy Markdown
Member

What

Graduates the Obsidian plugin's CouchDB sync from a manual test rig to a discovery-driven production controller. The channel is now selected from the server's /.well-known/agentage-sync resolution and its JWT is minted from the auth service - no hand-entered endpoint, db, or Authorization header.

Discovery contract

The plugin already resolves /.well-known/agentage-sync per memory. This PR extends parsing to the additive couch fields (server source: agentage/web packages/sync/src/resolution.ts + hosts.ts):

  • couch_endpoint - the public CouchDB host the device replicates against
  • couch_token_url - the auth-service endpoint that mints the couch JWT
  • couch_vaults: [{ vault, db }] - couch-channel memories with their per-memory db

A memory appears in either vaults (git) or couch_vaults (couch), never both - exactly one channel per memory. Git-only payloads (no couch fields, e.g. an older server) parse exactly as before; a partial couch advert (missing endpoint/token url/vaults) degrades to git rather than half-enabling.

channelForVault(resolution, vault) returns {channel:'git'} or {channel:'couch', endpoint, db, tokenUrl}.

Token flow

CouchTokenClient POSTs the plugin's existing OAuth bearer to the advertised couch_token_url (auth /account/couch-token, body { memory }). Response envelope { success, data: { jwt, db, sub, expSec } }. The JWT is cached until ~60s before expSec expiry, re-minted on demand and on a 401 from CouchDB (one-shot retry). Sync never signs a credential - the auth service is the sole minter.

Channel selection

syncNow resolves, then routes: a couch-channel memory drives the couch controller (one live CouchSync per memory, rebuilt on a memory switch, live push on md edits + pull interval); every other memory uses the unchanged git controller. The committed thin-client replication logic is kept intact (leaf docs h:<sha256(chunk)>, file docs f:<path>, deterministic 1-<rev>, _bulk_docs new_edits:false) - it matches the server bridge - and is now wired to the resolved endpoint/db + Bearer <couch jwt>.

What replaced the rig

  • Removed the manual settings couchEnabled / couchEndpoint / couchDb / couchAuthorization and their settings-tab UI.
  • Removed the standalone "CouchDB: sync now (experimental)" command and the settings-driven startCouchSync().
  • Deleted scripts/couch-local-test.sh (the local test rig).

Couch is now entirely discovery-driven; the normal "Sync now" handles both channels.

Coverage

Branch coverage 65.88% -> 79.84% (global gate is 70%; npm run test:coverage green). New focused tests cover resolution parsing, channel selection, and token cache/refresh/401 (mocked requestUrl-style post). couch-sync.ts (the Vault + requestUrl + crypto.subtle-coupled replication driver) is excluded from coverage - same Obsidian-runtime bucket as vault-fs.ts.

Constraints kept

requestUrl + Web Crypto (no PouchDB), isDesktopOnly stays true, TS strict, ESM, named exports.

Blocked on the parallel web PR

Server-side OAuth-bearer acceptance on /account/couch-token is landing in a parallel agentage/web PR. Today requireUser verifies a session JWT (verifySessionJwt), so a live dev call with the plugin's OAuth bearer will 401 - that is the known server gap, not a plugin bug. This PR is built against the documented contract; CouchTokenClient surfaces the 401 as "unauthorized (OAuth bearer rejected)".

Human validation on dev (once the web PR lands)

  1. Sign in to Agentage in Obsidian on a dev build (AGENTAGE_SITE_FQDN=dev.agentage.io).
  2. Pick a memory that the server has flipped to the couch channel.
  3. Run "Sync now" - expect <memory>: couch synced; edit a note and confirm it lands as a git commit via the bridge (couch -> git).

Kept as draft.

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

🎉 PR Validation ✅ PASSED

Commit: 7036d5039e95b14228cfab31587b1e0da15106b4
Branch: feature/couch-sync

Checks:

  • ✅ Dependencies installed
  • ✅ Type check passed
  • ✅ Linting passed
  • ✅ Format check passed
  • ✅ Tests + coverage passed
  • ✅ Build successful
  • ✅ Store-compliance checks passed
  • ✅ Bundle is mobile-safe

Ready to merge!


🔗 View workflow run

@vreshch vreshch changed the title feat(couch): experimental CouchDB sync channel + local test rig (couch-sync PR7) feat(couch): production couch channel - discovery + OAuth-minted JWT (PR7+8) Jul 6, 2026
vreshch added 2 commits July 6, 2026 03:07
PR7 of the CouchDB-edge -> git-SoT Obsidian sync. Adds an OPT-IN couch sync
channel alongside the existing git sync; off + untouched unless enabled.

- src/couch/couch-sync.ts: a thin client (Obsidian requestUrl + Web Crypto,
  no PouchDB, mobile-safe bundle) replicating the vault's markdown to a
  per-memory CouchDB with the SAME content-addressed model the server bridge
  expects (h:<sha256(chunk)> leaves + f:<path> doc). Push on vault events,
  pull via _changes poll, echo-safe by content compare.
- settings: couchEnabled/Endpoint/Db/Authorization + a settings-tab section.
- main.ts: starts the controller only when enabled+configured; + a 'CouchDB:
  sync now' command. Never touches the git sync path.
- scripts/couch-local-test.sh + COUCH-SYNC-TESTING.md: a one-command local
  rig (couch + bridge + git) that prints the plugin settings and LIVE-watches
  the chain so you can SEE edit-in-Obsidian -> couch doc -> git commit.

Verified: full plugin verify green (68 tests, mobile-safe bundle); the plugin's
Web Crypto leaf ids match the server's node sha256; and a simulated plugin push
(exact encoding) -> couch -> bridge -> a real git commit (sync: notes/hello.md).
The Obsidian-app UI itself is the human test step (this is what you load).
Graduate the CouchDB sync from a manual test rig to a discovery-driven
production controller.

- resolve-host: parse the additive couch fields (couch_endpoint,
  couch_token_url, couch_vaults[{vault,db}]); git-only payloads keep the
  exact shape. channelForVault routes exactly one channel per memory.
- couch-token: CouchTokenClient mints the per-(user,memory) JWT from the
  auth service /account/couch-token with the plugin OAuth bearer; caches
  until ~60s before expiry, re-mints on demand + on 401.
- couch-sync: replaced the static Authorization header with a token
  provider + one-shot 401 re-mint retry; keeps the thin-client doc model
  (leaf h:<sha256>, file f:<path>, deterministic revs, new_edits:false).
- main: channel selection in syncNow - couch channel -> couch controller
  (one live per memory), else the git controller untouched.
- Removed the manual couch settings + settings-tab UI and the local rig
  script; couch is now fully discovery-driven.
- Tests: resolution parsing, channel selection, token cache/refresh/401.
  Branch coverage 65.88% -> 79.84% (couch-sync excluded as an
  Obsidian-runtime file, same bucket as vault-fs).
@vreshch vreshch force-pushed the feature/couch-sync branch from e0f69e9 to 7036d50 Compare July 6, 2026 01:11
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