diff --git a/README.md b/README.md index 89a739df..dc6009f4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Run Codex, Claude Code, GitHub Copilot CLI, and future coding harnesses inside e Launch, observe, attach, and coordinate agent work through one neutral runtime substrate. [![MIT License](https://img.shields.io/badge/license-MIT-9A8ECD?style=flat-square)](LICENSE) -[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows%20x64-9A8ECD?style=flat-square)](#requirements) +[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows%20x64-9A8ECD?style=flat-square)](https://docs.opencoven.ai/docs/guide/platforms) [![npm](https://img.shields.io/badge/npm-%40opencoven%2Fcli-9A8ECD?style=flat-square)](https://www.npmjs.com/package/@opencoven/cli) [![Built with Rust](https://img.shields.io/badge/built%20with-Rust-9A8ECD?style=flat-square)](https://www.rust-lang.org/) @@ -30,159 +30,27 @@ Launch, observe, attach, and coordinate agent work through one neutral runtime s --- -## Table of Contents - -- [What is Coven?](#what-is-coven) -- [Why Coven?](#why-coven) -- [Features](#features) -- [Requirements](#requirements) -- [Install](#install) -- [Quick Start](#quick-start) -- [Commands Reference](#commands-reference) -- [Local API](#local-api) -- [Architecture](#architecture) -- [Repository Structure](#repository-structure) -- [Configuration](#configuration) -- [OpenCoven Integrations](#opencoven-integrations) -- [Documentation](#documentation) -- [FAQ](#faq) -- [Troubleshooting](#troubleshooting) -- [Contributing](#contributing) -- [Code of Conduct](#code-of-conduct) -- [Roadmap](#roadmap) -- [Security](#security) -- [License](#license) -- [Community & Support](#community--support) - ---- - ## What is Coven? Coven is the local harness substrate for the [OpenCoven](https://github.com/OpenCoven) ecosystem. It gives coding-agent CLIs like [Codex](https://github.com/openai/codex) and [Claude Code](https://docs.anthropic.com/en/docs/claude-code) a shared room where project work can happen visibly and safely. > **One project. Any harness. Visible work.** -Coven doesn't replace your coding agent, your UI, or other clients. It acts as a neutral runtime layer: - - **You choose the harness** — Codex, Claude Code, GitHub Copilot CLI, or future adapters. - **Coven owns the session** — project-scoped boundaries, PTY execution, event logging, SQLite persistence. - **Clients present the work** — CastCodes, the CLI/TUI, comux, or your own integration over the same-user local IPC API. -The Rust daemon is the authority boundary. All clients — including the CLI itself — are convenience layers. Security decisions flow inward to the daemon, never outward to clients. - ---- - -## Why Coven? - -| Without Coven | With Coven | -| --------------------------------------------------- | ----------------------------------------------------------- | -| Run `codex` directly; no persistent session history | Every run creates a session record with metadata and events | -| No project boundary enforcement | Agent is locked to an explicit project root; cannot escape | -| Lose track of agent work when the terminal closes | Sessions persist across daemon restarts via SQLite | -| Manually juggle multiple harness CLIs | One unified `coven run` entry point for all harnesses | -| No API for clients to consume agent sessions | Versioned `coven.daemon.v1` same-user local IPC API for all clients | -| No standard way to observe or replay past work | `coven sessions` browser with Rejoin, View Log, and Archive | - ---- - -## Features - -- **🏠 Project-root boundaries** — Every launch is tied to an explicit repository or project root. The daemon rejects working directories that escape the declared boundary. -- **🔌 Harness-neutral runtime** — bundled support stays focused on Codex, Claude Code, and GitHub Copilot CLI; trusted opt-in recipes cover Hermes and OpenCode, while Grok Build is an experimental opt-in recipe. -- **🖥️ Interactive session browser** — Live and completed work can be selected, rejoined, viewed, archived, or restored without memorizing IDs; eligible non-running, unadopted work can also be sacrificed. -- **📡 Attachable PTY sessions** — Live sessions can be replayed or followed from explicit CLI verbs. -- **🔌 Local daemon API** — CastCodes, comux, and the OpenClaw plugin coordinate through one versioned local IPC contract (`coven.daemon.v1`). -- **🗄️ SQLite-backed history** — Session metadata and event logs survive daemon restarts. -- **🦀 Rust authority layer** — Launch, cwd, input, kill, and path-sensitive requests are revalidated in Rust. Clients are never the trust boundary. -- **🔒 External OpenClaw bridge** — `@opencoven/coven` is an opt-in plugin; OpenClaw core does not include Coven code. -- **📦 @opencoven namespace** — CLI wrapper packages live under `@opencoven/*`; the user-facing command is always `coven`. -- **🩺 System diagnostics** — `coven pc` (macOS-first) surfaces CPU, memory, disk, and process health without launching a harness. - ---- - -## Requirements - -| Requirement | Notes | -| ---------------------------- | --------------------------------------------------------------------------- | -| **Rust stable toolchain** | Required only when building from source | -| **Git** | Required | -| **macOS arm64/x64, glibc-based Linux x64, or Windows x64** | Native npm packages are available for these targets | -| **Node.js 18+** | Required for the npm wrapper; `coven memory open` requires Node.js 24+ | -| **At least one harness CLI** | Codex, Claude Code, and/or GitHub Copilot CLI (see below) | - -### Installing harness CLIs - -Run `coven doctor` first — it reports local readiness and points missing -harnesses to `coven setup`. Doctor stays offline and does not verify provider -authentication. - -**Codex (OpenAI):** - -```bash -npm install -g @openai/codex -# or: brew install --cask codex -codex login -``` - -**Claude Code (Anthropic):** - -```bash -npm install -g @anthropic-ai/claude-code -claude auth login -``` - -**GitHub Copilot CLI (GitHub):** - -```bash -npm install -g @github/copilot -# or: brew install --cask copilot-cli -copilot login -``` - -The recommended guided path is `coven setup codex`, `coven setup claude`, or -`coven setup copilot`; `coven setup all` processes all three in order. Add -`--verify` for a separately consented provider turn, or use `--verify-only` -after an existing login. See the -[`coven setup` reference](docs/reference/cli-setup.md). - -After setup, run `coven doctor` again to confirm the harness is detected. If -Doctor still reports it missing, ensure the harness binary is on your `PATH`. +The Rust daemon is the authority boundary. All clients — including the CLI itself — are convenience layers. Security decisions flow inward to the daemon, never outward to clients. OpenClaw integrates only through the opt-in `@opencoven/coven` plugin in `packages/openclaw-coven`; OpenClaw core contains no Coven code. --- ## Install -Coven is available as an npm wrapper for the fastest install, or you can build from source. - -### npm (recommended) - -Install globally: - ```bash npm install -g @opencoven/cli coven doctor ``` -The memory dashboard is an opt-in companion on its own release train, so a CLI -install stays thin and never pulls the dashboard's application dependencies: - -```bash -npm install -g @opencoven/coven-memory-dashboard -coven memory open -``` - -`coven memory open` starts or reuses the installed local Coven daemon before -launching that companion. It does not require a checkout or running development -server from the `coven-memory` repository. Without the companion installed it -prints the install command above and exits; every other Coven command is -unaffected. - -The core npm wrapper supports Node.js 18 or newer. The memory dashboard -requires Node.js 24 or newer; on an older runtime, only `coven memory open` is -blocked and prints an upgrade instruction. - -**Available npm packages:** - | Package | Platform | | -------------------------- | ---------------------------------------------- | | `@opencoven/cli` | Universal wrapper — auto-selects your platform | @@ -190,41 +58,17 @@ blocked and prints an upgrade instruction. | `@opencoven/cli-macos-x64` | macOS Intel x64 | | `@opencoven/cli-linux-x64` | glibc-based Linux x64 (Alpine unsupported) | | `@opencoven/cli-windows` | Windows x64 | -| `@opencoven/coven-memory-dashboard` | Opt-in loopback memory dashboard companion (installed separately) | -### Build from source (recommended for contributors) +The memory dashboard is an opt-in companion installed separately with +`npm install -g @opencoven/coven-memory-dashboard` (Node.js 24+); see +[`docs/reference/cli-observe.md`](docs/reference/cli-observe.md). -```bash -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build --workspace -cargo run -p coven-cli -- doctor -``` - -> **Note:** Building from source requires Rust stable. See [Requirements](#requirements). +Install routes (npm, cargo, source), platform behavior, service managers, and +containers are documented at **https://docs.opencoven.ai/docs/guide/install**. --- -## Quick Start - -### Option A — Interactive UI (recommended for new users) - -```bash -cd /path/to/your/project -coven -# or explicitly: -coven chat -``` - -Bare `coven` opens the interactive Coven UI, powered by the **Coven engine**. -The first time you run it, `coven` offers to download and install the engine -automatically (or install it anytime with `coven engine install`); it's a -version-pinned, checksum-verified binary that `coven` manages for you — there's -no separate package to install. You can also pass a task directly — -`coven "fix the failing tests"` — and Coven will show a plan card and run it in -a recorded session. - -### Option B — Direct commands +## Quick start ```bash cd /path/to/your/project @@ -240,8 +84,6 @@ coven daemon start # 4. Launch a session coven run codex "fix the failing tests" -# or with Claude Code: -coven run claude "polish this UI" # 5. Browse and manage sessions coven sessions @@ -250,294 +92,9 @@ coven sessions coven daemon stop ``` -### Option C — OpenClaw rescue loop - -If OpenClaw breaks, Coven provides a predictable repair room: - -```bash -coven patch openclaw -``` - -Choose a repo, choose a harness, get a verified patch. - ---- - -## Commands Reference - -The canonical command reference lives at -[docs.opencoven.ai/docs/cli](https://docs.opencoven.ai/docs/cli). The core -verbs: - -| Command | Action | Details | -| --- | --- | --- | -| `coven` / `coven chat` | Open the interactive Coven UI (engine auto-installed on first run); `coven ""` plans and runs a free-text task | [Interactive UI](https://docs.opencoven.ai/docs/cli/interactive) | -| `coven doctor` | Detect supported harness CLIs and print install hints | [Doctor](https://docs.opencoven.ai/docs/cli/doctor) | -| `coven setup []` | Run provider-owned login and optional explicitly consented verification | [Setup](docs/reference/cli-setup.md) | -| `coven daemon start/status/restart/stop` | Manage the local daemon | [Daemon commands](https://docs.opencoven.ai/docs/cli/daemon) | -| `coven run ` | Launch a project-scoped harness session (`--cwd`, `--title`, `--model`, `--continue`, `--stream-json`, …) | [Run](https://docs.opencoven.ai/docs/cli/run) | -| `coven sessions` | Browse, search, and inspect sessions (`--plain`, `--json`, `--all`, `search`, `show`, `events`, `log`) | [Sessions](https://docs.opencoven.ai/docs/cli/sessions) | -| `coven attach ` | Replay/follow session output and forward input | [Sessions](https://docs.opencoven.ai/docs/cli/sessions#attach) | -| `coven archive` / `summon` / `sacrifice` | Session rituals (see below) | [Sessions](https://docs.opencoven.ai/docs/cli/sessions) | -| `coven kill` | Stop a live session on Unix-like hosts; Windows-capable integrations request `POST /api/v1/sessions/:id/kill` through daemon local IPC | [CLI reference](https://docs.opencoven.ai/docs/cli) | -| `coven adapter list/doctor/install` | Inspect harness adapters; opt into trusted adapter recipes (e.g. `coven adapter install grok`) | [Repository workflow](https://docs.opencoven.ai/docs/cli/repo-workflow) | -| `coven status` / `familiars` / `skills` / `research` / `calls` / `hub` | Read-only observability with `--json`, mirroring the daemon API routes | [Observability](https://docs.opencoven.ai/docs/cli/observe) | -| `coven memory` / `coven memory --json` / `coven memory open` | Preserve the memory list output or launch the private loopback dashboard | [Memory](https://docs.opencoven.ai/docs/memory-models) | -| `coven memory import` / `coven memory restore` | Preview, apply, verify, and logically restore one familiar's memory migration | [Memory](https://docs.opencoven.ai/docs/memory-models) | -| `coven wt` / `claim` / `hooks` | Parallel work protocol: worktrees, TTL-bounded claims, git hooks | [Repository workflow](https://docs.opencoven.ai/docs/cli/repo-workflow) | -| `coven pc` | macOS-first system diagnostics; write operations require `--confirm` | [PC diagnostics](https://docs.opencoven.ai/docs/cli/pc) | -| `coven patch openclaw` / `logs prune` / `vacuum` | OpenClaw rescue loop, log-retention pruning, store repair | [CLI reference](https://docs.opencoven.ai/docs/cli) | -| `coven completions ` | Generate shell completions (bash, zsh, fish, elvish, powershell) | [CLI reference](https://docs.opencoven.ai/docs/cli) | - -> Session-id arguments (`attach`, `summon`, `archive`, `sacrifice`, `kill`, and -> the `sessions show/events/log` subcommands) accept a unique prefix of the id -> (e.g. `coven attach 9099`), so you don't have to paste full UUIDs. - -> **Session rituals are intentionally explicit.** Archive is reversible and keeps the full event ledger. Summon brings an archived session back. Sacrifice is destructive, applies only to eligible non-running sessions without adopted/reserved evidence, and requires `--yes`. - -| Ritual | Reversible? | Works on | Description | -| ------------- | ----------- | -------------------- | ------------------------------------------------------------ | -| **Archive** | ✅ Yes | Non-running sessions | Hides from active list; all events preserved | -| **Summon** | N/A | Archived sessions | Restores to active list | -| **Sacrifice** | ❌ No | Eligible non-running, unadopted sessions | Permanently deletes session and all events; adopted/reserved evidence is retained; requires `--yes` | -| **Rejoin** | N/A | Live sessions | Reattaches to running session | - ---- - -## Local API - -The daemon exposes a versioned HTTP API over same-user local IPC. On Unix-like -hosts, this is `/coven.sock`; on Windows, it is an owner-only named -pipe selected by `COVEN_HOME`. Windows clients discover the fully qualified -pipe path as `state.daemon_ipc` from `coven config paths --json`; they must not -construct a pipe name from the Unix convention or use health/status transport -metadata as a named-pipe path. The current public contract is -`coven.daemon.v1` (prefix: `/api/v1`). - -The public API guide and interactive endpoint reference live at -[docs.opencoven.ai/docs/reference/api](https://docs.opencoven.ai/docs/reference/api). -[`docs/API-CONTRACT.md`](docs/API-CONTRACT.md) remains beside the code as the -normative versioned source contract: shapes, error codes, cursor pagination, -and compatibility rules. - -### Recommended client handshake - -All API clients should start with a health negotiation: - -```bash -# Unix-like example: health check via Unix socket -curl --unix-socket ~/.coven/coven.sock http://localhost/api/v1/health -``` - -**Before depending on any other endpoint:** - -1. Call `GET /api/v1/health` -2. Verify `apiVersion === "coven.daemon.v1"` and `capabilities.structuredErrors === true` -3. Check `capabilities.sessions === true` before using session endpoints and - `capabilities.events === true` before reading events -4. Check `capabilities.eventCursor === "sequence"` before using `afterSeq` pagination -5. Only then depend on the documented `v1` sessions/events shapes - -All API errors use a structured `{ "error": { "code", "message", "details" } }` envelope. Branch on `error.code`, never on `error.message`. - -Local dashboards use `GET /api/v1/memory`, `GET /api/v1/memory/overview`, -and `GET /api/v1/memory/:id`. The daemon resolves and validates memory files; -clients must not open the archival database, vector index, manifest, or memory -paths directly. - -`coven memory open` establishes local daemon readiness before delegating to the -installed `@opencoven/coven-memory-dashboard` executable. The npm wrapper -supplies only the installed Node executable and dashboard entrypoint; memory -data and daemon transport proofs are never passed through the environment. -The companion is not a dependency of the wrapper; install it globally with -`npm install -g @opencoven/coven-memory-dashboard`, which also puts -`coven-memory-dashboard` on `PATH` for direct native binary installs. The -dashboard requires Node.js 24 or newer; the rest of the npm-wrapped CLI remains -available on Node.js 18 or newer. - -Treat the local IPC API as the product contract. Clients may validate for better UX, but the Rust daemon remains the authority boundary. - ---- - -## Architecture - -### Runtime topology - -Coven is a local-first harness substrate. The Rust daemon is the authority boundary. All clients — including the CLI/TUI — are untrusted for enforcement purposes. - -``` -Developer - │ - ├── CastCodes workspace ─────────────────────┐ - ├── coven CLI / TUI ─────────────────────────┤ HTTP over local IPC - ├── comux (legacy/reference) ────────────────┤ local IPC API - └── @opencoven/coven (OpenClaw plugin) ───────┘ - │ - ┌───────────────▼──────────────────┐ - │ Coven Rust Daemon │ - │ │ - │ ┌───────────────────────────┐ │ - │ │ Authority boundary │ │ - │ │ • Canonicalize project root│ │ - │ │ • Validate cwd in root │ │ - │ │ • Allowlist harness id │ │ - │ │ • Validate session state │ │ - │ │ • Route action via policy │ │ - │ └────────────┬──────────────┘ │ - │ │ │ - │ ┌────────────▼─────────────┐ │ - │ │ Harness adapter router │ │ - │ └───────┬──────────────┬───┘ │ - │ │ │ │ - │ ┌───▼──┐ ┌───▼───┐ │ - │ │Codex │ │Claude │ │ - │ │ PTY │ │ PTY │ │ - │ └───┬──┘ └───┬───┘ │ - │ │ │ │ - │ ┌───────▼──────────────▼──────┐ │ - │ │ SQLite session ledger + │ │ - │ │ append-only event log │ │ - │ └──────────────────────────────┘ │ - └───────────────────────────────────┘ -``` - -### Authority boundary - -The Rust daemon validates every request before acting: - -1. `projectRoot` must be explicit — no fallback -2. `cwd` must canonicalize inside the declared project root -3. Harness ID must be allowlisted (`codex`, `claude`, `copilot`) -4. Session IDs must exist and be in the expected state -5. All harness commands are built with argv APIs — never `sh -c` - -Clients may improve UX by validating early, but they are never the enforcement boundary. A client cannot widen the project boundary, bypass the harness allowlist, or escape session state validation. - -### Session lifecycle - -``` -coven run codex "fix tests" - │ - ▼ -POST /api/v1/sessions { projectRoot, cwd, harness, prompt } - │ - ▼ -Daemon: canonicalize → validate → spawn or reject - │ - ▼ -Session record created in SQLite - │ - ▼ -Harness spawned in PTY → output events streamed to SQLite - │ - ▼ -coven sessions → Rejoin / View Log / Archive / Sacrifice -``` - -For full architecture diagrams (including Mermaid flow charts), see [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). - ---- - -## Repository Structure - -``` -coven/ -├── .github/ # GitHub Actions workflows, issue templates -├── assets/opencoven/ # Project assets (logos, icons for npm packages) -├── brand/ # OpenCoven brand system -│ ├── icons/ # Brand icon set (trident, agent-node, etc.) -│ ├── social/ # Social media assets (X, GitHub) -│ └── ui/ # CSS color tokens and typography scale -├── crates/ -│ ├── coven-cli/ # Main Rust binary — the `coven` command -│ └── coven-relay/ # Internal relay crate -├── docs/ # Source-adjacent contracts, maintainer docs, plans, and historical records -├── npm/ # npm wrapper package source for @opencoven/cli -├── packages/ -│ └── openclaw-coven/ # External OpenClaw bridge plugin (@opencoven/coven) -├── scripts/ -│ └── check-secrets.py # CI / pre-release secret scanner -├── skills/opencoven-design/ # Design skill files -├── web/ # Web surface files -├── Cargo.lock # Locked Rust dependency tree -├── Cargo.toml # Rust workspace manifest -├── CONTRIBUTING.md # Contribution guidelines -├── DESIGN.md # Full brand and design system reference -├── LICENSE # MIT license -├── README.md # This file -└── SECURITY.md # Security policy -``` - -**Key directories:** - -- **`crates/coven-cli`** — Everything that becomes the `coven` binary. This is where daemon, PTY adapter, session store, local IPC API, and CLI surface live in Rust. -- **`packages/openclaw-coven`** — The opt-in bridge between OpenClaw and Coven. Lives here (not in OpenClaw core) to keep the trust boundary clean. Published as `@opencoven/coven`. -- **`scripts/check-secrets.py`** — Required pre-release and pre-PR scan. Run it before pushing to avoid leaking credentials into git history. -- **`docs/`** — Source-adjacent contracts, maintainer/development guidance, - implementation plans, and historical records. Public user documentation is - canonical at [docs.opencoven.ai](https://docs.opencoven.ai/). - ---- - -## Configuration - -Coven works with zero configuration. State lives under `COVEN_HOME` (default `~/.coven`); privacy and retention knobs are optional. The deep detail lives in the docs: - -| Surface | What it controls | Reference | -| ------------------------------------ | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | -| `COVEN_HOME` | Root directory for daemon state: SQLite database, same-user local IPC, logs, encryption keys | [Daemon configuration](https://docs.opencoven.ai/docs/daemon/configuration) | -| Daemon environment and `privacy.toml` | Raw-artifact persistence (`COVEN_PERSIST_RAW_ARTIFACTS`), retention windows, redaction | [Daemon configuration](https://docs.opencoven.ai/docs/daemon/configuration) | -| `~/.config/coven/settings.json` | CLI settings under `covenCli.*`: repo registry, privacy keys, fuzzy paths | [`docs/SETTINGS.md`](docs/SETTINGS.md) | - -> **Tip:** If you run Coven in CI or need isolated environments, set `COVEN_HOME` to a unique path per environment. Coven will create the directory if it doesn't exist. - -Retention defaults (30 days for redacted event logs, 7 days for optional raw encrypted artifacts) and manual pruning via `coven logs prune` are covered in [`docs/reference/cli-logs.md`](docs/reference/cli-logs.md). - -For debugging local state without deleting an entire profile, use `coven reset` -to preview selected familiar, project, GitHub/Copilot, or runtime-adapter state, -plus secret, cache, session, or metadata state. Runtime -selectors cover Coven-local Claude, OpenClaw, Hermes, OpenCode, Grok Build, and -Gemini records only; mobile gateway state has its own selector. After -`coven daemon stop` and after other active Coven commands finish, Unix -`--apply` moves only the selected state into `COVEN_HOME/reset-backups/`. -Windows currently supports preview only. Reset never changes a provider CLI, -login, or account, and project reset never deletes a checkout. -Session reset keeps encrypted artifact records and their key together. See -[`docs/reference/cli-reset.md`](docs/reference/cli-reset.md). - -Never commit runtime state: `.coven/`, `*.sqlite*`, `*.db`, `*.sock`, `.env*`, and `*.key` are covered by `.gitignore`. Before submitting any PR, run the secret scanner (`python scripts/check-secrets.py`) — see [Security](#security). - ---- - -## OpenCoven Integrations - -Coven is the runtime layer. Other surfaces in the OpenCoven ecosystem sit above -it and connect through same-user local IPC: a Unix socket at -`COVEN_HOME/coven.sock` on Unix-like hosts or an owner-only named pipe on -Windows, whose fully qualified client path is `state.daemon_ipc` from -`coven config paths --json`. - -| Integration | Role | How it connects | -| -------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------- | -| **[CastCodes](https://github.com/OpenCoven/cast-codes)** | Primary public workspace; the local-first AI coding product built on Coven | HTTP over same-user local IPC | -| **comux** | Legacy terminal cockpit (useful reference; not the future public story) | HTTP over same-user local IPC | -| **OpenClaw** | External coding agent; integrates via opt-in plugin only | `@opencoven/coven` plugin → local IPC | - -> **Important:** OpenClaw core does not contain Coven code. The integration lives exclusively in `packages/openclaw-coven` and publishes as `@opencoven/coven`. This separation keeps the trust boundary clean — the plugin is treated as an untrusted socket client, and the Rust daemon revalidates every request it makes. - -### CastCodes - -CastCodes is the primary product users open: terminal/code workspace, visible agent lanes, review flows, and approval UX. It is the first-contact public story for Coven. - -The intended flow is: - -``` -User → CastCodes → coven run → Coven daemon → Harness PTY -Harness output → Coven event log → CastCodes session view -``` - -### comux (legacy reference) - -comux is a standalone terminal cockpit that proved the tmux-cockpit model for parallel agent work. Its useful primitives (worktree isolation, pane menus, agent launcher registry) are being folded into CastCodes-native concepts. comux is no longer the future-facing public surface. +Bare `coven` opens the interactive Coven UI instead — see +[Interactive UI](https://docs.opencoven.ai/docs/cli/interactive). The command +reference lives at [docs.opencoven.ai/docs/cli](https://docs.opencoven.ai/docs/cli). --- @@ -545,6 +102,15 @@ comux is a standalone terminal cockpit that proved the tmux-cockpit model for pa Public installation, CLI, daemon, harness, API, memory, and troubleshooting documentation is canonical at **[docs.opencoven.ai](https://docs.opencoven.ai/)**. +Start with: + +- [Getting started](https://docs.opencoven.ai/docs/guide/getting-started) +- [CLI reference](https://docs.opencoven.ai/docs/cli) +- [Daemon](https://docs.opencoven.ai/docs/daemon) +- [Harnesses](https://docs.opencoven.ai/docs/harnesses) +- [Local API](https://docs.opencoven.ai/docs/reference/api) +- [Memory](https://docs.opencoven.ai/docs/memory-models) +- [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) — or run `coven doctor` first This repository keeps only documentation that must evolve with the source: @@ -560,267 +126,24 @@ This repository keeps only documentation that must evolve with the source: --- -## FAQ - -**Q: What is Coven, exactly?** - -Coven is a local Rust daemon and CLI that supervises coding-agent CLI sessions -(like Codex or Claude Code) inside explicit project boundaries, records -everything to SQLite, and exposes it through a versioned HTTP API over -same-user local IPC: a Unix socket at `COVEN_HOME/coven.sock` on Unix-like -hosts or an owner-only named pipe on Windows. - -**Q: Does Coven replace Codex or Claude Code?** - -No. Coven wraps them. You still use the harness CLI for its AI capabilities — Coven adds project-scoped boundaries, session persistence, and a unified API on top. - -**Q: Does Coven require an internet connection or an account?** - -Core Coven operation and `coven doctor` are local. Your harness CLIs require -their own provider authentication and network access. Coven stores no provider -credentials; only an explicitly consented `coven setup --verify` or a harness -session launches a provider turn. - -**Q: Is Windows supported?** - -Yes. `@opencoven/cli-windows` ships a native Windows x64 binary, and the universal `@opencoven/cli` wrapper selects it automatically. Run `coven doctor` from the same PowerShell, Windows Terminal, or WSL2 environment where your harness CLI is installed. - -**Q: What is `coven pc`?** - -A macOS-first system diagnostics and relief tool built into the CLI. It shows CPU, memory, disk, and process health without launching a harness — useful when sessions feel slow or the daemon is sluggish to start. All read operations are side-effect-free. Write operations (kill, cache clear) require an explicit `--confirm` flag and cannot be bypassed. - -**Q: What does "Sacrifice" mean?** - -Sacrifice is Coven's intentionally explicit verb for permanently deleting an eligible non-running session and all its event history. It requires `--yes`. Sessions with adopted or historical reserved evidence are retained; O3 defines no retention/fence release. Archive + Summon are the reversible alternatives. - -**Q: What is `COVEN_HOME`?** - -The directory where Coven stores all local state: SQLite database, same-user -local IPC, logs, and encryption keys. On Unix-like hosts, IPC uses -`COVEN_HOME/coven.sock`; on Windows, it uses an owner-only named pipe. -Defaults to `~/.coven`. To isolate environments (e.g., in CI), set -`COVEN_HOME` to a separate path for each environment. - -**Q: Is CastCodes the same as Coven?** - -No. CastCodes is a separate product — the local-first AI coding workspace and primary public-facing product that runs on top of Coven. Coven is the runtime substrate. CastCodes is the workspace you open. - -**Q: What is the relationship with OpenClaw?** - -OpenClaw is an external coding agent that can optionally integrate with Coven through the `@opencoven/coven` plugin package. OpenClaw core contains no Coven code. The integration is strictly opt-in and requires installing the plugin separately. - -**Q: Can I build my own client on top of Coven?** - -Yes. The daemon exposes a stable `coven.daemon.v1` HTTP API over same-user -local IPC: a Unix socket at `COVEN_HOME/coven.sock` on Unix-like hosts or an -owner-only named pipe on Windows. Windows clients discover the fully qualified -pipe path as `state.daemon_ipc` from `coven config paths --json`. All clients -are untrusted for enforcement, but the API surface is stable and versioned. -See [`docs/API-CONTRACT.md`](docs/API-CONTRACT.md) and -[`docs/CLIENT-INTEGRATION.md`](docs/CLIENT-INTEGRATION.md). - -**Q: What if I want to add a new harness (like Aider or Gemini)?** - -See [`docs/HARNESS-ADAPTERS.md`](docs/HARNESS-ADAPTERS.md) for the adapter contract. The supported set is Codex, Claude Code, and GitHub Copilot CLI — new harnesses are planned for later milestones after adapter contracts are stable. - ---- - -## Troubleshooting - -The fastest first step for any broken setup: - -```bash -coven doctor -``` - -`coven doctor` checks store readiness, project detection, daemon status, and harness availability — and prints specific next steps for every failure branch. - -### Quick reference - -| Symptom | First step | -| ------------------------------------------- | -------------------------------------------------------------------------------------- | -| `coven: command not found` | Run `npm install -g @opencoven/cli`; verify binary is on `PATH` | -| `doctor` reports missing harness | Install and authenticate the harness CLI (see [Requirements](#requirements)) | -| Daemon won't start | Run `coven daemon restart`; check `$COVEN_HOME` ownership and permissions | -| Session browser shows a table, not a UI | Terminal isn't interactive; use `coven sessions --manage` to force the browser | -| `cwd` rejected at launch | The working directory resolves outside the project root; use a path inside it | -| Stale "running" sessions after daemon crash | Run `coven daemon restart` to mark dead sessions `orphaned`, then archive them; sacrifice is only for eligible rows without adopted/reserved evidence | -| Sessions feel slow / daemon sluggish | Run `coven pc status` to check system pressure; `coven pc top --n 10` for CPU culprits | -| `coven attach` won't accept input | The session is not live; attach replays logs for completed or archived sessions | -| Secret scan fails | Remove the secret from your working tree; rotate it if it entered git history | -| Local familiar/project/integration state is broken | Preview `coven reset --list-features`, then reset only the affected local category with `--apply` | -| API version mismatch | Update Coven to match the client's expected contract, or update the client | - -For the full diagnostic flowchart and detailed resolution steps, see -[Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting). - ---- - ## Contributing -> **Contribution Status — Updated July 2026** -> -> External Pull Requests are open. Please start from an issue for larger changes -> and include the readiness packet requested by the PR template. - -### First 10 minutes (source checkout) - -```bash -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build --workspace -cargo run -p coven-cli -- doctor -cargo test -p coven-cli --test smoke -- --nocapture -``` - -A healthy first pass: the workspace builds, `doctor` prints setup status, and the smoke test passes. The smoke test uses an isolated temporary `COVEN_HOME` and injects a fake `codex` binary into `PATH` — it does not require real harness credentials or a network connection. - -### Local development loop - -```bash -# Build -cargo build --workspace - -# Rust checks (required before any PR) -cargo fmt --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace --locked - -# Secret scanner (required before any PR) -python scripts/check-secrets.py +See [CONTRIBUTING.md](CONTRIBUTING.md) for the first-10-minutes checkout path, +the full local development loop, and the PR readiness packet. The short rules: -# Smoke test (required for daemon/session/attach/ritual changes) -cargo test -p coven-cli --test smoke -- --nocapture - -# Manual smoke run — use a throwaway project, not a real repository -cargo run -p coven-cli -- daemon start -cargo run -p coven-cli -- run codex "say hello from coven" -cargo run -p coven-cli -- sessions -cargo run -p coven-cli -- daemon stop -``` +- **Rust is the authority layer.** Launch, cwd validation, PTY lifecycle, session persistence, and IPC enforcement are Rust's responsibility; clients are never the trust boundary. +- **Keep harness support focused** on Codex, Claude Code, and GitHub Copilot CLI until adapter contracts are stable. +- **Run `python scripts/check-secrets.py` before every PR**, including docs-only changes, and never commit runtime state (`.coven/`, `*.sqlite*`, `*.sock`, `.env*`, `*.key`). -### CLI performance baselines - -Build the native binary first, then run the isolated benchmark fixture: +Performance baselines collect trend data without gating merges: ```bash cargo build -p coven-cli --locked node scripts/benchmark-cli.mjs --binary target/debug/coven --iterations 3 --output /tmp/coven-perf.json -cargo test -p coven-cli --bin coven tui::chat::events::tests::benchmark_schedule_metrics_emit_json --locked -- --ignored --nocapture -``` - -The runner uses disposable `COVEN_HOME` directories, a fixture-only fake Codex -executable, and the same-user local IPC API. It records command startup, cold daemon -start-to-health, daemon session-listing, event-tail, and harness-first-output -timings without reading real configuration, prompts, or session logs. Each cold -start sample gets a fresh home and a matching daemon stop. The ignored Rust test -prints deterministic TUI poll/draw counters. These outputs are trend data: CI -uploads them as artifacts and validates fixture construction, but does not fail -pull requests on wall-clock thresholds. Cave's managed-start contract retains -its 8-second hard deadline; benchmark p50/p95/p99 values do not replace that -product-level timeout. - -### Concurrent runtime baseline - -Run the complementary concurrent-session baseline before making a throughput, -storage, or cancellation optimization: - -```bash -cargo build -p coven-cli --locked node scripts/benchmark-chaos.mjs --binary target/debug/coven --output /tmp/coven-chaos.json ``` -The command always exercises 1, 8, and 32 concurrent deterministic harness -sessions, records launch-to-first-output percentiles, throughput, cancellation -to-terminal latency, SQLite file growth, writer connection/transaction deltas, -maximum sampled writer backlog, and sampled daemon RSS, and writes a redacted -JSON report. The writer queue values are periodic samples rather than -daemon-maintained high-water marks. Writer measurements come from the live -health contract; RSS is resolved by the exact daemon PID through -`coven pc top --json`, without retaining process names or command lines. Cave -owns the slow-WebSocket-consumer lane in #4317. - -Unsafe host-level faults use deterministic equivalents rather than filling a -real disk or killing an unrelated process: the report names the exact Rust -regressions for the free-disk watermark, real SQLite lock/retry behavior, and -persisted-session crash recovery. These coverage entries remain separate from -trend measurements, so a timing artifact cannot be mistaken for a passing -failure-path test. - -CI runs this step as `continue-on-error`, so it collects trend data without -gating merges — matching how the rest of this section describes these -baselines. The deterministic fixture tests (`benchmark-chaos.test.mjs`) still -gate. On timeout, the collector reports fixture execution count, session/event -state, and a bounded event-writer health snapshot. The event writer retries -only transient SQLite busy/locked commit failures before latching a permanent -failure; unrelated persistence errors remain fail-fast. - -### Architecture rules for contributors - -- **Rust is the authority layer.** Process launch, cwd/project-root validation, PTY lifecycle, session persistence, and local IPC request enforcement are all Rust's responsibility. TypeScript clients improve UX but are never the trust boundary. -- **All clients are untrusted for enforcement** — this includes comux and the OpenClaw plugin. -- **Keep harness support focused.** Supported harnesses are Codex, Claude Code, and GitHub Copilot CLI only until adapter contracts are stable. -- **OpenClaw separation.** Do not place Coven code in OpenClaw core. The integration belongs in `packages/openclaw-coven` as `@opencoven/coven`. -- **No future orchestration commands as user-facing** until they exist in the CLI and local IPC API. - -### Documentation rules - -- Use **OpenCoven** for the ecosystem and organization. Use **Coven** for the CLI and daemon product. -- The user-facing command is always `coven` — never `opencoven` or `@opencoven` in user-facing documentation. -- Use canonical community references: `discord.gg/opencoven` and `@OpenCvn`. -- Use placeholders in all examples: `/path/to/project`, `/Users/example`, `session-1`, `intent-1`. -- Run `python scripts/check-secrets.py` before submitting any PR, including docs-only changes. -- Update docs whenever command behavior, API behavior, or trust boundaries change. - -### Maintainer release checklist - -```bash -cargo fmt --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace --locked -python scripts/check-secrets.py -# For package releases: verify package contents with dry run, attach checksums for native binaries -``` - -See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development loop, release checklist, and documentation standards. - ---- - -## Code of Conduct - -OpenCoven is committed to building a welcoming, respectful community where people of all backgrounds and experience levels can contribute and learn. - -**We expect all contributors and community members to:** - -- Be respectful and kind in all interactions — issues, PRs, Discord, and X -- Focus criticism on ideas and code, not people -- Welcome newcomers and answer questions with patience -- Assume good faith before assuming bad - -**We do not tolerate:** - -- Harassment, discrimination, or abuse in any form -- Personal attacks or derogatory language -- Sustained or repeated disruptive behavior - -To report unacceptable behavior, contact the maintainers privately through GitHub or Discord. Reports will be handled with discretion. - ---- - -## Roadmap - -> **Last updated: May 2026.** See [`docs/ROADMAP.md`](docs/ROADMAP.md) for detailed milestone checklists with individual items. - -| Status | Milestone | Summary | -| --------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| ✅ **Shipped** | A: Local runtime foundation | `coven` CLI, Rust daemon, PTY sessions, SQLite ledger, versioned `coven.daemon.v1` API, Codex + Claude adapters, npm packages | -| 🔄 **Now** | B: CastCodes workspace | CastCodes as primary public workspace; Cast Agent + Coven integration direction | -| 🔄 **Now** | C: Community transparency | Public roadmap, Discord update cadence, public issue board | -| 📋 **Next** | D: Harness expansion | Generic command adapter from real usage, third harness proof, compatibility docs | -| 🔬 **Next/Lab** | E: Visible lane → verify → review | CastCodes-native agent lanes, live session display, verification gates, explicit PR/merge workflow | -| 🔭 **Later** | F: Multi-harness orchestration | Handoff protocol, capability routing, multi-instance coordination, audit dashboard (Phases 1–4) | - -The roadmap is written as a community-facing progress ledger, not an internal promise sheet. Items move when they are designed, implemented, tested, and released. Dates are avoided unless a release is already scheduled. +Both use disposable `COVEN_HOME` directories and a fixture-only fake harness; `scripts/benchmark-chaos.test.mjs` gates deterministically in CI. --- @@ -829,34 +152,21 @@ The roadmap is written as a community-facing progress ledger, not an internal pr Coven is pre-1.0 software. Treat it accordingly: - **Do not run untrusted harnesses or prompts in sensitive repositories.** Session logs capture harness output; if the harness dumps secrets, Coven logs them. -- **Do not commit runtime state.** `.coven/`, `*.sqlite`, `*.sock`, `.env*` files, and encryption keys should never enter source control. - **Do not paste secrets into prompts.** Event payloads are redacted before API display, but defense in depth starts with not having secrets in prompts. -**Reporting vulnerabilities:** Please use [GitHub Security Advisories](https://github.com/OpenCoven/coven/security/advisories) for this repository. If advisories are unavailable, contact the maintainer privately. Do not post exploit details in public issues. - -See [SECURITY.md](SECURITY.md) for vulnerability reporting and -[Safety](https://docs.opencoven.ai/docs/reference/safety) for the public trust -boundary and local access model. +**Reporting vulnerabilities:** Please use [GitHub Security Advisories](https://github.com/OpenCoven/coven/security/advisories) for this repository. See [SECURITY.md](SECURITY.md) for the policy and [Safety](https://docs.opencoven.ai/docs/reference/safety) for the public trust boundary. --- -## License +## Roadmap -MIT © Valentina Alexander and the OpenCoven contributors — see [LICENSE](LICENSE) for full terms. +The milestone ledger lives in [`docs/ROADMAP.md`](docs/ROADMAP.md); items move when they are designed, implemented, tested, and released. --- -## Community & Support - -| Channel | Link | -| ----------------------- | ---------------------------------------------------------- | -| 🌐 Website | [opencoven.ai](https://opencoven.ai/) | -| 📝 Feedback | [feedback.opencoven.ai](https://feedback.opencoven.ai/) | -| 💬 Discord | [discord.gg/opencoven](https://discord.gg/opencoven) | -| 🐦 X / Twitter | [@OpenCvn](https://x.com/OpenCvn) | -| 🐛 Issues & Bug Reports | [GitHub Issues](https://github.com/OpenCoven/coven/issues) | -| 📖 Documentation | [docs.opencoven.ai](https://docs.opencoven.ai/) | -| 🗺️ Public Roadmap | [docs/ROADMAP.md](docs/ROADMAP.md) | +## License + +MIT © Valentina Alexander and the OpenCoven contributors — see [LICENSE](LICENSE) for full terms. --- diff --git a/SECURITY.md b/SECURITY.md index 4ae7b701..874229bc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,114 +1,225 @@ # Security Policy -Coven is an early local-first harness substrate for project-scoped coding-agent sessions. -Please treat the repository as pre-1.0 software and avoid running untrusted harnesses or prompts in sensitive repositories. - -OpenClaw integration is externalized through the `@opencoven/coven` plugin. OpenClaw core is not part of Coven's trust root; the plugin should be treated as a local socket client, and the Rust daemon must continue validating launch paths, harness ids, input, and kill requests before acting. - -## Reporting vulnerabilities - -Please report suspected vulnerabilities privately through GitHub Security Advisories for this repository. -If advisories are unavailable, contact the maintainer privately and avoid posting exploit details in public issues. - -## Local data and credentials - -Coven should not require repository-stored secrets. Runtime state belongs outside source control: - -- `.coven/` -- `*.sqlite`, `*.sqlite3`, `*.db` -- `*.sock` -- `.env*` files -- private keys and certificates - -The CI secret guard scans both the current tree and git history for common token/key patterns without printing matched values. - -### Coven privacy guard - -As of 2026-07-26, Coven uses two explicit scanning tiers: - -1. `scripts/check-secrets.py` scans the current tree and full git history for - classic credentials, private keys, and high-entropy secret material. -2. `scripts/check-coven-privacy.py` fails closed on newly staged and - pull-request-changed files containing private session identifiers, messenger - IDs, absolute home paths, runtime-internal paths, phone numbers, or - invite/handoff URLs containing tokens. Managed hooks installed by - `coven hooks install` run this guard before commits, and CI is the - authoritative enforcement layer. - -The second tier intentionally applies to new changes while the repository's -legacy path examples are inventoried and converted to placeholders. This is a -documented baseline, not a claim that historical commits satisfy the newer -privacy rules. Rewriting public history requires explicit maintainer approval. +Coven is an early local-first harness substrate for project-scoped coding-agent +sessions: a small Rust authority layer that launches supported harness CLIs +inside explicit local project boundaries, plus TypeScript integration packages +around it. This is pre-1.0 software. This document is the single normative +security policy for the `OpenCoven/coven` repository. It separates what is +**enforced today**, what is a **residual risk**, and what remains a **design +goal**. + +> Scope note: this policy covers Coven the runtime/daemon/CLI and the code in +> this repository. Organization-wide OpenCoven reporting (protocol, memory +> substrate, other repositories) belongs in the +> [organization-level security policy](https://github.com/OpenCoven/.github/blob/main/SECURITY.md). +> The canonical public overviews live at +> [docs.opencoven.ai](https://docs.opencoven.ai/docs/reference/safety); this +> file stays beside the code as the source-adjacent contract. + +## 1. Supported surfaces and security status + +**Supported release family.** Security fixes land on the current minor release +line published in +[repository releases](https://github.com/OpenCoven/coven/releases) (v0.4.x as +of this update). Coven has no long-term-support commitment before 1.0; run the +latest release to pick up security fixes. + +**Security-supported surfaces.** + +- The Rust daemon authority boundary and its versioned local API, + `coven.daemon.v1`, over same-user local IPC. See the + [local API contract](docs/API-CONTRACT.md) and + [authentication and local access](docs/AUTH.md). +- The bundled CLI and daemon lifecycle surfaces that drive the same boundary + (see [README.md](README.md) and the + [safety model](docs/SAFETY-MODEL.md)). +- Local session state: the SQLite event store, default event/log redaction, and + artifact persistence defaults. See the + [session artifacts spec](specs/coven-session-artifacts/TECH.md) and the + [trust layer contract](specs/coven-trust-layer/PRODUCT.md). +- Repository content guards: the secret scan and the Coven privacy guard run in + CI (`Policy guard`) and in managed local hooks. + +**Experimental or disabled surfaces — not security-supported.** + +- **AgentFS NFS mount backend.** The `coven-afs` storage engine is shipped and + conformance-tested, but every mount backend sits behind the opt-in `mount` + cargo feature and remains a spike. Loopback access control and single-writer + SQLite remain open gates in + [`specs/coven-agent-fs/MOUNT-SPIKE.md`](specs/coven-agent-fs/MOUNT-SPIKE.md), + and the mount surface does not leave experimental status until the + dedicated end-to-end certification gate (#779) passes. Do not expose a + Coven AFS export beyond the local machine. +- **OpenClaw bridge plugin.** Disabled by default; it must be explicitly + selected as the ACP backend. OpenClaw core is not a Coven trust root, and + the plugin's client-side socket validation is defense in depth, not the + enforcement boundary. See [authentication and local access](docs/AUTH.md). +- **Remote and tunnel transports.** The daemon does not bind TCP by default + and has no remote authentication design yet. Only the documented remote + access paths are supported; do not proxy the raw local IPC endpoint into a + network or browser surface. A separate authenticated remote listener is + drafted but unshipped (#463). + +**Same-user trust is not sandboxing.** Coven's boundary assumes the operating +system separates users and that the person running `coven` controls the +machine. It distinguishes two different threats: + +- *Same-user local trust* — what Coven relies on: OS-enforced local IPC + permissions (a private Unix socket or owner-only named pipe) plus same-user + process locality. +- *Sandboxing against hostile local processes, prompts, or providers* — what + Coven does **not** provide. Harnesses run with your user's privileges. A + malicious prompt, harness output, or provider response can steer a harness + within those privileges; the daemon's checks validate requests against the + local API contract, they do not contain a running harness. Never run + untrusted harnesses or prompts in sensitive repositories. + +Coven makes no absolute containment claim (such as "cannot escape") for any +surface. Where a property is enforced, it is tied to the named contracts and +verification families in the next section. + +## 2. Enforced properties today + +Each property below is backed by a normative source-adjacent contract and a +verification family. This table is the whole list; anything documented only as +a draft spec or design goal is in +[Design goals vs guarantees](#5-design-goals-vs-guarantees). + +| Property | Normative contract | Verification | +|---|---|---| +| Rust-owned validation is authoritative over untrusted clients; every sensitive request is revalidated at the daemon and fails closed on unknown versions, action ids, harnesses, and session ids | [Safety model — trust boundary](docs/SAFETY-MODEL.md), [Authentication — Rust authority checks](docs/AUTH.md) | Rust workspace test suites (`cargo test --workspace`) run in CI on every PR | +| Capability advertisement never grants permission: `/api/v1/health` capabilities describe availability only, and clients must still pass every per-operation check | [API contract](docs/API-CONTRACT.md) (`Capabilities advertise availability and never grant permission`) | Health-negotiation contract tests (`crates/coven-client/tests/health.rs`) and daemon contract tests | +| Project, path, and session checks happen before effects: canonicalized `projectRoot`/`cwd`, allowlisted harness ids, live-session validation, and argv-only launch (never `sh -c`) | [Safety model — core rules](docs/SAFETY-MODEL.md), [API contract — error envelopes and fail-closed routes](docs/API-CONTRACT.md) | Rust workspace test suites, including daemon lifecycle and harness parity tests | +| Owner-protected local transport and peer negotiation: the daemon API travels only over same-user local IPC; the bundled Rust client discovers only the current user's private socket/pipe, binds health negotiation to a transport peer fingerprint, and never auto-replays a mutation | [Authentication and local access](docs/AUTH.md), [API contract — reusable Rust client](docs/API-CONTRACT.md) | Client transport and negotiation tests (`crates/coven-client/tests/health.rs`), Windows daemon lifecycle tests | +| Event/log redaction and sensitive-artifact defaults: event payloads are redacted before they are stored or returned by the API; raw sensitive artifact persistence is opt-in, off by default, and encrypted at rest with a private per-home key file | [Trust layer contract — defaults that must hold](specs/coven-trust-layer/PRODUCT.md), [Session artifacts spec](specs/coven-session-artifacts/TECH.md) | Redaction unit tests (`crates/coven-cli/src/privacy.rs`) and artifact store tests in the Rust workspace | +| Secret and privacy guards with a stated baseline: the secret scan covers the full tree and git history; the Coven privacy guard fails closed on new and PR-changed files; CI is the authoritative enforcement layer | [`scripts/check-secrets.py`](scripts/check-secrets.py), [`scripts/check-coven-privacy.py`](scripts/check-coven-privacy.py) | CI `Policy guard` job; managed hooks from `coven hooks install` | +| Mutation replay is explicit, never implicit: adopted launch/input operations use a normative replay-before-mutable ordering with exact first-adoption and exact-replay responses, and retained ambiguity is surfaced instead of silently resolved | [API contract — request ordering and durable side effects](docs/API-CONTRACT.md) | Adopted-route contract tests in the Rust workspace | + +The privacy guard is deliberately a **baseline for new changes**: it applies to +newly staged and PR-changed files while the repository's legacy path examples +are inventoried and converted to placeholders. It rejects sensitive examples, +including invite/handoff URLs containing tokens. It does not claim that +historical commits satisfy the newer privacy rules, and rewriting public +history requires explicit maintainer approval. Memory-layer code, tests, documentation, and PR discussion must describe memory -shape without including real memory content. Use synthetic placeholders such -as `FAMILIAR_ROOT`, ``, and `01JEXAMPLE...`; never copy real +shape without including real memory content. Use synthetic placeholders such as +`FAMILIAR_ROOT`, ``, and `01JEXAMPLE...`; never copy real attestation prose, session identifiers, chat IDs, or local workspace paths into the repository. -## Session logs and sensitive artifacts - -Coven treats session logs, prompts, harness output, tool payloads, and event history as sensitive local data. Do not place secrets in prompts or session context. - -Default session event payloads are redacted before they are stored in SQLite or returned from `/events`, `/sessions/:id/events`, or `/sessions/:id/log`. Redaction covers common authorization headers, cookies, provider token shapes, private key blocks, secret-like `.env` assignments, private gateway URLs, and configured extra patterns. - -Raw sensitive artifact persistence is disabled by default. If `privacy.toml` sets `persist_raw_artifacts = true` or `COVEN_PERSIST_RAW_ARTIFACTS=1`, Coven stores raw payload artifacts separately from normal logs using authenticated local encryption. The encryption key is generated under `/keys/session-artifacts.key` with private file permissions and is not stored in the repository or SQLite database. - -The local key-file provider is an MVP for local-first encryption. It protects raw artifact rows from casual database inspection, but it is not a replacement for OS keychain-backed key management on shared or higher-risk machines. - -Default retention is short for raw encrypted artifacts and bounded for operational logs: - -- Raw encrypted artifacts: 7 days. -- Redacted event logs: 30 days. -- Manual pruning: `coven logs prune`. - ---- - -## OpenCoven Security Disclosure Addendum - -## Security Policy - -### Reporting a Vulnerability - -If you discover a security vulnerability in OpenCoven, please report it responsibly. +## 3. Residual risk and safe configuration + +**What local-first does not protect against.** Local-first keeps data on your +machine and keeps the API off the network by default; it is not a defense +against software already running as your user, harness processes acting with +your privileges, or a hostile prompt/provider steering a harness. It also does +not yet harden daemon-side `COVEN_HOME` ownership and permission checks before +creating or removing daemon state; that remains a documented hardening priority +(see [authentication — current hardening gap](docs/AUTH.md)), so client-side +socket validation should be treated as defense in depth, not a complete +boundary. + +**Raw-artifact opt-in risk.** Setting `persist_raw_artifacts = true` in +`privacy.toml` (or `COVEN_PERSIST_RAW_ARTIFACTS=1`) stores unredacted payload +artifacts. They are encrypted at rest with a key generated under +`/keys/session-artifacts.key` with private file permissions, and +the key is not stored in the repository or the database. The local key-file +provider is an MVP for local-first encryption: it protects raw artifact rows +from casual database inspection, but it is not OS keychain-backed key +management and is not intended for shared or higher-risk machines. + +**Retention is data minimization, not secure deletion.** Raw encrypted +artifacts are retained for 7 days and redacted event logs for 30 days by +default, with manual pruning via `coven logs prune`. Retention bounds how long +sensitive rows persist in Coven's store; it does not overwrite database pages +or other copies your operating system or backup tooling may hold. + +**Untrusted harnesses and prompts.** Supported harness CLIs (Codex, Claude +Code, GitHub Copilot CLI; opt-in recipes beyond that set) execute with your +user's privileges inside the project you point them at. Coven validates how +they are launched and which sessions they reach; it does not police what a +running harness does inside those privileges. Do not paste secrets into +prompts, do not ask a harness to dump environment variables, and use throwaway +projects for demos and smoke tests. + +**AgentFS mount safe configuration.** Treat every AFS mount as experimental +scratch state for a single user on a single machine: loopback only, no +multi-user export, no exposure beyond localhost, and no use as durable +storage. The backend is feature-gated and uncertified; its remaining gates and +go/no-go status are tracked in +[`specs/coven-agent-fs/MOUNT-SPIKE.md`](specs/coven-agent-fs/MOUNT-SPIKE.md) +and the certification work under #779, which this section follows. If the +mount surface ships for real, this policy is updated before that release. + +**Targets and design goals are not enforced properties.** Performance targets, +SLOs, and architectural goals — in this repository or the wider OpenCoven +protocol work — are not security properties of Coven until a corresponding +test suite and release prove them, and release-gating security claims are +recorded in the shipped-truth/certification evidence produced by the release +governance work (#779, #805). + +## 4. Reporting a vulnerability **Do not open a public GitHub issue for security vulnerabilities.** -Contact the maintainers directly: -- Discord: https://discord.gg/OpenCoven (DM @BunsDev) -- Or open a GitHub Security Advisory on the repository - -We will acknowledge receipt within 48 hours and aim to address confirmed vulnerabilities within 14 days. - -### Scope - -Security reports are welcome for: -- OpenCoven core harness and routing logic -- OpenTrust memory and session substrate -- Authentication and identity handling -- Agent sandbox and execution boundaries -- Any mechanism that could allow one agent or user to access another's context - -### Out of Scope - -- Issues in third-party dependencies (report to the dependency maintainer) -- Issues in model provider APIs (report to the provider) - -### Our Commitment - -We take security seriously because OpenCoven handles personal context and agent execution on behalf of users. We will credit researchers who responsibly disclose vulnerabilities (with their permission). - ---- - -## Architectural Security Properties - -The following properties are design goals of OpenCoven. If you find a way to violate them, that's a security report: - -1. **Session isolation** — one user's agent context must not be accessible to another user or agent without explicit permission -2. **Memory ownership** — a user's stored memory and context must remain under their control -3. **Agent identity integrity** — a familiar's identity must not be forgeable by another agent or external caller -4. **Execution boundaries** — agent tool calls must not escape their intended scope - ---- - -*Last updated: 2026-07-26* +- **Primary path:** open a private + [GitHub Security Advisory](https://github.com/OpenCoven/coven/security/advisories/new) + on this repository. This is the monitored intake for Coven. +- **Organization-wide findings** (protocol behavior, cross-repository issues, + other OpenCoven repositories) belong in the + [organization-level security policy](https://github.com/OpenCoven/.github/blob/main/SECURITY.md). +- If you cannot use Security Advisories, mark a related tracking issue private + by contacting a maintainer through an organization-owned channel — please do + not depend on any individual's personal account as the reporting path, and + never post exploit details in public issues. + +Coven deliberately publishes **no acknowledgment or remediation deadline**. +Maintainers triage advisories through normal repository maintenance. Adding +response-time commitments requires an accountable process that can meet and +measure them; until such a process exists, this policy does not promise one. +Researchers who responsibly disclose may request credit in a release note, with +their permission. + +**Third-party dependencies and providers.** Findings that live purely inside a +third-party dependency or a model provider's API are best reported upstream to +that maintainer. Report them here as well — via a Security Advisory — when they +materially compromise Coven's supported behavior: bundled or pinned versions, +Coven's integration defaults, credential-handling boundaries, or anything that +turns a dependency flaw into a Coven compromise. + +## 5. Design goals vs guarantees + +The retired repository policy listed broad isolation properties beside enforced +behavior. They are **design goals of the OpenCoven protocol**, not enforced +Coven properties today, and they now live where they belong: + +- **Session isolation** across users and agents, **memory ownership**, and + **familiar identity integrity** are protocol-level goals described in the + [trust layer contract](specs/coven-trust-layer/PRODUCT.md) and related + OpenCoven protocol documents. +- **Agent-to-agent boundary policy and delegation** are active work in + #803 (input-guardrail parity across handoffs) and #804 (invocation and + delegation contracts). Coven's current local Runner does not implement A2A + isolation; do not rely on it as if it did. +- **Execution boundaries** are enforced only to the extent of the properties in + [Enforced properties today](#2-enforced-properties-today). + +A property becomes a Coven guarantee when an executable acceptance or control +family tests it on a shipped release — the table in section 2 is that list. +Violating an enforced property is a security report. A path that would defeat a +design goal (for example, cross-user or cross-agent access) is also worth +reporting, but it should be described as a protocol-boundary finding, not as a +broken Coven guarantee. + +## Policy maintenance + +- This file is the single normative security policy for this repository; the + organization-level default policy is not additive here. +- Update it in the same change as any security or secret-handling rule, per + [documentation maintenance](docs/DOCS-MAINTENANCE.md). +- Internal links are relative repository links so they resolve in both the + repository and deployed-doc contexts; docs link and freshness validation is + tracked under #778. + +*Last updated: 2026-08-30* diff --git a/docs/DOCS-MAINTENANCE.md b/docs/DOCS-MAINTENANCE.md index e9ea0209..f14982c8 100644 --- a/docs/DOCS-MAINTENANCE.md +++ b/docs/DOCS-MAINTENANCE.md @@ -35,6 +35,49 @@ When moving a topic: 4. Keep normative details here only when the public page links back to the source contract. +## Public-doc directory boundary + +The repository's public-doc directories may contain only two kinds of pages: + +- **Canonical pointers** — a stable repository entry point whose body links to + the canonical `docs.opencoven.ai` route. Use this shape: + + ```md + --- + title: "" + description: "Pointer to the canonical guidance." + --- + + Canonical guidance: **https://docs.opencoven.ai/docs/** + + + ``` + +- **Source-adjacent exceptions** — a page that must evolve with the code + (contracts, maintainer source maps, verification procedures). Every retained + page states its source-adjacent ownership reason, either in the page itself + or in the ownership table in [`README.md`](../README.md) and + [`docs/index.md`](index.md). + +Public-doc directories today: `docs/install/`, `docs/platforms/`, +`docs/start/`, `docs/help/`, `docs/harnesses/`, `docs/models/`, +`docs/memory/`, `docs/guides/`, `docs/reference/`, and the public operation +pages of `docs/daemon/`. Source-adjacent trees (`docs/design/`, +`docs/development/`, `docs/superpowers/`, `docs/architecture/`, +`docs/security/`) and the top-level normative contracts are exempt. + +Do not add a new public page to these directories, and do not restore +duplicated prose. If a canonical target is missing, the local page stays +unchanged until the canonical coverage lands in `coven-docs` — topical +similarity alone is not duplication, and an absent canonical target blocks +removal, never forces a rewrite here. + +Public user guidance pages that remain because their canonical target is still +pending (for example the platform pages retained until +`scripts/onboarding-docs-test.mjs` is migrated to canonical-pointer +expectations) are listed as pending exceptions in the tracking issue, not +silently kept. + ## Public content stance All committed documentation is public. It should describe OpenCoven and Coven diff --git a/docs/architecture/mobile-device-pairing-delivery-plan.md b/docs/architecture/mobile-device-pairing-delivery-plan.md index cbfc0a17..ca25cdcb 100644 --- a/docs/architecture/mobile-device-pairing-delivery-plan.md +++ b/docs/architecture/mobile-device-pairing-delivery-plan.md @@ -29,6 +29,8 @@ Exit criteria: language-level tests and cross-implementation vectors pass. ### PR 3 — TUI enrollment and device administration +Detailed slice plan: [`mobile-device-pairing-tui-bootstrap-plan.md`](mobile-device-pairing-tui-bootstrap-plan.md) (issue #785). + - `coven device pair` - scope selection and permission preview - terminal QR plus copyable fallback diff --git a/docs/architecture/mobile-device-pairing-tui-bootstrap-plan.md b/docs/architecture/mobile-device-pairing-tui-bootstrap-plan.md new file mode 100644 index 00000000..7c14d65e --- /dev/null +++ b/docs/architecture/mobile-device-pairing-tui-bootstrap-plan.md @@ -0,0 +1,777 @@ +# Coven TUI QR Bootstrap and End-to-End Encrypted Mobile Pairing — Plan + +Status: Proposed plan (implementation has not started) +Tracks: #785 (parent architecture #784) +Governing protocol contract: [`mobile-device-pairing-v1.md`](mobile-device-pairing-v1.md) +Elaborates: PR 3 ("TUI enrollment and device administration") and the rendezvous slice of PR 4 in the [delivery plan](mobile-device-pairing-delivery-plan.md) + +## 1. Purpose and scope + +This plan turns the pairing protocol contract into a concrete, reviewable +implementation plan for the first-time mobile enrollment UX: + +```text +$ coven device pair → capability preview → QR → E2EE handshake over a +rendezvous relay → six-word phrase confirmed on both endpoints → scoped, +revocable device grant +``` + +In scope: + +- the `coven device` command family (`pair`, `pair --scope`, status, cancel, + device administration entry points); +- a canonical, versioned pairing offer encoded in deterministic CBOR and + carried by a Universal Link plus the existing custom-scheme URL; +- an authenticated, forward-secret Noise handshake between TUI host and mobile + device, with a transcript that binds the offer, keys, capabilities, nonces, + endpoint identities, and protocol versions; +- a rendezvous/relay MVP so pairing works across NAT, SSH hosts, and + restrictive networks using outbound connections from both endpoints; +- human verification (short authentication phrase) and explicit grant + confirmation; +- the adversarial test matrix (replay, substitution, MITM, downgrade, relay, + malformed input). + +Out of scope (separate delivery-plan PRs, linked where they touch this plan): + +- returning-device reconnection, local discovery, and push wake-up + (delivery-plan PR 6); +- recovery, trusted-device introduction, and attestation (PR 7); +- the mobile client (Pocket) UI and platform key storage internals (PR 5); +- production rendezvous fleet operations beyond the single-reference relay. + +The QR is an out-of-band introduction, never a reusable login token. Every +requirement in this plan defers to [`mobile-device-pairing-v1.md`](mobile-device-pairing-v1.md) +where the two disagree; conflicts should be resolved by amending that contract. + +## 2. Current state and gap analysis + +The mobile track already shipped a working, memory-scoped pairing flow. The +plan extends it; nothing here starts from zero. + +### 2.1 What exists today + +| Capability | Current implementation | Code path | +| --- | --- | --- | +| Terminal pairing command | `coven memory mobile pair` renders a QR invitation, polls status, and asks the operator to confirm a six-word phrase | `crates/coven-cli/src/mobile_memory/mod.rs` (`run_pair`, `run_pair_unix`), command enum `MobileMemoryCommand` in `crates/coven-cli/src/main.rs` | +| Pairing engine | Single-use nonce, expiry pruning, host+device phrase confirmation, idempotent completion, bounded retry windows | `crates/coven-cli/src/mobile_memory/pairing.rs` (`PairingManager`, `PendingPairing`, `PairingError`) | +| Pairing v2 offer | `coven-memory://pair` URL with versioned fields and a canonical offer digest over length-prefixed fields | `crates/coven-cli/src/mobile_memory/pairing.rs` (`build_pairing_url`, `PairingOfferV2::hash`), contract in [`docs/design/mobile-pairing-protocol-v2.md`](../design/mobile-pairing-protocol-v2.md) | +| Transcript binding v2 | Offer digest, selected/supported versions, device key, device name, and app version bound into a digest that derives the six-word phrase | `crates/coven-cli/src/mobile_memory/pairing.rs` (`PairingTranscript::V2`), fixture `crates/coven-cli/tests/fixtures/mobile-pairing-v2/transcript-vector.json` | +| QR rendering | Unicode half-block rendering of the pairing URL plus a printed copyable URL and expiry line | `crates/coven-cli/src/mobile_memory/pairing.rs` (`render_pairing_invitation`), `qrcode` crate 0.14 in `crates/coven-cli/Cargo.toml` | +| Device grant model | Versioned grant object with scopes, restrictions, assurance levels, audience, and exact-action intents | `crates/coven-cli/src/mobile_memory/grant.rs` (`DeviceGrant`, `DeviceScope`, `AssuranceLevel`, `DeviceActionIntent`) | +| Request authentication | Canonical signed requests with timestamp, nonce, and body digest; replay window; per-device rate limiting | `crates/coven-cli/src/mobile_memory/auth.rs` (`canonical_request`, `MobileAuthenticator`) | +| Host identity | Stable P-256 host key, self-signed certificate, SHA-256 public-key fingerprint pinned in the QR | `crates/coven-cli/src/mobile_memory/identity.rs` (`load_or_create_host_identity`, `HostIdentity`) | +| Mobile gateway | Private-network rustls TLS listener with bounded routes, body caps, and inflight-connection limits; 5-minute pairing lifetime | `crates/coven-cli/src/mobile_memory/gateway.rs` (`MobileRoute`, `PAIRING_LIFETIME`) | +| Device registry | Atomically persisted, privacy-guarded device records with revocation | `crates/coven-cli/src/mobile_memory/registry.rs` (`DeviceRegistry`, `DeviceRecord`) | +| Audit events | Structured pairing/authentication/revocation audit records | `crates/coven-cli/src/mobile_memory/audit.rs` (`MobileAuditEvent`) | +| Rendezvous relay | Standalone bounded opaque WebSocket room relay (one `host` + one `client`, constant-time credential check, frame/idle/queue caps) — not yet used by the CLI | `crates/coven-relay/src/main.rs`, `crates/coven-relay/src/ws.rs` | +| Protocol contract and schemas | v1 protocol contract, diagnostic JSON schemas, domain-separation and conformance notes | [`mobile-device-pairing-v1.md`](mobile-device-pairing-v1.md), `spec/device-pairing/v1/*` | +| Accepted architecture | Trust-chain decision record that explicitly extends `coven-cli::mobile_memory` | [`docs/design/mobile-device-trust.md`](../design/mobile-device-trust.md) | + +### 2.2 Gap against issue #785 + +| Issue requirement | Today | Gap | +| --- | --- | --- | +| `coven device pair` / `--scope` | `coven memory mobile pair` with a fixed `memory_read` scope (`PAIRING_SCOPE_MEMORY_READ` in `pairing.rs`) | New top-level `device` command family; selectable, previewed scopes | +| Canonical CBOR offer + compact URL-safe encoding | URL query members (JSON-flavored, not CBOR) in `build_pairing_url` | Deterministic CBOR offer and base64url encoding per §5 | +| Universal Link/App Link | `coven-memory://pair` custom scheme only | HTTPS Universal Link carrying the offer in a fragment per §5.4 | +| Forward-secret E2EE handshake (Noise) | TLS 1.3 transport plus phrase confirmation; no application-layer AKEX, no session keys | Noise_XK handshake per §6; new crypto dependencies | +| Rendezvous for cross-network pairing | Gateway requires the phone to reach the host's advertised HTTPS endpoint | Relay session derived from the offer per §7; `coven-relay` already provides the room semantics | +| Countdown / status / cancel | Expiry printed once; Ctrl-C cancels the CLI loop only | Live countdown, explicit status/cancel commands, both-endpoint cancel per §8/§10 | +| Offer-bound capability approval | Phrase binds a fixed scope string | `requested_capabilities_hash` over the exact selected scope set, bound into offer and transcript per §5/§6/§11 | +| Short authentication phrase | Already implemented (six words, 2,048-word list, 66 bits) in `pairing.rs` | Re-derive from the Noise handshake hash per §9; keep six words | +| Bounded failed attempts | One enrollment attempt consumes the nonce (`pairing.rs` `enroll`); phrase failures destroy pending pairings | Add a bounded handshake-attempt counter per §8.4 | +| Replay, substitution, MITM, downgrade, relay, and malformed-input tests | Strong coverage for phrase and nonce paths in `pairing.rs` tests; no relay or handshake tests | Test matrix in §12 | + +The mobile gateway, grants, registry, and audit survive unchanged as the +authority plane; this plan adds a transport and handshake layer in front of +them. + +## 3. Target experience (golden example) + +```text +$ coven device pair --scope sessions.metadata.read,messages.send + +Pair OpenCoven Mobile +Requesting: + ✓ View sessions (sessions.metadata.read) + ✓ Send messages (messages.send) + ✕ Execute tools without approval (not requested: tool_execution_approve) + ✕ Export identity or memory (never grantable over pairing) + +[ QR CODE ] + +Link (if you cannot scan): https://pair.opencoven.ai/p# +Expires in 01:47 +Status: waiting for device · [c] cancel +``` + +After the device connects and the handshake completes, both endpoints display +the same six words; the host confirms only on an exact match: + +```text +Device "Val's iPhone" (app 1.0.0) requests the scopes above. + +Compare these words with the device: +1. willow 2. cinder 3. moon 4. harbor 5. linen 6. ridge + +[c]onfirm / [r]eject: c +Device enrolled. Grant id: 9f14... (revocable with `coven device revoke`) +``` + +The six-word phrase is the existing v2 mechanism (`phrase_for_hash` in +`pairing.rs`); the issue's three-word example (`willow-cinder-moon`) is +illustrative. Six words from a 2,048-word list carry 66 bits, which keeps the +phrase the strong second factor it is today; §15.5 recommends keeping six. + +## 4. Design overview + +### 4.1 Components and data flow + +```text +┌────────────────────── TUI host (coven device pair) ─────────────────────┐ +│ CLI: capability preview, QR render, countdown, confirm/cancel │ +│ │ local unix-socket control API (existing daemon) │ +│ Daemon: PairingSession authority │ +│ · offer minting (CBOR), session store, attempt bounds │ +│ · Noise responder (X25519 static = host pairing key) │ +│ · grant issuance via mobile_memory::grant, registry, audit │ +└───────┬─────────────────────────────────────────────────┬───────────────┘ + │ outbound WSS (rendezvous) │ optional direct + ▼ ▼ LAN TLS (existing) +┌─────────────────────── rendezvous relay ────────────────┐ gateway path +│ coven-relay: opaque room match + ciphertext forward │ +│ no plaintext, no keys, no authority │ +└───────▲─────────────────────────────────────────────────┘ + │ outbound WSS +┌───────┴────────────── Mobile device ────────────────────┐ +│ scan QR / open Universal Link → offer validation │ +│ Noise initiator, enrollment request signature, │ +│ phrase confirmation, grant receipt │ +└─────────────────────────────────────────────────────────┘ +``` + +Both endpoints make outbound connections only. The relay matches opaque room +identifiers and forwards binary frames; it never receives application +plaintext, keys, or grants. This is the delivery-plan PR 4 behavior applied to +pairing first. + +### 4.2 What stays, what changes + +Stays (authority plane unchanged): + +- `mobile_memory::grant` issuance/verification semantics, scope vocabulary, + assurance levels, and exact-action intents (`grant.rs`); +- the device registry, revocation, and audit surfaces (`registry.rs`, + `audit.rs`); +- the direct-LAN TLS gateway as the high-bandwidth path after pairing + (`gateway.rs`), with the phrase/handshake replacing "trust the LAN"; +- request authentication for post-pairing API calls (`auth.rs`). + +Changes (new or extended): + +- a new `device` command family in `crates/coven-cli/src/main.rs` (§10); +- a new pairing-session authority module (proposed + `crates/coven-cli/src/device_pairing/`) that owns offers, the Noise + handshake, and relay transport, and hands confirmed enrollments to + `mobile_memory::grant` + `registry`; +- CBOR offer encoding and Universal Link rendering (§5); +- new crate dependencies: `snow` (Noise), `x25519-dalek`, `hkdf`, `ciborium` + or `serde_cbor`-successor for deterministic CBOR (§15.2); +- `coven-relay` gains nothing conceptually — the CLI becomes its second + consumer; only small additions for derived-room validation if needed (§15.6). + +## 5. Pairing offer (version 1, deterministic CBOR) + +The offer follows the contract's `PairingOffer` (mobile-device-pairing-v1.md, +"Pairing offer") with concrete encodings. The existing v2 URL offer remains +accepted for one deprecation window (§15.7). + +### 5.1 Canonical CBOR layout + +Deterministic CBOR per RFC 8949 §4.2.1 (core deterministic encoding): map +keys in bytewise lexicographic order, shortest-form integers, no indefinite +lengths. All bstr fields are fixed length, so no length ambiguity exists. + +| Field | CBOR key (text string) | Type | Notes | +| --- | --- | --- | --- | +| version | `"v"` | uint (1) | Offer format version; handshake protocol version negotiated separately (§6) | +| pairing_session | `"s"` | bstr 32 | Cryptographically random, single-use session id (room derivation input, §7.1) | +| ephemeral_public_key | `"k"` | COSE_Key map | X25519 host ephemeral key for this pairing attempt; fresh every attempt (issue checklist item 1) | +| host_static_key_id | `"hf"` | bstr 32 | SHA-256 fingerprint of the host's X25519 pairing static key; the QR pin (§6.2) | +| rendezvous_hint | `"r"` | array of maps | Ordered transport hints (§7.2) | +| local_discovery_hint | `"d"` | tstr, optional | Opaque rotating local-discovery token; omitted in the MVP | +| requested_capabilities_hash | `"c"` | bstr 32 | SHA-256 over the canonical CBOR array of selected scope strings (§11.2) | +| expires_at | `"e"` | uint | Unix seconds; host rejects use after expiry (5-minute default, matching `PAIRING_LIFETIME` in `gateway.rs`) | + +An offer is ~170 bytes in CBOR (~230 base64url characters), well inside QR +byte-mode capacity at ECC level M. + +Forbidden in the offer (contract "MUST NOT" list, enforced by schema and +review): permanent API or bearer credentials, owner/familiar/installation +private keys, biometric material, hardware serials or advertising IDs, and +unnecessary identity metadata (no device name, owner name, or account id). + +### 5.2 TypeScript types (diagnostic/tooling form) + +Mirrors `spec/device-pairing/v1/pairing-offer.schema.json` (diagnostic JSON is +for tooling only; the wire format is CBOR): + +```ts +export interface PairingOfferV1 { + version: 1; + /** 32-byte cryptographically random single-use session id, base64url. */ + pairingSession: string; + /** COSE_Key (kty OKP, crv X25519), fresh per pairing attempt. */ + ephemeralPublicKey: CoseKey; + /** SHA-256 fingerprint of the host's X25519 pairing static key, base64url. */ + hostStaticKeyId: string; + rendezvousHints: RendezvousHint[]; + localDiscoveryHint?: string; + /** SHA-256 over canonical CBOR of the selected scope string array. */ + requestedCapabilitiesHash: string; + /** Unix seconds. */ + expiresAt: number; +} + +export interface CoseKey { + kty: "OKP"; + crv: "X25519"; + x: string; // base64url 32 bytes +} + +export interface RendezvousHint { + transport: "wss" | "https" | "local"; + endpoint: string; + priority?: number; // 0..255, lower is preferred +} +``` + +The diagnostic JSON schema gains the same fields (`spec/device-pairing/v1/ +pairing-offer.schema.json` already carries `rendezvous` and +`requestedCapabilitiesHash`; add `hostStaticKeyId`, keep `additionalProperties: +false`). Schema changes land with the implementation PR that emits them. + +### 5.3 Single-use and expiry rules + +- `pairing_session` is 32 bytes from the OS CSPRNG (same generator class as + the existing `begin_pairing` nonce in `pairing.rs`). +- An offer is consumable by exactly one successful handshake. First use pins + the session; second use fails closed (`PairingConsumed` semantics already + proven in `pairing.rs` tests). +- Offers expire after 5 minutes (default; `--ttl` may shorten, never extend, + with a hard maximum of 15 minutes). +- Terminal states destroy all pairing secrets (§8.4). + +### 5.4 Universal Link and QR payloads + +Primary payload — Universal Link with the offer in the URL fragment so +ordinary HTTP request processing never receives it (contract: "A Universal +Link/App Link MAY encode the offer in a URL fragment"): + +```text +https://pair.opencoven.ai/p# +``` + +The fragment never reaches a server; the domain is a routing hint only (same +model as the existing `endpoint` member, which `mobile-pairing-protocol-v2.md` +excludes from the offer digest because the key fingerprint authenticates the +endpoint). The mobile client validates the offer digest and keys locally; +scanning a forged link fails at offer validation. + +Secondary payload — compact custom scheme for terminal copy/paste without a +browser round-trip: + +```text +coven://pair# +``` + +The printed link uses the Universal Link form; both decoders share one CBOR +validator. QR mode: byte mode, ECC level M, quiet zone 4 modules; the +terminal renderer keeps the existing `qrcode` half-block output +(`render_pairing_invitation`) with added blank-line padding and an +`aria`-style plain-text link fallback printed beside it (§10.4). + +## 6. Handshake + +### 6.1 Pattern evaluation + +The contract requires "an established protocol/construction (evaluate Noise +patterns rather than inventing cryptography)". Who knows which static key +before the handshake decides the pattern: + +| Pattern | Initiator (device) knows responder (host) static | Responder knows initiator static | Fit | +| --- | --- | --- | --- | +| Noise_XX | no | no | Works, but the host is authenticated only after the human phrase check; the QR pin is unused cryptography | +| Noise_KK | yes | yes | Fails: first-time enrollment means the host cannot pre-know the device static key | +| Noise_XK | yes (QR-pinned fingerprint) | no (learned encrypted in message 2) | Fits exactly: device authenticates the host cryptographically in message 2, before any human action | +| Noise_IK | yes | yes | Fails like KK | + +**Recommendation: `Noise_XK_25519_ChaChaPoly_SHA256`** with: + +- initiator = mobile device, responder = TUI host (the device scans, the host + answers — matching the offer's direction of trust); +- responder static = a dedicated host **X25519 pairing key** (new; see §6.2), + whose SHA-256 fingerprint is `host_static_key_id` in the offer; +- prologue = the canonical CBOR offer bytes (binds every offer field — + including `requested_capabilities_hash` and expiry — into the handshake + transcript, per issue checklist item 3); +- initiator static = the device's new durable **device identity key**. The + architecture contract separates X25519 agreement from Ed25519 signatures; + the enrollment signature key is Ed25519 (§6.4), and the Noise static binds + the same device identity cryptographically. Implementations generate both + keys at first enrollment and store them together. + +Alternatives considered: + +- **TLS 1.3 + phrase only (today's model):** proven, but there are no + forward-secret application session keys off TLS, no binding of the offer + into a cryptographic transcript beyond the digest, and no protection if the + gateway TLS termination is ever exposed off-LAN; the contract asks for a + Noise handshake. +- **Noise_XX + phrase:** one less host key to manage, but weakens the + QR-pinned host authentication that already exists in v2 + (`fingerprint` member, `mobile-pairing-protocol-v2.md`). +- **Noise_KK with a pre-registered device key:** only applies to re-pairing a + known device; use it later as the reconnection optimization, not first + enrollment. + +The handshake implementation MUST use the `snow` crate (the maintained, +widely reviewed Rust Noise implementation) rather than hand-rolled +Noise state machines (§15.2). + +### 6.2 Host pairing key + +The existing host identity is P-256 and is pinned by TLS certificate +fingerprint (`identity.rs`). Noise needs X25519. Do not convert P-256 keys to +X25519 (non-standard and error-prone); instead: + +- generate a dedicated X25519 host pairing static key on first use, stored in + the same private directory with the same atomic-write and permission + discipline as `identity.rs` (`atomic_create_private`, + `ensure_private_mobile_dir`); +- its SHA-256 fingerprint goes in the offer (`host_staticKeyId`), so a relay + or MITM cannot substitute a host without forging the pinned key; +- rotate it only with an explicit operator action; rotation changes QR + fingerprints and therefore requires a fresh offer — which pairing already + is. + +The host certificate fingerprint mechanism (`identity.rs` `public_key_fingerprint`) +remains for the direct-LAN TLS path and is unchanged. + +### 6.3 Message flow over the rendezvous + +```text +device → relay → host : Noise message 1 (e) [XK: -> e] +host → relay → device: Noise message 2 (e, ee, s, es) [XK: <- e, ee, s, es] +device → relay → host : Noise message 3 (s, se) + enrollment [XK: -> s, se] + : encrypted frames both ways +``` + +- Message 3 carries the first encrypted application payload: the canonical + enrollment request (transcript hash, Ed25519 device public key, requested + scopes digest, device display name, app version, nonce, expiry) signed with + the Ed25519 device key — the contract's "Device enrollment request". +- The host replies with the signed `DeviceGrant` (COSE_Sign1 semantics already + modeled by `DeviceGrant` in `grant.rs`) or a rejection. +- Frames are length-prefixed (u32 big-endian) and capped at the relay's + existing `MAX_FRAME_BYTES` (64 KiB); enrollment payloads are far smaller + than `MAX_MOBILE_REQUEST_BYTES` (64 KiB in `mobile_memory/mod.rs`). +- Key material: Noise chaining key → HKDF-SHA-256 with domain string + `COVEN-PAIR-SESSION/1` splits into the post-handshake transport keys. Both + peers MUST zeroize handshake buffers and ephemeral secrets after use; + `Zeroizing` (already used in `identity.rs`) is the storage discipline. + +### 6.4 Transcript binding (issue checklist item 3) + +The final handshake hash MUST cover, directly or via the prologue and +encrypted payloads: + +1. offer format version and canonical CBOR offer bytes (prologue) — binds + `pairing_session`, ephemeral key, host fingerprint, rendezvous hints, + capabilities hash, expiry; +2. protocol version: Noise protocol name string plus the pairing protocol + range (min/max) from both sides, exchanged inside message 3's encrypted + payload (downgrade detection, §12); +3. host X25519 static (authenticated by XK message 2 against the QR pin); +4. device ephemeral keys (Noise-managed) and device Ed25519 enrollment key + (message 3); +5. the exact requested capability digest, restated inside the signed + enrollment request so the signature covers it independently; +6. fresh nonces from both peers (message-3 payload nonce + the existing + enrollment nonce semantics); +7. both endpoint identity references (host fingerprint, device key digest). + +Any mismatch aborts before any grant exists (contract: "Any mismatch MUST +abort the enrollment"). + +## 7. Rendezvous transport MVP + +### 7.1 Room derivation from the offer + +`coven-relay` rooms are `(32-byte room id, separate bearer credential, one +host + one client)` (`ws.rs`). Derive both from the offer's +`pairing_session` with domain separation: + +```text +room_id = SHA-256("COVEN-RENDEZVOUS-ROOM/1" || pairing_session) → base64url +room_token = SHA-256("COVEN-RENDEZVOUS-TOKEN/1" || pairing_session) → base64url +``` + +Both values are derivable only from the offer, so possession of the QR is the +capability to attempt pairing — which is exactly the threat model: the QR is +a short-lived single-use introduction, and a QR photographed by an attacker +still fails at host authentication (XK), phrase confirmation, and expiry. +The host creates the room (relay "first peer creates the room"); the device +joins as `client`. The relay sees only opaque ids and ciphertext. + +`rendezvous_hint` entries name the relay URL(s), ordered by `priority` +(e.g. `wss://relay.opencoven.ai/ws`). The relay deployment URL is a +maintainer decision (§15.6). A `local` hint may advertise the direct gateway +endpoint for same-network fast paths; discovery is never authentication +(contract §"Local discovery and direct transport"). + +### 7.2 Frame and abuse bounds + +Reuse the relay's existing bounds unchanged (they were built for this): +`MAX_MESSAGE_BYTES` 4 MiB, `MAX_FRAME_BYTES` 64 KiB, 120 s idle timeout, +bounded rooms/channels/queues, one host + one client per room, constant-time +credential comparison (`secret_eq` in `ws.rs`). Host-side additions: + +- at most 3 handshake attempts per pairing session; the session is destroyed + after the bound (§8.4); +- handshake frames must complete within 30 s of room join or the session is + cancelled (stale-session cleanup matching the relay's idle timeout). + +### 7.3 Direct-LAN fallback + +If a `local` hint is present and reachable, the device MAY complete the same +Noise handshake over the existing TLS gateway socket instead of the relay +(transport swap, identical protocol). Changing transport MUST NOT change +endpoint identity or authorization (contract requirement). The MVP ships +relay-first with the direct path opportunistic; if the direct path is +unreachable the relay path is always available — this is what makes pairing +work across different networks (acceptance criterion 1). + +## 8. Enrollment state machine + +Extends the contract's state machine with the host-side session lifecycle. +Invalid transitions fail closed. + +### 8.1 Host (TUI/daemon) + +```text +IDLE ──pair──▶ OFFER_CREATED ──device joined room──▶ RENDEZVOUS_CONNECTED + ▲ │ expired/cancelled │ handshake started + │ ▼ ▼ + │ EXPIRED/CANCELLED HANDSHAKE_ESTABLISHED + │ │ both phrases confirmed + │ any state ──cancel/expiry/failure──▶ CANCELLED/FAILED/EXPIRED + ▼ ▼ +ENROLLED ◀──────────────────────────────────── GRANT_PENDING +``` + +### 8.2 Device + +```text +OFFER_SCANNED ──validate──▶ RENDEZVOUS_CONNECTING ──joined──▶ HANDSHAKE_STARTED + │ invalid/expired │ unreachable (all hints) │ complete + ▼ ▼ ▼ + REJECTED FAILED PHRASE_PENDING ──match──▶ ENROLLED + │ mismatch ×N + ▼ + REJECTED +``` + +### 8.3 Countdown, status, cancel + +- The TUI renders a live `Expires in mm:ss` countdown from `expires_at` and a + one-line status (`waiting for device`, `device connected`, `verifying`, + `enrolled`, `cancelled`, `expired`) — extending the static output of + `render_pairing_invitation`. +- `coven device pair` blocks until a terminal state; `c` or Ctrl-C cancels. +- `coven device status [--json]` reports the active pairing session (state, + remaining seconds, connected device name once known). +- `coven device cancel` cancels the active session from the host. The device + can cancel by closing the relay room or sending the (already encrypted) + `cancel` frame; both destroy the session (acceptance criterion 5). +- The daemon-side control API gains the same endpoints as today's internal + pairing routes (`POST /api/v1/internal/mobile/pairings…` in `mod.rs`), moved + under the device-pairing module with status/cancel verbs. + +### 8.4 Secret erasure and failure bounds (issue checklist items 5–6) + +Every terminal state (`ENROLLED`, `EXPIRED`, `CANCELLED`, `REJECTED`, +`FAILED`) synchronously erases: the pairing session id, host ephemeral +private key, derived transport keys, and the pending-device record — the +`PendingPairing` lifecycle in `pairing.rs` already prunes this way and is the +template. Durables after success are exactly: the device registry record and +its grant (`registry.rs`, `grant.rs`). Nothing else persists. + +Bounded failures: 3 handshake attempts per session (§7.2); a phrase mismatch +before completion destroys the pending pairing (existing behavior, +`incomplete_pairing_mismatch_invalidates_the_retry_window` test); enrollment +nonce single-use (existing behavior, `pairing_nonce_is_consumed_on_first_enrollment_attempt`). + +## 9. Human verification + +- Both endpoints derive the six-word phrase from the **final Noise handshake + hash** (not the standalone transcript digest): HKDF-SHA-256 with info string + `COVEN-PAIR-SAS/1` over the handshake hash, then the existing 66-bit → 6 × + 11-bit word mapping (`phrase_for_hash`, 2,048-word list in + `pairing_words.txt`). Deriving from the handshake hash binds the phrase to + the full E2EE transcript automatically; the word list and rendering stay + identical to today's UX. +- The phrase is displayed on both endpoints after `HANDSHAKE_ESTABLISHED`. + The host requires typed confirmation (or `confirm` on the CLI) before + `GRANT_PENDING` completes; the device requires explicit user confirmation + too (existing two-sided confirmation semantics in `confirm()`). +- The phrase is defense in depth. In Noise_XK the host is already + authenticated by the QR pin; the phrase catches a QR-substitution attack + (attacker swaps the printed QR) which pin-verification alone would also + catch — the phrase additionally catches wrong-endpoint pairing where the + attacker holds a valid relay position but not the device's intent. +- Keep six words (66 bits) rather than the issue's three-word example; + §15.5 records the tradeoff. + +## 10. TUI command surface + +### 10.1 Commands + +```text +coven device pair [--scope LIST] [--ttl SECONDS] [--json] +coven device status [--json] # active pairing session, if any +coven device cancel # cancel the active pairing session +coven device list [--json] # paired devices (wraps mobile registry) +coven device inspect DEVICE_ID # grant details for one device +coven device rename DEVICE_ID NAME +coven device revoke DEVICE_ID # wraps registry revoke + audit event +``` + +`coven memory mobile …` remains as a thin compatibility alias for +enable/disable/status during the transition; new capability lives under +`coven device`. Rationale: the issue's target UX is `coven device pair`, the +delivery plan's PR 3 lists `coven device pair` and +`device list|inspect|rename|revoke`, and today's `MobileMemoryCommand` +(`main.rs`) couples device administration to the memory-gateway feature flag. + +### 10.2 Scope selection and preview (issue: `--scope`) + +- `--scope` accepts a comma-separated list from the `DeviceScope` + vocabulary (`grant.rs`: `memory_read`, `session_metadata_read`, + `conversation_read`, `message_send`, `tool_invocation_request`, + `tool_execution_approve`, `secrets_read`, `familiar_memory_admin`, + `device_admin`, `identity_admin`, `memory_export`, `identity_export`). +- Default (no flag): `session_metadata_read,messages_send` equivalent — the + issue's "view sessions, send messages" preview — never a silent + everything-grant. +- The preview renders each selected scope with ✓, and renders the salient + withheld classes with ✕ (at minimum: tool execution without approval and + any export class), matching the issue's target UX. +- `identity_export` and `memory_export` MUST be rejected by `device pair` in + v1 (they remain registry-manageable for other flows) — the issue's + "Export identity or memory" ✕ line is a hard rule, not styling. +- The exact selected set is hashed into `requested_capabilities_hash` (§5.1) + and bound into the offer digest, the Noise prologue, and the signed + enrollment request (§6.4) — the permission request is cryptographically + bound to what the user approves (acceptance criterion 4). + +### 10.3 Grant issuance + +On successful confirmation, the daemon issues a `DeviceGrant` +(`grant.rs::DeviceGrant::for_device`) with: + +- capabilities = the selected scope set (no broader — contract: "The grant + MUST be no broader than the permissions displayed and approved"); +- audience/restrictions per the existing restriction model (transport + constraint may record `relay` for relay-paired devices); +- the grant id returned to the TUI for the confirmation line and audit event + (`MobileAuditEvent::PairingCompleted` in `audit.rs`). + +### 10.4 QR rendering and accessibility (issue checklist items 4–5) + +- Keep the `qrcode`-crate unicode half-block renderer; add: blank-line quiet + zone, automatic fallback to ASCII (`#`/space) when the terminal reports + non-UTF-8, and a minimum-size check (offer URL ~230 chars → version ~11 QR + at ECC M, still legible at typical TUI widths). +- Always print the Universal Link on its own line for copy/paste (existing + behavior in `render_pairing_invitation`), plus `--json` output carrying + `{link, expiresAt, scopes}` so scripted/assistive clients can surface it. +- Document screen-reader behavior in `coven-docs` (public docs), not here; + this repo carries the contract, the public docs carry the tutorial. + +## 11. Capability mapping + +### 11.1 Contract vocabulary ↔ grant vocabulary + +The contract (mobile-device-pairing-v1.md) uses dotted names; the +implementation uses `DeviceScope` snake_case (`grant.rs`). The mapping is +1:1 and total: + +| Contract | `DeviceScope` | +| --- | --- | +| `sessions.metadata.read` | `session_metadata_read` | +| `conversations.read` | `conversation_read` | +| `messages.send` | `message_send` | +| `tools.request` | `tool_invocation_request` | +| `tools.approve` | `tool_execution_approve` | +| `secrets.read` | `secrets_read` | +| `memory.familiar.read` | `memory_read` (familiar-scoped via restrictions) | +| `memory.familiar.write` | `familiar_memory_admin` | +| `identity.admin` | `identity_admin` | +| `devices.enroll` / `devices.revoke` | `device_admin` | +| `identity.export` | `identity_export` (not pairable in v1) | +| `memory.export` | `memory_export` (not pairable in v1) | + +### 11.2 Capabilities hash + +```text +requested_capabilities_hash = SHA-256(canonical CBOR array of selected + DeviceScope strings, in sorted order) +``` + +Sorted order makes the hash independent of CLI argument order; duplicates are +rejected at parse time (`validate_scope_set` in `grant.rs` already validates +scope sets — extend it with the pairable-subset rule from §10.2). + +## 12. Test plan (issue checklist item 7 + contract "Required security tests") + +New tests live next to the implementation: unit tests in the new +`device_pairing` module, integration tests under `crates/coven-cli/tests/` +(the existing `mobile-pairing-v2` fixture and `pairing.rs` test style are the +template), relay adversarial cases in `crates/coven-relay/src/ws/tests.rs`. + +| Class | Case | Level | +| --- | --- | --- | +| Replay | Offer reuse after success fails (`PairingConsumed`) | unit | +| Replay | Offer reuse after expiry fails | unit | +| Replay | Duplicate enrollment request over a replayed message 3 | integration | +| Substitution | Any offer field change (session, key, capabilities hash, expiry) breaks the prologue → handshake abort | unit | +| Substitution | Device key/name/app-version substitution changes the SAS phrase (extends `pairing_v2_binds_offer_and_client_metadata`) | unit | +| MITM | Relay-position attacker with wrong host static fails at XK message 2 | integration | +| MITM | QR substitution: attacker's offer fails host pin check on device | unit | +| Downgrade | Peer offering min>current or max", + "deviceName": "Val’s iPhone", + "devicePublicKey": "", + "appVersion": "1.0.0", + "supportedProtocol": { "minimum": 1, "maximum": 2 }, + "stepUpAuthorization": { + "publicKey": "", + "assuranceClass": "biometric_only" + } +} +``` + +Rules: + +- The field is optional. Absent ⇒ the transcript digest, phrase, and all v2 + behavior are byte-for-byte identical to today (v1 devices and v2 clients + without step-up are unaffected). +- Present ⇒ the two extra fields are appended to the `COVEN-PAIR/2` transcript + input, changing the transcript digest and therefore the six-word phrase + (`derive_pairing_phrase`, `crates/coven-cli/src/mobile_memory/pairing.rs:542-556`). + Because both endpoints display the phrase derived from the same host-side + transcript, enrollment-time substitution of the step-up key by an attacker + who only photographed the QR changes the phrase and is caught by the human + comparison, exactly like device-key substitution. +- Older hosts reject the unknown member (`deny_unknown_fields`) and fail + closed. That is intentional: a client that requires step-up learns the host + does not support it instead of silently pairing without it. +- The step-up key MUST be a canonical uncompressed 65-byte P-256 X9.63 key, + validated with the same routine as `devicePublicKey` + (`crates/coven-cli/src/mobile_memory/pairing.rs:529-538`), and MUST differ + from the possession key. +- The authorization-key record is persisted in the same critical step that + registers the device and grant (the `both confirmed` branch, + `crates/coven-cli/src/mobile_memory/pairing.rs:330-372`), so a grant is never + issued with step-up requirements that no enrolled key can ever satisfy. + +**Recommendation — enrollment-time proof of possession.** The enrollment +should additionally carry a signature by the step-up private key over +`"COVEN-STEPUP-ENROLL/1\0" || transcript_hash`, verified before the record is +persisted. Rationale: it proves the key exists in the declared policy domain +(producing the signature exercises the platform gate — on iOS, creating a +signature with a biometry-gated key triggers exactly the LocalAuthentication +ceremony the class claims) and rejects mistyped/unusable keys at pairing time +instead of at first sensitive use. Alternatives considered: (a) skip it — +simpler pairing ceremony, but a key that can never sign is only discovered +later (fail-closed, so acceptable, but degrades UX); (b) require it only for +`biometric_only` class. Recommend: require it for all classes in v1; a failed +ceremony at enrollment means the declared class does not match platform policy. + +### Assurance classes and ceilings + +The enrollment declares the platform policy that protects the step-up key. The +class caps what proofs from that key can ever prove (the server-side ceiling in +[effective assurance](#effective-assurance-server-side-never-client-asserted)): + +| Class | Platform enforcement | Ceiling | +| --- | --- | --- | +| `biometric_only` | iOS `deviceOwnerAuthenticationWithBiometrics` on a Secure Enclave key; Android `BiometricPrompt` with `BIOMETRIC_STRONG` only | `FreshBiometric` | +| `user_verification` | iOS `deviceOwnerAuthentication` (biometric or passcode); Android `BIOMETRIC_WEAK\|DEVICE_CREDENTIAL` | `FreshUserVerification` | +| `device_credential` | PIN/pattern/password only (Android `DEVICE_CREDENTIAL` alone; iOS `kSecAccessControlDevicePasscode`) | `FreshUserVerification` | + +`device_credential` is a distinct class, per the issue's Android mapping: a +fresh passcode entry proves fresh *user verification*, never fresh *biometric*. +`AssuranceLevel::RecentUserVerification` is a server-side policy concept +(platform "recently unlocked" state) with no cryptographic proof; it is not a +claimable proof class. `AssuranceLevel::StepUp` remains reserved for the +recovery/other-device flow described in +`docs/design/mobile-device-trust.md` ("Biometrics and step-up authorization") +and is never minted by `COVEN-ASSURANCE/1`. + +### Storage (separate from the possession key) + +Authorization-key metadata lives in its own store, deliberately not in +`devices.json` (`registry.rs:18`), so possession identity and authorization +enrollment have independent lifecycles: + +`~/.coven/mobile/authorization-keys.json` + +```json +{ + "version": 1, + "keys": [ + { + "deviceId": "00000000-0000-0000-0000-000000000001", + "publicKeyX963": "", + "subjectKeyId": "", + "assuranceClass": "biometric_only", + "enrolledAt": "2026-07-29T12:00:00.000Z", + "revokedAt": null, + "keyEpoch": 1 + } + ] +} +``` + +- Written with the same private, atomic-replace discipline as the device + registry (`registry.rs:13` re-exports `config::atomic_replace_private`; + `validate_private_file` on read — `registry.rs:353-407` shows the pattern). +- At most one active (non-revoked) key per device; `subjectKeyId` reuses the + grant's key-id convention — base64url SHA-256 over the canonical public key + (`grant.rs:242-250`). +- The subject/possession key and its `subject_key_id` stay exactly where they + are (`DeviceRecord`, `DeviceGrant`), satisfying "store authorization-key + metadata separately from the device subject/possession key". +- Forgetting or revoking a device (`registry.rs:revoke`, and the + `--forget-devices` path, `mod.rs:65-88`) cascades to its authorization key. + +## Canonical proof bytes (COVEN-ASSURANCE/1) + +Framing follows the repo's canonical-byte conventions: a versioned ASCII domain +terminated by NUL (as `COVEN-ACTION/1\0`, `grant.rs:270`), then each field +framed as an unsigned 32-bit big-endian length followed by its bytes, exactly +like `DeviceActionIntent::canonical_bytes` (`grant.rs:266-284`) and +`update_length_prefixed` (`pairing.rs:503-506`). + +```text +"COVEN-ASSURANCE/1\0" +u32(len) || bytes for each field, in order: + 1. device_id — raw 16-byte UUID + 2. grant_id — raw 16-byte UUID (DeviceGrant::id, v5-derived, + grant.rs:114) + 3. revocation_epoch — unsigned 64-bit big-endian + 4. authorization_key_id — UTF-8 base64url(SHA-256(step-up public key)), + same derivation as grant.rs subject_key_id + 5. context_mode — ASCII "request" or "action" + 6. context_digest — raw 32-byte SHA-256 (defined below) + 7. challenge — raw 32-byte server-issued challenge + 8. issued_at — ASCII RFC 3339 UTC, millisecond precision + 9. expires_at — RFC 3339 UTC, same encoding + 10. requested_assurance — ASCII "fresh_user_verification" or "fresh_biometric" +``` + +The step-up key signs exactly these bytes with ECDSA P-256 over SHA-256, +DER-encoded, base64url — the same signature encoding the possession path +verifies (`auth.rs:239-260`, `Signature::from_der`). + +### Context digest (server-recomputed, never client-asserted) + +`context_digest = SHA-256(canonical_context_bytes)` where the server computes +the bytes itself — the client never sends a digest to trust: + +- **`request` mode** — the exact `COVEN-MEMORY/1` canonical request bytes the + possession key signed for this same request: + `canonical_request(method, path_and_query, timestamp, nonce, body_digest)` + (`crates/coven-cli/src/mobile_memory/auth.rs:34-51`). The proof therefore + covers byte-for-byte the same request the possession signature covers; there + is no gap in which one can be swapped. +- **`action` mode** — `DeviceActionIntent::canonical_bytes()` + (`crates/coven-cli/src/mobile_memory/grant.rs:266-284`, `COVEN-ACTION/1`), + recomputed by the server from the submitted intent. The intent already binds + scope, operation, target, effect digest, nonce, and its own window. + +The `context_mode` field makes the two domains non-substitutable. + +### Validity + +- `expires_at - issued_at ≤ 120` seconds (recommended default 60; the vector + below uses 60). For `action` mode, additionally + `proof.expires_at ≤ intent.expires_at` — the proof window is nested inside + the intent window (intent lifetime is capped at 300 s, + `grant.rs:12`). +- Server clock tolerance: none beyond the checks themselves; `issued_at ≤ now ≤ + expires_at` with `issued_at` within the challenge's own validity window. + +## Challenge issuance and replay protection + +A proof MUST cover a server-issued, single-use challenge: + +- **Issuance.** New possession-authenticated mobile route + `POST /api/v1/mobile/assurance/challenge` (protected exactly like today's + routes: `x-coven-protocol: 1` + `COVEN-MEMORY/1` headers, + `crates/coven-cli/src/mobile_memory/gateway.rs:677-717`). Response envelope + carries `{ "challenge": , "expiresAt": }`. +- **Binding.** The stored record binds `device_id`, `grant_id`, + `revocation_epoch`, `expires_at = issued + ≤120 s`, and `spent = false`. + Grant rotation or revocation immediately invalidates outstanding challenges. +- **Consumption.** Verification atomically flips `spent` under the store lock + before returning success (same single-winner pattern as + `auth.rs::insert_nonce`, `auth.rs:198-217`, including the bounded-map + discipline). A failed signature does not spend the challenge; a successful + one does. Two concurrent submissions of the same proof: exactly one wins. +- **Storage.** `~/.coven/mobile/assurance-challenges.json` — separate from the + request-nonce replay cache in `MobileAuthenticator` (`auth.rs:105`), which + keys `(device_id, request_nonce)` and serves `COVEN-MEMORY/1` replay + protection. Challenge state is persisted (not just in-memory) so a daemon + restart cannot resurrect a spent challenge inside a live proof window. +- **Why a server challenge.** The threat model lists "attacker with temporary + access to an unlocked endpoint" + (`docs/security/mobile-device-pairing-threat-model.md`). A server challenge + bounds pre-minting to one proof per challenge with a ≤120 s horizon — proofs + cannot be banked offline in bulk while the phone is unlocked. Alternative + considered: client-generated nonce + server replay cache (the + `insert_nonce` pattern). Rejected as the default: it permits offline + pre-minting of unlimited proofs while the device is unlocked. It remains a + viable fallback if a zero-round-trip flow is ever required; if adopted, its + replay store MUST still be a separate cache from the request-nonce cache. + +### Verification procedure (normative order) + +Given a possession-authenticated request carrying step-up proof headers: + +1. **Possession first.** The ordinary `COVEN-MEMORY/1` verification must have + succeeded (`gateway.rs:697-717`). A step-up proof is never evaluated for an + unauthenticated or revoked device. +2. **Load the enrolled authorization key** for `device_id` from the + authorization-key store. Absent or revoked → possession-only (or fail + closed, step 8). +3. **Challenge check.** Look up the presented challenge: must exist, belong to + this `device_id` and the grant's current `revocation_epoch`, be unspent, and + be unexpired. Spent/expired/unknown → proof invalid. +4. **Recompute the context digest** from the actual request bytes + (`canonical_request`, `auth.rs:34-51`) or the submitted + `DeviceActionIntent` (`grant.rs:266`). Never trust a client-supplied digest. +5. **Rebuild the canonical bytes** from: registry device id, grant id and + `revocation_epoch` (`registry.rs authorization_record`), the enrolled + `authorization_key_id`, the presented mode/`issued_at`/`expires_at`/ + `requested_assurance`, and the server-recomputed values above. +6. **Verify the signature** against the enrolled step-up public key (DER + P-256, `verify_signature` pattern, `auth.rs:239-260`). +7. **Compute effective assurance** (below) and pass it to + `DeviceGrant::authorize` (`grant.rs:157-194`). +8. **Fail closed:** any failure → effective assurance is `Possession`. The + grant's own policy then decides: if the requested scope requires stronger + assurance (`require_fresh_user_verification_for` or `minimum_assurance`, + `grant.rs:171-192`), `authorize` returns `GrantError::AssuranceRequired` + and the request is rejected — it is not silently downgraded to a weaker + success. + +### Effective assurance (server-side, never client-asserted) + +```text +effective = Possession # default +if the proof verifies end-to-end: + ceiling = class_ceiling(enrolled_key.assurance_class) + requested = parse(requested_assurance) # claim in the signed bytes + effective = min(requested, ceiling) # server caps the claim +# then, exactly as today: +DeviceGrant::authorize(required_scope, effective, now) # grant.rs:157-194 +``` + +- The client's requested level is inside the signature, so relabeling it after + the fact is a signature failure; the server caps it by the enrolled key's + declared class, so even a valid signature cannot mint a class the key's + platform policy does not support ("possession proof cannot be relabeled as + biometric proof" — a possession-key signature never verifies under the + step-up public key, and `COVEN-MEMORY/1` bytes are not `COVEN-ASSURANCE/1` + bytes). +- The existing `Ord` on `AssuranceLevel` (`grant.rs:50-58`) already gives the + right lattice: a `FreshBiometric` proof satisfies a + `FreshUserVerification` requirement. +- `RecentUserVerification` is not cryptographically provable (no ceremony to + sign) and stays a server-side policy notion, out of scope for proofs. +- `ensure_still_active` re-checks must reuse the same effective assurance + value for the request (the current re-check passes `Possession`, + `auth.rs:174-181`; with step-up it must not fail a legitimately + step-up-authorized request). `VerifiedMobileDevice` + (`auth.rs:96-101`) gains an `effective_assurance` field for that purpose. + +### Transport (wire shape) + +Six flat headers, mirroring the existing `x-coven-*` convention +(`gateway.rs:917-929`): + +| Header | Value | +| --- | --- | +| `x-coven-assurance-context` | `request` \| `action` | +| `x-coven-assurance-challenge` | base64url of the 32-byte challenge | +| `x-coven-assurance-issued-at` | RFC 3339 UTC, millis | +| `x-coven-assurance-expires-at` | RFC 3339 UTC, millis | +| `x-coven-assurance-level` | `fresh_user_verification` \| `fresh_biometric` | +| `x-coven-assurance-signature` | base64url DER ECDSA | + +For `action` mode the same headers ride on the request that submits the +`DeviceActionIntent`; the server hashes the intent from that request body. The +action-submission route itself is the #786 exact-action work and is out of +scope here; the proof contract is independent of which route carries it. + +## Rotation and revocation + +Rotation and revocation of the authorization key never change familiar or root +identity (identity separation table, `docs/design/mobile-device-trust.md`, +"Identity and credential separation"): + +- **Rotate** — enroll a replacement key for the device with a new `keyEpoch` + (same transcript-bound ceremony as initial enrollment, plus proof of + possession of the *old* step-up key or fresh possession-key authentication + per owner policy). Exactly one active key per device; the previous record is + retained with `revokedAt` for audit, and outstanding challenges are + invalidated. +- **Revoke the step-up key** — device falls back to possession-only; grants + requiring more fail closed. Does not revoke the device. +- **Revoke the device** — `registry.revoke` (`registry.rs:235-257`) cascades: + possession and step-up both die; the revocation epoch bump invalidates + outstanding challenges. +- **Compromise semantics** — possession-key compromise: revoke the device. + Step-up-key compromise: revoke the key (and re-enroll); a relay/account + compromise mints nothing — assurance requires a signature from a + policy-protected key the attacker never holds. + +## State machines + +```text +Authorization key: absent → enrolled → rotated (epoch+1) → … + ↘ revoked (per-key or device cascade) + +Challenge: issued ── verified+consumed (atomic) ──▶ spent + │ expires_at passed + ▼ + expired (pruned opportunistically, bounded store) + +Proof verification: possession OK + → load key → check challenge → recompute digest → rebuild bytes + → verify signature → check window/lifetime + → effective = min(claimed, class ceiling) + → DeviceGrant::authorize(required_scope, effective, now) + any failure ⇒ effective = Possession; grant policy then decides + (AssuranceRequired ⇒ reject; otherwise proceed) +``` + +## Platform mapping + +### iOS + +- Possession key: Secure Enclave P-256 (`kSecAttrTokenIDSecureEnclave`), no + per-request biometric prompt — keeps reconnect frictionless. +- Step-up key: separate Secure Enclave P-256 key with + `SecAccessControl` `.privateKeyUsage` plus the policy for its class: + `.biometryCurrentSet` (+ `LAContext` `deviceOwnerAuthenticationWithBiometrics` + for the biometric-only ceremony when policy demands biometric rather than + passcode fallback), or `.devicePasscode` for the device-credential class. +- Signature algorithm: the X9.62 message-signature member of the + `kSecKeyAlgorithm` family, `ECDSASignatureMessageX962SHA256` (the two halves + concatenate into the full constant) — DER output, matching the server's + `Signature::from_der` path (`auth.rs:252-259`). + +### Android + +- Hardware-backed Keystore P-256 where available; + `setUserAuthenticationRequired(true)` and + `setUserAuthenticationParameters(...)` / `setUserAuthenticationParameters(…, AUTH_BIOMETRIC_STRONG)` for the step-up key; + `setInvalidatedByBiometricEnrollment(true)` to keep "current biometry" honest. +- `BiometricPrompt` with `BIOMETRIC_STRONG` authenticators for + `biometric_only`; `DEVICE_CREDENTIAL`-only flows enroll as the separate + `device_credential` class — never labeled `FreshBiometric`. +- `Signature.getInstance("SHA256withECDSA")` produces DER — same wire format. + +Both platforms keep the possession key prompt-free and the step-up key +prompt-gated; only signatures cross the trust boundary. + +## Security invariants → mechanism + +| Invariant (issue) | Mechanism | +| --- | --- | +| Biometric material never leaves the OS subsystem | Only P-256 signatures transit; no biometric field exists anywhere in the protocol | +| Possession proof cannot be relabeled as biometric proof | Server computes effective assurance from a verified step-up signature; possession key ≠ step-up key; `COVEN-MEMORY/1` / `COVEN-ACTION/1` / `COVEN-ASSURANCE/1` domains are disjoint | +| Proof for action A cannot authorize action B | `context_digest` covers the exact canonical request/intent bytes, recomputed server-side | +| Proof for device/grant A cannot authorize device/grant B | `device_id` + `grant_id` (+ `revocation_epoch`) inside the signed bytes | +| Relay/account compromise cannot mint fresh-biometric assurance | Assurance requires the enrolled step-up private key; relays never hold it | +| Replayed proofs fail closed | Single-use server-issued challenge, atomically consumed; ≤120 s window; independent of request nonces | +| Self-hosted/unattested clients remain possible | Step-up is optional per grant/owner policy; possession always remains a valid baseline (`DeviceGrantRestrictions` defaults, `grant.rs:80-87`) | + +## Portable golden vector + +Synthetic, no live credential — same convention as +`crates/coven-cli/tests/fixtures/mobile-pairing-v2/transcript-vector.json` and +`crates/coven-cli/tests/fixtures/mobile-memory-v1/signature-vector.json`: every +byte string is documented as hex (the wire encodes challenges, digests, and +signatures as unpadded base64url; the vector stores raw bytes so any +implementation can reproduce them). An implementation PR adds this as +`crates/coven-cli/tests/fixtures/mobile-assurance-v1/assurance-vector.json`; +Swift/Android implementations must reproduce `canonicalProofBytesHex` exactly. +ECDSA P-256 signatures are randomized (`k` is per-signature; neither Secure +Enclave nor Android Keystore exposes deterministic RFC 6979 signing), so +implementations are not expected to reproduce `signatureDERHex` byte-for-byte: +they must **verify** it over `canonicalProofBytesHex` with +`stepUpPublicKeyX963Hex`, and their own signatures must verify the same way. + +```json +{ + "fixtureNotice": "SYNTHETIC TEST KEY — NOT A CREDENTIAL", + "deviceId": "00000000-0000-0000-0000-000000000001", + "grantId": "a67b9d68-b8c8-5a84-923f-3158b93ee261", + "revocationEpoch": 0, + "stepUpPrivateKeyScalarHex": "0202020202020202020202020202020202020202020202020202020202020202", + "stepUpPublicKeyX963Hex": "04550f471003f3df97c3df506ac797f6721fb1a1fb7b8f6f83d224498a65c88e24136093d7012e509a73715cbd0b00a3cc0ff4b5c01b3ffa196ab1fb327036b8e6", + "authorizationKeyIdHex": "fe00ab0f341901f863a49160cf554588d6928282d531b799addc4123f45ce85a", + "contextMode": "request", + "protectedCanonicalRequestHex": "434f56454e2d4d454d4f52592f310a4745540a2f6170692f76312f6d6f62696c652f6d656d6f72792f6f766572766965770a313738353332363430300a414141414141414141414141414141414141414141414141414141414141414141414141414141414141410a3437444551706a38484253612d5f54496d572d354a4365755165526b6d354e4d704a575a47336853754655", + "contextDigestHex": "dde33200a4ad41fa4d11d7f81713f74cdf0ce3971d6a5e5003b43fa789bdc12f", + "challengeHex": "0909090909090909090909090909090909090909090909090909090909090909", + "issuedAt": "2026-07-29T12:00:00.000Z", + "expiresAt": "2026-07-29T12:01:00.000Z", + "requestedAssurance": "fresh_biometric", + "canonicalProofBytesHex": "434f56454e2d4153535552414e43452f3100000000100000000000000000000000000000000100000010a67b9d68b8c85a84923f3158b93ee2610000000800000000000000000000002b5f674372447a515a4166686a704a46677a315646694e6153676f4c564d62655a72647842495f526336466f000000077265717565737400000020dde33200a4ad41fa4d11d7f81713f74cdf0ce3971d6a5e5003b43fa789bdc12f00000020090909090909090909090909090909090909090909090909090909090909090900000018323032362d30372d32395431323a30303a30302e3030305a00000018323032362d30372d32395431323a30313a30302e3030305a0000000f66726573685f62696f6d6574726963", + "signatureDERHex": "3044022035a34c02382512c29d05de88ceaff21b2141d60b592bc4ab2cc511bad976ab3702202f28af09e7c0343606682f927f349e7abf0d31b94346a8b73f0d88a7f4cb2c0c" +} +``` + +`protectedCanonicalRequestHex` decodes to the `COVEN-MEMORY/1` canonical +request from the existing memory-v1 fixture +(`GET /api/v1/mobile/memory/overview`, timestamp `1785326400`, the all-zero +nonce, and the SHA-256 body digest of the empty payload); its SHA-256 is +`contextDigestHex`. Notes for implementers: `grantId` is +`Uuid::new_v5(device_id, "coven-device-grant-v1")` (`grant.rs:114`); the +possession key of the protecting device is the existing memory-v1 vector key +(`signature-vector.json`, scalar `0x0101…`); the step-up key scalar is +`0x02`-repeated (the Rust tests' `public_key(seed)` convention). The signature +is DER-encoded ECDSA/P-256/SHA-256 over the exact `canonicalProofBytesHex`. + +### Schemas + +```typescript +// Client-facing assurance classes (declared at enrollment; platform policy). +type AssuranceClass = "biometric_only" | "user_verification" | "device_credential"; + +// Claim inside the signed bytes; server caps it by class ceiling. +type RequestedAssurance = "fresh_user_verification" | "fresh_biometric"; + +type AssuranceContextMode = "request" | "action"; + +interface StepUpAuthorizationEnrollment { // optional MobilePairingRequest member + publicKey: string; // canonical P-256 X9.63, base64url + assuranceClass: AssuranceClass; + enrollmentSignature?: string; // base64url DER over "COVEN-STEPUP-ENROLL/1" || transcript hash +} + +interface AssuranceProofHeaders { + "x-coven-assurance-context": AssuranceContextMode; + "x-coven-assurance-challenge": string; // base64url 32B, server-issued + "x-coven-assurance-issued-at": string; // RFC 3339 UTC, millis + "x-coven-assurance-expires-at": string; // RFC 3339 UTC, millis + "x-coven-assurance-level": RequestedAssurance; + "x-coven-assurance-signature": string; // base64url DER +} + +interface AssuranceChallenge { + challenge: string; // base64url 32B + expiresAt: string; // RFC 3339 +} + +interface DeviceAuthorizationKeyRecord { + deviceId: string; // UUID v4 + publicKeyX963: string; // canonical P-256 X9.63, base64url + subjectKeyId: string; // base64url SHA-256 over publicKeyX963 + assuranceClass: AssuranceClass; + enrolledAt: string; // RFC 3339 + revokedAt: string | null; + keyEpoch: number; // monotonic per device +} +``` + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "COVEN-ASSURANCE/1 golden vector", + "type": "object", + "additionalProperties": false, + "required": ["fixtureNotice", "deviceId", "grantId", "revocationEpoch", + "stepUpPrivateKeyScalarHex", "stepUpPublicKeyX963Hex", + "authorizationKeyIdHex", "contextMode", + "protectedCanonicalRequestHex", "contextDigestHex", + "challengeHex", "issuedAt", "expiresAt", "requestedAssurance", + "canonicalProofBytesHex", "signatureDERHex"], + "properties": { + "fixtureNotice": { "type": "string" }, + "deviceId": { "type": "string", "format": "uuid" }, + "grantId": { "type": "string", "format": "uuid" }, + "revocationEpoch": { "type": "integer", "minimum": 0 }, + "stepUpPrivateKeyScalarHex": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "stepUpPublicKeyX963Hex": { "type": "string", "pattern": "^04[0-9a-f]{128}$" }, + "authorizationKeyIdHex": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "contextMode": { "enum": ["request", "action"] }, + "protectedCanonicalRequestHex": { "type": "string", "pattern": "^[0-9a-f]+$" }, + "contextDigestHex": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "challengeHex": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "issuedAt": { "type": "string", "format": "date-time" }, + "expiresAt": { "type": "string", "format": "date-time" }, + "requestedAssurance": { "enum": ["fresh_user_verification", "fresh_biometric"] }, + "canonicalProofBytesHex": { "type": "string", "pattern": "^[0-9a-f]+$" }, + "signatureDERHex": { "type": "string", "pattern": "^30[0-9a-f]+$" } + } +} +``` + +## Requirement checklist (issue → section) + +| Issue requirement | Section | +| --- | --- | +| Bind optional step-up key + declared class into pairing-v2 transcript before grant issuance | [Enrollment](#enrollment-binding-the-step-up-key-into-the-pairing-v2-transcript) | +| Store authorization-key metadata separately from possession key | [Storage](#storage-separate-from-the-possession-key) (separate `authorization-keys.json`) | +| Canonical `COVEN-ASSURANCE/1` proof bytes | [Canonical proof bytes](#canonical-proof-bytes-coven-assurance1) | +| Verify signatures against enrolled key; never trust client assurance | [Verification procedure](#verification-procedure-normative-order), [Effective assurance](#effective-assurance-server-side-never-client-asserted) | +| Short validity + replay protection independent of request nonces | [Validity](#validity), [Challenge issuance](#challenge-issuance-and-replay-protection) | +| Effective assurance computed server-side, passed to `DeviceGrant::authorize` | [Effective assurance](#effective-assurance-server-side-never-client-asserted) | +| Absent/invalid/expired/replayed → possession-only or fail closed | [Verification procedure](#verification-procedure-normative-order) step 8 | +| Key rotation/revocation without touching familiar/root identity | [Rotation and revocation](#rotation-and-revocation) | +| Portable vectors for Swift/Android | [Portable golden vector](#portable-golden-vector) | +| Acceptance: `FreshBiometric` grant succeeds only with fresh signature from the enrolled biometric-policy key over the exact context | Verification steps 2–7 + the `biometric_only` ceiling mapping | + +## Recommendations and alternatives considered + +| Decision | Recommendation | Alternatives considered | +| --- | --- | --- | +| Transcript extension shape | Append optional fields to the `COVEN-PAIR/2` transcript when present | Bump to a `COVEN-PAIR/3` domain: cleaner versioning, but forks the phrase derivation for no security gain; the conditional fields are backward compatible (digest unchanged when absent) and both endpoints always agree on the request they hold | +| Server-issued challenge vs client nonce | Server-issued challenge (required) | Client nonce + replay cache: no new route/round trip, but permits bulk pre-minting while a device is unlocked; keep as documented fallback | +| Storage of authorization keys | Separate `authorization-keys.json` | Registry v3 with a nested field: entangles rotation with device-record migrations (`registry.rs:19` is at version 2 today) for no benefit | +| Error surfacing | Add `AssuranceRequired` to `MobileErrorCode` (`contract.rs:53-74`); today `auth.rs:150` maps every `authorize` failure to `DeviceRevoked`, which misreports assurance failures as revocation | Keep mapping into `DeviceRevoked`: breaks clients' ability to prompt for step-up | +| Enrollment-time possession proof of the step-up key | Require a signature over the transcript hash at enrollment (exercises the declared platform gate once, at pairing) | Skip it: pairing stays prompt-free, but an unusable/mismatched-policy key is discovered only at first sensitive use (still fail-closed) | +| New audit events | `StepUpVerified` / `StepUpRejected` in `MobileAuditEvent` (`audit.rs:19-28`) | Reuse `AuthenticationRejected`: loses the distinction operators need to tune step-up friction | +| Implementation home | `crates/coven-cli/src/mobile_memory/assurance.rs` alongside `auth.rs`/`grant.rs` | A new crate: premature until the #787 relay session work forces extraction (`docs/design/mobile-device-trust.md`, "Authority boundary") | + +## Implementation plan (follow-up PRs) + +1. `assurance.rs` — canonical bytes, challenge store, verification, effective + assurance; adversarial tests (tamper every field, replay, cross-device, + cross-grant, expired, absent-key, possession-key-as-step-up). +2. Pairing extension + authorization-key store + cascade revocation. +3. Gateway plumbing: headers, challenge route, error codes, audit events, + `ensure_still_active` effective-assurance reuse. +4. Golden-vector fixture + conformance test; platform notes validated against + the iOS/Android mappings above. + +Every implementation PR in this track passes the repository gates (`cargo fmt +--check`, `cargo clippy --workspace --all-targets -- -D warnings`, +`cargo test --workspace --locked`, secret scan, privacy guard — see +`AGENTS.md`) and may not weaken existing v1 privacy, replay, revocation, +canonicalization, or audit guarantees. diff --git a/docs/index.md b/docs/index.md index 955b1539..8858eed6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,3 +28,5 @@ implementation: implementation decisions, plans, and historical records. See [`DOCS-MAINTENANCE.md`](DOCS-MAINTENANCE.md) before adding or moving a page. +Its public-doc directory boundary defines which directories may contain only +canonical pointers or source-adjacent exceptions. diff --git a/docs/install/cargo.md b/docs/install/cargo.md index 9b2bcc56..8033c1d4 100644 --- a/docs/install/cargo.md +++ b/docs/install/cargo.md @@ -1,73 +1,10 @@ --- -summary: "Build and install Coven directly from crates.io with cargo." -read_when: - - You prefer building Rust binaries yourself title: "Install via cargo" -description: "Install Coven from source with cargo: build the Rust daemon and CLI, drop the binary on PATH, and verify the install with coven doctor." +description: "Pointer to the canonical Coven install guidance." --- -# Install via cargo +Canonical install guidance, including the source-checkout route: +**https://docs.opencoven.ai/docs/guide/install** -Use this route when you want to build the Rust CLI yourself. For most users, [Install via npm](/install/npm) is the shorter path. - -## From a checkout - -```sh -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build -p coven-cli --release -mkdir -p "$HOME/.local/bin" -cp target/release/coven "$HOME/.local/bin/coven" -coven doctor -``` - -On Windows, copy `target\release\coven.exe` to a directory on `PATH`, then open a new terminal and run: - -```powershell -coven doctor -``` - -## Running without copying - -From the repository checkout: - -```sh -cargo run -p coven-cli -- doctor -cargo run -p coven-cli -- daemon start -cargo run -p coven-cli -- run codex "describe this repo" -``` - -## Harness setup - -Coven still needs a harness CLI for real agent work: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -Run `coven doctor` after installing or changing harness auth. - -## Updating a cargo-built binary - -```sh -cd /path/to/coven -git pull --ff-only -cargo build -p coven-cli --release -cp target/release/coven "$HOME/.local/bin/coven" -coven daemon restart -coven doctor -``` - -Use the Windows binary name when updating a Windows install. - -## Related - -- [Install from source](/install/from-source) -- [COVEN_HOME layout](/daemon/coven-home) -- [Updating Coven](/install/updating) +Contributor build and verification instructions remain in +[`CONTRIBUTING.md`](../../CONTRIBUTING.md). diff --git a/docs/install/coven-home.md b/docs/install/coven-home.md index c4237cdc..45ea51ac 100644 --- a/docs/install/coven-home.md +++ b/docs/install/coven-home.md @@ -1,35 +1,10 @@ --- -summary: "What lives under COVEN_HOME and how to relocate it." -read_when: - - Customizing where Coven keeps state title: "COVEN_HOME layout" -description: "How to lay out COVEN_HOME on a fresh install: the SQLite ledger, append-only event log, sockets, and per-session directories the daemon expects." +description: "Pointer to the canonical daemon configuration guidance." --- -`COVEN_HOME` is Coven's local state directory. If you do not set it, Coven uses `/.coven`. +Canonical daemon configuration guidance, including relocating `COVEN_HOME`: +**https://docs.opencoven.ai/docs/daemon/configuration** -Coven resolves `` from the normal platform home directory. On Windows this includes `USERPROFILE` and `HOMEDRIVE` + `HOMEPATH`, so `coven doctor` should not require a Unix-style `HOME` variable. - -The directory contains: - -- `coven.sqlite3` — the local session ledger; -- `daemon.json` and daemon sockets/pipes — local daemon metadata; -- `sessions/` and event logs — per-session artifacts; -- `familiars.toml` — optional local familiar declarations; -- `adapters/` — trusted local harness adapter manifests, including recipes created by `coven adapter install `. - -Override it only when you want Coven state somewhere else: - -```sh -export COVEN_HOME="$HOME/.coven" -coven doctor -``` - -PowerShell: - -```powershell -$env:COVEN_HOME="$env:USERPROFILE\.coven" -coven doctor -``` - -See [Install overview](/install/index) for the broader install flow. +The normative source-adjacent state-layout contract remains in +[`../daemon/coven-home.md`](../daemon/coven-home.md). diff --git a/docs/install/docker.md b/docs/install/docker.md index 670142fe..656796af 100644 --- a/docs/install/docker.md +++ b/docs/install/docker.md @@ -1,88 +1,7 @@ --- -summary: "Run the Coven daemon inside a Docker container." -read_when: - - Containerizing Coven for CI or homelab use title: "Docker" -description: "Run Coven in Docker: a containerized daemon plus harness CLIs, with bind mounts for COVEN_HOME and the project root for each session." +description: "Pointer to the canonical Coven deployment guidance." --- -# Docker - -Docker is an advanced setup path. This repository does not define a canonical Coven application image in the install docs; build your own image when you need container isolation for CI, demos, or a homelab. - -Use native installs for normal workstation use: [macOS](/install/macos), [Linux](/install/linux), [Windows](/install/windows), or [WSL2](/install/wsl2). - -## Minimal source-built image - -Create a Dockerfile in your own deployment repo: - -```Dockerfile -FROM rust:1-bookworm AS build -WORKDIR /src -COPY . . -RUN cargo build -p coven-cli --release - -FROM debian:bookworm-slim -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates nodejs npm git \ - && rm -rf /var/lib/apt/lists/* -COPY --from=build /src/target/release/coven /usr/local/bin/coven -ENV COVEN_HOME=/var/lib/coven -WORKDIR /workspace -CMD ["coven", "doctor"] -``` - -Build it from a Coven source checkout: - -```sh -docker build -t coven-local . -``` - -## Run with explicit mounts - -```sh -mkdir -p "$HOME/.coven-container" -docker run --rm -it \ - -e COVEN_HOME=/var/lib/coven \ - -v "$HOME/.coven-container:/var/lib/coven" \ - -v "$PWD:/workspace" \ - -w /workspace \ - coven-local coven doctor -``` - -For real harness work, the container must also contain and authenticate the harness CLI. Provider credentials remain owned by that harness, not by Coven. - -## First container session - -```sh -docker run --rm -it \ - -e COVEN_HOME=/var/lib/coven \ - -v "$HOME/.coven-container:/var/lib/coven" \ - -v "$PWD:/workspace" \ - -w /workspace \ - coven-local coven daemon start -``` - -Then run a session in the same mounted environment: - -```sh -docker run --rm -it \ - -e COVEN_HOME=/var/lib/coven \ - -v "$HOME/.coven-container:/var/lib/coven" \ - -v "$PWD:/workspace" \ - -w /workspace \ - coven-local coven run codex "describe this repo" -``` - -## Notes - -- Bind-mount `COVEN_HOME` if you want session history to survive container exits. -- Bind-mount the project root you intend to run in. -- Do not expose the Coven daemon socket over TCP by default. -- Run `coven doctor` inside the same image and environment that will launch sessions. - -## Related - -- [Headless server](/install/headless-server) -- [Podman](/install/podman) -- [COVEN_HOME layout](/daemon/coven-home) +Canonical deployment guidance, including manual container integrations: +**https://docs.opencoven.ai/docs/guide/deployments** diff --git a/docs/install/from-source.md b/docs/install/from-source.md index af4b9265..c7f25016 100644 --- a/docs/install/from-source.md +++ b/docs/install/from-source.md @@ -1,94 +1,10 @@ --- -summary: "Clone the repo and build coven with cargo." -read_when: - - Developing Coven or running unreleased changes title: "Install from source" -description: "Build and install Coven from source: clone the repo, build the Rust daemon and CLI with cargo, and drop the binary on PATH for daily use." +description: "Pointer to the canonical Coven install guidance." --- -# Install from source +Canonical install guidance, including the source-checkout route: +**https://docs.opencoven.ai/docs/guide/install** -Use a source checkout when you are contributing to Coven, testing unreleased changes, or running on a platform where the npm native package is not available. - -## Requirements - -- Rust stable. -- Git. -- A supported shell for your platform. -- At least one harness CLI if you want to launch real sessions. - -## Build and verify - -```sh -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build --workspace -cargo run -p coven-cli -- doctor -``` - -Run the binary through Cargo while developing: - -```sh -cargo run -p coven-cli -- daemon start -cargo run -p coven-cli -- run codex "describe this repo" -cargo run -p coven-cli -- sessions -``` - -## Install the built binary - -After building, copy the release binary to a directory on `PATH`: - -```sh -cargo build -p coven-cli --release -mkdir -p "$HOME/.local/bin" -cp target/release/coven "$HOME/.local/bin/coven" -coven doctor -``` - -On Windows, copy `target\release\coven.exe` to a directory on `PATH`. - -## Harness setup - -Install and authenticate a harness in the same shell environment: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -Then run: - -```sh -coven doctor -``` - -## Development checks - -Before changing daemon, session, attach, or ritual behavior, run the workspace -checks described in [CONTRIBUTING.md](../../CONTRIBUTING.md) and -[Documentation maintenance](/DOCS-MAINTENANCE). - -For docs-only install work: - -```sh -python scripts/check-secrets.py -git diff --check -``` - -For code work: - -```sh -cargo fmt --check -cargo test --workspace --locked -``` - -## Related - -- [Install via npm](/install/npm) -- [Install via cargo](/install/cargo) -- [Linux install](/install/linux) +Contributor build and verification instructions remain in +[`CONTRIBUTING.md`](../../CONTRIBUTING.md). diff --git a/docs/install/headless-server.md b/docs/install/headless-server.md index e5adbe6a..53b91d20 100644 --- a/docs/install/headless-server.md +++ b/docs/install/headless-server.md @@ -1,99 +1,7 @@ --- -summary: "Install Coven on a headless Linux server with systemd." -read_when: - - Running Coven without a desktop title: "Headless server" -description: "Install Coven on a headless server: daemon-only setup, no TUI, with systemd or launchd supervision and remote access through SSH tunnels." +description: "Pointer to the canonical Coven deployment guidance." --- -# Headless server - -On a headless host, install Coven the same way as Linux, then operate it through SSH and explicit daemon commands. - -```sh -npm install -g @opencoven/cli -coven doctor -``` - -If the npm native package is not available on the server distribution, use [Install from source](/install/from-source). - -## Server layout - -Choose a dedicated user and keep state under that user's home directory: - -```sh -export COVEN_HOME="$HOME/.coven" -mkdir -p "$COVEN_HOME" -coven doctor -``` - -Keep `COVEN_HOME` on a local disk owned by the service user. Avoid sharing one state directory between multiple Unix users. - -## Harness setup - -Install and authenticate the harness CLI as the same user that will run Coven: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -Run: - -```sh -coven doctor -``` - -## Daemon lifecycle - -Start and inspect the daemon over SSH: - -```sh -coven daemon start -coven daemon status -``` - -Restart after updates or environment changes: - -```sh -coven daemon restart -coven doctor -``` - -Stop it before changing ownership, moving `COVEN_HOME`, or rebuilding the binary: - -```sh -coven daemon stop -``` - -## First remote session - -```sh -cd /path/to/project -coven run codex "summarize the current branch" -coven sessions -``` - -Use `coven attach ` to follow a live session from a later SSH connection. - -## Supervisor note - -The CLI daemon commands are the stable operational surface. If you wrap them with systemd, launchd, tmux, or another supervisor, keep the environment explicit: - -```sh -COVEN_HOME="$HOME/.coven" coven daemon start -``` - -Make sure the supervisor has the same `PATH` that exposes `coven`, `codex`, and `claude`. - -## Related - -- [Linux install](/install/linux) -- [COVEN_HOME layout](/daemon/coven-home) -- [Daemon lifecycle](/daemon/lifecycle) -- [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) +Canonical headless and cloud-host deployment guidance: +**https://docs.opencoven.ai/docs/guide/deployments** diff --git a/docs/install/index.md b/docs/install/index.md index 832dbae3..7343cdda 100644 --- a/docs/install/index.md +++ b/docs/install/index.md @@ -1,123 +1,13 @@ --- -summary: "All ways to install Coven on a workstation or server." -read_when: - - Choosing how to install Coven title: "Install overview" -description: "Install overview for Coven: pick a platform and a method (npm, cargo, Docker, Nix, source) and verify the daemon with coven doctor." +description: "Pointer to the canonical Coven install guidance." --- -# Install overview +Canonical install guidance: **https://docs.opencoven.ai/docs/guide/install** -Use this page to pick the right Coven install path, then verify the setup the same way on every platform: +Platform notes and service-manager guidance live at +**https://docs.opencoven.ai/docs/guide/platforms** and +**https://docs.opencoven.ai/docs/guide/deployments**. -```sh -coven doctor -coven daemon start -coven daemon status -``` - -After the daemon is running, launch the first session from a project directory: - -```sh -cd /path/to/project -coven run codex "describe this repo" -``` - -Or use Claude Code: - -```sh -coven run claude "describe this repo" -``` - -## Choose your route - -| Environment | Recommended path | Notes | -| --- | --- | --- | -| macOS Apple Silicon | [npm wrapper](/install/npm) or [macOS install](/install/macos) | Uses the universal `@opencoven/cli` package and the native macOS package. | -| Intel macOS x64 | [npm wrapper](/install/npm) or [macOS install](/install/macos) | Uses the universal `@opencoven/cli` package and the Intel native macOS package. | -| glibc-based Linux x64 | [npm wrapper](/install/npm) or [Linux install](/install/linux) | Alpine/musl is not part of the npm binary target today; build from source there. | -| Windows x64 | [Windows install](/install/windows) | Run Coven and harness CLIs from the same PowerShell, Windows Terminal, or native Windows shell. | -| WSL2 | [WSL2 install](/install/wsl2) | Treat WSL2 as a Linux environment and keep `COVEN_HOME` on the WSL filesystem. | -| Contributor checkout | [Install from source](/install/from-source) | Use this for unreleased changes and local development. | -| Rust-first install | [Install via cargo](/install/cargo) | Build the Rust CLI yourself and put the binary on `PATH`. | -| Server or automation host | [Headless server](/install/headless-server) | Use daemon commands over SSH or supervisor-managed shells. | -| macOS background service | [launchd service](/install/launchd) | Optional user-agent wrapper around `coven daemon start`. | -| Linux background service | [systemd unit](/install/systemd) | Optional user service wrapper around `coven daemon start`. | -| Raspberry Pi | [Raspberry Pi](/install/raspberry-pi) | Build from source on arm64 and keep state on persistent storage. | -| Container experiments | [Docker](/install/docker) or [Podman](/install/podman) | Build your own image; bind-mount state and project roots explicitly. | -| Nix-managed shell | [Nix](/install/nix) | Use Nix to pin prerequisites, then build or run Coven inside that shell. | - -## Baseline requirements - -- Node.js 18+ for the npm wrapper path. -- Git for source checkouts and project-root detection. -- Rust stable only when building from source or with cargo. -- At least one externally installed harness CLI with a Coven built-in adapter - on `PATH`: Codex, Claude Code, or GitHub Copilot CLI. - -Install and authenticate a harness before expecting `coven run` to launch work: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -```sh -npm install -g @github/copilot -copilot login -``` - -Then run: - -```sh -coven doctor -``` - -`doctor` reports store readiness, daemon/local IPC status, project-root hints, -and whether supported harness CLIs are available from the same shell. - -## State directory - -By default, Coven stores local state under `/.coven`. Override it only when you need a separate state root: - -```sh -export COVEN_HOME="$HOME/.coven" -coven doctor -``` - -PowerShell: - -```powershell -$env:COVEN_HOME="$env:USERPROFILE\.coven" -coven doctor -``` - -See [COVEN_HOME layout](/daemon/coven-home) for what lives inside that directory. - -## Common verification loop - -Use the same loop after install, after updates, and after changing harness auth: - -```sh -coven --version -coven doctor -coven daemon restart -coven daemon status -cd /path/to/project -coven run codex "say hello from Coven" -coven sessions -``` - -If `doctor` reports a missing harness after installation, open a new terminal so `PATH` refreshes, then run `coven doctor` again from the shell where you will use Coven. - -## Related - -- [Getting started](https://docs.opencoven.ai/docs/guide/getting-started) -- [Quickstart](/start/quickstart) -- [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) -- [CLI reference](/reference/cli) +The source-adjacent `COVEN_HOME` state-layout contract remains in +[`../daemon/coven-home.md`](../daemon/coven-home.md). diff --git a/docs/install/launchd.md b/docs/install/launchd.md index 2cf44fbe..db699e2f 100644 --- a/docs/install/launchd.md +++ b/docs/install/launchd.md @@ -1,92 +1,9 @@ --- -summary: "Run the Coven daemon as a launchd user agent on macOS." -read_when: - - Keeping the daemon up on macOS title: "launchd service" -description: "Run the Coven daemon under launchd on macOS: write a plist, load it, and have launchctl supervise the daemon across reboots and crashes." +description: "Pointer to the canonical Coven service-manager guidance." --- -# launchd service +Canonical service-manager guidance, including the macOS `launchd` route: +**https://docs.opencoven.ai/docs/guide/deployments** -Use a launchd user agent when you want the Coven daemon started automatically for your macOS user. Install and verify Coven manually first: - -```sh -npm install -g @opencoven/cli -coven doctor -coven daemon start -coven daemon status -coven daemon stop -``` - -## User agent plist - -Create the LaunchAgents directory: - -```sh -mkdir -p "$HOME/Library/LaunchAgents" -``` - -Write `~/Library/LaunchAgents/coven.plist`: - -```xml - - - - Label - coven - - ProgramArguments - - /usr/bin/env - coven - daemon - start - - - EnvironmentVariables - - COVEN_HOME - $HOME/.coven - - - RunAtLoad - - - -``` - -Replace `$HOME` with your absolute home path before loading the plist. launchd does not expand shell variables inside plist strings. - -Load and verify: - -```sh -launchctl bootstrap gui/UID ~/Library/LaunchAgents/coven.plist -launchctl kickstart -k gui/UID/coven -coven daemon status -``` - -Replace `UID` with the output of `id -u`. - -Unload: - -```sh -launchctl bootout gui/UID/coven -``` - -## PATH and harnesses - -launchd uses a smaller environment than your interactive shell. If `coven`, `codex`, or `claude` is installed in a user-local directory, prefer absolute paths in `ProgramArguments` or add a `PATH` entry under `EnvironmentVariables`. - -After changing the plist: - -```sh -launchctl bootout gui/UID/coven -launchctl bootstrap gui/UID ~/Library/LaunchAgents/coven.plist -coven doctor -``` - -## Related - -- [macOS install](/install/macos) -- [COVEN_HOME layout](/daemon/coven-home) -- [Updating Coven](/install/updating) +Platform-specific macOS behavior: **https://docs.opencoven.ai/docs/guide/platforms** diff --git a/docs/install/linux.md b/docs/install/linux.md index 1d0bc8b5..ca56acf9 100644 --- a/docs/install/linux.md +++ b/docs/install/linux.md @@ -1,99 +1,8 @@ --- -summary: "Install Coven on common Linux distros." -read_when: - - Installing on Linux title: "Linux install" -description: "Install Coven on Linux: install the @opencoven/cli wrapper, place the daemon binary on PATH, and verify the install with coven doctor." +description: "Pointer to the canonical Linux platform guidance." --- -# Linux install +Canonical Linux platform guidance: **https://docs.opencoven.ai/docs/guide/platforms** -Use the npm wrapper on glibc-based Linux x64 systems: - -```sh -npm install -g @opencoven/cli -coven --version -coven doctor -``` - -The universal wrapper selects the native Linux x64 package. Alpine and other musl-based environments should use [Install from source](/install/from-source). - -## Baseline packages - -Install Node.js 18+ for the npm wrapper. Install Git for project-root detection and source checkouts. - -Debian or Ubuntu: - -```sh -sudo apt-get update -sudo apt-get install -y nodejs npm git ca-certificates -``` - -Fedora: - -```sh -sudo dnf install -y nodejs npm git ca-certificates -``` - -Arch: - -```sh -sudo pacman -S --needed nodejs npm git ca-certificates -``` - -## Harness setup - -Install and authenticate at least one harness CLI: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -Run `coven doctor` from the same shell after installing harnesses. - -## First session - -```sh -cd /path/to/project -coven daemon start -coven daemon status -coven run codex "describe this repo" -coven sessions -``` - -Use Claude Code instead with: - -```sh -coven run claude "describe this repo" -``` - -## COVEN_HOME - -The default state directory is: - -```sh -$HOME/.coven -``` - -Keep it on the Linux filesystem, not a network mount, when possible. To override: - -```sh -export COVEN_HOME="$HOME/.local/share/coven" -coven doctor -``` - -## Server use - -For a non-desktop host, start with this page, then read [Headless server](/install/headless-server) for daemon lifecycle and SSH-oriented operation. - -## Related - -- [Install via npm](/install/npm) -- [WSL2 install](/install/wsl2) -- [Install from source](/install/from-source) +Install-method selection: **https://docs.opencoven.ai/docs/guide/install** diff --git a/docs/install/macos.md b/docs/install/macos.md index fc1f939a..883f64a3 100644 --- a/docs/install/macos.md +++ b/docs/install/macos.md @@ -1,95 +1,8 @@ --- -summary: "Install Coven on macOS via npm, Homebrew, or source." -read_when: - - Installing on macOS title: "macOS install" -description: "Install Coven on macOS: install the @opencoven/cli wrapper, place the daemon binary on PATH, and supervise the daemon with launchd." +description: "Pointer to the canonical macOS platform guidance." --- -# macOS install +Canonical macOS platform guidance: **https://docs.opencoven.ai/docs/guide/platforms** -Use the npm wrapper on macOS unless you are developing Coven itself. - -```sh -npm install -g @opencoven/cli -coven --version -coven doctor -``` - -The universal wrapper selects `@opencoven/cli-macos` on Apple Silicon and -`@opencoven/cli-macos-x64` on Intel macOS. Use the source install path only -when developing Coven itself or when a release is unavailable for your target. - -## Harness setup - -Install and authenticate at least one harness CLI from the same shell where you run Coven: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -Then verify: - -```sh -coven doctor -``` - -If `doctor` reports a missing harness, open a new terminal and check the command directly: - -```sh -command -v codex -command -v claude -``` - -## First session - -```sh -cd /path/to/project -coven daemon start -coven daemon status -coven run codex "describe this repo" -coven sessions -``` - -Use `coven run claude "describe this repo"` when Claude Code is the configured harness. - -## COVEN_HOME - -The default state directory is: - -```sh -$HOME/.coven -``` - -To isolate state for a demo, test account, or project: - -```sh -export COVEN_HOME="$HOME/.coven-demo" -coven doctor -coven daemon start -``` - -Keep `COVEN_HOME` on a local disk owned by your user. See [COVEN_HOME layout](/daemon/coven-home). - -## Source install for contributors - -```sh -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build --workspace -cargo run -p coven-cli -- doctor -``` - -Use [Install from source](/install/from-source) for the full contributor path. - -## Related - -- [Install via npm](/install/npm) -- [Updating Coven](/install/updating) -- [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) +Install-method selection: **https://docs.opencoven.ai/docs/guide/install** diff --git a/docs/install/nix.md b/docs/install/nix.md index 878c84f4..cf467871 100644 --- a/docs/install/nix.md +++ b/docs/install/nix.md @@ -1,83 +1,7 @@ --- -summary: "Reproducible Coven environment with Nix flakes." -read_when: - - You use Nix to manage tooling title: "Nix" -description: "Install Coven with Nix: a reproducible flake-based setup that pins the daemon, CLI, and supported harnesses across hosts and developer machines." +description: "Pointer to the canonical Coven deployment guidance." --- -# Nix - -Use Nix to pin build prerequisites and harness tooling around a source checkout. This repository does not currently use this page to promise an official Coven flake output. - -For the shortest install, use [Install via npm](/install/npm). For reproducible development shells, use the pattern below. - -## Development shell - -Create a local `flake.nix` in your own workspace: - -```nix -{ - description = "Coven development shell"; - - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - - outputs = { nixpkgs, ... }: - let - system = "x86_64-linux"; - pkgs = import nixpkgs { inherit system; }; - in { - devShells.${system}.default = pkgs.mkShell { - packages = [ - pkgs.rustc - pkgs.cargo - pkgs.pkg-config - pkgs.openssl - pkgs.nodejs_22 - pkgs.git - ]; - }; - }; -} -``` - -Enter the shell and build from source: - -```sh -nix develop -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build --workspace -cargo run -p coven-cli -- doctor -``` - -## Harness setup - -Install harness CLIs inside the environment where you run Coven, or add them to your Nix shell when available from your package set. - -For npm-managed harnesses: - -```sh -npm install -g @openai/codex -codex login -npm install -g @anthropic-ai/claude-code -claude doctor -coven doctor -``` - -## State isolation - -Use an explicit state directory per Nix shell or host: - -```sh -export COVEN_HOME="$PWD/.coven-state" -coven doctor -``` - -Do not commit `.coven-state` or any other `COVEN_HOME` contents. - -## Related - -- [Install from source](/install/from-source) -- [Install via cargo](/install/cargo) -- [COVEN_HOME layout](/daemon/coven-home) +Canonical deployment guidance, including Nix as an environment or build route: +**https://docs.opencoven.ai/docs/guide/deployments** diff --git a/docs/install/npm.md b/docs/install/npm.md index 9e002c8c..e05ff537 100644 --- a/docs/install/npm.md +++ b/docs/install/npm.md @@ -1,113 +1,9 @@ --- -summary: "Install the @opencoven/cli wrapper from npm." -read_when: - - Using npm or pnpm to install Coven title: "Install via npm" -description: "Install Coven with npm: run npm install -g @opencoven/cli to fetch the wrapper plus a prebuilt native daemon binary for supported macOS, Linux, and Windows targets." +description: "Pointer to the canonical Coven install guidance." --- -# Install via npm +Canonical install guidance, including the `npm install -g @opencoven/cli` +wrapper route: **https://docs.opencoven.ai/docs/guide/install** -The fastest workstation install is the universal npm wrapper: - -```sh -npm install -g @opencoven/cli -coven --version -coven doctor -``` - -The wrapper exposes the `coven` command and selects the native package for the current platform. - -The packaged loopback-only dashboard is a separate opt-in install on its own -release train. The wrapper stays thin: it depends only on the native binary for -your platform, so a CLI install never pulls the dashboard's application -dependencies. Install it when you want `coven memory open`: - -```sh -npm install -g @opencoven/coven-memory-dashboard -coven memory open -``` - -Without it, `coven memory open` prints this install instruction and exits; every -other Coven command is unaffected. - -Upgrading from a wrapper older than 0.4.1 removes a dashboard that arrived as an -implicit dependency, so `coven memory open` stops working until you install the -companion explicitly with the command above. Nothing else changes. - -The wrapper passes only the resolved dashboard entrypoint and the current Node -executable to the native CLI. It does not put memory content, daemon transport -proofs, or credentials in the environment. - -The core npm wrapper supports Node.js 18 or newer. The dashboard companion -requires Node.js 24 or newer. On Node.js 18–23, `coven memory open` prints an -upgrade instruction; list output and every other Coven command remain -available. - -## Supported npm targets - -| Platform | Native package | -| --- | --- | -| macOS Apple Silicon | `@opencoven/cli-macos` | -| Intel macOS x64 | `@opencoven/cli-macos-x64` | -| glibc-based Linux x64 | `@opencoven/cli-linux-x64` | -| Windows x64 | `@opencoven/cli-windows` | - -If the wrapper cannot find the native package, reinstall without disabling optional dependencies: - -```sh -npm uninstall -g @opencoven/cli -npm install -g @opencoven/cli -coven doctor -``` - -On Linux, use a glibc-based distribution for the prebuilt package. For Alpine or another musl-based environment, use [Install from source](/install/from-source). - -If Coven was installed as a direct native binary, the dashboard is found on -`PATH` rather than through the wrapper. The same global install puts it there: - -```sh -npm install -g @opencoven/coven-memory-dashboard -``` - -## Install harness CLIs - -Coven supervises existing harness CLIs. Install and authenticate at least one: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -Run `coven doctor` again after harness installation. If a harness is still missing, open a new terminal and verify the harness command is on `PATH` in that same shell. - -## First run - -```sh -cd /path/to/project -coven doctor -coven daemon start -coven run codex "describe this repo" -coven sessions -``` - -Use Claude Code instead when that is the authenticated harness: - -```sh -coven run claude "describe this repo" -``` - -## Updating - -```sh -npm update -g @opencoven/cli -coven daemon restart -coven doctor -``` - -See [Updating Coven](/install/updating) before updating shared automation hosts or long-running daemon environments. +Install troubleshooting: **https://docs.opencoven.ai/docs/cli/install-debugging** diff --git a/docs/install/podman.md b/docs/install/podman.md index 02e6f7d5..c78dd49b 100644 --- a/docs/install/podman.md +++ b/docs/install/podman.md @@ -1,77 +1,7 @@ --- -summary: "Run Coven under Podman with rootless containers." -read_when: - - Daemonless container hosting title: "Podman" -description: "Run Coven under Podman: a rootless containerized daemon plus harness CLIs, with bind mounts for COVEN_HOME and the project root per session." +description: "Pointer to the canonical Coven deployment guidance." --- -# Podman - -Podman is useful for rootless container experiments and homelab hosts. For ordinary workstation setup, prefer the native platform pages. - -This page assumes you build a local image from the Coven source checkout. There is no install-docs promise of an official Podman image. - -## Build a local image - -Use the Dockerfile pattern from [Docker](/install/docker), then build with Podman: - -```sh -podman build -t coven-local . -``` - -## Run doctor with persistent state - -```sh -mkdir -p "$HOME/.coven-container" -podman run --rm -it \ - -e COVEN_HOME=/var/lib/coven \ - -v "$HOME/.coven-container:/var/lib/coven:Z" \ - -v "$PWD:/workspace:Z" \ - -w /workspace \ - coven-local coven doctor -``` - -Drop the `:Z` label suffix on systems that do not use SELinux. - -## Harness setup - -Install and authenticate harness CLIs inside the container image or in a derived image: - -```Dockerfile -RUN npm install -g @openai/codex @anthropic-ai/claude-code -``` - -Then verify inside the same container environment: - -```sh -podman run --rm -it \ - -e COVEN_HOME=/var/lib/coven \ - -v "$HOME/.coven-container:/var/lib/coven:Z" \ - -v "$PWD:/workspace:Z" \ - -w /workspace \ - coven-local coven doctor -``` - -## First session - -```sh -podman run --rm -it \ - -e COVEN_HOME=/var/lib/coven \ - -v "$HOME/.coven-container:/var/lib/coven:Z" \ - -v "$PWD:/workspace:Z" \ - -w /workspace \ - coven-local coven run codex "describe this repo" -``` - -## Notes - -- Rootless Podman changes UID/GID mappings. Keep mounted state owned by the user that runs Podman. -- Use one mounted `COVEN_HOME` per environment. -- Run `coven doctor` after every image or mount change. - -## Related - -- [Docker](/install/docker) -- [Headless server](/install/headless-server) -- [Linux install](/install/linux) +Canonical deployment guidance, including manual container integrations: +**https://docs.opencoven.ai/docs/guide/deployments** diff --git a/docs/install/raspberry-pi.md b/docs/install/raspberry-pi.md index 25140066..a04fe260 100644 --- a/docs/install/raspberry-pi.md +++ b/docs/install/raspberry-pi.md @@ -1,90 +1,9 @@ --- -summary: "Run Coven on Raspberry Pi as a low-power home agent host." -read_when: - - Hosting Coven on a Pi title: "Raspberry Pi" -description: "Install Coven on a Raspberry Pi: arm64 daemon binary, COVEN_HOME on persistent storage, and systemd supervision for headless agent work." +description: "Pointer to the canonical Raspberry Pi platform guidance." --- -# Raspberry Pi +Canonical Raspberry Pi platform guidance: +**https://docs.opencoven.ai/docs/guide/platforms** -Raspberry Pi is a source-build path today. Use a 64-bit Raspberry Pi OS image and keep `COVEN_HOME` on persistent local storage. - -## Install prerequisites - -```sh -sudo apt-get update -sudo apt-get install -y git curl build-essential pkg-config libssl-dev nodejs npm ca-certificates -``` - -Install Rust stable if it is not already present: - -```sh -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -. "$HOME/.cargo/env" -``` - -## Build Coven - -```sh -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build -p coven-cli --release -mkdir -p "$HOME/.local/bin" -cp target/release/coven "$HOME/.local/bin/coven" -coven doctor -``` - -Make sure `$HOME/.local/bin` is on `PATH`. - -## Harness setup - -Install only harness CLIs that support your Pi architecture and auth flow. Then verify from the same shell: - -```sh -coven doctor -``` - -If Codex or Claude Code is installed with npm: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -## State and daemon - -Use an explicit state directory: - -```sh -export COVEN_HOME="$HOME/.coven" -coven daemon start -coven daemon status -``` - -For always-on operation, use [systemd unit](/install/systemd) after the manual `coven doctor` path works. - -## First session - -```sh -cd /path/to/project -coven run codex "describe this repo" -coven sessions -``` - -## Notes - -- Build times can be long on small Pi models. -- Keep swap and disk space healthy before building Rust dependencies. -- Avoid storing `COVEN_HOME` on removable media that may disappear while the daemon is running. - -## Related - -- [Linux install](/install/linux) -- [Headless server](/install/headless-server) -- [systemd unit](/install/systemd) +Install-method selection: **https://docs.opencoven.ai/docs/guide/install** diff --git a/docs/install/systemd.md b/docs/install/systemd.md index bd2f15fe..3e7cb5d7 100644 --- a/docs/install/systemd.md +++ b/docs/install/systemd.md @@ -1,87 +1,9 @@ --- -summary: "Run the Coven daemon as a systemd user unit." -read_when: - - Keeping the daemon up on Linux title: "systemd unit" -description: "Run the Coven daemon under systemd on Linux: a unit file, environment for COVEN_HOME, and journalctl access to daemon logs across reboots." +description: "Pointer to the canonical Coven service-manager guidance." --- -# systemd unit +Canonical service-manager guidance, including the Linux `systemd --user` route: +**https://docs.opencoven.ai/docs/guide/deployments** -Use a systemd user unit when you want the Coven daemon available after login on a Linux workstation or server. Install Coven and verify it manually first: - -```sh -npm install -g @opencoven/cli -coven doctor -coven daemon start -coven daemon status -coven daemon stop -``` - -## User unit - -Create the user unit directory: - -```sh -mkdir -p "$HOME/.config/systemd/user" -``` - -Write `~/.config/systemd/user/coven-daemon.service`: - -```ini -[Unit] -Description=Coven daemon -After=default.target - -[Service] -Type=oneshot -RemainAfterExit=yes -Environment=COVEN_HOME=%h/.coven -ExecStart=/usr/bin/env coven daemon start -ExecStop=/usr/bin/env coven daemon stop -ExecReload=/usr/bin/env coven daemon restart - -[Install] -WantedBy=default.target -``` - -Load and start it: - -```sh -systemctl --user daemon-reload -systemctl --user enable --now coven-daemon.service -systemctl --user status coven-daemon.service -coven daemon status -``` - -If you need the user service to start without an active login session, enable linger for that Linux user: - -```sh -loginctl enable-linger "$USER" -``` - -## PATH and harnesses - -The service must resolve the same commands that `coven doctor` reports from your shell. If `coven`, `codex`, or `claude` is installed under a user-local path, add an explicit `Environment=PATH=...` line to the unit. - -After changing PATH, harness auth, or `COVEN_HOME`: - -```sh -systemctl --user daemon-reload -systemctl --user restart coven-daemon.service -coven doctor -``` - -## Logs - -```sh -journalctl --user -u coven-daemon.service --since today -``` - -Use `coven daemon status` as the product-level health check; systemd only tells you whether the wrapper command ran. - -## Related - -- [Linux install](/install/linux) -- [Headless server](/install/headless-server) -- [COVEN_HOME layout](/daemon/coven-home) +Platform-specific Linux behavior: **https://docs.opencoven.ai/docs/guide/platforms** diff --git a/docs/install/uninstall.md b/docs/install/uninstall.md index 9715dcd5..da8433b5 100644 --- a/docs/install/uninstall.md +++ b/docs/install/uninstall.md @@ -1,99 +1,6 @@ --- -summary: "How to remove Coven cleanly without losing project sessions." -read_when: - - Removing Coven from a workstation title: "Uninstalling Coven" -description: "Uninstall Coven cleanly: stop the daemon, remove the wrapper, and decide whether to keep or wipe COVEN_HOME and the session ledger." +description: "Pointer to the canonical uninstall guidance." --- -# Uninstalling Coven - -Uninstall has two separate decisions: - -1. Remove the `coven` command. -2. Keep or delete `COVEN_HOME`, which contains local session history, sockets, logs, and keys. - -Stop the daemon first: - -```sh -coven daemon stop -``` - -## npm wrapper - -```sh -npm uninstall -g @opencoven/cli -``` - -Verify the command is gone: - -```sh -command -v coven -``` - -PowerShell: - -```powershell -Get-Command coven -``` - -If another install path still exposes `coven`, remove that binary or adjust `PATH`. - -## Source or cargo install - -Remove the binary you copied onto `PATH`: - -```sh -rm "$HOME/.local/bin/coven" -``` - -Windows PowerShell: - -```powershell -Remove-Item "$env:USERPROFILE\.local\bin\coven.exe" -``` - -Adjust the path if you installed the binary somewhere else. - -## Keep or delete state - -To preserve sessions for a later reinstall, leave `COVEN_HOME` in place. - -To delete the default state directory on macOS, Linux, or WSL2: - -```sh -rm -rf "$HOME/.coven" -``` - -PowerShell: - -```powershell -Remove-Item -Recurse -Force "$env:USERPROFILE\.coven" -``` - -Only delete `COVEN_HOME` after confirming there are no sessions, logs, or local keys you need to keep. - -## Services - -If you installed a launchd user agent: - -```sh -launchctl bootout gui/UID/coven -rm ~/Library/LaunchAgents/coven.plist -``` - -Replace `UID` with the output of `id -u`. - -If you installed a systemd user unit: - -```sh -systemctl --user disable --now coven-daemon.service -rm "$HOME/.config/systemd/user/coven-daemon.service" -systemctl --user daemon-reload -``` - -## Related - -- [COVEN_HOME layout](/daemon/coven-home) -- [Install overview](/install/index) -- [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) +Canonical uninstall guidance: **https://docs.opencoven.ai/docs/cli/uninstall** diff --git a/docs/install/updating.md b/docs/install/updating.md index 253f3cbb..b95bddab 100644 --- a/docs/install/updating.md +++ b/docs/install/updating.md @@ -1,112 +1,7 @@ --- -summary: "How to update Coven and what release channels exist." -read_when: - - Moving to a newer version of Coven title: "Updating Coven" -description: "Update Coven safely: upgrade the @opencoven/cli wrapper, drain or stop the daemon, migrate the store, and verify the contract version on restart." +description: "Pointer to the canonical install-debugging and update-recovery guidance." --- -# Updating Coven - -Update the wrapper or binary, restart the daemon, then run the same verification loop you used at install time. - -## npm wrapper - -```sh -npm update -g @opencoven/cli -coven --version -coven daemon restart -coven doctor -``` - -If the native package is missing after update, reinstall the wrapper without disabling optional dependencies: - -```sh -npm uninstall -g @opencoven/cli -npm install -g @opencoven/cli -coven doctor -``` - -## Source checkout - -```sh -cd /path/to/coven -git pull --ff-only -cargo build -p coven-cli --release -cp target/release/coven "$HOME/.local/bin/coven" -coven daemon restart -coven doctor -``` - -On Windows, copy `target\release\coven.exe` to the directory where your shell resolves `coven`. - -## Harness updates - -Coven supervises harness CLIs; it does not own their provider credentials. Update and verify harnesses separately: - -```sh -npm update -g @openai/codex -codex login -``` - -```sh -npm update -g @anthropic-ai/claude-code -claude doctor -``` - -Then run: - -```sh -coven doctor -``` - -## Verification loop - -```sh -coven --version -coven doctor -coven daemon restart -coven daemon status -cd /path/to/project -coven run codex "say hello from the updated Coven install" -coven sessions -``` - -Use `coven run claude ...` when Claude Code is your active harness. - -## Rollback notes - -Before changing package source, binary location, or `COVEN_HOME`, stop the daemon: - -```sh -coven daemon stop -``` - -If an update leaves the daemon unreachable, run: - -```sh -coven daemon restart -coven doctor -``` - -If `doctor` points at a `PATH` problem, open a new shell and verify: - -```sh -command -v coven -command -v codex -command -v claude -``` - -PowerShell: - -```powershell -Get-Command coven -Get-Command codex -Get-Command claude -``` - -## Related - -- [Install overview](/install/index) -- [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) -- [Daemon lifecycle](/daemon/lifecycle) +Canonical update-recovery and install-debugging guidance: +**https://docs.opencoven.ai/docs/cli/install-debugging** diff --git a/docs/install/windows.md b/docs/install/windows.md index d838358c..79c6dffd 100644 --- a/docs/install/windows.md +++ b/docs/install/windows.md @@ -1,100 +1,8 @@ --- -summary: "Install Coven on native Windows." -read_when: - - Installing on Windows title: "Windows install" -description: "Install Coven on Windows: how to set up the wrapper, native daemon binary, COVEN_HOME, and harness CLIs on a Windows host or WSL2 environment." +description: "Pointer to the canonical Windows platform guidance." --- -# Windows install +Canonical Windows platform guidance: **https://docs.opencoven.ai/docs/guide/platforms** -Install the wrapper globally from PowerShell, Windows Terminal, or any terminal that can run Node.js packages: - -```powershell -npm install -g @opencoven/cli -coven doctor -``` - -The wrapper exposes the `coven` command and launches the native Windows binary -when the release package includes one for your platform. `coven doctor` is the -first verification step: it checks local state and reports whether supported -harness CLIs such as Codex, Claude Code, or GitHub Copilot CLI are available on -`PATH`. - -Use native Windows and WSL2 as separate Coven environments. If you install Coven in PowerShell, install the harness CLIs in PowerShell too. If you install Coven inside WSL2, follow [WSL2 install](/install/wsl2) and keep the daemon state inside WSL. - -## First run - -From a project directory: - -```powershell -coven -``` - -Bare `coven`, `coven chat`, and `coven tui` open the managed Coven interactive -UI powered by `coven-code`. On the first interactive run, Coven offers to -install the pinned engine if it is missing. The older in-process TUI is a -temporary compatibility fallback: explicitly set `COVEN_LEGACY_TUI=1` to use -it. It is deprecated and will be removed. - -You can also use the explicit CLI flow: - -```powershell -coven doctor -coven daemon start -coven run codex "fix the failing tests" -coven run claude "audit this branch" --think -coven sessions -``` - -Install and authenticate at least one harness CLI before expecting `coven run` to launch work. If `coven doctor` reports a missing harness, install that tool, open a new terminal so `PATH` is refreshed, and run `coven doctor` again. - -Codex: - -```powershell -npm install -g @openai/codex -codex login -``` - -Claude Code: - -```powershell -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -## Windows notes - -- `coven doctor` should work in PowerShell even when the `HOME` environment variable is absent. Coven resolves its default store from `COVEN_HOME`, `HOME`, `USERPROFILE`, `HOMEDRIVE` + `HOMEPATH`, or the platform home directory. -- Keep `COVEN_HOME` on a local path owned by your Windows user when you override it. -- To override the store path in PowerShell, use: - -```powershell -$env:COVEN_HOME="$env:USERPROFILE\.coven" -coven doctor -``` - -- Run Coven and your harness CLI from the same environment. A harness installed only inside WSL2 is not available to native Windows PowerShell unless you expose it separately. -- The legacy in-process TUI is only for temporary compatibility. Set - `COVEN_LEGACY_TUI=1` explicitly if a legacy workflow requires it; do not use - it as the default Windows interactive UI. - -## Verification loop - -```powershell -coven --version -coven doctor -coven daemon restart -coven daemon status -cd C:\path\to\project -coven run codex "describe this repo" -coven sessions -``` - -## Related - -- [Get started with Coven](https://docs.opencoven.ai/docs/guide/getting-started) -- [Install overview](/install/index) -- [Coven TUI](/start/coven-tui) -- [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) -- [CLI reference](/reference/cli) +Install-method selection: **https://docs.opencoven.ai/docs/guide/install** diff --git a/docs/install/wsl2.md b/docs/install/wsl2.md index d6821bae..2cd112aa 100644 --- a/docs/install/wsl2.md +++ b/docs/install/wsl2.md @@ -1,79 +1,8 @@ --- -summary: "Install Coven inside WSL2 for the full Unix-socket experience." -read_when: - - Installing on WSL2 title: "WSL2 install" -description: "Install Coven inside WSL2: run the Linux daemon binary, pin COVEN_HOME on the WSL filesystem, and connect Windows clients to the socket." +description: "Pointer to the canonical WSL2 platform guidance." --- -# WSL2 install +Canonical WSL2 platform guidance: **https://docs.opencoven.ai/docs/guide/platforms** -Inside WSL2, install Coven as Linux software. Keep Coven, harness CLIs, project files, and `COVEN_HOME` in the WSL environment for the least surprising daemon and PTY behavior. - -```sh -npm install -g @opencoven/cli -coven --version -coven doctor -``` - -The npm wrapper uses the Linux x64 native package when your WSL distribution is glibc-based. - -## Recommended layout - -Use Linux paths for projects and state: - -```sh -mkdir -p "$HOME/code" -cd "$HOME/code" -export COVEN_HOME="$HOME/.coven" -``` - -Avoid putting active Coven state under `/mnt/c` because Windows filesystem semantics can make socket, permission, and file-watch behavior harder to reason about. - -## Harness setup - -Install harness CLIs inside WSL2: - -```sh -npm install -g @openai/codex -codex login -``` - -```sh -npm install -g @anthropic-ai/claude-code -claude doctor -``` - -Native Windows harness installs do not automatically appear inside WSL2. Run `coven doctor` from WSL after installing harnesses. - -## First session - -```sh -cd "$HOME/code/project" -coven daemon start -coven daemon status -coven run codex "describe this repo" -coven sessions -``` - -## WSL2 versus native Windows - -Pick one environment for each working session: - -- Native Windows: install Coven and harness CLIs in PowerShell or Windows Terminal; use [Windows install](/install/windows). -- WSL2: install Coven and harness CLIs inside the Linux distro; use Linux paths and Linux `COVEN_HOME`. - -Do not point native Windows Coven and WSL2 Coven at the same state directory. - -## Source fallback - -If the npm native package is not available for your WSL distribution: - -```sh -git clone https://github.com/OpenCoven/coven.git -cd coven -cargo build --workspace -cargo run -p coven-cli -- doctor -``` - -See [Install from source](/install/from-source). +Install-method selection: **https://docs.opencoven.ai/docs/guide/install** diff --git a/docs/roadmaps/coven-automations-v1.mapping.json b/docs/roadmaps/coven-automations-v1.mapping.json new file mode 100644 index 00000000..ece26477 --- /dev/null +++ b/docs/roadmaps/coven-automations-v1.mapping.json @@ -0,0 +1,206 @@ +{ + "schema": "coven.automations-v1.tracker-mapping", + "schema_version": 1, + "description": "Machine-readable Bead <-> GitHub outcome mapping for the Coven Automations v1 program (OpenCoven/coven#859). GitHub owns public outcomes, acceptance gates, and durable evidence links. Beads (in the OpenCoven/coven-cave embedded Dolt database `cave`) owns the implementation dependency graph, execution ownership, and mirror inputs. This file is the reconciliation contract between the two; it is hand-reviewed, not hand-synced.", + "sync": { + "last_sync": "2026-08-30T15:05:00Z", + "source_branch": "agent/issue-859-p0-control-operationalize-coven-automations-v1", + "writer": "One canonical writer/process is designated for this setup; see docs/superpowers/plans/2026-08-30-issue-859-coven-automations-v1-tracker-operationalization.md (Decision D1). Persisted tracker changes land only through reviewed PRs.", + "canonical_bead_store": "OpenCoven/coven-cave embedded Dolt database `cave`; provisioning is routed through OpenCoven/coven-cave#5220. A competing Beads database must not be initialized in OpenCoven/coven (operational correction on OpenCoven/coven#859, 2026-08-30).", + "public_bead_export": "OpenCoven/coven-cave `.beads/issues.jsonl` is a public-scrubbed review export, never canonical state and never a hand-edited sync mechanism.", + "beads_tool_reference": "Beads 1.2.2, schema v53, as recorded in docs/superpowers/plans/2026-08-20-coven-v0.4.1-release-program.md; live schema/version verification is owned by OpenCoven/coven-cave#5220.", + "drift_check": "node docs/roadmaps/drift-check.mjs (add --beads-export to cross-check an export; --selftest to verify detection rules)", + "priority_policy": { + "P0": "Current v1 correctness, security, data-loss/duplicate-execution risk, authority violation, broken migration, or certification blocker.", + "P1": "Committed SDK/product/docs/ecosystem work required to make the certified core usable and operable.", + "P2": "Post-v1 expansion or research that must not silently enter the release critical path." + } + }, + "program": "https://github.com/OpenCoven/coven/issues/854", + "outcomes": [ + { + "slug": "program", + "role": "program", + "github": { + "repo": "OpenCoven/coven", + "issue": 854, + "url": "https://github.com/OpenCoven/coven/issues/854", + "title": "Program: Coven Automations v1 — reliable, identity-bound familiar routines", + "state": "open", + "priority": "P0", + "owner": "BunsDev" + }, + "bead": { + "label": "automations-v1/program", + "surface": "shared", + "title": "Coven Automations v1 program (GitHub OpenCoven/coven#854)", + "id": null, + "provisioning": "pending:OpenCoven/coven-cave#5220", + "disposition": "active:release-gate-ownership", + "gate": "Owns release gates and cross-repository rollup; final #854 release rollup must be generable from reconciled tracker state and exact evidence. Must not be used as a catch-all implementation task.", + "evidence_status": "none", + "evidence": [] + }, + "depends_on": [], + "depends_on_external": [], + "notes": "Priority is P0 program/control: it gates the release but must not absorb implementation work." + }, + { + "slug": "foundation", + "role": "p0-foundation", + "github": { + "repo": "OpenCoven/coven", + "issue": 816, + "url": "https://github.com/OpenCoven/coven/issues/816", + "title": "Native familiar automations: replace harness-owned schedules with durable Coven routines", + "state": "open", + "priority": "P0", + "owner": "BunsDev" + }, + "bead": { + "label": "automations-v1/foundation", + "surface": "shared", + "title": "Coven Automations v1 — native routine foundation (GitHub OpenCoven/coven#816)", + "id": null, + "provisioning": "pending:OpenCoven/coven-cave#5220 (no pre-existing bead found in the public-scrubbed export at sync time; reuse-if-present check owned by OpenCoven/coven-cave#5220)", + "disposition": "active:reconciling-landed-evidence", + "gate": "#816 evidence checklist: landed commit/PR series linked with an exact final foundation revision; clean-clone automation-test verification; pre-automations schema migration and rollback proof; daemon startup/tick/shutdown/restart proof on supported platforms; one scheduled and one manual run shown to traverse the same claim/ledger/runtime/delivery path; stale lease cannot block the next eligible occurrence indefinitely; failed output delivery cannot report success; every original acceptance criterion reconciled as implemented, deferred, or superseded; Cave ownership migration (OpenCoven/coven-cave#4990) linked with remaining compatibility facade documented.", + "evidence_status": "partial", + "evidence": [ + "https://github.com/OpenCoven/coven/pull/846 (merged 2026-08-28; routine definitions and control actions, part 1)", + "https://github.com/OpenCoven/coven/pull/847 (merged 2026-08-28; legacy import series, parts 5-8)", + "https://github.com/OpenCoven/coven/issues/816 (program-status section of the issue body, updated 2026-08-30: landed inventory and open evidence checklist)" + ] + }, + "depends_on": [], + "depends_on_external": [], + "notes": "Implementation materially landed on main 2026-08-28 (crates/coven-cli/src/automations/); the issue stays open for foundation reconciliation and exact evidence." + }, + { + "slug": "protocol", + "role": "p0-workstream", + "github": { + "repo": "OpenCoven/coven", + "issue": 855, + "url": "https://github.com/OpenCoven/coven/issues/855", + "title": "P0: Specify coven.automations.v1 schemas, state machines, idempotency, and changefeed", + "state": "open", + "priority": "P0", + "owner": "BunsDev" + }, + "bead": { + "label": "automations-v1/protocol", + "surface": "shared", + "title": "Coven Automations v1 — protocol schemas, state, idempotency, changefeed (GitHub OpenCoven/coven#855)", + "id": null, + "provisioning": "pending:OpenCoven/coven-cave#5220", + "disposition": "blocked:pending-foundation-reconciliation-and-bead-provisioning", + "gate": "coven.automations.v1 schemas, state machines, idempotency rules, and changefeed contract are specified, reviewed, and covered by tests; schema/conformance artifact revisions recorded as evidence.", + "evidence_status": "none", + "evidence": [] + }, + "depends_on": ["foundation"], + "depends_on_external": [], + "notes": "Feeds SDK read/types/changefeed (P1), SDK mutations/approvals (P1), Cave oversight/recovery (P1), and Psyche adapter (P1) once those outcomes are created." + }, + { + "slug": "scheduler", + "role": "p0-workstream", + "github": { + "repo": "OpenCoven/coven", + "issue": 856, + "url": "https://github.com/OpenCoven/coven/issues/856", + "title": "P0: Harden automation time, retries, cancellation, fencing, and crash recovery", + "state": "open", + "priority": "P0", + "owner": "BunsDev" + }, + "bead": { + "label": "automations-v1/scheduler", + "surface": "shared", + "title": "Coven Automations v1 — time, retry, cancel, fencing, recovery (GitHub OpenCoven/coven#856)", + "id": null, + "provisioning": "pending:OpenCoven/coven-cave#5220", + "disposition": "blocked:pending-foundation-reconciliation-and-bead-provisioning", + "gate": "Time, retry, cancellation, fencing, and crash-recovery behaviors verified by tests, including stale-lease recovery, misfire semantics, and cancellation without duplicate execution.", + "evidence_status": "none", + "evidence": [] + }, + "depends_on": ["foundation", "protocol"], + "depends_on_external": [], + "notes": "Depends on #816 and #855 where state/error semantics are required." + }, + { + "slug": "authority", + "role": "p0-workstream", + "github": { + "repo": "OpenCoven/coven", + "issue": 857, + "url": "https://github.com/OpenCoven/coven/issues/857", + "title": "P0: Bind automation runs to principal authority, familiar revisions, capabilities, approvals, and receipts", + "state": "open", + "priority": "P0", + "owner": "BunsDev" + }, + "bead": { + "label": "automations-v1/authority", + "surface": "shared", + "title": "Coven Automations v1 — principal/familiar/authority/approval/receipt binding (GitHub OpenCoven/coven#857)", + "id": null, + "provisioning": "pending:OpenCoven/coven-cave#5220", + "disposition": "blocked:pending-foundation-reconciliation-and-bead-provisioning", + "gate": "Runs are bound to principal authority, familiar revisions, capabilities, approvals, and receipts; negative tests prove authority bypass fails closed; receipts are tamper-evident and out of tracker scope.", + "evidence_status": "none", + "evidence": [] + }, + "depends_on": ["foundation", "protocol"], + "depends_on_external": [], + "notes": "Additionally depends on upstream Familiar Contract and Threads profile outcomes once created; no such cross-repository outcomes existed at sync time, so depends_on_external is empty and must be made explicit when they appear." + }, + { + "slug": "certification", + "role": "p0-workstream", + "github": { + "repo": "OpenCoven/coven", + "issue": 858, + "url": "https://github.com/OpenCoven/coven/issues/858", + "title": "P0: Build automations conformance, chaos, SLO, and operator diagnostics", + "state": "open", + "priority": "P0", + "owner": "BunsDev" + }, + "bead": { + "label": "automations-v1/certification", + "surface": "shared", + "title": "Coven Automations v1 — conformance, chaos, SLO, operator diagnostics (GitHub OpenCoven/coven#858)", + "id": null, + "provisioning": "pending:OpenCoven/coven-cave#5220", + "disposition": "blocked:pending-p0-workstreams-and-bead-provisioning", + "gate": "Conformance, chaos, SLO, and operator-diagnostics suites exist and run without ambient production credentials; the v1 release gate report is produced from them and linked here.", + "evidence_status": "none", + "evidence": [] + }, + "depends_on": ["protocol", "scheduler", "authority"], + "depends_on_external": [], + "notes": "Certification blocker for the v1 release gate owned by the program outcome." + } + ], + "cross_repository_children": [], + "cross_repository_children_policy": "One Bead per SDK, Cave, Psyche, docs, organization-canary, Familiar Contract, and Threads outcome created under OpenCoven/coven#854, mapped one-to-one in this file as each is created. They generally depend on the protocol outcome and, where authority-bearing, the authority outcome; exact dependencies must be explicit rather than inferred from the program parent.", + "p2_exclusions": [ + "Event triggers", + "Multi-host routing", + "Hosted execution", + "Broad external action adapters" + ], + "p2_exclusions_policy": "These remain post-v1 (P2) and must not be encoded as implicit P0 blockers anywhere in the graph.", + "invariants": [ + "Each GitHub outcome maps to exactly one Bead; each Bead maps to exactly one GitHub outcome.", + "Dependencies and P0/P1/P2 priorities in Beads match this file and the roadmap table generated from it.", + "A P0 Bead has one accountable owner, one canonical GitHub outcome, explicit dependencies, a current acceptance gate, an active or explicitly blocked disposition, evidence requirements, and no contradictory closed public mirror.", + "A Bead closes only when the corresponding GitHub acceptance criteria are satisfied or the outcome is explicitly cancelled/superseded with rationale.", + "Generated mirror bodies (including the generated mapping table in docs/roadmaps/coven-automations-v1.md) change only through the generator contract (node docs/roadmaps/drift-check.mjs --render).", + "Tracker data is never queried as production automation state; the Coven runtime owns definitions, occurrences, runs, attempts, leases, approvals, artifacts, events, and receipts.", + "Tracker output must not contain secrets, private prompts, terminal dumps, credentials, unrestricted personal paths, or sensitive identity/authority payloads." + ] +} diff --git a/docs/roadmaps/coven-automations-v1.md b/docs/roadmaps/coven-automations-v1.md new file mode 100644 index 00000000..ac7fa2ee --- /dev/null +++ b/docs/roadmaps/coven-automations-v1.md @@ -0,0 +1,181 @@ +--- +title: "Coven Automations v1 delivery roadmap" +summary: "Canonical Bead <-> GitHub outcome graph, priorities, dependencies, release gates, and drift controls for the Coven Automations v1 program (OpenCoven/coven#854, operationalized by OpenCoven/coven#859)." +read_when: + - Working on any Coven Automations v1 P0/P1 outcome + - Reconciling Beads state against GitHub outcomes + - Running or interpreting the tracker drift check +description: "Delivery roadmap for Coven Automations v1: tracker roles, ownership, the P0/P1/P2 table, the Bead-GitHub mapping, the dependency graph, release gates, and active blockers." +--- + +# Coven Automations v1 delivery roadmap + +_Last synchronized: 2026-08-30T15:05:00Z (see the sync metadata block below)_ + +> [!WARNING] +> **Generated content.** The mapping table in the marked block below is generated from +> `docs/roadmaps/coven-automations-v1.mapping.json` by +> `node docs/roadmaps/drift-check.mjs --render`. Edit the mapping, never the block. +> Mutable run status is deliberately **not** duplicated here: authoritative state lives +> in the trackers and is linked from this document. + +## Program + +**Coven Automations v1 — reliable, identity-bound familiar routines** +([OpenCoven/coven#854](https://github.com/OpenCoven/coven/issues/854)). +Tracker operationalization control: +[OpenCoven/coven#859](https://github.com/OpenCoven/coven/issues/859). +Parent of the initial P0 graph (#816, #855, #856, #857, #858). The program owns release +gates and cross-repository rollup and must not be used as a catch-all implementation task. + +## Canonical tracker roles + +| Tracker | Owns | Must never own | +| --- | --- | --- | +| **Beads** (canonical store: `OpenCoven/coven-cave` embedded Dolt database `cave`) | implementation dependency graph; task/quest assignment and active execution ownership; current priority and blocked state; branch/worktree linkage; interaction/delivery evidence references; generated GitHub mirror synchronization inputs | public acceptance criteria, cross-repository issue links, or any role as a runtime ledger | +| **GitHub** | public outcome and rationale; canonical acceptance criteria and release gates; cross-repository issue links; durable PR/release/conformance evidence links; design/governance decisions | mutable execution state that belongs to the Coven runtime | +| **Coven runtime** | automation definitions and revisions; occurrences, runs, attempts, leases, approvals, artifacts, events, receipts | — tracker data is never queried as production automation state | + +`.beads/issues.jsonl` in `OpenCoven/coven-cave` is a public-scrubbed review export — +never canonical state and never a hand-edited sync mechanism. Tracker changes land only +through reviewed PRs (see +[the #859 status/decision record](../superpowers/plans/2026-08-30-issue-859-coven-automations-v1-tracker-operationalization.md)). + +## Sync metadata + +- **Last synchronization:** 2026-08-30T15:05:00Z (UTC) +- **Source branch:** `agent/issue-859-p0-control-operationalize-coven-automations-v1` (based on upstream `main` at `1364cec`) +- **Machine-readable mapping:** [`coven-automations-v1.mapping.json`](./coven-automations-v1.mapping.json) (schema `coven.automations-v1.tracker-mapping`, version 1) +- **Drift check:** `node docs/roadmaps/drift-check.mjs` (add `--beads-export ` to cross-check an export; `--selftest` verifies detection rules) — runs locally and in CI without ambient production credentials +- **Beads tool reference:** Beads 1.2.2, schema v53, as recorded in + [the v0.4.1 release program record](../superpowers/plans/2026-08-20-coven-v0.4.1-release-program.md); + live schema/version verification and bead provisioning are owned by + [OpenCoven/coven-cave#5220](https://github.com/OpenCoven/coven-cave/issues/5220) +- **Writer:** exactly one canonical writer/process for this setup (Decision D1 in the + #859 status/decision record); concurrent independent migrations and direct writes from + unrelated worktrees are refused + +## P0 / P1 / P2 policy + +- **P0:** current v1 correctness, security, data-loss/duplicate-execution risk, authority violation, broken migration, or certification blocker. +- **P1:** committed SDK/product/docs/ecosystem work required to make the certified core usable and operable. +- **P2:** post-v1 expansion or research that must not silently enter the release critical path (event triggers, multi-host routing, hosted execution, broad external action adapters). + +Every P0 bead must have: one accountable owner; one canonical GitHub outcome; explicit +dependencies; a current acceptance gate; an active or explicitly blocked disposition; +evidence requirements; and no contradictory closed public mirror. + +## Outcome mapping + +The table below is the canonical Bead ↔ GitHub mapping (also available as JSON): + + +| Outcome | GitHub | Priority | Bead label | Bead ID | Dependencies | Disposition | +| --- | --- | --- | --- | --- | --- | --- | +| program | [OpenCoven/coven#854](https://github.com/OpenCoven/coven/issues/854) | P0 | `automations-v1/program` | (pending provisioning) | (none) | active:release-gate-ownership | +| foundation | [OpenCoven/coven#816](https://github.com/OpenCoven/coven/issues/816) | P0 | `automations-v1/foundation` | (pending provisioning) | (none) | active:reconciling-landed-evidence | +| authority | [OpenCoven/coven#857](https://github.com/OpenCoven/coven/issues/857) | P0 | `automations-v1/authority` | (pending provisioning) | foundation, protocol | blocked:pending-foundation-reconciliation-and-bead-provisioning | +| certification | [OpenCoven/coven#858](https://github.com/OpenCoven/coven/issues/858) | P0 | `automations-v1/certification` | (pending provisioning) | protocol, scheduler, authority | blocked:pending-p0-workstreams-and-bead-provisioning | +| protocol | [OpenCoven/coven#855](https://github.com/OpenCoven/coven/issues/855) | P0 | `automations-v1/protocol` | (pending provisioning) | foundation | blocked:pending-foundation-reconciliation-and-bead-provisioning | +| scheduler | [OpenCoven/coven#856](https://github.com/OpenCoven/coven/issues/856) | P0 | `automations-v1/scheduler` | (pending provisioning) | foundation, protocol | blocked:pending-foundation-reconciliation-and-bead-provisioning | + +_Cross-repository child outcomes: none created yet. One Bead per SDK, Cave, Psyche, docs, organization-canary, Familiar Contract, and Threads outcome under the program is mapped here one-to-one as each is created._ + + +Bead IDs are pending until provisioning lands through +[OpenCoven/coven-cave#5220](https://github.com/OpenCoven/coven-cave/issues/5220) — the +mapping records the contract (`surface:shared`, exact GitHub links, one-to-one outcomes) +and the drift check reports the gap (`W010`) until IDs are declared. + +## Dependency graph + +Minimum canonical P0 graph (from OpenCoven/coven#859): + +```text +#816 foundation + ├─ #855 protocol + ├─ #856 scheduler reliability + └─ #857 identity + authority + +#855 ─┬─> #856 + └─> #857 + +#855 + #856 + #857 -> #858 certification +#858 -> v1 release gate + +#855 -> SDK read/types/changefeed (P1) +#855 + #857 -> SDK mutations/approvals (P1) +#855 + #856 + #857 -> Cave oversight/recovery (P1) +#855 + #857 -> Psyche adapter (P1) +upstream Familiar/Threads profiles -> #857 (cross-repo, when created) +``` + +Exact dependencies are recorded per outcome in the mapping file +(`depends_on` slugs; `depends_on_external` for cross-repository outcomes). P1 ecosystem +beads must declare their exact dependencies rather than inheriting them from the broad +program parent. + +## Release gates + +1. **Foundation reconciled** — #816 evidence checklist complete (landed series linked, + clean-clone test verification, migration/rollback proof, daemon wiring proof, unified + manual/scheduled run path, stale-lease recovery, delivery-failure non-success, + compatibility facade reconciled with + [OpenCoven/coven-cave#4990](https://github.com/OpenCoven/coven-cave/issues/4990)). +2. **Protocol specified** — #855 schemas/state machines/idempotency/changefeed reviewed + and test-covered. +3. **Scheduler hardened** — #856 time/retry/cancel/fencing/recovery behaviors proven, + including no duplicate execution. +4. **Authority bound** — #857 principal/familiar/authority/approval/receipt binding with + fail-closed negative tests. +5. **Certification** — #858 conformance/chaos/SLO/operator-diagnostics suites run without + ambient production credentials; the v1 release gate report is generated from them. +6. **Program rollup** — the final #854 release rollup can be generated from reconciled + tracker state and exact evidence; no P2 work has leaked onto the critical path. + +## Active blockers + +- **Bead provisioning pending** — the Automations v1 delivery epic and its + `surface:shared` beads do not exist yet in Cave's canonical Beads/Dolt graph; + [OpenCoven/coven-cave#5220](https://github.com/OpenCoven/coven-cave/issues/5220) + owns creation, dependency verification (`bd dep list`, `bd ready --json`), bounded + `pnpm beads:sync` evidence, and before/after `refs/dolt/data` OIDs. No competing Beads + database may be initialized in `OpenCoven/coven`. +- **Foundation evidence reconciliation** — + [#816](https://github.com/OpenCoven/coven/issues/816) implementation landed on main + (2026-08-28) but its evidence checklist (clean-clone verification, migration proof, + daemon wiring proof, run-path proof) is still open. +- **Cross-repository profiles** — the Familiar Contract and Threads profile outcomes that + #857 must depend on do not exist yet; `depends_on_external` stays empty and explicit + until they are created. + +## Evidence and completion semantics + +A bead may close only when the corresponding GitHub acceptance criteria are satisfied or +the outcome is explicitly cancelled/superseded with rationale. Required evidence includes, +as applicable: PR/merge commit and exact source revision; exact verification +commands/results; schema/vector/conformance artifact revisions; migration/rollback proof; +cross-repository canaries; security/privacy/authority impact; release artifact digest and +certification report; remaining known limitations. A GitHub issue is not closed merely +because a bead has no active assignee or a partial implementation landed. + +## Drift detection + +```sh +# verify the committed mapping, the generated roadmap block, and (optionally) an export +node docs/roadmaps/drift-check.mjs +node docs/roadmaps/drift-check.mjs --beads-export .beads/issues.jsonl # coven-cave checkout +node docs/roadmaps/drift-check.mjs --strict # pending provisioning also fails +node docs/roadmaps/drift-check.mjs --selftest # verify detection rules +``` + +The check reports identifiers, statuses, priorities, links, and evidence references only, +requires no network or credentials, and flags: state disagreement (bead closed while the +GitHub outcome is open and vice versa), priority disagreement, P0 beads without an active +P0 outcome (owner/gate/disposition missing), outcomes without exactly one bead mapping, +unknown/ambiguous parent or dependency mappings, dependency cycles, completed work +lacking PR/test/release evidence, generated mirror bodies edited outside the generator +contract, and tracker output containing secrets or sensitive payloads. Severity policy: +`error` fails CI; `warning` (currently the pending-provisioning `W010`) is reported +without failing until provisioning is declared, after which the missing-mapping class +escalates to `error`. diff --git a/docs/roadmaps/drift-check.mjs b/docs/roadmaps/drift-check.mjs new file mode 100644 index 00000000..62a9ef09 --- /dev/null +++ b/docs/roadmaps/drift-check.mjs @@ -0,0 +1,727 @@ +#!/usr/bin/env node +// Drift check for the Coven Automations v1 tracker mapping (OpenCoven/coven#859). +// +// Verifies that the machine-readable Bead <-> GitHub mapping +// (docs/roadmaps/coven-automations-v1.mapping.json), the generated mapping table +// inside docs/roadmaps/coven-automations-v1.md, and an optional Beads public +// export (.beads/issues.jsonl from OpenCoven/coven-cave) agree. +// +// Design constraints (from OpenCoven/coven#859): +// - runnable locally and in CI without ambient production credentials; +// - no network access; the optional Beads export is a local file; +// - reports identifiers, statuses, priorities, links, and evidence references only; +// - tracker data is never treated as production automation state. +// +// Usage: +// node docs/roadmaps/drift-check.mjs # verify committed state (exit 1 on error-severity drift) +// node docs/roadmaps/drift-check.mjs --strict # pending-provisioning warnings also fail +// node docs/roadmaps/drift-check.mjs --beads-export PATH # cross-check a Beads issues.jsonl export +// node docs/roadmaps/drift-check.mjs --render # regenerate the roadmap mapping table in place +// node docs/roadmaps/drift-check.mjs --selftest # run built-in detection fixtures + +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; + +const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +const ROADMAPS_DIR = path.resolve(SCRIPT_DIR); +const MAPPING_PATH = path.join(ROADMAPS_DIR, "coven-automations-v1.mapping.json"); +const ROADMAP_PATH = path.join(ROADMAPS_DIR, "coven-automations-v1.md"); + +const BLOCK_BEGIN = + ""; +const BLOCK_END = ""; + +const PRIORITIES = new Set(["P0", "P1", "P2"]); +const BEAD_PRIORITY_BY_NUMBER = { 0: "P0", 1: "P1", 2: "P2" }; + +// --------------------------------------------------------------------------- +// Sensitive-payload detection. Patterns are assembled from fragments so that +// this source file never itself contains a string matching the repo privacy +// guard or the detector below. +// --------------------------------------------------------------------------- + +function frag(...parts) { + return parts.join(""); +} + +const SENSITIVE_PATTERNS = [ + { + name: "coven_session_key", + pattern: new RegExp( + frag("agent:[A-Za-z0-9_-]+:(?:telegram|imessage|discord|whatsapp|", "signal|webchat):[a-z]+:\\S"), + ), + }, + { + name: "messenger_chat_id", + pattern: new RegExp( + frag("(?:telegram|imessage|discord|whatsapp|", "signal):(?:direct:)?\\d{6,}"), + ), + }, + { + name: "absolute_personal_path", + pattern: new RegExp(frag("/", "(?:Users|home)/[A-Za-z0-9._-]+/")), + }, + { + name: "runtime_internal_path", + pattern: new RegExp(frag("~/", "\\.(?:openclaw|coven)/(?:agents|workspaces|credentials|sessions)")), + }, + { + name: "phone_number", + pattern: new RegExp(frag("\\+[1-9]\\d{1,14}", "(?!\\d)")), + }, + { + name: "credential_bearing_url", + pattern: new RegExp(frag("ht", "tps?://\\S*(?:invite|handoff|ts\\.net)\\S*to", "ken\\S*")), + }, +]; + +function findSensitivePayloads(text) { + const hits = []; + for (const { name, pattern } of SENSITIVE_PATTERNS) { + const match = pattern.exec(text); + if (match !== null) { + hits.push({ rule: name, excerpt: "" }); + } + } + return hits; +} + +// --------------------------------------------------------------------------- +// Analysis core (pure; exercised by --selftest) +// --------------------------------------------------------------------------- + +function outcomeGithubRef(outcome) { + return `${outcome.github.repo}#${outcome.github.issue}`; +} + +function buildSlugIndex(mapping) { + const bySlug = new Map(); + for (const outcome of mapping.outcomes ?? []) { + bySlug.set(outcome.slug, outcome); + } + return bySlug; +} + +function findDependencyErrors(mapping) { + const findings = []; + const bySlug = buildSlugIndex(mapping); + const edges = new Map(); + + for (const outcome of mapping.outcomes ?? []) { + for (const dep of outcome.depends_on ?? []) { + if (!bySlug.has(dep)) { + findings.push({ + code: "E003", + severity: "error", + slug: outcome.slug, + message: `unknown dependency mapping: ${outcomeGithubRef(outcome)} depends on unknown slug '${dep}'`, + }); + } + } + edges.set(outcome.slug, [...(outcome.depends_on ?? [])]); + } + + const state = new Map(); + const stack = new Map(); + const visit = (slug) => { + if (state.get(slug) === "done") return true; + if (state.get(slug) === "visiting") { + findings.push({ + code: "E004", + severity: "error", + slug, + message: `dependency cycle involving '${slug}'`, + }); + return false; + } + state.set(slug, "visiting"); + for (const dep of edges.get(slug) ?? []) { + if (!visit(dep)) return false; + } + state.set(slug, "done"); + return true; + }; + for (const slug of edges.keys()) visit(slug); + + return findings; +} + +function findMappingErrors(mapping) { + const findings = []; + const seenRefs = new Map(); + const seenSlugs = new Set(); + const seenLabels = new Set(); + + for (const outcome of mapping.outcomes ?? []) { + const ref = outcomeGithubRef(outcome); + if (seenRefs.has(ref)) { + findings.push({ + code: "E001", + severity: "error", + slug: outcome.slug, + message: `GitHub outcome ${ref} maps to more than one bead ('${seenRefs.get(ref)}' and '${outcome.slug}')`, + }); + } else { + seenRefs.set(ref, outcome.slug); + } + + if (seenSlugs.has(outcome.slug)) { + findings.push({ + code: "E002", + severity: "error", + slug: outcome.slug, + message: `duplicate outcome slug '${outcome.slug}'`, + }); + } + seenSlugs.add(outcome.slug); + + const label = outcome.bead?.label; + if (label) { + if (seenLabels.has(label)) { + findings.push({ + code: "E002", + severity: "error", + slug: outcome.slug, + message: `duplicate bead label '${label}'`, + }); + } + seenLabels.add(label); + } + + const priority = outcome.github?.priority; + if (!PRIORITIES.has(priority)) { + findings.push({ + code: "E005", + severity: "error", + slug: outcome.slug, + message: `invalid or missing priority '${priority}' for ${ref} (expected one of ${[...PRIORITIES].join(", ")})`, + }); + } + + if (priority === "P0") { + const missing = []; + if (!outcome.github?.owner) missing.push("owner"); + if (!outcome.bead?.gate) missing.push("acceptance gate"); + if (!outcome.bead?.disposition) missing.push("disposition"); + if (missing.length > 0) { + findings.push({ + code: "E006", + severity: "error", + slug: outcome.slug, + message: `P0 outcome ${ref} is missing: ${missing.join(", ")}`, + }); + } + } + + const closedMirror = + outcome.github?.state === "closed" || + /^(complete|done|closed)/i.test(outcome.bead?.disposition ?? ""); + if (closedMirror && (outcome.bead?.evidence ?? []).length === 0) { + findings.push({ + code: "E007", + severity: "error", + slug: outcome.slug, + message: `completed work for ${ref} lacks PR/test/release evidence references`, + }); + } + } + + findings.push(...findDependencyErrors(mapping)); + return findings; +} + +function findPendingProvisioning(mapping) { + const findings = []; + for (const outcome of mapping.outcomes ?? []) { + if (outcome.bead?.id === null || outcome.bead?.id === undefined) { + const ref = outcomeGithubRef(outcome); + const provisioning = outcome.bead?.provisioning ?? "unrecorded"; + findings.push({ + code: "W010", + severity: "warning", + slug: outcome.slug, + message: `${ref} has no provisioned bead id yet (provisioning: ${provisioning})`, + }); + } + } + return findings; +} + +function renderMappingTable(mapping) { + const lines = [ + "| Outcome | GitHub | Priority | Bead label | Bead ID | Dependencies | Disposition |", + "| --- | --- | --- | --- | --- | --- | --- |", + ]; + const order = { program: 0, "p0-foundation": 1, "p0-workstream": 2 }; + const outcomes = [...(mapping.outcomes ?? [])].sort( + (a, b) => (order[a.role] ?? 9) - (order[b.role] ?? 9) || a.slug.localeCompare(b.slug), + ); + for (const outcome of outcomes) { + const ref = outcomeGithubRef(outcome); + const deps = (outcome.depends_on ?? []).join(", ") || "(none)"; + lines.push( + `| ${outcome.slug} | [${ref}](${outcome.github.url}) | ${outcome.github.priority} | \`${outcome.bead.label}\` | ${ + outcome.bead.id ?? "(pending provisioning)" + } | ${deps} | ${outcome.bead.disposition} |`, + ); + } + if ((mapping.cross_repository_children ?? []).length === 0) { + lines.push(""); + lines.push( + "_Cross-repository child outcomes: none created yet. One Bead per SDK, Cave, Psyche, docs, organization-canary, Familiar Contract, and Threads outcome under the program is mapped here one-to-one as each is created._", + ); + } + return lines.join("\n"); +} + +function extractGeneratedBlock(roadmapText) { + const begin = roadmapText.indexOf(BLOCK_BEGIN); + const end = roadmapText.indexOf(BLOCK_END); + if (begin === -1 || end === -1 || end < begin) return null; + const start = begin + BLOCK_BEGIN.length; + return roadmapText.slice(start, end).replace(/^\n/, "").replace(/\n\s*$/, "\n"); +} + +function findGeneratedBlockDrift(mapping, roadmapText) { + const committed = extractGeneratedBlock(roadmapText); + if (committed === null) { + return [ + { + code: "E008", + severity: "error", + slug: null, + message: "generated mapping table block missing from docs/roadmaps/coven-automations-v1.md", + }, + ]; + } + const expected = renderMappingTable(mapping); + if (committed.trimEnd() !== expected.trimEnd()) { + return [ + { + code: "E008", + severity: "error", + slug: null, + message: + "generated mapping table in docs/roadmaps/coven-automations-v1.md was edited outside the generator contract (run: node docs/roadmaps/drift-check.mjs --render)", + }, + ]; + } + return []; +} + +function beadPriorityLabel(priority) { + if (typeof priority === "number") return BEAD_PRIORITY_BY_NUMBER[priority] ?? `P${priority}`; + return String(priority ?? "unknown"); +} + +function collectBeadGithubRefs(bead) { + const haystack = [ + bead.external_ref, + bead.notes, + bead.design, + bead.acceptance_criteria, + ...(bead.comments ?? []).map((comment) => comment?.text ?? ""), + ] + .filter((value) => typeof value === "string") + .join("\n"); + const refs = new Set(); + for (const match of haystack.matchAll(/https:\/\/github\.com\/([\w.-]+\/[\w.-]+)\/issues\/(\d+)/g)) { + refs.add(`${match[1]}#${match[2]}`); + } + for (const match of haystack.matchAll(/\b([\w.-]+\/[\w.-]+)#(\d+)\b/g)) { + refs.add(`${match[1]}#${match[2]}`); + } + return refs; +} + +function findExportDrift(mapping, exportText) { + const findings = []; + const beads = []; + for (const [index, line] of exportText.split("\n").entries()) { + const trimmed = line.trim(); + if (!trimmed) continue; + let bead; + try { + bead = JSON.parse(trimmed); + } catch { + findings.push({ + code: "E100", + severity: "error", + slug: null, + message: `beads export line ${index + 1} is not valid JSON`, + }); + continue; + } + beads.push({ line: index + 1, bead }); + } + + for (const { line, bead } of beads) { + for (const hit of findSensitivePayloads(JSON.stringify(bead))) { + findings.push({ + code: "E009", + severity: "error", + slug: bead.id ?? null, + message: `tracker output contains sensitive payload (rule: ${hit.rule}) at export line ${line}`, + }); + } + } + + const byRef = new Map(); + for (const outcome of mapping.outcomes ?? []) { + byRef.set(outcomeGithubRef(outcome), outcome); + } + + const beadRefs = new Map(); + for (const { line, bead } of beads) { + for (const ref of collectBeadGithubRefs(bead)) { + if (!byRef.has(ref)) continue; + if (!beadRefs.has(ref)) beadRefs.set(ref, []); + beadRefs.get(ref).push({ line, bead }); + } + } + + for (const [ref, outcome] of byRef) { + if (outcome.bead?.id !== null && outcome.bead?.id !== undefined) { + const linked = beadRefs.get(ref) ?? []; + if (linked.length === 0) { + findings.push({ + code: "E101", + severity: "error", + slug: outcome.slug, + message: `GitHub outcome ${ref} has no bead referencing it in the export (expected exactly one)`, + }); + } else if (linked.length > 1) { + findings.push({ + code: "E101", + severity: "error", + slug: outcome.slug, + message: `GitHub outcome ${ref} is referenced by ${linked.length} beads in the export (expected exactly one)`, + }); + } + } + } + + for (const [ref, entries] of beadRefs) { + const outcome = byRef.get(ref); + for (const { bead } of entries) { + const beadOpen = bead.status !== undefined && !["closed", "done"].includes(bead.status); + const githubOpen = outcome.github?.state === "open"; + if (beadOpen !== githubOpen) { + findings.push({ + code: "E102", + severity: "error", + slug: outcome.slug, + message: `state drift for ${ref}: bead '${bead.id}' status '${bead.status}' vs GitHub state '${outcome.github?.state}'`, + }); + } + const expectedPriority = outcome.github?.priority; + const actualPriority = beadPriorityLabel(bead.priority); + if (PRIORITIES.has(expectedPriority) && actualPriority !== expectedPriority) { + findings.push({ + code: "E103", + severity: "error", + slug: outcome.slug, + message: `priority drift for ${ref}: bead '${bead.id}' is ${actualPriority}, mapping says ${expectedPriority}`, + }); + } + const labels = bead.labels ?? []; + if (!labels.includes("surface:shared")) { + findings.push({ + code: "E104", + severity: "error", + slug: outcome.slug, + message: `bead '${bead.id}' mapped to ${ref} lacks the surface:shared label (has: ${ + labels.length > 0 ? labels.join(", ") : "(none)" + })`, + }); + } + } + } + + return findings; +} + +function analyze(mapping, roadmapText, exportText) { + const findings = [ + ...findMappingErrors(mapping), + ...findGeneratedBlockDrift(mapping, roadmapText), + ...findPendingProvisioning(mapping), + ]; + if (typeof roadmapText === "string") { + for (const hit of findSensitivePayloads(roadmapText)) { + findings.push({ + code: "E009", + severity: "error", + slug: null, + message: `roadmap artifact contains sensitive payload (rule: ${hit.rule})`, + }); + } + } + const mappingText = JSON.stringify(mapping, null, 2); + for (const hit of findSensitivePayloads(mappingText)) { + findings.push({ + code: "E009", + severity: "error", + slug: null, + message: `mapping file contains sensitive payload (rule: ${hit.rule})`, + }); + } + if (exportText !== undefined) { + findings.push(...findExportDrift(mapping, exportText)); + } + return findings; +} + +function printFindings(findings) { + if (findings.length === 0) { + console.log("drift-check: no findings"); + return; + } + for (const finding of findings) { + const scope = finding.slug ? ` [${finding.slug}]` : ""; + console.log(`${finding.code}${scope} ${finding.severity}: ${finding.message}`); + } +} + +function loadMapping() { + return JSON.parse(fs.readFileSync(MAPPING_PATH, "utf8")); +} + +function writeRenderedBlock(mapping) { + let roadmap = fs.readFileSync(ROADMAP_PATH, "utf8"); + const begin = roadmap.indexOf(BLOCK_BEGIN); + const end = roadmap.indexOf(BLOCK_END); + if (begin === -1 || end === -1 || end < begin) { + console.error("drift-check: generated block markers missing from roadmap; cannot render"); + process.exitCode = 2; + return false; + } + const replacement = `${BLOCK_BEGIN}\n${renderMappingTable(mapping)}\n${BLOCK_END}`; + roadmap = roadmap.slice(0, begin) + replacement + roadmap.slice(end + BLOCK_END.length); + fs.writeFileSync(ROADMAP_PATH, roadmap); + return true; +} + +function runSelftest() { + const failures = []; + const expectFinding = (findings, code, label) => { + if (!findings.some((finding) => finding.code === code)) { + failures.push(`selftest: expected ${code} (${label}) to be detected`); + } + }; + const clone = (value) => JSON.parse(JSON.stringify(value)); + + const baseMapping = loadMapping(); + const baseRoadmap = fs.readFileSync(ROADMAP_PATH, "utf8"); + const pristine = analyze(baseMapping, baseRoadmap, undefined); + const pristineErrors = pristine.filter((finding) => finding.severity === "error"); + if (pristineErrors.length > 0) { + failures.push(`selftest: committed state has error-severity findings: ${JSON.stringify(pristineErrors)}`); + } + if (!pristine.some((finding) => finding.code === "W010")) { + failures.push("selftest: expected W010 pending-provisioning warnings on the committed mapping"); + } + + const duplicateRef = clone(baseMapping); + duplicateRef.outcomes[1].github.issue = duplicateRef.outcomes[2].github.issue; + expectFinding(analyze(duplicateRef, baseRoadmap, undefined), "E001", "duplicate GitHub mapping"); + + const unknownDep = clone(baseMapping); + unknownDep.outcomes[2].depends_on.push("does-not-exist"); + expectFinding(analyze(unknownDep, baseRoadmap, undefined), "E003", "unknown dependency"); + + const cycle = clone(baseMapping); + cycle.outcomes[0].depends_on.push("certification"); + cycle.outcomes[5].depends_on.push("program"); + expectFinding(analyze(cycle, baseRoadmap, undefined), "E004", "dependency cycle"); + + const badPriority = clone(baseMapping); + badPriority.outcomes[2].github.priority = "P9"; + expectFinding(analyze(badPriority, baseRoadmap, undefined), "E005", "invalid priority"); + + const noOwner = clone(baseMapping); + noOwner.outcomes[2].github.owner = null; + expectFinding(analyze(noOwner, baseRoadmap, undefined), "E006", "P0 without owner"); + + const closedNoEvidence = clone(baseMapping); + closedNoEvidence.outcomes[2].github.state = "closed"; + expectFinding(analyze(closedNoEvidence, baseRoadmap, undefined), "E007", "closed without evidence"); + + const tamperedBlock = baseRoadmap.replace( + /\| program \| \[/, + "| program (edited outside the generator contract) | [", + ); + expectFinding(analyze(baseMapping, tamperedBlock, undefined), "E008", "mirror edit"); + expectFinding( + analyze(clone(baseMapping), "no markers here", undefined), + "E008", + "missing generated block", + ); + + const sensitiveMapping = clone(baseMapping); + sensitiveMapping.outcomes[0].notes = [ + "operator note: ", + frag("agent:", "demo", ":telegram:", "direct", ":SECRETVALUE"), + ].join(""); + expectFinding(analyze(sensitiveMapping, baseRoadmap, undefined), "E009", "sensitive payload in mapping"); + + const exportFixtures = [ + { + label: "closed bead vs open outcome (E102)", + line: JSON.stringify({ + _type: "issue", + id: "automations-v1.1", + title: "protocol", + status: "closed", + priority: 0, + labels: ["surface:shared"], + external_ref: "https://github.com/OpenCoven/coven/issues/855", + }), + codes: ["E102"], + }, + { + label: "priority drift (E103)", + line: JSON.stringify({ + _type: "issue", + id: "automations-v1.1", + title: "protocol", + status: "open", + priority: 1, + labels: ["surface:shared"], + external_ref: "https://github.com/OpenCoven/coven/issues/855", + }), + codes: ["E103"], + }, + { + label: "missing surface:shared label (E104)", + line: JSON.stringify({ + _type: "issue", + id: "automations-v1.1", + title: "protocol", + status: "open", + priority: 0, + labels: ["surface:api"], + external_ref: "https://github.com/OpenCoven/coven/issues/855", + }), + codes: ["E104"], + }, + { + label: "sensitive payload in export (E009)", + line: JSON.stringify({ + _type: "issue", + id: "automations-v1.9", + title: "leaky", + status: "open", + priority: 0, + labels: ["surface:shared"], + notes: frag("session ", "agent:x", ":telegram:bot:", "SECRET"), + }), + codes: ["E009"], + }, + ]; + + const emptyOutcomeMapping = clone(baseMapping); + for (const outcome of emptyOutcomeMapping.outcomes) { + outcome.bead.id = null; + outcome.bead.provisioning = "selftest"; + } + + for (const fixture of exportFixtures) { + const findings = analyze(emptyOutcomeMapping, baseRoadmap, fixture.line); + for (const code of fixture.codes) { + expectFinding(findings, code, fixture.label); + } + } + + const duplicateBeadExport = [ + JSON.stringify({ + _type: "issue", + id: "automations-v1.1", + status: "open", + priority: 0, + labels: ["surface:shared"], + external_ref: "https://github.com/OpenCoven/coven/issues/855", + }), + JSON.stringify({ + _type: "issue", + id: "automations-v1.2", + status: "open", + priority: 0, + labels: ["surface:shared"], + external_ref: "OpenCoven/coven#855", + }), + ].join("\n"); + const dupFindings = analyze(emptyOutcomeMapping, baseRoadmap, duplicateBeadExport); + if (!dupFindings.some((finding) => finding.code === "E101")) { + // Provisioning is pending, so E101 only fires for outcomes with declared ids. + const declared = clone(baseMapping); + declared.outcomes[2].bead.id = "automations-v1.1"; + const declaredFindings = analyze(declared, baseRoadmap, duplicateBeadExport); + expectFinding(declaredFindings, "E101", "duplicate bead references for one outcome"); + } + + if (failures.length > 0) { + console.error(failures.join("\n")); + return false; + } + console.log(`drift-check: selftest passed (${SENSITIVE_PATTERNS.length} sensitive-payload rules, 11 drift fixtures)`); + return true; +} + +function main(argv) { + const args = argv.slice(2); + if (args.includes("--selftest")) { + process.exitCode = runSelftest() ? 0 : 1; + return; + } + + let mapping; + try { + mapping = loadMapping(); + } catch (error) { + console.error(`drift-check: cannot parse mapping: ${error.message}`); + process.exitCode = 2; + return; + } + + if (args.includes("--render")) { + const ok = writeRenderedBlock(mapping); + if (ok) console.log("drift-check: regenerated the roadmap mapping table"); + return; + } + + let roadmapText; + try { + roadmapText = fs.readFileSync(ROADMAP_PATH, "utf8"); + } catch (error) { + console.error(`drift-check: cannot read roadmap: ${error.message}`); + process.exitCode = 2; + return; + } + + let exportText; + const exportIndex = args.indexOf("--beads-export"); + if (exportIndex !== -1) { + const exportPath = args[exportIndex + 1]; + if (!exportPath) { + console.error("drift-check: --beads-export requires a path"); + process.exitCode = 2; + return; + } + exportText = fs.readFileSync(path.resolve(exportPath), "utf8"); + } + + const findings = analyze(mapping, roadmapText, exportText); + printFindings(findings); + + const strict = args.includes("--strict"); + const hasErrors = findings.some((finding) => finding.severity === "error"); + const hasWarnings = findings.some((finding) => finding.severity === "warning"); + if (hasErrors || (strict && hasWarnings)) { + process.exitCode = 1; + } +} + +main(process.argv); diff --git a/docs/start/coven-tui.md b/docs/start/coven-tui.md index 2b7b125d..0899d262 100644 --- a/docs/start/coven-tui.md +++ b/docs/start/coven-tui.md @@ -139,7 +139,7 @@ Selecting a session and pressing `Enter` shows contextual actions. Rejoin, View | **Archive** | session is not `running` and not archived | Hide from the active list; events preserved. | | **Sacrifice** | session is not `running` | Before the typed `sacrifice` confirmation, a store retention check runs; an adopted or reserved row returns the canonical `AdoptionRetentionError` denial instead and is left untouched. Once confirmed, the final delete repeats the retention and liveness checks and only removes a still-non-running row, closing the race window rather than trusting that earlier read. | -The map between actions and CLI verbs is documented in [Session lifecycle](/SESSION-LIFECYCLE). +The map between actions and CLI verbs is documented in [Session lifecycle](../SESSION-LIFECYCLE.md). ## Legacy SSH and remote use @@ -167,6 +167,6 @@ These verbs produce stable, scriptable output and are the same ones the TUI ulti ## Related - [Get started with Coven](https://docs.opencoven.ai/docs/guide/getting-started) -- [Session lifecycle](/SESSION-LIFECYCLE) -- [CLI reference](/reference/cli) +- [Session lifecycle](../SESSION-LIFECYCLE.md) +- [CLI reference](https://docs.opencoven.ai/docs/cli) - [Troubleshooting](https://docs.opencoven.ai/docs/reference/troubleshooting) diff --git a/docs/start/doctor.md b/docs/start/doctor.md index 7c15252a..b0c82ba1 100644 --- a/docs/start/doctor.md +++ b/docs/start/doctor.md @@ -1,24 +1,9 @@ --- -summary: "What coven doctor checks and how to read its output." -read_when: - - Diagnosing a fresh install or a broken environment title: "Doctor" -description: "Run coven doctor after install. It reports local readiness without launching providers, contacting provider networks, or verifying provider authentication." +description: "Pointer to the canonical coven doctor guidance." --- -`coven doctor` is the first command to run after install. It reports: +Canonical `coven doctor` guidance: **https://docs.opencoven.ai/docs/cli/doctor** -- Whether `$COVEN_HOME` is writable. -- Whether the daemon socket can bind. -- Whether `codex`, `claude`, and `copilot` are on `PATH`. -- Whether the SQLite store is reachable. - -Doctor is offline and hermetic: it launches no provider CLI process, performs -no provider network request, does not inspect provider tokens or credential -stores, and does not verify authentication. External harness credential rows -are advisory even when an executable is present. - -Each finding includes a remediation hint. Missing or unverified harnesses point -to `coven setup`, where provider-owned login and optional verification require -explicit consent. Re-run `coven doctor` after fixing any line marked -`needs attention`. +The offline no-auth-verification boundary is also stated in the +source-adjacent reference [`../reference/cli-doctor.md`](../reference/cli-doctor.md). diff --git a/docs/start/first-session.md b/docs/start/first-session.md index 5eaaf896..beb084e3 100644 --- a/docs/start/first-session.md +++ b/docs/start/first-session.md @@ -1,24 +1,6 @@ --- -summary: "A guided walkthrough of running, attaching, and archiving one session." -read_when: - - You have Coven installed and want a concrete walkthrough title: "Your first session" -description: "Walkthrough: launch your first Codex session in Coven, attach to it, watch it complete, and archive the result through the rituals surface." +description: "Pointer to the canonical Coven getting-started guide." --- -This walkthrough launches a Codex session, attaches to it, watches it complete, and archives the result. - - - - `cd` into a repo. Coven will canonicalize this path as the **project root**. - - - `coven run codex "describe the layout of this repo"` - - - `coven sessions` opens the browser. Select the new session and choose **Rejoin**. - - - Press `a` in the session browser or run `coven archive `. - - +Canonical first-session walkthrough: **https://docs.opencoven.ai/docs/guide/getting-started** diff --git a/docs/start/onboarding.md b/docs/start/onboarding.md index 601ff61e..8d8ec0f0 100644 --- a/docs/start/onboarding.md +++ b/docs/start/onboarding.md @@ -1,64 +1,10 @@ --- -summary: "Guided first run, project selection, harness verification, and ritual safety." -read_when: - - Walking a teammate through their first Coven setup title: "Onboarding" -description: "The coven onboarding flow: confirm COVEN_HOME, run doctor, validate a project root, pick a harness, and launch your first supervised session." +description: "Pointer to the canonical Coven getting-started guide." --- -Bare `coven`, `coven chat`, and `coven tui` open the managed Coven interactive -UI powered by `coven-code`. On the first interactive run, Coven offers to -install the pinned engine if it is missing. The onboarding flow: +Canonical onboarding guidance: **https://docs.opencoven.ai/docs/guide/getting-started** -1. Confirms `$COVEN_HOME` and creates it if missing. -2. Runs `coven doctor` and surfaces install hints. -3. Asks for the project root and validates it. -4. Picks a harness (`codex`, `claude`, or `copilot`) and checks that its CLI is - visible. -5. Suggests the safest first command. - -Doctor does not log in to a provider or verify provider access. Complete the -provider-owned login in the same terminal: - -```sh -coven setup codex -# or -coven setup claude -# or -coven setup copilot -``` - -These run `codex login`, `claude auth login`, and `copilot login` -respectively, after explicit consent. Use `coven setup all` to process all -three providers in order. - -Provider verification is optional and separately consented because it uses the -network and may incur provider usage or cost: - -```sh -coven setup codex --verify -# or, when login is already complete: -coven setup codex --verify-only -``` - -Setup requires a TTY and hands stdin, stdout, and stderr directly to the -provider. It does not capture provider output or emit machine JSON while the -provider runs. Release operators can write an atomic, redacted, fail-if-exists -report for one provider with `--report-json `. See -[`coven setup`](/reference/cli-setup) for the full privacy and report contract. - -The older in-process TUI is available only as the deprecated temporary -compatibility fallback `COVEN_LEGACY_TUI=1`; see [Coven TUI](/start/coven-tui) -for its legacy behavior. - -## First session - -After setup: - -```sh -coven doctor -coven daemon start -cd /path/to/project -coven run codex "explain this repo in 5 bullets" -coven sessions -``` +The provider-owned `coven setup` login, consent, and report contract is +described at **https://docs.opencoven.ai/docs/cli/setup**; its normative +reference remains in [`../reference/cli-setup.md`](../reference/cli-setup.md). diff --git a/docs/start/quickstart.md b/docs/start/quickstart.md index 86a5b296..66fb0b40 100644 --- a/docs/start/quickstart.md +++ b/docs/start/quickstart.md @@ -1,18 +1,6 @@ --- -summary: "The shortest copy-pasteable path to a live Coven session." -read_when: - - You already know what Coven is and want commands title: "Quickstart" -description: "Quickstart for Coven: install @opencoven/cli, run doctor, start the daemon, and summon your first Codex or Claude Code harness in a project root." +description: "Pointer to the canonical Coven getting-started guide." --- -```bash -npm install -g @opencoven/cli -coven doctor -coven daemon start -cd /path/to/your/project -coven run codex "fix the failing tests" -coven sessions -``` - -See [Getting started](/start/getting-started) for context. +Canonical getting-started guidance: **https://docs.opencoven.ai/docs/guide/getting-started** diff --git a/docs/start/showcase.md b/docs/start/showcase.md index df06b98b..98213f20 100644 --- a/docs/start/showcase.md +++ b/docs/start/showcase.md @@ -1,30 +1,7 @@ --- -summary: "Highlights of what Coven can do today and where it is heading." -read_when: - - Browsing for a one-page overview of Coven's value title: "Showcase" -description: "Showcase landing for Coven: a local-first runtime that supervises every coding-agent harness inside explicit project roots with auditable rituals." +description: "Pointer to the canonical Coven getting-started guide." --- -
-

Coven

-

A local-first runtime that supervises every coding-agent harness inside explicit project roots, with append-only events and rituals you can audit.

- -
- -## Highlights - - - - Three supported harnesses, more on the way through the adapter spec. - - - Session shape that CastCodes and advanced clients can replay. - - - Archive, summon, sacrifice — explicit verbs around destructive operations. - - +The canonical public journey starts at: +**https://docs.opencoven.ai/docs/guide/getting-started** diff --git a/docs/superpowers/plans/2026-08-30-coven-automations-v1-program-status.md b/docs/superpowers/plans/2026-08-30-coven-automations-v1-program-status.md new file mode 100644 index 00000000..d549dfcc --- /dev/null +++ b/docs/superpowers/plans/2026-08-30-coven-automations-v1-program-status.md @@ -0,0 +1,93 @@ +# Coven Automations v1 Program Status Record — 2026-08-30 + +**Type:** status/decision record (verified facts; no task plan) +**Subject:** OpenCoven/coven issue #854 — Program: Coven Automations v1 +**Evidence snapshot:** upstream `main` at `1364cec9dbaf1e2aca2e4544dec0e1ce807d859c` (2026-08-30), inspected locally; GitHub state read via REST on 2026-08-30 ~15:03–15:20 UTC +**Deconfliction:** no open PR references #854 upstream, and no `agent/*854*` branch exists on the CompleteDotTech/coven fork (checked 2026-08-30 ~15:08 UTC) + +--- + +## Verdict + +**Coven Automations v1 is not satisfied on `main`. The local durable-scheduler foundation has landed and is independently confirmed in code; the v1 protocol, authority, conformance, SDK, and tracker-control work has not started.** The issue's own "foundation-ready, not yet v1-certified" assessment (final implementation assessment, 2026-08-30) matches the code; every P0 child issue (#855–#859) was opened on 2026-08-30 and is open with no landed work yet. + +- Definition of done status: **not met** (no P0 gate is implemented and evidenced end-to-end; no release candidate exists). +- #854 must remain open: this record closes nothing. The one delivered artifact is this record itself, which unblocks #816's evidence-closure item and #859's mapping task. + +## What exists on `main` today (evidence) + +The `coven#816` automations series landed 2026-08-28 (PR #846 merged 2026-08-28T14:22:30Z; PR #847 merged 2026-08-28T19:55:52Z; parts 6–8 commits dated 2026-08-28 arrived via consolidated merges `52c3d81` 2026-08-29 and `1364cec` 2026-08-30). All of the following was verified by direct inspection of `main` at `1364cec`: + +| Foundation element | Evidence on main | +| --- | --- | +| Versioned Coven-owned routine definitions | `crates/coven-cli/src/automations/definition.rs` (217 lines; introduced in `882fc83`, PR #846) | +| SQLite definition / occurrence / run records | `automations/store.rs:14` (`automation_definitions`), `automations/occurrences.rs:20` (`automation_occurrences`), `automations/runs.rs:13` (`automation_runs`) | +| RRULE-backed daily/weekly planning | `automations/rrule.rs` (180 lines, 8 unit tests), `automations/schedule.rs` (178 lines, 6 unit tests) | +| Unique occurrence fencing | `automations/occurrences.rs:31` — `UNIQUE(automation_id, scheduled_for)`; planning is idempotent | +| Claim leases and expiry/recovery | `automations/health.rs:23-24,79-106` (`lease_owner`, `lease_expires_at`, `stale_reason`) | +| Latest-only misfire, overlap refusal | defaults `misfire: "latest"`, `overlap: "forbid"` (`definition.rs:61-62,159-160`; enforced in `daemon_tick.rs:69-70`) | +| Daemon-side recurring tick + scheduled dispatch | `automations/daemon_tick.rs:33-51` (thread `coven-automations-scheduler`, fixed 60s cadence), wired at `crates/coven-cli/src/daemon.rs:4285`; shared launch path in `automations/runner.rs` (415 lines) | +| Familiar ID propagation, bounded logs, atomic delivery | `definition.rs:68` (`familiar_id: Option`), `runner.rs` | +| Health + run-history projections | `automations/health.rs` (203 lines), `automations/runs.rs` (307 lines) | +| Non-destructive paused legacy import | `automations/import_legacy.rs` (249 lines; reads `~/.codex/automations//automation.toml`, imports PAUSED, never modifies sources; PR #847 merged 2026-08-28T19:55:52Z) | +| `coven.automations.*` control actions | `crates/coven-cli/src/control_plane.rs:103-118` — capability domain `coven.automations` with 10 actions (`list`, `get`, `create`, `update`, `delete`, `tick`, `runs`, `run`, `import`, `health`); API-level tests in `crates/coven-cli/src/api.rs` (~lines 10639–10845) | + +Module size: `crates/coven-cli/src/automations/` is 11 files / 2,719 lines with 43 unit tests (per-file `#[test]` counts summed); exercised further by `crates/coven-cli/src/api.rs` integration tests. Cave-side ownership migration is reported in OpenCoven/coven-cave#4990 (per #816's body; not independently verified in this repo). + +### What is absent on `main` (verified) + +- No automations spec under `specs/` (12 spec directories, none for automations) and no `coven.automations.v1` schema, state-machine, typed-error, idempotency, or changefeed contract anywhere on main → #855. +- No automations documentation under `docs/` (grep for "automations" returns nothing) → coven-docs#76. +- No automation surface in the npm SDK `npm/coven/src` (no matches) → sdk#80. +- No Beads store in this repo (`.beads` absent) and no live-Dolt mutation yet → #859 (its 2026-08-30 comment states mutation of Cave's embedded-Dolt Beads graph is "not yet completed"). +- No conformance, chaos, load/SLO, or release-receipt gate → #858. +- No principal/capability/approval/receipt binding: `familiar_id` is an optional unversioned string validated only for length (`definition.rs:68,131-134`); `automation_runs` carries no authority evidence → #857 (+ familiar-contract#17, coven-threads#29, cross-repo). +- Scheduler cadence is a fixed wall-clock `thread::sleep(60s)` loop (`daemon_tick.rs:35-50`) with no virtual-time, DST-transition, clock-jump, or leader-fencing contract → #856. +- A routine remains a schedule + familiar-bound prompt (`definition.rs:4`), not a trigger/condition/authorized-action model. + +## Program issue family (REST state, 2026-08-30) + +| Issue | State | Created | Evidence note | +| --- | --- | --- | --- | +| #854 program | open | 2026-08-30T13:36:04Z | 1 comment: BunsDev operationalization checkpoint (14:06:20Z) | +| #816 foundation | open | 2026-08-24T15:32:52Z | body records foundation "materially landed"; closure blocked on an evidence checklist (updated 2026-08-30T13:54:46Z) | +| #855 protocol schemas | open | 2026-08-30T13:37:23Z | no activity | +| #856 time/fencing/crash hardening | open | 2026-08-30T13:38:31Z | no activity | +| #857 authority binding + receipts | open | 2026-08-30T13:39:54Z | 1 design comment (receipt replay resistance, 14:23:31Z) | +| #858 conformance/chaos/SLO | open | 2026-08-30T13:40:55Z | no activity | +| #859 Beads/GitHub mirrors | open | 2026-08-30T13:41:50Z | 1 comment routing the graph to Cave's embedded Dolt DB (`cave-hlv` epic; coven-cave#5219 roadmap PR; coven-cave#5220 seed/verification task) | + +Cross-repo outcomes cited by the #854 checkpoint comment (not independently verified in this sweep): P0 — OpenCoven/familiar-contract#17, OpenCoven/coven-threads#29; P1 — OpenCoven/sdk#80, OpenCoven/coven-cave#5217, OpenCoven/psyche#18, OpenCoven/coven-docs#76, OpenCoven/.github#2. Zero PRs are open upstream at snapshot time; no PR implements any of #855–#859 yet. + +## Verdict against the issue's gates + +- **Gate A (durable local scheduler):** partially met — deterministic planning, unique occurrence fencing, bounded leases, latest-only misfire, overlap refusal, 60s daemon tick, and 43 module unit tests exist; DST/virtual-time/clock-jump/restart-convergence certification does not (#856 open). +- **Gate B (identity and authority):** not met — optional string `familiar_id` only; no principal authorization, capability grants, approval path, or exercised-authority receipts on any run record (#857, familiar-contract#17, coven-threads#29). +- **Gate C (public contract):** not met — the wire contract lives in Rust structs (`definition.rs`) with no independent versioned schemas or golden vectors; the SDK has no automation surface (#855, sdk#80). +- **Gate D (operations):** partial — health snapshot and run-history projections exist and are CLI/API-observable; chaos/restart certification, load/SLO evidence, alerts/retention/redaction exercise, and a machine-readable release receipt do not exist (#858 open). +- **Tracker (Beads/GitHub graph):** not started — the canonical graph lives in Cave's Dolt database; the seeding/verification task (coven-cave#5220) has not been executed (#859 open). + +## Critical path + +The #854 checkpoint comment (2026-08-30T14:06:20Z) fixes the engineering sequence, which matches the issue's Beads dependency rules and this record's code findings: + +1. coven-cave#5220 / #859 — seed and verify the Automations v1 delivery epic in Cave's embedded-Dolt Beads graph (first executable action). +2. #816 — attach landed-series evidence (PRs #846/#847 + parts 6–8 commits, clean-clone test run, migration and daemon/restart verification), then close #816 as the landed foundation. +3. #855 — versioned `coven.automations.v1` schemas, state machines, idempotency, typed errors, changefeed. +4. In parallel: #856 (deterministic time, DST, retries, cancellation, fencing, crash recovery) + familiar-contract#17 + coven-threads#29. +5. #857 — dispatch-time principal/familiar/authority/runtime/approval binding and receipts. +6. #858 — conformance, chaos, security/privacy, load/SLO, operator diagnostics. +7. P1 consumption — sdk#80, coven-cave#5217 (Cave oversight), psyche#18, coven-docs#76, .github#2. +8. Exact-release go/no-go packet (release receipt, certification). + +## Decision + +- Do **not** close #854, #816, #855–#859. #854 is correctly decomposed; its verdict ("foundation-ready, not yet v1-certified") is independently confirmed by the code inspection above. +- Unattended external side effects stay out of scope until #857 and #858 pass at exact immutable artifacts (per the #854 safety gate). +- Next executable actions: coven-cave#5220 (live Beads seeding) and #816 evidence closure; both precede any #855 contract work. + +## Sources + +- Code: `crates/coven-cli/src/automations/` at `1364cec` (files, tests, and line refs as cited above); `crates/coven-cli/src/control_plane.rs:103-116`; `crates/coven-cli/src/daemon.rs:4285`. +- History: commits `882fc83` (part 1, PR #846, merged 2026-08-28T14:22:30Z), `39b8618` (part 5, PR #847, merged 2026-08-28T19:55:52Z), `bd3b47d`/`a4a71af`/`1de50a8` (parts 6–8, 2026-08-28), `52c3d81` (2026-08-29), `1364cec` (2026-08-30). +- Issues (all read 2026-08-30 via REST): OpenCoven/coven#854, #816, #855, #856, #857, #858, #859; cross-repo train per the #854 checkpoint comment (familiar-contract#17, coven-threads#29, sdk#80, coven-cave#5217/#5219/#5220, psyche#18, coven-docs#76, .github#2). diff --git a/docs/superpowers/plans/2026-08-30-issue-807-shipped-reliability-scorecard.md b/docs/superpowers/plans/2026-08-30-issue-807-shipped-reliability-scorecard.md new file mode 100644 index 00000000..7e0613d4 --- /dev/null +++ b/docs/superpowers/plans/2026-08-30-issue-807-shipped-reliability-scorecard.md @@ -0,0 +1,172 @@ +# Issue #807 Status Record — Shipped Reliability, Recovery, and Usefulness Scorecard + +> **For agentic workers:** This is a dated status/decision record, not an +> implementation plan. It documents what exists on `main` relative to +> [OpenCoven/coven#807](https://github.com/OpenCoven/coven/issues/807) at the +> recorded base SHA. Facts and evidence links only; it proposes no SLOs and +> quotes no measurements. + +**Date:** 2026-08-30 +**Issue:** [#807 — P1: establish Coven shipped reliability, recovery, and usefulness scorecard](https://github.com/OpenCoven/coven/issues/807) (open; created 2026-08-24; 0 comments; no labels) +**Base inspected:** `main` @ [`1364cec`](https://github.com/OpenCoven/coven/commit/1364cec9) ("chore: preserve consolidated branch ancestry", 2026-08-30 06:31 -0500) +**Related issues at inspection:** [#805](https://github.com/OpenCoven/coven/issues/805) (P0 exact-commit release governance — **open**), [#779](https://github.com/OpenCoven/coven/issues/779) (installed-artifact E2E certification — **open**) +**Method:** REST-only GitHub API (search + pulls + branches for deconfliction; issues, releases, release-by-tag lookups) plus local inspection of the clone at the base SHA. No PR existed for #807 and no fork branch referenced it at inspection time (search/issues, `pulls?state=open`, `branches?per_page=100`, 2026-08-30). No benchmarks were executed for this record; every statement below is an existence/capability statement about code and docs on `main`, not a measurement. + +--- + +## What exists on main today + +### 1. Non-gating benchmark/trend corpus (the "strong test corpus" the issue refers to) + +| Instrument | What it covers | Evidence | +| --- | --- | --- | +| `scripts/benchmark-cli.mjs` | Command startup, cold daemon start-to-health, session-listing, event-tail, harness-first-output timings; per-run min/median/p95/max; disposable `COVEN_HOME`s, fake Codex fixture, env redaction. Last touched 2026-08-11. | [`scripts/benchmark-cli.mjs`](https://github.com/OpenCoven/coven/blob/1364cec9/scripts/benchmark-cli.mjs); [README §"CLI performance baselines"](https://github.com/OpenCoven/coven/blob/1364cec9/README.md) (lines ~702–721) | +| `scripts/benchmark-chaos.mjs` (report schema v3) | 1/8/32 concurrent deterministic sessions; launch-to-first-output percentiles; throughput; cancellation-to-terminal latency; SQLite file growth; writer connection/transaction deltas; sampled writer backlog; sampled daemon RSS via `coven pc top --json` (no process names/command lines retained); deterministic equivalents for free-disk watermark, SQLite lock/retry, persisted-session crash recovery. Last touched 2026-08-08. | [README §"Concurrent runtime baseline"](https://github.com/OpenCoven/coven/blob/1364cec9/README.md) (lines ~723–756); [`scripts/benchmark-chaos.mjs`](https://github.com/OpenCoven/coven/blob/1364cec9/scripts/benchmark-chaos.mjs) | +| CI collection (non-gating) | Both collectors run in the `performance-baseline` CI job with `continue-on-error: true` (ci.yml lines 248, 281); artifacts uploaded, no wall-clock gate. The deterministic fixture tests (`benchmark-cli.test.mjs`, `benchmark-chaos.test.mjs`) do gate. | [`.github/workflows/ci.yml`](https://github.com/OpenCoven/coven/blob/1364cec9/.github/workflows/ci.yml) | +| Deterministic Rust metric test | Ignored test `benchmark_schedule_metrics_emit_json` prints deterministic TUI poll/draw counters. | README lines ~709, 716–717 | + +The README already states the separation the issue demands: outputs are +"trend data", benchmark p50/p95/p99 "do not replace that product-level +timeout" (the Cave managed-start 8-second deadline), and chaos coverage +entries "remain separate from trend measurements, so a timing artifact cannot +be mistaken for a passing failure-path test". + +**Relevance to #807 and gap:** these are **benchmark condition/input** +instruments. They are per-run JSON artifacts — no history is retained on +`main`, no multi-run trend table exists, and the default sample count is 3 +iterations (`--iterations 3`), which the issue's non-goals correctly disallow +quoting as product statistics. + +### 2. Health/readiness and recovery surfaces (instrumentation for journey rows) + +- `GET /api/v1/health` includes a `storage` object: SQLite/WAL sizes, free + space, oldest retained event, prune/checkpoint ages, writer backlog; + `storage.status` becomes `critical` with `maintenanceBlocked: true` below + 256 MiB free. Recovery logging rotates at 4 MiB with three archives. + [docs/daemon/health.md](https://github.com/OpenCoven/coven/blob/1364cec9/docs/daemon/health.md). +- `coven doctor` gives first-run readiness guidance with no harness on PATH + ([docs/reference/cli-doctor.md](https://github.com/OpenCoven/coven/blob/1364cec9/docs/reference/cli-doctor.md)); + `coven setup --verify-only --report-json` emits a **redacted + certification report carrying only harness, cli_version, platform, + candidate_commit, duration, exit_class, completed** (v0.4.1 release notes). +- Recovery/operations docs and landed plans: orphan recovery, session handoff + and cursor recovery, upgrades, diagnostics under + [docs/daemon/](https://github.com/OpenCoven/coven/tree/1364cec9/docs/daemon); + plans `2026-08-01-incomplete-work-recovery`, `2026-08-06-session-handoff-cursor`, + `2026-08-05-output-truncation-markers`, `2026-08-09-pty-sigterm-load-resilience`, + `2026-08-03-mobile-pairing-retry-recovery`, + `2026-08-03-universal-runtime-capability-recovery` (all in + [docs/superpowers/plans/](https://github.com/OpenCoven/coven/tree/1364cec9/docs/superpowers/plans)). + +### 3. Release certification and structured-receipt pattern + +- [`scripts/certify-release.sh`](https://github.com/OpenCoven/coven/blob/1364cec9/scripts/certify-release.sh) + (added 2026-08-29, commit [`e0ad4b0`](https://github.com/OpenCoven/coven/commit/e0ad4b0)): + three-harness release certification packet; runs `coven setup + --verify-only --report-json` against real accounts and verifies + every report certifies the tagged commit. Operator-run local step (needs a + TTY; costs real provider turns). +- The [v0.4.1 release program plan](https://github.com/OpenCoven/coven/blob/1364cec9/docs/superpowers/plans/2026-08-20-coven-v0.4.1-release-program.md) + (2026-08-20) specifies a `release-evidence/v0.4.1-certification.json` + structured receipt bound to a frozen SHA. **`release-evidence/` is not + committed on `main` at the base SHA** — the receipt exists as a program + pattern, not a repo artifact. +- Published releases observed via REST (2026-08-30): v0.4.1 + (2026-08-28T15:29:09Z; 4 platform tarballs + `SHA256SUMS`), v0.4.0/v0.3.x + (2026-08-24), v0.2.5 (2026-08-09). The v0.4.1 release body documents the + redacted `--report-json` certification contract. + +### 4. Packaged-artifact journey evidence + +- [`scripts/user-journey-e2e.mjs`](https://github.com/OpenCoven/coven/blob/1364cec9/scripts/user-journey-e2e.mjs) + (updated 2026-08-28, commit [`8ae39cd`](https://github.com/OpenCoven/coven/commit/8ae39cd)): + hermetic npm-package journey — help contract, first-run `doctor` guidance, + fake Codex + engine fixture, daemon lifecycle, a real packaged `coven run` + turn, sessions/show/events/log inspection, archive/summon/sacrifice, bounded + `--cwd` rejection, daemon cleanup. Binary **pass/fail** journey coverage — + it does not yet emit stage-level timing/failure-stage observations. +- [`scripts/release-stress.mjs`](https://github.com/OpenCoven/coven/blob/1364cec9/scripts/release-stress.mjs) + + [`release-stress.yml`](https://github.com/OpenCoven/coven/blob/1364cec9/.github/workflows/release-stress.yml) + (added 2026-08-24 — the same day #807 was filed): bounded reliability stress + workflow, `workflow_dispatch`, OS matrix. + +### 5. AgentFS / boundary posture + +- `crates/coven-afs` with dedicated CI jobs `afs-mount-linux` / `afs-mount-macos` + (clippy+tests under the mount feature; a real-mount probe is + informational-only), plus `scripts/afs-mount-e2e.sh` / `afs-mount-smoke.sh` + and plan `2026-08-09-afs-macos-consent-confirmation`. The mount backend is + feature-gated with an informational probe rather than inheriting a generic + green test count — matching the issue's posture, though no certification + matrix for credential-observation/case-insensitivity/handle-reuse outcomes + is published. + +### 6. CI routing context + +`scripts/classify-ci-changes.py` routes docs-only changes away from the +Rust/Windows/macOS/AFS matrix (relevant to landing the scorecard document +itself); the policy guard (secret scan + privacy guard) runs on PRs. + +## What does not exist (grep- and path-verified at `1364cec`, 2026-08-30) + +- **No scorecard document anywhere** — case-insensitive grep for `scorecard` + across docs/, specs/, scripts/, crates/, workflows: 0 hits. +- **No metric-contract records** — no adopted metric carries the issue's + required fields (definition, numerator/denominator, cohort, window, source, + privacy treatment, owner-approved target, confidence, breach action). +- **No retained trend/observation history** — benchmark results are per-run + CI artifacts; nothing on `main` accumulates samples across runs. +- **No usefulness/outcome measurement** — no opt-in beta telemetry or study + harness exists (consistent with the issue's non-goals). +- **No escaped-defect / discovery-source / rollback tracking**; #805 and + #779, which would feed the release-quality rows, are both open. + +## Verdict against #807 acceptance criteria + +| # | Criterion (paraphrased) | Verdict | Basis | +| --- | --- | --- | --- | +| 1 | One current scorecard with definition/source/window/owner/confidence per row | **Not met** | No scorecard artifact exists (grep: 0 hits). | +| 2 | Journey / operation-reliability / recovery / unknown / output-loss / compatibility / release-quality rows have a baseline or are `not yet measured` with owner | **Not met as a decision view** | Instruments exist (§2–§4 above) but no view publishes baselines or an owned not-yet-measured registry. | +| 3 | Benchmark inputs/targets never displayed as achieved product results | **Partial** | Nothing violates it (no scorecard exists); existing convention already enforces the separation (README "trend data", chaos coverage vs. trend separation, 8 s product deadline noted). | +| 4 | Release certification (#779) populates the scorecard from a structured receipt | **Partial** | `certify-release.sh` (2026-08-29) + the redacted `--report-json` contract and the release-program receipt pattern exist; no receipt→scorecard pipeline, and no receipt is committed. | +| 5 | No privacy-sensitive prompts/credentials/content required to compute metrics | **Met for existing instruments** | By construction: disposable homes, fake harness fixtures, env redaction, `pc top --json` without process names, redacted certification reports carrying only the fields listed above. | +| 6 | Thresholds have explicit actions, not decorative dashboards | **Not met** | No thresholds adopted anywhere; README explicitly keeps baselines non-gating until they exist. | + +**Overall:** #807 is **not satisfied on `main`** as of `1364cec` (2026-08-30). +The measurement corpus is substantially stronger than the issue's framing +suggests (chaos diagnostics already cover output-loss, cancellation, +backpressure, and crash-recovery determinism), but the decision-grade +scorecard — the actual deliverable — does not exist. + +## What remains (critical path, dependency-ordered) + +1. **Create the scorecard** at a decided home (e.g. `docs/development/` or + `docs/reference/`) with the five row labels (**Observed current / + Historical observation / Target/SLO / Benchmark condition/input / Not yet + measured**), seeded from the instruments in §1–§4; everything without an + adopted baseline ships as `not yet measured` with an owner. +2. **Attach the metric contract** to every row (definition, cohort, window, + source+privacy treatment, confidence, breach action). No target/SLO row may + be created without an owner-approved decision. +3. **Structured receipts → scorecard**: emit machine-readable receipts from + `certify-release.sh`, `user-journey-e2e.mjs`, and the benchmark collectors + (the `release-evidence/` pattern from the v0.4.1 program), so the scorecard + links raw evidence instead of embedding tables. +4. **Sample counts before statistics**: raise benchmark iteration counts and + accumulate multi-run chaos samples (currently 3 iterations default; per-run + artifacts only) before any p95/p99 is quoted as an observed product value. +5. **Release-quality rows** land after #805 (exact-commit governance) and + #779 (per-platform artifact certification) provide their evidence feeds. +6. **Usefulness rows** stay `not yet measured` until an accountable product + decision defines the cohort and opt-in mechanism (the issue forbids + inventing adoption targets). +7. **Regression budgets with explicit breach actions** must precede any + performance check becoming gating. + +## Decision + +This record establishes the verified status of #807 for planning. It does not +implement the scorecard and introduces no measurements, targets, or SLOs. The +next dependent step is the implementation PR described in "What remains" +items 1–3; items 4–7 are explicitly blocked on the named decisions/issues, not +on further investigation. diff --git a/docs/superpowers/plans/2026-08-30-issue-859-coven-automations-v1-tracker-operationalization.md b/docs/superpowers/plans/2026-08-30-issue-859-coven-automations-v1-tracker-operationalization.md new file mode 100644 index 00000000..6a296f5b --- /dev/null +++ b/docs/superpowers/plans/2026-08-30-issue-859-coven-automations-v1-tracker-operationalization.md @@ -0,0 +1,203 @@ +# Issue #859 Status/Decision Record — Operationalize Coven Automations v1 in Beads and GitHub roadmap mirrors + +**Date:** 2026-08-30 +**Author:** Timothy Wayne Gregg +**Scope:** Investigation and reviewed tracker-setup deliverables for +[OpenCoven/coven#859](https://github.com/OpenCoven/coven/issues/859) ("P0 control: +Operationalize Coven Automations v1 through Cave's canonical Beads graph and GitHub +mirrors"). Facts only; each claim carries an evidence link or exact command. + +--- + +## 1. What was inspected + +- Upstream `OpenCoven/coven` `main` at `1364cec` (`1364cec9dbaf1e2aca2e4544dec0e1ce807d859c`), cloned 2026-08-30. +- GitHub issues [OpenCoven/coven#854](https://github.com/OpenCoven/coven/issues/854) (opened 2026-08-30T13:36:04Z), [#816](https://github.com/OpenCoven/coven/issues/816) (2026-08-24T15:32:52Z), [#855](https://github.com/OpenCoven/coven/issues/855), [#856](https://github.com/OpenCoven/coven/issues/856), [#857](https://github.com/OpenCoven/coven/issues/857), [#858](https://github.com/OpenCoven/coven/issues/858) (all opened 2026-08-30T13:37–13:41Z), and [#859](https://github.com/OpenCoven/coven/issues/859) (2026-08-30T13:41:50Z); all open, all assigned to `BunsDev`, no labels, no milestones. +- The single comment on #859 (BunsDev, 2026-08-30T14:05:44Z, + [comment 5469149789](https://github.com/OpenCoven/coven/issues/859#issuecomment-5469149789)) + — the operational correction redirecting the canonical Beads store to Cave. +- `OpenCoven/coven-cave` issues #5219 (roadmap/operating contract, opened 2026-08-30T14:03:02Z) and #5220 (Beads/Dolt seeding, opened 2026-08-30T14:04:34Z); both open. +- `OpenCoven/coven-cave` local checkout `.beads/` directory: `issues.jsonl` (public-scrubbed export), `config.yaml` (sync remote `git+https://github.com/OpenCoven/coven-cave.git`), README, hooks. +- Upstream CI state on the base SHA (see §4). +- Search for existing work on #859: no open PRs reference 859 (`search/issues` and `repos/OpenCoven/coven/pulls?state=open`, 2026-08-30); no `859` branch on `CompleteDotTech/coven`. + +## 2. What exists on `main` today + +**The native automations foundation is implemented on `main`; the tracker graph is not.** + +- `crates/coven-cli/src/automations/` exists on main: 11 Rust files, 2,719 lines total + (`definition.rs`, `store.rs`, `occurrences.rs`, `rrule.rs`, `schedule.rs`, `runner.rs`, + `runs.rs`, `health.rs`, `import_legacy.rs`, `daemon_tick.rs`, `mod.rs`), plus daemon + integration in `crates/coven-cli/src/daemon.rs`. +- The landed series is dated 2026-08-28 and is enumerated by + [#816's program-status section](https://github.com/OpenCoven/coven/issues/816) + (in the issue body, updated 2026-08-30): PR [#846](https://github.com/OpenCoven/coven/pull/846) (routine + definitions and control actions, part 1 — commit `882fc83`) and PR + [#847](https://github.com/OpenCoven/coven/pull/847) (legacy import — merge commit + `58bc547`), with parts 5–8 as commits `39b8618`, `bd3b47d`, `a4a71af`, `1de50a8`. + #816's comment enumerates what landed: versioned routine definitions; SQLite + definition/occurrence/lease/run storage; RRULE planning; unique occurrence fencing and + bounded claim leases; expired-lease recovery, latest-only misfire, overlap refusal; + daemon recurring tick and scheduled dispatch; shared manual/scheduled launch path; + familiar ID propagation; bounded logs and atomic output delivery; health and run-history + projections; source-preserving paused legacy import; `coven.automations.*` control + actions; Cave's migration away from direct Codex ownership + ([OpenCoven/coven-cave#4990](https://github.com/OpenCoven/coven-cave/issues/4990)). +- #816 itself states it "remains open for **foundation reconciliation and exact + evidence**, not because the original architecture is still absent", and lists the + evidence required before closing (linked commit/PR series with exact final revision, + clean-clone test verification, migration/rollback proof, daemon wiring proof on + supported platforms, unified run-path proof, stale-lease proof, delivery-failure + non-success proof, criterion-by-criterion reconciliation). +- **No Beads store exists in `OpenCoven/coven`** — no `.beads/` directory on `main`. + Per the #859 operational correction this is correct: the canonical Beads graph is Cave's + embedded-Dolt database `cave` in `OpenCoven/coven-cave`; a competing Beads database must + not be initialized in `OpenCoven/coven`. +- The Automations v1 delivery epic and its `surface:shared` beads **do not exist yet**: + the `coven-cave` public-scrubbed export (`.beads/issues.jsonl`, 4 entries checked + 2026-08-30) contains only the `cave-hlv` Beads-operating epic (`cave-hlv`, + `cave-hlv.1` in_progress, `cave-hlv.2`/`.3` deferred); no bead references + `OpenCoven/coven` issues #854/#816/#855–#858. Provisioning is owned by + [OpenCoven/coven-cave#5220](https://github.com/OpenCoven/coven-cave/issues/5220). + The live Dolt database could not be read from this environment (no `bd`/`dolt` binary + available); the export is the review-visible state. +- `docs/roadmaps/` did not exist on `main`; the repo's record location is + `docs/superpowers/plans/-.md` (47 existing records, 2026-05-04 → 2026-08-23). +- No SDK, Cave, Psyche, docs, organization-canary, Familiar Contract, or Threads child issues + existed under #854 at investigation time (issue-body search returned only #859 and the + P0 graph). +- `docs/ROADMAP.md` is the public product roadmap (last updated 2026-05-26) and does not + cover the Automations v1 delivery graph. + +## 3. Issue state and the operational correction + +[#859](https://github.com/OpenCoven/coven/issues/859) was opened 2026-08-30T13:41:50Z. +Its Phase 1 says to "inspect the current Coven Beads schema/version"; the +[operational correction](https://github.com/OpenCoven/coven/issues/859#issuecomment-5469149789) +(2026-08-30T14:05:44Z) resolves the ambiguity: + +- the canonical familiar execution queue is **Cave's embedded-Dolt Beads graph**; + references to inspecting "the current Coven Beads schema" mean inspecting Cave's + canonical Beads/Dolt schema and workflow through #5220; +- roadmap and reviewed operating contract: OpenCoven/coven-cave issue #5219; +- seeding, dependency verification (`bd dep` help, `bd dep list`, `bd ready --json`), + bounded `pnpm beads:sync`, and before/after `refs/dolt/data` OIDs: #5220; +- `.beads/issues.jsonl` is a public-scrubbed review export, never canonical state; +- do **not** initialize a competing Beads database in `OpenCoven/coven`; the original + tracker roles and acceptance gates remain valid. + +Consequently #859's GitHub-side deliverables (roadmap artifact, machine-readable mapping, +drift detection, and this record) land in this repository through review, while bead +creation stays with #5220 in `OpenCoven/coven-cave`. + +## 4. Pre-change integrity/status report (2026-08-30) + +| Check | Result | +| --- | --- | +| Working tree | clean before this change (only `docs/roadmaps/` additions by this branch) | +| Base SHA | `1364cec9dbaf1e2aca2e4544dec0e1ce807d859c` (even with upstream `main` and fork `main`) | +| `docs/superpowers/plans/` | 47 records present, latest `2026-08-23-maintenance-participant.md` | +| `docs/roadmaps/` | absent on base (created by this branch per #859's suggested path) | +| Upstream CI on base SHA | `CI` run 33309176793 (2026-08-30T11:32:21Z) **failed** at "Classify changes" — `scripts/classify-ci-changes.py` raised `ValueError: no paths provided` on the empty-diff `push` to `main` for commit `1364cec` ("chore: preserve consolidated branch ancestry"). This is a push-event classification edge case, not a PR-path failure: PR classification uses `PR_BASE_SHA...PR_HEAD_SHA`, which always contains paths. No other CI workflow run failed on the base SHA; the `Engine bump` workflow succeeded on the same SHA at 2026-08-30T13:48:51Z. | +| Upstream open PRs touching this area | none found for issue #859 | +| Fork (`CompleteDotTech/coven`) branch state | `main` mirrors upstream `main` at the same SHA; no `859` branch existed before this work | +| Beads export cross-check | `node docs/roadmaps/drift-check.mjs --beads-export .beads/issues.jsonl` (run against the `coven-cave` checkout export) → no errors; confirms zero Automations v1 beads and no sensitive payloads in the export | + +## 5. Decisions + +- **D1 — one canonical writer.** Exactly one checkout/process is designated schema + migrator and canonical writer for this setup; persisted tracker changes land only + through reviewed PRs (this branch/PR for GitHub-side artifacts; #5220's checkout for + Bead-side provisioning). Concurrent independent migrations and direct writes from + unrelated worktrees are refused. The `coven claim` registry could not be used here + (no Rust toolchain in this environment to build the CLI); REST deconfliction (§1) plus + a dedicated clone and branch satisfy the anti-duplication intent. +- **D2 — reuse, don't duplicate.** No bead mapping #816 was found in the review-visible + export, so nothing is duplicated: #816's mapping entry is declared once in the mapping + file with `provisioning` pointing at #5220, which owns the reuse-check against the live + Dolt store before creating anything. +- **D3 — no competing Beads store.** No `.beads/` is initialized in `OpenCoven/coven`; + the operational correction is honored verbatim. +- **D4 — mapping lives in both worlds correctly.** GitHub owns the public roadmap + artifact and the machine-readable mapping contract (this PR); Beads owns the + implementation dependency graph and execution state once provisioned. The mapping file + is the reconciliation contract; Bead IDs stay `null` (warn-level `W010`) until #5220 + declares them, after which a one-line reviewed change flips provisioning to `done` and + missing-mapping drift escalates to `error`. +- **D5 — drift detection without credentials.** `docs/roadmaps/drift-check.mjs` is + dependency-free, offline, and CI-safe; it verifies mapping-internal invariants, the + generated roadmap block (generator-contract enforcement), and an optional local Beads + export, and scans tracker output for sensitive payloads. `--selftest` proves every + detection class with fixtures (all 11 fixtures and 6 sensitive-payload rules pass). +- **D6 — severity policy.** `error` findings fail CI; `warning` findings (today: pending + provisioning) report without failing. This keeps the check honest (it reports the real + gap) without keeping CI red on work owned by another repository. +- **D7 — no premature closure.** This PR references #859 without `Closes`; the issue's + own completion semantics (#816 stays open until its evidence checklist is done; #859 + spans provisioning in `coven-cave`) mean nothing is closed by tracker work alone. + +## 6. Verdict against #859's acceptance criteria + +| # | Criterion | Verdict | +| --- | --- | --- | +| 1 | #854, #816, #855–#858 each map to exactly one Bead | **PARTIAL** — the one-to-one contract is committed (mapping file: 6 outcomes, unique slugs/labels/refs, enforced by `E001`/`E002`), but bead IDs are `null` until #5220 provisions them; drift check reports `W010` per outcome. | +| 2 | Cross-repository child outcomes map one-to-one as created | **SATISFIED (vacuously today)** — no child outcomes existed at sync time; `cross_repository_children` is empty and the policy + `E101` enforcement are in place. | +| 3 | Dependencies and P0/P1/P2 priorities match the canonical roadmap | **SATISFIED** — mapping `depends_on` mirrors #859's minimum graph; the roadmap table is generated from the same file, and any hand edit is flagged `E008`. | +| 4 | One writer/schema owner and reviewed change path documented | **SATISFIED** — D1 above; also recorded in the roadmap sync metadata. | +| 5 | Roadmap artifact and machine-readable mapping committed through review | **SATISFIED BY THIS PR** — `docs/roadmaps/coven-automations-v1.md` + `coven-automations-v1.mapping.json` + `drift-check.mjs`. | +| 6 | Drift detection catches state, priority, parent, evidence, generated-mirror disagreement | **SATISFIED** — classes `E001`–`E009` (plus `E100`–`E104` in export mode) cover state, priority, parent/dependency, evidence, generated-mirror, and sensitive payloads; proven by `--selftest`. | +| 7 | No tracker data treated as automation runtime truth | **SATISFIED (documented)** — canonical tracker roles in the roadmap and mapping invariants state it; the Coven runtime owns occurrences/runs/leases/approvals/receipts. | +| 8 | Final #854 release rollup generable from reconciled state and exact evidence | **PENDING** — the mapping carries evidence links and gates so the rollup is generable in principle, but certification evidence does not exist yet (no #855–#858 outcomes started; #816's evidence checklist open). | + +## 7. What remains + +1. Provision the Automations v1 delivery epic and six `surface:shared` beads in Cave's + canonical Beads/Dolt graph with dependency verification and bounded + `pnpm beads:sync`, recording before/after `refs/dolt/data` OIDs — + OpenCoven/coven-cave issue #5220 + (P0, on the critical path for criterion 1). +2. Land Cave's reviewed operating contract — OpenCoven/coven-cave issue #5219. +3. Fill #816's evidence checklist (criterion 1's "close only after" condition and the + foundation release gate). +4. After provisioning: set the real bead IDs in + `docs/roadmaps/coven-automations-v1.mapping.json` (one reviewed change; `W010` clears; + `E101` escalation arms). +5. Wire `node docs/roadmaps/drift-check.mjs` into relevant PR CI and the weekly program + rollup cadence once provisioning exists (the check itself needs no credentials). +6. Create the P1 cross-repository outcomes (SDK, Cave, Psyche, docs, + organization-canary, Familiar Contract, Threads) under #854 and map them one-to-one as + they appear, with explicit `depends_on`/`depends_on_external` edges. +7. #855–#858 implementation work per the graph below. + +## 8. Critical path (before/after P0 dependency graph) + +**Before this PR:** no recorded graph in this repository — the P0 ordering existed only +in #859's prose; the bead side did not exist at all. + +**After this PR (GitHub side recorded; bead provisioning pending → #5220):** + +```text +#854 program (P0 control — release gates, rollup) + └─ gate 1: #816 foundation (P0 — landed 2026-08-28, evidence reconciliation open) + ├─ #855 protocol (P0, depends: foundation) + │ ├─ #856 scheduler (P0, depends: foundation, protocol) + │ └─ #857 authority (P0, depends: foundation, protocol; + upstream Familiar/Threads profiles when created) + └─ #858 certification (P0, depends: protocol, scheduler, authority) + └─ v1 release gate (owned by #854) +``` + +## 9. Initial evidence packet + +- Pre-change tracker report: §4 above plus the `drift-check` export cross-check (no + Automations v1 beads; export contains only the `cave-hlv` operating epic). +- Created/reused bead IDs: none created by this repository (forbidden by the operational + correction); provisioning delegated to OpenCoven/coven-cave#5220 (D2/D3). +- Beads version/schema: Beads 1.2.2, schema v53, as recorded in + [2026-08-20-coven-v0.4.1-release-program.md](./2026-08-20-coven-v0.4.1-release-program.md); + live verification owned by #5220. +- Reviewed PR: this branch — + `agent/issue-859-p0-control-operationalize-coven-automations-v1` based on `1364cec`. +- Drift report: `node docs/roadmaps/drift-check.mjs` → 0 errors, 6 × `W010` (pending + provisioning); `--selftest` → pass. +- Final mapping: `docs/roadmaps/coven-automations-v1.mapping.json` (schema version 1). +- Before/after P0 dependency graph: §8.