pywrit 0.2.3__tar.gz → 0.2.4__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.
- pywrit-0.2.4/PKG-INFO +196 -0
- pywrit-0.2.4/README.md +182 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/pyproject.toml +1 -1
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit/__init__.py +1 -1
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit/cli.py +2 -2
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit/client.py +1 -1
- pywrit-0.2.4/src/pywrit.egg-info/PKG-INFO +196 -0
- pywrit-0.2.3/PKG-INFO +0 -97
- pywrit-0.2.3/README.md +0 -83
- pywrit-0.2.3/src/pywrit.egg-info/PKG-INFO +0 -97
- {pywrit-0.2.3 → pywrit-0.2.4}/LICENSE +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/setup.cfg +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit/errors.py +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit.egg-info/SOURCES.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit.egg-info/dependency_links.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit.egg-info/entry_points.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit.egg-info/requires.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/src/pywrit.egg-info/top_level.txt +0 -0
- {pywrit-0.2.3 → pywrit-0.2.4}/tests/test_client.py +0 -0
pywrit-0.2.4/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pywrit
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Python client + CLI for Writ — the gate before the write
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/AvenueDAdmin/pywrit
|
|
7
|
+
Project-URL: Documentation, https://withwrit.com/docs
|
|
8
|
+
Keywords: ai-agents,policy,audit-log,guardrails
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# pywrit
|
|
16
|
+
|
|
17
|
+
**Find the dangerous writes in your Python agent's code, then gate it.** `pywrit` is the Python
|
|
18
|
+
client and `writ` CLI for [Writ](https://withwrit.com): an allow/deny gate that sits
|
|
19
|
+
in front of your agent's consequential writes (database, HTTP, files, email, queues,
|
|
20
|
+
AWS) and records a hash-chained receipt for every decision.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
`writ scan` is **local, deterministic, and free**: it parses your code with Python's
|
|
25
|
+
`ast` module, makes no network calls, and needs no API key.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install pywrit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Python 3.9+. Installs the `writ` command and the `pywrit` Python client.
|
|
34
|
+
|
|
35
|
+
## 60-second quickstart: scan → apply → gate
|
|
36
|
+
|
|
37
|
+
**1. Scan.** See which functions write, and how many of them are gated.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
writ scan .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
writ scan: /path/to/support-agent
|
|
45
|
+
files scanned: 4 skipped: 0
|
|
46
|
+
write sites: 4 in 3 function(s)
|
|
47
|
+
gated: 0/4 (0%)
|
|
48
|
+
verbs discovered: 3
|
|
49
|
+
crm.update
|
|
50
|
+
payments.refund
|
|
51
|
+
tickets.close
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
It also prints a **risk report** (0-100, weighted by risk tier), writes the discovered
|
|
55
|
+
verbs to `writ-policy.json`, and shows the instrumentation it would add as a unified
|
|
56
|
+
diff. Nothing in your code changes yet. `writ scan . --score` prints just the risk report.
|
|
57
|
+
|
|
58
|
+
**2. Apply.** Insert a gate at the top of each writing function.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
writ scan . --apply # shows the diff, then asks before writing
|
|
62
|
+
writ scan . --apply --yes # no prompt (e.g. in CI)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each gated function now asks Writ before it writes, and fails closed:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
def issue_refund(charge_id, amount_cents):
|
|
69
|
+
if _writ_check("payments.refund") != "ALLOW":
|
|
70
|
+
raise PermissionError("writ denied payments.refund")
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Re-run `writ scan .` and you'll see `gated: 4/4 (100%)`.
|
|
75
|
+
|
|
76
|
+
**3. Gate.** Get a free API key, load the discovered policy, and run your agent.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
writ key --email you@example.com # free API key + tenant
|
|
80
|
+
export WRIT_API_KEY=writ_... # read by the inserted gate
|
|
81
|
+
export WRIT_SPONSOR=acme WRIT_AGENT=support-agent # optional: who is acting
|
|
82
|
+
writ scan . --push-policy --key "$WRIT_API_KEY" # upload the discovered verb policy
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Every gated write now gets `ALLOW`, `DENY`, or `STEP_UP` (a human sponsor must
|
|
86
|
+
approve), and every decision is written to your tenant's tamper-evident audit log:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
writ receipts --key "$WRIT_API_KEY" # latest receipts
|
|
90
|
+
writ verify-chain --key "$WRIT_API_KEY" # verify the receipt hash chain
|
|
91
|
+
writ stream --key "$WRIT_API_KEY" # tail decisions live
|
|
92
|
+
writ report --key "$WRIT_API_KEY" # Agent Action Report
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## What `writ scan` detects
|
|
96
|
+
|
|
97
|
+
Python (`.py`) files, parsed with the stdlib `ast` (no extra dependencies):
|
|
98
|
+
|
|
99
|
+
| Category | Examples |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Database | `cursor.execute(...)` / `executemany` / `executescript` (write SQL only; `SELECT`/`WITH`/... skipped), `session.add` / `commit` / `delete` / `merge` / `flush` |
|
|
102
|
+
| HTTP | `requests.post` / `put` / `patch`, `client.delete(...)`, `session.request(...)` |
|
|
103
|
+
| Files | `open(..., "w"/"a"/"x"/"+")`, `Path.write_text` / `write_bytes` / `unlink` / `rename`, `os.remove` / `rename` / `makedirs`, `shutil.rmtree` / `move` / `copy` |
|
|
104
|
+
| Email | `sendmail`, `send_message`, `send_email` |
|
|
105
|
+
| Queues | `publish`, `produce`, `enqueue`, `queue.send(...)` |
|
|
106
|
+
| AWS SDK | `put_object`, `put_item`, `delete_item`, `upload_file`, `send_message`, `start_execution`, ... |
|
|
107
|
+
|
|
108
|
+
- Each write is mapped to a verb such as `payments.refund` or `crm.update`, inferred
|
|
109
|
+
from the file path and function name.
|
|
110
|
+
- A function that already calls `writ_check(...)` / `_writ_check(...)` counts as gated.
|
|
111
|
+
- Skipped: tests, hidden directories, virtualenvs, `node_modules`, `dist`, `build`.
|
|
112
|
+
Use `--exclude SUBSTR` (repeatable) to skip more.
|
|
113
|
+
- Not covered (review by hand): writes behind dynamically built SQL, third-party SDK
|
|
114
|
+
calls such as `stripe.Refund.create(...)`, deferred task queues, or shared clients
|
|
115
|
+
several layers down. The risk report lists these gaps every time.
|
|
116
|
+
|
|
117
|
+
### TypeScript / JavaScript: coming soon (not yet released)
|
|
118
|
+
|
|
119
|
+
> **Not available in `pywrit` 0.2.2 or on PyPI yet.** The current release scans
|
|
120
|
+
> Python only. The preview below shows scan-only TS/JS support that is still in
|
|
121
|
+
> development; the command it uses is not available yet.
|
|
122
|
+
|
|
123
|
+

