From 44b540ea31d7c85e1742c7acb4e9d10fedeac183 Mon Sep 17 00:00:00 2001 From: Timothy Wayne Gregg Date: Sun, 30 Aug 2026 12:24:01 -0400 Subject: [PATCH 1/7] docs(automations): record Coven Automations v1 program status (coven#854) (#861) Adds one dated status/decision record for issue #854 in docs/superpowers/plans/. Independently verified against main @ 1364cec (2026-08-30): the coven#816 foundation series (PR #846, PR #847, parts 6-8 commits) landed the durable local scheduler core - definitions, SQLite definition/occurrence/run records with unique occurrence fencing, RRULE planning, bounded leases with expiry recovery, latest-only misfire / overlap refusal, the 60s daemon tick with scheduled dispatch, shared launch path, health and run history, non-destructive paused legacy import, and coven.automations.* control actions. Records what is still absent for v1 (versioned protocol schemas, authority/receipt binding, deterministic-time contract, SDK surface, conformance/chaos/SLO gates, live Beads mutation), the gate-by-gate verdict, and the critical path. The program issue remains open; this record closes no issue. Refs OpenCoven/coven#854. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --- ...-30-coven-automations-v1-program-status.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-30-coven-automations-v1-program-status.md 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). From fb302f298ce714a19515a32d49e3224bcfe62610 Mon Sep 17 00:00:00 2001 From: Timothy Wayne Gregg Date: Sun, 30 Aug 2026 12:48:57 -0400 Subject: [PATCH 2/7] docs: plan cryptographic fresh-user and biometric assurance proofs (coven#815) (#871) * docs: plan cryptographic fresh-user and biometric assurance proofs (coven#815) Specify COVEN-ASSURANCE/1, an independently verifiable step-up proof so request authentication can satisfy FreshUserVerification/FreshBiometric grant requirements without trusting client-asserted assurance levels. The plan defines: possession/step-up key separation; binding the optional step-up authorization key and its declared assurance class into the pairing-v2 transcript; a separate authorization-key store; canonical proof bytes over device, grant, revocation epoch, exact request or DeviceActionIntent digest, server-issued single-use challenge, window, and requested assurance; server-side effective assurance computed as min(claimed, class ceiling) and passed to DeviceGrant::authorize; fail-closed possession-only degradation; rotation/revocation semantics; iOS/Android platform mapping; a self-verifying synthetic golden vector; and TypeScript/JSON Schema for implementers. Every claim cites the current mobile_memory code path it builds on. No code changes: this is the design artifact the issue asks for. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> * docs(mobile): specify COVEN-ASSURANCE/1 step-up assurance proofs Add docs/design/mobile-assurance-step-up-v1.md, the plan and implementation contract for issue #815: a separately enrolled, platform-policy-protected step-up authorization key, bound into the pairing-v2 transcript before grant issuance, whose fresh signatures over canonical COVEN-ASSURANCE/1 bytes (device, grant, revocation epoch, exact request or DeviceActionIntent digest, single-use server challenge, validity window, requested assurance) are the only way to raise assurance above possession. The server verifies the signature, computes effective assurance itself, and passes it to DeviceGrant::authorize; absent/invalid/expired/replayed proofs fail closed. Covers storage separate from the possession key, replay protection independent of request nonces, rotation/revocation semantics, iOS/Android key-policy mapping, a security-invariant mapping, TypeScript types + JSON Schema, and a portable golden vector. Design artifact only; no behavior change. Refs OpenCoven/coven#815 Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --------- Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --- docs/design/mobile-assurance-step-up-v1.md | 620 +++++++++++++++++++++ 1 file changed, 620 insertions(+) create mode 100644 docs/design/mobile-assurance-step-up-v1.md diff --git a/docs/design/mobile-assurance-step-up-v1.md b/docs/design/mobile-assurance-step-up-v1.md new file mode 100644 index 00000000..e3c92762 --- /dev/null +++ b/docs/design/mobile-assurance-step-up-v1.md @@ -0,0 +1,620 @@ +# Mobile Step-Up Assurance Proofs (`COVEN-ASSURANCE/1`) + +**Status:** plan / implementation contract for [#815](https://github.com/OpenCoven/coven/issues/815) +**Parent:** `#786` (generalized device grants) · Builds on `#791` (device grants) and `#812` (pairing v2) +**Authority owner:** Coven daemon / Rust authority layer (`crates/coven-cli/src/mobile_memory/`) +**Compatibility:** additive. Pairing v1/v2 requests without the extension are unchanged; devices without an enrolled authorization key keep exactly today's possession-only behavior. + +## Purpose + +The grant model can require `FreshUserVerification` or `FreshBiometric` +(`restrictions.require_fresh_user_verification_for`, `minimum_assurance` — +`crates/coven-cli/src/mobile_memory/grant.rs:73-103`), but request authentication +can never satisfy those requirements: `MobileAuthenticator::verify` presents the +constant `AssuranceLevel::Possession` to `DeviceGrant::authorize` +(`crates/coven-cli/src/mobile_memory/auth.rs:149`), because request +authentication only proves possession of the enrolled device key. Today, the +only way to express a stronger assurance level would be for a client to +*assert* it — and a client must never be able to upgrade assurance by sending +an asserted enum or string. + +This document specifies an independently verifiable **step-up proof**: a fresh +signature made by a separately enrolled, platform-policy-protected +*authorization key*, over canonical bytes that bind the proof to exactly one +device, grant, request (or `DeviceActionIntent`), challenge, and time window. +The server verifies the signature, recomputes everything it checks, computes +the effective assurance itself, and passes that value to +`DeviceGrant::authorize` (`crates/coven-cli/src/mobile_memory/grant.rs:157-194`). +No biometric material ever leaves the device; OpenCoven receives signatures +only. + +This plan is a design document, not an implementation PR: it defines the wire +bytes, state machines, storage, platform mapping, and portable vectors an +implementation must satisfy. No behavior change ships with this document. + +## Problem statement, in current code + +| Gap | Where today | +| --- | --- | +| `authorize` is always called with hardcoded `AssuranceLevel::Possession` | `crates/coven-cli/src/mobile_memory/auth.rs:149` and again in the post-response re-check at `auth.rs:174-181` | +| `minimum_assurance` can never exceed `Possession` in practice; `DeviceGrant::for_device` pins it | `crates/coven-cli/src/mobile_memory/grant.rs:119` | +| `AssuranceLevel` has no verifiable source: nothing but the server itself can mint anything above `Possession` | `crates/coven-cli/src/mobile_memory/grant.rs:50-58` | +| No second (step-up) key exists in the device record or registry schema | `crates/coven-cli/src/mobile_memory/registry.rs:26-33` | +| Replay protection exists only for ordinary request nonces, keyed `(device_id, nonce)` | `crates/coven-cli/src/mobile_memory/auth.rs:105,198-217` | +| The pairing-v2 transcript binds the device key but no authorization key | `crates/coven-cli/src/mobile_memory/pairing.rs:443-467` | + +Note that pairing v2 deliberately excluded biometric *authorization* from the +transcript (`docs/design/mobile-pairing-protocol-v2.md`, "Security properties", +item 6). That statement remains true: the biometric *ceremony* never enters the +transcript. What this spec adds is binding of the *authorization public key* +and its declared assurance class — an enrollment fact — which is a different +thing from any biometric data. + +## Key separation + +A paired device holds two distinct, non-exportable P-256 keys: + +| | Possession key | Step-up authorization key | +| --- | --- | --- | +| Identifies | the enrolled device | the authorization capability of that device | +| Enrolled via | pairing transcript (existing `devicePublicKey`, `crates/coven-cli/src/mobile_memory/pairing.rs:214-232`) | pairing transcript extension (below) | +| Unlocked for | every request (`COVEN-MEMORY/1`, `crates/coven-cli/src/mobile_memory/auth.rs:34-51`) | only sensitive actions, gated by platform policy | +| Stored as | `DeviceRecord.public_key_x963` + `grant.subject_key_id` (`registry.rs:26-33`, `grant.rs:242-250`) | separate authorization-key record (below) | +| iOS protection | none beyond secure key storage | `SecAccessControl` + LocalAuthentication policy | +| Android | Keystore, no user-authentication requirement | `BiometricPrompt` / strong-biometric policy | + +Rules: + +1. The two keys are distinct key pairs. A key MUST NOT serve both roles; the + registry MUST reject an authorization key equal to the device possession key. +2. The server stores only public keys and policy metadata. It never receives + biometric templates, hashes, or platform state, and does not infer platform + state beyond the assurance class declared for the enrolled key. +3. The declared class is an enrollment-time property of the key, enforced by + platform key policy on the device (see [Platform mapping](#platform-mapping)). + The server trusts the class only because it was confirmed in the pairing + transcript — never because a client asserted it at proof time. + +## Enrollment (binding the step-up key into the pairing-v2 transcript) + +Pairing v2's transcript (`COVEN-PAIR/2`, `crates/coven-cli/src/mobile_memory/pairing.rs:443-467`) +is extended with two optional, length-prefixed fields appended after +`app_version`, present exactly when the enrollment request carries them: + +1. raw canonical 65-byte step-up authorization public key (same P-256 X9.63 encoding as the possession key); +2. UTF-8 declared assurance class (`biometric_only` | `user_verification` | `device_credential`). + +Concretely, the closed enrollment request shape +(`crates/coven-cli/src/mobile_memory/contract.rs:237-246`, `deny_unknown_fields`) +gains one optional member: + +```json +{ + "protocolVersion": 2, + "pairingNonce": "", + "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. From fcc026c89749e767a69e0dc35085405028c80f8e Mon Sep 17 00:00:00 2001 From: Timothy Wayne Gregg Date: Sun, 30 Aug 2026 13:27:03 -0400 Subject: [PATCH 3/7] docs: operationalize Coven Automations v1 tracker roadmap and drift check (coven#859) (#862) Establish the GitHub-side half of the #859 operational delivery graph: - docs/roadmaps/coven-automations-v1.md: program roadmap with canonical tracker roles, sync metadata, P0/P1/P2 policy, generated outcome-mapping table, dependency graph, release gates, active blockers, and drift detection usage. The mapping table block is generated by docs/roadmaps/drift-check.mjs --render and verified on every run. - docs/roadmaps/coven-automations-v1.mapping.json: machine-readable one-to-one Bead <-> GitHub mapping for #854, #816, #855, #856, #857, and #858, with explicit dependencies, acceptance gates, dispositions, and evidence links. Bead IDs stay null (warn-level W010) until provisioning lands through OpenCoven/coven-cave#5220 per the operational correction on #859; no competing Beads store is created in this repository. - docs/roadmaps/drift-check.mjs: credential-free, offline drift check covering state, priority, parent/dependency, evidence, generated-mirror, duplicate/missing mapping, cycle, and sensitive-payload drift classes, with --beads-export cross-checking and --selftest proving detection. - docs/superpowers/plans/2026-08-30-issue-859-coven-automations-v1-...md: dated status/decision record: what exists on main (automations series landed 2026-08-28 via PRs #846/#847), pre-change integrity report, decisions D1-D7, verdict against the #859 acceptance criteria, remaining work, critical path, and the initial evidence packet. Refs OpenCoven/coven#859 fix: keep automation tracker docs scanner-safe Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --- .../coven-automations-v1.mapping.json | 206 +++++ docs/roadmaps/coven-automations-v1.md | 181 +++++ docs/roadmaps/drift-check.mjs | 727 ++++++++++++++++++ ...tomations-v1-tracker-operationalization.md | 203 +++++ 4 files changed, 1317 insertions(+) create mode 100644 docs/roadmaps/coven-automations-v1.mapping.json create mode 100644 docs/roadmaps/coven-automations-v1.md create mode 100644 docs/roadmaps/drift-check.mjs create mode 100644 docs/superpowers/plans/2026-08-30-issue-859-coven-automations-v1-tracker-operationalization.md 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/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. From 957715fb8c771444ef074de4fcaf9787341e8a7c Mon Sep 17 00:00:00 2001 From: Timothy Wayne Gregg Date: Sun, 30 Aug 2026 13:48:37 -0400 Subject: [PATCH 4/7] docs: consolidate Coven security policy, threat boundary, and support claims * docs: consolidate Coven security policy, threat boundary, and support claims Replace the duplicated SECURITY.md (repository policy plus a copied organization-wide disclosure addendum) with one Coven-specific operational security contract that visibly separates: - supported release family and security-supported surfaces, with experimental/disabled surfaces (AFS mount backend, OpenClaw plugin, remote transports) marked explicitly; - enforced properties today, each tied to its normative contract and verification family (Rust authority checks, capability advertisement, pre-effect validation, owner-protected local transport, redaction and artifact defaults, secret/privacy guards, explicit mutation replay); - residual risk and safe configuration (same-user trust vs sandboxing, raw-artifact opt-in, retention limits, untrusted harnesses, AgentFS mount posture synced with #779); - private reporting via GitHub Security Advisories as the primary path, with no personal-channel dependency and no unsupported response-time commitment, plus scoped third-party dependency/provider handling; - design goals (session/memory/agent isolation) moved out of guarantees, cross-referencing #803/#804 instead of claiming unimplemented A2A isolation. Removes the stale OpenTrust-scope addendum, the personal Discord DM reporting path, and the 48-hour/14-day response promises that no accountable process currently backs. Refs OpenCoven/coven#808 Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> * fix: document tokenized invite URL privacy rule Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --------- Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --- SECURITY.md | 319 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 215 insertions(+), 104 deletions(-) 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* From 9847960ace19756fbc6de11e985279478b01198d Mon Sep 17 00:00:00 2001 From: Timothy Wayne Gregg Date: Sun, 30 Aug 2026 13:52:02 -0400 Subject: [PATCH 5/7] docs(pairing): plan TUI QR bootstrap and E2EE mobile pairing (refs #785) (#867) * docs(pairing): plan TUI QR bootstrap and E2EE mobile pairing (refs #785) Add the implementation plan for issue #785: the coven device command family, canonical CBOR pairing offer with Universal Link encoding, Noise_XK handshake with QR-pinned host authentication, rendezvous relay MVP on coven-relay, six-word human verification derived from the handshake hash, scope selection and capability binding, the adversarial test matrix, and the maintainer decision points. Extends the accepted mobile-device-trust architecture and elaborates PR 3 + the rendezvous slice of PR 4 of the pairing delivery train. Docs-only change; cross-links the delivery plan to the new plan. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> * chore(pairing): retrigger CI for the plan PR (no content change) The pull_request opened event raced the fork's lazy workflow registration, so no run was created. This empty commit re-emits the synchronize event; it changes no content. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --------- Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --- .../mobile-device-pairing-delivery-plan.md | 2 + ...obile-device-pairing-tui-bootstrap-plan.md | 777 ++++++++++++++++++ 2 files changed, 779 insertions(+) create mode 100644 docs/architecture/mobile-device-pairing-tui-bootstrap-plan.md 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 Date: Sun, 30 Aug 2026 14:00:50 -0400 Subject: [PATCH 6/7] docs: remove duplicate local public documentation (#870) * docs: point install guidance at canonical coverage The canonical install, platform, deployment, uninstall, and install-debugging guides are merged in OpenCoven/coven-docs, so the duplicate local pages under docs/install/ become stable pointers per the Wave A cleanup plan. docs/install/development-channels.md keeps its local content because no canonical page covers pre-release channels yet. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> * docs: point onboarding pages at canonical guidance Duplicate local onboarding pages (quickstart, first session, onboarding, showcase, doctor) become pointers to the canonical getting-started and doctor routes. coven-tui.md keeps its legacy-TUI contract and its root-absolute links are fixed to the retained local session-lifecycle contract and the canonical CLI reference. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> * docs: shrink the README to a landing page and codify the public-docs boundary The README stops duplicating the canonical manual and keeps the landing-page facts: what Coven is, one install and quick-start path, canonical links, the source-adjacent ownership table, contributor rules, and security. DOCS-MAINTENANCE.md gains the enforceable public-doc directory boundary (canonical pointers or source-adjacent exceptions with stated ownership reasons), and docs/index.md links to it. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> * ci: retrigger pull request checks after enabling fork Actions No file changes. The pull request was opened before Actions was enabled on this fork, so no workflow run was created for its head SHA. This empty commit fires a pull_request synchronize event so the CI matrix can report. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --------- Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --- README.md | 756 ++------------------------------ docs/DOCS-MAINTENANCE.md | 43 ++ docs/index.md | 2 + docs/install/cargo.md | 73 +-- docs/install/coven-home.md | 35 +- docs/install/docker.md | 87 +--- docs/install/from-source.md | 94 +--- docs/install/headless-server.md | 98 +---- docs/install/index.md | 124 +----- docs/install/launchd.md | 91 +--- docs/install/linux.md | 97 +--- docs/install/macos.md | 93 +--- docs/install/nix.md | 82 +--- docs/install/npm.md | 112 +---- docs/install/podman.md | 76 +--- docs/install/raspberry-pi.md | 89 +--- docs/install/systemd.md | 86 +--- docs/install/uninstall.md | 97 +--- docs/install/updating.md | 111 +---- docs/install/windows.md | 98 +---- docs/install/wsl2.md | 77 +--- docs/start/coven-tui.md | 6 +- docs/start/doctor.md | 23 +- docs/start/first-session.md | 22 +- docs/start/onboarding.md | 64 +-- docs/start/quickstart.md | 16 +- docs/start/showcase.md | 29 +- 27 files changed, 164 insertions(+), 2417 deletions(-) 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/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/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/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** From 4b5ca99e796914ad1d5f2b0dfc42f26c182a6b0d Mon Sep 17 00:00:00 2001 From: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:25:22 +0000 Subject: [PATCH 7/7] docs(807): record shipped reliability scorecard status decision Investigate issue #807 against main at 1364cec9 (2026-08-30) and record a dated status/decision document in the repo's record location (docs/superpowers/plans/). Facts recorded: the existing non-gating benchmark/chaos corpus, health and recovery surfaces, release certification (certify-release.sh, 2026-08-29), packaged-artifact journey E2E, release-stress workflow, and AFS gating posture, each with evidence links and dates; a verdict against all six acceptance criteria (not satisfied on main; partial on separation-by-convention, structured receipts, and privacy-by-design); and a dependency-ordered critical path. No measurements, targets, or SLOs are invented. Signed-off-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com> --- ...issue-807-shipped-reliability-scorecard.md | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-30-issue-807-shipped-reliability-scorecard.md 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.