from2c12d0f2a3246d10ab33f9ded3d4d530f7db0f2fd84d0dcf50ac2aba554f8178 → 2c12d0f2
tod5fba81e08a33c8252935eeeecda73a70b4e770518bfa3e6b1f7f83a2e99c9ab → d5fba81e

+4 −2

11 # loot
22
33 A from-scratch source-control system.
44
55 **Thesis:** visibility and permissions belong to *content and changes*, not to
66 the *repository*. Commit your `.env`. Keep files private inside a shared repo.
77 Embargo a security fix: merge it, cut the release, reveal the source later.
88
99 This is the unsolved problem in modern version control. Ergonomics (jj already
1010 nails them) are a layer for later.
1111
1212 ## Security
1313
1414 loot's cryptography is written from scratch and has never been independently
1515 reviewed. What each reader — a relay, a forge, a collaborator, the anonymous
1616 internet, the git mirror, and anyone holding your `.loot/` directory — can
1717 actually read is stated tier by tier at
1818 **<https://loot.millerbyte.com/trust>**. Read it before you put anything in a
1919 loot repo whose disclosure would hurt you. Two things it says that the pitch
2020 does not: the default tier is one a relay reads by design, and path names are
2121 never encrypted at any tier.
2222
2323 **Report a vulnerability to <security@millerbyte.com>** — not to the public
2424 Discord. One maintainer, best effort, expect days rather than hours. ⚠ This
2525 file is not the published route: the GitHub repo is permanently private
2626 (ADR 0045/0064), so a `SECURITY.md` here and GitHub's private-advisory button
2727 both reach nobody. `/trust` is the route; this section exists for whoever
2828 already has repo access.
2929
3030 ## What works today
3131
3232 The full loop from first init to relay-based collaboration is functional. This
33−block is the CLI's **full verb list** — all 75 verbs, regenerated from
33+block is the CLI's **full verb list** — all 76 verbs, regenerated from
3434 `loot --help` rather than curated (a guard test pins it to the dispatch table,
3535 so it cannot drift again; #1107):
3636
3737 ```text
38−abandon absorb adopt apply archive attest bisect blame bundle buoy burn cat
38+abandon absorb adopt apply apply-patch archive attest bisect blame bundle buoy
39+burn cat
3940 cherry-pick clone completions config conflicts converge count-objects
4041 describe diff doctor duplicate edit embargo-status evolog explain ferry
4142 format-patch gates gc grant grant-status grants grep heads id init keygen
4243 lane lanes lock log manifest maroon merge migrate new op peer propose pull
4344 pull-grants purges push rehome relay remote resolve restore revert serve
4445 shortlog show split squash status surface tag tutorial undo unlock verify
4546 view whoami
4647 ```
4748
4849 `loot <verb> --help` prints any verb's own usage block without running it.
4950
5051 ### Try it: private `.env` in a shared repo
5152
5253 ```bash
5354 cargo build --release
5455 export PATH="$PWD/target/release:$PATH"
5556
5657 cd $(mktemp -d)
5758 printf 'TOKEN=supersecret\n' > .env
5859 printf '# My Project\n' > README.md
5960 printf '.env restricted=alice\n*.md public\n' > .lootattributes
6061
6162 loot init --identity alice
6263 loot status -m "initial work"
6364 loot surface # alice: restores both README.md and .env
6465
6566 # switch to a non-keyholder to prove it
6667 printf mallory > .loot/identity
6768 rm -f .env README.md
6869 loot surface # mallory: README.md appears; .env stays sealed
6970 ```
7071
7172 The `.env` ciphertext lives in `.loot/` the whole time. Mallory cannot decrypt
7273 it, and if she snapshots and re-syncs, the sealed file is carried forward
7374 untouched — snapshot is visibility-aware.
7475
7576 ### Sync over a relay
7677
7778 A relay stores and forwards ciphertext it cannot read. Restricted keys never
7879 travel in a sync bundle (ADR 0003), so the relay's zero-knowledge property is
7980 enforced at the wire level, not by policy.
8081
8182 ```bash
8283 # Terminal 1: run a relay
8384 loot serve --dir /tmp/relay --addr 127.0.0.1:4000
8485
8586 # Terminal 2: alice pushes
8687 loot remote add origin http://127.0.0.1:4000
8788 loot push
8889
8990 # Terminal 3: bob pulls (bob only sees public content)
9091 loot clone http://127.0.0.1:4000 ./bob-repo --identity bob
9192 ```
9293
9394 ### Grants: sharing a content key
9495
9596 ```bash
9697 # alice knows bob's public key (from `loot whoami` on bob's machine)
9798 loot peer add bob "ssh-ed25519 AAAA..."
9899
99100 # deliver a sealed grant via the relay
100101 loot grant --relay origin .env bob
101102
102103 # bob fetches and applies it
103104 loot pull-grants # verifies alice's signature, checks peer registry
104105 loot surface # now bob can read .env
105106 ```
106107
107108 ### Embargo: timed reveals
108109
109110 ```bash
110111 # mark a file as embargoed until unix timestamp 1800000000
111112 echo "VULN_DETAILS=CVE-2025-XXXX" > security-fix.txt
112113 printf 'security-fix.txt embargoed=1800000000\n' >> .lootattributes
113114
114115 loot status -m "patch for CVE-2025-XXXX"
115116 loot push # relay holds the ciphertext; key withheld until reveal_at
116117 ```
117118
118119 At `reveal_at`, the next read promotes the key out of escrow, so anyone who
119120 pulls can read it.
120121 The seam for a third-party key custodian (network escrow) is designed and ready.
121122
122123 ## Architecture
123124
124125 ```text
125126 crates/
126127 loot-core canonical engine: encrypted DAG, per-content visibility, convergence
127128 loot-identity ed25519 keypairs, x25519 ECIES, signed push envelopes, peer registry
128129 loot-net relay HTTP server + sync client (stow/negotiate/grant mailbox)
129130 loot-cli the `loot` binary — commands are thin verbs over Workspace
130131 loot-bench shared 50k-file benchmark workload
131132 spike-dag thin shim re-exporting loot-core (bake-off compat)
132133 spike-crdt non-canonical CRDT model (retained so the bake-off is reproducible)
133134 ```
134135
135136 ### Key modules
136137
137138 | Module | What it owns |
138139 | --- | --- |
139140 | `loot-core::sealed` | Per-content encryption, key custody, embargo, public-content compression (ADR 0003, 0007, 0020) |
140141 | `loot-core::converge` | Merger/relay convergence rule — decrypt-then-merge (ADR 0001) |
141142 | `loot-core::engine` | Encrypted content-addressed DAG: put/get/record/surface/bundle/apply |
142143 | `loot-core::manifest` | Grant audit trail: grantee, grantor pubkeys, timestamps |
143144 | `loot-identity` | ed25519 sign/verify, x25519 derive, ECIES seal/unseal, push envelope |
144145 | `loot-net::mailbox` | Relay grant mailbox: pubkey-addressed, content-addressed loose blobs |
145146 | `loot-cli::workspace` | Ambient repo: identity, clock, persistence, idempotent snapshot |
146147
147148 ### ADRs (docs/adr/)
148149
149150 | # | Decision |
150151 | --- | --- |
151152 | 0001 | Per-content decrypt-then-merge convergence |
152153 | 0002 | Encrypted DAG as the canonical foundation (bake-off winner) |
153154 | 0003 | Sealed content module + keyring custody (restricted keys never travel) |
154155 | 0004 | Drop plaintext dedup equality oracle |
155156 | 0005 | CLI slice, persistence, .lootattributes |
156157 | 0006 | JJ-style workspace auto-snapshot |
157158 | 0007 | Embargo escrow module |
158159 | 0008 | Grant log and targeted key bundles |
159160 | 0009 | Two-level revocation |
160161 | 0010 | Forward-maroon implementation |
161162 | 0011 | Relay stow append-only |
162163 | 0012 | Per-object loose storage |
163164 | 0013 | Named remotes and grant bundle delivery |
164165 | 0014 | Identity keypairs: ed25519 OpenSSH, signed push envelopes |
165166 | 0015 | Grant authentication and trust (grantor signs, peer-registry gate) |
166167 | 0016 | Identity portability: export/import with passphrase wrapping |
167168 | 0017 | RepoStore: one home for the `.loot/` layout |
168169 | 0018 | Signed changes: author in id + validity enforcement |
169170 | 0019 | Format versioning + compatibility gate (newer reads older) |
170171 | 0020 | Compress public content (Zstd); format major → 2 |
171172 | 0021 | Object-level "wants" negotiation on push/pull |
172173 | 0022 | Concurrent-agent model: docks, harbor, optimistic convergence |
173174 | 0023 | Agent-facing machine output: porcelain-first, reconciliation verbs |
174175 | 0024 | Resumable transfer via batched, negotiated sync |
175176
176177 See [CONTEXT.md](CONTEXT.md) for the full domain glossary.
177178
178179 ## Build & test
179180
180181 ```bash
181182 cargo build
182183 cargo test # ~25s — includes HTTP relay integration tests
183184 cargo test -p loot-core # fast, no I/O, 67 tests
184185 ```
185186
186187 ## Command reference
187188
188189 ```text
189190 loot init [--identity <name>] initialize a repo (identity from global config if omitted)
190191 loot clone <url> <dir> clone a relay or forge repo into <dir>; ends with a materialized working tree
191192 loot config [--local] set <key> <val> set a config value; bare writes the global file (~/.config/loot/config), --local writes this repo's (.loot/config.local, never tracked)
192193 loot config --get <key> print one effective value (local over global); exits non-zero when unset
193194 loot status [-m <message>] snapshot the working tree into the working change (idempotent)
194195 loot describe -m <message> name the working change
195196 loot new finalize the working change; start a fresh one
196197 loot surface materialize what the current identity may see
197198 loot lane new [--name <n>] spawn a sealed lane (isolated tree + tip) over the shared store
198199 loot lanes list lanes with their tip, in-flight PR, and status
199200 loot log show change history with visibility hints
200201 loot gc [--dry-run] prune loose objects no change references
201202 loot verify [--accept-loss] integrity-check the object store (exits 1 on corrupt/missing; --accept-loss records unrecoverable losses)
202203 loot bundle <file> write a sync bundle (ciphertext, no keys)
203204 loot apply <file> merge a peer's bundle (idempotent)
205+loot apply-patch <file> [--check] apply a TEXT patch `loot format-patch` wrote — strict, with an ADR 0044 three-way fallback that stops on conflict like `loot apply` does; a path this identity cannot open refuses the whole run, and CRLF on either side is refused (ADR 0082). --check reports applicability and writes nothing
204206 loot grant <path> <identity> <file> write a targeted grant bundle (file delivery)
205207 loot grant --relay <remote> <path> <id> seal and deliver a grant via relay mailbox
206208 loot grants [<url>] peek pending grant count (no download)
207209 loot pull-grants [<url>] fetch, verify, and apply sealed grants from relay
208210 loot maroon [--hard] <path> <identity> cut off <identity> from future access
209211 loot migrate <path> <vis-spec> change a path's visibility
210212 loot manifest show the grant audit trail
211213 loot conflicts list paths needing resolution
212214 loot resolve <path> <file> resolve a conflict
213215 loot remote add <name> <url> register a relay URL
214216 loot push [<url>] publish changes to a relay
215217 loot pull [<url>] fetch and merge changes from a relay
216218 loot serve [--addr <host:port>] run a relay
217219 loot keygen generate an identity keypair
218220 loot whoami show identity and public key
219221 loot id export <file> export keypair, passphrase-encrypted
220222 loot id import <file> import keypair from passphrase-encrypted file
221223 loot peer add <name> <pubkey> register a peer's public key
222224 loot peer list list known peers
223225 ```
224226