gama-mandate 0.10.3__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.
- gama_mandate-0.10.3/AUTHORS +1 -0
- gama_mandate-0.10.3/CHANGELOG.md +124 -0
- gama_mandate-0.10.3/LICENSE +21 -0
- gama_mandate-0.10.3/MANIFEST.in +11 -0
- gama_mandate-0.10.3/PKG-INFO +238 -0
- gama_mandate-0.10.3/PLAN.md +41 -0
- gama_mandate-0.10.3/README.md +217 -0
- gama_mandate-0.10.3/docs/NON_GOALS.md +11 -0
- gama_mandate-0.10.3/docs/THREAT_MODEL.md +42 -0
- gama_mandate-0.10.3/examples/intent.json +13 -0
- gama_mandate-0.10.3/gama_mandate.egg-info/PKG-INFO +238 -0
- gama_mandate-0.10.3/gama_mandate.egg-info/SOURCES.txt +41 -0
- gama_mandate-0.10.3/gama_mandate.egg-info/dependency_links.txt +1 -0
- gama_mandate-0.10.3/gama_mandate.egg-info/entry_points.txt +4 -0
- gama_mandate-0.10.3/gama_mandate.egg-info/top_level.txt +1 -0
- gama_mandate-0.10.3/mandate/__init__.py +34 -0
- gama_mandate-0.10.3/mandate/__main__.py +4 -0
- gama_mandate-0.10.3/mandate/adapters/__init__.py +5 -0
- gama_mandate-0.10.3/mandate/adapters/erp.py +152 -0
- gama_mandate-0.10.3/mandate/adapters/smtp.py +56 -0
- gama_mandate-0.10.3/mandate/adapters/write.py +145 -0
- gama_mandate-0.10.3/mandate/broker.py +363 -0
- gama_mandate-0.10.3/mandate/cli.py +500 -0
- gama_mandate-0.10.3/mandate/core.py +470 -0
- gama_mandate-0.10.3/mandate/data/policy.json +14 -0
- gama_mandate-0.10.3/mandate/jcs.py +107 -0
- gama_mandate-0.10.3/mandate/journal.py +143 -0
- gama_mandate-0.10.3/mandate/log.py +238 -0
- gama_mandate-0.10.3/mandate/mcp_server.py +101 -0
- gama_mandate-0.10.3/mandate/py.typed +0 -0
- gama_mandate-0.10.3/mandate/sign.py +197 -0
- gama_mandate-0.10.3/mandate/witness.py +190 -0
- gama_mandate-0.10.3/policy.json +14 -0
- gama_mandate-0.10.3/pyproject.toml +46 -0
- gama_mandate-0.10.3/setup.cfg +4 -0
- gama_mandate-0.10.3/skills/mandate/SKILL.md +75 -0
- gama_mandate-0.10.3/spec/MANDATE-1.md +85 -0
- gama_mandate-0.10.3/tests/test_jcs.py +65 -0
- gama_mandate-0.10.3/tests/test_log.py +183 -0
- gama_mandate-0.10.3/tests/test_mandate.py +624 -0
- gama_mandate-0.10.3/tests/test_witness.py +161 -0
- gama_mandate-0.10.3/trust/allowed_signers +8 -0
- gama_mandate-0.10.3/trust/revoked_signers +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GAMA
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.10.3 — 2026-11-09
|
|
4
|
+
- PyPI distribution name is **`gama-mandate`**. `agent-mandate` was rejected by
|
|
5
|
+
PyPI as too similar to an existing project (`agentmandate`). Import package and
|
|
6
|
+
console scripts are unchanged (`mandate`, `mandate-broker`, `mandate-mcp`).
|
|
7
|
+
- Code identical to 0.10.2.
|
|
8
|
+
|
|
9
|
+
## 0.10.2 — 2026-11-09
|
|
10
|
+
- Published to GitHub: https://github.com/gamai-io/mandate (public, CI green on
|
|
11
|
+
Python 3.9–3.12). Release v0.10.1 with wheel, sdist, and checksums.
|
|
12
|
+
- Correct project URLs, README CI/license badges, install-from-git instructions.
|
|
13
|
+
|
|
14
|
+
## 0.10.1 — 2026-11-09
|
|
15
|
+
- Security: the broker requires a bearer token for non-loopback binds
|
|
16
|
+
(`--token` / `MANDATE_BROKER_TOKEN`); broker and witness reject request bodies
|
|
17
|
+
over 256 KiB (413).
|
|
18
|
+
- Lint: `ruff` config, clean `ruff check .`, CI lint + build/`twine check`.
|
|
19
|
+
- Packaging: `py.typed`, sdist/README install instructions, governance files
|
|
20
|
+
(`SECURITY.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `AUTHORS`,
|
|
21
|
+
`RELEASE.md`).
|
|
22
|
+
- PyPI name `agent-mandate` verified available (2026-11-09).
|
|
23
|
+
- 71 tests.
|
|
24
|
+
|
|
25
|
+
## 0.10.0 — 2026-11-09
|
|
26
|
+
- **Off-host STH witness**: pins the latest signed tree head, rejects rollback
|
|
27
|
+
(smaller tree), rejects split-view (same size, different root), and requires a
|
|
28
|
+
valid consistency proof for growth. State is written atomically (0600).
|
|
29
|
+
- CLI: `mandate witness accept | verify | show | serve | push`.
|
|
30
|
+
- Loopback HTTP witness with a bearer token (`/sth`, `/state`, `/health`);
|
|
31
|
+
`--allow-remote` required to bind elsewhere.
|
|
32
|
+
- 8 witness tests + 68 total.
|
|
33
|
+
|
|
34
|
+
## 0.9.0 — 2026-11-09
|
|
35
|
+
- **Merkle transparency log** over ledger entries (RFC 6962/9162 style):
|
|
36
|
+
`Leaf=SHA256(0x00||JCS(entry))`, `Node=SHA256(0x01||L||R)`, MTH, inclusion
|
|
37
|
+
proofs, consistency proofs.
|
|
38
|
+
- **Signed tree head** via `ssh-keygen -Y sign` in namespace `mandate-log`;
|
|
39
|
+
`log_id` is the log key SHA256 fingerprint.
|
|
40
|
+
- CLI: `mandate log head | sth-verify | proof | verify-inclusion | consistency |
|
|
41
|
+
verify-consistency`.
|
|
42
|
+
- Refuses to build a log over an invalid ledger hash chain.
|
|
43
|
+
- 9 Merkle tests + 60 total. Off-host **witness/anchor** is still required to
|
|
44
|
+
rule out split-view; tracked in PLAN.md.
|
|
45
|
+
|
|
46
|
+
## 0.8.0 — 2026-11-09
|
|
47
|
+
- **Effect journal** (SQLite) with a lease-based state machine:
|
|
48
|
+
`intent → executing → executed → receipted`, plus `failed` / `abandoned`.
|
|
49
|
+
- **Idempotent write adapter**: `mode=file` or `mode=sqlite` (a trusted SQL
|
|
50
|
+
statement runs in the same transaction as the idempotency insert). The
|
|
51
|
+
idempotency key is the **grant nonce**.
|
|
52
|
+
- The authorisation receipt is now appended **after** the effect commits,
|
|
53
|
+
fixing the old "ledger says executed but no effect happened" crash window.
|
|
54
|
+
- **Crash recovery on start**: `executing` with a committed effect → finish the
|
|
55
|
+
receipt; without an effect → abandoned; `executed` without a receipt →
|
|
56
|
+
receipted. Duplicate `execute` returns the same receipt/result instead of an
|
|
57
|
+
error.
|
|
58
|
+
- Config: `write`, `journal_db`, `lease_s`; `/health` reports journal counts.
|
|
59
|
+
- 43 tests (crash-after-effect recovery, crash-before-effect abandon, writer
|
|
60
|
+
failure, concurrent single effect, journal payload mismatch).
|
|
61
|
+
|
|
62
|
+
## 0.7.0 — 2026-11-09
|
|
63
|
+
- Canonicalization is now **RFC 8785 (JCS)**: UTF-16 code-unit key ordering,
|
|
64
|
+
ECMAScript number serialization, minimal string escaping, and strict rejection
|
|
65
|
+
of non-finite numbers, lone surrogates, non-string keys, and integers outside
|
|
66
|
+
the IEEE-754 safe range. Passes the RFC 8785 reference vector.
|
|
67
|
+
- **Breaking:** commitment/receipt hashes change (e.g. `1170.0` serializes as
|
|
68
|
+
`1170`). Regenerate grants and ledgers from earlier versions.
|
|
69
|
+
- CI: GitHub Actions test matrix on Python 3.9–3.12.
|
|
70
|
+
|
|
71
|
+
## 0.6.0 — 2026-11-09
|
|
72
|
+
- `allowed_signers` now requires an explicit `namespaces="mandate"` scope
|
|
73
|
+
(`namespaces="mandate-broker"` for broker result keys). A key without a scope
|
|
74
|
+
is rejected, so a grant key cannot sign broker receipts and vice versa.
|
|
75
|
+
- Validity windows (`valid-after` / `valid-before`) are enforced in Mandate's
|
|
76
|
+
own check and by OpenSSH.
|
|
77
|
+
- Key revocation: `mandate revoke --pub key.pub` writes the SHA256 fingerprint to
|
|
78
|
+
`trust/revoked_signers`; `redeem`, `verify`, and `verify-result` reject revoked
|
|
79
|
+
keys. Configurable via `--revoked` / `MANDATE_REVOKED`.
|
|
80
|
+
- `mandate signers` lists parsed principals, namespaces, validity, and fingerprints.
|
|
81
|
+
- 37 tests (namespace scope, validity window, revocation, revoke CLI).
|
|
82
|
+
|
|
83
|
+
## 0.5.0 — 2026-11-09
|
|
84
|
+
- Grant `audience`: a grant can be bound to one broker (`mandate grant --audience <broker>`).
|
|
85
|
+
The broker verifies `audience == broker_name` and rejects unbound grants by default
|
|
86
|
+
(`--allow-any-audience` to opt out). Closes cross-broker replay.
|
|
87
|
+
- Broker-side re-derivation: `erp.derive` re-reads counterparty/amount/currency from the
|
|
88
|
+
source of truth and rejects the effect if the agent's intent diverges
|
|
89
|
+
(`derivation_mismatch`), not just at the precondition amount check.
|
|
90
|
+
- 33 tests (audience mismatch, unbound grant, currency derivation mismatch).
|
|
91
|
+
|
|
92
|
+
## 0.4.0 — 2026-11-09
|
|
93
|
+
Security fixes from an independent multi-agent audit (Claude + subagent + web):
|
|
94
|
+
- `mandate grant` recomputes commitment/render_hash/proposal_id from the intent and rejects a mismatched proposal (approval-confusion fix).
|
|
95
|
+
- `redeem` enforces policy (unknown effects denied) and binds the grant to the agent (`agent` field), closing the actor-swap self-approval bypass.
|
|
96
|
+
- CLI/MCP redeem now performs the nonce check and append under one file lock; ledger append is fsync'd and anchored with `<ledger>.head`; `verify_chain` rejects empty or truncated ledgers.
|
|
97
|
+
- Intent schema validation: unknown effect/amount fields and non-finite amounts are rejected; `params` must be flat scalars.
|
|
98
|
+
- `render` quotes every value (no `|` injection) and includes all params.
|
|
99
|
+
- ERP: NaN/non-dict amounts rejected; only `bind_params` can fill query placeholders.
|
|
100
|
+
- `execute` refuses non-loopback brokers (unless `--allow-remote`) and does not follow redirects.
|
|
101
|
+
- MCP adapter moved into the package and shipped as `mandate-mcp`; `verify-result` validates the signed broker result entry.
|
|
102
|
+
- `doctor` fails when signers are missing; `max_uses` is honestly fixed at 1.
|
|
103
|
+
|
|
104
|
+
## 0.3.0 — 2026-11-09
|
|
105
|
+
- Read-only ERP precondition (SQLite `mode=ro`, named-param query, amount tolerance).
|
|
106
|
+
- SMTP adapter for `message.send` (`dry_run` default, `live` reads password from env).
|
|
107
|
+
- Broker `--config`, `/health` reports `smtp` and `erp`.
|
|
108
|
+
- Preconditions run before the nonce claim, so a failed check does not consume the grant.
|
|
109
|
+
- Packaging: `pyproject.toml`, console scripts `mandate` / `mandate-broker`,
|
|
110
|
+
`mandate init`, `mandate doctor`, `mandate version`.
|
|
111
|
+
|
|
112
|
+
## 0.2.0 — 2026-11-09
|
|
113
|
+
- Effect broker: `/execute` + `/health`, loopback by default.
|
|
114
|
+
- Atomic nonce claim (SQLite `UNIQUE`) prevents double execution.
|
|
115
|
+
- `render_hash` bound into commitment and grant (approver signs the rendered summary).
|
|
116
|
+
- Signed result entry chained after the authorisation receipt.
|
|
117
|
+
|
|
118
|
+
## 0.1.0 — 2026-11-09
|
|
119
|
+
- Canonical commitment (JCS), stable ids.
|
|
120
|
+
- Intent / Grant / Receipt objects.
|
|
121
|
+
- Off-host Ed25519 grant via `ssh-keygen -Y sign`.
|
|
122
|
+
- One-shot redeem, replay detection, hash-chained ledger, offline verification.
|
|
123
|
+
- Agent cannot self-approve; unknown effects denied.
|
|
124
|
+
- MCP adapter and agent skill.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GAMA
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include PLAN.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
include CHANGELOG.md
|
|
5
|
+
include policy.json
|
|
6
|
+
recursive-include spec *.md
|
|
7
|
+
recursive-include docs *.md
|
|
8
|
+
recursive-include skills *.md
|
|
9
|
+
recursive-include examples *.json
|
|
10
|
+
recursive-include trust *
|
|
11
|
+
recursive-include mandate/data *.json
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gama-mandate
|
|
3
|
+
Version: 0.10.3
|
|
4
|
+
Summary: Signed, verifiable business commitments for AI agents: canonical commitment, off-host Ed25519 grant, one-shot redemption, hash-chained receipts.
|
|
5
|
+
Author: GAMA
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/gamai-io/mandate
|
|
8
|
+
Project-URL: Repository, https://github.com/gamai-io/mandate
|
|
9
|
+
Project-URL: Issues, https://github.com/gamai-io/mandate/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/gamai-io/mandate/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ai-agents,trust,approval,commitment,ed25519,audit
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Classifier: Topic :: Office/Business
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
License-File: AUTHORS
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Mandate
|
|
23
|
+
|
|
24
|
+
[](https://github.com/gamai-io/mandate/actions/workflows/test.yml)
|
|
25
|
+
[](LICENSE)
|
|
26
|
+
|
|
27
|
+
**Signed, verifiable business commitments for AI agents.**
|
|
28
|
+
|
|
29
|
+
Most agent safety tools answer *"is this tool allowed?"*. Mandate answers the
|
|
30
|
+
question that actually matters: **"who ordered this exact payment, to this
|
|
31
|
+
supplier, for this amount?"** A tool permission is not a business mandate.
|
|
32
|
+
|
|
33
|
+
Every consequential effect becomes a canonical **Commitment** object. An agent
|
|
34
|
+
signs the intent; a human co-signs a **Grant** with an Ed25519 key; execution
|
|
35
|
+
produces a hash-chained **Receipt**. Anyone can verify the whole chain offline.
|
|
36
|
+
|
|
37
|
+
> Approval workflows are ephemeral events. A Mandate is a durable, portable,
|
|
38
|
+
> cryptographically bound artifact — a commit, not a log line.
|
|
39
|
+
|
|
40
|
+
## Why it is not another approval gate
|
|
41
|
+
|
|
42
|
+
| Approval gate | Mandate |
|
|
43
|
+
|---|---|
|
|
44
|
+
| Asks "may this tool run?" | Asks "is this exact effect authorised?" |
|
|
45
|
+
| Identity is a self-asserted field | Approver proves possession of an off-host key |
|
|
46
|
+
| Approval is a mutable DB row | Grant + receipt are signed, content-addressed |
|
|
47
|
+
| Logs what happened | Lets anyone prove what was authorised |
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
CLI + broker + MCP adapter. There is **no web UI** by design: Mandate is a
|
|
52
|
+
local trust primitive you embed in your own stack.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python3 -m pip install gama-mandate
|
|
56
|
+
# or from source / git
|
|
57
|
+
python3 -m pip install "git+https://github.com/gamai-io/mandate.git"
|
|
58
|
+
|
|
59
|
+
mandate doctor # checks python, ssh-keygen, policy
|
|
60
|
+
mandate init --dir ./my-deployment # policy, allowed_signers, config, example
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
export PYTHONPATH=$PWD
|
|
67
|
+
|
|
68
|
+
# 1. on the approver's device (never on the agent host)
|
|
69
|
+
ssh-keygen -t ed25519 -f ~/.ssh/mandate -C mandate
|
|
70
|
+
# append the PUBLIC key to trust/allowed_signers as: renato ssh-ed25519 AAAA... mandate
|
|
71
|
+
|
|
72
|
+
# 2. agent proposes
|
|
73
|
+
./bin/mandate propose --intent examples/intent.json --out /tmp/proposal.json
|
|
74
|
+
|
|
75
|
+
# 3. human grants (signs the commitment, bound to one broker + the agent)
|
|
76
|
+
./bin/mandate grant --proposal /tmp/proposal.json --approver renato \
|
|
77
|
+
--key ~/.ssh/mandate --audience local-broker --out /tmp/grant.json
|
|
78
|
+
|
|
79
|
+
# 4. agent redeems exactly once
|
|
80
|
+
./bin/mandate redeem --intent examples/intent.json --grant /tmp/grant.json \
|
|
81
|
+
--ledger ledger.jsonl
|
|
82
|
+
|
|
83
|
+
# 5. or let the effect broker execute it (credentials stay in the broker)
|
|
84
|
+
./bin/mandate-broker --port 8899 --signers trust/allowed_signers \
|
|
85
|
+
--ledger ledger.jsonl --nonce-db broker/used.sqlite &
|
|
86
|
+
./bin/mandate execute --broker http://127.0.0.1:8899 \
|
|
87
|
+
--intent examples/intent.json --grant /tmp/grant.json
|
|
88
|
+
|
|
89
|
+
# 5. anyone verifies offline
|
|
90
|
+
./bin/mandate verify --receipt ... --intent examples/intent.json --grant /tmp/grant.json
|
|
91
|
+
./bin/mandate verify-result --entry /tmp/result.json --broker-signers trust/broker_signers
|
|
92
|
+
./bin/mandate chain --ledger ledger.jsonl
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Exit codes for `check`: `0` allow · `10` ask (grant required) · `20` deny.
|
|
96
|
+
|
|
97
|
+
## Effect broker (Phase 2)
|
|
98
|
+
|
|
99
|
+
`bin/mandate-broker` is the only component that executes effects. It verifies
|
|
100
|
+
the grant, **atomically claims the nonce** (SQLite `UNIQUE`, so two concurrent
|
|
101
|
+
requests cannot both succeed), runs the effect handler, and appends a signed
|
|
102
|
+
result entry to the hash-chained ledger. The agent calls it over loopback; the
|
|
103
|
+
agent never holds the effect credentials.
|
|
104
|
+
|
|
105
|
+
The shipped handlers are a sandbox (they record the effect under `effects/` or
|
|
106
|
+
`outbox/`). Wiring real credentials (SQL write, SMTP, payment) is Phase 2
|
|
107
|
+
completion — see `PLAN.md`.
|
|
108
|
+
|
|
109
|
+
### Adapters
|
|
110
|
+
|
|
111
|
+
**ERP precondition (read-only).** Before a grant is consumed, the broker runs a
|
|
112
|
+
named-parameter SQLite query against master data. No row → `counterparty_not_found`;
|
|
113
|
+
amount outside `tolerance_pct` → `amount_mismatch`. The grant is **not**
|
|
114
|
+
consumed when a precondition fails, so a corrected request can retry safely.
|
|
115
|
+
|
|
116
|
+
**SMTP (`message.send`).** `mode=dry_run` writes an `.eml` to the outbox;
|
|
117
|
+
`mode=live` sends via SMTP. The password is read from the env var named in
|
|
118
|
+
`password_env` and never logged or returned.
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
"erp": {"db": "erp.sqlite", "checks": {"payment.send": {
|
|
122
|
+
"query": "SELECT invoice_total FROM vendors WHERE name = :target",
|
|
123
|
+
"amount_field": "invoice_total", "tolerance_pct": 0.5}}},
|
|
124
|
+
"smtp": {"mode": "dry_run", "host": "", "user": "",
|
|
125
|
+
"password_env": "MANDATE_SMTP_PASSWORD", "from": "noreply@gama.local"}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Key scoping and revocation
|
|
129
|
+
|
|
130
|
+
An `allowed_signers` line **must** declare its namespace scope:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
renato namespaces="mandate" ssh-ed25519 AAAAC3Nza... mandate
|
|
134
|
+
gama-broker namespaces="mandate-broker" ssh-ed25519 AAAAC3Nza... broker
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Optional validity: `valid-after="YYYYMMDD" valid-before="YYYYMMDD"`.
|
|
138
|
+
Revoke a key by fingerprint:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
mandate revoke --pub key.pub --reason "laptop lost"
|
|
142
|
+
mandate --signers trust/allowed_signers signers # inspect scopes + fingerprints
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Idempotent writes and recovery
|
|
146
|
+
|
|
147
|
+
The broker keeps a SQLite **effect journal** (`journal_db`). The flow is
|
|
148
|
+
`intent → executing → executed → receipted`; the authorisation receipt is
|
|
149
|
+
written only **after** the effect commits. On startup the broker recovers:
|
|
150
|
+
a committed effect without a receipt gets one; a claimed grant with no effect is
|
|
151
|
+
abandoned; a duplicate `execute` returns the original receipt and result.
|
|
152
|
+
|
|
153
|
+
`write.mode=sqlite` runs a trusted configured statement together with an
|
|
154
|
+
idempotency insert in **one transaction**, keyed by the grant nonce:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
"write": {"mode": "sqlite", "db": "erp.sqlite",
|
|
158
|
+
"statement": "INSERT INTO payments(nonce,vendor,amount) VALUES(:nonce,:target,:amount)"}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Transparency log
|
|
162
|
+
|
|
163
|
+
Every ledger entry is a leaf in an RFC 6962/9162-style Merkle tree
|
|
164
|
+
(`Leaf=SHA256(0x00||JCS(entry))`, `Node=SHA256(0x01||L||R)`). A signed tree
|
|
165
|
+
head (STH) fixes the root and size; inclusion and consistency proofs verify
|
|
166
|
+
offline:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
mandate log head --ledger ledger.jsonl --log-name log1 --log-key ~/.ssh/mandate-log --out sth.json
|
|
170
|
+
mandate --signers trust/allowed_signers log sth-verify --sth sth.json
|
|
171
|
+
mandate log proof --ledger ledger.jsonl --index 3 --out proof.json
|
|
172
|
+
mandate log verify-inclusion --entry entry.json --proof proof.json --sth sth.json
|
|
173
|
+
mandate log consistency --ledger ledger.jsonl --first 2 --second 3 --out cons.json
|
|
174
|
+
mandate log verify-consistency --old old_sth.json --new sth.json --proof cons.json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
A single log cannot prevent **split-view** by its operator; pin the STH on a
|
|
178
|
+
second machine (witness/anchor). The STH signer needs
|
|
179
|
+
`namespaces="mandate-log"` in `allowed_signers`.
|
|
180
|
+
|
|
181
|
+
### Off-host witness
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# on the second machine
|
|
185
|
+
mandate --signers log_signers witness serve --state witness.json \
|
|
186
|
+
--token "$WITNESS_TOKEN" --port 8898
|
|
187
|
+
|
|
188
|
+
# on the log host
|
|
189
|
+
mandate witness push --url http://witness:8898 --sth sth.json --token "$WITNESS_TOKEN"
|
|
190
|
+
mandate witness push --url http://witness:8898 --sth sth_new.json --proof cons.json --token "$WITNESS_TOKEN"
|
|
191
|
+
mandate witness show --state witness.json
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The witness pins the latest STH and rejects rollback or a same-size different
|
|
195
|
+
root (split-view). Growth requires a consistency proof from the pinned root.
|
|
196
|
+
`witness verify` checks without pinning; `witness accept` pins locally.
|
|
197
|
+
|
|
198
|
+
## Design invariants
|
|
199
|
+
|
|
200
|
+
1. **Effect, not tool.** The registry keys on `payment.send`, not on
|
|
201
|
+
`gama_sql`. The same effect is the same commitment regardless of executor.
|
|
202
|
+
2. **Canonical bytes.** **RFC 8785 (JCS)** canonical JSON; the commitment is the
|
|
203
|
+
hash of `{v, effect, requested_by, render_hash}`. Same content ⇒ same id, in
|
|
204
|
+
any RFC 8785 implementation.
|
|
205
|
+
3. **Off-host key.** The approver's private key never lives where the agent
|
|
206
|
+
runs. The host stores only public keys (`trust/allowed_signers`).
|
|
207
|
+
4. **One-shot, nonce-bound.** A grant is redeemable once; replays fail.
|
|
208
|
+
5. **Agent cannot self-approve.** Enforced in code, not by convention.
|
|
209
|
+
6. **Fail closed.** Unknown effects are denied. Missing signer ⇒ invalid.
|
|
210
|
+
|
|
211
|
+
## Layout
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
spec/ MANDATE-1 data model
|
|
215
|
+
mandate/ stdlib-only reference implementation
|
|
216
|
+
bin/ CLI
|
|
217
|
+
adapters/ MCP server
|
|
218
|
+
tests/ unit + bypass tests (red-team)
|
|
219
|
+
docs/ THREAT_MODEL, NON_GOALS, PLAN
|
|
220
|
+
skills/ agent skill instructions
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Status
|
|
224
|
+
|
|
225
|
+
**Alpha** — reference implementation v0.10.1: canonical commitment, Ed25519 grant
|
|
226
|
+
bound to the executing agent, one-shot redeem under a file lock, hash-chained
|
|
227
|
+
receipts with a `.head` anchor, idempotent effect journal + write adapter,
|
|
228
|
+
effect broker with signed result entries, read-only ERP preconditions, SMTP
|
|
229
|
+
adapter, Merkle transparency log with an off-host witness, MCP server
|
|
230
|
+
(`mandate-mcp`), and a red-team test suite. See `PLAN.md` for the roadmap and
|
|
231
|
+
`docs/THREAT_MODEL.md` for the known limits.
|
|
232
|
+
|
|
233
|
+
## Honest limitation
|
|
234
|
+
|
|
235
|
+
Cryptography proves *who authorised what*. It does **not** isolate
|
|
236
|
+
credentials. Until the credentials that cause the effect (SQL write, payment
|
|
237
|
+
API, mail) live only in an effect broker, a compromised agent can bypass
|
|
238
|
+
Mandate entirely. That broker is the next milestone — see `PLAN.md`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
## Phase 1 — Core (done, v0.1)
|
|
4
|
+
- [x] Canonical commitment (JCS), stable ids
|
|
5
|
+
- [x] Intent / Grant / Receipt objects
|
|
6
|
+
- [x] Ed25519 grant signing via OpenSSH (`ssh-keygen -Y sign`)
|
|
7
|
+
- [x] One-shot redeem with nonce, replay detection
|
|
8
|
+
- [x] Hash-chained ledger + offline verification
|
|
9
|
+
- [x] Agent cannot self-approve
|
|
10
|
+
- [x] Fail-closed policy (unknown effect = deny)
|
|
11
|
+
- [x] Bypass tests: tamper, replay, wrong signer, expiry, self-approval
|
|
12
|
+
|
|
13
|
+
## Phase 2 — Effect broker (core done, v0.2)
|
|
14
|
+
- [x] Broker is the only executor; agent never runs the effect itself
|
|
15
|
+
- [x] HTTP `/execute` + `/health`, loopback-only by default
|
|
16
|
+
- [x] Atomic nonce claim (SQLite UNIQUE) → no double execution under concurrency
|
|
17
|
+
- [x] `render_hash`: the human signs the rendered summary, bound into the commitment
|
|
18
|
+
- [x] Signed result entry chained after the authorisation receipt
|
|
19
|
+
- [x] Read-only ERP precondition (SQLite, fail-closed target + amount check)
|
|
20
|
+
- [x] SMTP adapter (`dry_run` default; `live` reads password from env)
|
|
21
|
+
- [ ] Move write credentials (SQL write, payment, WA) into the broker
|
|
22
|
+
- [ ] Per-effect adapters + integration tests against a fake ERP
|
|
23
|
+
|
|
24
|
+
## Phase 3 — Transparency + interop
|
|
25
|
+
- [x] Merkle transparency log (RFC 6962/9162 style) with inclusion + consistency proofs
|
|
26
|
+
- [x] Signed tree head via Ed25519 (`mandate-log` namespace)
|
|
27
|
+
- [x] Off-host witness/anchor for the STH (rules out split-view)
|
|
28
|
+
- [ ] Export receipt as JWS / C2PA-style manifest
|
|
29
|
+
- [ ] OIDC/WebAuthn co-sign (passkeys) alongside Ed25519
|
|
30
|
+
- [ ] Provenance enforcement: `data_ref` required for every choice parameter
|
|
31
|
+
|
|
32
|
+
## Phase 4 — Ecosystem
|
|
33
|
+
- [ ] MCP server packaged for Claude Code / opencode / Cursor
|
|
34
|
+
- [ ] Shell shim + GitHub Action for CI bypass tests
|
|
35
|
+
- [ ] Policy packs per domain (trade, finance, ops)
|
|
36
|
+
- [ ] `mandate audit` diff of policy and trust-root changes
|
|
37
|
+
|
|
38
|
+
## Definition of done for v1.0
|
|
39
|
+
An agent with no credentials to the target system cannot cause a consequential
|
|
40
|
+
effect without a valid, single-use, human-signed grant, and an auditor can prove
|
|
41
|
+
that offline.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Mandate
|
|
2
|
+
|
|
3
|
+
[](https://github.com/gamai-io/mandate/actions/workflows/test.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
**Signed, verifiable business commitments for AI agents.**
|
|
7
|
+
|
|
8
|
+
Most agent safety tools answer *"is this tool allowed?"*. Mandate answers the
|
|
9
|
+
question that actually matters: **"who ordered this exact payment, to this
|
|
10
|
+
supplier, for this amount?"** A tool permission is not a business mandate.
|
|
11
|
+
|
|
12
|
+
Every consequential effect becomes a canonical **Commitment** object. An agent
|
|
13
|
+
signs the intent; a human co-signs a **Grant** with an Ed25519 key; execution
|
|
14
|
+
produces a hash-chained **Receipt**. Anyone can verify the whole chain offline.
|
|
15
|
+
|
|
16
|
+
> Approval workflows are ephemeral events. A Mandate is a durable, portable,
|
|
17
|
+
> cryptographically bound artifact — a commit, not a log line.
|
|
18
|
+
|
|
19
|
+
## Why it is not another approval gate
|
|
20
|
+
|
|
21
|
+
| Approval gate | Mandate |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Asks "may this tool run?" | Asks "is this exact effect authorised?" |
|
|
24
|
+
| Identity is a self-asserted field | Approver proves possession of an off-host key |
|
|
25
|
+
| Approval is a mutable DB row | Grant + receipt are signed, content-addressed |
|
|
26
|
+
| Logs what happened | Lets anyone prove what was authorised |
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
CLI + broker + MCP adapter. There is **no web UI** by design: Mandate is a
|
|
31
|
+
local trust primitive you embed in your own stack.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python3 -m pip install gama-mandate
|
|
35
|
+
# or from source / git
|
|
36
|
+
python3 -m pip install "git+https://github.com/gamai-io/mandate.git"
|
|
37
|
+
|
|
38
|
+
mandate doctor # checks python, ssh-keygen, policy
|
|
39
|
+
mandate init --dir ./my-deployment # policy, allowed_signers, config, example
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export PYTHONPATH=$PWD
|
|
46
|
+
|
|
47
|
+
# 1. on the approver's device (never on the agent host)
|
|
48
|
+
ssh-keygen -t ed25519 -f ~/.ssh/mandate -C mandate
|
|
49
|
+
# append the PUBLIC key to trust/allowed_signers as: renato ssh-ed25519 AAAA... mandate
|
|
50
|
+
|
|
51
|
+
# 2. agent proposes
|
|
52
|
+
./bin/mandate propose --intent examples/intent.json --out /tmp/proposal.json
|
|
53
|
+
|
|
54
|
+
# 3. human grants (signs the commitment, bound to one broker + the agent)
|
|
55
|
+
./bin/mandate grant --proposal /tmp/proposal.json --approver renato \
|
|
56
|
+
--key ~/.ssh/mandate --audience local-broker --out /tmp/grant.json
|
|
57
|
+
|
|
58
|
+
# 4. agent redeems exactly once
|
|
59
|
+
./bin/mandate redeem --intent examples/intent.json --grant /tmp/grant.json \
|
|
60
|
+
--ledger ledger.jsonl
|
|
61
|
+
|
|
62
|
+
# 5. or let the effect broker execute it (credentials stay in the broker)
|
|
63
|
+
./bin/mandate-broker --port 8899 --signers trust/allowed_signers \
|
|
64
|
+
--ledger ledger.jsonl --nonce-db broker/used.sqlite &
|
|
65
|
+
./bin/mandate execute --broker http://127.0.0.1:8899 \
|
|
66
|
+
--intent examples/intent.json --grant /tmp/grant.json
|
|
67
|
+
|
|
68
|
+
# 5. anyone verifies offline
|
|
69
|
+
./bin/mandate verify --receipt ... --intent examples/intent.json --grant /tmp/grant.json
|
|
70
|
+
./bin/mandate verify-result --entry /tmp/result.json --broker-signers trust/broker_signers
|
|
71
|
+
./bin/mandate chain --ledger ledger.jsonl
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Exit codes for `check`: `0` allow · `10` ask (grant required) · `20` deny.
|
|
75
|
+
|
|
76
|
+
## Effect broker (Phase 2)
|
|
77
|
+
|
|
78
|
+
`bin/mandate-broker` is the only component that executes effects. It verifies
|
|
79
|
+
the grant, **atomically claims the nonce** (SQLite `UNIQUE`, so two concurrent
|
|
80
|
+
requests cannot both succeed), runs the effect handler, and appends a signed
|
|
81
|
+
result entry to the hash-chained ledger. The agent calls it over loopback; the
|
|
82
|
+
agent never holds the effect credentials.
|
|
83
|
+
|
|
84
|
+
The shipped handlers are a sandbox (they record the effect under `effects/` or
|
|
85
|
+
`outbox/`). Wiring real credentials (SQL write, SMTP, payment) is Phase 2
|
|
86
|
+
completion — see `PLAN.md`.
|
|
87
|
+
|
|
88
|
+
### Adapters
|
|
89
|
+
|
|
90
|
+
**ERP precondition (read-only).** Before a grant is consumed, the broker runs a
|
|
91
|
+
named-parameter SQLite query against master data. No row → `counterparty_not_found`;
|
|
92
|
+
amount outside `tolerance_pct` → `amount_mismatch`. The grant is **not**
|
|
93
|
+
consumed when a precondition fails, so a corrected request can retry safely.
|
|
94
|
+
|
|
95
|
+
**SMTP (`message.send`).** `mode=dry_run` writes an `.eml` to the outbox;
|
|
96
|
+
`mode=live` sends via SMTP. The password is read from the env var named in
|
|
97
|
+
`password_env` and never logged or returned.
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
"erp": {"db": "erp.sqlite", "checks": {"payment.send": {
|
|
101
|
+
"query": "SELECT invoice_total FROM vendors WHERE name = :target",
|
|
102
|
+
"amount_field": "invoice_total", "tolerance_pct": 0.5}}},
|
|
103
|
+
"smtp": {"mode": "dry_run", "host": "", "user": "",
|
|
104
|
+
"password_env": "MANDATE_SMTP_PASSWORD", "from": "noreply@gama.local"}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Key scoping and revocation
|
|
108
|
+
|
|
109
|
+
An `allowed_signers` line **must** declare its namespace scope:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
renato namespaces="mandate" ssh-ed25519 AAAAC3Nza... mandate
|
|
113
|
+
gama-broker namespaces="mandate-broker" ssh-ed25519 AAAAC3Nza... broker
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Optional validity: `valid-after="YYYYMMDD" valid-before="YYYYMMDD"`.
|
|
117
|
+
Revoke a key by fingerprint:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
mandate revoke --pub key.pub --reason "laptop lost"
|
|
121
|
+
mandate --signers trust/allowed_signers signers # inspect scopes + fingerprints
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Idempotent writes and recovery
|
|
125
|
+
|
|
126
|
+
The broker keeps a SQLite **effect journal** (`journal_db`). The flow is
|
|
127
|
+
`intent → executing → executed → receipted`; the authorisation receipt is
|
|
128
|
+
written only **after** the effect commits. On startup the broker recovers:
|
|
129
|
+
a committed effect without a receipt gets one; a claimed grant with no effect is
|
|
130
|
+
abandoned; a duplicate `execute` returns the original receipt and result.
|
|
131
|
+
|
|
132
|
+
`write.mode=sqlite` runs a trusted configured statement together with an
|
|
133
|
+
idempotency insert in **one transaction**, keyed by the grant nonce:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
"write": {"mode": "sqlite", "db": "erp.sqlite",
|
|
137
|
+
"statement": "INSERT INTO payments(nonce,vendor,amount) VALUES(:nonce,:target,:amount)"}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Transparency log
|
|
141
|
+
|
|
142
|
+
Every ledger entry is a leaf in an RFC 6962/9162-style Merkle tree
|
|
143
|
+
(`Leaf=SHA256(0x00||JCS(entry))`, `Node=SHA256(0x01||L||R)`). A signed tree
|
|
144
|
+
head (STH) fixes the root and size; inclusion and consistency proofs verify
|
|
145
|
+
offline:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
mandate log head --ledger ledger.jsonl --log-name log1 --log-key ~/.ssh/mandate-log --out sth.json
|
|
149
|
+
mandate --signers trust/allowed_signers log sth-verify --sth sth.json
|
|
150
|
+
mandate log proof --ledger ledger.jsonl --index 3 --out proof.json
|
|
151
|
+
mandate log verify-inclusion --entry entry.json --proof proof.json --sth sth.json
|
|
152
|
+
mandate log consistency --ledger ledger.jsonl --first 2 --second 3 --out cons.json
|
|
153
|
+
mandate log verify-consistency --old old_sth.json --new sth.json --proof cons.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
A single log cannot prevent **split-view** by its operator; pin the STH on a
|
|
157
|
+
second machine (witness/anchor). The STH signer needs
|
|
158
|
+
`namespaces="mandate-log"` in `allowed_signers`.
|
|
159
|
+
|
|
160
|
+
### Off-host witness
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# on the second machine
|
|
164
|
+
mandate --signers log_signers witness serve --state witness.json \
|
|
165
|
+
--token "$WITNESS_TOKEN" --port 8898
|
|
166
|
+
|
|
167
|
+
# on the log host
|
|
168
|
+
mandate witness push --url http://witness:8898 --sth sth.json --token "$WITNESS_TOKEN"
|
|
169
|
+
mandate witness push --url http://witness:8898 --sth sth_new.json --proof cons.json --token "$WITNESS_TOKEN"
|
|
170
|
+
mandate witness show --state witness.json
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The witness pins the latest STH and rejects rollback or a same-size different
|
|
174
|
+
root (split-view). Growth requires a consistency proof from the pinned root.
|
|
175
|
+
`witness verify` checks without pinning; `witness accept` pins locally.
|
|
176
|
+
|
|
177
|
+
## Design invariants
|
|
178
|
+
|
|
179
|
+
1. **Effect, not tool.** The registry keys on `payment.send`, not on
|
|
180
|
+
`gama_sql`. The same effect is the same commitment regardless of executor.
|
|
181
|
+
2. **Canonical bytes.** **RFC 8785 (JCS)** canonical JSON; the commitment is the
|
|
182
|
+
hash of `{v, effect, requested_by, render_hash}`. Same content ⇒ same id, in
|
|
183
|
+
any RFC 8785 implementation.
|
|
184
|
+
3. **Off-host key.** The approver's private key never lives where the agent
|
|
185
|
+
runs. The host stores only public keys (`trust/allowed_signers`).
|
|
186
|
+
4. **One-shot, nonce-bound.** A grant is redeemable once; replays fail.
|
|
187
|
+
5. **Agent cannot self-approve.** Enforced in code, not by convention.
|
|
188
|
+
6. **Fail closed.** Unknown effects are denied. Missing signer ⇒ invalid.
|
|
189
|
+
|
|
190
|
+
## Layout
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
spec/ MANDATE-1 data model
|
|
194
|
+
mandate/ stdlib-only reference implementation
|
|
195
|
+
bin/ CLI
|
|
196
|
+
adapters/ MCP server
|
|
197
|
+
tests/ unit + bypass tests (red-team)
|
|
198
|
+
docs/ THREAT_MODEL, NON_GOALS, PLAN
|
|
199
|
+
skills/ agent skill instructions
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Status
|
|
203
|
+
|
|
204
|
+
**Alpha** — reference implementation v0.10.1: canonical commitment, Ed25519 grant
|
|
205
|
+
bound to the executing agent, one-shot redeem under a file lock, hash-chained
|
|
206
|
+
receipts with a `.head` anchor, idempotent effect journal + write adapter,
|
|
207
|
+
effect broker with signed result entries, read-only ERP preconditions, SMTP
|
|
208
|
+
adapter, Merkle transparency log with an off-host witness, MCP server
|
|
209
|
+
(`mandate-mcp`), and a red-team test suite. See `PLAN.md` for the roadmap and
|
|
210
|
+
`docs/THREAT_MODEL.md` for the known limits.
|
|
211
|
+
|
|
212
|
+
## Honest limitation
|
|
213
|
+
|
|
214
|
+
Cryptography proves *who authorised what*. It does **not** isolate
|
|
215
|
+
credentials. Until the credentials that cause the effect (SQL write, payment
|
|
216
|
+
API, mail) live only in an effect broker, a compromised agent can bypass
|
|
217
|
+
Mandate entirely. That broker is the next milestone — see `PLAN.md`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Non-goals
|
|
2
|
+
|
|
3
|
+
- **Not a sandbox.** Mandate does not isolate code execution.
|
|
4
|
+
- **Not an observability platform.** It emits receipts; shipping them to a
|
|
5
|
+
SIEM is someone else's job.
|
|
6
|
+
- **Not a model.** No LLM is called anywhere in the decision path.
|
|
7
|
+
- **Not a payment processor.** It authorises effects; it does not move money.
|
|
8
|
+
- **Not a replacement for legal contracts.** A signed grant proves authorisation,
|
|
9
|
+
not legal validity.
|
|
10
|
+
- **Not zero-config in production.** A trust root (public keys) and a policy
|
|
11
|
+
must be provisioned deliberately.
|