feat: first-class Tailcat networking and server-to-server federation - #9525
feat: first-class Tailcat networking and server-to-server federation#9525BearHuddleston wants to merge 12 commits into
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… main process Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ion UI Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dist Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting). This review would cost an estimated $31.41, which exceeds your per-review limit of $10.00. The top 3 files driving up this estimate:
Tip To get this pull request reviewed, you can:
|
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces substantial Tailcat networking and server-to-server federation capabilities across the server, desktop, client runtime, UI, authentication, and release packaging paths. Its authentication/trusted-peer changes, product-default changes, and newly added static-analysis suppression directives make the scope and risk unsuitable for automatic approval. Not approved because:
Review your spending limits in Billing settings, or comment |
…, and clients Consolidates duplicated helpers into their owning packages (node-key fingerprint in contracts, connection-code preview in shared, platform keys in the tailcat manifest, the environment RPC subscription family in client-runtime, one absolute timestamp formatter in web) and removes dead state: the desktop runtime-availability IPC chain, the forward entry's stopping flag and duplicated connection id, the Tailcat "none" allow policy, unused transport labels, and the federation session subject prefix (peer sessions are now identified by the marker scope plus the peer's environment id). Federation polling no longer rewrites and republishes idle runs, keeps run events in memory instead of the peer store, caches HTTP clients per forward, gates the periodic refresh on live forwards, and reads local run events from the run's start sequence. Tailcat serve tracks the running handle instead of a generation counter, and both one-shot timers share one arm/disarm pair. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
There are 4 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a8394e9. Configure here.
…er codes Per-connection and per-peer locks were created with a read, a yield, and a write, so two first callers could each get their own semaphore and both spawn a forward. They are now created and published in a single Ref.modify. The idle sweeper snapshotted lastUsedAtMs and then dropped the forward without re-checking, so a call that reused the forward in between lost its tunnel. The drop now re-evaluates the idle condition under the peer lock. Offered peer-code scopes lived only in memory, so a restart during a code's lifetime consumed the pairing token and then rejected the code. They are now persisted in federation.json next to the peers and pruned on expiry, with a store test covering restart, redemption, expiry, and pre-existing state files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Problem
Reaching a T3 server on another machine today needs a LAN address, a tailnet, a manually managed SSH tunnel, or T3 Connect. Users on different networks without a VPN account have no first-class path, and two T3 environments cannot coordinate work with each other at all.
What this adds
Tailcat (the open-source point-to-point tunnel CLI) becomes a first-class, bundled transport, with server-to-server federation built on top of it. Tailcat is a transport underneath T3, never a trust boundary: it exposes the unchanged loopback listener, and pairing, sessions, scopes, and RPC run inside it exactly as before. The Tailcat allowlist is derived from T3 pairing state and never edited on its own.
Server (Machine B)
t3 serve --tailcat, or the new Remote access via Tailcat card in Settings → Connections, exposes the loopback listener throughtailcat serve. Identity is a 0600 key file in the secrets dir with a fixed relay region, so the address is stable across restarts.t3c://tailcat/…, text + QR) carry the address, port, and a single-use five-minute pairing token. Never a private key or reusable secret./oauth/tokenexchange when the redeemed credential was a Tailcat code and the client sent its node key (client_tailcat_node_key). Revoking a trusted device revokes its sessions and relocks the listener. Regenerate identity, rename, revoke, diagnostics, and a typed failure model (binary-missing,version-incompatible,process-exited, …) with jittered backoff are all included.Client (Machine A, desktop)
tailcat forwardon a reserved loopback port, probes readiness, then the existing descriptor → pairing → session path runs overhttp://127.0.0.1:<port>.TailcatConnectionTargetpersists the logical endpoint (address + remote port), never the ephemeral local port. Saved rows show "Tailcat · Direct / Relay", with a details dialog (forwarder status, path probe, restart, re-pair, copy diagnostics, forget).safeStorageand only materialized as a 0600 temp file while atailcatprocess starts. Web and mobile recognize codes and point to the desktop app.Bundled runtime
packages/tailcatsupervises resolve / serve / forward / ping with a version check againstnative/tailcat/manifest.json(pinned version + per-platform SHA-256). Resolution order:T3CODE_TAILCAT_BINARYoverride → bundled →PATH.scripts/fetch-tailcat.tsdownloads pinned release assets (macOS builds from the pinned source, since upstream ships no darwin archive), verifies checksums, stages into desktopextraResourcesand the CLI dist, and--update <version>re-pins. No runtime downloads. CI validates the manifest; release workflows fetch per matrix.Federation (protocol v1)
t3c://peer/…) pair two environments with explicit, per-side scopes (environment.read,projects.read,runs.read,runs.start,runs.cancel,artifacts.read). Identity reuses the existing Ed25519 environment key; calls use a challenge/JWT assertion that yields an ordinary T3 session carrying the marker scopefederation:peerwith the peer's environment id as its subject, confined to the federation HTTP group and refused at/ws.t3 remote tailcat status|enable|disable|code|peers|revokeandt3 peer code|add|list|remove|projects|run --wait.Docs:
docs/user/tailcat.md,docs/internals/tailcat.md(lifecycle, threat model, upstream limitations),docs/internals/federation.md, and ADR 001.Verification
TestClock-driven backoff), including a runtime timeout-race regression test. The single failure is the pre-existingbuild-desktop-artifactcase noted below.--tailcatserver, client forward, token exchange with node key, allowlist relock, untrusted key denied, revoke → session revoked and key locked out, missing binary →unavailable, override honored.Screenshots (macOS desktop, built from this branch)
Remote access card with a code and QR (code expired):
Add environment → Tailcat, empty and with a pasted code preview:
Saved Tailcat environment details after connecting to the Spark server:
Compatibility and notes for reviewers
--tailcatis explicit, and clients hide the UI unless the server advertisescapabilities.tailcatRemoteAccess/capabilities.federation. Older desktop shells lack the optional bridge methods, so the Tailcat card is hidden there.apps/server/scripts/cli.tsis the knob if that is too much.scripts/build-desktop-artifact.test.tshas one pre-existing failure on Linux ("skips the primary native probe for cross-architecture Windows payloads") that reproduces on main.Built with Claude Fable 5.1 in Claude Code.
🤖 Generated with Claude Code
Note
Add Tailcat remote-access transport and server-to-server federation
@t3tools/tailcatpackage providing address decoding, runtime supervision, version/manifest management, and typed errors for the Tailcat mesh transportTailcatRemoteAccess), federation identity (Ed25519 challenge/sign), durable peer store, transport, and HTTP/WS RPC surfaces; server startup now launches Tailcat after port binding and prints a connection code in headless modet3 remote tailcatandt3 peerthat authenticate against the running server via short-lived admin sessions to manage remote access, peers, and delegated runsscripts/fetch-tailcat.ts), desktop artifact staging, CI manifest validation, and release workflow steps for all platformsAuthFederationPeerScopeinwebsocketRpcRouteLayer(apps/server/src/ws.ts); federation state files written before pending-code support are accepted with empty pending codes but older server versions cannot read the newpendingPeerCodesfield. Bootstrap credential exchange (exchangeBootstrapCredential) now returnsBootstrapCredentialExchangemetadata in addition to the access token, and the auth HTTP handler records a trusted Tailcat peer on connection-code grants, logging (not failing) on record errors.Macroscope summarized a162dcf.
Note
High Risk
Changes authentication/token exchange, remote-access allowlisting, federation RPC authorization, and ships supervised native tunnel binaries across desktop, server, and npm publish paths.
Overview
Adds first-class Tailcat remote access and server federation on top of a new pinned, bundled
@t3tools/tailcatruntime (native/tailcat/manifest.json,scripts/fetch-tailcat.ts, CI verify + release caching/source builds for macOS).Desktop depends on
@t3tools/tailcat, wires a main-process Tailcat environment (loopbacktailcat forward, identity via encryptedsafeStorage, diagnostics/restart IPC), and passes a resolved bundled binary path into the backend bootstrap so server and client share the same build.Server gains
tailcatEnabled/ bootstraptailcatBinaryPath,exchangeBootstrapCredential(grant + session metadata), and on token exchange records trusted Tailcat peers when a connection-code grant is redeemed withclient_tailcat_node_key. RPC authorization maps new Tailcat admin and federation WebSocket methods to access/orchestration scopes. CLI: sharedjsonFlag,t3 remote/t3 peer(federation over authenticated/wsRPC), npm publish stages all platform Tailcat binaries intodist/tailcat.Mobile stubs
TailcatEnvironmentGatewayand rejectst3c://codes in pairing with desktop-specific guidance instead of invalid-URL errors.Reviewed by Cursor Bugbot for commit a162dcf. Bugbot is set up for automated code reviews on this repo. Configure here.