Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 73 additions & 41 deletions loopx/capabilities/machine_configuration/builtins.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,83 @@
from __future__ import annotations

from collections.abc import Mapping
from collections.abc import Callable, Mapping
from importlib import import_module
from typing import Any

from .contract import MachineConfigurationRegistry
from .contract import MachineConfigurationNamespace, MachineConfigurationRegistry

# Namespace id, owner module and factory. Owners stay at the composition
# boundary: the generic contract does not depend on any consumer capability,
# and a reader loads only the namespaces it resolves.
_BUILTIN_NAMESPACES = (
(
"context_delivery",
"loopx.capabilities.machine_configuration.context_delivery",
"context_delivery_machine_configuration_namespace",
),
(
"explore_harness",
"loopx.capabilities.explore.machine_defaults",
"explore_machine_configuration_namespace",
),
(
"codex_chat",
"loopx.capabilities.native_chat.machine_defaults",
"codex_chat_machine_configuration_namespace",
),
(
"goal_storage",
"loopx.capabilities.machine_configuration.goal_storage",
"goal_storage_machine_configuration_namespace",
),
(
"manager_runtime",
"loopx.capabilities.manager_runtime.machine_profile",
"manager_runtime_machine_configuration_namespace",
),
(
"periodic_report",
"loopx.capabilities.periodic_report.machine_defaults",
"periodic_report_machine_configuration_namespace",
),
(
"todo_replan_cadence",
"loopx.capabilities.todo_replan_cadence.machine_defaults",
"todo_replan_cadence_machine_configuration_namespace",
),
(
"change_quality_qualification",
"loopx.capabilities.change_quality.machine_defaults",
"change_quality_machine_configuration_namespace",
),
(
"pull_request_review",
"loopx.capabilities.pr_review_queue.machine_defaults",
"pull_request_review_machine_configuration_namespace",
),
(
"steward_executor",
"loopx.capabilities.steward_executor.machine_defaults",
"steward_executor_machine_configuration_namespace",
),
)


def _owner(module: str, factory: str) -> Callable[[], MachineConfigurationNamespace]:
def load() -> MachineConfigurationNamespace:
contract: MachineConfigurationNamespace = getattr(
import_module(module), factory
)()
return contract

return load


def build_builtin_machine_configuration_registry() -> MachineConfigurationRegistry:
# Imports stay at the composition boundary: the generic contract does not
# depend on any consumer capability.
from ..change_quality.machine_defaults import (
change_quality_machine_configuration_namespace,
)
from ..periodic_report.machine_defaults import (
periodic_report_machine_configuration_namespace,
)
from ..manager_runtime.machine_profile import (
manager_runtime_machine_configuration_namespace,
)
from ..pr_review_queue.machine_defaults import (
pull_request_review_machine_configuration_namespace,
)
from ..steward_executor.machine_defaults import (
steward_executor_machine_configuration_namespace,
)
from ..todo_replan_cadence.machine_defaults import (
todo_replan_cadence_machine_configuration_namespace,
)

from ..explore.machine_defaults import explore_machine_configuration_namespace
from .goal_storage import goal_storage_machine_configuration_namespace
from ..native_chat.machine_defaults import codex_chat_machine_configuration_namespace
from .context_delivery import context_delivery_machine_configuration_namespace

return (
MachineConfigurationRegistry()
.register(context_delivery_machine_configuration_namespace())
.register(explore_machine_configuration_namespace())
.register(codex_chat_machine_configuration_namespace())
.register(goal_storage_machine_configuration_namespace())
.register(manager_runtime_machine_configuration_namespace())
.register(periodic_report_machine_configuration_namespace())
.register(todo_replan_cadence_machine_configuration_namespace())
.register(change_quality_machine_configuration_namespace())
.register(pull_request_review_machine_configuration_namespace())
.register(steward_executor_machine_configuration_namespace())
)
registry = MachineConfigurationRegistry()
for namespace, module, factory in _BUILTIN_NAMESPACES:
registry.register_deferred(namespace, _owner(module, factory))
return registry


def project_goal_with_builtin_machine_configuration(
Expand Down
53 changes: 42 additions & 11 deletions loopx/capabilities/machine_configuration/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,38 +89,69 @@ class MachineConfigurationRegistry:

def __init__(self) -> None:
self._namespaces: dict[str, MachineConfigurationNamespace] = {}
self._deferred: dict[str, Callable[[], MachineConfigurationNamespace]] = {}

def _reserve(self, namespace: str) -> None:
if namespace in self._namespaces or namespace in self._deferred:
raise ValueError(
"machine-configuration namespace is already registered: " + namespace
)

def register(
self, contract: MachineConfigurationNamespace
) -> MachineConfigurationRegistry:
if contract.namespace in self._namespaces:
raise ValueError(
"machine-configuration namespace is already registered: "
+ contract.namespace
)
self._reserve(contract.namespace)
# Validate public catalog metadata before this registry can own effects.
# A bad provider template must fail before preview/apply/rollback runs.
contract.public_descriptor()
self._namespaces[contract.namespace] = contract
return self

def register_deferred(
self, namespace: str, load: Callable[[], MachineConfigurationNamespace]
) -> MachineConfigurationRegistry:
"""Register an owner that is loaded when its namespace is first resolved.

Readers then import only the namespaces they touch. Resolution applies
the same template validation as ``register`` before the owner is
returned, so a bad template still fails before that namespace can own a
preview, apply or rollback effect.
"""
if not _NAMESPACE_RE.fullmatch(namespace):
raise ValueError("machine-configuration namespace is invalid")
self._reserve(namespace)
self._deferred[namespace] = load
return self

def resolve(self, namespace: str) -> MachineConfigurationNamespace:
try:
return self._namespaces[namespace]
except KeyError as exc:
contract = self._namespaces.get(namespace)
if contract is not None:
return contract
load = self._deferred.get(namespace)
if load is None:
raise ValueError(
f"unsupported machine-configuration namespace: {namespace}"
) from exc
)
contract = load()
if contract.namespace != namespace:
raise ValueError(
"machine-configuration namespace owner declares another id: "
+ namespace
)
contract.public_descriptor()
del self._deferred[namespace]
self._namespaces[namespace] = contract
return contract

@property
def namespace_ids(self) -> tuple[str, ...]:
return tuple(sorted(self._namespaces))
return tuple(sorted({*self._namespaces, *self._deferred}))

def public_catalog(self) -> dict[str, Any]:
return {
"schema_version": MACHINE_CONFIGURATION_CATALOG_SCHEMA,
"namespaces": [
self._namespaces[namespace].public_descriptor()
self.resolve(namespace).public_descriptor()
for namespace in self.namespace_ids
],
}
Expand Down
Loading
Loading