sasana 1.0.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 (42) hide show
  1. sasana-1.0.0/LICENSE +21 -0
  2. sasana-1.0.0/PKG-INFO +216 -0
  3. sasana-1.0.0/README.md +193 -0
  4. sasana-1.0.0/archeion/__init__.py +12 -0
  5. sasana-1.0.0/archeion/keys.py +46 -0
  6. sasana-1.0.0/archeion/server.py +154 -0
  7. sasana-1.0.0/pyproject.toml +37 -0
  8. sasana-1.0.0/sasana/__init__.py +16 -0
  9. sasana-1.0.0/sasana/_utils.py +29 -0
  10. sasana-1.0.0/sasana/compliance/__init__.py +60 -0
  11. sasana-1.0.0/sasana/compliance/eu_ai_act.py +235 -0
  12. sasana-1.0.0/sasana/compliance/hipaa.py +247 -0
  13. sasana-1.0.0/sasana/compliance/siem.py +342 -0
  14. sasana-1.0.0/sasana/compliance/soc2.py +169 -0
  15. sasana-1.0.0/sasana/envelope.py +55 -0
  16. sasana-1.0.0/sasana/event_mapper.py +105 -0
  17. sasana-1.0.0/sasana/events.py +36 -0
  18. sasana-1.0.0/sasana/integrations/__init__.py +8 -0
  19. sasana-1.0.0/sasana/integrations/autogpt.py +301 -0
  20. sasana-1.0.0/sasana/integrations/crewai.py +361 -0
  21. sasana-1.0.0/sasana/integrations/langgraph.py +302 -0
  22. sasana-1.0.0/sasana/jcs.py +51 -0
  23. sasana-1.0.0/sasana/observer.py +140 -0
  24. sasana-1.0.0/sasana/rfc3161.py +288 -0
  25. sasana-1.0.0/sasana/signing.py +40 -0
  26. sasana-1.0.0/sasana/skill.py +129 -0
  27. sasana-1.0.0/sasana/sqlite_ledger.py +237 -0
  28. sasana-1.0.0/sasana/verifier.py +484 -0
  29. sasana-1.0.0/sasana.egg-info/PKG-INFO +216 -0
  30. sasana-1.0.0/sasana.egg-info/SOURCES.txt +40 -0
  31. sasana-1.0.0/sasana.egg-info/dependency_links.txt +1 -0
  32. sasana-1.0.0/sasana.egg-info/entry_points.txt +4 -0
  33. sasana-1.0.0/sasana.egg-info/requires.txt +15 -0
  34. sasana-1.0.0/sasana.egg-info/top_level.txt +5 -0
  35. sasana-1.0.0/sasana_cli.py +195 -0
  36. sasana-1.0.0/setup.cfg +4 -0
  37. sasana-1.0.0/tests/test_compliance.py +357 -0
  38. sasana-1.0.0/tests/test_e2e.py +252 -0
  39. sasana-1.0.0/tests/test_integrations.py +400 -0
  40. sasana-1.0.0/tests/test_rfc3161.py +296 -0
  41. sasana-1.0.0/tests/test_verifier.py +515 -0
  42. sasana-1.0.0/verify.py +121 -0
