Skip to content

[ConfigManager] Register Sections 2/4 - #3975

Queued
bdchatham wants to merge 8 commits into
mainfrom
plt-775-sections-2
Queued

[ConfigManager] Register Sections 2/4#3975
bdchatham wants to merge 8 commits into
mainfrom
plt-775-sections-2

Conversation

@bdchatham

@bdchatham bdchatham commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Registers the four configuration sections the app package owns.

  genesis           2 keys
  light_invariance  1 key
  state-commit      20 keys
  state-store       12 keys

light_invariance registers the struct its reader already fills. The other three
declare a schema next to their registration, because keys derived from the type the
reader fills are not the keys the reader looks up. The genesis import type carries no
mapstructure tags at all. State store and state commit tag their fields with something
other than the name an operator writes, and state commit keeps its settings in three
inner structs while the keys are flat names on the section, apart from the one flat
key-value setting that has a segment of its own.

State commit's write mode is declared as text rather than the reader's own named string
type, since the reader parses a written name into that type itself. Declaring the named
type would have one key answer as a named string from these defaults and as a plain
string from an operator's file.

Each section's test resolves the registry and compares the keys and values that come
back against the reader's own flag constants and its own defaults struct. It compares
what resolving produces rather than the registered struct, because the resolved map
carries both the key a tag produced and the value that tag's field held. A
struct-to-struct comparison agrees with itself while two tags sit on the wrong fields,
since each field still holds the value the test names for it. I put the two genesis tags
on each other's fields to check, and the key set comes out identical while the test
fails. I also dropped a key from the state store schema, misspelled another, replaced a
default with an empty string, made the write mode default one the reader's own parser
refuses, and made a default vary by node mode. Each of those fails a test.

The store section answers per mode

Two of these settings mean something different depending on what kind of node asks. An
archive node exists to keep history, so it declares a retention that keeps every version;
a validator and a seed serve no queries, so the store is off for them. Both come from the
mode rules the binary already applies when it writes a file, rather than being written
again here.

This is the correction PLT-955 assigns to the versioned manager. Declaring the rendered
value instead would have pinned that defect a second time, in the place meant to fix it,
and in the destructive direction: state store pruning deletes inside the store on a timer,
the archive volumes are protected against being deleted rather than against being emptied,
and pruning frees disk, so nothing that watches disk would fire.

Three places this departs from the file the command writes

One cause. The type the command renders declares a state store field of its own and fills it
from the mode-blind default, so every value the mode rules put on this section is applied and
then discarded. So this section follows the rules and departs from the command wherever the
rules moved a value, which is three rows across two keys:

Mode Key Declared here The command writes
validator ss-enable false true
seed ss-enable false true
archive ss-keep-recent 0 100000

ss-enable is the more consequential of the two, because it leaves a request surface on the
nodes the rules close it for. A first count of this said one departure and named only the
retention; the set is measured now, so a row that stops departing fails and a row that starts
departing fails too.

The half that measures the command lives beside the command. This package cannot import the
one that renders the file, so the comparison here reads the same default that file reads,
which means a change making the command keep the rules would leave it green. Reverting the
discard fails TestTheRenderedConfigStillDiscardsTheStateStoreRules three times by name.

The genesis section declares a third key

The upstream server reads genesis.genesis-stream-file into a configuration of its own and a
generated file renders it. A section name is the whole of what a registration owns and a
dotted one is refused, so a key under genesis is this registration's or nobody's. It was
recorded as a key with a target of its own and not declared; both halves now come from that
record, so adding a fourth moves one place.

The four keys under the nested flat key-value name are still deliberately undeclared, and the
comment says why rather than claiming another registration will take them, which nothing
could.

The divergences from the readers are measured

The two storage sections declare values their readers do not produce for a file missing the
keys, because most of those reads take no account of whether the key was present. Eight keys
diverge. A comment describing which ones cannot fail when it is wrong, so the set is measured
per mode and recorded as data: a key that starts diverging fails, and so does one that stops,
which means guarding a read has to account for its row.

The one worth naming is the write mode. Its read is guarded, and the reader then rewrites
the field unconditionally because the key that enables automatic selection defaults on. So a
node with nothing written commits in the derived mode rather than the one that key carries,
and this key selects which storage engine a validator commits through.

Each key set is also held against this package's own read-site record, which is kept for
another purpose and held against a golden file. That record spells its keys with the reader's
constants while the section derives them from tags, so a rename on either side alone fails.
I checked both directions.

Nothing consumes the registry on a boot path yet, so no node's configuration changes here.

Four sections owned by the app package, each registered where its struct lives.

  genesis           2 keys
  light_invariance  1 key
  state-commit      20 keys
  state-store       12 keys

