* feat(workbench): add construction evidence read model scaffolding * feat(workbench-ui): add construction evidence TS types * feat(workbench): add construction evidence journal projector * test(workbench): cover construction evidence read model * feat(workbench-ui): add construction evidence view helpers * test(workbench-ui): cover construction evidence view helpers * feat(workbench): add generalization audit evidence scaffolding * test(workbench): cover generalization audit evidence view * feat(workbench-ui): add generalization evidence TS types * feat(workbench-ui): add generalization evidence view helpers * test(workbench-ui): cover generalization evidence helpers * feat(workbench): add proposal artifact authority scaffolding * test(workbench): cover proposal artifact authority rules * feat(workbench-ui): add proposal artifact TS types * feat(workbench-ui): add proposal artifact view helpers * test(workbench-ui): cover proposal artifact view helpers * feat(workbench): add demo narrative evidence scaffolding * test(workbench): cover demo narrative scaffolding * feat(workbench-ui): add demo narrative TS types * feat(workbench-ui): add demo narrative view helpers * test(workbench-ui): cover demo narrative view helpers * fix(evals): resolve discovery_candidates.jsonl via EngineStateStore
185 lines
5.9 KiB
Python
185 lines
5.9 KiB
Python
"""Read-only construction evidence projection helpers for Workbench.
|
|
|
|
This module is intentionally inert: it defines the Workbench-facing construction
|
|
read model and the honest missing-evidence constructor for legacy turns. It does
|
|
not parse problem text, execute candidate operators, run replay, mutate journals,
|
|
or grant serving authority.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Literal
|
|
|
|
PipelineEvidenceStatus = Literal["recorded", "missing_evidence"]
|
|
CONSTRUCTION_EVIDENCE_ABSENT = "construction evidence was not persisted for this turn"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class SourceSpanView:
|
|
"""Exact source span projected to the Workbench.
|
|
|
|
The caller owns validation against the source string. This record must not be
|
|
normalized or repaired by the UI.
|
|
"""
|
|
|
|
start: int
|
|
end: int
|
|
text: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RoleObligationView:
|
|
role: str
|
|
required: bool
|
|
description: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ConstructionProposalView:
|
|
"""Diagnostic construction proposal.
|
|
|
|
A proposal is a hypothesis only. Runnable/refused authority belongs to the
|
|
corresponding ContractAssessmentView.
|
|
"""
|
|
|
|
family_id: str
|
|
relation_type: str
|
|
candidate_organ: str
|
|
status: Literal["proposed"]
|
|
evidence_spans: list[SourceSpanView] = field(default_factory=list)
|
|
role_obligations: list[RoleObligationView] = field(default_factory=list)
|
|
diagnostic_only: bool = True
|
|
serving_allowed: bool = False
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MentionView:
|
|
mention_id: str
|
|
kind: str
|
|
surface: str
|
|
span: SourceSpanView
|
|
fact_id: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MentionBindingView:
|
|
binding_type: str
|
|
source_mention_id: str
|
|
target_mention_id: str
|
|
evidence_spans: list[SourceSpanView] = field(default_factory=list)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class BoundRelationRoleView:
|
|
role: str
|
|
target_id: str
|
|
evidence_spans: list[SourceSpanView] = field(default_factory=list)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class BoundRelationView:
|
|
relation_type: str
|
|
roles: list[BoundRelationRoleView] = field(default_factory=list)
|
|
evidence_spans: list[SourceSpanView] = field(default_factory=list)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ContractAssessmentView:
|
|
"""Read-only projection of generate.problem_frame_contracts.ContractAssessment."""
|
|
|
|
candidate_organ: str
|
|
family_id: str | None
|
|
missing_bindings: list[str] = field(default_factory=list)
|
|
unresolved_hazards: list[str] = field(default_factory=list)
|
|
runnable: bool = False
|
|
explanation: str = ""
|
|
evidence_spans: list[SourceSpanView] = field(default_factory=list)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ConstructionEvidence:
|
|
"""Workbench-facing construction evidence for one turn.
|
|
|
|
This record is a projection, not an executor. `diagnostic_only=True` and
|
|
`serving_allowed=False` are load-bearing disclosure fields.
|
|
"""
|
|
|
|
schema_version: Literal["construction_evidence_v1"]
|
|
turn_id: int
|
|
status: PipelineEvidenceStatus
|
|
missing_reason: str | None
|
|
problem_text: str | None
|
|
proposals: list[ConstructionProposalView] = field(default_factory=list)
|
|
mentions: list[MentionView] = field(default_factory=list)
|
|
bindings: list[MentionBindingView] = field(default_factory=list)
|
|
bound_relations: list[BoundRelationView] = field(default_factory=list)
|
|
contract_assessments: list[ContractAssessmentView] = field(default_factory=list)
|
|
diagnostic_only: bool = True
|
|
serving_allowed: bool = False
|
|
|
|
|
|
def missing_construction_evidence(turn_id: int, reason: str) -> ConstructionEvidence:
|
|
"""Return the honest absence state for legacy turns.
|
|
|
|
Absence is not an error and not a failed proof. It means the selected turn has
|
|
no persisted construction evidence to project.
|
|
"""
|
|
|
|
return ConstructionEvidence(
|
|
schema_version="construction_evidence_v1",
|
|
turn_id=turn_id,
|
|
status="missing_evidence",
|
|
missing_reason=reason,
|
|
problem_text=None,
|
|
proposals=[],
|
|
mentions=[],
|
|
bindings=[],
|
|
bound_relations=[],
|
|
contract_assessments=[],
|
|
diagnostic_only=True,
|
|
serving_allowed=False,
|
|
)
|
|
|
|
|
|
def construction_evidence_from_journal_entry(entry: Any) -> ConstructionEvidence:
|
|
"""Project persisted construction evidence from a journal entry if present.
|
|
|
|
Current turn journals do not yet persist a construction evidence payload. This
|
|
helper is deliberately fail-closed and returns a typed missing-evidence record
|
|
rather than reconstructing a ProblemFrame from prose. When a later PR starts
|
|
persisting `construction_evidence`, this function is the narrow projection
|
|
seam to extend.
|
|
"""
|
|
|
|
turn_id = int(getattr(entry, "turn_id"))
|
|
payload = getattr(entry, "construction_evidence", None)
|
|
if payload is None:
|
|
return missing_construction_evidence(turn_id, CONSTRUCTION_EVIDENCE_ABSENT)
|
|
|
|
if isinstance(payload, ConstructionEvidence):
|
|
return payload
|
|
|
|
if not isinstance(payload, dict):
|
|
return missing_construction_evidence(
|
|
turn_id,
|
|
"construction evidence payload has unsupported shape",
|
|
)
|
|
|
|
# Future-compatible but conservative: do not attempt lossy coercion. The first
|
|
# persistence PR should replace this with explicit field-by-field projection and
|
|
# exact-span validation tests.
|
|
return missing_construction_evidence(
|
|
turn_id,
|
|
"construction evidence payload projection is not yet admitted",
|
|
)
|
|
|
|
|
|
def span_is_exact(problem_text: str, span: SourceSpanView) -> bool:
|
|
"""Return whether a span exactly matches its source text slice."""
|
|
|
|
return (
|
|
0 <= span.start <= span.end <= len(problem_text)
|
|
and bool(span.text)
|
|
and problem_text[span.start : span.end] == span.text
|
|
)
|