[ConfigManager] Register Sections 2/4 - #3975
Conversation
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.
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
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.
PR SummaryMedium Risk Overview State-store defaults are resolved from existing node-mode rules (archive keeps all versions; validators/seeds disable the store) and intentionally differ from what 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. |
There was a problem hiding this comment.
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:44—NewCustomAppConfigoverwritesStateStorewith the mode-blindseidbconfig.DefaultStateStoreConfig(), discarding everythingparams.SetAppConfigByModejust applied atcmd/seid/cmd/init.go:176.seid init --mode validatortherefore writesss-enable = trueandseid init --mode archivewritesss-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.
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>
Registers the four configuration sections the app package owns.
light_invarianceregisters the struct its reader already fills. The other threedeclare 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:
ss-enabless-enabless-keep-recentss-enableis the more consequential of the two, because it leaves a request surface on thenodes 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
TestTheRenderedConfigStillDiscardsTheStateStoreRulesthree times by name.The genesis section declares a third key
The upstream server reads
genesis.genesis-stream-fileinto a configuration of its own and agenerated file renders it. A section name is the whole of what a registration owns and a
dotted one is refused, so a key under
genesisis this registration's or nobody's. It wasrecorded 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.