abaa8efdd5
Implements non-negotiable #1: the AI assistant never writes autonomously. Every assistant/contributor "write" emits a ChangeProposal — a structured diff a human approves, edits, or rejects. Design: docs/design/change-proposal.md. Structural guarantee: a proposal's operations reach the DB ONLY via change_proposal_service.apply(), which requires the actor be an editor and dispatches each op through the normal editing services (person/name/event/ relationship/source/citation create/update/delete) — so every change passes the privacy engine and is audited as the approving human. propose() only inserts a pending row; it performs no domain mutation. Model providers stay read-only, so no model response can mutate tree data. - ChangeProposal model + migration (status pending|applied|rejected, origin assistant|contributor, JSONB operations, reviewer + apply_error). - Service: propose / list / get / apply (with optional edited ops) / reject / delete; a dispatcher mapping ops → editing services. v1 applies ops in order, not cross-op transactional (single-op is atomic; documented). - API /trees/{id}/proposals + a frontend review page (approve/reject; editor- gated) and sidebar entry. Tests: proposal doesn't apply until approved; reject doesn't apply; non-editor member can see but not apply; multi-op; approve-with-edits; apply-error keeps it pending. Full suite 87 passed; single alembic head. Closes #214 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Paul <justin@jpaul.me>
75 lines
1.9 KiB
Python
75 lines
1.9 KiB
Python
"""Closed-set enumerations that drive logic (authorization, privacy, traversal).
|
|
|
|
Open-ended, GEDCOM-extensible vocabularies (event type, name type, source type)
|
|
are stored as strings instead, so importing real-world files never fails on an
|
|
unknown tag.
|
|
"""
|
|
|
|
import enum
|
|
|
|
|
|
class TreeVisibility(enum.StrEnum):
|
|
public = "public" # anyone on the web (anonymous), listed + search-indexable
|
|
site_members = "site_members" # any authenticated user of this instance
|
|
unlisted = "unlisted" # anyone with the link (anonymous), not listed/indexed
|
|
private = "private" # members only (default)
|
|
|
|
|
|
class MembershipRole(enum.StrEnum):
|
|
owner = "owner"
|
|
editor = "editor"
|
|
viewer = "viewer"
|
|
|
|
|
|
class PersonPrivacy(enum.StrEnum):
|
|
"""Per-person override of the tree's visibility (PRD US-041)."""
|
|
|
|
inherit = "inherit"
|
|
private = "private"
|
|
public = "public"
|
|
|
|
|
|
class RelationshipType(enum.StrEnum):
|
|
parent_child = "parent_child"
|
|
partnership = "partnership"
|
|
sibling = "sibling"
|
|
|
|
|
|
class ParentChildQualifier(enum.StrEnum):
|
|
"""Qualifies a parent_child edge so adoption/donor/blended families are
|
|
first-class rather than edge cases (ARCHITECTURE §5)."""
|
|
|
|
biological = "biological"
|
|
adoptive = "adoptive"
|
|
step = "step"
|
|
foster = "foster"
|
|
donor = "donor"
|
|
guardian = "guardian"
|
|
|
|
|
|
class CitationConfidence(enum.StrEnum):
|
|
high = "high"
|
|
medium = "medium"
|
|
low = "low"
|
|
|
|
|
|
class AuditActorType(enum.StrEnum):
|
|
user = "user"
|
|
assistant = "assistant"
|
|
|
|
|
|
class TokenPurpose(enum.StrEnum):
|
|
email_verify = "email_verify"
|
|
password_reset = "password_reset"
|
|
|
|
|
|
class ChangeProposalStatus(enum.StrEnum):
|
|
pending = "pending"
|
|
applied = "applied"
|
|
rejected = "rejected"
|
|
|
|
|
|
class ChangeProposalOrigin(enum.StrEnum):
|
|
assistant = "assistant" # the AI assistant, acting on behalf of a user
|
|
contributor = "contributor" # an untrusted human edit awaiting moderation
|