Metadata-Version: 2.5
Name: accountable-surface
Version: 0.3.1
Summary: Gate AI-agent actions with explicit grants, durable journals, MCP server support, and verification.
Project-URL: Homepage, https://harperz9.github.io
Project-URL: Repository, https://github.com/HarperZ9/accountable-surface
Project-URL: Issues, https://github.com/HarperZ9/accountable-surface/issues
Project-URL: Changelog, https://github.com/HarperZ9/accountable-surface/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/HarperZ9/accountable-surface/blob/main/USAGE.md
Author: Zain Dana Harper
License-Expression: LicenseRef-FSL-1.1-MIT
License-File: LICENSE
Keywords: accountability,actuation,agent,gate,mcp,perception,stewardship,witness
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Requires-Dist: coherence-membrane>=0.1.0
Requires-Dist: proof-surface>=0.1.0
Provides-Extra: browser
Requires-Dist: playwright>=1.40; extra == 'browser'
Provides-Extra: server
Requires-Dist: mcp<2,>=1.0; extra == 'server'
Provides-Extra: test
Requires-Dist: build>=1.0; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

<p align="center"><img src="docs/art/accountable-surface-header.svg" alt="Accountable Surface" width="100%"></p>

# Accountable Surface

Accountable Surface lets an AI agent take only the file, command, web, or
browser action a person has approved. It checks the request and authorization,
blocks or pauses when needed, verifies the outcome, rolls back reversible
failures, and records decisions and outcomes in a journal. Persisted journals
are hash-chained so later edits, deletions, or reordering are detected.

![version](https://img.shields.io/badge/version-0.2.0-f8cc43?style=flat-square&labelColor=14041b) ![license](https://img.shields.io/badge/license-FSL--1.1--MIT-8f8095?style=flat-square&labelColor=14041b)

The Python core uses only the standard library. Browser automation is an
optional extra.

## Try it

After [cloning the three repositories and installing Accountable
Surface](#install), run the complete local file-action example:

```powershell
python examples/actuate_demo.py
```

The example refuses an unapproved write, performs an approved write, detects a
wrong result, restores the previous file, and prints the journal entries.

## Verified Today

The published GitHub `v0.2.0` release is available from commit
`b2ae9be77038753d9bdbda861e9908542883ff2c`. Its release assets are:

- `accountable_surface-0.2.0-py3-none-any.whl`
  (`sha256:f2fb09ac66d00bb188da7fdd6fd1ba37485f5fe245c0596902adc355f6bccdfa`).
- `accountable_surface-0.2.0.tar.gz`
  (`sha256:47a126a821861ce49581b49eb67892220a61633de8b71323e41749e8fa6538c6`).
- `SHA256SUMS.txt`
  (`sha256:b7acee277353171f79865a32640bf64b48efcfbb919a4e7c2143a4a71c845855`).

The release was verified from a clean isolated worktree with these checks:

- `python -m pytest`: Python suite passed.
- `node --test web/*.test.mjs`: browser-interface tests passed.
- `python tools/check_repo_art.py --json`, `python tools/check_repo_card.py`,
  and `python tools/check_repo_flow.py`: repository documentation/art gates passed.
- `python -m build --sdist --wheel`: built the local sdist and wheel.
- `python -m twine check dist/*`: passed with Twine 7.x.
- A fresh virtual environment installed the wheel, checked both console scripts,
  and ran a private synthetic durable-authority control against a disposable
  authority-state database.

These checks cover the repository's deterministic local paths. They do not
establish safety in every host environment or validate every third-party
browser, website, or model provider.

## Features

- **Third-party API actuation.** `ApiEffector` writes through a service's official API under the same contract as the rest. The caller hands in an intent and a body; the host, method, route shape, and credential belong to the effector, so an agent can ask to post a comment and cannot reach an admin route or read the token back. Verification re-reads the resource, so a service that answers 201 and stores nothing is refuted rather than believed. `FakeApiDriver` makes the whole path testable with no network and no credential.
- **Native web actuation, no browser.** `WebEffector` navigates, fills fields by their visible label, and submits forms on live server-rendered pages, driven by a stdlib HTTP and HTML parser backend (`HttpDriver`). Origin-bounded by construction.
- **JS-capable browser actuation, optional.** `BrowserEffector` clicks by accessible label, follows navigation, and runs JavaScript on single-page apps. Tests and offline demos use the deterministic `FakeBrowserDriver`; production can inject `PlaywrightDriver` (the `[browser]` extra, lazily imported, never a hard dependency).
- **OS command actuation.** `CommandEffector` runs allowlisted commands only, as argv with `shell=False`, in a bounded working directory. Irreversible commands escalate to needs-human.
- **Filesystem actuation with rollback.** `FilesystemEffector` is bounded to a root, verifies its own writes by re-perceiving, and rolls back a reversible action that fails verification.
- **Windows application actuation, by label not by pixel.** `UiaEffector` reads a window's control tree and acts on one control by its accessible name: press a button, set a field. It is built for a single window and refuses a target naming another. A click cannot be undone, so the caller declares what should follow it (a control appears, disappears, or carries some text) and a plan without that is refused before anything is touched. Verification re-reads the window, so an application that accepts a click and does nothing with it is refuted rather than believed. `FakeUiaDriver` runs the whole path on any operating system, with no window open.
- **A ladder that says what it cost.** Reaching a desktop application starts with the control tree and falls to pixels only when the cheap instrument cannot answer. The escalator asks each rung in that rung's own terms and records why it fell: the tree was clipped, the label named two controls, the window did not read. Falling does not buy an answer. When the ladder runs out you get NEEDS_HUMAN, the whole trace, and the witnessed sight the deepest rung produced, because a perceptual hash is a real receipt for an image and no answer at all about a control named `Save`.
- **Structured perception.** Organs read a target as a content-addressed structural observation with a falsifiable self-test, not a screenshot.
- **Grounding.** A reference cortex (`ReferenceCortex`) scores reference relevance for a subject and reports "ungrounded" instead of guessing, with native arXiv lookup via the stdlib. An action can carry a justification; an ungrounded premise escalates to needs-human.
- **Bounded autonomy.** `pursue` runs a multi-step plan under one grant envelope with no per-step prompt, halting the instant a step is denied or fails verification.
- **Shared world server.** A zero-dependency live server (stdlib `http.server` plus SSE) where proposed actions run the real loop and stream to every open browser tab, with a small web UI in `web/`. Optional pilots connect a model (Claude or Ollama) to drive it.
- **Durable, tamper-evident memory.** An append-only JSONL journal that replays across sessions, hash-chained so an edited, deleted, or reordered entry is caught on reload even though it still parses (corruption and tamper are counted separately, never conflated). `verify_journal()` re-derives the chain and returns the verdict; `interocept()` is a content-addressed view of the surface's own conduct.
- **Live MCP server.** `perceive`, `propose`, `actuate`, `session_journal`, and `interocept` exposed over MCP stdio (the `[server]` extra). `actuate` reaches only the effectors the operator exposed, and nothing is exposed by default.
- **Interoperable MCP server (no FastMCP).** A zero-third-party-dependency stdio server, `accountable_surface.interop_mcp`, carries the six accountable primitives (`perceive`, `propose`/gate, `actuate`, `journal`, `receipt`) plus the shipped read-only verb `device_ls`, so other harnesses (Claude Code, Codex, Cursor, the Flywheel bundled lane) adopt one seam. See `docs/interop-mcp.md` and `interop/`.
- **Action certificates.** `certify` composes the gate, effect, and grounding verdicts into one certificate token; a denial or failed effect makes the whole action REFUTED, and an escalation yields UNVERIFIABLE, never a rounded-up pass.

The gate is default-deny: with no operator grant loaded, nothing acts. The model cannot supply its own authorization.

## Install

```bash
pip install accountable-surface
```

Add the MCP server with `pip install "accountable-surface[server]"`. Use a source checkout when you need current `main` branch features, or a GitHub release asset when you want a wheel with a published checksum.

Current PyPI release: `0.3.1`, published by an OIDC trusted publisher with PEP 740 attestations, so the index records which workflow built the bytes. `v0.2.0` and `v0.1.0` remain older tagged GitHub packages. The package is still alpha.

Install the released wheel with the published checksum file:

```powershell
& {
  $ErrorActionPreference = 'Stop'

  $Version = "0.2.0"
  $Wheel = "accountable_surface-$Version-py3-none-any.whl"
  $PinnedWheelSha256 = "f2fb09ac66d00bb188da7fdd6fd1ba37485f5fe245c0596902adc355f6bccdfa"
  $Base = "https://github.com/HarperZ9/accountable-surface/releases/download/v$Version"

  Invoke-WebRequest "$Base/SHA256SUMS.txt" -OutFile SHA256SUMS.txt
  Invoke-WebRequest "$Base/$Wheel" -OutFile $Wheel

  $WheelPattern = [regex]::Escape($Wheel)
  $ChecksumRows = @(Select-String -Path SHA256SUMS.txt -Pattern "^\s*([0-9A-Fa-f]{64})\s+\*?$WheelPattern\s*$")
  if ($ChecksumRows.Count -ne 1) { throw "Expected exactly one SHA256SUMS row for $Wheel; found $($ChecksumRows.Count)" }

  $Expected = [regex]::Match($ChecksumRows[0].Line, "^\s*([0-9A-Fa-f]{64})\s+").Groups[1].Value.ToLowerInvariant()
  if ($Expected -ne $PinnedWheelSha256) { throw "SHA256SUMS row for $Wheel did not match the pinned GitHub release hash" }

  $Actual = (Get-FileHash $Wheel -Algorithm SHA256).Hash.ToLowerInvariant()
  if ($Actual -ne $Expected) { throw "SHA256 mismatch for $Wheel" }

  python -m pip install $Wheel
}
```

Current source checkout, including the durable-authority work in 0.2.0:

```powershell
git clone https://github.com/HarperZ9/accountable-surface.git
git clone https://github.com/HarperZ9/coherence-membrane.git
git clone https://github.com/HarperZ9/proof-surface.git
cd accountable-surface
$env:PYTHONPATH = "src;..\coherence-membrane\src;..\proof-surface\src"
python -m pip install -e ".[test]"
```

Requires Python 3.10+. The package declares `coherence-membrane` and `proof-surface` as runtime dependencies, because `accountable_surface` imports both. `mcp` stays optional under the `[server]` extra, since only the MCP server path needs it.

## Quickstart

```powershell
python examples/demo.py        # perceive, gate allow, gate deny, journal
python examples/actuate_demo.py  # the full act-verify-rollback loop
python -m pytest               # the Python test suite
```

`demo.py` prints a witnessed structural reading of a local page (title, links, sha256 digest), then a gate ALLOW for an action inside the grant, a gate DENY for one outside it, and the journal of every perception and decision.

More transcripts: `web_actuate_demo.py` (native web actuation against a real localhost server), `spa_actuate_demo.py` (the JS-capable browser path, offline), `goal_demo.py` (bounded autonomy), `grounding_demo.py` and `grounded_actuate_demo.py` (the reference cortex), `smoke_mcp.py` (a real MCP stdio round-trip).

## Worked example

An operator grant is a plain JSON object. The surface acts only when the gate allows the exact plan, then verifies the effect on disk.

```python
from accountable_surface import AccountableSurface, FilesystemEffector

grant = {
    "authorization_version": "0.1",
    "receipt_id": "rcpt-example",
    "kind": "authorization-grant",
    "principal": {"id": "operator-1", "role": "operator"},
    "agent": {"id": "example-agent"},
    "intent": "write the report file",
    "scope": {"allowed_actions": ["fs.write"], "allowed_targets": []},
    "granted_at": "2026-06-19T00:00:00+00:00",
    "expires_at": "2030-01-01T00:00:00+00:00",
    "revoked": False,
}

surface = AccountableSurface()
out = surface.actuate(
    FilesystemEffector("/path/to/sandbox"),
    target="/path/to/sandbox/report.txt",
    content=b"written natively, verified by re-perceiving",
    authorization=grant,
)
print(out.acted, out.decision, out.verified)  # True allow True
```

With `authorization={}` the same call returns `acted=False, decision="deny"` and the file is never created. A faulty effector that writes the wrong bytes is caught at verification and rolled back; `examples/actuate_demo.py` shows both paths.

<img src="docs/art/actuation-lane.svg" alt="Eight stages of one actuation: perceive, preview, gate, act, re-perceive, verify, roll back, journal. The target is read first, so there is a witnessed record of what it looked like before anything ran. The preview describes the intended write and content-addresses it, with no side effect. The gate answers allow, deny, or needs a human, against the operator's grant. The effector acts only on an allow that names this exact plan, and only inside the root it was constructed with. The target is then read back off disk. Verification compares what landed against the digest that was authorized. A reversible action that fails verification is undone, and the target is read once more. The journal records both digests, the decision and the verdict. Three outcomes: acted and verified, not acted, and rolled back." width="100%">

## What each verdict contributes

One actuation produces up to three verdicts: what the gate decided, what the re-read found, and how well the premise was grounded. They are composed into a single certificate by a lattice meet, so a worse step can never be laundered into a better result.

<img src="docs/art/verdict-composition.svg" alt="Ten rows mapping each verdict the surface produces onto what it contributes to the composed certificate. A gate allow, a passing effect and a grounded premise each contribute verified. A gate denial, a failed effect, a refusal by the effector and an ungrounded premise each contribute refuted, and one refuted input makes the whole certificate refuted. An escalation to a human and a weak premise contribute unverifiable, which pulls a verified result down without making it false. The last row is accented: a verdict string none of the maps recognizes becomes unverifiable rather than raising." width="100%">

## Run as an MCP server

```powershell
python -m pip install -e ".[server]"   # adds mcp
python -m accountable_surface.server   # or: accountable-surface-server
```

Client configuration:

```json
{
  "mcpServers": {
    "accountable-surface": {
      "command": "python",
      "args": ["-m", "accountable_surface.server"],
      "env": {
        "PYTHONPATH": "C:/path/to/accountable-surface/src;C:/path/to/coherence-membrane/src;C:/path/to/proof-surface/src",
        "ACCOUNTABLE_SURFACE_GRANTS": "C:/path/to/operator-grants.json",
        "ACCOUNTABLE_SURFACE_JOURNAL": "C:/path/to/session-journal.jsonl",
        "ACCOUNTABLE_SURFACE_AUTHORITY_STATE": "C:/path/to/authority-state.sqlite3"
      }
    }
  }
}
```

### Interoperable stdio server (no FastMCP)

For adoption by other harnesses, `accountable_surface.interop_mcp` is a stdlib-only
JSON-RPC-over-stdio server: no FastMCP, nothing to pip install for a harness to spawn
it and list tools. It carries the six accountable primitives plus the shipped
read-only verb and emits an offline-re-derivable `action-receipt/v1`.

```powershell
python -m accountable_surface.interop_mcp   # or: accountable-surface-mcp
```

Manifests for Claude Code, Codex, Cursor, and the Flywheel lane live in `interop/`;
the overview and the evidence-bound comparison over ungated computer use are in
`docs/interop-mcp.md`. The hard exclusions (CAPTCHA solving, anti-bot stealth /
fingerprint patching, reCAPTCHA token harvest, mass or obfuscated authenticated
outreach) are unreachable through any tool and asserted by test.

`ACCOUNTABLE_SURFACE_GRANTS` points to a JSON file with one authorization grant
or a list; with none loaded the gate is default-deny. Remote reads are scoped
separately from writes: `allowed_actions` may name `fs.write`, but the remote
server still refuses until `allowed_reads` covers the target state phases it must
read (`before`, `backup`, `after`, and `rollback` for filesystem writes).
`ACCOUNTABLE_SURFACE_JOURNAL` points to an append-only JSONL file; when set, the
journal replays on launch so the witnessed self-view spans sessions.

`ACCOUNTABLE_SURFACE_AUTHORITY_STATE` points to an optional stdlib-SQLite state file for durable revocation, atomic usage reservations, and idempotency. With it set, remote `actuate` requires an `idempotency_key`, refuses protected grant/state/journal paths even under broad filesystem grants, and keeps unresolved precommit reservations unavailable until operator recovery. See [docs/durable-authority.md](docs/durable-authority.md).

`ACCOUNTABLE_SURFACE_EFFECTORS` points to a JSON file naming which effectors a remote caller may reach:

```json
{"effectors": [
  {"action_kind": "fs.write", "type": "filesystem", "root": "/srv/agent-sandbox"},
  {"action_kind": "api.post", "type": "api", "service": "github"}
]}
```

Three operator decisions guard remote `actuate` and all have to agree: the
registry exposes an effector, `allowed_actions` names the write, and
`allowed_reads` names the state reads needed to precondition, verify, and roll
back that write. With the variable unset, the file empty, or the action kind
missing from it, `actuate` refuses before it reads a grant. The file refuses
`command`, `browser`, `web`, and `uia` by name, each with the reason. Ask
`doctor` for the exposed set, the reach of each entry, and the entries it turned
down. MCP `perceive` likewise needs a `web.document` read grant, and full
`session_journal` replay needs a `journal.session` read grant.

## Shared world server

A live surface you can watch in a browser: proposed actions run the real perceive-gate-act-verify loop and stream over SSE to every open subscriber.

```powershell
python -m accountable_surface.world.server 8808
```

It serves the web UI from `web/` and binds to localhost by default. Grants are operator-supplied at startup; the built-in fallback is an explicit sandbox-scoped demo grant, and default-deny still holds.

## Layout

- `src/accountable_surface/surface.py`: `AccountableSurface` with `perceive`, `propose`, `actuate`, `pursue`, `interocept`, and the durable journal.
- `effector.py`, `web_effector.py`, `browser_effector.py`, `os_effector.py`, `api_effector.py`, `uia_effector.py`: the six effectors and their drivers.
- `http_driver.py`: the stdlib HTTP and HTML backend behind native web actuation.
- `playwright_driver.py`: the optional JS-capable browser driver.
- `reference.py`: the grounding cortex, `certify.py`: action certificates, `grant.py`: grant helpers.
- `server.py`: the MCP server. `world/`: the shared world session, server, sight, and pilots.
- `tests/`: the Python test suite. `examples/`: eight runnable transcripts. `web/`: the shared world UI plus Node tests.
- `docs/`: design specs (`SPEC-actuation.md`, `SPEC-interoception.md`, `SPEC-persistence.md`), design notes, and [docs/INTRODUCTION.md](docs/INTRODUCTION.md), the first-ten-minutes guide.
- `docs/art/`: the diagrams above, rendered from `accountable-surface.art.json` by `tools/render_repo_art.py` and checked by `tools/check_repo_art.py`. Brand assets: `.github/assets/zentropy-banner.png`.

## Status

Alpha, version 0.2.0. The API is settling and may change between 0.x releases. CI runs the Python suite and the Node web tests on every push and pull request, with sibling checkouts of coherence-membrane and proof-surface. Local verification:

```powershell
$env:PYTHONPATH = "src;..\coherence-membrane\src;..\proof-surface\src"
python -m pytest
node --test web/*.test.mjs
```

Release packaging steps and hold points are in [docs/RELEASE.md](docs/RELEASE.md).

## Related repos

- [coherence-membrane](https://github.com/HarperZ9/coherence-membrane): the perception organs and certificate types this surface composes.
- [proof-surface](https://github.com/HarperZ9/proof-surface): the pre-execution gate (allow, deny, needs-human).
- [USAGE.md](USAGE.md): the operational guide, including the browser backend.

## Development disclosure

Zain Dana Harper maintains this repository. AI-assisted tools are used for
parts of development and documentation. Public source, tests, and release
artifacts are the evidence for what ships; AI output is not treated as proof.

## Why the gate and the journal

Agent autonomy without silent authority: every action here is checked against an operator grant before it runs, verified against its intended effect after it runs, and recorded in a journal you can replay and re-check. The receipt is the floor; the features above are the point.

A journal that can be quietly rewritten records nothing worth reading. Each entry is chained to the one before it, so an edit breaks the entry's own hash and a delete or a reorder breaks the link to its neighbour. `verify_journal.py` re-derives the whole chain with no import from this package, which means a stranger holding only the JSONL file can check it.

<img src="docs/art/journal-chain-lane.svg" alt="Eight stages of the journal chain: entry, canonical, chain hash, append, reload, edit, move, verdict. An entry is a kind, a summary and a detail, and nothing outside those three is hashed. The entry is serialized one way only, with sorted keys and no spaces, so the same content always produces the same bytes. Its chain hash is taken over the previous entry's hash together with those bytes. The line is appended as one compact JSON object. On reload every line is parsed and rechained from the genesis anchor. An edited field no longer hashes to the record that carries it. A deleted or reordered entry breaks the link to the running head. The verdict counts unparseable lines separately from chain breaks, and never merges the two. Three outcomes: match, drift, and unverifiable." width="100%">

## License

FSL-1.1-MIT. Copyright (c) 2026 Zain Dana Harper. See [LICENSE](LICENSE).

## What this believes

This tool is one lane of a family that holds a single belief steady across
every surface: knowledge open to anyone who can attain the means; acceptance
decided by external checks, never reputation; every result re-runnable;
honest nulls first-class; ownership earned by comprehension; learning woven
into the work. The full text lives in [CREDO.md](CREDO.md).
The long form of this belief: [The Unbundling](https://github.com/HarperZ9/flywheel/blob/fix/release-model-identity/docs/essays/2026-07-13-the-unbundling.md).

---

**[Zentropy Labs](https://github.com/ZentropyLabs-ai)** · order out of entropy. An independent lab building evidence-first tools that leave a re-checkable artifact behind. Built by Zain Dana Harper in Seattle. The full workbench is at [Project Telos](https://harperz9.github.io).
