Bot

Bot Core

AI Router, message drafts, owner-review state machine, queue model, audit trail.

🔴

SENDING_ENABLED = false

Send gate is LOCKED — Red-Zone owner approval required to change.

Review State Machine — 8 States

draftpending owner reviewapprovedrejectededited pending reapprovalready to sendexpiredcancelled

Terminal states (red): rejected, expired, cancelled — no outgoing transitions.

Actions (7)

submit_for_reviewapproverejecteditresubmitexpirecancel

Transitions (14)

draft—submit_for_review→pending owner review
draft—cancel→cancelled
pending owner review—approve→approved
pending owner review—reject→rejected
pending owner review—edit→edited pending reapproval
pending owner review—expire→expired
approved—submit_for_review→ready to send
approved—cancel→cancelled
approved—expire→expired
edited pending reapproval—resubmit→pending owner review
edited pending reapproval—cancel→cancelled
edited pending reapproval—expire→expired
ready to send—cancel→cancelled
ready to send—expire→expired

State Machine Invariants

  1. The only path to ready_to_send is through approved.
  2. Any edit forces edited_pending_reapproval — re-approval is mandatory.
  3. rejected, expired, cancelled are terminal — no outgoing transitions.
  4. consentStatus !== 'confirmed' blocks submit_for_review.

Queue Model — OwnerReviewItem

draftId: string
state: ReviewState
submittedAt: string
reviewedAt: string | null
reviewerRef: 'owner'
decisionNote: string | null
priority: 'normal' | 'high'
expiresAfterHours: number (default 24)

Example Flows

Happy Path

{ "description": "Happy path: draft submitted, approved, ready to send.", "steps": [ { "from": "draft", "action": "submit_for_review", "expectedTo": "pending_owner_review" }, { "from": "pending_owner_review", "action": "approve", "expectedTo": "approved" }, { "from": "approved", "action": "submit_for_review", "expectedTo": "ready_to_send" } ], "invariantsVerified": { "consentConfirmed": true, "consentBlocksSubmit": true, "terminalCheck": true, "readyToSendNotTerminal": true } }

Rejection

{ "description": "Rejection: draft submitted, owner rejects. Terminal.", "steps": [ { "from": "draft", "action": "submit_for_review", "expectedTo": "pending_owner_review" }, { "from": "pending_owner_review", "action": "reject", "expectedTo": "rejected" } ], "invariantsVerified": { "rejectedIsTerminal": true, "noActionsFromRejected": true } }

Edit → Reapproval

{ "description": "Edit forces reapproval: draft → pending → edit → edited_pending_reapproval → resubmit → pending → approve → approved.", "steps": [ { "from": "draft", "action": "submit_for_review", "expectedTo": "pending_owner_review" }, { "from": "pending_owner_review", "action": "edit", "expectedTo": "edited_pending_reapproval" }, { "from": "edited_pending_reapproval", "action": "resubmit", "expectedTo": "pending_owner_review" }, { "from": "pending_owner_review", "action": "approve", "expectedTo": "approved" } ], "invariantsVerified": { "editForcesReapproval": true, "canResubmit": true, "editedPendingIsNotTerminal": true } }

Audit Trail — maskPhone

PII-safe phone masking. Never stores raw phone numbers in audit events.

+970-599-123456→+970***56
972501234567→972***67
1234→12**
null→null