Skip to content

feat(configmanager): SeiConfigManager v2 body — validate-passthrough (PLT-775 PR2)#3678

Open
bdchatham wants to merge 3 commits into
mainfrom
feat/configmanager-v2-body-plt775
Open

feat(configmanager): SeiConfigManager v2 body — validate-passthrough (PLT-775 PR2)#3678
bdchatham wants to merge 3 commits into
mainfrom
feat/configmanager-v2-body-plt775

Conversation

@bdchatham

Copy link
Copy Markdown
Contributor

What

PR2 of the ConfigManager MVP (PLT-775): fills in the v2 SeiConfigManager behind SEI_CONFIG_MANAGER=v2 (the seam landed in #3671).

v2 boot is validate-passthrough. It reads the config through the sei-config unified model to validate it, then re-enters the unchanged legacy reader on the operator's original files. It does not rewrite, migrate, or author sei.toml at boot. So the two consumed channels (serverCtx.Config + serverCtx.Viper) are produced identically to legacy by construction; v2's boot value-add is the validation pass.

  • SeiConfigManager.Apply: resolveHomeDir (mirrors the legacy handler's flag > SEID_ env > default resolution exactly) → ReadConfigFromDirValidate → re-enter. Advisory: read/validate problems are logged, never refuse boot.
  • Pins sei-config v0.0.21 (the merged lenient-decode fix, sei-config#36) so a real seid config.toml parses for validation.
  • Legacy-vs-v2 differential test: both managers read the same home (v2 is passthrough); serverCtx.Config + Viper.AllSettings() must match, incl. after the start.go chain-id mutation.

Scope / constraints

  • Zero edits to the vendored sei-cosmos fork. Diff is confined to cmd/seid/cmd/configmanager/ + the differential test + go.mod/go.sum.

Acceptance-criteria drift contract

Per the canonical design (designs/config-manager/DESIGN.md, updated with the boot-path pivot):

Satisfies: Legacy untouched · v2 parity by construction (differential) · No silent fallback · Validation advisory (logged, never refuses boot; fails identically to legacy on a bad config) · Out-of-seam surface understood.

Deferred to the generate path (not MVP boot): the SEI_ collision audit, model→legacy render fidelity (the quoted-primitive / pruning write-side round-trip), and fatal validation (un-defer once sei-config read fidelity is hardened — a pruning read-mapping gap is surfaced advisory today).

Lineage

  • Issue: PLT-775 (rides the issue directly).
  • Design: bdchatham-designs designs/config-manager/DESIGN.md.
  • Depends on: sei-config#36 (merged, v0.0.21).

🤖 Generated with Claude Code

@cursor

cursor Bot commented Jun 30, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes node boot when SEI_CONFIG_MANAGER=v2 is enabled, but runtime config parity is guarded by differential tests; validation is advisory only and does not alter on-disk config.

Overview
When SEI_CONFIG_MANAGER=v2 is set, SeiConfigManager no longer returns “not implemented” error. Boot runs an advisory validate-passthrough: resolve --home the same way as legacy, ReadConfigFromDir + Validate via sei-config, log diagnostics to stderr (never block startup), then always delegate to InterceptConfigsPreRunHandler on the operator’s existing config.toml / app.toml without writing or migrating files.

Dependency: pins github.com/sei-protocol/sei-config v0.0.21 (and bumps BurntSushi/toml / mapstructure transitively). resolveHomeDir helper and unit tests replace the old stub test; a legacy vs v2 differential test asserts serverCtx.Config and Viper snapshot match after PreRun on the same home directory.

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

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJul 1, 2026, 4:44 PM

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.30%. Comparing base (8ebedda) to head (c18b062).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
cmd/seid/cmd/configmanager/configmanager.go 50.00% 6 Missing and 5 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3678      +/-   ##
==========================================
- Coverage   59.24%   58.30%   -0.94%     
==========================================
  Files        2272     2185      -87     
  Lines      188175   178428    -9747     
==========================================
- Hits       111479   104039    -7440     
+ Misses      66657    65149    -1508     
+ Partials    10039     9240     -799     
Flag Coverage Δ
sei-chain-pr 24.68% <50.00%> (?)
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cmd/seid/cmd/configmanager/configmanager.go 65.62% <50.00%> (-17.71%) ⬇️

... and 87 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seidroid seidroid 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.

Small, well-documented, well-tested change that fills in the v2 SeiConfigManager as a validate-passthrough behind SEI_CONFIG_MANAGER=v2; resolveHomeDir faithfully mirrors the legacy home resolution and all validation is advisory, so there are no correctness or boot-safety blockers. Only a stale doc-comment inconsistency and dependency-bump verification are worth noting.

Findings: 0 blocking | 4 non-blocking | 1 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • Cursor's second-opinion review (cursor-review.md) was empty — that pass produced no output. Codex (codex-review.md) reported no material findings but noted it could not run targeted tests due to a Go version mismatch (sandbox 1.24.13 vs required 1.25.6), so its pass was static-only.
  • go.mod/go.sum bump three deps (add sei-config v0.0.21, BurntSushi/toml 1.4.1-pseudo → 1.5.0, mapstructure v2.4.0 → v2.5.0). The toml and mapstructure bumps appear to be transitive pulls from sei-config; worth confirming go mod tidy is clean and that the toml 1.5.0 bump doesn't alter behavior of any existing config rendering/parsing in the repo, since CI here could not be run against Go 1.25.6.
  • Consider a follow-up note/tracking item so the advisory validation messages printed to stderr on every v2 boot (e.g. false-positive validation issues while sei-config read fidelity is still being hardened) don't become persistent log noise before validation is promoted to fatal.
  • 1 suggestion(s)/nit(s) flagged inline on specific lines.

// design (bdchatham-designs designs/config-manager/DESIGN.md).
// LegacyConfigManager re-enters the unchanged legacy handler verbatim (the
// default). SeiConfigManager re-authors the existing config through the
// sei-config library and then re-enters the same reader, so both channels are

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] Stale/misleading wording: this package-level doc says v2 "re-authors the existing config through the sei-config library," but the PR's whole thesis — and the SeiConfigManager/Apply doc comments below (lines 59-65, 75-77) — is that v2 does NOT rewrite/re-author; it reads the config only to validate, then re-enters the legacy reader on the operator's original files. "re-authors" implies a write and contradicts the validate-passthrough design. Suggest rewording to something like "reads the existing config through the sei-config library to validate it, then re-enters the same legacy reader."

Comment on lines +87 to +89
} else if res := seiconfig.Validate(cfg); res.HasErrors() {
fmt.Fprintf(cmd.ErrOrStderr(), "config-manager v2: validation found issues (advisory, not enforced): %v\n", res.Errors())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 v2's boot value-add is the validation pass, but the code drops all SeverityWarning diagnostics: HasErrors() returns false for warning-only results (the entire branch is skipped) and Errors() filters warnings out even when both severities are present. Operator-facing diagnostics like "test_only_dual_write must not be used in production", "unusual backend", and "EVM RPC is enabled on a validator node" — exactly the misconfigurations a validation pass exists to surface — are silently swallowed. Fix by gating on len(res.Diagnostics) > 0 (or HasErrors() || HasWarnings()) and printing res.Diagnostics rather than just res.Errors().

Extended reasoning...

The bug\n\nIn cmd/seid/cmd/configmanager/configmanager.go at lines 87-89:\n\ngo\n} else if res := seiconfig.Validate(cfg); res.HasErrors() {\n fmt.Fprintf(cmd.ErrOrStderr(), "config-manager v2: validation found issues (advisory, not enforced): %v\n", res.Errors())\n}\n\n\nBoth the gate (HasErrors()) and the printed value (Errors()) filter to SeverityError only. Warnings — which sei-config explicitly defines as part of the validation diagnostics — never reach the operator.\n\n## What sei-config actually returns\n\nReading sei-config@v0.0.21/validate.go directly confirms:\n\n- The library distinguishes three severities (validate.go:11-15): SeverityError ("prevents seid from starting"), SeverityWarning ("logged but does not block startup"), SeverityInfo.\n- ValidationResult.HasErrors() (lines 54-61) returns true only if a SeverityError is present.\n- ValidationResult.Errors() (lines 63-71) returns only SeverityError diagnostics, filtering out warnings.\n- The library emits addWarning for real, operator-actionable misconfigurations:\n - storage.state_commit.write_mode: "test_only_dual_write must not be used in production" (line 264)\n - storage.state_store.backend: "unusual backend %q; expected pebbledb or rocksdb" (line 278-279)\n - evm: "EVM RPC is enabled on a validator node; this is unusual and may increase attack surface" (line 292)\n\nThe library's own contract is that warnings should be logged (just not enforced). The v2 boot code never logs them.\n\n## Step-by-step proof\n\nConsider a validator operator who sets storage.state_commit.write_mode = test_only_dual_write in sei.toml — a config that is otherwise valid (no SeverityError diagnostics).\n\n1. seiconfig.ReadConfigFromDir(home) succeeds and returns cfg.\n2. seiconfig.Validate(cfg) runs validateStorage (validate.go:249). At line 263-265, since sc.WriteMode == WriteModeTestOnlyDualWrite, it appends a single Diagnostic{SeverityWarning, "storage.state_commit.write_mode", "test_only_dual_write must not be used in production"} to res.Diagnostics. No addError is called for this field.\n3. res.HasErrors() iterates res.Diagnostics, finds the single entry has Severity == SeverityWarning, and returns false.\n4. The else if branch is skipped entirely. Nothing is printed.\n5. The operator boots a validator with a production-unsafe write mode and receives no warning from the v2 manager — exactly the failure the validation pass exists to prevent.\n\nThe same hole applies to a validator that enables EVM RPC, or to any unusual state-store backend.\n\n## Why this matters here\n\nThe PR's doc comment on SeiConfigManager explicitly says it "surfaces validation diagnostics", and the PR description frames v2's boot value-add as "the validation pass". Warnings are diagnostics by sei-config's own contract. Dropping them defeats the only feature v2 adds over legacy at boot — there is nothing else v2 does differently on the boot path (it is a passthrough otherwise).\n\n## Fix\n\nGate on the presence of any diagnostic, and print all of them:\n\ngo\n} else if res := seiconfig.Validate(cfg); len(res.Diagnostics) > 0 {\n fmt.Fprintf(cmd.ErrOrStderr(), "config-manager v2: validation diagnostics (advisory, not enforced): %v\n", res.Diagnostics)\n}\n\n\nDiagnostic.String() (validate.go:37-39) already prefixes each entry with [ERROR]/[WARNING] so the operator can still tell which findings are blocking and which are advisory.

Comment on lines +5 to +9
// LegacyConfigManager re-enters the unchanged legacy handler verbatim (the
// default). SeiConfigManager re-authors the existing config through the
// sei-config library and then re-enters the same reader, so both channels are
// produced identically to legacy. See PLT-775 and the canonical design
// (bdchatham-designs designs/config-manager/DESIGN.md).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The package-level doc at the top of cmd/seid/cmd/configmanager/configmanager.go is stale in two places. Line 2 still calls the sei-config-backed manager (forthcoming) even though this very PR lands it, and lines 5-9 say SeiConfigManager re-authors the existing config through the sei-config library — but the actual implementation (and the type-level doc 30 lines below) explicitly says it does NOT rewrite/re-author anything; it's a validate-passthrough. Suggest replacing re-authors with something like reads and validates and dropping (forthcoming) so the package-level doc matches the rest of the file (and the PR description).

Extended reasoning...

What the bug is

The package-level doc comment that sits at the top of cmd/seid/cmd/configmanager/configmanager.go (lines 1-10) describes a version of SeiConfigManager that does not match what this PR actually ships. Two distinct stale phrases:

  1. Line 2 still describes the sei-config-backed manager as (forthcoming). That phrasing was accurate in PR1 (feat(seid): ConfigManager selection seam (PLT-775 PR1) #3671, seam only), but this PR (PLT-775 PR2) implements SeiConfigManager.Apply — it imports seiconfig, calls ReadConfigFromDir + Validate, and is wired into Select. The manager is no longer forthcoming; it is the active validate-passthrough.

  2. Lines 5-9 say: SeiConfigManager re-authors the existing config through the sei-config library and then re-enters the same reader, so both channels are produced identically to legacy. The word re-authors is wrong — Apply does not author/rewrite anything on disk. This contradicts (a) the type-level doc directly below at lines 59-65 (it does NOT rewrite them, migrate (...), or author sei.toml), (b) the ConfigManager interface doc at lines 35-37 (v2 reads the config to validate it, then re-enters the unchanged legacy reader), and (c) the PR description itself (v2 boot is validate-passthrough... It does not rewrite, migrate, or author sei.toml at boot).

Step-by-step proof

Walk a reader through the file top-to-bottom:

  1. Line 2-3 — reader is told the manager is (forthcoming).
  2. Lines 5-9 — reader is told SeiConfigManager re-authors the existing config through the sei-config library and then re-enters the same reader. Reader forms mental model: "v2 rewrites the legacy files via sei-config, then re-reads them."
  3. Lines 35-37 — interface doc says The boot path does not re-render the legacy files: v2 reads the config to validate it, then re-enters the unchanged legacy reader on the operator's existing files. Reader is confused: "Wait, does it re-render or not?"
  4. Lines 59-65 — type-level doc on SeiConfigManager says it does NOT rewrite them, migrate (...), or author sei.toml. Reader concludes: "OK, the top comment must be wrong."
  5. Lines 78-93Apply body confirms: resolveHomeDirseiconfig.ReadConfigFromDirseiconfig.Validate → log advisory diagnostics → server.InterceptConfigsPreRunHandler. No writes. The type-level doc is authoritative; the package-level doc was missed when the v2 plan pivoted from "re-author the legacy files" to "validate-passthrough".

Impact and fix

Doc-only — there is no runtime impact, no operator-visible behavior change, and the authoritative type-level doc is correct. But it is the very first thing a reader of this package sees, and it presents a mental model that the rest of the file immediately contradicts. Worth fixing in one small edit:

  • drop (forthcoming) from line 2
  • replace re-authors the existing config through the sei-config library and then re-enters the same reader with something like reads and validates the existing config through the sei-config library, then re-enters the unchanged legacy reader

Severity: nit. Two verifiers confirmed each sub-finding independently with no refutations, all agreeing this is a stale-comment cleanup rather than a functional bug.

@bdchatham

Copy link
Copy Markdown
Contributor Author

Review-gate summary (clean)

Coral /xreview slate — all RATIFY:

  • idiomatic-reviewer: RATIFY (reads native).
  • sei-network-specialist: COMPATIBLE — and verified advisory-not-fatal is the correct MVP choice (a default node fails sei-config validation today via the pruning template-omission gap, so fatal would refuse every node).
  • systems-engineer (assigned dissenter): RATIFY, no must-fix — verified resolveHomeDir fidelity, passthrough inertness, and the os.ErrNotExist chain.

Findings (all [should]/[nit]) resolved: package-comment doc-drift fixed; validation-error advisory made louder; on-disk-fidelity note added; resolveHomeDir test added. Env-precedence + mixed-home differential rows are deferred to the generate-path PR (the boot differential already proves passthrough parity by construction).

Bots: Cursor Bugbot ✅ SUCCESS; seidroid ✅ APPROVED (0 blocking).

Expected, not a regression

On a default node, v2 logs a benign advisory storage.pruning validation error — sei-config reads the on-disk app.toml directly, and the template omits the pruning key that cosmos defaults in code. Validation is advisory (never refuses boot); promoting to fatal is the un-defer once sei-config's read replays template-omitted defaults. Boot is identical to legacy by construction.

Merge prerequisites (separate from the review-gate)

Standard CI (Test / Lint / Race / etc.) + the category label, same as the seam PR.

bdchatham and others added 3 commits July 1, 2026 09:42
…(PLT-775 PR2)

Fills in the v2 ConfigManager behind SEI_CONFIG_MANAGER=v2 (seam landed in
#3671). v2 boot reads the config through the sei-config unified model to
VALIDATE it, then re-enters the unchanged legacy reader on the operator's
ORIGINAL files — it does not rewrite, migrate, or author sei.toml at boot.
The two consumed channels are produced identically to legacy by construction;
v2's boot value-add is the validation pass.

- SeiConfigManager.Apply: resolveHomeDir (mirrors the legacy handler's
  flag>SEID_env>default resolution exactly) -> ReadConfigFromDir -> Validate
  -> re-enter. Advisory: read/validate problems are logged, never refuse boot.
- Pin sei-config v0.0.21 (merged lenient-decode fix) so a real seid
  config.toml can be read for validation.
- Legacy-vs-v2 differential test: both managers read the same home (v2 is
  passthrough); serverCtx.Config + Viper.AllSettings() must match, incl. after
  the start.go chain-id mutation.

Design captured in bdchatham-designs designs/config-manager/DESIGN.md
(validate-passthrough; advisory validation; sei.toml-as-source at the generate
path; explicit migration). Fatal validation is the un-defer once sei-config
read fidelity is hardened (a pruning read-mapping gap is surfaced advisory).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y, home test)

PR2 xreview slate (all RATIFY) follow-ups:
- doc: package comment said v2 "re-authors" — stale from the old write-then-
  re-enter model; v2 reads to validate, does not rewrite (idiomatic-reviewer
  + seidroid, convergent).
- advisory: distinguish validation ERRORS with a louder lead-in — they are the
  same SeverityError class (sc-write-mode) legacy panics on later at app.New();
  surfaced earlier, not enforced (systems-engineer dissent F2).
- doc: note validation runs against the on-disk files only, not the merged
  AppOptions, so a default node's pruning advisory is expected/benign — the
  template omits the key cosmos defaults in code (sei-network-specialist).
- test: TestResolveHomeDir_Flag covers the --home resolution.

Deferred (dissent F4, sanctioned cut): env-precedence + mixed-home rows ride
the generate-path PR — the boot differential already proves passthrough parity
by construction. Design MVP-scope reconciled separately (bdchatham-designs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tic findings)

Addresses agentic reviewer findings on PR #3678:
- lint (errcheck, red CI): the three advisory fmt.Fprintf(cmd.ErrOrStderr())
  calls didn't check the return -> `_, _ =` (golangci-lint clean locally).
- claude[bot] (correctness): the advisory branch fired only on HasErrors() and
  printed only Errors(), silently dropping SeverityWarning diagnostics. Now
  logs the full res.Diagnostics (each carries its [ERROR]/[WARNING]/[INFO]
  severity) so warnings are surfaced too; errors stay distinguished.
- claude[bot] (doc): drop the stale "(forthcoming)" from the package comment —
  this PR lands the manager.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bdchatham bdchatham force-pushed the feat/configmanager-v2-body-plt775 branch from c18b062 to 13b3a44 Compare July 1, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant