The wire types
Every request/response message, enum and service, generated once and published to a per-language repo. A field renamed in the proto is a compile error in every consumer.
This is the load-bearing idea of Soundverse 2.0 Deployed. There is
exactly one source of truth for every wire shape and for every SQL-backed data
service: the soundverse-proto
repository. You write .proto files, annotate the data-plane RPCs with SQL, and a custom
toolchain generates the per-language SDKs plus the entire Go Connect service that talks to
Postgres. Nobody hand-writes a data service. Get this page and the rest of the codebase
reads itself.
This is the explanation — the “why” and the shape. For the step-by-step recipes see the how-to pages linked throughout and in Related.
The backend is a contract-first polyrepo: ~20 submodules in Go, Python, TypeScript, Swift and
Kotlin, all speaking the same wire types. A shared shape that lives in more than one place
drifts, so it lives in exactly one — the proto repo, mounted as the soundverse-proto/
submodule (it tracks the staging branch). Two very different things flow out of it:
The wire types
Every request/response message, enum and service, generated once and published to a per-language repo. A field renamed in the proto is a compile error in every consumer.
The data plane itself
Annotate a data RPC with the SQL it runs, and the toolchain emits the whole Go Connect
handler — pool selection, $N binding, row scanning, Redis caching, error mapping. See
How the DB codegen plugin works.
Every package is named soundverse_proto.<domain>.<...>.v1, one directory per domain under
proto/soundverse_proto/.
Two directories are special:
RequestContext (embedded in every request) + ErrorDetailThe full package/table catalog lives in the Proto contract reference.
All of codegen is driven by a handful of custom proto extensions declared in
db/v1/db_options.proto.
Method options describe an RPC; field options map a message field to a SQL column. A
data RPC is fully specified by its annotations — the plugin needs nothing else.
| Annotation | Extends | Option # | Role |
|---|---|---|---|
target_db |
method | 50001 | Pool key (e.g. "prod") selecting which pgxpool.Pool runs the query |
sql_query |
method | 50002 | The literal SQL the handler executes (explicit columns, no SELECT *; mutations need a WHERE) |
cache_ttl_seconds |
method | 50003 | >0 turns on read-through Redis caching of the marshalled response |
db_column |
field | 50004 | Maps a message field to a SQL result column for scanning |
require_internal_auth |
method | 50005 | Reject any bearer token ≠ the internal secret with CodeUnauthenticated |
cache_key |
method | 50006 | Key template like tools:active:{environment}; {name} binds to request fields |
invalidates_keys |
method | 50007 | Repeated key templates the handler DELs after a successful write |
db_enum_text |
field | 50008 | Enum request field targeting a Postgres ENUM: send the lowercase label, not the int |
From those, cmd/protoc-gen-soundverse-db
emits a complete Connect handler per data service. It fails the build on a dangerous or
inconsistent annotation (no SELECT *, no WHERE-less mutation, every projected column must
have a matching db_column). The full handler walkthrough is in
How the DB codegen plugin works.
schema.sql files sit next to the protos they back (e.g.
generation/task/v1/schema.sql).
They are hand-maintained reference DDL — not generated, not auto-applied, there is no
migration runner. Four things must always agree: the sql_query column list, the db_column
field annotations, the schema.sql table, and the request fields bound to $N.
Generation runs through four buf templates, in this exact order (it mirrors
ci.yml).
The first three are buf generate runs; the fourth is a plain Go program.
| # | Template / command | Emits |
|---|---|---|
| 1 | buf.gen.yaml |
Standard SDKs — Go (protocolbuffers/go + grpc/go), Python (+ mypy stubs), Swift, Kotlin |
| 2 | buf.gen.connect.yaml |
Connect Go handlers (connectrpc/go) + Connect-ES TypeScript clients (bufbuild/es, protobuf-es v2) into soundverse-proto-web |
| 3 | buf.gen.soundverse-db.yaml |
The custom DB plugin ./bin/protoc-gen-soundverse-db → one *.soundverse_db.go per data service |
| 4 | cmd/gen-db-registry (go run .) |
Scans the generated files → registry/db_registry.go with one RegisterAllDBServices(...) mount function |
A single .proto edit propagates across the fleet along two lanes with very different
ergonomics — automatic for Python, manual for Go.
flowchart TD P["soundverse-proto<br/>.proto + DB annotations + schema.sql"] P -->|"buf.gen.yaml"| SDK["soundverse-proto-go / -python<br/>-swift / -kotlin"] P -->|"buf.gen.connect.yaml"| WEB["soundverse-proto-web<br/>Connect-ES TS clients"] P -->|"protoc-gen-soundverse-db<br/>+ gen-db-registry"| DBH["*.soundverse_db.go + db_registry.go<br/>(in soundverse-proto-go)"] SDK -->|"proto_updated dispatch<br/>uv.lock bump PR"| PY["Python consumers<br/>gateways · identity · storage · soundverse-py"] PY -->|"soundverse_updated dispatch<br/>2nd hop"| WK["tool workers<br/>core-tool-*"] WEB --> FE["soundverse-saas-2.0"] DBH -.->|"MANUAL: go get @staging + go mod tidy"| GO["core-database · core-mcp"] classDef gateway fill:#0ea5e9,color:#fff,stroke:#0369a1 classDef data fill:#8b5cf6,color:#fff,stroke:#6d28d9 classDef worker fill:#10b981,color:#fff,stroke:#047857 classDef frontend fill:#6366f1,color:#fff,stroke:#4338ca class P data class GO data class PY gateway class WK worker class FE frontend
Release lanes. Pushing to the proto repo’s staging branch publishes rolling dev builds
to each language repo’s staging branch; pushing to prod cuts immutable versioned
releases.
Python — two hops, automatic. The staging publish also fires a proto_updated
repository_dispatch to the core* repos and to soundverse-py, each of which opens an
update-private-deps PR that re-locks soundverse-proto in uv.lock. Merging the
soundverse-py bump moves its main, which fires a second soundverse_updated dispatch to
the tool workers (core-tool-* + template-core-tool) so they re-lock soundverse. So a
contract change reaches a worker in two hops: proto → soundverse-py → worker.
Go — fully manual. The two Go consumers, core-database
and core-mcp, are outside the auto-bump system. They pin a frozen go.mod pseudo-version and
bump it by hand via each repo’s update-proto.sh
(go get github.com/soundversegit/soundverse-proto-go@staging && go mod tidy).