light_invariance registers the type its reader fills. The other three declare a
schema, because keys derived from the type the reader fills are not the keys the
reader looks up. The genesis import type carries no mapstructure tags at all.
State store and state commit both tag their fields with something other than the
name resolved from configuration, and state commit nests its settings under three
inner structs while the keys are flat names on the section, apart from the one
flat key-value setting that has a segment of its own.

State commit's write mode is text rather than the reader's named type, because
the reader parses a written name into that type itself. Declaring the named type
would have one key answer as a named string from these defaults and as a plain
one from an operator's file.

Each section's test holds its resolved keys and values against the reader's own
constants and its own defaults. Resolving is what it compares, rather than the
registered struct, because the resolved map carries the key a tag produced and
the value that tag's field held: a comparison of struct to struct agrees with
itself while two tags sit on the wrong fields, since each field still holds the
value the test names for it. Putting the genesis tags on each other's fields
leaves the key set identical and fails the test.

State store's declared defaults are not what its reader produces for a file
missing those keys. It starts from the declared defaults, then assigns eleven of
its twelve fields straight from a lookup with no check that the key was present,
so an absent key casts to a zero and clobbers the default beside it: the store
reads as disabled, with no backend, keeping every version, and committing
synchronously. Only the snapshot key is guarded, and its own comment at the read
says why. A node whose app.toml predates one of the other keys therefore runs the
clobbered value today and the declared default once something installs this
section, and guarding the remaining reads is what makes those the same thing.

The recorded configuration surface does not move, because nothing consumes the
registry on a boot path yet.
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 25, 2026, 3:32 PM

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.60%. Comparing base (198641f) to head (58c74b9).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3975      +/-   ##
==========================================
- Coverage   59.72%   57.60%   -2.12%     
==========================================
  Files        2261     2229      -32     
  Lines      194354   187256    -7098     
==========================================
- Hits       116071   107865    -8206     
- Misses      67565    69551    +1986     
+ Partials    10718     9840     -878     
Flag Coverage Δ
sei-chain-pr 51.79% <100.00%> (?)
sei-db 69.80% <ø> (ø)
sei-db-state-db ?

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

Files with missing lines Coverage Δ
app/config_register.go 100.00% <100.00%> (ø)

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

Two of the state store's settings mean something different depending on what
kind of node asks, and this section answered the same for all four. An archive
node exists to keep history, and it was declaring a retention of a hundred
thousand versions. The binary already says otherwise, in the mode rules it
applies when it writes a file: an archive node keeps everything, and a
validator and a seed run with the store off. Those rules are now where this
section's answer comes from, so a change to them moves this too.

That is the correction the pinned defect asks for. The record of it says to
pin how configuration resolves today and correct it in the versioned manager,
and this registry is the versioned manager, so declaring the rendered value
would have pinned the defect a second time in the place meant to fix it.
Nothing runs differently yet, because nothing consumes the registry on a boot
path, and the direction matters more than the timing: state store pruning
deletes inside the store on a timer, the archive volumes are protected against
being deleted rather than against being emptied, and pruning frees disk so
nothing that watches disk would fire.

The rest of this change replaces prose with measurement. The declared values
for the two storage sections are not what their readers produce for a file
missing the keys, and the comment describing which keys those are was wrong
three ways: it named four of the six store settings, missed two, named a
commitment setting that does not in fact differ, and missed the one that
selects how a node commits. The write mode is read through a presence check
and then rewritten unconditionally, so a node with nothing written commits in
the derived mode rather than the one that key carries.

A comment cannot fail when it is wrong. So the set is measured against the
readers now, per mode, and recorded as data: a key that starts diverging fails,
and so does one that stops, which means guarding a read has to account for its
row rather than quietly making a sentence stale. Each key set is also held
against this package's own read-site record, which is kept for another purpose
and held against a golden file, rather than against a list written beside it in
the same commit. The record spells its keys with the reader's constants and the
section derives them from tags, so a rename on either side alone fails.

One comment said the other keys under the commitment section's flat key-value
name have no reader. Four of them are read by the Cosmos server's own reader,
so they belong to whoever registers that section, and saying they reach nothing
would have closed the door on declaring them.

The whole-registry defect sweep here is now scoped to the four sections this
file registers. A refusal that depends on what else has registered is not this
package's to answer for, and the sweep that covers it lives where every section
is linked.
Four comments said more than their subject. The light-invariance default now
states the mode decision and leaves what the check compares to the check. The
genesis schema says why it exists and leaves what holds it to the test that
holds it. The helper that resolves says once that it renders every section, so
a failure naming another one is read correctly. And the write-mode default is
asked for every mode rather than one, which is how the tests beside it ask.
A declared value is what the seid init command writes for a kind of node. This
section departs from that once: the retention an archive node keeps. The mode
rules set it to keep everything and the command does not write that, because the
type it renders declares a state store field of its own and fills it from the
mode-blind default, so the rule is applied and then discarded.

