Skip to content

config: the configuration nervous system foundation -- bindings, layers, provenance#359

Merged
berrym merged 4 commits into
masterfrom
config/binding-keystone-prototype
Jun 25, 2026
Merged

config: the configuration nervous system foundation -- bindings, layers, provenance#359
berrym merged 4 commits into
masterfrom
config/binding-keystone-prototype

Conversation

@berrym

@berrym berrym commented Jun 25, 2026

Copy link
Copy Markdown
Owner

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 config builtin, display front-end, and TOML as views over it. Built as a de-risking prototype on the history.* section behind the unchanged config_registry_get/set API — full suite green at every step. Includes the north-star vision doc, grounded in this proven code rather than conjecture.

Three commits:

  1. 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-written sync_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 converting history.* and deleting both its sync hooks and its enum strcmp ladders.

  2. 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 zshconfig set history.finder.match prefixmode bash, and the value is still prefix (SESSION sits above MODE; a mode switch re-seeds MODE wholesale). New config explain <key> prints the shadowed stack with origins — "lush knows why," applied to config itself. history.* migrated off the legacy config_options[] table; config_set/get_bool/int made registry-aware.

  3. 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).
  • Real shell: config set round-trips; per-mode defaults apply via binding; lushrc.toml loads via binding; the mode-clobber scenario survives; config explain shows the layered provenance; config save persists 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 display coverage, and the wizard follow as scheduled in the doc's roadmap — each strangler step keeping the shell green.

🤖 Generated with Claude Code

berrym added 4 commits June 25, 2026 15:46
…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.
@berrym berrym merged commit 7be5a36 into master Jun 25, 2026
4 checks passed
@berrym berrym deleted the config/binding-keystone-prototype branch June 25, 2026 20:55
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.29787% with 69 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/config.c 37.09% 39 Missing ⚠️
src/config_registry.c 76.19% 30 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant