Skip to content

D747 — One settings schema in code documents both YAML files from inside

Decision. Each settings file has one key table declared in code (internal/settingschema holds the type and the renderer; internal/config/schema.go and internal/clientconfig/schema.go hold the tables): path, scope (server, kb, client), type, values, default, description, the env var and flag that override it, restart, secret. The table feeds the comments written into the file, the generated config.example.yaml, and the later config view and KB/server split. The client Save is a yaml.Node round-trip, and the three server-owned caches (known_kbs, server_mount_mode, server_routed_path) move to .cartographer-state.json.

Why. Both files were undocumented in place, and the only commented copy, config.example.yaml, was hand-written and drifted. The client Save marshalled a map, so every comment was dropped on every save, and because the caches were rewritten by every sync the file was rewritten constantly. Splitting the caches off makes the user file change only when a person changes a setting, which is what makes keeping their comments tractable.

Alternatives rejected. - Struct tags as the source: the server has a raw/public struct pair plus env and flag layers, so tags would be duplicated and still miss the overrides. A reflection test keeps the table and the structs in sync instead. - One merged file for client and server: owners and lifecycles differ (a remote server's config is GitOps, the client's is on the workstation) and the local service pins auth off (D268). - Keeping the caches in the YAML and fighting the rewrite churn: every sync would still touch the file a person edits. - Hand-maintaining config.example.yaml: it is the thing that drifted.

Consequences. A new YAML key needs a schema entry or the build fails. A file with no comments loads and behaves as before, comments being additive. A comment the user wrote, and any key the program does not know, survive every save; the schema only documents keys that carry no comment. An existing installation upgrades with no action: Load reads the caches from the YAML while there is no state file, the next save moves them and drops them from the YAML, and a corrupt state file is treated as absent. Release note: client files are rewritten with comments at the next save and lose the three cache keys. Scope kb marks the kbs[] keys that a KB may later carry itself.