feat(ingest): resolve project labels with Jev
CI / quality (push) Canceled after 0s

This commit is contained in:
vasily committed 2026-10-15 10:53:30 +08:00
1 parent 0e1a2ad008
commit 34f4923566
28 files changed
+3932 -81

No files matched your search

+8 -2
View File
@@ -9,7 +9,7 @@ from dataclasses import dataclass
from pathlib import Path, PurePosixPath
from typing import Protocol
from gewu.domain.contracts import AutoAcceptPolicy, ProjectRecord
from gewu.domain.contracts import AutoAcceptPolicy, ProjectFacetLabel, ProjectRecord
class CanonicalRepositoryError(RuntimeError):
@@ -74,10 +74,16 @@ class ProjectRegistry(Protocol):
def create(self, project: ProjectRecord, policy: AutoAcceptPolicy) -> ProjectRecord: ...
def update_content(self, project_id: str, *, description: str, north_star: str, key_questions: tuple[str, ...]) -> ProjectRecord: ...
def update_content(
self, project_id: str, *, description: str, north_star: str, key_questions: tuple[str, ...]
) -> ProjectRecord: ...
def archive(self, project_id: str) -> ProjectRecord: ...
def add_facet_labels(
self, project_id: str, labels: tuple[ProjectFacetLabel, ...]
) -> ProjectRecord: ...
class EmbeddingProvider(Protocol):
"""Replaceable, normalized text-vector provider for derived retrieval indexes."""
+87 -5
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import json
import unicodedata
from contextlib import AbstractContextManager
from dataclasses import dataclass
from pathlib import Path
@@ -20,6 +21,7 @@ from gewu.domain.contracts import (
AutoAcceptPolicy,
ExtractionRecord,
KnowledgeNode,
ProjectFacetLabel,
ProjectRecord,
ProjectStatus,
SourceRevisionRecord,
@@ -307,15 +309,23 @@ class LocalProjectRegistry:
self, project_id: str, *, description: str, north_star: str, key_questions: tuple[str, ...]
) -> ProjectRecord:
"""Update a project's human-authored description and research questions."""
if self.repository is None or self.index_directory is None or self.authority_lock_path is None:
if (
self.repository is None
or self.index_directory is None
or self.authority_lock_path is None
):
raise RegistryError("Canonical project publication is not configured")
with self._write_lock():
project = _index(self._projects(), "project_id").get(project_id)
if project is None:
raise RegistryNotFound("Project is not registered")
updated = project.model_copy(update={
"description": description, "north_star": north_star, "key_questions": key_questions,
})
updated = project.model_copy(
update={
"description": description,
"north_star": north_star,
"key_questions": key_questions,
}
)
try:
self.repository.publish_files(
expected_head=self.repository.head(),
@@ -324,7 +334,9 @@ class LocalProjectRegistry:
index_directory=self.index_directory,
)
except CanonicalConflictError as error:
raise RegistryConflict("Canonical project registry changed during update") from error
raise RegistryConflict(
"Canonical project registry changed during update"
) from error
return updated
def archive(self, project_id: str) -> ProjectRecord:
@@ -359,6 +371,72 @@ class LocalProjectRegistry:
) from error
return archived
def add_facet_labels(
self, project_id: str, labels: tuple[ProjectFacetLabel, ...]
) -> ProjectRecord:
"""Atomically add bounded Jev-qualified labels to the canonical project record."""
if not labels or len(labels) > 20:
raise ValueError("Facet label additions must contain between 1 and 20 labels")
if (
self.repository is None
or self.index_directory is None
or self.authority_lock_path is None
):
raise RegistryError("Canonical project publication is not configured")
with self._write_lock():
project = _index(self._projects(), "project_id").get(project_id)
if project is None:
raise RegistryNotFound("Project is not registered")
existing = {
(label.facet, _normalize_facet_label(label.canonical_label))
for label in project.facet_labels
if label.status == "active"
}
existing.update(
(label.facet, _normalize_facet_label(alias))
for label in project.facet_labels
if label.status == "active"
for alias in label.aliases
)
accepted: list[ProjectFacetLabel] = []
known_ids = {label.label_id for label in project.facet_labels}
for label in labels:
if label.status != "active" or label.merged_into is not None:
raise ValueError("Only active new facet labels may be added")
if label.label_id in known_ids:
raise RegistryConflict("Facet label ID is already registered")
normalized_names = (
label.canonical_label,
*label.aliases,
)
if any(
(label.facet, _normalize_facet_label(name)) in existing
for name in normalized_names
):
raise RegistryConflict("Facet label duplicates an existing project label")
for name in normalized_names:
existing.add((label.facet, _normalize_facet_label(name)))
known_ids.add(label.label_id)
accepted.append(label)
if len(project.facet_labels) + len(accepted) > 2_000:
raise RegistryError("Project facet label catalog exceeds its configured limit")
updated = project.model_copy(
update={"facet_labels": (*project.facet_labels, *accepted)}
)
try:
self.repository.publish_files(
expected_head=self.repository.head(),
files={_project_record_path(project_id): _project_markdown(updated)},
message=f"Add qualified facet labels to {project_id}",
index_directory=self.index_directory,
)
except CanonicalConflictError as error:
raise RegistryConflict(
"Canonical project registry changed while adding facet labels"
) from error
return updated
def _write_lock(self) -> AbstractContextManager[Any]:
lock_path = self.authority_lock_path
if lock_path is None:
@@ -381,6 +459,10 @@ def _project_record_path(project_id: str) -> str:
return f"projects/{project_id}/project.md"
def _normalize_facet_label(value: str) -> str:
return " ".join(unicodedata.normalize("NFC", value).casefold().split())
def _policy_record_path(policy_id: str) -> str:
import hashlib