The site for GuestGraph — the open-source guest identity graph for hospitality.
Live: https://guestgraph.io
One repository serves the whole domain. It was two — the talks had their own — and the split cost more than it saved: the talks index copies this site's shell, header and footer, so every nav change had to land in both repositories in the same breath, with no CI on either side able to see the seam. They were merged, history and all, in August 2026.
The repository is named guestgraph.github.io because that makes it the organization's GitHub Pages site, which is what puts it on the custom domain in CNAME. Renaming it or removing CNAME takes the whole domain down, talks included.
A repository named talks in this organization would claim guestgraph.io/talks/ the moment its Pages were enabled — shadowing the folder in this repository, which is how the old split had to be unwound. Do not recreate one.
| Path | |
|---|---|
/ |
The landing page. |
/talks/ |
The talks index. |
/talks/intro/ |
The introduction — narrated, German and English, with a PDF in each. |
/billing/ |
How the hosted service will bill. One meter, and it is arrivals. |
/privacy/ |
What this site collects, which is nothing. |
/problems/ |
What each refusal type a GuestGraph service answers means, and what to do about it. |
/principles/ |
GuestGraph's vision and values, rendered from model.json by build/pages.mjs. |
/processes/ |
A board per process of the seats that own, run and approve each phase, and which of them an agent holds, rendered from model.json. |
/surfaces/ |
Every place the model is published and what makes each, drawn as a lineage from model.json. |
/api/ |
The two APIs, generated from the services' OpenAPI documents at pinned commits. |
/model/ |
GuestGraph's own model, drawn on the stage from model.json, which is built from a pinned commit of guestgraph/mental-model. |
index.html— the landing page. Self-contained: even the fonts are served fromfonts/.billing/index.html— how the hosted service will bill. One meter, and it is arrivals.privacy/index.html— what this site collects (nothing) and how guest data will be handled.talks/index.html— the talks index, carrying this site's chrome so a visitor crossing into it meets no seam.talks/intro/— the deck:index.html,audio/{en,de}/, both PDFs, andtts/generate.py, which reads the deck's speaker notes as the single source for what is spoken.tokens.css,page.css,page.js,deck.css,deck.js— what@robertblust/designshares, written here bynpm run designand never edited here; the pages link them.fonts/— the self-hosted.woff2files, and the only copy. Every page and the deck point at them relatively, so the deck's relative links totokens.css,deck.cssanddeck.jsstill resolve underfile://too, even though a deck is no longer required to open that way.sitemap.xml— one flat list of every URL on the domain. It was an index pointing at a second sitemap while the talks lived elsewhere; there is one list now.verify/check.mjs— the suite, covering every page in one run, andverify/og-recipe.test.mjs, the share-card check's own tests.build/model.mjs,build/read.mjsandsource.json—model.json, written from the commit ofguestgraph/mental-modelthatsource.jsonpins, with the parsercompanygraph-meta-modelships.npm run modelrewrites it,npm run model:checkfails when it is not what the pin parses to, and CI runs the check. The pin is editorial, andnpm run pin:checkreports how far it has fallen behind, beside the API pins.stage.js,stage.css,card.jsand the vendored d3 are the design package'sstagegroup, which draws it.chat.js,chat.css,octicons.LICENSE.txt,mermaid.min.jsandmermaid.LICENSE.txt— the design package'schatgroup: the button at the foot of every prose page and the panel it opens, answered by chat.guestgraph.io, whose cites and names open on/model/. Each page carries one script tag naming the endpoint, and/privacy/says what leaves the browser once a visitor presses send.build/pages.mjs— every region derived frommodel.json, written by the design package's renderers:npm run pagesrewrites them,npm run pages:checkfails when one has drifted, and CI runs the check. It refuses amodel.jsonwhose commit is not the pin.build/jsonld.mjs— the model's Dataset node, written bybuild/pages.mjsinto every page's JSON-LD graph from one definition, after the nodes the page writes by hand; a page with a graph missing from its list stops the build.build-api.mjsandapi-sources.json— the API page's operation rows, written from the engine's and the connector's own OpenAPI documents at the commits the pins name.npm run apirewrites the rows,npm run api:checkfails when the page has drifted from the pins, and CI runs the check. The page's prose is hand-written around the rows and carries its German; an operation the script does not place stops the build rather than vanishing from the page. The pins are editorial, as every pin in this family is, andnpm run pin:checksays how far each has fallen behind the service it names. It never fails: a pin behind is a page publishing an older API on purpose, or an owner who has not looked lately, and only a person tells those apart. It prints a notice CI renders on the pull request, which is how a new operation stops being something the page has never heard of.og.pngat the root and beside every page'sindex.html— 1200×630 share cards, each rendered from the page it belongs to, and anog.shabeside each one: a hash of everything that went into the card, sonpm run og:checkcan say whether it still shows its page.og-recipe.mjsdefines what that is,export-og.mjsrenders every one and writes the stamps,og-check.mjsreports them.export-pdf.mjs, alongside it at the root, renders the deck's two PDFs intotalks/intro/.logo.svg— the mark: three open records resolving into one solid profile. UsescurrentColor, so it inherits whatever color it is placed in.favicon.svgavatar.svg/avatar.png— the org avatar, 1024×1024. Full-bleed square on purpose: GitHub rounds org avatars itself, and a rounded rect here would show its corners through that mask. Upload the PNG — GitHub does not accept SVG for avatars, and there is no API for it (Organization → Settings → Profile → Upload new picture).CNAME— the custom domainLICENSE— CC BY 4.0, see License below
Three hollow nodes, one filled. The sources stay open because records are never consumed — they are kept exactly as they arrived. The profile is solid because it is a conclusion derived from them, not a row anybody edited. That is the product in one glyph, and it is the same figure the hero animates and the talk draws.
No build step. Open index.html, or serve it:
npm install # once, for Playwright
npm run serve # → http://localhost:8000
npm run verify # renders every page and asserts the DOM
npm run og:check # do the share cards still show their pages?
npm run test:og # the card check's own tests (node --test)
npm run og # re-renders every card after a visual change
npm run sitemap # date each sitemap URL from its page's last commit — run before committing a page
npm run sitemap:check # are those dates still what git says?og:check needs no server and no browser — it re-derives each card's recipe and compares it with the og.sha committed beside it, which is why CI runs it before installing one. It does need npm ci first: the machinery lives in @robertblust/design, shared with blust.ch and companygraph.io. Commit each og.png with its og.sha, in the commit that moved the page.
The landing page opens on the title and the hook, then on the model's own vision and values, in a #vision and a #values section build/pages.mjs writes from model.json with @robertblust/design/render/home, each value's German name and never-line held to the same build/principles.de.json /principles/ reads. Its tiles send the visitor onward — to the chat, to the model or to the API — and the talk is reached from the nav rather than a button here; it is a better medium for what a visitor would ask next than a scrolling page of claims, and the six-minute talk is where that is answered.
That is also why it makes no claim about matching behavior, roadmap, or status. Those live where they are maintained — in the core repository and in docs/matching.md — so a change there cannot leave this page quietly wrong.
/billing/ is the one thing this site states in its own right rather than restating from elsewhere: the hosted service will bill on arrivals, one meter and nothing else, with ingestion, historical backfill, stored profiles and lookups free. Self-hosting stays free forever, because it is Apache 2.0 and pretending otherwise would cost more credibility than it earns.
It carries no prices and no bands. The model is the durable half; the numbers are the half that goes stale, and the hosted service is not open yet — which the page says out loud. That sentence is the difference between publishing an intention and advertising a product, and it stays until there is something to buy.
/privacy/ says what this site collects, which is nothing: no cookies, no analytics, no third-party requests, no forms, fonts served from this origin. The only thing stored is the language choice, in the visitor's own browser. verify records every request the page makes and fails on any that leaves this origin, because that claim is the kind that can quietly stop being true.
The same page says how guest data will be handled once the hosted service exists — hotel as controller, GuestGraph as processor, revDSG and GDPR, a cloud not yet chosen and named there before the first record is processed. There is no imprint yet, and the page says why: nothing here can be bought. It arrives the day the billing page stops saying the service is not open — on a page of its own, because an imprint is not a privacy note.
CC BY 4.0 for everything written here: the pages, the talk and its narration, the share cards, and the scripts that build and check them. Use it, quote it, build on it; credit it. The faces under fonts/ are not written here and keep their own license, in a text beside each: they come from @robertblust/design, whose NOTICE names each one. The license grants no right to a name or a mark: GuestGraph and its mark stay the project's.