<feed xmlns='http://www.w3.org/2005/Atom'>
<title>duet/tests/test-duet-transfer.el, branch main</title>
<subtitle>Unnamed repository; edit this file 'description' to name the repository.
</subtitle>
<id>https://git.cjennings.net/duet/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/duet/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/duet/'/>
<updated>2026-06-06T16:37:39+00:00</updated>
<entry>
<title>test: cover the remaining failure, contract, and safety branches</title>
<updated>2026-06-06T16:37:39+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-06T16:37:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/duet/commit/?id=0930ec3c259d93a68fc59854677cdc7fef634e71'/>
<id>urn:sha1:0930ec3c259d93a68fc59854677cdc7fef634e71</id>
<content type='text'>
The bug fixes raised line coverage on duet.el to 92.3%. This closes the rest. The new tests exercise the branches a review would want locked down before execution and UI build on this core: each arm of the minimal failure normalizer (missing executable, stall, signal cancellation, unknown-without-exit), a :match predicate rather than a regexp, the rsync normalizer mapping known stderr to permission/space/protocol classes, the capability-tier contract check, an explicit :async override, the case-collision path through the safety composite's injected predicates, and redaction of a pattern with no capture group.

Line coverage on duet.el is now 100%. The one deliberately trivial case left is the entry command, a not-yet-implemented stub that errors. Its test asserts that and will change when the pane layout lands.
</content>
</entry>
<entry>
<title>fix: build route-specific rsync specs and defer both-remote to Phase 6</title>
<updated>2026-06-06T16:28:45+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-06T16:28:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/duet/commit/?id=be0feb2b0d0070f23bc9cd348c877aa3ad6c8b7b'/>
<id>urn:sha1:be0feb2b0d0070f23bc9cd348c877aa3ad6c8b7b</id>
<content type='text'>
duet--rsync-command built one direct rsync argv for every pair, rendering each remote endpoint as host:path. That is right when at most one endpoint is remote, but rsync refuses a source and destination that are both remote, so the argv was unexecutable for every remote-to-remote route, not just the round-trip.

Now rsync builds a single argv only for :local and :local-remote, the routes it can run in one invocation. A both-remote pair returns a deferred spec: nil argv, an :exec-mode marker, and the route. Phase 6 reads the route to run rsync on a host (same-host), route through this machine (round-trip), or go direct host-to-host when the override is set.

transfer-spec now carries the classified :src-endpoint and :dst-endpoint alongside the route, so execution has the structured endpoints and never has to reinterpret an argv string. The runnable-shape check from the in-process-mode fix extends to accept :exec-mode, so a deferred spec reads as a real plan rather than a broken nil-argv command.

Tests cover local-to-remote and remote-to-local argv shapes including a port, and the same-host, round-trip, and direct routes each producing a deferred spec.
</content>
</entry>
<entry>
<title>fix: make the in-process execution mode explicit in specs and contract checks</title>
<updated>2026-06-06T16:23:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-06T16:23:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/duet/commit/?id=7757f6909bfcc20211e8ae1f4ad364082ca924f5'/>
<id>urn:sha1:7757f6909bfcc20211e8ae1f4ad364082ca924f5</id>
<content type='text'>
Two coupled holes surfaced in the Phase 0-3 review. duet--transfer-spec copied only :argv, :default-directory, and :process-environment out of a backend's command result, dropping the :tramp marker, so a TRAMP-routed spec arrived as :argv nil with nothing telling the executor to copy in process. And duet--check-command leaned on listp, where nil is a list in Elisp, so a command builder returning nil or a bare :argv nil passed the minimum tier.

Both turn on the same idea, so they share a fix. duet--command-spec-executable-p defines a runnable spec: a non-empty plist with either a non-empty :argv of strings (a CLI backend) or a declared in-process mode such as :tramp. The contract checker rejects anything else, and transfer-spec now carries :tramp through, so the TRAMP fallback has a positive execution signal rather than an ambiguous nil argv.

The legitimate TRAMP backend keeps passing because it declares its mode. A broken backend that forgets argv no longer slips through.
</content>
</entry>
<entry>
<title>feat: add transfer-spec, the endpoint matrix, and the conflict/move planners</title>
<updated>2026-06-06T15:58:46+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-06T15:58:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/duet/commit/?id=3d8778ba12cbbe2b8f6d5512d4b4a8f13a9c55ac'/>
<id>urn:sha1:3d8778ba12cbbe2b8f6d5512d4b4a8f13a9c55ac</id>
<content type='text'>
duet--transfer-spec classifies both endpoints, selects a backend through the registry, determines the route, and delegates argv construction to the backend (Phase 3 in the design spec). It returns the plist the executor will run: sources, destination, backend, route, argv, and async flag.

The route is decided independently of backend by duet--transfer-route: local, local-remote, remote-same-host, remote-direct, or remote-roundtrip. Different remote hosts default to the round-trip through this machine. Direct host-to-host runs only when a per-connection override asks for it, never automatically, because a direct route can silently fail where a round-trip always works.

This phase also registers the two stage-1 backends through the same duet-register-backend seam a plugin uses: rsync for local and ssh-reachable endpoints, TRAMP as the universal fallback that costs more so rsync wins whenever it applies. rsync receives its source and destination as separate argv elements, so a filename with spaces or shell metacharacters stays inert.

The two planners are pure and prompt-free, so the dangerous decisions are testable before a byte moves. duet--plan-conflicts resolves overwrite/skip/rename per file with an apply-to-all that stops asking, taking the existence check and the resolver as injected functions. duet--plan-move pairs each source's copy with a delete gated on that source's copy success, so a failed copy can never delete its source.

Remote-to-remote execution (honoring the round-trip route as a two-step through local) and TRAMP's in-process copy land in Phase 6. Here transfer-spec records the route and the rsync path the executor will use.
</content>
</entry>
</feed>
