feat(configmanager): SeiConfigManager v2 body — validate-passthrough (PLT-775 PR2)#3678
feat(configmanager): SeiConfigManager v2 body — validate-passthrough (PLT-775 PR2)#3678bdchatham wants to merge 13 commits into
Conversation
PR SummaryMedium Risk Overview
New legacy vs v2 differential coverage (table, env-home, config corpus, corrupt-config error parity, advisory never refuses boot) plus a fuzz target asserting identical Reviewed by Cursor Bugbot for commit 16e32f4. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3678 +/- ##
==========================================
- Coverage 59.27% 58.31% -0.97%
==========================================
Files 2272 2180 -92
Lines 188193 177996 -10197
==========================================
- Hits 111547 103792 -7755
+ Misses 66593 64980 -1613
+ Partials 10053 9224 -829
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 tidyis 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.
Review-gate summary (clean)Coral /xreview slate — all RATIFY:
Findings (all [should]/[nit]) resolved: package-comment doc-drift fixed; validation-error advisory made louder; on-disk-fidelity note added; Bots: Cursor Bugbot ✅ SUCCESS; seidroid ✅ APPROVED (0 blocking). Expected, not a regressionOn a default node, v2 logs a benign advisory Merge prerequisites (separate from the review-gate)Standard CI (Test / Lint / Race / etc.) + the category label, same as the seam PR. |
…(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>
c18b062 to
13b3a44
Compare
There was a problem hiding this comment.
PR2 fills in SeiConfigManager.Apply as a validate-passthrough behind SEI_CONFIG_MANAGER=v2: it reads/validates config advisorily, then re-enters the unchanged legacy handler on the operator's original files, backed by a legacy-vs-v2 differential test. The change is well-scoped, correct, and low-risk; only non-blocking notes remain.
Findings: 0 blocking | 4 non-blocking | 0 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Both second-opinion passes produced no material findings: Codex reported no findings (and could not run tests — local Go 1.24.13 vs required 1.25.6), and the Cursor review file was empty. REVIEW_GUIDELINES.md was also empty, so no repo-specific standards were applied.
- Dependency blast radius: go.mod bumps github.com/BurntSushi/toml (1.4.1-pre → 1.5.0) and go-viper/mapstructure (2.4.0 → 2.5.0), which affect TOML parsing / decoding repo-wide (e.g. tendermint config read/write), not just configmanager. The differential test only covers configmanager parity; worth confirming the broader CI suite passes on Go 1.25.6 given these are transitively-consumed libraries.
- resolveHomeDir duplicates the legacy handler's home-resolution logic (BindPFlags + SEID_ env prefix + AutomaticEnv). This is a documented, deliberate tradeoff, but it can silently drift from InterceptConfigsPreRunHandler if the legacy resolution ever changes; since divergence only affects which dir gets validated (advisory), impact is limited, but a comment cross-reference or shared helper would harden it.
- The env-precedence branch of resolveHomeDir is acknowledged as untested (only the --home flag path is covered in configmanager_test.go). Low risk since it mirrors legacy viper behavior, but the env path remains unexercised until the generate-path PR.
The flag-driven differential never exercised resolveHomeDir's SetEnvPrefix/AutomaticEnv/replacer mirror of the legacy handler. Add an env-driven twin harness + TestConfigManagerLegacyVsV2Differential_EnvHome: drive home through the environment (no --home) and assert legacy and v2 resolve the SAME home, with a non-vacuous guard that the env var actually drove resolution. Closes the silent-drift seam the advisory design cannot otherwise surface (systems-engineer review). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Clean, tightly-scoped PR2 that implements the v2 SeiConfigManager as an advisory validate-passthrough re-entering the unchanged legacy config reader; parity is guaranteed by construction and well-covered by flag- and env-driven differential tests. No blocking issues found; only minor non-blocking observations.
Findings: 0 blocking | 4 non-blocking | 0 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Second-opinion inputs were empty: cursor-review.md produced no output and REVIEW_GUIDELINES.md is empty, so this synthesis rests only on the Claude pass (Codex explicitly reported no findings, noting it could not run Go tests due to a 1.24 vs 1.25.6 toolchain mismatch).
- At boot under v2 the config is read twice (once by seiconfig.ReadConfigFromDir for validation, once by the re-entered legacy handler). This is negligible — one-time at startup and only when the experimental gate is enabled — but worth noting.
- resolveHomeDir duplicates the legacy handler's home-resolution logic rather than sharing it; the differential env-home test pins them together, but if the legacy resolution ever changes, the two could silently diverge (only the validation target would be wrong, not boot correctness, since validation is advisory). A shared helper would be more robust if/when the sei-cosmos fork can be touched.
- Diagnostics are printed to stderr via fmt.Fprintf rather than the node's structured logger; acceptable for an advisory MVP but a follow-up could route them through the logger for consistent formatting/filtering.
There was a problem hiding this comment.
Prior substantive concerns (warning-drop, package doc-drift) are all fixed; only a nit test-comment doc-drift remains and is already posted inline.
Extended reasoning...
Overview
This PR fills in the v2 SeiConfigManager body behind SEI_CONFIG_MANAGER=v2 (PLT-775 PR2). Scope is confined to cmd/seid/cmd/configmanager/ + one differential test + go.mod/go.sum (adds sei-config v0.0.21, bumps BurntSushi/toml and go-viper/mapstructure). The v2 body is validate-passthrough: resolveHomeDir → seiconfig.ReadConfigFromDir → Validate → advisory-log diagnostics → re-enter the unchanged InterceptConfigsPreRunHandler on the operator's original files.
Security risks
Low. Both channels the app consumes (serverCtx.Config and Viper) are produced by the same legacy reader as before; v2 writes nothing to disk and never refuses boot. resolveHomeDir mirrors the legacy handler's home resolution exactly (BindPFlags + SetEnvPrefix(path.Base(exe)) + AutomaticEnv), so validation and re-entry read the same directory. The os.ErrNotExist gate correctly avoids noise on fresh homes. No auth/crypto/permission surface is touched.
Level of scrutiny
Moderate — the code sits on the boot config resolution path, but the passthrough-by-construction design confines behavioral divergence from legacy to (a) an advisory stderr line and (b) the sei-config Validate side-effect (pure over an in-memory struct). The differential tests (TestConfigManagerLegacyVsV2Differential + the env-home twin added in commit 9756ba4) pin serverCtx.Config + Viper.AllSettings() parity across both home-resolution paths, including after the start.go chain-id mutation, which is exactly the property that matters for correctness. The v2 gate is opt-in (SEI_CONFIG_MANAGER=v2), so blast radius on default operators is nil.
Other factors
My prior review's substantive findings (SeverityWarning diagnostics being silently dropped by HasErrors()/Errors(), and stale package-level doc) have been directly addressed — the current code now gates on len(diags) > 0 and prints the full Diagnostics slice, and the top-of-file doc no longer says "(forthcoming)"/"re-authors". The only remaining finding is a nit that a test comment claims the env-precedence case "rides the generate-path PR" when commit 9756ba4 (DWC-1, this PR) added TestConfigManagerLegacyVsV2Differential_EnvHome covering exactly that case; posted inline. The review-gate summary shows all cross-reviewer RATIFY with no blocking findings, and codecov/cursor/seidroid are green.
…_Flag The DWC-1 commit (9756ba4) added TestConfigManagerLegacyVsV2Differential_EnvHome covering the env-driven home resolution, but left the TestResolveHomeDir_Flag comment claiming the env case 'rides the generate-path PR' — now self- contradictory. Point it at the env-home differential test instead (claude[bot]). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
PR2 fills in the SeiConfigManager v2 body as validate-passthrough: it reads/validates config advisorily via sei-config, then re-enters the unchanged legacy reader on the operator's original files, so the two consumed channels are identical to legacy by construction. The change is small, safe, and well-tested; no blocking issues found.
Findings: 0 blocking | 3 non-blocking | 0 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Cursor's second-opinion pass (cursor-review.md) was empty — no findings from that tool. Codex reported no material findings, matching this review. REVIEW_GUIDELINES.md was also empty, so no repo-specific standards were applied.
- Parity between v2 and legacy is guaranteed by re-entering the same legacy handler, but resolveHomeDir duplicates the legacy handler's home-resolution logic rather than sharing it. If the vendored sei-cosmos handler ever changes its env-prefix/flag-binding scheme, this copy could silently drift; TestConfigManagerLegacyVsV2Differential_EnvHome guards against that, which is good — worth keeping that test in mind as the load-bearing pin.
- Advisory validation writes diagnostics to stderr via fmt.Fprintf rather than the node's logger. This is intentional for the MVP (documented), but consider routing through the structured logger when validation is promoted to fatal so operators get consistent, filterable output.
… form The in-file documentation was overbearing. Move the package narrative into a dedicated doc.go (single home for the why), cut each exported symbol's go-doc to its point, and drop the inline block comments in Apply so the code reads on its own. doc.go leads with the safety invariant (never rewrites, never refuses boot) per the /lingua R2 pass (prose-steward). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
PR2 fills in SeiConfigManager v2 as a validate-passthrough: it resolves home the same way the legacy handler does, reads/validates the config advisorily, then re-enters the unchanged legacy reader on the operator's original files. The change is small, self-contained, and its home-resolution faithfully mirrors the legacy handler; no blocking issues found, with only minor non-blocking notes.
Findings: 0 blocking | 4 non-blocking | 0 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Test coverage gap (as Codex noted): the differential tests prove legacy/v2 channel parity but never assert that the advisory validation path actually emits diagnostics to stderr on a bad/invalid config. Consider a test that feeds a config triggering
seiconfig.Validatediagnostics and asserts the advisory message is written (and boot still proceeds). - The v2 advisory path silently swallows a resolveHomeDir/read mismatch by design: if resolveHomeDir ever diverged from the legacy handler's resolution, v2 would validate one directory while legacy boots another, with no error surfaced. TestConfigManagerLegacyVsV2Differential_EnvHome pins this today, but it's worth keeping in mind that the safety net is entirely test-based since the seam duplicates (rather than reuses) the legacy resolution logic.
- Dependency bumps (BurntSushi/toml 1.4.1-pre→1.5.0, go-viper/mapstructure 2.4.0→2.5.0, new sei-config v0.0.21) are pulled in transitively for sei-config; these libraries are used broadly (viper/toml config decoding across the tree), so ensure the full test suite passes on Go 1.25.6 since Codex could not run targeted tests locally.
- Second-opinion reviews produced no findings: cursor-review.md is empty and codex-review.md reports no material correctness/security findings (only the test-coverage residual-risk note above).
…e, fuzz Widen the legacy-vs-v2 proof beyond the single default fixture, all unit-tier: - Corpus differential (table-driven): default, leading comments/blanks, unknown section, quoted scalar (#36 lenient-decode) — parity holds for any on-disk shape, including ones that exercise sei-config's own reader. - Advisory-invariance: v2 never refuses boot on a valid config, and on an unreadable config it returns the SAME error legacy does (not masked, not invented) after logging the advisory read failure. - FuzzConfigManagerLegacyVsV2Parity: arbitrary app.toml suffix -> legacy and v2 reach the same outcome (identical channels, or the identical error). Runs the seed corpus deterministically in CI; open to -fuzz locally. Harness: runManager now captures stderr + the Apply error so the advisory paths are observable; execConfigManager keeps the happy-path assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Fills in the v2 SeiConfigManager as a validate-passthrough: it advisorily validates config via the sei-config library, then re-enters the unchanged legacy reader on the operator's original files, so consumed channels match legacy by construction. The change is tightly scoped, correct, and unusually well-tested; only minor non-blocking notes remain.
Findings: 0 blocking | 5 non-blocking | 0 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Dependency bumps have blast radius beyond configmanager:
github.com/BurntSushi/tomlv1.4.1-dev → v1.5.0 andgo-viper/mapstructure/v2v2.4.0 → v2.5.0 are direct/indirect deps used across the tree (TOML parsing of app.toml/config.toml everywhere, not just here). Worth a quick confirmation that existing TOML/mapstructure decoding behavior is unchanged, since this PR's differential tests only cover the configmanager path. - resolveHomeDir divergence would be silent: if v2's home resolution ever diverged from the legacy handler's, v2 would advisorily validate one directory while the re-entered legacy reader boots on another. The consumed-channel parity assertions can't catch this (re-entry does its own resolution), and by design the mismatch produces no error/diagnostic. TestResolveHomeDir_Flag and the EnvHome differential mitigate this today, but the seam relies on the legacy env-resolution logic never changing — consider a follow-up that asserts resolveHomeDir's result equals the legacy handler's resolved home directly.
- The
errors.Is(err, os.ErrNotExist)suppression in Apply assumes sei-config's ReadConfigFromDir wraps os.ErrNotExist for a missing file; if it returns a differently-typed 'not found' error, operators would see a spurious 'could not read config for validation' advisory line. Non-blocking since it's advisory-only and never affects boot. - Second-opinion passes produced no material output: Codex reported no findings (could not run tests — workspace has Go 1.24.13 and Go 1.25.6 download was network-blocked), and the Cursor review file was empty. Combined findings therefore rest on this pass.
- No detected prompt-injection attempts in the diff or PR description.
Adopt the systems-engineer's extensible pattern for the workstream: - Apply is now two named steps (validateAdvisory -> re-enter legacy), so the generate path can add its authoring/render step as a sibling instead of rewriting Apply. Advisory pass is a void helper; boot outcome is unchanged. - Shared configCorpus() is the single source of interesting on-disk shapes, consumed by BOTH the table-driven differential and the fuzz target (fuzz now crosses every shape with an arbitrary suffix). Kills the corpus/seed drift. - Add the cosmos-only-write-mode case: the deprecated state-commit.sc-write-mode version-skew class. sei-config still accepts it, so v2 raises no diagnostic today; the case proves read-parity now and becomes a caught case once fatal validation + a sei-config deprecation rule land. - doc.go states the reduction lemma: the node boots on the two channels, not the model, so channel-equality is the whole correctness argument. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
A well-scoped, well-documented change that fills in the v2 SeiConfigManager as a pure advisory validate-passthrough: it validates via sei-config, logs diagnostics, and re-enters the unchanged legacy reader on the operator's original files, so boot parity with legacy holds by construction and is proven by strong differential + fuzz tests. Only a minor test-only integer-overflow issue in the fuzz target is worth noting; no production blockers.
Findings: 0 blocking | 3 non-blocking | 1 posted inline
Blockers
- None at the file/PR level.
Non-blocking
cursor-review.mdandREVIEW_GUIDELINES.mdwere both empty — the Cursor pass produced no output and no repo-specific review guidelines were available, so those inputs did not contribute to this review.resolveHomeDirduplicates the legacy handler's home-resolution logic (SetEnvPrefix/AutomaticEnv/replacer) by hand rather than sharing it; this is a deliberate mirror and is guarded byTestConfigManagerLegacyVsV2Differential_EnvHome, but it remains a drift risk if the legacy resolution changes — worth a code comment pointer or a shared helper in a future PR.- 1 suggestion(s)/nit(s) flagged inline on specific lines.
idiomatic-reviewer nits on the fold-in: - Fuzz index is now uint, dropping the sign guard and the math.MinInt negation edge (negating MinInt stays negative -> corpus[neg] panic; a fuzzer would hit it). Validated: 15s / 4354 execs, no panic, no divergence. - replaceInFile params renamed oldStr/newStr to stop shadowing builtin new. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
PR2 fills in the v2 SeiConfigManager as a strictly advisory validate-then-passthrough: it reads/validates the config via sei-config, logs any diagnostics, and re-enters the unchanged legacy handler on the operator's original files. The design is sound, the home-resolution mirror is faithful to the legacy handler, and the differential/fuzz test coverage is unusually thorough. A couple of non-blocking notes on the repo-wide dependency bumps and the empty second-opinion inputs.
Findings: 0 blocking | 4 non-blocking | 0 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- go.mod bumps
github.com/BurntSushi/toml1.4.1-pre → 1.5.0 andgithub.com/go-viper/mapstructure/v22.4.0 → 2.5.0. These are repo-wide TOML/decoding dependencies, so the change is not truly confined toconfigmanager/as the PR description states — it affects the legacy config path and any other TOML/mapstructure consumers. The differential test (which reads a legacy-created config) gives partial assurance of parity, but consider confirming no behavior change in the broader config-parsing surface before merge. - Both second-opinion inputs were effectively empty:
cursor-review.mdwas blank andcodex-review.mdreported no findings but noted it could not run the Go test suite (local toolchain Go 1.24.13 vs required 1.25.6, and sandboxed network blocked the toolchain download). So neither external pass verified the tests actually pass — worth confirming CI is green on Go 1.25.6. REVIEW_GUIDELINES.mdin the working tree is empty, so no repo-specific review standards were applied.- No prompt-injection or malicious content was detected in the PR diff, title, or description.
Comments that couldn't be anchored to the diff
cmd/seid/cmd/configmanager/configmanager.go:132-- [nit] Minor robustness/observability gap: when home resolves to an empty string (neither --home nor the env var set), this passes "" toReadConfigFromDir, which will read relative to the process CWD rather than the node home the legacy handler would use. It's harmless here because the whole pass is advisory and swallowed, but it means the advisory validation could silently validate the wrong directory without any diagnostic. Consider logging when home is empty, since the whole point of the env-home differential test is to guard against exactly this kind of home-resolution drift going unsurfaced.
….Fprintf Advisory validation diagnostics now go through seilog (the seid-wide logger, already used in root.go) at Warn, instead of raw fmt.Fprintf to cmd stderr. seilog is a package-global slog logger available in PersistentPreRunE — it does not depend on the node/server-context logger (wired later inside the re-entered handler) — so logging is now structured, level-controlled, and consistent with the rest of seid. Tests: the advisory paths' behavior (I2 — v2 returns the same error as legacy, boots identically) is unchanged and still asserted. seilog fixes its output sink at package init (no per-test hook), so the log-TEXT assertion is dropped — the invariant, not the log text, is what the test protects. runManager simplified to (ctx, err). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
idiomatic-reviewer follow-ups on the seilog switch:
- Log diagnostics as a []string of Diagnostic.String() instead of
fmt.Sprintf("%v", diags): structured/queryable under the JSON handler and
still readable under text, where a bare []Diagnostic would struct-dump.
- Use the "error" attribute key to match the sibling logger calls (ethreplay.go).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
PR2 fills in the v2 SeiConfigManager as a validate-passthrough: it advisorily validates config via sei-config, then re-enters the unchanged legacy reader on the operator's original files. The change is tightly scoped, the home-resolution mirror matches the legacy handler exactly, and parity is proven by differential + fuzz tests. No blockers; a couple of non-blocking robustness/dependency notes.
Findings: 0 blocking | 4 non-blocking | 1 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Dependency bumps ride in transitively via sei-config: BurntSushi/toml 1.4.1-pre → 1.5.0 and go-viper/mapstructure/v2 2.4.0 → 2.5.0. These libraries are used repo-wide for TOML/mapstructure decoding, not just by the new package. The differential/fuzz tests only prove legacy-vs-v2 parity within configmanager; they don't cover behavior changes these bumps could introduce for other config/serialization consumers. Worth a sanity check that the broader test suite is green on 1.25.6 (Codex could not run tests due to a Go 1.24 vs 1.25.6 toolchain mismatch, so no independent test signal here).
- Minor: on the v2 path the on-disk config is read twice at boot (once by sei-config for validation, once by the re-entered legacy reader). This is a one-time startup cost and acceptable, but worth noting as the generate path evolves.
- Process notes: Cursor produced no output (cursor-review.md is empty) and REVIEW_GUIDELINES.md is empty/missing, so no repo-specific standards were applied. Codex reported no material findings but explicitly could not execute Go tests.
- 1 suggestion(s)/nit(s) flagged inline on specific lines.
The advisory pass's contract is 'never refuses boot,' but a panic in seiconfig.ReadConfigFromDir/Validate (read fidelity still being hardened) would propagate out of Apply and crash boot — violating advisory-invariance (I2). Guard validateAdvisory with a recover that logs the panic and lets boot proceed via the re-entered legacy reader, so the pass truly cannot change the boot outcome (seidroid). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
A well-scoped, thoroughly-tested PR that fills in the v2 SeiConfigManager as an advisory validate-then-passthrough over the unchanged legacy config handler; v2's consumed channels are identical to legacy by construction and the differential/fuzz suite proves it. No blockers found; only informational notes about the second-opinion passes.
Findings: 0 blocking | 4 non-blocking | 0 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Cursor's review file (cursor-review.md) is empty — that second-opinion pass produced no output.
- Codex reported no material findings but noted its focused
go testcould not run because the sandbox blocked module downloads, so its pass was effectively static-analysis-only. - REVIEW_GUIDELINES.md (from the base branch) is empty, so no repo-specific review standards could be applied.
- validateAdvisory logs every sei-config diagnostic at Warn regardless of the diagnostic's own severity. Fine for the advisory MVP, but when validation later becomes fatal it would be worth mapping diagnostic severity to log level (and to boot-refusal) rather than treating all diagnostics uniformly.
A recovered panic is an unexpected defect (a crash in sei-config), not a benign advisory diagnostic — Error surfaces it even when SEI_LOG_LEVEL filters Warn. The message still states the node boots. (idiomatic-reviewer) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
PR2 fills in the v2 SeiConfigManager as an advisory validate-passthrough: it reads/validates config through sei-config but re-enters the unchanged legacy reader on the operator's original files, so both consumed channels are produced identically to legacy by construction. The change is tightly scoped, defensively coded (panic-recovered, never refuses boot), and backed by strong legacy-vs-v2 differential + fuzz tests. No blocking issues; only minor non-blocking notes.
Findings: 0 blocking | 5 non-blocking | 1 posted inline
Blockers
- None at the file/PR level.
Non-blocking
- Dependency bumps ride along with sei-config v0.0.21: BurntSushi/toml 1.4.1-pre → 1.5.0 and go-viper/mapstructure/v2 2.4.0 → 2.5.0. Verified BurntSushi/toml is only imported by sei-tendermint e2e test code in this repo, so the risk is low; worth a sanity check that no consensus/app-hash path depends on toml parse behavior (PR is labeled non-app-hash-breaking, which is consistent with this).
- The differential/fuzz tests prove parity between the legacy and v2 paths under the new dependency set (both read via the same new toml/mapstructure), so they do not guard against legacy-old vs legacy-new parsing drift introduced by the version bumps. This is inherent to the approach and acceptable given minor-version bumps, but noting the tests don't cover it.
- Both second-opinion passes (Codex, Cursor) returned no material findings; Codex additionally noted it could not run the Go test suite because the 1.25.6 toolchain download was network-blocked in its environment, so its 'no findings' verdict was static-analysis only.
- REVIEW_GUIDELINES.md is empty in the base branch, so no repo-specific standards were applied beyond AGENTS.md/CLAUDE.md.
- 1 suggestion(s)/nit(s) flagged inline on specific lines.
| // trimming or case-folding) and never falls back silently. getenv is injected | ||
| // for tests; callers pass os.Getenv. | ||
| func Select(getenv func(string) string) (ConfigManager, error) { | ||
| switch v := getenv(EnvVar); v { |
There was a problem hiding this comment.
[nit] This silent-skip relies on seiconfig.ReadConfigFromDir wrapping a missing-file error such that errors.Is(err, os.ErrNotExist) holds. If sei-config returns a custom "config not found" error instead, every fresh/default-home boot under v2 would emit a spurious could not read config for validation (advisory) warning (harmless but noisy). Worth confirming sei-config's error wrapping, or matching on its own not-found sentinel as well.
There was a problem hiding this comment.
Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.
Once credits are available, reopen this pull request to trigger a review.
What
PR2 of the ConfigManager MVP (PLT-775): fills in the v2
SeiConfigManagerbehindSEI_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.tomlat 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'sflag > SEID_ env > defaultresolution exactly) →ReadConfigFromDir→Validate→ re-enter. Advisory: read/validate problems are logged, never refuse boot.config.tomlparses for validation.serverCtx.Config+Viper.AllSettings()must match, incl. after thestart.gochain-id mutation.Scope / constraints
sei-cosmosfork. Diff is confined tocmd/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
bdchatham-designsdesigns/config-manager/DESIGN.md.🤖 Generated with Claude Code