diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9e8bbfd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,84 @@ +# Praefectus + +Policy-neutral Rust library and JSON CLI for host-authorized computer-use execution. + +## Layout + +``` +. +├── src/lib.rs # library: Engine, protocol types, NativeExecutor, CdpExecutor +├── src/main.rs # JSON CLI binary `praefectus` +├── src/semantic.rs # semantic observation and fenced target types +├── src/cdp.rs # library-only CDP executor +├── src/linux_*.rs # Linux AT-SPI2 / input +├── src/macos_capture.rs # optional macOS ScreenCaptureKit +├── src/windows_*.rs # Windows UIA / ACL / capture +├── tests/ # protocol, CLI, fencing, native, live tests +├── examples/ # runnable library scenarios +├── plugins/openclaw/ # OpenClaw host plugin (not the crates.io package) +├── plugins/hermes/ # Hermes host plugin (not the crates.io package) +├── Cargo.toml +└── README.md +``` + +## Build and test + +MSRV is Rust 1.88 (edition 2024). + +```sh +cargo fmt --all -- --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-features +cargo check --all-targets --all-features +cargo build +cargo run --example capabilities +``` + +CI additionally uses `--locked` and `cargo package --locked`. + +OpenClaw plugin (Bun): + +```sh +cd plugins/openclaw +bun install --frozen-lockfile +bun run format +bun run lint +bun run typecheck +bun test +bun run build +``` + +Hermes plugin (Python): + +```sh +python3 -m pip install --requirement plugins/hermes/requirements-dev.txt +python3 -m ruff format --check plugins/hermes +python3 -m ruff check plugins/hermes +python3 plugins/hermes/test_plugin.py +``` + +## CLI vs library + +CLI path: `src/main.rs`, binary name `praefectus`. Existing commands: + +- `status --ledger OPERATION_ID` +- `capabilities` +- `surfaces` +- `observe` +- `observe-surface SURFACE_ID` + +The CLI writes one JSON envelope to stdout: `{"ok":true,"data":...}` or `{"ok":false,"error":{"code":...,"message":...}}`. Exit `0` success, `2` usage, `3` protocol/observation/serialization, `1` envelope write failure. + +Library path: `Engine::new(executor, ledger_path, authority)` in `src/lib.rs`. Trusted hosts inject `Ed25519AuthorityVerifier` and call `Engine::execute`. Observation, surface listing, and CDP configuration (`CdpConfig`, loopback-only) are library APIs. `NativeExecutor::list_surfaces`, `observe_semantic`, `observe_surface`, and `observe_coordinates` are the observation entry points. + +`DenyAuthority` is the CLI verifier for `status` and `capabilities`. It cannot authorize dispatch. + +## Do not invent + +- Do not add, document, or demo a working `praefectus execute` CLI. `execute` is library-only and requires a host-injected trusted `AuthorityVerifier`. +- Do not invent credentials, keys, signatures, issuer IDs, authority paths, or model-facing authority parameters. +- Do not print secrets, screenshot bytes, typed text, clipboard contents, selectors, or backend-error detail. +- Do not invent remote CDP endpoints, arbitrary JavaScript execution, or CLI/model CDP configuration. +- Do not invent host approval, policy, or identity ledgers. Praefectus does not own those. +- Do not treat `outcome_unknown` as cancelled or retry-safe. +- Do not add OpenClaw or Hermes plugins to the crates.io package. diff --git a/Cargo.toml b/Cargo.toml index 2e778c3..f8ab67c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ include = [ "/Cargo.toml", "/Cargo.lock", "/README.md", + "/examples/**", "/LICENSE", "/ACKNOWLEDGEMENTS.md", "/THIRD_PARTY_LICENSES.md", @@ -34,6 +35,10 @@ path = "src/lib.rs" name = "praefectus" path = "src/main.rs" +[[example]] +name = "capabilities" +path = "examples/capabilities.rs" + [dependencies] ed25519-dalek = { version = "2.1", default-features = false, features = ["std"] } fs2 = "0.4" diff --git a/README.md b/README.md index 43974e3..9cadabe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,25 @@ # Praefectus -Praefectus 0.7.0 implements protocol v2 as a policy-neutral Rust library and JSON CLI for computer-use execution. Models propose strict `ActionRequest` values; the host retains planning, identity, approval, permissions, and policy ownership. The host signs one bounded `AuthorityGrant` with Ed25519, and Praefectus verifies it against a host-pinned issuer key before it claims or dispatches an operation. +**Policy-neutral Rust library and JSON CLI for host-authorized computer-use execution.** + +## Quick Start + +```sh +cargo build +cargo test +cargo run -- capabilities +cargo run -- surfaces +cargo run -- status --ledger ./operations.jsonl OPERATION_ID +cargo run --example capabilities +``` + +`capabilities` returns the native executor's advertised platform, backend, session isolation, and actions. `surfaces` lists bounded opaque native surface references when observation is available. `status` returns the durable terminal acknowledgement when one exists. The library hello in [`examples/capabilities.rs`](examples/capabilities.rs) constructs `Engine` with `NativeExecutor` and `DenyAuthority` and does not execute an action. + +The standalone CLI has no `execute` command: a same-user process cannot establish an independent host-authority boundary from a caller-selected file. Trusted hosts execute through the library with an injected `Ed25519AuthorityVerifier`. + +## Trust model + +Praefectus implements protocol v2 as a policy-neutral Rust library and JSON CLI for computer-use execution. Models propose strict `ActionRequest` values; the host retains planning, identity, approval, permissions, and policy ownership. The host signs one bounded `AuthorityGrant` with Ed25519, and Praefectus verifies it against a host-pinned issuer key before it claims or dispatches an operation. The protocol provides durable at-most-once dispatch. Desktop and browser APIs are not transactional, so a crash, cancellation, or verification failure after dispatch can produce `outcome_unknown`; Praefectus never reports those cases as safely cancelled or retries them automatically. @@ -79,6 +98,7 @@ cargo clippy --all-targets --all-features -- -D warnings cargo test --all-features cargo check --all-targets --all-features cargo build --release +cargo run --example capabilities cargo package ``` diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..0cf5b81 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,38 @@ +# Praefectus Examples + +This directory contains working examples demonstrating Praefectus capabilities. + +## Examples + +### 1. [capabilities](capabilities.rs) +Library hello: construct `Engine` with `NativeExecutor` and `DenyAuthority`, print capabilities, and read status for an unknown operation ID. + +```bash +cargo run --example capabilities +``` + +**Demonstrates:** +- `Engine::new` with a host-selected ledger path +- `Engine::capabilities` without credentials +- `Engine::status` for an unknown operation ID (`None` when the ledger has no claim) +- Why `execute` stays library-only (`DenyAuthority` cannot authorize dispatch) + +## Quick Start + +```bash +cargo run --example capabilities +cargo run -- capabilities +cargo run -- surfaces +``` + +`capabilities` needs no desktop session. `surfaces` and `observe` use the native accessibility backend and return `observation_error` (CLI exit `3`) when observation is unavailable. + +There is no `execute` example. Trusted hosts execute through the library with an injected `Ed25519AuthorityVerifier`. + +## Directory Structure + +``` +examples/ +├── README.md +└── capabilities.rs +``` diff --git a/examples/capabilities.rs b/examples/capabilities.rs new file mode 100644 index 0000000..28daeaa --- /dev/null +++ b/examples/capabilities.rs @@ -0,0 +1,17 @@ +use praefectus::{DenyAuthority, Engine, NativeExecutor}; + +fn main() -> Result<(), Box> { + let directory = tempfile::tempdir()?; + let ledger = directory.path().join("operations.jsonl"); + let engine = Engine::new(NativeExecutor::default(), ledger, DenyAuthority); + let capabilities = engine.capabilities()?; + println!("platform={}", capabilities.platform); + println!("backend={}", capabilities.backend); + println!("session_isolation={:?}", capabilities.session_isolation); + println!("supported_actions={:?}", capabilities.supported_actions); + match engine.status("example-missing-operation")? { + Some(acknowledgement) => println!("status={}", acknowledgement.operation_id), + None => println!("status=none"), + } + Ok(()) +}