Skip to content

Task 2: Version policy for the shared server #472

Description

@AkaraChen

Summary

Version policy for the shared Herdr server for parent plan #466. Task 1 (#470 on task-1-attach-user-session) already attaches to the user's session (default $XDG_CONFIG_HOME/herdr/herdr.sock, never a private 2code namespace). Production still pins the sidecar exactly: incompatibility_reasons requires version == PINNED_VERSION ("0.9.0") and protocol == 22, and report_version requires the exact line herdr 0.9.0. resolve_gui_sidecar only looks at the bundled sidecar. A user running Herdr 0.8.2 (protocol: 20) on that shared socket is therefore rejected without naming herdr update, and a newer compatible server (protocol >= 22) would also be rejected. Wheel scroll needs pane.scroll (protocol 22); protocol 20 is a silent break, not a reason to spawn a second server.

Probe the reachable server's protocol/version instead of treating the sidecar pin as the only acceptable live server. Accept protocol >= 22 (Herdr >= 0.9.0). When the reachable server is older, fail closed with an actionable message that names the found version, the required version, and herdr update — never spawn a private instance. Prefer the user's installed herdr over the bundled sidecar when both exist; keep the sidecar only as the no-installation fallback and still start it on the shared socket. Same clicks and labels.

Herdr is the profile authority. sqlite profiles is not source of truth (the table is DROPped). projects / project_groups / checkout_notes stay in 2code sqlite. Default-runtime switch and deleting Local/PTY/profiles rows already shipped in #436 — do not re-litigate or re-add them. Never let Local and Herdr own the same worktree (Local is gone; do not bring it back). Do not change interaction. Reuse the existing socket client, snapshot/event sync, frame bridge, and worktree/pane RPCs — this task changes which binary 2code trusts and how it classifies a live server, not the protocol layer or which socket it talks to.

This is #466 Task 2, not #394 Task 2 (#402), not #436 Task 2 (#439), and not #463 Task 2. Do not implement or close those tickets here. Do not start Task 3 (bidirectional e2e proof + sharing-contract docs rewrite).

Dependencies: Parent plan #466. Prerequisite: #470 (Task 1, accepted on task-1-attach-user-session). Open #471 is Task 1 line-count tracking only — not a blocker and not work for this branch. Open task tickets from #394 / #436 / #458 / #463, leftover gaps (#396, #397, #398, #399, #401), and line-count trackers are out of scope.

Work to complete

  1. Classify the reachable server by protocol floor, not exact sidecar pin

    • incompatibility_reasons / classify_status: compatible means protocol >= 22 (Herdr >= 0.9.0). Drop the exact version != PINNED_VERSION reject. A 0.9.1 / 0.10.0 server with protocol 22+ is Compatible. Protocol 20 / version 0.8.2 is Incompatible.
    • Keep status.compatible == false / endpoint_compatible == false as incompatible. Keep today's endpoint-generation check if current v0.9.x still reports generation 1; do not invent a new generation policy.
    • report_version on the GUI connect path must not require the exact line herdr 0.9.0. User PATH binaries >= 0.9.0 are allowed. The bundled sidecar pin (pin.json, PINNED_TAG / checksums / scripts/herdr-sidecar.mjs) stays as the fallback binary; do not retarget the pin to latest.
    • Production still talks to Task 1's shared socket. An incompatible reachable server still fails closed and must not spawn a private 2code session or a second server on another socket.
  2. Prefer the user's installed herdr; sidecar is no-installation fallback

    • resolve_gui_sidecar / a dedicated resolve helper: after PATH is fixed (fix_path_env::fix already runs at process start), look up herdr on PATH and use it when present. Only if no user herdr exists, use try_resolve_sidecar.
    • The chosen executable is what ensure_herdr_listener starts (when absent) and what CLI attach uses. Sidecar start remains on the shared default socket, never sessions/2code/.
    • If a user herdr exists but is older than 0.9.0: fail closed with the herdr update message. Do not ignore it and start the sidecar on the same socket (two versions fighting for one session). Sidecar starts only when herdr is not installed.
    • Tests must isolate PATH / XDG so the developer's real herdr and ~/.config/herdr do not leak into fixtures. Explicit GuiHerdrConnect.sidecar stays a test override.
  3. Actionable old-server error; UI is not silent

    • Incompatible (protocol < 22) HerdrServerIncompatible text must name: the found version/protocol, the required floor (Herdr >= 0.9.0 / protocol >= 22), and the command herdr update. Absent vs incompatible stay distinct; absent must not claim herdr update as if a server were found.
    • HerdrStubAdapter::fail_closed already returns that startup error on later ops. A user-visible surface must show it (existing sonner / mutation error on New Tab or New Profile, or a connect-time toast using that same string). Do not add a Settings runtime health page or picker.
    • Named UX exception (not a click/label redesign): when the shared server is too old or absent, the same New Tab / New Profile / Git / sidebar controls stay; they surface this actionable error instead of a silent empty catalog or a private 2code server. list_projects may still yield profiles: [] on Herdr-down; the error string must still appear on a fail-closed action or connect toast.
    • GUI exit / lease drop still must not herdr server stop.
  4. Prove the old-server path; do not swallow Task 3

    • Fake-herdr: running protocol 20 / 0.8.2 on the isolated default socket → Probe::Incompatible, starts == 0, stop_count == 0, error contains found version, required floor, and herdr update. No sessions/2code/ and no /tmp/2code-herdr-*.sock.
    • Fake-herdr: protocol 22+ / 0.9.1 (or 0.10.0) on that socket is Compatible and is reused (no second start).
    • PATH herdr wins over a present sidecar when both exist; missing PATH uses sidecar; old PATH herdr does not fall through to sidecar start.
    • Invert tests that lock exact pin on the live server: incompatibility_reasons exact PINNED_VERSION, report_version exact EXPECTED_VERSION_LINE on the GUI connect path, incompatible_sidecar_fails_closed_without_flipping if it only covers a non-herdr binary. Keep contract-probe / pin.json / live sidecar checksum tests on v0.9.0.
    • Update production current-state sentences that say GUI startup only resolves the pinned sidecar / exact 0.9.0 server: docs/herdr-integration.md opening current-state paragraph, docs/architecture.md infra table / diagram, docs/configuration.md if it still implies sidecar-only. AGENTS.md / CLAUDE.md twins only if they still advertise exact-pin-as-the-only-live-server. Keep the v0.9.0 pin as the fallback artifact.
    • Do not write Task 3's bidirectional command log (herdr workspace list reverse direction) or replace the whole sharing-contract section.
    • If line count grows past the estimate, open a tracking issue; do not fold Task 3 into this branch.

