# Edut Secret System - Two-Factor Designation Spec ## Overview The Edut designation flow is a two-factor protocol: 1. Phone verification by SMS (Twilio). 2. Email verification by classified mailto request (Mailgun). Both factors bind to the same designation code and auth token. This is not a throwaway waitlist. It is the pre-launch identity envelope that can transition into launch activation workflows. ## End-to-End Flow ``` Visitor clicks orb on edut.ai -> phone input appears -> user submits phone POST /secret/initiate -> code issued -> row created/updated in SQLite -> SMS sent via Twilio User receives SMS and replies: CONFIRM Twilio POST /twilio/inbound -> signature verified -> pending designation matched -> phone_verified_at set Landing page polls /secret/status/{code} -> sees phone_verified=true -> opens mailto:{code}@secret.edut.ai with protocol body User sends email Mailgun POST /mailgun/inbound -> signature verified -> code parsed from recipient local-part -> email_verified_at set -> classified confirmation reply sent Landing page stores acknowledged token ``` ## Infrastructure | Service | Domain / System | Purpose | |---------|------------------|---------| | Landing UI | `edut.ai`, `edut.dev` | Orb, phone capture, mailto initiation | | Twilio | Toll-free SMS number | Outbound designation SMS + inbound CONFIRM replies | | Mailgun | `secret.edut.ai` | Inbound email processing + confirmation replies | | API host | `api.edut.ai` | `/secret/*`, `/twilio/inbound`, `/mailgun/inbound` | | Database | SQLite (`/var/lib/edut/secrets.db`) | Durable designation state | ## Twilio Channel Design ### Number strategy - Use a toll-free SMS number. - Voice is disabled or rejected. - Complete toll-free verification before production traffic. ### Outbound SMS template ``` ━━━━━━━━━━━━━━━━━ EDUT GOVERNANCE PROTOCOL Designation: {token} Reply CONFIRM to proceed. ━━━━━━━━━━━━━━━━━ ``` Where `{token}` is formatted from the code: - code: `0217073045482` - token: `0217-0730-4548-2` ## API Surface ## 1) Initiate ### `POST /secret/initiate` Request JSON: ```json { "phone": "+12065550123", "origin": "https://edut.ai" } ``` Behavior: 1. Normalize phone to E.164. 2. Rate-limit by IP, phone, and rolling time window. 3. Generate designation code (`MMDDHHmmssmmm`) and auth token. 4. Insert or upsert designation row with `phone` and `created_at`. 5. Send SMS via Twilio. 6. Return `200` with minimal response: ```json { "code": "0217073045482", "token": "0217-0730-4548-2", "status": "sms_sent" } ``` Notes: - Do not expose internal row id. - Never return auth token in API responses. ## 2) Status ### `GET /secret/status/{code}` Response JSON: ```json { "code": "0217073045482", "phone_verified": true, "email_verified": false, "status": "phone_verified" } ``` Rules: - Polling endpoint is read-only. - Response is minimal and does not expose phone/email values. - Apply rate limits to prevent enumeration. ## 3) Twilio inbound ### `POST /twilio/inbound` Expected Twilio fields include: - `From` (sender phone) - `To` (Edut toll-free number) - `Body` (message body) - `MessageSid` Verification: - Validate `X-Twilio-Signature` against Twilio auth token. - Reject unsigned or invalid payloads. Processing: 1. Normalize sender phone. 2. Accept `CONFIRM` (case-insensitive, trim whitespace). 3. Find latest pending designation for that phone within validity window. 4. Idempotency key: `MessageSid`. 5. Mark `phone_verified_at` and set state `phone_verified`. 6. Return `2xx` only after persistence. ## 4) Mailgun inbound ### `POST /mailgun/inbound` Expected Mailgun fields include: - `recipient` (`{code}@secret.edut.ai`) - `sender` - `Message-Id` - `subject` Verification: - Verify Mailgun webhook signature using Mailgun signing key. Processing: 1. Parse `code` from recipient local-part. 2. Require existing designation row with `phone_verified_at IS NOT NULL`. 3. Record `email`, `message_id`, and `email_verified_at`. 4. Idempotency key: `Message-Id` (or Mailgun event id fallback). 5. Send confirmation reply through Mailgun. ## Confirmation Email Template ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EDUT GOVERNANCE PROTOCOL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ACCESS REGISTRATION CONFIRMED Designation: #{designation_number} Auth Token: {auth_token} Classification: OBSERVER Status: ACKNOWLEDGED Phone: VERIFIED Email: VERIFIED Timestamp: {iso_timestamp} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ This designation is permanent and non-transferable. You will be notified when your access level changes. Do not reply to this message. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Edut LLC. All rights reserved. Development and licensing of deterministic governance systems. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` ## SQLite Schema ### File: `/var/lib/edut/secrets.db` ```sql CREATE TABLE IF NOT EXISTS designations ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL UNIQUE, phone TEXT, phone_verified_at DATETIME, email TEXT, email_verified_at DATETIME, recipient TEXT, message_id TEXT, auth_token TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending_phone', created_at DATETIME DEFAULT (datetime('now')), replied_at DATETIME, reply_status TEXT DEFAULT 'pending' ); CREATE INDEX idx_designations_code ON designations(code); CREATE INDEX idx_designations_phone ON designations(phone); CREATE INDEX idx_designations_email ON designations(email); CREATE INDEX idx_designations_status ON designations(status); CREATE INDEX idx_designations_created ON designations(created_at); CREATE TABLE IF NOT EXISTS twilio_inbound_events ( message_sid TEXT PRIMARY KEY, from_phone TEXT NOT NULL, body TEXT, received_at DATETIME DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS mailgun_inbound_events ( message_id TEXT PRIMARY KEY, sender TEXT, recipient TEXT, received_at DATETIME DEFAULT (datetime('now')) ); ``` ## Nginx Routing ### File: `/etc/nginx/sites-available/api.edut.ai` ```nginx server { listen 80; server_name api.edut.ai; location /secret/ { proxy_pass http://127.0.0.1:3847; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /twilio/inbound { proxy_pass http://127.0.0.1:3847; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /mailgun/inbound { proxy_pass http://127.0.0.1:3847; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ## Environment Variables ```bash SECRETS_DB_PATH=/var/lib/edut/secrets.db AUTH_SALT= # Twilio TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= TWILIO_FROM_NUMBER=<+1 toll-free number> # Mailgun MAILGUN_API_KEY= MAILGUN_DOMAIN=secret.edut.ai MAILGUN_SIGNING_KEY= ``` ## Error Handling and Idempotency - Twilio signature failure: return `403`. - Mailgun signature failure: return `403`. - Duplicate inbound event id: treat as idempotent success. - Transient DB/send failure: return `5xx` so provider retries. - Unknown or expired confirmation attempts: return `200` and no state mutation. ## Security Controls - Signature verification on both inbound providers. - Per-IP and per-phone initiate throttles. - Per-code polling throttles. - Do not expose auth token except in confirmation email. - Back up SQLite and protect file permissions. ## Launch Evolution At launch, each fully verified designation (`phone_verified_at` + `email_verified_at`) is eligible for activation messaging: - Classification can transition from `OBSERVER` to `OPERATOR`. - Auth token remains stable. - Message can include deployment handoff link or activation instructions. ## Important Boundary This two-factor designation flow is a pre-launch identity bootstrap. It does not replace runtime device trust anchors, workspace isolation controls, or offline license enforcement in the product runtime.