arborist/bench/prometheus_sigma_trigger_probe.py
russell@unturf.com dbe824944a
#000012 Phase 1c follow-through: wire #000037 §12 Trigger 1 probe to fork_score_branches
trigger_1_branch_density in bench/prometheus_sigma_trigger_probe.py read
the fork_score_branches table via branch_set_density() instead of the
"density check not yet implemented" stub. It groups rows by branch_set_id,
fires when the most-recently-recorded checkpoint carries >= 4 branches
(BRANCH_DENSITY_FLOOR), and surfaces n_checkpoints / latest_density /
max_density / n_checkpoints_clearing_floor in the markdown report so
section 12's "regularly" qualifier stays visible. Density sums across
shards per branch_set_id.

With no branch sets persisted yet the probe reports "table present but
empty across shards" (data_available True, fires False) rather than a
false negative. Re-ran the probe against the live shards:
bench/results/prometheus-sigma-triggers-2026-05-11.md.

6 new tests in tests/test_prometheus_trigger_probe.py (probe loaded via
importlib): no-table to no-data, empty-table to data-available-no-fire,
latest-checkpoint->=4 to fires, earlier-dense-but-latest-sparse to no-fire,
density-sums-across-shards, report-renders-density-lines.

Updated #000012 Phase 1c landing receipt, #000037 section 12 Trigger 1
note, and the TICKETS.md index rows for both. Pure measurement: no
mutation, no LLM call, no schema change.
2026-05-11 06:56:09 -04:00

