This commit is contained in:
1 parent
0e1a2ad008
commit
34f4923566
28 files changed
+3932
-81
No files matched your search
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user