Spaces:
Paused
Paused
| """ | |
| Memo service (universal) — visibility + permission rules over the store. | |
| Visibility model (decided 2026-05-18): | |
| - ``private`` (default): visible to the author and to admins/adjudicators. | |
| - ``shared``: visible to the author, admins/adjudicators, AND peer | |
| annotators on the same project. | |
| - Admins/adjudicators can ALWAYS read every memo regardless of setting. | |
| Permissions: | |
| - Read: per the visibility rule above. | |
| - Edit (body/visibility): author only. | |
| - Delete: author OR an admin/adjudicator (moderation). | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, List, Optional | |
| from . import store | |
| VALID_VISIBILITY = ("private", "shared") | |
| class MemoError(Exception): | |
| """Base error for memo operations (maps to 4xx at the API layer).""" | |
| class MemoNotFound(MemoError): | |
| pass | |
| class MemoPermissionError(MemoError): | |
| pass | |
| def _can_read(memo: Dict[str, Any], requester: str, is_privileged: bool) -> bool: | |
| return ( | |
| is_privileged | |
| or memo["created_by"] == requester | |
| or memo["visibility"] == "shared" | |
| ) | |
| def create_memo( | |
| task_dir: str, | |
| *, | |
| project: str, | |
| instance_id: str, | |
| body: str, | |
| created_by: str, | |
| anchor: Optional[Dict[str, Any]] = None, | |
| visibility: str = "private", | |
| ) -> Dict[str, Any]: | |
| body = (body or "").strip() | |
| if not body: | |
| raise MemoError("Memo body must not be empty") | |
| if visibility not in VALID_VISIBILITY: | |
| raise MemoError( | |
| f"visibility must be one of {VALID_VISIBILITY} (got {visibility!r})" | |
| ) | |
| if anchor is not None: | |
| if not isinstance(anchor, dict) or "start" not in anchor or "end" not in anchor: | |
| raise MemoError("anchor must be {start, end[, field]} or null") | |
| return store.create( | |
| task_dir, project=project, instance_id=instance_id, body=body, | |
| created_by=created_by, anchor=anchor, visibility=visibility, | |
| ) | |
| def list_visible( | |
| task_dir: str, | |
| *, | |
| project: str, | |
| instance_id: str, | |
| requester: str, | |
| is_privileged: bool = False, | |
| ) -> List[Dict[str, Any]]: | |
| """Memos on an instance the requester is allowed to see.""" | |
| return [ | |
| m for m in store.list_for_instance(task_dir, project, instance_id) | |
| if _can_read(m, requester, is_privileged) | |
| ] | |
| def _load_or_404(task_dir: str, memo_id: str) -> Dict[str, Any]: | |
| memo = store.get(task_dir, memo_id) | |
| if memo is None: | |
| raise MemoNotFound(f"Memo {memo_id} not found") | |
| return memo | |
| def update_memo( | |
| task_dir: str, | |
| memo_id: str, | |
| *, | |
| requester: str, | |
| is_privileged: bool = False, | |
| body: Optional[str] = None, | |
| visibility: Optional[str] = None, | |
| ) -> Dict[str, Any]: | |
| memo = _load_or_404(task_dir, memo_id) | |
| if memo["created_by"] != requester: | |
| raise MemoPermissionError("Only the memo author may edit it") | |
| if body is not None and not body.strip(): | |
| raise MemoError("Memo body must not be empty") | |
| if visibility is not None and visibility not in VALID_VISIBILITY: | |
| raise MemoError( | |
| f"visibility must be one of {VALID_VISIBILITY} (got {visibility!r})" | |
| ) | |
| return store.update( | |
| task_dir, memo_id, | |
| body=body.strip() if body is not None else None, | |
| visibility=visibility, | |
| ) | |
| def delete_memo( | |
| task_dir: str, | |
| memo_id: str, | |
| *, | |
| requester: str, | |
| is_privileged: bool = False, | |
| ) -> None: | |
| memo = _load_or_404(task_dir, memo_id) | |
| if memo["created_by"] != requester and not is_privileged: | |
| raise MemoPermissionError( | |
| "Only the author or an admin/adjudicator may delete this memo" | |
| ) | |
| store.delete(task_dir, memo_id) | |