graph LR
subgraph "Set-Valued Map Instances"
B["Group B<br/>Hypergraph + Set-Valued Map<br/>(11 protocols)<br/>SQL, Parquet, Arrow, ..."]
end
subgraph "W-Type Instances"
A["Group A<br/>Constrained Multigraph + W-Type<br/>(30 protocols)<br/>ATProto, JSON Schema, OpenAPI, ..."]
D["Group D<br/>Typed Graph + W-Type + Interfaces<br/>(16 protocols)<br/>GraphQL, TypeScript, Rust Serde, ..."]
E["Group E<br/>Constrained Multigraph + W-Type + Meta<br/>(9 protocols)<br/>XML/XSD, HTML, Neo4j, ..."]
end
subgraph "Flat Instances"
C["Group C<br/>Simple Graph + Flat<br/>(9 protocols)<br/>Protobuf, Avro, Thrift, ..."]
end
3 Protocols as Parameters
4 Protocols as Parameters
A theory tells panproto what a schema looks like. But knowing the shape of schemas is only half the story. To migrate data, the engine also needs to know what data looks like: is it rows in a table? Nested JSON documents? Flat key-value records? The answer varies by protocol, and it determines how migration actually works.
panproto captures this with a simple idea: a protocol is a pair of theories. One theory for the shape of schemas, one for the shape of data. From that pair, the engine derives everything else—migration operators, existence checks, breaking-change classifiers, bidirectional lenses—without any protocol-specific code.
4.1 The two-theory architecture
Every supported protocol is described by exactly two parameters:
A schema theory \(\mathbb{T}_S\), whose models are schemas. This determines what “a schema” looks like: vertices and edges, hyperedges, constraints, interfaces, or some combination.
An instance theory \(\mathbb{T}_I\), whose models are data conforming to a schema. This determines what “a record” or “a row” looks like once you have a schema in hand.
A protocol is thus a pair \(\mathcal{P} = (\mathbb{T}_S, \mathbb{T}_I)\). The following table shows representative protocols from each of the six theory groups that panproto identifies. (The full catalog of all 76 supported protocols is in Appendix C.)
A note on the naming convention: the Th prefix means “theory of.” ThGraph is the theory of directed graphs. ThConstraint is the theory of constraint-decorated structures. And \(\mathrm{colimit}(\text{ThGraph}, \text{ThConstraint}, \text{ThMulti})\) means “merge these three theories into one, identifying their shared parts.” The colimit construction is how panproto assembles composite schema theories; we will come back to it later in this chapter.
| Protocol | Schema Theory | Instance Theory | Restriction Operator |
|---|---|---|---|
| Group A: Constrained Multigraph + W-Type | |||
| ATProto | colimit(ThGraph, ThConstraint, ThMulti) | ThWType + ThMeta | Signature restriction + BFS + contraction |
| JSON Schema | colimit(ThGraph, ThConstraint, ThMulti) | ThWType | Signature restriction + BFS + contraction |
| Group B: Hypergraph + Set-Valued Map | |||
| SQL | colimit(ThHypergraph, ThConstraint) | ThFunctor | restrict (precomposition) |
| Parquet | colimit(ThHypergraph, ThConstraint) | ThFunctor | restrict (precomposition) |
| Group C: Simple Graph + Flat | |||
| Protobuf | ThSimpleGraph + ThConstraint | ThFlat | Field projection |
| Avro | ThSimpleGraph + ThConstraint | ThFlat | Field projection |
| Group D: Typed Graph + W-Type with Interfaces | |||
| GraphQL | colimit(ThGraph, ThConstraint, ThMulti, ThInterface) | ThWType | Signature restriction + BFS + contraction |
| TypeScript | colimit(ThGraph, ThConstraint, ThMulti, ThInterface) | ThWType | Signature restriction + BFS + contraction |
| Group E: Constrained Multigraph + W-Type + Meta | |||
| XML/XSD | colimit(ThGraph, ThConstraint, ThMulti, ThMeta) | ThWType | Signature restriction + BFS + contraction |
| Neo4j | colimit(ThGraph, ThConstraint, ThMulti, ThMeta) | ThWType | Signature restriction + BFS + contraction |
Three patterns jump out of this table.
First, schema theories are assembled from building blocks. Real-world protocols combine orthogonal concerns—graph structure, constraints, multiplicity, interfaces—and panproto assembles the theory by merging smaller component theories.1
Second, instance theories cluster into just three families: set-valued maps (\(\text{ThFunctor}\)), tree-shaped data (\(\text{ThWType}\)), and flat records (\(\text{ThFlat}\)). This is a surprisingly small number, given the diversity of formats.
Third, the restriction operator—the thing that actually migrates data—is determined by the instance theory. You do not choose a migration strategy. It follows from the theory. Set-valued map instances migrate via restrict (precomposition). W-type instances migrate via a tree surgery pipeline. Flat instances use field projection. The same migration morphism can be applied to any of these, but the data-level operation is different in each case.
4.2 Three families of instances
The instance theory determines what “data” means for a given schema. Each of the three families has a different data shape, a different migration operator, and different performance characteristics.
4.2.1 Set-valued maps (\(\text{ThFunctor}\))
Think of a set-valued map instance as a filing cabinet. Each table gets a drawer. Each drawer holds a set of rows. Foreign keys are cross-references between drawers: “for this row in the posts drawer, the author is this row in the authors drawer.”
Formally, a schema is a collection of tables connected by foreign keys, and an instance assigns to each table a set of rows and to each foreign key a lookup function that maps rows of the source table to rows of the target table.2 Referential integrity is built into the structure: following two foreign keys in sequence gives the same result whether you compose them first or chase them one at a time.
Migration in this family is restrict: given a schema morphism \(f\colon S_1 \to S_2\) and an instance \(I\) over \(S_2\), you get an instance \(f^* I\) over \(S_1\) by looking up each old table’s corresponding new table and pulling back its rows. Foreign keys compose automatically.3
SQL databases, Confluent Schema Registry with Avro, Parquet files, and Arrow tables all fall into this family.
4.2.2 W-Types: tree-shaped data (\(\text{ThWType}\))
Most protocols—ATProto, GraphQL, JSON Schema, TypeScript, and the majority of the 76 panproto supports—traffic in tree-shaped data. A Bluesky post is not a row in a table. It is a JSON document with nested objects, arrays, and references.
A W-type (wellfounded tree type) instance over a schema is a finitely-branching tree where each node is anchored to a schema vertex, each branch is labeled by a schema edge, and every path from root to leaf is finite.4 The root might be anchored to Post, a child to Text, a sibling to CreatedAt.
Migration here runs a five-step pipeline: signature restriction, reachability BFS, ancestor contraction, edge resolution, and fan reconstruction. The chapter on data lifting covers each step in detail.
4.2.3 Flat instances (\(\text{ThFlat}\))
\(\text{ThFlat}\) is a disciplined restriction of \(\text{ThWType}\). When the schema is a simple graph (no parallel edges, no recursive types) and instances are constrained to depth-1 trees, the full W-type machinery reduces to field projection: keep the fields whose schema edges appear in the image of the migration map, discard the rest.
This captures a real semantic boundary. Protocols like Protobuf, Avro, and Thrift are deliberately flat. They are designed for high-throughput serialization, where recursive nesting would impose unacceptable overhead. Treating \(\text{ThFlat}\) as a proper theory (rather than a runtime flag on \(\text{ThWType}\)) lets panproto enforce the depth-1 constraint structurally.
Every flat record is trivially a depth-1 tree, so any \(\text{ThFlat}\) instance is automatically a valid \(\text{ThWType}\) instance. The converse does not hold.
Protobuf uses \(\text{ThFlat}\) for its instance theory, but Protobuf messages can contain nested messages. Why is \(\text{ThFlat}\) sufficient? What structural property of Protobuf makes nesting different from, say, JSON nesting?
In Protobuf, each nested message is a separately addressable, independently encoded unit with its own field numbers. The nesting is a reference, not a recursive tree structure. A Protobuf message is fully described by its immediate fields, which makes it depth-1 by design. Migration reduces to field projection, which is cheaper than the full tree surgery pipeline.
4.2.4 Comparing the three families
| Aspect | Set-Valued Map (\(\text{ThFunctor}\)) | W-Type (\(\text{ThWType}\)) | Flat (\(\text{ThFlat}\)) |
|---|---|---|---|
| Data shape | Tables of rows | Nested trees | Depth-1 key-value records |
| Migration | restrict (precomposition) |
Tree surgery pipeline | Field projection |
| Canonical users | SQL, Parquet, Arrow | ATProto, GraphQL, TypeScript | Protobuf, Avro, Thrift |
| Compositionality | Automatic | Requires reachability analysis | Trivial |
No family is universally better. Set-valued maps have the best compositional properties (migration maps compose, and extend/complete give you round-tripping for free), but they require tabular data. W-types handle trees natively, at the cost of more complex migration logic. \(\text{ThFlat}\) trades away arbitrary nesting for a migration operator so cheap it is nearly free to compute.
4.3 Assembling schema theories from building blocks
Look at the schema theory column in Table 4.1 again. Almost every entry is a colimit of simpler theories. This is not a coincidence: real-world protocols are assembled from orthogonal concerns, and the colimit construction lets panproto mirror that assembly mathematically.
The building blocks are:
- ThGraph: vertices and directed edges.
- ThConstraint: invariants on vertex/edge data.
- ThMulti: arrays, optional fields, repeated edges.
- ThInterface: abstract types implemented by concrete ones.
- ThHyperEdge: edges connecting more than two vertices simultaneously.
- ThMeta: out-of-band annotation layers.
ATProto needs graphs, constraints, and multiplicity: \(\mathrm{colimit}(\text{ThGraph}, \text{ThConstraint}, \text{ThMulti})\). GraphQL adds interfaces: \(\mathrm{colimit}(\text{ThGraph}, \text{ThConstraint}, \text{ThMulti}, \text{ThInterface})\). SQL replaces graphs with hypergraphs and drops multiplicity: \(\mathrm{colimit}(\text{ThHypergraph}, \text{ThConstraint})\). Each protocol gets exactly the combination it needs.
The colimit itself is a category-theoretic construction that merges two theories while identifying their shared sub-theory.5 In practice, it works like a type-safe merge: sorts with the same name in both theories are unified, operations with the same signature are unified, and conflicts (same name, different structure) are reported as errors. The result is a single theory that simultaneously satisfies both components.
The pattern is extensible. If a new protocol needs graph structure, constraints, and some novel “versioning” concern, you write \(\text{ThVersioning}\) as a new theory and take its colimit with the existing pieces. The engine derives migration, existence checks, and breaking-change detection for the new combination automatically.
4.4 How the engine uses the pair
When you instantiate panproto for a particular protocol, the engine reads the two theories and derives everything it needs:
From \(\mathbb{T}_S\) it determines what sorts exist (vertices, edges, constraints, hyperedges, interfaces, …) and what operations connect them. This gives the engine the migration type: a migration must carry a map for each sort.
From \(\mathbb{T}_I\) it determines the data representation (set-valued map, W-type, or flat) and therefore the restriction operator: how to apply a migration to an instance.
From the axioms in \(\mathbb{T}_S\) it derives the existence checks: which conditions a proposed migration must satisfy to be valid (the next chapter covers these in detail).
From the interaction of \(\mathbb{T}_S\) and \(\mathbb{T}_I\) it derives the breaking-change classifier: which migrations are safe to deploy and which are not.
None of this requires protocol-specific code. The protocol author provides two theories; the engine provides the rest.
4.5 Defining a protocol in code
Here is how a protocol is defined using panproto’s TypeScript API:
import { Panproto } from '@panproto/core';
const panproto = await Panproto.init();
// start snippet define-protocol
const tomlConfig = panproto.defineProtocol({
name: 'toml-config',
schemaTheory: 'ThConstrainedGraph',
instanceTheory: 'ThWType',
edgeRules: [
{ edgeKind: 'key', srcKinds: ['section'], tgtKinds: [] },
{ edgeKind: 'subsection', srcKinds: ['section'], tgtKinds: ['section'] },
],
objKinds: ['section'],
constraintSorts: ['required', 'deprecated'],
});
// end snippet define-protocol
// start snippet use-protocol
const configSchema = tomlConfig.schema()
.vertex('root', 'section')
.vertex('root.database', 'section')
.vertex('root.database.host', 'string')
.vertex('root.database.port', 'integer')
.edge('root', 'root.database', 'subsection', { name: 'database' })
.edge('root.database', 'root.database.host', 'key', { name: 'host' })
.edge('root.database', 'root.database.port', 'key', { name: 'port' })
.constraint('root.database.host', 'required', 'true')
.build();
// end snippet use-protocolA protocol is a record with two fields. The engine reads the theory structure and derives migration types, restriction operators, existence checks, and breaking-change classifiers from it.
If you designed a protocol with \(\text{ThHypergraph}\) for schemas and \(\text{ThWType}\) for instances, what kind of data format would it describe? Can you think of a real-world format that fits?
A format with multi-ary structural relationships (like SQL’s compound foreign keys) but tree-shaped data instances (like JSON documents). This would describe something like a document database with compound keys and nested values. MongoDB comes close: its schemas involve multi-field indexes and compound shard keys (hyperedge structure) while its documents are nested JSON trees (W-type instances).
4.6 The protocol design space
The two-parameter architecture organizes all 76 supported protocols into a design space. Each point is a \((\mathbb{T}_S, \mathbb{T}_I)\) pair; the six theory groups in Table 4.1 are six clusters in that space.
Some questions this framing makes natural:
What separates Group A from Group D? Both use W-type instances over constrained multigraph schemas. The difference is that Group D adds \(\text{ThInterface}\): abstract types (interfaces, traits, type classes) as first-class schema citizens.
What makes Group E distinct from Group A? Group E adds \(\text{ThMeta}\) to the schema colimit and lifts the instance theory to \(\mathrm{colimit}(\text{ThWType}, \text{ThMeta})\). The metadata layer participates in migration. This captures the fact that formats like XML, HTML, and Neo4j carry an annotation layer (XML attributes, HTML metadata, Neo4j property maps) alongside the tree structure.
What lives between Group B and Group A? A protocol with hypergraph schemas but W-type instances—perhaps a document database with compound keys and nested values. panproto can accommodate this by combining \(\text{ThHypergraph}\), \(\text{ThConstraint}\), and \(\text{ThWType}\).
The design space is open. Adding a new protocol means adding a new point.
4.7 Historical context
The idea that data migration can be parameterized by algebraic structure has roots in several traditions.
Spivak (2012) showed that migration via structure-preserving maps between schema categories gives a clean, compositional account of SQL-style migration. The CQL project (Schultz et al. 2017) turned this into a practical tool for relational databases.
The W-type framework comes from type theory, where Gambino and Hyland (2004) showed that tree-shaped data structures arise naturally as initial algebras of polynomial functors. Abbott et al. (2005) connected this to “containers,” a way of describing data shapes that panproto uses to determine how a schema’s structure constrains the tree shape of its instances.
The insight that these traditions can be unified—that set-valued map instances, W-type instances, and flat instances all arise as models of different instance theories, with migration derived from the theory structure, and that 76 real-world protocols fall cleanly into six theory groups under this classification—is, as far as we know, original to panproto.
We now have the mathematical framework. The next chapter puts it to work: we will walk through a concrete migration, step by step, and see how the engine uses the theory pair to transform data.
The merge operation is formally a colimit (pushout) in the category of theories. Appendix A has the details.↩︎
An instance is a functor \(I\colon \mathcal{C}^{\mathrm{op}} \to \mathrm{Set}\), where \(\mathcal{C}\) is the schema viewed as a category. See Spivak (2012) and Appendix A.↩︎
In categorical language, \(f^*\) is the pullback functor \(\Delta_f\), with left adjoint
extend(\(\Sigma_f\)) and right adjointcomplete(\(\Pi_f\)).↩︎The mathematical model is a W-type from dependent type theory (Gambino and Hyland 2004), related to the containers of Abbott et al. (2005). Appendix A spells this out.↩︎