pixad currently starts with whatever the config file contains; bad values surface later as runtime errors. Production P0 from TODO.md Future Steps (queued after #51): validate the full configuration at startup and exit nonzero with a clear error before binding the listener.
Definition of done
Every config option is validated at load time: required values present (signing_key unless a documented keyless mode is intended — decide and document), values in range (ports, timeouts, sizes positive and sane), allowlist_hosts entries well-formed, paths creatable/writable, etc.
Unknown keys in the YAML file are an error, not silently ignored (catches typos like the old source_host_whitelist/whitelist_hosts README mismatch).
Per the repo's no-silent-fallback rule: an INVALID explicit value is always an error; defaults apply only to OMITTED values.
Error messages name the offending key and value.
TDD: failing tests first covering representative invalid configs and the unknown-key case; then the implementation.
make check green; TODO.md Workflow bookkeeping in the finishing commit; finishing commit title ends with (closes #N) for this issue.
Process
Standard workflow: feature branch from main, direction via comments on this issue, discussion on the PR, adversarial review, merge-ready + assign sneak on pass.
`pixad` currently starts with whatever the config file contains; bad values surface later as runtime errors. Production P0 from `TODO.md` Future Steps (queued after #51): validate the full configuration at startup and exit nonzero with a clear error before binding the listener.
## Definition of done
1. Every config option is validated at load time: required values present (`signing_key` unless a documented keyless mode is intended — decide and document), values in range (ports, timeouts, sizes positive and sane), `allowlist_hosts` entries well-formed, paths creatable/writable, etc.
2. Unknown keys in the YAML file are an error, not silently ignored (catches typos like the old `source_host_whitelist`/`whitelist_hosts` README mismatch).
3. Per the repo's no-silent-fallback rule: an INVALID explicit value is always an error; defaults apply only to OMITTED values.
4. Error messages name the offending key and value.
5. TDD: failing tests first covering representative invalid configs and the unknown-key case; then the implementation.
6. `make check` green; `TODO.md` Workflow bookkeeping in the finishing commit; finishing commit title ends with ` (closes #N)` for this issue.
## Process
Standard workflow: feature branch from `main`, direction via comments on this issue, discussion on the PR, adversarial review, `merge-ready` + assign sneak on pass.
Picking this up. Definition of done and implementation plan follow; PR to come on a feature branch from current main.
Definition of done
A config value that is SET but unparseable or invalid is a startup-aborting error. Defaults apply ONLY to OMITTED keys. This is the core no-silent-fallback rule and it currently does not hold: the helpers in internal/config/config.go (getString, getInt, getBool) swallow every conversion error and silently return the default, and loadConfigFile logs a warning and CONTINUES when a config file at a standard location exists but fails to parse. Both become hard errors.
Unknown keys at the top level of the config file are a startup-aborting error naming each unknown key (catches typos like the old whitelist_hosts vs allowlist_hosts mismatch). The env section stays permitted (smartconfig consumes it for environment injection), and nested metrics subkeys are validated too (username/password only).
Range/sanity validation on every option: port an integer in 1-65535 (no float truncation: 8080.5 is an error, not 8080); upstream_connections_per_host an integer of at least 1; signing_key required and at least 32 characters (existing rule, kept — no keyless mode; documented in the config error message); allowlist_hosts entries must be bare hostnames (non-empty strings, no scheme, no slash, no whitespace); state_dir non-empty, must be creatable and writable (verified at startup with a probe file); sentry_dsn if set must parse as a URL with scheme and host; metrics.username and metrics.password must be set together or not at all; debug/maintenance_mode/allow_http must be booleans or ParseBool-able strings, not arbitrary numbers.
Every error message names the offending key and the offending value.
Startup path: config.New returns the error, fx aborts before binding the listener, process exits nonzero.
Implementation plan
internal/config/config.go: extract a newFromSmartConfig(sc) construction/validation function that New wraps (keeps fx wiring untouched, makes the logic testable without fx). Replace the silent-default getters with strict variants that distinguish key-absent (default) from key-present-but-invalid (error). Add validateKnownKeys against the canonical key set (debug, maintenance_mode, port, state_dir, sentry_dsn, db_url, metrics, signing_key, allowlist_hosts, allow_http, upstream_connections_per_host, env). Extend validate() with the range/wellformedness rules above. Make a parse failure of an existing config file fatal in loadConfigFile instead of warn-and-continue.
internal/config/config_test.go: TDD — failing tests committed first, covering (a) omitted keys use defaults (minimal config with only a valid signing_key), (b) a table of set-but-invalid values each asserting startup fails with an error naming the key (invalid port string, port 0, port 70000, float port, non-bool debug, zero/negative upstream_connections_per_host, allowlist entry with scheme, non-string allowlist entry, short signing_key, missing signing_key, metrics.username without password, invalid sentry_dsn, explicitly empty state_dir), (c) unknown top-level key aborts naming the key, (d) env section is not flagged as unknown.
Workflow: feature branch from main, tests committed first, then the implementation, make fmt on touched markdown, make check green, TODO.md bookkeeping in the finishing commit, PR title ending with (closes#52), label needs-review.
Picking this up. Definition of done and implementation plan follow; PR to come on a feature branch from current `main`.
## Definition of done
1. A config value that is SET but unparseable or invalid is a startup-aborting error. Defaults apply ONLY to OMITTED keys. This is the core no-silent-fallback rule and it currently does not hold: the helpers in `internal/config/config.go` (`getString`, `getInt`, `getBool`) swallow every conversion error and silently return the default, and `loadConfigFile` logs a warning and CONTINUES when a config file at a standard location exists but fails to parse. Both become hard errors.
2. Unknown keys at the top level of the config file are a startup-aborting error naming each unknown key (catches typos like the old `whitelist_hosts` vs `allowlist_hosts` mismatch). The `env` section stays permitted (smartconfig consumes it for environment injection), and nested `metrics` subkeys are validated too (`username`/`password` only).
3. Range/sanity validation on every option: `port` an integer in 1-65535 (no float truncation: `8080.5` is an error, not 8080); `upstream_connections_per_host` an integer of at least 1; `signing_key` required and at least 32 characters (existing rule, kept — no keyless mode; documented in the config error message); `allowlist_hosts` entries must be bare hostnames (non-empty strings, no scheme, no slash, no whitespace); `state_dir` non-empty, must be creatable and writable (verified at startup with a probe file); `sentry_dsn` if set must parse as a URL with scheme and host; `metrics.username` and `metrics.password` must be set together or not at all; `debug`/`maintenance_mode`/`allow_http` must be booleans or ParseBool-able strings, not arbitrary numbers.
4. Every error message names the offending key and the offending value.
5. Startup path: `config.New` returns the error, fx aborts before binding the listener, process exits nonzero.
## Implementation plan
- `internal/config/config.go`: extract a `newFromSmartConfig(sc)` construction/validation function that `New` wraps (keeps fx wiring untouched, makes the logic testable without fx). Replace the silent-default getters with strict variants that distinguish key-absent (default) from key-present-but-invalid (error). Add `validateKnownKeys` against the canonical key set (`debug`, `maintenance_mode`, `port`, `state_dir`, `sentry_dsn`, `db_url`, `metrics`, `signing_key`, `allowlist_hosts`, `allow_http`, `upstream_connections_per_host`, `env`). Extend `validate()` with the range/wellformedness rules above. Make a parse failure of an existing config file fatal in `loadConfigFile` instead of warn-and-continue.
- `internal/config/config_test.go`: TDD — failing tests committed first, covering (a) omitted keys use defaults (minimal config with only a valid `signing_key`), (b) a table of set-but-invalid values each asserting startup fails with an error naming the key (invalid port string, port 0, port 70000, float port, non-bool `debug`, zero/negative `upstream_connections_per_host`, allowlist entry with scheme, non-string allowlist entry, short `signing_key`, missing `signing_key`, `metrics.username` without password, invalid `sentry_dsn`, explicitly empty `state_dir`), (c) unknown top-level key aborts naming the key, (d) `env` section is not flagged as unknown.
- Workflow: feature branch from `main`, tests committed first, then the implementation, `make fmt` on touched markdown, `make check` green, `TODO.md` bookkeeping in the finishing commit, PR title ending with (closes #52), label `needs-review`.
Implementation is up as PR #53 (branch feature/config-validation, labeled needs-review), executed per the plan above: TDD (failing enforcement tests committed first, then the implementation), make check green, end-to-end verified (pixad exits 1 with config key "port": value "banana" is not an integer on a bad value and unknown config keys: whitelist_hosts on a typo'd key). One deviation from the plan, documented in the PR: the lenient getStringSlice helper is retained because existing tests exercise it and test modification needs explicit approval — strictness for allowlist_hosts is enforced on the raw value by validateAllowlistHostsValue before extraction, so the no-silent-fallback guarantee holds regardless. Awaiting adversarial review per the standard workflow.
Implementation is up as PR #53 (branch `feature/config-validation`, labeled `needs-review`), executed per the plan above: TDD (failing enforcement tests committed first, then the implementation), `make check` green, end-to-end verified (`pixad` exits 1 with `config key "port": value "banana" is not an integer` on a bad value and `unknown config keys: whitelist_hosts` on a typo'd key). One deviation from the plan, documented in the PR: the lenient `getStringSlice` helper is retained because existing tests exercise it and test modification needs explicit approval — strictness for `allowlist_hosts` is enforced on the raw value by `validateAllowlistHostsValue` before extraction, so the no-silent-fallback guarantee holds regardless. Awaiting adversarial review per the standard workflow.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
pixadcurrently starts with whatever the config file contains; bad values surface later as runtime errors. Production P0 fromTODO.mdFuture Steps (queued after #51): validate the full configuration at startup and exit nonzero with a clear error before binding the listener.Definition of done
signing_keyunless a documented keyless mode is intended — decide and document), values in range (ports, timeouts, sizes positive and sane),allowlist_hostsentries well-formed, paths creatable/writable, etc.source_host_whitelist/whitelist_hostsREADME mismatch).make checkgreen;TODO.mdWorkflow bookkeeping in the finishing commit; finishing commit title ends with(closes #N)for this issue.Process
Standard workflow: feature branch from
main, direction via comments on this issue, discussion on the PR, adversarial review,merge-ready+ assign sneak on pass.Picking this up. Definition of done and implementation plan follow; PR to come on a feature branch from current
main.Definition of done
internal/config/config.go(getString,getInt,getBool) swallow every conversion error and silently return the default, andloadConfigFilelogs a warning and CONTINUES when a config file at a standard location exists but fails to parse. Both become hard errors.whitelist_hostsvsallowlist_hostsmismatch). Theenvsection stays permitted (smartconfig consumes it for environment injection), and nestedmetricssubkeys are validated too (username/passwordonly).portan integer in 1-65535 (no float truncation:8080.5is an error, not 8080);upstream_connections_per_hostan integer of at least 1;signing_keyrequired and at least 32 characters (existing rule, kept — no keyless mode; documented in the config error message);allowlist_hostsentries must be bare hostnames (non-empty strings, no scheme, no slash, no whitespace);state_dirnon-empty, must be creatable and writable (verified at startup with a probe file);sentry_dsnif set must parse as a URL with scheme and host;metrics.usernameandmetrics.passwordmust be set together or not at all;debug/maintenance_mode/allow_httpmust be booleans or ParseBool-able strings, not arbitrary numbers.config.Newreturns the error, fx aborts before binding the listener, process exits nonzero.Implementation plan
internal/config/config.go: extract anewFromSmartConfig(sc)construction/validation function thatNewwraps (keeps fx wiring untouched, makes the logic testable without fx). Replace the silent-default getters with strict variants that distinguish key-absent (default) from key-present-but-invalid (error). AddvalidateKnownKeysagainst the canonical key set (debug,maintenance_mode,port,state_dir,sentry_dsn,db_url,metrics,signing_key,allowlist_hosts,allow_http,upstream_connections_per_host,env). Extendvalidate()with the range/wellformedness rules above. Make a parse failure of an existing config file fatal inloadConfigFileinstead of warn-and-continue.internal/config/config_test.go: TDD — failing tests committed first, covering (a) omitted keys use defaults (minimal config with only a validsigning_key), (b) a table of set-but-invalid values each asserting startup fails with an error naming the key (invalid port string, port 0, port 70000, float port, non-booldebug, zero/negativeupstream_connections_per_host, allowlist entry with scheme, non-string allowlist entry, shortsigning_key, missingsigning_key,metrics.usernamewithout password, invalidsentry_dsn, explicitly emptystate_dir), (c) unknown top-level key aborts naming the key, (d)envsection is not flagged as unknown.main, tests committed first, then the implementation,make fmton touched markdown,make checkgreen,TODO.mdbookkeeping in the finishing commit, PR title ending with (closes #52), labelneeds-review.Implementation is up as PR #53 (branch
feature/config-validation, labeledneeds-review), executed per the plan above: TDD (failing enforcement tests committed first, then the implementation),make checkgreen, end-to-end verified (pixadexits 1 withconfig key "port": value "banana" is not an integeron a bad value andunknown config keys: whitelist_hostson a typo'd key). One deviation from the plan, documented in the PR: the lenientgetStringSlicehelper is retained because existing tests exercise it and test modification needs explicit approval — strictness forallowlist_hostsis enforced on the raw value byvalidateAllowlistHostsValuebefore extraction, so the no-silent-fallback guarantee holds regardless. Awaiting adversarial review per the standard workflow.