PLT-955 records that and records the decision, which is to pin what a node
resolves today and correct it in the versioned declaration rather than at the
point that loses it. So the departure is intended, and it is now held rather than
asserted. It fails if the command starts carrying the rule, which is the day the
departure should be deleted. It fails if this section stops departing, which
would declare a retention on the one kind of node whose purpose is keeping what
it would prune. Both directions are checked, because a departure nothing measures
cannot be told from an oversight.
@bdchatham
bdchatham marked this pull request as ready for review August 24, 2026 18:02
@cursor

cursor Bot commented Aug 24, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches state-store enablement and archive retention defaults that affect pruning and query surfaces; divergences are deliberate and heavily tested, but wrong future wiring could change node behavior.

Overview
Registers four app-owned configuration sections (light_invariance, genesis, state-store, state-commit) with the shared config registry via a new init() hook, using schema structs and per-mode default functions where operator-facing keys do not match the types existing readers fill.

State-store defaults are resolved from existing node-mode rules (archive keeps all versions; validators/seeds disable the store) and intentionally differ from what seid init currently writes for three mode/key pairs (PLT-955), documented and enforced by paired tests in app and cmd/seid/cmd. Agreement tests measure which declared defaults diverge from today’s readers when keys are absent, so future registry-driven installs cannot silently change pruning, enablement, or write mode without updating the recorded sets.

Nothing in this PR wires the registry into the live boot path yet; behavior changes only when a later step starts consuming these declarations.

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

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

Four new configuration sections are registered with schemas, per-mode defaults, and a well-constructed set of agreement tests; the tag/value mappings, mode rules, and the recorded reader divergences all check out. The findings are about section completeness (two operator-facing keys can only ever be declared by these registrations and are not) and two doc/test claims that overstate what is actually measured.

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

Blockers

  • None at the file/PR level.

Non-blocking

  • [suggestion] The new godocs run to multiple paragraphs of rationale, mechanism, and design history (including the PLT-955 ticket and "the rule is applied and then discarded"), which is what AGENTS.md "Godoc" explicitly rules out: godocs say what a thing is, rationale belongs in an inline comment at the line that needs it, and "Never record design history." The load-bearing reasoning here is genuinely worth keeping — the ask is to move it inline (or into the test that measures it) and leave one or two sentences of what on each declaration.
  • 4 suggestion(s)/nit(s) flagged inline on specific lines.
  • 1 non-blocking pre-existing issue(s) listed below under pre-existing issues.

Pre-existing issues

  • [suggestion] cmd/seid/cmd/app_config.go:44NewCustomAppConfig overwrites StateStore with the mode-blind seidbconfig.DefaultStateStoreConfig(), discarding everything params.SetAppConfigByMode just applied at cmd/seid/cmd/init.go:176. seid init --mode validator therefore writes ss-enable = true and seid init --mode archive writes ss-keep-recent = 100000, contradicting both mode rules. This PR corrects the effect in the versioned declaration rather than at this site (per PLT-955), so the defect itself is untouched.

Comment thread app/config_register.go Outdated
Comment thread app/config_register_test.go Outdated
Comment thread app/config_register.go
Comment thread app/config_register.go Outdated
bdchatham and others added 3 commits August 24, 2026 15:24
Four findings from review, all measured rather than argued.

The state store section says it departs from what the command writes in one
place and it departs in three, across two keys and from one cause. A validator
and a seed keep the store off where the command writes it on, which is the more
consequential of the two: it leaves a request surface on the nodes the rules
close it for. The record now holds all three rows, so one that stops departing
fails and one that starts departing fails too.

That record could not see the command. This package cannot import the one that
renders the file, so the mode-blind side was read from the same default the
command reads, which meant a change making the command keep the rules would have
left the record green and stale. The half that fails then now lives beside the
command, and reverting the discard fails it three times by name.

The genesis section declares a third key. The upstream server reads it into a
configuration of its own and a generated file renders it, and a section name is
the whole of what a registration owns, so a key under genesis is this
registration's or nobody's. The record already named it; the declaration did
not. The test now reads that record rather than a list written again beside it.

The comment on the nested flat key-value segment said four further keys belong to
whoever registers that reader's section. No such registration can exist, for the
same reason as the genesis key. It now says what is true: they are deliberately
not declared, the template renders none of them, and an operator who writes one
is told it reached nothing rather than having a value applied for a setting this
section does not state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bdchatham
bdchatham added this pull request to the merge queue Aug 25, 2026
Any commits made after this event will not be merged.
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 25, 2026
@bdchatham
bdchatham added this pull request to the merge queue Aug 25, 2026
Any commits made after this event will not be merged.
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 25, 2026
@bdchatham
bdchatham added this pull request to the merge queue Aug 25, 2026
Any commits made after this event will not be merged.
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.

2 participants