obstat 0.1.0__tar.gz → 0.2.0__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.
- {obstat-0.1.0 → obstat-0.2.0}/.gitignore +3 -0
- obstat-0.2.0/CLAUDE.md +52 -0
- {obstat-0.1.0 → obstat-0.2.0}/PKG-INFO +25 -3
- {obstat-0.1.0 → obstat-0.2.0}/README.md +24 -2
- obstat-0.2.0/docs/obstat-spec.md +587 -0
- {obstat-0.1.0 → obstat-0.2.0}/examples/server.py +3 -2
- {obstat-0.1.0 → obstat-0.2.0}/obstat/__init__.py +1 -1
- {obstat-0.1.0 → obstat-0.2.0}/obstat/__main__.py +11 -0
- obstat-0.2.0/obstat/approval.py +200 -0
- {obstat-0.1.0 → obstat-0.2.0}/obstat/guard.py +79 -22
- {obstat-0.1.0 → obstat-0.2.0}/obstat/policy.py +8 -2
- obstat-0.2.0/obstat/record.py +259 -0
- {obstat-0.1.0 → obstat-0.2.0}/pyproject.toml +1 -1
- {obstat-0.1.0 → obstat-0.2.0}/tests/test_guard.py +179 -8
- {obstat-0.1.0 → obstat-0.2.0}/uv.lock +1 -1
- obstat-0.1.0/.idea/.gitignore +0 -10
- obstat-0.1.0/.idea/awsToolkit.xml +0 -17
- obstat-0.1.0/.idea/inspectionProfiles/Project_Default.xml +0 -23
- obstat-0.1.0/.idea/misc.xml +0 -10
- obstat-0.1.0/.idea/modules.xml +0 -8
- obstat-0.1.0/.idea/obstat.iml +0 -11
- obstat-0.1.0/.idea/vcs.xml +0 -6
- obstat-0.1.0/obstat/approval.py +0 -139
- obstat-0.1.0/obstat/record.py +0 -122
- {obstat-0.1.0 → obstat-0.2.0}/.github/workflows/ci.yml +0 -0
- {obstat-0.1.0 → obstat-0.2.0}/.github/workflows/publish.yml +0 -0
- {obstat-0.1.0 → obstat-0.2.0}/LICENSE +0 -0
- {obstat-0.1.0 → obstat-0.2.0}/obstat/paths.py +0 -0
- {obstat-0.1.0 → obstat-0.2.0}/obstat.toml +0 -0
- {obstat-0.1.0 → obstat-0.2.0}/tests/conftest.py +0 -0
obstat-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# obstat
|
|
2
|
+
|
|
3
|
+
An auditable decision record for agent tool calls. The one claim the project
|
|
4
|
+
makes: **the decision is on disk, fsynced, before the tool body runs.** Anything
|
|
5
|
+
that weakens that ordering is a bug, not an optimisation.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv run pytest -q # 26 tests, ~0.3s
|
|
11
|
+
uv run ruff check .
|
|
12
|
+
uv run ruff format .
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Rules that are not negotiable
|
|
16
|
+
|
|
17
|
+
- **No runtime dependencies.** `tomllib`, `sqlite3`, `hashlib`, `json` are
|
|
18
|
+
stdlib. `mcp` is an optional extra for `examples/server.py` only. A governance
|
|
19
|
+
library nobody can try on a laptop is one nobody adopts.
|
|
20
|
+
- **`docs/obstat-spec.md` is normative.** Behaviour changes update the spec in
|
|
21
|
+
the same commit. Where spec and code disagree, one of them is a bug.
|
|
22
|
+
- **Nothing matching the policy is a deny**, and a missing policy file is an
|
|
23
|
+
error — never an implicit allow.
|
|
24
|
+
- **Denials reach the caller with a record id and nothing else.** The reason goes
|
|
25
|
+
to the record. A denial that explains itself teaches a caller what to work
|
|
26
|
+
around.
|
|
27
|
+
|
|
28
|
+
## Conventions
|
|
29
|
+
|
|
30
|
+
Comments say *why*, not what, and name the alternative that was rejected. A
|
|
31
|
+
`ponytail:` comment marks a deliberate shortcut and its upgrade path — those are
|
|
32
|
+
debts, not decoration.
|
|
33
|
+
|
|
34
|
+
Spec section numbers (`§5.1`) are referenced from docstrings; keep them in step
|
|
35
|
+
when the spec is renumbered.
|
|
36
|
+
|
|
37
|
+
## Releasing
|
|
38
|
+
|
|
39
|
+
1. Bump `version` in `pyproject.toml` **and** `__version__` in
|
|
40
|
+
`obstat/__init__.py` — two places, no single source yet.
|
|
41
|
+
2. `git tag vX.Y.Z && git push --tags`
|
|
42
|
+
|
|
43
|
+
`.github/workflows/publish.yml` runs the tests against the tag, builds, and
|
|
44
|
+
uploads via PyPI trusted publishing (OIDC — there is no token anywhere). **A PyPI
|
|
45
|
+
version is immutable**: a bad build can be superseded, never withdrawn.
|
|
46
|
+
|
|
47
|
+
## Gotcha
|
|
48
|
+
|
|
49
|
+
`uv run --with /path/to/obstat` serves a **cached wheel** and will happily run
|
|
50
|
+
code you edited minutes ago as if you hadn't. `--reinstall-package` does not
|
|
51
|
+
dislodge it; `--refresh` does. Two verification runs were wasted on this — if a
|
|
52
|
+
local install seems to ignore your change, that is why.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: obstat
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: An auditable decision record for agent tool calls. The clearance is written down before the call runs.
|
|
5
5
|
Project-URL: Homepage, https://github.com/marcinmarzeta/obstat
|
|
6
6
|
Project-URL: Issues, https://github.com/marcinmarzeta/obstat/issues
|
|
@@ -21,6 +21,11 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
|
|
22
22
|
# obstat
|
|
23
23
|
|
|
24
|
+
[](https://pypi.org/project/obstat/)
|
|
25
|
+
[](https://pypi.org/project/obstat/)
|
|
26
|
+
[](https://github.com/marcinmarzeta/obstat/actions/workflows/ci.yml)
|
|
27
|
+
[](LICENSE)
|
|
28
|
+
|
|
24
29
|
**An auditable decision record for agent tool calls.** The clearance is written
|
|
25
30
|
down before the call runs — not reconstructed from logs afterwards.
|
|
26
31
|
|
|
@@ -37,6 +42,9 @@ pip install obstat
|
|
|
37
42
|
No dependencies. Not AWS, not an identity provider, not a policy service — the
|
|
38
43
|
decorator, `tomllib`, `sqlite3` and a file.
|
|
39
44
|
|
|
45
|
+
[`docs/obstat-spec.md`](docs/obstat-spec.md) is normative, and its §8 lists what
|
|
46
|
+
is still weak.
|
|
47
|
+
|
|
40
48
|
## 60 seconds
|
|
41
49
|
|
|
42
50
|
`obstat.toml`:
|
|
@@ -87,7 +95,7 @@ the whole story.
|
|
|
87
95
|
## What it is not
|
|
88
96
|
|
|
89
97
|
There are several good libraries that gate MCP tool calls. This one is built
|
|
90
|
-
around a narrower claim: **the record is the product.**
|
|
98
|
+
around a narrower claim: **the record is the product.** Four things follow from
|
|
91
99
|
that, and they are the reason to pick this over a permission wrapper.
|
|
92
100
|
|
|
93
101
|
**The decision is durable before the body runs.** Not flushed after, not written
|
|
@@ -117,6 +125,19 @@ q3-report" cannot be spent on deleting something else, and cannot be spent twice
|
|
|
117
125
|
This is enforced in one `BEGIN IMMEDIATE` transaction, so two concurrent retries
|
|
118
126
|
cannot both win.
|
|
119
127
|
|
|
128
|
+
**The record says when it has been edited.** Every record carries the hash of the
|
|
129
|
+
one before it, and `obstat verify` recomputes the chain:
|
|
130
|
+
|
|
131
|
+
```console
|
|
132
|
+
$ obstat verify
|
|
133
|
+
line 3: record cd53f9db… follows a record that is no longer in the log
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
An edited line and a deleted line both show up. A truncated tail does not, and
|
|
137
|
+
anyone who can write the file can recompute the whole chain — this is
|
|
138
|
+
tamper-evidence, not non-repudiation, and
|
|
139
|
+
[§8](docs/obstat-spec.md#8-still-open) says so in those words.
|
|
140
|
+
|
|
120
141
|
## Identity is optional
|
|
121
142
|
|
|
122
143
|
Most MCP servers today have no token at all: stdio, one local user, or a gateway
|
|
@@ -180,6 +201,7 @@ obstat pending # approvals waiting on a human
|
|
|
180
201
|
obstat approve <id> [--by] # decide
|
|
181
202
|
obstat deny <id> [--by]
|
|
182
203
|
obstat log -n 50 # the decision record
|
|
204
|
+
obstat verify # recompute the chain; exit 1 if anything was edited
|
|
183
205
|
obstat stop # deny every guarded call
|
|
184
206
|
obstat resume
|
|
185
207
|
```
|
|
@@ -213,4 +235,4 @@ you cannot try.
|
|
|
213
235
|
|
|
214
236
|
## License
|
|
215
237
|
|
|
216
|
-
Apache-2.0.
|
|
238
|
+
Apache-2.0. Copyright 2026 Marcin Marzęta.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# obstat
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/obstat/)
|
|
4
|
+
[](https://pypi.org/project/obstat/)
|
|
5
|
+
[](https://github.com/marcinmarzeta/obstat/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
3
8
|
**An auditable decision record for agent tool calls.** The clearance is written
|
|
4
9
|
down before the call runs — not reconstructed from logs afterwards.
|
|
5
10
|
|
|
@@ -16,6 +21,9 @@ pip install obstat
|
|
|
16
21
|
No dependencies. Not AWS, not an identity provider, not a policy service — the
|
|
17
22
|
decorator, `tomllib`, `sqlite3` and a file.
|
|
18
23
|
|
|
24
|
+
[`docs/obstat-spec.md`](docs/obstat-spec.md) is normative, and its §8 lists what
|
|
25
|
+
is still weak.
|
|
26
|
+
|
|
19
27
|
## 60 seconds
|
|
20
28
|
|
|
21
29
|
`obstat.toml`:
|
|
@@ -66,7 +74,7 @@ the whole story.
|
|
|
66
74
|
## What it is not
|
|
67
75
|
|
|
68
76
|
There are several good libraries that gate MCP tool calls. This one is built
|
|
69
|
-
around a narrower claim: **the record is the product.**
|
|
77
|
+
around a narrower claim: **the record is the product.** Four things follow from
|
|
70
78
|
that, and they are the reason to pick this over a permission wrapper.
|
|
71
79
|
|
|
72
80
|
**The decision is durable before the body runs.** Not flushed after, not written
|
|
@@ -96,6 +104,19 @@ q3-report" cannot be spent on deleting something else, and cannot be spent twice
|
|
|
96
104
|
This is enforced in one `BEGIN IMMEDIATE` transaction, so two concurrent retries
|
|
97
105
|
cannot both win.
|
|
98
106
|
|
|
107
|
+
**The record says when it has been edited.** Every record carries the hash of the
|
|
108
|
+
one before it, and `obstat verify` recomputes the chain:
|
|
109
|
+
|
|
110
|
+
```console
|
|
111
|
+
$ obstat verify
|
|
112
|
+
line 3: record cd53f9db… follows a record that is no longer in the log
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
An edited line and a deleted line both show up. A truncated tail does not, and
|
|
116
|
+
anyone who can write the file can recompute the whole chain — this is
|
|
117
|
+
tamper-evidence, not non-repudiation, and
|
|
118
|
+
[§8](docs/obstat-spec.md#8-still-open) says so in those words.
|
|
119
|
+
|
|
99
120
|
## Identity is optional
|
|
100
121
|
|
|
101
122
|
Most MCP servers today have no token at all: stdio, one local user, or a gateway
|
|
@@ -159,6 +180,7 @@ obstat pending # approvals waiting on a human
|
|
|
159
180
|
obstat approve <id> [--by] # decide
|
|
160
181
|
obstat deny <id> [--by]
|
|
161
182
|
obstat log -n 50 # the decision record
|
|
183
|
+
obstat verify # recompute the chain; exit 1 if anything was edited
|
|
162
184
|
obstat stop # deny every guarded call
|
|
163
185
|
obstat resume
|
|
164
186
|
```
|
|
@@ -192,4 +214,4 @@ you cannot try.
|
|
|
192
214
|
|
|
193
215
|
## License
|
|
194
216
|
|
|
195
|
-
Apache-2.0.
|
|
217
|
+
Apache-2.0. Copyright 2026 Marcin Marzęta.
|