Files
provenance/backend/app/models/__init__.py
T
justin abaa8efdd5 Fix #214: ChangeProposal (propose-then-confirm)
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>
2026-06-09 15:44:40 -04:00

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",
]