Protocol Buffers logoGovernance

Put the Generated .proto in a Pull Request

That is the test. Not a feature list, not a pipeline diagram — a reviewer, a diff, and a wire contract other people's clients depend on. If a governance layer cannot survive that moment, everything upstream of it is decoration.

Put the Generated .proto in a Pull Request

That is the test. Not a feature list, not a pipeline diagram — a reviewer, a diff, and a wire contract other people's clients depend on. If a governance layer cannot survive that moment, everything upstream of it is decoration.

Most generated schemas fail immediately. They reorder fields, renumber things, reflow whitespace, and produce a 200-line diff for a one-field change. The reviewer skims, approves, and from then on treats the generated file as noise. Once that happens the artifact is no longer governed; it is merely produced.

So the governance story for proto3 in CoreModels is built backwards from the review. Here is what holds it up.

Writes are additive, and they carry their provenance

Importing a .proto writes through the same profile layer and role checks as every other CoreModels graph operation — writing requires Admin, reading requires Viewer. It only ever touches the project you post it to.

Every node the decoder produces remembers where it came from — the source format stamped as protobuf, each type, field, enum, and enum value carrying the dotted name it was read from, the package on the schema itself. Those provenance stamps live in the transform layer; what the project durably keeps is the naming discipline: element labels keep the original wire spelling — order_id stays order_id — while internal node ids are sanitized to the alphanumeric form CoreModels requires, so the original name is never the thing that gets mangled.

Translation is mechanical; meaning changes are gated

The distinction we hold to is that converting a format is a mechanical act, and changing what something means is a human decision that a machine may propose but never ratify.

That shows up in the surface shape. The stateless mapping endpoint transforms a proto3 schema toward another format and writes nothing at all — every call is inherently a dry run, and it returns the executed plan as an artifact you can store. The map-import endpoint, which does write, takes a dry-run flag, and the documented working contract is blunt about the order: dry-run first, read the lossiness, then import.

Whatever produced the plan, it passes one universal validation gate before anything executes. An explicit mapping guide written by hand earns no special trust; a label-inference match earns none; and an AI proposal earns none either. The AI mapping kind gets at most one repair attempt, and a rejected repair is a rejection — the gate is never relaxed to let a proposal through. Plan operations may carry a self-reported confidence score, which is advisory only; the gate does not read it as evidence. Because that mode spends the server's model budget and sends schema content to an external model provider server-side, it requires Editor or Admin membership on the project rather than the Viewer access that covers ordinary reads, and the tool's own description says so out loud.

What survives the gate is a plan: reviewable, storable, and replayable against the same source with the guarantee that the same plan plus the same input yields the same output. What was proposed, what was approved, and what ran are one object rather than three accounts of an event.

The ledger is the review checklist

Every response carries a lossiness list, and every entry has a kind, a path, and an explanation in plain English. Proto3 exercises all four kinds, in both directions.

A uint64 field becomes an integer in the model — a type approximation, recorded at the exact field path, with the original token preserved so the export re-emits uint64 rather than a widened guess. A oneof flattens into optional fields — a semantic narrowing, named after the group, because the mutual-exclusion constraint is not modeled. A service block is skipped — a structural drop, because RPC definitions are not data schema. Going out, an element marked nullable meets a format that does not distinguish null from absent — a constraint relaxation.

The rule we hold ourselves to is that success: true means "it ran," never "nothing changed." An empty lossiness list is a claim, and we only make it when it is true. That is also why the decoder is deliberately hard to fail: it refuses malformed text and proto2 input — a declared syntax = "proto2" or a required field — and almost everything else it cannot model, it skips on the record. Even the smallest edge case is reported: a file with no syntax statement defaults to proto2 by specification, so when we decode it with proto3 semantics anyway, that assumption goes into the ledger.

Drift becomes evidence instead of argument

Now back to the diff. The encoder is deterministic — the same model produces byte-identical output, and a decode-encode cycle through the transform is a fixed point, so feeding an exported file back through gives you the same bytes. Preserved field numbers re-emit verbatim. New elements are minted the next free numbers in declaration order, skipping numbers already in use and the 19000–19999 range the specification reserves for the implementation. reserved statements — proto3's own governance fences — are carried through the transform's annotation channel and re-emitted intact, so a retired number stays fenced across a stateless round trip.

The consequence is the property the review needs: when the exported .proto changes, the diff contains the meaning that changed and nothing else. No churn to skim past. A reviewer who sees a field number move knows something real happened, because nothing cosmetic ever moves.

The loop, end to end

Import additively, with provenance on every node. Read the ledger as a checklist — each entry has a path you can inspect. Gate anything that changes meaning behind a dry run, a validation gate that treats every proposer identically, and a human decision. Export deterministically, so the artifact diffs cleanly and can live in the service repository as source. And when proto3 cannot express something the governed model holds — inheritance, free-standing relations, hierarchies inside an enum, bounds on a repeated field, an element belonging to no message — say so in the report rather than smuggling it into a comment convention the wire format never defined.

Protocol Buffers earned its reputation by refusing to let wire compatibility be an accident. A layer that governs it should refuse to let meaning be an accident. The pull request is where both claims get tested, which is why we optimized for it first.

For the endpoint contract, the role model, and the four lossiness kinds in full, see the schema transform documentation in the CoreModels docs.