arborist/aborist/qa/keys.py
russell@unturf.com 57cf1b183b
add Q&A layer: v9.8 providence_cache writes with Merkle-bound proofs
aborist/qa/ implements the cache-first answer flow from the providence
whitepaper, scaled up to v9.8's full 8-dim admissibility invariant.

cache_key = SHA-256 of:
  source_root | question_hash | model_profile_hash | conversation_hash
  | governance_policy_hash | schema_version | canonicalization_version
  | chunking_version

Any drift in any dimension yields a distinct cache_key — prior records
cannot serve. Falsification states (failed/stale/quarantined) gate
every cache hit.

- qa/keys.py        — pure hash functions, deterministic & testable
- qa/client.py      — ChatClient Protocol + StubClient + OpenAI-compatible
                      HTTP client (vllm/llama.cpp/uncloseai compatible)
- qa/runner.py      — ask(): lookup -> hit (no LLM call, hit_count++)
                      OR miss (call client, write record, audit event,
                      proof binds answer to source root)
- cli.py            — `aborist ask` and `aborist providence` subcommands
- pyproject.toml    — httpx promoted from extras to core (used by both
                      html and qa); selectolax stays in [html] extras

Smoke (StubClient, no network): cache miss writes record with chunk_0
Merkle proof reconstructing source_root; cache hit returns same record
without calling client; 1085 audit events chained 0 breaks across
ingest/derive/evict/rehydrate/providence_write.
2026-04-27 08:01:23 -04:00

96 lines
3.1 KiB
Python

"""The 8-dim Merkle-AGI v9.8 cache_key.
v9.8 invariant: no answer is reused unless all eight match and the
record is live (not failed/stale/quarantined):
1. source_root — content fingerprint of the document
2. question_hash — SHA-256 of normalized question text
3. model_profile_hash — model_id + revision + quantization
4. conversation_hash — full canonical OpenAI messages array
5. governance_policy_hash — sampling/policy parameters dict
6. schema_version — aborist DB schema version
7. canonicalization_version — text normalization rules
8. chunking_version — chunker name & parameters
Bumping ANY of these eight dimensions yields a distinct cache_key, so
prior records cannot be served. This is the runtime drift detection
the providence whitepaper compresses into "cache_key = source_root +
':' + question_hash" — that's a simplification; the rigorous form is
all eight dimensions hashed together.
"""
from __future__ import annotations
import hashlib
import json
from aborist.document import canonicalize
def _sha256(s: str) -> str:
return hashlib.sha256(s.encode("utf-8")).hexdigest()
def _canonical_json(obj) -> str:
return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def question_hash(question: str) -> str:
"""SHA-256 of canonicalized + lowercased question text."""
return _sha256(canonicalize(question).lower())
def model_profile_hash(
model_id: str, revision: str = "", quantization: str = ""
) -> str:
"""SHA-256 of model identity. Bumping any field bumps the cache key."""
return _sha256(f"{model_id}|{revision}|{quantization}")
def conversation_hash(messages: list[dict]) -> str:
"""SHA-256 of canonical JSON of the full OpenAI messages array.
Order matters: a 6-turn dialogue arriving at the same final question
produces a different hash than a single-turn ask.
"""
return _sha256(_canonical_json(messages))
def governance_policy_hash(policy: dict) -> str:
"""SHA-256 of canonical JSON of the sampling/policy dict.
Includes temperature, top_p, max_tokens, and the system prompt — any
of those changing means the answer is governed differently and the
cache must miss.
"""
return _sha256(_canonical_json(policy))
def cache_key(
source_root: str,
question_hash_value: str,
model_profile_hash_value: str,
conversation_hash_value: str,
governance_policy_hash_value: str,
schema_version: str,
canonicalization_version: str,
chunking_version: str,
) -> str:
"""SHA-256 of all 8 dimensions joined with '|'.
Any drift in any dimension produces a distinct cache_key.
"""
return _sha256(
"|".join(
[
source_root,
question_hash_value,
model_profile_hash_value,
conversation_hash_value,
governance_policy_hash_value,
schema_version,
canonicalization_version,
chunking_version,
]
)
)