sum-engine 0.4.1__tar.gz → 0.6.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.
- {sum_engine-0.4.1 → sum_engine-0.6.0}/PKG-INFO +12 -7
- {sum_engine-0.4.1 → sum_engine-0.6.0}/README.md +3 -3
- {sum_engine-0.4.1 → sum_engine-0.6.0}/pyproject.toml +24 -5
- sum_engine-0.6.0/sum_cli/audit_log.py +171 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_cli/main.py +265 -1
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine.egg-info/PKG-INFO +12 -7
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine.egg-info/SOURCES.txt +44 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine.egg-info/requires.txt +10 -3
- sum_engine-0.6.0/sum_engine_internal/agent_surface/__init__.py +22 -0
- sum_engine-0.6.0/sum_engine_internal/agent_surface/bind.py +138 -0
- sum_engine-0.6.0/sum_engine_internal/agent_surface/mcp_bind.py +677 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/syntactic_sieve.py +9 -1
- sum_engine-0.6.0/sum_engine_internal/compliance/__init__.py +48 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/_predicates.py +86 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/eu_ai_act_article_12.py +197 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/gdpr_article_30.py +207 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/hipaa_164_312_b.py +230 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/iso_27001_8_15.py +160 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/pci_dss_4_req_10.py +264 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/report.py +77 -0
- sum_engine-0.6.0/sum_engine_internal/compliance/soc_2_cc_7_2.py +153 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/llm_dispatch.py +183 -16
- sum_engine-0.6.0/sum_engine_internal/evidence/__init__.py +45 -0
- sum_engine-0.6.0/sum_engine_internal/evidence/chain.py +160 -0
- sum_engine-0.6.0/sum_engine_internal/graph_store/__init__.py +26 -0
- sum_engine-0.6.0/sum_engine_internal/graph_store/base.py +105 -0
- sum_engine-0.6.0/sum_engine_internal/graph_store/egglog_store.py +360 -0
- sum_engine-0.6.0/sum_engine_internal/graph_store/unionfind_store.py +206 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/akashic_ledger.py +34 -11
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/canonical_codec.py +374 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/mcp_server/server.py +172 -1
- sum_engine-0.6.0/sum_engine_internal/research/__init__.py +21 -0
- sum_engine-0.6.0/sum_engine_internal/research/bootstrap/__init__.py +41 -0
- sum_engine-0.6.0/sum_engine_internal/research/bootstrap/multiplier_bootstrap.py +181 -0
- sum_engine-0.6.0/sum_engine_internal/research/conformal/__init__.py +44 -0
- sum_engine-0.6.0/sum_engine_internal/research/conformal/entropy_baseline.py +182 -0
- sum_engine-0.6.0/sum_engine_internal/research/conformal/split_conformal.py +184 -0
- sum_engine-0.6.0/sum_engine_internal/research/lsh/__init__.py +19 -0
- sum_engine-0.6.0/sum_engine_internal/research/lsh/bundle_index.py +179 -0
- sum_engine-0.6.0/sum_engine_internal/research/mmd/__init__.py +54 -0
- sum_engine-0.6.0/sum_engine_internal/research/mmd/baseline.py +328 -0
- sum_engine-0.6.0/sum_engine_internal/research/mmd/mmd.py +184 -0
- sum_engine-0.6.0/sum_engine_internal/research/robust_pca/__init__.py +35 -0
- sum_engine-0.6.0/sum_engine_internal/research/robust_pca/axiom_embedding.py +69 -0
- sum_engine-0.6.0/sum_engine_internal/research/robust_pca/pcp.py +173 -0
- sum_engine-0.6.0/sum_engine_internal/research/sequential/__init__.py +21 -0
- sum_engine-0.6.0/sum_engine_internal/research/sequential/sprt.py +183 -0
- sum_engine-0.6.0/sum_engine_internal/research/sheaf_laplacian.py +231 -0
- sum_engine-0.6.0/sum_engine_internal/research/sheaf_laplacian_v2.py +649 -0
- sum_engine-0.6.0/sum_engine_internal/research/sheaf_laplacian_v3.py +509 -0
- sum_engine-0.6.0/sum_engine_internal/research/sheaf_laplacian_v32.py +170 -0
- sum_engine-0.6.0/sum_engine_internal/research/smt_consistency/__init__.py +34 -0
- sum_engine-0.6.0/sum_engine_internal/research/smt_consistency/consistency.py +188 -0
- sum_engine-0.6.0/sum_engine_internal/research/smt_consistency/predicate_library.py +77 -0
- sum_engine-0.6.0/sum_engine_internal/research/spectral_entropy/__init__.py +41 -0
- sum_engine-0.6.0/sum_engine_internal/research/spectral_entropy/vn_entropy.py +141 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/LICENSE +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/setup.cfg +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_cli/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine.egg-info/dependency_links.txt +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine.egg-info/entry_points.txt +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine.egg-info/top_level.txt +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/adapters/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/adapters/format_pivot.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/causal_discovery.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/chunked_corpus.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/minhash.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/predicate_canon.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/semantic_arithmetic.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/algorithms/zk_semantics.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/automated_scientist.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/autonomous_agent.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/causal_triggers.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/confidence_calibrator.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/data/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/data/common_english_2000.txt +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/data/common_english_5000.txt +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/epistemic_arbiter.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/epistemic_loop.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/extraction_validator.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/gauge_orchestrator.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/live_llm_adapter.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/llm_entailment.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/mass_semantic_engine.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/ouroboros.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/s25_interventions.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/semantic_dedup.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/slider_renderer.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/tome_generator.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/tome_sliders.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/vector_bridge.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/ensemble/venn_abers.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/jcs.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/jose_envelope.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/key_manager.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/p2p_mesh.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/prov_o.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/provenance.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/rate_limiter.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/resource_guards.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/scheme_registry.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/state_encoding.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/telemetry.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/tome_parser.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/verifiable_credential.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/infrastructure/zig_bridge.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/mcp_server/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/mcp_server/__main__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/mcp_server/errors.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/merkle_sidecar/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/merkle_sidecar/tree.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/render_receipt/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/render_receipt/verifier.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/trust_root/__init__.py +0 -0
- {sum_engine-0.4.1 → sum_engine-0.6.0}/sum_engine_internal/trust_root/verifier.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sum-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: SUM — bidirectional knowledge distillation with optional cryptographic attestation. Pipe prose, get a CanonicalBundle (HMAC / Ed25519 / W3C VC 2.0), verify anywhere.
|
|
5
5
|
Author: ototao
|
|
6
6
|
License: Apache-2.0
|
|
@@ -25,9 +25,11 @@ Requires-Dist: cryptography>=41.0.0
|
|
|
25
25
|
Requires-Dist: sympy>=1.12
|
|
26
26
|
Provides-Extra: sieve
|
|
27
27
|
Requires-Dist: spacy>=3.7.0; extra == "sieve"
|
|
28
|
+
Provides-Extra: openai
|
|
29
|
+
Requires-Dist: openai<3.0.0,>=1.40.0; extra == "openai"
|
|
30
|
+
Requires-Dist: pydantic>=2.0.0; extra == "openai"
|
|
28
31
|
Provides-Extra: llm
|
|
29
|
-
Requires-Dist: openai
|
|
30
|
-
Requires-Dist: pydantic>=2.0.0; extra == "llm"
|
|
32
|
+
Requires-Dist: sum-engine[openai]; extra == "llm"
|
|
31
33
|
Provides-Extra: anthropic
|
|
32
34
|
Requires-Dist: anthropic>=0.97.0; extra == "anthropic"
|
|
33
35
|
Requires-Dist: pydantic>=2.0.0; extra == "anthropic"
|
|
@@ -35,6 +37,9 @@ Provides-Extra: receipt-verify
|
|
|
35
37
|
Requires-Dist: joserfc>=1.0.0; extra == "receipt-verify"
|
|
36
38
|
Provides-Extra: mcp
|
|
37
39
|
Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
40
|
+
Provides-Extra: research
|
|
41
|
+
Requires-Dist: numpy>=1.24.0; extra == "research"
|
|
42
|
+
Requires-Dist: scipy>=1.10.0; extra == "research"
|
|
38
43
|
Provides-Extra: omni-format
|
|
39
44
|
Requires-Dist: markitdown==0.1.5; extra == "omni-format"
|
|
40
45
|
Provides-Extra: dev
|
|
@@ -48,7 +53,7 @@ Requires-Dist: build>=1.0.0; extra == "dev"
|
|
|
48
53
|
Requires-Dist: hypothesis>=6.0.0; extra == "dev"
|
|
49
54
|
Provides-Extra: all
|
|
50
55
|
Requires-Dist: sum-engine[sieve]; extra == "all"
|
|
51
|
-
Requires-Dist: sum-engine[
|
|
56
|
+
Requires-Dist: sum-engine[openai]; extra == "all"
|
|
52
57
|
Requires-Dist: sum-engine[anthropic]; extra == "all"
|
|
53
58
|
Requires-Dist: sum-engine[receipt-verify]; extra == "all"
|
|
54
59
|
Requires-Dist: sum-engine[mcp]; extra == "all"
|
|
@@ -106,7 +111,7 @@ A minimal Node verifier using `jose` + `canonicalize` is in [`docs/RENDER_RECEIP
|
|
|
106
111
|
|
|
107
112
|
| Surface | Status | Verifies |
|
|
108
113
|
|---|---|---|
|
|
109
|
-
| `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on
|
|
114
|
+
| `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on PyPI ≥ 0.4.1 | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
|
|
110
115
|
| Cloudflare Worker at `sum-demo.ototao.workers.dev` | shipped | `/api/render` → tome + `render_receipt`; `/.well-known/jwks.json` → JWKS; `/api/qid` → Wikidata resolver |
|
|
111
116
|
| Single-file browser demo (`single_file_demo/index.html`) | shipped | paste prose → in-browser attest → CanonicalBundle JSON; same bytes verify under `node standalone_verifier/verify.js` (Chrome / Firefox / Safari with WebCrypto Ed25519 support) |
|
|
112
117
|
| Cross-runtime trust triangle | locked by CI (`make xruntime`) | K1 / K1-mw / K2 / K3 / K4 — Python ↔ Node ↔ Browser agree byte-for-byte on valid bundles. `make xruntime-adversarial` adds A1–A6 rejection-class equivalence. |
|
|
@@ -162,7 +167,7 @@ The reverse direction also runs under explicit slider control. The local path ac
|
|
|
162
167
|
sum render --density 0.5 < bundle.json
|
|
163
168
|
# → keeps the lex-prefix half of the axioms; @sliders header records what was requested
|
|
164
169
|
|
|
165
|
-
sum render --length 0.9 --use-worker https://sum.ototao.
|
|
170
|
+
sum render --length 0.9 --use-worker https://sum-demo.ototao.workers.dev --json < bundle.json
|
|
166
171
|
# → LLM-conditioned tome + signed render_receipt (sum.render_receipt.v1) on stdout
|
|
167
172
|
```
|
|
168
173
|
|
|
@@ -189,7 +194,7 @@ pip install 'sum-engine[mcp,sieve]'
|
|
|
189
194
|
|
|
190
195
|
### Calling SUM over HTTP
|
|
191
196
|
|
|
192
|
-
The hosted Worker at `https://sum.ototao.
|
|
197
|
+
The hosted Worker at `https://sum-demo.ototao.workers.dev` exposes `/api/render`, `/api/complete`, `/api/qid`, and the `/.well-known/{jwks,revoked-kids}.json` verification surfaces. [`docs/API_REFERENCE.md`](docs/API_REFERENCE.md) is the wire spec — request/response shapes, error codes, the six-step receipt-verification flow, working Node + Python examples. Use this when the caller is a web app, mobile app, or server-side service; use the MCP server when the caller is a local LLM client.
|
|
193
198
|
|
|
194
199
|
---
|
|
195
200
|
|
|
@@ -48,7 +48,7 @@ A minimal Node verifier using `jose` + `canonicalize` is in [`docs/RENDER_RECEIP
|
|
|
48
48
|
|
|
49
49
|
| Surface | Status | Verifies |
|
|
50
50
|
|---|---|---|
|
|
51
|
-
| `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on
|
|
51
|
+
| `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on PyPI ≥ 0.4.1 | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
|
|
52
52
|
| Cloudflare Worker at `sum-demo.ototao.workers.dev` | shipped | `/api/render` → tome + `render_receipt`; `/.well-known/jwks.json` → JWKS; `/api/qid` → Wikidata resolver |
|
|
53
53
|
| Single-file browser demo (`single_file_demo/index.html`) | shipped | paste prose → in-browser attest → CanonicalBundle JSON; same bytes verify under `node standalone_verifier/verify.js` (Chrome / Firefox / Safari with WebCrypto Ed25519 support) |
|
|
54
54
|
| Cross-runtime trust triangle | locked by CI (`make xruntime`) | K1 / K1-mw / K2 / K3 / K4 — Python ↔ Node ↔ Browser agree byte-for-byte on valid bundles. `make xruntime-adversarial` adds A1–A6 rejection-class equivalence. |
|
|
@@ -104,7 +104,7 @@ The reverse direction also runs under explicit slider control. The local path ac
|
|
|
104
104
|
sum render --density 0.5 < bundle.json
|
|
105
105
|
# → keeps the lex-prefix half of the axioms; @sliders header records what was requested
|
|
106
106
|
|
|
107
|
-
sum render --length 0.9 --use-worker https://sum.ototao.
|
|
107
|
+
sum render --length 0.9 --use-worker https://sum-demo.ototao.workers.dev --json < bundle.json
|
|
108
108
|
# → LLM-conditioned tome + signed render_receipt (sum.render_receipt.v1) on stdout
|
|
109
109
|
```
|
|
110
110
|
|
|
@@ -131,7 +131,7 @@ pip install 'sum-engine[mcp,sieve]'
|
|
|
131
131
|
|
|
132
132
|
### Calling SUM over HTTP
|
|
133
133
|
|
|
134
|
-
The hosted Worker at `https://sum.ototao.
|
|
134
|
+
The hosted Worker at `https://sum-demo.ototao.workers.dev` exposes `/api/render`, `/api/complete`, `/api/qid`, and the `/.well-known/{jwks,revoked-kids}.json` verification surfaces. [`docs/API_REFERENCE.md`](docs/API_REFERENCE.md) is the wire spec — request/response shapes, error codes, the six-step receipt-verification flow, working Node + Python examples. Use this when the caller is a web app, mobile app, or server-side service; use the MCP server when the caller is a local LLM client.
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sum-engine"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
description = "SUM — bidirectional knowledge distillation with optional cryptographic attestation. Pipe prose, get a CanonicalBundle (HMAC / Ed25519 / W3C VC 2.0), verify anywhere."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -39,10 +39,19 @@ dependencies = [
|
|
|
39
39
|
[project.optional-dependencies]
|
|
40
40
|
# Dependency-injection extras. Users pick the extractor they want:
|
|
41
41
|
# pip install sum-engine[sieve] # deterministic, offline spaCy path
|
|
42
|
-
# pip install sum-engine[
|
|
43
|
-
# pip install sum-engine[
|
|
42
|
+
# pip install sum-engine[openai] # OpenAI structured-output path
|
|
43
|
+
# pip install sum-engine[llm] # alias for [openai] (legacy name)
|
|
44
|
+
# pip install sum-engine[all] # everything, plus dev tooling
|
|
44
45
|
sieve = ["spacy>=3.7.0"]
|
|
45
|
-
|
|
46
|
+
# `[openai]` is the canonical, vendor-named extra; `[llm]` is kept as a
|
|
47
|
+
# back-compat alias because it predates the multi-provider dispatcher
|
|
48
|
+
# (Anthropic and OpenAI now have their own named extras). Both install
|
|
49
|
+
# identical dependencies — the openai SDK plus pydantic for structured
|
|
50
|
+
# outputs. The vendor adapter is at
|
|
51
|
+
# sum_engine_internal/ensemble/llm_dispatch.py::OpenAIAdapter; the
|
|
52
|
+
# render-path TS companion is worker/src/routes/render.ts::callOpenAI.
|
|
53
|
+
openai = ["openai>=1.40.0,<3.0.0", "pydantic>=2.0.0"]
|
|
54
|
+
llm = ["sum-engine[openai]"]
|
|
46
55
|
# Anthropic Messages-API adapter (Claude family). Installed alongside
|
|
47
56
|
# [llm] for the §2.5 frontier-LLM benchmark runs that compare
|
|
48
57
|
# generator-side interventions across providers. The runner picks
|
|
@@ -60,6 +69,16 @@ receipt-verify = ["joserfc>=1.0.0"]
|
|
|
60
69
|
# `mcp` package is the official Python SDK; FastMCP is its
|
|
61
70
|
# decorator-style high-level API. See docs/MCP_INTEGRATION.md.
|
|
62
71
|
mcp = ["mcp>=1.0.0"]
|
|
72
|
+
# Research-grade modules under sum_engine_internal/research/. NOT on
|
|
73
|
+
# the production install path; APIs may change between minor releases
|
|
74
|
+
# without backwards-compatibility guarantees. Currently provides the
|
|
75
|
+
# v1 sheaf-Laplacian hallucination detector grounded in Gebhart,
|
|
76
|
+
# Hansen & Schrater (2023, AISTATS, arXiv:2110.03789) and the
|
|
77
|
+
# sheaf-Laplacian theory of Hansen & Ghrist (2019). See
|
|
78
|
+
# docs/SHEAF_HALLUCINATION_DETECTOR.md for the spec, including
|
|
79
|
+
# verified blindspots (predicate-flip, off-graph fact-fabrication,
|
|
80
|
+
# empty-render false negative).
|
|
81
|
+
research = ["numpy>=1.24.0", "scipy>=1.10.0"]
|
|
63
82
|
# Omni-format adapter. Markdown is the canonical pivot for the
|
|
64
83
|
# attest pipeline: any input format -> markdown -> existing
|
|
65
84
|
# extract/state/bundle path. Source URI anchors to the original
|
|
@@ -97,7 +116,7 @@ dev = [
|
|
|
97
116
|
]
|
|
98
117
|
all = [
|
|
99
118
|
"sum-engine[sieve]",
|
|
100
|
-
"sum-engine[
|
|
119
|
+
"sum-engine[openai]",
|
|
101
120
|
"sum-engine[anthropic]",
|
|
102
121
|
"sum-engine[receipt-verify]",
|
|
103
122
|
"sum-engine[mcp]",
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""sum_cli.audit_log — universal audit-log streaming for compliance.
|
|
2
|
+
|
|
3
|
+
When the ``SUM_AUDIT_LOG`` environment variable is set to a file path,
|
|
4
|
+
every ``sum attest`` / ``sum verify`` / ``sum render`` operation
|
|
5
|
+
appends a single JSONL row to that file describing what was done.
|
|
6
|
+
|
|
7
|
+
This is the regime-agnostic foundation of the compliance primitives
|
|
8
|
+
direction (Path 3). A specific regime (GDPR-Art-30, HIPAA-164.514,
|
|
9
|
+
EU AI Act Annex IV, etc.) can be implemented as a downstream
|
|
10
|
+
consumer of this audit log: tail the file, validate per-regime
|
|
11
|
+
required fields, raise on policy violation. The audit log itself
|
|
12
|
+
makes no regime-specific assumptions — it records *what happened*
|
|
13
|
+
verbatim so any auditor can reconstruct the operation.
|
|
14
|
+
|
|
15
|
+
Schema: ``sum.audit_log.v1`` — additive; new optional fields may
|
|
16
|
+
appear in future minor versions; consumers should ignore unknown
|
|
17
|
+
keys.
|
|
18
|
+
|
|
19
|
+
Required fields per row:
|
|
20
|
+
- ``schema`` = ``"sum.audit_log.v1"``
|
|
21
|
+
- ``timestamp`` (ISO 8601 UTC, e.g. ``"2026-05-01T18:35:14.123Z"``)
|
|
22
|
+
- ``operation`` ∈ ``{"attest", "verify", "render"}``
|
|
23
|
+
- ``cli_version`` (the ``sum-cli`` version that produced the row)
|
|
24
|
+
|
|
25
|
+
Operation-specific optional fields:
|
|
26
|
+
- attest: ``source_uri``, ``state_integer_digits``, ``axiom_count``,
|
|
27
|
+
``extractor``, ``signed`` (bool — Ed25519 attached?),
|
|
28
|
+
``hmac`` (bool), ``branch``
|
|
29
|
+
- verify: ``state_integer_digits``, ``axiom_count``, ``signatures``
|
|
30
|
+
``{ed25519, hmac}``, ``branch``, ``ok`` (bool)
|
|
31
|
+
- render: ``axiom_count_input``, ``mode`` (``"local-deterministic"``
|
|
32
|
+
or ``"worker"``), ``sliders``, ``render_receipt_kid`` (if
|
|
33
|
+
worker), ``worker_url`` (if worker)
|
|
34
|
+
|
|
35
|
+
Failures (exit code != 0) emit a row with ``error`` field describing
|
|
36
|
+
the failure class. Audit-log writes themselves never fail loudly —
|
|
37
|
+
if ``SUM_AUDIT_LOG`` points at a non-writable path, the operation
|
|
38
|
+
proceeds and the failure is reported on stderr only when ``--verbose``
|
|
39
|
+
is set; the audit semantics fail-open by design (a non-functional
|
|
40
|
+
audit destination should not break the trust loop).
|
|
41
|
+
|
|
42
|
+
Env var precedence:
|
|
43
|
+
1. ``SUM_AUDIT_LOG`` env var (path to JSONL file; appended to)
|
|
44
|
+
2. unset → no audit logging
|
|
45
|
+
|
|
46
|
+
The path may be ``-`` for stdout (rare; mostly useful for piping
|
|
47
|
+
into another tool); otherwise treated as a file path with append-mode
|
|
48
|
+
open.
|
|
49
|
+
|
|
50
|
+
Concurrency: writes are O_APPEND on POSIX, so multiple sum
|
|
51
|
+
processes writing to the same audit log produce a serialised
|
|
52
|
+
ordering (atomic per write() up to PIPE_BUF on most systems —
|
|
53
|
+
single-line JSONL records well under that bound). We write each
|
|
54
|
+
row in one ``f.write()`` call to maximise atomicity.
|
|
55
|
+
"""
|
|
56
|
+
from __future__ import annotations
|
|
57
|
+
|
|
58
|
+
import json
|
|
59
|
+
import os
|
|
60
|
+
import sys
|
|
61
|
+
from datetime import datetime, timezone
|
|
62
|
+
from typing import Any
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
_AUDIT_LOG_SCHEMA = "sum.audit_log.v1"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _audit_log_path() -> str | None:
|
|
69
|
+
"""Return the configured audit-log destination, or None if unset."""
|
|
70
|
+
p = os.environ.get("SUM_AUDIT_LOG")
|
|
71
|
+
if p is None or p == "":
|
|
72
|
+
return None
|
|
73
|
+
return p
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _identity_fields() -> dict[str, Any]:
|
|
77
|
+
"""Optional identity fields populated from env vars.
|
|
78
|
+
|
|
79
|
+
Sprint 4 of the intensification path to arXiv (PR #140). Closes
|
|
80
|
+
the PCI DSS Req 10.2.2 user-identification gap named in
|
|
81
|
+
``docs/COMPLIANCE_PCI_DSS_4_REQ_10.md``. Three env vars are
|
|
82
|
+
consulted; each populated value becomes an optional field on
|
|
83
|
+
the audit-log row:
|
|
84
|
+
|
|
85
|
+
- ``SUM_AUDIT_USER_ID`` → ``user_id`` field. Per PCI Req 10.2.2,
|
|
86
|
+
"user identification" is the FIRST required field for each
|
|
87
|
+
audit-log event. Operators running SUM behind an
|
|
88
|
+
authenticating proxy populate this from the proxy's session
|
|
89
|
+
identity at process start.
|
|
90
|
+
- ``SUM_AUDIT_HOST_ID`` → ``host_id`` field. Multi-host
|
|
91
|
+
deployments (clusters, k8s pods, container fleets) use this
|
|
92
|
+
to attribute events to specific compute units.
|
|
93
|
+
- ``SUM_AUDIT_IP_ADDRESS`` → ``ip_address`` field. Network-
|
|
94
|
+
layer origination, useful for incident-response / forensic
|
|
95
|
+
analysis under PCI Req 10.2.2's "origination of event"
|
|
96
|
+
requirement.
|
|
97
|
+
|
|
98
|
+
All three are *optional*. An unset env var produces an absent
|
|
99
|
+
field (not a null value). The audit-log schema stays at
|
|
100
|
+
``sum.audit_log.v1``; these are additive optional fields under
|
|
101
|
+
the existing schema's "consumers should ignore unknown keys"
|
|
102
|
+
convention. Backward compat: rows without these fields still
|
|
103
|
+
pass every existing validator.
|
|
104
|
+
|
|
105
|
+
PCI DSS validator R7 (``pci-dss-4-req-10.user-identification-
|
|
106
|
+
recommended``, added in the same PR as this function) treats
|
|
107
|
+
a missing ``user_id`` as a Req 10.2.2 violation in compliance-
|
|
108
|
+
mode runs.
|
|
109
|
+
"""
|
|
110
|
+
out: dict[str, Any] = {}
|
|
111
|
+
for env_var, field in (
|
|
112
|
+
("SUM_AUDIT_USER_ID", "user_id"),
|
|
113
|
+
("SUM_AUDIT_HOST_ID", "host_id"),
|
|
114
|
+
("SUM_AUDIT_IP_ADDRESS", "ip_address"),
|
|
115
|
+
):
|
|
116
|
+
value = os.environ.get(env_var)
|
|
117
|
+
if value: # non-empty (skips both None and empty string)
|
|
118
|
+
out[field] = value
|
|
119
|
+
return out
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def emit_audit_event(operation: str, payload: dict[str, Any]) -> None:
|
|
123
|
+
"""Append a single JSONL row to the audit log if configured.
|
|
124
|
+
|
|
125
|
+
Fail-open: if the audit destination is unwritable, the operation
|
|
126
|
+
proceeds normally; the failure is silent unless verbose stderr
|
|
127
|
+
is enabled by the caller. Audit logging is *advisory* — the
|
|
128
|
+
canonical bundle / receipt still carries the load-bearing trust
|
|
129
|
+
properties; the audit log is for downstream compliance tools
|
|
130
|
+
to ingest.
|
|
131
|
+
|
|
132
|
+
Identity fields (``user_id`` / ``host_id`` / ``ip_address``) are
|
|
133
|
+
populated from env vars by :func:`_identity_fields`. They appear
|
|
134
|
+
on the row only when the corresponding env var is set; absent
|
|
135
|
+
env vars produce absent fields (not null values). The
|
|
136
|
+
``payload`` argument takes precedence over identity fields if
|
|
137
|
+
a caller passes overlapping keys — useful for tests that want
|
|
138
|
+
to pin specific identity values without touching the
|
|
139
|
+
environment.
|
|
140
|
+
"""
|
|
141
|
+
path = _audit_log_path()
|
|
142
|
+
if path is None:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
from sum_cli import __version__
|
|
146
|
+
|
|
147
|
+
row = {
|
|
148
|
+
"schema": _AUDIT_LOG_SCHEMA,
|
|
149
|
+
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.")
|
|
150
|
+
+ f"{datetime.now(timezone.utc).microsecond // 1000:03d}Z",
|
|
151
|
+
"operation": operation,
|
|
152
|
+
"cli_version": __version__,
|
|
153
|
+
# Identity fields from env, then payload last so the caller
|
|
154
|
+
# wins on overlap (intentional for test seams).
|
|
155
|
+
**_identity_fields(),
|
|
156
|
+
**payload,
|
|
157
|
+
}
|
|
158
|
+
line = json.dumps(row, separators=(",", ":")) + "\n"
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
if path == "-":
|
|
162
|
+
sys.stdout.write(line)
|
|
163
|
+
sys.stdout.flush()
|
|
164
|
+
else:
|
|
165
|
+
with open(path, "a", encoding="utf-8") as f:
|
|
166
|
+
f.write(line)
|
|
167
|
+
except OSError:
|
|
168
|
+
# Fail-open: audit destination unavailable; do not block
|
|
169
|
+
# the operation. The canonical bundle / receipt remains the
|
|
170
|
+
# load-bearing trust artifact.
|
|
171
|
+
pass
|
|
@@ -420,6 +420,18 @@ def cmd_attest(args: argparse.Namespace) -> int:
|
|
|
420
420
|
f"{len(bundle['state_integer'])} digits",
|
|
421
421
|
file=sys.stderr,
|
|
422
422
|
)
|
|
423
|
+
|
|
424
|
+
from sum_cli.audit_log import emit_audit_event
|
|
425
|
+
emit_audit_event("attest", {
|
|
426
|
+
"source_uri": source_uri,
|
|
427
|
+
"axiom_count": len(triples),
|
|
428
|
+
"state_integer_digits": len(bundle["state_integer"]),
|
|
429
|
+
"extractor": extractor,
|
|
430
|
+
"branch": args.branch,
|
|
431
|
+
"signed": "public_signature" in bundle,
|
|
432
|
+
"hmac": "signature" in bundle,
|
|
433
|
+
"input_format": format_sidecar.get("input_format"),
|
|
434
|
+
})
|
|
423
435
|
return 0
|
|
424
436
|
|
|
425
437
|
|
|
@@ -844,6 +856,16 @@ def cmd_verify(args: argparse.Namespace) -> int:
|
|
|
844
856
|
f"sum: ✓ verified {axioms} axiom(s), state integer matches ({marks})",
|
|
845
857
|
file=sys.stderr,
|
|
846
858
|
)
|
|
859
|
+
|
|
860
|
+
from sum_cli.audit_log import emit_audit_event
|
|
861
|
+
emit_audit_event("verify", {
|
|
862
|
+
"ok": True,
|
|
863
|
+
"axiom_count": axioms,
|
|
864
|
+
"state_integer_digits": len(claimed_state_str),
|
|
865
|
+
"branch": bundle.get("branch", "main"),
|
|
866
|
+
"signatures": {"hmac": hmac_status, "ed25519": ed25519_status},
|
|
867
|
+
"extraction": extraction,
|
|
868
|
+
})
|
|
847
869
|
return 0
|
|
848
870
|
|
|
849
871
|
|
|
@@ -926,7 +948,15 @@ def _post_render_to_worker(
|
|
|
926
948
|
endpoint,
|
|
927
949
|
data=payload,
|
|
928
950
|
method="POST",
|
|
929
|
-
headers={
|
|
951
|
+
headers={
|
|
952
|
+
"content-type": "application/json",
|
|
953
|
+
# Cloudflare in front of hosted Workers (incl. the
|
|
954
|
+
# default sum-demo.ototao.workers.dev) rejects the
|
|
955
|
+
# default Python urllib User-Agent with HTTP 403 /
|
|
956
|
+
# error 1010. Identify ourselves as a known scripted
|
|
957
|
+
# client so the request passes the bot-detection layer.
|
|
958
|
+
"user-agent": f"sum-cli/{__version__} (+https://github.com/OtotaO/SUM)",
|
|
959
|
+
},
|
|
930
960
|
)
|
|
931
961
|
try:
|
|
932
962
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
@@ -1130,6 +1160,20 @@ def _emit_render_output(envelope: dict, args: argparse.Namespace) -> int:
|
|
|
1130
1160
|
kid = envelope["render_receipt"].get("kid", "?")
|
|
1131
1161
|
msg += f", receipt kid={kid}"
|
|
1132
1162
|
print(msg, file=sys.stderr)
|
|
1163
|
+
|
|
1164
|
+
from sum_cli.audit_log import emit_audit_event
|
|
1165
|
+
audit_payload: dict = {
|
|
1166
|
+
"mode": envelope.get("mode"),
|
|
1167
|
+
"axiom_count_input": envelope.get("axiom_count_input"),
|
|
1168
|
+
"tome_chars": len(tome_text),
|
|
1169
|
+
"sliders": envelope.get("sliders"),
|
|
1170
|
+
}
|
|
1171
|
+
if "render_receipt" in envelope:
|
|
1172
|
+
receipt = envelope["render_receipt"]
|
|
1173
|
+
audit_payload["render_receipt_kid"] = receipt.get("kid")
|
|
1174
|
+
audit_payload["render_receipt_schema"] = receipt.get("schema")
|
|
1175
|
+
audit_payload["worker_url"] = envelope.get("worker_url")
|
|
1176
|
+
emit_audit_event("render", audit_payload)
|
|
1133
1177
|
return 0
|
|
1134
1178
|
|
|
1135
1179
|
|
|
@@ -1499,6 +1543,172 @@ def cmd_schema(args: argparse.Namespace) -> int:
|
|
|
1499
1543
|
return 0
|
|
1500
1544
|
|
|
1501
1545
|
|
|
1546
|
+
# ─── compliance ──────────────────────────────────────────────────────
|
|
1547
|
+
#
|
|
1548
|
+
# Per-regime validators consuming sum.audit_log.v1. The audit log is
|
|
1549
|
+
# regime-agnostic substrate; ``sum compliance check`` is the actionable
|
|
1550
|
+
# layer that turns it into a pass/fail verdict for a specific regime.
|
|
1551
|
+
# Exit code 0 when ok=true, 1 otherwise — pipe-friendly for CI gates.
|
|
1552
|
+
|
|
1553
|
+
_COMPLIANCE_REGIMES: dict[str, str] = {
|
|
1554
|
+
"eu-ai-act-article-12": (
|
|
1555
|
+
"EU AI Act (Regulation (EU) 2024/1689) Article 12 — record-"
|
|
1556
|
+
"keeping for high-risk AI systems. Pins per-row traceability "
|
|
1557
|
+
"fields (timestamp, operation, cli_version), schema, and "
|
|
1558
|
+
"operation-specific anchors (source_uri / axiom_count / "
|
|
1559
|
+
"state_integer_digits / mode)."
|
|
1560
|
+
),
|
|
1561
|
+
"gdpr-article-30": (
|
|
1562
|
+
"GDPR (Regulation (EU) 2016/679) Article 30 — Records of "
|
|
1563
|
+
"Processing Activities. Pins the per-row floor enabling Art "
|
|
1564
|
+
"30 reporting (schema, timestamp, ISO-8601-UTC parseability, "
|
|
1565
|
+
"processing-category indicator, processor identity). The "
|
|
1566
|
+
"controller separately maintains record-set-scope metadata "
|
|
1567
|
+
"(Art 30(1)(a)–(g) controller name, purposes, categories, "
|
|
1568
|
+
"recipients, transfers, retention, security measures) "
|
|
1569
|
+
"out-of-band; this validator does not pin those."
|
|
1570
|
+
),
|
|
1571
|
+
"hipaa-164-312-b": (
|
|
1572
|
+
"HIPAA Security Rule 45 CFR § 164.312(b) — Audit Controls "
|
|
1573
|
+
"(Technical Safeguards). Pins the per-row form requirements "
|
|
1574
|
+
"for an audit recording that supports examination of "
|
|
1575
|
+
"activity (schema, timestamp, ISO-8601-UTC, activity type, "
|
|
1576
|
+
"system component identification, per-operation examination "
|
|
1577
|
+
"anchors). Deployment-scope obligations — auditor function, "
|
|
1578
|
+
"retention, ePHI inventory — live outside this validator."
|
|
1579
|
+
),
|
|
1580
|
+
"iso-27001-8-15": (
|
|
1581
|
+
"ISO/IEC 27001:2022 Annex A.8.15 — Logging. Pins the per-"
|
|
1582
|
+
"row form floor an audit log must satisfy for the recording "
|
|
1583
|
+
"to count as a 'produced' log under A.8.15 (schema, "
|
|
1584
|
+
"timestamp, ISO-8601-UTC, activity, system component). The "
|
|
1585
|
+
"'stored', 'protected', 'analysed' verbs map to deployment-"
|
|
1586
|
+
"scope obligations (file-system policy, access control, "
|
|
1587
|
+
"SIEM integration) outside this validator."
|
|
1588
|
+
),
|
|
1589
|
+
"soc-2-cc-7-2": (
|
|
1590
|
+
"SOC 2 Trust Services Criteria CC7.2 — System Operations. "
|
|
1591
|
+
"Pins the per-row form floor required to enable the "
|
|
1592
|
+
"monitoring criterion (schema, timestamp, ISO-8601-UTC, "
|
|
1593
|
+
"activity classification, system component identification). "
|
|
1594
|
+
"The detection / monitoring / analysis activities themselves "
|
|
1595
|
+
"(SIEM rules, alert routing, oncall rotations) live at "
|
|
1596
|
+
"deployment scope outside this validator."
|
|
1597
|
+
),
|
|
1598
|
+
"pci-dss-4-req-10": (
|
|
1599
|
+
"PCI DSS v4.0 Requirement 10 — Log and Monitor All Access "
|
|
1600
|
+
"to System Components and Cardholder Data. Pins per-row "
|
|
1601
|
+
"content visible in audit_log.v1 against Req 10.2.2 (event "
|
|
1602
|
+
"content) plus 10.6 (consistent time): schema, timestamp, "
|
|
1603
|
+
"ISO-8601-UTC, event type, origination, event-content "
|
|
1604
|
+
"completeness. NOT pinned: 10.1 organisational policies; "
|
|
1605
|
+
"10.2.1.* specific event-type coverage; 10.2.2 user "
|
|
1606
|
+
"identification (audit_log.v1 has no user_id field); 10.3 "
|
|
1607
|
+
"log file protection; 10.4 log review process; 10.5 12-"
|
|
1608
|
+
"month retention; 10.7 failure detection / alerting."
|
|
1609
|
+
),
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
|
|
1613
|
+
def _compliance_validators():
|
|
1614
|
+
"""Return the regime → validate-callable dispatch table.
|
|
1615
|
+
|
|
1616
|
+
Built lazily to avoid importing compliance modules at CLI
|
|
1617
|
+
startup (each module imports the dataclass infrastructure;
|
|
1618
|
+
deferring keeps `sum --help` cold-start fast). Registered
|
|
1619
|
+
regimes must match :data:`_COMPLIANCE_REGIMES` keys exactly —
|
|
1620
|
+
a mismatch surfaces as a KeyError at dispatch, intentional
|
|
1621
|
+
(better than silent fallthrough)."""
|
|
1622
|
+
from sum_engine_internal.compliance import ( # local import — see docstring
|
|
1623
|
+
eu_ai_act_article_12,
|
|
1624
|
+
gdpr_article_30,
|
|
1625
|
+
hipaa_164_312_b,
|
|
1626
|
+
iso_27001_8_15,
|
|
1627
|
+
pci_dss_4_req_10,
|
|
1628
|
+
soc_2_cc_7_2,
|
|
1629
|
+
)
|
|
1630
|
+
return {
|
|
1631
|
+
"eu-ai-act-article-12": eu_ai_act_article_12.validate,
|
|
1632
|
+
"gdpr-article-30": gdpr_article_30.validate,
|
|
1633
|
+
"hipaa-164-312-b": hipaa_164_312_b.validate,
|
|
1634
|
+
"iso-27001-8-15": iso_27001_8_15.validate,
|
|
1635
|
+
"soc-2-cc-7-2": soc_2_cc_7_2.validate,
|
|
1636
|
+
"pci-dss-4-req-10": pci_dss_4_req_10.validate,
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
|
|
1640
|
+
def cmd_compliance_check(args: argparse.Namespace) -> int:
|
|
1641
|
+
if args.regime not in _COMPLIANCE_REGIMES:
|
|
1642
|
+
print(
|
|
1643
|
+
f"sum: unknown regime {args.regime!r}. Known: "
|
|
1644
|
+
f"{sorted(_COMPLIANCE_REGIMES)}",
|
|
1645
|
+
file=sys.stderr,
|
|
1646
|
+
)
|
|
1647
|
+
return 2
|
|
1648
|
+
|
|
1649
|
+
if args.audit_log == "-":
|
|
1650
|
+
text = sys.stdin.read()
|
|
1651
|
+
else:
|
|
1652
|
+
try:
|
|
1653
|
+
with open(args.audit_log, "r", encoding="utf-8") as f:
|
|
1654
|
+
text = f.read()
|
|
1655
|
+
except OSError as e:
|
|
1656
|
+
print(f"sum: cannot read --audit-log {args.audit_log!r}: {e}", file=sys.stderr)
|
|
1657
|
+
return 2
|
|
1658
|
+
|
|
1659
|
+
rows: list[dict] = []
|
|
1660
|
+
parse_errors: list[tuple[int, str]] = []
|
|
1661
|
+
for i, line in enumerate(text.splitlines()):
|
|
1662
|
+
line = line.strip()
|
|
1663
|
+
if not line:
|
|
1664
|
+
continue
|
|
1665
|
+
try:
|
|
1666
|
+
rows.append(json.loads(line))
|
|
1667
|
+
except json.JSONDecodeError as e:
|
|
1668
|
+
parse_errors.append((i, str(e)))
|
|
1669
|
+
|
|
1670
|
+
validators = _compliance_validators()
|
|
1671
|
+
try:
|
|
1672
|
+
validate = validators[args.regime]
|
|
1673
|
+
except KeyError:
|
|
1674
|
+
# Defensive — _COMPLIANCE_REGIMES gate above should have caught this.
|
|
1675
|
+
# Reaching here means a regime is registered in _COMPLIANCE_REGIMES
|
|
1676
|
+
# but missing from _compliance_validators() — a wiring drift.
|
|
1677
|
+
print(
|
|
1678
|
+
f"sum: regime {args.regime!r} listed but not wired "
|
|
1679
|
+
f"(internal: missing from _compliance_validators dispatch)",
|
|
1680
|
+
file=sys.stderr,
|
|
1681
|
+
)
|
|
1682
|
+
return 2
|
|
1683
|
+
report = validate(rows)
|
|
1684
|
+
|
|
1685
|
+
out = report.to_dict()
|
|
1686
|
+
if parse_errors:
|
|
1687
|
+
# Surface parse errors alongside rule violations — both are
|
|
1688
|
+
# compliance-relevant. A malformed JSONL line is itself a
|
|
1689
|
+
# traceability defect.
|
|
1690
|
+
out["parse_errors"] = [
|
|
1691
|
+
{"line_index": idx, "error": msg} for idx, msg in parse_errors
|
|
1692
|
+
]
|
|
1693
|
+
|
|
1694
|
+
json.dump(out, sys.stdout, indent=2 if args.pretty else None)
|
|
1695
|
+
sys.stdout.write("\n")
|
|
1696
|
+
return 0 if (report.ok and not parse_errors) else 1
|
|
1697
|
+
|
|
1698
|
+
|
|
1699
|
+
def cmd_compliance_regimes(args: argparse.Namespace) -> int:
|
|
1700
|
+
out = {
|
|
1701
|
+
"schema": "sum.compliance_regimes.v1",
|
|
1702
|
+
"regimes": [
|
|
1703
|
+
{"id": rid, "description": desc}
|
|
1704
|
+
for rid, desc in sorted(_COMPLIANCE_REGIMES.items())
|
|
1705
|
+
],
|
|
1706
|
+
}
|
|
1707
|
+
json.dump(out, sys.stdout, indent=2)
|
|
1708
|
+
sys.stdout.write("\n")
|
|
1709
|
+
return 0
|
|
1710
|
+
|
|
1711
|
+
|
|
1502
1712
|
# ─── Argparse wiring ─────────────────────────────────────────────────
|
|
1503
1713
|
|
|
1504
1714
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -1878,6 +2088,60 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1878
2088
|
)
|
|
1879
2089
|
p_schema.set_defaults(func=cmd_schema)
|
|
1880
2090
|
|
|
2091
|
+
# compliance — regime validators consuming sum.audit_log.v1.
|
|
2092
|
+
p_compliance = subparsers.add_parser(
|
|
2093
|
+
"compliance",
|
|
2094
|
+
help="Validate a sum.audit_log.v1 stream against a compliance regime.",
|
|
2095
|
+
description=(
|
|
2096
|
+
"Apply a per-regime validator to a sum.audit_log.v1 JSONL stream "
|
|
2097
|
+
"and emit a sum.compliance_report.v1 verdict. The audit log is "
|
|
2098
|
+
"regime-agnostic substrate; this verb is the actionable layer "
|
|
2099
|
+
"that turns it into a compliance-grade pass/fail."
|
|
2100
|
+
),
|
|
2101
|
+
)
|
|
2102
|
+
p_compliance_sub = p_compliance.add_subparsers(
|
|
2103
|
+
dest="compliance_cmd", required=True, metavar="<compliance-cmd>",
|
|
2104
|
+
)
|
|
2105
|
+
|
|
2106
|
+
p_comp_check = p_compliance_sub.add_parser(
|
|
2107
|
+
"check",
|
|
2108
|
+
help="Validate an audit-log stream against a regime; emit a JSON report.",
|
|
2109
|
+
description=(
|
|
2110
|
+
"Read a sum.audit_log.v1 JSONL stream from --audit-log (or stdin "
|
|
2111
|
+
"if '-'), validate against the named regime, emit a "
|
|
2112
|
+
"sum.compliance_report.v1 JSON object on stdout. Exit code is "
|
|
2113
|
+
"0 when ok=true, 1 otherwise — pipe-friendly for CI gates."
|
|
2114
|
+
),
|
|
2115
|
+
)
|
|
2116
|
+
p_comp_check.add_argument(
|
|
2117
|
+
"--regime",
|
|
2118
|
+
required=True,
|
|
2119
|
+
choices=["eu-ai-act-article-12"],
|
|
2120
|
+
help="Compliance regime to validate against.",
|
|
2121
|
+
)
|
|
2122
|
+
p_comp_check.add_argument(
|
|
2123
|
+
"--audit-log",
|
|
2124
|
+
required=True,
|
|
2125
|
+
help="Path to a sum.audit_log.v1 JSONL file ('-' for stdin).",
|
|
2126
|
+
)
|
|
2127
|
+
p_comp_check.add_argument(
|
|
2128
|
+
"--pretty",
|
|
2129
|
+
action="store_true",
|
|
2130
|
+
help="Pretty-print the report JSON.",
|
|
2131
|
+
)
|
|
2132
|
+
p_comp_check.set_defaults(func=cmd_compliance_check)
|
|
2133
|
+
|
|
2134
|
+
p_comp_regimes = p_compliance_sub.add_parser(
|
|
2135
|
+
"regimes",
|
|
2136
|
+
help="List available compliance regimes.",
|
|
2137
|
+
description=(
|
|
2138
|
+
"Emit the set of regime identifiers this CLI can validate "
|
|
2139
|
+
"against. Adding a new regime appends to this list; "
|
|
2140
|
+
"existing identifiers are stable."
|
|
2141
|
+
),
|
|
2142
|
+
)
|
|
2143
|
+
p_comp_regimes.set_defaults(func=cmd_compliance_regimes)
|
|
2144
|
+
|
|
1881
2145
|
return parser
|
|
1882
2146
|
|
|
1883
2147
|
|