1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Add project-owned adapter client launchers

This commit is contained in:
Andraxion 2026-07-29 14:33:42 -04:00
parent 0d498fc385
commit cb52bf8ae6
5 changed files with 1441 additions and 0 deletions

View file

@ -16,6 +16,12 @@ 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
@ -1079,3 +1085,450 @@ def generate_client_configuration(
}
_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")