fix(config): a mistyped capability flag must be said out loud, not silently guessed - #472
Merged
Conversation
…lently guessed Settles both halves of #459 separately, taking a different answer on each. FAILURE DIRECTION. Neither flag inverts. #459 proposed the discriminator "can the flag's behaviour reach the network?" -- DIG_WALLET_ENABLE_CHAIN_SYNC does, so that test says fail closed, and applied here it would produce a defect: failing closed on a typo silently stops the replica advancing, and #416 records that a stale replica's zero balance is indistinguishable from an empty wallet. The surviving generalisation is to fail in whichever direction cannot make a surface assert a falsehood -- closed for an isolation knob, open for a default-ON read path. What both cases share is that silence is wrong, so an unrecognised value now names the variable, the rejected value, and the default applied. VOCABULARY. Five capability knobs adopt the shared off-tokens, so 'disabled' works on all of them as it does on the three isolation knobs. The empty-is-off rule is NOT inherited: it is correct for a knob holding a LIST (#312) and would, for a capability knob, reach #416's false zero through the vocabulary having just been refused through the failure direction. is_off_token is crate-private to dig-node-core, so the vocabulary is exposed UP from the crate root rather than copied a fifth time. Closes #459
…nob it named Writing the SPEC clause for #459 found a FIFTH capability flag the ticket did not list: DIG_NODE_PROFILE_SYNC carried its own private off-vocabulary. The clause asserts that every capability flag reads one vocabulary, so leaving it would have made the clause false in the commit that introduced it -- the four knobs #459 names were the four someone had listed, not the four that exist. Also removes two section references (4.1b, 4.1c) that this SPEC does not contain; they were invented while drafting and cite nothing.
This was referenced Aug 31, 2026
# Conflicts: # Cargo.lock # Cargo.toml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Closes #459.
Five default-ON capability knobs each read their own private off-vocabulary, narrower than the one
#282/#352 unified for the three isolation knobs, and all five applied their default silently on
an unrecognised value.
#459 asks for two separable decisions. This takes a different answer on each.
Decision 1 — failure direction: neither flag inverts; both now warn
The ticket's own discriminator gives the wrong answer for one of its two flags, and that correction
is the substantive part of this PR.
The proposed test is "can the flag's behaviour reach the network? If yes, fail closed."
DIG_WALLET_ENABLE_CHAIN_SYNCdoes — it dials chia peers — so the rule says fail closed. Applied, itwould silently stop the replica advancing on a typo, and dig-node#416 records that a stale replica's
zero balance is indistinguishable from an empty wallet. It converts a typo into a surface asserting a
falsehood about the operator's money: the exact defect class the sibling fix was protecting against.
The generalisation the proxy was standing in for, and which survives:
Closed for an isolation knob (fail-open leaves a node dialling a network its operator asked it to
leave). Open for a default-ON read path (fail-closed manufactures a false zero). Same principle,
opposite outcome — recorded at the call site so the next reader inherits the reasoning, not the proxy.
DIG_NODE_DIGLOCALbinds loopback only (127.0.0.2:80/443,[::1]:443), so it never engaged the rule.What both share is that the residue was never the direction — it was the silence. An unrecognised
value now names the variable, the rejected value, and the default applied, and says the setting had no
effect.
Decision 2 — vocabulary: adopt the shared TOKENS, subtract the empty rule
All five capability knobs adopt the shared off/on tokens, so
disabledworks everywhereoffdoes.DIG_NODE_STORE_MELTmost needed it: it stops the node's only irreversible-delete path, so anunrecognised off-token meant content deleted by a node whose operator believed they had stopped it.
The empty-is-off rule is deliberately NOT inherited. For an isolation knob an empty value names the
empty LIST (#312, correct, untouched). A capability knob holds no list, and
X=is whatexport X="$UNSET_VAR"produces — so inheriting it would reach #416's false zero through thevocabulary, having just refused it through the failure direction.
peer::is_off_tokenispub(crate), which is how five private copies came to exist. Rather thanwrite a sixth (§2.0), the vocabulary is exposed UP from the crate root.
peer.rsis untouched — itbelongs to a live lane.
Blast radius checked
gitnexus indexes for this repo are ~300 commits stale (§2.0: a stale index returns a false-safe
impactedCount: 0), so this was taken by grep + direct read and is stated as such.parse_dig_local_flag/parse_chain_sync_flag: callers areConfigconstruction and their owntests.
ingest_enabled,resolve_store_melt_enabled,profile_sync_enabled: one env-reading callereach plus tests.
dig-node-core(is_capability_off_token,is_capability_on_token,classify_flag,FlagWord,describe_unrecognised_flag) — additive only, no signature changed.peer.rs,mirror/*,spend_audit.rs,server.rs,download.rs,dispatch.rs,content_serve.rs,admission.rs,conduct.rs.Evidence
New tests:
4 passed; 0 failed(count read, not exit status). Full suites on the final tree:dig-node-core 1030 passed / 0 failed, dig-node-service 692 passed / 0 failed.
#459 warns that the existing
peer_network_enabledtest passed under both the old and new parsers,because it listed only lowercase exact tokens. So every alternative was actually implemented and run,
each on a committed tree:
3 passed; 1 failedan_unrecognised_capability_flag_keeps_its_default_and_says_so3 passed; 1 failedis_off_tokenwholesale, empty included3 passed; 1 failedan_empty_capability_flag_is_absent_not_off1 passed; 3 failedthe_capability_flags_read_the_shared_off_vocabularyThe typo fixture is
"fasle"— a real misspelling offalse, where the two candidate failuredirections give opposite answers — not a token neither implementation would accept.
a_recognised_capability_flag_says_nothingis the control against warning unconditionally.SPEC
SPEC.mdgains a normative Capability-flag vocabulary and failure direction clause. Writing itsurfaced a fifth capability flag in neither the ticket's list nor
peer.rs's —DIG_NODE_PROFILE_SYNC— which is fixed here, because leaving it would have made the clause false inthe commit that introduced it. The clause is written as a definition rather than a list, so a sixth
flag is bound without anyone remembering to extend an enumeration.
Two section references I had drafted (
§4.1b,§4.1c) cite nothing in this SPEC and were removed.Status
DRAFT — the gate round has not returned.