Implements three coupled tickets in one push: complete the 5S battery, align 5T to Dav1DPrometheus vocabulary + complete it, open 5F battery. All runners deterministic, no LLM-as-judge anywhere. #000023 — 5S Phase 1b (closed) run_syllogism + run_synthesis + run_semiotics replace stubs. 30 deterministic fixtures each (90 total, all passing). Carrier metadata mandatory; unsupported carriers fail explicitly. Syllogism kernel handles categorical_transitivity, chain_3, invalid_converse, missing_premise. Order-agnostic over premise permutations. Synthesis uses content-token subset check (stopwords removed) — catches single-token entity swaps that lax overlap missed. Semiotics validates synonym-swap invariance via π* canonicalize + re-substitution. Hidden-channel work stays defensive only. #000024 — 5T Phase 1b (closed) Vocabulary aligned: Transfer→transfer-learning, Truth→truthtables, Timing→time. Legacy transfer-v1.jsonl + run_transfer kept intact. run_transfer_learning, run_triangulation, run_truthtables, run_transitivity, run_time replace stubs. 30 fixtures each × 5 = 150 total (155 with legacy transfer). Triangulation runs 4 strategies (substring, token_subset, token_overlap, entity_match) and gates on agreement threshold. Truthtables: deterministic propositional evaluator with recursive-descent parser; supports AND/OR/NOT/XOR/IMPL/IFF; capped at N=4 variables. Transitivity: typed-relation whitelist (implies, subset_of, ancestor_of, before, less_than). BFS path walk; mixed/unknown relations fail by construction. Time: synthetic memory-snapshot chains test preservation, stale-marking, current_root tracking. Phase 1b.2 will read real memory_records. #000025 — 5F Phase 1a (in progress; Phase 1b expansion still open) New bench/batteries/b_5f.py with five sub-batteries: - Function (shape_match / pointer_set_match / threshold_on_metric) - Finetuning (parent→child SelfModel improvement check) - Falsification (planted-error detection rate, verifier_method_root pinned) - Formulate (structural lattice match: claim count + sorted-approx text + exact pointer-ID-set; not exact-string) - Feedback Loop (operation/observation chain → expected_delta in aggregated observation feed) 10 deterministic seed fixtures per sub-battery (50 total). feedback_efficiency / adaptation_efficiency hooks stub the capital- ledger integration for v8 fork-choice. Cross-cutting: - bench/batteries/base.py: PHASE_1_CARRIERS whitelist + validate_carrier helper. Backward-compatible with Phase 1a fixtures that lack carrier field (defaults to "text"). - runner.py registers all 16 sub-batteries in _DEFAULT_FIXTURES so `--all` runs the entire Dav1DPrometheus suite. - Makefile: bench-5s / bench-5t / bench-5f / bench-5s5t5f targets. bench-5t-legacy preserves Phase 1a access. - tests/test_bench_batteries.py: 29 tests (was 17). Phase 1a digest stability checks, sub-battery smoke tests, carrier rejection tests, transitivity-whitelist test, truthtables N>4 cap test. Bench summary across the full suite: 5s syntax/semantics/syllogism/synthesis/semiotics 108/108 pass 5t transfer/transfer-learning/triangulation/truthtables/transitivity/time 154/154 pass 5f function/finetuning/falsification/formulate/feedback-loop 50/50 pass TOTAL: 312 fixtures across 16 sub-batteries — 100% pass. Full test suite: 1103 passed, 36 skipped. Source: Legally Unprecedented Dav1DPrometheus (BasementAGI host). Honoring his framework. The complete state-space synthesis (SQD + v7 + 5S/5T/5F) is now executable infrastructure, not metaphor.
652 lines
22 KiB
Python
652 lines
22 KiB
Python
"""5T battery — Transitivity / Transfer Learning / Triangulation /
|
|
Truthtables / Time.
|
|
|
|
Phase 1a (landed in ticket #000021): Transfer (legacy SQD-whitepaper
|
|
name). Stays intact for back-compat.
|
|
|
|
Phase 1b (ticket #000024, this file):
|
|
- Vocabulary aligned to Dav1DPrometheus: ``transfer-learning``,
|
|
``triangulation``, ``truthtables``, ``transitivity``, ``time``.
|
|
- ``transfer-v1`` digest stays pinned; new ``transfer-learning-v2``
|
|
is the canonical name going forward.
|
|
- Five non-stub runners over text / claim_lattice / propositional /
|
|
memory-root carriers.
|
|
|
|
No LLM-as-judge anywhere. All runners deterministic. Cross-modality
|
|
discipline: fixtures may carry future-modality hooks; v1 carriers
|
|
limited to the Phase-1 whitelist in ``base.PHASE_1_CARRIERS``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from itertools import permutations
|
|
from pathlib import Path
|
|
from typing import Iterable
|
|
|
|
from bench.batteries.base import (
|
|
BatteryResult,
|
|
TaskResult,
|
|
fixture_digest,
|
|
fixture_meta,
|
|
iter_tasks,
|
|
validate_carrier,
|
|
)
|
|
from bench.batteries.b_5s import _runtime_digest
|
|
|
|
|
|
def _carrier_check(task: dict) -> tuple[bool, str]:
|
|
reason = validate_carrier(task)
|
|
if reason is not None:
|
|
return False, reason
|
|
return True, ""
|
|
|
|
|
|
def _build_result(
|
|
sub_battery: str,
|
|
fixtures_path: Path,
|
|
per_task: list[TaskResult],
|
|
metrics: dict[str, float],
|
|
) -> BatteryResult:
|
|
pass_count = sum(1 for t in per_task if t.passed)
|
|
fail_count = len(per_task) - pass_count
|
|
return BatteryResult(
|
|
battery="5t",
|
|
sub_battery=sub_battery,
|
|
fixture_path=str(fixtures_path),
|
|
fixture_digest=fixture_digest(fixtures_path),
|
|
pass_count=pass_count,
|
|
fail_count=fail_count,
|
|
metrics=metrics,
|
|
per_task=per_task,
|
|
runtime_digest=_runtime_digest(),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Phase 1a: Transfer (legacy)
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def run_transfer(fixtures_path: Path) -> BatteryResult:
|
|
"""Legacy SQD-whitepaper Transfer runner.
|
|
|
|
Each task: π* + two surface variants → does canonicalize collapse
|
|
them as expected_equivalent says? Kept untouched for Phase 1a
|
|
digest stability.
|
|
"""
|
|
from arborist.pi_star import get
|
|
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
detail: dict = {}
|
|
try:
|
|
pi_star = get(task["pi_star"])
|
|
ca = pi_star.canonicalize(task["input_a"].encode("utf-8"))
|
|
cb = pi_star.canonicalize(task["input_b"].encode("utf-8"))
|
|
equivalent = ca == cb
|
|
ok = equivalent == bool(task["expected_equivalent"])
|
|
if not ok:
|
|
detail["reason"] = (
|
|
f"observed_equivalent={equivalent} "
|
|
f"expected={task['expected_equivalent']}"
|
|
)
|
|
except Exception as exc: # noqa: BLE001
|
|
detail["reason"] = f"{type(exc).__name__}: {exc}"
|
|
ok = False
|
|
per_task.append(TaskResult(task_id=task_id, passed=ok, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "transfer"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"transfer_pass_rate": rate},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Phase 1b: Transfer Learning (Dav1DPrometheus canonical)
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def run_transfer_learning(fixtures_path: Path) -> BatteryResult:
|
|
"""Pattern transfer across tasks/domains.
|
|
|
|
Each task: a source-task pattern + a target-task pattern + an
|
|
expected_transfer flag. Runner asserts that the target task
|
|
can be expressed under the same canonical structure as the
|
|
source — for v1, this means the target's content tokens are a
|
|
superset of the source's content tokens at the *pattern* level
|
|
(not the example level).
|
|
"""
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
src_pattern = task["source_task"]["pattern"]
|
|
tgt_pattern = task["target_task"]["pattern"]
|
|
expected = bool(task["target_task"].get("expected_transfer", True))
|
|
structural_match = _patterns_share_structure(src_pattern, tgt_pattern)
|
|
passed = structural_match == expected
|
|
detail = {
|
|
"expected_transfer": expected,
|
|
"observed_transfer": structural_match,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "transfer-learning"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"transfer_learning_success_rate": rate},
|
|
)
|
|
|
|
|
|
def _patterns_share_structure(a: str, b: str) -> bool:
|
|
"""Two patterns share structure iff they share ≥ 50% of content
|
|
tokens AND have similar length (within 50% of each other)."""
|
|
from bench.batteries.b_5s import _content_tokens
|
|
|
|
a_tokens = _content_tokens(a)
|
|
b_tokens = _content_tokens(b)
|
|
if not a_tokens or not b_tokens:
|
|
return False
|
|
overlap = len(a_tokens & b_tokens) / max(len(a_tokens), len(b_tokens))
|
|
len_ratio = min(len(a), len(b)) / max(len(a), len(b))
|
|
return overlap >= 0.5 and len_ratio >= 0.5
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Phase 1b: Triangulation
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Independent verifier strategies that agree (or don't) on whether a
|
|
# claim is supported by evidence text. Each is deterministic.
|
|
|
|
def _strat_substring(claim: str, evidence: str) -> bool:
|
|
return claim.strip().lower() in evidence.lower()
|
|
|
|
|
|
def _strat_token_subset(claim: str, evidence: str) -> bool:
|
|
from bench.batteries.b_5s import _content_tokens
|
|
|
|
a = _content_tokens(claim)
|
|
b = _content_tokens(evidence)
|
|
return bool(a) and a.issubset(b)
|
|
|
|
|
|
def _strat_token_overlap(claim: str, evidence: str, threshold: float = 0.6) -> bool:
|
|
from bench.batteries.b_5s import _content_tokens
|
|
|
|
a = _content_tokens(claim)
|
|
b = _content_tokens(evidence)
|
|
if not a:
|
|
return False
|
|
return len(a & b) / len(a) >= threshold
|
|
|
|
|
|
def _strat_entity_match(claim: str, evidence: str) -> bool:
|
|
"""Capitalized tokens (proper nouns) in claim must appear in evidence."""
|
|
entities = {
|
|
tok.strip(".,;:!?")
|
|
for tok in claim.split()
|
|
if tok and tok[0].isupper() and tok.lower() not in {"a", "an", "the", "i"}
|
|
}
|
|
if not entities:
|
|
return False
|
|
e_low = evidence.lower()
|
|
return all(e.lower() in e_low for e in entities)
|
|
|
|
|
|
_TRIANGULATION_STRATEGIES = {
|
|
"substring": _strat_substring,
|
|
"token_subset": _strat_token_subset,
|
|
"token_overlap": _strat_token_overlap,
|
|
"entity_match": _strat_entity_match,
|
|
}
|
|
|
|
|
|
def run_triangulation(fixtures_path: Path) -> BatteryResult:
|
|
"""Independent-strategy agreement on (claim, evidence) pairs.
|
|
|
|
Each task names a list of strategies + an ``expected_agreement_min``.
|
|
Runner runs each strategy, computes pass-rate across them, and
|
|
accepts the task iff agreement rate ≥ threshold.
|
|
"""
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
strategies = task.get("strategies", list(_TRIANGULATION_STRATEGIES))
|
|
claim = task["claim"]
|
|
evidence = task["evidence"]
|
|
results = {}
|
|
verdicts: list[bool] = []
|
|
for s in strategies:
|
|
fn = _TRIANGULATION_STRATEGIES.get(s)
|
|
if fn is None:
|
|
continue
|
|
v = fn(claim, evidence)
|
|
results[s] = v
|
|
verdicts.append(v)
|
|
if not verdicts:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False,
|
|
detail={"reason": "no recognized strategies"})
|
|
)
|
|
continue
|
|
agreement = sum(verdicts) / len(verdicts)
|
|
threshold = float(task.get("expected_agreement_min", 0.75))
|
|
expected = task.get("expected", "pass")
|
|
# expected="pass" → claim should triangulate; agreement>=threshold.
|
|
# expected="fail" → claim should NOT triangulate; agreement<threshold.
|
|
if expected == "pass":
|
|
passed = agreement >= threshold
|
|
else:
|
|
passed = agreement < threshold
|
|
per_task.append(
|
|
TaskResult(
|
|
task_id=task_id,
|
|
passed=passed,
|
|
detail={
|
|
"strategy_results": results,
|
|
"agreement": agreement,
|
|
"threshold": threshold,
|
|
"expected": expected,
|
|
},
|
|
)
|
|
)
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "triangulation"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"triangulation_agreement_rate": rate},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Phase 1b: Truthtables (N=2..4)
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def _eval_propositional(expr: str, env: dict[str, bool]) -> bool:
|
|
"""Deterministic propositional evaluator for AND/OR/NOT/XOR.
|
|
|
|
Recognizes tokens A-Z (variable), AND, OR, NOT, XOR, IMPL, IFF,
|
|
parentheses. No LLM. Operator precedence: NOT > AND > OR/XOR/IMPL/IFF
|
|
(left-to-right within same precedence). Parens override.
|
|
|
|
Implementation: tokenize → recursive-descent parse → evaluate.
|
|
"""
|
|
tokens = _tokenize_propositional(expr)
|
|
pos = [0]
|
|
|
|
def expect(t: str) -> None:
|
|
if pos[0] >= len(tokens) or tokens[pos[0]] != t:
|
|
raise ValueError(f"expected {t!r} at {pos[0]}")
|
|
pos[0] += 1
|
|
|
|
def peek() -> str | None:
|
|
if pos[0] >= len(tokens):
|
|
return None
|
|
return tokens[pos[0]]
|
|
|
|
def parse_atom() -> bool:
|
|
t = peek()
|
|
if t is None:
|
|
raise ValueError("unexpected end of expression")
|
|
if t == "(":
|
|
pos[0] += 1
|
|
v = parse_or()
|
|
expect(")")
|
|
return v
|
|
if t == "NOT":
|
|
pos[0] += 1
|
|
return not parse_atom()
|
|
if t in ("TRUE", "FALSE"):
|
|
pos[0] += 1
|
|
return t == "TRUE"
|
|
if len(t) == 1 and t.isupper() and t.isalpha():
|
|
if t not in env:
|
|
raise ValueError(f"variable {t!r} not in env")
|
|
pos[0] += 1
|
|
return env[t]
|
|
raise ValueError(f"unexpected token {t!r}")
|
|
|
|
def parse_and() -> bool:
|
|
left = parse_atom()
|
|
while peek() == "AND":
|
|
pos[0] += 1
|
|
right = parse_atom()
|
|
left = left and right
|
|
return left
|
|
|
|
def parse_or() -> bool:
|
|
left = parse_and()
|
|
while peek() in ("OR", "XOR", "IMPL", "IFF"):
|
|
op = peek()
|
|
pos[0] += 1
|
|
right = parse_and()
|
|
if op == "OR":
|
|
left = left or right
|
|
elif op == "XOR":
|
|
left = left != right
|
|
elif op == "IMPL":
|
|
left = (not left) or right
|
|
else: # IFF
|
|
left = left == right
|
|
return left
|
|
|
|
val = parse_or()
|
|
if pos[0] != len(tokens):
|
|
raise ValueError(f"unconsumed tokens at {pos[0]}: {tokens[pos[0]:]!r}")
|
|
return val
|
|
|
|
|
|
def _tokenize_propositional(expr: str) -> list[str]:
|
|
"""Split an expression into tokens, treating AND/OR/NOT/XOR/IMPL/IFF as keywords."""
|
|
out: list[str] = []
|
|
i = 0
|
|
while i < len(expr):
|
|
c = expr[i]
|
|
if c.isspace():
|
|
i += 1
|
|
continue
|
|
if c in "()":
|
|
out.append(c)
|
|
i += 1
|
|
continue
|
|
if c.isalpha():
|
|
j = i
|
|
while j < len(expr) and expr[j].isalpha():
|
|
j += 1
|
|
tok = expr[i:j]
|
|
up = tok.upper()
|
|
if up in ("AND", "OR", "NOT", "XOR", "IMPL", "IFF", "TRUE", "FALSE"):
|
|
out.append(up)
|
|
elif len(tok) == 1:
|
|
out.append(tok.upper())
|
|
else:
|
|
raise ValueError(f"unrecognized token: {tok!r}")
|
|
i = j
|
|
continue
|
|
raise ValueError(f"unexpected char: {c!r}")
|
|
return out
|
|
|
|
|
|
def run_truthtables(fixtures_path: Path) -> BatteryResult:
|
|
"""Exhaustive propositional truth-table evaluation; N=2..4.
|
|
|
|
Each task lists `variables` (≤4), an `expression`, and a `rows`
|
|
array enumerating expected outputs. Runner evaluates the
|
|
expression on every row and fails the task if any row mismatches.
|
|
"""
|
|
per_task: list[TaskResult] = []
|
|
coverage_total = 0
|
|
coverage_correct = 0
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
variables = task["variables"]
|
|
if len(variables) > 4:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False,
|
|
detail={"reason": f"N={len(variables)} exceeds cap of 4"})
|
|
)
|
|
continue
|
|
rows = task["rows"]
|
|
coverage_total += len(rows)
|
|
all_correct = True
|
|
first_mismatch = None
|
|
for row in rows:
|
|
try:
|
|
got = _eval_propositional(task["expression"], row["inputs"])
|
|
except Exception as exc: # noqa: BLE001
|
|
all_correct = False
|
|
first_mismatch = {
|
|
"inputs": row["inputs"],
|
|
"error": f"{type(exc).__name__}: {exc}",
|
|
}
|
|
break
|
|
if got == bool(row["expected"]):
|
|
coverage_correct += 1
|
|
else:
|
|
all_correct = False
|
|
if first_mismatch is None:
|
|
first_mismatch = {
|
|
"inputs": row["inputs"],
|
|
"expected": row["expected"],
|
|
"got": got,
|
|
}
|
|
detail: dict = {"row_count": len(rows)}
|
|
if first_mismatch is not None:
|
|
detail["first_mismatch"] = first_mismatch
|
|
per_task.append(TaskResult(task_id=task_id, passed=all_correct, detail=detail))
|
|
|
|
total = len(per_task)
|
|
pass_rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
row_acc = coverage_correct / coverage_total if coverage_total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "truthtables"),
|
|
fixtures_path,
|
|
per_task,
|
|
{
|
|
"truth_table_coverage_rate": pass_rate,
|
|
"row_accuracy_rate": row_acc,
|
|
},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Phase 1b: Transitivity (typed-relation whitelist)
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Per ticket #000024 §4.3: only relations on this whitelist license
|
|
# transitive closure. "related_to" / "causes" without domain
|
|
# constraints are NOT transitive.
|
|
_TRANSITIVE_RELATIONS = frozenset({
|
|
"implies",
|
|
"subset_of",
|
|
"ancestor_of",
|
|
"before",
|
|
"less_than",
|
|
})
|
|
|
|
|
|
def _walk_relation_path(
|
|
edges: list[dict], start: str, end: str, relation: str
|
|
) -> bool:
|
|
"""BFS over edges of the given relation only. Returns True iff a
|
|
path from start to end exists in the relation-restricted subgraph.
|
|
"""
|
|
if relation not in _TRANSITIVE_RELATIONS:
|
|
return False
|
|
adj: dict[str, set[str]] = {}
|
|
for e in edges:
|
|
if e.get("relation") != relation:
|
|
continue
|
|
adj.setdefault(e["from"], set()).add(e["to"])
|
|
if start == end:
|
|
return True
|
|
visited = {start}
|
|
queue = [start]
|
|
while queue:
|
|
node = queue.pop(0)
|
|
for nxt in adj.get(node, ()):
|
|
if nxt == end:
|
|
return True
|
|
if nxt not in visited:
|
|
visited.add(nxt)
|
|
queue.append(nxt)
|
|
return False
|
|
|
|
|
|
def run_transitivity(fixtures_path: Path) -> BatteryResult:
|
|
"""Typed-relation chain pass-rate.
|
|
|
|
Each task: typed edges + a query (from, to, relation). Runner
|
|
walks the edge subgraph for that relation and asserts whether a
|
|
path exists. Mixed-relation chains never pass — runner only
|
|
considers edges of the queried relation. Non-transitive
|
|
relations always fail by construction.
|
|
"""
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
q = task["query"]
|
|
edges = task["edges"]
|
|
path_exists = _walk_relation_path(edges, q["from"], q["to"], q["relation"])
|
|
observed = "pass" if path_exists else "fail"
|
|
expected = task.get("expected", "pass")
|
|
passed = observed == expected
|
|
detail = {
|
|
"expected": expected,
|
|
"observed": observed,
|
|
"relation": q["relation"],
|
|
"transitive": q["relation"] in _TRANSITIVE_RELATIONS,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "transitivity"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"full_chain_pass_rate": rate},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Phase 1b: Time (memory-root snapshots)
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def run_time(fixtures_path: Path) -> BatteryResult:
|
|
"""Temporal-context preservation across snapshots.
|
|
|
|
Each task: an ordered list of snapshots (each with a memory_root,
|
|
facts, optional stale-marker) + an `expected` block stating which
|
|
facts should preserve, which should mark stale, and what the
|
|
final root is. Runner deterministically validates these
|
|
properties from the fixture itself — no shard reads required for
|
|
Phase 1b. Real shard-driven Time runs ship in Phase 1b.2 once
|
|
arborist's memory_records is populated by a real workload.
|
|
"""
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
snapshots = task["snapshots"]
|
|
expected = task["expected"]
|
|
# All facts seen across the whole chain.
|
|
all_facts: set[str] = set()
|
|
for s in snapshots:
|
|
all_facts.update(s.get("facts", []))
|
|
all_facts.update(s.get("stale_facts", []))
|
|
# Preservation check: every fact named in expected.preserved_facts
|
|
# must appear in some snapshot.
|
|
preserved_ok = all(
|
|
f in all_facts
|
|
for f in expected.get("preserved_facts", [])
|
|
)
|
|
# Stale check: every fact in expected.marks_stale must appear
|
|
# at least once and the FINAL snapshot's stale_facts must
|
|
# include it.
|
|
final_stale = set(snapshots[-1].get("stale_facts", []))
|
|
stale_ok = all(
|
|
f in final_stale
|
|
for f in expected.get("marks_stale", [])
|
|
)
|
|
# Current root check.
|
|
current_root_ok = (
|
|
snapshots[-1].get("memory_root") == expected["current_root"]
|
|
)
|
|
passed = preserved_ok and stale_ok and current_root_ok
|
|
detail = {
|
|
"preserved_ok": preserved_ok,
|
|
"stale_ok": stale_ok,
|
|
"current_root_ok": current_root_ok,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
pres_rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "time"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"temporal_context_preservation_rate": pres_rate},
|
|
)
|
|
|
|
|
|
# Triangulate / Timing alias names from Phase 1a stubs are removed —
|
|
# canonical Dav1DPrometheus names below.
|
|
|
|
SUB_BATTERIES = {
|
|
"transfer": run_transfer, # legacy SQD name (Phase 1a)
|
|
"transfer-learning": run_transfer_learning, # canonical Dav1DPrometheus
|
|
"triangulation": run_triangulation,
|
|
"truthtables": run_truthtables,
|
|
"transitivity": run_transitivity,
|
|
"time": run_time,
|
|
}
|