# Config I/O State Ownership **Rule:** The `AppController` is the single source of truth for the in-memory config (`self.config`) and the only authorized caller of the file I/O primitives (the config-load/save helpers, which moved out of `src/models.py` to `src/project_manager.py` per `module_taxonomy_refactor_20260627`). ## Why 1. **The controller owns the in-memory state.** If other modules write to `config.toml` directly, the controller's `self.config` silently drifts from disk. Tests can corrupt the user's TOML files; users lose data without warning. 2. **Test isolation breaks.** When `models.save_config(...)` is called from anywhere in `src/`, tests cannot intercept the write without patching the I/O primitive. The test then couples to the file format, not the controller's behavior. 3. **Path resolution can't be enforced.** The controller respects `SLOP_CONFIG` env var at call time. Direct calls to `models.save_config` would only respect it if the path is re-resolved (which it is in `_save_config_to_disk`, but only because someone remembered). ## What is Forbidden in `src/` - `models.load_config(...)` (legacy public function) - `models.save_config(...)` (legacy public function) - `models._load_config_from_disk(...)` (private I/O primitive) - `models._save_config_to_disk(...)` (private I/O primitive) The only allowed call sites are inside `AppController` itself (`load_config()` and `save_config()` methods). ## The Public API ```python # In AppController: def load_config(self) -> Dict[str, Any]: """Re-read the global config.toml from disk and update self.config.""" self.config = models._load_config_from_disk() return self.config def save_config(self) -> None: """Flush self.config to disk.""" models._save_config_to_disk(self.config) ``` Callers (including `gui_2.py`, `commands.py`, etc.) go through the controller: ```python # In App class methods (gui_2.py): __getattr__ delegates to controller self.save_config() # -> controller.save_config() app.save_config() # -> controller.save_config() (via __getattr__) app.load_config() # -> controller.load_config() (via __getattr__) # In AppController: self.save_config() # direct self.load_config() # direct ```