picatz / picatz/flowstate

Decide on the protobuf Opaque API, and record the answer either way

Open
#516 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

design engine kind/decision
Dominant language
Go
Stars
9
Forks
0
Avg merge
3h 3m
Merged PRs (30d)
509

Description

Raised by the owner, 2026-08-13, reading https://go.dev/blog/protobuf-opaque. This issue exists to produce a written decision, because a question this size gets re-proposed every few months and answering it twice is worse than answering it once.

What it is

A protoc-gen-go code generation mode. Generated structs stop exposing fields directly and expose accessors instead, which lets the implementation choose its own memory layout and forbids a class of mistakes: copying a message struct by value, comparing messages with ==, taking the address of a field, distinguishing unset from zero by reading a pointer.

Why it fits this repo in principle

This is a proto-first codebase by policy. CLAUDE.md's first rule is that types describing the system live in the schema rather than as hand-written Go structs, and behaviour attaches to generated types as methods. The Opaque API is the same idea pushed one level further: the generated shape becomes an implementation detail rather than a public struct layout. Nothing about our design argues against it.

What it would actually cost here

  • Every construction site of every generated type, across both drivers, the server, the CLI, the LSP, flowtest and the examples harness.
  • The plugin SDK and the five plugin modules compile against these types as separate modules, so their diffs land at the same time and the boundary has to stay coherent through it.
  • buf generation configuration changes, so the descriptorset pin and the generated-code drift checks move with it.
  • One very large mechanical diff, which is precisely the shape of change where a real defect hides in the noise, and the shape our review process is weakest against. The flow fix corruption history is the local evidence: two of those landed inside changes that looked mechanical.

What we would get today

Mostly latent value. The mistakes it forbids are ones we do not currently appear to make: nothing compares messages with == (comparisons go through proto.Equal or cmp.Diff with protocmp.Transform), and the schema is already the source of truth. So the immediate defect reduction is close to zero, and the benefit is optionality for the protobuf implementation plus protection against future mistakes.

The decision this issue wants

One of:

  1. Adopt now. Justified if we expect the memory or layout benefits to matter, or want the mistake class closed before the codebase gets larger.
  2. Adopt at a natural break, for example alongside #407's proto split, where a large generated diff is already expected and reviewers are already reading generated output carefully.
  3. Decline for now, with the reason recorded, so the next person reading the blog post finds this issue instead of re-deriving it.

My read, for whoever decides: option 2 or 3. The change is philosophically right and practically expensive, and its value does not decay while we wait, whereas the risk of a giant mechanical diff is highest right now, while several language surfaces (triggers, async, interpolation) are still settling. #407 is the natural moment if we want it.

Related, already answered

  • protocmp: already used correctly, four files, always protocmp.Transform() inside cmp.Diff. No gap, nothing to do.
  • dynamicpb: unused and not wanted. Its purpose is building messages from descriptors when the generated type is absent; our plugin boundary deliberately resolves descriptors to validate against known types instead.
  • protorange/protopath: evaluated in #515, which adopted the descriptor-driven idea as a completeness guard rather than as a value-walking traversal.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with CLAUDE.md, the buf generation configuration, the descriptorset pin, and the generated-code drift checks mentioned in the issue. Review the scope across the drivers, server, CLI, LSP, flowtest, examples harness, and plugin modules, then compare the proposed timing with #407. Done means recording a decision to adopt now, adopt at that break, or decline with its reason.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
documentation, tooling
Issue type
Documentation
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.