0001 — A recording carries its own prose, not the protobuf project's
-
Status: Accepted.
-
Implementation status: Built.
descriptor::splicecopies aca3/file's bytes unaltered and removessource_code_infofrom every other file in the closure. Nothing else changed:descriptor.binis still exactly whatprotocemits, andfor_messageis 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_pb2raw = 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 0own = 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 23992Re-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:
| schema | closure before | after | saved |
|---|---|---|---|
ca3.canonical.v1.GazeSample | 134,992 | 72,756 | 62,236 (46%) |
ca3.canonical.v1.ImuSample | 121,401 | 59,165 | 62,236 (51%) |
ca3.canonical.v1.AudioFrame | 116,461 | 54,225 | 62,236 (53%) |
ca3.canonical.v1.DeviceState | 114,268 | 52,032 | 62,236 (54%) |
ca3.canonical.v1.Event | 5,695 | 5,695 | 0 |
ca3.canonical.v1.KeySample | 16,071 | 16,071 | 0 |
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:
| recording | before | after | saved |
|---|---|---|---|
conforming.ca3 — 2 streams, 16 messages | 515,484 | 266,540 | 248,944 (48.3%) |
foreign-namespace.ca3 | 515,500 | 266,556 | 248,944 (48.3%) |
edited-identifier.ca3 | 488,302 | 239,358 | 248,944 (51.0%) |
ca3 mock — 5 schemas, video and audio payloads | 8,734,482 | 8,236,594 | 497,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 readssource_code_infofrom 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 linkingca3-canonicalwould be the case. Then move the strip intotools/regen-protoand 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::Prosenames, 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 — andca3 conformance's per-kind counts are where it would show up.