Skip to main content

0001 — A recording carries its own prose, not the protobuf project's

  • Status: Accepted.

  • Implementation status: Built. descriptor::splice copies a ca3/ file's bytes unaltered and removes source_code_info from every other file in the closure. Nothing else changed: descriptor.bin is still exactly what protoc emits, and for_message is still a byte splice.

    Re-derive what is in the committed set — the three-way split between structure, our comments, and theirs:

    python - <<'PY'
    from google.protobuf import descriptor_pb2
    raw = open('crates/ca3-canonical/src/generated/descriptor.bin','rb').read()
    fds = descriptor_pb2.FileDescriptorSet(); fds.ParseFromString(raw)
    si = lambda f: f.source_code_info.ByteSize() if f.HasField('source_code_info') else 0
    own = sum(si(f) for f in fds.file if not f.name.startswith('google/'))
    theirs = sum(si(f) for f in fds.file if f.name.startswith('google/'))
    print(len(raw), own, theirs, len(raw) - own - theirs)
    PY
    # 253627 167355 62231 23992

    Re-derive what a recording carries, per schema:

    cargo build --release
    ./target/release/ca3 mock probe.ca3
    ./target/release/ca3 schema probe.ca3 | grep '^schema'

    Re-derive the closure figure the tests pin:

    cargo test -p ca3-canonical --lib the_closure_is_a_fraction -- --nocapture
    # closure = 72756 bytes, whole set = 253627 bytes
  • Date: 2026-08-11

Context

Every CA3 recording embeds a FileDescriptorSet per declared schema — §5.1's transitive closure of the message's imports — and §5's opening requires it twice, once before the first chunk and once in the summary. A restating checkpoint writes it again.

