Fix #214: ChangeProposal (propose-then-confirm) #236

Merged
justin merged 1 commits from change-proposal into main 2026-06-09 15:44:42 -04:00
Owner

Implements non-negotiable #1 — the AI assistant never writes autonomously. Design note: docs/design/change-proposal.md.

Structural guarantee: a proposal's ops reach the DB only via apply(), which requires the actor be an editor and dispatches each op through the normal editing services (person/name/event/relationship/source/citation) — so every change hits the privacy engine and is audited as the approving human. propose() only inserts a pending row. Model providers stay read-only.

  • ChangeProposal model + migration (status, origin, JSONB operations, reviewer, apply_error).
  • Service: propose/list/get/apply(+edits)/reject/delete + op→service dispatcher. v1 applies ops in order (single-op atomic; cross-op not yet transactional — documented).
  • /trees/{id}/proposals API + a review page (editor-gated approve/reject) + sidebar entry.

Tests: not-applied-until-approved, reject, non-editor-can-see-not-apply, multi-op, approve-with-edits, apply-error-keeps-pending. 87 passed, single alembic head.

Closes #214

🤖 Generated with Claude Code

Implements non-negotiable #1 — the AI assistant never writes autonomously. Design note: `docs/design/change-proposal.md`. **Structural guarantee:** a proposal's ops reach the DB **only** via `apply()`, which requires the actor be an editor and dispatches each op through the normal editing services (person/name/event/relationship/source/citation) — so every change hits the privacy engine and is audited as the approving human. `propose()` only inserts a pending row. Model providers stay read-only. - `ChangeProposal` model + migration (status, origin, JSONB operations, reviewer, apply_error). - Service: propose/list/get/apply(+edits)/reject/delete + op→service dispatcher. v1 applies ops in order (single-op atomic; cross-op not yet transactional — documented). - `/trees/{id}/proposals` API + a review page (editor-gated approve/reject) + sidebar entry. Tests: not-applied-until-approved, reject, non-editor-can-see-not-apply, multi-op, approve-with-edits, apply-error-keeps-pending. **87 passed**, single alembic head. Closes #214 🤖 Generated with [Claude Code](https://claude.com/claude-code)
justin added 1 commit 2026-06-09 15:44:41 -04:00
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>
justin merged commit 9187c0a791 into main 2026-06-09 15:44:42 -04:00
justin deleted branch change-proposal 2026-06-09 15:44:42 -04:00
Sign in to join this conversation.