mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 01:14:45 +02:00
Pre-existing issue surfaced by v0.6-tail-3: prod config at
/data/config/config.yaml:82 uses !include to compose from separate
files, but the loader had no constructor registered so PUT
/api/config/<section> returned 500 with could-not-determine-constructor
when the section save path round-tripped YAML. This adds the !include
constructor (read path) + preserves the include structure on write so
multi-file config layouts work end-to-end via the GUI. The runtime
config behavior is unchanged; this only fixes the PUT-and-round-trip
case.
Implementation note: the read-only runtime path
(_load_yaml_with_includes) already had a working !include constructor
that recursively substitutes content. The bug was specifically in
save_section() -- it used plain yaml.safe_load() to re-read the target
file off disk for secret-ref preservation and for in-place section
updates. When target_file == "config.yaml" that file contains !include
directives for OTHER sections, and safe_load died on them.
Adding a third constructor that substitutes !include on save would
have flattened the multi-file layout to a single file the first time
anyone PUT an inline section. Instead this commit adds a preserve-mode
loader/dumper pair:
- _load_yaml_preserve() returns an Include("path") sentinel for each
!include node instead of recursing into the referenced file.
- _dump_yaml_preserve() re-emits Include("path") back to disk as
`!include path`. (PyYAML auto-quotes when the scalar contains a
period, so the on-disk form is `!include 'foo.yaml'`; both forms
are equivalent at parse time.)
- save_section()'s three yaml-touching sites (the secret-ref raw
read, the existing-target read, and the final dump) now use these
helpers. Local.yaml stays on yaml.safe_load/dump because local.yaml
never contains !include.
The runtime loader is untouched, so boot-time config still substitutes
includes and Config dataclasses see real values. Only the GUI's
section-save path round-trips through the preserve helpers.
Tests (tests/test_include_roundtrip.py, 8 cases):
- Runtime loader still substitutes !include content (regression guard)
- Preserve loader returns Include() sentinels
- Preserve dumper re-emits `!include path` (tolerant of PyYAML
auto-quoting)
- Read -> write -> read identity through the preserve helpers
- save_section('bot', ...) on a config.yaml that uses !include for
sibling sections succeeds AND leaves the includes intact on disk
(this is the exact prod PUT 500 case from v0.6-tail-3)
- After save_section, the runtime loader re-resolves all !include
files AND sees the saved change to the inline section
- save_section on a dedicated file (env_feeds.yaml) writes only that
file; config.yaml's !include directives are untouched
- Runtime cycle detection still trips on A!include->B!include->A
Live verification on CT108 after rebuild:
PUT /api/config/bot {"name":"AIDA","owner":"Malice","respond_to_dms":true,"filter_bbs_protocols":true}
-> HTTP 200 {"saved":true,"restart_required":false,"changed_keys":[]}
/data/config/config.yaml retains all 7 !include directives
(meshtastic, mesh_sources, mesh_intelligence, environmental,
notifications, llm, dashboard)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| fixtures/central_envelopes | ||
| conftest.py | ||
| test_adapter_avalanche.py | ||
| test_adapter_config_api.py | ||
| test_adapter_config_foundation.py | ||
| test_adapter_ducting.py | ||
| test_adapter_fires.py | ||
| test_adapter_firms.py | ||
| test_adapter_nws.py | ||
| test_adapter_roads511.py | ||
| test_adapter_swpc.py | ||
| test_adapter_traffic.py | ||
| test_adapter_usgs.py | ||
| test_adapter_usgs_quake.py | ||
| test_avalanche_v057.py | ||
| test_band_conditions.py | ||
| test_central_consumer.py | ||
| test_central_envelope_to_wire_v057.py | ||
| test_central_normalizer.py | ||
| test_central_region_routing.py | ||
| test_central_sub_adapter_routing.py | ||
| test_channel_rendering.py | ||
| test_cold_start_grace.py | ||
| test_config_loader.py | ||
| test_config_source_field.py | ||
| test_consumer_default_deny.py | ||
| test_curation.py | ||
| test_dashboard_config_save.py | ||
| test_dispatcher_persistence.py | ||
| test_env_reporter.py | ||
| test_fire_v057.py | ||
| test_firms_handler.py | ||
| test_incident_handler.py | ||
| test_include_roundtrip.py | ||
| test_itd_511_work_zone.py | ||
| test_notification_toggles.py | ||
| test_nwis_handler.py | ||
| test_nws_dedup_relaxation.py | ||
| test_nws_handler.py | ||
| test_or_arch_continuous.py | ||
| test_persistence.py | ||
| test_pipeline_digest.py | ||
| test_pipeline_grouper.py | ||
| test_pipeline_inhibitor_grouper.py | ||
| test_pipeline_persistence.py | ||
| test_pipeline_scheduler.py | ||
| test_pipeline_skeleton.py | ||
| test_pipeline_toggle_filter.py | ||
| test_quake_handler.py | ||
| test_reminders.py | ||
| test_renderers.py | ||
| test_rf_v057.py | ||
| test_router_env_scope.py | ||
| test_save_section_secret_preserve.py | ||
| test_seismic_v057.py | ||
| test_swpc_handler.py | ||
| test_tail_followups.py | ||
| test_tracking_v057.py | ||
| test_traffic_v057.py | ||
| test_v052_dispatcher.py | ||
| test_water_v057.py | ||
| test_weather_v057.py | ||
| test_wfigs_handler.py | ||
| test_work_zone_renderer.py | ||