[v0.1/Config Review] client overlay の schema/runtime 乖離と additionalProperties 凍結 #140
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#140
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
EffectiveConfigは#[serde(default)]による lenient parsing を永続契約とする(docs/explanation/architecture/control-surfaces.md「Lenient config parsing」: 共有文書なので vocabulary を1消費者が所有しない)。具体的には:crates/felis-client-core/src/config.rsのEffectiveConfigと全 sub-struct は#[serde(default)]。serde_ignoredで warning に回し、parse error にしない。client: BTreeMap<String, toml::Value>は他 client セクションを raw TOML のまま保持し、この client の schema で検証しない。一方 JSON Schema (
crates/felis-client-core/felis-config.schema.json,just schema生成) は:と
schemars(with = ...)で上書きし、他 client セクションをEffectiveConfigとして厳密に検証する形で出力する。runtime はtoml::Valueで lenient、schema はEffectiveConfigで strict という乖離がある。さらに runtime が
BTreeMap<String, toml::Value>であるため、トップレベルにadditionalProperties: falseを置けない理由が「単一文書に複数 client の vocabulary が同居する」ことだけで、1.0 で凍結するとサードパーティ frontend が独自キーを追加した際の editor 上の typo 検出と forward-compat の両立が permanent に lenient に固定される。#4「単一文書 + オーバレイ vs 複数ファイル」でconfig.d/<client>.tomlの drop-in 案が提示されたが、additionalPropertiesの strict 化可否という schema 観点の決定が未分離。これは config の「キー名は今しか変えられないが、追加は additive で読める」という契約の核心で、1.0 でキー集合を凍結する際に schema/runtime の乖離を残すと、後から strict 化は breaking(既存の lenient ファイルが error になる)。
問い
1.0 で以下を決める:
client型を一致させるか —schemars(with = BTreeMap<String, EffectiveConfig>)をやめてBTreeMap<String, toml::Value>相当の permissive schema にするか、逆に runtime もBTreeMap<String, EffectiveConfig>に寄せて「他 client セクションも警告対象」に昇格するか。additionalPropertiesを non-strict のまま凍結するか — 現行の「unknown key は warn」契約を 1.0 で規範化し、felis-config.schema.jsonのトップレベルadditionalProperties: falseを永遠に置かない決定としてreference/config.mdに明記するか。config.d/に移行するなら今が最後のチャンスで、#4と合わせて判断が必要。client.otherの中身が壊れた TOML でも current client の起動を妨げないことはテストされているか。diagnosticsが他 client の unknown_key を report しないことは 1.0 の契約として固定するか。提案
client型を runtime に揃える —schemars(with = BTreeMap<String, toml::Value>)相当の open map にし、「他 client セクションは inert、検証しない」ことをreference/config.md「Per-client overrides」に frozen として追記。additionalProperties: falseを置かない理由をfelis-config.schema.jsonのトップコメントとconfig.mdに残す。explanation/architecture/control-surfaces.mdに Revisit trigger を残す —「全 client が schema を共有できた時、またはadditionalProperties: falseで typo 検出を厳密にしたい要望が継続したらconfig.d/への移行を検討」。#4とリンク。just schemaの snapshot とfelis config checkの diagnostics カバレッジに「他 client の壊れた TOML が current client を壊さない」ケースを追加。判定基準
felis-config.schema.jsonを VS Code に当てた時に、自 client セクションの typo は波線、他 client セクションの独自キーは波線にならない(inert)ことが editor 上で確認できること。cargo test -p felis-client-core --features schemaの schema snapshot が runtime のEffectiveConfigと乖離しないこと(schemarsのwith乖離が無い)。reference/config.md「Per-client overrides」「Behavior on missing / malformed values」表に「他 client セクションは inert」が 1.0 凍結として明記されていること。explanation/architecture/control-surfaces.mdに strict 化の Revisit trigger が記録されていること。対象ファイル
crates/felis-client-core/src/config.rs(EffectiveConfig.client型、schemarsattribute)crates/felis-client-core/felis-config.schema.json(生成物、トップコメント)docs/reference/config.md「Per-client overrides」「Behavior on missing / malformed values」docs/explanation/architecture/control-surfaces.md「Lenient config parsing」tests/(config diagnostics の cross-crate guard)Parent: #12 および #11 / #4
Triage (2026-09-05)
Verdict: accept, P1. Verified:
config.rs:73overrides the schema type ofclienttoBTreeMap<String, EffectiveConfig>while runtime holdstoml::Value. #29 publishes this schema, so the mismatch must be gone before then. Take the short-term proposal: an open map in the schema matching the runtime, "other client sections are inert and unvalidated" frozen inreference/config.md, the "no top-leveladditionalProperties: false" reason in the schema header, and a Revisit trigger (config.d/drop-ins) inexplanation/architecture/control-surfaces.md. Add the "broken TOML under another client section does not stop this client" case to the diagnostics tests. Order: before #29.Triage plan (2026-09-05)
Source-grounded triage against
mainat07a77ae4, reviewed through six rounds of an independent reviewer (piluna then sol); every finding was verified against the source and folded in, and the design decisions below were settled with the maintainer on 2026-09-05. The order that supersedes the tracker's is posted on #12: this issue lands before #29 (schemas, goldens, and reference tables freeze there).Config schema
clientmap: match the runtimeClaim check
crates/felis-client-core/src/config.rs:70-75: runtimeclient: BTreeMap<String, toml::Value>, schema overridden toBTreeMap<String, EffectiveConfig>, which in the generatedfelis-config.schema.json:7-16becomesadditionalProperties: {"$ref": "#"}.So an editor validates every
[client.<name>]section against thisclient's vocabulary, while the runtime validates none but its own
(
document.rs:137-165,serde_ignoredafter the overlay merge).additionalProperties: false(the 23 occurrencesare nested closed shapes such as keymap entries), so today an editor
flags neither own-section typos nor foreign-section keys; it flags only
type mismatches in any section. The issue's acceptance criterion
("own-client typos squiggle") is therefore not satisfiable by the
clientchange alone; it needs a decision on root strictness.Verdict
accept. Root strictness is not open:
docs/explanation/architecture/control-surfaces.md:796-812already records "the generated JSON schema is deliberately non-strict"
(editor completion and hover; the runtime parser is the authority;
Revisit: none). So the root and every section stay open, and the only
change is the
clientmap: name the known client id explicitly,properties: { "felis": {"$ref": "#"} },additionalProperties: true.That validates the GUI's own overlay with its own schema and leaves every
other client's section open, which is the runtime split. The issue's
"own-client typos squiggle" criterion is therefore out of scope (it would
need the strictness that decision rejects); say so in the plan comment.
Approach
config.rs:70-75: replaceschemars(with = …)byschemars(schema_with = "client_overlay_schema")producing the shape above (the client idcomes from the same constant the loader matches on; verify its name in
document.rs).just schema; update the schema header text to state the two reasons(open map: other clients' sections are inert; no root
additionalProperties: false: shared document, unknown keys warn).docs/reference/config.md"Per-client overrides": "Other clientsections are inert" becomes a frozen 1.0 statement, plus one sentence
that the published schema validates only the
felisoverlay.docs/explanation/architecture/control-surfaces.md: the "Lenientconfig parsing" decision keeps its
Revisit: none. Theconfig.d/<client>.tomldrop-in idea belongs to the one-documentdecision it already sits under (
:789), so add the Revisit triggerthere, with a concrete condition: a second first-party client ships
whose top-level vocabulary diverges from the GUI's (a section one
client owns and the other must not read), linking #4.
config.rs(:2503) asserts theclientshape; (2) diagnostics test: a[client.other]section holdinga wrong-typed known key and an unknown key yields no diagnostic and does
not stop this client's load.
Dependencies / risk / labels
Before #29 (publishes the schema). S. Keep labels.
CHANGELOG.md:the schema's overlay validation changes (a config surface).