config: the configuration nervous system foundation -- bindings, layers, provenance#359
Merged
Merged
Conversation
…ync hooks
Introduce runtime bindings: config_registry_bind_{boolean,integer,string,enum}
tie a registered key to the real runtime cell (e.g. &config.history_size). The
single writer (config_registry_set) write-throughs every bound cell on every
change from any surface -- config builtin, TOML load, mode preset -- so the cell
is an always-current cache with no hand-written sync. Binding an unregistered key
returns CREG_ERROR_NOT_FOUND, turning the old "phantom sync" (a forgotten or
mistyped key silently no-opping) into a loud failure.
Convert history.* as the proof: bind its nine keys (bool/int/enum) and delete
history_sync_to_runtime and history_sync_from_runtime entirely, including the
search-mode and finder strcmp ladders -- the enum-string-to-int mapping is now a
declarative pair table the registry applies once per change. The section's sync
hooks are NULL.
Verified: config set, per-mode defaults (lush=fuzzy/prefix, bash=substring/plain),
and lushrc.toml all reach config.history_* through the binding; full suite green,
so history behavior is unchanged with the sync code removed. Hot paths still read
the plain config.history_* fields; the registry is just their sole writer.
This is the de-risked keystone the config-nervous-system vision rests on. Layered
slots (provenance + the mode-clobber fix), the type vtable, and config explain
follow as the next increments, retiring the legacy/struct duplication section by
section behind the unchanged get/set API.
Replace each option's single {value, default_val} cell with an ordered slot
array, one value per layer: DEFAULT < MODE < SYSTEM < USER < SESSION. The
effective value is the highest present layer (a pure resolve); writes route to a
layer by source -- registration to DEFAULT, mode presets to MODE, lushrc.toml to
USER, interactive config set to SESSION -- through one set_in_layer funnel that
recomputes the effective value and only then write-throughs bindings and
notifies.
This structurally fixes the mode-default clobber: apply_mode_defaults clears the
MODE layer wholesale and re-seeds it, so a config set (SESSION) sits above MODE
and survives a mode switch, while a stale preset from the previous mode falls
away. config reset clears the SESSION layer and re-resolves (falls through to
file/mode/default) rather than stamping the static default.
The slots make provenance free: config_registry_inspect reports the effective
value, the winning layer, and every present layer's value and origin. New
`config explain <key>` prints the shadowed stack -- history.finder.match =
prefix (from session); shadows mode=substring, default=fuzzy -- the "lush knows
why" guarantee applied to configuration itself.
Migrate history.* fully off the legacy config_options[] table (its keys now
resolve through the registry, which is what lets config set reach the SESSION
layer) and make config_set/get_bool/int registry-aware for migrated keys. The
dead history enum tables and the two sync hooks are gone. Full suite green.
Capture the north star for CREG as lush's central configuration system: a schema-first, reactive, layered store that is the single source of truth, with the config builtin, display front-end, TOML, and a future wizard as views over it -- a discoverable alternative to traditional dotfile editing that solves the shell-customization learning curve. The document distinguishes proven from planned at every point: the bindings keystone (single-writer write-through, no sync hooks) and the layered store (default<mode<user<session precedence, the structural mode-clobber fix, and config explain provenance) are built and verified on history.* with the suite green; the type vtable, CI invariant, schema tiers, versioning/migration, and the wizard are designed and scheduled. Records the config-vs-code boundary, the research backbone (GSettings/Nix/Emacs/git/VS Code/K8s), the strangler-migration strategy, the roadmap, and the open questions. Linked from CLAUDE.md.
The CREG_VALUE_NONE case used snprintf(out, n, "") to empty the buffer. gcc rejects the zero-length format string under -Werror=format-zero-length (clang is silent, so the macOS build passed while ubuntu CI failed). Clear the buffer directly instead.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The proven foundation for evolving CREG into lush's central configuration nervous system: a schema-first, reactive, layered store that is the single source of truth, with the
configbuiltin,displayfront-end, and TOML as views over it. Built as a de-risking prototype on thehistory.*section behind the unchangedconfig_registry_get/setAPI — full suite green at every step. Includes the north-star vision doc, grounded in this proven code rather than conjecture.Three commits:
The binding keystone.
config_registry_bind_{boolean,integer,string,enum}ties a key to the real runtime cell;config_registry_set(the single writer) write-throughs every change from any surface. Replaces hand-writtensync_to_runtime/sync_from_runtime; binding an unregistered key fails loudly (CREG_ERROR_NOT_FOUND) — phantom-sync can no longer ship silently. Hot paths keep reading the plain struct field; the registry is just its sole writer. Proven by convertinghistory.*and deleting both its sync hooks and its enum strcmp ladders.Layered slots + provenance. Each option holds a value per layer (
DEFAULT < MODE < SYSTEM < USER < SESSION); effective = highest present. Writes route by source. This structurally fixes the mode-default clobber:mode zsh→config set history.finder.match prefix→mode bash, and the value is stillprefix(SESSION sits above MODE; a mode switch re-seeds MODE wholesale). Newconfig explain <key>prints the shadowed stack with origins — "lush knows why," applied to config itself.history.*migrated off the legacyconfig_options[]table;config_set/get_bool/intmade registry-aware.The vision doc (
docs/development/CONFIG_NERVOUS_SYSTEM.md) — the model, principles, the proven mechanism, surfaces-as-views, the config-vs-code boundary, the research backbone (GSettings/Nix/Emacs/git/VS Code/K8s), the strangler-migration strategy, roadmap, and open questions. PROVEN vs PLANNED marked throughout.Verification
meson test: 156/156, zero warnings (werror).config setround-trips; per-mode defaults apply via binding;lushrc.tomlloads via binding; the mode-clobber scenario survives;config explainshows the layered provenance;config savepersists the effective value.What this is and isn't
This is the foundation (mechanism + one proven section migration + the doc), not the whole migration. The remaining sections, the legacy-table retirement, the type vtable, the CI startup invariant, the full
displaycoverage, and the wizard follow as scheduled in the doc's roadmap — each strangler step keeping the shell green.🤖 Generated with Claude Code