Newer
Older
navi-1 / navi / overlay / schema.py
"""Which settings the central overlay may touch, and which take effect live.

Two lists, and the asymmetry between them is deliberate.

``DENIED_SETTINGS`` are fields a hive document can never change. They are not
a matter of taste: the bootstrap ones (``HIVE_URL``, the key path, the cache
path) are needed *before* the overlay can be fetched, so an overlay that set
them could never describe the channel it arrives through. The rest are the
ways a config can make every machine unreachable or silently point it at the
wrong database — a valid value that is merely wrong is the one failure class
validation cannot catch.

``HOT_SETTINGS`` are fields read from the singleton at call time, so writing a
new value into the live object takes effect immediately. Everything else
changes something built at startup (CORS middleware, backends, the MCP pool,
the push service) and is reported as ``restart_required`` instead of being
pretended into effect.
"""

from __future__ import annotations

# Never overlaid. Unknown to the hive's validator? No — known, and refused.
DENIED_SETTINGS: frozenset[str] = frozenset(
    {
        # Bootstrap: needed before the overlay exists.
        "hive_url",
        "swarm_key_file",
        "instance_file",
        "hive_config_enabled",
        "hive_config_cache_file",
        "hive_config_timeout_sec",
        # Reachability and data: a wrong-but-valid value is unrecoverable
        # without a local edit (or, for the DB, looks like lost history).
        "database_url",
        "navi_host",
        "navi_port",
        "navi_ui_mcp_host",
        "navi_ui_mcp_port",
        "navi_auth_enabled",
    }
)

# Applied to the live settings object on refresh; everything else is staged
# until the next start. Only fields verified to be read at call time belong
# here — a wrong entry would claim an effect that never happens.
HOT_SETTINGS: frozenset[str] = frozenset(
    {
        # gnexus-auth: read per request / per login. The cached default OAuth
        # client is reset by an apply hook (navi/auth/client.py).
        "gnauth_base_url",
        "gnauth_client_id",
        "gnauth_client_secret",
        "gnauth_redirect_uri",
        "gnauth_admin_role_slug",
        "gnauth_user_role_slug",
        "gnauth_profile_path",
        "gnauth_webhook_secret",
        # Synapse outbound: a client is built per emit.
        "synapse_source_url",
        "synapse_source_api_key",
        "synapse_source_name",
        # Read at call time.
        "public_url",
        "navi_default_profile_id",
        "navi_push_cooldown_sec",
        "peer_ask_timeout_sec",
        "session_files_max_size_mb",
        "share_file_max_size_mb",
        "terminal_allowed_commands",
        "terminal_user_allowed_commands",
        "fs_allowed_paths",
        "backgroundable_tools",
        "parallel_tool_calls",
        "message_queue_max",
        "tasks_max_per_session",
        "tasks_max_global",
        "tasks_max_spawn",
        "tasks_ttl_sec",
        "tasks_event_buffer_size",
        "tasks_rate_limit",
        "task_notes_max_pending",
        "task_notes_per_turn",
        # LLM timeouts / retries.
        "llm_complete_timeout",
        "planning_llm_timeout_sec",
        "llm_stream_first_chunk_timeout",
        "llm_stream_chunk_timeout",
        "llm_complete_retries",
        "llm_retry_backoff_sec",
        # Sub-agent guards.
        "subagent_thinking_stall_seconds",
        "subagent_thinking_stall_chars",
        "subagent_timeout_seconds",
        # Context compression.
        "context_compression_enabled",
        "context_compression_threshold",
        "context_compression_target",
        "context_keep_recent",
        "context_summary_temperature",
        "context_summary_max_tokens",
        "output_reserve_tokens",
        "context_message_token_budget",
    }
)

KNOWN_SECTIONS: frozenset[str] = frozenset(
    {"settings", "profiles", "llm", "mcp_servers"}
)