Wires teaching/math_proposals/proposals.jsonl into the CORE Workbench
API (ADR-0160) alongside the existing cognition proposal queue:
workbench/schemas.py
- MathReasoningStep, MathProposalSummary, MathProposalDetail,
MathRatifyResult schemas
workbench/readers.py
- MATH_PROPOSALS_JSONL + _DEFAULT_MATH_AUDIT_PATH constants
- teaching/math_proposals added to ALLOWED_ARTIFACT_ROOTS
- _HANDLER_DISPATCH table (vocabulary_addition→LexicalClaim; all
others not yet implemented)
- list_math_proposals(), read_math_proposal(), ratify_math_proposal()
- read_math_proposal() re-runs decompose_audit() to recover full
4-step reasoning trace (canonical_bytes only carries trace_id)
- ratify_math_proposal() raises NotImplementedError with clear
"handler not yet implemented: {change_kind}" for unhandled kinds
workbench/api.py
- GET /math-proposals, GET /math-proposals/{id}
- POST /math-proposals/{id}/ratify → _math_ratify()
(vocabulary_addition→200/routed; unhandled→501 with loud message)
tests/test_adr_0172_w4_workbench_e2e.py — 6 tests:
1. loads from JSONL
2. renders domain:math badge (distinct from cognition /proposals)
3. ratify-vocabulary_addition routes to LexicalClaim (200)
4. ratify-matcher_extension fails loudly (501 "handler not yet
implemented")
5. all 4 trace steps visible in detail response
6. no cross-contamination between math and cognition queues
teaching + runtime suites green (28 + 20 passed).
Brief-gap note: canonical_bytes() excludes proposal_id and serialises
evidence pointers as hashes only. D1 loader derives proposal_id via
sha256(line_bytes) and re-runs decompose_audit() to recover full trace
for read_math_proposal(). This works but means the JSONL cannot be
loaded without the original audit file. If a future wave needs
standalone JSONL loading, C1 should emit a richer format.
245 lines
6.1 KiB
Python
245 lines
6.1 KiB
Python
"""Typed UI-facing schemas for CORE Workbench v1."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict, dataclass, field
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Literal
|
|
|
|
|
|
ErrorCode = Literal[
|
|
"bad_request",
|
|
"not_found",
|
|
"unsupported",
|
|
"read_error",
|
|
"eval_failed",
|
|
"runtime_unavailable",
|
|
]
|
|
|
|
MutationMode = Literal["read_only", "runtime_turn"]
|
|
GroundingSource = Literal["pack", "teaching", "vault", "partial", "oov", "none"]
|
|
EpistemicStateValue = Literal[
|
|
"perceived",
|
|
"evidenced",
|
|
"evidenced_incomplete",
|
|
"verified",
|
|
"decoded",
|
|
"decoded_unarticulated",
|
|
"inferred",
|
|
"unverified_possible",
|
|
"unverified_novel",
|
|
"contradicted",
|
|
"ambiguous",
|
|
"undetermined",
|
|
"scope_boundary",
|
|
"computationally_bounded",
|
|
"epistemic_state_needed",
|
|
]
|
|
NormativeClearanceValue = Literal[
|
|
"cleared",
|
|
"violated",
|
|
"unassessable",
|
|
"suppressed",
|
|
]
|
|
|
|
|
|
def utc_now() -> str:
|
|
return datetime.now(timezone.utc).isoformat()
|
|
|
|
|
|
def to_data(value: Any) -> Any:
|
|
if hasattr(value, "as_dict") and callable(value.as_dict):
|
|
return value.as_dict()
|
|
if hasattr(value, "__dataclass_fields__"):
|
|
return asdict(value)
|
|
if isinstance(value, dict):
|
|
return {str(k): to_data(v) for k, v in value.items()}
|
|
if isinstance(value, (list, tuple)):
|
|
return [to_data(v) for v in value]
|
|
return value
|
|
|
|
|
|
def ok(data: Any) -> dict[str, Any]:
|
|
return {"ok": True, "generated_at": utc_now(), "data": to_data(data)}
|
|
|
|
|
|
def error(code: ErrorCode, message: str, *, detail: Any | None = None) -> dict[str, Any]:
|
|
payload: dict[str, Any] = {"code": code, "message": message}
|
|
if detail is not None:
|
|
payload["detail"] = to_data(detail)
|
|
return {"ok": False, "generated_at": utc_now(), "error": payload}
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class RuntimeStatus:
|
|
backend: Literal["numpy", "mlx", "rust", "unknown"]
|
|
git_revision: str
|
|
engine_state_present: bool
|
|
checkpoint_revision: str
|
|
revision_warning: bool
|
|
active_session_id: str | None
|
|
mutation_mode: MutationMode = "read_only"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class TurnVerdict:
|
|
outcome: Literal["cleared", "violated", "unassessable"]
|
|
runtime_detail: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ProposalRef:
|
|
candidate_id: str
|
|
source_kind: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ChatTurnResult:
|
|
prompt: str
|
|
surface: str
|
|
articulation_surface: str | None
|
|
walk_surface: str | None
|
|
grounding_source: GroundingSource
|
|
epistemic_state: EpistemicStateValue
|
|
normative_clearance: NormativeClearanceValue
|
|
normative_detail: str
|
|
trace_hash: str | None
|
|
refusal_emitted: bool
|
|
hedge_injected: bool
|
|
mutation_mode: MutationMode
|
|
identity_verdict: TurnVerdict | None
|
|
safety_verdict: TurnVerdict | None
|
|
ethics_verdict: TurnVerdict | None
|
|
proposal_candidates: list[ProposalRef]
|
|
turn_cost_ms: int
|
|
checkpoint_emitted: bool
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ArtifactRef:
|
|
artifact_id: str
|
|
kind: Literal[
|
|
"trace",
|
|
"eval_result",
|
|
"proposal",
|
|
"contemplation_report",
|
|
"telemetry",
|
|
"engine_state_manifest",
|
|
"unknown",
|
|
]
|
|
path: str
|
|
digest: str | None
|
|
created_at: str | None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ArtifactDetail(ArtifactRef):
|
|
content_type: Literal["json", "jsonl", "text", "unknown"]
|
|
content: Any
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ProposalSummary:
|
|
proposal_id: str
|
|
state: Literal["pending", "accepted", "rejected", "withdrawn", "unknown"]
|
|
source_kind: str
|
|
replay_equivalent: bool | None
|
|
created_at: str | None
|
|
downstream_effect: Literal["unknown", "none", "observed"]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ProposalDetail(ProposalSummary):
|
|
proposed_chain: Any
|
|
replay_evidence: Any
|
|
source: Any
|
|
evidence: list[Any] = field(default_factory=list)
|
|
artifact_refs: list[ArtifactRef] = field(default_factory=list)
|
|
suggested_cli: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class EvalLaneSummary:
|
|
lane: str
|
|
versions: list[str]
|
|
read_only: bool
|
|
description: str | None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class EvalRunResult:
|
|
lane: str
|
|
version: str
|
|
split: str
|
|
passed: bool | None
|
|
metrics: dict[str, Any]
|
|
cases: list[Any]
|
|
source_digest: str | None = None
|
|
|
|
|
|
ReplayDivergenceSeverity = Literal["info", "warning", "failure"]
|
|
ReplayStatus = Literal["equivalent", "not_yet_replayed", "diverged", "evidence_unavailable"]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ReplayDivergence:
|
|
path: str
|
|
original: Any
|
|
replay: Any
|
|
severity: ReplayDivergenceSeverity
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ReplayComparison:
|
|
artifact_id: str
|
|
original_hash: str | None
|
|
replay_hash: str | None
|
|
equivalent: bool
|
|
divergences: list[ReplayDivergence] = field(default_factory=list)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ADR-0172 W4 — Math proposal schemas
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MathReasoningStep:
|
|
step_index: int
|
|
step_kind: str
|
|
claim: str
|
|
justification: str
|
|
input_pointers: list[str]
|
|
output_payload: Any
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MathProposalSummary:
|
|
proposal_id: str
|
|
domain: Literal["math"]
|
|
shape_category: str
|
|
proposed_change_kind: str
|
|
structural_commonality: str
|
|
evidence_count: int
|
|
replay_equivalence_hash: str
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MathProposalDetail(MathProposalSummary):
|
|
wrong_zero_assertion: str
|
|
proposed_change_payload: Any
|
|
reasoning_trace_id: str
|
|
reasoning_trace_steps: list[MathReasoningStep]
|
|
evidence_hashes: list[str]
|
|
handler_name: str | None
|
|
suggested_ratify_cli: str | None
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class MathRatifyResult:
|
|
proposal_id: str
|
|
change_kind: str
|
|
handler_name: str
|
|
routing_status: Literal["routed", "not_implemented"]
|
|
message: str
|
|
suggested_cli: str | None = None
|