agp-tpe 2.3.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.
- agp_tpe-2.3.3/.gitignore +22 -0
- agp_tpe-2.3.3/LICENSE +9 -0
- agp_tpe-2.3.3/PKG-INFO +201 -0
- agp_tpe-2.3.3/README.md +169 -0
- agp_tpe-2.3.3/canonicalization/python/canonicalize.py +293 -0
- agp_tpe-2.3.3/decision_context/python/validate_decision_context.py +212 -0
- agp_tpe-2.3.3/pyproject.toml +67 -0
- agp_tpe-2.3.3/registry/schemas/agp.canonicalization-receipt-1.schema.json +78 -0
- agp_tpe-2.3.3/registry/schemas/agp.decision-context-1.schema.json +163 -0
- agp_tpe-2.3.3/registry/schemas/agp.decision-context-2.schema.json +169 -0
- agp_tpe-2.3.3/registry/schemas/agp.signature-statement-1.schema.json +61 -0
- agp_tpe-2.3.3/registry/schemas/agp.signature-statement-2.schema.json +61 -0
- agp_tpe-2.3.3/registry/schemas/agp.signed-decision-context-1.schema.json +65 -0
- agp_tpe-2.3.3/registry/schemas/agp.signed-decision-context-2.schema.json +65 -0
- agp_tpe-2.3.3/registry/schemas/agp.trust-policy-1.schema.json +101 -0
- agp_tpe-2.3.3/registry/schemas/agp.trust-policy-2.schema.json +614 -0
- agp_tpe-2.3.3/registry/schemas/reserved.schema.json +6 -0
- agp_tpe-2.3.3/signed_decision_context/python/validate_signed_decision_context.py +350 -0
- agp_tpe-2.3.3/signed_decision_context/python/verify_signed_decision_context.py +329 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/README.md +16 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/__init__.py +65 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/composition.py +174 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/dispatcher.py +80 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/policy_evaluation.py +408 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/policy_set.py +126 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/policy_tree.py +179 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/primitive.py +27 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/registry.py +49 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/result.py +139 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/engine/state.py +139 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/evaluate_trust_policy_v2.py +1066 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/__init__.py +42 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/all_of_signers.py +138 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/any_of_signers.py +132 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/at_least_n_signers.py +148 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/at_most_n_signers.py +148 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/exactly_n_signers.py +148 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/exactly_one_of_signers.py +132 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/global_signature_threshold.py +108 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/global_weight_threshold.py +106 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/mutual_exclusion.py +132 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/prohibited_signer.py +96 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/required_signer.py +99 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/role_threshold.py +136 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/role_weight_threshold.py +140 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/separation_of_duties.py +155 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/signer_threshold.py +143 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/primitives/time_window.py +122 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/trust_primitive_engine/__init__.py +13 -0
- agp_tpe-2.3.3/trust_primitive_engine/python/trust_primitive_engine/api.py +193 -0
agp_tpe-2.3.3/.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
|
|
5
|
+
results/
|
|
6
|
+
generated/
|
|
7
|
+
|
|
8
|
+
*.exe
|
|
9
|
+
*.out
|
|
10
|
+
*.o
|
|
11
|
+
|
|
12
|
+
agp-log-go
|
|
13
|
+
agp-signed-go
|
|
14
|
+
agp-resolver-go
|
|
15
|
+
|
|
16
|
+
# Go build artifacts
|
|
17
|
+
signed_decision_context/go/agp-signed-decision-context-verify
|
|
18
|
+
|
|
19
|
+
# Python package build artifacts
|
|
20
|
+
dist/
|
|
21
|
+
build/
|
|
22
|
+
*.egg-info/
|
agp_tpe-2.3.3/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 AGP contributors
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at the URL above.
|
agp_tpe-2.3.3/PKG-INFO
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agp-tpe
|
|
3
|
+
Version: 2.3.3
|
|
4
|
+
Summary: AGP Trust Primitive Engine for deterministic Trust Policy 2 evaluation
|
|
5
|
+
Project-URL: Homepage, https://github.com/agpprotocol/agp
|
|
6
|
+
Project-URL: Repository, https://github.com/agpprotocol/agp
|
|
7
|
+
Project-URL: Documentation, https://github.com/agpprotocol/agp/blob/main/trust_primitive_engine/INTEGRATION-GUIDE.md
|
|
8
|
+
Project-URL: Issues, https://github.com/agpprotocol/agp/issues
|
|
9
|
+
Author: AGP Protocol
|
|
10
|
+
License: Apache License
|
|
11
|
+
Version 2.0, January 2004
|
|
12
|
+
http://www.apache.org/licenses/
|
|
13
|
+
|
|
14
|
+
Copyright 2026 AGP contributors
|
|
15
|
+
|
|
16
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
17
|
+
you may not use this file except in compliance with the License.
|
|
18
|
+
You may obtain a copy of the License at the URL above.
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Keywords: agp,deterministic-evaluation,ed25519,policy-engine,trust-policy
|
|
21
|
+
Classifier: Development Status :: 4 - Beta
|
|
22
|
+
Classifier: Intended Audience :: Developers
|
|
23
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
24
|
+
Classifier: Programming Language :: Python :: 3
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Classifier: Topic :: Security :: Cryptography
|
|
28
|
+
Requires-Python: >=3.12
|
|
29
|
+
Requires-Dist: cryptography>=42.0
|
|
30
|
+
Requires-Dist: jsonschema<5,>=4.26
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Agent Governance Protocol (AGP)
|
|
34
|
+
|
|
35
|
+
**AGP is an experimental governance layer for multi-agent systems.**
|
|
36
|
+
|
|
37
|
+
It defines how independent agents or authorities can propose, review, approve,
|
|
38
|
+
reject, veto and audit high-impact decisions without trusting a single
|
|
39
|
+
coordinator as the sole source of truth.
|
|
40
|
+
|
|
41
|
+
> Status: experimental. AGP is not yet a production standard.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## 120-Second Demo
|
|
46
|
+
|
|
47
|
+
Run the public AGP demo with no external dependencies:
|
|
48
|
+
|
|
49
|
+
python3 examples/120_second_demo/demo.py
|
|
50
|
+
|
|
51
|
+
Fast mode:
|
|
52
|
+
|
|
53
|
+
python3 examples/120_second_demo/demo.py --fast
|
|
54
|
+
|
|
55
|
+
Valid governance path:
|
|
56
|
+
|
|
57
|
+
python3 examples/120_second_demo/demo.py --happy-path --fast
|
|
58
|
+
|
|
59
|
+
The demo shows a coordinator changing a deployment payload after approval.
|
|
60
|
+
A coordinator-trusting workflow accepts it because the approval count is still present.
|
|
61
|
+
AGP rejects it because the approvals are bound to the exact approved input.
|
|
62
|
+
|
|
63
|
+
[Read the demo documentation](examples/120_second_demo/README.md)
|
|
64
|
+
|
|
65
|
+
## Whitepaper
|
|
66
|
+
|
|
67
|
+
The current public review draft is:
|
|
68
|
+
|
|
69
|
+
**AGP Whitepaper v0.9.2 — A Deterministic Governance Layer for Multi-Agent Systems**
|
|
70
|
+
|
|
71
|
+
- [Read online](docs/whitepaper/AGP_Whitepaper_v0.9.2.md)
|
|
72
|
+
- [Download PDF](https://github.com/agpprotocol/agp/releases/tag/whitepaper-v0.9.2)
|
|
73
|
+
- [Submit technical feedback](https://github.com/agpprotocol/agp/issues/1)
|
|
74
|
+
|
|
75
|
+
The whitepaper is experimental, is not a production standard, and has not yet received independent security review.
|
|
76
|
+
|
|
77
|
+
## Why AGP?
|
|
78
|
+
|
|
79
|
+
- **MCP** connects models to tools and data.
|
|
80
|
+
- **A2A-style protocols** let agents communicate.
|
|
81
|
+
- **Workflow engines** coordinate execution.
|
|
82
|
+
- **AGP** governs collective decisions.
|
|
83
|
+
|
|
84
|
+
A workflow can record that a deployment was approved. AGP additionally aims to
|
|
85
|
+
make the authority snapshot, evidence, ballots, resolution and history
|
|
86
|
+
independently verifiable.
|
|
87
|
+
|
|
88
|
+
## Core properties
|
|
89
|
+
|
|
90
|
+
- deterministic resolution;
|
|
91
|
+
- canonical serialization;
|
|
92
|
+
- independent Python and Go implementations;
|
|
93
|
+
- Ed25519 signed envelopes;
|
|
94
|
+
- replay, expiration and revocation checks;
|
|
95
|
+
- append-only transparency log;
|
|
96
|
+
- external audit receipts;
|
|
97
|
+
- conformance and adversarial test vectors.
|
|
98
|
+
|
|
99
|
+
## Architecture
|
|
100
|
+
|
|
101
|
+
```mermaid
|
|
102
|
+
flowchart LR
|
|
103
|
+
P[Proposal] --> E[Evidence]
|
|
104
|
+
E --> B[Signed ballots]
|
|
105
|
+
B --> R[Deterministic resolution]
|
|
106
|
+
R --> L[Transparency log]
|
|
107
|
+
L --> A[Independent audit]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## AGP vs. a conventional workflow
|
|
111
|
+
|
|
112
|
+
| Property | Workflow | AGP |
|
|
113
|
+
|---|---:|---:|
|
|
114
|
+
| Coordinates steps | Yes | Yes |
|
|
115
|
+
| Deterministic replay across implementations | Not inherent | Yes |
|
|
116
|
+
| Signed authority actions | Optional/custom | Built into profile |
|
|
117
|
+
| Evidence version validation | Optional/custom | Yes |
|
|
118
|
+
| Revocation-aware decisions | Optional/custom | Yes |
|
|
119
|
+
| Tamper-evident history | Optional/custom | Yes |
|
|
120
|
+
| Independent audit without trusting coordinator | Not inherent | Yes |
|
|
121
|
+
|
|
122
|
+
AGP is intentionally more complex. For low-risk internal automation, a
|
|
123
|
+
conventional workflow is usually the better choice.
|
|
124
|
+
|
|
125
|
+
## Reproducible results
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
AGP v0.3 Conformance: 260/260 passed
|
|
129
|
+
AGP v0.4 Signed Conformance: 10/10 passed
|
|
130
|
+
AGP v0.5 Transparency: 8/8 passed
|
|
131
|
+
AGP vs Workflow benchmark: 8/8 attacks detected by AGP
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The workflow baseline in the benchmark detected 0/8 because it treated
|
|
135
|
+
coordinator state as authoritative. This is a limited experimental benchmark,
|
|
136
|
+
not a claim that all workflow engines are insecure.
|
|
137
|
+
|
|
138
|
+
## Quick start
|
|
139
|
+
|
|
140
|
+
Requirements:
|
|
141
|
+
|
|
142
|
+
- Python 3.10+
|
|
143
|
+
- Go 1.22+
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
git clone https://github.com/agpprotocol/agp.git
|
|
147
|
+
cd agp
|
|
148
|
+
python3 -m venv .venv
|
|
149
|
+
source .venv/bin/activate
|
|
150
|
+
pip install -r requirements-v0.4.txt
|
|
151
|
+
python3 run_benchmark_all.py
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Expected final line:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
AGP BENCHMARK COMPLETE
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Repository map
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
spec/ Conformance profile
|
|
164
|
+
python/ Python resolver
|
|
165
|
+
go/ Go resolver
|
|
166
|
+
signed/ Signed envelope conformance
|
|
167
|
+
transparency/ Append-only audit log
|
|
168
|
+
benchmark/ AGP vs workflow experiment
|
|
169
|
+
examples/ Reference scenarios
|
|
170
|
+
docs/ Architecture, threat model and evaluation
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## What AGP does not claim
|
|
174
|
+
|
|
175
|
+
AGP does not:
|
|
176
|
+
|
|
177
|
+
- replace authentication, authorization or IAM;
|
|
178
|
+
- replace orchestration engines;
|
|
179
|
+
- guarantee that evidence is factually true;
|
|
180
|
+
- eliminate compromised members;
|
|
181
|
+
- solve governance for every multi-agent system;
|
|
182
|
+
- claim production maturity.
|
|
183
|
+
|
|
184
|
+
## Current research question
|
|
185
|
+
|
|
186
|
+
> Do high-impact multi-agent systems need a portable, independently verifiable
|
|
187
|
+
> governance layer distinct from communication and orchestration?
|
|
188
|
+
|
|
189
|
+
## Documentation
|
|
190
|
+
|
|
191
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
192
|
+
- [Threat model](docs/THREAT_MODEL.md)
|
|
193
|
+
- [Benchmark](docs/BENCHMARK.md)
|
|
194
|
+
- [Roadmap](ROADMAP.md)
|
|
195
|
+
- [Contributing](CONTRIBUTING.md)
|
|
196
|
+
- [Security policy](SECURITY.md)
|
|
197
|
+
- [Governance](GOVERNANCE.md)
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
agp_tpe-2.3.3/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Agent Governance Protocol (AGP)
|
|
2
|
+
|
|
3
|
+
**AGP is an experimental governance layer for multi-agent systems.**
|
|
4
|
+
|
|
5
|
+
It defines how independent agents or authorities can propose, review, approve,
|
|
6
|
+
reject, veto and audit high-impact decisions without trusting a single
|
|
7
|
+
coordinator as the sole source of truth.
|
|
8
|
+
|
|
9
|
+
> Status: experimental. AGP is not yet a production standard.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## 120-Second Demo
|
|
14
|
+
|
|
15
|
+
Run the public AGP demo with no external dependencies:
|
|
16
|
+
|
|
17
|
+
python3 examples/120_second_demo/demo.py
|
|
18
|
+
|
|
19
|
+
Fast mode:
|
|
20
|
+
|
|
21
|
+
python3 examples/120_second_demo/demo.py --fast
|
|
22
|
+
|
|
23
|
+
Valid governance path:
|
|
24
|
+
|
|
25
|
+
python3 examples/120_second_demo/demo.py --happy-path --fast
|
|
26
|
+
|
|
27
|
+
The demo shows a coordinator changing a deployment payload after approval.
|
|
28
|
+
A coordinator-trusting workflow accepts it because the approval count is still present.
|
|
29
|
+
AGP rejects it because the approvals are bound to the exact approved input.
|
|
30
|
+
|
|
31
|
+
[Read the demo documentation](examples/120_second_demo/README.md)
|
|
32
|
+
|
|
33
|
+
## Whitepaper
|
|
34
|
+
|
|
35
|
+
The current public review draft is:
|
|
36
|
+
|
|
37
|
+
**AGP Whitepaper v0.9.2 — A Deterministic Governance Layer for Multi-Agent Systems**
|
|
38
|
+
|
|
39
|
+
- [Read online](docs/whitepaper/AGP_Whitepaper_v0.9.2.md)
|
|
40
|
+
- [Download PDF](https://github.com/agpprotocol/agp/releases/tag/whitepaper-v0.9.2)
|
|
41
|
+
- [Submit technical feedback](https://github.com/agpprotocol/agp/issues/1)
|
|
42
|
+
|
|
43
|
+
The whitepaper is experimental, is not a production standard, and has not yet received independent security review.
|
|
44
|
+
|
|
45
|
+
## Why AGP?
|
|
46
|
+
|
|
47
|
+
- **MCP** connects models to tools and data.
|
|
48
|
+
- **A2A-style protocols** let agents communicate.
|
|
49
|
+
- **Workflow engines** coordinate execution.
|
|
50
|
+
- **AGP** governs collective decisions.
|
|
51
|
+
|
|
52
|
+
A workflow can record that a deployment was approved. AGP additionally aims to
|
|
53
|
+
make the authority snapshot, evidence, ballots, resolution and history
|
|
54
|
+
independently verifiable.
|
|
55
|
+
|
|
56
|
+
## Core properties
|
|
57
|
+
|
|
58
|
+
- deterministic resolution;
|
|
59
|
+
- canonical serialization;
|
|
60
|
+
- independent Python and Go implementations;
|
|
61
|
+
- Ed25519 signed envelopes;
|
|
62
|
+
- replay, expiration and revocation checks;
|
|
63
|
+
- append-only transparency log;
|
|
64
|
+
- external audit receipts;
|
|
65
|
+
- conformance and adversarial test vectors.
|
|
66
|
+
|
|
67
|
+
## Architecture
|
|
68
|
+
|
|
69
|
+
```mermaid
|
|
70
|
+
flowchart LR
|
|
71
|
+
P[Proposal] --> E[Evidence]
|
|
72
|
+
E --> B[Signed ballots]
|
|
73
|
+
B --> R[Deterministic resolution]
|
|
74
|
+
R --> L[Transparency log]
|
|
75
|
+
L --> A[Independent audit]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## AGP vs. a conventional workflow
|
|
79
|
+
|
|
80
|
+
| Property | Workflow | AGP |
|
|
81
|
+
|---|---:|---:|
|
|
82
|
+
| Coordinates steps | Yes | Yes |
|
|
83
|
+
| Deterministic replay across implementations | Not inherent | Yes |
|
|
84
|
+
| Signed authority actions | Optional/custom | Built into profile |
|
|
85
|
+
| Evidence version validation | Optional/custom | Yes |
|
|
86
|
+
| Revocation-aware decisions | Optional/custom | Yes |
|
|
87
|
+
| Tamper-evident history | Optional/custom | Yes |
|
|
88
|
+
| Independent audit without trusting coordinator | Not inherent | Yes |
|
|
89
|
+
|
|
90
|
+
AGP is intentionally more complex. For low-risk internal automation, a
|
|
91
|
+
conventional workflow is usually the better choice.
|
|
92
|
+
|
|
93
|
+
## Reproducible results
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
AGP v0.3 Conformance: 260/260 passed
|
|
97
|
+
AGP v0.4 Signed Conformance: 10/10 passed
|
|
98
|
+
AGP v0.5 Transparency: 8/8 passed
|
|
99
|
+
AGP vs Workflow benchmark: 8/8 attacks detected by AGP
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The workflow baseline in the benchmark detected 0/8 because it treated
|
|
103
|
+
coordinator state as authoritative. This is a limited experimental benchmark,
|
|
104
|
+
not a claim that all workflow engines are insecure.
|
|
105
|
+
|
|
106
|
+
## Quick start
|
|
107
|
+
|
|
108
|
+
Requirements:
|
|
109
|
+
|
|
110
|
+
- Python 3.10+
|
|
111
|
+
- Go 1.22+
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git clone https://github.com/agpprotocol/agp.git
|
|
115
|
+
cd agp
|
|
116
|
+
python3 -m venv .venv
|
|
117
|
+
source .venv/bin/activate
|
|
118
|
+
pip install -r requirements-v0.4.txt
|
|
119
|
+
python3 run_benchmark_all.py
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Expected final line:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
AGP BENCHMARK COMPLETE
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Repository map
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
spec/ Conformance profile
|
|
132
|
+
python/ Python resolver
|
|
133
|
+
go/ Go resolver
|
|
134
|
+
signed/ Signed envelope conformance
|
|
135
|
+
transparency/ Append-only audit log
|
|
136
|
+
benchmark/ AGP vs workflow experiment
|
|
137
|
+
examples/ Reference scenarios
|
|
138
|
+
docs/ Architecture, threat model and evaluation
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## What AGP does not claim
|
|
142
|
+
|
|
143
|
+
AGP does not:
|
|
144
|
+
|
|
145
|
+
- replace authentication, authorization or IAM;
|
|
146
|
+
- replace orchestration engines;
|
|
147
|
+
- guarantee that evidence is factually true;
|
|
148
|
+
- eliminate compromised members;
|
|
149
|
+
- solve governance for every multi-agent system;
|
|
150
|
+
- claim production maturity.
|
|
151
|
+
|
|
152
|
+
## Current research question
|
|
153
|
+
|
|
154
|
+
> Do high-impact multi-agent systems need a portable, independently verifiable
|
|
155
|
+
> governance layer distinct from communication and orchestration?
|
|
156
|
+
|
|
157
|
+
## Documentation
|
|
158
|
+
|
|
159
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
160
|
+
- [Threat model](docs/THREAT_MODEL.md)
|
|
161
|
+
- [Benchmark](docs/BENCHMARK.md)
|
|
162
|
+
- [Roadmap](ROADMAP.md)
|
|
163
|
+
- [Contributing](CONTRIBUTING.md)
|
|
164
|
+
- [Security policy](SECURITY.md)
|
|
165
|
+
- [Governance](GOVERNANCE.md)
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
MAX_INPUT_BYTES = 1_048_576
|
|
11
|
+
MAX_DEPTH = 64
|
|
12
|
+
MIN_SAFE_INTEGER = -(2**53 - 1)
|
|
13
|
+
MAX_SAFE_INTEGER = 2**53 - 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CanonicalizationError(ValueError):
|
|
17
|
+
def __init__(self, code: str, detail: str = "") -> None:
|
|
18
|
+
super().__init__(detail or code)
|
|
19
|
+
self.code = code
|
|
20
|
+
self.detail = detail
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _reject_constant(value: str) -> Any:
|
|
24
|
+
raise CanonicalizationError("INVALID_NUMBER", value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parse_integer(value: str) -> int:
|
|
28
|
+
number = int(value, 10)
|
|
29
|
+
if number < MIN_SAFE_INTEGER or number > MAX_SAFE_INTEGER:
|
|
30
|
+
raise CanonicalizationError("INTEGER_OUT_OF_RANGE", value)
|
|
31
|
+
return number
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _reject_decimal(value: str) -> Any:
|
|
35
|
+
raise CanonicalizationError("DECIMAL_NOT_SUPPORTED", value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _normalize_string(value: str) -> str:
|
|
39
|
+
output: list[str] = []
|
|
40
|
+
index = 0
|
|
41
|
+
|
|
42
|
+
while index < len(value):
|
|
43
|
+
codepoint = ord(value[index])
|
|
44
|
+
|
|
45
|
+
if 0xD800 <= codepoint <= 0xDBFF:
|
|
46
|
+
if index + 1 >= len(value):
|
|
47
|
+
raise CanonicalizationError(
|
|
48
|
+
"INVALID_UNICODE",
|
|
49
|
+
f"unpaired high surrogate U+{codepoint:04X}",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
low = ord(value[index + 1])
|
|
53
|
+
if not 0xDC00 <= low <= 0xDFFF:
|
|
54
|
+
raise CanonicalizationError(
|
|
55
|
+
"INVALID_UNICODE",
|
|
56
|
+
f"unpaired high surrogate U+{codepoint:04X}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
combined = (
|
|
60
|
+
0x10000
|
|
61
|
+
+ ((codepoint - 0xD800) << 10)
|
|
62
|
+
+ (low - 0xDC00)
|
|
63
|
+
)
|
|
64
|
+
output.append(chr(combined))
|
|
65
|
+
index += 2
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
if 0xDC00 <= codepoint <= 0xDFFF:
|
|
69
|
+
raise CanonicalizationError(
|
|
70
|
+
"INVALID_UNICODE",
|
|
71
|
+
f"unpaired low surrogate U+{codepoint:04X}",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
output.append(value[index])
|
|
75
|
+
index += 1
|
|
76
|
+
|
|
77
|
+
return "".join(output)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _normalize_value(value: Any) -> Any:
|
|
81
|
+
if isinstance(value, str):
|
|
82
|
+
return _normalize_string(value)
|
|
83
|
+
|
|
84
|
+
if isinstance(value, list):
|
|
85
|
+
return [_normalize_value(item) for item in value]
|
|
86
|
+
|
|
87
|
+
if isinstance(value, dict):
|
|
88
|
+
normalized: dict[str, Any] = {}
|
|
89
|
+
for key, item in value.items():
|
|
90
|
+
normalized_key = _normalize_string(key)
|
|
91
|
+
if normalized_key in normalized:
|
|
92
|
+
raise CanonicalizationError(
|
|
93
|
+
"DUPLICATE_KEY",
|
|
94
|
+
normalized_key,
|
|
95
|
+
)
|
|
96
|
+
normalized[normalized_key] = _normalize_value(item)
|
|
97
|
+
return normalized
|
|
98
|
+
|
|
99
|
+
return value
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _object_from_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
103
|
+
result: dict[str, Any] = {}
|
|
104
|
+
for key, value in pairs:
|
|
105
|
+
normalized_key = _normalize_string(key)
|
|
106
|
+
if normalized_key in result:
|
|
107
|
+
raise CanonicalizationError("DUPLICATE_KEY", normalized_key)
|
|
108
|
+
result[normalized_key] = value
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _validate_string(value: str) -> None:
|
|
113
|
+
_normalize_string(value)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _validate_value(value: Any, depth: int = 0) -> None:
|
|
117
|
+
if depth > MAX_DEPTH:
|
|
118
|
+
raise CanonicalizationError("MAX_DEPTH_EXCEEDED")
|
|
119
|
+
|
|
120
|
+
if value is None or isinstance(value, bool):
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
if isinstance(value, int):
|
|
124
|
+
if value < MIN_SAFE_INTEGER or value > MAX_SAFE_INTEGER:
|
|
125
|
+
raise CanonicalizationError("INTEGER_OUT_OF_RANGE")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
if isinstance(value, float):
|
|
129
|
+
raise CanonicalizationError("DECIMAL_NOT_SUPPORTED")
|
|
130
|
+
|
|
131
|
+
if isinstance(value, str):
|
|
132
|
+
_validate_string(value)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
if isinstance(value, list):
|
|
136
|
+
for item in value:
|
|
137
|
+
_validate_value(item, depth + 1)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
if isinstance(value, dict):
|
|
141
|
+
for key, item in value.items():
|
|
142
|
+
if not isinstance(key, str):
|
|
143
|
+
raise CanonicalizationError("NON_STRING_KEY")
|
|
144
|
+
_validate_string(key)
|
|
145
|
+
_validate_value(item, depth + 1)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
raise CanonicalizationError("UNSUPPORTED_TYPE", type(value).__name__)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def parse_json_bytes(raw: bytes) -> Any:
|
|
152
|
+
if len(raw) > MAX_INPUT_BYTES:
|
|
153
|
+
raise CanonicalizationError("INPUT_TOO_LARGE")
|
|
154
|
+
|
|
155
|
+
if raw.startswith(b"\xef\xbb\xbf"):
|
|
156
|
+
raise CanonicalizationError("UTF8_BOM_NOT_ALLOWED")
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
text = raw.decode("utf-8", errors="strict")
|
|
160
|
+
except UnicodeDecodeError as error:
|
|
161
|
+
raise CanonicalizationError("INVALID_UTF8", str(error)) from error
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
value = json.loads(
|
|
165
|
+
text,
|
|
166
|
+
object_pairs_hook=_object_from_pairs,
|
|
167
|
+
parse_int=_parse_integer,
|
|
168
|
+
parse_float=_reject_decimal,
|
|
169
|
+
parse_constant=_reject_constant,
|
|
170
|
+
)
|
|
171
|
+
except CanonicalizationError:
|
|
172
|
+
raise
|
|
173
|
+
except (json.JSONDecodeError, RecursionError) as error:
|
|
174
|
+
raise CanonicalizationError("INVALID_JSON", str(error)) from error
|
|
175
|
+
|
|
176
|
+
value = _normalize_value(value)
|
|
177
|
+
_validate_value(value)
|
|
178
|
+
return value
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _escape_string(value: str) -> str:
|
|
182
|
+
_validate_string(value)
|
|
183
|
+
output: list[str] = ['"']
|
|
184
|
+
|
|
185
|
+
short_escapes = {
|
|
186
|
+
0x08: r"\b",
|
|
187
|
+
0x09: r"\t",
|
|
188
|
+
0x0A: r"\n",
|
|
189
|
+
0x0C: r"\f",
|
|
190
|
+
0x0D: r"\r",
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for character in value:
|
|
194
|
+
codepoint = ord(character)
|
|
195
|
+
|
|
196
|
+
if character == '"':
|
|
197
|
+
output.append(r"\"")
|
|
198
|
+
elif character == "\\":
|
|
199
|
+
output.append(r"\\")
|
|
200
|
+
elif codepoint in short_escapes:
|
|
201
|
+
output.append(short_escapes[codepoint])
|
|
202
|
+
elif codepoint <= 0x1F:
|
|
203
|
+
output.append(f"\\u{codepoint:04x}")
|
|
204
|
+
else:
|
|
205
|
+
output.append(character)
|
|
206
|
+
|
|
207
|
+
output.append('"')
|
|
208
|
+
return "".join(output)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def canonical_text(value: Any) -> str:
|
|
212
|
+
_validate_value(value)
|
|
213
|
+
|
|
214
|
+
if value is None:
|
|
215
|
+
return "null"
|
|
216
|
+
|
|
217
|
+
if value is True:
|
|
218
|
+
return "true"
|
|
219
|
+
|
|
220
|
+
if value is False:
|
|
221
|
+
return "false"
|
|
222
|
+
|
|
223
|
+
if isinstance(value, int):
|
|
224
|
+
return str(value)
|
|
225
|
+
|
|
226
|
+
if isinstance(value, str):
|
|
227
|
+
return _escape_string(value)
|
|
228
|
+
|
|
229
|
+
if isinstance(value, list):
|
|
230
|
+
return "[" + ",".join(canonical_text(item) for item in value) + "]"
|
|
231
|
+
|
|
232
|
+
if isinstance(value, dict):
|
|
233
|
+
entries = (
|
|
234
|
+
_escape_string(key) + ":" + canonical_text(value[key])
|
|
235
|
+
for key in sorted(value)
|
|
236
|
+
)
|
|
237
|
+
return "{" + ",".join(entries) + "}"
|
|
238
|
+
|
|
239
|
+
raise CanonicalizationError("UNSUPPORTED_TYPE", type(value).__name__)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def canonical_bytes(value: Any) -> bytes:
|
|
243
|
+
return canonical_text(value).encode("utf-8")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def sha256_digest(value: Any) -> str:
|
|
247
|
+
return "sha256:" + hashlib.sha256(canonical_bytes(value)).hexdigest()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def make_receipt(raw: bytes) -> dict[str, Any]:
|
|
251
|
+
try:
|
|
252
|
+
value = parse_json_bytes(raw)
|
|
253
|
+
encoded = canonical_bytes(value)
|
|
254
|
+
return {
|
|
255
|
+
"accepted": True,
|
|
256
|
+
"canonical": encoded.decode("utf-8"),
|
|
257
|
+
"digest": "sha256:" + hashlib.sha256(encoded).hexdigest(),
|
|
258
|
+
"error_codes": [],
|
|
259
|
+
}
|
|
260
|
+
except CanonicalizationError as error:
|
|
261
|
+
return {
|
|
262
|
+
"accepted": False,
|
|
263
|
+
"canonical": None,
|
|
264
|
+
"digest": None,
|
|
265
|
+
"error_codes": [error.code],
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def main() -> int:
|
|
270
|
+
if len(sys.argv) != 3:
|
|
271
|
+
print(
|
|
272
|
+
"usage: canonicalize.py INPUT.json OUTPUT.json",
|
|
273
|
+
file=sys.stderr,
|
|
274
|
+
)
|
|
275
|
+
return 2
|
|
276
|
+
|
|
277
|
+
source = Path(sys.argv[1])
|
|
278
|
+
target = Path(sys.argv[2])
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
raw = source.read_bytes()
|
|
282
|
+
except OSError as error:
|
|
283
|
+
print(f"cannot read input: {error}", file=sys.stderr)
|
|
284
|
+
return 2
|
|
285
|
+
|
|
286
|
+
receipt = make_receipt(raw)
|
|
287
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
target.write_bytes(canonical_bytes(receipt) + b"\n")
|
|
289
|
+
return 0
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
raise SystemExit(main())
|