Changes touching this path

  • TS SDK slice 1: in-memory public read against a real relay (#423) Completes the slice-1 tracer bullet on top of the landed WASM core: an in-memory agent connects to a relay and reads a public file byte-for-byte with no .loot/ on disk. The single passing read() exercises the whole stack — WASM codec + fetch transport + client-side path-scoping + AES decrypt + host-side zstd inflate. - loot-wasm: add encode_fetch_request so the /fetch wire framing (version marker + have/wants) stays single-sourced with the binary; frozen-vector parity added (native + wasm-pack test --node). - sdk/ (@millerbyte/loot-sdk, ESM): connectRelay(url, identity) -> LootRepo with list() and streaming read()/.bytes(). Transport speaks the relay HTTP wire via fetch(); loot-net never crosses. Client-side path-scoping (#380): a metadata fetch resolves path->oid, a scoped fetch pulls just that object's bytes (both have=[], since the relay gathers objects+keys from the changes not in `have`). zstd inflate is host-side (fzstd) — the wasm core has no zstd. Typed errors (LootError/TransportError/NotFoundError/AuthError, #382). - Seam 1: a vitest suite drives the real `loot serve` relay, seeded via the real `loot` CLI (init -> author -> new -> push); 4 tests green (list, byte- for-byte read, streaming, NotFound). The wasm pkg is built by `npm run build:wasm` (gitignored, regenerated from crates/loot-wasm). Deferred to later slices: write path, private/grant read, physical backend. Refs #423, #421. db75b0b7 · dbf3dbe6…
  • Slice 1 review fixes: prove path-scoping, reusable suite, SDK docs (#423) Addresses the code-review findings on de6e281: - Add sdk/README.md — states plainly that loot hides CONTENT, not path names or tree shape ("don't put secrets in path names"), documents usage and the build:wasm step. Closes the missing-docs AC. - Make path-scoping observable: the behavior suite now seeds TWO public files and asserts at the wire level that a scoped /fetch returns the requested object's bytes but NOT the sibling's — while the sibling's address is still visible (structure is public, content is scoped). - Make the read suite backend-parameterized: assertions live in runReadContract(makeRepo) so #422's physical openRepo reuses them verbatim. - Honest streaming: ReadStream now documents that a sealed object is one AES-GCM unit yielded as a single chunk (not chunked streaming), which the iterable shape grows into for later slices. Left as-is: currentTree resolves heads in TS — that's the #381-sanctioned thin store-orchestration (only crypto/codec must stay Rust), not drift. Refs #423. f736b628 · dbf3dbe6…diff
  • 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…diff
  • 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
  • the sdk suite hands push a positional url again: --remote means a name now (#880) f8caca6b · dbf3dbe6…diff
  • the browser SDK reads a relay at the heads generation and refuses a host that cannot give it one: connectRelay list() and read() open with a fetch carrying depth one, the heads nodes and nothing older with the whole manifest on each, and the object round is bounded the same way rather than paying the history a second time, since a want is answered by address across the cut, so a read is two bounded rounds where it was one unbounded and one that repeated it, against the 53 MB per call the map measured on this repo; the host is asked once per repo, on GET /info, which the transport seam gains as an optional get so a lane that never asks stays honest, and a host whose answer does not say fetch_depth, or answers with a non-2xx, or not JSON, or through a transport with no get, is read as one that did not say and is refused as a SetupError naming the host and the version line it reported, before any bytes are asked for, unless the caller passed unboundedRead, which accepts the cost on that host and asks a capable one for the bound all the same; a probe that could not reach the host is a TransportError and is asked again next time. heads() names the set the bounded read answered with, on a RelayLootRepo the connect door now returns, and several heads fold as they did, a later head write winning a shared path; push, status and grant ride the same bounded round because the parents a change builds on and the tree it carries are the heads own, which is all they ever read. pinned on a fake transport with the golden bundles, the request bytes bounded on the metadata and the object rounds, the old host refused by name on list and on read with nothing posted, the opt-in against it and not against a capable host, the probe asked once across list and read, the three did-not-say shapes each refused and each opted into, the unreachable probe re-asked, and heads() one; and over a spawned relay with two generations, the request four bytes longer than the unbounded one ending in one, the answer one node whose parent is the base change a whole fetch shows, every round of a read one node, the head named and then two after a fork the second session pushes off a replayed metadata answer, listed as the union, and the field stripped off the real /info refused by name and paid for on opt-in. README states the cost and the option, CONTEXT and ADR 0089 say the browser SDK refuses rather than is to. red under mutation, counts read each time: the depth never sent (21 passed and 5 failed), every host assumed to cut (21 passed and 5 failed), unboundedRead ignored (22 passed and 4 failed), the probe asked on every read (18 passed and 1 failed), a failed probe remembered (17 passed and 2 failed), heads() naming nothing (23 passed and 3 failed), the refusal not naming the host (24 passed and 2 failed), a transport without get reading as capable (18 passed and 1 failed), a 404 on /info reading as capable (18 passed and 1 failed), an error on /info remembered as a refusal (18 passed and 1 failed), an explicit false read as absent (18 passed and 1 failed), a JSON non-object left unclassified (18 passed and 1 failed), the head derivation skipping the parented filter (6 passed and 1 failed), and the object round unbounded (24 passed and 2 failed). no rust, no migration, no wire or format byte moves, and no host behaviour moves, so this owes no deploy beyond the one #2123 already owes the release; the SDK suite is green in the lane (126 passed over 11 files, against release binaries and sdk/wasm built in the lane) and the workspace suite is untouched by a TypeScript change and was green at the base (4017 passed over 126 binaries, 7 ignored, at 5a007435) (#2124) 8eb8e8cd · dbf3dbe6…diff
  • the readiness waits over a spawned binary in sdk stop being budgets and start ending on the host itself, and the reason they were never seen to fire is now written where a reader meets them: every behaviour suite that stands a relay up polled a real loot serve behind Date.now() plus 20 seconds, which is a SMALLER budget than the 30 seconds that already lost in #1693 over a LARGER startup, and the 60 second beforeAll hook outside it sat inside the same measured spread, so raising the inner number alone would have left the effective bound where it was. the new sdk/test/spawned-host.ts has three endings and only the last is a clock: the probe succeeding, which is the condition these suites actually need since it proves the host ANSWERS rather than that it printed a line; the child exiting, reported with its code instead of polled at for the rest of the wait, seeded from the childs recorded state as well as its future exit event so a host already gone is not waited on; and a backstop carrying the same 600 seconds tutorial.rs carries, off the same saturated measurement and for the same reason, with the hook timeout derived strictly larger so the named refusal wins the race. the fixture generator imports that module rather than restating it, which node does by stripping the types itself. THE BEHAVIOUR SUITES CANNOT PIN ANY OF THIS AND THE CONTROL SAYS SO: with the condition deleted outright the whole sdk suite still read 11 files passed, because an idle machine has the relay up before the next await, so a dedicated pin over a spawned node child carries it instead. red under mutation, counts read each time: the exit ending deleted failed two of the three pins on vitests own timeout (2 failed and 1 passed, both at 5000ms, the wait running on to the backstop), the already-gone seed dropped failed the third alone (1 failed and 2 passed), and the probe discarded so the wait returns on the first look failed all three (3 failed and 0 passed, asked 1 against 3 and two promises resolving instead of rejecting), each restored to 3 passed and the sdk suite to 129 passed over 12 files. the gate question is NOT answered here and #2080 still owns it, but the sdk README now states what decides it and gives the grep that checks it rather than listing the workflows, and tutorial.rs stops naming budgets that no longer exist. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4178 passed over 133 binaries, 9 ignored) (#1914) 2cac70f9 · dbf3dbe6…diff
  • the review-sweep fix-up over #2295, #2096 and #2137. the discovering layout door takes its caller body need: workspace::ambient_store_dot, which fixed BodyNeed::Unread and so would have admitted a body-reading verb that found its store through it to a body-deferred store, is now discovered_store_dot(start, bodies), passing the need through to resolve_store_dot, and loot doctor declares Unread at its call. ambient_local_config keeps a fixed Unread, since it hands back the config path and lane id rather than the store, and its doc says so. pinned in workspace.rs: a Held caller is refused naming the state from the root and from a subdirectory, an Unread caller still finds the store, and the same repo before the record is the control; red first with the need ignored (0 passed and 1 failed), red under mutation with the door fixed at Held (0 and 1), restored to green. the refusal text, the doctor bodies line and the ADR 0093 section 3 heading now say a verb that declares neither that it fetches first nor that it reads none is refused, since Unread passes. set sentences replaced by the property in workspace.rs, CONTEXT.md and ADR 0093: a door that finds a store without reaching BodyNeed::admit is unguarded and whether that is safe is a question for that door, is_relay is recorded as why a relay store needs no guard, and a verb behind an admit door meets the refusal unless a need other than Held is named there. the site marker pin: the hash now covers every module the page reaches by a relative import, so RETENTION_DAYS on /privacy moves it, and the privacy pin is re-pinned with its date unchanged; the scan reads markers with comments stripped, so a comment saying last updated no longer makes a file unreadable; the header defines a dated marker as the words last updated and says why the known-issues Last reviewed date is left to its own pins. red first against the old test, counts read: RETENTION_DAYS moved to 31 stayed green (9 passed), a terms comment saying last updated went red (2 failed and 6 passed); with the change the first is red (1 failed and 10 passed) and the second green (11 passed). red under mutation: the import reach dropped from the pin (1 failed and 10 passed), comment stripping dropped from the marker read (1 and 10), the import walk reading nothing (2 and 9), each restored to green. stale sentences fixed: HUNT-PERF names every other read the open makes rather than the reads in the body of assemble, the in_progress verb is the verb that wrote the record rather than a STOPPING member, the SDK read test no longer calls its shape the one where the head derivation is load-bearing, and repo.ts and the ChangeView doc name the superseded-head half of within_depth_of_heads and say its working-change branch is not mirrored. no migration, no format byte and no published wording moves, so this owes no deploy. the site gate is green (680 passed and 62 skipped over 62 files, 62 surfaces) with no ceiling moved, the sdk build:wasm and type-check are green, and the workspace suite is green (4324 passed over 137 binaries, 12 ignored) (#2299) d7d5ef94 · 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.