sotashimozono_obsidian-remo.../docs/en/api/protocol-evolution.md
Souta 4052a92f97 docs(en): round 3 — protocol-evolution + firewall + multi-user
Three pages closing the medium-risk gaps in the EN doc tree. All claims
verified against source before writing:

- api/protocol-evolution.md — versioning policy + capability detection
  + cross-version operability matrix. Locked-in regime: strict equality
  on protocolVersion (verified at plugin/src/transport/RpcConnection.ts:71),
  current value 1 (verified at server/internal/proto/types.go:13). Lists
  what bumps the version vs what's safe to add via capabilities.
- server/firewall.md — leads with the punchline: zero new network ports
  needed. Verified daemon binds Unix-only via net.Listen('unix', ...) at
  server/cmd/obsidian-remote-server/main.go:99. Then walks through 4
  topologies (LAN, home NAT, cloud VPS, corporate-firewalled) and
  hardening checklist for sshd.
- server/multi-user.md — natural per-OS-user model works without config
  (defaults are home-relative). Documents the shared-user anti-pattern
  + reasons (single token, daemon thrash, no audit trail). Per-vault
  multiple-profiles flow + container-isolated layout.

Wired into api/overview.md (5th sub-page) and server/overview.md
(extends the 'three ways to run it' picture with two ops-time concerns).

Bump: 1.0.45-beta.7 → 1.0.45-beta.8.
2026-05-10 20:15:42 +09:00

5.7 KiB

title tags
Protocol evolution & versioning
api
reference
protocol

Protocol evolution & versioning

How the wire protocol changes over time, what's safe vs breaking, and how clients should detect server capabilities.

Two version axes

The wire has two versions:

Axis Field Today Bumps when
Protocol version server.info.protocolVersion (int) 1 A breaking change to existing methods (params/result shape, semantics, error codes)
Capabilities server.info.capabilities (sorted string array) All registered method names — currently 22 A new method is added (non-breaking); an old method is removed (breaking)

The protocol version is the major-axis compatibility number. Capabilities are the per-method feature flags within a protocol version.

The strictness rule (today)

The plugin's RpcConnection checks info.protocolVersion !== PROTOCOL_VERSION (strict equality) and rejects with ProtocolVersionTooOld (-32021) on any mismatch. Per plugin/src/transport/RpcConnection.ts:71:

if (info.protocolVersion !== PROTOCOL_VERSION) {
  // → fall through to redeploy or fail the connect
}

So today, plugin and daemon must agree exactly on the protocol version. There's no forward- or backward-compat negotiation. The tryReuseExistingDaemon probe relies on this: a version mismatch fails the handshake, the deploy fallback uploads the matching binary, version matches, connect succeeds. See en/operations/upgrading for the lifecycle view.

This is intentional pre-2.0 — the cost of a redeploy is small (~5 s) and the strictness avoids "subtly broken" runs where a client uses a method the daemon implements differently.

What's a breaking change

Bumps protocolVersion (1 → 2):

  • Renaming an existing method
  • Removing a method
  • Removing or renaming a field in an existing method's params or result
  • Changing the type of a field (e.g. mtime: numbermtime: string)
  • Changing semantics of an existing field (e.g. "mtime is unix-ms" → "mtime is unix-seconds")
  • Changing the framing format (currently LSP-style Content-Length: <N>\r\n\r\n)
  • Adding a required field to an existing method's params

What's a non-breaking change (capabilities-gated)

Stays at protocolVersion: 1; new methods/fields advertised via capabilities:

  • Adding a new method (e.g. a future fs.search) — added to capabilities; clients feature-detect by checking capabilities.includes("fs.search") before calling
  • Adding an optional field to an existing method's params (server defaults if absent)
  • Adding an optional field to an existing method's result (clients ignore unknown fields)
  • Adding a new error code (clients should map unknown codes to a generic "unrecognized error" rather than crash)

How clients should feature-detect

const info = await rpc.call("server.info", {});
if (info.protocolVersion !== EXPECTED_PROTOCOL_VERSION) {
  // hard reject — strict-equality regime
  throw new Error("protocol version mismatch");
}
if (info.capabilities.includes("fs.thumbnail")) {
  // safe to call rpc.call("fs.thumbnail", ...)
}

The plugin uses this exact pattern for fs.thumbnail — added partway through the 0.4.x cycle. Older daemons without it would fail the capability check; the plugin falls back to fs.readBinary + client-side resize.

Cross-version operability matrix

For protocol version 1 (the only one shipped to date):

Client version Daemon version Result
1.0.X 1.0.X (same) Match — full speed (reuse probe attaches to running daemon)
1.0.X 1.0.Y (Y < X) Mismatch on info.version string — reuse probe fails, plugin redeploys to bring daemon to 1.0.X
1.0.X 1.0.Y (Y > X) Same flow — older client redeploys older binary over the newer one
1.0.X 2.0.X (future) protocolVersion 2 ≠ 1ProtocolVersionTooOld → redeploy fallback uploads the bundled (1.0.X-matching) daemon

The reuse probe is the safety net. Users do not see protocol drift errors — they see a one-time ~5-second redeploy on the first connect after upgrading either side.

When we'd bump to protocol 2

The pre-1.0 line saw zero breaking proto changes, and 1.x is committed to the same. Hypothetical triggers for a protocolVersion 2:

  • A wire-format change (e.g. moving from JSON-RPC to gRPC for performance)
  • A semantics change like "mtime now means content-modification time, not metadata-modification time" — semantically incompatible at the per-message level
  • A protocol-level auth change (e.g. moving from per-startup token to per-call signed nonce)

When that happens:

  1. Daemon supports both protocol versions for one release (returns protocolVersion: 2 from new daemons; old clients see this and redeploy).
  2. Plugin bundle gets updated to expect protocol 2.
  3. Capabilities reset / re-curated as part of the 2.0 audit.

Mobile / WSS transport (v2.0 milestone)

The mobile-relay plan (en/roadmap) introduces a WSS transport alongside SSH. The transport is independent of the JSON-RPC protocol — same auth / server.info / fs.* methods, different framing + connection setup. The protocol version itself does not need to bump just to add a new transport.

See also