# Keys the way git does them, PKI access to a vault, and copy-on-write: what is already published, and what is not, cli.sgit.ai

> Before writing two articles, a map of what is already published on keys the way git does them, PKI access to a vault and copy-on-write: seven questions from the memo, where each stands, the facts the articles must not get wrong, and seven code questions for the thread.

*Source: <https://cli.sgit.ai/research/keys-pki-copy-on-write-coverage-map.html> · generated from the same content as the page, so the two cannot drift. Published from <https://github.com/SGit-AI/SGit-AI__CLI/blob/182cfc16578fc599b151edb62e221722946c4a22/team/humans/dinis_cruz/claude-code-web/10/16/research__keys-pki-cow__coverage-map.md>.*

---
# Keys the way git does them, PKI access to a vault, and copy-on-write: what is already published, and what is not

**Date:** 16 Oct 2026. **For:** the agent that manages sgit; input to one or two newsroom articles.
**No code changes:** this is research and mapping. Anything that would need code is listed in §5 as a
question for the thread. **Sources read:** every newsroom article on keys and identity (11), the
sgit.ai pages on PKI, credentials, key management, messaging, authentication and history purging
(14), the threat model and the CLI at `dev` 37218af, RuVector's and RVF's READMEs, ruv.sgit.ai.

## 1. The questions in the memo, and where each stands

