257 lines
9.1 KiB
Python
257 lines
9.1 KiB
Python
"""Kernel fact and provenance primitives for the substrate layer.
|
|
|
|
Tranche 1 — broad base-layer foundations.
|
|
|
|
Immutable, typed records that form the canonical substrate fact model.
|
|
All records are frozen dataclasses per the codebase immutability convention.
|
|
|
|
Provenance rules enforced at construction time:
|
|
- ``problem_text`` facts require exact source spans
|
|
- ``derived`` facts require input_fact_ids
|
|
- Pack/world facts must not masquerade as problem text
|
|
- ``speculative`` facts cannot be consumed by serving
|
|
|
|
These primitives are domain-agnostic — they are not tied to GSM8K or
|
|
any specific benchmark. They provide the shared vocabulary that future
|
|
organs and the ProblemFrame IR will consume.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from fractions import Fraction
|
|
from typing import Union
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Provenance kinds — closed set per Tranche 1 brief.
|
|
# ---------------------------------------------------------------------------
|
|
PROVENANCE_KINDS: frozenset[str] = frozenset({
|
|
"problem_text",
|
|
"derived",
|
|
"kernel_unit",
|
|
"kernel_calendar",
|
|
"kernel_math",
|
|
"kernel_world_fact",
|
|
"reviewed_pack",
|
|
"speculative",
|
|
})
|
|
|
|
# Provenance kinds that require exact source spans.
|
|
_SPAN_REQUIRED_KINDS: frozenset[str] = frozenset({"problem_text"})
|
|
|
|
# Provenance kinds that require input_fact_ids.
|
|
_INPUT_REQUIRED_KINDS: frozenset[str] = frozenset({"derived"})
|
|
|
|
# Provenance kinds that are NOT allowed to carry source spans
|
|
# (they must not masquerade as problem text).
|
|
_SPAN_FORBIDDEN_KINDS: frozenset[str] = frozenset({
|
|
"kernel_unit",
|
|
"kernel_calendar",
|
|
"kernel_math",
|
|
"kernel_world_fact",
|
|
"reviewed_pack",
|
|
})
|
|
|
|
# Fact types for SubstrateFact.
|
|
FACT_TYPES: frozenset[str] = frozenset({
|
|
"grounded_scalar",
|
|
"grounded_unit",
|
|
"candidate_relation",
|
|
})
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Source span — exact character range in problem text.
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass(frozen=True, slots=True)
|
|
class SourceSpan:
|
|
"""Exact character range in the original problem text.
|
|
|
|
``start`` and ``end`` are zero-indexed character offsets (inclusive start,
|
|
exclusive end — matching Python slice semantics). ``sentence_index`` is
|
|
an optional zero-indexed sentence ordinal within the problem.
|
|
"""
|
|
text: str
|
|
start: int
|
|
end: int
|
|
sentence_index: int | None = None
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.start < 0:
|
|
raise ValueError(f"SourceSpan.start must be >= 0, got {self.start}")
|
|
if self.end < self.start:
|
|
raise ValueError(
|
|
f"SourceSpan.end ({self.end}) must be >= start ({self.start})"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Kernel provenance — tracks origin of every substrate fact.
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass(frozen=True, slots=True)
|
|
class KernelProvenance:
|
|
"""Provenance record for a substrate fact.
|
|
|
|
Construction enforces:
|
|
- ``kind`` must be in :data:`PROVENANCE_KINDS`
|
|
- ``problem_text`` requires non-empty ``source_spans``
|
|
- ``derived`` requires non-empty ``input_fact_ids``
|
|
- Pack/world kinds must not carry source spans
|
|
"""
|
|
kind: str
|
|
source_spans: tuple[SourceSpan, ...] = ()
|
|
input_fact_ids: tuple[str, ...] = ()
|
|
pack_id: str | None = None
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.kind not in PROVENANCE_KINDS:
|
|
raise ValueError(
|
|
f"KernelProvenance.kind must be one of {sorted(PROVENANCE_KINDS)}, "
|
|
f"got {self.kind!r}"
|
|
)
|
|
if self.kind in _SPAN_REQUIRED_KINDS and not self.source_spans:
|
|
raise ValueError(
|
|
f"Provenance kind {self.kind!r} requires non-empty source_spans"
|
|
)
|
|
if self.kind in _INPUT_REQUIRED_KINDS and not self.input_fact_ids:
|
|
raise ValueError(
|
|
f"Provenance kind {self.kind!r} requires non-empty input_fact_ids"
|
|
)
|
|
if self.kind in _SPAN_FORBIDDEN_KINDS and self.source_spans:
|
|
raise ValueError(
|
|
f"Provenance kind {self.kind!r} must not carry source_spans "
|
|
f"(pack/world facts must not masquerade as problem text)"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Kernel hazard — annotates risk on a fact or surface.
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass(frozen=True, slots=True)
|
|
class KernelHazard:
|
|
"""An ambiguity or risk annotation on a substrate fact or surface.
|
|
|
|
The hazard registry (:mod:`language_packs.ambiguity_hazards`) owns the
|
|
canonical set of hazard categories; this record carries a reference to
|
|
one of them.
|
|
"""
|
|
hazard_id: str
|
|
category: str
|
|
surface: str
|
|
description: str
|
|
context_required: tuple[str, ...] = ()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Grounded scalar — exact rational value from problem text or pack.
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass(frozen=True, slots=True)
|
|
class GroundedScalar:
|
|
"""A grounded scalar quantity as an exact rational number.
|
|
|
|
``value`` is always a :class:`~fractions.Fraction` — never a float.
|
|
The ``provenance`` tracks where this scalar came from.
|
|
"""
|
|
fact_id: str
|
|
surface: str
|
|
value: Fraction
|
|
provenance: KernelProvenance
|
|
hazards: tuple[KernelHazard, ...] = ()
|
|
|
|
def __post_init__(self) -> None:
|
|
if not isinstance(self.value, Fraction):
|
|
raise TypeError(
|
|
f"GroundedScalar.value must be Fraction, got {type(self.value).__name__}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Grounded unit — unit fact with dimension classification.
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass(frozen=True, slots=True)
|
|
class GroundedUnit:
|
|
"""A unit fact with its dimension classification.
|
|
|
|
``dimension`` is the dimension class name (e.g., ``'length'``, ``'money'``).
|
|
"""
|
|
fact_id: str
|
|
surface: str
|
|
dimension: str
|
|
singular: str
|
|
provenance: KernelProvenance
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Relation role — role in a candidate relation.
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass(frozen=True, slots=True)
|
|
class RelationRole:
|
|
"""A typed role within a :class:`CandidateRelation`."""
|
|
name: str
|
|
required: bool
|
|
description: str
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Candidate relation — typed relation between quantities/entities.
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass(frozen=True, slots=True)
|
|
class CandidateRelation:
|
|
"""A typed candidate relation between quantities, entities, or both.
|
|
|
|
This is a *candidate* — it annotates structure, not a solved answer.
|
|
"""
|
|
relation_id: str
|
|
relation_type: str
|
|
roles: tuple[RelationRole, ...] = ()
|
|
provenance: KernelProvenance | None = None
|
|
hazards: tuple[KernelHazard, ...] = ()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Substrate fact — the canonical union wrapper.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Content type union for SubstrateFact.
|
|
SubstrateContent = Union[GroundedScalar, GroundedUnit, CandidateRelation]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SubstrateFact:
|
|
"""Union wrapper — the canonical substrate fact record.
|
|
|
|
``fact_type`` must be one of :data:`FACT_TYPES` and must match the
|
|
runtime type of ``content``.
|
|
|
|
``speculative`` provenance facts are blocked from serving consumption —
|
|
the provenance check is on the ``provenance`` field of this wrapper.
|
|
"""
|
|
fact_id: str
|
|
fact_type: str
|
|
content: SubstrateContent
|
|
provenance: KernelProvenance
|
|
hazards: tuple[KernelHazard, ...] = ()
|
|
|
|
def __post_init__(self) -> None:
|
|
if self.fact_type not in FACT_TYPES:
|
|
raise ValueError(
|
|
f"SubstrateFact.fact_type must be one of {sorted(FACT_TYPES)}, "
|
|
f"got {self.fact_type!r}"
|
|
)
|
|
# Verify content type matches fact_type.
|
|
_CONTENT_TYPE_MAP = {
|
|
"grounded_scalar": GroundedScalar,
|
|
"grounded_unit": GroundedUnit,
|
|
"candidate_relation": CandidateRelation,
|
|
}
|
|
expected = _CONTENT_TYPE_MAP[self.fact_type]
|
|
if not isinstance(self.content, expected):
|
|
raise TypeError(
|
|
f"SubstrateFact with fact_type={self.fact_type!r} expects "
|
|
f"{expected.__name__} content, got {type(self.content).__name__}"
|
|
)
|
|
|
|
@property
|
|
def is_speculative(self) -> bool:
|
|
"""True if this fact has speculative provenance and must not be
|
|
consumed by serving."""
|
|
return self.provenance.kind == "speculative"
|