Skip to content

#77: /admin/reload-config — hot-reload env-driven globals#94

Open
BrettKinny wants to merge 2 commits into
mainfrom
loop-batch-77-reload-config
Open

#77: /admin/reload-config — hot-reload env-driven globals#94
BrettKinny wants to merge 2 commits into
mainfrom
loop-batch-77-reload-config

Conversation

@BrettKinny
Copy link
Copy Markdown
Owner

Summary

  • Adds POST /admin/reload-config that re-reads a whitelisted set of env vars into the matching module globals — no bridge restart needed.
  • Scope is intentionally narrow: VISION_, VLM_, WEATHER_*, CALENDAR_TTL_SEC. Background-task-coupled vars (CALENDAR_IDS, CALENDAR_POLL_SEC, *_STATE paths, LOCAL_TZ, DOTTY_VOICE_PROVIDER) are deliberately excluded — those still need a real restart.
  • Response shape: {ok, reloaded:[{name, old, new}], unchanged:[name]}. Secrets are masked as first4…last4 (API_KEY, _KEY, TOKEN in the var name).
  • Route lives on the existing _admin_router (localhost-only via _admin_require_localhost) — no CSRF or extra auth needed.

Why

The issue body called for a scoped reload endpoint and a design call on which vars are reload-safe. The whitelist above is the conservative cut: pure data with no background-task or watcher coupling.

Test plan

  • .venv/bin/pytest tests/test_bridge_routes.py -q — 38 passed (35 prior + 3 new)
  • .venv/bin/pytest tests/ -q — 262 passed
  • New tests cover: no-changes baseline, single-var change (with live-global verification), and secret masking.
  • Tests use dependency_overrides to bypass the localhost guard and toggle bridge.csrf._ENFORCE (the documented kill-switch) because TestClient requests go through CSRFMiddleware.

Closes #77

🤖 Generated with Claude Code

BrettKinny and others added 2 commits May 23, 2026 10:21
…bearing

The face-detected bare "Hi!" path previously suppressed only when at least
one roster member had an `appearance:` field. Households where members
were registered with just display_name + calendar prefix (the common
configuration) still saw a bare greet stack on top of the room-view named
greet, producing a double-greeting on each walk-in.

Suppress when the registry has any members — accept silent-listen-mode
for unidentified faces as the design (`Closes #23`, approach b).

- HouseholdRegistry: add `__len__` (with the standard `_reload_if_changed`
  guard).
- FaceGreeter: `_roster_has_appearances()` → `_roster_is_populated()`,
  checking `bool(self._household)`.
- Tests: new non-empty case (display_name only fixture); existing
  appearance-bearing case preserved.

Closes #23

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iterating on .env config required a full bridge restart. Add a maintenance
POST /admin/reload-config that re-reads a whitelist of env vars into the
module globals in place, returning a diff of what changed.

Scope is deliberately tight: vars whose only consumer is the request path
(VISION_*, VLM_*, WEATHER_*, CALENDAR_TTL_SEC). Vars tied to background
loops (CALENDAR_POLL_SEC, CALENDAR_IDS), file-watcher paths, LOCAL_TZ, or
the voice-provider selector are excluded — those need a real restart.

The route lives on the existing _admin_router (localhost-only via
_admin_require_localhost), so no CSRF, no extra auth. Secrets are masked
in the response: first4…last4, or *** for short values.

Tests bypass the localhost guard via dependency_overrides and toggle
bridge.csrf._ENFORCE (the documented kill-switch) since TestClient
requests go through CSRFMiddleware.

Closes #77

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 23, 2026 01:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a scoped admin endpoint to hot-reload a whitelist of env-driven config globals in bridge.py, and updates the behaviour-layer face greeter to suppress bare greetings whenever the household registry is non-empty (with accompanying tests).

Changes:

  • Add POST /admin/reload-config to reload selected WEATHER_*, VISION_*, VLM_*, and CALENDAR_TTL_SEC globals and return a structured “reloaded vs unchanged” response with secret masking.
  • Change FaceGreeter suppression logic from “roster has appearances” to “roster is non-empty”, enabled by adding HouseholdRegistry.__len__.
  • Add tests covering the new reload endpoint and the updated face greeter suppression behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
bridge.py Implements /admin/reload-config and secret-masking helpers on the localhost-only admin router.
tests/test_bridge_routes.py Adds boundary tests for /admin/reload-config, including masking and live-global update assertions.
dotty-behaviour/consumers/face_greeter.py Updates face-detected suppression criteria to trigger whenever the household registry is populated.
dotty-behaviour/household/registry.py Adds __len__ to support efficient truthiness checks with hot-reload semantics.
dotty-behaviour/tests/test_consumer_face_greeter.py Adds a test ensuring bare greeting is suppressed when the roster is non-empty (even without appearances).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread bridge.py
Comment on lines +5637 to +5648
old = g.get(var_name)
raw = os.environ.get(env_name, "")
try:
new = caster(raw) if raw != "" else (
"" if caster is str else caster(0)
)
except (TypeError, ValueError):
log.warning(
"reload-config: cast failed for %s=%r as %s",
env_name, raw, caster.__name__,
)
continue
Comment thread bridge.py
Comment on lines +5628 to +5632
@_admin_router.post("/reload-config")
async def _admin_reload_config() -> dict:
"""Re-read whitelisted env vars into module globals. Returns
{ok, reloaded:[{name, old, new}], unchanged:[name]}. Secrets are
masked in the response."""
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.

Dashboard: /ui/reload-config to reread env vars without restart

2 participants