feat: add source config ownership policy

This commit is contained in:
lda
2026-06-04 17:49:05 +07:00 Unverified
parent b211eef1c7
commit 00305c1398
15 changed files with 1112 additions and 36 deletions
+63 -15
View File
@@ -5,7 +5,12 @@ from dataclasses import dataclass, field
from ...connections import ConnectionRegistry, parse_connection_id
from ...models import BrokerConfig, ConnectionConfig
from ...shared.names import RESERVED_CONNECTION_IDS
from ...source_registry import SourceRegistryStore, registry_entry_to_connection_config
from ...source_registry import (
SourceRegistryFile,
SourceRegistryStore,
connection_config_to_registry_entry,
registry_entry_to_connection_config,
)
from .events import BrokerEventRecorder
from .source_catalog import SourceCatalogService
@@ -61,25 +66,68 @@ class ConnectionService:
source_registry_store: SourceRegistryStore | None = None,
) -> None:
"""Reconcile registry/source state after the public server reloads config."""
# Config-defined connections win over registry entries with the same id;
# registry entries fill ids not present in config.
connections = list(config.connections)
config_ids = {connection.id for connection in connections}
config_by_id = {connection.id: connection for connection in connections}
registry_entries = {}
registry_changed = False
if source_registry_store is not None:
registry = source_registry_store.load_registry()
for entry in registry.sources:
if entry.id in config_ids:
self.events.record_kind(
"source_registry_ignored_config_shadow",
connection_id=entry.id,
payload={
"server": entry.provider,
"account": entry.account,
"reason": "config_connection_takes_precedence",
},
registry_entries = registry.source_map()
for connection in connections:
if connection.source_config_ownership != "seed":
continue
if connection.id in registry_entries:
continue
seeded = connection_config_to_registry_entry(connection)
registry_entries[seeded.id] = seeded
registry_changed = True
self.events.record_kind(
"source_registry_seeded_from_config",
connection_id=seeded.id,
payload={"server": seeded.provider, "account": seeded.account},
)
if registry_changed:
source_registry_store.save_registry(
SourceRegistryFile(sources=list(registry_entries.values()))
)
merged_connections: list[ConnectionConfig] = []
for connection in connections:
registry_entry = registry_entries.get(connection.id)
if (
connection.source_config_ownership == "seed"
and registry_entry is not None
):
merged_connections.append(
registry_entry_to_connection_config(registry_entry)
)
continue
connections.append(registry_entry_to_connection_config(entry))
merged_connections.append(connection)
merged_ids = {connection.id for connection in merged_connections}
for entry in registry_entries.values():
config_connection = config_by_id.get(entry.id)
if config_connection is not None:
if config_connection.source_config_ownership == "locked":
self.events.record_kind(
"source_registry_ignored_config_shadow",
connection_id=entry.id,
payload={
"server": entry.provider,
"account": entry.account,
"reason": "locked_config_connection_takes_precedence",
},
)
continue
if entry.id not in merged_ids:
merged_connections.append(
registry_entry_to_connection_config(entry)
)
connections = merged_connections
source_catalog = self._source_catalog()
next_ids = {connection.id for connection in connections}
@@ -33,8 +33,20 @@ class SourceRegistryAdminProvider(WorkflowSourceRegistryMutationProvider):
def config_source_ids(self) -> set[str]:
return {connection.id for connection in self.config_connections}
def config_source_ownership(self) -> dict[str, str]:
return {
connection.id: connection.source_config_ownership
for connection in self.config_connections
}
# -- private helpers ----------------------------------------------------
def _config_connection(self, source_id: str) -> ConnectionConfig | None:
for connection in self.config_connections:
if connection.id == source_id:
return connection
return None
def _load(self) -> SourceRegistryFile:
return self.source_registry_store.load_registry()
@@ -58,9 +70,13 @@ class SourceRegistryAdminProvider(WorkflowSourceRegistryMutationProvider):
def add_registry_entry(self, entry: Mapping[str, Any]) -> McpSourceRegistryEntry:
source_id = str(entry["id"])
if source_id in self.config_source_ids():
config_connection = self._config_connection(source_id)
if (
config_connection is not None
and config_connection.source_config_ownership == "locked"
):
raise ValueError(
f"cannot add {source_id!r}: id is shadowed by a config connection"
f"cannot add {source_id!r}: id is locked by a config connection"
)
validated = McpSourceRegistryEntry.model_validate(dict(entry))
registry = self._load()