sasana-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sahiee-dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sasana-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: sasana
3
+ Version: 1.0.0
4
+ Summary: Tamper-evident cryptographic audit trail for OpenClaw and multi-agent AI sessions
5
+ Author-email: Sahir Shaik Ahamed <sahir150305@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: cryptography>=41.0
11
+ Provides-Extra: observer
12
+ Requires-Dist: websockets>=12.0; extra == "observer"
13
+ Requires-Dist: pyyaml>=6.0; extra == "observer"
14
+ Provides-Extra: archeion
15
+ Requires-Dist: fastapi>=0.110; extra == "archeion"
16
+ Requires-Dist: uvicorn[standard]>=0.27; extra == "archeion"
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7.0; extra == "dev"
19
+ Requires-Dist: ruff>=0.4; extra == "dev"
20
+ Requires-Dist: httpx>=0.27; extra == "dev"
21
+ Requires-Dist: fastapi>=0.110; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # Sasana
25
+
26
+ **Your AI agent logs are not evidence. Sasana makes them defensible.**
27
+
28
+ Every major observability tool — LangSmith, Arize, Langfuse — stores logs in mutable,
29
+ operator-controlled storage. An administrator with file-system or database access can
30
+ modify or delete a record with no detectable trace. Those logs satisfy a reporting
31
+ requirement. They do not prove what happened.
32
+
33
+ Sasana records a SHA-256 hash-chained audit trail where any modification — to any byte,
34
+ in any historical event — is detectable. Raw content never leaves your machine: only
35
+ hashes are stored.
36
+
37
+ ```
38
+ $ sasana verify session.jsonl --trust-key <archeion-pubkey>
39
+
40
+ Sasana Verifier v1.0.0
41
+ File : session.jsonl
42
+ Session : 3f8a2c1d-…
43
+ Events : 7
44
+ Evidence : AUTHORITATIVE_EVIDENCE
45
+
46
+ [1/5] Structural validity ... PASS
47
+ [2/5] Sequence integrity ... PASS
48
+ [3/5] Hash chain integrity ... PASS
49
+ [4/5] Session completeness ... PASS
50
+ [5/5] Seal signature ... PASS
51
+
52
+ Result: INTACT ✅
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Who this is for
58
+
59
+ Teams deploying AI agents in regulated environments — fintech, healthtech, HR tech.
60
+ Specifically:
61
+
62
+ - Compliance engineers implementing **EU AI Act Article 12** (tamper-evident logging for
63
+ high-risk AI systems)
64
+ - Security teams who need a cryptographically verifiable audit trail for incident response
65
+ - DevSecOps engineers who need to prove a session log has not been touched since it ended
66
+
67
+ If you are building an AI agent for a use case that falls under GDPR, SOC 2, HIPAA, or
68
+ EU AI Act audit requirements, Sasana is the audit layer.
69
+
70
+ ---
71
+
72
+ ## What it produces
73
+
74
+ A completed session produces a JSONL file. The verifier checks five properties and
75
+ returns one of three results:
76
+
77
+ | Result | Meaning |
78
+ |---|---|
79
+ | `INTACT` | All checks pass. The log has not been modified. |
80
+ | `PARTIAL` | Hash chain intact but events were dropped during the session. |
81
+ | `COMPROMISED` | Hash chain broken. Log has been modified after the fact. |
82
+
83
+ Exit codes: `0` (INTACT), `1` (COMPROMISED), `2` (PARTIAL), `3` (ERROR) — suitable for
84
+ CI pipeline integration.
85
+
86
+ The evidence class tells you how strong the guarantee is:
87
+
88
+ | Class | Meaning |
89
+ |---|---|
90
+ | `AUTHORITATIVE_EVIDENCE` | Independent sealing authority verified this log. The agent could not have forged this. |
91
+ | `SIGNED_NON_AUTHORITATIVE` | Ed25519 signatures present. Requires private key to forge. |
92
+ | `NON_AUTHORITATIVE_EVIDENCE` | Hash chain intact. Proves no post-hoc modification. |
93
+
94
+ ---
95
+
96
+ ## Install
97
+
98
+ ```bash
99
+ pip install sasana
100
+ ```
101
+
102
+ ### Install via OpenClaw
103
+
104
+ ```bash
105
+ # Via GitHub URL
106
+ openclaw skill install https://github.com/sahiee-dev/Sasana
107
+
108
+ # Via skills.sh shorthand (when listed in the registry)
109
+ openclaw skill install sahiee-dev/Sasana/sasana
110
+ ```
111
+
112
+ Every session automatically produces `~/.openclaw/sasana/<session_id>.jsonl`.
113
+ No configuration required.
114
+
115
+ ---
116
+
117
+ ## Quick start
118
+
119
+ ```python
120
+ from sasana.sqlite_ledger import SqliteLedger
121
+ import hashlib
122
+
123
+ def sha256(text: str) -> str:
124
+ return hashlib.sha256(text.encode()).hexdigest()
125
+
126
+ ledger = SqliteLedger(db_path="session.db")
127
+ ledger.connect()
128
+ ledger.open_session(session_id="my-session", agent_id="my-agent")
129
+ ledger.record("LLM_CALL", {"prompt_hash": sha256(prompt)})
130
+ ledger.record("LLM_RESPONSE", {"response_hash": sha256(response)})
131
+ ledger.close_session(status="success")
132
+ ledger.export_jsonl("session.jsonl")
133
+ ledger.close()
134
+ ```
135
+
136
+ Verify:
137
+
138
+ ```bash
139
+ sasana verify session.jsonl
140
+ ```
141
+
142
+ Passive observer (zero code changes required):
143
+
144
+ ```bash
145
+ pip install sasana[observer]
146
+ sasana observe # auto-detects OpenClaw WebSocket port
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Authority sealing
152
+
153
+ For regulatory submissions and legal proceedings, `NON_AUTHORITATIVE_EVIDENCE` means the
154
+ operator is attesting their own logs. That is insufficient when the operator is a party
155
+ to a dispute.
156
+
157
+ [Archeion](archeion/) is a sealing server that runs inside your security perimeter,
158
+ controlled by your security team — structurally separate from the agent process. The
159
+ agent cannot forge a seal. The sealed log carries `AUTHORITATIVE_EVIDENCE`.
160
+
161
+ ```bash
162
+ # Start Archeion (self-hosted, inside your perimeter)
163
+ docker compose up -d
164
+
165
+ # Seal a completed session
166
+ sasana seal session.jsonl --server http://localhost:8747
167
+
168
+ # Verify with key pinning
169
+ sasana verify session.jsonl --trust-key <archeion-pubkey>
170
+ ```
171
+
172
+ See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for the full deployment guide — key
173
+ lifecycle, network isolation, and what to tell a security reviewer.
174
+
175
+ ---
176
+
177
+ ## How it works
178
+
179
+ Each event is stored with:
180
+
181
+ - **SHA-256 hash** over RFC 8785 canonical JSON — any mutation changes the hash
182
+ - **`prev_hash`** — each event commits to all prior events, forming a chain
183
+ - **Ed25519 signature** — optional per-session keypair; or Archeion's independent key
184
+
185
+ Raw content is **never stored** — only hashes. You cannot reconstruct what the agent said
186
+ from a Sasana log.
187
+
188
+ A Rust binary (`sasana-rs/`) verifies sessions without a Python dependency — for forensic
189
+ environments where Python is not present or trusted.
190
+
191
+ ---
192
+
193
+ ## Compliance mapping
194
+
195
+ | Regulation | Requirement addressed |
196
+ |---|---|
197
+ | EU AI Act Article 12 | Tamper-evident automatic recording for high-risk AI systems |
198
+ | SOC 2 CC7.2 | System monitoring with cryptographically verifiable audit trail |
199
+ | HIPAA §164.312(b) | Audit control for healthcare AI; raw PHI never recorded |
200
+
201
+ ---
202
+
203
+ ## What Sasana does not do
204
+
205
+ - **Does not record raw content.** Hashes only — you cannot reconstruct prompts or
206
+ responses.
207
+ - **Does not prevent tampering.** Detects it. Detection and prevention are different.
208
+ - **Does not replace LangSmith or Arize.** Those tools are for observability. Sasana is
209
+ for evidence production. They are complementary.
210
+ - **Does not have a managed cloud offering.** Self-hosted only.
211
+
212
+ ---
213
+
214
+ ## License
215
+
216
+ MIT
sasana-1.0.0/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # Sasana
2
+
3
+ **Your AI agent logs are not evidence. Sasana makes them defensible.**
4
+
5
+ Every major observability tool — LangSmith, Arize, Langfuse — stores logs in mutable,
6
+ operator-controlled storage. An administrator with file-system or database access can
7
+ modify or delete a record with no detectable trace. Those logs satisfy a reporting
8
+ requirement. They do not prove what happened.
9
+
10
+ Sasana records a SHA-256 hash-chained audit trail where any modification — to any byte,
11
+ in any historical event — is detectable. Raw content never leaves your machine: only
12
+ hashes are stored.
13
+
14
+ ```
15
+ $ sasana verify session.jsonl --trust-key <archeion-pubkey>
16
+
17
+ Sasana Verifier v1.0.0
18
+ File : session.jsonl
19
+ Session : 3f8a2c1d-…
20
+ Events : 7
21
+ Evidence : AUTHORITATIVE_EVIDENCE
22
+
23
+ [1/5] Structural validity ... PASS
24
+ [2/5] Sequence integrity ... PASS
25
+ [3/5] Hash chain integrity ... PASS
26
+ [4/5] Session completeness ... PASS
27
+ [5/5] Seal signature ... PASS
28
+
29
+ Result: INTACT ✅
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Who this is for
35
+
36
+ Teams deploying AI agents in regulated environments — fintech, healthtech, HR tech.
37
+ Specifically:
38
+
39
+ - Compliance engineers implementing **EU AI Act Article 12** (tamper-evident logging for
40
+ high-risk AI systems)
41
+ - Security teams who need a cryptographically verifiable audit trail for incident response
42
+ - DevSecOps engineers who need to prove a session log has not been touched since it ended
43
+
44
+ If you are building an AI agent for a use case that falls under GDPR, SOC 2, HIPAA, or
45
+ EU AI Act audit requirements, Sasana is the audit layer.
46
+
47
+ ---
48
+
49
+ ## What it produces
50
+
51
+ A completed session produces a JSONL file. The verifier checks five properties and
52
+ returns one of three results:
53
+
54
+ | Result | Meaning |
55
+ |---|---|
56
+ | `INTACT` | All checks pass. The log has not been modified. |
57
+ | `PARTIAL` | Hash chain intact but events were dropped during the session. |
58
+ | `COMPROMISED` | Hash chain broken. Log has been modified after the fact. |
59
+
60
+ Exit codes: `0` (INTACT), `1` (COMPROMISED), `2` (PARTIAL), `3` (ERROR) — suitable for
61
+ CI pipeline integration.
62
+
63
+ The evidence class tells you how strong the guarantee is:
64
+
65
+ | Class | Meaning |
66
+ |---|---|
67
+ | `AUTHORITATIVE_EVIDENCE` | Independent sealing authority verified this log. The agent could not have forged this. |
68
+ | `SIGNED_NON_AUTHORITATIVE` | Ed25519 signatures present. Requires private key to forge. |
69
+ | `NON_AUTHORITATIVE_EVIDENCE` | Hash chain intact. Proves no post-hoc modification. |
70
+
71
+ ---
72
+
73
+ ## Install
74
+
75
+ ```bash
76
+ pip install sasana
77
+ ```
78
+
79
+ ### Install via OpenClaw
80
+
81
+ ```bash
82
+ # Via GitHub URL
83
+ openclaw skill install https://github.com/sahiee-dev/Sasana
84
+
85
+ # Via skills.sh shorthand (when listed in the registry)
86
+ openclaw skill install sahiee-dev/Sasana/sasana
87
+ ```
88
+
89
+ Every session automatically produces `~/.openclaw/sasana/<session_id>.jsonl`.
90
+ No configuration required.
91
+
92
+ ---
93
+
94
+ ## Quick start
95
+
96
+ ```python
97
+ from sasana.sqlite_ledger import SqliteLedger
98
+ import hashlib
99
+
100
+ def sha256(text: str) -> str:
101
+ return hashlib.sha256(text.encode()).hexdigest()
102
+
103
+ ledger = SqliteLedger(db_path="session.db")
104
+ ledger.connect()
105
+ ledger.open_session(session_id="my-session", agent_id="my-agent")
106
+ ledger.record("LLM_CALL", {"prompt_hash": sha256(prompt)})
107
+ ledger.record("LLM_RESPONSE", {"response_hash": sha256(response)})
108
+ ledger.close_session(status="success")
109
+ ledger.export_jsonl("session.jsonl")
110
+ ledger.close()
111
+ ```
112
+
113
+ Verify:
114
+
115
+ ```bash
116
+ sasana verify session.jsonl
117
+ ```
118
+
119
+ Passive observer (zero code changes required):
120
+
121
+ ```bash
122
+ pip install sasana[observer]
123
+ sasana observe # auto-detects OpenClaw WebSocket port
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Authority sealing
129
+
130
+ For regulatory submissions and legal proceedings, `NON_AUTHORITATIVE_EVIDENCE` means the
131
+ operator is attesting their own logs. That is insufficient when the operator is a party
132
+ to a dispute.
133
+
134
+ [Archeion](archeion/) is a sealing server that runs inside your security perimeter,
135
+ controlled by your security team — structurally separate from the agent process. The
136
+ agent cannot forge a seal. The sealed log carries `AUTHORITATIVE_EVIDENCE`.
137
+
138
+ ```bash
139
+ # Start Archeion (self-hosted, inside your perimeter)
140
+ docker compose up -d
141
+
142
+ # Seal a completed session
143
+ sasana seal session.jsonl --server http://localhost:8747
144
+
145
+ # Verify with key pinning
146
+ sasana verify session.jsonl --trust-key <archeion-pubkey>
147
+ ```
148
+
149
+ See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for the full deployment guide — key
150
+ lifecycle, network isolation, and what to tell a security reviewer.
151
+
152
+ ---
153
+
154
+ ## How it works
155
+
156
+ Each event is stored with:
157
+
158
+ - **SHA-256 hash** over RFC 8785 canonical JSON — any mutation changes the hash
159
+ - **`prev_hash`** — each event commits to all prior events, forming a chain
160
+ - **Ed25519 signature** — optional per-session keypair; or Archeion's independent key
161
+
162
+ Raw content is **never stored** — only hashes. You cannot reconstruct what the agent said
163
+ from a Sasana log.
164
+
165
+ A Rust binary (`sasana-rs/`) verifies sessions without a Python dependency — for forensic
166
+ environments where Python is not present or trusted.
167
+
168
+ ---
169
+
170
+ ## Compliance mapping
171
+
172
+ | Regulation | Requirement addressed |
173
+ |---|---|
174
+ | EU AI Act Article 12 | Tamper-evident automatic recording for high-risk AI systems |
175
+ | SOC 2 CC7.2 | System monitoring with cryptographically verifiable audit trail |
176
+ | HIPAA §164.312(b) | Audit control for healthcare AI; raw PHI never recorded |
177
+
178
+ ---
179
+
180
+ ## What Sasana does not do
181
+
182
+ - **Does not record raw content.** Hashes only — you cannot reconstruct prompts or
183
+ responses.
184
+ - **Does not prevent tampering.** Detects it. Detection and prevention are different.
185
+ - **Does not replace LangSmith or Arize.** Those tools are for observability. Sasana is
186
+ for evidence production. They are complementary.
187
+ - **Does not have a managed cloud offering.** Self-hosted only.
188
+
189
+ ---
190
+
191
+ ## License
192
+
193
+ MIT
@@ -0,0 +1,12 @@
1
+ # Archeion — Sasana authority sealing server.
2
+
3
+
4
+ def run() -> None:
5
+ """Entry point for the `archeion` CLI command."""
6
+ try:
7
+ from archeion.server import run as _run
8
+ except ModuleNotFoundError as exc:
9
+ raise SystemExit(
10
+ f"archeion: missing dependency ({exc.name}). Install with: pip install sasana[archeion]"
11
+ ) from exc
12
+ _run()
@@ -0,0 +1,46 @@
1
+ """
2
+ archeion/keys.py — Persistent Ed25519 server keypair.
3
+
4
+ Storage priority:
5
+ 1. ARCHEION_PRIVATE_KEY env var (base64 raw bytes) — for containers / CI.
6
+ 2. ARCHEION_KEY_FILE env var path.
7
+ 3. ~/.archeion/server_key.b64 — default persistent location.
8
+
9
+ A new keypair is generated and persisted when none of the above exist.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ import os
16
+ from pathlib import Path
17
+
18
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
19
+
20
+ _DEFAULT_KEY_FILE = Path.home() / ".archeion" / "server_key.b64"
21
+
22
+
23
+ def _key_path() -> Path:
24
+ return Path(os.environ.get("ARCHEION_KEY_FILE", str(_DEFAULT_KEY_FILE)))
25
+
26
+
27
+ def load_or_generate() -> Ed25519PrivateKey:
28
+ """Return the server private key, generating and persisting one if absent."""
29
+ raw_b64 = os.environ.get("ARCHEION_PRIVATE_KEY")
30
+ if raw_b64:
31
+ return Ed25519PrivateKey.from_private_bytes(base64.b64decode(raw_b64))
32
+
33
+ key_path = _key_path()
34
+ if key_path.exists():
35
+ return Ed25519PrivateKey.from_private_bytes(base64.b64decode(key_path.read_text().strip()))
36
+
37
+ private_key = Ed25519PrivateKey.generate()
38
+ key_path.parent.mkdir(parents=True, exist_ok=True)
39
+ key_path.write_text(base64.b64encode(private_key.private_bytes_raw()).decode())
40
+ key_path.chmod(0o600)
41
+ return private_key
42
+
43
+
44
+ def pubkey_b64(private_key: Ed25519PrivateKey) -> str:
45
+ """Return the base64-encoded raw public key bytes."""
46
+ return base64.b64encode(private_key.public_key().public_bytes_raw()).decode()
@@ -0,0 +1,154 @@
1
+ """
2
+ archeion/server.py — Sasana authority sealing server.
3
+
4
+ Endpoints:
5
+ POST /seal — Verify a session JSONL, append a signed CHAIN_SEAL, return sealed JSONL.
6
+ GET /pubkey — Return the server Ed25519 public key.
7
+
8
+ Run:
9
+ uvicorn archeion.server:app --host 0.0.0.0 --port 8747
10
+
11
+ The server key is loaded from ~/.archeion/server_key.b64 on startup (generated if absent).
12
+ Override with ARCHEION_PRIVATE_KEY (base64) or ARCHEION_KEY_FILE env vars.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import datetime
18
+ import json
19
+ import logging
20
+
21
+ from fastapi import FastAPI, HTTPException, Request
22
+ from fastapi.responses import PlainTextResponse
23
+
24
+ from archeion.keys import load_or_generate, pubkey_b64
25
+ from sasana.signing import sign_event_hash
26
+ from sasana.verifier import compute_event_hash, verify
27
+
28
+ logger = logging.getLogger("archeion")
29
+
30
+ app = FastAPI(
31
+ title="Archeion",
32
+ description="Sasana authority sealing server — upgrades sessions to AUTHORITATIVE_EVIDENCE.",
33
+ version="1.0.0",
34
+ )
35
+
36
+ # Keypair is loaded once at module import so TestClient and uvicorn share the same key.
37
+ _private_key = load_or_generate()
38
+ _pubkey = pubkey_b64(_private_key)
39
+
40
+
41
+ @app.get("/health")
42
+ async def health() -> dict:
43
+ """Liveness + readiness probe. Returns current server pubkey for key-change detection."""
44
+ return {"status": "ok", "version": app.version, "pubkey": _pubkey}
45
+
46
+
47
+ @app.get("/pubkey")
48
+ async def get_pubkey() -> dict:
49
+ """Return the server Ed25519 public key. Clients pin this to trust sealed sessions."""
50
+ return {"pubkey": _pubkey, "algorithm": "ed25519", "encoding": "base64"}
51
+
52
+
53
+ @app.post("/seal", response_class=PlainTextResponse)
54
+ async def seal(request: Request) -> str:
55
+ """
56
+ Accept a session JSONL body, verify its hash chain, append a signed CHAIN_SEAL event,
57
+ and return the sealed JSONL.
58
+
59
+ Errors:
60
+ 400 — malformed or empty body.
61
+ 409 — session already carries a CHAIN_SEAL.
62
+ 422 — hash chain broken or session incomplete.
63
+ """
64
+ body = await request.body()
65
+ try:
66
+ events = [json.loads(ln) for ln in body.decode().splitlines() if ln.strip()]
67
+ except (json.JSONDecodeError, UnicodeDecodeError) as exc:
68
+ raise HTTPException(status_code=400, detail=f"Invalid JSONL: {exc}")
69
+
70
+ if not events:
71
+ raise HTTPException(status_code=400, detail="Empty session body")
72
+
73
+ if any(e.get("event_type") == "CHAIN_SEAL" for e in events):
74
+ raise HTTPException(status_code=409, detail="Session is already sealed")
75
+
76
+ result = verify(events)
77
+ if result.status == "PARTIAL":
78
+ raise HTTPException(
79
+ status_code=422,
80
+ detail=(
81
+ f"Cannot seal: session has {result.log_drop_count} LOG_DROP event(s). "
82
+ "Sealing a partial log would falsely imply a complete record."
83
+ ),
84
+ )
85
+ if result.status != "INTACT":
86
+ raise HTTPException(
87
+ status_code=422,
88
+ detail=f"Cannot seal: session is {result.status}. Errors: {result.errors[:5]}",
89
+ )
90
+ if not result.checks.get("completeness", {}).get("has_session_end"):
91
+ raise HTTPException(status_code=422, detail="Cannot seal: SESSION_END not present")
92
+
93
+ sorted_events = sorted(events, key=lambda e: e.get("seq", 0))
94
+ last = sorted_events[-1]
95
+
96
+ # Verify RFC 3161 timestamp token if SESSION_START carries one
97
+ _ts_verified = False
98
+ _ts_utc: str | None = None
99
+ session_start = next((e for e in sorted_events if e.get("event_type") == "SESSION_START"), None)
100
+ if session_start:
101
+ import base64 as _b64
102
+ import hashlib as _hl
103
+
104
+ from sasana.jcs import canonicalize as _jcs
105
+ from sasana.rfc3161 import verify_timestamp
106
+
107
+ token_b64 = session_start.get("payload", {}).get("rfc3161_token")
108
+ if token_b64:
109
+ try:
110
+ token_der = _b64.b64decode(token_b64)
111
+ payload_no_tok = {
112
+ k: v
113
+ for k, v in session_start.get("payload", {}).items()
114
+ if k != "rfc3161_token"
115
+ }
116
+ ev_no_tok = {
117
+ k: v for k, v in session_start.items() if k not in ("event_hash", "signature")
118
+ }
119
+ ev_no_tok["payload"] = payload_no_tok
120
+ pre_token_hash = _hl.sha256(_jcs(ev_no_tok)).digest()
121
+ _ts_verified, _ts_utc = verify_timestamp(token_der, pre_token_hash)
122
+ except Exception as exc:
123
+ logger.debug("Archeion: RFC 3161 verification skipped: %s", exc)
124
+
125
+ ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z"
126
+ seal_payload: dict = {
127
+ "session_hash": last["event_hash"],
128
+ "sealed_by": "archeion",
129
+ "server_pubkey": _pubkey,
130
+ "timestamp_verified": _ts_verified,
131
+ }
132
+ if _ts_utc is not None:
133
+ seal_payload["timestamp_utc"] = _ts_utc
134
+
135
+ seal_event: dict = {
136
+ "seq": last["seq"] + 1,
137
+ "event_type": "CHAIN_SEAL",
138
+ "session_id": last["session_id"],
139
+ "timestamp": ts,
140
+ "payload": seal_payload,
141
+ "prev_hash": last["event_hash"],
142
+ }
143
+ seal_event["event_hash"] = compute_event_hash(seal_event)
144
+ seal_event["signature"] = sign_event_hash(_private_key, seal_event["event_hash"])
145
+
146
+ logger.info("Sealed session=%s events=%d", last["session_id"], len(sorted_events) + 1)
147
+ return "\n".join(json.dumps(e) for e in sorted_events + [seal_event]) + "\n"
148
+
149
+
150
+ def run() -> None:
151
+ """Entry point for `archeion` CLI command."""
152
+ import uvicorn
153
+
154
+ uvicorn.run("archeion.server:app", host="0.0.0.0", port=8747, reload=False)
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sasana"
7
+ version = "1.0.0"
8
+ description = "Tamper-evident cryptographic audit trail for OpenClaw and multi-agent AI sessions"
9
+ authors = [{name = "Sahir Shaik Ahamed", email = "sahir150305@gmail.com"}]
10
+ readme = "README.md"
11
+ license = { text = "MIT" }
12
+ requires-python = ">=3.10"
13
+ dependencies = [
14
+ "cryptography>=41.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ observer = ["websockets>=12.0", "pyyaml>=6.0"]
19
+ archeion = ["fastapi>=0.110", "uvicorn[standard]>=0.27"]
20
+ dev = ["pytest>=7.0", "ruff>=0.4", "httpx>=0.27", "fastapi>=0.110"]
21
+
22
+ [project.scripts]
23
+ sasana = "sasana_cli:main"
24
+ agentops-verify = "verify:main"
25
+ archeion = "archeion:run"
26
+
27
+ [tool.setuptools.packages.find]
28
+ include = ["sasana*", "archeion*"]
29
+
30
+ [tool.setuptools]
31
+ py-modules = ["verify", "sasana_cli"]
32
+
33
+ [tool.ruff]
34
+ line-length = 100
35
+
36
+ [tool.ruff.lint]
37
+ ignore = ["E501"]
@@ -0,0 +1,16 @@
1
+ """
2
+ sasana — Tamper-evident cryptographic audit trail for OpenClaw sessions.
3
+
4
+ Every session produces two artifacts:
5
+ ~/.openclaw/sasana/<session_id>.db — SHA-256 hash-chained SQLite ledger
6
+ ~/.openclaw/sasana/<session_id>.jsonl — JSONL export for offline verification
7
+
8
+ Verify with:
9
+ sasana verify ~/.openclaw/sasana/<session_id>.jsonl
10
+ """
11
+
12
+ from sasana.skill import SasanaSkill
13
+ from sasana.observer import OpenClawObserver
14
+
15
+ __version__ = "1.0.0"
16
+ __all__ = ["SasanaSkill", "OpenClawObserver"]