# §86 — The workspace with a keychain

Each project carries a credential. That credential does not go in the JSON file, and this is the passage that exists to say so.

`projects.json` gets synced, backed up, copied into a bug report, and read by anything on the machine. Secrets go to the OS keychain, keyed per project, and are deleted when the project is forgotten — an orphan keychain entry is the kind of thing nobody notices until a security review.

One trap, and it is silent: **the macOS keychain hex-encodes a value containing a newline**, which corrupts it on read. `Keychain::put_json` writes single-line JSON and asserts it in debug builds for exactly this reason.

## What you are carrying

- `store::Keychain` — One service per app, one entry per key.
- `host` — The facade that opens the project and holds the cursor.
- `app` — Home ⇄ workspace, two state scopes.

## Start it

```bash
scripts/new.sh Acme ~/Dev/acme --template workspace
```

## What it looks like

`crates/store/src/lib.rs`

```rust
impl Store {
    /// A project's secret. Never projects.json — that file gets synced,
    /// backed up, and pasted into bug reports.
    pub fn secret(&self, project_id: &str) -> Option<String> {
        self.inner.keychain().get(&format!("project.{project_id}"))
    }

    pub fn save_secret(&self, project_id: &str, secret: &str) -> bool {
        self.inner.keychain().put(&format!("project.{project_id}"), secret)
    }

    /// Called when a project is forgotten. An orphan entry outlives the app.
    pub fn drop_secret(&self, project_id: &str) {
        self.inner.keychain().delete(&format!("project.{project_id}"));
    }
}
```

## Where now

- Sign it and ship it — turn to §101 (Appendix B)
- Hand it to an agent — turn to §102 (Appendix C)
- Walk it again from the start — turn to §1