|
|
124
|
+
|
|
125
|
+
## Python client
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from pywrit import WritClient
|
|
129
|
+
|
|
130
|
+
client = WritClient(api_key="writ_...")
|
|
131
|
+
|
|
132
|
+
result = client.check(
|
|
133
|
+
sponsor_id="acme",
|
|
134
|
+
agent_id="agent-7",
|
|
135
|
+
verb="db.write",
|
|
136
|
+
target="prod.customers",
|
|
137
|
+
purpose="backfill region field",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if result.decision == "ALLOW":
|
|
141
|
+
# result.auth_token is a short-lived token bound to this exact write
|
|
142
|
+
perform_write(...)
|
|
143
|
+
elif result.decision == "STEP_UP":
|
|
144
|
+
# a human sponsor must approve first: client.grant(...), then re-check
|
|
145
|
+
...
|
|
146
|
+
else:
|
|
147
|
+
# DENY
|
|
148
|
+
...
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
No API key yet? Try the keyless sandbox:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
client = WritClient()
|
|
155
|
+
client.sandbox({
|
|
156
|
+
"sponsorId": "acme",
|
|
157
|
+
"agentId": "agent-7",
|
|
158
|
+
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
159
|
+
"target": "demo-customers", # ... on targets starting with demo-
|
|
160
|
+
"purpose": "trying the gate",
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
What's covered:
|
|
165
|
+
|
|
166
|
+
- `check(...)`: the gate. `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
167
|
+
- `verify_token(...)`: validate an `ALLOW` auth token (catches purpose drift)
|
|
168
|
+
- `grant(...)`: human-sponsor approval for the `STEP_UP` path
|
|
169
|
+
- `get_policy()` / `set_policy(...)`: manage the tenant policy
|
|
170
|
+
- `revoke(...)` / `reinstate(...)` / `revoked()`: the kill switch
|
|
171
|
+
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()`: the audit log
|
|
172
|
+
- `sandbox(...)`: keyless trial, no API key required
|
|
173
|
+
|
|
174
|
+
## CLI reference
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
178
|
+
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
179
|
+
writ policy --key writ_... --set payments.refund require_grant
|
|
180
|
+
writ revoke --sponsor acme --agent agent-7 --reason "runaway loop" # kill switch (sponsor token)
|
|
181
|
+
writ grant --sponsor acme --agent agent-7 --verb payments.refund \
|
|
182
|
+
--target ch_123 --purpose "approved refund" # STEP_UP approval (sponsor token)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Sponsor-token commands (`revoke`, `reinstate`, `revoked`, `grant`) read
|
|
186
|
+
`--sponsor-token` or `WRIT_SPONSOR_TOKEN`. Run `writ --help` for the full command list.
|
|
187
|
+
|
|
188
|
+
## Docs
|
|
189
|
+
|
|
190
|
+
Full docs: [docs.withwrit.com](https://docs.withwrit.com) ·
|
|
191
|
+
Quickstart: [docs.withwrit.com/quickstart](https://docs.withwrit.com/quickstart) ·
|
|
192
|
+
Site: [withwrit.com](https://withwrit.com)
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
pywrit-0.2.4/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# pywrit
|
|
2
|
+
|
|
3
|
+
**Find the dangerous writes in your Python agent's code, then gate it.** `pywrit` is the Python
|
|
4
|
+
client and `writ` CLI for [Writ](https://withwrit.com): an allow/deny gate that sits
|
|
5
|
+
in front of your agent's consequential writes (database, HTTP, files, email, queues,
|
|
6
|
+
AWS) and records a hash-chained receipt for every decision.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
`writ scan` is **local, deterministic, and free**: it parses your code with Python's
|
|
11
|
+
`ast` module, makes no network calls, and needs no API key.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install pywrit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Python 3.9+. Installs the `writ` command and the `pywrit` Python client.
|
|
20
|
+
|
|
21
|
+
## 60-second quickstart: scan → apply → gate
|
|
22
|
+
|
|
23
|
+
**1. Scan.** See which functions write, and how many of them are gated.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
writ scan .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
writ scan: /path/to/support-agent
|
|
31
|
+
files scanned: 4 skipped: 0
|
|
32
|
+
write sites: 4 in 3 function(s)
|
|
33
|
+
gated: 0/4 (0%)
|
|
34
|
+
verbs discovered: 3
|
|
35
|
+
crm.update
|
|
36
|
+
payments.refund
|
|
37
|
+
tickets.close
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
It also prints a **risk report** (0-100, weighted by risk tier), writes the discovered
|
|
41
|
+
verbs to `writ-policy.json`, and shows the instrumentation it would add as a unified
|
|
42
|
+
diff. Nothing in your code changes yet. `writ scan . --score` prints just the risk report.
|
|
43
|
+
|
|
44
|
+
**2. Apply.** Insert a gate at the top of each writing function.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
writ scan . --apply # shows the diff, then asks before writing
|
|
48
|
+
writ scan . --apply --yes # no prompt (e.g. in CI)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Each gated function now asks Writ before it writes, and fails closed:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
def issue_refund(charge_id, amount_cents):
|
|
55
|
+
if _writ_check("payments.refund") != "ALLOW":
|
|
56
|
+
raise PermissionError("writ denied payments.refund")
|
|
57
|
+
...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Re-run `writ scan .` and you'll see `gated: 4/4 (100%)`.
|
|
61
|
+
|
|
62
|
+
**3. Gate.** Get a free API key, load the discovered policy, and run your agent.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
writ key --email you@example.com # free API key + tenant
|
|
66
|
+
export WRIT_API_KEY=writ_... # read by the inserted gate
|
|
67
|
+
export WRIT_SPONSOR=acme WRIT_AGENT=support-agent # optional: who is acting
|
|
68
|
+
writ scan . --push-policy --key "$WRIT_API_KEY" # upload the discovered verb policy
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every gated write now gets `ALLOW`, `DENY`, or `STEP_UP` (a human sponsor must
|
|
72
|
+
approve), and every decision is written to your tenant's tamper-evident audit log:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
writ receipts --key "$WRIT_API_KEY" # latest receipts
|
|
76
|
+
writ verify-chain --key "$WRIT_API_KEY" # verify the receipt hash chain
|
|
77
|
+
writ stream --key "$WRIT_API_KEY" # tail decisions live
|
|
78
|
+
writ report --key "$WRIT_API_KEY" # Agent Action Report
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## What `writ scan` detects
|
|
82
|
+
|
|
83
|
+
Python (`.py`) files, parsed with the stdlib `ast` (no extra dependencies):
|
|
84
|
+
|
|
85
|
+
| Category | Examples |
|
|
86
|
+
|---|---|
|
|
87
|
+
| Database | `cursor.execute(...)` / `executemany` / `executescript` (write SQL only; `SELECT`/`WITH`/... skipped), `session.add` / `commit` / `delete` / `merge` / `flush` |
|
|
88
|
+
| HTTP | `requests.post` / `put` / `patch`, `client.delete(...)`, `session.request(...)` |
|
|
89
|
+
| Files | `open(..., "w"/"a"/"x"/"+")`, `Path.write_text` / `write_bytes` / `unlink` / `rename`, `os.remove` / `rename` / `makedirs`, `shutil.rmtree` / `move` / `copy` |
|
|
90
|
+
| Email | `sendmail`, `send_message`, `send_email` |
|
|
91
|
+
| Queues | `publish`, `produce`, `enqueue`, `queue.send(...)` |
|
|
92
|
+
| AWS SDK | `put_object`, `put_item`, `delete_item`, `upload_file`, `send_message`, `start_execution`, ... |
|
|
93
|
+
|
|
94
|
+
- Each write is mapped to a verb such as `payments.refund` or `crm.update`, inferred
|
|
95
|
+
from the file path and function name.
|
|
96
|
+
- A function that already calls `writ_check(...)` / `_writ_check(...)` counts as gated.
|
|
97
|
+
- Skipped: tests, hidden directories, virtualenvs, `node_modules`, `dist`, `build`.
|
|
98
|
+
Use `--exclude SUBSTR` (repeatable) to skip more.
|
|
99
|
+
- Not covered (review by hand): writes behind dynamically built SQL, third-party SDK
|
|
100
|
+
calls such as `stripe.Refund.create(...)`, deferred task queues, or shared clients
|
|
101
|
+
several layers down. The risk report lists these gaps every time.
|
|
102
|
+
|
|
103
|
+
### TypeScript / JavaScript: coming soon (not yet released)
|
|
104
|
+
|
|
105
|
+
> **Not available in `pywrit` 0.2.2 or on PyPI yet.** The current release scans
|
|
106
|
+
> Python only. The preview below shows scan-only TS/JS support that is still in
|
|
107
|
+
> development; the command it uses is not available yet.
|
|
108
|
+
|
|
109
|
+

|
|
110
|
+
|
|
111
|
+
## Python client
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from pywrit import WritClient
|
|
115
|
+
|
|
116
|
+
client = WritClient(api_key="writ_...")
|
|
117
|
+
|
|
118
|
+
result = client.check(
|
|
119
|
+
sponsor_id="acme",
|
|
120
|
+
agent_id="agent-7",
|
|
121
|
+
verb="db.write",
|
|
122
|
+
target="prod.customers",
|
|
123
|
+
purpose="backfill region field",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if result.decision == "ALLOW":
|
|
127
|
+
# result.auth_token is a short-lived token bound to this exact write
|
|
128
|
+
perform_write(...)
|
|
129
|
+
elif result.decision == "STEP_UP":
|
|
130
|
+
# a human sponsor must approve first: client.grant(...), then re-check
|
|
131
|
+
...
|
|
132
|
+
else:
|
|
133
|
+
# DENY
|
|
134
|
+
...
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
No API key yet? Try the keyless sandbox:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
client = WritClient()
|
|
141
|
+
client.sandbox({
|
|
142
|
+
"sponsorId": "acme",
|
|
143
|
+
"agentId": "agent-7",
|
|
144
|
+
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
145
|
+
"target": "demo-customers", # ... on targets starting with demo-
|
|
146
|
+
"purpose": "trying the gate",
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
What's covered:
|
|
151
|
+
|
|
152
|
+
- `check(...)`: the gate. `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
153
|
+
- `verify_token(...)`: validate an `ALLOW` auth token (catches purpose drift)
|
|
154
|
+
- `grant(...)`: human-sponsor approval for the `STEP_UP` path
|
|
155
|
+
- `get_policy()` / `set_policy(...)`: manage the tenant policy
|
|
156
|
+
- `revoke(...)` / `reinstate(...)` / `revoked()`: the kill switch
|
|
157
|
+
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()`: the audit log
|
|
158
|
+
- `sandbox(...)`: keyless trial, no API key required
|
|
159
|
+
|
|
160
|
+
## CLI reference
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
164
|
+
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
165
|
+
writ policy --key writ_... --set payments.refund require_grant
|
|
166
|
+
writ revoke --sponsor acme --agent agent-7 --reason "runaway loop" # kill switch (sponsor token)
|
|
167
|
+
writ grant --sponsor acme --agent agent-7 --verb payments.refund \
|
|
168
|
+
--target ch_123 --purpose "approved refund" # STEP_UP approval (sponsor token)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Sponsor-token commands (`revoke`, `reinstate`, `revoked`, `grant`) read
|
|
172
|
+
`--sponsor-token` or `WRIT_SPONSOR_TOKEN`. Run `writ --help` for the full command list.
|
|
173
|
+
|
|
174
|
+
## Docs
|
|
175
|
+
|
|
176
|
+
Full docs: [docs.withwrit.com](https://docs.withwrit.com) ·
|
|
177
|
+
Quickstart: [docs.withwrit.com/quickstart](https://docs.withwrit.com/quickstart) ·
|
|
178
|
+
Site: [withwrit.com](https://withwrit.com)
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
|
@@ -13,7 +13,7 @@ from datetime import datetime, timezone
|
|
|
13
13
|
|
|
14
14
|
BASE = os.environ.get(
|
|
15
15
|
"WRIT_BASE",
|
|
16
|
-
"https://
|
|
16
|
+
"https://api.withwrit.com",
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
@@ -99,7 +99,7 @@ import urllib.request as _writ_urllib
|
|
|
99
99
|
|
|
100
100
|
_WRIT_BASE = _writ_os.environ.get(
|
|
101
101
|
"WRIT_BASE",
|
|
102
|
-
"https://
|
|
102
|
+
"https://api.withwrit.com",
|
|
103
103
|
)
|
|
104
104
|
_WRIT_KEY = _writ_os.environ.get("WRIT_API_KEY", "")
|
|
105
105
|
_WRIT_SPONSOR = _writ_os.environ.get("WRIT_SPONSOR", "app")
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pywrit
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Python client + CLI for Writ — the gate before the write
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/AvenueDAdmin/pywrit
|
|
7
|
+
Project-URL: Documentation, https://withwrit.com/docs
|
|
8
|
+
Keywords: ai-agents,policy,audit-log,guardrails
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# pywrit
|
|
16
|
+
|
|
17
|
+
**Find the dangerous writes in your Python agent's code, then gate it.** `pywrit` is the Python
|
|
18
|
+
client and `writ` CLI for [Writ](https://withwrit.com): an allow/deny gate that sits
|
|
19
|
+
in front of your agent's consequential writes (database, HTTP, files, email, queues,
|
|
20
|
+
AWS) and records a hash-chained receipt for every decision.
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
`writ scan` is **local, deterministic, and free**: it parses your code with Python's
|
|
25
|
+
`ast` module, makes no network calls, and needs no API key.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install pywrit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Python 3.9+. Installs the `writ` command and the `pywrit` Python client.
|
|
34
|
+
|
|
35
|
+
## 60-second quickstart: scan → apply → gate
|
|
36
|
+
|
|
37
|
+
**1. Scan.** See which functions write, and how many of them are gated.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
writ scan .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
writ scan: /path/to/support-agent
|
|
45
|
+
files scanned: 4 skipped: 0
|
|
46
|
+
write sites: 4 in 3 function(s)
|
|
47
|
+
gated: 0/4 (0%)
|
|
48
|
+
verbs discovered: 3
|
|
49
|
+
crm.update
|
|
50
|
+
payments.refund
|
|
51
|
+
tickets.close
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
It also prints a **risk report** (0-100, weighted by risk tier), writes the discovered
|
|
55
|
+
verbs to `writ-policy.json`, and shows the instrumentation it would add as a unified
|
|
56
|
+
diff. Nothing in your code changes yet. `writ scan . --score` prints just the risk report.
|
|
57
|
+
|
|
58
|
+
**2. Apply.** Insert a gate at the top of each writing function.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
writ scan . --apply # shows the diff, then asks before writing
|
|
62
|
+
writ scan . --apply --yes # no prompt (e.g. in CI)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each gated function now asks Writ before it writes, and fails closed:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
def issue_refund(charge_id, amount_cents):
|
|
69
|
+
if _writ_check("payments.refund") != "ALLOW":
|
|
70
|
+
raise PermissionError("writ denied payments.refund")
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Re-run `writ scan .` and you'll see `gated: 4/4 (100%)`.
|
|
75
|
+
|
|
76
|
+
**3. Gate.** Get a free API key, load the discovered policy, and run your agent.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
writ key --email you@example.com # free API key + tenant
|
|
80
|
+
export WRIT_API_KEY=writ_... # read by the inserted gate
|
|
81
|
+
export WRIT_SPONSOR=acme WRIT_AGENT=support-agent # optional: who is acting
|
|
82
|
+
writ scan . --push-policy --key "$WRIT_API_KEY" # upload the discovered verb policy
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Every gated write now gets `ALLOW`, `DENY`, or `STEP_UP` (a human sponsor must
|
|
86
|
+
approve), and every decision is written to your tenant's tamper-evident audit log:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
writ receipts --key "$WRIT_API_KEY" # latest receipts
|
|
90
|
+
writ verify-chain --key "$WRIT_API_KEY" # verify the receipt hash chain
|
|
91
|
+
writ stream --key "$WRIT_API_KEY" # tail decisions live
|
|
92
|
+
writ report --key "$WRIT_API_KEY" # Agent Action Report
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## What `writ scan` detects
|
|
96
|
+
|
|
97
|
+
Python (`.py`) files, parsed with the stdlib `ast` (no extra dependencies):
|
|
98
|
+
|
|
99
|
+
| Category | Examples |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Database | `cursor.execute(...)` / `executemany` / `executescript` (write SQL only; `SELECT`/`WITH`/... skipped), `session.add` / `commit` / `delete` / `merge` / `flush` |
|
|
102
|
+
| HTTP | `requests.post` / `put` / `patch`, `client.delete(...)`, `session.request(...)` |
|
|
103
|
+
| Files | `open(..., "w"/"a"/"x"/"+")`, `Path.write_text` / `write_bytes` / `unlink` / `rename`, `os.remove` / `rename` / `makedirs`, `shutil.rmtree` / `move` / `copy` |
|
|
104
|
+
| Email | `sendmail`, `send_message`, `send_email` |
|
|
105
|
+
| Queues | `publish`, `produce`, `enqueue`, `queue.send(...)` |
|
|
106
|
+
| AWS SDK | `put_object`, `put_item`, `delete_item`, `upload_file`, `send_message`, `start_execution`, ... |
|
|
107
|
+
|
|
108
|
+
- Each write is mapped to a verb such as `payments.refund` or `crm.update`, inferred
|
|
109
|
+
from the file path and function name.
|
|
110
|
+
- A function that already calls `writ_check(...)` / `_writ_check(...)` counts as gated.
|
|
111
|
+
- Skipped: tests, hidden directories, virtualenvs, `node_modules`, `dist`, `build`.
|
|
112
|
+
Use `--exclude SUBSTR` (repeatable) to skip more.
|
|
113
|
+
- Not covered (review by hand): writes behind dynamically built SQL, third-party SDK
|
|
114
|
+
calls such as `stripe.Refund.create(...)`, deferred task queues, or shared clients
|
|
115
|
+
several layers down. The risk report lists these gaps every time.
|
|
116
|
+
|
|
117
|
+
### TypeScript / JavaScript: coming soon (not yet released)
|
|
118
|
+
|
|
119
|
+
> **Not available in `pywrit` 0.2.2 or on PyPI yet.** The current release scans
|
|
120
|
+
> Python only. The preview below shows scan-only TS/JS support that is still in
|
|
121
|
+
> development; the command it uses is not available yet.
|
|
122
|
+
|
|
123
|
+

|
|
124
|
+
|
|
125
|
+
## Python client
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from pywrit import WritClient
|
|
129
|
+
|
|
130
|
+
client = WritClient(api_key="writ_...")
|
|
131
|
+
|
|
132
|
+
result = client.check(
|
|
133
|
+
sponsor_id="acme",
|
|
134
|
+
agent_id="agent-7",
|
|
135
|
+
verb="db.write",
|
|
136
|
+
target="prod.customers",
|
|
137
|
+
purpose="backfill region field",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if result.decision == "ALLOW":
|
|
141
|
+
# result.auth_token is a short-lived token bound to this exact write
|
|
142
|
+
perform_write(...)
|
|
143
|
+
elif result.decision == "STEP_UP":
|
|
144
|
+
# a human sponsor must approve first: client.grant(...), then re-check
|
|
145
|
+
...
|
|
146
|
+
else:
|
|
147
|
+
# DENY
|
|
148
|
+
...
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
No API key yet? Try the keyless sandbox:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
client = WritClient()
|
|
155
|
+
client.sandbox({
|
|
156
|
+
"sponsorId": "acme",
|
|
157
|
+
"agentId": "agent-7",
|
|
158
|
+
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
159
|
+
"target": "demo-customers", # ... on targets starting with demo-
|
|
160
|
+
"purpose": "trying the gate",
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
What's covered:
|
|
165
|
+
|
|
166
|
+
- `check(...)`: the gate. `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
167
|
+
- `verify_token(...)`: validate an `ALLOW` auth token (catches purpose drift)
|
|
168
|
+
- `grant(...)`: human-sponsor approval for the `STEP_UP` path
|
|
169
|
+
- `get_policy()` / `set_policy(...)`: manage the tenant policy
|
|
170
|
+
- `revoke(...)` / `reinstate(...)` / `revoked()`: the kill switch
|
|
171
|
+
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()`: the audit log
|
|
172
|
+
- `sandbox(...)`: keyless trial, no API key required
|
|
173
|
+
|
|
174
|
+
## CLI reference
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
178
|
+
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
179
|
+
writ policy --key writ_... --set payments.refund require_grant
|
|
180
|
+
writ revoke --sponsor acme --agent agent-7 --reason "runaway loop" # kill switch (sponsor token)
|
|
181
|
+
writ grant --sponsor acme --agent agent-7 --verb payments.refund \
|
|
182
|
+
--target ch_123 --purpose "approved refund" # STEP_UP approval (sponsor token)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Sponsor-token commands (`revoke`, `reinstate`, `revoked`, `grant`) read
|
|
186
|
+
`--sponsor-token` or `WRIT_SPONSOR_TOKEN`. Run `writ --help` for the full command list.
|
|
187
|
+
|
|
188
|
+
## Docs
|
|
189
|
+
|
|
190
|
+
Full docs: [docs.withwrit.com](https://docs.withwrit.com) ·
|
|
191
|
+
Quickstart: [docs.withwrit.com/quickstart](https://docs.withwrit.com/quickstart) ·
|
|
192
|
+
Site: [withwrit.com](https://withwrit.com)
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
pywrit-0.2.3/PKG-INFO
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pywrit
|
|
3
|
-
Version: 0.2.3
|
|
4
|
-
Summary: Python client + CLI for Writ — the gate before the write
|
|
5
|
-
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/AvenueDAdmin/pywrit
|
|
7
|
-
Project-URL: Documentation, https://withwrit.com/docs
|
|
8
|
-
Keywords: ai-agents,policy,audit-log,guardrails
|
|
9
|
-
Requires-Python: >=3.9
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: httpx>=0.27
|
|
13
|
-
Dynamic: license-file
|
|
14
|
-
|
|
15
|
-
# pywrit
|
|
16
|
-
|
|
17
|
-
Python client + CLI for [Writ](https://withwrit.com) — the gate before the write.
|
|
18
|
-
Put a policy gate in front of your agent's dangerous actions.
|
|
19
|
-
|
|
20
|
-
## Install
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
pip install pywrit
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Python client
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from pywrit import WritClient
|
|
30
|
-
|
|
31
|
-
client = WritClient(api_key="writ_...")
|
|
32
|
-
|
|
33
|
-
result = client.check(
|
|
34
|
-
sponsor_id="acme",
|
|
35
|
-
agent_id="agent-7",
|
|
36
|
-
verb="db.write",
|
|
37
|
-
target="prod.customers",
|
|
38
|
-
purpose="backfill region field",
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
if result.decision == "ALLOW":
|
|
42
|
-
# result.auth_token is a short-lived token bound to this exact write
|
|
43
|
-
perform_write(...)
|
|
44
|
-
elif result.decision == "STEP_UP":
|
|
45
|
-
# a human sponsor must approve first: client.grant(...), then re-check
|
|
46
|
-
...
|
|
47
|
-
else:
|
|
48
|
-
# DENY
|
|
49
|
-
...
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
No API key yet? Try the keyless sandbox:
|
|
53
|
-
|
|
54
|
-
```python
|
|
55
|
-
client = WritClient()
|
|
56
|
-
client.sandbox({
|
|
57
|
-
"sponsorId": "acme",
|
|
58
|
-
"agentId": "agent-7",
|
|
59
|
-
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
60
|
-
"target": "demo-customers", # ... on targets starting with demo-
|
|
61
|
-
"purpose": "trying the gate",
|
|
62
|
-
})
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### What's covered
|
|
66
|
-
|
|
67
|
-
- `check(...)` — the gate: `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
68
|
-
- `verify_token(...)` — validate an `ALLOW` auth token (catches purpose drift)
|
|
69
|
-
- `grant(...)` — human-sponsor approval for the `STEP_UP` path
|
|
70
|
-
- `get_policy()` / `set_policy(...)` — manage the tenant policy
|
|
71
|
-
- `revoke(...)` / `reinstate(...)` / `revoked()` — the kill switch
|
|
72
|
-
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()` — the audit log
|
|
73
|
-
- `sandbox(...)` — keyless trial, no API key required
|
|
74
|
-
|
|
75
|
-
Every decision writes a receipt, so the audit log is the meter.
|
|
76
|
-
|
|
77
|
-
## CLI
|
|
78
|
-
|
|
79
|
-
The same package ships the `writ` command:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
83
|
-
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
84
|
-
writ scan ./my-repo
|
|
85
|
-
writ receipts --key writ_...
|
|
86
|
-
writ verify-chain --key writ_...
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Run `writ --help` for the full command list.
|
|
90
|
-
|
|
91
|
-
## Docs
|
|
92
|
-
|
|
93
|
-
Full API reference: [withwrit.com/docs](https://withwrit.com/docs)
|
|
94
|
-
|
|
95
|
-
## License
|
|
96
|
-
|
|
97
|
-
MIT
|
pywrit-0.2.3/README.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# pywrit
|
|
2
|
-
|
|
3
|
-
Python client + CLI for [Writ](https://withwrit.com) — the gate before the write.
|
|
4
|
-
Put a policy gate in front of your agent's dangerous actions.
|
|
5
|
-
|
|
6
|
-
## Install
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
pip install pywrit
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Python client
|
|
13
|
-
|
|
14
|
-
```python
|
|
15
|
-
from pywrit import WritClient
|
|
16
|
-
|
|
17
|
-
client = WritClient(api_key="writ_...")
|
|
18
|
-
|
|
19
|
-
result = client.check(
|
|
20
|
-
sponsor_id="acme",
|
|
21
|
-
agent_id="agent-7",
|
|
22
|
-
verb="db.write",
|
|
23
|
-
target="prod.customers",
|
|
24
|
-
purpose="backfill region field",
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
if result.decision == "ALLOW":
|
|
28
|
-
# result.auth_token is a short-lived token bound to this exact write
|
|
29
|
-
perform_write(...)
|
|
30
|
-
elif result.decision == "STEP_UP":
|
|
31
|
-
# a human sponsor must approve first: client.grant(...), then re-check
|
|
32
|
-
...
|
|
33
|
-
else:
|
|
34
|
-
# DENY
|
|
35
|
-
...
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
No API key yet? Try the keyless sandbox:
|
|
39
|
-
|
|
40
|
-
```python
|
|
41
|
-
client = WritClient()
|
|
42
|
-
client.sandbox({
|
|
43
|
-
"sponsorId": "acme",
|
|
44
|
-
"agentId": "agent-7",
|
|
45
|
-
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
46
|
-
"target": "demo-customers", # ... on targets starting with demo-
|
|
47
|
-
"purpose": "trying the gate",
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### What's covered
|
|
52
|
-
|
|
53
|
-
- `check(...)` — the gate: `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
54
|
-
- `verify_token(...)` — validate an `ALLOW` auth token (catches purpose drift)
|
|
55
|
-
- `grant(...)` — human-sponsor approval for the `STEP_UP` path
|
|
56
|
-
- `get_policy()` / `set_policy(...)` — manage the tenant policy
|
|
57
|
-
- `revoke(...)` / `reinstate(...)` / `revoked()` — the kill switch
|
|
58
|
-
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()` — the audit log
|
|
59
|
-
- `sandbox(...)` — keyless trial, no API key required
|
|
60
|
-
|
|
61
|
-
Every decision writes a receipt, so the audit log is the meter.
|
|
62
|
-
|
|
63
|
-
## CLI
|
|
64
|
-
|
|
65
|
-
The same package ships the `writ` command:
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
69
|
-
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
70
|
-
writ scan ./my-repo
|
|
71
|
-
writ receipts --key writ_...
|
|
72
|
-
writ verify-chain --key writ_...
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Run `writ --help` for the full command list.
|
|
76
|
-
|
|
77
|
-
## Docs
|
|
78
|
-
|
|
79
|
-
Full API reference: [withwrit.com/docs](https://withwrit.com/docs)
|
|
80
|
-
|
|
81
|
-
## License
|
|
82
|
-
|
|
83
|
-
MIT
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pywrit
|
|
3
|
-
Version: 0.2.3
|
|
4
|
-
Summary: Python client + CLI for Writ — the gate before the write
|
|
5
|
-
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/AvenueDAdmin/pywrit
|
|
7
|
-
Project-URL: Documentation, https://withwrit.com/docs
|
|
8
|
-
Keywords: ai-agents,policy,audit-log,guardrails
|
|
9
|
-
Requires-Python: >=3.9
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: httpx>=0.27
|
|
13
|
-
Dynamic: license-file
|
|
14
|
-
|
|
15
|
-
# pywrit
|
|
16
|
-
|
|
17
|
-
Python client + CLI for [Writ](https://withwrit.com) — the gate before the write.
|
|
18
|
-
Put a policy gate in front of your agent's dangerous actions.
|
|
19
|
-
|
|
20
|
-
## Install
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
pip install pywrit
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Python client
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from pywrit import WritClient
|
|
30
|
-
|
|
31
|
-
client = WritClient(api_key="writ_...")
|
|
32
|
-
|
|
33
|
-
result = client.check(
|
|
34
|
-
sponsor_id="acme",
|
|
35
|
-
agent_id="agent-7",
|
|
36
|
-
verb="db.write",
|
|
37
|
-
target="prod.customers",
|
|
38
|
-
purpose="backfill region field",
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
if result.decision == "ALLOW":
|
|
42
|
-
# result.auth_token is a short-lived token bound to this exact write
|
|
43
|
-
perform_write(...)
|
|
44
|
-
elif result.decision == "STEP_UP":
|
|
45
|
-
# a human sponsor must approve first: client.grant(...), then re-check
|
|
46
|
-
...
|
|
47
|
-
else:
|
|
48
|
-
# DENY
|
|
49
|
-
...
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
No API key yet? Try the keyless sandbox:
|
|
53
|
-
|
|
54
|
-
```python
|
|
55
|
-
client = WritClient()
|
|
56
|
-
client.sandbox({
|
|
57
|
-
"sponsorId": "acme",
|
|
58
|
-
"agentId": "agent-7",
|
|
59
|
-
"verb": "demo_write", # sandbox only allows demo_write ...
|
|
60
|
-
"target": "demo-customers", # ... on targets starting with demo-
|
|
61
|
-
"purpose": "trying the gate",
|
|
62
|
-
})
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### What's covered
|
|
66
|
-
|
|
67
|
-
- `check(...)` — the gate: `ALLOW` / `DENY` / `STEP_UP`, plus a receipt every time
|
|
68
|
-
- `verify_token(...)` — validate an `ALLOW` auth token (catches purpose drift)
|
|
69
|
-
- `grant(...)` — human-sponsor approval for the `STEP_UP` path
|
|
70
|
-
- `get_policy()` / `set_policy(...)` — manage the tenant policy
|
|
71
|
-
- `revoke(...)` / `reinstate(...)` / `revoked()` — the kill switch
|
|
72
|
-
- `receipts()` / `receipt(id)` / `verify_chain()` / `stream_receipts()` — the audit log
|
|
73
|
-
- `sandbox(...)` — keyless trial, no API key required
|
|
74
|
-
|
|
75
|
-
Every decision writes a receipt, so the audit log is the meter.
|
|
76
|
-
|
|
77
|
-
## CLI
|
|
78
|
-
|
|
79
|
-
The same package ships the `writ` command:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
writ check --key writ_... --sponsor acme --agent agent-7 \
|
|
83
|
-
--verb db.write --target prod.customers --purpose "backfill region field"
|
|
84
|
-
writ scan ./my-repo
|
|
85
|
-
writ receipts --key writ_...
|
|
86
|
-
writ verify-chain --key writ_...
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Run `writ --help` for the full command list.
|
|
90
|
-
|
|
91
|
-
## Docs
|
|
92
|
-
|
|
93
|
-
Full API reference: [withwrit.com/docs](https://withwrit.com/docs)
|
|
94
|
-
|
|
95
|
-
## License
|
|
96
|
-
|
|
97
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|