1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages
DocForge2/src/docforge/client_config.py

1534 lines
56 KiB
Python

"""Deterministic, explicit client-configuration plans for DocForge MCP."""
from __future__ import annotations
import hashlib
import json
import os
import re
import secrets
import stat
import subprocess
import sys
from collections.abc import Callable
from contextlib import suppress
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, cast
from .adapter_launcher import (
AdapterLauncherV1,
AdapterSourceAvailabilityV1,
adapter_source_availability,
validate_adapter_launcher,
)
from .changeset_contract import document_hash
from .errors import DocForgeError
from .models import ProjectDescriptor, ProjectService
from .policy import CapabilityMode, compose_effective_policy
from .project import Project, project_root_fingerprint, validate_descriptor_binding
from .projection_policy import compose_projection_policy
ClientName = Literal["codex", "claude", "openclaw"]
CLIENT_NAMES: tuple[ClientName, ...] = ("codex", "claude", "openclaw")
MAX_CLIENT_FRAGMENT_BYTES = 1_000_000
GENERATED_CAPABILITY_MODES: tuple[CapabilityMode, ...] = (
"read",
"proposal",
"application",
)
_SERVER_NAME = re.compile(r"[a-z0-9][a-z0-9_-]{0,63}")
@dataclass(frozen=True)
class _FileIdentity:
device: int
inode: int
mode: int
size: int
mtime_ns: int
ctime_ns: int
uid: int
link_count: int
def _file_identity(status: os.stat_result) -> _FileIdentity:
return _FileIdentity(
device=status.st_dev,
inode=status.st_ino,
mode=status.st_mode,
size=status.st_size,
mtime_ns=status.st_mtime_ns,
ctime_ns=status.st_ctime_ns,
uid=status.st_uid,
link_count=status.st_nlink,
)
def _client_name(value: str) -> ClientName:
if value not in CLIENT_NAMES:
raise DocForgeError(
"unsupported_client",
"Client configuration target is unsupported",
client=value,
allowed=list(CLIENT_NAMES),
)
return value
def _capability_mode(value: str) -> CapabilityMode:
if value not in GENERATED_CAPABILITY_MODES:
raise DocForgeError(
"invalid_capability_mode",
"Generated configuration supports read, proposal, or application mode",
capability_mode=value,
allowed=list(GENERATED_CAPABILITY_MODES),
)
return value
def _bounded_seconds(value: int, *, field: str, maximum: int) -> int:
if type(value) is not int or value < 1 or value > maximum:
raise DocForgeError(
"invalid_timeout",
"Client timeout is outside the supported range",
field=field,
minimum=1,
maximum=maximum,
)
return value
def _default_server_name(project_id: str, fingerprint: str) -> str:
prefix = re.sub(r"[^a-z0-9_-]+", "-", project_id.lower()).strip("-_")
prefix = prefix or "project"
suffix = f"-{fingerprint}"
available = 64 - len("docforge-") - len(suffix)
return f"docforge-{prefix[:available]}{suffix}"
def _validated_server_name(value: str | None, *, project_id: str, fingerprint: str) -> str:
selected = value or _default_server_name(project_id, fingerprint)
if _SERVER_NAME.fullmatch(selected) is None:
raise DocForgeError(
"invalid_server_name",
"Generated server name must be a stable lowercase client identifier",
pattern=_SERVER_NAME.pattern,
maximum_length=64,
)
return selected
def _toml_string(value: str) -> str:
return json.dumps(value, ensure_ascii=False)
def _toml_array(values: list[str]) -> str:
return "[" + ", ".join(_toml_string(value) for value in values) + "]"
def _artifact(
client: ClientName,
*,
server_name: str,
command: str,
arguments: list[str],
startup_timeout: int,
tool_timeout: int,
) -> tuple[str, str, str | None]:
if client == "codex":
content = "\n".join(
(
f'[mcp_servers."{server_name}"]',
f"command = {_toml_string(command)}",
f"args = {_toml_array(arguments)}",
"env = {}",
f"startup_timeout_sec = {startup_timeout}",
f"tool_timeout_sec = {tool_timeout}",
"",
)
)
return "codex-toml-fragment-v1", content, None
if client == "openclaw":
content = (
json.dumps(
{
"mcp": {
"servers": {
server_name: {
"args": arguments,
"command": command,
"connectTimeout": startup_timeout,
"env": {},
"supportsParallelToolCalls": False,
"timeout": tool_timeout,
}
}
}
},
ensure_ascii=False,
indent=2,
sort_keys=True,
)
+ "\n"
)
return "openclaw-json-fragment-v1", content, None
content = (
json.dumps(
{
"mcpServers": {
server_name: {
"args": arguments,
"command": command,
"env": {},
}
}
},
ensure_ascii=False,
indent=2,
sort_keys=True,
)
+ "\n"
)
return (
"claude-json-fragment-v1",
content,
"Claude per-server timeout representation is not yet verified.",
)
def _signature(
directory_fd: int,
name: str,
) -> _FileIdentity | None:
try:
status = os.stat(name, dir_fd=directory_fd, follow_symlinks=False)
except FileNotFoundError:
return None
except OSError as error:
raise DocForgeError(
"unsafe_output",
"Configuration output cannot be inspected safely",
) from error
if stat.S_ISLNK(status.st_mode) or not stat.S_ISREG(status.st_mode):
raise DocForgeError(
"unsafe_output",
"Configuration output must be a regular file and not a symbolic link",
)
return _file_identity(status)
def _parent_binding_current(path: Path, directory_fd: int) -> bool:
try:
before = path.lstat()
resolved = path.resolve(strict=True)
after = path.lstat()
opened = os.fstat(directory_fd)
return (
not stat.S_ISLNK(before.st_mode)
and stat.S_ISDIR(before.st_mode)
and resolved == path
and (before.st_dev, before.st_ino, before.st_mode)
== (after.st_dev, after.st_ino, after.st_mode)
== (opened.st_dev, opened.st_ino, opened.st_mode)
)
except OSError:
return False
def _require_parent_binding(path: Path, directory_fd: int) -> None:
if not _parent_binding_current(path, directory_fd):
raise DocForgeError(
"output_changed",
"Configuration output parent changed during publication",
)
def _bound_parent(path: Path) -> tuple[Path, int]:
absolute = Path(os.path.abspath(path.expanduser()))
parent = absolute.parent
try:
parent_status = parent.lstat()
resolved = parent.resolve(strict=True)
except OSError as error:
raise DocForgeError(
"invalid_output",
"Configuration output parent does not exist",
) from error
if (
stat.S_ISLNK(parent_status.st_mode)
or not stat.S_ISDIR(parent_status.st_mode)
or resolved != parent
or absolute.name in {"", ".", ".."}
):
raise DocForgeError(
"unsafe_output",
"Configuration output parent must be one real non-symlinked directory",
)
try:
directory_fd = os.open(
parent,
os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
)
except OSError as error:
raise DocForgeError(
"unsafe_output",
"Configuration output parent cannot be opened safely",
) from error
opened = os.fstat(directory_fd)
if opened.st_dev != parent_status.st_dev or opened.st_ino != parent_status.st_ino:
with suppress(OSError):
os.close(directory_fd)
raise DocForgeError(
"output_changed",
"Configuration output parent changed while it was opened",
)
return absolute, directory_fd
def _read_existing(
directory_fd: int,
name: str,
signature: _FileIdentity,
) -> bytes:
if signature.size > MAX_CLIENT_FRAGMENT_BYTES:
raise DocForgeError(
"output_oversized",
"Existing configuration output exceeds the bounded fragment limit",
maximum_bytes=MAX_CLIENT_FRAGMENT_BYTES,
)
try:
descriptor = os.open(
name,
os.O_RDONLY | os.O_NOFOLLOW,
dir_fd=directory_fd,
)
except OSError as error:
raise DocForgeError(
"unsafe_output",
"Configuration output cannot be opened safely",
) from error
try:
opened = os.fstat(descriptor)
opened_signature = _file_identity(opened)
if opened_signature != signature:
raise DocForgeError(
"output_changed",
"Configuration output changed while it was opened",
)
remaining = MAX_CLIENT_FRAGMENT_BYTES + 1
chunks: list[bytes] = []
while remaining:
chunk = os.read(descriptor, min(65_536, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
raw = b"".join(chunks)
finally:
with suppress(OSError):
os.close(descriptor)
if len(raw) > MAX_CLIENT_FRAGMENT_BYTES or _signature(directory_fd, name) != signature:
raise DocForgeError(
"output_changed",
"Configuration output changed while it was read",
)
return raw
def _private_existing(identity: _FileIdentity) -> bool:
return (
identity.uid == os.geteuid()
and stat.S_IMODE(identity.mode) & 0o077 == 0
and identity.link_count == 1
)
def _rollback_link(
directory_fd: int,
name: str,
expected: _FileIdentity,
) -> bool:
try:
current = _signature(directory_fd, name)
if current is None:
return True
if current.device != expected.device or current.inode != expected.inode:
return True
os.unlink(name, dir_fd=directory_fd)
return True
except (DocForgeError, OSError):
return False
def _rollback_and_sync(
directory_fd: int,
name: str,
expected: _FileIdentity,
) -> bool:
if not _rollback_link(directory_fd, name, expected):
return False
try:
os.fsync(directory_fd)
except OSError:
return False
return True
def _atomic_write(
path: Path,
content: str,
*,
validate_binding: Callable[[], None],
) -> tuple[str, str, str | None, Path | None]:
target, directory_fd = _bound_parent(path)
encoded = content.encode("utf-8")
if len(encoded) > MAX_CLIENT_FRAGMENT_BYTES:
with suppress(OSError):
os.close(directory_fd)
raise DocForgeError(
"output_oversized",
"Generated configuration fragment exceeds the bounded limit",
maximum_bytes=MAX_CLIENT_FRAGMENT_BYTES,
)
temporary_name = f".docforge-client-{secrets.token_hex(12)}"
temporary_created = False
committed = False
linked_identity: _FileIdentity | None = None
try:
_require_parent_binding(target.parent, directory_fd)
before = _signature(directory_fd, target.name)
if before is not None:
if not _private_existing(before):
raise DocForgeError(
"unsafe_output",
(
"Existing configuration fragment must be owned by the current user, "
"private, and singly linked"
),
)
existing = _read_existing(directory_fd, target.name, before)
if existing == encoded:
validate_binding()
_require_parent_binding(target.parent, directory_fd)
current = _signature(directory_fd, target.name)
if (
current != before
or current is None
or not _private_existing(current)
or _read_existing(directory_fd, target.name, current) != encoded
):
raise DocForgeError(
"output_changed",
"Configuration output changed before unchanged publication was confirmed",
)
validate_binding()
_require_parent_binding(target.parent, directory_fd)
return "unchanged", "not_applicable", None, target
raise DocForgeError(
"output_conflict",
"Configuration fragment already exists with different content",
existing_sha256=hashlib.sha256(existing).hexdigest(),
generated_sha256=hashlib.sha256(encoded).hexdigest(),
)
try:
temporary_fd = os.open(
temporary_name,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o600,
dir_fd=directory_fd,
)
except OSError as error:
raise DocForgeError(
"output_publication_failed",
"Configuration fragment temporary file could not be created",
) from error
temporary_created = True
try:
with os.fdopen(temporary_fd, "wb", closefd=True) as handle:
handle.write(encoded)
handle.flush()
os.fsync(handle.fileno())
except OSError as error:
raise DocForgeError(
"output_publication_failed",
"Configuration fragment temporary file could not be written durably",
) from error
temporary_identity = _signature(directory_fd, temporary_name)
if temporary_identity is None:
raise DocForgeError(
"output_changed",
"Configuration fragment temporary file disappeared before publication",
)
if _signature(directory_fd, target.name) is not None:
raise DocForgeError(
"output_changed",
"Configuration output appeared before atomic publication",
)
_require_parent_binding(target.parent, directory_fd)
validate_binding()
try:
os.link(
temporary_name,
target.name,
src_dir_fd=directory_fd,
dst_dir_fd=directory_fd,
follow_symlinks=False,
)
except FileExistsError as error:
raise DocForgeError(
"output_changed",
"Configuration output appeared during atomic publication",
) from error
except OSError as error:
raise DocForgeError(
"output_publication_failed",
"Configuration fragment could not be published atomically",
) from error
linked_identity = temporary_identity
try:
validate_binding()
except DocForgeError:
if _rollback_link(directory_fd, target.name, temporary_identity):
raise
committed = True
return (
"created",
"unconfirmed",
"publication_binding_unconfirmed",
None,
)
linked = _signature(directory_fd, target.name)
if (
linked is None
or linked.device != temporary_identity.device
or linked.inode != temporary_identity.inode
or _read_existing(directory_fd, target.name, linked) != encoded
or not _parent_binding_current(target.parent, directory_fd)
):
if _rollback_link(directory_fd, target.name, temporary_identity):
raise DocForgeError(
"output_changed",
"Configuration output changed during atomic publication",
)
committed = True
return (
"created",
"unconfirmed",
"publication_location_unconfirmed",
None,
)
durability = "confirmed"
warning: str | None = None
try:
os.unlink(temporary_name, dir_fd=directory_fd)
temporary_created = False
published = _signature(directory_fd, target.name)
if (
published is None
or not _private_existing(published)
or published.device != temporary_identity.device
or published.inode != temporary_identity.inode
or _read_existing(directory_fd, target.name, published) != encoded
or not _parent_binding_current(target.parent, directory_fd)
):
if _rollback_link(directory_fd, target.name, temporary_identity):
raise DocForgeError(
"output_changed",
"Configuration output changed after atomic publication",
)
committed = True
return (
"created",
"unconfirmed",
"publication_location_unconfirmed",
None,
)
try:
validate_binding()
except DocForgeError:
if _rollback_link(directory_fd, target.name, temporary_identity):
raise
committed = True
return (
"created",
"unconfirmed",
"publication_binding_unconfirmed",
None,
)
os.fsync(directory_fd)
except OSError:
durability = "unconfirmed"
warning = "publication_durability_unconfirmed"
try:
validate_binding()
except DocForgeError:
if _rollback_and_sync(directory_fd, target.name, temporary_identity):
raise
committed = True
return (
"created",
"unconfirmed",
"publication_binding_unconfirmed",
None,
)
try:
published = _signature(directory_fd, target.name)
publication_current = (
published is not None
and _private_existing(published)
and published.device == temporary_identity.device
and published.inode == temporary_identity.inode
and _read_existing(directory_fd, target.name, published) == encoded
and _parent_binding_current(target.parent, directory_fd)
)
except DocForgeError:
publication_current = False
if not publication_current:
if _rollback_and_sync(directory_fd, target.name, temporary_identity):
raise DocForgeError(
"output_changed",
"Configuration output changed before publication was finalized",
)
committed = True
return (
"created",
"unconfirmed",
"publication_location_unconfirmed",
None,
)
committed = True
return "created", durability, warning, target
except Exception:
if not committed and linked_identity is not None:
_rollback_link(directory_fd, target.name, linked_identity)
if not committed and temporary_created:
with suppress(OSError):
os.unlink(temporary_name, dir_fd=directory_fd)
raise
finally:
with suppress(OSError):
os.close(directory_fd)
def _validate_configuration_result(
result: dict[str, object],
*,
trusted_descriptor: ProjectDescriptor | None = None,
) -> None:
artifact = cast(dict[str, object], result["artifact"])
binding = cast(dict[str, object], result["binding"])
policy = cast(dict[str, object], result["effective_policy"])
projection_policy = cast(dict[str, object], result["projection_policy"])
projection_availability = cast(
dict[str, object],
result["projection_availability"],
)
project = cast(dict[str, object], result["project"])
if trusted_descriptor is None:
try:
bound_descriptor = Project.open(cast(str, project["project_root"])).descriptor
except (DocForgeError, KeyError, TypeError) as error:
raise AssertionError(
"Generated client project binding cannot be independently validated"
) from error
else:
bound_descriptor = trusted_descriptor
if (
project["project_id"] != bound_descriptor.project_id
or project["project_root"] != str(bound_descriptor.root)
or project["project_root_fingerprint"] != project_root_fingerprint(bound_descriptor.root)
or project["adapter"] != bound_descriptor.adapter
or project["descriptor_hash"] != bound_descriptor.descriptor_hash
):
raise AssertionError("Generated client project binding drifted")
content = cast(str, artifact["content"])
if artifact["content_sha256"] != hashlib.sha256(content.encode("utf-8")).hexdigest():
raise AssertionError("Generated client content hash drifted")
timeouts = cast(dict[str, object], binding["timeouts"])
expected_format, expected_content, _ = _artifact(
cast(ClientName, result["client"]),
server_name=cast(str, result["server_name"]),
command=cast(str, binding["command"]),
arguments=cast(list[str], binding["args"]),
startup_timeout=cast(int, timeouts["startup_seconds"]),
tool_timeout=cast(int, timeouts["tool_seconds"]),
)
if artifact["format"] != expected_format or content != expected_content:
raise AssertionError("Generated client artifact drifted from its binding")
adapter_policy = cast(dict[str, object], binding["adapter_policy"])
render_policy = cast(dict[str, object], binding["render_policy"])
arguments = cast(list[str], binding["args"])
prefix = [
"-I",
"-m",
"docforge.mcp_server",
"--project-root",
cast(str, project["project_root"]),
"--capability-mode",
cast(str, binding["capability_mode"]),
]
if arguments[:7] != prefix:
raise AssertionError("Generated client arguments drifted from their binding")
remaining = arguments[7:]
no_ast_argument = "--no-ast" in arguments
if no_ast_argument:
if remaining[-1:] != ["--no-ast"] or arguments.count("--no-ast") != 1:
raise AssertionError("Generated no-AST argument layout drifted")
remaining = remaining[:-1]
projection_arguments: dict[str, str] = {}
authority_arguments: list[str] = []
position = 0
projection_options = {
"--manual-render-policy": "manual",
"--portable-graph-policy": "portable_graph",
"--live-viewer-policy": "live_viewer",
}
while position < len(remaining):
option = remaining[position]
field = projection_options.get(option)
if field is None:
authority_arguments.append(option)
position += 1
continue
if position + 1 >= len(remaining) or option in projection_arguments:
raise AssertionError("Generated projection policy argument layout drifted")
value = remaining[position + 1]
projection_arguments[option] = value
if projection_policy[field] != value:
raise AssertionError("Generated projection policy argument drifted")
position += 2
remaining = authority_arguments
mode = binding["capability_mode"]
if (
(mode == "read" and remaining)
or (
mode == "proposal"
and (len(remaining) != 2 or remaining[0] != "--proposal-writer" or not remaining[1])
)
or (
mode == "application"
and (
len(remaining) != 4
or remaining[0] != "--proposal-writer"
or remaining[2] != "--canonical-applier"
or not remaining[1]
or remaining[1] != remaining[3]
)
)
):
raise AssertionError("Generated authority argument layout drifted")
expected_projection_policy = compose_projection_policy(
manual=projection_arguments.get("--manual-render-policy"),
portable_graph=projection_arguments.get("--portable-graph-policy"),
live_viewer=projection_arguments.get("--live-viewer-policy"),
manual_configured=cast(bool, projection_availability["manual_configured"]),
portable_graph_configured=cast(
bool,
projection_availability["portable_graph_configured"],
),
application_enabled=cast(
bool,
projection_availability["application_enabled"],
),
live_viewer_available=cast(
bool,
projection_availability["live_viewer_available"],
),
)
if (
projection_policy != expected_projection_policy.as_dict()
or projection_availability["manual_configured"] != (render_policy["manual"] != "disabled")
or projection_availability["manual_configured"] != (bound_descriptor.render is not None)
or projection_availability["portable_graph_configured"]
!= (bound_descriptor.graph_render is not None)
or projection_availability["application_enabled"] != (mode == "application")
or projection_availability["live_viewer_available"] is not True
):
raise AssertionError("Generated projection policy drifted from its availability")
composed_policy = compose_effective_policy(
selected_mode=cast(CapabilityMode, mode),
capability_source="explicit",
no_ast=adapter_policy["mode"] == "preserve-no-ast",
diagnostics=False,
render_configured=render_policy["manual"] != "disabled",
application_enabled=mode == "application",
)
expected_policy = composed_policy.as_dict()
if (
policy != expected_policy
or adapter_policy != composed_policy.adapter_policy()
or binding["capability_mode"] != policy["capability_mode"]
or no_ast_argument != (adapter_policy["mode"] == "preserve-no-ast")
or render_policy["manual"] != policy["manual_render"]
or render_policy["graph"] != policy["graph_render"]
or render_policy["live_viewer"] != policy["live_viewer"]
or (
adapter_policy["mode"] == "preserve-no-ast"
and (
policy["adapter_evolution"] != "preserve"
or policy["ast_analysis"] != "forbidden"
or policy["logic_indexing"] != "off"
)
)
or (
adapter_policy["mode"] == "standard"
and (
policy["adapter_evolution"] != "allowed"
or policy["ast_analysis"] != "allowed"
or policy["logic_indexing"] != "full"
)
)
):
raise AssertionError("Generated client policy drifted from its binding")
if (
result["projection_policy_hash"]
!= hashlib.sha256(
json.dumps(
projection_policy,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
).hexdigest()
):
raise AssertionError("Generated projection policy hash drifted")
expected_hash = document_hash(
{
"schema_version": 1,
"client": result["client"],
"server_name": result["server_name"],
"project": project,
"binding": binding,
"effective_policy": policy,
"projection_policy": projection_policy,
"projection_policy_hash": result["projection_policy_hash"],
"projection_availability": projection_availability,
"artifact_format": artifact["format"],
"artifact_content_sha256": artifact["content_sha256"],
}
)
if result["configuration_hash"] != expected_hash:
raise AssertionError("Generated client configuration hash drifted")
def generate_client_configuration(
project: ProjectService,
client: str,
*,
server_name: str | None = None,
capability_mode: str = "read",
proposal_writer: str | None = None,
canonical_applier: str | None = None,
no_ast: bool = False,
manual_render_policy: str | None = None,
portable_graph_policy: str | None = None,
live_viewer_policy: str | None = None,
startup_timeout: int = 30,
tool_timeout: int = 300,
output: Path | None = None,
) -> dict[str, object]:
"""Build one deterministic client fragment and optionally publish it explicitly."""
validate_descriptor_binding(project.descriptor)
selected_client = _client_name(client)
selected_mode = _capability_mode(capability_mode)
startup_seconds = _bounded_seconds(
startup_timeout,
field="startup_timeout",
maximum=3_600,
)
tool_seconds = _bounded_seconds(
tool_timeout,
field="tool_timeout",
maximum=86_400,
)
descriptor = project.descriptor
if descriptor.adapter != "generic":
raise DocForgeError(
"client_configuration_unavailable",
"Generic CLI configuration cannot reconstruct a project-owned adapter",
adapter=descriptor.adapter,
)
writer_ids = {writer.writer_id for writer in descriptor.proposal_writers}
if selected_mode == "read":
if proposal_writer is not None or canonical_applier is not None:
raise DocForgeError(
"invalid_capability_binding",
"Read configuration cannot bind proposal or application authority",
)
elif selected_mode == "proposal":
if proposal_writer is None or proposal_writer not in writer_ids:
raise DocForgeError(
"capability_unavailable",
"Proposal configuration requires a descriptor-declared writer",
required="proposal_writer",
)
if canonical_applier is not None:
raise DocForgeError(
"invalid_capability_binding",
"Proposal configuration cannot bind a canonical applier",
)
else:
if (
proposal_writer is None
or canonical_applier is None
or proposal_writer != canonical_applier
or proposal_writer not in writer_ids
):
raise DocForgeError(
"capability_unavailable",
"Application configuration requires one declared writer/applier identity",
required="matching_declared_writer_and_applier",
)
fingerprint = project_root_fingerprint(descriptor.root)
selected_name = _validated_server_name(
server_name,
project_id=descriptor.project_id,
fingerprint=fingerprint,
)
executable = Path(os.path.abspath(sys.executable))
try:
executable_status = executable.stat()
except OSError as error:
raise DocForgeError(
"client_configuration_unavailable",
"Current Python executable cannot be inspected",
) from error
if not stat.S_ISREG(executable_status.st_mode) or not os.access(executable, os.X_OK):
raise DocForgeError(
"client_configuration_unavailable",
"Current Python executable is not a runnable regular file",
)
try:
probe = subprocess.run(
[
str(executable),
"-I",
"-B",
"-c",
"import docforge.mcp_server",
],
check=False,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=10,
env=os.environ.copy(),
)
except (OSError, subprocess.SubprocessError) as error:
raise DocForgeError(
"client_configuration_unavailable",
"Current isolated Python executable could not be probed safely",
) from error
if probe.returncode != 0:
raise DocForgeError(
"client_configuration_unavailable",
"Current isolated Python executable cannot import docforge.mcp_server",
)
arguments = [
"-I",
"-m",
"docforge.mcp_server",
"--project-root",
str(descriptor.root),
"--capability-mode",
selected_mode,
]
if proposal_writer is not None:
arguments.extend(("--proposal-writer", proposal_writer))
if canonical_applier is not None:
arguments.extend(("--canonical-applier", canonical_applier))
policy = compose_effective_policy(
selected_mode=selected_mode,
capability_source="explicit",
no_ast=no_ast,
diagnostics=False,
render_configured=descriptor.render is not None,
application_enabled=canonical_applier is not None,
)
projection_policy = compose_projection_policy(
manual=manual_render_policy,
portable_graph=portable_graph_policy,
live_viewer=live_viewer_policy,
manual_configured=descriptor.render is not None,
portable_graph_configured=descriptor.graph_render is not None,
application_enabled=canonical_applier is not None,
)
default_projection_policy = compose_projection_policy(
manual_configured=descriptor.render is not None,
portable_graph_configured=descriptor.graph_render is not None,
application_enabled=canonical_applier is not None,
)
for option, selected, default in (
(
"--manual-render-policy",
projection_policy.manual,
default_projection_policy.manual,
),
(
"--portable-graph-policy",
projection_policy.portable_graph,
default_projection_policy.portable_graph,
),
(
"--live-viewer-policy",
projection_policy.live_viewer,
default_projection_policy.live_viewer,
),
):
if selected != default:
arguments.extend((option, selected))
if no_ast:
arguments.append("--no-ast")
artifact_format, content, warning = _artifact(
selected_client,
server_name=selected_name,
command=str(executable),
arguments=arguments,
startup_timeout=startup_seconds,
tool_timeout=tool_seconds,
)
if output is None:
validate_descriptor_binding(descriptor)
write_state = "not_requested"
durability = "not_applicable"
publication_warning = None
output_path = None
else:
validate_descriptor_binding(descriptor)
write_state, durability, publication_warning, published_path = _atomic_write(
output,
content,
validate_binding=lambda: validate_descriptor_binding(descriptor),
)
output_path = str(published_path) if published_path is not None else None
artifact = {
"format": artifact_format,
"content": content,
"content_sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
"output_path": output_path,
"write_state": write_state,
"durability": durability,
}
binding = {
"transport": "stdio",
"capability_mode": selected_mode,
"adapter_policy": policy.adapter_policy(),
"render_policy": {
"manual": policy.manual_render,
"graph": policy.graph_render,
"live_viewer": policy.live_viewer,
},
"command": str(executable),
"args": arguments,
"environment": {},
"timeouts": {
"startup_seconds": startup_seconds,
"tool_seconds": tool_seconds,
},
}
project_binding = {
"project_id": descriptor.project_id,
"project_root": str(descriptor.root),
"project_root_fingerprint": fingerprint,
"adapter": descriptor.adapter,
"descriptor_hash": descriptor.descriptor_hash,
}
policy_payload = policy.as_dict()
plan_hash = document_hash(
{
"schema_version": 1,
"client": selected_client,
"server_name": selected_name,
"project": project_binding,
"binding": binding,
"effective_policy": policy_payload,
"projection_policy": projection_policy.as_dict(),
"projection_policy_hash": projection_policy.policy_hash,
"projection_availability": {
"manual_configured": descriptor.render is not None,
"portable_graph_configured": descriptor.graph_render is not None,
"application_enabled": canonical_applier is not None,
"live_viewer_available": True,
},
"artifact_format": artifact_format,
"artifact_content_sha256": artifact["content_sha256"],
}
)
result: dict[str, object] = {
"status": "ok",
"schema_version": 1,
"operation": "client.configure",
"action": "write" if output is not None else "preview",
"client": selected_client,
"server_name": selected_name,
"project": project_binding,
"binding": binding,
"effective_policy": policy_payload,
"projection_policy": projection_policy.as_dict(),
"projection_policy_hash": projection_policy.policy_hash,
"projection_availability": {
"manual_configured": descriptor.render is not None,
"portable_graph_configured": descriptor.graph_render is not None,
"application_enabled": canonical_applier is not None,
"live_viewer_available": True,
},
"artifact": artifact,
"configuration_hash": plan_hash,
"warnings": [
*([] if warning is None else [{"code": "timeout_format_unverified"}]),
*([] if publication_warning is None else [{"code": publication_warning}]),
],
}
_validate_configuration_result(result, trusted_descriptor=descriptor)
return result
def generate_adapter_client_configuration(
project: ProjectService,
launcher: AdapterLauncherV1,
client: str,
*,
server_name: str | None = None,
capability_mode: str = "read",
proposal_writer: str | None = None,
canonical_applier: str | None = None,
no_ast: bool = False,
manual_render_policy: str | None = None,
portable_graph_policy: str | None = None,
live_viewer_policy: str | None = None,
startup_timeout: int = 30,
tool_timeout: int = 300,
output: Path | None = None,
) -> dict[str, object]:
"""Generate one client fragment for an explicitly constructed project-owned adapter."""
validate_adapter_launcher(project, launcher)
selected_client = _client_name(client)
selected_mode = _capability_mode(capability_mode)
startup_seconds = _bounded_seconds(
startup_timeout,
field="startup_timeout",
maximum=3_600,
)
tool_seconds = _bounded_seconds(
tool_timeout,
field="tool_timeout",
maximum=86_400,
)
descriptor = project.descriptor
_validate_adapter_authority(
descriptor,
selected_mode=selected_mode,
proposal_writer=proposal_writer,
canonical_applier=canonical_applier,
)
source_availability = adapter_source_availability(project, launcher)
fingerprint = project_root_fingerprint(descriptor.root)
selected_name = _validated_server_name(
server_name,
project_id=descriptor.project_id,
fingerprint=fingerprint,
)
executable = Path(os.path.abspath(sys.executable))
try:
executable_status = executable.stat()
except OSError as error:
raise DocForgeError(
"client_configuration_unavailable",
"Current Python executable cannot be inspected",
) from error
if not stat.S_ISREG(executable_status.st_mode) or not os.access(executable, os.X_OK):
raise DocForgeError(
"client_configuration_unavailable",
"Current Python executable is not a runnable regular file",
)
policy = compose_effective_policy(
selected_mode=selected_mode,
capability_source="explicit",
no_ast=no_ast,
diagnostics=False,
render_configured=descriptor.render is not None,
application_enabled=canonical_applier is not None,
)
projection_policy = compose_projection_policy(
manual=manual_render_policy,
portable_graph=portable_graph_policy,
live_viewer=live_viewer_policy,
manual_configured=descriptor.render is not None,
portable_graph_configured=descriptor.graph_render is not None,
application_enabled=canonical_applier is not None,
)
default_projection_policy = compose_projection_policy(
manual_configured=descriptor.render is not None,
portable_graph_configured=descriptor.graph_render is not None,
application_enabled=canonical_applier is not None,
)
arguments = [
"-I",
"-m",
launcher.module,
"--project-root",
str(descriptor.root),
"--capability-mode",
selected_mode,
]
if proposal_writer is not None:
arguments.extend(("--proposal-writer", proposal_writer))
if canonical_applier is not None:
arguments.extend(("--canonical-applier", canonical_applier))
for option, selected, default in (
(
"--manual-render-policy",
projection_policy.manual,
default_projection_policy.manual,
),
(
"--portable-graph-policy",
projection_policy.portable_graph,
default_projection_policy.portable_graph,
),
(
"--live-viewer-policy",
projection_policy.live_viewer,
default_projection_policy.live_viewer,
),
):
if selected != default:
arguments.extend((option, selected))
if no_ast:
arguments.append("--no-ast")
artifact_format, content, warning = _artifact(
selected_client,
server_name=selected_name,
command=str(executable),
arguments=arguments,
startup_timeout=startup_seconds,
tool_timeout=tool_seconds,
)
def validate_current_binding() -> None:
validate_adapter_launcher(project, launcher)
current = adapter_source_availability(project, launcher)
if current != source_availability:
raise DocForgeError(
"source_changed",
"Adapter sources changed during client configuration generation",
)
if output is None:
validate_current_binding()
write_state = "not_requested"
durability = "not_applicable"
publication_warning = None
output_path = None
else:
validate_current_binding()
write_state, durability, publication_warning, published_path = _atomic_write(
output,
content,
validate_binding=validate_current_binding,
)
output_path = str(published_path) if published_path is not None else None
artifact = {
"format": artifact_format,
"content": content,
"content_sha256": hashlib.sha256(content.encode("utf-8")).hexdigest(),
"output_path": output_path,
"write_state": write_state,
"durability": durability,
}
binding = {
"transport": "stdio",
"capability_mode": selected_mode,
"adapter_policy": policy.adapter_policy(),
"render_policy": {
"manual": policy.manual_render,
"graph": policy.graph_render,
"live_viewer": policy.live_viewer,
},
"command": str(executable),
"args": arguments,
"environment": {},
"timeouts": {
"startup_seconds": startup_seconds,
"tool_seconds": tool_seconds,
},
"launcher_hash": launcher.launcher_hash,
}
project_binding = {
"project_id": descriptor.project_id,
"project_root": str(descriptor.root),
"project_root_fingerprint": fingerprint,
"adapter": descriptor.adapter,
"descriptor_hash": descriptor.descriptor_hash,
}
projection_availability = {
"manual_configured": descriptor.render is not None,
"portable_graph_configured": descriptor.graph_render is not None,
"application_enabled": canonical_applier is not None,
"live_viewer_available": True,
}
policy_payload = policy.as_dict()
result: dict[str, object] = {
"status": "ok",
"schema_version": 1,
"operation": "adapter_client.configure",
"action": "write" if output is not None else "preview",
"client": selected_client,
"server_name": selected_name,
"project": project_binding,
"launcher": launcher.as_dict(),
"launcher_hash": launcher.launcher_hash,
"source_availability": source_availability.as_dict(),
"source_availability_hash": source_availability.availability_hash,
"binding": binding,
"effective_policy": policy_payload,
"projection_policy": projection_policy.as_dict(),
"projection_policy_hash": projection_policy.policy_hash,
"projection_availability": projection_availability,
"artifact": artifact,
"warnings": [
*([] if warning is None else [{"code": "timeout_format_unverified"}]),
*([] if publication_warning is None else [{"code": publication_warning}]),
],
}
result["configuration_hash"] = document_hash(_adapter_configuration_hash_payload(result))
_validate_adapter_configuration_result(
result,
project=project,
launcher=launcher,
source_availability=source_availability,
)
return result
def _validate_adapter_authority(
descriptor: ProjectDescriptor,
*,
selected_mode: CapabilityMode,
proposal_writer: str | None,
canonical_applier: str | None,
) -> None:
writer_ids = {writer.writer_id for writer in descriptor.proposal_writers}
if selected_mode == "read":
if proposal_writer is not None or canonical_applier is not None:
raise DocForgeError(
"invalid_capability_binding",
"Read configuration cannot bind proposal or application authority",
)
elif selected_mode == "proposal":
if proposal_writer is None or proposal_writer not in writer_ids:
raise DocForgeError(
"capability_unavailable",
"Proposal configuration requires a descriptor-declared writer",
required="proposal_writer",
)
if canonical_applier is not None:
raise DocForgeError(
"invalid_capability_binding",
"Proposal configuration cannot bind a canonical applier",
)
elif (
proposal_writer is None
or canonical_applier is None
or proposal_writer != canonical_applier
or proposal_writer not in writer_ids
):
raise DocForgeError(
"capability_unavailable",
"Application configuration requires one declared writer/applier identity",
required="matching_declared_writer_and_applier",
)
def _adapter_configuration_hash_payload(result: dict[str, object]) -> dict[str, object]:
artifact = cast(dict[str, object], result["artifact"])
return {
"schema_version": 1,
"client": result["client"],
"server_name": result["server_name"],
"project": result["project"],
"launcher": result["launcher"],
"launcher_hash": result["launcher_hash"],
"source_availability": result["source_availability"],
"source_availability_hash": result["source_availability_hash"],
"binding": result["binding"],
"effective_policy": result["effective_policy"],
"projection_policy": result["projection_policy"],
"projection_policy_hash": result["projection_policy_hash"],
"projection_availability": result["projection_availability"],
"artifact_format": artifact["format"],
"artifact_content_sha256": artifact["content_sha256"],
}
def _validate_adapter_configuration_result(
result: dict[str, object],
*,
project: ProjectService,
launcher: AdapterLauncherV1,
source_availability: AdapterSourceAvailabilityV1,
) -> None:
validate_adapter_launcher(project, launcher)
descriptor = project.descriptor
project_binding = cast(dict[str, object], result["project"])
if (
result["launcher"] != launcher.as_dict()
or result["launcher_hash"] != launcher.launcher_hash
or result["source_availability"] != source_availability.as_dict()
or result["source_availability_hash"] != source_availability.availability_hash
or project_binding
!= {
"project_id": descriptor.project_id,
"project_root": str(descriptor.root),
"project_root_fingerprint": project_root_fingerprint(descriptor.root),
"adapter": descriptor.adapter,
"descriptor_hash": descriptor.descriptor_hash,
}
):
raise AssertionError("Generated adapter client identity drifted")
artifact = cast(dict[str, object], result["artifact"])
binding = cast(dict[str, object], result["binding"])
content = cast(str, artifact["content"])
if artifact["content_sha256"] != hashlib.sha256(content.encode("utf-8")).hexdigest():
raise AssertionError("Generated adapter client content hash drifted")
timeouts = cast(dict[str, object], binding["timeouts"])
expected_format, expected_content, _ = _artifact(
cast(ClientName, result["client"]),
server_name=cast(str, result["server_name"]),
command=cast(str, binding["command"]),
arguments=cast(list[str], binding["args"]),
startup_timeout=cast(int, timeouts["startup_seconds"]),
tool_timeout=cast(int, timeouts["tool_seconds"]),
)
if artifact["format"] != expected_format or content != expected_content:
raise AssertionError("Generated adapter client artifact drifted from its binding")
if binding["environment"] != {} or binding["launcher_hash"] != launcher.launcher_hash:
raise AssertionError("Generated adapter launch boundary drifted")
adapter_policy = cast(dict[str, object], binding["adapter_policy"])
render_policy = cast(dict[str, object], binding["render_policy"])
projection_policy = cast(dict[str, object], result["projection_policy"])
projection_availability = cast(dict[str, object], result["projection_availability"])
arguments = cast(list[str], binding["args"])
prefix = [
"-I",
"-m",
launcher.module,
"--project-root",
str(descriptor.root),
"--capability-mode",
cast(str, binding["capability_mode"]),
]
if arguments[:7] != prefix:
raise AssertionError("Generated adapter client arguments drifted from their binding")
remaining = arguments[7:]
no_ast_argument = "--no-ast" in arguments
if no_ast_argument:
if remaining[-1:] != ["--no-ast"] or arguments.count("--no-ast") != 1:
raise AssertionError("Generated adapter no-AST argument layout drifted")
remaining = remaining[:-1]
projection_arguments: dict[str, str] = {}
authority_arguments: list[str] = []
projection_options = {
"--manual-render-policy": "manual",
"--portable-graph-policy": "portable_graph",
"--live-viewer-policy": "live_viewer",
}
position = 0
while position < len(remaining):
option = remaining[position]
field = projection_options.get(option)
if field is None:
authority_arguments.append(option)
position += 1
continue
if position + 1 >= len(remaining) or option in projection_arguments:
raise AssertionError("Generated adapter projection argument layout drifted")
value = remaining[position + 1]
projection_arguments[option] = value
if projection_policy[field] != value:
raise AssertionError("Generated adapter projection policy argument drifted")
position += 2
mode = binding["capability_mode"]
if (
(mode == "read" and authority_arguments)
or (
mode == "proposal"
and (
len(authority_arguments) != 2
or authority_arguments[0] != "--proposal-writer"
or not authority_arguments[1]
)
)
or (
mode == "application"
and (
len(authority_arguments) != 4
or authority_arguments[0] != "--proposal-writer"
or authority_arguments[2] != "--canonical-applier"
or not authority_arguments[1]
or authority_arguments[1] != authority_arguments[3]
)
)
):
raise AssertionError("Generated adapter authority argument layout drifted")
expected_projection_policy = compose_projection_policy(
manual=projection_arguments.get("--manual-render-policy"),
portable_graph=projection_arguments.get("--portable-graph-policy"),
live_viewer=projection_arguments.get("--live-viewer-policy"),
manual_configured=cast(bool, projection_availability["manual_configured"]),
portable_graph_configured=cast(
bool,
projection_availability["portable_graph_configured"],
),
application_enabled=cast(
bool,
projection_availability["application_enabled"],
),
live_viewer_available=cast(
bool,
projection_availability["live_viewer_available"],
),
)
policy = cast(dict[str, object], result["effective_policy"])
composed_policy = compose_effective_policy(
selected_mode=cast(CapabilityMode, mode),
capability_source="explicit",
no_ast=adapter_policy["mode"] == "preserve-no-ast",
diagnostics=False,
render_configured=render_policy["manual"] != "disabled",
application_enabled=mode == "application",
)
if (
projection_policy != expected_projection_policy.as_dict()
or projection_availability["manual_configured"] != (descriptor.render is not None)
or projection_availability["portable_graph_configured"]
!= (descriptor.graph_render is not None)
or projection_availability["application_enabled"] != (mode == "application")
or projection_availability["live_viewer_available"] is not True
or policy != composed_policy.as_dict()
or adapter_policy != composed_policy.adapter_policy()
or no_ast_argument != (adapter_policy["mode"] == "preserve-no-ast")
or binding["launcher_hash"] != result["launcher_hash"]
):
raise AssertionError("Generated adapter client policy drifted")
if result["projection_policy_hash"] != hashlib.sha256(
json.dumps(
projection_policy,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
).hexdigest() or result["configuration_hash"] != document_hash(
_adapter_configuration_hash_payload(result)
):
raise AssertionError("Generated adapter client hash drifted")