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.
Files changed (59) hide show
  1. axiomgate_kernel-0.10.0/LICENSE +149 -0
  2. axiomgate_kernel-0.10.0/PKG-INFO +436 -0
  3. axiomgate_kernel-0.10.0/README.md +411 -0
  4. axiomgate_kernel-0.10.0/axiomgate_kernel/__init__.py +91 -0
  5. axiomgate_kernel-0.10.0/axiomgate_kernel/approval.py +554 -0
  6. axiomgate_kernel-0.10.0/axiomgate_kernel/audit.py +439 -0
  7. axiomgate_kernel-0.10.0/axiomgate_kernel/authentication.py +221 -0
  8. axiomgate_kernel-0.10.0/axiomgate_kernel/authorization.py +271 -0
  9. axiomgate_kernel-0.10.0/axiomgate_kernel/canonical.py +29 -0
  10. axiomgate_kernel-0.10.0/axiomgate_kernel/canonical_request.py +113 -0
  11. axiomgate_kernel-0.10.0/axiomgate_kernel/capability.py +238 -0
  12. axiomgate_kernel-0.10.0/axiomgate_kernel/client.py +68 -0
  13. axiomgate_kernel-0.10.0/axiomgate_kernel/config.py +238 -0
  14. axiomgate_kernel-0.10.0/axiomgate_kernel/crypto.py +41 -0
  15. axiomgate_kernel-0.10.0/axiomgate_kernel/decision.py +28 -0
  16. axiomgate_kernel-0.10.0/axiomgate_kernel/domain.py +127 -0
  17. axiomgate_kernel-0.10.0/axiomgate_kernel/escalation.py +132 -0
  18. axiomgate_kernel-0.10.0/axiomgate_kernel/evidence.py +283 -0
  19. axiomgate_kernel-0.10.0/axiomgate_kernel/execution.py +227 -0
  20. axiomgate_kernel-0.10.0/axiomgate_kernel/grant.py +270 -0
  21. axiomgate_kernel-0.10.0/axiomgate_kernel/mediator.py +891 -0
  22. axiomgate_kernel-0.10.0/axiomgate_kernel/observation.py +260 -0
  23. axiomgate_kernel-0.10.0/axiomgate_kernel/persistence.py +507 -0
  24. axiomgate_kernel-0.10.0/axiomgate_kernel/policy.py +80 -0
  25. axiomgate_kernel-0.10.0/axiomgate_kernel/principal.py +17 -0
  26. axiomgate_kernel-0.10.0/axiomgate_kernel/principal_context.py +340 -0
  27. axiomgate_kernel-0.10.0/axiomgate_kernel/provenance.py +128 -0
  28. axiomgate_kernel-0.10.0/axiomgate_kernel/provisioning.py +64 -0
  29. axiomgate_kernel-0.10.0/axiomgate_kernel/redaction.py +195 -0
  30. axiomgate_kernel-0.10.0/axiomgate_kernel/serializers.py +202 -0
  31. axiomgate_kernel-0.10.0/axiomgate_kernel/strict.py +238 -0
  32. axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/PKG-INFO +436 -0
  33. axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/SOURCES.txt +57 -0
  34. axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/dependency_links.txt +1 -0
  35. axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/requires.txt +4 -0
  36. axiomgate_kernel-0.10.0/axiomgate_kernel.egg-info/top_level.txt +1 -0
  37. axiomgate_kernel-0.10.0/pyproject.toml +47 -0
  38. axiomgate_kernel-0.10.0/setup.cfg +4 -0
  39. axiomgate_kernel-0.10.0/tests/test_approval.py +617 -0
  40. axiomgate_kernel-0.10.0/tests/test_audit.py +197 -0
  41. axiomgate_kernel-0.10.0/tests/test_audit_anchoring.py +234 -0
  42. axiomgate_kernel-0.10.0/tests/test_authentication.py +257 -0
  43. axiomgate_kernel-0.10.0/tests/test_authorization.py +128 -0
  44. axiomgate_kernel-0.10.0/tests/test_capability.py +253 -0
  45. axiomgate_kernel-0.10.0/tests/test_escalation.py +82 -0
  46. axiomgate_kernel-0.10.0/tests/test_evidence.py +137 -0
  47. axiomgate_kernel-0.10.0/tests/test_execution.py +358 -0
  48. axiomgate_kernel-0.10.0/tests/test_grant.py +232 -0
  49. axiomgate_kernel-0.10.0/tests/test_mediator.py +226 -0
  50. axiomgate_kernel-0.10.0/tests/test_mediator_failclosed.py +184 -0
  51. axiomgate_kernel-0.10.0/tests/test_no_embedded_identity.py +86 -0
  52. axiomgate_kernel-0.10.0/tests/test_observation.py +272 -0
  53. axiomgate_kernel-0.10.0/tests/test_persistence.py +477 -0
  54. axiomgate_kernel-0.10.0/tests/test_policy.py +51 -0
  55. axiomgate_kernel-0.10.0/tests/test_principal_context.py +80 -0
  56. axiomgate_kernel-0.10.0/tests/test_provenance.py +37 -0
  57. axiomgate_kernel-0.10.0/tests/test_provenance_ceiling.py +489 -0
  58. axiomgate_kernel-0.10.0/tests/test_redaction.py +79 -0
  59. 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