564 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Prometheus-Σ §12 trigger-probe — ticket #000037 Phase 0 evidence-gather.
Phase 1 of #000037 (the recursive falsification controller) gates on
one of four §12 triggers firing. Triggers 1-3 are measurable from
already-committed state; trigger 4 is operator decision. This script
walks every shard's ``audit_events``, ``capital_ledger``, and
``fork_score_branches`` (#000012 Phase 1c) tables, computes the §12
quantities, and emits a markdown report.
Pure measurement. No mutation. No LLM call. No schema change. The
report is the deliverable; fox uses it to decide go/no-go on Phase 1.
The probe is defensive about data-availability: it explicitly reports
"no data" when a trigger structurally cannot fire yet (e.g. trigger 1
when no branch sets have been persisted to ``fork_score_branches``)
rather than synthesising a false signal.
"""
from __future__ import annotations
import argparse
import json
import math
import sqlite3
import sys
from datetime import datetime, timezone
from pathlib import Path
# §12 thresholds, transcribed verbatim from the ticket.
DIVERGENCE_N_MIN = 30
DIVERGENCE_VARIANCE_RATIO_FLOOR = 0.5
DIVERGENCE_ABS_STDDEV_FLOOR = 0.10
WITNESS_COST_SHARE_FLOOR = 0.30
DIVERGENCE_EPSILON = 1e-6 # max(mean, ε) guard against div-by-zero
BRANCH_DENSITY_FLOOR = 4 # §12 trigger 1: ≥4 candidate branches per checkpoint
def _read_witness_events(shards: list[Path]) -> list[dict]:
"""Pull every ``providence_canonical_witness`` body across shards."""
bodies: list[dict] = []
for sp in shards:
try:
conn = sqlite3.connect(sp)
except sqlite3.OperationalError:
continue
try:
cur = conn.execute(
"SELECT body, ts FROM audit_events "
"WHERE event_type = 'providence_canonical_witness' "
"ORDER BY seq"
)
for row in cur:
try:
body = json.loads(row[0])
except (TypeError, json.JSONDecodeError):
continue
body["__ts"] = row[1]
body["__shard"] = sp.name
bodies.append(body)
except sqlite3.OperationalError:
# Pre-#000028 shard with no audit_events table or schema gap;
# not a probe-failure mode.
pass
finally:
conn.close()
return bodies
def _read_capital_ledger(shards: list[Path]) -> list[dict]:
"""Aggregate every ``capital_ledger`` row across shards."""
rows: list[dict] = []
for sp in shards:
try:
conn = sqlite3.connect(sp)
conn.row_factory = sqlite3.Row
except sqlite3.OperationalError:
continue
try:
cur = conn.execute(
"SELECT op_type, living, material, financial, "
" intellectual, experiential, social, cultural, "
" spiritual, recorded_at "
"FROM capital_ledger"
)
for r in cur:
d = dict(r)
d["__shard"] = sp.name
rows.append(d)
except sqlite3.OperationalError:
pass
finally:
conn.close()
return rows
# -------------------------------------------------------------- triggers
def trigger_1_branch_density(shards: list[Path]) -> dict:
"""Trigger 1 — branch density: ≥4 candidate branches per checkpoint.
Reads the ``fork_score_branches`` sibling table (#000012 Phase 1c)
via :func:`arborist.substrate.fork_score.branch_set_density`. Each
distinct ``branch_set_id`` is a checkpoint; the trigger fires when
the most-recently-recorded checkpoint carries ≥4 distinct branches.
The report also surfaces how many of *all* recorded checkpoints
clear the floor so §12's "regularly" qualifier stays visible.
Density is summed across shards per ``branch_set_id`` (each
``arborist substrate score --persist-shard`` invocation writes one
shard; a checkpoint normally lives in exactly one). When the table
is absent across every shard — the common case, since ForkScore
Phase 1a is single-validator and ``arborist substrate score`` only
persists branch sets when ``--branch-set`` is passed — the trigger
reports "no data" rather than synthesising a false signal.
"""
from arborist.substrate.fork_score import branch_set_density
density: dict[str, int] = {}
last_ts: dict[str, int] = {}
table_present = False
for sp in shards:
try:
conn = sqlite3.connect(sp)
except sqlite3.OperationalError:
continue
try:
checkpoints = conn.execute(
"SELECT branch_set_id, MAX(recorded_at) "
"FROM fork_score_branches GROUP BY branch_set_id"
).fetchall()
except sqlite3.OperationalError:
# Pre-#000012-Phase-1c shard, or one that has never had a
# branch set persisted. Not a probe-failure mode.
conn.close()
continue
table_present = True
for sid, ts in checkpoints:
density[sid] = density.get(sid, 0) + branch_set_density(conn, sid)
last_ts[sid] = max(last_ts.get(sid, 0), ts or 0)
conn.close()
if not table_present:
return {
"trigger": 1,
"name": "branch density (≥4 branches/checkpoint)",
"fires": False,
"reason": (
"no fork_score_branches table across shards; ForkScore is "
"single-validator (Phase 1a) and `arborist substrate score` "
"only persists branch sets when `--branch-set` is passed. "
"Multi-branch fork-score persistence is the prerequisite — "
"see #000012 Phase 1c."
),
"data_available": False,
"n_checkpoints": 0,
}
if not density:
return {
"trigger": 1,
"name": "branch density (≥4 branches/checkpoint)",
"fires": False,
"reason": (
"fork_score_branches table present but empty across shards; "
"no branch sets persisted yet"
),
"data_available": True,
"n_checkpoints": 0,
}
latest_sid = max(density, key=lambda s: last_ts[s])
latest_density = density[latest_sid]
max_density = max(density.values())
n_clear = sum(1 for d in density.values() if d >= BRANCH_DENSITY_FLOOR)
fires = latest_density >= BRANCH_DENSITY_FLOOR
if fires:
reason = (
f"latest checkpoint `{latest_sid}` carries {latest_density} "
f"branches ≥ {BRANCH_DENSITY_FLOOR}; "
f"{n_clear}/{len(density)} recorded checkpoints clear the floor"
)
else:
reason = (
f"latest checkpoint `{latest_sid}` carries {latest_density} "
f"branch(es) < {BRANCH_DENSITY_FLOOR}; "
f"{n_clear}/{len(density)} recorded checkpoints clear the floor "
f"(max density seen: {max_density})"
)
return {
"trigger": 1,
"name": "branch density (≥4 branches/checkpoint)",
"fires": fires,
"reason": reason,
"data_available": True,
"n_checkpoints": len(density),
"latest_branch_set_id": latest_sid,
"latest_density": latest_density,
"max_density": max_density,
"n_checkpoints_clearing_floor": n_clear,
}
def _divergence_score(agreement_label: str | None) -> float | None:
"""Map a #000028 §1.2 agreement label to a binary LLM-divergence
score for trigger 2.
Returns:
- 0.0 when the LLM converged with the kernel
- 1.0 when the LLM diverged from the kernel
- None when the LLM modality was absent / unparseable, so the
event tells us nothing about LLM divergence.
The N_min=30 sample population is the set of events that produced
a 0/1 score. KERNEL-CACHE-AGREE excludes LLM (unparseable) so it
contributes no signal to trigger 2; CACHE-DRIFT means K=L (LLM
agreed with kernel) so it counts as 0.0.
"""
if not agreement_label:
return None
label = agreement_label.upper()
LLM_AGREE = {
"STRICT-WITNESSED", # K=C=L
"KERNEL-LLM-AGREE", # no cache; K=L
"CACHE-DRIFT", # K=L; cache stale (counts as agree for LLM)
}
LLM_DIVERGED = {
"LLM-DIVERGED", # K=C; L≠K
"KERNEL-LLM-DIVERGED", # no cache; L≠K
"LLM-AND-CACHE-DIVERGED", # only K matches itself
}
LLM_ABSENT = {
"KERNEL-CACHE-AGREE", # LLM unparseable; no signal
}
if label in LLM_AGREE:
return 0.0
if label in LLM_DIVERGED:
return 1.0
if label in LLM_ABSENT:
return None
# Unknown label — substring fallback so renames don't silently
# exclude data. Errs on the side of inclusion.
if "DIVERGED" in label or "DRIFT_LLM" in label:
return 1.0
if "AGREE" in label or "WITNESSED" in label:
return 0.0
return None
def _stddev(samples: list[float]) -> float:
n = len(samples)
if n < 2:
return 0.0
mean = sum(samples) / n
sq = sum((s - mean) ** 2 for s in samples)
return math.sqrt(sq / (n - 1))
def trigger_2_divergence_variance(events: list[dict]) -> dict:
"""Trigger 2 — divergence variance: N≥30, ratio>0.5 or abs>0.10.
Concrete signal from §12:
sample_count >= N_min
AND (stddev/max(mean, ε) > 0.5 OR absolute stddev > 0.10)
"""
scores = [
s for s in (_divergence_score(e.get("agreement_label")) for e in events)
if s is not None
]
n = len(scores)
if n == 0:
return {
"trigger": 2,
"name": "divergence variance",
"fires": False,
"reason": (
"no providence_canonical_witness events with kernel-vs-LLM "
"outcomes found; #000028 not exercised on this corpus yet"
),
"data_available": False,
"n": 0,
}
mean = sum(scores) / n
sd = _stddev(scores)
ratio = sd / max(mean, DIVERGENCE_EPSILON)
abs_fired = sd > DIVERGENCE_ABS_STDDEV_FLOOR
ratio_fired = ratio > DIVERGENCE_VARIANCE_RATIO_FLOOR
n_ok = n >= DIVERGENCE_N_MIN
fires = n_ok and (abs_fired or ratio_fired)
if not n_ok:
reason = f"only {n} samples; N_min = {DIVERGENCE_N_MIN}"
elif fires:
which = []
if ratio_fired:
which.append(f"ratio {ratio:.3f} > {DIVERGENCE_VARIANCE_RATIO_FLOOR}")
if abs_fired:
which.append(f"abs σ {sd:.3f} > {DIVERGENCE_ABS_STDDEV_FLOOR}")
reason = "; ".join(which)
else:
reason = (
f"N={n}{DIVERGENCE_N_MIN}, but "
f"ratio {ratio:.3f}{DIVERGENCE_VARIANCE_RATIO_FLOOR} "
f"and abs σ {sd:.3f}{DIVERGENCE_ABS_STDDEV_FLOOR}"
)
return {
"trigger": 2,
"name": "divergence variance",
"fires": fires,
"reason": reason,
"data_available": True,
"n": n,
"mean": round(mean, 4),
"stddev": round(sd, 4),
"ratio": round(ratio, 4),
}
def trigger_3_witness_cost_share(rows: list[dict]) -> dict:
"""Trigger 3 — witness cost share: witness_compute / total_compute > 0.30.
"Compute" is read as the ``material`` column (kWh proxy), which is
the closest analogue to "compute" in the 8-form ledger. The ticket
leaves the form vague; surfacing both ``material`` and ``financial``
in the report so fox can choose if the threshold should be against
a different form.
Per §12 the trigger requires #000026 baseline to exist (it does;
bench/results/real-shard-baseline.md, 2026-05-08). The ledger
here is whatever has been recorded to date — not a controlled
workload — so the ratio is informational pending a controlled
sweep.
"""
if not rows:
return {
"trigger": 3,
"name": "witness cost share (material > 0.30)",
"fires": False,
"reason": "capital_ledger empty across all shards",
"data_available": False,
"witness_material": 0.0,
"total_material": 0.0,
}
witness_material = sum(
r["material"] or 0.0 for r in rows if r["op_type"] == "canonical_witness"
)
total_material = sum(r["material"] or 0.0 for r in rows)
witness_financial = sum(
r["financial"] or 0.0 for r in rows if r["op_type"] == "canonical_witness"
)
total_financial = sum(r["financial"] or 0.0 for r in rows)
if total_material == 0.0:
return {
"trigger": 3,
"name": "witness cost share (material > 0.30)",
"fires": False,
"reason": "total compute denominator is zero (per §12: do not divide)",
"data_available": False,
"witness_material": witness_material,
"total_material": 0.0,
}
ratio = witness_material / total_material
fires = ratio > WITNESS_COST_SHARE_FLOOR
return {
"trigger": 3,
"name": "witness cost share (material > 0.30)",
"fires": fires,
"reason": (
f"witness material / total material = "
f"{witness_material:.6f} / {total_material:.6f} = {ratio:.3f}"
),
"data_available": True,
"witness_material": round(witness_material, 6),
"total_material": round(total_material, 6),
"witness_financial": round(witness_financial, 6),
"total_financial": round(total_financial, 6),
"ratio": round(ratio, 4),
"n_witness_rows": sum(
1 for r in rows if r["op_type"] == "canonical_witness"
),
"n_total_rows": len(rows),
"caveat": (
"ledger reflects ad-hoc activity, not a controlled #000026 sweep — "
"ratio is informational. Re-run after a #000026 baseline sweep "
"for a workload-anchored answer."
),
}
def trigger_4_operator_need() -> dict:
return {
"trigger": 4,
"name": "operator mission need",
"fires": None,
"reason": "operator decision; not measurable from committed state",
"data_available": False,
}
# -------------------------------------------------------------- report
def render_markdown(
shards_dir: Path,
shards: list[Path],
triggers: list[dict],
events: list[dict],
rows: list[dict],
) -> str:
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
out = []
out.append("# Prometheus-Σ §12 trigger probe")
out.append("")
out.append(f"**Date:** {now}")
out.append(f"**Shards directory:** `{shards_dir}`")
out.append(f"**Shards walked:** {len(shards)} "
f"({', '.join(s.name for s in shards)})")
out.append("")
out.append(
"Phase 1 of ticket #000037 gates on one §12 trigger firing. "
"This probe walks `fork_score_branches` + `audit_events` + "
"`capital_ledger` and reports the empirical state of triggers "
"13. Trigger 4 is operator-stated."
)
out.append("")
out.append("## Verdict")
out.append("")
out.append("| # | Trigger | Fires? | Reason |")
out.append("|---|---------|--------|--------|")
for t in triggers:
fires = t["fires"]
if fires is True:
mark = "**YES**"
elif fires is False:
mark = "no"
else:
mark = "n/a"
out.append(f"| {t['trigger']} | {t['name']} | {mark} | {t['reason']} |")
out.append("")
any_fires = any(t["fires"] is True for t in triggers)
if any_fires:
out.append("**Phase 1 trigger has fired.** Prometheus-Σ Phase 1 may proceed.")
else:
out.append(
"**No measurable §12 trigger has fired yet.** Phase 1 of #000037 "
"remains paper-only; trigger 4 (operator-stated need) bypasses the "
"measured-pressure gates if fox has explicit mission need."
)
out.append("")
out.append("## Trigger 1 — branch density")
out.append("")
t1 = triggers[0]
out.append(f"- Data available: {t1['data_available']}")
if t1.get("n_checkpoints"):
out.append(f"- Recorded checkpoints: {t1['n_checkpoints']}")
out.append(
f"- Latest checkpoint: `{t1['latest_branch_set_id']}` "
f"{t1['latest_density']} branches "
f"(floor {BRANCH_DENSITY_FLOOR})"
)
out.append(f"- Max density across checkpoints: {t1['max_density']}")
out.append(
f"- Checkpoints clearing the floor: "
f"{t1['n_checkpoints_clearing_floor']} / {t1['n_checkpoints']}"
)
out.append(f"- {t1['reason']}")
out.append("")
out.append("## Trigger 2 — divergence variance")
out.append("")
t2 = triggers[1]
if t2.get("data_available"):
out.append(f"- Sample count (N): {t2['n']} "
f"(N_min = {DIVERGENCE_N_MIN})")
out.append(f"- Mean divergence rate: {t2['mean']}")
out.append(f"- Stddev: {t2['stddev']}")
out.append(f"- Ratio (σ/mean): {t2['ratio']}")
out.append(f"- Threshold ratio: > {DIVERGENCE_VARIANCE_RATIO_FLOOR} "
f"OR absolute σ > {DIVERGENCE_ABS_STDDEV_FLOOR}")
out.append(f"- {t2['reason']}")
if events:
from collections import Counter
agree_counts = Counter(e.get("agreement_label", "?") for e in events)
out.append("")
out.append("Agreement-label distribution across all shards:")
out.append("")
out.append("| label | count |")
out.append("|-------|-------|")
for k, v in sorted(agree_counts.items(), key=lambda kv: (-kv[1], kv[0])):
out.append(f"| `{k}` | {v} |")
out.append("")
out.append("## Trigger 3 — witness cost share")
out.append("")
t3 = triggers[2]
if t3.get("data_available"):
out.append(f"- Witness material (kWh proxy): "
f"{t3['witness_material']}")
out.append(f"- Total material: {t3['total_material']}")
out.append(f"- Ratio: {t3['ratio']} "
f"(threshold > {WITNESS_COST_SHARE_FLOOR})")
out.append(f"- Witness financial: {t3['witness_financial']}")
out.append(f"- Total financial: {t3['total_financial']}")
out.append(f"- Witness rows: {t3['n_witness_rows']}")
out.append(f"- Total ledger rows: {t3['n_total_rows']}")
out.append(f"- {t3['reason']}")
if t3.get("caveat"):
out.append("")
out.append(f"**Caveat:** {t3['caveat']}")
out.append("")
out.append("## Trigger 4 — operator mission need")
out.append("")
out.append("- Operator-stated; not measurable from committed state.")
out.append("- Bypasses §12 measured-pressure gates per ticket §12.")
out.append("")
return "\n".join(out)
# -------------------------------------------------------------- main
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument(
"--shards-dir",
type=Path,
default=Path.home() / ".arborist" / "shards",
help="Directory of *.db shard files (default: ~/.arborist/shards)",
)
p.add_argument(
"--out",
type=Path,
default=None,
help="Write markdown report to this path. Default: stdout.",
)
args = p.parse_args(argv)
if not args.shards_dir.exists():
print(f"shards-dir not found: {args.shards_dir}", file=sys.stderr)
return 2
shards = sorted(args.shards_dir.glob("*.db"))
if not shards:
print(f"no .db files in {args.shards_dir}", file=sys.stderr)
return 2
events = _read_witness_events(shards)
rows = _read_capital_ledger(shards)
triggers = [
trigger_1_branch_density(shards),
trigger_2_divergence_variance(events),
trigger_3_witness_cost_share(rows),
trigger_4_operator_need(),
]
md = render_markdown(args.shards_dir, shards, triggers, events, rows)
if args.out:
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(md, encoding="utf-8")
print(f"wrote {args.out}")
else:
print(md)
return 0
if __name__ == "__main__":
sys.exit(main())