Likely files and directories

Estimated changes: 250–500 lines total, additions plus deletions, including tests and docs; excluding generated files, lockfile churn and binary artifacts. Material scope growth (bidirectional e2e command log, sharing-contract rewrite, unpinning the sidecar) should become a separate issue.

Acceptance criteria

  • Live-server compatibility is protocol >= 22 (Herdr >= 0.9.0), not exact PINNED_VERSION / exact herdr 0.9.0. A newer protocol-22+ server is reused. Protocol < 22 is incompatible.
  • Old reachable server: fail closed, no second start, no private 2code session. Error names found version/protocol, required floor, and herdr update. Absent vs incompatible stay distinct.
  • User-installed herdr on PATH is preferred over the bundled sidecar when both exist. Sidecar is used only when herdr is not installed, and then starts on the shared default socket. Old PATH herdr does not fall through to a sidecar start on that socket.
  • Sidecar pin remains v0.9.0 (checksums, pin.json, bundle script). latest is still refused. Contract probe still pins v0.9.0.
  • A user-visible surface shows the old-server message (existing toast / fail-closed op). No Settings runtime health UI. Same New Tab / New Profile / Git / sidebar clicks and labels.
  • Fake-herdr tests: old-server path asserts no private server and the error names herdr update; newer compatible server is reused; PATH preference vs sidecar as above. cargo test --workspace from src-tauri (or cargo test --workspace --exclude code where GTK is unavailable). Frontend lint/typecheck/tests only if frontend scope is introduced. bun run typegen only if IPC signatures change (none expected); do not hand-edit src/generated/.
  • Live current-state docs no longer say GUI startup only accepts the exact pinned sidecar server. Pin-as-fallback and contract-probe isolation notes stay. Task 3 bidirectional proof is not this branch.
  • No behavior change beyond binary preference and version policy. sqlite profiles is not source of truth. projects still round-trip in sqlite. No Local adapter. No second events.subscribe. lib.rs still does not name HerdrRuntimeSync. Join key still filters profiles by project folder. GUI/lease drop leaves the user's server running.
  • Named UX exception: old/absent shared server surfaces found/required versions and herdr update instead of a silent empty catalog or a private 2code server. Existing Herdr-backed exceptions stay (route ids workspace_id; tab ids pane_id; splits flattened as extra tabs; non-git New Profile is folder workspace.create; Herdr-down New Tab errors).
  • No push. No pull request.

Out of scope

Standing constraints

  • Never start a private session. Nothing in 2code may spawn a server on a socket the user's own herdr cannot see. An old shared server is not a reason to open sessions/2code/.
  • Never stop the user's server. GUI exit, disconnect, crash and update must not issue herdr server stop or kill the shared server process.
  • Fail with an actionable message, never with silence. If the reachable server is too old (protocol < 22) or absent, the UI shows what is wrong and what to run (herdr update for old; distinct copy for absent), and does not fall back to a private instance.
  • Do not change interaction. Same clicks and labels. If a label genuinely cannot survive, name the exception in this issue (old/absent server shows the upgrade/absent error; no new health page).
  • Reuse the code that already works — socket client, snapshot/event sync, frame bridge, worktree and pane RPCs.
  • Do not treat sqlite profiles as source of truth. projects stay in 2code sqlite.
  • Never let Local and Herdr own the same worktree (Local is already removed; do not bring it back).
  • Default-runtime switch and deleting Local/PTY/profiles rows already shipped in Plan: 2code as a Herdr client (profiles derived from Herdr) #436; this task does not re-litigate those decisions and does not change them.
  • One task; stack on task-1-attach-user-session. Do not push. Do not open a PR.

Why this is next

#466 has three tasks. Task 1 (#470) is accepted on task-1-attach-user-session. Task 2 is the version policy / user-binary / herdr update work Task 1 explicitly deferred. Task 3's bidirectional proof depends on attaching to a compatible shared server rather than an exact sidecar pin. The leftover open gaps (#396/#397/#398/#399/#401) and #471 are Linux-unverifiable leftovers or line-count tracking, not in-flight replacements. #402 is #394 Task 2. #439 is #436 Task 2.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions