revoco 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {revoco-0.2.0 → revoco-0.2.2}/.github/workflows/ci.yml +7 -0
- revoco-0.2.0/README.md → revoco-0.2.2/PKG-INFO +83 -21
- revoco-0.2.0/PKG-INFO → revoco-0.2.2/README.md +50 -54
- {revoco-0.2.0 → revoco-0.2.2}/docs/ADAPTERS.md +37 -2
- {revoco-0.2.0 → revoco-0.2.2}/pyproject.toml +6 -4
- revoco-0.2.2/scripts/validate_workstation.py +580 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/workstation.py +15 -6
- {revoco-0.2.0 → revoco-0.2.2}/.github/workflows/release.yml +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/.gitignore +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/LICENSE +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/docs/RELEASING.md +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses.yaml +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_cloud.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_database.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_devops.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_identity.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_saas.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_sap.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_workday.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_workstation.json +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/examples/policy.yaml +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/scripts/bump_version.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/__init__.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/__init__.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/cloud.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/database.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/devops.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/identity.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/saas.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/sap.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/workday.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/__init__.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/action.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/delegation.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/engine.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/principals.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/revocation.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/scope.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/__init__.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/corpus.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/harness.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/report.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/scenario.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/world.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/cli.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/controlplane.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/__init__.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/crypto.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/errors.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/ids.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/demo.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/detect.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/drills.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/evidence.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/__init__.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/conditions.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/decision.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/engine.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/policy.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/session.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/threats.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/ledger.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/__init__.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/budget.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/engine.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/horizon.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/model.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/registry.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_adapter_catalog.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_adapters.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_authority.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_bench.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_budget_and_drills.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_controlplane.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_core.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_demo_and_cli.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_gate.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_horizon_and_scheduling.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_ledger.py +0 -0
- {revoco-0.2.0 → revoco-0.2.2}/tests/test_reversal.py +0 -0
|
@@ -58,6 +58,13 @@ jobs:
|
|
|
58
58
|
# starts losing something new does.
|
|
59
59
|
run: uv run revoco bench
|
|
60
60
|
|
|
61
|
+
- name: Validate the workstation adapter against a real filesystem and git
|
|
62
|
+
# The only adapter that can be validated without a vendor sandbox. It drills
|
|
63
|
+
# every inverse against real files and a real `git init` repo, and probes the
|
|
64
|
+
# prose claims the classifications rest on. Keeping it in CI is what stops the
|
|
65
|
+
# 14 specs sliding back to "unvalidated" the next time someone edits them.
|
|
66
|
+
run: uv run python scripts/validate_workstation.py
|
|
67
|
+
|
|
61
68
|
- name: End-to-end demo
|
|
62
69
|
run: uv run python -m revoco.demo
|
|
63
70
|
|
|
@@ -1,13 +1,59 @@
|
|
|
1
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: revoco
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Undo for AI agent actions. Plans the rollback before the action runs, proves it still works, and rolls back a compromised grant's whole blast radius in one call.
|
|
5
|
+
Project-URL: Homepage, https://github.com/rsh1k/revoco
|
|
6
|
+
Project-URL: Source, https://github.com/rsh1k/revoco
|
|
7
|
+
Author: rsh1k
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agent-governance,agentic-ai,ai-agents,ai-security,audit-trail,compensating-transaction,eu-ai-act,guardrails,mcp,nist,owasp,reversibility,rollback,sox,sr-11-7,tamper-evident,undo
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: cryptography>=42.0
|
|
22
|
+
Provides-Extra: api
|
|
23
|
+
Requires-Dist: fastapi>=0.110; extra == 'api'
|
|
24
|
+
Requires-Dist: uvicorn>=0.29; extra == 'api'
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
30
|
+
Provides-Extra: yaml
|
|
31
|
+
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Revoco — undo for AI agent actions
|
|
2
35
|
|
|
3
36
|
[](https://github.com/rsh1k/revoco/actions/workflows/ci.yml)
|
|
4
|
-
[](https://pypi.org/project/revoco/)
|
|
38
|
+
[](https://pypi.org/project/revoco/)
|
|
39
|
+
[](LICENSE)
|
|
7
40
|
|
|
8
|
-
|
|
41
|
+
Agent governance is crowded with tools that answer *was this allowed* and *was this logged*. Almost none answer **can we take it back** — and that's the question that decides whether an incident costs an afternoon or a quarter.
|
|
9
42
|
|
|
10
|
-
|
|
43
|
+
When an agent repoints a supplier's bank account or deletes a production Deployment, knowing exactly what happened is necessary and not sufficient. Somebody still has to put it back, by hand, under time pressure, while the auditors watch.
|
|
44
|
+
|
|
45
|
+
Revoco does four things:
|
|
46
|
+
|
|
47
|
+
- **Plans the rollback before the action runs** — the only moment prior state still exists. A plan built afterwards can record what changed but not what to restore.
|
|
48
|
+
- **Rolls back a whole compromised grant in one call** — revoke the authority *and* its sub-delegated subtree, then undo everything done under it, newest first.
|
|
49
|
+
- **Proves the rollback still works** — drills each inverse against a disposable canary and compares state, because a backup nobody has restored is a hypothesis.
|
|
50
|
+
- **Produces evidence someone who distrusts you can verify** — one hash-chained ledger over authority, enforcement and reversal.
|
|
51
|
+
|
|
52
|
+
*Revoco* is Latin for "I call it back."
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install revoco
|
|
56
|
+
```
|
|
11
57
|
|
|
12
58
|
Revoco merges three earlier tools and adds the layer none of them had:
|
|
13
59
|
|
|
@@ -20,13 +66,9 @@ Revoco merges three earlier tools and adds the layer none of them had:
|
|
|
20
66
|
|
|
21
67
|
---
|
|
22
68
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
Agent governance tooling is overwhelmingly about detection: was this call allowed, was it logged, was it anomalous. That leaves the expensive half of an incident untouched.
|
|
69
|
+
## Reversibility as an authorization input
|
|
26
70
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
So Revoco treats reversibility as **a property of the action, declared and planned before the action runs**, and as a **first-class authorization input**:
|
|
71
|
+
Reversibility isn't a recovery procedure you write afterwards — by then the prior state is gone. So Revoco treats it as a property of the action, declared before the action runs, which makes it available to the thing that decides whether the action happens at all:
|
|
30
72
|
|
|
31
73
|
```yaml
|
|
32
74
|
- id: no-undo-needs-a-human
|
|
@@ -41,11 +83,7 @@ Enforcement stops being only *"may this agent do it?"* and becomes also *"and ca
|
|
|
41
83
|
|
|
42
84
|
## Quick start
|
|
43
85
|
|
|
44
|
-
|
|
45
|
-
pip install revoco
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Or from source:
|
|
86
|
+
Installed above, or from source:
|
|
49
87
|
|
|
50
88
|
```bash
|
|
51
89
|
git clone https://github.com/rsh1k/revoco.git
|
|
@@ -267,7 +305,8 @@ lists the sequenced undos, the one-shot specs, and every gate your `GateEvaluato
|
|
|
267
305
|
|
|
268
306
|
This is a **working foundation**, published so it can be read, run, and extended. It is not a finished or certified product.
|
|
269
307
|
|
|
270
|
-
- **
|
|
308
|
+
- **One adapter surface is validated; seven are specifications.** `workstation` (14 specs) is drilled against a real filesystem and a real git repo by `scripts/validate_workstation.py`, in CI — 11 of 11 drillable inverses restore state, 10 prose claims probed. **The other seven have never been executed against a live system.** They were written from vendor documentation and KBAs; argument names differ across S/4HANA Cloud, on-premise releases and your middleware, and Workday business process configuration is per-tenant. **A tool mapped to the wrong inverse produces a confident, wrong rollback** — worse than none. Work through the checklist in [docs/ADAPTERS.md](docs/ADAPTERS.md) first, and re-validate after every ERP upgrade. Validating the one cheap surface found five defects that would all have recurred elsewhere, which is the argument for doing it before you touch a vendor sandbox — and a scheduled drill is the only thing that catches a spec going stale after an upgrade.
|
|
309
|
+
- **`ap_starter_registry()` is illustrative**, not a starting point for production. It exists so the demo and the quick start have something to run against.
|
|
271
310
|
- **A `const:` value in a spec is a placeholder.** The SAP specs default `ReversalReason` to `01`, which as delivered permits only the original posting date. Use the reason your finance team configured.
|
|
272
311
|
- **Compensating actions are not inverses.** You can void a payment; you cannot recall the remittance advice already sent. `Reversibility.COMPENSABLE` requires you to name the `residue` — what survives the undo — because an unnamed side effect is an unowned risk.
|
|
273
312
|
- **A policy engine's false-positive rate is a property of the policies you write**, not of the engine. A flawless engine still blocks a legitimate call if a rule is too broad. The goal is an engine you can reason about and test exhaustively.
|
|
@@ -275,6 +314,7 @@ This is a **working foundation**, published so it can be read, run, and extended
|
|
|
275
314
|
- **Intent-drift detection is lexical overlap.** It flags divergence; it does not establish intent.
|
|
276
315
|
- **A hash chain does not detect truncation.** Edits, reorders, and interior deletions break verification; dropping the most recent entries leaves a valid prefix. Anchor the head hash externally — `Ledger.checkpoint()` gives you the value to publish.
|
|
277
316
|
- **In-memory stores are single-process.** `InMemorySessionStore.would_exceed` followed by `commit` is not atomic, so two concurrent calls can both pass a check only one should. A shared store must make that pair atomic.
|
|
317
|
+
- **Nothing persists yet.** The ledger, the reversal journal, and the drill register are all in memory, so they reset on restart. Three consequences worth knowing: the horizon forgets undo windows that are still open, `RecoverabilityRegister`'s freshness window means nothing across deploys, and a restart loses the evidence chain rather than breaking it — which is a different failure from tampering and currently indistinguishable from it. Persisting the ledger needs WAL with `synchronous=FULL` and append-only enforced by triggers (SQLite has no `GRANT`), the ledger append and journal write in one transaction, and a startup grace period so a long outage does not mass-degrade every proof to `IRREVERSIBLE` and block legitimate work.
|
|
278
318
|
- **Control mappings are a self-assessment aid, not a certification** or a legal opinion. NIST AI RMF is voluntary; EU AI Act conformity is assessed against a quality-management system of which logging is one clause.
|
|
279
319
|
|
|
280
320
|
Every place needing production hardening is marked `# HARDENING:` in the source. Search for it before deploying.
|
|
@@ -302,6 +342,8 @@ ASI04 (supply chain) and ASI05 (unexpected code execution) are deliberately left
|
|
|
302
342
|
## CLI
|
|
303
343
|
|
|
304
344
|
```bash
|
|
345
|
+
revoco bench # the containment benchmark
|
|
346
|
+
revoco surfaces --gates # what the adapters cover, and every gate to implement
|
|
305
347
|
revoco policy-check policy.yaml # validate a policy, warn on risky defaults
|
|
306
348
|
revoco inverses-check inverses.yaml # validate an inverse registry
|
|
307
349
|
revoco coverage inverses.yaml --tools a,b # rollback readiness for a tool surface
|
|
@@ -309,7 +351,17 @@ revoco controls # print the control mapping
|
|
|
309
351
|
revoco demo # end-to-end AP fraud scenario
|
|
310
352
|
```
|
|
311
353
|
|
|
312
|
-
|
|
354
|
+
Plus one script, because it needs a real filesystem rather than a package entry point:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
python scripts/validate_workstation.py # drill the workstation adapter for real
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Three of these are CI gates**, and each encodes a rule the package makes about itself:
|
|
361
|
+
|
|
362
|
+
- `bench` exits non-zero on an unexpected loss or any false positive. A scenario whose designed outcome is loss doesn't fail the build; a regression that starts losing something new does.
|
|
363
|
+
- `coverage` exits non-zero when a tool has no declared inverse — so adding a write operation without classifying its undo path fails the build.
|
|
364
|
+
- `inverses-check` exits non-zero when a spec reads `snapshot.X` without declaring `X`, which is the easiest way to author a phantom rollback.
|
|
313
365
|
|
|
314
366
|
---
|
|
315
367
|
|
|
@@ -320,13 +372,23 @@ src/revoco/
|
|
|
320
372
|
core/ crypto (Ed25519, SHA-256, RFC-8785 canonical JSON), ids, errors
|
|
321
373
|
authority/ principals · scope · delegation · revocation · chain reconstruction
|
|
322
374
|
gate/ policy · conditions · threat scanner · session budgets · PDP
|
|
323
|
-
reversal/ model ·
|
|
324
|
-
adapters/
|
|
375
|
+
reversal/ model · registry · engine · budget · horizon
|
|
376
|
+
adapters/ 91 inverse specs across 8 surfaces (docs/ADAPTERS.md)
|
|
377
|
+
bench/ containment benchmark: world · scenarios · harness · corpus · report
|
|
378
|
+
drills.py recovery drills, proof-gated classification, attestations
|
|
325
379
|
ledger.py one append-only hash-chained ledger
|
|
326
380
|
detect.py OWASP ASI + PRA01/PRA02 detectors
|
|
327
381
|
controlplane.py the orchestrator
|
|
328
382
|
evidence.py evidence packs + readiness reports
|
|
329
383
|
demo.py runnable AP-fraud scenario
|
|
384
|
+
|
|
385
|
+
scripts/
|
|
386
|
+
validate_workstation.py drills the workstation adapter against real fs + git
|
|
387
|
+
bump_version.py used by the release workflow
|
|
388
|
+
|
|
389
|
+
docs/
|
|
390
|
+
ADAPTERS.md per-spec semantics, citations, validation checklist
|
|
391
|
+
RELEASING.md the release path, and how to schedule drills
|
|
330
392
|
```
|
|
331
393
|
|
|
332
394
|
**One ledger, not three.** Each merged tool had its own hash-chained log. Three chains cannot be verified as one history: an attacker who altered a policy decision in one and the matching action record in another breaks both independently, and nothing could prove the two logs described the same event. A single chain over authority, enforcement, and reversal makes "what happened, in what order, under whose authority" one verifiable question.
|
|
@@ -1,46 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
Name: revoco
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: An action control plane for AI agents: delegated authority, per-action policy enforcement, reversible execution, and regulator-grade evidence.
|
|
5
|
-
Project-URL: Homepage, https://github.com/rsh1k/revoco
|
|
6
|
-
Project-URL: Source, https://github.com/rsh1k/revoco
|
|
7
|
-
Author: rsh1k
|
|
8
|
-
License: Apache-2.0
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Keywords: agentic-ai,ai-agents,ai-security,audit,eu-ai-act,governance,mcp,nist,owasp,rollback,sox
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Intended Audience :: System Administrators
|
|
14
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
-
Classifier: Topic :: Security
|
|
20
|
-
Requires-Python: >=3.10
|
|
21
|
-
Requires-Dist: cryptography>=42.0
|
|
22
|
-
Provides-Extra: api
|
|
23
|
-
Requires-Dist: fastapi>=0.110; extra == 'api'
|
|
24
|
-
Requires-Dist: uvicorn>=0.29; extra == 'api'
|
|
25
|
-
Provides-Extra: dev
|
|
26
|
-
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
27
|
-
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
-
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
29
|
-
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
30
|
-
Provides-Extra: yaml
|
|
31
|
-
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
|
|
32
|
-
Description-Content-Type: text/markdown
|
|
33
|
-
|
|
34
|
-
# Revoco
|
|
1
|
+
# Revoco — undo for AI agent actions
|
|
35
2
|
|
|
36
3
|
[](https://github.com/rsh1k/revoco/actions/workflows/ci.yml)
|
|
37
|
-
[](https://pypi.org/project/revoco/)
|
|
5
|
+
[](https://pypi.org/project/revoco/)
|
|
6
|
+
[](LICENSE)
|
|
40
7
|
|
|
41
|
-
|
|
8
|
+
Agent governance is crowded with tools that answer *was this allowed* and *was this logged*. Almost none answer **can we take it back** — and that's the question that decides whether an incident costs an afternoon or a quarter.
|
|
42
9
|
|
|
43
|
-
|
|
10
|
+
When an agent repoints a supplier's bank account or deletes a production Deployment, knowing exactly what happened is necessary and not sufficient. Somebody still has to put it back, by hand, under time pressure, while the auditors watch.
|
|
11
|
+
|
|
12
|
+
Revoco does four things:
|
|
13
|
+
|
|
14
|
+
- **Plans the rollback before the action runs** — the only moment prior state still exists. A plan built afterwards can record what changed but not what to restore.
|
|
15
|
+
- **Rolls back a whole compromised grant in one call** — revoke the authority *and* its sub-delegated subtree, then undo everything done under it, newest first.
|
|
16
|
+
- **Proves the rollback still works** — drills each inverse against a disposable canary and compares state, because a backup nobody has restored is a hypothesis.
|
|
17
|
+
- **Produces evidence someone who distrusts you can verify** — one hash-chained ledger over authority, enforcement and reversal.
|
|
18
|
+
|
|
19
|
+
*Revoco* is Latin for "I call it back."
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install revoco
|
|
23
|
+
```
|
|
44
24
|
|
|
45
25
|
Revoco merges three earlier tools and adds the layer none of them had:
|
|
46
26
|
|
|
@@ -53,13 +33,9 @@ Revoco merges three earlier tools and adds the layer none of them had:
|
|
|
53
33
|
|
|
54
34
|
---
|
|
55
35
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
Agent governance tooling is overwhelmingly about detection: was this call allowed, was it logged, was it anomalous. That leaves the expensive half of an incident untouched.
|
|
36
|
+
## Reversibility as an authorization input
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
So Revoco treats reversibility as **a property of the action, declared and planned before the action runs**, and as a **first-class authorization input**:
|
|
38
|
+
Reversibility isn't a recovery procedure you write afterwards — by then the prior state is gone. So Revoco treats it as a property of the action, declared before the action runs, which makes it available to the thing that decides whether the action happens at all:
|
|
63
39
|
|
|
64
40
|
```yaml
|
|
65
41
|
- id: no-undo-needs-a-human
|
|
@@ -74,11 +50,7 @@ Enforcement stops being only *"may this agent do it?"* and becomes also *"and ca
|
|
|
74
50
|
|
|
75
51
|
## Quick start
|
|
76
52
|
|
|
77
|
-
|
|
78
|
-
pip install revoco
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Or from source:
|
|
53
|
+
Installed above, or from source:
|
|
82
54
|
|
|
83
55
|
```bash
|
|
84
56
|
git clone https://github.com/rsh1k/revoco.git
|
|
@@ -300,7 +272,8 @@ lists the sequenced undos, the one-shot specs, and every gate your `GateEvaluato
|
|
|
300
272
|
|
|
301
273
|
This is a **working foundation**, published so it can be read, run, and extended. It is not a finished or certified product.
|
|
302
274
|
|
|
303
|
-
- **
|
|
275
|
+
- **One adapter surface is validated; seven are specifications.** `workstation` (14 specs) is drilled against a real filesystem and a real git repo by `scripts/validate_workstation.py`, in CI — 11 of 11 drillable inverses restore state, 10 prose claims probed. **The other seven have never been executed against a live system.** They were written from vendor documentation and KBAs; argument names differ across S/4HANA Cloud, on-premise releases and your middleware, and Workday business process configuration is per-tenant. **A tool mapped to the wrong inverse produces a confident, wrong rollback** — worse than none. Work through the checklist in [docs/ADAPTERS.md](docs/ADAPTERS.md) first, and re-validate after every ERP upgrade. Validating the one cheap surface found five defects that would all have recurred elsewhere, which is the argument for doing it before you touch a vendor sandbox — and a scheduled drill is the only thing that catches a spec going stale after an upgrade.
|
|
276
|
+
- **`ap_starter_registry()` is illustrative**, not a starting point for production. It exists so the demo and the quick start have something to run against.
|
|
304
277
|
- **A `const:` value in a spec is a placeholder.** The SAP specs default `ReversalReason` to `01`, which as delivered permits only the original posting date. Use the reason your finance team configured.
|
|
305
278
|
- **Compensating actions are not inverses.** You can void a payment; you cannot recall the remittance advice already sent. `Reversibility.COMPENSABLE` requires you to name the `residue` — what survives the undo — because an unnamed side effect is an unowned risk.
|
|
306
279
|
- **A policy engine's false-positive rate is a property of the policies you write**, not of the engine. A flawless engine still blocks a legitimate call if a rule is too broad. The goal is an engine you can reason about and test exhaustively.
|
|
@@ -308,6 +281,7 @@ This is a **working foundation**, published so it can be read, run, and extended
|
|
|
308
281
|
- **Intent-drift detection is lexical overlap.** It flags divergence; it does not establish intent.
|
|
309
282
|
- **A hash chain does not detect truncation.** Edits, reorders, and interior deletions break verification; dropping the most recent entries leaves a valid prefix. Anchor the head hash externally — `Ledger.checkpoint()` gives you the value to publish.
|
|
310
283
|
- **In-memory stores are single-process.** `InMemorySessionStore.would_exceed` followed by `commit` is not atomic, so two concurrent calls can both pass a check only one should. A shared store must make that pair atomic.
|
|
284
|
+
- **Nothing persists yet.** The ledger, the reversal journal, and the drill register are all in memory, so they reset on restart. Three consequences worth knowing: the horizon forgets undo windows that are still open, `RecoverabilityRegister`'s freshness window means nothing across deploys, and a restart loses the evidence chain rather than breaking it — which is a different failure from tampering and currently indistinguishable from it. Persisting the ledger needs WAL with `synchronous=FULL` and append-only enforced by triggers (SQLite has no `GRANT`), the ledger append and journal write in one transaction, and a startup grace period so a long outage does not mass-degrade every proof to `IRREVERSIBLE` and block legitimate work.
|
|
311
285
|
- **Control mappings are a self-assessment aid, not a certification** or a legal opinion. NIST AI RMF is voluntary; EU AI Act conformity is assessed against a quality-management system of which logging is one clause.
|
|
312
286
|
|
|
313
287
|
Every place needing production hardening is marked `# HARDENING:` in the source. Search for it before deploying.
|
|
@@ -335,6 +309,8 @@ ASI04 (supply chain) and ASI05 (unexpected code execution) are deliberately left
|
|
|
335
309
|
## CLI
|
|
336
310
|
|
|
337
311
|
```bash
|
|
312
|
+
revoco bench # the containment benchmark
|
|
313
|
+
revoco surfaces --gates # what the adapters cover, and every gate to implement
|
|
338
314
|
revoco policy-check policy.yaml # validate a policy, warn on risky defaults
|
|
339
315
|
revoco inverses-check inverses.yaml # validate an inverse registry
|
|
340
316
|
revoco coverage inverses.yaml --tools a,b # rollback readiness for a tool surface
|
|
@@ -342,7 +318,17 @@ revoco controls # print the control mapping
|
|
|
342
318
|
revoco demo # end-to-end AP fraud scenario
|
|
343
319
|
```
|
|
344
320
|
|
|
345
|
-
|
|
321
|
+
Plus one script, because it needs a real filesystem rather than a package entry point:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
python scripts/validate_workstation.py # drill the workstation adapter for real
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Three of these are CI gates**, and each encodes a rule the package makes about itself:
|
|
328
|
+
|
|
329
|
+
- `bench` exits non-zero on an unexpected loss or any false positive. A scenario whose designed outcome is loss doesn't fail the build; a regression that starts losing something new does.
|
|
330
|
+
- `coverage` exits non-zero when a tool has no declared inverse — so adding a write operation without classifying its undo path fails the build.
|
|
331
|
+
- `inverses-check` exits non-zero when a spec reads `snapshot.X` without declaring `X`, which is the easiest way to author a phantom rollback.
|
|
346
332
|
|
|
347
333
|
---
|
|
348
334
|
|
|
@@ -353,13 +339,23 @@ src/revoco/
|
|
|
353
339
|
core/ crypto (Ed25519, SHA-256, RFC-8785 canonical JSON), ids, errors
|
|
354
340
|
authority/ principals · scope · delegation · revocation · chain reconstruction
|
|
355
341
|
gate/ policy · conditions · threat scanner · session budgets · PDP
|
|
356
|
-
reversal/ model ·
|
|
357
|
-
adapters/
|
|
342
|
+
reversal/ model · registry · engine · budget · horizon
|
|
343
|
+
adapters/ 91 inverse specs across 8 surfaces (docs/ADAPTERS.md)
|
|
344
|
+
bench/ containment benchmark: world · scenarios · harness · corpus · report
|
|
345
|
+
drills.py recovery drills, proof-gated classification, attestations
|
|
358
346
|
ledger.py one append-only hash-chained ledger
|
|
359
347
|
detect.py OWASP ASI + PRA01/PRA02 detectors
|
|
360
348
|
controlplane.py the orchestrator
|
|
361
349
|
evidence.py evidence packs + readiness reports
|
|
362
350
|
demo.py runnable AP-fraud scenario
|
|
351
|
+
|
|
352
|
+
scripts/
|
|
353
|
+
validate_workstation.py drills the workstation adapter against real fs + git
|
|
354
|
+
bump_version.py used by the release workflow
|
|
355
|
+
|
|
356
|
+
docs/
|
|
357
|
+
ADAPTERS.md per-spec semantics, citations, validation checklist
|
|
358
|
+
RELEASING.md the release path, and how to schedule drills
|
|
363
359
|
```
|
|
364
360
|
|
|
365
361
|
**One ledger, not three.** Each merged tool had its own hash-chained log. Three chains cannot be verified as one history: an attacker who altered a policy decision in one and the matching action record in another breaks both independently, and nothing could prove the two logs described the same event. A single chain over authority, enforcement, and reversal makes "what happened, in what order, under whose authority" one verifiable question.
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# System-of-record adapters
|
|
2
2
|
|
|
3
|
-
**Status:
|
|
3
|
+
**Status: one surface validated, seven specified.**
|
|
4
|
+
|
|
5
|
+
`workstation` (14 specs) is **validated against a real filesystem and a real git repo** — 11 of 11 drillable inverses restore state, and 10 prose claims were probed empirically. Run it yourself:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python scripts/validate_workstation.py
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The other seven surfaces are **specification, not validated integration.**
|
|
4
12
|
|
|
5
13
|
Everything else in Revoco is transferable across customers. This is not. Knowing that an SAP payment reversal is a three-step ordered sequence, that a Workday rescind dies the moment payroll runs, or that an S3 delete is recoverable only if someone enabled versioning first, is per-system knowledge that has to be built once and maintained forever. It is also the moat — capital cannot shortcut it, and a horizontal identity vendor will not do it.
|
|
6
14
|
|
|
@@ -23,7 +31,7 @@ revoco surfaces --gates # what is covered, and every gate you must implement
|
|
|
23
31
|
| `devops` | 12 | GitHub refs and protection, Kubernetes, feature flags |
|
|
24
32
|
| `database` | 8 | Row writes, arbitrary SQL, schema migrations |
|
|
25
33
|
| `saas` | 12 | Salesforce records, Slack messages, Stripe payments |
|
|
26
|
-
| `workstation` | 14 | Filesystem, git, shell — what coding agents actually touch |
|
|
34
|
+
| `workstation` | 14 | Filesystem, git, shell — what coding agents actually touch — **validated** |
|
|
27
35
|
|
|
28
36
|
Two numbers from `revoco surfaces` are worth reporting upward:
|
|
29
37
|
|
|
@@ -301,3 +309,30 @@ Load only the surfaces you actually govern — a registry claiming to classify S
|
|
|
301
309
|
Return `True` to open a gate, `False` to close it, or a **string** to close it with an explanation that reaches the incident responder. Raising is treated as closed.
|
|
302
310
|
|
|
303
311
|
A spec that declares gates and runs without an evaluator refuses to execute, and an authorize-phase gate with no evaluator degrades the classification to irreversible. Both are deliberate: an unverifiable precondition is not a precondition, and at authorize time "assume the worst" is what makes the escalation trustworthy.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## What validating one surface actually taught us
|
|
316
|
+
|
|
317
|
+
`scripts/validate_workstation.py` drills all 11 drillable inverses against a real filesystem and a real `git init` repo, then probes the prose claims the classifications rest on. It found five things, and every one of them is a pattern that will recur on the surfaces still unvalidated.
|
|
318
|
+
|
|
319
|
+
**1. A snapshot field that looks right can produce an undo that succeeds and leaves you somewhere else.** `git.checkout`'s inverse restores `snapshot.head_ref`. The first integration captured that as `git symbolic-ref HEAD` — `refs/heads/main` — and feeding a full ref path to `git checkout` **detaches HEAD** instead of switching to the branch. The inverse returned success. State was materially different. Nothing but a state comparison catches that, which is the entire argument for drills over health checks.
|
|
320
|
+
|
|
321
|
+
**2. Drills contaminate each other unless canaries are independent.** The first version shared one repo across all drills. Earlier drills left the tree clean, so `git.switch_with_stash` stashed nothing and its inverse died with *"No stash entries found"*. One drill's residue had become the next one's precondition. Each drill now gets its own sandbox. **If you point a drill suite at production, canaries must be independent or you are measuring the order you happened to run them in.**
|
|
322
|
+
|
|
323
|
+
**3. A plausible mechanism can be the wrong reason for a correct classification.** `fs.delete_file` is `COMPENSABLE`, and the original residue explained why: the recreated file gets a new inode. The probe found the inode **immediately reused** when nothing else referenced it. The classification was right; the stated reason was not. The residue now cites the broken hard link, which held on every run. A residue nobody has tested is a plausible story, and plausible stories are what this package exists to replace.
|
|
324
|
+
|
|
325
|
+
**4. Captured-and-never-restored fields need saying out loud.** `fs.delete_file` snapshots `mtime` and `owner`, and its inverse — a write — cannot restore either. That is not dead code: they let an evidence pack state precisely what was lost. But the original residue implied restoring them was an option the spec declined to take, which reads as a choice rather than a limit.
|
|
326
|
+
|
|
327
|
+
**5. A drill runner with no gate evaluator cannot drill anything gated.** Three specs failed with *"no inverse operation exists"* until an evaluator was supplied, because an unverifiable authorize-phase gate degrades the classification to irreversible. The machinery refusing to guess is correct. The trap is that a harness without one appears to validate the whole surface while silently covering only the ungated part of it.
|
|
328
|
+
|
|
329
|
+
### What this means for the other seven surfaces
|
|
330
|
+
|
|
331
|
+
The failure modes above are not filesystem-specific. Expect the same shapes in SAP and Workday:
|
|
332
|
+
|
|
333
|
+
- an argument captured in the upstream API's vocabulary that means something subtly different when fed back (finding 1 — the highest-risk class, because it looks like success)
|
|
334
|
+
- canaries that interfere, especially on surfaces with shared state like an accounting period or a payroll run (finding 2)
|
|
335
|
+
- residue prose written from documentation rather than observation (finding 3)
|
|
336
|
+
- gates that no evaluator answers, silently shrinking what a drill actually covers (finding 5)
|
|
337
|
+
|
|
338
|
+
Which is the argument for validating the cheap surface first: none of these five needed an ERP sandbox to find, and all five would have cost far more to discover there.
|
|
@@ -4,15 +4,17 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "revoco"
|
|
7
|
-
version = "0.2.
|
|
8
|
-
description = "
|
|
7
|
+
version = "0.2.2"
|
|
8
|
+
description = "Undo for AI agent actions. Plans the rollback before the action runs, proves it still works, and rolls back a compromised grant's whole blast radius in one call."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { text = "Apache-2.0" }
|
|
12
12
|
authors = [{ name = "rsh1k" }]
|
|
13
13
|
keywords = [
|
|
14
|
-
"ai-
|
|
15
|
-
"
|
|
14
|
+
"ai-agents", "agentic-ai", "ai-security", "rollback", "undo",
|
|
15
|
+
"reversibility", "compensating-transaction", "agent-governance",
|
|
16
|
+
"audit-trail", "tamper-evident", "mcp", "guardrails",
|
|
17
|
+
"owasp", "nist", "eu-ai-act", "sox", "sr-11-7",
|
|
16
18
|
]
|
|
17
19
|
classifiers = [
|
|
18
20
|
"Development Status :: 3 - Alpha",
|