Software Architecture Specification

OmniScout OS

Autonomous Marketing Operating System — architettura 100% serverless purista progettata per gestire centralmente il marketing di OmniaCoin e VIPTravelScout con supervisione umana garantita.

Cloudflare Workers Cloudflare Pages Supabase PostgreSQL Multi-Tenant Ready Human-in-the-Loop Server-Side Tracking Programmatic SEO
Moduli architetturali
05
CMO · Ads · pSEO · SST · DB
Infrastruttura
0
Server dedicati (Serverless Purista)
Ad Networks
3
Meta · Google · TikTok
Autonomia AI
Zero spesa senza approvazione umana
01

Il CMO Engine
— Orchestratore AI

Trigger Doppio
Cron Cloudflare ogni 4 ore + webhook HTTP su richiesta. Il ciclo è idempotente: se fallisce, riparte senza duplicati.
CRON: 0 */4 * * *
🧠
Claude Sonnet 4.5
Analizza KPI storici + dati GSC e genera 3 scenari di budget (Low/Medium/High) con previsioni ROAS, CPA e ad copy per ogni canale.
MODEL: claude-sonnet-4-5
🔒
Zero Spend Policy
Ogni proposta nasce con status: 'pending'. Nessuna chiamata API verso Meta/Google finché l'umano non approva esplicitamente.
status: pending → approved
📲
Telegram Notification
Notifica immediata all'admin con deep link alla dashboard, sommario dei 3 scenari e bottone inline per aprire la review.
Bot API + InlineKeyboard
workers/cmo-engine/index.js — Data Flow
┌──────────────────────────────────────────────────────────────────┐
│              [CF WORKER] cmo-engine-orchestrator                  │
│                                                                   │
│  INGRESS TRIGGERS                                                 │
│  ┌──────────────┐   ┌─────────────────┐   ┌─────────────────┐  │
│  │ Cron /4h     │   │ GSC Search API  │   │ SST Events     │  │
│  └──────┬───────┘   └────────┬────────┘   └────────┬────────┘  │
│         └────────────────────▼───────────────────────┘          │
│                              │                                   │
│                   ┌──────────▼──────────┐                       │
│                   │  DataAggregator()  │  ←──── Supabase RPC   │
│                   │  · KPI storici 30gg  │                       │
│                   │  · Budget spend/ch.  │                       │
│                   │  · Conv rate/ROAS    │                       │
│                   └──────────┬──────────┘                       │
│                              │                                   │
│                   ┌──────────▼──────────┐                       │
│                   │  Claude Analyzer()  │  ←──── Anthropic API  │
│                   │  · 3 scenari budget  │                       │
│                   │  · Forecast KPI      │                       │
│                   │  · Ad copy per ch.   │                       │
│                   └──────────┬──────────┘                       │
│                              │                                   │
│                   ┌──────────▼──────────┐                       │
│                   │  ProposalWriter()   │  ────► Supabase       │
│                   │  status: pending    │       budget_proposals│
│                   └──────────┬──────────┘                       │
│                              │                                   │
│                   ┌──────────▼──────────┐                       │
│                   │  NotifyAdmin()      │  ────► Telegram Bot   │
│                   └─────────────────────┘                       │
└──────────────────────────────────────────────────────────────────┘
JavaScript — CF Worker
// Claude AI Analysis — Modello predittivo 3 scenari
async function runClaudeAnalysis(data, tenant, env) {
  const response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    body: JSON.stringify({
      model: 'claude-sonnet-4-5',
      max_tokens: 4096,
      system: `Sei il CMO AI per ${tenant.portal_name}.
Rispondi SOLO con JSON valido. Nessun testo libero.`,
      messages: [{ role: 'user', content: buildCMOPrompt(data) }]
    })
  });
  const result = await response.json();
  return JSON.parse(result.content[0].text); // → { scenarios: {low,medium,high} }
}

// Ogni proposta nasce PAUSED — zero spesa finché status != 'approved'
await fetch(`${env.SUPABASE_URL}/rest/v1/budget_proposals`, {
  method: 'POST',
  body: JSON.stringify({
    tenant_id: tenant.id,
    scenario_type: 'medium',
    monthly_budget_eur: 5000,
    projected_kpis: { roas: 3.2, cpa_eur: 22.5, leads: 450 },
    status: 'pending',         // ← NESSUNA SPESA
    expires_at: new Date(Date.now() + 72 * 3600000).toISOString()
  })
});
02

Ad Networks &
Human-in-the-Loop

