1776 lines
74 KiB
Python
Executable File
1776 lines
74 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Profile discovery, composition validation, and safe profile packs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import stat
|
|
import tarfile
|
|
import tempfile
|
|
import uuid
|
|
import zipfile
|
|
from collections.abc import Mapping
|
|
from ipaddress import IPv6Address, ip_address
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from mmo_catalog_data import load_catalog, validate_catalog_data
|
|
from mmo_schema import validate_schema_definition
|
|
from mmo_tool_mcp import (
|
|
load_tool_mcp_registry,
|
|
tool_mcp_environment_names,
|
|
validate_tool_mcp_grants,
|
|
)
|
|
from mmo_util import (
|
|
atomic_write_text,
|
|
config_root,
|
|
copy_tree_static,
|
|
deep_merge,
|
|
install_root,
|
|
package_version,
|
|
read_toml,
|
|
stable_hash,
|
|
strict_json_loads,
|
|
validate_id,
|
|
)
|
|
from mmo_version import MMO_SCHEMA_VERSION, PACKAGE_VERSION
|
|
|
|
ALLOWED_PERMISSIONS = {"read-only", "workspace-write"}
|
|
ALLOWED_TRUST = {"low", "normal", "high", "adversarial_reviewer"}
|
|
ALLOWED_VERIFICATION = {"always", "material_changes", "risk_based", "root_adjudication"}
|
|
ALLOWED_WAIT_POLICIES = {"dependency_only"}
|
|
ALLOWED_RESULT_VISIBILITY = {"ancestors", "session"}
|
|
ALLOWED_CONTRADICTION_POLICIES = {"primary_evidence", "designated_judge", "root_adjudication"}
|
|
ALLOWED_WEB_SEARCH = {"disabled", "cached", "indexed", "live"}
|
|
ALLOWED_APPROVAL_POLICIES = {"untrusted", "on-request", "never"}
|
|
ALLOWED_CONTRACT_ENFORCEMENT = {"warn", "strict"}
|
|
ALLOWED_ORCHESTRATION = {"mcp", "native", "hybrid"}
|
|
ALLOWED_AGENT_BACKENDS = {"mcp", "native"}
|
|
ALLOWED_MODALITIES = {"text", "image", "audio", "video", "file"}
|
|
ALLOWED_REASONING = {"none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"}
|
|
ALLOWED_EXECUTION_MODES = {"turn", "goal"}
|
|
ALLOWED_CONTROL_ACTIONS = {
|
|
"inspect",
|
|
"trace",
|
|
"steer",
|
|
"interrupt",
|
|
"pause",
|
|
"continue",
|
|
"detach",
|
|
"stop",
|
|
"finalize",
|
|
"compact",
|
|
"respond",
|
|
"set_effort",
|
|
"fork",
|
|
}
|
|
LOW_TRUST_TASK_KINDS = {"locate", "references", "extract", "summarize_supplied"}
|
|
ALLOWED_PROFILE_MATURITY = {"featured", "lab"}
|
|
AGENT_MCP_MANAGEMENT_TOOLS = (
|
|
"agent_spawn",
|
|
"agents_spawn",
|
|
"agent_status",
|
|
"agents_wait",
|
|
"agent_result",
|
|
"agent_result_accept",
|
|
"agent_result_reject",
|
|
"agent_patch_integrate",
|
|
"agent_cancel",
|
|
)
|
|
AGENT_MCP_CONTROL_TOOLS = (
|
|
"agent_list",
|
|
"agent_inspect",
|
|
"agent_trace",
|
|
"agent_trace_record",
|
|
"agent_steer",
|
|
"agent_interrupt",
|
|
"agent_pause",
|
|
"agent_continue",
|
|
"agent_detach",
|
|
"agent_stop",
|
|
"agent_finalize",
|
|
"agent_compact",
|
|
"agent_respond",
|
|
"agent_set_effort",
|
|
"agent_fork",
|
|
)
|
|
ALLOWED_PROFILE_FILES = {
|
|
"profile.toml",
|
|
"catalog.toml",
|
|
"smoke.toml",
|
|
"README.md",
|
|
"LICENSE",
|
|
}
|
|
ALLOWED_PROFILE_SUFFIXES = {
|
|
"agents": {".md"},
|
|
"contracts": {".json"},
|
|
}
|
|
MAX_PROFILE_ARCHIVE_BYTES = 32 * 1024 * 1024
|
|
MAX_PROFILE_ARCHIVE_FILES = 256
|
|
MAX_PROFILE_MEMBER_BYTES = 1024 * 1024
|
|
MAX_PROFILE_UNCOMPRESSED_BYTES = 16 * 1024 * 1024
|
|
MAX_ZIP_COMPRESSION_RATIO = 200
|
|
|
|
COORDINATION_DEFAULTS: dict[str, Any] = {
|
|
"mode": "rooted_team",
|
|
"orchestration": "mcp",
|
|
"max_active_agents": 5,
|
|
"max_depth": 1,
|
|
"max_children_per_agent": 4,
|
|
"max_active_writers": 2,
|
|
"reject_ancestor_role": True,
|
|
"wait_policy": "dependency_only",
|
|
"write_conflict_policy": "reject",
|
|
"contradiction_policy": "primary_evidence",
|
|
"result_visibility": "ancestors",
|
|
"default_result_chars": 12000,
|
|
"max_result_chars": 30000,
|
|
"native_max_concurrent_threads": None,
|
|
"native_interrupt_message": True,
|
|
"native_nested_delegation": False,
|
|
}
|
|
|
|
|
|
def agent_mcp_tool_names(
|
|
agents: Mapping[str, Mapping[str, Any]],
|
|
*,
|
|
root_agent: str,
|
|
agent_id: str,
|
|
) -> set[str]:
|
|
"""Return the exact runtime-owned Agent MCP tools exposed to one role."""
|
|
|
|
agent = agents[agent_id]
|
|
has_mcp_children = any(
|
|
"mcp" in agents[child].get("backends", []) for child in agent.get("can_spawn", [])
|
|
)
|
|
controls = agent.get("controls", {})
|
|
granted_actions = {
|
|
action
|
|
for grant in controls.values()
|
|
if isinstance(grant, Mapping)
|
|
for action in grant.get("actions", [])
|
|
}
|
|
tools: set[str] = set(AGENT_MCP_MANAGEMENT_TOOLS if has_mcp_children else ())
|
|
action_tools = {
|
|
"inspect": {"agent_list", "agent_inspect", "agent_status"},
|
|
"trace": {"agent_list", "agent_trace", "agent_trace_record"},
|
|
"steer": {"agent_list", "agent_steer"},
|
|
"interrupt": {"agent_list", "agent_interrupt"},
|
|
"pause": {"agent_list", "agent_pause"},
|
|
"continue": {"agent_list", "agent_continue"},
|
|
"detach": {"agent_list", "agent_detach"},
|
|
"stop": {"agent_list", "agent_stop"},
|
|
"finalize": {"agent_list", "agent_finalize"},
|
|
"compact": {"agent_list", "agent_compact"},
|
|
"respond": {"agent_list", "agent_respond"},
|
|
"set_effort": {"agent_list", "agent_set_effort"},
|
|
"fork": {"agent_list", "agent_fork"},
|
|
}
|
|
for action in granted_actions:
|
|
tools.update(action_tools[action])
|
|
if granted_actions:
|
|
# Controllers may inspect terminal output, but only normal lineage
|
|
# authorities may cancel, accept, reject, or integrate jobs at runtime.
|
|
tools.update({"agent_status", "agents_wait", "agent_result"})
|
|
return tools
|
|
|
|
|
|
def control_targets(agent: Mapping[str, Any]) -> list[str]:
|
|
"""Return stable configured control targets for one resolved agent."""
|
|
|
|
controls = agent.get("controls", {})
|
|
return sorted(controls) if isinstance(controls, Mapping) else []
|
|
|
|
|
|
def control_actions(agent: Mapping[str, Any], target: str) -> frozenset[str]:
|
|
"""Return the exact actions one resolved agent may apply to a target role."""
|
|
|
|
controls = agent.get("controls", {})
|
|
grant = controls.get(target) if isinstance(controls, Mapping) else None
|
|
actions = grant.get("actions", []) if isinstance(grant, Mapping) else []
|
|
return frozenset(str(action) for action in actions)
|
|
|
|
|
|
def native_agent_ids(resolved: Mapping[str, Any]) -> list[str]:
|
|
return [
|
|
key
|
|
for key, agent in resolved["agents"].items()
|
|
if agent["kind"] != "root" and "native" in agent.get("backends", [])
|
|
]
|
|
|
|
|
|
def native_children(resolved: Mapping[str, Any], agent_id: str) -> list[str]:
|
|
return [
|
|
child
|
|
for child in resolved["agents"][agent_id]["can_spawn"]
|
|
if "native" in resolved["agents"][child].get("backends", [])
|
|
]
|
|
|
|
|
|
def mcp_children(resolved: Mapping[str, Any], agent_id: str) -> list[str]:
|
|
return [
|
|
child
|
|
for child in resolved["agents"][agent_id]["can_spawn"]
|
|
if "mcp" in resolved["agents"][child].get("backends", [])
|
|
]
|
|
|
|
|
|
def reachable_native_agent_ids(resolved: Mapping[str, Any], start_agent: str) -> list[str]:
|
|
"""Return native descendants reachable from an agent in stable BFS order."""
|
|
|
|
result: list[str] = []
|
|
seen = {start_agent}
|
|
pending = [start_agent]
|
|
while pending:
|
|
current = pending.pop(0)
|
|
for child in native_children(resolved, current):
|
|
if child in seen:
|
|
continue
|
|
seen.add(child)
|
|
result.append(child)
|
|
pending.append(child)
|
|
return result
|
|
|
|
|
|
AGENT_DEFAULTS: dict[str, Any] = {
|
|
"kind": "participant",
|
|
"description": "",
|
|
"reasoning": "high",
|
|
"plan_reasoning": None,
|
|
"permissions": "read-only",
|
|
"can_spawn": [],
|
|
"controls": {},
|
|
"max_active": 1,
|
|
"max_children": None,
|
|
"write_scope_required": True,
|
|
"trust": "normal",
|
|
"verification": "material_changes",
|
|
"allowed_task_kinds": ["analysis", "other"],
|
|
"max_task_chars": 12000,
|
|
"min_task_chars": 12,
|
|
"execution_mode": "turn",
|
|
"goal_token_budget": None,
|
|
"max_goal_token_budget": None,
|
|
"stall_warning_seconds": 1800,
|
|
"finalization_grace_seconds": 900,
|
|
"allowed_reasoning_efforts": None,
|
|
"requires_modalities": ["text"],
|
|
"requires_output_modalities": ["text"],
|
|
"requires_tool_images": False,
|
|
"requires_documents": False,
|
|
"attachments_allowed": False,
|
|
"network_access": False,
|
|
"web_search": "disabled",
|
|
"output_contract": None,
|
|
"contract_enforcement": "warn",
|
|
"resource_group": None,
|
|
"resource_units": 1,
|
|
"instructions": None,
|
|
"approval_policy": "never",
|
|
"backends": None,
|
|
"native_name": None,
|
|
"tool_mcp_servers": {},
|
|
}
|
|
|
|
SETTINGS_FIELDS = {
|
|
"schema_version",
|
|
"default_profile",
|
|
"base_codex_home",
|
|
"auth_link_mode",
|
|
"gateway_host",
|
|
"gateway_port_min",
|
|
"gateway_port_max",
|
|
"gateway_start_timeout_seconds",
|
|
"gateway_idle_timeout_seconds",
|
|
"job_retention_days",
|
|
"session_retention_days",
|
|
"codex_bin",
|
|
"switchyard_bin",
|
|
}
|
|
PROFILE_FIELDS = {
|
|
"schema_version",
|
|
"id",
|
|
"version",
|
|
"display_name",
|
|
"description",
|
|
"tags",
|
|
"maturity",
|
|
"root",
|
|
"catalog",
|
|
"smoke",
|
|
"coordination",
|
|
"agents",
|
|
}
|
|
SMOKE_FIELDS = {"schema_version", "tasks"}
|
|
SMOKE_TASK_FIELDS = {
|
|
"agent",
|
|
"task",
|
|
"task_kind",
|
|
"literal_task",
|
|
"mode",
|
|
"backend",
|
|
"wall_timeout_seconds",
|
|
"wait_seconds",
|
|
"write_scope",
|
|
"attachments",
|
|
"required_mcp_tools",
|
|
}
|
|
|
|
|
|
def _reject_unknown_fields(value: Mapping[str, Any], allowed: set[str], label: str) -> None:
|
|
unknown = sorted(set(value) - allowed)
|
|
if unknown:
|
|
raise ValueError(f"{label} has unknown fields: {', '.join(unknown)}")
|
|
|
|
|
|
def _schema_version(value: Any, expected: int, label: str) -> int:
|
|
if not isinstance(value, int) or isinstance(value, bool) or value != expected:
|
|
raise ValueError(f"unsupported {label} schema_version")
|
|
return value
|
|
|
|
|
|
def _nonempty_string(value: Any, label: str) -> str:
|
|
if not isinstance(value, str) or not value.strip():
|
|
raise ValueError(f"{label} must be a non-empty string")
|
|
return value
|
|
|
|
|
|
def _optional_nonempty_string(value: Any, label: str) -> str | None:
|
|
if value is None:
|
|
return None
|
|
return _nonempty_string(value, label)
|
|
|
|
|
|
def _enum_string(value: Any, allowed: set[str], label: str) -> str:
|
|
if not isinstance(value, str) or value not in allowed:
|
|
raise ValueError(f"{label} must be one of {sorted(allowed)}")
|
|
return value
|
|
|
|
|
|
def _required_id(value: Any, label: str) -> str:
|
|
return validate_id(_nonempty_string(value, label), label)
|
|
|
|
|
|
def builtin_profiles_root() -> Path:
|
|
return install_root() / "profiles"
|
|
|
|
|
|
def user_profiles_root() -> Path:
|
|
return config_root() / "profiles.d"
|
|
|
|
|
|
def load_settings() -> dict[str, Any]:
|
|
defaults_path = install_root() / "config" / "settings.toml"
|
|
settings = read_toml(defaults_path)
|
|
user_path = config_root() / "settings.toml"
|
|
if user_path.is_file():
|
|
settings = deep_merge(settings, read_toml(user_path))
|
|
_reject_unknown_fields(settings, SETTINGS_FIELDS, "settings")
|
|
_schema_version(settings.get("schema_version"), MMO_SCHEMA_VERSION, "settings")
|
|
for field in (
|
|
"default_profile",
|
|
"base_codex_home",
|
|
"codex_bin",
|
|
"switchyard_bin",
|
|
):
|
|
settings[field] = _nonempty_string(settings.get(field), f"settings.{field}")
|
|
validate_id(settings["default_profile"], "settings.default_profile")
|
|
try:
|
|
base_codex_home = Path(settings["base_codex_home"]).expanduser()
|
|
except RuntimeError as exc:
|
|
raise ValueError("settings.base_codex_home has an unknown home-directory user") from exc
|
|
if not base_codex_home.is_absolute():
|
|
raise ValueError("settings.base_codex_home must expand to an absolute path")
|
|
settings["base_codex_home"] = str(base_codex_home.resolve())
|
|
gateway_host = settings.get("gateway_host")
|
|
if not isinstance(gateway_host, str) or not gateway_host:
|
|
raise ValueError("settings.gateway_host must be a non-empty string")
|
|
try:
|
|
parsed_gateway_host = ip_address(gateway_host)
|
|
except ValueError as exc:
|
|
raise ValueError(
|
|
"settings.gateway_host must be an unbracketed IPv4 or IPv6 literal"
|
|
) from exc
|
|
if isinstance(parsed_gateway_host, IPv6Address) and parsed_gateway_host.scope_id is not None:
|
|
raise ValueError("settings.gateway_host must not contain an IPv6 scope identifier")
|
|
if not parsed_gateway_host.is_loopback:
|
|
raise ValueError("settings.gateway_host must be a loopback address")
|
|
settings["gateway_host"] = str(parsed_gateway_host)
|
|
_enum_string(
|
|
settings.get("auth_link_mode"),
|
|
{"shared", "copy", "none"},
|
|
"settings.auth_link_mode",
|
|
)
|
|
for field, minimum, maximum in (
|
|
("gateway_port_min", 1024, 65535),
|
|
("gateway_port_max", 1024, 65535),
|
|
("gateway_start_timeout_seconds", 1, 3600),
|
|
("gateway_idle_timeout_seconds", 0, 31_536_000),
|
|
("job_retention_days", 0, 36_500),
|
|
("session_retention_days", 0, 36_500),
|
|
):
|
|
settings[field] = _positive_int(settings.get(field), f"settings.{field}", minimum, maximum)
|
|
if settings["gateway_port_min"] > settings["gateway_port_max"]:
|
|
raise ValueError("settings.gateway_port_min exceeds gateway_port_max")
|
|
return settings
|
|
|
|
|
|
def builtin_auth_link_mode(
|
|
provider: Mapping[str, Any], settings: Mapping[str, Any] | None = None
|
|
) -> str:
|
|
"""Resolve how a generated Codex home receives file-backed built-in auth.
|
|
|
|
A provider may explicitly override the operator default. Bundled providers
|
|
intentionally omit the field so ``settings.toml`` remains effective.
|
|
"""
|
|
|
|
effective_settings = settings if settings is not None else load_settings()
|
|
value = provider.get("auth_link_mode")
|
|
if value is None:
|
|
value = effective_settings.get("auth_link_mode", "shared")
|
|
return _enum_string(value, {"shared", "copy", "none"}, "auth_link_mode")
|
|
|
|
|
|
def discover_profiles() -> dict[str, dict[str, Any]]:
|
|
results: dict[str, dict[str, Any]] = {}
|
|
for source, root in (("builtin", builtin_profiles_root()), ("user", user_profiles_root())):
|
|
if not root.is_dir():
|
|
continue
|
|
for directory in sorted(path for path in root.iterdir() if path.is_dir()):
|
|
manifest = directory / "profile.toml"
|
|
if not manifest.is_file():
|
|
continue
|
|
try:
|
|
data = read_toml(manifest)
|
|
profile_id = _required_id(data.get("id"), "profile id")
|
|
except (OSError, ValueError):
|
|
continue
|
|
# User profiles intentionally override a bundled profile with the same id.
|
|
results[profile_id] = {
|
|
"id": profile_id,
|
|
"source": source,
|
|
"path": str(directory),
|
|
"display_name": data.get("display_name", profile_id),
|
|
"description": data.get("description", ""),
|
|
"version": data.get("version", "0"),
|
|
"maturity": data.get("maturity"),
|
|
"root": data.get("root"),
|
|
"tags": data.get("tags", []),
|
|
}
|
|
return results
|
|
|
|
|
|
def resolve_profile_dir(value: str | Path) -> tuple[Path, str]:
|
|
candidate = Path(value).expanduser()
|
|
if candidate.exists():
|
|
directory = candidate.resolve()
|
|
if directory.is_file():
|
|
directory = directory.parent
|
|
if not (directory / "profile.toml").is_file():
|
|
raise ValueError(f"profile.toml not found in {directory}")
|
|
return directory, "path"
|
|
profile_id = validate_id(str(value), "profile id")
|
|
profiles = discover_profiles()
|
|
if profile_id not in profiles:
|
|
raise FileNotFoundError(f"unknown profile {profile_id!r}")
|
|
item = profiles[profile_id]
|
|
return Path(item["path"]), item["source"]
|
|
|
|
|
|
def validate_profile_pack_tree(directory: Path) -> list[str]:
|
|
errors: list[str] = []
|
|
if not directory.is_dir():
|
|
return [f"profile pack is not a directory: {directory}"]
|
|
for path in sorted(directory.rglob("*")):
|
|
relative = path.relative_to(directory)
|
|
if path.is_symlink():
|
|
errors.append(f"symbolic links are not allowed: {relative}")
|
|
continue
|
|
if path.is_dir():
|
|
if relative.parts[0] not in {"agents", "contracts"}:
|
|
errors.append(f"unknown profile directory: {relative}")
|
|
continue
|
|
if not path.is_file():
|
|
errors.append(f"special files are not allowed: {relative}")
|
|
continue
|
|
if len(relative.parts) == 1:
|
|
if relative.name not in ALLOWED_PROFILE_FILES:
|
|
errors.append(f"unknown top-level profile file: {relative}")
|
|
elif len(relative.parts) == 2:
|
|
parent, name = relative.parts
|
|
suffixes = ALLOWED_PROFILE_SUFFIXES.get(parent)
|
|
if suffixes is None or Path(name).suffix not in suffixes:
|
|
errors.append(f"unsupported profile file: {relative}")
|
|
else:
|
|
errors.append(f"nested profile paths are not allowed: {relative}")
|
|
if path.stat().st_size > 1024 * 1024:
|
|
errors.append(f"profile file exceeds 1 MiB: {relative}")
|
|
if not (directory / "profile.toml").is_file():
|
|
errors.append("profile.toml is required")
|
|
return errors
|
|
|
|
|
|
def _profile_pack_content_hash(directory: Path) -> str:
|
|
"""Hash every validated pack-owned file by relative name and exact bytes."""
|
|
|
|
digest = hashlib.sha256(b"codex-mmo-profile-pack-v1\0")
|
|
for path in sorted(item for item in directory.rglob("*") if item.is_file()):
|
|
relative = path.relative_to(directory).as_posix().encode("utf-8")
|
|
payload = path.read_bytes()
|
|
digest.update(len(relative).to_bytes(8, "big"))
|
|
digest.update(relative)
|
|
digest.update(len(payload).to_bytes(8, "big"))
|
|
digest.update(payload)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def _string_list(value: Any, label: str, *, allow_empty: bool = True) -> list[str]:
|
|
if value is None and allow_empty:
|
|
return []
|
|
if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
|
|
raise ValueError(f"{label} must be an array of strings")
|
|
if any(not item.strip() for item in value):
|
|
raise ValueError(f"{label} cannot contain empty strings")
|
|
if not allow_empty and not value:
|
|
raise ValueError(f"{label} cannot be empty")
|
|
if len(value) != len(set(value)):
|
|
raise ValueError(f"{label} cannot contain duplicates")
|
|
return list(value)
|
|
|
|
|
|
def _positive_int(value: Any, label: str, minimum: int = 1, maximum: int = 1_000_000) -> int:
|
|
if not isinstance(value, int) or isinstance(value, bool) or not minimum <= value <= maximum:
|
|
raise ValueError(f"{label} must be an integer between {minimum} and {maximum}")
|
|
return value
|
|
|
|
|
|
def _validate_controls(value: Any, label: str) -> dict[str, dict[str, list[str]]]:
|
|
if not isinstance(value, Mapping):
|
|
raise ValueError(f"{label} must be a table keyed by target agent")
|
|
result: dict[str, dict[str, list[str]]] = {}
|
|
for target, raw_grant in value.items():
|
|
validate_id(target, f"{label} target")
|
|
if not isinstance(raw_grant, Mapping):
|
|
raise ValueError(f"{label}.{target} must be a table")
|
|
_reject_unknown_fields(raw_grant, {"actions"}, f"{label}.{target}")
|
|
actions = _string_list(
|
|
raw_grant.get("actions"),
|
|
f"{label}.{target}.actions",
|
|
allow_empty=False,
|
|
)
|
|
unknown = sorted(set(actions) - ALLOWED_CONTROL_ACTIONS)
|
|
if unknown:
|
|
raise ValueError(f"{label}.{target}.actions contains unsupported actions: {unknown}")
|
|
result[target] = {"actions": actions}
|
|
return result
|
|
|
|
|
|
_HEADER_NAME = re.compile(r"[!#$%&'*+.^_`|~0-9A-Za-z-]+")
|
|
_ENVIRONMENT_NAME = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
|
|
|
|
def _validate_coordination(profile: Mapping[str, Any]) -> dict[str, Any]:
|
|
raw_coordination = profile.get("coordination", {})
|
|
if not isinstance(raw_coordination, Mapping):
|
|
raise ValueError("profile.coordination must be a table")
|
|
_reject_unknown_fields(raw_coordination, set(COORDINATION_DEFAULTS), "profile.coordination")
|
|
coordination = deep_merge(COORDINATION_DEFAULTS, raw_coordination)
|
|
coordination["orchestration"] = _enum_string(
|
|
coordination["orchestration"],
|
|
ALLOWED_ORCHESTRATION,
|
|
"coordination.orchestration",
|
|
)
|
|
for field, minimum, maximum in (
|
|
("max_active_agents", 1, 64),
|
|
("max_depth", 0, 8),
|
|
("max_children_per_agent", 1, 64),
|
|
("max_active_writers", 0, 32),
|
|
("default_result_chars", 500, 100_000),
|
|
("max_result_chars", 500, 500_000),
|
|
):
|
|
coordination[field] = _positive_int(
|
|
coordination[field], f"coordination.{field}", minimum, maximum
|
|
)
|
|
native_limit = coordination.get("native_max_concurrent_threads")
|
|
if native_limit is None:
|
|
native_limit = max(1, coordination["max_active_agents"] - 1)
|
|
coordination["native_max_concurrent_threads"] = _positive_int(
|
|
native_limit,
|
|
"coordination.native_max_concurrent_threads",
|
|
1,
|
|
64,
|
|
)
|
|
if not isinstance(coordination.get("native_interrupt_message"), bool):
|
|
raise ValueError("coordination.native_interrupt_message must be boolean")
|
|
if not isinstance(coordination.get("native_nested_delegation"), bool):
|
|
raise ValueError("coordination.native_nested_delegation must be boolean")
|
|
if not isinstance(coordination.get("reject_ancestor_role"), bool):
|
|
raise ValueError("coordination.reject_ancestor_role must be boolean")
|
|
if not isinstance(coordination.get("mode"), str) or not coordination["mode"]:
|
|
raise ValueError("coordination.mode must be a non-empty string")
|
|
if coordination["default_result_chars"] > coordination["max_result_chars"]:
|
|
raise ValueError("coordination.default_result_chars exceeds max_result_chars")
|
|
coordination["wait_policy"] = _enum_string(
|
|
coordination["wait_policy"], ALLOWED_WAIT_POLICIES, "coordination.wait_policy"
|
|
)
|
|
coordination["result_visibility"] = _enum_string(
|
|
coordination["result_visibility"],
|
|
ALLOWED_RESULT_VISIBILITY,
|
|
"coordination.result_visibility",
|
|
)
|
|
coordination["contradiction_policy"] = _enum_string(
|
|
coordination["contradiction_policy"],
|
|
ALLOWED_CONTRADICTION_POLICIES,
|
|
"coordination.contradiction_policy",
|
|
)
|
|
if (
|
|
not isinstance(coordination["write_conflict_policy"], str)
|
|
or coordination["write_conflict_policy"] != "reject"
|
|
):
|
|
raise ValueError("only write_conflict_policy='reject' is supported")
|
|
return coordination
|
|
|
|
|
|
def _default_agent_backends(orchestration: str) -> list[str]:
|
|
if orchestration == "mcp":
|
|
return ["mcp"]
|
|
if orchestration == "native":
|
|
return ["native"]
|
|
return ["mcp", "native"]
|
|
|
|
|
|
def _load_relative_text(
|
|
profile_dir: Path,
|
|
relative: str,
|
|
label: str,
|
|
*,
|
|
parent: str,
|
|
suffix: str,
|
|
) -> str:
|
|
relative_path = Path(relative)
|
|
if (
|
|
relative_path.is_absolute()
|
|
or len(relative_path.parts) != 2
|
|
or relative_path.parts[0] != parent
|
|
or relative_path.suffix != suffix
|
|
):
|
|
raise ValueError(f"{label} must be a relative {parent}/*{suffix} path")
|
|
path = (profile_dir / relative).resolve()
|
|
if profile_dir not in path.parents:
|
|
raise ValueError(f"{label} path escapes the profile pack: {relative}")
|
|
if not path.is_file():
|
|
raise ValueError(f"{label} file is missing: {relative}")
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def _validate_agent(
|
|
key: str,
|
|
agent: Mapping[str, Any],
|
|
*,
|
|
profile_dir: Path,
|
|
models: Mapping[str, Any],
|
|
routes: Mapping[str, Any],
|
|
resources: Mapping[str, Any],
|
|
tool_mcp_registry: Mapping[str, Mapping[str, Any]],
|
|
binding: str | None,
|
|
orchestration: str,
|
|
) -> dict[str, Any]:
|
|
validate_id(key, "agent id")
|
|
_reject_unknown_fields(
|
|
agent,
|
|
set(AGENT_DEFAULTS) | {"model"},
|
|
f"agent {key}",
|
|
)
|
|
result = deep_merge(AGENT_DEFAULTS, agent)
|
|
if binding:
|
|
result["model"] = binding
|
|
model_key = result.get("model")
|
|
if not isinstance(model_key, str) or model_key not in models:
|
|
raise ValueError(f"agent {key}: unknown model {model_key!r}")
|
|
model = models[model_key]
|
|
route = routes[model["route"]]
|
|
if not model.get("agent_compatible", False):
|
|
raise ValueError(
|
|
f"agent {key}: model {model_key!r} is catalogued but is not Codex-agent compatible"
|
|
)
|
|
result["model"] = model_key
|
|
result["route"] = model["route"]
|
|
result["driver"] = route["driver"]
|
|
result["kind"] = _enum_string(result["kind"], {"root", "participant"}, f"agent {key}.kind")
|
|
if not isinstance(result["description"], str):
|
|
raise ValueError(f"agent {key}.description must be a string")
|
|
raw_backends = result.get("backends")
|
|
if result["kind"] == "root":
|
|
if raw_backends not in (None, []):
|
|
raise ValueError(f"agent {key}: root agents cannot declare execution backends")
|
|
result["backends"] = []
|
|
else:
|
|
result["backends"] = (
|
|
_default_agent_backends(orchestration)
|
|
if raw_backends is None
|
|
else _string_list(raw_backends, f"agent {key}.backends", allow_empty=False)
|
|
)
|
|
if len(result["backends"]) != len(set(result["backends"])):
|
|
raise ValueError(f"agent {key}.backends contains duplicates")
|
|
unknown_backends = sorted(set(result["backends"]) - ALLOWED_AGENT_BACKENDS)
|
|
if unknown_backends:
|
|
raise ValueError(f"agent {key}: unsupported backends {unknown_backends}")
|
|
allowed_for_mode = {
|
|
"mcp": {"mcp"},
|
|
"native": {"native"},
|
|
"hybrid": {"mcp", "native"},
|
|
}[orchestration]
|
|
invalid_backends = sorted(set(result["backends"]) - allowed_for_mode)
|
|
if invalid_backends:
|
|
raise ValueError(
|
|
f"agent {key}: backends {invalid_backends} are incompatible with "
|
|
f"coordination.orchestration={orchestration!r}"
|
|
)
|
|
configured_native_name = result.get("native_name")
|
|
native_name = (
|
|
key
|
|
if configured_native_name is None
|
|
else _nonempty_string(configured_native_name, f"agent {key}.native_name")
|
|
)
|
|
result["native_name"] = validate_id(native_name, f"agent {key} native_name")
|
|
result["tool_mcp_servers"] = validate_tool_mcp_grants(
|
|
result.get("tool_mcp_servers"),
|
|
tool_mcp_registry,
|
|
label=f"agent {key}.tool_mcp_servers",
|
|
)
|
|
for field, allowed in (
|
|
("permissions", ALLOWED_PERMISSIONS),
|
|
("trust", ALLOWED_TRUST),
|
|
("verification", ALLOWED_VERIFICATION),
|
|
("web_search", ALLOWED_WEB_SEARCH),
|
|
("contract_enforcement", ALLOWED_CONTRACT_ENFORCEMENT),
|
|
):
|
|
result[field] = _enum_string(result[field], allowed, f"agent {key}.{field}")
|
|
result["can_spawn"] = _string_list(result["can_spawn"], f"agent {key}.can_spawn")
|
|
result["controls"] = _validate_controls(result["controls"], f"agent {key}.controls")
|
|
result["allowed_task_kinds"] = _string_list(
|
|
result["allowed_task_kinds"],
|
|
f"agent {key}.allowed_task_kinds",
|
|
allow_empty=result["kind"] == "root",
|
|
)
|
|
for task_kind in result["allowed_task_kinds"]:
|
|
validate_id(task_kind, f"agent {key} task kind")
|
|
result["requires_modalities"] = _string_list(
|
|
result["requires_modalities"], f"agent {key}.requires_modalities", allow_empty=False
|
|
)
|
|
result["requires_output_modalities"] = _string_list(
|
|
result["requires_output_modalities"],
|
|
f"agent {key}.requires_output_modalities",
|
|
allow_empty=False,
|
|
)
|
|
for boolean_field in (
|
|
"write_scope_required",
|
|
"requires_tool_images",
|
|
"requires_documents",
|
|
"attachments_allowed",
|
|
"network_access",
|
|
):
|
|
if not isinstance(result[boolean_field], bool):
|
|
raise ValueError(f"agent {key}.{boolean_field} must be boolean")
|
|
result["approval_policy"] = _enum_string(
|
|
result.get("approval_policy"),
|
|
ALLOWED_APPROVAL_POLICIES,
|
|
f"agent {key}.approval_policy",
|
|
)
|
|
if not isinstance(result.get("reasoning"), str):
|
|
raise ValueError(f"agent {key}.reasoning must be a string")
|
|
missing_model = sorted(set(result["requires_modalities"]) - set(model["modalities"]))
|
|
missing_transport = sorted(
|
|
set(result["requires_modalities"]) - set(route["transport_modalities"])
|
|
)
|
|
missing_model_output = sorted(
|
|
set(result["requires_output_modalities"]) - set(model["output_modalities"])
|
|
)
|
|
missing_transport_output = sorted(
|
|
set(result["requires_output_modalities"]) - set(route["transport_output_modalities"])
|
|
)
|
|
if missing_model:
|
|
raise ValueError(f"agent {key}: model lacks required modalities {missing_model}")
|
|
if missing_transport:
|
|
raise ValueError(
|
|
f"agent {key}: route transport loses required modalities {missing_transport}"
|
|
)
|
|
if missing_model_output:
|
|
raise ValueError(
|
|
f"agent {key}: model lacks required output modalities {missing_model_output}"
|
|
)
|
|
if missing_transport_output:
|
|
raise ValueError(
|
|
f"agent {key}: route transport loses required output modalities "
|
|
f"{missing_transport_output}"
|
|
)
|
|
if result["requires_tool_images"]:
|
|
if not model.get("supports_tool_images", False):
|
|
raise ValueError(f"agent {key}: model cannot consume image-bearing tool results")
|
|
if not route.get("preserves_tool_media", False) or "image" not in route.get(
|
|
"tool_result_modalities", []
|
|
):
|
|
raise ValueError(f"agent {key}: route does not preserve image-bearing tool results")
|
|
if result["requires_documents"]:
|
|
if not model.get("supports_documents", False):
|
|
raise ValueError(f"agent {key}: model does not support document/file input")
|
|
if not route.get("supports_documents", False):
|
|
raise ValueError(f"agent {key}: route does not preserve documents")
|
|
if not model.get("tool_calling", False):
|
|
raise ValueError(f"agent {key}: model must support tool calling for Codex execution")
|
|
if result["reasoning"] == "auto":
|
|
result["reasoning"] = model["default_reasoning"]
|
|
if result["reasoning"] not in model["reasoning_levels"]:
|
|
raise ValueError(
|
|
f"agent {key}: reasoning {result['reasoning']!r} is not supported by model {model_key}"
|
|
)
|
|
configured_efforts = result.get("allowed_reasoning_efforts")
|
|
if configured_efforts is None:
|
|
configured_efforts = [result["reasoning"]]
|
|
result["allowed_reasoning_efforts"] = _string_list(
|
|
configured_efforts,
|
|
f"agent {key}.allowed_reasoning_efforts",
|
|
allow_empty=False,
|
|
)
|
|
if len(result["allowed_reasoning_efforts"]) != len(set(result["allowed_reasoning_efforts"])):
|
|
raise ValueError(f"agent {key}.allowed_reasoning_efforts contains duplicates")
|
|
unsupported_efforts = sorted(
|
|
set(result["allowed_reasoning_efforts"]) - set(model["reasoning_levels"])
|
|
)
|
|
if unsupported_efforts:
|
|
raise ValueError(
|
|
f"agent {key}: unsupported allowed reasoning efforts for {model_key}: "
|
|
+ ", ".join(unsupported_efforts)
|
|
)
|
|
if result["reasoning"] not in result["allowed_reasoning_efforts"]:
|
|
raise ValueError(
|
|
f"agent {key}: initial reasoning must be present in allowed_reasoning_efforts"
|
|
)
|
|
if result.get("plan_reasoning") == "auto":
|
|
result["plan_reasoning"] = model["default_reasoning"]
|
|
if result.get("plan_reasoning") is not None and not isinstance(result["plan_reasoning"], str):
|
|
raise ValueError(f"agent {key}: plan_reasoning must be a string")
|
|
if (
|
|
result.get("plan_reasoning") is not None
|
|
and result["plan_reasoning"] not in model["reasoning_levels"]
|
|
):
|
|
raise ValueError(
|
|
f"agent {key}: plan_reasoning {result['plan_reasoning']!r} is not supported by model {model_key}"
|
|
)
|
|
result["max_active"] = _positive_int(result["max_active"], f"agent {key}.max_active", 1, 64)
|
|
result["resource_units"] = _positive_int(
|
|
result["resource_units"], f"agent {key}.resource_units", 1, 64
|
|
)
|
|
for field, minimum, maximum in (
|
|
("max_task_chars", 64, 100_000),
|
|
("min_task_chars", 1, 10_000),
|
|
("finalization_grace_seconds", 30, 3600),
|
|
("stall_warning_seconds", 60, 86_400),
|
|
):
|
|
result[field] = _positive_int(result[field], f"agent {key}.{field}", minimum, maximum)
|
|
if result["min_task_chars"] > result["max_task_chars"]:
|
|
raise ValueError(f"agent {key}: min_task_chars exceeds max_task_chars")
|
|
result["execution_mode"] = _enum_string(
|
|
result["execution_mode"],
|
|
ALLOWED_EXECUTION_MODES,
|
|
f"agent {key}.execution_mode",
|
|
)
|
|
if result["execution_mode"] == "goal":
|
|
if "native" in result["backends"]:
|
|
raise ValueError(
|
|
f"agent {key}: native participants must use turn execution; "
|
|
"Codex native delegation does not expose a mechanically owned goal lifecycle"
|
|
)
|
|
result["goal_token_budget"] = _positive_int(
|
|
result.get("goal_token_budget"),
|
|
f"agent {key}.goal_token_budget",
|
|
10_000,
|
|
100_000_000,
|
|
)
|
|
result["max_goal_token_budget"] = _positive_int(
|
|
result.get("max_goal_token_budget"),
|
|
f"agent {key}.max_goal_token_budget",
|
|
10_000,
|
|
100_000_000,
|
|
)
|
|
if result["goal_token_budget"] > result["max_goal_token_budget"]:
|
|
raise ValueError(f"agent {key}: goal_token_budget exceeds max_goal_token_budget")
|
|
else:
|
|
if (
|
|
result.get("goal_token_budget") is not None
|
|
or result.get("max_goal_token_budget") is not None
|
|
):
|
|
raise ValueError(f"agent {key}: turn execution cannot declare goal token budgets")
|
|
if result.get("max_children") is not None:
|
|
result["max_children"] = _positive_int(
|
|
result["max_children"], f"agent {key}.max_children", 1, 64
|
|
)
|
|
configured_resource = _optional_nonempty_string(
|
|
result.get("resource_group"), f"agent {key}.resource_group"
|
|
)
|
|
resource_key = configured_resource or model.get("resource_group")
|
|
if resource_key is not None and resource_key not in resources:
|
|
raise ValueError(f"agent {key}: unknown resource group {resource_key!r}")
|
|
result["resource_group"] = resource_key
|
|
instructions_path = result.get("instructions")
|
|
if instructions_path is not None:
|
|
instructions_path = _nonempty_string(instructions_path, f"agent {key}.instructions")
|
|
result["instructions_text"] = (
|
|
_load_relative_text(
|
|
profile_dir,
|
|
instructions_path,
|
|
f"agent {key} instructions",
|
|
parent="agents",
|
|
suffix=".md",
|
|
)
|
|
if instructions_path
|
|
else ""
|
|
)
|
|
contract_path = result.get("output_contract")
|
|
if contract_path is not None:
|
|
contract_path = _nonempty_string(contract_path, f"agent {key}.output_contract")
|
|
if contract_path:
|
|
contract_text = _load_relative_text(
|
|
profile_dir,
|
|
contract_path,
|
|
f"agent {key} contract",
|
|
parent="contracts",
|
|
suffix=".json",
|
|
)
|
|
try:
|
|
contract = strict_json_loads(contract_text)
|
|
except json.JSONDecodeError as exc:
|
|
raise ValueError(f"agent {key}: invalid JSON output contract: {exc}") from exc
|
|
schema_errors = validate_schema_definition(contract)
|
|
if schema_errors:
|
|
raise ValueError(f"agent {key}: invalid output contract: {'; '.join(schema_errors)}")
|
|
result["output_contract_schema"] = contract
|
|
else:
|
|
result["output_contract_schema"] = None
|
|
if result["contract_enforcement"] == "strict" and result["output_contract_schema"] is None:
|
|
raise ValueError(f"agent {key}: strict contract enforcement requires output_contract")
|
|
if result["contract_enforcement"] == "strict" and result["backends"] == ["native"]:
|
|
raise ValueError(
|
|
f"agent {key}: native-only agents cannot claim strict contract enforcement"
|
|
)
|
|
if result["trust"] == "low":
|
|
# Low-trust roles are mechanically contained. These are runtime
|
|
# invariants, not advisory prompt text.
|
|
if result["kind"] == "root":
|
|
raise ValueError(f"agent {key}: low-trust agents cannot be profile roots")
|
|
if result["backends"] != ["mcp"]:
|
|
raise ValueError(
|
|
f"agent {key}: low-trust agents must use only the supervised MCP backend"
|
|
)
|
|
if result["permissions"] != "read-only":
|
|
raise ValueError(f"agent {key}: low-trust agents must be read-only")
|
|
if result["verification"] != "always":
|
|
raise ValueError(f"agent {key}: low-trust agents require verification='always'")
|
|
if result["output_contract_schema"] is None:
|
|
raise ValueError(f"agent {key}: low-trust agents require an output contract")
|
|
if result["contract_enforcement"] != "strict":
|
|
raise ValueError(f"agent {key}: low-trust agents require strict contract enforcement")
|
|
if result["can_spawn"]:
|
|
raise ValueError(f"agent {key}: low-trust agents cannot spawn descendants")
|
|
if result["controls"]:
|
|
raise ValueError(f"agent {key}: low-trust agents cannot control other workers")
|
|
if result["max_active"] != 1:
|
|
raise ValueError(f"agent {key}: low-trust agents require max_active=1")
|
|
if result["network_access"] or result["web_search"] != "disabled":
|
|
raise ValueError(f"agent {key}: low-trust agents cannot use network or web search")
|
|
if result["attachments_allowed"]:
|
|
raise ValueError(f"agent {key}: low-trust agents cannot receive attachments")
|
|
unknown_low_trust_tasks = sorted(set(result["allowed_task_kinds"]) - LOW_TRUST_TASK_KINDS)
|
|
if unknown_low_trust_tasks:
|
|
raise ValueError(
|
|
f"agent {key}: low-trust task kinds are limited to literal evidence work; "
|
|
f"invalid values: {unknown_low_trust_tasks}"
|
|
)
|
|
if result["max_task_chars"] > 2500:
|
|
raise ValueError(f"agent {key}: low-trust task briefs are limited to 2500 characters")
|
|
if resource_key is None:
|
|
raise ValueError(f"agent {key}: low-trust agents require a single-slot resource group")
|
|
if resources[resource_key]["max_active"] != 1:
|
|
raise ValueError(
|
|
f"agent {key}: low-trust resource group {resource_key!r} must have max_active=1"
|
|
)
|
|
if result["resource_units"] != 1:
|
|
raise ValueError(f"agent {key}: low-trust agents require resource_units=1")
|
|
result["model_config"] = model
|
|
result["route_config"] = route
|
|
return result
|
|
|
|
|
|
def derive_coordination_capacities(
|
|
agents: Mapping[str, Mapping[str, Any]],
|
|
resources: Mapping[str, Mapping[str, Any]],
|
|
*,
|
|
root_agent: str,
|
|
max_depth: int,
|
|
) -> dict[str, Any]:
|
|
"""Derive reachable active, writer, and weighted-resource ceilings."""
|
|
|
|
reachable = {root_agent}
|
|
frontier = {root_agent}
|
|
for _depth in range(max_depth):
|
|
frontier = {
|
|
child
|
|
for parent in frontier
|
|
for child in agents[parent]["can_spawn"]
|
|
if child not in reachable
|
|
}
|
|
reachable.update(frontier)
|
|
reachable_workers = sorted(reachable - {root_agent})
|
|
active_slots = {key: int(agents[key]["max_active"]) for key in reachable_workers}
|
|
grouped: dict[str, list[str]] = {}
|
|
ungrouped = 0
|
|
for key, slots in active_slots.items():
|
|
resource_key = agents[key].get("resource_group")
|
|
if resource_key is None:
|
|
ungrouped += slots
|
|
continue
|
|
lock_key = str(resources[str(resource_key)]["lock_key"])
|
|
grouped.setdefault(lock_key, []).append(key)
|
|
|
|
resource_feasible_workers = ungrouped
|
|
root_resource = agents[root_agent].get("resource_group")
|
|
root_lock = (
|
|
str(resources[str(root_resource)]["lock_key"]) if root_resource is not None else None
|
|
)
|
|
for lock_key, keys in grouped.items():
|
|
resource = resources[str(agents[keys[0]]["resource_group"])]
|
|
available_units = int(resource["max_active"])
|
|
if lock_key == root_lock:
|
|
available_units -= int(agents[root_agent]["resource_units"])
|
|
weights = sorted(
|
|
int(agents[key]["resource_units"]) for key in keys for _slot in range(active_slots[key])
|
|
)
|
|
used_units = 0
|
|
admitted_slots = 0
|
|
for weight in weights:
|
|
if used_units + weight > max(0, available_units):
|
|
break
|
|
used_units += weight
|
|
admitted_slots += 1
|
|
resource_feasible_workers += admitted_slots
|
|
|
|
feasible_workers = resource_feasible_workers
|
|
writable_slots = sum(
|
|
active_slots[key]
|
|
for key in reachable_workers
|
|
if agents[key]["permissions"] == "workspace-write" and "mcp" in agents[key]["backends"]
|
|
)
|
|
return {
|
|
"reachable_workers": reachable_workers,
|
|
"feasible_max_active_agents": 1 + feasible_workers,
|
|
"writable_slots": writable_slots,
|
|
}
|
|
|
|
|
|
def resolve_profile(
|
|
value: str | Path,
|
|
*,
|
|
bindings: Mapping[str, str] | None = None,
|
|
) -> dict[str, Any]:
|
|
directory, source = resolve_profile_dir(value)
|
|
pack_errors = validate_profile_pack_tree(directory)
|
|
if pack_errors:
|
|
raise ValueError("invalid profile pack: " + "; ".join(pack_errors))
|
|
profile = read_toml(directory / "profile.toml")
|
|
_reject_unknown_fields(profile, PROFILE_FIELDS, f"profile {directory}")
|
|
try:
|
|
_schema_version(profile.get("schema_version"), MMO_SCHEMA_VERSION, "profile")
|
|
except ValueError as exc:
|
|
raise ValueError(f"unsupported profile schema_version in {directory}") from exc
|
|
profile_id = _required_id(profile.get("id"), "profile id")
|
|
root_agent = _required_id(profile.get("root"), "root agent id")
|
|
for field in ("version", "display_name", "description"):
|
|
if not isinstance(profile.get(field), str) or not profile[field].strip():
|
|
raise ValueError(f"profile.{field} must be a non-empty string")
|
|
if profile["version"] != PACKAGE_VERSION:
|
|
raise ValueError(
|
|
f"profile version must exactly match the active package version {PACKAGE_VERSION}"
|
|
)
|
|
catalog = validate_catalog_data(
|
|
load_catalog(directory, profile), label=f"profile {profile_id} catalog"
|
|
)
|
|
tool_mcp_registry = load_tool_mcp_registry()
|
|
maturity = _enum_string(profile.get("maturity"), ALLOWED_PROFILE_MATURITY, "profile.maturity")
|
|
routes = catalog["routes"]
|
|
resources = catalog["resources"]
|
|
models = catalog["models"]
|
|
coordination = _validate_coordination(profile)
|
|
agent_tables = profile.get("agents")
|
|
if not isinstance(agent_tables, Mapping) or not agent_tables:
|
|
raise ValueError(f"profile {profile_id}: [agents] must define at least one agent")
|
|
if not all(isinstance(item, Mapping) for item in agent_tables.values()):
|
|
raise ValueError(f"profile {profile_id}: every agent must be a table")
|
|
supplied_bindings = dict(bindings or {})
|
|
unknown_bindings = sorted(set(supplied_bindings) - set(agent_tables))
|
|
if unknown_bindings:
|
|
raise ValueError(f"unknown agent bindings: {', '.join(unknown_bindings)}")
|
|
agents = {
|
|
key: _validate_agent(
|
|
key,
|
|
value,
|
|
profile_dir=directory,
|
|
models=models,
|
|
routes=routes,
|
|
resources=resources,
|
|
tool_mcp_registry=tool_mcp_registry,
|
|
binding=supplied_bindings.get(key),
|
|
orchestration=coordination["orchestration"],
|
|
)
|
|
for key, value in agent_tables.items()
|
|
}
|
|
if root_agent not in agents:
|
|
raise ValueError(f"profile {profile_id}: root agent {root_agent!r} is not defined")
|
|
roots = [key for key, agent in agents.items() if agent["kind"] == "root"]
|
|
if roots != [root_agent]:
|
|
raise ValueError(
|
|
f"profile {profile_id}: exactly the configured root must have kind='root'; found {roots}"
|
|
)
|
|
for key, agent in agents.items():
|
|
for child in agent["can_spawn"]:
|
|
if child not in agents:
|
|
raise ValueError(f"agent {key}: unknown spawn target {child!r}")
|
|
if child == root_agent:
|
|
raise ValueError(f"agent {key}: the root agent cannot be spawned")
|
|
if not agents[child]["backends"]:
|
|
raise ValueError(f"agent {key}: child {child!r} has no execution backend")
|
|
for target, grant in agent["controls"].items():
|
|
if target not in agents:
|
|
raise ValueError(f"agent {key}: unknown control target {target!r}")
|
|
if target == key:
|
|
raise ValueError(f"agent {key}: an agent cannot grant itself controls")
|
|
if not set(grant["actions"]).issubset(ALLOWED_CONTROL_ACTIONS):
|
|
raise ValueError(f"agent {key}: control target {target!r} has invalid actions")
|
|
if target == root_agent and "fork" in grant["actions"]:
|
|
raise ValueError(
|
|
f"agent {key}: the immutable root run cannot be a fork control target"
|
|
)
|
|
# Native root -> participant spawning is supported directly by Codex. By
|
|
# default, nested participant delegation is forced through Agent MCP so the
|
|
# MMO kernel can authenticate the caller and enforce graph, depth, budget,
|
|
# resource, scope, and result policies. Pure native nesting is available
|
|
# only as an explicit advisory opt-in because Codex exposes no supervisor
|
|
# hook that can mechanically validate each native edge.
|
|
if not coordination["native_nested_delegation"]:
|
|
for key, agent in agents.items():
|
|
if key == root_agent or "native" not in agent["backends"]:
|
|
continue
|
|
for child in agent["can_spawn"]:
|
|
if "mcp" not in agents[child]["backends"]:
|
|
raise ValueError(
|
|
f"agent {key}: child {child!r} must support MCP when "
|
|
"coordination.native_nested_delegation=false"
|
|
)
|
|
|
|
native_names: dict[str, str] = {}
|
|
for key, agent in agents.items():
|
|
if "native" not in agent["backends"]:
|
|
continue
|
|
name = agent["native_name"]
|
|
if name in native_names:
|
|
raise ValueError(
|
|
f"agents {native_names[name]!r} and {key!r} share native_name {name!r}"
|
|
)
|
|
native_names[name] = key
|
|
if coordination["max_depth"] == 0 and any(agent["can_spawn"] for agent in agents.values()):
|
|
raise ValueError("max_depth=0 is incompatible with configured spawn edges")
|
|
if coordination["max_active_agents"] < 2 and agents[root_agent]["can_spawn"]:
|
|
raise ValueError("a spawning profile requires max_active_agents >= 2")
|
|
for key, agent in agents.items():
|
|
resource_key = agent.get("resource_group")
|
|
if resource_key is None:
|
|
continue
|
|
units = int(agent["resource_units"])
|
|
capacity = int(resources[resource_key]["max_active"])
|
|
if units > capacity:
|
|
raise ValueError(
|
|
f"agent {key}.resource_units ({units}) exceeds resource group "
|
|
f"{resource_key!r} capacity ({capacity})"
|
|
)
|
|
capacities = derive_coordination_capacities(
|
|
agents,
|
|
resources,
|
|
root_agent=root_agent,
|
|
max_depth=int(coordination["max_depth"]),
|
|
)
|
|
reachable_workers = capacities["reachable_workers"]
|
|
feasible_active_agents = int(capacities["feasible_max_active_agents"])
|
|
if coordination["max_active_agents"] > feasible_active_agents:
|
|
raise ValueError(
|
|
"coordination.max_active_agents exceeds compiler-derived feasible concurrency "
|
|
f"({feasible_active_agents})"
|
|
)
|
|
writable_slots = int(capacities["writable_slots"])
|
|
if coordination["max_active_writers"] > writable_slots:
|
|
raise ValueError(
|
|
"coordination.max_active_writers exceeds reachable writable MCP capacity "
|
|
f"({writable_slots})"
|
|
)
|
|
if maturity == "featured":
|
|
native_writers = [
|
|
key
|
|
for key in reachable_workers
|
|
if "native" in agents[key]["backends"]
|
|
and agents[key]["permissions"] == "workspace-write"
|
|
]
|
|
if native_writers:
|
|
raise ValueError(
|
|
"featured profiles require native participants to be read-only: "
|
|
+ ", ".join(native_writers)
|
|
)
|
|
coordination["feasible_max_active_agents"] = feasible_active_agents
|
|
|
|
used_model_keys = sorted({agent["model"] for agent in agents.values()})
|
|
used_route_keys = sorted({models[key]["route"] for key in used_model_keys})
|
|
used_resource_keys = sorted(
|
|
{agent["resource_group"] for agent in agents.values() if agent["resource_group"]}
|
|
)
|
|
used_tool_mcp_server_ids = sorted(
|
|
{server_id for agent in agents.values() for server_id in agent["tool_mcp_servers"]}
|
|
)
|
|
smoke_data: dict[str, Any] | None = None
|
|
smoke_name = profile.get("smoke", "smoke.toml")
|
|
if not isinstance(smoke_name, str) or not smoke_name:
|
|
raise ValueError("profile.smoke must be a non-empty relative path string")
|
|
if Path(smoke_name).is_absolute():
|
|
raise ValueError("profile.smoke must be a non-empty relative path string")
|
|
smoke_path = (directory / smoke_name).resolve()
|
|
if directory not in smoke_path.parents:
|
|
raise ValueError("profile smoke path escapes the profile pack")
|
|
if smoke_path.is_file():
|
|
smoke_data = read_toml(smoke_path)
|
|
_reject_unknown_fields(smoke_data, SMOKE_FIELDS, f"profile {profile_id} smoke")
|
|
try:
|
|
_schema_version(smoke_data.get("schema_version"), MMO_SCHEMA_VERSION, "smoke")
|
|
except ValueError as exc:
|
|
raise ValueError(f"profile {profile_id}: unsupported smoke schema") from exc
|
|
tasks = smoke_data.get("tasks", [])
|
|
if not isinstance(tasks, list):
|
|
raise ValueError(f"profile {profile_id}: smoke.tasks must be an array of tables")
|
|
for index, task in enumerate(tasks):
|
|
if not isinstance(task, Mapping):
|
|
raise ValueError(f"profile {profile_id}: smoke task {index} must be a table")
|
|
_reject_unknown_fields(
|
|
task,
|
|
SMOKE_TASK_FIELDS,
|
|
f"profile {profile_id} smoke task {index}",
|
|
)
|
|
task_agent = task.get("agent")
|
|
if not isinstance(task_agent, str) or task_agent not in agents:
|
|
raise ValueError(f"profile {profile_id}: smoke task {index} uses unknown agent")
|
|
smoke_agent = agents[task_agent]
|
|
if smoke_agent["trust"] == "low":
|
|
if "task" in task or "task_kind" in task:
|
|
raise ValueError(
|
|
f"profile {profile_id}: low-trust smoke task {index} must use "
|
|
"literal_task instead of free-form task/task_kind"
|
|
)
|
|
literal_task = task.get("literal_task")
|
|
if not isinstance(literal_task, Mapping):
|
|
raise ValueError(
|
|
f"profile {profile_id}: low-trust smoke task {index} requires literal_task"
|
|
)
|
|
operation = literal_task.get("operation")
|
|
if operation not in LOW_TRUST_TASK_KINDS:
|
|
raise ValueError(
|
|
f"profile {profile_id}: low-trust smoke task {index} has invalid "
|
|
"literal operation"
|
|
)
|
|
task_text = ""
|
|
task_kind = str(operation)
|
|
else:
|
|
if "literal_task" in task:
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} reserves literal_task "
|
|
"for low-trust agents"
|
|
)
|
|
if not isinstance(task.get("task"), str) or not task["task"].strip():
|
|
raise ValueError(f"profile {profile_id}: smoke task {index} has no task text")
|
|
task_text = task["task"].strip()
|
|
task_kind = _required_id(
|
|
task.get("task_kind"), f"profile {profile_id} smoke task kind"
|
|
)
|
|
if task_agent != root_agent and task_kind not in smoke_agent["allowed_task_kinds"]:
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} has invalid task_kind"
|
|
)
|
|
mode = task.get("mode", "read-only")
|
|
mode = _enum_string(
|
|
mode,
|
|
ALLOWED_PERMISSIONS,
|
|
f"profile {profile_id} smoke task {index}.mode",
|
|
)
|
|
if mode == "workspace-write" and smoke_agent["permissions"] != "workspace-write":
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} exceeds agent permissions"
|
|
)
|
|
for field in ("write_scope", "attachments"):
|
|
_string_list(
|
|
task.get(field, []),
|
|
f"profile {profile_id} smoke task {index}.{field}",
|
|
)
|
|
required_mcp_tools = _string_list(
|
|
task.get("required_mcp_tools", []),
|
|
f"profile {profile_id} smoke task {index}.required_mcp_tools",
|
|
)
|
|
grants = smoke_agent["tool_mcp_servers"]
|
|
agent_mcp_tools = agent_mcp_tool_names(
|
|
agents,
|
|
root_agent=root_agent,
|
|
agent_id=task_agent,
|
|
)
|
|
for qualified_tool in required_mcp_tools:
|
|
if "." not in qualified_tool:
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} required MCP tool "
|
|
f"{qualified_tool!r} must use server.tool form"
|
|
)
|
|
matches = [
|
|
(server_id, qualified_tool[len(server_id) + 1 :])
|
|
for server_id, grant in grants.items()
|
|
if qualified_tool.startswith(server_id + ".")
|
|
and qualified_tool[len(server_id) + 1 :] in grant["enabled_tools"]
|
|
]
|
|
if qualified_tool.startswith("mmo_mesh."):
|
|
tool_name = qualified_tool.removeprefix("mmo_mesh.")
|
|
if tool_name in agent_mcp_tools:
|
|
matches.append(("mmo_mesh", tool_name))
|
|
if not matches:
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} requires ungranted "
|
|
f"MCP tool {qualified_tool!r}"
|
|
)
|
|
if len(matches) > 1:
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} required MCP tool "
|
|
f"{qualified_tool!r} is ambiguous across granted servers"
|
|
)
|
|
if mode == "read-only" and task.get("write_scope"):
|
|
raise ValueError(
|
|
f"profile {profile_id}: read-only smoke task {index} has write_scope"
|
|
)
|
|
if "wall_timeout_seconds" in task:
|
|
_positive_int(
|
|
task["wall_timeout_seconds"],
|
|
f"profile {profile_id} smoke task {index}.wall_timeout_seconds",
|
|
1,
|
|
172_800,
|
|
)
|
|
if "wait_seconds" in task:
|
|
_positive_int(
|
|
task["wait_seconds"],
|
|
f"profile {profile_id} smoke task {index}.wait_seconds",
|
|
0,
|
|
120,
|
|
)
|
|
backend = task.get("backend")
|
|
if task_agent == root_agent and backend is not None:
|
|
raise ValueError(
|
|
f"profile {profile_id}: root smoke task {index} cannot select a backend"
|
|
)
|
|
if backend is not None and backend not in smoke_agent["backends"]:
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} requests unsupported backend {backend!r}"
|
|
)
|
|
selected_backend = backend
|
|
if task_agent != root_agent and selected_backend is None:
|
|
available_backends = list(smoke_agent["backends"])
|
|
selected_backend = (
|
|
"mcp"
|
|
if "mcp" in available_backends
|
|
else (available_backends[0] if len(available_backends) == 1 else None)
|
|
)
|
|
if selected_backend is None:
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} has no executable backend"
|
|
)
|
|
if task_agent != root_agent and selected_backend == "mcp":
|
|
if smoke_agent["trust"] != "low" and not (
|
|
int(smoke_agent["min_task_chars"])
|
|
<= len(task_text)
|
|
<= int(smoke_agent["max_task_chars"])
|
|
):
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} length is outside "
|
|
f"agent {task_agent}'s MCP task bounds"
|
|
)
|
|
if (
|
|
mode == "workspace-write"
|
|
and smoke_agent.get("write_scope_required", True)
|
|
and not task.get("write_scope")
|
|
):
|
|
raise ValueError(
|
|
f"profile {profile_id}: MCP workspace-write smoke task {index} "
|
|
"requires write_scope"
|
|
)
|
|
if task.get("attachments") and not smoke_agent.get("attachments_allowed"):
|
|
raise ValueError(
|
|
f"profile {profile_id}: smoke task {index} supplies attachments to "
|
|
f"agent {task_agent}, which does not permit them"
|
|
)
|
|
elif "smoke" in profile:
|
|
raise ValueError(f"explicit profile smoke file is missing: {smoke_name}")
|
|
|
|
warnings: list[str] = []
|
|
native_agents = [key for key, agent in agents.items() if "native" in agent["backends"]]
|
|
mcp_agents = [key for key, agent in agents.items() if "mcp" in agent["backends"]]
|
|
for key in native_agents:
|
|
agent = agents[key]
|
|
if agent["permissions"] == "workspace-write":
|
|
warnings.append(
|
|
f"native agent {key}: MMO write-scope leasing/audit is unavailable; "
|
|
"Codex sandboxing and integration review remain required"
|
|
)
|
|
if agent["contract_enforcement"] == "strict":
|
|
warnings.append(
|
|
f"native agent {key}: output contract is prompt-enforced only on the native backend"
|
|
)
|
|
credential_servers = [
|
|
server_id
|
|
for server_id in agent["tool_mcp_servers"]
|
|
if tool_mcp_environment_names(tool_mcp_registry[server_id])
|
|
]
|
|
if credential_servers:
|
|
warnings.append(
|
|
f"native agent {key}: tool MCP credentials for {credential_servers} share the "
|
|
"parent Codex process environment; use the Agent MCP backend for strict "
|
|
"credential isolation"
|
|
)
|
|
if native_agents and coordination["native_nested_delegation"]:
|
|
warnings.append(
|
|
"native nested delegation is advisory: Codex does not expose a supervisor hook "
|
|
"for MMO to mechanically enforce every native edge, depth, resource, or scope limit"
|
|
)
|
|
|
|
public_agents: dict[str, Any] = {}
|
|
for key, agent in agents.items():
|
|
item = dict(agent)
|
|
item.pop("model_config", None)
|
|
item.pop("route_config", None)
|
|
public_agents[key] = item
|
|
resolved: dict[str, Any] = {
|
|
"schema_version": MMO_SCHEMA_VERSION,
|
|
"package_version": package_version(),
|
|
"profile": {
|
|
"id": profile_id,
|
|
"version": profile["version"],
|
|
"display_name": profile["display_name"],
|
|
"description": profile["description"],
|
|
"tags": _string_list(profile.get("tags", []), "profile.tags"),
|
|
"maturity": maturity,
|
|
"source": source,
|
|
"root": root_agent,
|
|
},
|
|
"coordination": coordination,
|
|
"routes": {key: routes[key] for key in used_route_keys},
|
|
"models": {key: models[key] for key in used_model_keys},
|
|
"resources": {key: resources[key] for key in used_resource_keys},
|
|
"tool_mcp_servers": {key: tool_mcp_registry[key] for key in used_tool_mcp_server_ids},
|
|
"agents": public_agents,
|
|
"smoke": smoke_data,
|
|
"bindings": supplied_bindings,
|
|
"capabilities": {
|
|
"native_agents": native_agents,
|
|
"mcp_agents": mcp_agents,
|
|
"tool_mcp_servers": used_tool_mcp_server_ids,
|
|
"orchestration": coordination["orchestration"],
|
|
},
|
|
"warnings": warnings,
|
|
}
|
|
# Profile location is discovery metadata, not execution semantics. The same
|
|
# static profile must compile to the same logical identity whether bundled,
|
|
# installed by a user, or addressed directly by path.
|
|
hash_payload = dict(resolved)
|
|
hash_payload["profile"] = dict(resolved["profile"])
|
|
hash_payload["profile"].pop("source", None)
|
|
resolved["logical_hash"] = stable_hash(hash_payload)
|
|
return resolved
|
|
|
|
|
|
def profile_summary(
|
|
value: str | Path, *, bindings: Mapping[str, str] | None = None
|
|
) -> dict[str, Any]:
|
|
resolved = resolve_profile(value, bindings=bindings)
|
|
return {
|
|
"id": resolved["profile"]["id"],
|
|
"version": resolved["profile"]["version"],
|
|
"display_name": resolved["profile"]["display_name"],
|
|
"description": resolved["profile"]["description"],
|
|
"maturity": resolved["profile"]["maturity"],
|
|
"root": resolved["profile"]["root"],
|
|
"agents": {
|
|
key: {
|
|
"kind": agent["kind"],
|
|
"model": agent["model"],
|
|
"route": agent["route"],
|
|
"permissions": agent["permissions"],
|
|
"trust": agent["trust"],
|
|
"can_spawn": agent["can_spawn"],
|
|
"backends": agent["backends"],
|
|
"tool_mcp_servers": agent["tool_mcp_servers"],
|
|
}
|
|
for key, agent in resolved["agents"].items()
|
|
},
|
|
"coordination": resolved["coordination"],
|
|
"logical_hash": resolved["logical_hash"],
|
|
}
|
|
|
|
|
|
def active_profile_id() -> str:
|
|
path = config_root() / "active-profile"
|
|
if path.is_file():
|
|
value = path.read_text(encoding="utf-8").strip()
|
|
if value:
|
|
return validate_id(value, "active profile id")
|
|
settings = load_settings()
|
|
configured = settings.get("default_profile")
|
|
if configured:
|
|
return validate_id(configured, "default profile")
|
|
profiles = discover_profiles()
|
|
if not profiles:
|
|
raise RuntimeError("no composition profiles are installed")
|
|
return sorted(profiles)[0]
|
|
|
|
|
|
def set_active_profile(profile_id: str) -> None:
|
|
validate_id(profile_id, "profile id")
|
|
resolve_profile(profile_id)
|
|
atomic_write_text(config_root() / "active-profile", profile_id + "\n", 0o600)
|
|
|
|
|
|
def _safe_archive_target(destination: Path, raw_name: str) -> Path:
|
|
if not raw_name or "\x00" in raw_name or "\\" in raw_name or raw_name.startswith("/"):
|
|
raise ValueError("archive member has an invalid name")
|
|
normalized = raw_name[:-1] if raw_name.endswith("/") else raw_name
|
|
parts = normalized.split("/")
|
|
if not normalized or any(part in {"", ".", ".."} for part in parts):
|
|
raise ValueError(f"unsafe archive member: {raw_name}")
|
|
target = destination.joinpath(*parts).resolve()
|
|
root = destination.resolve()
|
|
if target != root and root not in target.parents:
|
|
raise ValueError(f"unsafe archive member: {raw_name}")
|
|
return target
|
|
|
|
|
|
def _copy_limited(source: Any, output: Any, *, maximum: int, label: str) -> int:
|
|
copied = 0
|
|
while True:
|
|
block = source.read(min(1024 * 1024, maximum - copied + 1))
|
|
if not block:
|
|
return copied
|
|
copied += len(block)
|
|
if copied > maximum:
|
|
raise ValueError(f"profile archive member exceeds {maximum} bytes: {label}")
|
|
output.write(block)
|
|
|
|
|
|
def _safe_extract_archive(archive: Path, destination: Path) -> None:
|
|
if archive.stat().st_size > MAX_PROFILE_ARCHIVE_BYTES:
|
|
raise ValueError(f"profile archive exceeds {MAX_PROFILE_ARCHIVE_BYTES} compressed bytes")
|
|
file_count = 0
|
|
total_bytes = 0
|
|
seen_targets: set[str] = set()
|
|
if archive.suffix.lower() == ".zip":
|
|
with zipfile.ZipFile(archive) as handle:
|
|
infos = handle.infolist()
|
|
if len(infos) > MAX_PROFILE_ARCHIVE_FILES * 2:
|
|
raise ValueError("profile archive contains too many members")
|
|
for info in infos:
|
|
target = _safe_archive_target(destination, info.filename)
|
|
target_key = target.relative_to(destination.resolve()).as_posix()
|
|
if target_key in seen_targets:
|
|
raise ValueError(f"duplicate profile archive member: {info.filename}")
|
|
seen_targets.add(target_key)
|
|
unix_mode = (info.external_attr >> 16) & 0xFFFF
|
|
file_type = stat.S_IFMT(unix_mode)
|
|
if file_type not in {0, stat.S_IFREG, stat.S_IFDIR}:
|
|
raise ValueError(f"links and special files are not allowed: {info.filename}")
|
|
if info.flag_bits & 0x1:
|
|
raise ValueError(
|
|
f"encrypted profile archive member is not allowed: {info.filename}"
|
|
)
|
|
if info.is_dir() or file_type == stat.S_IFDIR:
|
|
target.mkdir(parents=True, exist_ok=True)
|
|
continue
|
|
file_count += 1
|
|
if file_count > MAX_PROFILE_ARCHIVE_FILES:
|
|
raise ValueError("profile archive contains too many files")
|
|
if info.file_size > MAX_PROFILE_MEMBER_BYTES:
|
|
raise ValueError(f"profile archive member exceeds 1 MiB: {info.filename}")
|
|
total_bytes += int(info.file_size)
|
|
if total_bytes > MAX_PROFILE_UNCOMPRESSED_BYTES:
|
|
raise ValueError("profile archive expands beyond the allowed size")
|
|
if info.file_size and info.compress_size == 0:
|
|
raise ValueError(f"invalid compressed size for archive member: {info.filename}")
|
|
if (
|
|
info.compress_size > 0
|
|
and info.file_size / info.compress_size > MAX_ZIP_COMPRESSION_RATIO
|
|
):
|
|
raise ValueError(f"suspicious compression ratio: {info.filename}")
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
with handle.open(info) as source, target.open("wb") as output:
|
|
copied = _copy_limited(
|
|
source, output, maximum=MAX_PROFILE_MEMBER_BYTES, label=info.filename
|
|
)
|
|
if copied != info.file_size:
|
|
raise ValueError(f"archive member size mismatch: {info.filename}")
|
|
else:
|
|
with tarfile.open(archive, "r|*") as handle:
|
|
member_count = 0
|
|
for member in handle:
|
|
member_count += 1
|
|
if member_count > MAX_PROFILE_ARCHIVE_FILES * 2:
|
|
raise ValueError("profile archive contains too many members")
|
|
if member.size < 0:
|
|
raise ValueError(f"archive member has a negative size: {member.name}")
|
|
target = _safe_archive_target(destination, member.name)
|
|
target_key = target.relative_to(destination.resolve()).as_posix()
|
|
if target_key in seen_targets:
|
|
raise ValueError(f"duplicate profile archive member: {member.name}")
|
|
seen_targets.add(target_key)
|
|
if member.issym() or member.islnk() or member.isdev():
|
|
raise ValueError(f"links and special files are not allowed: {member.name}")
|
|
if not (member.isdir() or member.isfile()):
|
|
raise ValueError(f"unsupported archive member: {member.name}")
|
|
if member.isdir():
|
|
target.mkdir(parents=True, exist_ok=True)
|
|
continue
|
|
file_count += 1
|
|
if file_count > MAX_PROFILE_ARCHIVE_FILES:
|
|
raise ValueError("profile archive contains too many files")
|
|
if member.size > MAX_PROFILE_MEMBER_BYTES:
|
|
raise ValueError(f"profile archive member exceeds 1 MiB: {member.name}")
|
|
total_bytes += int(member.size)
|
|
if total_bytes > MAX_PROFILE_UNCOMPRESSED_BYTES:
|
|
raise ValueError("profile archive expands beyond the allowed size")
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
tar_source = handle.extractfile(member)
|
|
if tar_source is None:
|
|
raise ValueError(f"unable to read archive member: {member.name}")
|
|
with tar_source, target.open("wb") as output:
|
|
copied = _copy_limited(
|
|
tar_source,
|
|
output,
|
|
maximum=MAX_PROFILE_MEMBER_BYTES,
|
|
label=member.name,
|
|
)
|
|
if copied != member.size:
|
|
raise ValueError(f"archive member size mismatch: {member.name}")
|
|
|
|
|
|
def _find_profile_root(extracted: Path) -> Path:
|
|
candidates = [path.parent for path in extracted.rglob("profile.toml")]
|
|
if len(candidates) != 1:
|
|
raise ValueError("profile archive must contain exactly one profile.toml")
|
|
return candidates[0]
|
|
|
|
|
|
def install_profile_pack(source: Path, *, replace: bool = False) -> str:
|
|
source = source.expanduser().resolve()
|
|
with tempfile.TemporaryDirectory(prefix="mmo-profile-") as temporary:
|
|
stage_root = Path(temporary)
|
|
if source.is_dir():
|
|
profile_root = source
|
|
elif source.is_file():
|
|
_safe_extract_archive(source, stage_root)
|
|
profile_root = _find_profile_root(stage_root)
|
|
else:
|
|
raise FileNotFoundError(source)
|
|
errors = validate_profile_pack_tree(profile_root)
|
|
if errors:
|
|
raise ValueError("invalid profile pack: " + "; ".join(errors))
|
|
data = read_toml(profile_root / "profile.toml")
|
|
profile_id = _required_id(data.get("id"), "profile id")
|
|
destination = user_profiles_root() / profile_id
|
|
profiles_root = user_profiles_root()
|
|
profiles_root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
staged = Path(tempfile.mkdtemp(prefix=f".{profile_id}.staging-", dir=profiles_root))
|
|
try:
|
|
copy_tree_static(profile_root, staged)
|
|
# Validation reads the path directly, so user profile discovery is irrelevant.
|
|
incoming = resolve_profile(staged)
|
|
if destination.exists():
|
|
try:
|
|
installed = resolve_profile(destination)
|
|
except (OSError, UnicodeError, ValueError):
|
|
if not replace:
|
|
raise FileExistsError(
|
|
f"profile {profile_id} already exists but is not valid for the active "
|
|
"generation; use --replace to replace it atomically"
|
|
) from None
|
|
else:
|
|
incoming_content_hash = _profile_pack_content_hash(staged)
|
|
installed_content_hash = _profile_pack_content_hash(destination)
|
|
if (
|
|
incoming["logical_hash"] == installed["logical_hash"]
|
|
and incoming_content_hash == installed_content_hash
|
|
):
|
|
return profile_id
|
|
if not replace:
|
|
raise FileExistsError(
|
|
f"profile {profile_id} already exists with different "
|
|
"current-generation content; use --replace to replace it atomically"
|
|
)
|
|
backup = destination.with_name(f".{profile_id}.backup-{uuid.uuid4().hex}")
|
|
os.replace(destination, backup)
|
|
try:
|
|
os.replace(staged, destination)
|
|
except Exception:
|
|
os.replace(backup, destination)
|
|
raise
|
|
with contextlib.suppress(OSError):
|
|
shutil.rmtree(backup)
|
|
else:
|
|
os.replace(staged, destination)
|
|
finally:
|
|
if staged.exists():
|
|
shutil.rmtree(staged, ignore_errors=True)
|
|
return profile_id
|
|
|
|
|
|
def remove_profile(profile_id: str) -> None:
|
|
profile_id = validate_id(profile_id, "profile id")
|
|
path = user_profiles_root() / profile_id
|
|
if not path.is_dir():
|
|
if (builtin_profiles_root() / profile_id).is_dir():
|
|
raise ValueError("bundled profiles cannot be removed")
|
|
raise FileNotFoundError(f"user profile not found: {profile_id}")
|
|
if active_profile_id() == profile_id:
|
|
raise ValueError("cannot remove the active profile; select another profile first")
|
|
shutil.rmtree(path)
|
|
|
|
|
|
def clone_profile(source_id: str, destination_id: str, *, replace: bool = False) -> Path:
|
|
source_dir, _ = resolve_profile_dir(source_id)
|
|
destination_id = validate_id(destination_id, "destination profile id")
|
|
destination = user_profiles_root() / destination_id
|
|
if destination.exists() and not replace:
|
|
raise FileExistsError(destination)
|
|
with tempfile.TemporaryDirectory(prefix="mmo-clone-") as temporary:
|
|
staged = Path(temporary) / destination_id
|
|
staged.mkdir(parents=True)
|
|
copy_tree_static(source_dir, staged)
|
|
manifest = staged / "profile.toml"
|
|
text = manifest.read_text(encoding="utf-8")
|
|
replaced, count = re.subn(
|
|
r"""(?m)^(?P<indent>[ \t]*)(?:id|"id"|'id')[ \t]*=[ \t]*"""
|
|
r"""(?:"[^"\r\n]*"|'[^'\r\n]*')(?P<suffix>[ \t]*(?:#[^\r\n]*)?)$""",
|
|
lambda match: f'{match.group("indent")}id = "{destination_id}"{match.group("suffix")}',
|
|
text,
|
|
count=1,
|
|
)
|
|
if count != 1:
|
|
raise ValueError("unable to rewrite profile id")
|
|
manifest.write_text(replaced, encoding="utf-8")
|
|
install_profile_pack(staged, replace=replace)
|
|
return destination
|