arborist-viz/arborist_viz/schemas.py
russell@unturf.com 0cd96ab1d7
Initial commit: arborist VIZ — Merkle command center
Pyramid web app for inspecting arborist Merkle trees over sharded SQLite:
document roots, inclusion proofs, an interactive 3D Merkle lattice
(six.js / WebGL with graceful fallback), audit-chain linkage, and a live
SSE event stream.

Generated artifacts (env/, vendored static/six/ bundles, demo databases)
are gitignored and rebuilt via the Makefile (venv, six, init-db, serve).
2026-05-28 09:22:59 -04:00

376 lines
10 KiB
Python

"""Phase 0 schemas — Arborist VIZ ticket §7, §8, §11.
These are the Python-side mirrors of the proposal's TypeScript shapes.
The TypeScript proposal was superseded by §3-corrected (Pyramid+Jinja2
+vanilla-JS), so the canonical shape now lives here.
Hard rules baked into the schema:
* Every widget exposes the query + source roots it derived from
(``WidgetSpec.source_roots`` — §19.3).
* Activation/circuit traces are never labeled causal unless intervention
or ablation evidence exists (``CausalStatus`` enum, ticket §11).
* Soft signals (correlational/attribution) live alongside hard hashes
but never enter the proof path (CLAUDE.md soft/hard convention).
* Private chunks/prompts/raw traces never appear here — only hashes,
commitments, and selective-disclosure objects (§14).
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional
# ---------- enums --------------------------------------------------------
class NodeKind(str, Enum):
DOCUMENT_ROOT = "document_root"
LEAF = "leaf"
CHUNK = "chunk"
CLAIM = "claim"
EVIDENCE = "evidence"
FALSIFIER = "falsifier"
GRAVEYARD_ENTRY = "graveyard_entry"
AUDIT_EVENT = "audit_event"
CACHE_KEY = "cache_key"
RUN = "run"
FORK = "fork"
BENCHMARK = "benchmark"
PEER = "peer"
ACTIVATION = "activation"
CIRCUIT = "circuit"
FEATURE = "feature"
INTERVENTION = "intervention"
class NodeStatus(str, Enum):
LIVE = "live"
FAILED = "failed"
STALE = "stale"
QUARANTINED = "quarantined"
SPECULATIVE = "speculative"
UNDERDETERMINED = "underdetermined"
SELF_PROVENANCE_QUARANTINED = "self_provenance_quarantined"
FORMALLY_PROVED = "formally_proved"
class WarrantLevel(str, Enum):
UNGROUNDED = "UNGROUNDED"
POINTER_LINKED = "POINTER_LINKED"
ANCHOR_WARRANTED = "ANCHOR_WARRANTED"
EVIDENCE_WARRANTED = "EVIDENCE_WARRANTED"
FORMALLY_PROVED = "FORMALLY_PROVED"
class EdgeKind(str, Enum):
CONTAINS = "contains"
SUPPORTS = "supports"
CONTRADICTS = "contradicts"
DEPENDS_ON = "depends_on"
DERIVED_FROM = "derived_from"
FALSIFIES = "falsifies"
COMMITTED_IN = "committed_in"
VERIFIED_BY = "verified_by"
REPLAYED_BY = "replayed_by"
SERVED_BY_CACHE = "served_by_cache"
INVALIDATED_BY = "invalidated_by"
DESCENDS_FROM = "descends_from"
ACTIVATES = "activates"
ATTRIBUTED_TO = "attributed_to"
CAUSALLY_AFFECTS = "causally_affects"
PATCHED_BY = "patched_by"
ABLATED_BY = "ablated_by"
class CausalStatus(str, Enum):
"""Ticket §11 — never causal without intervention or ablation evidence."""
CORRELATIONAL_TRACE = "CORRELATIONAL_TRACE"
ATTRIBUTION_ESTIMATE = "ATTRIBUTION_ESTIMATE"
INTERVENTION_SUPPORTED = "INTERVENTION_SUPPORTED"
ABLATION_SUPPORTED = "ABLATION_SUPPORTED"
CAUSALLY_UNDERDETERMINED = "CAUSALLY_UNDERDETERMINED"
class FailureClass(str, Enum):
FALSE = "false"
STALE = "stale"
POISONED = "poisoned"
UNSUPPORTED = "unsupported"
SELF_CONFIRMING = "self_confirming"
POLICY_BLOCKED = "policy_blocked"
BENCHMARK_REGRESSION = "benchmark_regression"
CANONICALIZATION_FAILED = "canonicalization_failed"
class WidgetType(str, Enum):
MERKLE_PROOF = "merkle-proof"
ROOT_EXPLORER = "root-explorer"
CLAIM_WARRANT = "claim-warrant"
CLAIM_GRAPH = "claim-graph"
RUN_DAG = "run-dag"
AUDIT_TIMELINE = "audit-timeline"
CACHE_KEY = "cache-key"
ROOT_DIFF = "root-diff"
CLAIM_GRAVEYARD = "claim-graveyard"
CIRCUIT_TRACE = "circuit-trace"
MERKLE_LATTICE_3D = "merkle-lattice-3d"
MESH_PEER = "mesh-peer"
# ---------- graph primitives --------------------------------------------
@dataclass(slots=True)
class VizNode:
"""Unified epistemic object node (ticket §8.1)."""
id: str
kind: NodeKind
status: NodeStatus
hash: Optional[str] = None
label: Optional[str] = None
warrant: Optional[WarrantLevel] = None
time: Optional[str] = None
roots: dict[str, str] = field(default_factory=dict)
metrics: dict[str, float] = field(default_factory=dict)
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass(slots=True)
class VizEdge:
"""Unified epistemic object edge (ticket §8.2)."""
id: str
source: str
target: str
kind: EdgeKind
weight: Optional[float] = None
confidence: Optional[float] = None
timestamp: Optional[str] = None
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass(slots=True)
class GraphPatch:
"""One audit-chain-derived patch applied to the live dashboard (ticket §8.3).
Patches are content-addressed by ``event_hash``; the dashboard treats
them as idempotent and ordered by ``seq``.
"""
seq: int
event_hash: str
timestamp: str
root_hash: Optional[str] = None
# Each patch op is a tuple-like dict:
# {"op": "node.upsert"|"node.status"|"edge.upsert"|
# "edge.delete"|"metric.update", ...}
patches: list[dict[str, Any]] = field(default_factory=list)
# ---------- proof + audit ------------------------------------------------
@dataclass(slots=True)
class ProofPath:
"""One Merkle inclusion proof (ticket §6.1, §13).
Inclusion is integrity, not semantic truth — DoD #10.
"""
root_hash: str
leaf_index: int
leaf_hash: str
canonical_bytes_hash: str
sibling_hashes: list[str]
left_right_flags: list[bool]
computed_root: str
expected_root: str
domain_separator: str
canonicalization_version: str
chunking_version: str
schema_version: str
verifier_policy_hash: str
@property
def passed(self) -> bool:
return self.computed_root == self.expected_root
@dataclass(slots=True)
class AuditEvent:
"""One audit-chain event (ticket §6.6, §13)."""
event_hash: str
prev_event_hash: Optional[str]
timestamp: str
actor: str
event_kind: str
root_hash: Optional[str] = None
claim_id: Optional[str] = None
run_id: Optional[str] = None
body_hash: Optional[str] = None
metadata: dict[str, Any] = field(default_factory=dict)
# ---------- activation / circuit (ticket §11) ---------------------------
@dataclass(slots=True)
class ActivationHookpoint:
layer: int
module: str # attention | mlp | residual | embedding | logits
activation_value: float
head_id: Optional[int] = None
neuron_id: Optional[int] = None
feature_id: Optional[str] = None
activation_rank: Optional[int] = None
tensor_hash: Optional[str] = None
@dataclass(slots=True)
class ActivationTrace:
"""Mechanistic/interp activation trace (ticket §11)."""
trace_id: str
run_id: str
model_id: str
model_profile_hash: str
prompt_hash: str
token_span: tuple[int, int]
hookpoints: list[ActivationHookpoint]
trace_root: str
linked_claim_ids: list[str] = field(default_factory=list)
output_span: Optional[tuple[int, int]] = None
causal_status: CausalStatus = CausalStatus.CORRELATIONAL_TRACE
@dataclass(slots=True)
class CircuitNode:
id: str
layer: int
module: str
activation_summary: float
feature_id: Optional[str] = None
@dataclass(slots=True)
class CircuitEdge:
source: str
target: str
attribution_weight: float
method: str # activation_patching | integrated_gradients | logit_lens | sae | manual
@dataclass(slots=True)
class CircuitTrace:
"""Mechanistic circuit (ticket §11)."""
circuit_id: str
model_id: str
trace_ids: list[str]
nodes: list[CircuitNode]
edges: list[CircuitEdge]
causal_status: CausalStatus = CausalStatus.CORRELATIONAL_TRACE
linked_claim_ids: list[str] = field(default_factory=list)
# ---------- dashboard composition (ticket §7) ---------------------------
@dataclass(slots=True)
class FilterSpec:
field: str
op: str # eq | neq | in | gte | lte | range | contains
value: Any
@dataclass(slots=True)
class QuerySpec:
"""Compiled at the Pyramid view layer to SQL against arborist shards."""
select: list[str] = field(default_factory=list)
from_: str = ""
where: dict[str, Any] = field(default_factory=dict)
join: list[str] = field(default_factory=list)
limit: int = 1000
graph: Optional[str] = None # graph query alternative form
root: Optional[str] = None
depth: Optional[int] = None
edge_kinds: list[str] = field(default_factory=list)
include_statuses: list[str] = field(default_factory=list)
@dataclass(slots=True)
class ViewSpec:
"""Renderer hints for a widget."""
renderer: str # table | dag | graph | three-d | timeline | card
options: dict[str, Any] = field(default_factory=dict)
@dataclass(slots=True)
class InteractionSpec:
"""Cross-widget linkage (ticket §4 interaction model)."""
on: str # select | hover | filter
target_widget_ids: list[str] = field(default_factory=list)
bind_field: str = ""
@dataclass(slots=True)
class DataSourceSpec:
id: str
kind: str # arborist_shard | proof_pack | audit_stream | trace_file | mesh_snapshot
locator: str
read_only: bool = True
@dataclass(slots=True)
class PanelLayout:
panel_id: str
x: int
y: int
w: int
h: int
min_w: Optional[int] = None
min_h: Optional[int] = None
dock: Optional[str] = None # left | right | bottom | floating | main
@dataclass(slots=True)
class WidgetSpec:
"""A single widget instance (ticket §7).
``source_roots`` is the §19.3 transparency contract — every widget
exposes the roots it derived from so the dashboard never asserts
state, only projects it.
"""
widget_id: str
type: WidgetType
title: str
data_source_id: str
query: QuerySpec
view: ViewSpec
source_roots: list[str] = field(default_factory=list)
interactions: list[InteractionSpec] = field(default_factory=list)
@dataclass(slots=True)
class DashboardConfig:
"""Top-level dashboard (ticket §7)."""
id: str
title: str
layout: list[PanelLayout]
widgets: list[WidgetSpec]
data_sources: list[DataSourceSpec] = field(default_factory=list)
global_filters: list[FilterSpec] = field(default_factory=list)
interactions: list[InteractionSpec] = field(default_factory=list)
description: Optional[str] = None
version: str = "dashboard-v1"