Skip to content

feat(mcpx): daemon transport interface + tsnet identity listener (#1967) - #2203

Merged
dezren39 merged 12 commits into
mainfrom
queue/2902-1967-tsnet-transport
Oct 30, 2026
Merged

dezren39 merged 12 commits into
mainfrom
queue/2902-1967-tsnet-transport

Conversation

@dezren39

@dezren39 dezren39 commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

First implementation slice for #1967 (queue task 2902), per the merged decision doc (tsnet as the first identity transport; iroh/cgo rejected there and not revisited).

What this does

  • internal/daemon/transport.go: a Transport interface (transports produce a net.Listener). Server.Listen now binds the unix socket and TCP through unixTransport/tcpTransport — behaviour unchanged, existing daemon tests untouched and passing.
  • internal/daemon/transport_tsnet.go: a tsnet transport behind the same interface. New setting daemon.tsnet.hostname (default empty = off) names the daemon's tailnet identity; the node state lives under <state>/tsnet so the MagicDNS name is stable. The auth key is read from the TS_AUTHKEY environment variable only — never a setting, so it cannot land in a config file or settings dump. With a hostname set but no key, Listen fails with that reason: no silent fallback to a local-only daemon.
  • Client: daemon.endpoint grows the tsnet://<name>[:port] scheme (default port 80, matching the listener). The client joins the tailnet as an ephemeral node (mcpx-client) to dial it; a malformed tsnet:// endpoint and a missing key both produce clear errors at dial time, never a retry of the name as plain TCP.
  • Status: Server.TSNetStatus exposes the node's identity plus per-peer direct-vs-relayed state (from tsnet's local API) for the status surface to render.

Verified on the laptop (CI is the authority)

  • go vet ./internal/daemon/ ./internal/cli/ ./internal/settings/ clean
  • go test ./internal/daemon/ -run 'Transport|TSNet|ParseTSNet' pass — builtin transports bind/dial through the interface, endpoint parsing, and both no-auth-key failure modes
  • go test ./internal/settings/ pass (docs/configuration.md regenerated for the new setting)

Known remaining pieces (not hidden)

Refs #1967. The browser-reachability question escalated to Drew on #1967 is untouched by this slice (phone-yes / browser-no per the decision doc).

🤖 queue task 2902, lane A


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@dezren39
dezren39 requested a review from a team as a code owner October 29, 2026 09:16
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 44 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dc4f876a-2cba-4009-9ad4-3c4451bbce8a
Commits

Reviewing files that changed from the base of the PR and between 5a34fa7 and d9e4197.

