Redesign document approval flow

Replace the per-approver add/remove model with a quorum-based approval
system. Documents now have default approvers that are pre-populated when
requesting approval, and the publish dialog lets users adjust the list
before submitting.

Key changes:

- Add PENDING_APPROVAL document version status with dedicated transitions
- Introduce approval quorums with request/approve/reject/void lifecycle
- Add default approvers per document (stored in document_default_approvers)
  with MERGE-based upsert for efficient sync
- Add NoDuplicates validator for slice fields
- Split ALTER TYPE ADD VALUE migrations into separate files (required by
  PostgreSQL when run inside transactions)
- Use VOIDED consistently for both quorum status and decision state enums
- Expose void/approve/reject through GraphQL and MCP, with e2e tests
- Add approval management UI: publish dialog with approver selection,
  approval list with void support, and external approve/reject page

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-04-09 11:17:51 +02:00
parent 17f579b8a2
commit ab5f42ad74
38 changed files with 2125 additions and 922 deletions

View File

@@ -424,6 +424,40 @@ WHERE
return nil
}
func (d *DocumentVersionApprovalDecisions) VoidPendingByQuorumID(
ctx context.Context,
conn pg.Tx,
scope Scoper,
quorumID gid.GID,
now time.Time,
) error {
q := `
UPDATE document_version_approval_decisions
SET
state = 'VOIDED',
updated_at = @updated_at
WHERE
%s
AND quorum_id = @quorum_id
AND state = 'PENDING'
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"quorum_id": quorumID,
"updated_at": now,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot void pending approval decisions: %w", err)
}
return nil
}
func (d *DocumentVersionApprovalDecisions) CountByQuorumID(
ctx context.Context,
conn pg.Querier,