The Graph Was Already There

Here's a question that sounds like a trick and isn't: what does a SaaS billing dashboard have in common with a real estate listing pipeline?

On the surface, nothing. One is invoices and subscriptions. The other is showings and closings. Different users, different vocabulary, different business entirely. But strip away the labels and look only at the shape — how many stages, how those stages connect, what can happen from where — and you find the same skeleton wearing two different skins. That's not a coincidence, and it's not really even a design decision anyone consciously made. It's a mathematical fact about the architecture that was true before we had a name for it.

The name is graph theory, and once you start looking for it, it turns out DataPublisher has been built out of graphs from the beginning — we just hadn't called them that.

Start with the easiest one: who depends on whom

Every DataPublisher Domain App Pack — the SaaS company pack, the real estate pack, the school-coop pack — already works on its own: schema, templates, document creation and distribution, out of the box. What extends it is binding it to a set of Capability Packs: crm-lite for contacts and deals, cp_scheduling for calendars, cp_accounting-lite for the money. That binding is a relationship, and a relationship between two things is, formally, an edge. Domain apps and capability packs are nodes; bindings are the lines connecting them. Draw enough of those lines and you get a graph — specifically, a dependency graph, the same kind of structure that shows up any time software packages, build steps, or org charts depend on each other.

The interesting part isn't that this is a graph. It's what falls out once you notice it is one.

cp_scheduling is bound into the SaaS pack, the real estate pack, and the school-coop pack. crm-lite is bound into two of those three. cp_accounting-lite is bound into one. If you count how many arrows point into each capability pack, you get a ranked list of which capability packs the most domain apps actually rely on — not by gut feeling, but by counting. That count is called in-degree in graph theory, and it turns out to be exactly the metric you'd want if someone asked "which capability pack should we harden first?" The graph already contains the answer. It was just sitting there, uncounted.

The failure mode that only makes sense once you see the shape

Not every connection in the system is a clean dependency, though. DataPublisher packs also talk to each other through an event-bus — one pack publishes "this record changed," another pack is listening and reacts, and that reaction might itself publish another event. That's also a graph. But unlike the dependency graph, nothing guarantees this one doesn't loop back on itself.

Picture two packs that are each, individually, doing exactly what they're supposed to: pack A notices a record changed and tells everyone. Pack B hears that, reindexes something, and announces it's done. Pack A is listening for exactly that announcement, and reacts by noticing the record changed again. Nobody wrote a bug. Each pack behaved correctly in isolation. But together they've built a loop, and loops in an event graph don't announce themselves the way a crash does — they show up as a system that's mysteriously busy, or slow, or occasionally falls over under load, days or weeks after the two packs that caused it were shipped separately by people who never saw each other's code.

Once you know to look for cycles in a graph, though, finding them is a solved problem — it's a standard traversal, the kind of thing computer science has had a clean answer for since long before software architecture existed. Run that check at the moment a new pack registers its event subscriptions, and a two-hop loop like the one above gets caught before it ships, not after it pages someone.

The idea worth the whole post

Here's the one that actually matters most, and it's the reason the SaaS billing dashboard and the real estate pipeline rhyme.

Every domain that DataPublisher supports — and there are 141 of them in the marketplace — needs some version of a records lifecycle. A thing gets created, it moves through some stages, it ends up somewhere final. A SaaS lead goes capture → qualify → convert. A real estate listing goes list → showing → close. A construction job might go bid → scheduled → complete. Different words, every time. And for a long time the natural assumption is that these are 141 different processes that happen to be shaped similarly, the way a bicycle and a motorcycle are both shaped similarly because they both need two wheels and a place to sit.

But that's not what's actually going on underneath DataPublisher. There is exactly one records lifecycle — one invariant graph of stages and the moves between them — and every domain pack doesn't invent its own version. It supplies a translation: this domain's word for stage one, this domain's word for stage two, mapped onto the one underlying structure that never changes. The SaaS pack and the real estate pack aren't two similar processes. They are the same process, wearing different vocabulary, the way "chien" and "dog" are the same animal in two languages rather than two different animals that happen to look alike.

That property — two structures that are actually identical once you strip the labels off — has a name in mathematics: isomorphism. And it's not a cute metaphor bolted on after the fact. It's a precise description of what the "domain-specific declaration" step has been doing in DataPublisher's polymorphic records model all along: each declaration is a specific mapping from domain vocabulary onto the one invariant lifecycle graph, and as long as that mapping is complete and one-to-one, the domain pack gets the full lifecycle machinery — audit trail, state validation, everything — for free, because it's not a new machine. It's the same machine with new labels.

This is also why populating good defaults across all 141 marketplace domain packs isn't 141 separate design problems. It's one design problem — the invariant lifecycle graph — applied 141 times as a translation exercise. That reframing is the difference between a task that grows linearly worse with every new vertical and one that doesn't.

What this buys, concretely

None of this is architecture for its own sake. Each piece translates directly into something DataPublisher gets to do that it couldn't do as cleanly otherwise:

  • Knowing what to build next isn't a guess. Counting dependency edges across the full marketplace tells you, with actual numbers, which capability packs the most domain apps depend on for their extended features — and therefore which ones deserve the next round of hardening investment.
  • Cross-pack bugs get caught before launch, not after. A cycle check on the event-bus graph, run once as shared infrastructure, protects every pack built on top of it — including ones that don't exist yet.
  • New verticals get cheaper, not more expensive, over time. Because every new domain pack is a translation onto one fixed structure rather than a bespoke build, the 142nd marketplace pack is a mapping exercise, not a new architecture.
  • Scattered customer data becomes one picture instead of four. Contacts, leads, calls, and invoices currently live in separate systems that happen to reference the same person. Recognizing all four as one connected graph — rather than four tables joined at query time — means "show me everything about this contact" becomes one lookup instead of an assembly project.

The takeaway

None of the graph theory in this post is new mathematics. Dependency graphs, cycle detection, isomorphism — these are old, well-understood ideas, some of them centuries old. What's worth noticing isn't that DataPublisher invented anything mathematically novel. It's that a genuinely good architecture, built by paying close attention to what actually needed to connect to what, turns out to already be a textbook graph structure, sitting there waiting to be recognized. The recognition is what unlocks the toolkit — resolution ordering, safety checks, a real prioritization metric, a unification strategy — that the mathematics has had ready and waiting the whole time.

The graph was already there. We just had to look at the architecture sideways to see it.