Skip to content

refactor(sidecarapi): extract the sidecar wire contract into its own module - #503

Merged
bdchatham merged 2 commits into
mainfrom
refactor/sidecarapi-contract-module
Aug 13, 2026
Merged

refactor(sidecarapi): extract the sidecar wire contract into its own module#503
bdchatham merged 2 commits into
mainfrom
refactor/sidecarapi-contract-module

Conversation

@bdchatham

@bdchatham bdchatham commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Phase 2 of moving the sidecar out of seictl. This lands the contract module and repoints the controller; the implementation move is Phase 3 (#504).

Why

The controller reached the sidecar through github.com/sei-protocol/seictl, so it depended on the seictl repo for a contract it co-owns. That dependency also made go mod tidy fail outright:

sei-k8s-controller/cmd imports
  seictl/sidecar/client tested by
  seictl/sidecar/client.test imports
  seictl/sidecar/tasks imports
  sei-chain/sei-cosmos/client imports
  gogo/protobuf/grpc: unknown revision v1.3.3

gogo/protobuf v1.3.3 exists only in the regen-network fork, and the replace that redirects it is seictl's — Go ignores replace directives in dependency modules.

What

sidecarapi/ is that contract as its own module: the OpenAPI spec, the client generated from it, and the dependency-free wire types. 8 dependencies, no sei-chain, no k8s, no replace directives (a dependency module's replaces are ignored, so carrying any would be a silent no-op).

The root module resolves it through a filesystem replace, deliberately:

  • a tagged require would need the commit to exist before the commit that adds it, forcing two merges;
  • a go.work is worse — it promotes a used module's replaces to main-module status, so go build ./... locally would succeed where GOWORK=off (Docker, release CI, external consumers) fails. Measured.

Things worth a second look

The k8s replace block is deleted, not inherited. Its comment blamed seictl's transitive constraints. That was true — with seictl dropped, MVS settles k8s at v0.35.1 unaided and controller-runtime v0.23.1 still builds. Re-derived rather than carried forward.

A depguard rule replaces what I first wrote as a bespoke test. contract-stays-light denies the chain graph to anything under sidecarapi/, _test.go files included — which is where this broke before (seictl#238 added such an import to a test). depguard is the conventional mechanism for an import restriction, already ships in the linter this repo runs, and Kubernetes solves the same problem with import-boss.

An earlier revision of this PR had a test that shelled out to go list instead. It was worse in three measurable ways: it only saw the host platform while go mod tidy resolves all of them, go test caching keyed on the wrong inputs so it returned a cached pass with a real leak present, and its deny-list of heavy names missed go-ethereum entirely. Gone.

One detail for reviewers: note the trailing slash on the sidecar/ deny entry. pkg is a prefix match, so without it the rule also denies sidecarapi itself — I hit that while testing.

CI now fans out per module. Go package patterns stop at a nested module boundary, so go list ./... in the root never sees sidecarapi/. A root-only lint and test would have gone green over uncompiled code. Adds a lint matrix, a hygiene job running go mod tidy -diff per module, and MODULES-driven make targets. Between them: depguard catches the offending import, tidy-check catches the unresolvable graph it produces.

openapi.yaml declared no security on /v0/status while the server requires X-Remote-User there. Harmless while nothing reads the spec; not harmless once this module is the contract of record, since reconciling the server down to the spec would expose the status snapshot. /v0/healthz gets an explicit empty security: [] so public-by-design and block-forgotten stay distinguishable.

The 27 import sites change group, not just prefix. The path is under goimports' local-prefixes now, so a bare sed would have left a lint-failing tree.

Test plan

  • gofmt -s, goimports -local, go build ./..., go vet ./... clean
  • make tidy-check — both modules tidy
  • make verify-generated — no generated drift
  • 14 root + 3 sidecarapi test packages pass, 0 failures
  • Controller build closure: 0 sei-chain / cosmos-sdk / cometbft / gogo packages
  • go mod tidy succeeds with no replace for gogo
  • depguard exercised by reintroducing the seictl#238 import — reported at the file with its reason; clean tree reports 0 issues

Not verified locally — please confirm in CI: the Docker builds. No daemon was available, so the .dockerignore fix (!go.mod matched the root only, leaving the replace target out of the build context) was checked by simulating pattern resolution rather than by building. The depguard rule was validated with golangci-lint against a copy of the module with its go directive lowered, because the pinned linter cannot load a go 1.26.0 module in this environment.

Follow-ups, not in this PR

🤖 Generated with Claude Code

@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches the controller’s sidecar HTTP contract and module graph (dropping seictl and k8s replaces); mistakes could break builds, Docker prefetch, or wire compatibility, though behavior is intended to be a straight client move with added CI guards.

Overview
Introduces sidecarapi/ as a standalone Go module (OpenAPI spec, generated client, typed tasks, wire types) and repoints the controller from github.com/sei-protocol/seictl to github.com/sei-protocol/sei-k8s-controller/sidecarapi. The root module drops seictl, adds a filesystem replace for sidecarapi, and removes the old k8s replace pins now that MVS can settle on k8s v0.35.1 without seictl in the graph.

Build & CI now treat nested modules explicitly: MODULES drives per-module lint, test, and tidy-check; CI runs golangci-lint per module and a hygiene job for go mod tidy -diff. Docker/.dockerignore copy nested go.mod/go.sum so go mod download works with the replace target.

Guardrails: depguard contract-stays-light blocks chain/cosmos/cometbft/gogo (and sidecar impl) imports under sidecarapi/; OpenAPI documents /v0/status auth vs public probe paths.

Reviewed by Cursor Bugbot for commit a9d89c5. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7514425. Configure here.

Comment thread .github/workflows/ci.yml
…module

The controller talked to the sidecar through github.com/sei-protocol/seictl,
so it depended on the seictl repo for a contract it co-owns. That dependency
also made `go mod tidy` fail outright: seictl requires gogo/protobuf v1.3.3,
a version only the regen-network fork has, and the replace that redirects it
is seictl's, not ours — Go ignores replace directives in dependency modules.

sidecarapi is that contract as its own module: the OpenAPI spec, the client
generated from it, and the dependency-free wire types. Eight dependencies, no
sei-chain, no k8s, and no replace directives of its own (a dependency
module's replaces are ignored, so carrying any would be a silent no-op).

The root module resolves it through a filesystem `replace`, deliberately, not
a tag. A tagged require would need the commit to exist before the commit that
adds it, forcing this into two merges; a `go.work` is worse — it promotes a
used module's replaces to main-module status, so a local `go build ./...`
would succeed where GOWORK=off (Docker, release CI, external consumers)
fails.

Also here, because each one is load-bearing rather than incidental:

- The k8s v0.35.0 replace block is gone, not inherited. Its comment blamed
  seictl's transitive constraints; that turned out to be true, so with seictl
  dropped MVS settles on v0.35.1 unaided and controller-runtime v0.23.1 still
  builds. Re-derived rather than carried forward.

- A depguard rule, `contract-stays-light`, denies the chain graph to anything
  under sidecarapi/ — _test.go files included, which is where this broke
  before (seictl#238 added such an import to a test and every consumer's
  `go mod tidy` stopped working). depguard is the conventional mechanism for
  an import restriction and it already ships in the linter this repo runs;
  Kubernetes solves the same problem with import-boss. Note the trailing
  slash on the sidecar/ deny entry: `pkg` is a prefix match, so without it the
  rule also denies sidecarapi itself.

- CI now fans out per module. Go package patterns stop at a nested module
  boundary, so `go list ./...` in the root never sees sidecarapi/ — a
  root-only lint and test would have gone green over uncompiled code. Adds a
  lint matrix, a hygiene job running `go mod tidy -diff` per module, and
  MODULES-driven make targets. Between them, depguard catches the offending
  import and tidy-check catches the unresolvable graph it produces.

- Both Dockerfiles copy sidecarapi/go.mod before `go mod download`, and
  .dockerignore re-includes nested module files. `!go.mod` matched the root
  only, so the replace target was absent from the build context.

- openapi.yaml declared no security on /v0/status while the server requires
  X-Remote-User there. Harmless while nothing read the spec; not harmless once
  this module is the contract of record, because reconciling the server
  "down" to it would expose the status snapshot. /v0/healthz gets an explicit
  empty security block so public-by-design and block-forgotten stay
  distinguishable.

The 27 import sites move group, not just prefix — the path is under
goimports' local-prefixes now, so a bare sed would have left a
lint-failing tree.

Verified: gofmt, goimports, go build, go vet, tidy-check and
verify-generated all clean; 14 root and 3 sidecarapi test packages pass; the
controller's build closure holds zero sei-chain/cosmos/cometbft/gogo
packages. The depguard rule was exercised against a reintroduction of the
seictl#238 import and reports it at the file with its reason. Not verified
locally: the Docker builds — no daemon available here, so the .dockerignore
fix was checked by simulating pattern resolution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bdchatham
bdchatham force-pushed the refactor/sidecarapi-contract-module branch from 7514425 to d2da03a Compare August 13, 2026 20:13
Adding sidecarapi to the lint matrix surfaced 51 findings in code that had
never been linted — seictl carries no golangci-lint config at all. The root
module's `lint (.)` leg hides its own backlog behind only-new-issues, but
every file here is new to this branch, so nothing was filtered.

34 modernize hits were interface{} where any will do. Replaced in the three
hand-written files. sidecar.gen.go keeps its two: it is generated and marked
DO NOT EDIT, and the generated-exclusion already keeps it out of the report.

goconst is excluded for sidecarapi/client instead. What it flags there are the
JSON keys inside the request-params maps — chainId, keyName, fees, gas, title,
initialDeposit, address. Those literals are the wire contract: being able to
read a params map and see the JSON it emits is what makes this client
reviewable against the server's struct tags, and it is why the client/server
key mismatch this module was extracted over was findable at all. Hoisting them
behind constants trades that legibility for a DRY score. The rest of the hits
are test fixtures (1usei, config.toml, an RPC URL).

Verified with golangci-lint against the module: 0 issues. Build, tests,
tidy-check and verify-generated unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bdchatham
bdchatham merged commit d1e1bc4 into main Aug 13, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant