axiomgate-kernel 0.10.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.
- axiomgate_kernel-0.10.0/LICENSE +149 -0
- axiomgate_kernel-0.10.0/PKG-INFO +436 -0
- axiomgate_kernel-0.10.0/README.md +411 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/__init__.py +91 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/approval.py +554 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/audit.py +439 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/authentication.py +221 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/authorization.py +271 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/canonical.py +29 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/canonical_request.py +113 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/capability.py +238 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/client.py +68 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/config.py +238 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/crypto.py +41 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/decision.py +28 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/domain.py +127 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/escalation.py +132 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/evidence.py +283 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/execution.py +227 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/grant.py +270 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/mediator.py +891 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/observation.py +260 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/persistence.py +507 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/policy.py +80 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/principal.py +17 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/principal_context.py +340 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/provenance.py +128 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/provisioning.py +64 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/redaction.py +195 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/serializers.py +202 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel/strict.py +238 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/PKG-INFO +436 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/SOURCES.txt +57 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/dependency_links.txt +1 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/requires.txt +4 -0
- axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/top_level.txt +1 -0
- axiomgate_kernel-0.10.0/pyproject.toml +47 -0
- axiomgate_kernel-0.10.0/setup.cfg +4 -0
- axiomgate_kernel-0.10.0/tests/test_approval.py +617 -0
- axiomgate_kernel-0.10.0/tests/test_audit.py +197 -0
- axiomgate_kernel-0.10.0/tests/test_audit_anchoring.py +234 -0
- axiomgate_kernel-0.10.0/tests/test_authentication.py +257 -0
- axiomgate_kernel-0.10.0/tests/test_authorization.py +128 -0
- axiomgate_kernel-0.10.0/tests/test_capability.py +253 -0
- axiomgate_kernel-0.10.0/tests/test_escalation.py +82 -0
- axiomgate_kernel-0.10.0/tests/test_evidence.py +137 -0
- axiomgate_kernel-0.10.0/tests/test_execution.py +358 -0
- axiomgate_kernel-0.10.0/tests/test_grant.py +232 -0
- axiomgate_kernel-0.10.0/tests/test_mediator.py +226 -0
- axiomgate_kernel-0.10.0/tests/test_mediator_failclosed.py +184 -0
- axiomgate_kernel-0.10.0/tests/test_no_embedded_identity.py +86 -0
- axiomgate_kernel-0.10.0/tests/test_observation.py +272 -0
- axiomgate_kernel-0.10.0/tests/test_persistence.py +477 -0
- axiomgate_kernel-0.10.0/tests/test_policy.py +51 -0
- axiomgate_kernel-0.10.0/tests/test_principal_context.py +80 -0
- axiomgate_kernel-0.10.0/tests/test_provenance.py +37 -0
- axiomgate_kernel-0.10.0/tests/test_provenance_ceiling.py +489 -0
- axiomgate_kernel-0.10.0/tests/test_redaction.py +79 -0
- axiomgate_kernel-0.10.0/tests/test_strict.py +289 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Required Notice: Copyright (c) 2026 Robin Svensson
|
|
2
|
+
|
|
3
|
+
AxiomGate Kernel is source-available. It is licensed under the PolyForm
|
|
4
|
+
Noncommercial License 1.0.0, reproduced in full below. Any noncommercial
|
|
5
|
+
purpose is permitted: read it, run it, test it, study it. Commercial use of
|
|
6
|
+
any kind requires a separate written agreement with the copyright holder.
|
|
7
|
+
|
|
8
|
+
To ask a technical question, open an issue on the repository. To discuss
|
|
9
|
+
commercial use, integration, or a licence, write to the address below —
|
|
10
|
+
GitHub has no private messaging, and a commercial enquiry does not belong
|
|
11
|
+
in a public issue thread.
|
|
12
|
+
|
|
13
|
+
Contact: robinsvensson493@gmail.com
|
|
14
|
+
|
|
15
|
+
The full text of the licence follows. Nothing above modifies it.
|
|
16
|
+
|
|
17
|
+
--------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# PolyForm Noncommercial License 1.0.0
|
|
20
|
+
|
|
21
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
22
|
+
|
|
23
|
+
## Acceptance
|
|
24
|
+
|
|
25
|
+
In order to get any license under these terms, you must agree
|
|
26
|
+
to them as both strict obligations and conditions to all
|
|
27
|
+
your licenses.
|
|
28
|
+
|
|
29
|
+
## Copyright License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a copyright license for the
|
|
32
|
+
software to do everything you might do with the software
|
|
33
|
+
that would otherwise infringe the licensor's copyright
|
|
34
|
+
in it for any permitted purpose. However, you may
|
|
35
|
+
only distribute the software according to [Distribution
|
|
36
|
+
License](#distribution-license) and make changes or new works
|
|
37
|
+
based on the software according to [Changes and New Works
|
|
38
|
+
License](#changes-and-new-works-license).
|
|
39
|
+
|
|
40
|
+
## Distribution License
|
|
41
|
+
|
|
42
|
+
The licensor grants you an additional copyright license
|
|
43
|
+
to distribute copies of the software. Your license
|
|
44
|
+
to distribute covers distributing the software with
|
|
45
|
+
changes and new works permitted by [Changes and New Works
|
|
46
|
+
License](#changes-and-new-works-license).
|
|
47
|
+
|
|
48
|
+
## Notices
|
|
49
|
+
|
|
50
|
+
You must ensure that anyone who gets a copy of any part of
|
|
51
|
+
the software from you also gets a copy of these terms or the
|
|
52
|
+
URL for them above, as well as copies of any plain-text lines
|
|
53
|
+
beginning with `Required Notice:` that the licensor provided
|
|
54
|
+
with the software. For example:
|
|
55
|
+
|
|
56
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
57
|
+
|
|
58
|
+
## Changes and New Works License
|
|
59
|
+
|
|
60
|
+
The licensor grants you an additional copyright license to
|
|
61
|
+
make changes and new works based on the software for any
|
|
62
|
+
permitted purpose.
|
|
63
|
+
|
|
64
|
+
## Patent License
|
|
65
|
+
|
|
66
|
+
The licensor grants you a patent license for the software that
|
|
67
|
+
covers patent claims the licensor can license, or becomes able
|
|
68
|
+
to license, that you would infringe by using the software.
|
|
69
|
+
|
|
70
|
+
## Noncommercial Purposes
|
|
71
|
+
|
|
72
|
+
Any noncommercial purpose is a permitted purpose.
|
|
73
|
+
|
|
74
|
+
## Personal Uses
|
|
75
|
+
|
|
76
|
+
Personal use for research, experiment, and testing for
|
|
77
|
+
the benefit of public knowledge, personal study, private
|
|
78
|
+
entertainment, hobby projects, amateur pursuits, or religious
|
|
79
|
+
observance, without any anticipated commercial application,
|
|
80
|
+
is use for a permitted purpose.
|
|
81
|
+
|
|
82
|
+
## Noncommercial Organizations
|
|
83
|
+
|
|
84
|
+
Use by any charitable organization, educational institution,
|
|
85
|
+
public research organization, public safety or health
|
|
86
|
+
organization, environmental protection organization,
|
|
87
|
+
or government institution is use for a permitted purpose
|
|
88
|
+
regardless of the source of funding or obligations resulting
|
|
89
|
+
from the funding.
|
|
90
|
+
|
|
91
|
+
## Fair Use
|
|
92
|
+
|
|
93
|
+
You may have "fair use" rights for the software under the
|
|
94
|
+
law. These terms do not limit them.
|
|
95
|
+
|
|
96
|
+
## No Other Rights
|
|
97
|
+
|
|
98
|
+
These terms do not allow you to sublicense or transfer any of
|
|
99
|
+
your licenses to anyone else, or prevent the licensor from
|
|
100
|
+
granting licenses to anyone else. These terms do not imply
|
|
101
|
+
any other licenses.
|
|
102
|
+
|
|
103
|
+
## Patent Defense
|
|
104
|
+
|
|
105
|
+
If you make any written claim that the software infringes or
|
|
106
|
+
contributes to infringement of any patent, your patent license
|
|
107
|
+
for the software granted under these terms ends immediately. If
|
|
108
|
+
your company makes such a claim, your patent license ends
|
|
109
|
+
immediately for work on behalf of your company.
|
|
110
|
+
|
|
111
|
+
## Violations
|
|
112
|
+
|
|
113
|
+
The first time you are notified in writing that you have
|
|
114
|
+
violated any of these terms, or done anything with the software
|
|
115
|
+
not covered by your licenses, your licenses can nonetheless
|
|
116
|
+
continue if you come into full compliance with these terms,
|
|
117
|
+
and take practical steps to correct past violations, within
|
|
118
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
119
|
+
end immediately.
|
|
120
|
+
|
|
121
|
+
## No Liability
|
|
122
|
+
|
|
123
|
+
***As far as the law allows, the software comes as is, without
|
|
124
|
+
any warranty or condition, and the licensor will not be liable
|
|
125
|
+
to you for any damages arising out of these terms or the use
|
|
126
|
+
or nature of the software, under any kind of legal claim.***
|
|
127
|
+
|
|
128
|
+
## Definitions
|
|
129
|
+
|
|
130
|
+
The **licensor** is the individual or entity offering these
|
|
131
|
+
terms, and the **software** is the software the licensor makes
|
|
132
|
+
available under these terms.
|
|
133
|
+
|
|
134
|
+
**You** refers to the individual or entity agreeing to these
|
|
135
|
+
terms.
|
|
136
|
+
|
|
137
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
138
|
+
or other kind of organization that you work for, plus all
|
|
139
|
+
organizations that have control over, are under the control of,
|
|
140
|
+
or are under common control with that organization. **Control**
|
|
141
|
+
means ownership of substantially all the assets of an entity,
|
|
142
|
+
or the power to direct its management and policies by vote,
|
|
143
|
+
contract, or otherwise. Control can be direct or indirect.
|
|
144
|
+
|
|
145
|
+
**Your licenses** are all the licenses granted to you for the
|
|
146
|
+
software under these terms.
|
|
147
|
+
|
|
148
|
+
**Use** means anything you do with the software requiring one
|
|
149
|
+
of your licenses.
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: axiomgate-kernel
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: AxiomGate Kernel — deterministic, fail-closed authorization and audit for AI agents
|
|
5
|
+
Author-email: Robin Svensson <228113985+robin-svensson@users.noreply.github.com>
|
|
6
|
+
License-Expression: PolyForm-Noncommercial-1.0.0
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Intended Audience :: Information Technology
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: cryptography>=41.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# AxiomGate Kernel
|
|
27
|
+
|
|
28
|
+
**Your AI agents can act. This decides whether they may.**
|
|
29
|
+
|
|
30
|
+
AxiomGate Kernel is a deterministic, fail-closed authorization and audit kernel for AI agents.
|
|
31
|
+
An agent does not call the tool — it asks the kernel, and the kernel answers `PERMIT`,
|
|
32
|
+
`DENY` or `ESCALATE`. Every answer is signed into an append-only, tamper-evident
|
|
33
|
+
chain. Nothing that goes wrong inside the kernel can produce a permission — not a
|
|
34
|
+
failing audit backend, and not an injected dependency that misbehaves.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
decision = mediator.evaluate(signed_request)
|
|
38
|
+
|
|
39
|
+
if decision.verdict is not Verdict.PERMIT:
|
|
40
|
+
return refuse(decision.reason) # DENY, or ESCALATE pending the owner
|
|
41
|
+
proceed(decision.grant)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## The four protections
|
|
47
|
+
|
|
48
|
+
| | What it means | Where |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| **Capability control** | An agent can only do what it holds an explicit, unexpired, scoped capability for — principal, domain, action type and risk ceiling all have to match. | `authorization.py` |
|
|
51
|
+
| **Audit chain** | Every decision is appended to an HMAC-chained log. The chain does not prevent an edit; it makes an edit impossible to hide. Each entry carries its position inside the hashed body, so a cut-short tail is detectable against an external anchor. Secrets are redacted at the write point, before hashing — by credential *format* and by field *name*. | `audit.py` |
|
|
52
|
+
| **Producer separation** | Whoever produces evidence cannot verify it, and cannot be its final authority. Roles are enforced, not advisory. | `evidence.py` |
|
|
53
|
+
| **Commit gate** | Actions in `OWNER_MANDATORY_ACTIONS` (today: `COMMIT`) always escalate to the owner, regardless of capability. | `authorization.py`, `domain.py` |
|
|
54
|
+
|
|
55
|
+
Plus two mechanisms that are not reconstructable from off-the-shelf parts:
|
|
56
|
+
|
|
57
|
+
- **Atomic grant consumption** — `grant.py:consume_if_valid` redeems an
|
|
58
|
+
escalate→decide→re-enter cycle exactly once, and only if **eleven** bound fields
|
|
59
|
+
still match: principal, agent, request, action, domain, risk, payload hash,
|
|
60
|
+
capability id, capability scope hash, policy hash and provenance identity. When
|
|
61
|
+
the escalation was itself *caused* by a policy or provenance mismatch, **three**
|
|
62
|
+
of the eleven are skipped — that dimension plus the two capability bindings,
|
|
63
|
+
since the owner's ruling routinely comes with a reissued capability — leaving
|
|
64
|
+
**eight**. Re-entry still re-runs the live capability check and requires ALLOW,
|
|
65
|
+
so the exception drops the binding to one specific capability, never the
|
|
66
|
+
requirement to hold a valid one. A grant cannot be replayed and cannot survive an
|
|
67
|
+
unrelated policy change.
|
|
68
|
+
- **Three-state evidence machine** — `CANDIDATE → VERIFIED → AUTHORIZED`, one-way,
|
|
69
|
+
with enforced R-IV / R-DEC roles and no retraction path.
|
|
70
|
+
|
|
71
|
+
**All four protections have been mutation-tested** (2026-09-09): each check was
|
|
72
|
+
removed in memory and the scenario re-run. Three of the four have no second
|
|
73
|
+
barrier — see [docs/TRACEABILITY.md](docs/TRACEABILITY.md) for exactly which.
|
|
74
|
+
|
|
75
|
+
Two later mechanisms were mutation-tested on the same terms: the provenance-bounded
|
|
76
|
+
risk ceiling (`scripts/mutate_r1.py`) and the externally anchored audit chain
|
|
77
|
+
(`scripts/mutate_r2.py`). Both are **opt-in** — `require_principal_context=True` and
|
|
78
|
+
`AuditLog(..., anchor=...)` respectively — and two of three checks in each have no
|
|
79
|
+
second barrier. What that means, and why the default is the weaker one, is written out
|
|
80
|
+
in [docs/ROADMAP.md](docs/ROADMAP.md) R1 and R2. Since 2026-09-11 a kernel can be asked
|
|
81
|
+
which of the two it actually has, and built through a door that cannot produce half of
|
|
82
|
+
them — `strictness_report` and `strict_mediator`, R3.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Quick start
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
git clone https://github.com/robin-svensson/axiomgate-kernel.git
|
|
90
|
+
cd axiomgate-kernel
|
|
91
|
+
pip install -e ".[dev]"
|
|
92
|
+
pytest -q # 346 tests
|
|
93
|
+
python examples/01_permit.py # a request that is allowed
|
|
94
|
+
python examples/02_deny.py # three that are refused, three different ways
|
|
95
|
+
python examples/03_audit_trail.py # tampering with the log, and being caught
|
|
96
|
+
bash scripts/verify_claims.sh # every number in this README, checked mechanically
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The distribution is `axiomgate-kernel`; the import is `axiomgate_kernel`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from axiomgate_kernel import Mediator, AuditLog, Verdict
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
One runtime dependency (`cryptography`) and one for development (`pytest`). The
|
|
106
|
+
suite runs in a fresh virtual environment containing nothing else — 346 passed,
|
|
107
|
+
with no `PYTHONPATH` and no editable install of anything but this package — so a
|
|
108
|
+
green suite here is not an artefact of a pre-populated environment. Reproduce it
|
|
109
|
+
with `bash scripts/verify_clean_install.sh`, which builds the empty environment,
|
|
110
|
+
installs only this package, and runs the suite with `env -i`. It is kept out of
|
|
111
|
+
`verify_claims.sh` because it needs the network; a review pointed out that this
|
|
112
|
+
paragraph was the one claim here that asked to be believed.
|
|
113
|
+
|
|
114
|
+
`verify_claims.sh` is the point. Nothing in this repository asks to be believed.
|
|
115
|
+
|
|
116
|
+
### How redaction works, and where it stops
|
|
117
|
+
|
|
118
|
+
`axiomgate_kernel/redaction.py` applies two independent rules at the write point, before the
|
|
119
|
+
entry is hashed:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
audit.append({"key": "sk-AAAA…"}) # format rule -> [REDACTED_SECRET:openai-***AA]
|
|
123
|
+
audit.append({"password": "hunter2"}) # name rule -> [REDACTED_SECRET:field-name]
|
|
124
|
+
audit.append({"api_key": 1234567890}) # name rule -> masked even though it is an int
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- **By format** — fourteen credential shapes (`sk-`, `sk-ant-`, `ghp_`, `github_pat_`,
|
|
128
|
+
`AKIA…`, `AIza…`, `hf_`, Stripe, Slack, private-key headers, and `password = "…"`
|
|
129
|
+
style assignments).
|
|
130
|
+
- **By field name** — any key whose normalised name contains `password`, `passwd`,
|
|
131
|
+
`secret`, `token`, `apikey`, `authorization`, `credential`, `privatekey`,
|
|
132
|
+
`passphrase`, `sessionkey` or `accesskey`. Matching ignores case and separators, so
|
|
133
|
+
`db_secret`, `authToken` and `X-Api-Key` are all caught, and the value is masked
|
|
134
|
+
whatever its type.
|
|
135
|
+
|
|
136
|
+
The name rule is deliberately blunt. In an append-only, HMAC-chained log a leak cannot
|
|
137
|
+
be cleaned up afterwards — editing the entry breaks the chain that is the evidence — so
|
|
138
|
+
masking a harmless field is the cheaper error. Verified against the kernel's own audit
|
|
139
|
+
entries: of the 22 fields written in a real decision flow, none are masked —
|
|
140
|
+
counted by `scripts/count_audit_fields.py`, which runs a real `PERMIT` through the
|
|
141
|
+
mediator, and checked by `scripts/verify_claims.sh`. It said 17 until 2026-09-10:
|
|
142
|
+
the number was true when written and rotted when provenance and mandates added four
|
|
143
|
+
fields to the entry. A documented number that nothing re-counts only records when it
|
|
144
|
+
was last read.
|
|
145
|
+
|
|
146
|
+
**Where it still stops:** a secret under an innocuous key, in a shape no pattern knows —
|
|
147
|
+
`{"note": "the door code is 4711"}` — is written verbatim. Nothing infers meaning from
|
|
148
|
+
free text. Keep credentials out of payloads.
|
|
149
|
+
|
|
150
|
+
### What the log can and cannot prove about itself
|
|
151
|
+
|
|
152
|
+
Every entry carries its position (`seq`) inside the hashed body, and `AuditLog.head()`
|
|
153
|
+
returns `(last_hash, count)`. Pass those back to `verify_chain(expected_head,
|
|
154
|
+
expected_count)` and a truncated tail is caught.
|
|
155
|
+
|
|
156
|
+
Without that anchor it is not, and cannot be. A chain cut short still verifies as
|
|
157
|
+
internally consistent — every entry that remains does link correctly to the one before
|
|
158
|
+
it. **A self-certifying log cannot prove its own length.** Store the head somewhere the
|
|
159
|
+
writer cannot reach; this is the same reason Certificate Transparency signs tree heads
|
|
160
|
+
instead of trusting a log to describe itself. How often to anchor, where to put it, and
|
|
161
|
+
what an anchor still cannot prove are in [docs/ANCHORING.md](docs/ANCHORING.md).
|
|
162
|
+
|
|
163
|
+
### Bounding authority by how a call arose
|
|
164
|
+
|
|
165
|
+
An agent's name is not its authority. `axiomgate_kernel/principal_context.py` carries the
|
|
166
|
+
*mandate* a call is running under — a `direct`, `delegated`, `cron` or `mcp` origin,
|
|
167
|
+
mandate scopes, and monotonic propagation to subagents (`child_mandate ⊆
|
|
168
|
+
parent_mandate`). Since 2026-09-10 verdicts read it:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
mediator = Mediator(..., require_principal_context=True)
|
|
172
|
+
|
|
173
|
+
with principal_context_scope(ctx): # ctx.mandate = "propose"
|
|
174
|
+
mediator.evaluate(req) # MEDIUM risk -> DENY
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The effective risk ceiling is `min(capability.risk_ceiling, mandate ceiling)`, so a
|
|
178
|
+
capability good for MEDIUM does not reach MEDIUM under a mandate that only reaches
|
|
179
|
+
LOW. A missing or invalid context is a `DENY`, never a fallback to the most
|
|
180
|
+
permissive value, and the audit record says which bound applied — `provenance`,
|
|
181
|
+
`mandate_id` and `effective_risk_ceiling`, each `None` when there is nothing to
|
|
182
|
+
record. Mutation-tested 2026-09-10 (`scripts/mutate_r1.py`): two of the three checks
|
|
183
|
+
have no second barrier.
|
|
184
|
+
|
|
185
|
+
**The flag is off by default, and with it off there is no provenance ceiling at
|
|
186
|
+
all.** Turning it on for an integrator that binds no context would deny everything.
|
|
187
|
+
That is a declared gap, held in place by a test, and its exact terms — including
|
|
188
|
+
what a `ContextVar` ceiling cannot defend against — are in
|
|
189
|
+
[docs/ROADMAP.md](docs/ROADMAP.md) R1.
|
|
190
|
+
|
|
191
|
+
## Formal verification — and its exact limits
|
|
192
|
+
|
|
193
|
+
The governance model in `formal/` is written in TLA+ and exhaustively
|
|
194
|
+
model-checked with TLC:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
INVARIANT GovernanceSecurityBoundary == I1 /\ I2 /\ ... /\ I10
|
|
198
|
+
373 933 states generated, 345 322 distinct, depth 11
|
|
199
|
+
Model checking completed. No error has been found.
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**What that does not mean.** The run is bounded to one authority, one entity,
|
|
203
|
+
two actions and two resources (`formal/GovernanceMCV6.cfg`). The Python kernel
|
|
204
|
+
was not generated from the model and has not been proven to refine it.
|
|
205
|
+
|
|
206
|
+
Until 2026-09-10 the source code cited invariant numbers that did not match what
|
|
207
|
+
the model defines. Those citations were removed rather than quietly corrected,
|
|
208
|
+
and replaced with a declared correspondence table stating, per invariant,
|
|
209
|
+
whether Python enforces it (`ENFORCED`), enforces something weaker (`PARTIAL`),
|
|
210
|
+
or cannot express it at all (`MODEL-ONLY`).
|
|
211
|
+
|
|
212
|
+
Read [docs/TRACEABILITY.md](docs/TRACEABILITY.md) before citing the formal work
|
|
213
|
+
anywhere. Authority that is referenced but not derived is worse than no
|
|
214
|
+
authority at all — and being able to say precisely where the proof stops is the
|
|
215
|
+
part competitors cannot copy.
|
|
216
|
+
|
|
217
|
+
### Checking three of the invariants at runtime
|
|
218
|
+
|
|
219
|
+
I1, I4 and I6 were marked `MODEL-ONLY` — and for one shared reason, not three. All
|
|
220
|
+
three relate the model's *observation* log to its *enforcement* log, and the kernel
|
|
221
|
+
had only the second. It already did the right thing: authentication happens before
|
|
222
|
+
any decision is dispatched. But that is a property of the source text, provable only
|
|
223
|
+
by reading it, and an invariant that holds by inspection is not enforced — it is true
|
|
224
|
+
until somebody edits the file.
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from axiomgate_kernel import ObservationLog, check_invariants
|
|
228
|
+
|
|
229
|
+
obs = ObservationLog()
|
|
230
|
+
mediator = Mediator(authenticator=…, registry=…, audit=audit, provenance=…,
|
|
231
|
+
observations=obs)
|
|
232
|
+
...
|
|
233
|
+
report = check_invariants(obs, audit.entries())
|
|
234
|
+
report["I1"]["status"] # HOLDS | PARTIAL | VIOLATED | UNOBSERVABLE
|
|
235
|
+
report["holds"] # every one of the three, or False
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Each audit record carries an `observation_seq` field joining it to the observation
|
|
239
|
+
taken before that enforcement, or `None` when the request was denied before an
|
|
240
|
+
identity existed — a missing value, not a zero.
|
|
241
|
+
|
|
242
|
+
**The four states are the point.** A check that cannot say *I don't know* will
|
|
243
|
+
eventually say *yes* when it means it. No observation log at all is `UNOBSERVABLE`,
|
|
244
|
+
not a quiet pass over an empty set; a log that saw nothing while enforcements were
|
|
245
|
+
recorded is `VIOLATED`; an `observation_seq` pointing at no record is `VIOLATED` and
|
|
246
|
+
never `PARTIAL`, because a dangling reference is a contradiction rather than missing
|
|
247
|
+
data.
|
|
248
|
+
|
|
249
|
+
The three invariants are now `PARTIAL`, not `ENFORCED`, and the difference is real:
|
|
250
|
+
the log is opt-in, it lives in memory without a MAC chain, and `_observe` swallows its
|
|
251
|
+
own exceptions on purpose — evidence about a run must not turn a legitimate PERMIT
|
|
252
|
+
into a DENY. When it fails, the enforcement counts as unobserved and I1 drops to
|
|
253
|
+
`PARTIAL`. The report gets worse, which is what should happen.
|
|
254
|
+
See [docs/ROADMAP.md](docs/ROADMAP.md) R4.
|
|
255
|
+
|
|
256
|
+
### The fourth: what a rollback does to an execution log
|
|
257
|
+
|
|
258
|
+
I5 — every execution has a matching observation — was `MODEL-ONLY` for the same single
|
|
259
|
+
reason, one log short. Execution here is the redemption of a reserved grant, and that
|
|
260
|
+
redemption wrote nothing. The `consumed` flag on the grant was not a substitute, and
|
|
261
|
+
`unconsume` is why: a redemption is rolled back when downstream audit logging fails, and a
|
|
262
|
+
log that only appended would keep reporting an execution the kernel deliberately took back.
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from axiomgate_kernel import ExecutionLog, check_execution_invariant
|
|
266
|
+
|
|
267
|
+
execs = ExecutionLog()
|
|
268
|
+
mediator = Mediator(authenticator=…, registry=…, audit=audit, provenance=…,
|
|
269
|
+
observations=obs, executions=execs)
|
|
270
|
+
...
|
|
271
|
+
report = check_execution_invariant(execs, obs)
|
|
272
|
+
report["status"] # HOLDS | PARTIAL | VIOLATED | UNOBSERVABLE
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
So the log marks rather than deletes, and the check reads three states: executed, rolled
|
|
276
|
+
back, never happened. A rolled-back record needs no observation; a live one without a
|
|
277
|
+
matching observation is `VIOLATED`. An **empty** execution log is `PARTIAL`, never `HOLDS` —
|
|
278
|
+
nothing has been contradicted, and a green answer over an empty set is the vacuous truth the
|
|
279
|
+
four states exist to refuse. A log of **nothing but rollbacks** is the same: no execution
|
|
280
|
+
stands, and the entries only make the report look substantiated.
|
|
281
|
+
|
|
282
|
+
I5 is now `PARTIAL`. The limit worth naming: the check trusts the rollback marker, because
|
|
283
|
+
nothing inside the process can outrank the code that set it.
|
|
284
|
+
See [docs/ROADMAP.md](docs/ROADMAP.md) R5.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Why this exists
|
|
289
|
+
|
|
290
|
+
Regulation now assumes something like this exists. EU AI Act **Art. 12(1)** requires
|
|
291
|
+
high-risk systems to "technically allow for the automatic recording of events (logs)
|
|
292
|
+
over the lifetime of the system"; **Art. 14(1)** requires that they "can be effectively
|
|
293
|
+
overseen by natural persons"; **Art. 19(1)** and **Art. 26(6)** require provider and
|
|
294
|
+
deployer to keep those logs "of at least six months". An agent that calls tools with
|
|
295
|
+
no enforced permission boundary and no tamper-evident record has no story to tell an
|
|
296
|
+
auditor.
|
|
297
|
+
|
|
298
|
+
Two corrections to what this README used to say, because getting them wrong is the
|
|
299
|
+
same error as citing an invariant the model does not define:
|
|
300
|
+
|
|
301
|
+
- **The penalty tier for a missing audit trail is Art. 99(4) — €15M or 3% of worldwide
|
|
302
|
+
turnover.** The €35M / 7% headline is **Art. 99(3)**, and it applies to Article 5
|
|
303
|
+
prohibited practices, not to logging or oversight failures.
|
|
304
|
+
- **The deadline is no longer 2 August 2026.** Regulation (EU) 2026/1744 deferred the
|
|
305
|
+
high-risk obligations to **2 December 2027** (Annex III) and **2 August 2028**
|
|
306
|
+
(Annex I). Prohibited practices, GPAI duties and Art. 50 transparency are unaffected
|
|
307
|
+
and already in force.
|
|
308
|
+
|
|
309
|
+
The deferral happened partly because the harmonised standards were not ready, and they
|
|
310
|
+
still are not: **no AI Act standard has been cited in the Official Journal**, so there
|
|
311
|
+
is no presumption of conformity under Art. 40 to rely on — and CEN-CENELEC names
|
|
312
|
+
logging as one of the areas still in development. An organisation picking a mechanism
|
|
313
|
+
today picks it without that cover, and has to defend it on its merits.
|
|
314
|
+
|
|
315
|
+
Every article quoted above, the evidence status of the deferral, and what could not be
|
|
316
|
+
verified are in [docs/REGULATORY.md](docs/REGULATORY.md). The Official Journal text of
|
|
317
|
+
2026/1744 was **not** read directly — EUR-Lex refused automated retrieval — so that
|
|
318
|
+
one rests on convergent secondary sources and is labelled as such.
|
|
319
|
+
|
|
320
|
+
Third-party figures, **retrieved from the publisher and quoted verbatim** on
|
|
321
|
+
2026-09-10. Earlier versions of this table paraphrased them; two of the five were
|
|
322
|
+
wrong, and both corrections are noted below.
|
|
323
|
+
|
|
324
|
+
| Figure, as the source states it | Source |
|
|
325
|
+
|---|---|
|
|
326
|
+
| "92% are concerned about the use of AI agents across the workforce and their impact on security" | *State of AI Cybersecurity Report 2026*, survey by Darktrace, published by the Cloud Security Alliance, 27 May 2026 |
|
|
327
|
+
| "92% of organizations lack full visibility into AI identities, and 95% doubt they could detect misuse if it happened" | *2026 CISO AI Risk Report*, Saviynt with Cybersecurity Insiders, 21 April 2026 (n > 200 CISOs and security leaders) |
|
|
328
|
+
| "86% don’t enforce access policies for AI identities. Only 17% govern even half of their AI identities like human users, and just 5% feel confident they could contain a compromised agent" | *ibid.* |
|
|
329
|
+
| "71% of CISOs say AI has access to core business systems, but only 16% govern that access effectively" | *ibid.* |
|
|
330
|
+
| AI-native applications are the fastest-growing spend category, "up 393% year over year in organizations with more than 10,000 employees and up 108% overall" | Zylo, *2026 SaaS Management Index*, 29 January 2026 |
|
|
331
|
+
|
|
332
|
+
Two corrections, on the same terms as the legal ones above:
|
|
333
|
+
|
|
334
|
+
- The table used to read **"16% have effective access control."** The source says 16%
|
|
335
|
+
govern *access to core business systems* effectively — a narrower claim about a
|
|
336
|
+
specific population, not a statement about access control in general.
|
|
337
|
+
- The table used to attribute **"Governance platforms: $4K–$15K/month" to Zylo Research
|
|
338
|
+
2026.** That figure does not appear in Zylo's 2026 index or its related material;
|
|
339
|
+
the attribution could not be substantiated and the row is removed rather than
|
|
340
|
+
re-sourced. AxiomGate Kernel's own pricing is not set here, and no competitor price is quoted
|
|
341
|
+
in this repository.
|
|
342
|
+
|
|
343
|
+
Adjacent work: KLA (runtime control plane), Odock (EU gateway, MCP tool
|
|
344
|
+
permissions), ServiceNow AI Control Tower, Microsoft Agent Governance Toolkit.
|
|
345
|
+
None of them ships a model-checked governance specification.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Documentation
|
|
350
|
+
|
|
351
|
+
| | |
|
|
352
|
+
|---|---|
|
|
353
|
+
| [docs/API.md](docs/API.md) | Public API, extracted from the running package |
|
|
354
|
+
| [docs/TRACEABILITY.md](docs/TRACEABILITY.md) | TLA+ ↔ Python, per invariant, with honest status |
|
|
355
|
+
| [docs/ROADMAP.md](docs/ROADMAP.md) | Declared debt: R1–R3 closed, R4 and R5 partial, each with the deviation that keeps its protection opt-in written out |
|
|
356
|
+
| [docs/REGULATORY.md](docs/REGULATORY.md) | The AI Act articles quoted verbatim, with dates, sources, and what could not be verified |
|
|
357
|
+
| [docs/ANCHORING.md](docs/ANCHORING.md) | Integration note: how to anchor the audit chain, at what cadence, and what an anchor still cannot prove |
|
|
358
|
+
| `examples/` | Three runnable examples: permit, refuse, trace |
|
|
359
|
+
| `formal/` | TLA+ specification, model config and the TLC run log |
|
|
360
|
+
| `scripts/verify_claims.sh` | Mechanical check of every claim above |
|
|
361
|
+
|
|
362
|
+
## Status and licence
|
|
363
|
+
|
|
364
|
+
Version 0.10.0. The code is mature — 346 tests, mutation-tested protections — but
|
|
365
|
+
has never run outside a development environment. Treat it as beta.
|
|
366
|
+
|
|
367
|
+
**Two protections are off until you turn them on.** The provenance ceiling requires
|
|
368
|
+
`Mediator(..., require_principal_context=True)`, and the audit chain is only checked
|
|
369
|
+
against an external anchor if you pass `anchor=` to `AuditLog` and hold that anchor
|
|
370
|
+
somewhere the writing process cannot reach. Neither default is fail-closed, and the
|
|
371
|
+
reason each was chosen is in [docs/ROADMAP.md](docs/ROADMAP.md). A deployment that
|
|
372
|
+
wires neither gets the kernel as it was before those items were closed — which is
|
|
373
|
+
still the four protections above, but not the two the roadmap describes.
|
|
374
|
+
|
|
375
|
+
**So ask the kernel which one you are running.** The two flags sit on two different
|
|
376
|
+
objects, and a half-wired kernel is indistinguishable from a whole one from the
|
|
377
|
+
inside — same records, same verdicts, same green suite. Either take the door that
|
|
378
|
+
cannot produce the half-wired case:
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
from axiomgate_kernel import NEW_LOG, strict_audit_log, strict_mediator, strictness_report
|
|
382
|
+
|
|
383
|
+
audit = strict_audit_log(path, key, NEW_LOG) # or a prior head() you kept outside
|
|
384
|
+
mediator = strict_mediator(authenticator=…, registry=…, audit=audit, provenance=…)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
or check the one you already built:
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
report = strictness_report(mediator)
|
|
391
|
+
if not report["strict"]:
|
|
392
|
+
for gap in report["gaps"]:
|
|
393
|
+
log.warning("axiomgate: %s", gap)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`strict_audit_log` has no default for its anchor argument, and `strict_mediator`
|
|
397
|
+
refuses to build on an unanchored log or to have the ceiling turned off. Neither
|
|
398
|
+
changes any default: import nothing from `strict` and the kernel behaves exactly as
|
|
399
|
+
it did.
|
|
400
|
+
|
|
401
|
+
The report is **self-reporting, not verification**. `AuditLog.anchored` is an ordinary
|
|
402
|
+
writable attribute, so a caller who sets it by hand gets a clean report. This closes the
|
|
403
|
+
gap where an honest integrator cannot tell which kernel they are running; it is not a
|
|
404
|
+
defence against code lying about its own audit trail, and nothing in-process could be.
|
|
405
|
+
See [docs/ROADMAP.md](docs/ROADMAP.md) R3.
|
|
406
|
+
|
|
407
|
+
## The linter, for what happens before runtime
|
|
408
|
+
|
|
409
|
+
The kernel enforces attenuation at the moment a call is made. It cannot tell you
|
|
410
|
+
that your codebase is full of delegation that will hit it. That is a static
|
|
411
|
+
question, and it has its own tool:
|
|
412
|
+
[**axiomgate-lint**](https://github.com/robin-svensson/axiomgate-lint) — a
|
|
413
|
+
dependency-free AST linter that finds unattenuated agent delegation in source,
|
|
414
|
+
with a GitHub Action. It is MIT, it is genuinely separate, and it is useful
|
|
415
|
+
without this kernel.
|
|
416
|
+
|
|
417
|
+
## Licence and contact
|
|
418
|
+
|
|
419
|
+
**Source-available, not open source.** AxiomGate Kernel is licensed under the
|
|
420
|
+
[PolyForm Noncommercial License 1.0.0](LICENSE). Any noncommercial purpose is
|
|
421
|
+
permitted — read it, run it, test it against your own agents, study how the
|
|
422
|
+
authorization and audit paths are built. Commercial use of any kind requires a
|
|
423
|
+
separate written agreement with the copyright holder.
|
|
424
|
+
|
|
425
|
+
That split is deliberate. A kernel that decides what an agent may do is worth
|
|
426
|
+
nothing to you if you cannot read it, so it is here to be read and tested. What
|
|
427
|
+
it is not is free to build a product on.
|
|
428
|
+
|
|
429
|
+
- **Technical questions, bugs, findings** — open an issue. Preferably with the
|
|
430
|
+
command you ran and what came back; the repo's own claims are checked that way
|
|
431
|
+
too (`scripts/verify_claims.sh`).
|
|
432
|
+
- **Commercial use, integration, or a licence** — write to the address below.
|
|
433
|
+
GitHub has no private messaging, and a commercial enquiry does not belong in a
|
|
434
|
+
public issue thread.
|
|
435
|
+
|
|
436
|
+
Robin Svensson · robinsvensson493@gmail.com
|