config: degrade an unknown enum token per field #316

Merged
natsukium merged 3 commits from config-enum-fallback into main 2026-09-21 15:53:15 +09:00
Owner

A closed config enum had no forward-compatible growth rule: one token an older build did not recognize failed the whole merged document and replaced every setting with the built-in defaults, so a file a newer felis wrote lost its font, theme, keymap, and window settings over one presentation option.

An unknown string token now degrades only its own field to that field's documented default and emits a value warning carrying the full key path, client.felis. prefix included when the overlay wrote it. Shape is still checked, so a number or a table where a token belongs stays a structural error, as do syntax errors and structurally unreadable TOML. shader.post degrades at the smallest typed boundary: an unrecognized builtin name or an unknown outer selector leaves the rest of [shader] alone and reports shader.post.builtin or shader.post.

Because the failure became a warning, felis config check reports it at exit 0, startup loads the document instead of falling to defaults, and live reload applies the degraded value rather than keeping the previous state. The generated JSON schema is unchanged apart from a $comment sentence: it keeps enumerating only the tokens this build understands, since editor validation describes the vocabulary in hand while the runtime rule is what carries a file between versions.

Docs: docs/reference/config.md (the additive-schema sentence and two rows in the malformed-value table), a new decision bullet in docs/explanation/architecture/control-surfaces.md "Configuration architecture" recording whole-document rejection as the rejected alternative, and a CHANGELOG entry.

Closes #314

A closed config enum had no forward-compatible growth rule: one token an older build did not recognize failed the whole merged document and replaced every setting with the built-in defaults, so a file a newer felis wrote lost its font, theme, keymap, and window settings over one presentation option. An unknown string token now degrades only its own field to that field's documented default and emits a `value` warning carrying the full key path, `client.felis.` prefix included when the overlay wrote it. Shape is still checked, so a number or a table where a token belongs stays a structural error, as do syntax errors and structurally unreadable TOML. `shader.post` degrades at the smallest typed boundary: an unrecognized `builtin` name or an unknown outer selector leaves the rest of `[shader]` alone and reports `shader.post.builtin` or `shader.post`. Because the failure became a warning, `felis config check` reports it at exit 0, startup loads the document instead of falling to defaults, and live reload applies the degraded value rather than keeping the previous state. The generated JSON schema is unchanged apart from a `$comment` sentence: it keeps enumerating only the tokens this build understands, since editor validation describes the vocabulary in hand while the runtime rule is what carries a file between versions. Docs: `docs/reference/config.md` (the additive-schema sentence and two rows in the malformed-value table), a new decision bullet in `docs/explanation/architecture/control-surfaces.md` "Configuration architecture" recording whole-document rejection as the rejected alternative, and a CHANGELOG entry. Closes #314
config: degrade an unknown enum token per field
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Has been cancelled
darwin / detect relevant changes (pull_request) Has been cancelled
darwin / build felis (aarch64-darwin) (pull_request) Has been cancelled
darwin / darwin docs-only no-op (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
pr / publish felis (x86_64-linux) (pull_request) Has been cancelled
windows / detect relevant changes (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
windows / windows docs-only no-op (pull_request) Has been cancelled
f6bf7f32a0
The config reference calls the schema additive and promises a file stays
readable across versions, but a closed enum had no such property: one
unrecognized token failed the whole merged document and replaced every
setting with the built-in defaults. An older build reading a file a newer
felis wrote lost its font, theme, keymap, and window settings over one
presentation option it has no code for, which would make every added
variant a config break needing a flag day.

An unknown string token now degrades only its own field to that field's
documented default and warns with the full key path, overlay spelling
included. Shape is still checked: a number or a table where a token
belongs remains a structural error, as does a syntax error, because no
version of felis accepted those. `shader.post` degrades at the smallest
typed boundary, so an unrecognized `builtin` name leaves the rest of
`[shader]` alone.

Because the failure is now a warning, `felis config check` reports it
without the exit-1 error, startup loads the document instead of falling
to defaults, and live reload applies the degraded value rather than
keeping the previous state. The generated schema keeps enumerating only
the tokens this build understands: editor validation describes the
vocabulary in hand, while the runtime rule is what carries a file
between versions.

Assisted-by: Claude Code
config: state field-local enum fallback as a requirement
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Has been cancelled
darwin / detect relevant changes (pull_request) Has been cancelled
darwin / build felis (aarch64-darwin) (pull_request) Has been cancelled
darwin / darwin docs-only no-op (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
pr / publish felis (x86_64-linux) (pull_request) Has been cancelled
windows / detect relevant changes (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
windows / windows docs-only no-op (pull_request) Has been cancelled
c68c746939
The rule that an unknown enum token degrades one field rather than the
document is a compatibility promise a reader needs to find in the
numbered set, not only in the config reference prose, so REQ-1103 names
it and cites the page that commits to it.

The helper's doc comment argued the case over seven lines, past the
five-line ceiling the comment gate enforces: the argument and its
rejected alternative belong in the control-surfaces decision bullet,
leaving the invariant at the site.

Assisted-by: Claude Code
docs: bound the enum-fallback rule to fields with a default
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Failing after 4m28s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 2m38s
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
pr / nix flake check (pull_request) Successful in 1m29s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m58s
pr / wire schema is compatible with the base (pull_request) Successful in 15s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m23s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 5s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 14m30s
windows / frontend smoke (Windows) (pull_request) Successful in 4m12s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 16s
a54f204b0e
"An enum-valued config key" read as every enum in the document, keymap
binding kinds and arguments included, which follow the opposite rule:
a binding has no default to fall back to, so an unrecognized one drops
that entry and the remaining keybindings apply. Naming the six
closed-enum fields that do carry a default keeps the two blast radii
distinguishable, and REQ-1103 now says which rule a keymap binding
takes and cites the page that owns it.

Assisted-by: Claude Code
natsukium deleted branch config-enum-fallback 2026-09-21 15:53:15 +09:00
Commenting is not possible because the repository is archived.
No description provided.