Refresh proto stubs after a contract change
Once soundverse-proto republishes its generated SDKs, nothing
in a consumer repo changes until you pull the new stubs in. This page is about that pull — the
last, easy-to-forget hop where a moved contract actually lands in core-database, a Python worker,
or the SaaS frontend.
Three refresh shapes
Section titled “Three refresh shapes”Every consumer ships an update-proto.sh (the frontend calls its update-protos.sh — note the
plural) that pulls the freshly-published stubs for that language. There are three shapes:
| Consumer language | Repos | What its update-proto.sh runs |
|---|---|---|
| Go manual only | core-database, core-mcp |
go get github.com/soundversegit/soundverse-proto-go@staging then go mod tidy |
| Python automated via PR | core-gateway-consumer, core-identity, core-storage, core-billing, all core-tool-*, template-core-tool, soundverse-py |
uv sync --upgrade-package soundverse-proto --refresh-package soundverse-proto (repos that also depend on the SDK add a second line for the soundverse package) |
| Web / TS | soundverse-saas-2.0 |
npm install @soundversegit/soundverse-proto-web --force then a plain npm install + npx tsc --noEmit |
The two Go data-plane services pin a frozen go.mod pseudo-version. There is no automation for
them — bump by hand:
go get github.com/soundversegit/soundverse-proto-go@staginggo mod tidysoundverse-proto-go is the private Go module CI publishes to the staging branch of
soundversegit/soundverse-proto-go. To pin
a specific contract commit for prod, go get …@<commit-sha> instead of @staging, then
go mod tidy.
Every Python consumer refreshes the soundverse-proto stubs; every repo that also depends on the
soundverse SDK (all of them except soundverse-py itself,
which is that SDK) refreshes soundverse too:
uv sync --upgrade-package soundverse-proto --refresh-package soundverse-protouv sync --upgrade-package soundverse --refresh-package soundverse--refresh-package busts uv’s cache for the git-sourced dependency so the second resolve actually
sees the moved branch HEAD, not a cached commit. Keep the order: refresh soundverse-proto first,
because the soundverse build you pull on line two itself pins a soundverse-proto version.
The frontend consumes the compiled TS clients from @soundversegit/soundverse-proto-web (pinned in
package.json as github:soundversegit/soundverse-proto-web#staging). Its refresh is deliberately
more than npm install --force:
npm install @soundversegit/soundverse-proto-web --force # re-resolve the staging branch HEADnpm install # plain install re-validates the WHOLE treenpx tsc --noEmit # typecheck against the new contract--force is required to re-resolve a git branch ref past the commit pinned in
package-lock.json, but it also makes npm accept a broken peer-dep tree. The plain npm install
that follows fails loudly on any conflict --force would have silently baked in (an
OpenTelemetry peer conflict slipped in exactly this way once).
Refresh everything in one pass
Section titled “Refresh everything in one pass”From the super-repo root, update-all-protos.sh
walks every submodule and runs its refresh script:
./update-all-protos.sh # finds each repo's update-proto.sh / update-protos.sh and runs it in placeIt finds update-proto.sh and update-protos.sh at -maxdepth 2, so both the Python/Go
scripts and the frontend’s plural-named one get picked up.
Refresh a single consumer
Section titled “Refresh a single consumer”-
Confirm the contract is published. Your change must already be on
maininsoundverse-proto(staging release fans it out to the per-language repos’stagingbranches). If you’re still iterating locally, don’t use this flow — point the dependency at a local path or feature branch and regenerate in place. See Run proto codegen locally. -
Run the repo’s refresh script from inside that repo:
Terminal window cd core-tool-sansaarm && ./update-proto.sh -
Typecheck / build against the new contract. For Python,
make check(ruff + pyright strict + pytest) is the exact CI gate; for Go,go build ./...; the frontend’s script already runsnpx tsc --noEmit. -
Commit the lockfile. The diff is the pin only —
uv.lockfor Python,go.mod/go.sumfor Go,package-lock.jsonfor the frontend. No generated source is vendored (proto output lives in the published packages, andsoundverse-proto’s owngen/is gitignored).
The automated Python path Deployed
Section titled “The automated Python path ”You rarely run the Python update-proto.sh by hand, because CI opens the bump for you. Each Python
consumer carries .github/workflows/update-private-deps.yml,
which runs uv lock --upgrade-package <dep> and opens (or updates in place) a single PR on the
fixed branch automation/update-private-deps. It never auto-merges — a human merges once CI is
green.
It fires on three triggers:
repository_dispatch— the instant path.soundverse-proto’s staging release dispatchesproto_updatedto thecore*repos andsoundverse-py; mergingsoundverse-pythen dispatchessoundverse_updatedto thecore-tool-*repos +template-core-tool. That’s a two-hop cascade (proto → SDK → workers), covered end-to-end in Drive the proto → deploy cascade.- a daily
cronat0 6 * * *— a safety net for any missed dispatch. workflow_dispatch— fire it manually from the Actions tab.
Related
Section titled “Related”- Drive the proto → deploy cascade — the two-hop Python fan-out and the manual Go path, end to end
- Run proto codegen locally — regenerate the SDKs before you refresh them
- The contract: proto as source of truth — why
soundverse-protois authoritative - Test unmerged soundverse-py SDK changes — the editable-overlay escape hatch
- core-database — the Go data plane whose stubs you refresh manually