Changes touching this path
- SDK: inject transport/runner seams + map binary error codes (#432, #433, #434)
Three architecture-review deepenings that make the two LootRepo adapters'
decision logic testable without a live relay or the real binary, and replace
stderr regex-scraping with the binary's coded error channel.
#432 — RelayTransport seam. A narrow dumb-pipe (`post`) + default
HttpRelayTransport, injected via `connectRelay(url, id, { transport })`. All
interpretation stays adapter-side: response classification (401→AuthError+pubkey,
non-2xx/connection-fail→TransportError) is a pure helper, and the push
visibility-resolution + GuardError enforcement is extracted to a pure
`resolvePushVisibilities`. New relay.unit.test.ts proves error classification,
path-scoping, decode, and compose/guard against a fake transport — the WASM core
exposes no bundle encoder, so decode/path-scoping replay golden `/fetch` bytes
captured from a real relay (test/fixtures, regenerate with gen-relay-fixtures.mjs
after a format bump). Relay integration trimmed to the read + write round-trip
smokes (read.behavior/write.behavior); push-errors.behavior deleted.
#433 — LootRunner seam. `run` (buffered, never throws on non-zero) + `spawn`
(streaming) + default SubprocessRunner, injected via `openRepo(path, { runner })`.
Physical error-mapping, arg composition, and pull streaming are unit-tested
against a fake runner (physical.unit.test.ts); physical integration trimmed to
the read + write round-trip smoke.
#434 — map binary error codes → LootErrorCode. physical.ts reads `error.code`
from the binary's `{"error":{"code","message"}}` under --json and maps it in one
place (demotion/mis_seal/seal_wip→guard; unsupported_format/no_repo/unknown_flag
→setup; not_found→not-found; else generic); all stderr prose regexes dropped.
`run`/`pull` append --json, so the CLI's `new`/`describe` verbs now accept --json
(not --porcelain — no consumer) to emit coded failures. loot's engine has no
conflict-family slug (it accumulates forks rather than rejecting), so conflict
stays deferred/generic — ConflictError remains exported for when a slug lands.
e75ba364 · dbf3dbe6…
Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.