You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Free-form primitives for constrained values:logging.format/logging.level are Strings matched at runtime with silent fallthrough arms (src/main.rsinit_logging), health.bind_address is an unparsed String, relay URLs are Vec<String> with validation only for the clearnet list, cache/concurrency sizes are plain usize.
Silent coercions far from load: zero/invalid values are "fixed" inside constructors where the operator can't see it — RateLimiter::new swaps max_entries = 0 for 100k (src/rate_limiter.rs:191), the dedup cache does the same (src/nostr/events.rs:511), event_processing_permits clamps (src/main.rs:166) while an oversized value panics in Semaphore::new.
Each member issue is one instance of "config that lies": a value the operator sets that either does nothing, does something else, or detonates later.
Wave 1; fully parallel (config.rs + constructor signatures only). The rate-limit admission tracker consumes this tracker's typed NonZeroUsize configs, so it starts after this lands (soft dependency).
Root cause
Configuration correctness is enforced ad hoc, in three scattered ways, so misconfiguration fails silently at runtime instead of loudly at load:
set_default(...)inbase_config_builder()(src/config.rs:579-661) and a serdedefault_*fn — the serde ones are dead on the production load path, so the pairs can silently drift ([LOW] Config defaults are duplicated between base_config_builder set_default(...) and serde default_* fns (which are dead for the load path) — silent drift risk #171).logging.format/logging.levelareStrings matched at runtime with silent fallthrough arms (src/main.rsinit_logging),health.bind_addressis an unparsedString, relay URLs areVec<String>with validation only for the clearnet list, cache/concurrency sizes are plainusize.RateLimiter::newswapsmax_entries = 0for 100k (src/rate_limiter.rs:191), the dedup cache does the same (src/nostr/events.rs:511),event_processing_permitsclamps (src/main.rs:166) while an oversized value panics inSemaphore::new.Each member issue is one instance of "config that lies": a value the operator sets that either does nothing, does something else, or detonates later.
Refactor design
set_defaultladder; serde default fns (backed by the existingDEFAULT_*consts) become the only place a default is written ([LOW] Config defaults are duplicated between base_config_builder set_default(...) and serde default_* fns (which are dead for the load path) — silent drift risk #171).NonZeroUsizeformax_rate_limit_cache_size/max_dedup_cache_size/max_tokens_per_event/max_concurrent_event_processing(with a sane upper bound for the semaphore), making the constructor coercions unrepresentable ([LOW] RateLimiter::new silently coerces max_entries = 0 to the 100,000 default instead of surfacing the misconfiguration #166, [LOW]server.max_tokens_per_event = 0(andmax_dedup_cache_size = 0) bypassServerConfig::validate()— silent push outage / silent default swap #149, [LOW]max_concurrent_event_processinghas no upper bound — an oversized value panics at startup inSemaphore::new#179).enum LogFormat { Json, Pretty, Off }deserialized like the existingApnsPayloadMode, removing both the silent fallthrough ([LOW] Invalidlogging.formatsilently falls back to default formatter #142) and the undocumented"off"blackout arm ([LOW]logging.format = "off"silently disables ALL logging and ignoreslogging.level— an undocumented format value that causes a total log blackout #150) — silencing islevel = "off", and an explicitformat = "off"becomes a load error or documented behavior.health.bind_address: SocketAddr([LOW] health.bind_address is never validated at config load — a typo silently disables the health/readiness/metrics server while the process runs #167).RelayUrlat load and normalized once; onion entries validated symmetrically with clearnet (scheme +.onionhost) so a plaintextws://inrelays.onionfails fast ([MEDIUM] Onion relay URLs bypass all URL validation — a plaintextws://entry inrelays.oniondefeats the wss-only TLS enforcement, and typo'd.onionURLs are silently dropped #122);inbox_relay_event_is_currentcompares normalized URLs and picks the newest event bycreated_at([LOW] inbox_relay_event_is_current compares raw config strings and uses events.first(), causing spurious kind-10050 republishing and possibly skipping needed updates #124).AppConfig::validateto every section, including cross-field checks: warn loudly onmetrics.enabled && !health.enabled(structural fix — mounting/metricsindependently — belongs to the health-server tracker) ([LOW]metrics.enabled = truewithhealth.enabled = falsesilently produces an unreachable/metrics— collector runs with no endpoint to scrape it #196 rider).private_key_filemode grants group/other read, mirroring the0600guarantee the generate path already enforces ([LOW] Server private-key file is loaded with no permission check — a world/group-readable key file is accepted silently #146).configcrate'sValuetree — resolveprivate_key/private_key_filethrough a dedicatedZeroizingpath outside the builder ([LOW] Inlineserver.private_keyleaves un-zeroized plaintext copies inside theconfigcrate's value tree #156, acceptance test owned by the secret-hygiene tracker).Members
health.bind_addressasSocketAddrat loadrate_limit.max_entries = 0instead of silent 100k swapserver.max_tokens_per_event = 0(andmax_dedup_cache_size = 0) bypassServerConfig::validate()— silent push outage / silent default swap #149 — rejectmax_tokens_per_event = 0/max_dedup_cache_size = 0max_concurrent_event_processinghas no upper bound — an oversized value panics at startup inSemaphore::new#179 — upper-boundmax_concurrent_event_processing(noSemaphore::newpanic)logging.format = "off"silently disables ALL logging and ignoreslogging.level— an undocumented format value that causes a total log blackout #150 — remove/reject theformat = "off"log blackoutlogging.formatsilently falls back to default formatter #142 — reject unknownlogging.formatvaluesws://entry inrelays.oniondefeats the wss-only TLS enforcement, and typo'd.onionURLs are silently dropped #122 — validate onion relay URLs (scheme +.onionhost)server.private_keyleaves un-zeroized plaintext copies inside theconfigcrate's value tree #156 — (rider, tracked by secret-hygiene tracker) private key never enters the configValuetreeIn-flight PRs
reconnect_interval_secs/max_reconnect_attemptsconfig is never wired to the nostr-sdk client, so the documented reconnection settings have no effect #92, wirereconnect_interval_secs/max_reconnect_attemptsto nostr-sdk): merge first — this tracker builds on it; [LOW]reconnect_interval_secs/max_reconnect_attemptsconfig is never wired to the nostr-sdk client, so the documented reconnection settings have no effect #92 is the same "documented config that does nothing" class.max_concurrent_event_processinghas no upper bound — an oversized value panics at startup inSemaphore::new#179, validate concurrency cap): merge first — this tracker absorbs it into the typed-field design.reconnect_interval_secs/max_reconnect_attemptsconfig is never wired to the nostr-sdk client, so the documented reconnection settings have no effect #92 — reconnect settings actually applied (point fix in flight)Sequencing
Wave 1; fully parallel (config.rs + constructor signatures only). The rate-limit admission tracker consumes this tracker's typed
NonZeroUsizeconfigs, so it starts after this lands (soft dependency).