aevum-core 0.2.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 (56) hide show
  1. aevum_core-0.2.0/.gitignore +31 -0
  2. aevum_core-0.2.0/PKG-INFO +42 -0
  3. aevum_core-0.2.0/README.md +10 -0
  4. aevum_core-0.2.0/pyproject.toml +72 -0
  5. aevum_core-0.2.0/src/aevum/core/__init__.py +30 -0
  6. aevum_core-0.2.0/src/aevum/core/audit/__init__.py +1 -0
  7. aevum_core-0.2.0/src/aevum/core/audit/event.py +77 -0
  8. aevum_core-0.2.0/src/aevum/core/audit/hlc.py +36 -0
  9. aevum_core-0.2.0/src/aevum/core/audit/ledger.py +71 -0
  10. aevum_core-0.2.0/src/aevum/core/audit/sigchain.py +166 -0
  11. aevum_core-0.2.0/src/aevum/core/barriers.py +105 -0
  12. aevum_core-0.2.0/src/aevum/core/complications/__init__.py +46 -0
  13. aevum_core-0.2.0/src/aevum/core/complications/circuit_breaker.py +83 -0
  14. aevum_core-0.2.0/src/aevum/core/complications/conflict.py +43 -0
  15. aevum_core-0.2.0/src/aevum/core/complications/manifest_validator.py +117 -0
  16. aevum_core-0.2.0/src/aevum/core/complications/registry.py +181 -0
  17. aevum_core-0.2.0/src/aevum/core/complications/webhook.py +168 -0
  18. aevum_core-0.2.0/src/aevum/core/consent/__init__.py +1 -0
  19. aevum_core-0.2.0/src/aevum/core/consent/ledger.py +64 -0
  20. aevum_core-0.2.0/src/aevum/core/consent/models.py +47 -0
  21. aevum_core-0.2.0/src/aevum/core/control_plane/__init__.py +1 -0
  22. aevum_core-0.2.0/src/aevum/core/control_plane/api.py +19 -0
  23. aevum_core-0.2.0/src/aevum/core/engine.py +340 -0
  24. aevum_core-0.2.0/src/aevum/core/envelope/__init__.py +1 -0
  25. aevum_core-0.2.0/src/aevum/core/envelope/models.py +217 -0
  26. aevum_core-0.2.0/src/aevum/core/exceptions.py +51 -0
  27. aevum_core-0.2.0/src/aevum/core/functions/__init__.py +1 -0
  28. aevum_core-0.2.0/src/aevum/core/functions/commit.py +64 -0
  29. aevum_core-0.2.0/src/aevum/core/functions/ingest.py +85 -0
  30. aevum_core-0.2.0/src/aevum/core/functions/query.py +167 -0
  31. aevum_core-0.2.0/src/aevum/core/functions/replay.py +64 -0
  32. aevum_core-0.2.0/src/aevum/core/functions/review.py +152 -0
  33. aevum_core-0.2.0/src/aevum/core/graph/__init__.py +1 -0
  34. aevum_core-0.2.0/src/aevum/core/graph/memory.py +42 -0
  35. aevum_core-0.2.0/src/aevum/core/policy/__init__.py +1 -0
  36. aevum_core-0.2.0/src/aevum/core/policy/bridge.py +198 -0
  37. aevum_core-0.2.0/src/aevum/core/protocols/__init__.py +13 -0
  38. aevum_core-0.2.0/src/aevum/core/protocols/audit_ledger.py +32 -0
  39. aevum_core-0.2.0/src/aevum/core/protocols/complication.py +12 -0
  40. aevum_core-0.2.0/src/aevum/core/protocols/consent_ledger.py +23 -0
  41. aevum_core-0.2.0/src/aevum/core/protocols/graph_store.py +12 -0
  42. aevum_core-0.2.0/src/aevum/core/py.typed +0 -0
  43. aevum_core-0.2.0/src/aevum/core/session.py +16 -0
  44. aevum_core-0.2.0/tests/conformance_adapter.py +50 -0
  45. aevum_core-0.2.0/tests/test_agent_integration.py +147 -0
  46. aevum_core-0.2.0/tests/test_barriers.py +71 -0
  47. aevum_core-0.2.0/tests/test_canary.py +68 -0
  48. aevum_core-0.2.0/tests/test_cedar_policy.py +96 -0
  49. aevum_core-0.2.0/tests/test_complication_integration.py +209 -0
  50. aevum_core-0.2.0/tests/test_complications.py +315 -0
  51. aevum_core-0.2.0/tests/test_conformance_adapter.py +65 -0
  52. aevum_core-0.2.0/tests/test_envelope.py +68 -0
  53. aevum_core-0.2.0/tests/test_functions.py +132 -0
  54. aevum_core-0.2.0/tests/test_opa_policy.py +106 -0
  55. aevum_core-0.2.0/tests/test_sigchain.py +58 -0
  56. aevum_core-0.2.0/tests/test_webhook_retry.py +108 -0
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ .venv/
7
+ *.egg-info/
8
+
9
+ # Build
10
+ dist/
11
+ build/
12
+
13
+ # Tools
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .pytest_cache/
17
+ .hypothesis/
18
+
19
+ # IDE
20
+ .vscode/
21
+ .idea/
22
+ *.swp
23
+ *.swo
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+
29
+ # Verify scripts (run locally, never commit)
30
+ verify_phase*.py
31
+ scripts/verify_phase*.py
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: aevum-core
3
+ Version: 0.2.0
4
+ Summary: Aevum — the sealed movement. Replay-first, policy-governed context kernel.
5
+ Project-URL: Homepage, https://aevum.build
6
+ Project-URL: Repository, https://github.com/aevum-labs/aevum
7
+ Project-URL: Issues, https://github.com/aevum-labs/aevum/issues
8
+ License: Apache-2.0
9
+ Keywords: ai,audit,context,governance,provenance,replay
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: cryptography>=42.0
19
+ Requires-Dist: httpx>=0.27
20
+ Requires-Dist: pydantic<3.0,>=2.0
21
+ Provides-Extra: cedar
22
+ Requires-Dist: cedarpy>=0.3; extra == 'cedar'
23
+ Provides-Extra: default
24
+ Requires-Dist: aevum-store-oxigraph; extra == 'default'
25
+ Provides-Extra: dev
26
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest>=8.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.9; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # aevum-core
34
+
35
+ The Aevum context kernel — five governed functions, cryptographic sigchain, consent ledger, and absolute barriers.
36
+
37
+ ```bash
38
+ pip install aevum-core
39
+ pip install "aevum-core[cedar]" # + real Cedar consent enforcement
40
+ ```
41
+
42
+ See the [main repository README](https://github.com/aevum-labs/aevum) for a quickstart and architecture overview.
@@ -0,0 +1,10 @@
1
+ # aevum-core
2
+
3
+ The Aevum context kernel — five governed functions, cryptographic sigchain, consent ledger, and absolute barriers.
4
+
5
+ ```bash
6
+ pip install aevum-core
7
+ pip install "aevum-core[cedar]" # + real Cedar consent enforcement
8
+ ```
9
+
10
+ See the [main repository README](https://github.com/aevum-labs/aevum) for a quickstart and architecture overview.
@@ -0,0 +1,72 @@
1
+ [project]
2
+ name = "aevum-core"
3
+ version = "0.2.0"
4
+ description = "Aevum — the sealed movement. Replay-first, policy-governed context kernel."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "Apache-2.0" }
8
+ keywords = ["ai", "governance", "provenance", "audit", "context", "replay"]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Intended Audience :: Developers",
12
+ "License :: OSI Approved :: Apache Software License",
13
+ "Programming Language :: Python :: 3.11",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
16
+ "Typing :: Typed",
17
+ ]
18
+ dependencies = [
19
+ "pydantic>=2.0,<3.0",
20
+ "cryptography>=42.0",
21
+ "httpx>=0.27",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ default = ["aevum-store-oxigraph"]
26
+ cedar = ["cedarpy>=0.3"]
27
+ dev = [
28
+ "pytest>=8.0",
29
+ "pytest-asyncio>=0.23",
30
+ "hypothesis>=6.100",
31
+ "mypy>=1.10",
32
+ "ruff>=0.9",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://aevum.build"
37
+ Repository = "https://github.com/aevum-labs/aevum"
38
+ Issues = "https://github.com/aevum-labs/aevum/issues"
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/aevum"]
46
+
47
+ [tool.uv.sources]
48
+ # aevum-core has no workspace dependencies — it IS the foundation
49
+ aevum-store-oxigraph = { workspace = true }
50
+
51
+ [tool.mypy]
52
+ mypy_path = "src"
53
+ strict = true
54
+ python_version = "3.11"
55
+ ignore_missing_imports = true
56
+
57
+ [tool.ruff]
58
+ line-length = 130
59
+ target-version = "py311"
60
+
61
+ [tool.ruff.lint]
62
+ select = ["E", "F", "UP", "B", "SIM", "I", "ANN"]
63
+ ignore = ["ANN401"]
64
+
65
+ [tool.ruff.lint.per-file-ignores]
66
+ "tests/**" = ["ANN"]
67
+
68
+ [tool.pytest.ini_options]
69
+ testpaths = ["tests"]
70
+ asyncio_mode = "auto"
71
+ addopts = "--tb=short"
72
+ pythonpath = ["src", "tests"]
@@ -0,0 +1,30 @@
1
+ """
2
+ aevum.core — The Aevum context kernel.
3
+
4
+ Usage:
5
+ from aevum.core import Engine
6
+ engine = Engine()
7
+ result = engine.commit(event_type="app.event", payload={}, actor="user-1")
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from aevum.core.engine import Engine
13
+ from aevum.core.envelope.models import OutputEnvelope
14
+ from aevum.core.exceptions import (
15
+ AevumError,
16
+ BarrierViolationError,
17
+ ConsentRequiredError,
18
+ ProvenanceRequiredError,
19
+ )
20
+
21
+ __version__ = "0.2.0"
22
+
23
+ __all__ = [
24
+ "Engine",
25
+ "OutputEnvelope",
26
+ "AevumError",
27
+ "BarrierViolationError",
28
+ "ConsentRequiredError",
29
+ "ProvenanceRequiredError",
30
+ ]
@@ -0,0 +1 @@
1
+ """aevum.core.audit — Episodic ledger, sigchain, HLC, AuditEvent."""
@@ -0,0 +1,77 @@
1
+ """
2
+ AuditEvent — the 18-field episodic ledger entry. Spec Section 06.2.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import dataclasses
8
+ import hashlib
9
+ import json
10
+ from typing import Any
11
+
12
+
13
+ @dataclasses.dataclass(frozen=True)
14
+ class AuditEvent:
15
+ """Immutable episodic ledger entry. All 18 fields required."""
16
+
17
+ event_id: str
18
+ episode_id: str
19
+ sequence: int
20
+ event_type: str
21
+ schema_version: str
22
+ valid_from: str
23
+ valid_to: str | None
24
+ system_time: int
25
+ causation_id: str | None
26
+ correlation_id: str | None
27
+ actor: str
28
+ trace_id: str | None
29
+ span_id: str | None
30
+ payload: dict[str, Any]
31
+ payload_hash: str
32
+ prior_hash: str
33
+ signature: str
34
+ signer_key_id: str
35
+
36
+ def __post_init__(self) -> None:
37
+ if not self.actor:
38
+ raise ValueError("actor MUST NOT be empty")
39
+ if self.sequence < 1:
40
+ raise ValueError(f"sequence must be >= 1, got {self.sequence}")
41
+ if not self.event_type:
42
+ raise ValueError("event_type MUST NOT be empty")
43
+
44
+ @staticmethod
45
+ def canonical_payload(payload: dict[str, Any]) -> bytes:
46
+ return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
47
+
48
+ @staticmethod
49
+ def hash_payload(payload: dict[str, Any]) -> str:
50
+ return hashlib.sha3_256(AuditEvent.canonical_payload(payload)).hexdigest()
51
+
52
+ @staticmethod
53
+ def hash_event_for_chain(event: AuditEvent) -> str:
54
+ """SHA3-256 over all fields (excluding signature) for prior_hash chaining."""
55
+ fields = {
56
+ "event_id": event.event_id,
57
+ "episode_id": event.episode_id,
58
+ "sequence": event.sequence,
59
+ "event_type": event.event_type,
60
+ "schema_version": event.schema_version,
61
+ "valid_from": event.valid_from,
62
+ "valid_to": event.valid_to,
63
+ "system_time": event.system_time,
64
+ "causation_id": event.causation_id,
65
+ "correlation_id": event.correlation_id,
66
+ "actor": event.actor,
67
+ "trace_id": event.trace_id,
68
+ "span_id": event.span_id,
69
+ "payload_hash": event.payload_hash,
70
+ "prior_hash": event.prior_hash,
71
+ "signer_key_id": event.signer_key_id,
72
+ }
73
+ canonical = json.dumps(fields, sort_keys=True, separators=(",", ":")).encode()
74
+ return hashlib.sha3_256(canonical).hexdigest()
75
+
76
+ def audit_id(self) -> str:
77
+ return f"urn:aevum:audit:{self.event_id}"
@@ -0,0 +1,36 @@
1
+ """
2
+ HybridLogicalClock — causal ordering. Spec Section 06.5.
3
+ Timestamp: bits 63-16 = ms since epoch, bits 15-0 = logical counter.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import threading
9
+ import time
10
+
11
+ _lock = threading.Lock()
12
+ _last_ms: int = 0
13
+ _counter: int = 0
14
+
15
+
16
+ def now() -> int:
17
+ global _last_ms, _counter
18
+ with _lock:
19
+ ms = int(time.time() * 1000)
20
+ if ms > _last_ms:
21
+ _last_ms = ms
22
+ _counter = 0
23
+ else:
24
+ _counter += 1
25
+ if _counter > 0xFFFF:
26
+ _last_ms += 1
27
+ _counter = 0
28
+ return (_last_ms << 16) | _counter
29
+
30
+
31
+ def to_millis(ts: int) -> int:
32
+ return ts >> 16
33
+
34
+
35
+ def to_counter(ts: int) -> int:
36
+ return ts & 0xFFFF
@@ -0,0 +1,71 @@
1
+ """
2
+ Episodic ledger — append-only. Barrier 4 enforced here. Spec Section 06.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import threading
8
+ from typing import Any
9
+
10
+ from aevum.core.audit.event import AuditEvent
11
+ from aevum.core.audit.sigchain import Sigchain
12
+ from aevum.core.exceptions import BarrierViolationError, ReplayNotFoundError
13
+
14
+
15
+ class InMemoryLedger:
16
+ """Thread-safe append-only in-memory episodic ledger. Suitable for development and testing."""
17
+
18
+ def __init__(self, sigchain: Sigchain) -> None:
19
+ self._sigchain = sigchain
20
+ self._events: list[AuditEvent] = []
21
+ self._index: dict[str, AuditEvent] = {}
22
+ self._lock = threading.Lock()
23
+
24
+ def append(
25
+ self,
26
+ *,
27
+ event_type: str,
28
+ payload: dict[str, Any],
29
+ actor: str,
30
+ episode_id: str | None = None,
31
+ causation_id: str | None = None,
32
+ correlation_id: str | None = None,
33
+ ) -> AuditEvent:
34
+ with self._lock:
35
+ event = self._sigchain.new_event(
36
+ event_type=event_type,
37
+ payload=payload,
38
+ actor=actor,
39
+ episode_id=episode_id,
40
+ causation_id=causation_id,
41
+ correlation_id=correlation_id,
42
+ )
43
+ self._events.append(event)
44
+ self._index[event.audit_id()] = event
45
+ return event
46
+
47
+ def get(self, audit_id: str) -> AuditEvent:
48
+ event = self._index.get(audit_id)
49
+ if event is None:
50
+ raise ReplayNotFoundError(f"No ledger entry for {audit_id!r}")
51
+ return event
52
+
53
+ def all_events(self) -> list[AuditEvent]:
54
+ with self._lock:
55
+ return list(self._events)
56
+
57
+ def count(self) -> int:
58
+ with self._lock:
59
+ return len(self._events)
60
+
61
+ def __delitem__(self, key: object) -> None:
62
+ """Barrier 4: deletion forbidden."""
63
+ raise BarrierViolationError(
64
+ "Attempted to delete a ledger entry — Barrier 4 (Audit Immutability) violated."
65
+ )
66
+
67
+ def __setitem__(self, key: object, value: object) -> None:
68
+ """Barrier 4: overwrite forbidden."""
69
+ raise BarrierViolationError(
70
+ "Attempted to overwrite a ledger entry — Barrier 4 (Audit Immutability) violated."
71
+ )
@@ -0,0 +1,166 @@
1
+ """
2
+ Sigchain — Ed25519 signing and SHA3-256 chaining. Spec Section 06.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import base64
8
+ import datetime
9
+ import hashlib
10
+ import json
11
+ import os
12
+ import time
13
+ from typing import Any
14
+
15
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
16
+ Ed25519PrivateKey,
17
+ Ed25519PublicKey,
18
+ )
19
+
20
+ from aevum.core.audit.event import AuditEvent
21
+ from aevum.core.audit.hlc import now as hlc_now
22
+
23
+ GENESIS_HASH = hashlib.sha3_256(b"aevum:genesis").hexdigest()
24
+
25
+
26
+ def _uuid7() -> str:
27
+ """UUID version 7 (time-ordered). Inline — no external dep."""
28
+ ts_ms = int(time.time() * 1000) & 0xFFFFFFFFFFFF
29
+ rand = int.from_bytes(os.urandom(10), "big")
30
+ rand_a = (rand >> 62) & 0x0FFF
31
+ rand_b = rand & 0x3FFFFFFFFFFFFFFF
32
+ hi = (ts_ms << 16) | 0x7000 | rand_a
33
+ lo = 0x8000000000000000 | rand_b
34
+ h = f"{hi:016x}{lo:016x}"
35
+ return f"{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:]}"
36
+
37
+
38
+ class Sigchain:
39
+ """Per-node Ed25519 signing chain. Append-only by design."""
40
+
41
+ def __init__(
42
+ self,
43
+ private_key: Ed25519PrivateKey | None = None,
44
+ key_id: str | None = None,
45
+ ) -> None:
46
+ self._private_key = private_key or Ed25519PrivateKey.generate()
47
+ self._key_id = key_id or _uuid7()
48
+ self._sequence: int = 0
49
+ self._prior_hash: str = GENESIS_HASH
50
+
51
+ @property
52
+ def key_id(self) -> str:
53
+ return self._key_id
54
+
55
+ @property
56
+ def public_key(self) -> Ed25519PublicKey:
57
+ return self._private_key.public_key()
58
+
59
+ def _sign(self, fields: dict[str, Any]) -> str:
60
+ canonical = json.dumps(fields, sort_keys=True, separators=(",", ":")).encode()
61
+ sig_bytes = self._private_key.sign(canonical)
62
+ return base64.urlsafe_b64encode(sig_bytes).rstrip(b"=").decode()
63
+
64
+ def new_event(
65
+ self,
66
+ *,
67
+ event_type: str,
68
+ payload: dict[str, Any],
69
+ actor: str,
70
+ episode_id: str | None = None,
71
+ causation_id: str | None = None,
72
+ correlation_id: str | None = None,
73
+ trace_id: str | None = None,
74
+ span_id: str | None = None,
75
+ valid_from: str | None = None,
76
+ valid_to: str | None = None,
77
+ ) -> AuditEvent:
78
+ """Append a new signed event to the chain."""
79
+ self._sequence += 1
80
+ event_id = _uuid7()
81
+ ep_id = episode_id or _uuid7()
82
+ vf = valid_from or datetime.datetime.now(datetime.UTC).isoformat()
83
+ ts = hlc_now()
84
+ payload_hash = AuditEvent.hash_payload(payload)
85
+ prior = self._prior_hash
86
+
87
+ signing_fields: dict[str, Any] = {
88
+ "event_id": event_id,
89
+ "episode_id": ep_id,
90
+ "sequence": self._sequence,
91
+ "event_type": event_type,
92
+ "schema_version": "1.0",
93
+ "valid_from": vf,
94
+ "valid_to": valid_to,
95
+ "system_time": ts,
96
+ "causation_id": causation_id,
97
+ "correlation_id": correlation_id,
98
+ "actor": actor,
99
+ "trace_id": trace_id,
100
+ "span_id": span_id,
101
+ "payload_hash": payload_hash,
102
+ "prior_hash": prior,
103
+ "signer_key_id": self._key_id,
104
+ }
105
+ signature = self._sign(signing_fields)
106
+
107
+ event = AuditEvent(
108
+ event_id=event_id,
109
+ episode_id=ep_id,
110
+ sequence=self._sequence,
111
+ event_type=event_type,
112
+ schema_version="1.0",
113
+ valid_from=vf,
114
+ valid_to=valid_to,
115
+ system_time=ts,
116
+ causation_id=causation_id,
117
+ correlation_id=correlation_id,
118
+ actor=actor,
119
+ trace_id=trace_id,
120
+ span_id=span_id,
121
+ payload=payload,
122
+ payload_hash=payload_hash,
123
+ prior_hash=prior,
124
+ signature=signature,
125
+ signer_key_id=self._key_id,
126
+ )
127
+ self._prior_hash = AuditEvent.hash_event_for_chain(event)
128
+ return event
129
+
130
+ def verify_chain(self, events: list[AuditEvent]) -> bool:
131
+ """Verify entire chain from genesis. Returns True if intact."""
132
+ public_key = self.public_key
133
+ expected_prior = GENESIS_HASH
134
+ for event in events:
135
+ if event.prior_hash != expected_prior:
136
+ return False
137
+ if AuditEvent.hash_payload(event.payload) != event.payload_hash:
138
+ return False
139
+ signing_fields: dict[str, Any] = {
140
+ "event_id": event.event_id,
141
+ "episode_id": event.episode_id,
142
+ "sequence": event.sequence,
143
+ "event_type": event.event_type,
144
+ "schema_version": event.schema_version,
145
+ "valid_from": event.valid_from,
146
+ "valid_to": event.valid_to,
147
+ "system_time": event.system_time,
148
+ "causation_id": event.causation_id,
149
+ "correlation_id": event.correlation_id,
150
+ "actor": event.actor,
151
+ "trace_id": event.trace_id,
152
+ "span_id": event.span_id,
153
+ "payload_hash": event.payload_hash,
154
+ "prior_hash": event.prior_hash,
155
+ "signer_key_id": event.signer_key_id,
156
+ }
157
+ canonical = json.dumps(
158
+ signing_fields, sort_keys=True, separators=(",", ":")
159
+ ).encode()
160
+ try:
161
+ sig_bytes = base64.urlsafe_b64decode(event.signature + "==")
162
+ public_key.verify(sig_bytes, canonical)
163
+ except Exception:
164
+ return False
165
+ expected_prior = AuditEvent.hash_event_for_chain(event)
166
+ return True
@@ -0,0 +1,105 @@
1
+ """
2
+ Absolute Barriers — hardcoded, unconditional, non-configurable.
3
+ Spec Section 09.3. Canary tests in tests/test_canary.py.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from aevum.core.envelope.models import OutputEnvelope, ProvenanceRecord
11
+
12
+ _CRISIS_KEYWORDS: frozenset[str] = frozenset({
13
+ "kill myself", "end my life", "want to die", "commit suicide",
14
+ "hurt someone", "going to hurt", "harm myself",
15
+ "heart attack", "can't breathe", "i can't breathe",
16
+ "overdose", "going to shoot", "going to stab",
17
+ })
18
+
19
+ _CRISIS_SAFE_MESSAGE = (
20
+ "It sounds like you or someone you know may be going through something "
21
+ "very difficult. Please reach out to a crisis service — you don't have to face this alone."
22
+ )
23
+ _CRISIS_RESOURCES = [
24
+ "988 Suicide & Crisis Lifeline: call or text 988 (US)",
25
+ "Crisis Text Line: text HOME to 741741 (US)",
26
+ "International Association for Suicide Prevention: https://www.iasp.info/resources/Crisis_Centres/",
27
+ ]
28
+
29
+
30
+ def _text_from_data(data: dict[str, Any]) -> str:
31
+ parts = []
32
+ for v in data.values():
33
+ parts.append(str(v) if not isinstance(v, str) else v)
34
+ return " ".join(parts).lower()
35
+
36
+
37
+ def _kernel_provenance(audit_id: str) -> ProvenanceRecord:
38
+ return ProvenanceRecord(
39
+ source_id="aevum-core", ingest_audit_id=audit_id,
40
+ chain_of_custody=["aevum-core"], classification=0,
41
+ )
42
+
43
+
44
+ def check_crisis(data: dict[str, Any], audit_id: str) -> OutputEnvelope | None:
45
+ """Barrier 1 — CRISIS. Returns crisis envelope or None."""
46
+ if any(kw in _text_from_data(data) for kw in _CRISIS_KEYWORDS):
47
+ return OutputEnvelope.crisis(
48
+ audit_id=audit_id,
49
+ safe_message=_CRISIS_SAFE_MESSAGE,
50
+ resources=_CRISIS_RESOURCES,
51
+ provenance=_kernel_provenance(audit_id),
52
+ )
53
+ return None
54
+
55
+
56
+ def apply_classification_ceiling(
57
+ results: dict[str, Any],
58
+ classifications: dict[str, int],
59
+ actor_clearance: int,
60
+ ) -> tuple[dict[str, Any], list[str]]:
61
+ """Barrier 2 — CLASSIFICATION CEILING. Redacts above-clearance items."""
62
+ filtered: dict[str, Any] = {}
63
+ redacted: list[str] = []
64
+ for entity_id, entity_data in results.items():
65
+ if classifications.get(entity_id, 0) <= actor_clearance:
66
+ filtered[entity_id] = entity_data
67
+ else:
68
+ redacted.append(entity_id)
69
+ return filtered, redacted
70
+
71
+
72
+ def check_consent(
73
+ *,
74
+ subject_id: str,
75
+ operation: str,
76
+ grantee_id: str,
77
+ consent_ledger: Any,
78
+ audit_id: str,
79
+ ) -> OutputEnvelope | None:
80
+ """Barrier 3 — CONSENT. Returns error envelope or None."""
81
+ if not consent_ledger.has_consent(
82
+ subject_id=subject_id, operation=operation, grantee_id=grantee_id
83
+ ):
84
+ return OutputEnvelope.error(
85
+ audit_id=audit_id,
86
+ error_code="consent_required",
87
+ error_detail=f"No active consent grant for operation '{operation}' on subject '{subject_id}' by '{grantee_id}'",
88
+ provenance=_kernel_provenance(audit_id),
89
+ )
90
+ return None
91
+
92
+
93
+ # Barrier 4 — AUDIT IMMUTABILITY enforced by InMemoryLedger.__delitem__/__setitem__
94
+
95
+
96
+ def check_provenance(provenance: dict[str, Any], audit_id: str) -> OutputEnvelope | None:
97
+ """Barrier 5 — PROVENANCE. Returns error envelope or None."""
98
+ if not provenance or not provenance.get("source_id"):
99
+ return OutputEnvelope.error(
100
+ audit_id=audit_id,
101
+ error_code="provenance_required",
102
+ error_detail="Provenance record is missing or has no source_id",
103
+ provenance=_kernel_provenance(audit_id),
104
+ )
105
+ return None
@@ -0,0 +1,46 @@
1
+ """
2
+ aevum.core.complications — Complication governance lifecycle.
3
+
4
+ ComplicationRegistry — 7-state machine: install/approve/suspend/decommission
5
+ CircuitBreaker — threshold-based, monotonic clock
6
+ ManifestValidator — schema + Ed25519 (optional)
7
+ ConflictDetector — capability overlap, fail-closed
8
+ WebhookRegistry — register/dispatch review events
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import concurrent.futures
15
+ from typing import Any
16
+
17
+ from aevum.core.complications.circuit_breaker import CircuitBreaker
18
+ from aevum.core.complications.conflict import ConflictDetector
19
+ from aevum.core.complications.manifest_validator import ManifestValidator
20
+ from aevum.core.complications.registry import ComplicationRegistry, ComplicationState
21
+ from aevum.core.complications.webhook import WebhookRegistry
22
+
23
+
24
+ def _run_coro(coro: Any) -> Any:
25
+ """
26
+ Run a coroutine from sync context.
27
+ Handles the case where we are already inside a running event loop
28
+ (e.g. FastAPI handlers) by delegating to a thread pool.
29
+ """
30
+ try:
31
+ asyncio.get_running_loop()
32
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
33
+ return pool.submit(asyncio.run, coro).result()
34
+ except RuntimeError:
35
+ return asyncio.run(coro)
36
+
37
+
38
+ __all__ = [
39
+ "ComplicationRegistry",
40
+ "ComplicationState",
41
+ "CircuitBreaker",
42
+ "ManifestValidator",
43
+ "ConflictDetector",
44
+ "WebhookRegistry",
45
+ "_run_coro",
46
+ ]