feat(wit): prevent protobuf and WIT schema drift#498
Conversation
yordis
commented
Jul 22, 2026
- Reduce schema drift between protobuf contracts and component-model consumers.
- Preserve protobuf presence and alternatives across WASM component boundaries.
- Keep contract evolution deterministic and reviewable against scheduler schemas.
PR SummaryMedium Risk Overview Generation maps messages to records, oneofs to variants (with CI and tooling: Go 1.26.5 via mise, aggregate Reviewed by Cursor Bugbot for commit f54154a. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
Warning Review limit reached
Next review available in: 24 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
WalkthroughAdds a Go-based protobuf-to-WIT protoc generator with deterministic naming, schema validation, edition support, WIT rendering, extensive snapshots, and Go workspace CI integration. ChangesProtobuf-to-WIT generator
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant protoc
participant protowit.Run
participant generateFiles
participant render
participant Response
protoc->>protowit.Run: CodeGeneratorRequest
protowit.Run->>generateFiles: generate request
generateFiles->>render: render witSchema
render-->>generateFiles: WIT content
generateFiles-->>protowit.Run: generated files
protowit.Run->>Response: deterministic CodeGeneratorResponse
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a Go-based protoc plugin that renders protobuf message schemas into deterministic WIT interface messages definitions, along with snapshot-based tests/fixtures and CI/mise wiring to prevent schema drift between protobuf contracts and WIT consumers.
Changes:
- Introduce
internal/protowitgenerator (schema building, name normalization, rendering, and typed schema errors) andprotoc-gen-witentrypoint. - Add extensive snapshot fixtures + generator tests covering scalars, oneofs, presence, multi-package ordering, and scheduler event protos.
- Add Go workspace + CI/mise tasks to format/test/vet and validate generated WIT via
wasm-tools.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| goworkspace/internal/protowit/run.go | Protoc plugin I/O wrapper with deterministic response encoding and edition features. |
| goworkspace/internal/protowit/render.go | Renders witSchema into WIT source text. |
| goworkspace/internal/protowit/names.go | Normalizes protobuf identifiers/packages into WIT-safe identifiers and paths. |
| goworkspace/internal/protowit/generate.go | Core schema extraction + WIT type mapping + determinism/cycle checks. |
| goworkspace/internal/protowit/errors.go | Typed schema errors and user-facing error strings for unsupported shapes. |
| goworkspace/internal/protowit/generate_test.go | Snapshot + property tests for determinism, presence, oneofs, imports, and scheduler fixtures. |
| goworkspace/internal/protowit/testdata/snapshots/scalars/example/events-v1/messages.wit | Snapshot fixture for scalar field mapping. |
| goworkspace/internal/protowit/testdata/snapshots/scalar-oneof/example/events-v1/messages.wit | Snapshot fixture for scalar oneof → variant mapping. |
| goworkspace/internal/protowit/testdata/snapshots/message-oneof/example/events-v1/messages.wit | Snapshot fixture for message oneof → variant + empty records. |
| goworkspace/internal/protowit/testdata/snapshots/multiple-oneofs/example/events-v1/messages.wit | Snapshot fixture for multiple oneofs determinism and naming. |
| goworkspace/internal/protowit/testdata/snapshots/oneof-import/example/events-v1/messages.wit | Snapshot fixture for oneof cases that reference imported same-package messages. |
| goworkspace/internal/protowit/testdata/snapshots/referenced-import-inclusion/example/events-v1/messages.wit | Snapshot fixture ensuring referenced same-package imports are included transitively. |
| goworkspace/internal/protowit/testdata/snapshots/imported-file-exclusion/example/events-v1/messages.wit | Snapshot fixture ensuring unreferenced imported files are excluded. |
| goworkspace/internal/protowit/testdata/snapshots/same-package/example/events-v1/messages.wit | Snapshot fixture for aggregating multiple files in the same proto package. |
| goworkspace/internal/protowit/testdata/snapshots/nested-messages/example/events-v1/messages.wit | Snapshot fixture for nested messages and references. |
| goworkspace/internal/protowit/testdata/snapshots/nested-required/example/events-v1/messages.wit | Snapshot fixture for required nested message handling. |
| goworkspace/internal/protowit/testdata/snapshots/proto3-optional/example/events-v1/messages.wit | Snapshot fixture for proto3 optional presence → option<>. |
| goworkspace/internal/protowit/testdata/snapshots/edition-2024-presence/example/events-v1/messages.wit | Snapshot fixture for edition-2024 presence semantics. |
| goworkspace/internal/protowit/testdata/snapshots/duplicate-input-paths/example/events-v1/messages.wit | Snapshot fixture for idempotent duplicate FileToGenerate inputs. |
| goworkspace/internal/protowit/testdata/snapshots/multiple-packages/alpha/messages-v1/messages.wit | Snapshot fixture for deterministic multi-package path ordering (alpha). |
| goworkspace/internal/protowit/testdata/snapshots/multiple-packages/omega/messages-v1/messages.wit | Snapshot fixture for deterministic multi-package path ordering (omega). |
| goworkspace/internal/protowit/testdata/snapshots/scheduler-events/trogonai/scheduler-schedules-v1/messages.wit | Snapshot fixture for real scheduler schedule event protos. |
| goworkspace/cmd/protoc-gen-wit/main.go | protoc-gen-wit binary entrypoint. |
| goworkspace/go.mod | Go module definition for the new Go workspace. |
| goworkspace/go.sum | Go dependency checksums for new workspace dependencies. |
| .mise.toml | Adds Go tool + Go build/check tasks; validates snapshot WIT with wasm-tools. |
| .github/workflows/ci-go.yml | New workflow running Go checks/tests/build (including race tests) on relevant changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@goworkspace/internal/protowit/render.go`:
- Around line 14-24: Update the render flow around record.fields so records with
no fields are not emitted as WIT declarations. Adjust the generator or message
model to omit unreachable empty message records, while preserving rendering of
records containing fields and their existing formatting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a4face1f-ab52-4363-b108-f985aceb5472
⛔ Files ignored due to path filters (1)
goworkspace/go.sumis excluded by!**/*.sum
📒 Files selected for processing (26)
.github/workflows/ci-go.yml.mise.tomlgoworkspace/cmd/protoc-gen-wit/main.gogoworkspace/go.modgoworkspace/internal/protowit/errors.gogoworkspace/internal/protowit/generate.gogoworkspace/internal/protowit/generate_test.gogoworkspace/internal/protowit/names.gogoworkspace/internal/protowit/render.gogoworkspace/internal/protowit/run.gogoworkspace/internal/protowit/testdata/snapshots/duplicate-input-paths/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/edition-2024-presence/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/imported-file-exclusion/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/message-oneof/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/multiple-oneofs/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/multiple-packages/alpha/messages-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/multiple-packages/omega/messages-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/nested-messages/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/nested-required/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/oneof-import/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/proto3-optional/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/referenced-import-inclusion/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/same-package/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/scalar-oneof/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/scalars/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/scheduler-events/trogonai/scheduler-schedules-v1/messages.wit
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
6513fd3 to
fff36e0
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 27 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
goworkspace/internal/protowit/names.go:83
normalizeIdentifierrepeatedly callsoutput.String()inside the hot loop to inspect the last written byte. That incurs extra work (and can allocate depending on Go version) and makes this function slower when processing many identifiers.
Track the last written byte explicitly instead of rebuilding the string each time.
for index := 0; index < len(input); index++ {
current := input[index]
if current == '_' || current == '-' || current == '.' {
if output.Len() > 0 && output.String()[output.Len()-1] != '-' {
output.WriteByte('-')
}
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
894e805 to
e737c3c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
goworkspace/internal/protowit/names.go:84
normalizeIdentifiercallsoutput.String()inside the per-byte loop to inspect the last written character. This allocates repeatedly and can devolve to O(n²) behavior for long identifiers. Track the last written byte instead to keep normalization linear-time.
if current == '_' || current == '-' || current == '.' {
if output.Len() > 0 && output.String()[output.Len()-1] != '-' {
output.WriteByte('-')
}
continue
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
goworkspace/internal/protowit/names.go:81
normalizeIdentifiercallsoutput.String()inside the per-byte loop to inspect the last written byte.strings.Builder.String()allocates a new string each time, which makes this loop O(n²) and adds avoidable allocations for longer identifiers.
Track whether the last emitted byte was a dash (or track the last byte) instead of materializing the builder repeatedly.
var output strings.Builder
for index := 0; index < len(input); index++ {
current := input[index]
if current == '_' || current == '-' || current == '.' {
if output.Len() > 0 && output.String()[output.Len()-1] != '-' {
Empty WIT records are rejected by component tooling, so empty messages now map to payload-less variant cases and the snapshot gate encodes packages to actually validate them. Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…builder Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
.config/mise/tasks/check/go:20
xargs -n1is redundant when used with-I{}(GNU xargs will ignore-nand may emit a warning). Dropping-n1avoids noisy output and keeps the script portable.
Also consider -r to avoid running wasm-tools with no arguments if the snapshot directory is empty.
find internal/protowit/testdata/snapshots -name '*.wit' -exec dirname {} \; \
| sort -u \
| xargs -n1 -I{} wasm-tools component wit {} --wasm -o /dev/null
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
.config/mise/tasks/check/go:20
- The snapshot validation pipeline will invoke
wasm-toolsonce even iffindproduces no directories (xargs default behavior), which can makemise run check:gofail unexpectedly if snapshots are absent/cleaned. Using awhile readloop avoids the empty-input edge case and also handles paths with spaces more safely.
find internal/protowit/testdata/snapshots -name '*.wit' -exec dirname {} \; \
| sort -u \
| xargs -n1 -I{} wasm-tools component wit {} --wasm -o /dev/null
Code Coverage SummaryDetailsDiff against mainResults for commit: f54154a Minimum allowed coverage is ♻️ This comment has been updated with latest results |
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6ba4bc5. Configure here.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
.config/mise/tasks/check/go (1)
18-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the redundant
-n1fromxargs.
-n1and-I{}select mutually exclusive batching modes; GNUxargswarns and ignores-n1, while other implementations may differ.-I{}already processes one input line per invocation. (man7.org)Proposed fix
- | xargs -n1 -I{} wasm-tools component wit {} --wasm -o /dev/null + | xargs -I{} wasm-tools component wit {} --wasm -o /dev/null🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.config/mise/tasks/check/go around lines 18 - 20, Remove the redundant -n1 option from the xargs invocation in the snapshot validation command, keeping -I{} so each input directory is processed in a separate wasm-tools invocation.goworkspace/internal/protowit/generate_test.go (1)
699-706: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider skipping instead of failing when
bufis unavailable.
require.NoErroronexec.LookPath("buf")fails the test outright ifbufisn't on PATH, rather than skipping it. At.Skipwith the same message would makego test ./...runs outsidemisecleaner without losing the guidance.♻️ Proposed change
bufPath, err := exec.LookPath("buf") - require.NoError( - t, - err, - "buf is required to compile repository protobuf fixtures; run tests through mise: mise exec -- go -C goworkspace test ./...", - ) + if err != nil { + t.Skip("buf is required to compile repository protobuf fixtures; run tests through mise: mise exec -- go -C goworkspace test ./...") + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@goworkspace/internal/protowit/generate_test.go` around lines 699 - 706, Update schedulerEventRequest to skip the test when exec.LookPath("buf") cannot find buf, replacing the require.NoError failure while preserving the existing guidance message. Keep the resolved buf path available for the subsequent fixture-compilation logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In @.config/mise/tasks/check/go:
- Around line 18-20: Remove the redundant -n1 option from the xargs invocation
in the snapshot validation command, keeping -I{} so each input directory is
processed in a separate wasm-tools invocation.
In `@goworkspace/internal/protowit/generate_test.go`:
- Around line 699-706: Update schedulerEventRequest to skip the test when
exec.LookPath("buf") cannot find buf, replacing the require.NoError failure
while preserving the existing guidance message. Keep the resolved buf path
available for the subsequent fixture-compilation logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ed31a130-5bd5-41f0-b208-7c4b2430d828
⛔ Files ignored due to path filters (1)
goworkspace/go.sumis excluded by!**/*.sum
📒 Files selected for processing (27)
.config/mise/tasks/check/go.github/workflows/ci-go.yml.mise.tomlgoworkspace/cmd/protoc-gen-wit/main.gogoworkspace/go.modgoworkspace/internal/protowit/errors.gogoworkspace/internal/protowit/generate.gogoworkspace/internal/protowit/generate_test.gogoworkspace/internal/protowit/names.gogoworkspace/internal/protowit/render.gogoworkspace/internal/protowit/run.gogoworkspace/internal/protowit/testdata/snapshots/duplicate-input-paths/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/edition-2024-presence/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/imported-file-exclusion/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/message-oneof/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/multiple-oneofs/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/multiple-packages/alpha/messages-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/multiple-packages/omega/messages-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/nested-messages/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/nested-required/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/oneof-import/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/proto3-optional/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/referenced-import-inclusion/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/same-package/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/scalar-oneof/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/scalars/example/events-v1/messages.witgoworkspace/internal/protowit/testdata/snapshots/scheduler-events/trogonai/scheduler-schedules-v1/messages.wit
🚧 Files skipped from review as they are similar to previous changes (22)
- goworkspace/internal/protowit/testdata/snapshots/proto3-optional/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/multiple-packages/omega/messages-v1/messages.wit
- goworkspace/go.mod
- goworkspace/internal/protowit/testdata/snapshots/duplicate-input-paths/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/nested-messages/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/scalars/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/oneof-import/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/edition-2024-presence/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/same-package/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/multiple-packages/alpha/messages-v1/messages.wit
- goworkspace/cmd/protoc-gen-wit/main.go
- goworkspace/internal/protowit/testdata/snapshots/nested-required/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/referenced-import-inclusion/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/imported-file-exclusion/example/events-v1/messages.wit
- .github/workflows/ci-go.yml
- goworkspace/internal/protowit/render.go
- goworkspace/internal/protowit/testdata/snapshots/scheduler-events/trogonai/scheduler-schedules-v1/messages.wit
- goworkspace/internal/protowit/errors.go
- goworkspace/internal/protowit/testdata/snapshots/scalar-oneof/example/events-v1/messages.wit
- goworkspace/internal/protowit/testdata/snapshots/multiple-oneofs/example/events-v1/messages.wit
- goworkspace/internal/protowit/names.go
- goworkspace/internal/protowit/generate.go
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
