DRV-138: own the schema provenance stamp and check - #34
Merged
Merged
Conversation
A generated schema is marked "Do not edit manually" and nothing asserted it, so hand edits and generator drift both landed silently. control4-esphome#131 fixed that with a provenance header, but put the prover in the driver repo. The script is consumer-agnostic in shape and reads this repo's own VERSION convention, so it belongs with the generator that emits the banner it stamps over. It also enforces an invariant that is this library's to own: the schema, the runtime protobuf.lua that decodes against it, and now the prover all come from one release. A consumer vendors the script alongside the runtime, so the two versions travel together. Generalized from three fixed arguments to opaque key=value fields. The tool records and compares whatever a caller passes without interpreting it; control4-esphome passes esphome=<version>. A field recorded in the header but not supplied on the command line now fails rather than going uncompared, so a consumer cannot silently retire a comparison by dropping it from a Makefile. The header format is unchanged: this tool re-stamps control4-esphome's committed proto_schema.lua to the same bytes the embedded copy produces, and validates the already-stamped file without a re-stamp. The 13 positive controls from #131 move here as the library's own test, plus 14 covering the opaque-field handling and the loud-failure paths for an untagged or non-tag runtime VERSION. Wired into `make check` as check-provenance: it needs no venv, no protoc and no network, the same footing a consumer's check runs on.
Its previous slot, immediately above the stylua targets, is the same insertion point lua-protobuf#33 uses for check-option-order, so the two conflicted on an anchor rather than on anything semantic.
The paragraph told consumers to vendor `tools/proto-provenance` alongside `protobuf.lua` and run their own copy. The tool stays here, and a consumer reaches it by checking out this repo at the release tag its vendored `protobuf.lua` came from, so the prover and the runtime it asserts against stay a matched pair. That also makes "no lua-protobuf checkout" false: the checkout is how the consumer gets the tool. What `check` actually avoids is regenerating, and the toolchain and network a regeneration would need.
derek-miller
approved these changes
Sep 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes the proto-schema provenance mechanism out of control4-esphome and into this repo, so every consumer that generates a schema from the library gets it and the prover travels with the release it proves.
Part of DRV-138. Prompted by control4-esphome#131 (DRV-128), which added
tools/proto-provenanceto the driver repo; that PR is reworked to consume this instead of shipping its own copy.Why it belongs here
local VERSION = "..."from the vendored runtime. That is this repo's convention, injected bygit describein our ownbuildtarget. There is no ESPHome in it.tools/gen_lua_proto_schemaemits. Those two strings had to agree across repos and now sit in one.tools/already carriesgen_*andcheck_*, includingcheck_schema_refs.lua.protobuf.luathat decodes against it, and now the prover all come from one release.What the tool does
stampwrites a header;checkasserts three things against files already in the tree.generatorequalsVERSIONin the vendored runtimekey=valueequals the header line of that namebody-sha256equals a fresh hash of everything below the boundaryNo protoc, no Python, no stylua, no network. POSIX shell. A consumer gets the
tool by checking out this repo at the release tag its vendored
protobuf.luacame from, which needs no toolchain, and runs it against its own tree.
Comparison 1 is the matched-pair invariant, carried over from
a0780c1con #131: the generator that emits the schema and the runtime that decodes it ship from one release, and how a field is represented is co-designed with how it is read back. It is not a staleness check with false positives.What changed in the move
Consumer fields are now opaque. The signature went from three fixed arguments to
key=valuepairs the tool records and compares without interpreting. control4-esphome passesesphome=2026.8.2; this repo does not know what ESPHome is.generatorandbody-sha256are reserved.An unsupplied field is an error, not a pass.
checkcompares the set of keys recorded in the header against the set supplied on the command line, both directions. Without that, a consumer that droppedesphome=from its Makefile would silently retire comparison 2 while still reporting green. This also closes a header-injection path: a-- protoc: 29.3line added above the boundary is not covered by the body hash, but it now fails the key-set comparison.The header format is unchanged. Verified two ways against control4-esphome's committed
proto_schema.lua:So the consumer rework needs no re-stamp.
Controls
The 13 from #131 move here as this repo's own test of the tool, plus 14 more.
make check-provenance, 27 total, each perturbing one thing and restoring before the next.generatormoved back a release; runtime moved forward; consumer field bumped without restampingbody-sha256removed; duplicate boundary; runtimeVERSION=dev; runtimeVERSIONnot a release tagkey=value; zero-field round trip; two-field round trip checked in the other orderThe hash-boundary group is asserted from both sides on purpose. An always-red or always-green boundary is the failure mode that would make the whole check theatre.
The suite is mutation-tested. Making each comparison vacuous in turn fails exactly the controls that cover it and no others:
generatormoved back, runtime moved forward)Without that, 27 green controls would be evidence about the harness rather than about the tool.
Verification
make checkexit 0, withcheck-provenancein the chain (make -n checkshows the recipe)make testexit 0, 8/8 modules across both math modesdashas well assh, because CI's/bin/shis dashmake check-provenancered, exit 2, so the target is not decorativeOne local note that is not about this branch:
make lintfails on this machine because Homebrew's stockluais 5.5 and luacheck 1.2.0 cannot load under it. Pristinemainfails identically, so it is environmental. Re-run under a 5.4 rocks tree it is 0 warnings / 0 errors, which is what CI's pinned 5.4 will do.Not in this PR
tools/check_option_orderin #33 lands at the same Makefile anchor this originally used, socheck-provenanceis grouped with the othercheck-*targets instead.git merge-treeagainst #33's head is conflict-free in both orders.Releasing is a separate step: control4-esphome cannot vendor this until a tag carries it.