Skip to content

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

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

feat(configmanager): SeiConfigManager v2 body — validate-passthrough (PLT-775 PR2)#3678
bdchatham wants to merge 13 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
Touches node startup config resolution behind an env gate; behavior is designed to match legacy, but wrong home resolution or parity bugs could affect operators who enable v2.

Overview
When SEI_CONFIG_MANAGER=v2, SeiConfigManager no longer errors at boot: it runs an advisory sei-config read/validate pass (warn-only, panic-safe), then still loads config through the unchanged legacy InterceptConfigsPreRunHandler on the operator’s on-disk files—no writes, migrations, or new boot failures from validation alone.

resolveHomeDir mirrors legacy --home / env resolution so validation targets the same directory the legacy reader uses. go.mod adds github.com/sei-protocol/sei-config v0.0.21 (and bumps BurntSushi/toml).

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 serverCtx.Config, Viper settings, and errors between managers.

Reviewed by Cursor Bugbot for commit 16e32f4. 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, 10:18 PM

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.57143% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.31%. Comparing base (d4cb291) to head (16e32f4).
⚠️ Report is 3 commits behind head on main.

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

Impacted file tree graph

@@            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     
Flag Coverage Δ
sei-chain-pr 25.78% <68.57%> (?)
sei-db ?
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 75.55% <68.57%> (-7.78%) ⬇️

... and 92 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.

Comment thread cmd/seid/cmd/configmanager/configmanager.go Outdated
Comment thread cmd/seid/cmd/configmanager/configmanager.go Outdated
Comment thread cmd/seid/cmd/configmanager/configmanager.go Outdated
@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

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

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>

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

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.

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

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: resolveHomeDirseiconfig.ReadConfigFromDirValidate → 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.

Comment thread cmd/seid/cmd/configmanager/configmanager_test.go Outdated
…_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>

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

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>

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

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.Validate diagnostics 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>

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

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/toml v1.4.1-dev → v1.5.0 and go-viper/mapstructure/v2 v2.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>

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

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.md and REVIEW_GUIDELINES.md were 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.
  • resolveHomeDir duplicates the legacy handler's home-resolution logic (SetEnvPrefix/AutomaticEnv/replacer) by hand rather than sharing it; this is a deliberate mirror and is guarded by TestConfigManagerLegacyVsV2Differential_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.

Comment thread cmd/seid/cmd/configmanager_differential_test.go Outdated
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>

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

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/toml 1.4.1-pre → 1.5.0 and github.com/go-viper/mapstructure/v2 2.4.0 → 2.5.0. These are repo-wide TOML/decoding dependencies, so the change is not truly confined to configmanager/ 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.md was blank and codex-review.md reported 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.md in 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 "" to ReadConfigFromDir, 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.

bdchatham and others added 2 commits July 1, 2026 14:50
….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>

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

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.

Comment thread cmd/seid/cmd/configmanager/configmanager.go
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>

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

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 test could 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>

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

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 {

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] 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.

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

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.

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