core/workbench/construction_endpoint.py
Shay 4c23aa4b00
feat(workbench): prepare construction evidence endpoint seam (#893)
* feat(workbench): add construction endpoint handler seam

* test(workbench): cover construction endpoint handler seam

* feat(workbench-ui): add construction evidence endpoint helpers

* test(workbench-ui): cover construction endpoint helpers
2026-06-23 10:11:21 -07:00

71 lines
2.2 KiB
Python

"""Narrow construction-evidence endpoint seam for Workbench.
This helper keeps the endpoint behavior testable without editing the large
`workbench.api` dispatch table from environments that cannot run local syntax and
route tests. The final API wiring is intentionally one line of dispatch:
return construction_evidence_response(self._journal, raw_turn_id)
The helper itself is read-only and projects only persisted construction evidence
through `construction_evidence_from_journal_entry`.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from workbench.construction_evidence import construction_evidence_from_journal_entry
from workbench.schemas import error, ok
@dataclass(frozen=True, slots=True)
class ConstructionEndpointResponse:
status: int
payload: dict[str, Any]
def construction_evidence_response(
journal: Any,
raw_turn_id: str,
) -> ConstructionEndpointResponse:
"""Return JSON-envelope payload for `/trace/<turn_id>/construction`.
The function is deliberately free of parsing/reconstruction side effects. It
reads a journal entry and projects construction evidence when already
persisted; legacy entries receive a typed `missing_evidence` data payload.
"""
try:
turn_id = int(raw_turn_id)
except ValueError:
return ConstructionEndpointResponse(
404,
error("not_found", f"trace construction not found: {raw_turn_id}"),
)
try:
entry = journal.get_entry(turn_id)
except FileNotFoundError:
return ConstructionEndpointResponse(
404,
error("not_found", f"trace construction not found: {turn_id}"),
)
return ConstructionEndpointResponse(
200,
ok(construction_evidence_from_journal_entry(entry)),
)
def construction_turn_id_from_path(path: str) -> str | None:
"""Extract raw turn id from `/trace/<turn_id>/construction`.
Returns None for non-matching paths so the main dispatch table can remain
explicit and order-safe.
"""
if not (path.startswith("/trace/") and path.endswith("/construction")):
return None
raw = path.removeprefix("/trace/").removesuffix("/construction").strip("/")
return raw or None