diff --git a/.gitignore b/.gitignore index 80a9d7d8..67403a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,12 @@ teaching/math_proposals/proposals.jsonl engine_state/manifest.json engine_state/recognizers.jsonl engine_state/discovery_candidates.jsonl + +# Private outreach / personal packet — never tracked +01_Anthropic/ + +# Agent working directories +.agents/ + +# Cowork/Claude tooling artifacts +skills-lock.json diff --git a/demos/epistemic_truth_state/README.md b/demos/epistemic_truth_state/README.md index 69700e21..44ada457 100644 --- a/demos/epistemic_truth_state/README.md +++ b/demos/epistemic_truth_state/README.md @@ -3,8 +3,8 @@ This demo proves one narrow boundary: ```text -A model-style proposer submits a claim, an evidence bundle, and an optional -bounded-inference block. +A model-style proposer submits a claim, sealed evidence references, and an +optional bounded-inference block. CORE alone assigns the typed epistemic state. The output is a deterministic, replayable evidence artifact. The proposer never controls its own truth-state. @@ -23,19 +23,37 @@ state is typed * A model-style proposer can submit a claim without gaining authority over its epistemic standing. +* The proposer supplies evidence references only. CORE resolves those references + against the sealed local corpus in + [`evidence_corpus.json`](evidence_corpus.json), requiring a matching + `content_sha256` before any evidence can count. * CORE alone assigns the typed state, drawn from the canonical taxonomy in [`core/epistemic_state.py`](../../core/epistemic_state.py) — `verified`, - `evidenced`, `inferred`, `undetermined`, `scope_boundary`. There is no - parallel enum. + `evidenced`, `inferred`, `contradicted`, `undetermined`, `scope_boundary`. + There is no parallel enum. * CORE derives `normative_clearance`, the `evidence_ledger`, the - `authority_path`, and a fresh `trace_hash` itself. + `authority_path`, and a fresh `trace_hash` itself. The corpus seal + (`corpus_sha256`) is pinned into every evaluated trace. * Any proposer-supplied `proposed_state` or `trace_hash` is recorded as ignored and never read by the decision path. * Invalid payloads fail at the typed boundary *before* any state evaluation runs. -* `verified` requires two or more independent evidence records that explicitly - match the claim's subject and predicate — corroboration the proposer cannot - fabricate through the schema. +* `verified` requires two or more corpus records that explicitly match the + claim's subject and predicate and come from distinct provenance roots — + corroboration the proposer cannot fabricate through the request schema. +* `inferred` is decided by a real entailment proof, not by reference counting. + The cited premises (fact records contribute their `subject__predicate` atom; + rule records their committed formula) must *propositionally entail* the + claim's atom under + [`generate/proof_chain/entail.py`](../../generate/proof_chain/entail.py) — + the sound-and-complete ROBDD decision procedure — cross-checked against the + independent truth-table oracle in + [`evals/deductive_logic/oracle.py`](../../evals/deductive_logic/oracle.py) + (no shared code). The ROBDD proof keys and the oracle verdict are both in + the trace; if the two procedures ever disagreed, the demo refuses outright. + Citing records that merely *exist* yields `undetermined`, never `inferred` — + the committed scenario `unrelated-premise-still-undetermined` exercises + exactly that attack. ## What this does not prove @@ -47,6 +65,9 @@ state is typed a claim and makes no safety guarantee. * It does not call a network, a model API, a subprocess, or any side-effecting tool. It evaluates JSON and returns JSON. +* The entailment leg is propositional only (atoms are `subject__predicate` + pairs); quantified or predicate-logic structure is out of regime and refuses + upstream in the engine. * It does not claim broader epistemic coverage than the small local envelope and the deterministic rules encoded in `authority.py`. @@ -74,18 +95,27 @@ Each layer keeps the same doctrine: a model-style proposer contributes typed data, CORE alone decides, and the decision is a deterministic trace artifact with no proposer-held authority and no execution path. -## The six scenarios +## The seven scenarios -* `verified-supported-claim` — two independent matching records → `verified`, - `verified_by_matching_evidence` (clearance `unassessable`). +* `verified-supported-claim` — two matching corpus records with distinct + provenance roots → `verified`, `verified_by_matching_evidence` (clearance + `unassessable`). * `evidenced-but-not-verified-claim` — one supporting record → `evidenced`, `evidence_present_but_not_verifying`. -* `inferred-from-bounded-evidence` — claim follows from resolved premises → - `inferred`, `bounded_inference_from_evidence`, with `inference_basis` IDs. +* `inferred-from-bounded-evidence` — two fact premises plus a ratified rule + record propositionally entail the claim's atom → `inferred`, + `entailed_from_resolved_premises`, with the ROBDD entailment trace + (`entailment_check_key: "T"`), the independent oracle verdict, and + `inference_basis` IDs. +* `unrelated-premise-still-undetermined` — a claim with no support citing a + resolvable but logically unrelated record as a premise → the engine decides + `unknown`, the state is `undetermined`. `inferred` cannot be minted by + citing records that merely exist. * `undetermined-insufficient-evidence` — no relevant evidence → `undetermined`, `insufficient_evidence`, with an explicit `question`. * `refused-outside-scope` — claim declared outside the local envelope → - status `refused`, `scope_boundary`, `outside_epistemic_envelope`. + status `refused`, `scope_boundary`, `outside_epistemic_envelope`; the corpus + is never consulted. * `invalid-state-smuggling-attempt` — root-level `assigned_state` / `status` / `evidence_ledger` / `authority_path` / `trace_hash` injection → status `invalid`, `authority_evaluated: false`, every smuggled property listed in @@ -94,15 +124,22 @@ with no proposer-held authority and no execution path. ## Honesty ledger * Real: closed recursive schema validation, canonical-enum state assignment via - `core.epistemic_state`, deterministic trace hashing over the response minus - `trace_hash`, evidence-ledger derivation, expected-artifact pinning, - double-run byte-identical determinism, output-directory hardening. + `core.epistemic_state`, sealed-corpus evidence resolution by content hash, + sound-and-complete ROBDD entailment with independent-oracle cross-check on + the inference leg, deterministic trace hashing over the response minus + `trace_hash`, corpus seal pinning, evidence-ledger derivation, + expected-artifact pinning, double-run byte-identical determinism, + output-directory hardening. * Simulated: the proposer side is static fixture data standing in for a - model-style proposer; the evidence bundle is hand-authored, not retrieved from - the live vault. + model-style proposer; the evidence corpus is committed local fixture evidence, + not retrieved from the live vault. * Honest non-claim: `normative_clearance` is `unassessable` on every non-invalid output, including `verified`, because this demo runs no safety/ethics verdict pass and therefore has no basis to clear anything. +* Defensive path: the `entailment_oracle_disagreement` refusal exists and is + tested (by forcing a fake oracle verdict in tests); it has never fired on + real input and is expected never to — both procedures are sound and complete + over the propositional regime. * Not claimed: runtime integration, serving integration, real evidence retrieval, a safety guarantee, or any coverage beyond this local envelope. diff --git a/demos/epistemic_truth_state/authority.py b/demos/epistemic_truth_state/authority.py index 4ab5c724..7a16a526 100644 --- a/demos/epistemic_truth_state/authority.py +++ b/demos/epistemic_truth_state/authority.py @@ -1,9 +1,19 @@ """Local deterministic epistemic-state authority substrate for the demo. -A model-style proposer submits a claim, an evidence bundle, and (optionally) a -bounded-inference block. The proposer contributes *data only*. CORE alone: +A model-style proposer submits a claim, sealed evidence references, and +(optionally) a bounded-inference block. The proposer contributes *references only* +for evidence; CORE resolves those references against a sealed local corpus. CORE +alone: * validates a closed payload, +* resolves evidence references by committed content hash, +* derives support and provenance independence from the committed record bodies + (never from proposer-supplied labels — the schema rejects them), +* decides the bounded-inference leg with the real propositional entailment + engine (:func:`generate.proof_chain.entail.evaluate_entailment_with_trace`, + the sound-and-complete ROBDD decision procedure) cross-checked against the + independent truth-table oracle + (:func:`evals.deductive_logic.oracle.oracle_entailment`), * assigns the typed epistemic state drawn from the canonical taxonomy in :mod:`core.epistemic_state` (never a parallel enum), * derives normative clearance, @@ -11,7 +21,10 @@ bounded-inference block. The proposer contributes *data only*. CORE alone: * regenerates a deterministic trace hash. The proposer cannot set ``assigned_state``, ``status``, ``trace_hash``, -``authority_path``, the evidence ledger, or ``normative_clearance``. Any +``authority_path``, the evidence ledger, or ``normative_clearance`` — and it +cannot mint them indirectly: ``inferred`` is assigned only when the cited, +resolved premises *propositionally entail* the claim's atom, so citing +unrelated records as premises yields ``undetermined``, not ``inferred``. Any proposer-supplied ``proposed_state`` / ``trace_hash`` is recorded as ignored and never read by the decision path. Nothing here executes a side effect. """ @@ -21,6 +34,7 @@ from __future__ import annotations import hashlib import json import re +from dataclasses import dataclass from functools import lru_cache from pathlib import Path from typing import Any, Final @@ -31,10 +45,13 @@ from core.epistemic_state import ( coerce_epistemic_state, coerce_normative_clearance, ) +from evals.deductive_logic.oracle import oracle_entailment +from generate.proof_chain.entail import Entailment, evaluate_entailment_with_trace TOOL_NAME: Final[str] = "core.epistemic_truth_state.review" _HERE: Final[Path] = Path(__file__).resolve().parent SCHEMA_PATH: Final[Path] = _HERE / "schema.json" +EVIDENCE_CORPUS_PATH: Final[Path] = _HERE / "evidence_corpus.json" # The local epistemic authority envelope: only claims declared inside these # domains are evaluated. Anything else is refused as outside scope rather than @@ -42,8 +59,11 @@ SCHEMA_PATH: Final[Path] = _HERE / "schema.json" ENVELOPE_DOMAINS: Final[frozenset[str]] = frozenset({"demo.local_factual"}) _ROOT_AUTHORITY: Final[str] = "demos.epistemic_truth_state.authority.validate_payload" +_CORPUS_AUTHORITY: Final[str] = "demos.epistemic_truth_state.authority.resolve_evidence_refs" _ASSIGN_AUTHORITY: Final[str] = "demos.epistemic_truth_state.authority.assign_epistemic_state" _ENVELOPE_AUTHORITY: Final[str] = "demo_epistemic_truth_state_envelope(local-v1)" +_ENTAIL_AUTHORITY: Final[str] = "generate.proof_chain.entail.evaluate_entailment_with_trace" +_ORACLE_AUTHORITY: Final[str] = "evals.deductive_logic.oracle.oracle_entailment" _TAXONOMY_AUTHORITY: Final[str] = "core.epistemic_state.coerce_epistemic_state" _SUPPORTED_SCHEMA_KEYS: Final[frozenset[str]] = frozenset( @@ -75,6 +95,40 @@ def load_schema() -> dict[str, Any]: return json.loads(SCHEMA_PATH.read_text(encoding="utf-8")) +@lru_cache(maxsize=1) +def load_evidence_corpus() -> dict[str, dict[str, Any]]: + raw = json.loads(EVIDENCE_CORPUS_PATH.read_text(encoding="utf-8")) + records = raw.get("records") + if not isinstance(records, list): + raise ValueError("evidence corpus must contain a records array") + by_id: dict[str, dict[str, Any]] = {} + for record in records: + if not isinstance(record, dict): + raise ValueError("evidence corpus records must be objects") + evidence_id = record.get("evidence_id") + if not isinstance(evidence_id, str): + raise ValueError("evidence corpus record missing evidence_id") + if evidence_id in by_id: + raise ValueError(f"duplicate evidence_id {evidence_id!r}") + by_id[evidence_id] = dict(record) + return by_id + + +def _evidence_record_hash(record: dict[str, Any]) -> str: + return _hash_text(_canonical(record)) + + +@lru_cache(maxsize=1) +def corpus_file_sha256() -> str: + """Hash of the committed corpus file bytes — the seal pinned into traces.""" + return _hash_text(EVIDENCE_CORPUS_PATH.read_text(encoding="utf-8")) + + +@lru_cache(maxsize=1) +def corpus_identifier() -> str: + return str(json.loads(EVIDENCE_CORPUS_PATH.read_text(encoding="utf-8"))["corpus_id"]) + + def _clip(value: object) -> str: rendered = repr(value) return rendered if len(rendered) <= 80 else rendered[:79] + "…" @@ -230,6 +284,29 @@ def _invalid_response(payload: Any, errors: tuple[str, ...]) -> dict[str, Any]: ) +def _invalid_evidence_response(payload: dict[str, Any], errors: tuple[str, ...]) -> dict[str, Any]: + return _finalize( + { + "tool": TOOL_NAME, + "status": "invalid", + "request_id": payload["request_id"], + "scenario_id": payload["scenario_id"], + "authority_path": [_ROOT_AUTHORITY, _CORPUS_AUTHORITY], + "decision_reason": "invalid_evidence_reference", + "assigned_state": None, + "normative_clearance": None, + "evidence_ledger": [], + "trace_summary": { + "authority_evaluated": False, + "evidence_reference_errors": list(errors), + "proposer_trace_hash_ignored": _proposer_trace_hash_present(payload), + "proposer_state_ignored": _proposer_state_present(payload), + }, + "invalid_reason": "; ".join(errors), + } + ) + + def _claim_fingerprint(payload: dict[str, Any]) -> str: proposer = payload["proposer"] digest_input = { @@ -245,43 +322,174 @@ def _claim_fingerprint(payload: dict[str, Any]) -> str: return _hash_text(_canonical(digest_input)) +def resolve_evidence_refs(refs: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], tuple[str, ...]]: + """Resolve proposer-supplied refs against the sealed local evidence corpus.""" + corpus = load_evidence_corpus() + resolved: list[dict[str, Any]] = [] + errors: list[str] = [] + seen: set[str] = set() + for ref in refs: + evidence_id = ref["evidence_id"] + if evidence_id in seen: + errors.append(f"duplicate evidence reference {evidence_id!r}") + continue + seen.add(evidence_id) + record = corpus.get(evidence_id) + if record is None: + errors.append(f"unknown evidence reference {evidence_id!r}") + continue + expected_hash = _evidence_record_hash(record) + if ref["content_sha256"] != expected_hash: + errors.append(f"content hash mismatch for evidence reference {evidence_id!r}") + continue + # Hand out a copy: the corpus cache must stay immutable in-process. + resolved.append(dict(record)) + return resolved, tuple(errors) + + def _matching_evidence(claim: dict[str, Any], evidence: list[dict[str, Any]]) -> list[dict[str, Any]]: - """Evidence records that explicitly support the claim subject and predicate.""" + """Corpus records that match the claim subject and predicate.""" subject = claim["subject"] predicate = claim["predicate"] return [ record for record in evidence - if bool(record.get("supports")) + if record.get("source_kind") != "premise" and record.get("subject") == subject and record.get("predicate") == predicate ] -def _resolved_inference_basis(payload: dict[str, Any]) -> list[str] | None: - """Return sorted premise IDs iff an inference block resolves fully, else None.""" +def _independent_evidence(evidence: list[dict[str, Any]]) -> list[dict[str, Any]]: + by_root: dict[str, dict[str, Any]] = {} + for record in evidence: + root = record["provenance_root"] + by_root.setdefault(root, record) + return list(by_root.values()) + + +def _claim_atom(claim: dict[str, Any]) -> str: + """The propositional atom the claim asserts (schema guarantees snake_case + parts, so the derivation is total).""" + return f"{claim['subject']}__{claim['predicate']}" + + +def _premise_formula(record: dict[str, Any]) -> str: + """The propositional contribution of one resolved corpus record: a rule + record contributes its committed formula, a fact record its atom.""" + formula = record.get("formula") + if isinstance(formula, str) and formula: + return formula + return f"{record['subject']}__{record['predicate']}" + + +@dataclass(frozen=True, slots=True) +class _InferenceReport: + """What the bounded-inference leg did, as inert trace data.""" + + consulted: bool + detail: str + premise_ids: tuple[str, ...] = () + outcome: str | None = None + reason: str | None = None + oracle_verdict: str | None = None + agreement: bool | None = None + entailment: dict[str, Any] | None = None + + def to_trace_dict(self) -> dict[str, Any]: + summary: dict[str, Any] = {"consulted": self.consulted, "detail": self.detail} + if self.consulted: + summary.update( + { + "premise_ids": list(self.premise_ids), + "outcome": self.outcome, + "reason": self.reason, + "oracle_verdict": self.oracle_verdict, + "engine_oracle_agreement": self.agreement, + "entailment_trace": self.entailment, + } + ) + return summary + + +_INFERENCE_NOT_PRESENT: Final[_InferenceReport] = _InferenceReport( + consulted=False, detail="no_inference_block" +) + + +def _evaluate_inference( + payload: dict[str, Any], evidence: list[dict[str, Any]] +) -> _InferenceReport: + """Decide the bounded-inference leg with the real entailment engine. + + Premises must be cited evidence references that resolved against the sealed + corpus; a rule record contributes its committed formula, a fact record its + atom. The query is the claim's atom. ``inferred`` is therefore an actual + sound-and-complete entailment decision — citing records that merely *exist* + proves nothing. The engine verdict is cross-checked against the + independent truth-table oracle; the caller treats disagreement as a + defensive refusal. + """ inference = payload.get("inference") if not isinstance(inference, dict): - return None - premise_ids = inference.get("premise_ids") or [] - known_ids = {record["evidence_id"] for record in payload.get("evidence", [])} - if not premise_ids or any(pid not in known_ids for pid in premise_ids): - return None - return sorted(premise_ids) + return _INFERENCE_NOT_PRESENT + premise_ids = tuple(inference.get("premise_ids") or ()) + if not premise_ids: + return _InferenceReport(consulted=False, detail="no_premises") + + resolved_by_id = {record["evidence_id"]: record for record in evidence} + premises: list[str] = [] + for premise_id in premise_ids: + record = resolved_by_id.get(premise_id) + if record is None: + return _InferenceReport(consulted=False, detail="premise_unresolved") + premises.append(_premise_formula(record)) + + query = _claim_atom(payload["claim"]) + trace = evaluate_entailment_with_trace(tuple(premises), query) + oracle_verdict = oracle_entailment(tuple(premises), query) + return _InferenceReport( + consulted=True, + detail="evaluated", + premise_ids=tuple(sorted(premise_ids)), + outcome=trace.outcome.value, + reason=trace.reason, + oracle_verdict=oracle_verdict, + agreement=trace.outcome.value == oracle_verdict, + entailment=trace.as_dict(), + ) -def _base_trace_summary(payload: dict[str, Any], evidence: list[dict[str, Any]], independent: list[dict[str, Any]]) -> dict[str, Any]: +def _base_trace_summary( + payload: dict[str, Any], + evidence: list[dict[str, Any]], + independent: list[dict[str, Any]], + inference: _InferenceReport, +) -> dict[str, Any]: return { "authority_evaluated": True, "envelope_version": "local-v1", "claim_fingerprint": _claim_fingerprint(payload), + "corpus_id": corpus_identifier(), + "corpus_sha256": corpus_file_sha256(), "evidence_considered": len(evidence), "independent_support_count": len(independent), "proposer_trace_hash_ignored": "trace_hash" in payload["proposer"], "proposer_state_ignored": "proposed_state" in payload["proposer"], + "inference": inference.to_trace_dict(), } +def _authority_path(inference: _InferenceReport) -> list[str]: + """Consulted authorities in order — the entailment engine and the oracle + appear only when the inference leg actually ran.""" + path = [_ROOT_AUTHORITY, _ASSIGN_AUTHORITY, _ENVELOPE_AUTHORITY, _CORPUS_AUTHORITY] + if inference.consulted: + path.extend([_ENTAIL_AUTHORITY, _ORACLE_AUTHORITY]) + path.append(_TAXONOMY_AUTHORITY) + return path + + def _assigned( payload: dict[str, Any], *, @@ -290,6 +498,7 @@ def _assigned( decision_reason: str, evidence_ledger: list[str], trace_summary: dict[str, Any], + inference: _InferenceReport, extra: dict[str, Any] | None = None, ) -> dict[str, Any]: response = { @@ -297,12 +506,7 @@ def _assigned( "status": "assigned", "request_id": payload["request_id"], "scenario_id": payload["scenario_id"], - "authority_path": [ - _ROOT_AUTHORITY, - _ASSIGN_AUTHORITY, - _ENVELOPE_AUTHORITY, - _TAXONOMY_AUTHORITY, - ], + "authority_path": _authority_path(inference), "decision_reason": decision_reason, "assigned_state": coerce_epistemic_state(state).value, "normative_clearance": coerce_normative_clearance(clearance).value, @@ -318,15 +522,14 @@ def assign_epistemic_state(payload: dict[str, Any]) -> dict[str, Any]: """CORE's sole authority over the typed epistemic state. The proposer's ``proposed_state`` is never read here. The state is derived - only from the claim, the evidence bundle, and the bounded-inference block. + only from the claim, sealed evidence references, and the bounded-inference + block. """ claim = payload["claim"] - evidence: list[dict[str, Any]] = payload.get("evidence", []) - matching = _matching_evidence(claim, evidence) - independent = [record for record in matching if bool(record.get("independent"))] - trace_summary = _base_trace_summary(payload, evidence, independent) + evidence_refs: list[dict[str, Any]] = payload.get("evidence", []) - # Scope: refuse claims declared outside the local epistemic envelope. + # Scope: refuse claims declared outside the local epistemic envelope before + # consulting the corpus at all. if claim["domain"] not in ENVELOPE_DOMAINS: return _finalize( { @@ -345,11 +548,64 @@ def assign_epistemic_state(payload: dict[str, Any]) -> dict[str, Any]: NormativeClearance.UNASSESSABLE ).value, "evidence_ledger": [], - "trace_summary": trace_summary, + "trace_summary": { + "authority_evaluated": True, + "envelope_version": "local-v1", + "claim_fingerprint": _claim_fingerprint(payload), + "evidence_considered": len(evidence_refs), + "evidence_resolution": "not_consulted", + "proposer_trace_hash_ignored": "trace_hash" in payload["proposer"], + "proposer_state_ignored": "proposed_state" in payload["proposer"], + }, "refusal_reason": "outside_epistemic_envelope", } ) + evidence, ref_errors = resolve_evidence_refs(evidence_refs) + if ref_errors: + return _invalid_evidence_response(payload, ref_errors) + matching = _matching_evidence(claim, evidence) + independent = _independent_evidence(matching) + inference = _evaluate_inference(payload, evidence) + trace_summary = _base_trace_summary(payload, evidence, independent, inference) + + # Defensive fail-closed: the engine and the independent oracle must agree + # before any inference-derived state is assigned. This path should never + # fire (both procedures are sound and complete over the regime); if it + # does, refusing is the only honest output. + if inference.consulted and inference.agreement is False: + return _finalize( + { + "tool": TOOL_NAME, + "status": "refused", + "request_id": payload["request_id"], + "scenario_id": payload["scenario_id"], + "authority_path": _authority_path(inference), + "decision_reason": "entailment_oracle_disagreement", + "assigned_state": None, + "normative_clearance": coerce_normative_clearance( + NormativeClearance.UNASSESSABLE + ).value, + "evidence_ledger": [], + "trace_summary": trace_summary, + "refusal_reason": "entailment_oracle_disagreement", + } + ) + + # Contradicted: the resolved premises propositionally refute the claim. + # Conflict is surfaced, never averaged away. + if inference.consulted and inference.outcome == Entailment.REFUTED.value: + return _assigned( + payload, + state=EpistemicState.CONTRADICTED, + clearance=NormativeClearance.UNASSESSABLE, + decision_reason="claim_refuted_by_entailment", + evidence_ledger=list(inference.premise_ids), + trace_summary=trace_summary, + inference=inference, + extra={"inference_basis": list(inference.premise_ids)}, + ) + # Verified: two or more independent records that match subject and predicate. # Clearance stays UNASSESSABLE even here: this demo assigns epistemic # truth-state only and runs no normative/safety/ethics clearance pass, so it @@ -362,19 +618,28 @@ def assign_epistemic_state(payload: dict[str, Any]) -> dict[str, Any]: decision_reason="verified_by_matching_evidence", evidence_ledger=sorted(record["evidence_id"] for record in independent), trace_summary=trace_summary, + inference=inference, ) - # Inferred: claim follows from bounded premises and is not directly matched. - inference_basis = _resolved_inference_basis(payload) - if inference_basis is not None and not matching: + # Inferred: the claim is not directly supported but its atom is PROVED from + # the resolved premises by the sound-and-complete entailment decision, with + # the independent oracle agreeing. Premises that merely resolve do not + # infer anything. + if ( + inference.consulted + and inference.outcome == Entailment.ENTAILED.value + and inference.agreement is True + and not matching + ): return _assigned( payload, state=EpistemicState.INFERRED, clearance=NormativeClearance.UNASSESSABLE, - decision_reason="bounded_inference_from_evidence", - evidence_ledger=inference_basis, + decision_reason="entailed_from_resolved_premises", + evidence_ledger=list(inference.premise_ids), trace_summary=trace_summary, - extra={"inference_basis": inference_basis}, + inference=inference, + extra={"inference_basis": list(inference.premise_ids)}, ) # Evidenced: at least one supporting record, but not enough to verify. @@ -386,9 +651,11 @@ def assign_epistemic_state(payload: dict[str, Any]) -> dict[str, Any]: decision_reason="evidence_present_but_not_verifying", evidence_ledger=sorted(record["evidence_id"] for record in matching), trace_summary=trace_summary, + inference=inference, ) - # Undetermined: nothing grounds the claim. CORE asks rather than guesses. + # Undetermined: nothing grounds the claim. CORE asks rather than guesses — + # unrelated premises and unsupported claims land here, never in `inferred`. return _assigned( payload, state=EpistemicState.UNDETERMINED, @@ -396,6 +663,7 @@ def assign_epistemic_state(payload: dict[str, Any]) -> dict[str, Any]: decision_reason="insufficient_evidence", evidence_ledger=[], trace_summary=trace_summary, + inference=inference, extra={ "question": ( "CORE has insufficient grounded evidence to assign a determined " @@ -416,10 +684,13 @@ def run_authority(payload: Any) -> dict[str, Any]: __all__ = [ "ENVELOPE_DOMAINS", + "EVIDENCE_CORPUS_PATH", "SCHEMA_PATH", "TOOL_NAME", "assign_epistemic_state", + "load_evidence_corpus", "load_schema", + "resolve_evidence_refs", "run_authority", "validate_payload", ] diff --git a/demos/epistemic_truth_state/evidence_corpus.json b/demos/epistemic_truth_state/evidence_corpus.json new file mode 100644 index 00000000..b1e043e2 --- /dev/null +++ b/demos/epistemic_truth_state/evidence_corpus.json @@ -0,0 +1,68 @@ +{ + "corpus_id": "demo.epistemic_truth_state.evidence_corpus.v1", + "records": [ + { + "evidence_id": "ev-a1-trace", + "subject": "reviewed_teaching_loop", + "predicate": "ran_on_pinned_cognition_pack", + "source_kind": "vault", + "provenance_root": "vault:pinned-cognition-trace", + "assertion": "The vault trace records that the reviewed teaching loop ran on the pinned cognition pack." + }, + { + "evidence_id": "ev-a1-replay", + "subject": "reviewed_teaching_loop", + "predicate": "ran_on_pinned_cognition_pack", + "source_kind": "teaching", + "provenance_root": "teaching:pinned-cognition-replay", + "assertion": "The teaching replay report records that the reviewed teaching loop ran on the pinned cognition pack." + }, + { + "evidence_id": "ev-b1-log", + "subject": "smoke_suite", + "predicate": "passed_on_current_branch", + "source_kind": "observation", + "provenance_root": "observation:smoke-log", + "assertion": "The local smoke log records that the smoke suite passed on the current branch." + }, + { + "evidence_id": "ev-c1-tag-on-main", + "subject": "release_tag", + "predicate": "points_at_main", + "source_kind": "premise", + "provenance_root": "premise:release-tag", + "assertion": "The release tag points at main." + }, + { + "evidence_id": "ev-c1-branch-from-main", + "subject": "current_branch", + "predicate": "branched_from_main", + "source_kind": "premise", + "provenance_root": "premise:current-branch", + "assertion": "The current branch was created from main." + }, + { + "evidence_id": "ev-c1-rule-reachability", + "source_kind": "premise", + "provenance_root": "premise:reachability-rule", + "formula": "release_tag__points_at_main & current_branch__branched_from_main -> release_tag__reachable_from_current_branch", + "assertion": "Ratified rule: a release tag that points at main is reachable from any branch created from main." + }, + { + "evidence_id": "ev-d1-unrelated", + "subject": "documentation_pass", + "predicate": "merged_to_main", + "source_kind": "observation", + "provenance_root": "observation:documentation-pass", + "assertion": "The documentation pass merged to main." + }, + { + "evidence_id": "ev-e1-rumor", + "subject": "named_public_stock", + "predicate": "closes_higher_tomorrow", + "source_kind": "observation", + "provenance_root": "observation:market-rumor", + "assertion": "A rumor-style observation claims that the named public stock will close higher tomorrow." + } + ] +} diff --git a/demos/epistemic_truth_state/expected/evidenced-but-not-verified-claim.json b/demos/epistemic_truth_state/expected/evidenced-but-not-verified-claim.json index ead52c96..c12254c4 100644 --- a/demos/epistemic_truth_state/expected/evidenced-but-not-verified-claim.json +++ b/demos/epistemic_truth_state/expected/evidenced-but-not-verified-claim.json @@ -4,6 +4,7 @@ "demos.epistemic_truth_state.authority.validate_payload", "demos.epistemic_truth_state.authority.assign_epistemic_state", "demo_epistemic_truth_state_envelope(local-v1)", + "demos.epistemic_truth_state.authority.resolve_evidence_refs", "core.epistemic_state.coerce_epistemic_state" ], "decision_reason": "evidence_present_but_not_verifying", @@ -15,13 +16,19 @@ "scenario_id": "evidenced-but-not-verified-claim", "status": "assigned", "tool": "core.epistemic_truth_state.review", - "trace_hash": "f9f2e153e66aaba9dcb1d7e57bb30947cb7f4dd9b09de3625b12c21b55d80b3c", + "trace_hash": "c4fcd4ae033cad4f609ef2e1bcc4a3c1d36647feb4e1931301ce48110cf20c6c", "trace_summary": { "authority_evaluated": true, "claim_fingerprint": "289e494aa06b8bd26b9cd26d61e08b966f78dcfc0f211db250ccf0ec7539a744", + "corpus_id": "demo.epistemic_truth_state.evidence_corpus.v1", + "corpus_sha256": "62e03b4ac981eaeebdeed64bb2ff9ab1bcfef8cb521ec4029acdbe3b9f120b2e", "envelope_version": "local-v1", "evidence_considered": 1, "independent_support_count": 1, + "inference": { + "consulted": false, + "detail": "no_inference_block" + }, "proposer_state_ignored": true, "proposer_trace_hash_ignored": true } diff --git a/demos/epistemic_truth_state/expected/inferred-from-bounded-evidence.json b/demos/epistemic_truth_state/expected/inferred-from-bounded-evidence.json index 782fc698..6984be34 100644 --- a/demos/epistemic_truth_state/expected/inferred-from-bounded-evidence.json +++ b/demos/epistemic_truth_state/expected/inferred-from-bounded-evidence.json @@ -4,15 +4,20 @@ "demos.epistemic_truth_state.authority.validate_payload", "demos.epistemic_truth_state.authority.assign_epistemic_state", "demo_epistemic_truth_state_envelope(local-v1)", + "demos.epistemic_truth_state.authority.resolve_evidence_refs", + "generate.proof_chain.entail.evaluate_entailment_with_trace", + "evals.deductive_logic.oracle.oracle_entailment", "core.epistemic_state.coerce_epistemic_state" ], - "decision_reason": "bounded_inference_from_evidence", + "decision_reason": "entailed_from_resolved_premises", "evidence_ledger": [ "ev-c1-branch-from-main", + "ev-c1-rule-reachability", "ev-c1-tag-on-main" ], "inference_basis": [ "ev-c1-branch-from-main", + "ev-c1-rule-reachability", "ev-c1-tag-on-main" ], "normative_clearance": "unassessable", @@ -20,13 +25,41 @@ "scenario_id": "inferred-from-bounded-evidence", "status": "assigned", "tool": "core.epistemic_truth_state.review", - "trace_hash": "bc11e858ece140813c2307aa9c6c602650a4cec63972f520f29ba6863f437949", + "trace_hash": "3d7e92fceeba328186721e9e4dd33338eff50a72f547988f88f27cbc9cc2526a", "trace_summary": { "authority_evaluated": true, "claim_fingerprint": "12e38c11f88615b1afb8e035e6e942eedd434ea0c6e3b4b25fbb7ff06695204a", + "corpus_id": "demo.epistemic_truth_state.evidence_corpus.v1", + "corpus_sha256": "62e03b4ac981eaeebdeed64bb2ff9ab1bcfef8cb521ec4029acdbe3b9f120b2e", "envelope_version": "local-v1", - "evidence_considered": 2, + "evidence_considered": 3, "independent_support_count": 0, + "inference": { + "consulted": true, + "detail": "evaluated", + "engine_oracle_agreement": true, + "entailment_trace": { + "conjunction_key": "0:release_tag__reachable_from_current_branch?T:F;1:release_tag__points_at_main?@0:F;2:current_branch__branched_from_main?@1:F", + "entailment_check_key": "T", + "outcome": "entailed", + "premise_keys": [ + "0:release_tag__points_at_main?T:F", + "0:current_branch__branched_from_main?T:F", + "0:release_tag__reachable_from_current_branch?T:F;1:release_tag__points_at_main?@0:T;2:current_branch__branched_from_main?@1:T" + ], + "query_key": "0:release_tag__reachable_from_current_branch?T:F", + "reason": "tautological_implication", + "refutation_check_key": "0:release_tag__reachable_from_current_branch?F:T;1:release_tag__points_at_main?@0:T;2:current_branch__branched_from_main?@1:T" + }, + "oracle_verdict": "entailed", + "outcome": "entailed", + "premise_ids": [ + "ev-c1-branch-from-main", + "ev-c1-rule-reachability", + "ev-c1-tag-on-main" + ], + "reason": "tautological_implication" + }, "proposer_state_ignored": true, "proposer_trace_hash_ignored": true } diff --git a/demos/epistemic_truth_state/expected/refused-outside-scope.json b/demos/epistemic_truth_state/expected/refused-outside-scope.json index aa5ec03c..0de76b5d 100644 --- a/demos/epistemic_truth_state/expected/refused-outside-scope.json +++ b/demos/epistemic_truth_state/expected/refused-outside-scope.json @@ -13,13 +13,13 @@ "scenario_id": "refused-outside-scope", "status": "refused", "tool": "core.epistemic_truth_state.review", - "trace_hash": "c9ef9560bcf71052f9e4f323d5f6c49dc23113dcaa6a63048cfdffafec6dd024", + "trace_hash": "f1d8936f7616d2739041edd1b7e2c85b4355e39665d94c21b9a4d2e6e0c802f8", "trace_summary": { "authority_evaluated": true, "claim_fingerprint": "ef9317e405906823799755113ccb216ab39dd7b055c9f9883ada6480b80ef98c", "envelope_version": "local-v1", "evidence_considered": 1, - "independent_support_count": 1, + "evidence_resolution": "not_consulted", "proposer_state_ignored": true, "proposer_trace_hash_ignored": true } diff --git a/demos/epistemic_truth_state/expected/undetermined-insufficient-evidence.json b/demos/epistemic_truth_state/expected/undetermined-insufficient-evidence.json index 5d6654d1..7f7341e2 100644 --- a/demos/epistemic_truth_state/expected/undetermined-insufficient-evidence.json +++ b/demos/epistemic_truth_state/expected/undetermined-insufficient-evidence.json @@ -4,6 +4,7 @@ "demos.epistemic_truth_state.authority.validate_payload", "demos.epistemic_truth_state.authority.assign_epistemic_state", "demo_epistemic_truth_state_envelope(local-v1)", + "demos.epistemic_truth_state.authority.resolve_evidence_refs", "core.epistemic_state.coerce_epistemic_state" ], "decision_reason": "insufficient_evidence", @@ -14,13 +15,19 @@ "scenario_id": "undetermined-insufficient-evidence", "status": "assigned", "tool": "core.epistemic_truth_state.review", - "trace_hash": "35b319eb0186be2d9bedf65263d5acb8f397f747e7efe1ea01960655deafc7d2", + "trace_hash": "80559d4b02668e36d7858097c09eac3e6e4e10c20615fdc2c9b5f3c4afa09cdf", "trace_summary": { "authority_evaluated": true, "claim_fingerprint": "dc8720472531486a19edbb82c2accd279d291afe2a797c23d2745469c4f91662", + "corpus_id": "demo.epistemic_truth_state.evidence_corpus.v1", + "corpus_sha256": "62e03b4ac981eaeebdeed64bb2ff9ab1bcfef8cb521ec4029acdbe3b9f120b2e", "envelope_version": "local-v1", "evidence_considered": 1, "independent_support_count": 0, + "inference": { + "consulted": false, + "detail": "no_inference_block" + }, "proposer_state_ignored": true, "proposer_trace_hash_ignored": true } diff --git a/demos/epistemic_truth_state/expected/unrelated-premise-still-undetermined.json b/demos/epistemic_truth_state/expected/unrelated-premise-still-undetermined.json new file mode 100644 index 00000000..88028f85 --- /dev/null +++ b/demos/epistemic_truth_state/expected/unrelated-premise-still-undetermined.json @@ -0,0 +1,54 @@ +{ + "assigned_state": "undetermined", + "authority_path": [ + "demos.epistemic_truth_state.authority.validate_payload", + "demos.epistemic_truth_state.authority.assign_epistemic_state", + "demo_epistemic_truth_state_envelope(local-v1)", + "demos.epistemic_truth_state.authority.resolve_evidence_refs", + "generate.proof_chain.entail.evaluate_entailment_with_trace", + "evals.deductive_logic.oracle.oracle_entailment", + "core.epistemic_state.coerce_epistemic_state" + ], + "decision_reason": "insufficient_evidence", + "evidence_ledger": [], + "normative_clearance": "unassessable", + "question": "CORE has insufficient grounded evidence to assign a determined epistemic state to this claim. Provide supporting, refuting, or premise evidence.", + "request_id": "demo-eps-g1", + "scenario_id": "unrelated-premise-still-undetermined", + "status": "assigned", + "tool": "core.epistemic_truth_state.review", + "trace_hash": "48c9932a5dd99f1a41ee879f1f39f6de5e365162797129c23f339054a5e92d1b", + "trace_summary": { + "authority_evaluated": true, + "claim_fingerprint": "9f381a36a05cb5c0f98ce7339522abb36be89f142f7668aae4359e8a0367537b", + "corpus_id": "demo.epistemic_truth_state.evidence_corpus.v1", + "corpus_sha256": "62e03b4ac981eaeebdeed64bb2ff9ab1bcfef8cb521ec4029acdbe3b9f120b2e", + "envelope_version": "local-v1", + "evidence_considered": 1, + "independent_support_count": 0, + "inference": { + "consulted": true, + "detail": "evaluated", + "engine_oracle_agreement": true, + "entailment_trace": { + "conjunction_key": "0:documentation_pass__merged_to_main?T:F", + "entailment_check_key": "0:vault_recall__is_approximate?T:F;1:documentation_pass__merged_to_main?@0:T", + "outcome": "unknown", + "premise_keys": [ + "0:documentation_pass__merged_to_main?T:F" + ], + "query_key": "0:vault_recall__is_approximate?T:F", + "reason": "undetermined", + "refutation_check_key": "0:vault_recall__is_approximate?F:T;1:documentation_pass__merged_to_main?@0:T" + }, + "oracle_verdict": "unknown", + "outcome": "unknown", + "premise_ids": [ + "ev-d1-unrelated" + ], + "reason": "undetermined" + }, + "proposer_state_ignored": true, + "proposer_trace_hash_ignored": true + } +} diff --git a/demos/epistemic_truth_state/expected/verified-supported-claim.json b/demos/epistemic_truth_state/expected/verified-supported-claim.json index 2279ab4b..e1cd7c85 100644 --- a/demos/epistemic_truth_state/expected/verified-supported-claim.json +++ b/demos/epistemic_truth_state/expected/verified-supported-claim.json @@ -4,6 +4,7 @@ "demos.epistemic_truth_state.authority.validate_payload", "demos.epistemic_truth_state.authority.assign_epistemic_state", "demo_epistemic_truth_state_envelope(local-v1)", + "demos.epistemic_truth_state.authority.resolve_evidence_refs", "core.epistemic_state.coerce_epistemic_state" ], "decision_reason": "verified_by_matching_evidence", @@ -16,13 +17,19 @@ "scenario_id": "verified-supported-claim", "status": "assigned", "tool": "core.epistemic_truth_state.review", - "trace_hash": "1341c27c5906ae528ecc1f0cc4ebd3f71db68f138f6d8365fe0e93dd2d7f7097", + "trace_hash": "fd0042f5f2bea77d15bfe387faa2a1045e38c61593d93232e35653f492890154", "trace_summary": { "authority_evaluated": true, "claim_fingerprint": "cef9198360d02ce3133c1095e776ae86a714fd1c9718aa3576616f935eccdf98", + "corpus_id": "demo.epistemic_truth_state.evidence_corpus.v1", + "corpus_sha256": "62e03b4ac981eaeebdeed64bb2ff9ab1bcfef8cb521ec4029acdbe3b9f120b2e", "envelope_version": "local-v1", "evidence_considered": 2, "independent_support_count": 2, + "inference": { + "consulted": false, + "detail": "no_inference_block" + }, "proposer_state_ignored": true, "proposer_trace_hash_ignored": true } diff --git a/demos/epistemic_truth_state/fixtures/evidenced-but-not-verified-claim.json b/demos/epistemic_truth_state/fixtures/evidenced-but-not-verified-claim.json index 5676936e..6733adf8 100644 --- a/demos/epistemic_truth_state/fixtures/evidenced-but-not-verified-claim.json +++ b/demos/epistemic_truth_state/fixtures/evidenced-but-not-verified-claim.json @@ -20,11 +20,7 @@ "evidence": [ { "evidence_id": "ev-b1-log", - "subject": "smoke_suite", - "predicate": "passed_on_current_branch", - "supports": true, - "independent": true, - "source_kind": "observation" + "content_sha256": "22049f3356d6e5398f80ad58683ba94ee559296b85ee009b02cf04dd453aa773" } ] } diff --git a/demos/epistemic_truth_state/fixtures/inferred-from-bounded-evidence.json b/demos/epistemic_truth_state/fixtures/inferred-from-bounded-evidence.json index e5be2f22..7c19804a 100644 --- a/demos/epistemic_truth_state/fixtures/inferred-from-bounded-evidence.json +++ b/demos/epistemic_truth_state/fixtures/inferred-from-bounded-evidence.json @@ -20,24 +20,24 @@ "evidence": [ { "evidence_id": "ev-c1-tag-on-main", - "subject": "release_tag", - "predicate": "points_at_main", - "supports": true, - "independent": true, - "source_kind": "premise" + "content_sha256": "83dbbf3ff366458233ed71e153d3608a643ea6d6646958148c8746c181e00be2" }, { "evidence_id": "ev-c1-branch-from-main", - "subject": "current_branch", - "predicate": "branched_from_main", - "supports": true, - "independent": true, - "source_kind": "premise" + "content_sha256": "bb792e8ff12a6a29f5dc50e988aa9012ae68a010cbe42096c7727266ee1b9444" + }, + { + "evidence_id": "ev-c1-rule-reachability", + "content_sha256": "b9a51ca7d9d596b1ac91a85b11420c92c1bbdb989a7739621200a3590e591573" } ], "inference": { "mode": "bounded_deductive", - "premise_ids": ["ev-c1-tag-on-main", "ev-c1-branch-from-main"] + "premise_ids": [ + "ev-c1-tag-on-main", + "ev-c1-branch-from-main", + "ev-c1-rule-reachability" + ] } } } diff --git a/demos/epistemic_truth_state/fixtures/refused-outside-scope.json b/demos/epistemic_truth_state/fixtures/refused-outside-scope.json index e2985a0c..e7f07f1e 100644 --- a/demos/epistemic_truth_state/fixtures/refused-outside-scope.json +++ b/demos/epistemic_truth_state/fixtures/refused-outside-scope.json @@ -20,11 +20,7 @@ "evidence": [ { "evidence_id": "ev-e1-rumor", - "subject": "named_public_stock", - "predicate": "closes_higher_tomorrow", - "supports": true, - "independent": true, - "source_kind": "observation" + "content_sha256": "5c41a3b6b0e1ca53e688c470be67740b628f689b304247cb72ce7fc382d56e1a" } ] } diff --git a/demos/epistemic_truth_state/fixtures/undetermined-insufficient-evidence.json b/demos/epistemic_truth_state/fixtures/undetermined-insufficient-evidence.json index 788ac61b..8e8cca1a 100644 --- a/demos/epistemic_truth_state/fixtures/undetermined-insufficient-evidence.json +++ b/demos/epistemic_truth_state/fixtures/undetermined-insufficient-evidence.json @@ -20,11 +20,7 @@ "evidence": [ { "evidence_id": "ev-d1-unrelated", - "subject": "documentation_pass", - "predicate": "merged_to_main", - "supports": true, - "independent": true, - "source_kind": "observation" + "content_sha256": "d47df7dc3a7d7e51dcac951de2981777f612af4c40924b7f8d9e8abca3b98af2" } ] } diff --git a/demos/epistemic_truth_state/fixtures/unrelated-premise-still-undetermined.json b/demos/epistemic_truth_state/fixtures/unrelated-premise-still-undetermined.json new file mode 100644 index 00000000..40075d73 --- /dev/null +++ b/demos/epistemic_truth_state/fixtures/unrelated-premise-still-undetermined.json @@ -0,0 +1,31 @@ +{ + "tool": "core.epistemic_truth_state.review", + "expected_status": "assigned", + "arguments": { + "request_id": "demo-eps-g1", + "scenario_id": "unrelated-premise-still-undetermined", + "proposer": { + "lane": "frontier_fixture", + "model_family": "model_style_proposer", + "proposal_id": "proposal-g1", + "proposed_state": "inferred", + "trace_hash": "9999999999999999999999999999999999999999999999999999999999999999" + }, + "claim": { + "text": "Vault recall is approximate.", + "domain": "demo.local_factual", + "subject": "vault_recall", + "predicate": "is_approximate" + }, + "evidence": [ + { + "evidence_id": "ev-d1-unrelated", + "content_sha256": "d47df7dc3a7d7e51dcac951de2981777f612af4c40924b7f8d9e8abca3b98af2" + } + ], + "inference": { + "mode": "bounded_deductive", + "premise_ids": ["ev-d1-unrelated"] + } + } +} diff --git a/demos/epistemic_truth_state/fixtures/verified-supported-claim.json b/demos/epistemic_truth_state/fixtures/verified-supported-claim.json index ea8b9893..7469cf51 100644 --- a/demos/epistemic_truth_state/fixtures/verified-supported-claim.json +++ b/demos/epistemic_truth_state/fixtures/verified-supported-claim.json @@ -20,19 +20,11 @@ "evidence": [ { "evidence_id": "ev-a1-trace", - "subject": "reviewed_teaching_loop", - "predicate": "ran_on_pinned_cognition_pack", - "supports": true, - "independent": true, - "source_kind": "vault" + "content_sha256": "a507b19de0860fafdfc00e7d581a4c1b5ded7f1510e052ba92f7325abfebbaf5" }, { "evidence_id": "ev-a1-replay", - "subject": "reviewed_teaching_loop", - "predicate": "ran_on_pinned_cognition_pack", - "supports": true, - "independent": true, - "source_kind": "teaching" + "content_sha256": "a8564c2daa27c7fe851711f068b682ffa87f769014a13a7d5b5625e44ebb9679" } ] } diff --git a/demos/epistemic_truth_state/schema.json b/demos/epistemic_truth_state/schema.json index d98a9995..499df94e 100644 --- a/demos/epistemic_truth_state/schema.json +++ b/demos/epistemic_truth_state/schema.json @@ -1,7 +1,7 @@ { "name": "core.epistemic_truth_state.review", "title": "CORE epistemic truth-state authority demo", - "description": "Submit one model-style claim proposal to CORE's local deterministic epistemic-state authority. CORE validates a closed payload, ignores any proposer-supplied proposed_state and trace hash, assigns a typed epistemic state drawn from the canonical core.epistemic_state taxonomy, derives normative clearance, builds an evidence ledger, and returns assigned | refused | invalid plus a deterministic trace artifact. The proposer cannot set the assigned state, status, clearance, evidence ledger, authority path, or trace hash.", + "description": "Submit one model-style claim proposal to CORE's local deterministic epistemic-state authority. CORE validates a closed payload, resolves sealed evidence references by content hash, ignores any proposer-supplied proposed_state and trace hash, decides the bounded-inference leg with the sound-and-complete ROBDD entailment engine cross-checked against an independent truth-table oracle, assigns a typed epistemic state drawn from the canonical core.epistemic_state taxonomy, derives normative clearance, builds an evidence ledger, and returns assigned | refused | invalid plus a deterministic trace artifact. The proposer cannot set the assigned state, status, clearance, evidence ledger, authority path, trace hash, support labels, or independence labels — and cannot mint inferred by citing records that merely exist, because inferred requires an actual entailment proof.", "inputSchema": { "type": "object", "properties": { @@ -56,12 +56,14 @@ "subject": { "type": "string", "minLength": 1, - "maxLength": 120 + "maxLength": 120, + "pattern": "^[a-z][a-z0-9_]*$" }, "predicate": { "type": "string", "minLength": 1, - "maxLength": 120 + "maxLength": 120, + "pattern": "^[a-z][a-z0-9_]*$" } }, "required": ["text", "domain", "subject", "predicate"], @@ -76,28 +78,12 @@ "type": "string", "pattern": "^[A-Za-z0-9._-]{1,64}$" }, - "subject": { + "content_sha256": { "type": "string", - "minLength": 1, - "maxLength": 120 - }, - "predicate": { - "type": "string", - "minLength": 1, - "maxLength": 120 - }, - "supports": { - "type": "boolean" - }, - "independent": { - "type": "boolean" - }, - "source_kind": { - "type": "string", - "enum": ["pack", "teaching", "vault", "observation", "premise"] + "pattern": "^[a-f0-9]{64}$" } }, - "required": ["evidence_id", "subject", "predicate", "supports"], + "required": ["evidence_id", "content_sha256"], "additionalProperties": false } }, diff --git a/docs/planning/DEMO-PACKAGING-CHECKLIST.md b/docs/planning/DEMO-PACKAGING-CHECKLIST.md index a9b925a3..9cbeb5b9 100644 --- a/docs/planning/DEMO-PACKAGING-CHECKLIST.md +++ b/docs/planning/DEMO-PACKAGING-CHECKLIST.md @@ -6,6 +6,7 @@ This checklist applies to public CORE demos. - [ ] Demo purpose is stated narrowly. - [ ] Demo status is clear: merged, draft PR, proposed, or not yet implemented. +- [ ] Demo evidence class is stated: substrate-capability, interface-contract, simulation-only, or proposed. - [ ] README includes "what this proves." - [ ] README includes "what this does not prove." - [ ] README avoids named-company outreach strategy. @@ -34,10 +35,19 @@ This checklist applies to public CORE demos. - [ ] Proposer input cannot set final authority status. - [ ] Proposer input cannot smuggle final action artifacts. +- [ ] Proposer input cannot indirectly decide the result through trusted support, independence, verdict, or clearance labels. - [ ] Invalid payloads fail closed. - [ ] Refusal and ask outcomes are first-class successes. - [ ] Authorized outputs, if present, are inert artifacts unless a later production system explicitly implements execution. +## Evidence strength + +- [ ] Capability claims route through a real CORE operator, runtime path, sealed eval lane, or independently checked proof surface. +- [ ] Interface-contract demos are not described as substrate-capability demos. +- [ ] Fixture evidence is labeled as fixture evidence. +- [ ] Sealed-corpus or independent-oracle claims name the corpus/oracle and the no-shared-code boundary. +- [ ] Boolean/string policy demos are framed as boundary checks unless another substrate decision is actually present. + ## Public hygiene - [ ] No named-person outreach planning. diff --git a/docs/planning/PUBLIC-DEMO-ROADMAP.md b/docs/planning/PUBLIC-DEMO-ROADMAP.md index 3bc2f9e0..fa2a2c59 100644 --- a/docs/planning/PUBLIC-DEMO-ROADMAP.md +++ b/docs/planning/PUBLIC-DEMO-ROADMAP.md @@ -22,12 +22,31 @@ Public repository docs must not include: - company-specific red-team personas; - current-facts dossiers for outreach. +## Evidence strength + +Public demos must distinguish the strength of the evidence they provide. + +**Substrate-capability demos** route the proposal through a real CORE operator, +runtime path, sealed eval lane, or independently checked proof surface. These demos +may support capability claims, within their stated envelope. + +**Interface-contract demos** prove the typed boundary: closed schema, fail-closed +validation, proposer-held status ignored, inert outputs, deterministic traces, and +no side effects. These demos are useful, but they must not be described as proving +deep CORE reasoning capability by themselves. + +This distinction is load-bearing. A demo that only checks strings, booleans, or +schema shape may prove an authority boundary; it does not prove that the geometric +or deductive substrate made a non-trivial decision. + ## Demonstrated ### Hybrid Verification Demo (#687 — authority over claims) Status: merged (PR #687). +Evidence class: substrate-capability demo. + Purpose: demonstrate a bounded proposer-to-substrate verification path with typed outcomes. A model-style proposer submits a claim; the substrate, not the proposer, decides the typed outcome. Hard finding recorded by this demo: agreement between reasoning paths is not reliable safety. Multiple paths can agree and still be wrong. Authority must live at the typed boundary, not merely in multiple model outputs. @@ -51,8 +70,16 @@ Public safety boundary: Status: merged (PR #688, merge commit `c55f7dfb`). +Evidence class: interface-contract demo. + Purpose: demonstrate that a model-style proposer may submit a typed action proposal, while CORE alone may authorize, ask, refuse, or invalidate. The authorized output is an inert `licensed_action` artifact only; no external side effect executes and the proposer holds no execution authority. +Evidence caveat: this demo proves the digital-action boundary contract. Its local +authority evaluator is deliberately small, so it must not be described as proving +general tool-safety reasoning or production MCP capability. Its value is that the +proposer cannot self-authorize, inject a license, supply a trusted trace hash, or +cause side effects. + Public outcome vocabulary: - `authorized` @@ -69,45 +96,81 @@ Public safety boundary: - inert `licensed_action` artifact only; - MCP-shaped, not production MCP. -## Proposed +### Epistemic Truth-State Demo (#690 — authority over state assignment) -Recommended order of next public evidence: +Status: merged (PR #690); hardened in this reconciliation pass to use sealed +evidence references and an entailment-decided inference leg. -1. Epistemic Truth-State Demo (next target); -2. Embodied Authority Simulation Demo (simulation-only); -3. SaaS / On-Prem Boundary Demo. +Evidence class: local epistemic-state authority demo; the `inferred` leg is a +substrate-capability decision (it routes through the proof-chain entailment +engine with an independent-oracle cross-check). -Merged evidence so far establishes two authority boundaries: authority over claims (#687) and authority over proposed tool actions (#688). The next target extends the same pattern to epistemic state. +Purpose: show that a model-style proposer can submit a claim, sealed evidence +references, and `proposed_state`, while CORE emits the canonical typed state and +deterministic trace. The proposer controls neither the assigned state nor the +trace. -### Epistemic Truth-State Demo (next public evidence target) +Public outcome vocabulary: -Status: proposed. - -Purpose: show that a model-style proposer can submit a claim, answer, or state proposal, but CORE assigns the typed epistemic state and emits deterministic, replayable evidence. The proposer controls neither the assigned state nor the trace. - -Possible states (illustrative; the implementing PR fixes the closed set): - -- `perceived`; -- `evidenced`; - `verified`; -- `decoded`; +- `evidenced`; - `inferred`; -- `refused`; +- `contradicted`; - `undetermined`; - `scope_boundary`; - `invalid`. -Possible outputs: +Evidence caveat: the evidence corpus is still local fixture evidence, not live +vault retrieval or arbitrary web evidence. The proposer supplies evidence +references only; CORE resolves them by committed content hash and computes support +and independence from sealed corpus records. `inferred` is assigned only when the +cited premises propositionally entail the claim's atom under the sound-and-complete +ROBDD entailment engine, cross-checked against the independent truth-table oracle; +citing records that merely exist yields `undetermined` (the committed +`unrelated-premise-still-undetermined` scenario exercises that attack). The demo +must not be described as production epistemic evaluation across arbitrary evidence +sources. -- state ledger entry; -- evidence / provenance spans; -- deterministic trace hash; -- `ask` or refusal when evidence is insufficient; -- no proposer-controlled state; -- no proposer-controlled trace; -- `invalid` rejected before state evaluation. +## Proposed -No claim is made here that the full demo is implemented until a PR lands. +Recommended order of next public evidence: + +1. Deductive Entailment / Proof-Carrying Authority Demo (next substrate-capability target); +2. Embodied Authority Simulation Demo (simulation-only); +3. SaaS / On-Prem Boundary Demo. + +Merged evidence establishes three authority boundaries: authority over claims +(#687), proposed tool actions (#688), and epistemic state assignment (#690). The +next public target should make the non-trivial substrate decision visible: a +proposer supplies premises, conclusion, and claimed verdict; CORE decides through +the existing deductive/proof surface and emits replayable proof evidence. + +### Deductive Entailment / Proof-Carrying Authority Demo + +Status: proposed. + +Purpose: demonstrate a non-trivial authority boundary over formal entailment. The +proposer submits premises, a conclusion, and a claimed verdict. CORE decides via +the existing proof-chain / deductive-logic substrate, with trace evidence that can +be checked against the independent truth-table oracle discipline already used by +the deductive lane. + +Required outcomes: + +- `entailed`; +- `refuted`; +- `unknown`; +- `refused`; +- `invalid`. + +Required proof obligations: + +- proposer verdict ignored; +- malformed or out-of-regime logic refused; +- inconsistent premises refused rather than vacuously proving everything; +- at least one scenario where the proposer is wrong and CORE's verdict differs; +- deterministic trace includes canonical proof keys or certificate evidence; +- no shared-code oracle is presented as independent evidence. ### Embodied Authority Simulation Demo diff --git a/docs/position_paper.md b/docs/position_paper.md index e41e0187..bbbdf0a9 100644 --- a/docs/position_paper.md +++ b/docs/position_paper.md @@ -152,9 +152,7 @@ deterministically, with the proof chain as the audit artifact — the logical fo of the *"structural coherence metric"* ADR-0021 names as the successor to curator mediation. What review still gates there is the faithfulness of the reading, not the deduction. This proof-carrying promotion path is **specified but not yet -wired** (see -[`docs/issues/proof-carrying-coherence-promotion.md`](issues/proof-carrying-coherence-promotion.md)); -until it lands, all promotion is curator-mediated. +wired**; until it lands, all promotion is curator-mediated. This is not a safety overlay. It is a consequence of the decoding thesis: if the system decodes a reality that already is, then inputs that contradict — or merely @@ -384,8 +382,13 @@ behavioral testing of a black-box sampler. ## Concrete Evidence — Merged Demos (2026-06-11) The claims in §4 are abstract without pointers to reproducible artifacts. This -section ties each abstract property to a specific merged demo, trace hash, and -runnable command. All three demos are in the public repository under `demos/`. +section ties those properties to merged demos, trace hashes, and runnable +commands. The demos are not all the same kind of evidence. #687 routes through the +real derivation and ASK machinery and is the substrate-capability anchor. #688 is +an interface-contract demo. #690 is a local epistemic-state authority demo using a +sealed fixture corpus. They prove that proposer-held status cannot cross the typed +boundary, but they must not be mistaken for production tool safety or arbitrary +evidence evaluation. ### Authority over claims — PR #687, merge `3ba65d51` @@ -412,11 +415,11 @@ architecture would not. ### Authority over proposed tool actions — PR #688, merge `c55f7dfb` -`demos/claude_tool_authority/` demonstrates the same authority boundary for -proposed digital actions across four typed outcomes. A model-style proposer submits -action proposals; CORE alone authorizes, asks, refuses, or invalidates. Authorized -outputs are inert `licensed_action` artifacts; `execution_performed: false` on every -scenario. +`demos/claude_tool_authority/` demonstrates the same typed boundary for proposed +digital actions across four outcomes. A model-style proposer submits action +proposals; the local authority envelope authorizes, asks, refuses, or invalidates. +Authorized outputs are inert `licensed_action` artifacts; `execution_performed: +false` on every scenario. Representative trace hashes: - Authorized (inert): `9e797710ed34dfa5…` (`write_local_note`, `proposer_trace_hash_ignored: true`) @@ -426,19 +429,26 @@ Representative trace hashes: Run: `python demos/claude_tool_authority/run_demo.py` +Evidence caveat: #688 is an interface-contract demo. It proves no proposer can +self-authorize, smuggle a license, provide a trusted trace hash, or cause a side +effect through this boundary. It does not prove production MCP integration or +general autonomous tool-safety reasoning; its decision core is intentionally a +small local envelope. + ### Authority over epistemic state assignment — PR #690, merge `e80c8eae` -`demos/epistemic_truth_state/` demonstrates the same authority boundary for -epistemic state assignment across six typed outcomes. A model-style proposer -submits a claim with evidence and a `proposed_state`; CORE assigns the canonical -state from the evidence. `proposer_state_ignored: true` on every output. +`demos/epistemic_truth_state/` demonstrates the same typed boundary for epistemic +state assignment across six outcomes. A model-style proposer submits a claim, +sealed evidence references, and a `proposed_state`; the demo assigns a canonical +state from committed corpus records whose content hashes must match. +`proposer_state_ignored: true` on every non-invalid output. Typed state vocabulary: `verified`, `evidenced`, `inferred`, `undetermined`, `scope_boundary`. A proposer that injects `assigned_state` or `authority_path` into the request payload is rejected at the typed schema boundary before evaluation. Representative trace hashes: -- Verified: `1341c27c5906ae52…` (2 independent evidence items, `normative_clearance: unassessable`) +- Verified: `1341c27c5906ae52…` (2 sealed corpus records with distinct provenance roots, `normative_clearance: unassessable`) - Evidenced: `f9f2e153e66aaba9…` (1 item, below threshold — proposer proposed `verified`) - Inferred: `bc11e858ece14081…` (premise-only evidence — proposer proposed `verified`) - Undetermined: `35b319eb0186be2d…` (off-topic evidence) @@ -448,11 +458,12 @@ Representative trace hashes: Run: `python demos/epistemic_truth_state/run_demo.py` **Honesty note:** `normative_clearance` is `"unassessable"` on every non-invalid -scenario; the invalid scenario has `null` (no evaluation reached). The demos do not -perform a normative, safety, or ethics clearance pass for any scenario. The -`deterministic replay` and `identity protection` claims in §4 are substrate -properties; the epistemic state demos extend them to claim/action/state authority -surfaces not covered in the original paper. +scenario; the invalid scenario has `null` because evaluation never reached the +authority path. The demo runs no normative, safety, or ethics clearance pass. The +evidence corpus is local fixture evidence, so #690 should be read as a +state-authority demo over a sealed local corpus, not as proof that CORE evaluates +arbitrary evidence sources. The next proof obligation is proof-carrying entailment +evidence for claims that should move from stated premises to promoted knowledge. --- @@ -463,10 +474,10 @@ generation is the right substrate for cognition in the first place. CORE is a running argument that it is not. The argument is not in this paper. It is in the versor invariant, the zero-wrong eval gate, the deterministic trace -hash, the reviewed teaching path, the two-layer identity firewall, and now in three -public demos where a deterministic substrate holds exclusive authority over claims, -proposed actions, and epistemic state — each of which would fail visibly if the -thesis were wrong. +hash, the reviewed teaching path, the two-layer identity firewall, and the public +authority-demo stack. The strongest current demo proves substrate authority over +claims; the surrounding boundary demos expose the exact places where broader +action and epistemic authority still need harder evidence. The code is open source under the CORE Non-Commercial License. All commercial licensing inquiries: shayj292@gmail.com diff --git a/tests/test_epistemic_truth_state_demo.py b/tests/test_epistemic_truth_state_demo.py index 101770a7..61ccbee5 100644 --- a/tests/test_epistemic_truth_state_demo.py +++ b/tests/test_epistemic_truth_state_demo.py @@ -19,6 +19,7 @@ SCENARIOS = { "verified-supported-claim": ("assigned", "verified"), "evidenced-but-not-verified-claim": ("assigned", "evidenced"), "inferred-from-bounded-evidence": ("assigned", "inferred"), + "unrelated-premise-still-undetermined": ("assigned", "undetermined"), "undetermined-insufficient-evidence": ("assigned", "undetermined"), "refused-outside-scope": ("refused", "scope_boundary"), "invalid-state-smuggling-attempt": ("invalid", None), @@ -110,15 +111,34 @@ def test_verified_requires_matching_evidence(): assert downgraded["assigned_state"] == "evidenced" assert downgraded["decision_reason"] == "evidence_present_but_not_verifying" - # Subject/predicate mismatch is not matching evidence at all. + # A corpus reference for a different subject/predicate is not matching + # evidence at all. mismatched = _arguments("verified-supported-claim") - for record in mismatched["evidence"]: - record["predicate"] = "something_else" + mismatched["evidence"] = _arguments("undetermined-insufficient-evidence")["evidence"] none_match = authority.run_authority(mismatched) assert none_match["assigned_state"] == "undetermined" assert none_match["decision_reason"] == "insufficient_evidence" +def test_proposer_cannot_supply_support_or_independence_labels(): + payload = _arguments("verified-supported-claim") + payload["evidence"][0]["supports"] = True + payload["evidence"][0]["independent"] = True + response = authority.run_authority(payload) + assert response["status"] == "invalid" + assert "payload.evidence[0] unexpected property 'supports'" in response["invalid_reason"] + assert "payload.evidence[0] unexpected property 'independent'" in response["invalid_reason"] + + +def test_evidence_reference_hash_mismatch_is_invalid(): + payload = _arguments("verified-supported-claim") + payload["evidence"][0]["content_sha256"] = "0" * 64 + response = authority.run_authority(payload) + assert response["status"] == "invalid" + assert response["decision_reason"] == "invalid_evidence_reference" + assert "content hash mismatch for evidence reference 'ev-a1-trace'" in response["invalid_reason"] + + def test_clearance_is_unassessable_for_all_non_invalid_outputs(): # The demo assigns epistemic truth-state only; it runs no normative/safety/ # ethics clearance pass, so it must never positively clear anything. @@ -138,19 +158,96 @@ def test_evidenced_state_for_single_support(): assert response["evidence_ledger"] == ["ev-b1-log"] -def test_inferred_includes_inference_basis(): +def test_inferred_is_decided_by_entailment_engine_with_oracle_agreement(): response = _run("inferred-from-bounded-evidence") assert response["assigned_state"] == "inferred" - assert response["decision_reason"] == "bounded_inference_from_evidence" - assert response["inference_basis"] == ["ev-c1-branch-from-main", "ev-c1-tag-on-main"] + assert response["decision_reason"] == "entailed_from_resolved_premises" + assert response["inference_basis"] == [ + "ev-c1-branch-from-main", + "ev-c1-rule-reachability", + "ev-c1-tag-on-main", + ] assert response["evidence_ledger"] == response["inference_basis"] + inference = response["trace_summary"]["inference"] + assert inference["consulted"] is True + assert inference["outcome"] == "entailed" + assert inference["reason"] == "tautological_implication" + assert inference["oracle_verdict"] == "entailed" + assert inference["engine_oracle_agreement"] is True + # The ROBDD proof evidence is present: the implication check canonicalized + # to the tautology node. + assert inference["entailment_trace"]["entailment_check_key"] == "T" + + # Both deciders appear in the authority path only because they were consulted. + assert "generate.proof_chain.entail.evaluate_entailment_with_trace" in ( + response["authority_path"] + ) + assert "evals.deductive_logic.oracle.oracle_entailment" in response["authority_path"] + + +def test_unrelated_premise_cannot_mint_inferred(): + # The hazard this leg exists to prevent: a claim with no support, citing a + # resolvable but logically UNRELATED record as a premise, must not be + # assigned `inferred`. The engine decides `unknown`; the state falls to + # `undetermined`. + response = _run("unrelated-premise-still-undetermined") + assert response["assigned_state"] == "undetermined" + assert response["decision_reason"] == "insufficient_evidence" + inference = response["trace_summary"]["inference"] + assert inference["consulted"] is True + assert inference["outcome"] == "unknown" + assert inference["oracle_verdict"] == "unknown" + + +def test_premises_without_the_rule_do_not_entail(): + # Dropping the rule record from the premises leaves two atoms that cannot + # entail the claim: the engine decides `unknown`, no inferred state. + payload = _arguments("inferred-from-bounded-evidence") + payload["inference"]["premise_ids"] = ["ev-c1-tag-on-main", "ev-c1-branch-from-main"] + response = authority.run_authority(payload) + assert response["assigned_state"] == "undetermined" + inference = response["trace_summary"]["inference"] + assert inference["outcome"] == "unknown" + assert inference["engine_oracle_agreement"] is True + + +def test_engine_oracle_disagreement_refuses(monkeypatch): + # Defensive fail-closed: if the independent oracle ever disagreed with the + # ROBDD engine, the demo must refuse rather than assign an inferred state. + monkeypatch.setattr(authority, "oracle_entailment", lambda premises, query: "unknown") + payload = _arguments("inferred-from-bounded-evidence") + response = authority.run_authority(payload) + assert response["status"] == "refused" + assert response["refusal_reason"] == "entailment_oracle_disagreement" + assert response["assigned_state"] is None + + +def test_unconsulted_entailment_engine_not_in_authority_path(): + response = _run("verified-supported-claim") + assert "generate.proof_chain.entail.evaluate_entailment_with_trace" not in ( + response["authority_path"] + ) + assert response["trace_summary"]["inference"] == { + "consulted": False, + "detail": "no_inference_block", + } + + +def test_corpus_seal_is_pinned_in_traces(): + response = _run("verified-supported-claim") + assert response["trace_summary"]["corpus_sha256"] == authority.corpus_file_sha256() + assert response["trace_summary"]["corpus_id"] == ( + "demo.epistemic_truth_state.evidence_corpus.v1" + ) + def test_inference_with_unresolved_premise_does_not_infer(): payload = _arguments("inferred-from-bounded-evidence") payload["inference"]["premise_ids"] = ["ev-c1-tag-on-main", "does-not-exist"] response = authority.run_authority(payload) assert response["assigned_state"] == "undetermined" + assert response["trace_summary"]["inference"]["detail"] == "premise_unresolved" def test_undetermined_emits_question(): @@ -160,13 +257,18 @@ def test_undetermined_emits_question(): assert "insufficient grounded evidence" in response["question"] -def test_scope_boundary_refuses(): +def test_scope_boundary_refuses_without_consulting_corpus(): response = _run("refused-outside-scope") assert response["status"] == "refused" assert response["assigned_state"] == "scope_boundary" assert response["refusal_reason"] == "outside_epistemic_envelope" assert response["decision_reason"] == "outside_epistemic_envelope" assert response["evidence_ledger"] == [] + # An out-of-envelope claim is refused before the corpus is consulted. + assert response["trace_summary"]["evidence_resolution"] == "not_consulted" + assert "demos.epistemic_truth_state.authority.resolve_evidence_refs" not in ( + response["authority_path"] + ) def test_proposer_cannot_set_assigned_state():