docs: proof runbook → clean CLI + sweep #126 markdown corruption #127
3 changed files with 72 additions and 55 deletions
|
|
@ -3,10 +3,11 @@
|
|||
## Project Identity
|
||||
|
||||
Colibri is the Clawdie control plane core — a small, cross-platform (FreeBSD 15
|
||||
+ Linux) Rust daemon. Developed and validated from an operator USB environment;
|
||||
deploys as the **Clawdie service** on bare metal. It unifies a coordination
|
||||
model (agents-as-teammates, task board, team skills) with a cache-first cost
|
||||
discipline (byte-stable prompt prefixes, cache-hit metering).
|
||||
|
||||
- Linux) Rust daemon. Developed and validated from an operator USB environment;
|
||||
deploys as the **Clawdie service** on bare metal. It unifies a coordination
|
||||
model (agents-as-teammates, task board, team skills) with a cache-first cost
|
||||
discipline (byte-stable prompt prefixes, cache-hit metering).
|
||||
|
||||
**License:** MIT (matches `layered-soul`). **Version:** 0.11.0, unified with `clawdie-iso`.
|
||||
|
||||
|
|
|
|||
|
|
@ -70,14 +70,13 @@ video → local transcript → topic extraction → how-to/runbook
|
|||
|
||||
## Ownership
|
||||
|
||||
| Layer | Role | Writes | Reads |
|
||||
| ---------------- | ---------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------ |
|
||||
| Clawdie-AI | Source of truth | Skill artifacts via PR | N/A |
|
||||
| `colibri-skills` | Runtime consumer | Writes only to the runtime store; source repo remains read-only for the skills |
|
||||
| consumer. | Indexed skill structs from committed artifacts |
|
||||
| Agents | Authors/reviewers | Candidate skill artifact PRs | Skill content for task routing |
|
||||
| `system_brain` | Agent/user memory | Personal/user/agent context | Not canonical skill docs |
|
||||
| `system_ops` | Runtime state | Live task/service state | Not skills |
|
||||
| Layer | Role | Writes | Reads |
|
||||
| ---------------- | ----------------- | ------------------------------------------------------------------------------ | ---------------------------------------------- |
|
||||
| Clawdie-AI | Source of truth | Skill artifacts via PR | N/A |
|
||||
| `colibri-skills` | Runtime consumer | Writes only to the runtime store; source repo remains read-only for the skills | Indexed skill structs from committed artifacts |
|
||||
| Agents | Authors/reviewers | Candidate skill artifact PRs | Skill content for task routing |
|
||||
| `system_brain` | Agent/user memory | Personal/user/agent context | Not canonical skill docs |
|
||||
| `system_ops` | Runtime state | Live task/service state | Not skills |
|
||||
|
||||
## What `colibri-skills` does
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
# Vault Provision — First-Proof Runbook (osa)
|
||||
|
||||
**Status.** The spawn → vault-provision → `.env` chain is **wired and unit-tested in
|
||||
code** (`colibri-daemon` spawn hook → `colibri-vault::provision`), but it is **not yet
|
||||
drivable from the CLI**. Two gaps make the live proof a manual procedure today:
|
||||
**Status.** The spawn → vault-provision → `.env` chain is **wired, hardened, and
|
||||
drivable from the CLI**. The three gaps that previously forced a manual path are
|
||||
all closed:
|
||||
|
||||
- **#101** — no `register-tenant` socket command / CLI verb; tenants are only insertable
|
||||
via raw SQLite.
|
||||
- **#102** — `colibri spawn-agent`/`spawn-local` hardcode `jail: None`; a jailed spawn
|
||||
(the only kind that triggers provisioning) must be sent as raw socket JSON.
|
||||
- **#101** ✅ `colibri register-tenant` + `list-tenants` socket command / CLI verb landed
|
||||
(PR #107).
|
||||
- **#102** ✅ `colibri spawn-agent`/`spawn-local` accept `--jail-name` / `--jail-root`
|
||||
flags (PR #107).
|
||||
- **#92** ✅ provision-target containment guard landed — `colibri-vault::provision`
|
||||
canonicalizes the target and asserts it is strictly under the allowed jail-root base
|
||||
before any write (PR #119).
|
||||
|
||||
This runbook proves the chain using that interim path. When #101/#102 land, steps 3–4
|
||||
collapse into `colibri register-tenant …` + `colibri spawn-agent … --jail-name …`.
|
||||
This runbook proves the chain live on osa using the **clean CLI** — no raw SQLite, no
|
||||
`nc -U` JSON. It validates the production deployment pattern (Bastille jail + provisioned
|
||||
`.env`); see `AGENTS.md` Project Identity — the bare-metal Clawdie service runs exactly
|
||||
this model.
|
||||
|
||||
**First-proof policy** (see `layered-soul/docs/HIVE-ONBOARDING.md`): use a **scratch jail +
|
||||
throwaway test collection only** — no real tenant data until the path hardening (#92) lands.
|
||||
throwaway test collection only** — no real tenant data.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -23,9 +28,12 @@ throwaway test collection only** — no real tenant data until the path hardenin
|
|||
`store.get_tenant(jail_name)`; **if no tenant row matches, it no-ops.**
|
||||
- It then requires `tenant.jail_root_path == spawned root` (trailing-slash-normalized) —
|
||||
a mismatch **refuses** provisioning.
|
||||
- It calls `colibri_vault::provision(&tenant.tenant_id, jail_root_path)` — so the
|
||||
**Vaultwarden collection must be named exactly `tenant_id`**, and `tenant_id` =
|
||||
jail name = collection name (the 1:1:1 contract).
|
||||
- `colibri-vault::provision` then **canonicalizes** the target and asserts it is strictly
|
||||
under the allowed jail-root base (`COLIBRI_JAIL_ROOT_BASE`, defaults to
|
||||
`/usr/local/bastille/jails` on FreeBSD) — a traversal/symlink escape is refused with
|
||||
`TargetEscapesRoot` before any directory or file is created (#92/#119).
|
||||
- It calls the `bw` CLI to fetch items by **name** from the collection named
|
||||
`tenant_id`, so `tenant_id` = jail name = collection name (the 1:1:1 contract).
|
||||
- On success it writes `<jail_root>/.env` at `0600` and flips tenant status → `active`.
|
||||
|
||||
## Paths (FreeBSD daemon)
|
||||
|
|
@ -36,7 +44,7 @@ throwaway test collection only** — no real tenant data until the path hardenin
|
|||
|
||||
## Prerequisites
|
||||
|
||||
1. `colibri-daemon` running on osa.
|
||||
1. `colibri-daemon` running on osa (`colibri` ≥ 0.11.0 — has the CLI verbs + flags).
|
||||
2. `/usr/local/etc/colibri/provider.env` (mode 600) has `BW_SERVER` plus the three
|
||||
bootstrap secrets `BW_CLIENTID` / `BW_CLIENTSECRET` / `BW_PASSWORD` (PR #69), and the
|
||||
daemon has them in its environment (the rc.d loads provider.env).
|
||||
|
|
@ -63,40 +71,36 @@ In the web UI, create a **Collection named exactly `$T`**, and add one **Login**
|
|||
|
||||
The bootstrap account must have read access to that collection.
|
||||
|
||||
## Step 3 — register the tenant _(interim: manual SQLite — gap #101)_
|
||||
## Step 3 — register the tenant (CLI — #101)
|
||||
|
||||
```sh
|
||||
T=proof0
|
||||
DB=/var/db/colibri/colibri.sqlite
|
||||
sudo sqlite3 "$DB" "INSERT INTO tenants
|
||||
(tenant_id, jail_root_path, collection_id, status, created_at, updated_at)
|
||||
VALUES ('$T', '/usr/local/bastille/jails/$T/root', '$T', 'provisioned',
|
||||
datetime('now'), datetime('now'));"
|
||||
sudo colibri register-tenant "$T" "/usr/local/bastille/jails/$T/root" "$T"
|
||||
# expect JSON: {"tenant_id":"proof0","jail_root_path":"...","collection_id":"proof0","status":"provisioned",...}
|
||||
```
|
||||
|
||||
- `jail_root_path` **must exactly match** the spawned root (the hook compares them).
|
||||
- `collection_id` is `NOT NULL UNIQUE`, but the hook resolves the collection by
|
||||
`tenant_id` (name) — so set `collection_id = tenant_id` to satisfy the constraint
|
||||
(it's vestigial; see #88/#93).
|
||||
- SQLite runs in WAL, so this insert is safe while the daemon is up.
|
||||
- The collection is resolved by `tenant_id` (name) at provision time; pass `collection_id
|
||||
= tenant_id` to keep the 1:1:1 contract explicit.
|
||||
- Verify anytime without raw SQLite: `sudo colibri list-tenants`.
|
||||
|
||||
## Step 4 — trigger a jailed spawn _(interim: raw socket JSON — gap #102)_
|
||||
## Step 4 — trigger a jailed spawn (CLI — #102)
|
||||
|
||||
```sh
|
||||
T=proof0
|
||||
SOCK=/var/run/colibri/colibri.sock
|
||||
printf '%s\n' \
|
||||
'{"cmd":"spawn-agent","provider":"local","model":"/usr/local/bin/colibri-test-agent","local_args":["--session-id","'"$T"'-proof","--step-ms","10","--hold-secs","1"],"jail":{"name":"'"$T"'","root_path":"/usr/local/bastille/jails/'"$T"'/root"}}' \
|
||||
| sudo nc -U "$SOCK"
|
||||
sudo colibri spawn-agent local /usr/local/bin/colibri-test-agent \
|
||||
--jail-name "$T" \
|
||||
--jail-root "/usr/local/bastille/jails/$T/root" \
|
||||
--session-id "$T-proof"
|
||||
```
|
||||
|
||||
- `provider: "local"` uses the `colibri-test-agent` binary copied into the jail by
|
||||
`agent-jail-bootstrap.sh`, so the proof does not depend on provider API keys or a
|
||||
separate `COLIBRI_AGENT_BINARY` being present in the jail.
|
||||
- `jail.name` → `jexec` into the existing jail; `jail.root_path` → where the hook writes
|
||||
`.env`. The provision hook fires after the local test agent spawns successfully.
|
||||
- (Equivalent: send the same JSON line with the Python raw-socket helper used by the
|
||||
poller.)
|
||||
- `--jail-name` enters the existing jail (`jexec`); `--jail-root` is the host-visible
|
||||
root where the hook writes `.env`. **Both are required to trigger provisioning** — a
|
||||
spawn without `--jail-name`/`--jail-root` skips the provision hook entirely.
|
||||
- The provision hook fires after the local test agent spawns successfully.
|
||||
|
||||
## Step 5 — verify
|
||||
|
||||
|
|
@ -105,7 +109,7 @@ T=proof0; DB=/var/db/colibri/colibri.sqlite; R=/usr/local/bastille/jails/$T/root
|
|||
# daemon log shows: "provisioning tenant env from vault" then "vault provision complete"
|
||||
sudo stat -f '%Sp %N' "$R/.env" # expect -rw------- (0600)
|
||||
sudo grep -c '^FIRST_PROOF_KEY=' "$R/.env" # expect 1 (value not printed)
|
||||
sudo sqlite3 "$DB" "SELECT tenant_id,status FROM tenants WHERE tenant_id='$T';" # expect active
|
||||
sudo colibri list-tenants | grep proof0 | grep active # expect status=active
|
||||
```
|
||||
|
||||
Pass = `.env` at `0600`, key present, tenant `status=active`.
|
||||
|
|
@ -113,11 +117,12 @@ Pass = `.env` at `0600`, key present, tenant `status=active`.
|
|||
## Cleanup (scratch proof)
|
||||
|
||||
```sh
|
||||
T=proof0; DB=/var/db/colibri/colibri.sqlite
|
||||
sudo sqlite3 "$DB" "DELETE FROM tenants WHERE tenant_id='$T';"
|
||||
T=proof0
|
||||
sudo rm -f /usr/local/bastille/jails/$T/root/.env
|
||||
sudo bastille destroy "$T"
|
||||
# delete the test Collection + item in Vaultwarden
|
||||
# tenant row: list-tenants will stop showing it after bastille destroy; remove the
|
||||
# row from the SQLite store if you want it gone immediately (no CLI verb yet for delete).
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -125,12 +130,24 @@ sudo bastille destroy "$T"
|
|||
## Security notes
|
||||
|
||||
- Scratch jail + test collection only (first-proof policy) — no real tenant secrets.
|
||||
Bootstrap creds (`BW_*`) remain confined to the daemon's `provider.env`
|
||||
(0600); only the resolved `.env` enters the jail.
|
||||
- Bootstrap creds (`BW_*`) remain confined to the daemon's `provider.env` (0600); only
|
||||
the resolved `.env` enters the jail.
|
||||
- Provision target is containment-checked (#92/#119): canonicalized and asserted under
|
||||
the allowed jail-root base before any write.
|
||||
|
||||
## Follow-ups that retire the manual steps
|
||||
## What landed (closed)
|
||||
|
||||
- **#101** register-tenant socket command + CLI → replaces step 3.
|
||||
- **#102** `--jail` flags on `colibri spawn-agent` → replaces step 4.
|
||||
- **#92** path canonicalization/containment; **#100** crate `bw` hardening (server-match,
|
||||
serialize) — land before promoting beyond scratch.
|
||||
- **#101** register-tenant socket command + CLI → step 3 is now `colibri register-tenant`.
|
||||
- **#102** `--jail-name` / `--jail-root` on `colibri spawn-agent` → step 4 is now
|
||||
`colibri spawn-agent … --jail-name`.
|
||||
- **#92** path canonicalization/containment guard in `colibri-vault::provision`.
|
||||
- **#100** crate `bw` hardening (server-match fail-closed, serialize, note-key
|
||||
validation).
|
||||
|
||||
## Still open (not blockers for this proof)
|
||||
|
||||
- A `colibri delete-tenant` / `unregister-tenant` CLI verb — cleanup is manual today
|
||||
(see Step Cleanup).
|
||||
- The Forgejo Actions runner has been intermittently down; CI has not been gating merges
|
||||
reliably. Verify gates locally (`cargo fmt --check`, `cargo clippy --workspace
|
||||
--all-targets -- -D warnings`, `cargo test --workspace`) until it recovers.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue