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>
36 lines
905 B
Python
36 lines
905 B
Python
"""Import every model so ``Base.metadata`` is complete for Alembic autogenerate
|
|
and for ``create_all`` in tests."""
|
|
|
|
from app.models.audit import AuditEntry
|
|
from app.models.auth import Session, UserToken
|
|
from app.models.base import Base
|
|
from app.models.change_proposal import ChangeProposal
|
|
from app.models.event import Event
|
|
from app.models.media import Media
|
|
from app.models.person import Name, Person
|
|
from app.models.place import Place, PlaceName
|
|
from app.models.relationship import Relationship
|
|
from app.models.source import Citation, Source
|
|
from app.models.tree import Tree, TreeMembership
|
|
from app.models.user import User
|
|
|
|
__all__ = [
|
|
"Base",
|
|
"User",
|
|
"Tree",
|
|
"TreeMembership",
|
|
"Person",
|
|
"Name",
|
|
"Place",
|
|
"PlaceName",
|
|
"Relationship",
|
|
"Event",
|
|
"Source",
|
|
"Citation",
|
|
"AuditEntry",
|
|
"Session",
|
|
"UserToken",
|
|
"Media",
|
|
"ChangeProposal",
|
|
]
|