Spaces:
Running on Zero
Running on Zero
| #!/usr/bin/env python3 | |
| """Grounding Bridge — Phase B1: observe what the synthesizer forges, grounded. | |
| Connects the CREATE half (cocoon_synthesizer forges qualitative reasoning paths | |
| and strategies) to the VERIFY half (grounding.verify). It reads a forged thought, | |
| pulls any checkable claims, grounds them, and returns an HONEST report. | |
| The honesty is the entire point, and it is subtle here: | |
| - Most forged thoughts are QUALITATIVE ("rational discomfort", "principled | |
| plasticity"). They contain no arithmetic claim. The correct report for such a | |
| thought is UNGROUNDED — "no checkable claim found" — NEVER "verified". | |
| Reporting a qualitative thought as verified because nothing was refuted would | |
| be the exact lie this project exists to prevent. | |
| - A thought that asserts something false and checkable (a bad number) is FLAGGED. | |
| So a forged thought lands in one of three honest states: | |
| FLAGGED — at least one checkable claim was REFUTED. Look here. | |
| SUPPORTED — checkable claims were found and all VERIFIED. | |
| UNGROUNDED — no checkable claim; arithmetic grounding says nothing about it. | |
| (This is most of them, today. It is honest, not a pass.) | |
| Shadow only. Nothing in the runtime calls this yet; when it does (Phase D) it | |
| observes and logs, it does not gate. See docs/NEUROSYMBOLIC_GROUNDING.md. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import time | |
| from dataclasses import dataclass, field, asdict | |
| from pathlib import Path | |
| from typing import List, Optional | |
| from reasoning_forge.grounding import ( | |
| verify, extract_claims, verify_consistency, GroundingResult, Verdict, | |
| ) | |
| # Honest three-state status for a whole forged thought. | |
| FLAGGED = "flagged" # >=1 checkable claim refuted | |
| SUPPORTED = "supported" # checkable claims found, all verified | |
| UNGROUNDED = "ungrounded" # no checkable claim — grounding says nothing (NOT a pass) | |
| class GroundingReport: | |
| """Honest grounding summary for one forged thought.""" | |
| source_kind: str # "reasoning_path" | "strategy" | "pattern" | "text" | |
| source_id: str | |
| status: str # FLAGGED / SUPPORTED / UNGROUNDED | |
| claims_found: int | |
| verified: int | |
| refuted: int | |
| unverifiable: int | |
| results: List[dict] = field(default_factory=list) | |
| note: str = "" | |
| ts: float = field(default_factory=time.time) | |
| def to_dict(self) -> dict: | |
| return asdict(self) | |
| def _classify(results: List[GroundingResult]) -> tuple: | |
| """Return (status, note) from the per-claim verdicts. Omit-never-fabricate: | |
| no checkable claim => UNGROUNDED, never SUPPORTED.""" | |
| checkable = [r for r in results if r.verdict in (Verdict.VERIFIED, Verdict.REFUTED)] | |
| refuted = [r for r in checkable if r.verdict is Verdict.REFUTED] | |
| verified = [r for r in checkable if r.verdict is Verdict.VERIFIED] | |
| if refuted: | |
| return FLAGGED, f"{len(refuted)} checkable claim(s) REFUTED — inspect this forged thought" | |
| if verified: | |
| return SUPPORTED, f"{len(verified)} checkable claim(s) found, all verified" | |
| return UNGROUNDED, ( | |
| "no arithmetic-checkable claim found — this is a qualitative thought " | |
| "grounding cannot speak to yet (honest UNGROUNDED, not a pass)" | |
| ) | |
| def ground_text(text: str, *, source_kind: str = "text", source_id: str = "") -> GroundingReport: | |
| """Ground the checkable claims in a block of text. Pure (no logging). | |
| Beyond per-claim grounding, checks the claims JOINTLY for contradiction (z3): | |
| a thought whose individual claims each pass but are mutually impossible | |
| (a circular ordering) is FLAGGED even though nothing was individually refuted. | |
| """ | |
| claims = extract_claims(text or "") | |
| results = [verify(c) for c in claims] | |
| status, note = _classify(results) | |
| # Cross-claim contradiction: catches what per-claim checks miss. | |
| consistency = verify_consistency(claims) | |
| if consistency.verdict is Verdict.REFUTED and status != FLAGGED: | |
| status = FLAGGED | |
| note = f"claims are individually fine but JOINTLY CONTRADICTORY — {consistency.detail}" | |
| return GroundingReport( | |
| source_kind=source_kind, | |
| source_id=source_id or "", | |
| status=status, | |
| claims_found=len(claims), | |
| verified=sum(1 for r in results if r.verdict is Verdict.VERIFIED), | |
| refuted=sum(1 for r in results if r.verdict is Verdict.REFUTED), | |
| unverifiable=sum(1 for r in results if r.verdict is Verdict.UNVERIFIABLE), | |
| results=[r.to_dict() for r in results], | |
| note=note, | |
| ) | |
| def ground_reasoning_path(path) -> GroundingReport: | |
| """Ground a cocoon_synthesizer ReasoningPath (steps + conclusion). | |
| Accepts the dataclass or a dict with 'steps'/'conclusion'/'strategy_name'. | |
| """ | |
| steps = getattr(path, "steps", None) | |
| conclusion = getattr(path, "conclusion", None) | |
| name = getattr(path, "strategy_name", None) | |
| if steps is None and isinstance(path, dict): | |
| steps = path.get("steps", []) | |
| conclusion = path.get("conclusion", "") | |
| name = path.get("strategy_name", "") | |
| text = "\n".join(list(steps or []) + [conclusion or ""]) | |
| return ground_text(text, source_kind="reasoning_path", source_id=name or "") | |
| def ground_strategy(strategy) -> GroundingReport: | |
| """Ground a ReasoningStrategy (definition + mechanism + rationale).""" | |
| def g(attr): | |
| return getattr(strategy, attr, None) if not isinstance(strategy, dict) else strategy.get(attr) | |
| text = "\n".join(str(g(a) or "") for a in ("definition", "mechanism", "improvement_rationale")) | |
| sid = g("strategy_id") or g("name") or "" | |
| return ground_text(text, source_kind="strategy", source_id=str(sid)) | |
| def ground_pattern(pattern) -> GroundingReport: | |
| """Ground a CocoonPattern (description + structural_similarity + evidence).""" | |
| def g(attr): | |
| return getattr(pattern, attr, None) if not isinstance(pattern, dict) else pattern.get(attr) | |
| evidence = g("evidence") or [] | |
| text = "\n".join([str(g("description") or ""), str(g("structural_similarity") or "")] + [str(e) for e in evidence]) | |
| return ground_text(text, source_kind="pattern", source_id=str(g("name") or "")) | |
| def observe(report: GroundingReport, path: str | Path = None) -> None: | |
| """Append a report to the grounding shadow log. SHADOW ONLY (applied: false).""" | |
| path = Path(path) if path else Path(__file__).resolve().parent.parent / "data" / "grounding_shadow.jsonl" | |
| rec = report.to_dict() | |
| rec["mode"] = "shadow" | |
| rec["applied"] = False | |
| rec["record"] = "bridge_report" | |
| try: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| with path.open("a", encoding="utf-8") as f: | |
| f.write(json.dumps(rec, ensure_ascii=False) + "\n") | |
| except Exception: | |
| pass # logging must never break a turn | |