Files ignored due to path filters: 1
  • pkgs/mcpx/go.sum is excluded by !**/*.sum
Files selected for processing: 15
  • pkgs/mcpx/docs/configuration.md
  • pkgs/mcpx/go.mod
  • pkgs/mcpx/internal/cli/client.go
  • pkgs/mcpx/internal/daemon/origin.go
  • pkgs/mcpx/internal/daemon/origin_tsnet_test.go
  • pkgs/mcpx/internal/daemon/server.go
  • pkgs/mcpx/internal/daemon/takeover.go
  • pkgs/mcpx/internal/daemon/transport.go
  • pkgs/mcpx/internal/daemon/transport_test.go
  • pkgs/mcpx/internal/daemon/transport_tsnet.go
  • pkgs/mcpx/internal/defaults/defaults.go
  • pkgs/mcpx/internal/defaults/defaults.json
  • pkgs/mcpx/internal/settings/registry.go
  • pkgs/mcpx/package.nix
  • treefmt.toml
Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2fa3dc249

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkgs/mcpx/go.mod Resolved
Comment thread pkgs/mcpx/internal/daemon/server.go Resolved
Comment thread pkgs/mcpx/internal/daemon/server.go Resolved
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T17:03:04.722356Z d9e4197 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

The branch was cut from a local main carrying repo-keeper snapshot
commits; bin/sessions.json is machine-local state, not part of the
tsnet transport change. Restore it to origin/main.
The treefmt PR gate (nix-ci.yml, added 2026-10-12) checks changed files,
and configuration.md's tables were never prettier-formatted. Adding the
daemon.tsnet.hostname row therefore makes prettier rewrite every table
row in the file. Apply the formatter once so the gate passes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d5d620b37

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkgs/mcpx/internal/daemon/server.go Resolved
… treefmt

TestTheDocumentationListsEverySetting byte-compares the generated
section of docs/configuration.md against settings.Registry(); the
prettier format commit padded those tables, failing the test (and
the nix build) on every CI run. Regenerate with the test -update
flag and add configuration.md to the treefmt excludes next to
parity.md, which is excluded for exactly this reason.
@dezren39

Copy link
Copy Markdown
Member Author

Agent note — queue task 2902, Lane D, 2026-10-29

CI failure on head 6d5d620b diagnosed and fixed in 86efb61f:

  • Both red checks (mcpx test, mcpx nix build) failed on the single test TestTheDocumentationListsEverySetting: docs/configuration.md no longer matched settings.Registry(). Root cause: the earlier prettier format commit padded the generated tables to satisfy treefmt, but that test byte-compares the generated section — and configuration.md was missing from the treefmt excludes list that already covers parity.md for exactly this reason. Fix: regenerated the doc with the test's -update flag (verified passing locally, -count=1) and added pkgs/mcpx/docs/configuration.md to the treefmt global excludes with the same rationale comment.

Codex findings disposition (merge gate — this PR does not merge until each is fixed or answered):

  • package.nix vendorHash P1 — answered on its thread: hash already updated in this PR; nix build reached the test phase, proving it matches.
  • Six findings remain open and blocking, all in the tsnet/takeover interaction surface, to be fixed on this branch as the resume work for task 2902:
    1. P1 server.go:309 — restore the tsnet listener during daemon takeover (TakeOver/commitTakeover adopt only unix+TCP).
    2. P1 server.go:352 — Host check (refuseBrowserPages/origin.go) rejects tailnet requests under the default loopback address.
    3. P2 transport_tsnet.go:80 — await tailnet auth (Up() with bounded context) before reporting startup success.
    4. P2 transport_tsnet.go:199 — ephemeral clients need an isolated in-memory state store (empty Dir does not disable on-disk persistence).
    5. P2 transport_tsnet.go:204 — reject tsnet:// destinations outside the authenticated tailnet (Server.Dial falls back to system dialing).
    6. P2 server.go:315 — drain/close non-transferable tsnet (gonet) connections at takeover instead of enqueueing them for descriptor handoff.

Task 2902 is parked on: CI for 86efb61f + the six fixes above. Worktree (laptop): ~/git/fleet/2902-1967, branch queue/2902-1967-tsnet-transport.

dezren39 and others added 3 commits October 29, 2026 09:17
…(PR #2203)

- takeover: predecessor closes its tsnet node before kindEnd and the
  successor recreates the identity listener in commitTakeover before
  committing (listenTSNet factored out of Listen); a handover that
  does not commit recreates it in restoreHTTP.
- takeover: sendConns drains connections with no descriptor (tsnet
  gonet conns) via release() instead of aborting the upgrade.
- origin: the Host check distinguishes the tailnet listener (local
  addr in 100.64/10 or the Tailscale ULA) and requires this daemon's
  own tailnet name/address there, keeping the loopback rule intact
  for the loopback listener.
- tsnet: Listen awaits Up() with a bounded context so an expired or
  revoked auth key fails startup instead of silently never joining.
- tsnet: ephemeral clients get an in-memory state store, so a client
  process cannot stay attached to a previous tailnet's node.
- tsnet: the client dialer verifies the destination is a peer (or
  itself) on the authenticated tailnet before dialing; tsnet Dial
  otherwise falls back to system DNS and plain TCP.

Tests: TestTailnetLocalIP, TestTailnetHost (incl. the
mcpx-daemon.evil.example rejection), TestSendConnsDrainsDescriptorless
on the laptop; daemon takeover/handoff/upgrade groups pass.
TestNoPackageRedeclaresADefault failed on the tsnet transport's
const tsnetUpTimeout: timeouts belong in internal/defaults/defaults.json.
Moves it to daemon.tsnetUpTimeout (30s, unchanged) and consumes it from
internal/defaults.

Refs #1967
@dezren39

Copy link
Copy Markdown
Member Author

agent-note:v1 — worker repo-worker-code (openchamber scheduled lane)

Parked: CI pending on basic: nix build + basic: test.

Root cause of the earlier red on 3ad2372: TestNoPackageRedeclaresADefault flagged const tsnetUpTimeout = 30 * time.Second in internal/daemon/transport_tsnet.go — timeouts must live in internal/defaults/defaults.json.

Fixed in 71b237d:

  • added daemon.tsnetUpTimeout: "30s" to internal/defaults/defaults.json
  • struct field + defaults.TSNetUpTimeout in internal/defaults/defaults.go
  • transport consumes it instead of a local const (value unchanged)

Local verification: go build ./... OK; go test ./internal/defaults ./internal/daemon ./internal/settings PASS. Merged origin/main in first.

Resume: read checks on 71b237d, then Codex re-review, then merge. Task 2902 parked 20 min.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71b237dd1e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkgs/mcpx/internal/daemon/takeover.go Resolved
Comment thread pkgs/mcpx/internal/daemon/server.go Resolved
… dir per config

Codex re-review of PR #2203 (head 71b237d) findings:
- P1 takeover.go: closeTSNetNode closed the tsnet node without stopping
  the tracked connListener, so the blocked Accept forwarded the tsnet
  listener-close error to httpErr and Serve treated it as fatal during
  a handover. Stop the wrapper first so Accept converts it to
  http.ErrServerClosed.
- P2 server.go: every daemon shared <state>/tsnet as node state, so a
  second configured daemon loaded the first daemon's node keys and
  its auth key was ignored. tsnetStateDir now keys the directory by
  the FingerprintConfig file-set identity (stable across takeover),
  with the unkeyed dir kept for config-less daemons. Regression test
  TestTSNetStateDirKeyedByConfig.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14b2c40ef6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkgs/mcpx/internal/daemon/takeover.go Resolved
… drain + cold-join budget (#1967)

1. passConns now stops the tracked tsnet connListener alongside the unix/TCP listeners, before the connection sweep and Registry.Detach — a tailnet connection accepted after the sweep was gathered by nobody and could reach a detached pool or be cut by the node shutdown. stop() closes only the inner listener; the node stays ours until closeTSNetNode, and restoreHTTP re-listens on the same node when a handover that stopped it does not commit. 2. TSNetDialer gains ensureJoined: the first dial waits for the ephemeral node to reach Running once, with the daemon.tsnetUpTimeout (30s) budget on a background context instead of the caller 2s health-probe deadline — a cold join (registration + auth) bounded by autostart.pingTimeout made Connect reject a healthy daemon, and every new process repeated the cold join. Tests: go test ./internal/daemon/ full package pass on the laptop.
@dezren39

Copy link
Copy Markdown
Member Author

@codex review

Both findings from the 14b2c40 review are fixed in d9e4197 (passConns stops the tsnet listener before the sweep/Detach with same-node restore; TSNetDialer cold join gets the tsnetUpTimeout budget) with disposition replies on each thread. Please re-review the current head.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: d9e4197dfe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dezren39

Copy link
Copy Markdown
Member Author

CI re-run evidence (queue task 2902, openchamber scheduled lane)

basic: test failed on head d9e4197 with internal/e2e 1 failed / 3529 passed, and the job log plus check annotations do not name the failing test.

Same head, run locally on the laptop:

cd pkgs/mcpx && go test ./internal/e2e/ -count=1
ok  github.com/dezren39/mcpx/internal/e2e  581.715s

That is the whole e2e package, green, on the exact CI head — so the red check is most likely a timing-sensitive e2e test rather than a deterministic regression from d9e4197 (the same branch was green at 71b237d, and d9e4197 touches only internal/daemon takeover/tsnet code).

Next step on resume (task parked 20 min, resume brief on #1967): re-run the failed basic: test job on d9e4197; if it goes green, enqueue to the merge queue, and if it fails again, the failing test name is in the re-run's log and must be reproduced with -run <Test> -count=5 on this branch and on clean main before merging.

@dezren39
dezren39 added this pull request to the merge queue Oct 29, 2026
@dezren39

Copy link
Copy Markdown
Member Author

🤖 Agent note · parked

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to Branch Protection failures Oct 29, 2026
You're not authorized to push to this branch. Visit "About protected branches" for more information.
@dezren39
dezren39 added this pull request to the merge queue Oct 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to Branch Protection failures Oct 29, 2026
You're not authorized to push to this branch. Visit "About protected branches" for more information.
@dezren39
dezren39 added this pull request to the merge queue Oct 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to Branch Protection failures Oct 29, 2026
You're not authorized to push to this branch. Visit "About protected branches" for more information.
@dezren39
dezren39 added this pull request to the merge queue Oct 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to Branch Protection failures Oct 29, 2026
You're not authorized to push to this branch. Visit "About protected branches" for more information.
@dezren39
dezren39 added this pull request to the merge queue Oct 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to Branch Protection failures Oct 29, 2026
You're not authorized to push to this branch. Visit "About protected branches" for more information.
@dezren39
dezren39 added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit ac411b1 Oct 30, 2026
10 of 11 checks passed
@dezren39
dezren39 deleted the queue/2902-1967-tsnet-transport branch October 30, 2026 01:00
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