forked from cardosofelipe/fast-next-template
docs: add remaining ADRs and comprehensive architecture documentation
Added 7 new Architecture Decision Records completing the full set: - ADR-008: Knowledge Base and RAG (pgvector) - ADR-009: Agent Communication Protocol (structured messages) - ADR-010: Workflow State Machine (transitions + PostgreSQL) - ADR-011: Issue Synchronization (webhook-first + polling) - ADR-012: Cost Tracking (LiteLLM callbacks + Redis budgets) - ADR-013: Audit Logging (hash chaining + tiered storage) - ADR-014: Client Approval Flow (checkpoint-based) Added comprehensive ARCHITECTURE.md that: - Summarizes all 14 ADRs in decision matrix - Documents full system architecture with diagrams - Explains all component interactions - Details technology stack with self-hostability guarantee - Covers security, scalability, and deployment Updated IMPLEMENTATION_ROADMAP.md to mark Phase 0 completed items. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
280
docs/adrs/ADR-014-client-approval-flow.md
Normal file
280
docs/adrs/ADR-014-client-approval-flow.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# ADR-014: Client Approval Flow Architecture
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2025-12-29
|
||||
**Deciders:** Architecture Team
|
||||
**Related Spikes:** SPIKE-012
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Syndarix supports configurable autonomy levels. Depending on the level, agents may require client approval before proceeding with certain actions. We need a flexible approval system that:
|
||||
- Respects autonomy level configuration
|
||||
- Provides clear approval UX
|
||||
- Handles timeouts gracefully
|
||||
- Supports mobile-friendly approvals
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- **Configurability:** Per-project autonomy settings
|
||||
- **Usability:** Easy approve/reject with context
|
||||
- **Reliability:** Approvals must not be lost
|
||||
- **Flexibility:** Support batch and individual approvals
|
||||
- **Responsiveness:** Real-time notifications
|
||||
|
||||
## Decision
|
||||
|
||||
**Implement checkpoint-based approval system** with:
|
||||
- Queue-based approval management
|
||||
- Confidence-aware routing
|
||||
- Multi-channel notifications (SSE, email, mobile push)
|
||||
- Configurable timeout and escalation policies
|
||||
|
||||
## Implementation
|
||||
|
||||
### Autonomy Levels
|
||||
|
||||
| Level | Description | Approval Required |
|
||||
|-------|-------------|-------------------|
|
||||
| **FULL_CONTROL** | Approve every significant action | All actions |
|
||||
| **MILESTONE** | Approve at sprint boundaries | Sprint start/end, major decisions |
|
||||
| **AUTONOMOUS** | Only critical decisions | Budget, production, architecture |
|
||||
|
||||
### Approval Categories
|
||||
|
||||
```python
|
||||
class ApprovalCategory(str, Enum):
|
||||
CRITICAL = "critical" # Always require approval
|
||||
MILESTONE = "milestone" # MILESTONE and FULL_CONTROL
|
||||
ROUTINE = "routine" # FULL_CONTROL only
|
||||
UNCERTAINTY = "uncertainty" # Low confidence decisions
|
||||
EXPERTISE = "expertise" # Agent requests human input
|
||||
```
|
||||
|
||||
### Approval Matrix
|
||||
|
||||
| Action | FULL_CONTROL | MILESTONE | AUTONOMOUS |
|
||||
|--------|--------------|-----------|------------|
|
||||
| Requirements approval | Required | Required | Required |
|
||||
| Architecture decisions | Required | Required | Required |
|
||||
| Sprint start | Required | Required | Auto |
|
||||
| Story implementation | Required | Auto | Auto |
|
||||
| PR merge | Required | Auto | Auto |
|
||||
| Sprint completion | Required | Required | Auto |
|
||||
| Budget threshold exceeded | Required | Required | Required |
|
||||
| Production deployment | Required | Required | Required |
|
||||
|
||||
### Database Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE approval_requests (
|
||||
id UUID PRIMARY KEY,
|
||||
project_id UUID NOT NULL,
|
||||
|
||||
-- What needs approval
|
||||
category VARCHAR(50) NOT NULL,
|
||||
action_type VARCHAR(100) NOT NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
context JSONB NOT NULL,
|
||||
|
||||
-- Who requested
|
||||
requested_by_agent_id UUID,
|
||||
requested_at TIMESTAMPTZ NOT NULL,
|
||||
|
||||
-- Status
|
||||
status VARCHAR(50) DEFAULT 'pending', -- pending, approved, rejected, expired
|
||||
decided_by_user_id UUID,
|
||||
decided_at TIMESTAMPTZ,
|
||||
decision_comment TEXT,
|
||||
|
||||
-- Timeout handling
|
||||
expires_at TIMESTAMPTZ,
|
||||
escalation_policy JSONB,
|
||||
|
||||
-- AI context
|
||||
confidence_score FLOAT,
|
||||
ai_recommendation VARCHAR(50),
|
||||
reasoning TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### Approval Service
|
||||
|
||||
```python
|
||||
class ApprovalService:
|
||||
async def request_approval(
|
||||
self,
|
||||
project_id: str,
|
||||
action_type: str,
|
||||
category: ApprovalCategory,
|
||||
context: dict,
|
||||
requested_by: str,
|
||||
confidence: float | None = None,
|
||||
ai_recommendation: str | None = None
|
||||
) -> ApprovalRequest:
|
||||
"""Create an approval request and notify stakeholders."""
|
||||
|
||||
project = await self.get_project(project_id)
|
||||
|
||||
# Check if approval needed based on autonomy level
|
||||
if not self._needs_approval(project.autonomy_level, category):
|
||||
return ApprovalRequest(status="auto_approved")
|
||||
|
||||
# Create request
|
||||
request = ApprovalRequest(
|
||||
project_id=project_id,
|
||||
category=category,
|
||||
action_type=action_type,
|
||||
context=context,
|
||||
requested_by_agent_id=requested_by,
|
||||
confidence_score=confidence,
|
||||
ai_recommendation=ai_recommendation,
|
||||
expires_at=datetime.utcnow() + self._get_timeout(category)
|
||||
)
|
||||
await self.db.add(request)
|
||||
|
||||
# Send notifications
|
||||
await self._notify_approvers(project, request)
|
||||
|
||||
return request
|
||||
|
||||
async def await_decision(
|
||||
self,
|
||||
request_id: str,
|
||||
timeout: timedelta = timedelta(hours=24)
|
||||
) -> ApprovalDecision:
|
||||
"""Wait for approval decision (used in workflows)."""
|
||||
deadline = datetime.utcnow() + timeout
|
||||
|
||||
while datetime.utcnow() < deadline:
|
||||
request = await self.get_request(request_id)
|
||||
|
||||
if request.status == "approved":
|
||||
return ApprovalDecision.APPROVED
|
||||
elif request.status == "rejected":
|
||||
return ApprovalDecision.REJECTED
|
||||
elif request.status == "expired":
|
||||
return await self._handle_expiration(request)
|
||||
|
||||
await asyncio.sleep(5)
|
||||
|
||||
return await self._handle_timeout(request)
|
||||
|
||||
async def _handle_timeout(self, request: ApprovalRequest) -> ApprovalDecision:
|
||||
"""Handle approval timeout based on escalation policy."""
|
||||
policy = request.escalation_policy or {"action": "block"}
|
||||
|
||||
if policy["action"] == "auto_approve":
|
||||
request.status = "auto_approved"
|
||||
return ApprovalDecision.APPROVED
|
||||
elif policy["action"] == "escalate":
|
||||
await self._escalate(request, policy["escalate_to"])
|
||||
return await self.await_decision(request.id, timedelta(hours=24))
|
||||
else: # block
|
||||
request.status = "expired"
|
||||
return ApprovalDecision.BLOCKED
|
||||
```
|
||||
|
||||
### Notification Channels
|
||||
|
||||
```python
|
||||
class ApprovalNotifier:
|
||||
async def notify(self, project: Project, request: ApprovalRequest):
|
||||
# SSE for real-time dashboard
|
||||
await self.event_bus.publish(f"project:{project.id}", {
|
||||
"type": "approval_required",
|
||||
"request_id": str(request.id),
|
||||
"title": request.title,
|
||||
"category": request.category
|
||||
})
|
||||
|
||||
# Email for async notification
|
||||
await self.email_service.send_approval_request(
|
||||
to=project.owner.email,
|
||||
request=request
|
||||
)
|
||||
|
||||
# Mobile push if configured
|
||||
if project.push_enabled:
|
||||
await self.push_service.send(
|
||||
user_id=project.owner_id,
|
||||
title="Approval Required",
|
||||
body=request.title,
|
||||
data={"request_id": str(request.id)}
|
||||
)
|
||||
```
|
||||
|
||||
### Batch Approval UI
|
||||
|
||||
For FULL_CONTROL mode with many routine approvals:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ APPROVAL QUEUE (12 pending) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ ☑ PR #45: Add user authentication [ROUTINE] 2h ago │
|
||||
│ ☑ PR #46: Fix login validation [ROUTINE] 2h ago │
|
||||
│ ☑ PR #47: Update dependencies [ROUTINE] 1h ago │
|
||||
│ ☐ Sprint 4 Start [MILESTONE] 30m │
|
||||
│ ☐ Production Deploy v1.2 [CRITICAL] 15m │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ [Approve Selected (3)] [Reject Selected] [Review All] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Decision Context Display
|
||||
|
||||
```python
|
||||
class ApprovalContextBuilder:
|
||||
def build_context(self, request: ApprovalRequest) -> ApprovalContext:
|
||||
"""Build rich context for approval decision."""
|
||||
return ApprovalContext(
|
||||
summary=request.title,
|
||||
description=request.description,
|
||||
|
||||
# What the AI recommends
|
||||
ai_recommendation=request.ai_recommendation,
|
||||
confidence=request.confidence_score,
|
||||
reasoning=request.reasoning,
|
||||
|
||||
# Impact assessment
|
||||
affected_files=request.context.get("files", []),
|
||||
estimated_impact=request.context.get("impact", "unknown"),
|
||||
|
||||
# Agent info
|
||||
requesting_agent=self._get_agent_info(request.requested_by_agent_id),
|
||||
|
||||
# Quick actions
|
||||
approve_url=f"/api/approvals/{request.id}/approve",
|
||||
reject_url=f"/api/approvals/{request.id}/reject"
|
||||
)
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- Flexible autonomy levels support various client preferences
|
||||
- Real-time notifications ensure timely responses
|
||||
- Batch approval reduces friction in FULL_CONTROL mode
|
||||
- AI confidence routing escalates appropriately
|
||||
|
||||
### Negative
|
||||
- Approval latency can slow autonomous workflows
|
||||
- Complex state management for pending approvals
|
||||
|
||||
### Mitigation
|
||||
- Encourage MILESTONE mode for efficiency
|
||||
- Configurable timeouts with auto-approve options
|
||||
- Mobile notifications for quick responses
|
||||
|
||||
## Compliance
|
||||
|
||||
This decision aligns with:
|
||||
- FR-601-605: Human-in-the-loop requirements
|
||||
- FR-102: Autonomy level configuration
|
||||
|
||||
---
|
||||
|
||||
*This ADR establishes the client approval flow architecture for Syndarix.*
|
||||
Reference in New Issue
Block a user