{
  "id": "beacon",
  "name": "Beacon",
  "version": "0.2.0",
  "maturity": "Stage 2 partial development: HOME-001 and CASE-011 have accepted full-clause evidence; D1-backed channel, notification, customer, receipt, Start, and widget seams have bounded implementation evidence. WIDGET-001\u2013005 client/configuration/contact verification and all other product clauses remain pending full-clause acceptance.",
  "category": "support",
  "spine": {
    "id": "conversation-desk",
    "record": "A conversation between a customer and a team, with separately raised cases as the work records over it."
  },
  "replaces": [
    {
      "name": "Intercom",
      "edition": null
    }
  ],
  "description": "A self-hosted conversation desk for a business's customer conversations and separately raised cases.",
  "capabilities": [
    {
      "id": "owner-front-door",
      "clauses": [
        "HOME-001"
      ]
    },
    {
      "id": "workspace-access",
      "clauses": [
        "OPS-001",
        "OPS-002",
        "OPS-003",
        "OPS-004",
        "OPS-005",
        "OPS-006"
      ]
    },
    {
      "id": "customer-channels",
      "clauses": [
        "CHAN-001",
        "CHAN-002",
        "CHAN-003",
        "CHAN-004",
        "CHAN-005",
        "CHAN-006",
        "CHAN-007",
        "CHAN-008",
        "CHAN-009",
        "CHAN-010",
        "CHAN-011",
        "CHAN-012",
        "CHAN-013",
        "CHAN-014",
        "CHAN-015",
        "CHAN-016",
        "WIDGET-001",
        "WIDGET-002",
        "WIDGET-003",
        "WIDGET-004",
        "WIDGET-005",
        "WIDGET-006"
      ]
    },
    {
      "id": "reviewed-outbound",
      "clauses": [
        "OUT-001",
        "OUT-002",
        "OUT-003",
        "OUT-004",
        "OUT-005"
      ]
    },
    {
      "id": "conversation-workbench",
      "clauses": [
        "CONV-001",
        "CONV-002",
        "CONV-003",
        "CONV-004",
        "CONV-005",
        "CONV-006",
        "CONV-007",
        "CONV-008",
        "CONV-009",
        "CONV-010",
        "CONV-011",
        "CONV-012",
        "CONV-013",
        "CONV-014"
      ]
    },
    {
      "id": "linked-cases",
      "clauses": [
        "CASE-001",
        "CASE-002",
        "CASE-003",
        "CASE-004",
        "CASE-005",
        "CASE-006",
        "CASE-007",
        "CASE-008",
        "CASE-009",
        "CASE-010",
        "CASE-011",
        "CASE-012",
        "CASE-013",
        "CASE-014",
        "CASE-015",
        "CASE-016",
        "CASE-017",
        "CASE-018",
        "CASE-019",
        "CASE-020",
        "CASE-021",
        "CASE-022",
        "CASE-023"
      ]
    },
    {
      "id": "customer-context",
      "clauses": [
        "CUST-001",
        "CUST-002",
        "CUST-003",
        "CUST-004",
        "CUST-005",
        "CUST-006",
        "CUST-007",
        "CUST-008",
        "CUST-009",
        "CUST-010"
      ]
    },
    {
      "id": "evidence-and-knowledge",
      "clauses": [
        "EVID-001",
        "EVID-002",
        "EVID-003",
        "EVID-004",
        "EVID-005",
        "EVID-006",
        "EVID-007",
        "EVID-008",
        "KNOW-001",
        "KNOW-002",
        "KNOW-003",
        "KNOW-004",
        "KNOW-005",
        "KNOW-006",
        "KNOW-007",
        "KNOW-008"
      ]
    },
    {
      "id": "ai-preparation",
      "clauses": [
        "AI-001",
        "AI-002",
        "AI-003",
        "AI-004",
        "AI-005",
        "AI-006",
        "AI-007",
        "AI-008",
        "AI-009",
        "AI-010",
        "AI-011",
        "AI-012",
        "AI-013",
        "AI-014",
        "AI-015",
        "AI-016",
        "AI-017",
        "AI-018"
      ]
    },
    {
      "id": "reviewed-decisions",
      "clauses": [
        "JUDG-001",
        "JUDG-002",
        "JUDG-003",
        "JUDG-004",
        "JUDG-005",
        "JUDG-006",
        "JUDG-007",
        "JUDG-008",
        "JUDG-009",
        "JUDG-010",
        "JUDG-011",
        "APPR-001",
        "APPR-002",
        "APPR-003",
        "APPR-004",
        "APPR-005",
        "APPR-006",
        "APPR-007",
        "APPR-008",
        "APPR-009"
      ]
    },
    {
      "id": "provider-actions",
      "clauses": [
        "ACT-001",
        "ACT-002",
        "ACT-003",
        "ACT-004",
        "ACT-005",
        "ACT-006",
        "ACT-007",
        "ACT-008",
        "ACT-009",
        "ACT-010",
        "ACT-011",
        "ACT-012",
        "ACT-013",
        "ACT-014",
        "ACT-015",
        "ACT-016",
        "ACT-017",
        "ACT-018",
        "ACT-019"
      ]
    },
    {
      "id": "receipts-and-notifications",
      "clauses": [
        "RCPT-001",
        "RCPT-002",
        "RCPT-003",
        "RCPT-004",
        "RCPT-005",
        "NOTIFY-001",
        "NOTIFY-002",
        "NOTIFY-003",
        "NOTIFY-004",
        "NOTIFY-005",
        "NOTIFY-006",
        "NOTIFY-007"
      ]
    },
    {
      "id": "reporting-and-setup",
      "clauses": [
        "ANALYTICS-001",
        "ANALYTICS-002",
        "ANALYTICS-003",
        "ANALYTICS-004",
        "ANALYTICS-005",
        "ANALYTICS-006",
        "SETUP-001"
      ]
    },
    {
      "id": "portable-data",
      "clauses": [
        "DATA-001",
        "DATA-002"
      ]
    },
    {
      "id": "intercom-import",
      "clauses": [
        "IMPORT-001"
      ]
    }
  ],
  "nonGoals": [
    "SMS and WhatsApp channel adapters are deferred beyond 1.0.",
    "In-app announcements, mobile push and carousel, product tours and checklists, and surveys are outside 1.0.",
    "A native mobile operator application is outside 1.0.",
    "Live autonomous AI replies, customer-message automation, and external-provider automation are outside 1.0.",
    "Generic workflow and runtime plugin systems are outside 1.0.",
    "Multiple workspaces or tenants are outside 1.0.",
    "Help Scout replacement and import are deferred until its report and importer qualify."
  ],
  "externals": [
    {
      "id": "mail-provider",
      "required": true,
      "why": "An external mail provider receives customer email and delivers email outside the deployment, which owned Cloudflare primitives cannot do.",
      "data": [
        "Customer and sender email addresses.",
        "Customer message headers, bodies, and permitted attachments.",
        "Delivery identifiers and provider outcome requests."
      ],
      "adapters": [
        "mail.inbound.v1",
        "mail.sender.v1"
      ]
    },
    {
      "id": "operations-provider",
      "required": false,
      "requiredWhen": "A declared executable external operation is enabled.",
      "why": "The external provider owns the action target and authoritative outcome, which the deployment cannot reproduce with an owned Cloudflare primitive.",
      "data": [
        "The reviewed operation target, approved parameters, reason, and Beacon idempotency key.",
        "Provider status and reconciliation requests for the declared operation."
      ],
      "adapters": [
        "operations.provider.v1"
      ]
    },
    {
      "id": "ai-provider",
      "required": false,
      "requiredWhen": "AI preparation is enabled.",
      "why": "A replaceable AI provider performs requested preparation that no owned Cloudflare primitive provides.",
      "data": [
        "The operator-requested task context and cited workspace sources selected for AI preparation."
      ],
      "adapters": [
        "ai.provider.v1"
      ]
    }
  ],
  "env": [
    {
      "name": "BEACON_ENVIRONMENT",
      "required": false,
      "secret": false,
      "why": "Selects local, test, preview, or production platform-configuration validation.",
      "default": "local"
    },
    {
      "name": "BEACON_PUBLIC_ORIGIN",
      "required": false,
      "requiredWhen": "BEACON_ENVIRONMENT=preview or BEACON_ENVIRONMENT=production",
      "secret": false,
      "why": "Names the canonical HTTPS deployment origin used by preview and production platform integrations."
    },
    {
      "name": "MAIL_FROM",
      "required": false,
      "requiredWhen": "BEACON_ENVIRONMENT=production",
      "secret": false,
      "why": "Verified support sender for the selected outbound provider; also the configured support mailbox for optional signed Resend ingress."
    },
    {
      "name": "NOTICE_FROM",
      "required": false,
      "requiredWhen": "BEACON_ENVIRONMENT=production",
      "secret": false,
      "why": "Verified notice sender for the selected outbound provider, distinct from MAIL_FROM, used for operator notifications and receipt links."
    },
    {
      "name": "RESEND_API_KEY",
      "required": false,
      "requiredWhen": "Resend inbound is configured or MAIL_PROVIDER=resend in production",
      "secret": true,
      "why": "Authenticates the configured Resend inbound and reviewed-mail adapter."
    },
    {
      "name": "RESEND_WEBHOOK_SECRET",
      "required": false,
      "requiredWhen": "Resend inbound is configured or MAIL_PROVIDER=resend in production",
      "secret": true,
      "why": "Verifies signed Resend Svix webhook payloads before any mail mutation."
    },
    {
      "name": "WIDGET_IDENTITY_SECRET",
      "required": false,
      "requiredWhen": "A host configures signed widget customer identity.",
      "secret": true,
      "why": "Verifies host-signed widget customer identity before any customer history is linked to a visitor."
    },
    {
      "name": "RECEIPT_LINK_SECRET",
      "required": false,
      "requiredWhen": "BEACON_ENVIRONMENT=production",
      "secret": true,
      "why": "Signs and verifies one-customer, one-contact receipt links without exposing a reference-only public receipt route."
    },
    {
      "name": "RATE_LIMIT_SECRET",
      "required": false,
      "requiredWhen": "BEACON_ENVIRONMENT=production",
      "secret": true,
      "why": "Keys one-way D1 rate-limit subject hashes for public and authentication ingress."
    },
    {
      "name": "OPENAI_API_KEY",
      "required": false,
      "requiredWhen": "OpenAI preparation is configured.",
      "secret": true,
      "why": "Authenticates the optional OpenAI Responses AI preparation adapter."
    },
    {
      "name": "OPENAI_MODEL",
      "required": false,
      "requiredWhen": "OpenAI preparation is configured.",
      "secret": false,
      "why": "Pins the model used by the optional OpenAI Responses AI preparation adapter."
    },
    {
      "name": "BEACON_SAMPLE_DATA",
      "required": false,
      "secret": false,
      "why": "Enables loopback-only sample data and local magic-link delivery for development.",
      "default": "false"
    },
    {
      "name": "BEACON_LOCAL_SIGNIN",
      "required": false,
      "secret": false,
      "why": "Enables native one-use sign-in link delivery only in the local environment on a loopback host, independently of sample data.",
      "default": "false"
    },
    {
      "name": "BEACON_PORT",
      "required": false,
      "secret": false,
      "why": "Selects the local development server port.",
      "default": "18474"
    },
    {
      "name": "BEACON_PERSIST_PATH",
      "required": false,
      "secret": false,
      "why": "Selects an optional local persistence directory for development."
    },
    {
      "name": "MAIL_PROVIDER",
      "required": false,
      "secret": false,
      "default": "resend",
      "why": "Explicitly selects resend or sendgrid outbound mail; it does not select or enable inbound webhook verification."
    },
    {
      "name": "SENDGRID_API_KEY",
      "required": false,
      "requiredWhen": "MAIL_PROVIDER=sendgrid",
      "secret": true,
      "why": "Authenticates explicitly selected SendGrid outbound mail; acceptance is not delivery confirmation and ambiguous attempts are not automatically retried."
    }
  ],
  "deploy": {
    "healthPath": "/health",
    "operatorPath": "/app",
    "apiPath": "/api/v1/",
    "publicPaths": [
      "/",
      "/health",
      "/signin",
      "/auth/verify",
      "/contact/verify",
      "/api/v1/auth/",
      "/api/public/mail/resend",
      "/api/public/widget/availability",
      "/api/public/widget/sessions",
      "/api/public/widget/messages",
      "/api/public/widget/messages/history",
      "/api/public/widget/messages/acknowledgements",
      "/api/public/widget/typing",
      "/api/public/widget/contact-handoffs",
      "/widget/v1.js",
      "/api/public/knowledge",
      "/api/public/knowledge/",
      "/api/public/receipts/",
      "/receipt/",
      "/_serverFn/",
      "/help",
      "/help/",
      "/assets/"
    ],
    "notes": "The root and every public API path without a trailing slash are exact; each trailing-slash entry is a prefix. /_serverFn/ is TanStack Start transport, rate-limited but never an authorisation grant: private handlers recheck the native session. /app and every non-auth /api/v1 path require an operator session."
  },
  "customFields": [
    "customer",
    "case"
  ],
  "extensionPoints": {
    "contributions": [
      "app.routes.v1",
      "app.pages.v1",
      "app.navigation.v1",
      "app.settings.v1",
      "jobs.consumers.v1",
      "schedules.cron.v1"
    ],
    "policies": [
      "approval.eligibility.v2",
      "conversation.assignment.v1",
      "case.assignment.v1",
      "case.due-time.v1",
      "evidence.requirements.v2",
      "ai.review-boundary.v1",
      "ai.instructions.v1",
      "notification.routing.v1"
    ],
    "events": [
      "conversation.created.v1",
      "conversation.status-changed.v1",
      "conversation.merged.v1",
      "message.received.v1",
      "case.created.v1",
      "case.assigned.v1",
      "case.status-changed.v1",
      "case.escalated.v1",
      "case.merged.v1",
      "case.linked.v1",
      "evidence.collected.v1",
      "decision.created.v1",
      "approval.requested.v1",
      "approval.decided.v1",
      "action.submitted.v1",
      "action.succeeded.v1",
      "action.failed.v1",
      "action.indeterminate.v1",
      "customer-notification.status-changed.v1",
      "case.completed.v1",
      "receipt.created.v1",
      "import.completed.v1"
    ],
    "slots": [
      "app.navigation.after.v1",
      "conversation.list.row-actions.v1",
      "conversation.detail.header.after.v1",
      "conversation.timeline.after.v1",
      "conversation.customer-context.after.v1",
      "conversation.linked-cases.after.v1",
      "case.detail.header.after.v1",
      "case.evidence.after.v1",
      "case.decision.after.v1",
      "case.actions.after.v1",
      "approval.review.after.v1",
      "customer.profile.tabs.after.v1",
      "resolution.receipt.after.v1",
      "settings.sections.after.v1",
      "analytics.dashboard.after.v1"
    ],
    "adapters": [
      "mail.inbound.v1",
      "mail.sender.v1",
      "operations.provider.v1",
      "ai.provider.v1"
    ]
  },
  "limits": {
    "status": "Estimated bootstrap rate limits are implemented but unmeasured; capacity limits await a load test.",
    "publicRequestsPerIpPerMinute": 60,
    "magicLinkRequestsPerIpPer15Minutes": 20,
    "magicLinkRequestsPerEmailPer15Minutes": 5,
    "magicLinkVerificationRequestsPerIpPer15Minutes": 30,
    "inboundAttachmentBytes": 26214400
  },
  "accessibility": {
    "level": "WCAG 2.2 AA",
    "status": "Not yet verified for the conversation-desk contract declaration."
  },
  "operatingCost": {
    "status": "Unmeasured: local resource use and declared provider pricing/rate bases still need recording; this deployable-only wave does not require a hosted runtime."
  },
  "composition": {
    "base": "base",
    "family": "support",
    "edition": "beacon",
    "modules": [
      "support-intake",
      "support-email",
      "sendgrid-email"
    ]
  }
}
