mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:31:13 -05:00
Approve tool use
The user has approved the tool use.
This commit is contained in:
290
docs/ATOMIC_APPROVAL_TRANSACTIONS.md
Normal file
290
docs/ATOMIC_APPROVAL_TRANSACTIONS.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# Atomic Approval Transactions
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 1 of the atomic transaction RPC implementation has been completed. This replaces the error-prone manual rollback logic in the `process-selective-approval` edge function with a true PostgreSQL ACID transaction.
|
||||
|
||||
## Architecture
|
||||
|
||||
### OLD Flow (process-selective-approval)
|
||||
```
|
||||
Edge Function (2,759 lines) ──┐
|
||||
├─ Create entity 1 ├─ Manual rollback on error
|
||||
├─ Create entity 2 ├─ Network failure = orphaned data
|
||||
├─ Create entity 3 ├─ Edge function crash = partial state
|
||||
└─ Manual rollback if error─┘
|
||||
```
|
||||
|
||||
### NEW Flow (process-selective-approval-v2)
|
||||
```
|
||||
Edge Function (~200 lines)
|
||||
│
|
||||
└──> RPC: process_approval_transaction()
|
||||
│
|
||||
└──> PostgreSQL Transaction ───────────┐
|
||||
├─ Create entity 1 │
|
||||
├─ Create entity 2 │ ATOMIC
|
||||
├─ Create entity 3 │ (all-or-nothing)
|
||||
└─ Commit OR Rollback ──────────┘
|
||||
(any error = auto rollback)
|
||||
```
|
||||
|
||||
## Key Benefits
|
||||
|
||||
✅ **True ACID Transactions**: All operations succeed or fail together
|
||||
✅ **Automatic Rollback**: ANY error triggers immediate rollback
|
||||
✅ **Network Resilient**: Edge function crash = automatic rollback
|
||||
✅ **Zero Orphaned Entities**: Impossible by design
|
||||
✅ **Simpler Code**: Edge function reduced from 2,759 to ~200 lines
|
||||
|
||||
## Database Functions Created
|
||||
|
||||
### Main Transaction Function
|
||||
```sql
|
||||
process_approval_transaction(
|
||||
p_submission_id UUID,
|
||||
p_item_ids UUID[],
|
||||
p_moderator_id UUID,
|
||||
p_submitter_id UUID,
|
||||
p_request_id TEXT DEFAULT NULL
|
||||
) RETURNS JSONB
|
||||
```
|
||||
|
||||
### Helper Functions
|
||||
- `create_entity_from_submission()` - Creates entities (parks, rides, companies, etc.)
|
||||
- `update_entity_from_submission()` - Updates existing entities
|
||||
- `delete_entity_from_submission()` - Soft/hard deletes entities
|
||||
|
||||
### Monitoring Table
|
||||
- `approval_transaction_metrics` - Tracks performance, success rate, and rollbacks
|
||||
|
||||
## Feature Flag
|
||||
|
||||
The new flow is **disabled by default** to allow gradual rollout and testing.
|
||||
|
||||
### Enabling the New Flow
|
||||
|
||||
#### For Moderators (via Admin UI)
|
||||
1. Navigate to Admin Settings
|
||||
2. Find "Approval Transaction Mode" card
|
||||
3. Toggle "Use Atomic Transaction RPC" to ON
|
||||
4. Page will reload automatically
|
||||
|
||||
#### Programmatically
|
||||
```typescript
|
||||
// Enable
|
||||
localStorage.setItem('use_rpc_approval', 'true');
|
||||
|
||||
// Disable
|
||||
localStorage.setItem('use_rpc_approval', 'false');
|
||||
|
||||
// Check status
|
||||
const isEnabled = localStorage.getItem('use_rpc_approval') === 'true';
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### Basic Functionality ✓
|
||||
- [ ] Enable feature flag via admin UI
|
||||
- [ ] Approve a simple submission (1-2 items)
|
||||
- [ ] Verify entities created correctly
|
||||
- [ ] Check console logs for "Using edge function: process-selective-approval-v2"
|
||||
- [ ] Verify version history shows correct attribution
|
||||
|
||||
### Error Scenarios ✓
|
||||
- [ ] Submit invalid data → verify full rollback
|
||||
- [ ] Trigger validation error → verify no partial state
|
||||
- [ ] Kill edge function mid-execution → verify auto rollback
|
||||
- [ ] Check logs for "Transaction failed, rolling back" messages
|
||||
|
||||
### Concurrent Operations ✓
|
||||
- [ ] Two moderators approve same submission → one succeeds, one gets locked error
|
||||
- [ ] Verify only one set of entities created (no duplicates)
|
||||
|
||||
### Data Integrity ✓
|
||||
- [ ] Run orphaned entity check (see SQL query below)
|
||||
- [ ] Verify session variables cleared after transaction
|
||||
- [ ] Check `approval_transaction_metrics` for success rate
|
||||
|
||||
## Monitoring Queries
|
||||
|
||||
### Check for Orphaned Entities
|
||||
```sql
|
||||
-- Should return 0 rows after migration
|
||||
SELECT
|
||||
'parks' as table_name,
|
||||
COUNT(*) as orphaned_count
|
||||
FROM parks p
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM park_versions pv
|
||||
WHERE pv.park_id = p.id
|
||||
)
|
||||
AND p.created_at > NOW() - INTERVAL '24 hours'
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
'rides' as table_name,
|
||||
COUNT(*) as orphaned_count
|
||||
FROM rides r
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM ride_versions rv
|
||||
WHERE rv.ride_id = r.id
|
||||
)
|
||||
AND r.created_at > NOW() - INTERVAL '24 hours';
|
||||
```
|
||||
|
||||
### Transaction Success Rate
|
||||
```sql
|
||||
SELECT
|
||||
DATE_TRUNC('hour', created_at) as hour,
|
||||
COUNT(*) as total_transactions,
|
||||
COUNT(*) FILTER (WHERE success) as successful,
|
||||
COUNT(*) FILTER (WHERE rollback_triggered) as rollbacks,
|
||||
ROUND(AVG(duration_ms), 2) as avg_duration_ms,
|
||||
ROUND(100.0 * COUNT(*) FILTER (WHERE success) / COUNT(*), 2) as success_rate
|
||||
FROM approval_transaction_metrics
|
||||
WHERE created_at > NOW() - INTERVAL '24 hours'
|
||||
GROUP BY hour
|
||||
ORDER BY hour DESC;
|
||||
```
|
||||
|
||||
### Rollback Rate Alert
|
||||
```sql
|
||||
-- Alert if rollback_rate > 5%
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE rollback_triggered) as rollbacks,
|
||||
COUNT(*) as total_attempts,
|
||||
ROUND(100.0 * COUNT(*) FILTER (WHERE rollback_triggered) / COUNT(*), 2) as rollback_rate
|
||||
FROM approval_transaction_metrics
|
||||
WHERE created_at > NOW() - INTERVAL '1 hour'
|
||||
HAVING COUNT(*) FILTER (WHERE rollback_triggered) > 0;
|
||||
```
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues are detected after enabling the new flow:
|
||||
|
||||
### Immediate Rollback (< 5 minutes)
|
||||
```javascript
|
||||
// Disable feature flag globally (or ask users to toggle off)
|
||||
localStorage.setItem('use_rpc_approval', 'false');
|
||||
window.location.reload();
|
||||
```
|
||||
|
||||
### Data Recovery (if needed)
|
||||
```sql
|
||||
-- Identify submissions processed with v2 during problem window
|
||||
SELECT
|
||||
atm.submission_id,
|
||||
atm.created_at,
|
||||
atm.success,
|
||||
atm.error_message
|
||||
FROM approval_transaction_metrics atm
|
||||
WHERE atm.created_at BETWEEN '2025-11-06 19:00:00' AND '2025-11-06 20:00:00'
|
||||
AND atm.success = false
|
||||
AND atm.rollback_triggered = true;
|
||||
|
||||
-- Check for orphaned entities (if any exist)
|
||||
-- Use the orphaned entity query above
|
||||
```
|
||||
|
||||
## Success Metrics
|
||||
|
||||
After full rollout, these metrics should be achieved:
|
||||
|
||||
| Metric | Target | Current |
|
||||
|--------|--------|---------|
|
||||
| Zero orphaned entities | 0 | ✓ TBD |
|
||||
| Zero manual rollback logs | 0 | ✓ TBD |
|
||||
| Transaction success rate | >99% | ✓ TBD |
|
||||
| Avg transaction time | <500ms | ✓ TBD |
|
||||
| Rollback rate | <1% | ✓ TBD |
|
||||
|
||||
## Deployment Phases
|
||||
|
||||
### Phase 1: ✅ COMPLETE
|
||||
- [x] Create RPC functions (helper + main transaction)
|
||||
- [x] Create new edge function v2
|
||||
- [x] Add feature flag support to frontend
|
||||
- [x] Create admin UI toggle
|
||||
- [x] Add monitoring table + RLS policies
|
||||
|
||||
### Phase 2: 🟡 IN PROGRESS
|
||||
- [ ] Test with single moderator account
|
||||
- [ ] Monitor metrics for 24 hours
|
||||
- [ ] Verify zero orphaned entities
|
||||
- [ ] Collect feedback from test moderator
|
||||
|
||||
### Phase 3: 🔲 PENDING
|
||||
- [ ] Enable for 10% of requests (weighted sampling)
|
||||
- [ ] Monitor for 24 hours
|
||||
- [ ] Check rollback rate < 1%
|
||||
|
||||
### Phase 4: 🔲 PENDING
|
||||
- [ ] Enable for 50% of requests
|
||||
- [ ] Monitor for 48 hours
|
||||
- [ ] Compare performance metrics with old flow
|
||||
|
||||
### Phase 5: 🔲 PENDING
|
||||
- [ ] Enable for 100% of requests
|
||||
- [ ] Monitor for 1 week
|
||||
- [ ] Mark old edge function as deprecated
|
||||
|
||||
### Phase 6: 🔲 PENDING
|
||||
- [ ] Remove old edge function
|
||||
- [ ] Archive manual rollback code
|
||||
- [ ] Update all documentation
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Feature flag not working
|
||||
**Symptom**: Logs still show "process-selective-approval" even with flag enabled
|
||||
**Solution**: Clear localStorage and reload: `localStorage.clear(); window.location.reload()`
|
||||
|
||||
### Issue: "RPC function not found" error
|
||||
**Symptom**: Edge function fails with "process_approval_transaction not found"
|
||||
**Solution**: Run the migration again or check function exists:
|
||||
```sql
|
||||
SELECT proname FROM pg_proc WHERE proname = 'process_approval_transaction';
|
||||
```
|
||||
|
||||
### Issue: High rollback rate (>5%)
|
||||
**Symptom**: Many transactions rolling back in metrics
|
||||
**Solution**:
|
||||
1. Check error messages in `approval_transaction_metrics.error_message`
|
||||
2. Disable feature flag immediately
|
||||
3. Investigate root cause (validation issues, data integrity, etc.)
|
||||
|
||||
### Issue: Orphaned entities detected
|
||||
**Symptom**: Entities exist without corresponding versions
|
||||
**Solution**:
|
||||
1. Disable feature flag immediately
|
||||
2. Run orphaned entity query to identify affected entities
|
||||
3. Investigate cause (likely edge function crash during v1 flow)
|
||||
4. Consider data cleanup (manual deletion or version creation)
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Can I switch back to the old flow without data loss?**
|
||||
A: Yes. Simply toggle off the feature flag. All data remains intact.
|
||||
|
||||
**Q: What happens if the edge function crashes mid-transaction?**
|
||||
A: PostgreSQL automatically rolls back the entire transaction. No orphaned data.
|
||||
|
||||
**Q: How do I know which flow approved a submission?**
|
||||
A: Check `approval_transaction_metrics` table. If a row exists, v2 was used.
|
||||
|
||||
**Q: Can I use both flows simultaneously?**
|
||||
A: Yes. The feature flag is per-browser, so different moderators can use different flows.
|
||||
|
||||
**Q: When will the old flow be removed?**
|
||||
A: After 30 days of stable operation at 100% rollout (Phase 6).
|
||||
|
||||
## References
|
||||
|
||||
- [Moderation Documentation](./versioning/MODERATION.md)
|
||||
- [JSONB Elimination](./JSONB_ELIMINATION_COMPLETE.md)
|
||||
- [Error Tracking](./ERROR_TRACKING.md)
|
||||
- [PostgreSQL Transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html)
|
||||
- [ACID Properties](https://en.wikipedia.org/wiki/ACID)
|
||||
100
src/components/admin/ApprovalTransactionToggle.tsx
Normal file
100
src/components/admin/ApprovalTransactionToggle.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { AlertCircle, CheckCircle2, Database } from 'lucide-react';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
||||
/**
|
||||
* Admin toggle for switching between approval flows:
|
||||
* - OLD: Manual rollback in edge function (error-prone)
|
||||
* - NEW: Atomic PostgreSQL transaction (true ACID guarantees)
|
||||
*/
|
||||
export function ApprovalTransactionToggle() {
|
||||
const [useRpcApproval, setUseRpcApproval] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Read feature flag from localStorage
|
||||
const enabled = localStorage.getItem('use_rpc_approval') === 'true';
|
||||
setUseRpcApproval(enabled);
|
||||
}, []);
|
||||
|
||||
const handleToggle = (checked: boolean) => {
|
||||
localStorage.setItem('use_rpc_approval', checked ? 'true' : 'false');
|
||||
setUseRpcApproval(checked);
|
||||
|
||||
// Force page reload to ensure all components pick up the new setting
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Database className="w-5 h-5" />
|
||||
Approval Transaction Mode
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Control which approval flow is used for moderation
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="rpc-approval-toggle" className="flex-1">
|
||||
<div className="font-medium">Use Atomic Transaction RPC</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{useRpcApproval
|
||||
? 'Using process-selective-approval-v2 (NEW)'
|
||||
: 'Using process-selective-approval (OLD)'}
|
||||
</div>
|
||||
</Label>
|
||||
<Switch
|
||||
id="rpc-approval-toggle"
|
||||
checked={useRpcApproval}
|
||||
onCheckedChange={handleToggle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{useRpcApproval ? (
|
||||
<Alert>
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<strong>Atomic Transaction Mode Enabled</strong>
|
||||
<ul className="mt-2 space-y-1 text-sm">
|
||||
<li>✅ True ACID transactions</li>
|
||||
<li>✅ Automatic rollback on errors</li>
|
||||
<li>✅ Network-resilient (edge function crash = auto rollback)</li>
|
||||
<li>✅ Zero orphaned entities</li>
|
||||
</ul>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<strong>Legacy Mode Active</strong>
|
||||
<ul className="mt-2 space-y-1 text-sm">
|
||||
<li>⚠️ Manual rollback logic (error-prone)</li>
|
||||
<li>⚠️ Risk of orphaned entities if edge function crashes</li>
|
||||
<li>⚠️ No true atomicity guarantee</li>
|
||||
</ul>
|
||||
<p className="mt-2 font-medium">
|
||||
Consider enabling Atomic Transaction Mode for improved reliability.
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="text-xs text-muted-foreground pt-2 border-t">
|
||||
<p><strong>Testing Instructions:</strong></p>
|
||||
<ol className="list-decimal list-inside space-y-1 mt-1">
|
||||
<li>Enable the toggle and approve a submission</li>
|
||||
<li>Check logs for "Using edge function: process-selective-approval-v2"</li>
|
||||
<li>Verify no orphaned entities in the database</li>
|
||||
<li>Test error scenarios (invalid data, network issues)</li>
|
||||
</ol>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -177,14 +177,42 @@ export async function approvePhotoSubmission(
|
||||
* @param itemIds - Array of item IDs to approve
|
||||
* @returns Action result
|
||||
*/
|
||||
/**
|
||||
* Feature flag to enable atomic transaction RPC approval flow.
|
||||
* Set to true to use the new process-selective-approval-v2 edge function
|
||||
* that wraps the entire approval in a single PostgreSQL transaction.
|
||||
*
|
||||
* Benefits of v2:
|
||||
* - True atomic transactions (all-or-nothing)
|
||||
* - Automatic rollback on ANY error
|
||||
* - Network-resilient (edge function crash = auto rollback)
|
||||
* - Eliminates orphaned entities
|
||||
*
|
||||
* To enable: localStorage.setItem('use_rpc_approval', 'true')
|
||||
* To disable: localStorage.setItem('use_rpc_approval', 'false')
|
||||
*/
|
||||
const USE_RPC_APPROVAL = typeof window !== 'undefined' &&
|
||||
localStorage.getItem('use_rpc_approval') === 'true';
|
||||
|
||||
export async function approveSubmissionItems(
|
||||
supabase: SupabaseClient,
|
||||
submissionId: string,
|
||||
itemIds: string[]
|
||||
): Promise<ModerationActionResult> {
|
||||
try {
|
||||
// Use v2 edge function if feature flag is enabled
|
||||
const edgeFunctionName = USE_RPC_APPROVAL
|
||||
? 'process-selective-approval-v2'
|
||||
: 'process-selective-approval';
|
||||
|
||||
console.log(`[Approval] Using edge function: ${edgeFunctionName}`, {
|
||||
submissionId,
|
||||
itemCount: itemIds.length,
|
||||
useRpcApproval: USE_RPC_APPROVAL
|
||||
});
|
||||
|
||||
const { data: approvalData, error: approvalError, requestId } = await invokeWithTracking(
|
||||
'process-selective-approval',
|
||||
edgeFunctionName,
|
||||
{
|
||||
itemIds,
|
||||
submissionId,
|
||||
|
||||
@@ -42,6 +42,9 @@ verify_jwt = true
|
||||
[functions.process-selective-approval]
|
||||
verify_jwt = false
|
||||
|
||||
[functions.process-selective-approval-v2]
|
||||
verify_jwt = false
|
||||
|
||||
[functions.send-escalation-notification]
|
||||
verify_jwt = true
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Enable RLS on approval_transaction_metrics table
|
||||
ALTER TABLE approval_transaction_metrics ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Policy: Only moderators and admins can view metrics
|
||||
CREATE POLICY "Moderators can view approval metrics"
|
||||
ON approval_transaction_metrics
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM user_roles
|
||||
WHERE user_roles.user_id = auth.uid()
|
||||
AND user_roles.role IN ('moderator', 'admin', 'superuser')
|
||||
)
|
||||
);
|
||||
|
||||
-- Policy: System can insert metrics (SECURITY DEFINER functions)
|
||||
CREATE POLICY "System can insert approval metrics"
|
||||
ON approval_transaction_metrics
|
||||
FOR INSERT
|
||||
TO authenticated
|
||||
WITH CHECK (true);
|
||||
|
||||
COMMENT ON POLICY "Moderators can view approval metrics" ON approval_transaction_metrics IS
|
||||
'Allows moderators, admins, and superusers to view approval transaction metrics for monitoring and analytics';
|
||||
|
||||
COMMENT ON POLICY "System can insert approval metrics" ON approval_transaction_metrics IS
|
||||
'Allows the process_approval_transaction function to log metrics. The function is SECURITY DEFINER so it runs with elevated privileges';
|
||||
Reference in New Issue
Block a user