| # | Question (from the memo) | Already published? | Where | Gap |
|---|---|---|---|---|
| 1 | Native public/private keys for a vault (read key pair, write key pair) | **Yes, as a design (RFC)** | newsroom *RFC 0001: two ways to add public-key cryptography to sgit* (9 Oct): Design 1, native PKI vault mode, two key pairs neither derivable from the other, data key sealed to the read public key, heads signed by the write private key, a write-only depositor; Design 2, sealed files; a shared first step, a vault signing key on today's vaults. Effort estimates and 11 reviewer questions. | Not built. The memo's "public key as read key, private key as write key" is the exact construction the RFC corrects ("whatever decrypts must be secret"): two pairs, not one. |
| 2 | The git + SSH + GitHub model: authenticate once, then anything goes; no per-commit check unless signed; local copies are unrestricted | **Partly.** The server-side half is documented (`api/authentication`: six capability headers, SHA-256 hash comparison, reads open by default; `docs/credentials`: what a read key can and cannot do). The git comparison is not written anywhere. | sgit.ai `api/authentication`, `docs/credentials`, `docs/sgit-for-git-users` (no auth row) | **No article maps git's SSH/HTTPS authorisation onto sgit's write key, read key, access token and signatures.** That is the article to write (§2 of the draft). |
| 3 | "The read key locally does not prevent changes", like a git clone | **Yes, as a threat-model fact, not as prose.** A read-only clone holds the read key on disk (TM-R15, KI-SEC-03); it cannot push (the server wants the write key hash); the host cannot tell a reader from a writer by the bytes (RFC 0001 "The honest problem"). | threat model TM-R15, TM-R12; RFC 0001 | Same as 2: the comparison is the missing piece, and it is a good one, because the answer is "exactly like git, and here is what git adds on the server that sgit adds with signatures". |
| 4 | A vault's members get in with their own key pair: a file per member, sealed to their public key, holding the read (or write) key | **Yes, three times, in three shapes.** (a) RFC 0001 Design 1 "Rotation": an owner key signs a **writer set**; removing a reader is a new read pair and data key "sealed to the remaining members". (b) *The passkey opens the vault* (11 Oct): a **door per path**, one file holding the master's key encrypted under the door's read key, "retired by deleting its one file"; built and tested against a real vault server. (c) *secrets.sgit.ai on public keys* (14 Oct), way 8: "A team. One inbox per member… with revocation by rotating and re-sharing"; and "The private key lives in a vault". Also `docs/pki` "Where public keys live in a vault": `bare/keys/key-rnd-imm-*`. | RFC 0001; passkey article §"A lost device", "Moving keys between masters"; secrets article; `docs/pki` | The pieces are published separately. **Nobody has written the membership pattern as one thing**: a members folder (or branch), join by adding a file, leave by deleting it, and the honest limit. |
| 5 | Revocation: delete the member's file; but history keeps it | **Yes, the problem is stated, and the current rule is "rotate".** Passkey article: "The catch is history. Vaults keep their versions, so an entry deleted from A is still in A's past for anyone who can open A. The rule: keys that move for security reasons get rotated." `docs/key-management`: "Rotation is not retroactive." TM-R12: "Read access cannot be revoked: a read key reads all history forever." `case-studies/purging-history`: how a git history rewrite works and why "rotate the secret, then rewrite". | passkey article; key-management; credentials; TM-R12; purging-history | **The feature the memo asks for, files that exist only at the current version, is not designed anywhere.** §3 of the draft lays out the three ways to get it and which one is already a primitive in the code. |
| 6 | The key never stored locally, only in memory for the run | **Partly.** `sgit clone -` reads the key from stdin "keeps it out of argv and history" (0.21.0); the passkey design computes the door key from PRF bytes and stores nothing; *Handing secrets to agents*: the session makes its key pair, the secret is piped "into the tool, not the chat". But every clone still writes `.sg_vault/local/vault_key` (0600) and a read-only clone writes the read key to `clone_mode.json` (TM-R15). | CHANGELOG 0.21.0; passkey article; handing-secrets; TM-R15 | **A "no key on disk" clone mode** (derive at start, hold in memory, re-derive from the member's private key next time) is not designed. It falls out of §3's pattern. |
| 7 | Copy-on-write, as asked for by rUv's team | **No.** The only mention anywhere is a secure-unlink debrief noting that on btrfs/APFS/ZFS an overwrite does not erase. ruv.sgit.ai records that RuVector "already has snapshots and copy-on-write branching (rvf derive)" and that POC2 "adds an sgit vault around the memory files", without saying what copy-on-write would mean for the vault itself. | ruv.sgit.ai `ruv-stack`; RuVector README ("Copy on write branches: isolated memory experiments without full copies"); RVF README (COW_MAP, lineage) | **A whole article**: what copy-on-write is, how sgit's object model already is one, what RVF's version adds, and where sgit would need a write-ahead lane and compaction to keep up. §4 and the second draft. |

## 2. What the code does today (the facts the articles must not get wrong)

- **Three credentials, one derivation.** `passphrase:vault_id` → PBKDF2 (600k) → read key (encrypts everything, AES-256-GCM) and write key (a bearer the server compares as a SHA-256 hash in `x-sgraph-vault-write-key`); a separate account token `x-sgraph-access-token`. Reads on the shared host need no credential; the ciphertext is the protection (`api/authentication`).
- **The server authorises a write by a shared secret, not by who you are.** Every writer presents the same write-key hash. The server cannot tell writers apart; nor can a reader tell whether a head was written by a writer or by the host (RFC 0001).
- **Commits are signed already.** Each clone has an ECDSA P-256 branch key pair (`Vault__Key_Manager`, raw r||s for Web Crypto); the public half is stored as `bare/keys/key-rnd-imm-*`; commits carry `author_key_id`; `sgit check verify` classifies every commit (verified / bad / unsigned / unknown key); the vault feature `signatures-required` makes `pull` refuse the first unsigned incoming commit, and `signed-since-<hex>` records where the policy began (0.19.0). `sgit pki` separately makes RSA-4096 encryption pairs and ECDSA signing pairs, encrypts to a recipient (RSA-OAEP wrapping AES-256-GCM), signs and verifies files; no revocation, no expiry, no directory (`docs/pki`).
- **The named branch only moves forward** (`last_remote_head`, rewind detection, `--accept-rewind`), and the branch index is a shared document merged by every client under compare-and-swap (0.19.0).
- **Mutable objects exist and are a first-class pattern.** Refs (`ref-pid-muw-*`), the branch index, the cache layer's value and pointer objects (`bare/cache/`, written directly to a path-derived id, random IV, "derived, never authoritative", D10), and the door file in the passkey design. These are the objects that have no history by construction.
- **Deletion exists.** `DELETE` of one object with the write key; `destroy` of a vault with a tombstone (`api/vault-objects`); `sgit vault delete-on-remote`; `sgit vault rekey` (wipe, init, commit under a new key: a new vault); `sgit vault move`.
- **History cannot be rewritten in place.** `history reset` + `push --force` rewinds a branch, but the old objects stay on the host until deleted, and every reader keeps what it fetched (TM-R12). The purge case study is about git, and its rule applies here: rotate first.

## 3. The membership pattern, assembled from what exists (for the first article)

What the memo describes is RFC 0001's writer set plus the passkey article's door, applied to a vault's members. In one place:

- A member is a public key (the `sgit pki` export, or an SSH key: Ed25519 SSH keys sign and X25519-convert for sealing; RSA SSH keys seal with RSA-OAEP as `pki encrypt` does today).
- The vault keeps, per member, one small object holding the data key (today's read key; under Design 1 the sealed data key) **encrypted to that member's public key**, at a location derived from the member's fingerprint. Opening the vault = fetch that one object, unwrap it with the private key, hold the data key in memory, never write it to disk.
- Join = a writer adds the member's object (it needs only the public key). Leave = delete it. Nothing else changes for the other members.
- The honest limit, stated by every existing page: deletion stops the next open, it does not undo a copy already made (TM-R12, `docs/credentials`). A member removed for cause means a rotation of the data key and re-sealing to the rest, which is RFC 0001's "removing a reader" step, and which `rekey` approximates today at the cost of a new vault.
- For writing, Design 1's write key pair: the server verifies a signature from a key in the writer set instead of comparing a shared hash. That is the "GitHub-equivalent": the SSH key authorises the push, and the commit signature says who wrote it.

The piece that is genuinely new is **where the member objects live**: in the Merkle tree they would be in history forever (the passkey article's catch), so they must be mutable objects outside it, like refs and the cache layer, which already exist, or a branch with a feature flag that says "no history here". §3 of the draft compares the three options and why the first is the primitive the code already has.

## 4. Copy-on-write, in one paragraph each (for the second article)

- **What it is.** Share until you write; copy only the part you change. Filesystems (ZFS, btrfs, APFS) use it for snapshots; virtual machines for disk images; the kernel for `fork()`; git for its object store (a commit is a new tree that shares every unchanged subtree by hash). The cost moves from the copy to the first write, and the gain is that a snapshot or a branch is free.
- **What RVF does.** A `.rvf` child file "shares all parent data; only changed vectors are copied: a 1M-vector parent with 100 edits produces a ~2.5 MB child instead of a 512 MB copy" (`COW_MAP` segment, cluster granularity, lineage recorded parent to child with hashes, a hash-linked witness chain, append-only so a crash never corrupts). That is a file format doing what a filesystem does, for one memory artefact. `rvf derive` is the verb.
- **What sgit already is.** A branch is a ref to a commit: creating one copies nothing. A commit stores only the new blobs and the rebuilt spine; every unchanged folder keeps its tree id. A scoped clone takes the spine and its folder. So sgit is copy-on-write at the object level with Merkle sharing, which is git's design, and the newsroom's *Agent workflows on ephemeral vaults* measured the consequence ("scoped clones that barely grow with the vault").
- **What it is not.** Two things RVF and the filesystems have that sgit does not: copy-on-write **inside a file** (a changed byte in a 100 MB file is a new 100 MB object in sgit; RVF copies the changed cluster), and a **cheap derived vault** (a vault cannot share objects with another vault, because they are encrypted under different keys; a fork is a branch, or a re-encryption). And a write path that is one commit per write (`sgit write`: a commit, a spine rebuild, a push, 0.5 to 1.5 s against a local server in the ruv.sgit ledger), which is fine for a hundred writes an hour and not for a hundred a second.
- **The memo's proposal, and why it is the right one.** Writes go to an append lane first (the newsroom measured 205 entries a second with none lost; caps of 1,000 waiting entries and 5 MB each), and a normaliser folds them into commits later. That is a write-ahead log with compaction, the shape of every LSM store, of RuVector's own `ruvector-lsm-ann` ("mutable L0 memory plus background L1 and L2 compaction") and of Scion's changeset log. The second draft sets out the semantics (what a reader sees between compactions, who signs the folded commit, de-duplication by hash) and the numbers.

## 5. Questions for the thread (each would be a code change; none is made here)

1. **Member objects outside the tree.** Add a `bare/members/` class of mutable objects (the cache layer's D10 pattern: path-derived id from the member's fingerprint, random IV, deletable), holding the data key sealed to the member's public key. Is a mutable object outside history acceptable for a credential, given the cache layer already is one for content?
2. **Or a `no-history` branch feature.** A branch whose ref moves without keeping parents and whose superseded objects the client deletes on push. Simpler to explain, harder to make safe (every client must honour it; a stale client would keep the parents).
3. **A no-key-on-disk open.** `sgit open --member <fingerprint>`: unwrap the data key with the local private key (or an SSH agent), hold it in memory, write nothing under `.sg_vault/local/`. The read-only clone's `clone_mode.json` (TM-R15) is the thing this replaces.
4. **Signed writes (RFC 0001 Design 1, first three phases).** Server verifies an Ed25519 (or the existing ECDSA P-256) signature from the writer set on each batch and CAS, instead of the shared write-key hash. Server work with the SG/Send team.
5. **SSH keys as sgit keys.** Accept an `~/.ssh/id_ed25519` (sign; X25519-convert to seal) or `id_rsa` (RSA-OAEP, as `pki encrypt` already does) as a member identity, and an SSH agent for signing. Pure client work; the RFC's "an ssh key" box in Design 2's provider list.
6. **Copy-on-write write path.** `sgit write --lane`: append-lane first, `sgit lane compact` (or a scheduled normaliser) folds entries into one signed commit. Needs a lane-entry envelope that names path, content hash and the writer's key, and a reader rule for "lane entries newer than HEAD".
7. **In-file copy-on-write for large files.** Content-defined chunking of blobs over a threshold, so a changed region is a new chunk, not a new file; keeps every chunk a `-imm-` object. The trade is against the whole-object simplicity the Scion response praised; probably a later step, and only for the large-file case.

---

*[Site index for agents](../llms.txt) · [HTML version](https://cli.sgit.ai/research/keys-pki-copy-on-write-coverage-map.html)*
