feat: implement memory and evidence administration with guided repairs
Publish documentation / publish (push) Successful in 1m27s
Publish documentation / publish (push) Successful in 1m27s
Add PostgreSQL-backed memory, editable evidence with source review and activation, and human-approved archive repairs across the harness, API, and UI. Include migrations, deployment support, regression coverage, and validation documentation. Refresh permissions from validated session roles so existing administrator logins can access newly deployed archive management features.
This commit is contained in:
@@ -98,6 +98,19 @@ class ReviewItem(StrictModel):
|
||||
field: str | None = None
|
||||
|
||||
|
||||
class ManualEvidenceProvenance(StrictModel):
|
||||
"""The curator supports the current content; an earlier document is only its origin."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
kind: Literal["manual"] = "manual"
|
||||
declared_by: str = Field(min_length=1)
|
||||
original: EvidenceProvenance | None = None
|
||||
|
||||
@property
|
||||
def source_file(self) -> str:
|
||||
return f"Manual declaration: {self.declared_by}"
|
||||
|
||||
|
||||
class FormulaPayload(StrictModel):
|
||||
concept: str
|
||||
columns: tuple[str, ...]
|
||||
@@ -229,19 +242,23 @@ _EVIDENCE_ID = re.compile(r"^evidence:[a-z0-9]+(?:-[a-z0-9]+)*$")
|
||||
|
||||
|
||||
class CuratedEvidence(StrictModel):
|
||||
schema_version: Literal[1, 2, 3]
|
||||
schema_version: Literal[1, 2, 3, 4]
|
||||
id: str
|
||||
title: str
|
||||
kind: EvidenceKind
|
||||
purposes: tuple[EvidencePurpose, ...]
|
||||
applies_to: EvidenceScope = Field(default_factory=EvidenceScope)
|
||||
language: str
|
||||
provenance: EvidenceProvenance
|
||||
provenance: EvidenceProvenance | ManualEvidenceProvenance
|
||||
review_items: tuple[ReviewItem, ...] = ()
|
||||
payload: EvidencePayload
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_kind_payload(self) -> CuratedEvidence:
|
||||
if isinstance(self.provenance, ManualEvidenceProvenance) and self.schema_version != 4:
|
||||
raise ValueError("manual declarations require Curated unit schema v4")
|
||||
if self.schema_version == 4 and (not self.purposes or not self.language.strip()):
|
||||
raise ValueError("editable Evidence requires a language and at least one purpose")
|
||||
if not is_evidence_id(self.id):
|
||||
raise ValueError("id must use the evidence:<slug> form")
|
||||
expected = _PAYLOAD_TYPE_BY_KIND.get(self.kind)
|
||||
@@ -1056,13 +1073,19 @@ def parse_curated_markdown(text: str, *, path: Path | None = None) -> CuratedEvi
|
||||
_, frontmatter, body = text.split("---\n", 2)
|
||||
except ValueError as error:
|
||||
raise ValueError("curated evidence frontmatter is malformed") from error
|
||||
raw = yaml.safe_load(frontmatter)
|
||||
try:
|
||||
raw = yaml.safe_load(frontmatter)
|
||||
except yaml.YAMLError as error:
|
||||
raise ValueError("curated evidence frontmatter is malformed") from error
|
||||
try:
|
||||
data = dict(raw)
|
||||
except (TypeError, ValueError) as error:
|
||||
raise ValueError("curated evidence frontmatter must be a mapping") from error
|
||||
if data.get("schema_version") == 2:
|
||||
data = _parse_v2_body(data, body)
|
||||
elif data.get("schema_version") == 4:
|
||||
from tht.evidence.editable import parse_document, parse_metadata
|
||||
data = parse_document(parse_metadata(frontmatter), body)
|
||||
else:
|
||||
if body.strip():
|
||||
raise ValueError("curated evidence must not contain an ignored body")
|
||||
@@ -1081,6 +1104,9 @@ def parse_curated_markdown(text: str, *, path: Path | None = None) -> CuratedEvi
|
||||
|
||||
def dump_curated_markdown(value: CuratedEvidence) -> str:
|
||||
"""Render one canonical Curated Evidence Markdown document."""
|
||||
if value.schema_version == 4:
|
||||
from tht.evidence.editable import render_document
|
||||
return render_document(value)
|
||||
if value.schema_version == 3:
|
||||
return f"{_render_v3_metadata(value)}\n{_render_v3_body(value)}"
|
||||
if value.schema_version == 2:
|
||||
|
||||
Reference in New Issue
Block a user