1
CMO Engine genera proposals
3 righe in budget_proposals con status: 'pending'. Notifica Telegram inviata all'admin con link alla dashboard.
2
Admin review su CF Pages Dashboard
Visualizza i 3 scenari Low/Medium/High con forecast KPI, preview creative e breakdown per canale. Sceglie quale approvare.
3
Click "APPROVE" → Approval Worker
PATCH /proposals/:id/approve — JWT verificato via Supabase Auth. Il worker aggiorna lo status e triggera il pipeline in ctx.waitUntil() (non blocca la response).
4
Creative Engine → Placid API
Fetcha asset da Cloudflare Images per tag semantici (es. "maldives", "luxury-resort"). Manda a Placid: testo Claude + foto CF Images + template ID → riceve URL immagini on-brand in tutti i formati.
5
Campaign Launcher → Meta / Google / TikTok Draft
Crea Campaign → AdSet → AdCreative → Ad su ogni piattaforma. TUTTI con status: 'PAUSED'. Zero budget speso. Salva gli ID esterni su Supabase.
6
Second Gate — Publish
Admin vede il draft live in Meta/Google Ads Manager. Click "PUBLISH" → API status: ACTIVE → SOLO ORA inizia la spesa. Status Supabase aggiornato a 'active'.
JavaScript — Meta Ads API v21.0
// Campaign creata SEMPRE con status PAUSED — nessuna spesa automatica
const campaign = await apiPost(`${baseUrl}/campaigns`, {
  name: `[OMNISCOUT][DRAFT] ${proposal.tenant_id} - MEDIUM`,
  objective: 'LEAD_GENERATION',
  status: 'PAUSED',                     // ← SEMPRE PAUSED
  spend_cap: Math.round(proposal.monthly_budget_eur * 0.5 * 100),
});

const adSet = await apiPost(`${baseUrl}/adsets`, {
  campaign_id: campaign.id,
  status: 'PAUSED',
  daily_budget: Math.round((proposal.monthly_budget_eur * 0.5 / 30) * 100),
  targeting: {
    interests: [{ id: '6003136214740', name: 'Maldives' }],  // luxury_travel
    income: [{ id: '6167250041607' }],             // Top 10% income
    age_min: 35, age_max: 65
  }
});

// Google Ads: Target CPA automatico in micros
const googleCampaign = {
  status: 'PAUSED',                               // ← SEMPRE PAUSED
  biddingStrategyType: 'TARGET_CPA',
  targetCpa: { targetCpaMicros: 22500000 },      // €22.50 CPA previsto
  campaignBudget: budgetResourceName
};
03

Live SEO &
Motore pSEO

📊
GSC Gap Analysis
Query con impressioni >50 e posizione >10 (pagina 2+). Il portale viene visto ma non clickato — golden opportunities.
impressions > 50 AND position > 10
🤖
Claude Haiku Scoring
Haiku (economico per batch) classifica intent, genera opportunity_score 0-100, page_type e H2 outline completo per ogni keyword gap.
MODEL: claude-haiku-4-5
🔗
Webhook HMAC Firmato
Il payload inviato al portale è firmato con HMAC-SHA256. Il portale verifica la firma prima di generare la pagina pSEO automatica.
X-OmniScout-Signature: sha256hex
📄
pSEO Auto-Generation
VIPTravelScout riceve il webhook e genera la pagina (es. "Soneva Jani vs Velaa Private Island") con Claude + dati strutturati.
status: pending → published
Keyword Score Intent Page Type Traffic Est. Status
Soneva Jani vs Velaa Private Island 87 commercial comparison ~2,400/mo pending
best luxury resorts Maldives 2025 81 commercial guide ~5,800/mo generating
Bitcoin price prediction Q4 2025 74 informational coin_analysis ~8,200/mo published
Four Seasons Bora Bora overwater bungalow price 79 transactional destination ~1,600/mo pending
Ethereum staking yield comparison 68 commercial comparison ~3,100/mo low priority
04

Server-Side Tracking
— L'Arma Segreta

