Skip to content

feat(truffle): connectMille and the reconnect facade#29

Merged
jamesyong-42 merged 2 commits into
mainfrom
feat/remote-client
Jul 27, 2026
Merged

feat(truffle): connectMille and the reconnect facade#29
jamesyong-42 merged 2 commits into
mainfrom
feat/remote-client

Conversation

@jamesyong-42

Copy link
Copy Markdown
Member

Summary

  • Adds connectMille and the RemoteFileExplorer facade — spec §26 PR 5, completing Phase 1's client half. Owns one live session and swaps it underneath the caller on reconnect.
  • Backoff/retry policy lives in state.ts as pure functions with injected randomness, so an unbounded loop or a wrongly-retried terminal error fails an assertion rather than needing a network to observe.
  • Bumps Truffle to 0.7.8. The diff across packages/core/src, crates/truffle-napi/src and crates/truffle-core/src between 0.7.6 and 0.7.8 is empty (Swift/SPM packaging and release plumbing), but the live probe was re-run against it anyway.
  • Fixes two URL leftovers main still had: Cargo.toml pointed at jamesyong/file-explorer, a repo that never moved and 404s; and the docs site linked to jamesyong-42.github.io, which 404s because GitHub Pages has no post-transfer redirect (the github.com ones do redirect, which is why this was missed).

Rebuilt on top of main rather than continuing feat/explorer-channel. That branch predates the squash-merge of #28 and would have reverted the scripts/*.sh OIDC fixes and the darwin-x64 smoke-matrix rework.

Test Plan

  • pnpm -r --if-present test — mille 413, mille-truffle 30, mille-ui 470, playground 29, all fail 0.
  • pnpm -r --if-present typecheck clean.
  • Live tailnet, both public APIs, 13/13: connect, browse, mutate, a read-only export refusing writes with EROFS, read-write on a read-only export refused outright, then the server killed and replaced mid-session — stale → reconnecting → online, exactly one identityReset, tree usable again.
  • prettier --check clean on changed files (docs/*.html fail on main already; unchanged by this PR).
  • CI.

🤖 Generated with Claude Code

Completes Phase 1's client half. connectMille dials, runs the open handshake,
and returns a RemoteFileExplorer that owns one live session and swaps it
underneath the caller on reconnect — an application holds the facade, so a
dropped connection does not invalidate the reference it is using.

Two behaviours are the point of the facade rather than incidental to it.

A dropped connection leaves the last mirror snapshot readable through
getSnapshot(). The user was looking at something; a stale tree beats a blank
one. `remote.explorer` still throws while offline, so new work fails fast
instead of hanging on a promise that will never settle.

Mutations are never replayed. A write that did not return a result frame did
not happen as far as the caller is concerned, and re-issuing it after a gap
could duplicate a rename or clobber a file that changed in the interim. This
is the one place where doing less is the correct behaviour.

Backoff and retry policy live in state.ts as pure functions with an injected
random, so the schedule is asserted directly rather than observed through a
network. Jitter is clamped so a large value cannot push a retry past the
ceiling. ACCESS_DENIED, PROTOCOL_MISMATCH and LIMIT_EXCEEDED are terminal:
the answer will not change, and a client redialling a denial looks like a
brute-force attempt to whoever reads the server's log.

Reject codes are mapped explicitly rather than passed through. The server
says why it refused; the client says what kind of failure this is.
INVALID_REQUEST means *we* sent something malformed — a permanent protocol
mismatch, not a transient fault, so treating it as retryable would loop on a
bug forever.

Reconnecting to a replaced host emits identityReset, because every EntryId
the caller holds then refers to nothing, or worse to something else.

Also bumps the Truffle peer/dev dependency to 0.7.8. The diff across
packages/core/src, crates/truffle-napi/src and crates/truffle-core/src
between 0.7.6 and 0.7.8 is empty — the release is Swift/SPM packaging and
release plumbing — but the live probe was re-run against it regardless.

And fixes Cargo.toml's repository URL, which pointed at
github.com/jamesyong/file-explorer: a repository that never moved and 404s
today, unlike the jamesyong-42 references that redirect.

Verified across a real tailnet with both public APIs, not just the fake mesh:
connect, browse, mutate, a read-only export refusing writes with EROFS,
read-write on a read-only export refused outright, then the server killed and
replaced mid-session — stale → reconnecting → online, exactly one
identityReset, tree usable again. Thirteen checks.

Suite green: mille 413, mille-truffle 30, mille-ui 470, playground 29.

Claude-Session: https://claude.ai/code/session_01BxUdtf4tecfv4uLAqRa1CR
docs/api.html and docs/index.html still linked to jamesyong-42. Two kinds of
breakage, only one of which the redirect covers.

github.com/jamesyong-42/mille redirects to vibecook-dev, so those links
worked. jamesyong-42.github.io/mille does not — GitHub Pages has no
post-transfer redirect. It returns 404 while vibecook-dev.github.io/mille
returns 200, so the "Docs site" link on the published site pointed at
nothing.

Checked both with curl rather than assuming they behaved the same way; an
earlier note in this repo claimed all the stale references still resolved,
which was true only of the github.com ones.

Claude-Session: https://claude.ai/code/session_01BxUdtf4tecfv4uLAqRa1CR
@jamesyong-42
jamesyong-42 merged commit 34cdb82 into main Jul 27, 2026
13 checks passed
@jamesyong-42
jamesyong-42 deleted the feat/remote-client branch July 27, 2026 16:50
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