* fix(epistemic): make empty resonance evidence undetermined * fix(evals): classify verified realizer failures separately * fix(packs): treat absent domain manifests as valid noop * test(packs): cover missing manifests and scope boundary domains * test(epistemic): cover phase 2 known bug fixes * fix(vault): make FALSIFIED exclusion explicit in _status_admits FALSIFIED entries previously fell through to the ADMISSIBLE_AS_EVIDENCE set-check, which excluded them correctly but left the distinction between CONTRADICTED (FALSIFIED) and UNVERIFIED-POSSIBLE (SPECULATIVE) implicit. Add an early guard so FALSIFIED is explicitly rejected before the tier filter, matching the CONTRADICTED semantics from the epistemic taxonomy.
217 lines
7.5 KiB
Python
217 lines
7.5 KiB
Python
"""ADR-0091 domain-pack contract validation.
|
|
|
|
This module is intentionally read-only. It validates optional manifest
|
|
metadata that links a language pack to capability-ledger evidence, but it
|
|
does not mutate packs and it does not promote capability status.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
_ALLOWED_SPLITS = {"dev", "public", "holdout"}
|
|
_KNOWN_DOMAIN_IDS = {
|
|
"systems_software",
|
|
"mathematics_logic",
|
|
"physics",
|
|
"hebrew_greek_textual_reasoning",
|
|
"philosophy_theology",
|
|
}
|
|
_SCOPE_BOUNDARY_PREFIX = "scope_boundary"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class DomainEvalLane:
|
|
lane: str
|
|
version: str
|
|
splits: tuple[str, ...]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class DomainPackContract:
|
|
domain_contract_version: int
|
|
domain_id: str
|
|
axioms: str | None
|
|
rules: str | None
|
|
teaching_chains: tuple[str, ...] = field(default_factory=tuple)
|
|
eval_lanes: tuple[DomainEvalLane, ...] = field(default_factory=tuple)
|
|
reviewers: tuple[str, ...] = field(default_factory=tuple)
|
|
known_gaps: tuple[str, ...] = field(default_factory=tuple)
|
|
provenance: str = ""
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class DomainContractValidation:
|
|
pack_id: str
|
|
present: bool
|
|
valid: bool
|
|
errors: tuple[str, ...] = field(default_factory=tuple)
|
|
contract: DomainPackContract | None = None
|
|
|
|
def as_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"pack_id": self.pack_id,
|
|
"present": self.present,
|
|
"valid": self.valid,
|
|
"errors": list(self.errors),
|
|
"contract": _contract_as_dict(self.contract) if self.contract else None,
|
|
}
|
|
|
|
|
|
def _contract_as_dict(contract: DomainPackContract | None) -> dict[str, Any] | None:
|
|
if contract is None:
|
|
return None
|
|
return {
|
|
"domain_contract_version": contract.domain_contract_version,
|
|
"domain_id": contract.domain_id,
|
|
"axioms": contract.axioms,
|
|
"rules": contract.rules,
|
|
"teaching_chains": list(contract.teaching_chains),
|
|
"eval_lanes": [
|
|
{"lane": lane.lane, "version": lane.version, "splits": list(lane.splits)}
|
|
for lane in contract.eval_lanes
|
|
],
|
|
"reviewers": list(contract.reviewers),
|
|
"known_gaps": list(contract.known_gaps),
|
|
"provenance": contract.provenance,
|
|
}
|
|
|
|
|
|
def _as_string_tuple(value: object, *, field_name: str, errors: list[str]) -> tuple[str, ...]:
|
|
if value is None:
|
|
return ()
|
|
if not isinstance(value, list):
|
|
errors.append(f"{field_name}:must_be_list")
|
|
return ()
|
|
out: list[str] = []
|
|
for idx, item in enumerate(value):
|
|
if not isinstance(item, str) or not item.strip():
|
|
errors.append(f"{field_name}[{idx}]:must_be_nonempty_string")
|
|
continue
|
|
out.append(item.strip())
|
|
return tuple(out)
|
|
|
|
|
|
def _optional_path(value: object, *, field_name: str, errors: list[str]) -> str | None:
|
|
if value is None:
|
|
return None
|
|
if not isinstance(value, str) or not value.strip():
|
|
errors.append(f"{field_name}:must_be_null_or_nonempty_string")
|
|
return None
|
|
if Path(value).is_absolute() or ".." in Path(value).parts:
|
|
errors.append(f"{field_name}:unsafe_path")
|
|
return None
|
|
return value.strip()
|
|
|
|
|
|
def parse_domain_contract(manifest: dict[str, Any], *, pack_id: str) -> DomainContractValidation:
|
|
if "domain_contract_version" not in manifest:
|
|
return DomainContractValidation(pack_id=pack_id, present=False, valid=True)
|
|
|
|
errors: list[str] = []
|
|
version_raw = manifest.get("domain_contract_version")
|
|
if version_raw != 1:
|
|
errors.append("domain_contract_version:unsupported")
|
|
version = 0
|
|
else:
|
|
version = 1
|
|
|
|
domain_id = manifest.get("domain_id")
|
|
if not isinstance(domain_id, str) or not domain_id.strip():
|
|
errors.append("domain_id:required")
|
|
domain_id_s = ""
|
|
else:
|
|
domain_id_s = domain_id.strip()
|
|
if domain_id_s not in _KNOWN_DOMAIN_IDS:
|
|
errors.append(f"{_SCOPE_BOUNDARY_PREFIX}:domain_id:unknown")
|
|
|
|
axioms = _optional_path(manifest.get("axioms"), field_name="axioms", errors=errors)
|
|
rules = _optional_path(manifest.get("rules"), field_name="rules", errors=errors)
|
|
teaching_chains = _as_string_tuple(
|
|
manifest.get("teaching_chains"), field_name="teaching_chains", errors=errors
|
|
)
|
|
reviewers = _as_string_tuple(manifest.get("reviewers"), field_name="reviewers", errors=errors)
|
|
known_gaps = _as_string_tuple(manifest.get("known_gaps"), field_name="known_gaps", errors=errors)
|
|
|
|
eval_lanes: list[DomainEvalLane] = []
|
|
raw_lanes = manifest.get("eval_lanes")
|
|
if raw_lanes is None:
|
|
raw_lanes = []
|
|
if not isinstance(raw_lanes, list):
|
|
errors.append("eval_lanes:must_be_list")
|
|
else:
|
|
for idx, raw in enumerate(raw_lanes):
|
|
if not isinstance(raw, dict):
|
|
errors.append(f"eval_lanes[{idx}]:must_be_object")
|
|
continue
|
|
lane = raw.get("lane")
|
|
version_s = raw.get("version")
|
|
splits_raw = raw.get("splits")
|
|
if not isinstance(lane, str) or not lane.strip():
|
|
errors.append(f"eval_lanes[{idx}].lane:required")
|
|
continue
|
|
if not isinstance(version_s, str) or not version_s.strip():
|
|
errors.append(f"eval_lanes[{idx}].version:required")
|
|
continue
|
|
splits = _as_string_tuple(splits_raw, field_name=f"eval_lanes[{idx}].splits", errors=errors)
|
|
unknown = sorted(set(splits) - _ALLOWED_SPLITS)
|
|
if unknown:
|
|
errors.append(f"eval_lanes[{idx}].splits:unknown:{','.join(unknown)}")
|
|
eval_lanes.append(DomainEvalLane(lane=lane.strip(), version=version_s.strip(), splits=splits))
|
|
|
|
provenance = manifest.get("provenance")
|
|
if not isinstance(provenance, str) or not provenance.strip():
|
|
errors.append("provenance:required")
|
|
provenance_s = ""
|
|
else:
|
|
provenance_s = provenance.strip()
|
|
|
|
contract = DomainPackContract(
|
|
domain_contract_version=version,
|
|
domain_id=domain_id_s,
|
|
axioms=axioms,
|
|
rules=rules,
|
|
teaching_chains=teaching_chains,
|
|
eval_lanes=tuple(eval_lanes),
|
|
reviewers=reviewers,
|
|
known_gaps=known_gaps,
|
|
provenance=provenance_s,
|
|
)
|
|
return DomainContractValidation(
|
|
pack_id=pack_id,
|
|
present=True,
|
|
valid=not errors,
|
|
errors=tuple(errors),
|
|
contract=contract,
|
|
)
|
|
|
|
|
|
def validate_domain_contract_pack(pack_id: str, *, data_root: Path | None = None) -> DomainContractValidation:
|
|
root = data_root or Path(__file__).resolve().parent / "data"
|
|
manifest_path = root / pack_id / "manifest.json"
|
|
if not manifest_path.exists():
|
|
return DomainContractValidation(
|
|
pack_id=pack_id,
|
|
present=False,
|
|
valid=True,
|
|
)
|
|
try:
|
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
except json.JSONDecodeError:
|
|
return DomainContractValidation(
|
|
pack_id=pack_id,
|
|
present=False,
|
|
valid=False,
|
|
errors=("manifest:invalid_json",),
|
|
)
|
|
if not isinstance(manifest, dict):
|
|
return DomainContractValidation(
|
|
pack_id=pack_id,
|
|
present=False,
|
|
valid=False,
|
|
errors=("manifest:must_be_object",),
|
|
)
|
|
return parse_domain_contract(manifest, pack_id=pack_id)
|