Hosted Product Design (v0.4.0+)

Most of the hosted product remains design. The first P0 foundation now lives in apps/hosted/: Firebase email/password and Google auth, verification/recovery, protected company metadata, and a retry-safe uid-keyed founder bundle. The bundle creates exactly one tenant, company, provisioning ledger, CRM default, consent-first analytics default, business-memory scaffold, obligation register, paused Operator roster, and company-presence draft. A founder can preview and publish immutable website versions, roll back, receive consented leads into CRM, and review PII-free first-party analytics on a temporary Hosting path. This bounded path is deployed on Firebase project lycatoprod; its lifecycle is covered by emulator integration/browser suites, while a disposable production canary remains pending. Mail, customer-managed/platform hostname, repository, billing, executable hosted Operator work, and lifecycle remain pending or blocked as described below.

Implemented hosted foundation

All initial Firestore resources use the verified Firebase UID as their document id and are created in one transaction. The site draft is provisioned in a second retry-safe transaction after the founder bundle exists. Replays only create missing deterministic documents. Security rules keep the foundation records create-only, bind every private record and query to the verified owner, expose only an explicit published-site projection, and withhold the provisioning ledger until the complete internal bundle exists.

The stored ledger distinguishes provisioned defaults from external readiness:

The implemented Firebase foundation is logically tenant-isolated. It is not the future per-tenant compute/volume architecture described below and does not claim physical isolation yet.

What is in the repo but not in the product

Nothing. The unshipped/ tree that used to sit beside web/ — nine modules the hosted app never imported (agent-roster.js, agent-trust.js, billing.js, crm-ops.js, mailbox-mediator.js, margin-report.js, tenant-context.js, tenant-health.js, tenant-saga.js, plus an ops-console/ shell) — was deleted in the 2026-08-23 apps/ move (commit 65ee343). That removed the plan-gate and billing subsystem, the resumable-provisioning saga, the company switcher, the mailbox mediator and the margin report from the repo entirely. Dated documents that still describe those modules as "present but unshipped" are describing the pre-65ee343 tree; recover them from git history if they are wanted back.

The real import graph is small: app.js reaches bootstrap, diagnostics, entitlements, lifecycle, operator-admin, presence and roles; site.js reaches bootstrap and presence. apps/hosted/test/shipped-graph.test.ts fails if an unreferenced .js file appears in web/.

Operator access is decided in firestore.rules (isOperatorAdmin(), backed by an operator_admin custom claim or an operators/{uid} document no client may write) and is read-only. The browser-side elevate and tenant-isolation helpers that used to live in operator-admin.js are deleted; a check running in the tenant's own browser was never an access control.

Hosted Operator production boundary

The hosted browser cannot safely provision Ujex or a runtime directly. Lycato needs a server-side mediator before any roster entry may become executable:

  1. The founder signs in only to Lycato Firebase. The Lycato server verifies the UID, tenant ownership, budget, permissions, and idempotency request.
  2. That server calls a future Ujex OEM endpoint. Ujex remains the authority for agent identity, policy, approval, audit, and mailbox projection through Ruzayo. Lycato must not reuse the founder's Lycato token as a Ujex login.
  3. Ujex delivers a one-time scoped agent credential directly to the selected runtime vault and returns only an opaque identity and credential reference.
  4. The runtime receives Lycato's own scoped product credential server-to-server, registers/heartbeats with Ujex, and returns only an opaque runtime id.

As of 2026-07-23, Lycato has no server Function, Ujex has no OEM provisioning endpoint with runtime-vault delivery, and no cloud runtime/vault provider has been selected. Blaze billing does not fill those interfaces. The roster therefore stays paused with zero budget and pending_external_runtime; the browser never receives Ujex, runtime, or Ruzayo service credentials.

What the hosted product is

A managed, single-tenant-per-founder deployment of the Lycato cockpit backed by per-tenant encrypted volumes. The founder gets:

What it is not: a multi-tenant SaaS. Each tenant gets isolated compute + storage. The "multi-tenant" we ship in v0.4.0 is operational multi-tenant (one control plane runs many single-tenant deployments), not request-multiplexed shared compute.

Pricing (intended, not chargeable today)

None of this is purchasable. The deployed product has exactly one plan, founder, at £0: apps/hosted/web/entitlements.js reports priceMonthlyGbp: 0, stripeConnected: false, checkoutAvailable: false, and firestore.rules admits plan == 'founder' on tenant create and denies every tenant update, so no founder can change plan. There is no Stripe integration deployed and no server to receive its webhooks.

The intended hosted price is £39/month for one company. Nothing is charged before the founder sees and confirms the terms. The master brand defines no public feature ladder or team tier.

Self-hosting always free; AGPL ensures the open code stays open.

Docs in this directory

File Scope ADR target
SECURITY_MODEL.md KMS-backed secret store, encryption at rest ADR-0025 (future)
AUTH.md Passkeys + magic-link, session handling ADR-0026
BILLING.md Stripe checkout, plan tiers, dunning ADR-0027
MULTI_TENANT.md Single-tenant per founder; control plane ADR-0028
CAPTURE_PIPELINE.md Postmark inbound email + Whisper voice + push ADR-0029
BACKUPS.md Daily encrypted backup to founder-owned S3 + restore ADR-0030

Sequencing

These docs are designed to be implemented in this order. Each block gates the next:

  1. SECURITY_MODEL + AUTH — without these, no secrets can be stored.
  2. MULTI_TENANT (control plane) — gates per-tenant provisioning.
  3. BILLING — gates monetisation. Self-host always free regardless.
  4. BACKUPS — required before we promise SLAs.
  5. CAPTURE_PIPELINE — last, since it depends on auth + storage.

Cross-cutting principles

  1. Tenant isolation is physical, not logical. One container, one volume, one DB per tenant. Easier audit, easier delete.
  2. Founder-owned data path stays git-native. Even hosted, the tenant volume is a git repo. We push it to the founder's GitHub on commit so the founder owns the canonical copy at all times.
  3. No tenant-readable secrets. Adapter keys never leave the server-side decrypt boundary. The cockpit displays "configured" / "not configured" only.
  4. Compliance posture from day one. GDPR + SOC 2 Type I alignment from launch. Self-hosters keep their existing posture unchanged.
  5. Hosted does not break self-host. Anything that requires the hosted control plane is opt-in. The CLI and cockpit run unchanged against a local company repo.