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.
Files changed (80) hide show
  1. {revoco-0.2.0 → revoco-0.2.2}/.github/workflows/ci.yml +7 -0
  2. revoco-0.2.0/README.md → revoco-0.2.2/PKG-INFO +83 -21
  3. revoco-0.2.0/PKG-INFO → revoco-0.2.2/README.md +50 -54
  4. {revoco-0.2.0 → revoco-0.2.2}/docs/ADAPTERS.md +37 -2
  5. {revoco-0.2.0 → revoco-0.2.2}/pyproject.toml +6 -4
  6. revoco-0.2.2/scripts/validate_workstation.py +580 -0
  7. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/workstation.py +15 -6
  8. {revoco-0.2.0 → revoco-0.2.2}/.github/workflows/release.yml +0 -0
  9. {revoco-0.2.0 → revoco-0.2.2}/.gitignore +0 -0
  10. {revoco-0.2.0 → revoco-0.2.2}/LICENSE +0 -0
  11. {revoco-0.2.0 → revoco-0.2.2}/docs/RELEASING.md +0 -0
  12. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses.yaml +0 -0
  13. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_cloud.json +0 -0
  14. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_database.json +0 -0
  15. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_devops.json +0 -0
  16. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_identity.json +0 -0
  17. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_saas.json +0 -0
  18. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_sap.json +0 -0
  19. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_workday.json +0 -0
  20. {revoco-0.2.0 → revoco-0.2.2}/examples/inverses_workstation.json +0 -0
  21. {revoco-0.2.0 → revoco-0.2.2}/examples/policy.yaml +0 -0
  22. {revoco-0.2.0 → revoco-0.2.2}/scripts/bump_version.py +0 -0
  23. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/__init__.py +0 -0
  24. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/__init__.py +0 -0
  25. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/cloud.py +0 -0
  26. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/database.py +0 -0
  27. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/devops.py +0 -0
  28. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/identity.py +0 -0
  29. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/saas.py +0 -0
  30. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/sap.py +0 -0
  31. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/adapters/workday.py +0 -0
  32. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/__init__.py +0 -0
  33. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/action.py +0 -0
  34. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/delegation.py +0 -0
  35. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/engine.py +0 -0
  36. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/principals.py +0 -0
  37. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/revocation.py +0 -0
  38. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/authority/scope.py +0 -0
  39. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/__init__.py +0 -0
  40. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/corpus.py +0 -0
  41. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/harness.py +0 -0
  42. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/report.py +0 -0
  43. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/scenario.py +0 -0
  44. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/bench/world.py +0 -0
  45. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/cli.py +0 -0
  46. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/controlplane.py +0 -0
  47. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/__init__.py +0 -0
  48. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/crypto.py +0 -0
  49. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/errors.py +0 -0
  50. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/core/ids.py +0 -0
  51. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/demo.py +0 -0
  52. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/detect.py +0 -0
  53. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/drills.py +0 -0
  54. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/evidence.py +0 -0
  55. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/__init__.py +0 -0
  56. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/conditions.py +0 -0
  57. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/decision.py +0 -0
  58. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/engine.py +0 -0
  59. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/policy.py +0 -0
  60. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/session.py +0 -0
  61. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/gate/threats.py +0 -0
  62. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/ledger.py +0 -0
  63. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/__init__.py +0 -0
  64. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/budget.py +0 -0
  65. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/engine.py +0 -0
  66. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/horizon.py +0 -0
  67. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/model.py +0 -0
  68. {revoco-0.2.0 → revoco-0.2.2}/src/revoco/reversal/registry.py +0 -0
  69. {revoco-0.2.0 → revoco-0.2.2}/tests/test_adapter_catalog.py +0 -0
  70. {revoco-0.2.0 → revoco-0.2.2}/tests/test_adapters.py +0 -0
  71. {revoco-0.2.0 → revoco-0.2.2}/tests/test_authority.py +0 -0
  72. {revoco-0.2.0 → revoco-0.2.2}/tests/test_bench.py +0 -0
  73. {revoco-0.2.0 → revoco-0.2.2}/tests/test_budget_and_drills.py +0 -0
  74. {revoco-0.2.0 → revoco-0.2.2}/tests/test_controlplane.py +0 -0
  75. {revoco-0.2.0 → revoco-0.2.2}/tests/test_core.py +0 -0
  76. {revoco-0.2.0 → revoco-0.2.2}/tests/test_demo_and_cli.py +0 -0
  77. {revoco-0.2.0 → revoco-0.2.2}/tests/test_gate.py +0 -0
  78. {revoco-0.2.0 → revoco-0.2.2}/tests/test_horizon_and_scheduling.py +0 -0
  79. {revoco-0.2.0 → revoco-0.2.2}/tests/test_ledger.py +0 -0
  80. {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
- # Revoco
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
  [![ci](https://github.com/rsh1k/revoco/actions/workflows/ci.yml/badge.svg)](https://github.com/rsh1k/revoco/actions/workflows/ci.yml)
4
- [![PyPI](https://img.shields.io/pypi/v/revoco.svg)](https://pypi.org/project/revoco/)
5
- [![Python](https://img.shields.io/pypi/pyversions/revoco.svg)](https://pypi.org/project/revoco/)
6
- [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
37
+ [![PyPI](https://img.shields.io/pypi/v/revoco)](https://pypi.org/project/revoco/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/revoco)](https://pypi.org/project/revoco/)
39
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
7
40
 
8
- **An action control plane for AI agents.** Delegated authority, per-action policy enforcement, **reversible execution**, and evidence a regulator can verify.
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
- *Revoco* is Latin for **"I call it back."** That is the whole thesis: agent governance is crowded with tools that answer *was this allowed* and *was this logged*, and almost none that answer *can we take it back*. The third question is the one that decides whether an incident costs an afternoon or a quarter.
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
- ## The gap this closes
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
- When an agent has already repointed a supplier's bank account and paid an invoice into it, 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. Reversibility is not a recovery procedure you write afterwards. By then the prior state is gone.
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
- ```bash
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
- - **The inverse registries are specifications, not validated integrations.** `ap_starter_registry()` is illustrative; the SAP and Workday registries were written from vendor documentation and KBAs and have **never been executed against a live system**. Argument names differ across S/4HANA Cloud, on-premise releases, and your middleware; Workday business process configuration is per-tenant. **A tool mapped to the wrong inverse will produce a confident, wrong rollback** — worse than no rollback. Work through the validation checklist in [docs/ADAPTERS.md](docs/ADAPTERS.md) before any of it governs a real write, and re-validate after every ERP upgrade: a changed API contract turns a correct spec into a wrong one and nothing here can detect that for you.
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
- `coverage` exits non-zero when tools have no declared inverse, so it works as a CI gate: adding a write operation without classifying its undo path fails the build.
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 · inverse registry · reversal engine <- new
324
- adapters/ SAP + Workday inverse specs (see docs/ADAPTERS.md) <- new
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
- Metadata-Version: 2.4
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
  [![ci](https://github.com/rsh1k/revoco/actions/workflows/ci.yml/badge.svg)](https://github.com/rsh1k/revoco/actions/workflows/ci.yml)
37
- [![PyPI](https://img.shields.io/pypi/v/revoco.svg)](https://pypi.org/project/revoco/)
38
- [![Python](https://img.shields.io/pypi/pyversions/revoco.svg)](https://pypi.org/project/revoco/)
39
- [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
4
+ [![PyPI](https://img.shields.io/pypi/v/revoco)](https://pypi.org/project/revoco/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/revoco)](https://pypi.org/project/revoco/)
6
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
40
7
 
41
- **An action control plane for AI agents.** Delegated authority, per-action policy enforcement, **reversible execution**, and evidence a regulator can verify.
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
- *Revoco* is Latin for **"I call it back."** That is the whole thesis: agent governance is crowded with tools that answer *was this allowed* and *was this logged*, and almost none that answer *can we take it back*. The third question is the one that decides whether an incident costs an afternoon or a quarter.
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
- ## The gap this closes
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
- When an agent has already repointed a supplier's bank account and paid an invoice into it, 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. Reversibility is not a recovery procedure you write afterwards. By then the prior state is gone.
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
- ```bash
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
- - **The inverse registries are specifications, not validated integrations.** `ap_starter_registry()` is illustrative; the SAP and Workday registries were written from vendor documentation and KBAs and have **never been executed against a live system**. Argument names differ across S/4HANA Cloud, on-premise releases, and your middleware; Workday business process configuration is per-tenant. **A tool mapped to the wrong inverse will produce a confident, wrong rollback** — worse than no rollback. Work through the validation checklist in [docs/ADAPTERS.md](docs/ADAPTERS.md) before any of it governs a real write, and re-validate after every ERP upgrade: a changed API contract turns a correct spec into a wrong one and nothing here can detect that for you.
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
- `coverage` exits non-zero when tools have no declared inverse, so it works as a CI gate: adding a write operation without classifying its undo path fails the build.
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 · inverse registry · reversal engine <- new
357
- adapters/ SAP + Workday inverse specs (see docs/ADAPTERS.md) <- new
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: specification, not a validated integration.**
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.0"
8
- description = "An action control plane for AI agents: delegated authority, per-action policy enforcement, reversible execution, and regulator-grade evidence."
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-security", "ai-agents", "agentic-ai", "governance", "audit",
15
- "owasp", "nist", "eu-ai-act", "sox", "mcp", "rollback",
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",