nis2-agile/docs/STANDARD_MARKETING_TENANT_PROVISIONING.md
DevEnv nis2-agile c0bf7b6c15 [DOCS] Standard cross-suite AgileHub + governance CLAUDE.md + registri agent
- CLAUDE.md: TZ, SSO, vault-steward, versioning, persona v2.0, multitenant, KB RAG
- docs/standards: persona-conversational-rules v2.0
- docs/STANDARD_*: installer-integration, email-relay, AI-prodotto, marketing-tenant, multitenant
- AGENT_CHANGES.md + OPEN_TICKETS.md (registri agent automatico)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 15:41:54 +02:00

16 KiB
Raw Permalink Blame History

Standard marketing-tenant-provisioning v1.4

Owner: AgileHub MARKETER agent (governance) + TITAN (esecuzione DKIM) + MAESTRO (orchestratore atomico AC30) Stato: adopted (LIVE 2026-04-26 mattina v1.3 — DKIM per-tenant; v1.4 mattina — AC30 pronto in branch, deploy pending trigger) Applies to: * (tutti i prodotti suite — TRPG, SUSTAINAI, NIS2, LG231, TAXAI, DFM, MKTG, ALLRISK, WMS, MADEBYCLOUD, AGILEHUB) Versione: 1.4 MS implementatore: nexus-marketing-ms (porta 4221) + email-automation-ms (porta 4004) + OpenDKIM milter Postfix Helsinki + agilehub-workflow-engine (porta 4230, blocco AWE AC30_MarketingTenantProvision) Endpoint base: /api/marketing/*


1. Scopo

Lo standard definisce il contratto cross-suite tra prodotti consumer e modulo Marketing AgileHub centralizzato. Ogni prodotto (TRPG come pilot) consuma 28 endpoint stabili /api/marketing/* per fornire ai propri clienti (consulting firm) un servizio email marketing white-label multi-tenant.

2. Multi-tenancy

Concetto Identificativo Source of truth
Tenant marketing tenant_id (es. tnt_agile-technology_b4f9a3141333) nexus_marketing_db.tenants
Consulting firm consumer consulting_firm_id (FK logico) DB del prodotto consumer (es. trpg.consulting_firms)
firm_slug derivato da slugify(consulting_firm.name) calcolato server-side al provisioning
API key per-tenant ah_live_* (prod) / ah_test_* (sandbox) nexus_marketing_db.api_keys

Vincolo: 1:1 tra tenant_id e consulting_firm_id nel prodotto consumer (nessuna 1:N).

Naming convention firm_slug (formalizzato v1.1):

  • Algoritmo: slugify(consulting_firm.name) — lowercase ASCII, separatore -, rimozione caratteri non alfanumerici, max 64 char
  • Esempi:
    • "Tremolada Consulting S.r.l."tremolada-consulting
    • "Agile Technology s.r.l."agile-technology
    • "O'Brien & Co."obrien-co
  • tenant_id auto-generato server-side: tnt_${firm_slug}_${randomId(6)} (esempio: tnt_agile-technology_b4f9a3141333)
  • Sottodominio derivato: {firm_slug}.agile.software (es. agile-technology.agile.software)
  • Reply-to default: noreply@{firm_slug}.agile.software
  • Il consumer NON costruisce mai il firm_slug autonomamente: lo riceve in risposta a POST /admin/tenants e lo persiste come dato derivato

3. Autenticazione

  • Header obbligatorio: X-AgileHub-Key: <api_key_plaintext>
  • Header obbligatorio: X-AgileHub-API-Version: 1.0
  • Hash SHA-256 della key in DB (api_keys.key_hash); plaintext mai persistito leggibile (cifrato AES-256-GCM in api_keys.key_encrypted)
  • Provisioning admin via endpoint interno /api/marketing/admin/tenants/:id/api-keys (header X-Internal-Key solo Esperto Agile/installer)

4. API versioning policy

  • Header request: X-AgileHub-API-Version: 1.0 (obbligatorio)
  • Header response: X-AgileHub-API-Version: 1.0 (echo)
  • Header response: X-Request-Id: req_<uuid> (cross-system tracing)
  • Deprecation policy: 6 mesi notice (X-Deprecation-Date) + 6 mesi sunset (X-Sunset-Date) prima rimozione versione precedente
  • Canary opt-in: X-API-Canary: true su feature flag tenant

5. Rate limiting

Endpoint family Default Override
Read endpoints 60 req/min
/contacts/import 10 req/min RATE_LIMIT_IMPORT env
/segments/preview 20 req/min RATE_LIMIT_PREVIEW env
Tracking pixel/click nessuno (pubblico)

Header response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Risposta 429 con retry_after_sec.

6. Error envelope

{
  "error": {
    "code": "STRING_CODE",
    "message": "Human readable",
    "details": { ... opzionale ... }
  },
  "request_id": "req_<uuid>"
}

Codici stabili documentati: MISSING_API_KEY, INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED, TENANT_NOT_FOUND, TENANT_DELETED, TENANT_SUSPENDED, UNSUPPORTED_API_VERSION, RATE_LIMITED, DSL_INVALID, DSL_TOO_DEEP, DSL_FIELD_NOT_ALLOWED, DSL_OP_NOT_ALLOWED, CONTACT_SUPPRESSED, CONTACT_NOT_FOUND, INVALID_EMAIL, CAMPAIGN_NOT_FOUND, CAMPAIGN_LOCKED, CAMPAIGN_FINAL, CAMPAIGN_NOT_SCHEDULABLE, NO_RECIPIENTS, TEMPLATE_NOT_FOUND, TEMPLATE_INVALID, TEMPLATE_REQUIRED, EXPORT_JOB_NOT_FOUND, IMPORT_JOB_NOT_FOUND, INTERNAL_ERROR.

7. SLA

Indicatore Target Note
Uptime 99.5% rolling 30gg Allineato altri MS suite
p95 GET <500ms Cache Redis 30s su preview
p95 POST <2000ms DB persist + audit + idempotency
/segments/preview 50k contatti <2s Cache 30s + indici DB
RTO 30 min mysqldump nightly + restore documentato
RPO 1h Replication MySQL slave (futuro)

8. GDPR compliance

  • Double opt-in obbligatorio (POST /contacts con consent_pre_confirmed=false invia email confirm)
  • Footer unsubscribe RFC 8058 auto in tutti i template (one-click List-Unsubscribe + List-Unsubscribe-Post)
  • Suppression list permanente per-tenant (unsubscribe, complaint, hard_bounce, gdpr_request)
  • Hard delete contact (POST /contacts/:id/hard-delete) ritorna audit_trail_id
  • Export ZIP portabilità (POST /exports) — contacts.csv + campaigns.json + suppression.csv
  • Offboarding tenant: 90gg grace → hard delete (zero retention post-offboard)
  • Retention metrics_events: 90gg rolling, cron pulizia notturna

9. Branding tenant

  • Sottodominio {firm-slug}.agile.software per envelope From
  • 4 placeholder firm: firm.name, firm.logo_url, firm.primary_color, firm.accent_color
  • 3 personalization tag contact: contact.first_name, contact.last_name, contact.email
  • Reply-to configurabile per tenant
  • DKIM selector dedicato per tenant (deliverability isolation)

10. 28 endpoint

Vedi /var/www/agile-services/nexus-marketing-ms/src/routes/* o ticket TICKET_AGILEHUB_MARKETING_API.md per spec completa. Riassunto:

  • Sprint 2 (5 read-only): /tenants/me, /contacts GET, /campaigns GET/{id}, /templates GET, /metrics/overview
  • Sprint 3 (12 write): contacts POST, segments POST/GET/preview, campaigns POST/PATCH/schedule/cancel, templates render, test-send, metrics per-campaign
  • Sprint 4 (10 GDPR): contacts PATCH/DELETE/hard-delete/import, exports POST/{id}, quota, admin tenants suspend/resume/delete

11. Sandbox

  • URL primary: https://agilehub-staging.agile.software/api/marketing (LIVE 2026-04-25)
  • URL secondary (legacy): https://staging.agilehub.agile.software/api/marketing
  • Tenant test corrente: tnt_agile-technology_b4f9a3141333 (plan='enterprise_test', quota_default=5000)
  • Tenant precedente: tnt_test_tremolada_001 (suspended 2026-04-25 sera, swap-out per testing su entità Agile Technology s.r.l.)
  • Key dedicata: ah_test_* (mai mescolare con ah_live_*)
  • DB: nexus_marketing_db (clone notturno cron)
  • Subdomain tenant: {firm_slug}.agile.software (es. agile-technology.agile.software) — zone Cloudflare-managed, automazione full
  • DNS deliverability sandbox: SPF + DMARC + DKIM tutti active (LIVE 2026-04-26 mattina)
  • DKIM signing: OpenDKIM milter su Postfix Helsinki, selector agile2026, RSA 2048, rotation annuale Q1

12. Deroghe

I prodotti consumer non possono opporsi unilateralmente. Per esenzioni: aprire ticket AgileHub con tag standard-exception + motivazione tecnica + termine. MARKETER + REGENT decidono entro 5gg lavorativi.

13. Distribuzione

Prodotto docs_file claude_md_section claude_memory adoption_status
trpg TODO TODO TODO pending → acknowledged after deploy
sustainai TBD M5 pending
nis2 TBD M5 pending
altri 8 TBD post-Tremolada pending

14. Riferimenti

  • Spec produttore TRPG: /var/www/trpg-agile/docs/PROMPT_MARKETING_FEATURE.md v1.3
  • Spec contratto: /var/www/trpg-agile/docs/TICKET_AGILEHUB_MARKETING_API.md
  • Doc agente MARKETER: docs/AGENT_MARKETER.md
  • Reply formale TRPG: docs/REPLY_TO_TRPG_MARKETING_REQUEST_DRAFT.md
  • Audit gap: docs/HANDOVER_FROM_TRPG_MARKETING_FEATURE_REQUEST.md

15. Changelog

v1.4 (2026-04-26 mattina, AC30 blocco AWE pronto in branch — MAESTRO impl + 11/11 test)

  • Nuovo blocco AWE AC30_MarketingTenantProvision implementato + testato (619 righe blocco + 397 righe test, file untracked nel branch feature/awe-m1-m2-foundation)
  • Orchestrazione atomica end-to-end provisioning tenant marketing in 1 nodo workflow: tenant create + 3 DNS Cloudflare (A + SPF + DMARC) + DKIM SSH exec dkim-provision.js + DPA opzionale + API key generation
  • Compensation pattern: hook compensate(ctx, result) engine-level + rollback inline su partial-state failure (cancella DNS records, revoca DKIM via --revoke, soft-delete tenant 90gg grace)
  • Idempotency H7: sha256(runId + nodeId + {firmSlug, firmLegalName}) TTL 7gg
  • Dry-run mode completo (mock conforme outputSchema, no side-effect)
  • Vault integration: tier1__nexus-hub-ms__cloudflare/api_token per CF token, INTERNAL_SERVICE_KEY env per X-Internal-Key marketing-ms admin endpoints
  • SSH credential: pattern loadAndDecrypt(sshCredentialId) riusato da AC23
  • Auto-loaded dal catalog registry (src/blocks/index.js) come 13° blocco action
  • Test verdi: 11/11 (6 scenari §9 handover MAESTRO + 3 schema/shape + 2 dryRun) — happy path + rollback DNS + rollback DKIM + skipDkim + firm_slug duplicato + idempotency
  • Trigger originale Q3 2026 (≥3 tenant prod), implementazione anticipata disponibile on-demand
  • Decisione GO/NO-GO deploy: pending — richiede pm2 reload agilehub-workflow-engine + bump health.js block count + smoke E2E contro marketing-ms reale
  • Spec autoritativa: HANDOVER_AC30_MARKETING_TENANT_PROVISION_FOR_MAESTRO.md
  • Coordinamento agenti (al deploy): MAESTRO (impl) + TITAN (SSH exec coord) + MARKETER (acceptance + bump v1.5 con esempio workflow JSON pubblicato) + PRISMA (form auto-render verify) + VIGILE (security inventory blocco side-effect)

v1.3 (2026-04-26 mattina, DKIM per-tenant LIVE — TITAN Phase 1-4)

  • DKIM signing OPERATIVO: OpenDKIM 2.11.0 installato su Postfix Helsinki, milter inet:localhost:8891, milter_default_action=accept (fail-open per zero downtime)
  • Schema scope chiave: 1 keypair RSA 2048 per-tenant, selector agile2026._domainkey.{firm_slug}.agile.software. Pattern conferma standard §16 (deliverability isolation per-tenant).
  • Tenant Agile Technology: keypair generata + DNS TXT pubblicato Cloudflare + KeyTable + SigningTable + DB UPDATE → dkim_status='active'
  • Automation script: scripts/dkim-provision.js self-contained Node.js (no npm install, mysql CLI + https native + execSync). Idempotente. Eseguito on-host Hetzner.
  • §16 ggiornato: Strategia DKIM sandbox vs prod rimossa "skip sandbox" (DKIM ora attivo anche in sandbox per testing realistico). Mantengono dkim_status='not_required' come stato simbolico per tenant esplicitamente esentati.
  • Rotation policy: annuale Q1 (Q1 2027 prossimo turno), dual-key 30gg overlap durante transizione, coordinata con VIGILE Pillar 1
  • Backward compat: 99.994% del traffico Postfix Helsinki (cron, www-data, noreply@agile.software) NON matcha SigningTable → continua identica a oggi (15.466 email/7gg invariate, verificato empiricamente)

v1.2 (2026-04-25 sera, post-allineamento Cristiano TRPG — stesso giorno di v1.1)

  • Convention dominio cambiata: *.agilehub.it*.agile.software (correzione architetturale: Agile ha un solo dominio commerciale agile.software, già zone Cloudflare-managed per altri sottodomini come trpg.agile.software, agilehub.agile.software)
  • Schema tenant_branding.dkim_status: ALTER additive enum, aggiunto valore not_required per sandbox/non-prod (oltre a pending/active/failed)
  • Tenant tnt_agile-technology_b4f9a3141333: UPDATE branding subdomain → agile-technology.agile.software, reply_to → noreply@agile-technology.agile.software, dkim_status=not_required, spf_status=active, dmarc_status=active
  • §16 riscritto: sostituito vecchio scenario register.it con scenario reale Cloudflare-managed
  • §11 Sandbox: aggiornato per nuovo subdomain pattern + DNS deliverability LIVE
  • DKIM strategia: skipped per sandbox, mandatory per produzione (go-live Tremolada 23/6 — vedi §16)

v1.1 (2026-04-25 sera, superseded by v1.2 stesso giorno)

  • §2 Multi-tenancy: formalizzato firm_slug = slugify(consulting_firm.name) come algoritmo derivato server-side. Aggiunti 3 esempi concreti, regola tenant_id = tnt_${firm_slug}_${randomId(6)}, derivazione automatica sottodominio + reply-to.
  • §11 Sandbox: aggiornato tenant corrente a tnt_agile-technology_b4f9a3141333 (sostituisce tnt_test_tremolada_001 suspended)
  • v1.1 documentava convention *.agilehub.it (corretta in v1.2 stesso giorno)

16. Gestione zone DNS *.agile.software + deliverability

Provider DNS authoritative: Cloudflare (zone id e3d677355677a6397d2caa77264cbfa2). MX inbound: separato (Microsoft 365 su zone agilehub.it); modulo Marketing fa SOLO outbound, no impatto MX. Automazione: full via Cloudflare API (token in vault tier1__nexus-hub-ms__cloudflare/api_token).

Pattern record per tenant (provisioning automatico con MARKETER+TITAN):

Record Esempio Agile Technology Scope
A agile-technology.agile.software → 135.181.149.254 Helsinki Postfix outbound
TXT SPF v=spf1 ip4:135.181.149.254 -all autorizzazione mittente
TXT DMARC _dmarc.agile-technology.agile.software TXT v=DMARC1; p=none; rua=mailto:dmarc-reports@agile.software; pct=100 report-only modalità monitoring
TXT DKIM agile2026._domainkey.agile-technology.agile.software TXT v=DKIM1; k=rsa; p=<pubkey> solo PROD, skip sandbox

Strategia DKIM sandbox vs prod (v1.3 LIVE 2026-04-26):

  • DKIM mandatory per tutti i tenant produttivi (no skip). DMARC inizia p=none (monitoring) e bumppa p=quarantine poi p=reject quando deliverability stabile post-3 settimane di metriche pulite.
  • dkim_status='not_required': stato simbolico per tenant esplicitamente esentati (es. tenant disabilitati o test temporanei senza traffico real-world). Widget consumer lo mappa come "n/a".
  • Sandbox attiva DKIM by default dal v1.3: agile-technology.agile.software ha DKIM active per testing realistico pre-prod.

Effort provisioning per nuovo tenant prod (con script automation v1.3):

  1. POST /admin/tenants (auto-genera firm_slug + subdomain) — 1s
  2. POST 3 record Cloudflare base (A + SPF + DMARC) — manuale via curl o futuro workflow AWE — 10s
  3. node scripts/dkim-provision.js --tenant-id <id> --firm-slug <slug> ON HOST HETZNER (root):
    • genera keypair RSA 2048 (idempotente)
    • append KeyTable + SigningTable
    • publish DNS DKIM TXT su Cloudflare (idempotente, PATCH se esiste)
    • UPDATE tenant_branding.dkim_* + dkim_status='active'
    • systemctl reload opendkim hot
    • ~3-5 secondi end-to-end

Totale: ~15s tenant pronto. Future workflow AWE blocco AC30_MarketingTenantProvision Q3 2026 può chiamare script via webhook host.

Rotation policy DKIM (annuale Q1, coord VIGILE):

  1. Q1 ogni anno: VIGILE notifica scadenza
  2. TITAN: nuovo selector (es. agile2027 per Q1 2027), genera keypair nuovo
  3. Pubblica DNS DKIM nuovo (entrambi vecchio+nuovo coesistono per 30gg overlap)
  4. Aggiorna KeyTable + SigningTable per usare nuovo selector
  5. systemctl reload opendkim → email firmate con nuova chiave
  6. T+30gg: revoca vecchio selector (DELETE DNS vecchio + cleanup KeyTable/SigningTable)
  7. Audit log TITAN+VIGILE entry coordinated

Comandi rapidi rotation (futuro Phase 4 enhancement):

# T0: nuova chiave
node scripts/dkim-provision.js --tenant-id <id> --firm-slug <slug> --selector agile2027

# T+30gg: revoca vecchia
node scripts/dkim-provision.js --revoke --tenant-id <id> --firm-slug <slug> --selector agile2026