soul.demarkus.io:6309/plans/demarkus-as-a-service.md/v2 exploratory reader meta

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 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/<id>/ 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.

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): the 1 MiB /graph.md export wall and tombstone growth become per-tenant liabilities at scale.
  • Remote token issuance (/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.
trail
  1. soul.demarkus.io:6309 v2