Complete independent projection runtime
This commit is contained in:
parent
1134c2d375
commit
f1fabaf0ca
38 changed files with 4907 additions and 87 deletions
|
|
@ -18,9 +18,10 @@ from typing import Literal, cast
|
|||
|
||||
from .changeset_contract import document_hash
|
||||
from .errors import DocForgeError
|
||||
from .models import ProjectService
|
||||
from .models import ProjectDescriptor, ProjectService
|
||||
from .policy import CapabilityMode, compose_effective_policy
|
||||
from .project import project_root_fingerprint, validate_descriptor_binding
|
||||
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")
|
||||
|
|
@ -605,11 +606,37 @@ def _atomic_write(
|
|||
os.close(directory_fd)
|
||||
|
||||
|
||||
def _validate_configuration_result(result: dict[str, object]) -> None:
|
||||
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")
|
||||
|
|
@ -644,6 +671,29 @@ def _validate_configuration_result(result: dict[str, object]) -> None:
|
|||
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)
|
||||
|
|
@ -663,6 +713,34 @@ def _validate_configuration_result(result: dict[str, object]) -> None:
|
|||
)
|
||||
):
|
||||
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",
|
||||
|
|
@ -698,6 +776,18 @@ def _validate_configuration_result(result: dict[str, object]) -> None:
|
|||
)
|
||||
):
|
||||
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,
|
||||
|
|
@ -706,6 +796,9 @@ def _validate_configuration_result(result: dict[str, object]) -> None:
|
|||
"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"],
|
||||
}
|
||||
|
|
@ -723,6 +816,9 @@ def generate_client_configuration(
|
|||
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,
|
||||
|
|
@ -839,9 +935,6 @@ def generate_client_configuration(
|
|||
arguments.extend(("--proposal-writer", proposal_writer))
|
||||
if canonical_applier is not None:
|
||||
arguments.extend(("--canonical-applier", canonical_applier))
|
||||
if no_ast:
|
||||
arguments.append("--no-ast")
|
||||
|
||||
policy = compose_effective_policy(
|
||||
selected_mode=selected_mode,
|
||||
capability_source="explicit",
|
||||
|
|
@ -850,6 +943,40 @@ def generate_client_configuration(
|
|||
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,
|
||||
|
|
@ -902,6 +1029,7 @@ def generate_client_configuration(
|
|||
"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(
|
||||
|
|
@ -912,6 +1040,14 @@ def generate_client_configuration(
|
|||
"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"],
|
||||
}
|
||||
|
|
@ -926,6 +1062,14 @@ def generate_client_configuration(
|
|||
"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": [
|
||||
|
|
@ -933,5 +1077,5 @@ def generate_client_configuration(
|
|||
*([] if publication_warning is None else [{"code": publication_warning}]),
|
||||
],
|
||||
}
|
||||
_validate_configuration_result(result)
|
||||
_validate_configuration_result(result, trusted_descriptor=descriptor)
|
||||
return result
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue