cortexm 0.3.0__py3-none-any.whl

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 (120) hide show
  1. context_m.py +17 -0
  2. cortexm/__init__.py +45 -0
  3. cortexm/accel.py +403 -0
  4. cortexm/api/__init__.py +0 -0
  5. cortexm/api/chaos.py +118 -0
  6. cortexm/api/memory.py +635 -0
  7. cortexm/bench/__init__.py +0 -0
  8. cortexm/bench/abilities.py +311 -0
  9. cortexm/bench/baselines.py +89 -0
  10. cortexm/bench/beam_loader.py +317 -0
  11. cortexm/bench/generator.py +376 -0
  12. cortexm/bench/harness.py +211 -0
  13. cortexm/bench/messy.py +218 -0
  14. cortexm/bench/micro.py +251 -0
  15. cortexm/bench/ood.py +443 -0
  16. cortexm/bench/run.py +137 -0
  17. cortexm/bridge/__init__.py +0 -0
  18. cortexm/bridge/dates.py +178 -0
  19. cortexm/bridge/decoders.py +204 -0
  20. cortexm/bridge/enrich.py +255 -0
  21. cortexm/bridge/extractor.py +316 -0
  22. cortexm/bridge/fallback.py +332 -0
  23. cortexm/bridge/onnx_runtime.py +158 -0
  24. cortexm/bridge/patterns.py +760 -0
  25. cortexm/bridge/ppr.py +104 -0
  26. cortexm/bridge/prefilter.py +188 -0
  27. cortexm/bridge/query_extract.py +420 -0
  28. cortexm/bridge/reader.py +1174 -0
  29. cortexm/bridge/rerank.py +204 -0
  30. cortexm/bridge/writer.py +492 -0
  31. cortexm/cli.py +295 -0
  32. cortexm/cognition/__init__.py +53 -0
  33. cortexm/cognition/abstraction.py +192 -0
  34. cortexm/cognition/analogy.py +159 -0
  35. cortexm/cognition/engine.py +204 -0
  36. cortexm/cognition/gaps.py +365 -0
  37. cortexm/cognition/scanner.py +204 -0
  38. cortexm/config.py +375 -0
  39. cortexm/cortexm.py +8 -0
  40. cortexm/enterprise/__init__.py +0 -0
  41. cortexm/enterprise/audit.py +178 -0
  42. cortexm/enterprise/governance.py +239 -0
  43. cortexm/errors.py +35 -0
  44. cortexm/features/__init__.py +0 -0
  45. cortexm/features/git.py +204 -0
  46. cortexm/features/prefetch.py +88 -0
  47. cortexm/features/zk.py +105 -0
  48. cortexm/federation/__init__.py +39 -0
  49. cortexm/federation/crdt.py +275 -0
  50. cortexm/federation/fabric.py +109 -0
  51. cortexm/federation/hlc.py +80 -0
  52. cortexm/federation/node.py +145 -0
  53. cortexm/federation/schema_report.py +73 -0
  54. cortexm/federation/transport.py +164 -0
  55. cortexm/index/__init__.py +19 -0
  56. cortexm/index/nsg.py +386 -0
  57. cortexm/mcp/__init__.py +0 -0
  58. cortexm/mcp/server.py +985 -0
  59. cortexm/metrics.py +62 -0
  60. cortexm/migrate/__init__.py +0 -0
  61. cortexm/migrate/importers.py +192 -0
  62. cortexm/provenance/__init__.py +78 -0
  63. cortexm/provenance/agent.py +214 -0
  64. cortexm/provenance/cose.py +201 -0
  65. cortexm/provenance/scitt.py +258 -0
  66. cortexm/provenance/vc.py +250 -0
  67. cortexm/security/__init__.py +0 -0
  68. cortexm/security/crypto.py +162 -0
  69. cortexm/security/hashes.py +140 -0
  70. cortexm/security/injection.py +149 -0
  71. cortexm/security/mind.py +154 -0
  72. cortexm/security/pii.py +265 -0
  73. cortexm/security/rbac.py +169 -0
  74. cortexm/security/sandbox.py +131 -0
  75. cortexm/security/zk_hamming.py +142 -0
  76. cortexm/security/zk_sql.py +485 -0
  77. cortexm/server/__init__.py +0 -0
  78. cortexm/server/metrics.py +88 -0
  79. cortexm/server/rest.py +936 -0
  80. cortexm/server/sparql.py +984 -0
  81. cortexm/text/__init__.py +0 -0
  82. cortexm/text/dissim.py +252 -0
  83. cortexm/text/embedder.py +155 -0
  84. cortexm/text/fuzzy.py +218 -0
  85. cortexm/text/idiolect.py +253 -0
  86. cortexm/text/labse.py +374 -0
  87. cortexm/text/tokenizer.py +79 -0
  88. cortexm/trace/__init__.py +0 -0
  89. cortexm/trace/blob_arena.py +277 -0
  90. cortexm/trace/consolidate.py +337 -0
  91. cortexm/trace/contradictions.py +69 -0
  92. cortexm/trace/dedup.py +114 -0
  93. cortexm/trace/edges.py +214 -0
  94. cortexm/trace/fact.py +121 -0
  95. cortexm/trace/fade.py +245 -0
  96. cortexm/trace/lifecycle.py +112 -0
  97. cortexm/trace/rebuild.py +173 -0
  98. cortexm/trace/rules.py +171 -0
  99. cortexm/trace/store.py +680 -0
  100. cortexm/trace/structural.py +183 -0
  101. cortexm/trace/tmt.py +335 -0
  102. cortexm/util.py +148 -0
  103. cortexm/vsa/__init__.py +0 -0
  104. cortexm/vsa/attribution.py +149 -0
  105. cortexm/vsa/cleanup.py +161 -0
  106. cortexm/vsa/codecs.py +397 -0
  107. cortexm/vsa/hologram_overlay.py +139 -0
  108. cortexm/vsa/index.py +163 -0
  109. cortexm/vsa/ops.py +149 -0
  110. cortexm/vsa/palace.py +446 -0
  111. cortexm/vsa/role_vectors.py +236 -0
  112. cortexm/vsa/slb.py +78 -0
  113. cortexm/vsa/tlsh_trie.py +137 -0
  114. cortexm/vsa/working_memory.py +249 -0
  115. cortexm-0.3.0.dist-info/METADATA +482 -0
  116. cortexm-0.3.0.dist-info/RECORD +120 -0
  117. cortexm-0.3.0.dist-info/WHEEL +5 -0
  118. cortexm-0.3.0.dist-info/entry_points.txt +2 -0
  119. cortexm-0.3.0.dist-info/licenses/LICENSE +190 -0
  120. cortexm-0.3.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,485 @@
1
+ """Zero-knowledge SQL proofs (Halo2/PLONKish-inspired) for the Trace.
2
+
3
+ PoneglyphDB-style proof: prove a SQL aggregate query returned a specific
4
+ value, without revealing other facts. The circuit is a PLONKish arithmetic
5
+ circuit (a polynomial commitment over the witness assignments).
6
+
7
+ This is a prototype: the proof system uses BLAKE3 commitments + Fiat-Shamir
8
+ transcript (not KZG or FRI), so the prover/verifier are linear in trace
9
+ size, NOT succinct. The API surface mirrors what a production Halo2
10
+ integration would expose — swapping the commitment scheme is a one-line
11
+ change to the `commit()` function.
12
+
13
+ Provable queries:
14
+ - MEMBERSHIP(subject, relation) -> bool (fact exists)
15
+ - COUNT(relation) -> int (count of facts with that relation)
16
+ - SUM(relation, value_predicate) -> float (sum of values matching predicate)
17
+ - AVG(relation, value_predicate) -> float
18
+ - MIN/MAX(relation, value_predicate) -> float
19
+
20
+ The proof reveals:
21
+ - The claimed result
22
+ - The Merkle root of the trace at proof-time
23
+ - A non-interactive PLONKish-style proof transcript
24
+
25
+ The proof does NOT reveal:
26
+ - Other facts in the trace
27
+ - The exact matching facts (only the count / sum / etc.)
28
+
29
+ Honest scope (do not ship this to adversarial environments):
30
+ * The commitment is BLAKE3 (a hash, not a homomorphic commitment). The
31
+ verifier cannot independently re-evaluate the witness polynomial — they
32
+ trust the prover's HMAC attestation (the prover holds ZK_SQL_KEY). A
33
+ malicious prover WITH the key could forge any proof; external attackers
34
+ (without the key) cannot. This is documented as a known limitation.
35
+ * The verifier's CHECK path is O(1) in trace size (commitment is just
36
+ hash equality, eval_at_1 is one int compare, HMAC is constant-time).
37
+ The full PROVER protocol is O(N) — proving requires touching every
38
+ fact once to build the witness set and compute the polynomial eval.
39
+ A production Halo2 backend would compress that to O(log N) via KZG
40
+ commitments; this prototype demonstrates the API surface.
41
+ """
42
+ from __future__ import annotations
43
+
44
+ import json
45
+ import secrets
46
+ from dataclasses import dataclass, field, asdict
47
+ from typing import Any
48
+
49
+ from cortexm.security.hashes import (HashProvider, attest, merkle_root,
50
+ verify_attest)
51
+ from cortexm.trace.fact import Fact
52
+ from cortexm.util import new_id
53
+
54
+ # PLONKish gate types (used by the circuit description helper).
55
+ GATE_TYPES = ("CONST", "VAR", "ADD", "MUL", "SELECTOR")
56
+
57
+ # Scalar field for the witness polynomial. We use the Mersenne prime
58
+ # 2^61 - 1 — small enough that int arithmetic in Python is exact, large
59
+ # enough that collisions on real traces (n_facts < 2^40) are negligible.
60
+ # Production Halo2 would use the BN254 scalar field (~2^254); the swap
61
+ # is one line below.
62
+ _FIELD_MOD = (1 << 61) - 1
63
+
64
+
65
+ @dataclass
66
+ class CircuitGate:
67
+ """One PLONKish gate: q_L * w_L + q_R * w_R + q_O * w_O + q_M * w_L*w_R + q_C = 0
68
+
69
+ The prototype's only gate is a SUM-of-witnesses: q_L = 1, q_O = -1
70
+ for the running accumulator, plus n_facts SELECTOR gates that pick
71
+ out the witness bit for each fact.
72
+ """
73
+ q_L: float = 0.0
74
+ q_R: float = 0.0
75
+ q_O: float = 1.0
76
+ q_M: float = 0.0
77
+ q_C: float = 0.0
78
+
79
+
80
+ @dataclass
81
+ class ZkSqlProof:
82
+ """A ZK-SQL proof: claim + commitment + transcript."""
83
+ query: str # "COUNT(works_at)"
84
+ claimed_result: float
85
+ merkle_root: str # BLAKE3 root of the trace at proof time
86
+ n_facts_committed: int
87
+ transcript: dict # Fiat-Shamir challenges + prover responses
88
+ circuit_gates: int
89
+ proof_id: str = ""
90
+
91
+ # ----- serialization for storage / audit -----------------------------
92
+ def to_dict(self) -> dict:
93
+ return {
94
+ "query": self.query,
95
+ "claimed_result": self.claimed_result,
96
+ "merkle_root": self.merkle_root,
97
+ "n_facts_committed": self.n_facts_committed,
98
+ "transcript": self.transcript,
99
+ "circuit_gates": self.circuit_gates,
100
+ "proof_id": self.proof_id,
101
+ }
102
+
103
+ def serialize(self) -> str:
104
+ """Compact canonical JSON — for storage / transmission / audit.
105
+
106
+ The serialized form NEVER contains the underlying fact values
107
+ (only the aggregate), by construction. The transcript exposes
108
+ only: FS challenge `r`, polynomial evaluation `eval_y`,
109
+ evaluation at 1 (== sum of witnesses, the same as the aggregate
110
+ for COUNT / SUM), the witness commitment, and an HMAC tag.
111
+ """
112
+ return json.dumps(self.to_dict(), sort_keys=True,
113
+ separators=(",", ":"), default=str)
114
+
115
+
116
+ class ZkSqlProver:
117
+ """Prove SQL aggregate queries over the Trace without revealing it."""
118
+
119
+ # -------------------------------------------------------------------
120
+ def __init__(self, store, hash_provider: HashProvider | None = None):
121
+ self.store = store
122
+ self.hasher = hash_provider or store.hasher
123
+ key_hex = self.store.kv_get("ZK_SQL_KEY")
124
+ if not key_hex:
125
+ key_hex = secrets.token_hex(32)
126
+ self.store.kv_set("ZK_SQL_KEY", key_hex)
127
+ self._key = bytes.fromhex(key_hex)
128
+
129
+ # ----- helpers -------------------------------------------------------
130
+ def _active_facts(self, user_id: str | None = None) -> list[Fact]:
131
+ return self.store.query_facts(user_id=user_id, active=True,
132
+ include_quarantined=False)
133
+
134
+ def _fact_leaf(self, f: Fact) -> str:
135
+ """Content-free Merkle leaf: H(fact_id || source_hash).
136
+
137
+ Publishing the root of this leaf set proves the witness set was
138
+ drawn from a committed trace — without revealing which facts.
139
+ """
140
+ return self.hasher.hash_text(f"{f.id}:{f.source_hash}")
141
+
142
+ def _trace_root(self, facts: list[Fact]) -> str:
143
+ leaves = [self._fact_leaf(f) for f in facts]
144
+ return merkle_root(self.hasher, leaves)
145
+
146
+ def _witness_commitment(self, witnesses: list[float], pad: bytes) -> str:
147
+ """H(permuted_witnesses || pad).
148
+
149
+ The witnesses are packed as a canonical JSON array (preserves
150
+ ordering for the polynomial evaluation); a 32-byte random pad is
151
+ appended so the commitment is non-invertible: an attacker with
152
+ the commitment cannot recover the witness list. The pad is
153
+ generated fresh per-proof and never published.
154
+ """
155
+ blob = (json.dumps([float(w) for w in witnesses],
156
+ separators=(",", ":")).encode("utf-8")
157
+ + pad)
158
+ return self.hasher.hash_bytes(blob)
159
+
160
+ def _fs_challenge(self, *parts: str) -> int:
161
+ """Fiat-Shamir: H(parts) reduced to a non-zero scalar in F_p.
162
+
163
+ Non-zero because we need r^0 = 1 and don't want P(r) = 0
164
+ degenerately. The challenge is derived from the public statement
165
+ (query, root, n_facts, commitment), so it cannot be precomputed
166
+ by the prover before committing.
167
+ """
168
+ joined = "|".join(parts)
169
+ digest = self.hasher.hash_text(joined)
170
+ # 64-bit slice mod (p-1), +1 to dodge zero
171
+ return (int(digest[:16], 16) % (_FIELD_MOD - 1)) + 1
172
+
173
+ def _eval_poly(self, witnesses: list[float], r: int) -> int:
174
+ """P(r) = sum(w_i * r^i) mod p over the witness list."""
175
+ acc = 0
176
+ pow_r = 1
177
+ for w in witnesses:
178
+ # field-reduce only the integer part of w — for the COUNT/MEMBERSHIP
179
+ # case w in {0,1}, for SUM w is a value (we take int(w) mod p).
180
+ wi = int(round(w)) % _FIELD_MOD
181
+ acc = (acc + (wi * pow_r) % _FIELD_MOD) % _FIELD_MOD
182
+ pow_r = (pow_r * r) % _FIELD_MOD
183
+ return acc
184
+
185
+ def _sign(self, msg: str) -> str:
186
+ return attest(self.hasher, self._key, msg)
187
+
188
+ def _verify_sig(self, msg: str, tag: str) -> bool:
189
+ return verify_attest(self.hasher, self._key, msg, tag)
190
+
191
+ # ----- proof construction -------------------------------------------
192
+ def _build_proof(self, *, query: str, witnesses: list[float],
193
+ claimed_result: float, merkle_root: str,
194
+ n_matching: int = 0) -> ZkSqlProof:
195
+ """Build a PLONKish-style proof for the given witness assignment.
196
+
197
+ witnesses: list of witness values (in {0,1} for COUNT/MEMBERSHIP,
198
+ the fact's numeric value for SUM/AVG/MIN/MAX; 0 if the
199
+ fact does not match the predicate).
200
+ claimed_result: the public SQL aggregate (COUNT / SUM / AVG / etc.).
201
+ """
202
+ n_facts = len(witnesses)
203
+ pad = secrets.token_bytes(32)
204
+ commitment = self._witness_commitment(witnesses, pad)
205
+ r = self._fs_challenge(query, merkle_root, str(n_facts), commitment)
206
+ eval_y = self._eval_poly(witnesses, r)
207
+ # P(1) = sum(w_i) — this is the COUNT/SUM identity. For AVG it is
208
+ # the SUM, and claimed_result = sum / n_matching (verified below).
209
+ # For MIN/MAX it has no clean relationship to claimed; we still
210
+ # publish it (it's the sum of all matching values) so the verifier
211
+ # can run the AVG check on the AVG proof type.
212
+ eval_at_1 = self._eval_poly(witnesses, 1)
213
+ msg = "|".join([
214
+ query, repr(claimed_result), merkle_root, str(n_facts),
215
+ commitment, str(r), str(eval_y), str(eval_at_1),
216
+ str(n_matching),
217
+ ])
218
+ attestation = self._sign(msg)
219
+ transcript = {
220
+ "r": r,
221
+ "eval_y": eval_y,
222
+ "eval_at_1": eval_at_1,
223
+ "commitment": commitment,
224
+ "attestation": attestation,
225
+ "n_matching": n_matching,
226
+ }
227
+ # circuit_gates: 1 SUM accumulator + 1 SELECTOR per witness fact
228
+ return ZkSqlProof(
229
+ query=query,
230
+ claimed_result=claimed_result,
231
+ merkle_root=merkle_root,
232
+ n_facts_committed=n_facts,
233
+ transcript=transcript,
234
+ circuit_gates=1 + n_facts,
235
+ proof_id=new_id(),
236
+ )
237
+
238
+ # -------------------------------------------------------------------
239
+ def membership_proof(self, subject: str, relation: str,
240
+ value: str | None = None) -> ZkSqlProof:
241
+ """Prove (subject, relation[, value]) exists in the trace.
242
+
243
+ Reveal: just the boolean exists. Not the fact_id, not the
244
+ chunk_id, not the timestamp, not the user_id.
245
+
246
+ Raises ``VerificationError`` if no such fact exists — the prover
247
+ cannot honestly prove existence of a fact that isn't there. To
248
+ prove non-existence, query ``count_proof`` (which will return 0).
249
+ """
250
+ from cortexm.errors import VerificationError
251
+
252
+ facts = self._active_facts()
253
+ witnesses = [
254
+ 1.0 if (f.subject == subject and f.relation == relation
255
+ and (value is None or f.value == value)) else 0.0
256
+ for f in facts
257
+ ]
258
+ if not any(int(w) == 1 for w in witnesses):
259
+ vs = f" = {value!r}" if value else ""
260
+ raise VerificationError(
261
+ f"no active fact ({subject}, {relation}{vs}) in trace "
262
+ f"of {len(facts)} facts — cannot prove existence")
263
+ root = self._trace_root(facts)
264
+ query_str = (f"MEMBERSHIP({subject},{relation}"
265
+ + (f",{value}" if value else "") + ")")
266
+ return self._build_proof(
267
+ query=query_str, witnesses=witnesses,
268
+ claimed_result=1.0, merkle_root=root, n_matching=1)
269
+
270
+ # -------------------------------------------------------------------
271
+ def count_proof(self, relation: str, user_id: str | None = None) -> ZkSqlProof:
272
+ """Prove COUNT(relation) -> N. Reveals N but not which facts."""
273
+ facts = self._active_facts(user_id=user_id)
274
+ witnesses = [1.0 if f.relation == relation else 0.0 for f in facts]
275
+ n_match = sum(1 for w in witnesses if int(w) == 1)
276
+ root = self._trace_root(facts)
277
+ scope = f" FOR USER {user_id}" if user_id else ""
278
+ query_str = f"COUNT({relation}{scope})"
279
+ return self._build_proof(
280
+ query=query_str, witnesses=witnesses,
281
+ claimed_result=float(n_match), merkle_root=root,
282
+ n_matching=n_match)
283
+
284
+ # -------------------------------------------------------------------
285
+ def sum_proof(self, relation: str, value_filter: str | None = None,
286
+ user_id: str | None = None) -> ZkSqlProof:
287
+ """Prove SUM(relation.value WHERE value_filter matches) -> S.
288
+
289
+ value_filter: a substring that must be in the value (e.g. 'Google'
290
+ for SUM over values containing 'Google'). If None, all values
291
+ for that relation are summed.
292
+
293
+ The value strings are interpreted as numbers via float(); facts
294
+ whose value cannot be parsed as a number contribute 0 to the
295
+ sum (i.e. are excluded). This is the prototype's SQL semantics;
296
+ a production version would type-check the value column.
297
+ """
298
+ facts = self._active_facts(user_id=user_id)
299
+ witnesses: list[float] = []
300
+ n_match = 0
301
+ for f in facts:
302
+ if f.relation != relation:
303
+ witnesses.append(0.0)
304
+ continue
305
+ if value_filter is not None and value_filter not in f.value:
306
+ witnesses.append(0.0)
307
+ continue
308
+ try:
309
+ v = float(f.value)
310
+ except (TypeError, ValueError):
311
+ witnesses.append(0.0)
312
+ continue
313
+ witnesses.append(v)
314
+ n_match += 1
315
+ total = sum(witnesses)
316
+ root = self._trace_root(facts)
317
+ scope = f" FOR USER {user_id}" if user_id else ""
318
+ flt = f" WHERE value LIKE %{value_filter}%" if value_filter else ""
319
+ query_str = f"SUM({relation}.value{flt}{scope})"
320
+ return self._build_proof(
321
+ query=query_str, witnesses=witnesses,
322
+ claimed_result=float(total), merkle_root=root,
323
+ n_matching=n_match)
324
+
325
+ # -------------------------------------------------------------------
326
+ def avg_proof(self, relation: str, value_filter: str | None = None,
327
+ user_id: str | None = None) -> ZkSqlProof:
328
+ """Prove AVG(relation.value) -> mean. Reveals mean + matching count."""
329
+ facts = self._active_facts(user_id=user_id)
330
+ witnesses: list[float] = []
331
+ n_match = 0
332
+ for f in facts:
333
+ if f.relation != relation:
334
+ witnesses.append(0.0)
335
+ continue
336
+ if value_filter is not None and value_filter not in f.value:
337
+ witnesses.append(0.0)
338
+ continue
339
+ try:
340
+ v = float(f.value)
341
+ witnesses.append(v)
342
+ n_match += 1
343
+ except (TypeError, ValueError):
344
+ witnesses.append(0.0)
345
+ total = sum(witnesses)
346
+ avg = total / n_match if n_match else 0.0
347
+ root = self._trace_root(facts)
348
+ scope = f" FOR USER {user_id}" if user_id else ""
349
+ flt = f" WHERE value LIKE %{value_filter}%" if value_filter else ""
350
+ query_str = f"AVG({relation}.value{flt}{scope})"
351
+ return self._build_proof(
352
+ query=query_str, witnesses=witnesses,
353
+ claimed_result=float(avg), merkle_root=root,
354
+ n_matching=n_match)
355
+
356
+ # -------------------------------------------------------------------
357
+ def minmax_proof(self, relation: str, op: str,
358
+ value_filter: str | None = None,
359
+ user_id: str | None = None) -> ZkSqlProof:
360
+ """Prove MIN/MAX(relation.value) -> extreme."""
361
+ if op not in ("MIN", "MAX"):
362
+ raise ValueError(f"op must be MIN or MAX, got {op!r}")
363
+ facts = self._active_facts(user_id=user_id)
364
+ witnesses: list[float] = []
365
+ n_match = 0
366
+ values: list[float] = []
367
+ for f in facts:
368
+ if f.relation != relation:
369
+ witnesses.append(0.0)
370
+ continue
371
+ if value_filter is not None and value_filter not in f.value:
372
+ witnesses.append(0.0)
373
+ continue
374
+ try:
375
+ v = float(f.value)
376
+ witnesses.append(v)
377
+ values.append(v)
378
+ n_match += 1
379
+ except (TypeError, ValueError):
380
+ witnesses.append(0.0)
381
+ if not values:
382
+ result = 0.0
383
+ else:
384
+ result = min(values) if op == "MIN" else max(values)
385
+ root = self._trace_root(facts)
386
+ scope = f" FOR USER {user_id}" if user_id else ""
387
+ flt = f" WHERE value LIKE %{value_filter}%" if value_filter else ""
388
+ query_str = f"{op}({relation}.value{flt}{scope})"
389
+ return self._build_proof(
390
+ query=query_str, witnesses=witnesses,
391
+ claimed_result=float(result), merkle_root=root,
392
+ n_matching=n_match)
393
+
394
+ # -------------------------------------------------------------------
395
+ def verify(self, proof: ZkSqlProof) -> bool:
396
+ """Verify a ZK-SQL proof. Returns True if the proof is valid.
397
+
398
+ Verifier path (all O(1) in trace size — sublinear):
399
+ 1. Recompute the Fiat-Shamir challenge r from the public
400
+ statement (query, merkle_root, n_facts, commitment).
401
+ 2. Run the polynomial-identity check appropriate to the query
402
+ type (COUNT/SUM/MEMBERSHIP: eval_at_1 == claimed_result
403
+ mod field; AVG: claimed_result * n_matching ≈ eval_at_1;
404
+ MIN/MAX: trust HMAC).
405
+ 3. Verify the HMAC attestation (prover bound to the claim).
406
+ """
407
+ try:
408
+ t = proof.transcript
409
+ # 1. FS challenge — must match what the prover derived
410
+ r_expected = self._fs_challenge(
411
+ proof.query, proof.merkle_root,
412
+ str(proof.n_facts_committed), t["commitment"])
413
+ if r_expected != t["r"]:
414
+ return False
415
+ # 2. polynomial-identity check, by query type
416
+ cr = proof.claimed_result
417
+ q = proof.query.upper()
418
+ if q.startswith("COUNT(") or q.startswith("MEMBERSHIP("):
419
+ # eval_at_1 = sum of {0,1} witnesses = claimed count
420
+ if t["eval_at_1"] != int(cr) % _FIELD_MOD:
421
+ return False
422
+ elif q.startswith("SUM("):
423
+ if abs(float(t["eval_at_1"]) - float(cr)) > 1e-6:
424
+ # eval_at_1 is reduced mod p; lift back: the witness
425
+ # sum should fit in < 2^61 for any real trace.
426
+ ea1 = t["eval_at_1"]
427
+ # if wrapped mod p, this check fails — but for any
428
+ # trace size < 2^40 with values < 2^20, no wrap.
429
+ if not (ea1 == int(cr) % _FIELD_MOD
430
+ or abs(float(ea1) - float(cr)) <= 1e-6):
431
+ return False
432
+ elif q.startswith("AVG("):
433
+ # claimed = sum / n_matching → claimed * n_matching == sum
434
+ n_m = int(t.get("n_matching", 0))
435
+ if n_m > 0:
436
+ if abs(cr * n_m - float(t["eval_at_1"])) > 1e-3:
437
+ return False
438
+ elif cr != 0.0:
439
+ return False
440
+ # MIN/MAX: no cheap identity check — fall through to HMAC
441
+ # 3. HMAC attestation
442
+ msg = "|".join([
443
+ proof.query, repr(proof.claimed_result), proof.merkle_root,
444
+ str(proof.n_facts_committed), t["commitment"],
445
+ str(t["r"]), str(t["eval_y"]), str(t["eval_at_1"]),
446
+ str(t.get("n_matching", 0)),
447
+ ])
448
+ if not self._verify_sig(msg, t["attestation"]):
449
+ return False
450
+ return True
451
+ except (KeyError, AttributeError, TypeError):
452
+ return False
453
+
454
+ # -------------------------------------------------------------------
455
+ def proof_report(self, proof: ZkSqlProof) -> str:
456
+ """Human-readable proof report (for audit logs)."""
457
+ lines = [
458
+ "=" * 68,
459
+ "ZK-SQL Proof Report (PoneglyphDB-style PLONKish)",
460
+ "=" * 68,
461
+ f" Proof ID : {proof.proof_id}",
462
+ f" Query : {proof.query}",
463
+ f" Claimed result : {proof.claimed_result}",
464
+ f" Trace Merkle root: {proof.merkle_root[:16]}…"
465
+ f" ({proof.n_facts_committed} facts committed)",
466
+ f" Circuit gates : {proof.circuit_gates}",
467
+ "-" * 68,
468
+ " PLONKish transcript:",
469
+ f" Fiat-Shamir challenge r : {proof.transcript.get('r')}",
470
+ f" Polynomial eval P(r) : {proof.transcript.get('eval_y')}",
471
+ f" Polynomial eval P(1)=Σwᵢ : {proof.transcript.get('eval_at_1')}",
472
+ f" Witness commitment (BLAKE3): "
473
+ f"{str(proof.transcript.get('commitment'))[:32]}…",
474
+ f" Matching facts (public) : "
475
+ f"{proof.transcript.get('n_matching', '?')}",
476
+ f" HMAC attestation : "
477
+ f"{str(proof.transcript.get('attestation'))[:32]}…",
478
+ "-" * 68,
479
+ f" Verification: {'PASS' if self.verify(proof) else 'FAIL'}",
480
+ "=" * 68,
481
+ ]
482
+ return "\n".join(lines)
483
+
484
+
485
+ __all__ = ["ZkSqlProver", "ZkSqlProof", "CircuitGate", "GATE_TYPES"]
File without changes
@@ -0,0 +1,88 @@
1
+ """Prometheus-format metrics + structured operation counters.
2
+
3
+ Renders the process-wide counters from ``cortexm.metrics`` plus
4
+ server-side request metrics (status codes, latency histogram, rate-limit
5
+ rejections) in Prometheus text exposition format 0.0.4 — no client
6
+ libraries, no dependencies, scrape-ready.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import threading
12
+ import time
13
+
14
+
15
+ class PromRegistry:
16
+ def __init__(self) -> None:
17
+ self._lock = threading.Lock()
18
+ self._counters: dict[str, dict[str, float]] = {}
19
+ self._histograms: dict[str, list[float]] = {}
20
+ self._gauges: dict[str, float] = {}
21
+
22
+ def inc(self, name: str, labels: dict | None = None, value: float = 1) -> None:
23
+ key = _label_key(labels)
24
+ with self._lock:
25
+ bucket = self._counters.setdefault(name, {})
26
+ bucket[key] = bucket.get(key, 0.0) + value
27
+
28
+ def observe(self, name: str, value: float) -> None:
29
+ with self._lock:
30
+ self._histograms.setdefault(name, []).append(value)
31
+
32
+ def gauge(self, name: str, value: float) -> None:
33
+ with self._lock:
34
+ self._gauges[name] = value
35
+
36
+ def render(self) -> str:
37
+ from cortexm import metrics as core
38
+ lines: list[str] = []
39
+ c = core.counters()
40
+ lines.append("# HELP contextm_ingested_tokens_total Total tokens ingested (mu=0 path).")
41
+ lines.append("# TYPE contextm_ingested_tokens_total counter")
42
+ lines.append(f"contextm_ingested_tokens_total {c['ingested_tokens']}")
43
+ lines.append("# HELP contextm_ingested_messages_total Total messages ingested.")
44
+ lines.append("# TYPE contextm_ingested_messages_total counter")
45
+ lines.append(f"contextm_ingested_messages_total {c['ingested_messages']}")
46
+ lines.append("# HELP contextm_extracted_facts_total Facts extracted.")
47
+ lines.append("# TYPE contextm_extracted_facts_total counter")
48
+ lines.append(f"contextm_extracted_facts_total {c['extracted_facts']}")
49
+ lines.append("# HELP contextm_retrievals_total Search operations served.")
50
+ lines.append("# TYPE contextm_retrievals_total counter")
51
+ lines.append(f"contextm_retrievals_total {c['retrievals']}")
52
+ lines.append("# HELP contextm_llm_calls_total LLM calls (must be 0 under mu=0).")
53
+ lines.append("# TYPE contextm_llm_calls_total counter")
54
+ lines.append(f"contextm_llm_calls_total {c['llm_calls']}")
55
+ with self._lock:
56
+ for name, bucket in sorted(self._counters.items()):
57
+ lines.append(f"# TYPE {name} counter")
58
+ for key, val in sorted(bucket.items()):
59
+ lines.append(f"{name}{key} {val}")
60
+ for name, val in sorted(self._gauges.items()):
61
+ lines.append(f"# TYPE {name} gauge")
62
+ lines.append(f"{name} {val}")
63
+ for name, obs in sorted(self._histograms.items()):
64
+ lines.append(f"# TYPE {name} histogram")
65
+ bounds = [0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1,
66
+ 0.25, 0.5, 1.0, 2.5, 5.0]
67
+ for b in bounds:
68
+ cnt = sum(1 for o in obs if o <= b)
69
+ lines.append(f"{name}_bucket{{le=\"{b}\"}} {cnt}")
70
+ lines.append(f"{name}_bucket{{le=\"+Inf\"}} {len(obs)}")
71
+ if obs:
72
+ lines.append(f"{name}_sum {sum(obs):.6f}")
73
+ lines.append(f"{name}_count {len(obs)}")
74
+ return "\n".join(lines) + "\n"
75
+
76
+
77
+ def _label_key(labels: dict | None) -> str:
78
+ if not labels:
79
+ return ""
80
+ inner = ",".join(f'{k}="{_esc(str(v))}"' for k, v in sorted(labels.items()))
81
+ return "{" + inner + "}"
82
+
83
+
84
+ def _esc(s: str) -> str:
85
+ return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
86
+
87
+
88
+ REGISTRY = PromRegistry()