🛡️
Anti-AdBlock + Cookie-less
Il tracker JS è <1KB, senza cookie di terze parti e senza dipendenze. Le chiamate server-to-server bypassano completamente AdBlock.
🔐
SHA-256 Hashing PII
Email, telefono, nome e cognome vengono hashati con SHA-256 (normalizzati lowercase) lato server prima di qualsiasi invio alle piattaforme. GDPR compliant.
KV Deduplication
Ogni event_id viene checkato nel CF KV Store con TTL 24h. Elimina doppi fire tra pixel browser-side e server-side, evitando sovra-conteggio conversioni.
🎯
Fan-out Asincrono
Promise.allSettled() garantisce che il fallimento su un canale non blocchi gli altri. Meta CAPI + Google EC + TikTok Events API in parallelo.
BROWSER → CF WORKER
SST Gateway
t.omniscout.io/e
📘
Meta CAPI
Graph API v21.0
🔵
Google EC
Ads API v18
🎵
TikTok Events
Business API v1.3
🗄️
Supabase
Raw events log
JavaScript — omniscout-tracker.js (Frontend, <1KB)
// Da includere su ogni portale — no cookie, no dipendenze, no AdBlock
(function() {
  const GATEWAY = 'https://t.omniscout.io/e';

  // Session ID effimero: sparisce alla chiusura del tab
  const sid = sessionStorage.getItem('_os_sid') || (() => {
    const id = crypto.randomUUID();
    sessionStorage.setItem('_os_sid', id);
    return id;
  })();

  window.OmniScoutTrack = async (eventType, data = {}) => {
    const payload = {
      event_type: eventType,
      event_id: crypto.randomUUID(),   // Per dedup server-side
      session_id: sid,
      page_url: window.location.href,
      referrer: document.referrer,
      fbclid: new URLSearchParams(location.search).get('fbclid'),
      gclid:  new URLSearchParams(location.search).get('gclid'),
      ...data   // { email?, value?, currency?, order_id? }
    };
    await fetch(GATEWAY, { method: 'POST', keepalive: true,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload) });
  };

  OmniScoutTrack('page_view'); // Auto-fire on load
})();
05

Schema Database
— Architettura Dati

tenants
MASTER TABLE
idPKUUID
nameVARCHAR(100)
slugVARCHAR(50) UNIQUE
verticalvertical_type
portal_urlTEXT
gsc_property_urlTEXT
pseo_webhook_urlTEXT
monthly_budget_cap_eurNUMERIC
brand_guidelinesJSONB
plan_tierVARCHAR(20)
budget_proposals
CORE: HiTL
idPKUUID
tenant_idFKUUID
scenario_typeENUM low|med|high
monthly_budget_eurNUMERIC
channel_splitJSONB
projected_kpisJSONB
ad_creatives_draftJSONB[]
statuspending|approved|…
approved_byFKauth.users
platform_draft_idsJSONB
expires_atTIMESTAMPTZ
marketing_campaigns
PERFORMANCE
idPKUUID
tenant_idFKUUID
channelad_channel ENUM
statuscampaign_status ENUM
external_campaign_idVARCHAR(100)
total_spend_eurNUMERIC
impressionsBIGINT
conversionsINTEGER
roasNUMERIC(8,4)
cpa_eurNUMERIC(10,2)
targeting_configJSONB
seo_opportunities
pSEO ENGINE
idPKUUID
tenant_idFKUUID
keywordVARCHAR(500)
keyword_normalizedGENERATED STORED
opportunity_scoreSMALLINT 0-100
intentseo_intent ENUM
page_typeseo_page_type ENUM
h2_outlineTEXT[]
statuspending→published→ranking
generated_page_urlTEXT
UNIQUE(tenant_id, keyword_normalized)
tracking_events
PARTITIONED BY MONTH
idPKUUID
tenant_idFKUUID
event_idVARCHAR UNIQUE
event_typeVARCHAR(50)
fbclid / gclid / ttclidVARCHAR(200)
value_eurNUMERIC
order_idVARCHAR(100)
sent_to_metaBOOLEAN
sent_to_googleBOOLEAN
send_errorsJSONB
PARTITION BY RANGE (created_at)
tenant_users
RLS MAPPING
tenant_idPK+FKUUID
user_idPK+FKauth.users
roleowner|admin|analyst|viewer
RLS Policy:
CREATE POLICY "isolation" ON *
FOR ALL USING (
tenant_id = ANY(
get_user_tenant_ids()
)
);
🔒
Row Level Security — Isolamento Multi-Tenant Garantito
Tutte le 6 tabelle hanno RLS abilitata. La funzione get_user_tenant_ids() ritorna solo i tenant_id a cui l'utente autenticato ha accesso. I Cloudflare Workers usano la SUPABASE_SERVICE_ROLE_KEY che bypassa RLS per default — accesso completo per operazioni backend server-to-server. La Dashboard Admin usa JWT utente — RLS attiva — isolamento garantito.