Skip to content

D800 — The demo guestbook is a second KB, written only through an edge-moderated template

Decision. The live MCP demo (D751, D789) mounts a second KB, guestbook, beside astronomy, behind the same URL. The only write the demo accepts is a concept_new from the message template on guestbook. The Cloudflare Worker is the only holder of the write token: it attaches it to that one call after moderation (shape rules, a per-IP and a daily rate limit, a Llama Guard classifier that fails closed, a kill switch) and assigns the message id itself. Every other request, write tools included, carries the read-only token and is refused by the server's own scope check. The guestbook KB lives in a private git remote, cloned by the image's entrypoint and pushed synchronously after each write. The public Atlas on GitHub Pages reads this live demo and falls back to the D745 snapshot when it does not answer. No server code changes, and nothing leaves the free tiers.

Why. A read-only demo shows what an agent can read, not the point of Cartographer: many agents writing to one knowledge base through rules they cannot break. The server already enforces the hard part (per-KB scopes, concept_new creating only and with exactly the template's variables), so the edge only adds what a server cannot know about a public audience: rate, content, a kill switch. Granting the write token per request, rather than issuing one to visitors, keeps the demo authless (D789), and an approve-list of one call means a write tool added to the server later fails closed instead of opening a hole. Messages are one git commit each in a private repository, so removal is a git revert there. Keeping the Atlas on Pages, not behind the Worker, means static assets cost no Worker invocation (100k a day on the free plan); only API calls and revision polls (every 20 s on that build) do, and an edge cache of 5 s shares one upstream answer between visitors. The cost: a determined attacker can exhaust the daily Worker quota, which the free plan accepts because the failure is graceful (the Atlas shows the snapshot, MCP is unavailable until 00:00 UTC), and an open guestbook relies on a third-party classifier.

Alternatives rejected. - A write token given to visitors, or a Worker blocklist of write tools: the first cannot be revoked per visitor, the second fails open on every new tool. - The guestbook inside the astronomy KB or on its branch: astronomy is rebuilt with replayed history on every image build, so its commit hashes change and it cannot share a remote. - Cloning the guestbook through the server (kbs[].remote): a clone failure is fatal at startup, so a GitHub or token outage would take the astronomy demo down; the entrypoint clones with a timeout and starts without it. - Serving the Atlas from the Worker: every asset of every page load would count against the free quota. - A paid plan or an always-on instance: the demo is meant to cost nothing when idle. - A built-in maintainer notification service: a signed generic webhook (Standard Webhooks) leaves what receives it to the operator.

Consequences. The Pages Atlas depends on the Worker's CORS answer for its origin and on a cold start under 6 s, else it shows the snapshot with a notice. The repository behind GUESTBOOK_REMOTE must be named guestbook (the KB name comes from the remote's basename). The tools of /mcp now carry a required kb argument while the guestbook is mounted, which is the one visible change for existing users; with the guestbook missing, /mcp serves astronomy alone and the tools carry none. Rotating the git token and removing a message are manual (deploy/demo/README.md). Current behaviour: docs/deployment.md §The hosted MCP demo.

Found at rollout. A message must link to a concept, not to a map index, or lint reports it as an orphan: a link to [[messages]] does not count, and the journal's index is curated, so keeping index_entry would have grown it by one line per message. The message template therefore ends with a link to about/guestbook, the page that states the guestbook's rules, so every message hangs off one hub in the graph. The seed ships with the first message, which keeps that hub linked and the seed lint-clean.