Changes touching this path
- TS SDK slice 6: physical mode openRepo over the shared LootRepo interface (#428)
The second backend: openRepo(path) drives an on-disk .loot/ checkout by
shelling out to the installed loot binary, returning the IDENTICAL LootRepo
the in-memory mode defines — so calling code is backend-agnostic. The binary
owns all crypto/codec; physical mode adds none (no WASM).
- CLI (sanctioned tiny machine-output addition, #428): `loot surface
--porcelain`/`--json` emit the current readable tree as path+visibility
(loot-core `verdict::surface_{porcelain,json}`), so `list()` never scrapes
human text. An empty repo is an empty tree, not an error.
- sdk/src/physical.ts: openRepo → PhysicalRepo via child_process. list()
parses `surface --json`; read() streams the materialized file (a real byte
stream) → NotFound on ENOENT; edit/remove write the working copy
(capture-first) and record a client-side overlay so status/diff report kinds
(added/modified/removed) against a committed baseline captured at open +
refreshed on push — loot folds a described change into the current tree, so
surface alone can't tell add from modify. describe/push shell out
(`describe -m`, `new`); guards map to --allow-demote. Errors map to the
shared taxonomy (missing binary → setup error; parent-moved → ConflictError;
non-repo → NotFoundError).
- Seam: `runReadContract` extracted to sdk/test/read-contract.ts and now runs
VERBATIM against BOTH backends (connectRelay and openRepo) — the proof they
are interchangeable behind one interface. physical.behavior.test.ts adds a
write round-trip + error surface. 32 SDK tests; loot-core/loot-cli green.
Deferred (documented): physical private-visibility authoring (a .lootattributes
rule) and pull-with-remote behavior — public content covers the AC and the
in-memory backend covers private.
Refs #428, #422.
9d12a388 · dbf3dbe6… - 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…diff
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.