agec 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agec-0.1.0/PKG-INFO +206 -0
- agec-0.1.0/README.md +179 -0
- agec-0.1.0/pyproject.toml +50 -0
- agec-0.1.0/setup.cfg +4 -0
- agec-0.1.0/src/agec/__init__.py +44 -0
- agec-0.1.0/src/agec/audit.py +114 -0
- agec-0.1.0/src/agec/core.py +195 -0
- agec-0.1.0/src/agec/guard.py +129 -0
- agec-0.1.0/src/agec/policies.py +79 -0
- agec-0.1.0/src/agec/validator.py +145 -0
- agec-0.1.0/src/agec.egg-info/PKG-INFO +206 -0
- agec-0.1.0/src/agec.egg-info/SOURCES.txt +14 -0
- agec-0.1.0/src/agec.egg-info/dependency_links.txt +1 -0
- agec-0.1.0/src/agec.egg-info/requires.txt +10 -0
- agec-0.1.0/src/agec.egg-info/top_level.txt +1 -0
- agec-0.1.0/tests/test_agec.py +318 -0
agec-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agec
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Authorized Governance Execution Context for AI agents
|
|
5
|
+
Author-email: Onur Esmercan <onuresmercan@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/onur-esmercan/agec
|
|
8
|
+
Project-URL: Repository, https://github.com/onur-esmercan/agec
|
|
9
|
+
Keywords: ai-agents,authorization,governance,audit,security
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Provides-Extra: openai
|
|
21
|
+
Requires-Dist: openai-agents>=0.1; extra == "openai"
|
|
22
|
+
Provides-Extra: langchain
|
|
23
|
+
Requires-Dist: langchain-core>=0.2; extra == "langchain"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# AGEC
|
|
29
|
+
|
|
30
|
+
Pre-execution governance layer for AI agents.
|
|
31
|
+
|
|
32
|
+
Every AI agent action should be authorized before execution. AGEC validates
|
|
33
|
+
intent, semantic context, execution path, and data processing permissions
|
|
34
|
+
before any tool call is executed. It provides deterministic authorization,
|
|
35
|
+
replayable decisions, and auditability for autonomous AI agents.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install agec
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from agec import guard, AGECBlockedError
|
|
49
|
+
|
|
50
|
+
@guard(
|
|
51
|
+
intent="send_email",
|
|
52
|
+
purpose="customer_support",
|
|
53
|
+
allowed_tools=["gmail.send"],
|
|
54
|
+
legal_basis="consent",
|
|
55
|
+
)
|
|
56
|
+
def send_email() -> str:
|
|
57
|
+
return "Email sent."
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@guard(
|
|
61
|
+
intent="transfer_money",
|
|
62
|
+
purpose="unknown",
|
|
63
|
+
allowed_tools=["bank.transfer"],
|
|
64
|
+
intent_confidence=0.48, # below the 0.7 threshold
|
|
65
|
+
)
|
|
66
|
+
def unsafe_transfer() -> str:
|
|
67
|
+
return "Transferred $1M."
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
print(send_email()) # → Email sent.
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
unsafe_transfer()
|
|
74
|
+
except AGECBlockedError as exc:
|
|
75
|
+
print("AGEC BLOCKED EXECUTION")
|
|
76
|
+
print(f"Reason: {exc.reason}")
|
|
77
|
+
print(f"Audit ID: {exc.agec.agec_id}")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
If validation fails, execution is blocked **before** the wrapped function runs.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Why AGEC?
|
|
85
|
+
|
|
86
|
+
Traditional authorization systems answer:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
Can this identity access this resource?
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
AGEC answers a different question:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
Should this exact action execute right now?
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
AGEC introduces a mandatory governance layer between agent planning and tool
|
|
99
|
+
execution — combining intent validation, policy enforcement, and tamper-evident
|
|
100
|
+
audit logging in a single decorator.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## What AGEC Validates
|
|
105
|
+
|
|
106
|
+
| Check | What it enforces |
|
|
107
|
+
|---|---|
|
|
108
|
+
| **Intent** | Declared intent must be in the policy allowlist |
|
|
109
|
+
| **Confidence** | Intent confidence must meet the minimum threshold |
|
|
110
|
+
| **Execution path** | Every tool step must be explicitly permitted |
|
|
111
|
+
| **Purpose** | Data processing purpose must be allowed |
|
|
112
|
+
| **Legal basis** | GDPR-aligned legal basis must be declared and allowed |
|
|
113
|
+
| **Data categories** | Blocked data categories are rejected before execution |
|
|
114
|
+
| **Expiry (TTL)** | Stale contexts are cancelled automatically |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Architecture
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
User
|
|
122
|
+
│
|
|
123
|
+
AI Agent (planning)
|
|
124
|
+
│
|
|
125
|
+
AGEC ◄─── Policy + Validator
|
|
126
|
+
│ │
|
|
127
|
+
│ AuditLog ──► audit.jsonl (optional)
|
|
128
|
+
│
|
|
129
|
+
Tool Execution
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Lower-Level API
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from agec import AGEC, Intent, ExecutionPath, DataPermissions, Policy, AGECValidator
|
|
138
|
+
|
|
139
|
+
policy = Policy(
|
|
140
|
+
allowed_intents=["send_email"],
|
|
141
|
+
allowed_tools=["gmail.send"],
|
|
142
|
+
allowed_purposes=["customer_support"],
|
|
143
|
+
allowed_legal_bases=["consent", "contract"],
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
agec = AGEC(
|
|
147
|
+
intent=Intent(type="send_email", confidence=0.95),
|
|
148
|
+
context={"user_id": "123"},
|
|
149
|
+
execution_path=ExecutionPath(path_id="email_path", steps=["gmail.send"]),
|
|
150
|
+
data_permissions=DataPermissions(
|
|
151
|
+
purpose="customer_support",
|
|
152
|
+
legal_basis="consent",
|
|
153
|
+
allowed_operations=["send"],
|
|
154
|
+
data_categories=["email"],
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
validator = AGECValidator(policy)
|
|
159
|
+
result = validator.validate(agec)
|
|
160
|
+
|
|
161
|
+
print(result.allowed) # True
|
|
162
|
+
print(result.reason) # AGEC validation passed.
|
|
163
|
+
print(agec.status) # AGECStatus.ACTIVE
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Persisting the Audit Log
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from agec import AuditLog
|
|
170
|
+
|
|
171
|
+
log = AuditLog()
|
|
172
|
+
# ... pass log to AGECValidator(policy, audit_log=log) ...
|
|
173
|
+
|
|
174
|
+
# Save all recorded events to disk
|
|
175
|
+
log.save_json("audit.jsonl")
|
|
176
|
+
|
|
177
|
+
# Reload later for replay or compliance review
|
|
178
|
+
restored = AuditLog.load_json("audit.jsonl")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Roadmap
|
|
184
|
+
|
|
185
|
+
- [ ] OpenAI Agents SDK adapter
|
|
186
|
+
- [ ] LangGraph adapter
|
|
187
|
+
- [ ] CrewAI adapter
|
|
188
|
+
- [ ] AutoGen adapter
|
|
189
|
+
- [ ] CLI demo runner
|
|
190
|
+
- [ ] Policy manifest (YAML/JSON) support
|
|
191
|
+
- [x] Replayable audit log (JSON persistence)
|
|
192
|
+
- [x] Deterministic execution path hashing
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Contributing
|
|
197
|
+
|
|
198
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
199
|
+
|
|
200
|
+
## Changelog
|
|
201
|
+
|
|
202
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
Apache-2.0
|
agec-0.1.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# AGEC
|
|
2
|
+
|
|
3
|
+
Pre-execution governance layer for AI agents.
|
|
4
|
+
|
|
5
|
+
Every AI agent action should be authorized before execution. AGEC validates
|
|
6
|
+
intent, semantic context, execution path, and data processing permissions
|
|
7
|
+
before any tool call is executed. It provides deterministic authorization,
|
|
8
|
+
replayable decisions, and auditability for autonomous AI agents.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install agec
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from agec import guard, AGECBlockedError
|
|
22
|
+
|
|
23
|
+
@guard(
|
|
24
|
+
intent="send_email",
|
|
25
|
+
purpose="customer_support",
|
|
26
|
+
allowed_tools=["gmail.send"],
|
|
27
|
+
legal_basis="consent",
|
|
28
|
+
)
|
|
29
|
+
def send_email() -> str:
|
|
30
|
+
return "Email sent."
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@guard(
|
|
34
|
+
intent="transfer_money",
|
|
35
|
+
purpose="unknown",
|
|
36
|
+
allowed_tools=["bank.transfer"],
|
|
37
|
+
intent_confidence=0.48, # below the 0.7 threshold
|
|
38
|
+
)
|
|
39
|
+
def unsafe_transfer() -> str:
|
|
40
|
+
return "Transferred $1M."
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
print(send_email()) # → Email sent.
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
unsafe_transfer()
|
|
47
|
+
except AGECBlockedError as exc:
|
|
48
|
+
print("AGEC BLOCKED EXECUTION")
|
|
49
|
+
print(f"Reason: {exc.reason}")
|
|
50
|
+
print(f"Audit ID: {exc.agec.agec_id}")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If validation fails, execution is blocked **before** the wrapped function runs.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Why AGEC?
|
|
58
|
+
|
|
59
|
+
Traditional authorization systems answer:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
Can this identity access this resource?
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
AGEC answers a different question:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
Should this exact action execute right now?
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
AGEC introduces a mandatory governance layer between agent planning and tool
|
|
72
|
+
execution — combining intent validation, policy enforcement, and tamper-evident
|
|
73
|
+
audit logging in a single decorator.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## What AGEC Validates
|
|
78
|
+
|
|
79
|
+
| Check | What it enforces |
|
|
80
|
+
|---|---|
|
|
81
|
+
| **Intent** | Declared intent must be in the policy allowlist |
|
|
82
|
+
| **Confidence** | Intent confidence must meet the minimum threshold |
|
|
83
|
+
| **Execution path** | Every tool step must be explicitly permitted |
|
|
84
|
+
| **Purpose** | Data processing purpose must be allowed |
|
|
85
|
+
| **Legal basis** | GDPR-aligned legal basis must be declared and allowed |
|
|
86
|
+
| **Data categories** | Blocked data categories are rejected before execution |
|
|
87
|
+
| **Expiry (TTL)** | Stale contexts are cancelled automatically |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Architecture
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
User
|
|
95
|
+
│
|
|
96
|
+
AI Agent (planning)
|
|
97
|
+
│
|
|
98
|
+
AGEC ◄─── Policy + Validator
|
|
99
|
+
│ │
|
|
100
|
+
│ AuditLog ──► audit.jsonl (optional)
|
|
101
|
+
│
|
|
102
|
+
Tool Execution
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Lower-Level API
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from agec import AGEC, Intent, ExecutionPath, DataPermissions, Policy, AGECValidator
|
|
111
|
+
|
|
112
|
+
policy = Policy(
|
|
113
|
+
allowed_intents=["send_email"],
|
|
114
|
+
allowed_tools=["gmail.send"],
|
|
115
|
+
allowed_purposes=["customer_support"],
|
|
116
|
+
allowed_legal_bases=["consent", "contract"],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
agec = AGEC(
|
|
120
|
+
intent=Intent(type="send_email", confidence=0.95),
|
|
121
|
+
context={"user_id": "123"},
|
|
122
|
+
execution_path=ExecutionPath(path_id="email_path", steps=["gmail.send"]),
|
|
123
|
+
data_permissions=DataPermissions(
|
|
124
|
+
purpose="customer_support",
|
|
125
|
+
legal_basis="consent",
|
|
126
|
+
allowed_operations=["send"],
|
|
127
|
+
data_categories=["email"],
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
validator = AGECValidator(policy)
|
|
132
|
+
result = validator.validate(agec)
|
|
133
|
+
|
|
134
|
+
print(result.allowed) # True
|
|
135
|
+
print(result.reason) # AGEC validation passed.
|
|
136
|
+
print(agec.status) # AGECStatus.ACTIVE
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Persisting the Audit Log
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from agec import AuditLog
|
|
143
|
+
|
|
144
|
+
log = AuditLog()
|
|
145
|
+
# ... pass log to AGECValidator(policy, audit_log=log) ...
|
|
146
|
+
|
|
147
|
+
# Save all recorded events to disk
|
|
148
|
+
log.save_json("audit.jsonl")
|
|
149
|
+
|
|
150
|
+
# Reload later for replay or compliance review
|
|
151
|
+
restored = AuditLog.load_json("audit.jsonl")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Roadmap
|
|
157
|
+
|
|
158
|
+
- [ ] OpenAI Agents SDK adapter
|
|
159
|
+
- [ ] LangGraph adapter
|
|
160
|
+
- [ ] CrewAI adapter
|
|
161
|
+
- [ ] AutoGen adapter
|
|
162
|
+
- [ ] CLI demo runner
|
|
163
|
+
- [ ] Policy manifest (YAML/JSON) support
|
|
164
|
+
- [x] Replayable audit log (JSON persistence)
|
|
165
|
+
- [x] Deterministic execution path hashing
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Contributing
|
|
170
|
+
|
|
171
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
172
|
+
|
|
173
|
+
## Changelog
|
|
174
|
+
|
|
175
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
Apache-2.0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agec"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Authorized Governance Execution Context for AI agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "Apache-2.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Onur Esmercan", email = "onuresmercan@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
keywords = ["ai-agents", "authorization", "governance", "audit", "security"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
"Topic :: Security",
|
|
21
|
+
]
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
openai = ["openai-agents>=0.1"]
|
|
26
|
+
langchain = ["langchain-core>=0.2"]
|
|
27
|
+
dev = ["pytest>=8.0", "pytest-cov"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/onur-esmercan/agec"
|
|
31
|
+
Repository = "https://github.com/onur-esmercan/agec"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["setuptools>=68", "wheel"]
|
|
35
|
+
build-backend = "setuptools.build_meta"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
addopts = "-v --tb=short"
|
|
43
|
+
|
|
44
|
+
[tool.coverage.run]
|
|
45
|
+
source = ["agec"]
|
|
46
|
+
branch = true
|
|
47
|
+
|
|
48
|
+
[tool.coverage.report]
|
|
49
|
+
show_missing = true
|
|
50
|
+
fail_under = 80
|
agec-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""AGEC — Authorized Governance Execution Context.
|
|
2
|
+
|
|
3
|
+
A pre-execution governance layer for AI agents. Every agent action is
|
|
4
|
+
validated for intent, semantic context, execution path, and data
|
|
5
|
+
processing permissions before the tool call is allowed to run.
|
|
6
|
+
|
|
7
|
+
Quick start::
|
|
8
|
+
|
|
9
|
+
from agec import guard
|
|
10
|
+
|
|
11
|
+
@guard(
|
|
12
|
+
intent="send_email",
|
|
13
|
+
purpose="customer_support",
|
|
14
|
+
allowed_tools=["gmail.send"],
|
|
15
|
+
)
|
|
16
|
+
def send_email() -> str:
|
|
17
|
+
return "Email sent."
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .audit import AuditEvent, AuditLog
|
|
21
|
+
from .core import AGEC, AGECStatus, AGECTransitionError, DataPermissions, ExecutionPath, Intent
|
|
22
|
+
from .guard import AGECBlockedError, guard
|
|
23
|
+
from .policies import DEFAULT_LEGAL_BASES, Policy
|
|
24
|
+
from .validator import AGECValidator, ValidationResult, validate
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AGEC",
|
|
30
|
+
"AGECStatus",
|
|
31
|
+
"AGECTransitionError",
|
|
32
|
+
"AGECBlockedError",
|
|
33
|
+
"AGECValidator",
|
|
34
|
+
"AuditEvent",
|
|
35
|
+
"AuditLog",
|
|
36
|
+
"DEFAULT_LEGAL_BASES",
|
|
37
|
+
"DataPermissions",
|
|
38
|
+
"ExecutionPath",
|
|
39
|
+
"Intent",
|
|
40
|
+
"Policy",
|
|
41
|
+
"ValidationResult",
|
|
42
|
+
"guard",
|
|
43
|
+
"validate",
|
|
44
|
+
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Audit logging for AGEC governance decisions.
|
|
2
|
+
|
|
3
|
+
Records every validation allow/deny event with a timestamp and
|
|
4
|
+
structured metadata. Events can be persisted to and loaded from
|
|
5
|
+
JSON files for replay and compliance auditing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import asdict, dataclass
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class AuditEvent:
|
|
19
|
+
"""A single governance decision recorded by AGEC.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
agec_id: UUID of the AGEC context that produced this event.
|
|
23
|
+
event_type: Dot-separated event type, e.g. ``validation.allowed``.
|
|
24
|
+
message: Human-readable description of the outcome.
|
|
25
|
+
metadata: Arbitrary structured data attached to the event.
|
|
26
|
+
timestamp: ISO-8601 UTC timestamp of when the event was recorded.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
agec_id: str
|
|
30
|
+
event_type: str
|
|
31
|
+
message: str
|
|
32
|
+
metadata: dict[str, Any]
|
|
33
|
+
timestamp: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AuditLog:
|
|
37
|
+
"""Append-only log of :class:`AuditEvent` records.
|
|
38
|
+
|
|
39
|
+
Events are stored in memory and can optionally be persisted to a
|
|
40
|
+
newline-delimited JSON file (one JSON object per line) for replay
|
|
41
|
+
and compliance use-cases.
|
|
42
|
+
|
|
43
|
+
Example::
|
|
44
|
+
|
|
45
|
+
log = AuditLog()
|
|
46
|
+
log.record("abc-123", "validation.allowed", "Passed.", {"intent": "send_email"})
|
|
47
|
+
log.save_json("audit.jsonl")
|
|
48
|
+
restored = AuditLog.load_json("audit.jsonl")
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
self.events: list[AuditEvent] = []
|
|
53
|
+
|
|
54
|
+
def record(
|
|
55
|
+
self,
|
|
56
|
+
agec_id: str,
|
|
57
|
+
event_type: str,
|
|
58
|
+
message: str,
|
|
59
|
+
metadata: dict[str, Any] | None = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Append a new event to the log.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
agec_id: UUID of the originating AGEC context.
|
|
65
|
+
event_type: Dot-separated event identifier.
|
|
66
|
+
message: Human-readable outcome description.
|
|
67
|
+
metadata: Optional structured data to attach.
|
|
68
|
+
"""
|
|
69
|
+
self.events.append(
|
|
70
|
+
AuditEvent(
|
|
71
|
+
agec_id=agec_id,
|
|
72
|
+
event_type=event_type,
|
|
73
|
+
message=message,
|
|
74
|
+
metadata=metadata or {},
|
|
75
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def to_list(self) -> list[dict[str, Any]]:
|
|
80
|
+
"""Return all events as a list of plain dicts."""
|
|
81
|
+
return [asdict(event) for event in self.events]
|
|
82
|
+
|
|
83
|
+
def save_json(self, path: str | Path, *, append: bool = False) -> None:
|
|
84
|
+
"""Persist events to a newline-delimited JSON file.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
path: File path to write to.
|
|
88
|
+
append: If ``True``, append to an existing file instead of
|
|
89
|
+
overwriting it. Useful for long-running processes.
|
|
90
|
+
"""
|
|
91
|
+
mode = "a" if append else "w"
|
|
92
|
+
with open(path, mode, encoding="utf-8") as fh:
|
|
93
|
+
for event in self.events:
|
|
94
|
+
fh.write(json.dumps(asdict(event), ensure_ascii=False) + "\n")
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def load_json(cls, path: str | Path) -> "AuditLog":
|
|
98
|
+
"""Load events from a previously saved newline-delimited JSON file.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
path: File path to read from.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
A new :class:`AuditLog` pre-populated with the stored events.
|
|
105
|
+
"""
|
|
106
|
+
log = cls()
|
|
107
|
+
with open(path, encoding="utf-8") as fh:
|
|
108
|
+
for line in fh:
|
|
109
|
+
line = line.strip()
|
|
110
|
+
if not line:
|
|
111
|
+
continue
|
|
112
|
+
data = json.loads(line)
|
|
113
|
+
log.events.append(AuditEvent(**data))
|
|
114
|
+
return log
|