Nine tenths of those bytes are comments. Of the committed 253,627-byte set, 23,992 bytes are structure: field numbers, types, and the §5.3 annotations that state units and frames. 167,355 bytes are ca3/*'s comments and 62,231 are google/protobuf/descriptor.proto's, which an annotated schema's closure reaches because §5.3 hangs the annotations off google.protobuf.FieldOptions.

The line, and it is not about size

ca3/*'s comments stay. A recording should carry, inside itself, the human-readable statement of what its numbers mean, and a_descriptor_carries_the_prose_that_states_the_units is what says so. That is two thirds of a descriptor and it is the format doing its job.

google/protobuf/descriptor.proto's comments go, and the argument that separates them is not that they are large. It is that they are unreachable. They document FieldDescriptorProto — the type a reader must already have parsed in order to be looking at them. A reader that cannot parse a FileDescriptorSet never reaches the explanation of one, and a reader that can already has the file: the Python reader (bindings/python, here) resolves it from protobuf's default pool at import time, and the JavaScript reader — bindings/js when this was written, @extendedresearch/dataviz now — resolves it from node_modules/protobufjs at build time. Neither has ever read a byte of it out of a recording. So those bytes are the one part of a descriptor that cannot teach anybody anything.

Stated as a rule rather than as a list, because a schema that one day imports a third party's file should not have to be noticed: a file this repository wrote keeps its comments; every other file loses them.

The measurement, including the rows where it changes nothing

Per schema, the closure loses a flat 62,236 bytes when it reaches descriptor.proto, and nothing when it does not:

schemaclosure beforeaftersaved
ca3.canonical.v1.GazeSample134,99272,75662,236 (46%)
ca3.canonical.v1.ImuSample121,40159,16562,236 (51%)
ca3.canonical.v1.AudioFrame116,46154,22562,236 (53%)
ca3.canonical.v1.DeviceState114,26852,03262,236 (54%)
ca3.canonical.v1.Event5,6955,6950
ca3.canonical.v1.KeySample16,07116,0710

Event and KeySample carry no physical quantity, so neither imports the options file, so neither closure reaches descriptor.proto at all. They are in this table because a table of only the schemas that improved would not be evidence.

Per recording, and this is the number that decided it — a short recording is where a fixed per-schema cost bites, not a long one:

recordingbeforeaftersaved
conforming.ca3 — 2 streams, 16 messages515,484266,540248,944 (48.3%)
foreign-namespace.ca3515,500266,556248,944 (48.3%)
edited-identifier.ca3488,302239,358248,944 (51.0%)
ca3 mock — 5 schemas, video and audio payloads8,734,4828,236,594497,888 (5.7%)

Nearly half of a two-stream sixteen-message recording was Google's documentation of its own wire format, carried twice per schema. On the 8 MB mock it is 5.7%, and on a long capture it tends toward nothing — the cost is fixed per schema, so it matters exactly where recordings are short and numerous.

What was tried and rejected

Drop google/protobuf/descriptor.proto from the closure entirely — worth 75,416 bytes rather than 62,236, taking an ImuSample closure to 45,985. Rejected because it breaks, tested rather than reasoned about. A Python consumer building a pool from the recording alone gets Couldn't build proto file into descriptor pool: Depends on file 'google/protobuf/descriptor.proto', but it has not been loaded: the options file names it in dependency, and a descriptor set is resolved by name against what it contains. Its declarations are load-bearing even though its comments are not.

Strip ca3/options/v1/options.proto's 29,943 bytes of comments too — worth a further 29,948 per schema, taking a gaze closure to 42,808 bytes. Rejected. It is our file, and its comments are the only thing in a recording that says what (ca3.options.v1.unit) means to somebody holding the file and nothing else. That is the same argument that keeps imu.proto's prose, applied one level up. The distinction that admits descriptor.proto and not this one: a reader needs options.proto's prose after parsing the descriptor, and needs descriptor.proto's only before, when it cannot yet read anything.

Strip in tools/regen-proto instead, so descriptor.bin itself shrinks — would save 62 KB of static data in every binary linking ca3-canonical as well. Rejected for now. The strip must copy spans rather than re-encode, for the reason descriptor's module documentation opens with, and the walker that does that lives in ca3-canonical::wire, which tools/regen-proto must not depend on — it is the tool that generates that crate's sources. Doing it in for_message reuses the walker, puts the decision beside the tests that guard it, and leaves descriptor.bin byte-identical to what protoc emits, which is worth something on its own.

Normalise in conformance::check, so both forms compare equal — would make a pre-strip recording Registered again. Rejected. Whether §5.7's canonical claim should be byte equality at all is a larger decision than this one, and bundling it here would settle it by accident. What was done instead is below: the comparison is untouched and the report gained the distinction.

What it costs, and what had to be fixed for that cost to be honest

A recording written before this is Divergent under conformance::check. That is inherent: §5.7's check has always been byte equality, so every edit to a .proto comment has always done the same thing. This is a large instance of it, and it is the reason to do this now rather than later — §0.6's trigger is the first recording anyone intends to keep, and it has not fired.

What was not acceptable was what the verdict said about such a file. It read:

FAILED ca3.canonical.v1.GazeSample
... the descriptor announced with it is not the registered one
(134992 bytes announced, 72756 registered).
The announcement names a schema it does not carry

The last sentence is false. The file carries exactly the schema it names — every field number, type and §5.3 annotation — and only the comments differ. Somebody sent looking for a corruption on the strength of that sentence finds none, and a verdict that misdescribes why it fired is worse than one that is merely inconvenient.

So Verdict::Divergent gained a divergence field, conformance::Divergence, with two values: Message when the descriptors describe different messages, Prose when they describe the same one and differ only in comments. They are told apart by descriptor::without_comments on both sides — field 9 removed from every file, everything else copied span for span, so every field number, type and annotation survives the comparison. The same split reaches wellformed::check_conformance, which reports the §5.7 breach through a Finding rather than a Verdict, because a distinction made in one report and not the other would be worse than one made nowhere. ca3 conformance counts the three failures apart and prints only the advice that applies.

§5.7's comparison is unchanged, and both kinds are still not canonical. Divergence is computed only after byte equality has already failed. What it decides is the wording.

Verify, on a recording written before the strip:

ca3 conformance old.ca3
# FAILED ca3.canonical.v1.GazeSample
# ... describes the same message (134992 bytes announced, 72756
# registered). Every field number, type and declaration agrees ...

Reading is unaffected, and a_descriptor_written_before_the_strip_still_reads is the regression test. Confirmed against a real file as well: ca3 info, ca3 cat and ca3 validate all read a pre-strip recording, and ca3 schema still reports its units and frames.

It is not a wire-format change. A FileDescriptorSet with less source_code_info is a FileDescriptorSet; source_code_info is an optional field of FileDescriptorProto and every consumer parses the message the same way with it absent.

What would change it

  • §0.6's trigger fires — once field numbers become permanent and a registered schema's descriptor bytes are a published identity, changing what travels in them stops being an edit. Do this before that, or not at all.
  • Something starts reading descriptor.proto's comments out of a recording. Nothing did when this was written: a Python reader, a JavaScript reader and a well-formedness scanner were all checked, and none of the three reads source_code_info from any file. A consumer that wanted to render a descriptor's documentation would want ours, not protobuf's; if one wants protobuf's, this record is wrong.
  • SET's 62 KB of static data starts mattering — an embedded target linking ca3-canonical would be the case. Then move the strip into tools/regen-proto and accept the duplicated walker.
  • A canonical schema imports a third party's file whose comments a reader does need. The rule is an allowlist, so that file would be stripped without anyone deciding to; the allowlist would need to become a list.
  • §5.7 stops being byte equality. Divergence::Prose names, per file, the case a looser comparison would accept. If enough real recordings land there to make the verdict noise rather than information, that is the evidence for reopening the larger question this record deliberately left alone — and ca3 conformance's per-kind counts are where it would show up.