# Plan: demarkus as a service Status: exploratory draft, recorded 2026-07-17 (Fritz + Claude). Revised 2026-07-25 to fold in repo ADR 0005 (`docs/adr/0005-hosted-tenant-density.md`, proposed), which settles the density question this plan originally left open. Question explored: if demarkus were offered as a hosted service (signup, everyone gets their own library), how would it work? Conclusion: it is Phase 6 plus a self-service control plane; no new core, no new protocol. Not scheduled; recorded so the shape and the remaining hard problem are not lost. ## One-line A hosted demarkus: signup provisions a tenant world behind the shared broker, readable and editable through the library, joinable from Claude Code, with the broker holding every token so users never touch one. ## Architecture: world-per-tenant behind one broker The tenant unit is a world (one demarkus-server process + PVC), fronted by the existing broker and library. ADR 0005 confirms this as the decided shape rather than a provisional one. Signup decomposes into three steps that all reuse Phase 6 machinery: 1. **Identity**: account in the IdP the broker fronts (OIDC; email or social login). Phase 6.3. 2. **Provision**: append the tenant to the `worlds:` list; the ApplicationSet stamps out a StatefulSet + PVC; the bootstrap Job mints the world's tokens into a Secret. Phase 6.1/6.2, unchanged. 3. **Grant**: broker AllowConfig row mapping the identity to the world (read + write). The broker mints and holds all capability tokens; users never see one. This dissolves the remote-token-issuance problem for hosted tenants: the broker is the sidecar of [/plans/remote-token-issuance.md](/plans/remote-token-issuance.md) with a Secret writer instead of a file writer, already built. Hand over three things at signup completion: the library URL (reading room via broker SSO, ADR 0004 in demarkus-library), the Phase 6.4 `GET /me/install` one-shot script (wires Claude Code MCP), and, once the cataloging desk ships, in-browser editing. The hosted case is fully brokered, so the brokerless-editing gap (library direct-QUIC adapter is read-only; desk design assumes broker auth) never arises. **Collaboration**: inviting someone to your world is an AllowConfig grant plus an IdP invite. Broker-side only; no new machinery. The join-URL work (step 1 of remote-token-issuance) stays relevant for self-hosted users, not hosted tenants. **Portability**: OKF export gives tenants "leave with your whole world as a conformant bundle", a differentiator most hosted note tools cannot match. Import likewise eases migration in. ## Density and idle tenants: settled by ADR 0005 World-per-tenant means one running pod + PVC per signup, including dormant users. The original draft called this the go/no-go gate for open signup and left the resolution open. ADR 0005 closes it: **the process boundary stays the isolation boundary**. One world, one process, one volume, one token file, one broker grant. Neither scale-to-zero nor an in-process multi-world server gets built now, and the hosted offering ships to team-sized tenants priced to cover a warm pod. The reasoning, condensed (full version in the ADR): - **The mechanism was never the obstacle.** In-process multi-tenancy is available without touching the protocol: the client sets TLS `ServerName` from the hostname it dials, and the server already takes a `GetCertificate` callback receiving `ClientHelloInfo`, so a connection can be bound to a world at handshake via SNI. Per-tenant DNS name, wildcard cert, broker world entry pointing at one shared Service, resolution once per connection. - **Isolation is the objection that survives.** A world today is enforced four separate ways (process, PVC, token file, certificate). A shared process collapses those into a map lookup in one address space, so any path-scoping or catalog-keying bug becomes cross-tenant disclosure, and one panic becomes a multi-tenant outage. Wrong risk for a single maintainer against a cost curve that does not bite yet. - **The payoff is conditional on a tier that does not exist.** Density only pays with many dormant tenants, meaning a consumer tier, and this plan already concluded a free tier is viable only after density works. Team tenants are not dormant. **Reversal trigger** (from the ADR): revisit when dormant-tenant infra cost exceeds their revenue, or when a consumer tier is actually decided on. That follow-up ADR must clear an isolation bar this one does not attempt, namely a conformance suite proving cross-world access is impossible through every verb, plus a per-world resource ceiling, before any shared-process code ships. Also recorded as rejected, so the next pass does not re-derive them: a tenant key in request metadata or a `/tenants//` path prefix (both put the tenancy scheme into document identity, so every published `mark://` URL, graph edge, and OKF export carries it), per-tenant ports (break the default 6309 dial every client and join string assumes), and a world column in the postgres schema (drags tenancy into the `storetest` conformance suite, which by the backend-parity principle forces it onto the file backend too). Postgres remains the right backend for hosted worlds on its own merits; what was rejected is pgstore as the enabler for one process serving many tenants. ## Quotas and abuse: the remaining hard problem Unaffected by ADR 0005: these are per-tenant liabilities under any density model, and they gate opening signup to strangers. - Per-document 1 MiB cap and per-IP rate limits exist; there is no per-world storage quota and version history grows unbounded (retention is per-doc, opt-in). - Needed before opening signup: world-level storage quota (enforced where? store-level fits backend parity; a sidecar sweeper is the zero-core-change shape), version-history budget defaults for hosted worlds, and per-tenant rate limits at the broker. - Graph completeness Tier 1 belongs on this list too: the 1 MiB `/graph.md` export wall with silent publish failure, and tombstone accumulation with no eviction path. See [/plans/graph-completeness.md](/plans/graph-completeness.md). ## The control plane (Phase 7 pulled forward) Phase 6 assumes an operator edits the worlds list. A SaaS needs an API that performs identity + provision + grant atomically, plus lifecycle: - **Signup service**: creates the IdP user, provisions the world (initially: commit to the worlds list and let Argo converge; later: the deferred Phase 7 operator with a `World` CRD), writes the AllowConfig grant, returns the three URLs. - **Deletion**: delete world = PVC + Secrets + grants + IdP unlink. Clean at world granularity; ARCHIVE's no-hard-delete semantics stop mattering when the deletion unit is the whole world. This is also the honest GDPR story. ADR 0005 keeps this simple: a stable one-world-per-tenant mapping means deletion never has to reason about shared state. - **Domains**: per-tenant subdomain mapping to broker world names, wildcard cert. - **Billing**: out of demarkus scope entirely; the signup service owns it. Keep it there. **Caveat on GitOps provisioning.** "Commit to the worlds list and let Argo converge" is adequate for waitlist-gated manual approval, not for a self-service signup button: sync latency sits in the user's face, concurrent signups contend on the repo, and the failure mode is a dirty repo. Either keep signup manual and say so, or treat the Phase 7 operator as the correctness fix rather than a scale optimization. Not decided here. The explicitly deferred Phase 7 items (World CRD operator, web UI on the broker) are exactly this control plane; a hosted offering is the business case that pulls them forward. ## Who the tenant is: raised, not settled The original draft assumed "everyone gets their own library", a personal-tenant product. That is a crowded, low willingness-to-pay market (Obsidian Sync, Notion). The differentiators that actually exist here (agent-native read and write, OKF export as real portability, the promote pipeline) point at teams already running Claude Code. This is not just positioning: team tenants are fewer, denser, and non-dormant, which is part of why ADR 0005's warm-pod answer is coherent at all. A personal free tier would put the density problem straight back on the critical path. Worth weighing against hosting entirely: `install-stack.sh` is already a five-minute appliance, so self-host plus paid support or a managed appliance is a commercial step with near-zero new engineering and none of the uptime, backup, or GDPR burden. Hosting is the expensive version of the same revenue. ## Dependencies on other plans - Phase 6.1-6.4 (universe deployment): the substrate; 6.3/6.4 must be past prototype. - demarkus-library cataloging desk (its /demarkus-library/plans/phase-3-cataloging-desk.md): in-browser editing for tenants; hosted case is the broker-authenticated path it already assumes. - demarkus-library Phase 5 public face (anonymous read): whether tenant libraries can have public/shareable pages. - Graph completeness Tier 1 ([/plans/graph-completeness.md](/plans/graph-completeness.md)): the 1 MiB /graph.md export wall and tombstone growth become per-tenant liabilities at scale. - Remote token issuance ([/plans/remote-token-issuance.md](/plans/remote-token-issuance.md)): unaffected for hosted tenants (broker covers them); stays the self-hosted story. ## Sequencing sketch (when this becomes real) Revised 2026-07-25. The old step 4, "scale-to-zero design + measurement (the go/no-go gate for open signup)", is removed from the near path by ADR 0005; it returns only if the reversal trigger fires. 1. Harden Phase 6.3/6.4 (broker + install flow) on the existing knowledge.demarkus.io deployment. 2. World-level quotas + hosted retention defaults + graph export sharding. 3. Signup service v1: waitlist-gated, provision via worlds-list commit, manual approval. Team tenants, warm pods, priced. 4. Phase 7 operator when world count makes GitOps-converge provisioning too slow, or sooner if self-service signup is wanted (see the caveat above). ## Open questions - Free tier at all? ADR 0005 makes this sharper rather than softer: with warm pods, a free tier costs a running pod per freeloader, so no free tier before the reversal trigger fires and density work actually lands. - One shared library deployment for all tenants vs per-tenant library pods (shared is the ADR 0004 shape and the obvious answer; confirm nothing in the desk design assumes otherwise). - Postgres-per-world vs shared Postgres with per-world databases for the pgstore backend at tenant scale. Still open, and now strictly a storage-topology question: ADR 0005 removed the shared-process variant that would have forced a world column. - Does the hosted broker stay the same binary as the OSS broker (it should; divergence here recreates the fork demarkus exists to avoid)? - Team tenant or personal tenant as the launch wedge? See the section above; unresolved, and it is upstream of pricing, quotas, and whether density ever comes back.