From 864e3e29433093c9745ad39f7424e6ed4e82a905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Fri, 28 Aug 2026 20:53:32 +0800 Subject: [PATCH 1/8] fix: ship DevKit tools with required MCP metadata --- .codex-plugin/main-artifact-allowlist.json | 3 + .../marketplace-artifact-allowlist.json | 9 +- README.md | 11 ++- README.zh-CN.md | 7 +- mcp-tools/devkit_runtime/tool_metadata.py | 27 +++++ mcp-tools/devkit_runtime/tool_result.py | 28 ++---- mcp-tools/pyproject.toml | 1 + mcp-tools/server.py | 68 ++++++++----- mcp-tools/tests/test_plugin_tool_packaging.py | 98 +++++++++++++++++++ mcp-tools/tests/test_primary_artifact.py | 17 +++- mcp-tools/uv.lock | 57 +++++++++++ skills/code-atlas/agents/openai.yaml | 7 ++ skills/fast-lane-routing/agents/openai.yaml | 7 ++ skills/workflow-design/agents/openai.yaml | 7 ++ 14 files changed, 294 insertions(+), 53 deletions(-) create mode 100644 mcp-tools/devkit_runtime/tool_metadata.py create mode 100644 mcp-tools/tests/test_plugin_tool_packaging.py create mode 100644 skills/code-atlas/agents/openai.yaml create mode 100644 skills/fast-lane-routing/agents/openai.yaml create mode 100644 skills/workflow-design/agents/openai.yaml diff --git a/.codex-plugin/main-artifact-allowlist.json b/.codex-plugin/main-artifact-allowlist.json index ae66b2b..4bb7975 100644 --- a/.codex-plugin/main-artifact-allowlist.json +++ b/.codex-plugin/main-artifact-allowlist.json @@ -23,6 +23,9 @@ "mcp-tools/devkit_fastlane/scripts/team_efficiency.py" ], "trees": [ + "skills/fast-lane-routing", + "skills/code-atlas", + "skills/workflow-design", "mcp-tools/bugkiller", "mcp-tools/devkit_atlas", "mcp-tools/devkit_relay", diff --git a/.codex-plugin/marketplace-artifact-allowlist.json b/.codex-plugin/marketplace-artifact-allowlist.json index 5e3654c..9dee881 100644 --- a/.codex-plugin/marketplace-artifact-allowlist.json +++ b/.codex-plugin/marketplace-artifact-allowlist.json @@ -23,7 +23,14 @@ "mcp-tools/devkit_fastlane/scripts/team_efficiency.py" ], "trees": [ - "skills", + "skills/bugkiller", + "skills/code-atlas", + "skills/devkit-overview", + "skills/fast-lane-routing", + "skills/mcp-server-dev", + "skills/oss-repo-ops", + "skills/python-engineering", + "skills/workflow-design", "mcp-tools/bugkiller", "mcp-tools/devkit_atlas", "mcp-tools/devkit_relay", diff --git a/README.md b/README.md index 0db0b61..753a0bf 100644 --- a/README.md +++ b/README.md @@ -207,11 +207,14 @@ tree. Choose an output directory outside the source tree: The artifact contains the manifest, .mcp.json, LICENSE, the locked Python project, and the runtime files selected by .codex-plugin/main-artifact-allowlist.json. Its executable runtime surface is -the MCP server; the ZIP also carries the Fast Lane contract, required references +the MCP server. The ZIP also carries the Fast Lane contract, required references and policy assets, the `team_efficiency.py` compatibility entry point, its -routing modules. It deliberately excludes the optional Skill manual bundle, -command helpers, hooks, CI files, host-private state, prompts, static agents, -and arbitrary repository files. +routing modules, and the exact `fast-lane-routing`, `code-atlas`, and +`workflow-design` Skill directories whose `agents/openai.yaml` files require +that MCP server. The builder only supports file and directory allowlist roots, +so those three directories are named individually and archive tests reject any +other Skill directory. Command helpers, hooks, CI files, host-private state, +prompts, top-level static agents, and arbitrary repository files remain excluded. Run Fast Lane through its executable entry point to inspect its fail-closed result: diff --git a/README.zh-CN.md b/README.zh-CN.md index c992654..0550350 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -184,8 +184,11 @@ allowlist builder 会在插件源码树之外生成确定性的 ZIP。请选择 产物包含 manifest、.mcp.json、LICENSE、锁定的 Python 项目,以及 .codex-plugin/main-artifact-allowlist.json 选中的运行时文件。它的可执行运行时 表面是 MCP 服务器;ZIP 同时携带 Fast Lane 契约、必需参考资料和策略 assets、 -`team_efficiency.py` 兼容入口及其路由模块。它明确不包含可选的 Skill 说明书 bundle、 -命令辅助文件、hooks、CI 文件、宿主私有状态、prompts、静态 agent 或任意仓库文件。 +`team_efficiency.py` 兼容入口、路由模块,以及明确依赖该 MCP 服务器的 +`fast-lane-routing`、`code-atlas`、`workflow-design` 三个 Skill 目录及其 +`agents/openai.yaml`。builder 的 allowlist 只支持文件或目录根,因此这里逐项 +列出三个目录,并由归档测试拒绝其他 Skill 目录。命令辅助文件、hooks、CI 文件、 +宿主私有状态、prompts、顶层静态 agent 和任意仓库文件仍不进入主产物。 Fast Lane 可通过以下可执行入口检查其 fail-closed 结果: diff --git a/mcp-tools/devkit_runtime/tool_metadata.py b/mcp-tools/devkit_runtime/tool_metadata.py new file mode 100644 index 0000000..95d5e5e --- /dev/null +++ b/mcp-tools/devkit_runtime/tool_metadata.py @@ -0,0 +1,27 @@ +"""Lightweight MCP tool metadata safe to import during server registration.""" + +from typing import Final + +TOOL_ANNOTATIONS: Final[dict[str, tuple[bool, bool, bool, bool]]] = { + "project_index_register": (False, False, True, False), + "project_index_sync": (False, False, True, False), + "project_index_status": (True, False, True, False), + "project_index_query": (False, False, True, False), + "worktree_checkpoint_create": (False, False, True, False), + "worktree_checkpoint_status": (True, False, True, False), + "worktree_checkpoint_restore": (False, True, False, False), + "atlas_query": (True, False, True, False), + "atlas_prepare": (False, False, True, False), + "atlas_render": (True, False, True, False), + "atlas_accept": (False, False, True, False), + "relay_compile": (True, False, True, False), + "fastlane_compile": (True, False, True, False), + "relay_start": (False, False, True, False), + "relay_status": (True, False, True, False), + "relay_handoff": (False, False, False, False), + "relay_integrate": (False, True, False, False), +} +TOOL_ANNOTATION_TABLE = TOOL_ANNOTATIONS + + +__all__ = ["TOOL_ANNOTATIONS", "TOOL_ANNOTATION_TABLE"] diff --git a/mcp-tools/devkit_runtime/tool_result.py b/mcp-tools/devkit_runtime/tool_result.py index 8f762a3..cd3bc61 100644 --- a/mcp-tools/devkit_runtime/tool_result.py +++ b/mcp-tools/devkit_runtime/tool_result.py @@ -32,6 +32,7 @@ from devkit_relay.compiler import RelayPlanError from devkit_relay.service import RelayError from devkit_relay.store import RelayStoreError +from devkit_runtime.tool_metadata import TOOL_ANNOTATIONS from project_index.checkpoints import Checkpoint, RestoreResult from project_index.models import ( CoverageGap, @@ -50,6 +51,7 @@ ) RESULT_SCHEMA: Final = "2718lab-devkit/tool-result-v1" +TOOL_ANNOTATION_TABLE = TOOL_ANNOTATIONS MAX_RESULT_BYTES: Final = 524_288 MAX_STRING_BYTES: Final = 65_536 MAX_LIST_ITEMS: Final = 512 @@ -61,27 +63,6 @@ SUCCESS_KEYS: Final = frozenset({"schema", "ok", "data"}) FAILURE_KEYS: Final = frozenset({"schema", "ok", "error"}) -TOOL_ANNOTATIONS: Final[dict[str, tuple[bool, bool, bool, bool]]] = { - "project_index_register": (False, False, True, False), - "project_index_sync": (False, False, True, False), - "project_index_status": (True, False, True, False), - "project_index_query": (False, False, True, False), - "worktree_checkpoint_create": (False, False, True, False), - "worktree_checkpoint_status": (True, False, True, False), - "worktree_checkpoint_restore": (False, True, False, False), - "atlas_query": (True, False, True, False), - "atlas_prepare": (False, False, True, False), - "atlas_render": (True, False, True, False), - "atlas_accept": (False, False, True, False), - "relay_compile": (True, False, True, False), - "fastlane_compile": (True, False, True, False), - "relay_start": (False, False, True, False), - "relay_status": (True, False, True, False), - "relay_handoff": (False, False, False, False), - "relay_integrate": (False, True, False, False), -} -TOOL_ANNOTATION_TABLE = TOOL_ANNOTATIONS - class ResultContractError(ValueError): """Raised when a value cannot cross the public result boundary.""" @@ -585,7 +566,10 @@ def _package_page_data( public_packages: list[dict[str, object]] = [] for package in packages: candidate = _package_descriptor(package) - if _encoded_size({"packages": [*public_packages, candidate]}) > PACKAGE_PAGE_BYTE_BUDGET: + if ( + _encoded_size({"packages": [*public_packages, candidate]}) + > PACKAGE_PAGE_BYTE_BUDGET + ): break public_packages.append(candidate) if packages and not public_packages: diff --git a/mcp-tools/pyproject.toml b/mcp-tools/pyproject.toml index 32a3f02..c7fc5f1 100644 --- a/mcp-tools/pyproject.toml +++ b/mcp-tools/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ [dependency-groups] dev = [ "pyright>=1.1", + "pyyaml>=6.0", "pytest>=8.0", "ruff>=0.12", ] diff --git a/mcp-tools/server.py b/mcp-tools/server.py index 9e7773f..68e945c 100644 --- a/mcp-tools/server.py +++ b/mcp-tools/server.py @@ -5,29 +5,21 @@ import atexit from collections.abc import Callable, Mapping from pathlib import Path -from typing import Literal, Protocol, TypeVar, cast +from typing import TYPE_CHECKING, Literal, Protocol, TypeVar, cast from mcp.server.fastmcp import FastMCP from mcp.types import ToolAnnotations from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator -from devkit_fastlane import compile_fast_lane -from devkit_relay.compiler import compile_plan -from devkit_relay.service import RelayService -from devkit_runtime.bootstrap import RuntimeBootstrap -from devkit_runtime.composition import RuntimeRoot -from devkit_runtime.config import RuntimeConfig, RuntimeConfigError -from devkit_runtime.relay_runtime import RelayRuntime, RelayRuntimeError -from devkit_runtime.tool_result import ( - TOOL_ANNOTATIONS, - ResultContractError, - envelope_failure, - envelope_success, - result_from_exception, -) -from devkit_runtime.uow import RuntimeUnitOfWork -from project_index.checkpoints import WorkspaceOwnership -from project_index.models import IndexState, IndexStatusResult, IndexSyncResult +from devkit_runtime.tool_metadata import TOOL_ANNOTATIONS + +if TYPE_CHECKING: + from devkit_relay.service import RelayService + from devkit_runtime.composition import RuntimeRoot + from devkit_runtime.config import RuntimeConfigError + from devkit_runtime.relay_runtime import RelayRuntime, RelayRuntimeError + from devkit_runtime.uow import RuntimeUnitOfWork + from project_index.checkpoints import WorkspaceOwnership PLUGIN_ROOT = Path(__file__).resolve().parent.parent mcp = FastMCP(name="2718lab-devkit") @@ -215,6 +207,10 @@ def _tool_annotations(name: str) -> ToolAnnotations: def _default_runtime_root() -> RuntimeRoot: """Bootstrap durable local stores before exposing the default process root.""" + from devkit_runtime.bootstrap import RuntimeBootstrap + from devkit_runtime.composition import RuntimeRoot + from devkit_runtime.config import RuntimeConfig + config = RuntimeConfig.load(protected_roots=(PLUGIN_ROOT,)) required_databases = ( config.orchestrator_database, @@ -239,6 +235,8 @@ def _runtime_root() -> RuntimeRoot: def _install_runtime_root_for_host(root: RuntimeRoot) -> None: """Private embedding seam for a host-injected broker or proof resolver.""" + from devkit_runtime.composition import RuntimeRoot + if not isinstance(root, RuntimeRoot): raise TypeError("root must be a RuntimeRoot") global _RUNTIME_ROOT @@ -265,12 +263,16 @@ def _shutdown_runtime() -> None: def _failure(code: str) -> dict[str, object]: + from devkit_runtime.tool_result import envelope_failure + return envelope_failure(code) def _runtime_failure( error: RuntimeConfigError | RelayRuntimeError, ) -> dict[str, object]: + from devkit_runtime.config import RuntimeConfigError + if isinstance(error, RuntimeConfigError): if error.code in { "DATA_ROOT_INVALID", @@ -301,11 +303,18 @@ def _invoke( return uow.tool_results.project(tool_name, operation(uow)) except _RequestError as error: return _failure(error.code) - except (RuntimeConfigError, RelayRuntimeError) as error: - return _runtime_failure(error) - except ResultContractError: - return _failure("INTERNAL_ERROR") except Exception as error: + from devkit_runtime.config import RuntimeConfigError + from devkit_runtime.relay_runtime import RelayRuntimeError + from devkit_runtime.tool_result import ( + ResultContractError, + result_from_exception, + ) + + if isinstance(error, (RuntimeConfigError, RelayRuntimeError)): + return _runtime_failure(error) + if isinstance(error, ResultContractError): + return _failure("INTERNAL_ERROR") return result_from_exception(error, invalid_code=invalid_code) @@ -430,6 +439,8 @@ def _require_lease_authority() -> _TaskLeaseAuthority: def _workspace_ownership( task_lease: TaskLeaseRef, *, workspace_id: str ) -> WorkspaceOwnership: + from project_index.checkpoints import WorkspaceOwnership + ownership = _require_lease_authority().ownership_for( task_lease, workspace_id=workspace_id ) @@ -446,6 +457,8 @@ def _workspace_ownership( def _relay_runtime(uow: RuntimeUnitOfWork) -> RelayRuntime: + from devkit_runtime.relay_runtime import RelayRuntime + relay = uow.relay if not isinstance(relay, RelayRuntime): raise _RequestError("RELAY_REQUEST_INVALID") @@ -455,6 +468,8 @@ def _relay_runtime(uow: RuntimeUnitOfWork) -> RelayRuntime: def _relay_service(runtime: RelayRuntime) -> RelayService: """Use the lifecycle service already owned by the typed Relay runtime.""" + from devkit_relay.service import RelayService + service = runtime._relay_service if not isinstance(service, RelayService): raise _RequestError("RELAY_REQUEST_INVALID") @@ -498,6 +513,8 @@ def project_index_sync( return _failure(error.code) def operation(uow: RuntimeUnitOfWork) -> object: + from project_index.models import IndexSyncResult + authority = _require_lease_authority() if lease is not None else None snapshot = uow.project_checkpoint.project_index.sync( workspace_id, include_paths @@ -547,6 +564,8 @@ def project_index_status( return _failure(error.code) def operation(uow: RuntimeUnitOfWork) -> object: + from project_index.models import IndexState, IndexStatusResult + status = uow.project_checkpoint.project_index.status( workspace_id, snapshot_id, @@ -788,6 +807,8 @@ def atlas_accept( def relay_compile(request: RelayCompileRequest) -> dict[str, object]: """Compile a locked Relay request using only verified read snapshots.""" + from devkit_relay.compiler import compile_plan + try: payload = _relay_compile_request(request) except _RequestError as error: @@ -817,6 +838,9 @@ def fastlane_compile( if type(request) is not dict or type(enable) is not bool: return _failure("FASTLANE_REQUEST_INVALID") + from devkit_fastlane import compile_fast_lane + from devkit_runtime.tool_result import ResultContractError, envelope_success + try: plan = compile_fast_lane( request, diff --git a/mcp-tools/tests/test_plugin_tool_packaging.py b/mcp-tools/tests/test_plugin_tool_packaging.py new file mode 100644 index 0000000..1c175a6 --- /dev/null +++ b/mcp-tools/tests/test_plugin_tool_packaging.py @@ -0,0 +1,98 @@ +"""Plugin packaging contracts required for first-turn DevKit tool discovery.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +MCP_SKILLS = ("fast-lane-routing", "code-atlas", "workflow-design") +MCP_SKILL_TREES = tuple(f"skills/{name}" for name in MCP_SKILLS) + + +def test_mcp_backed_skills_require_the_local_devkit_server() -> None: + plugin = json.loads( + (ROOT / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8") + ) + mcp_configuration = json.loads((ROOT / ".mcp.json").read_text(encoding="utf-8")) + assert plugin["mcpServers"] == "./.mcp.json" + assert set(mcp_configuration["mcpServers"]) == {"2718lab-devkit"} + + for skill_name in MCP_SKILLS: + metadata = ROOT / "skills" / skill_name / "agents" / "openai.yaml" + assert metadata.is_file(), f"missing Codex metadata for {skill_name}" + document = yaml.safe_load(metadata.read_text(encoding="utf-8")) + assert set(document) == {"interface", "dependencies"} + assert set(document["interface"]) == {"display_name", "short_description"} + assert document["dependencies"] == { + "tools": [{"type": "mcp", "value": "2718lab-devkit"}] + } + + +def test_release_and_marketplace_artifacts_include_skills_and_mcp_runtime() -> None: + for allowlist_name in ( + "main-artifact-allowlist.json", + "marketplace-artifact-allowlist.json", + ): + allowlist = json.loads( + (ROOT / ".codex-plugin" / allowlist_name).read_text(encoding="utf-8") + ) + selected = {*allowlist["files"], *allowlist["trees"]} + assert ".mcp.json" in selected + assert "mcp-tools/server.py" in selected + for runtime_tree in ( + "mcp-tools/bugkiller", + "mcp-tools/devkit_atlas", + "mcp-tools/devkit_relay", + "mcp-tools/devkit_runtime", + "mcp-tools/devkit_continuity", + "mcp-tools/orchestrator", + "mcp-tools/project_index", + ): + assert runtime_tree in selected + assert "skills" not in selected + if allowlist_name == "main-artifact-allowlist.json": + assert all(tree in selected for tree in MCP_SKILL_TREES) + assert "skills/bugkiller" not in selected + else: + source_skill_trees = { + f"skills/{path.name}" + for path in (ROOT / "skills").iterdir() + if path.is_dir() + } + assert source_skill_trees <= selected + + +def test_importing_server_registers_tools_without_loading_runtime_stores() -> None: + mcp_root = ROOT / "mcp-tools" + heavy_modules = ( + "devkit_runtime.bootstrap", + "devkit_runtime.composition", + "devkit_runtime.config", + "devkit_runtime.relay_runtime", + "devkit_runtime.uow", + "devkit_relay.compiler", + "devkit_relay.service", + "project_index.checkpoints", + "project_index.models", + ) + probe = ( + "import asyncio,json,sys; import server; " + "tools=asyncio.run(server.mcp.list_tools()); " + f"heavy={heavy_modules!r}; " + "print(json.dumps({'tool_count':len(tools)," + "'loaded':[name for name in heavy if name in sys.modules]}))" + ) + result = subprocess.run( + [sys.executable, "-c", probe], + cwd=mcp_root, + text=True, + capture_output=True, + check=False, + ) + assert result.returncode == 0, result.stderr + assert json.loads(result.stdout) == {"tool_count": 17, "loaded": []} diff --git a/mcp-tools/tests/test_primary_artifact.py b/mcp-tools/tests/test_primary_artifact.py index aef516b..1d90be7 100644 --- a/mcp-tools/tests/test_primary_artifact.py +++ b/mcp-tools/tests/test_primary_artifact.py @@ -47,6 +47,9 @@ "mcp-tools/devkit_fastlane/scripts/team_efficiency.py", ) EXPECTED_TREES = ( + "skills/fast-lane-routing", + "skills/code-atlas", + "skills/workflow-design", "mcp-tools/bugkiller", "mcp-tools/devkit_atlas", "mcp-tools/devkit_relay", @@ -97,7 +100,7 @@ def _copy_fixture(destination: Path) -> Path: excluded = { "mcp-tools/tests/not-runtime.py": "raise AssertionError('not packaged')\n", - "skills/not-primary.txt": "legacy skill\n", + "skills/not-primary.txt": "unselected skill\n", "agents/not-primary.md": "legacy agent\n", "commands/not-primary.md": "legacy command\n", "hooks/not-primary.py": "legacy hook\n", @@ -159,7 +162,7 @@ def _expected_names(plugin_root: Path) -> list[str]: return sorted(names) -def test_primary_allowlist_is_explicit_and_runtime_only() -> None: +def test_primary_allowlist_is_explicit_and_plugin_complete() -> None: allowlist = _load_allowlist() assert set(allowlist) == {"schema", "files", "trees"} @@ -264,6 +267,16 @@ def test_two_builds_are_byte_identical_with_normalized_zip_metadata( assert all((info.external_attr >> 16) & 0o777 == 0o644 for info in infos) assert all("tests/" not in name for name in names) assert all("__pycache__/" not in name for name in names) + allowed_skill_prefixes = ( + "skills/fast-lane-routing/", + "skills/code-atlas/", + "skills/workflow-design/", + ) + skill_members = [name for name in names if name.startswith("skills/")] + assert skill_members + assert all(name.startswith(allowed_skill_prefixes) for name in skill_members) + assert "skills/bugkiller/SKILL.md" not in names + assert "skills/not-primary.txt" not in names def _run_barrier_action( diff --git a/mcp-tools/uv.lock b/mcp-tools/uv.lock index 06e5079..c5bc3a4 100644 --- a/mcp-tools/uv.lock +++ b/mcp-tools/uv.lock @@ -20,6 +20,7 @@ dependencies = [ dev = [ { name = "pyright" }, { name = "pytest" }, + { name = "pyyaml" }, { name = "ruff" }, ] @@ -30,6 +31,7 @@ requires-dist = [{ name = "mcp", extras = ["cli"], specifier = ">=1,<2" }] dev = [ { name = "pyright", specifier = ">=1.1" }, { name = "pytest", specifier = ">=8.0" }, + { name = "pyyaml", specifier = ">=6.0" }, { name = "ruff", specifier = ">=0.12" }, ] @@ -659,6 +661,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, ] +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + [[package]] name = "referencing" version = "0.37.0" diff --git a/skills/code-atlas/agents/openai.yaml b/skills/code-atlas/agents/openai.yaml new file mode 100644 index 0000000..b5f1738 --- /dev/null +++ b/skills/code-atlas/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Code Atlas" + short_description: "Query and prepare verified local code context" +dependencies: + tools: + - type: "mcp" + value: "2718lab-devkit" diff --git a/skills/fast-lane-routing/agents/openai.yaml b/skills/fast-lane-routing/agents/openai.yaml new file mode 100644 index 0000000..54a87cd --- /dev/null +++ b/skills/fast-lane-routing/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Fast Lane Routing" + short_description: "Compile verified DevKit routing contracts" +dependencies: + tools: + - type: "mcp" + value: "2718lab-devkit" diff --git a/skills/workflow-design/agents/openai.yaml b/skills/workflow-design/agents/openai.yaml new file mode 100644 index 0000000..2dda192 --- /dev/null +++ b/skills/workflow-design/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Workflow Design" + short_description: "Design bounded DevKit workflows" +dependencies: + tools: + - type: "mcp" + value: "2718lab-devkit" From 82080a03afde35f195321aeea83ff10bd38c212c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Fri, 28 Aug 2026 22:09:17 +0800 Subject: [PATCH 2/8] feat: bind Fast Lane dispatch to attested host facts --- .../devkit_runtime/fastlane_host_adapter.py | 517 ++++++++++++++++- mcp-tools/devkit_runtime/host_session.py | 48 +- mcp-tools/tests/test_fastlane_host_adapter.py | 549 ++++++++++++++++++ mcp-tools/tests/test_host_session.py | 35 ++ 4 files changed, 1119 insertions(+), 30 deletions(-) diff --git a/mcp-tools/devkit_runtime/fastlane_host_adapter.py b/mcp-tools/devkit_runtime/fastlane_host_adapter.py index 77adeca..57cfb43 100644 --- a/mcp-tools/devkit_runtime/fastlane_host_adapter.py +++ b/mcp-tools/devkit_runtime/fastlane_host_adapter.py @@ -10,13 +10,21 @@ import json import re from collections.abc import Sequence +from dataclasses import dataclass from typing import Final -from .host_session import HostCapabilityFact, HostSession +from .host_session import ( + HostCapabilityFact, + HostRoute, + HostSchedulingFacts, + HostSession, + _CompilerInvocation, +) NO_SAFE_WORK: Final = "NO_SAFE_WORK" _HASH: Final = re.compile(r"sha256:[0-9a-f]{64}\Z") _LABEL: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z") +_PATH_PART: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*\Z") _TRANSFER_ROLES: Final = { "coordinator_to_worker": ("coordinator", "worker"), "worker_to_coordinator": ("worker", "coordinator"), @@ -25,22 +33,111 @@ _SCHEDULER_ROLES: Final = frozenset( {"execution", "verification", "prewarm", "review", "design_probe"} ) +_DISPATCH_MODES: Final = frozenset({"parallel", "serial", "isolated_worktree"}) +_DISPATCH_REQUEST_FIELDS: Final = frozenset({"schema", "action", "assignments"}) +_DISPATCH_ASSIGNMENT_FIELDS: Final = frozenset( + { + "task_id", + "route", + "lease_id", + "lease_epoch", + "task_version", + "assignment_token", + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "worktree_identity", + "worktree_base", + "integration_head", + "predecessor_hash", + "source_plan_hash", + "ledger_epoch", + "active_lease_set_hash", + "dispatch_binding_hash", + } +) +_DISPATCH_ROUTE_FIELDS: Final = frozenset( + { + "model", + "reasoning_effort", + "routing_context_hash", + "routing_result_hash", + "require_explicit_route", + } +) +_MAX_DISPATCH_REQUEST_BYTES: Final = 65_536 + + +@dataclass(frozen=True) +class _HostDispatchRoute: + model: str + reasoning_effort: str + routing_context_hash: str + routing_result_hash: str + require_explicit_route: bool + + +@dataclass(frozen=True) +class _HostDispatchFact: + task_id: str + route: _HostDispatchRoute + lease_id: str + lease_epoch: int + task_version: int + assignment_token: str + write_scope: tuple[str, ...] + concurrency_mode: str + dispatch_order: int + index_context_hash: str + worktree_identity: str + worktree_base: str + integration_head: str + predecessor_hash: str + source_plan_hash: str + ledger_epoch: int + active_lease_set_hash: str + + +@dataclass(frozen=True) +class _PreparedHostFacts: + session: HostSession + evidence: object + capability_facts: tuple[HostCapabilityFact, ...] def prepare_verified_host_facts( session: object, *, capability_facts: Sequence[HostCapabilityFact] | object, -) -> str: + preparation_id: object = None, +) -> _PreparedHostFacts | str: """Accept no public substitute for session-owned compiler evidence.""" + normalized_preparation_id = _label(preparation_id) if ( type(session) is not HostSession or not isinstance(capability_facts, Sequence) or isinstance(capability_facts, (str, bytes, bytearray)) + or normalized_preparation_id is None ): return NO_SAFE_WORK - return NO_SAFE_WORK + try: + scheduling = session.scheduling_facts(tuple(capability_facts)) + if type(scheduling) is not HostSchedulingFacts: + return NO_SAFE_WORK + evidence = session.prepare_compiler_evidence( + preparation_id=normalized_preparation_id + ) + if evidence == NO_SAFE_WORK: + return NO_SAFE_WORK + return _PreparedHostFacts( + session=session, + evidence=evidence, + capability_facts=tuple(capability_facts), + ) + except Exception: + return NO_SAFE_WORK def compile_fast_lane_with_host_facts( @@ -48,11 +145,74 @@ def compile_fast_lane_with_host_facts( *, reasoning_effort: object, verified_host_facts: object, -) -> str: - """Return only ``NO_SAFE_WORK`` at this public, bearer-free boundary.""" +) -> dict[str, object] | str: + """Validate one entire trusted batch and emit an inert dispatch request.""" - del request, reasoning_effort, verified_host_facts - return NO_SAFE_WORK + if type(verified_host_facts) is not _PreparedHostFacts: + return NO_SAFE_WORK + prepared = verified_host_facts + try: + normalized_request = _dispatch_request(request) + if _bounded_json_size(normalized_request) > _MAX_DISPATCH_REQUEST_BYTES: + return NO_SAFE_WORK + request_bytes = _canonical_bytes(normalized_request) + if len(request_bytes) > _MAX_DISPATCH_REQUEST_BYTES: + return NO_SAFE_WORK + material = prepared.session.consume_compiler_evidence(prepared.evidence) + if type(material) is not _CompilerInvocation: + return NO_SAFE_WORK + if ( + type(reasoning_effort) is not str + or material.reasoning_effort != reasoning_effort + or _hash_bytes(request_bytes) != material.request_hash + ): + return NO_SAFE_WORK + facts = _normalized_dispatch_facts(material.dispatch_facts) + fact_mappings = [_dispatch_fact_mapping(fact) for fact in facts] + if normalized_request["assignments"] != fact_mappings: + return NO_SAFE_WORK + if tuple(sorted(fact.route.routing_result_hash for fact in facts)) != tuple( + material.verified_route_result_hashes + ): + return NO_SAFE_WORK + if tuple(sorted(_lease_scope_binding_hash(fact) for fact in facts)) != tuple( + material.verified_lease_scope_bindings + ): + return NO_SAFE_WORK + dispatch_binding_hashes = tuple( + mapping["dispatch_binding_hash"] for mapping in fact_mappings + ) + if dispatch_binding_hashes != material.dispatch_binding_hashes: + return NO_SAFE_WORK + scheduling = prepared.session.scheduling_facts(prepared.capability_facts) + if type(scheduling) is not HostSchedulingFacts: + return NO_SAFE_WORK + attested_routes = set(scheduling.routes) + if any( + HostRoute( + model=fact.route.model, + effort=fact.route.reasoning_effort, + ) + not in attested_routes + for fact in facts + ): + return NO_SAFE_WORK + _validate_batch_fences(facts) + batch: dict[str, object] = { + "schema": "2718lab-devkit/fastlane-host-dispatch-batch-v1", + "action": "dispatch_all", + "selection_authority": "host_attested_compiler", + "llm_choice": False, + "source_plan_hash": facts[0].source_plan_hash, + "ledger_epoch": facts[0].ledger_epoch, + "active_lease_set_hash": facts[0].active_lease_set_hash, + "dispatch_binding_hashes": list(dispatch_binding_hashes), + "assignments": fact_mappings, + } + batch["batch_hash"] = _canonical_hash(batch) + return batch + except Exception: + return NO_SAFE_WORK def project_role_transfer( @@ -135,21 +295,340 @@ def _digest_list(value: object, *, maximum: int) -> list[str] | None: return digests -def _canonical_hash(value: object) -> str: - return ( - "sha256:" - + hashlib.sha256( - json.dumps( - value, - ensure_ascii=False, - sort_keys=True, - separators=(",", ":"), - allow_nan=False, - ).encode("utf-8") - ).hexdigest() +def _dispatch_request(value: object) -> dict[str, object]: + if ( + type(value) is not dict + or len(value) != len(_DISPATCH_REQUEST_FIELDS) + or set(value) != _DISPATCH_REQUEST_FIELDS + ): + raise ValueError("dispatch request is invalid") + assignments = value.get("assignments") + if ( + value.get("schema") != "2718lab-devkit/fastlane-host-dispatch-request-v1" + or value.get("action") != "dispatch_all" + or type(assignments) is not list + or not assignments + or len(assignments) > 16 + ): + raise ValueError("dispatch request is invalid") + return { + "schema": value["schema"], + "action": value["action"], + "assignments": [ + _bounded_dispatch_assignment(assignment) for assignment in assignments + ], + } + + +def _bounded_dispatch_assignment(value: object) -> dict[str, object]: + if ( + type(value) is not dict + or len(value) != len(_DISPATCH_ASSIGNMENT_FIELDS) + or set(value) != _DISPATCH_ASSIGNMENT_FIELDS + ): + raise ValueError("dispatch assignment is invalid") + route = value.get("route") + if ( + type(route) is not dict + or len(route) != len(_DISPATCH_ROUTE_FIELDS) + or set(route) != _DISPATCH_ROUTE_FIELDS + ): + raise ValueError("dispatch route is invalid") + normalized_route = { + "model": _bounded_string(route.get("model"), maximum=128), + "reasoning_effort": _bounded_string(route.get("reasoning_effort"), maximum=128), + "routing_context_hash": _bounded_string( + route.get("routing_context_hash"), maximum=71 + ), + "routing_result_hash": _bounded_string( + route.get("routing_result_hash"), maximum=71 + ), + "require_explicit_route": route.get("require_explicit_route"), + } + if normalized_route["require_explicit_route"] is not True: + raise ValueError("dispatch route is invalid") + write_scope = value.get("write_scope") + if type(write_scope) is not list or not write_scope or len(write_scope) > 32: + raise ValueError("dispatch write scope is invalid") + normalized_scope = list(_canonical_write_scope(tuple(write_scope))) + integer_fields = ( + "lease_epoch", + "task_version", + "dispatch_order", + "ledger_epoch", + ) + if any(type(value.get(field)) is not int for field in integer_fields): + raise ValueError("dispatch integer field is invalid") + if ( + not 0 < value["lease_epoch"] <= 2**63 - 1 + or not 0 <= value["task_version"] <= 2**63 - 1 + or not 0 <= value["dispatch_order"] <= 16 + or not 0 < value["ledger_epoch"] <= 2**63 - 1 + ): + raise ValueError("dispatch integer field is out of bounds") + string_bounds = { + "task_id": 128, + "lease_id": 128, + "assignment_token": 71, + "concurrency_mode": 32, + "index_context_hash": 71, + "worktree_identity": 71, + "worktree_base": 71, + "integration_head": 71, + "predecessor_hash": 71, + "source_plan_hash": 71, + "active_lease_set_hash": 71, + "dispatch_binding_hash": 71, + } + normalized = { + field: _bounded_string(value.get(field), maximum=maximum) + for field, maximum in string_bounds.items() + } + return { + "task_id": normalized["task_id"], + "route": normalized_route, + "lease_id": normalized["lease_id"], + "lease_epoch": value["lease_epoch"], + "task_version": value["task_version"], + "assignment_token": normalized["assignment_token"], + "write_scope": normalized_scope, + "concurrency_mode": normalized["concurrency_mode"], + "dispatch_order": value["dispatch_order"], + "index_context_hash": normalized["index_context_hash"], + "worktree_identity": normalized["worktree_identity"], + "worktree_base": normalized["worktree_base"], + "integration_head": normalized["integration_head"], + "predecessor_hash": normalized["predecessor_hash"], + "source_plan_hash": normalized["source_plan_hash"], + "ledger_epoch": value["ledger_epoch"], + "active_lease_set_hash": normalized["active_lease_set_hash"], + "dispatch_binding_hash": normalized["dispatch_binding_hash"], + } + + +def _bounded_string(value: object, *, maximum: int) -> str: + if type(value) is not str or not value or len(value) > maximum: + raise ValueError("dispatch string field is invalid") + return value + + +def _bounded_json_size(value: object, *, depth: int = 0) -> int: + """Count exact compact-JSON bytes without constructing the whole document.""" + + if depth > 4: + raise ValueError("dispatch request is too deep") + if type(value) is str: + return len( + json.dumps(value, ensure_ascii=False, allow_nan=False).encode("utf-8") + ) + if type(value) is bool: + return 4 if value else 5 + if type(value) is int: + return len(str(value)) + if type(value) is list: + return ( + 2 + + max(0, len(value) - 1) + + sum(_bounded_json_size(item, depth=depth + 1) for item in value) + ) + if type(value) is dict: + return ( + 2 + + max(0, len(value) - 1) + + sum( + _bounded_json_size(key, depth=depth + 1) + + 1 + + _bounded_json_size(item, depth=depth + 1) + for key, item in value.items() + ) + ) + raise ValueError("dispatch request contains an unsupported value") + + +def _normalized_dispatch_facts(value: object) -> tuple[_HostDispatchFact, ...]: + if type(value) is not tuple or not value or len(value) > 16: + raise ValueError("dispatch facts are unavailable") + facts = tuple(_normalized_dispatch_fact(fact) for fact in value) + if len({fact.task_id for fact in facts}) != len(facts): + raise ValueError("dispatch tasks are duplicated") + return facts + + +def _normalized_dispatch_fact(value: object) -> _HostDispatchFact: + if type(value) is not _HostDispatchFact: + raise ValueError("dispatch fact is foreign") + route = value.route + scope = _canonical_write_scope(value.write_scope) + if ( + type(route) is not _HostDispatchRoute + or _label(route.model) is None + or _label(route.reasoning_effort) is None + or route.reasoning_effort == "ultra" + or _digest(route.routing_context_hash) is None + or _digest(route.routing_result_hash) is None + or route.require_explicit_route is not True + or _label(value.task_id) is None + or _label(value.lease_id) is None + or type(value.lease_epoch) is not int + or value.lease_epoch <= 0 + or type(value.task_version) is not int + or value.task_version < 0 + or _digest(value.assignment_token) is None + or value.concurrency_mode not in _DISPATCH_MODES + or type(value.dispatch_order) is not int + or value.dispatch_order < 0 + or (value.concurrency_mode == "serial") != (value.dispatch_order > 0) + or _digest(value.index_context_hash) is None + or _digest(value.worktree_identity) is None + or _digest(value.worktree_base) is None + or _digest(value.integration_head) is None + or _digest(value.predecessor_hash) is None + or _digest(value.source_plan_hash) is None + or type(value.ledger_epoch) is not int + or value.ledger_epoch <= 0 + or _digest(value.active_lease_set_hash) is None + ): + raise ValueError("dispatch fact is invalid") + return _HostDispatchFact(**{**value.__dict__, "write_scope": scope}) + + +def _canonical_write_scope(value: object) -> tuple[str, ...]: + if type(value) is not tuple or not value or len(value) > 32: + raise ValueError("write scope is invalid") + normalized: list[str] = [] + for item in value: + if ( + type(item) is not str + or not item + or len(item) > 256 + or item != item.strip() + or "\\" in item + or item.startswith("/") + ): + raise ValueError("write scope is invalid") + parts = item.split("/") + if any( + _PATH_PART.fullmatch(part) is None + or part in {".", ".."} + or part.endswith((".", " ")) + for part in parts + ): + raise ValueError("write scope is invalid") + normalized.append(item) + if tuple(sorted(normalized)) != tuple(normalized): + raise ValueError("write scope is not canonical") + if len(set(normalized)) != len(normalized): + raise ValueError("write scope is duplicated") + return tuple(normalized) + + +def _dispatch_fact_mapping(value: object) -> dict[str, object]: + fact = _normalized_dispatch_fact(value) + mapping: dict[str, object] = { + "task_id": fact.task_id, + "route": { + "model": fact.route.model, + "reasoning_effort": fact.route.reasoning_effort, + "routing_context_hash": fact.route.routing_context_hash, + "routing_result_hash": fact.route.routing_result_hash, + "require_explicit_route": True, + }, + "lease_id": fact.lease_id, + "lease_epoch": fact.lease_epoch, + "task_version": fact.task_version, + "assignment_token": fact.assignment_token, + "write_scope": list(fact.write_scope), + "concurrency_mode": fact.concurrency_mode, + "dispatch_order": fact.dispatch_order, + "index_context_hash": fact.index_context_hash, + "worktree_identity": fact.worktree_identity, + "worktree_base": fact.worktree_base, + "integration_head": fact.integration_head, + "predecessor_hash": fact.predecessor_hash, + "source_plan_hash": fact.source_plan_hash, + "ledger_epoch": fact.ledger_epoch, + "active_lease_set_hash": fact.active_lease_set_hash, + } + mapping["dispatch_binding_hash"] = _canonical_hash(mapping) + return mapping + + +def _lease_scope_binding_hash(value: object) -> str: + fact = _normalized_dispatch_fact(value) + return _canonical_hash( + { + "task_id": fact.task_id, + "lease_id": fact.lease_id, + "lease_epoch": fact.lease_epoch, + "task_version": fact.task_version, + "assignment_token": fact.assignment_token, + "write_scope": list(fact.write_scope), + "worktree_identity": fact.worktree_identity, + "predecessor_hash": fact.predecessor_hash, + "ledger_epoch": fact.ledger_epoch, + "active_lease_set_hash": fact.active_lease_set_hash, + } ) +def _validate_batch_fences(facts: tuple[_HostDispatchFact, ...]) -> None: + if len({fact.source_plan_hash for fact in facts}) != 1: + raise ValueError("source plans are mixed") + if len({fact.ledger_epoch for fact in facts}) != 1: + raise ValueError("ledger epochs are mixed") + if len({fact.active_lease_set_hash for fact in facts}) != 1: + raise ValueError("active lease sets are mixed") + serial_orders = [ + fact.dispatch_order for fact in facts if fact.concurrency_mode == "serial" + ] + if len(serial_orders) != len(set(serial_orders)): + raise ValueError("serial order is duplicated") + for index, left in enumerate(facts): + for right in facts[index + 1 :]: + if not _scopes_overlap(left.write_scope, right.write_scope): + continue + if left.concurrency_mode == right.concurrency_mode == "serial": + continue + if ( + left.concurrency_mode == right.concurrency_mode == "isolated_worktree" + and left.worktree_identity != right.worktree_identity + ): + continue + raise ValueError("parallel write scopes overlap") + + +def _scopes_overlap(left: tuple[str, ...], right: tuple[str, ...]) -> bool: + for left_item in left: + left_folded = left_item.casefold() + for right_item in right: + right_folded = right_item.casefold() + if ( + left_folded == right_folded + or left_folded.startswith(right_folded + "/") + or right_folded.startswith(left_folded + "/") + ): + return True + return False + + +def _canonical_hash(value: object) -> str: + return _hash_bytes(_canonical_bytes(value)) + + +def _canonical_bytes(value: object) -> bytes: + return json.dumps( + value, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + allow_nan=False, + ).encode("utf-8") + + +def _hash_bytes(value: bytes) -> str: + return "sha256:" + hashlib.sha256(value).hexdigest() + + __all__ = [ "NO_SAFE_WORK", "compile_fast_lane_with_host_facts", diff --git a/mcp-tools/devkit_runtime/host_session.py b/mcp-tools/devkit_runtime/host_session.py index 8e161a4..f258f68 100644 --- a/mcp-tools/devkit_runtime/host_session.py +++ b/mcp-tools/devkit_runtime/host_session.py @@ -145,6 +145,8 @@ class _CompilerInvocationBinding: reasoning_effort: str verified_route_result_hashes: tuple[str, ...] verified_lease_scope_bindings: tuple[str, ...] + dispatch_facts: tuple[object, ...] = () + dispatch_binding_hashes: tuple[str, ...] = () CompilerInvocationResolver: TypeAlias = Callable[ @@ -163,6 +165,8 @@ class _CompilerInvocation: reasoning_effort: str verified_route_result_hashes: tuple[str, ...] verified_lease_scope_bindings: tuple[str, ...] + dispatch_facts: tuple[object, ...] + dispatch_binding_hashes: tuple[str, ...] issued_at: float expires_at: float binding_hash: str @@ -304,6 +308,19 @@ def prepare_compiler_evidence( except Exception: return _NO_SAFE_WORK preparation = _CompilerPreparation() + invocation_binding = { + "preparation_id": preparation_id, + "request_hash": binding.request_hash, + "reasoning_effort": binding.reasoning_effort, + "verified_route_result_hashes": binding.verified_route_result_hashes, + "verified_lease_scope_bindings": binding.verified_lease_scope_bindings, + "issued_at": issued_at, + "expires_at": expires_at, + } + if binding.dispatch_binding_hashes: + invocation_binding["dispatch_binding_hashes"] = ( + binding.dispatch_binding_hashes + ) material = _CompilerInvocation( schema="2718lab-devkit/compiler-invocation-v2", preparation_id=preparation_id, @@ -311,19 +328,11 @@ def prepare_compiler_evidence( reasoning_effort=binding.reasoning_effort, verified_route_result_hashes=binding.verified_route_result_hashes, verified_lease_scope_bindings=binding.verified_lease_scope_bindings, + dispatch_facts=binding.dispatch_facts, + dispatch_binding_hashes=binding.dispatch_binding_hashes, issued_at=issued_at, expires_at=expires_at, - binding_hash=_hash( - { - "preparation_id": preparation_id, - "request_hash": binding.request_hash, - "reasoning_effort": binding.reasoning_effort, - "verified_route_result_hashes": binding.verified_route_result_hashes, - "verified_lease_scope_bindings": binding.verified_lease_scope_bindings, - "issued_at": issued_at, - "expires_at": expires_at, - } - ), + binding_hash=_hash(invocation_binding), ) material_state = _compiler_invocation_state(material) try: @@ -1022,6 +1031,15 @@ def _strict_hash_tuple(value: object) -> bool: ) +def _optional_ordered_hash_tuple(value: object) -> bool: + return ( + type(value) is tuple + and len(value) <= 16 + and all(_is_hash(item) for item in value) + and len(set(value)) == len(value) + ) + + def _normalized_compiler_invocation_binding( value: object, ) -> _CompilerInvocationBinding: @@ -1032,6 +1050,10 @@ def _normalized_compiler_invocation_binding( or value.reasoning_effort not in {"low", "medium", "high", "xhigh", "max"} or not _strict_hash_tuple(value.verified_route_result_hashes) or not _strict_hash_tuple(value.verified_lease_scope_bindings) + or type(value.dispatch_facts) is not tuple + or len(value.dispatch_facts) > 16 + or not _optional_ordered_hash_tuple(value.dispatch_binding_hashes) + or len(value.dispatch_facts) != len(value.dispatch_binding_hashes) ): raise ValueError("compiler invocation binding is invalid") return _CompilerInvocationBinding( @@ -1039,6 +1061,8 @@ def _normalized_compiler_invocation_binding( reasoning_effort=value.reasoning_effort, verified_route_result_hashes=value.verified_route_result_hashes, verified_lease_scope_bindings=value.verified_lease_scope_bindings, + dispatch_facts=value.dispatch_facts, + dispatch_binding_hashes=value.dispatch_binding_hashes, ) @@ -1052,6 +1076,8 @@ def _compiler_invocation_state(value: _CompilerInvocation) -> tuple[object, ...] value.reasoning_effort, value.verified_route_result_hashes, value.verified_lease_scope_bindings, + value.dispatch_facts, + value.dispatch_binding_hashes, value.issued_at, value.expires_at, value.binding_hash, diff --git a/mcp-tools/tests/test_fastlane_host_adapter.py b/mcp-tools/tests/test_fastlane_host_adapter.py index 0fa7bc6..3161eaf 100644 --- a/mcp-tools/tests/test_fastlane_host_adapter.py +++ b/mcp-tools/tests/test_fastlane_host_adapter.py @@ -1,10 +1,15 @@ from __future__ import annotations import ast +import hashlib import importlib import inspect +import json +import os import sys +import threading from collections.abc import Sequence +from dataclasses import replace from pathlib import Path import pytest @@ -22,6 +27,160 @@ def _hash(character: str) -> str: return "sha256:" + character * 64 +def _canonical_hash(value: object) -> str: + return ( + "sha256:" + + hashlib.sha256( + json.dumps( + value, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + allow_nan=False, + ).encode("utf-8") + ).hexdigest() + ) + + +def _pipe_pair() -> tuple[object, object]: + from devkit_runtime.host_bridge import InheritedHandleHostBridge + + child_to_host_read, child_to_host_write = os.pipe() + host_to_child_read, host_to_child_write = os.pipe() + key = b"k" * 32 + nonce = b"fastlane-dispatch-private-nonce" + return ( + InheritedHandleHostBridge.from_file_descriptors( + read_fd=host_to_child_read, + write_fd=child_to_host_write, + session_key=key, + session_nonce=nonce, + ), + InheritedHandleHostBridge.from_file_descriptors( + read_fd=child_to_host_read, + write_fd=host_to_child_write, + session_key=key, + session_nonce=nonce, + ), + ) + + +def _dispatch_fact(adapter: object, *, task: str, scope: str) -> object: + task_hash_character = "a" if task.endswith("a") else "b" + return adapter._HostDispatchFact( + task_id=task, + route=adapter._HostDispatchRoute( + model="gpt-5.6-terra", + reasoning_effort="high", + routing_context_hash=_hash("1"), + routing_result_hash=_hash(task_hash_character), + require_explicit_route=True, + ), + lease_id=f"lease-{task}", + lease_epoch=7, + task_version=3, + assignment_token=_hash("3"), + write_scope=(scope,), + concurrency_mode="parallel", + dispatch_order=0, + index_context_hash=_hash("4"), + worktree_identity=_hash("5"), + worktree_base=_hash("6"), + integration_head=_hash("7"), + predecessor_hash=_hash("8"), + source_plan_hash=_hash("9"), + ledger_epoch=11, + active_lease_set_hash=_hash("a"), + ) + + +def _dispatch_request(adapter: object, facts: tuple[object, ...]) -> dict[str, object]: + return { + "schema": "2718lab-devkit/fastlane-host-dispatch-request-v1", + "action": "dispatch_all", + "assignments": [adapter._dispatch_fact_mapping(fact) for fact in facts], + } + + +def _prepared_dispatch( + adapter: object, + facts: tuple[object, ...], + *, + trusted_facts: tuple[object, ...] | None = None, + trusted_lease_hashes: tuple[str, ...] | None = None, + trusted_dispatch_hashes: tuple[str, ...] | None = None, +) -> tuple[object, object, object]: + import devkit_runtime.host_session as host_session + from devkit_runtime.host_envelopes import EnvelopeBinding + + request = _dispatch_request(adapter, facts) + child, host = _pipe_pair() + binding = EnvelopeBinding( + task_id="task-1", + lease_epoch=7, + assignment_token=_hash("b"), + dispatch_context_hash=_hash("c"), + route_hash=_hash("d"), + expires_at=1_700_000_060, + ) + route = host_session.HostRoute(model="gpt-5.6-terra", effort="high") + resolver_facts = facts if trusted_facts is None else trusted_facts + + def reply() -> None: + probe = host.receive_capability_probe(now=1_700_000_000, expected=binding) + host.send_capability_report( + probe=probe, + capability_hashes={name: _hash("e") for name in probe.capability_names}, + now=1_700_000_000, + ) + + thread = threading.Thread(target=reply, daemon=True) + thread.start() + session = host_session.HostSession( + bridge=child, + clock=lambda: 1_700_000_000, + compiler_evidence_provider=lambda preparation: preparation, + compiler_invocation_resolver=lambda _preparation_id: ( + host_session._CompilerInvocationBinding( + request_hash=_canonical_hash(request), + reasoning_effort="high", + verified_route_result_hashes=tuple( + sorted(fact.route.routing_result_hash for fact in resolver_facts) + ), + verified_lease_scope_bindings=tuple( + sorted( + adapter._lease_scope_binding_hash(fact) + for fact in resolver_facts + ) + if trusted_lease_hashes is None + else trusted_lease_hashes + ), + dispatch_facts=resolver_facts, + dispatch_binding_hashes=( + tuple( + adapter._dispatch_fact_mapping(fact)["dispatch_binding_hash"] + for fact in resolver_facts + ) + if trusted_dispatch_hashes is None + else trusted_dispatch_hashes + ), + ) + ), + ) + capability_facts = session.attest_routes( + binding=binding, + routes=(route,), + now=1_700_000_000, + ) + thread.join(timeout=2) + prepared = adapter.prepare_verified_host_facts( + session, + capability_facts=capability_facts, + preparation_id="dispatch-batch", + ) + return request, prepared, (child, host) + + def test_adapter_fails_closed_when_verified_host_facts_are_missing() -> None: adapter = _adapter() @@ -33,6 +192,396 @@ def test_adapter_fails_closed_when_verified_host_facts_are_missing() -> None: ) +def test_private_host_facts_form_one_mechanical_dispatch_all_request() -> None: + adapter = _adapter() + facts = ( + _dispatch_fact(adapter, task="task-a", scope="src/a.py"), + _dispatch_fact(adapter, task="task-b", scope="src/b.py"), + ) + request, prepared, bridges = _prepared_dispatch(adapter, facts) + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result["schema"] == "2718lab-devkit/fastlane-host-dispatch-batch-v1" + assert result["action"] == "dispatch_all" + assert result["selection_authority"] == "host_attested_compiler" + assert result["llm_choice"] is False + assert [item["task_id"] for item in result["assignments"]] == ["task-a", "task-b"] + assert all( + item["route"]["require_explicit_route"] is True + for item in result["assignments"] + ) + assert result["dispatch_binding_hashes"] == [ + item["dispatch_binding_hash"] for item in result["assignments"] + ] + assert result["batch_hash"] == _canonical_hash( + {key: value for key, value in result.items() if key != "batch_hash"} + ) + + +def test_any_public_request_tamper_burns_the_entire_dispatch_batch() -> None: + adapter = _adapter() + facts = (_dispatch_fact(adapter, task="task-a", scope="src/a.py"),) + request, prepared, bridges = _prepared_dispatch(adapter, facts) + tampered = json.loads(json.dumps(request)) + tampered["assignments"][0]["lease_epoch"] = 8 + try: + result = adapter.compile_fast_lane_with_host_facts( + tampered, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + + +def test_dispatch_binding_hash_covers_every_assignment_field() -> None: + adapter = _adapter() + mapping = adapter._dispatch_fact_mapping( + _dispatch_fact(adapter, task="task-a", scope="src/a.py") + ) + binding_hash = mapping.pop("dispatch_binding_hash") + mutations = ( + (("task_id",), "task-z"), + (("route", "model"), "gpt-5.6-luna"), + (("route", "reasoning_effort"), "max"), + (("route", "routing_context_hash"), _hash("b")), + (("route", "routing_result_hash"), _hash("c")), + (("route", "require_explicit_route"), False), + (("lease_id",), "lease-rebound"), + (("lease_epoch",), 8), + (("task_version",), 4), + (("assignment_token",), _hash("d")), + (("write_scope",), ["src/b.py"]), + (("concurrency_mode",), "serial"), + (("dispatch_order",), 1), + (("index_context_hash",), _hash("e")), + (("worktree_identity",), _hash("f")), + (("worktree_base",), _hash("0")), + (("integration_head",), _hash("b")), + (("predecessor_hash",), _hash("c")), + (("source_plan_hash",), _hash("d")), + (("ledger_epoch",), 12), + (("active_lease_set_hash",), _hash("e")), + ) + for path, replacement in mutations: + mutated = json.loads(json.dumps(mapping)) + target = mutated + for part in path[:-1]: + target = target[part] + target[path[-1]] = replacement + assert _canonical_hash(mutated) != binding_hash, path + + valid_fact = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + request, prepared, bridges = _prepared_dispatch(adapter, (valid_fact,)) + assignment = request["assignments"][0] + request_target = assignment + for part in path[:-1]: + request_target = request_target[part] + request_target[path[-1]] = replacement + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + assert result == adapter.NO_SAFE_WORK, path + + +@pytest.mark.parametrize( + "scope", + ( + ".", + "..", + "../outside.py", + "src/name.", + "src/alias./child.py", + "src/*", + "src/file?.py", + "src/[abc].py", + "src/space name.py", + "src/[]", + ), +) +def test_illegal_or_wildcard_scope_against_concrete_scope_fails_closed( + scope: str, +) -> None: + adapter = _adapter() + valid = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + request, prepared, bridges = _prepared_dispatch(adapter, (valid,)) + request["assignments"][0]["write_scope"] = [scope] + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + + +def test_invalid_scope_in_trusted_host_fact_fails_after_request_hash_matches() -> None: + adapter = _adapter() + valid = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + invalid = replace(valid, write_scope=("src/windows-alias.",)) + request, prepared, bridges = _prepared_dispatch( + adapter, + (valid,), + trusted_facts=(invalid,), + trusted_lease_hashes=(_hash("f"),), + trusted_dispatch_hashes=(_hash("0"),), + ) + assert ( + _canonical_hash(request) + == prepared.session._compiler_evidence[prepared.evidence].request_hash + ) + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + + +def test_scope_conflict_respects_segment_boundary_and_blocks_parent_escape() -> None: + adapter = _adapter() + + assert adapter._scopes_overlap(("src/a",), ("src/ab",)) is False + assert adapter._scopes_overlap(("src/a",), ("src/a/child.py",)) is True + with pytest.raises(ValueError, match="write scope is invalid"): + adapter._canonical_write_scope(("src/a/../../outside.py",)) + + +def test_matching_request_hash_still_rejects_trusted_dispatch_binding_mismatch() -> ( + None +): + adapter = _adapter() + valid = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + request, prepared, bridges = _prepared_dispatch( + adapter, + (valid,), + trusted_dispatch_hashes=(_hash("0"),), + ) + assert ( + _canonical_hash(request) + == prepared.session._compiler_evidence[prepared.evidence].request_hash + ) + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + + +def test_matching_request_hash_rejects_different_but_valid_trusted_fact() -> None: + adapter = _adapter() + valid = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + rebound = replace(valid, worktree_base=_hash("0")) + request, prepared, bridges = _prepared_dispatch( + adapter, + (valid,), + trusted_facts=(rebound,), + ) + assert ( + _canonical_hash(request) + == prepared.session._compiler_evidence[prepared.evidence].request_hash + ) + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + + +def test_request_shape_is_bounded_before_canonical_hash( + monkeypatch: pytest.MonkeyPatch, +) -> None: + adapter = _adapter() + valid = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + request, prepared, bridges = _prepared_dispatch(adapter, (valid,)) + request["assignments"][0]["route"]["model"] = "m" * 129 + canonical_called = False + + def forbidden_canonical(_value: object) -> bytes: + nonlocal canonical_called + canonical_called = True + raise AssertionError("unbounded request reached canonical JSON") + + monkeypatch.setattr(adapter, "_canonical_bytes", forbidden_canonical) + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + assert canonical_called is False + + +@pytest.mark.parametrize("level", ("root", "assignment", "route")) +def test_overwide_mapping_short_circuits_before_key_iteration_or_canonical_json( + level: str, + monkeypatch: pytest.MonkeyPatch, +) -> None: + adapter = _adapter() + valid = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + request, prepared, bridges = _prepared_dispatch(adapter, (valid,)) + + class CountingKey: + def __init__(self) -> None: + self.hash_calls = 0 + + def __hash__(self) -> int: + self.hash_calls += 1 + return 987_654_321 + + def __eq__(self, other: object) -> bool: + return self is other + + target = request + if level in {"assignment", "route"}: + target = request["assignments"][0] + if level == "route": + target = target["route"] + for index in range(2_048): + target[f"unexpected-{index}"] = index + counting_key = CountingKey() + target[counting_key] = "must-not-be-visited" + counting_key.hash_calls = 0 + canonical_called = False + + def forbidden_canonical(_value: object) -> bytes: + nonlocal canonical_called + canonical_called = True + raise AssertionError("overwide mapping reached canonical JSON") + + monkeypatch.setattr(adapter, "_canonical_bytes", forbidden_canonical) + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + assert counting_key.hash_calls == 0 + assert canonical_called is False + + +def test_request_serialized_size_is_capped_before_hashing( + monkeypatch: pytest.MonkeyPatch, +) -> None: + adapter = _adapter() + valid = _dispatch_fact(adapter, task="task-a", scope="src/a.py") + request, prepared, bridges = _prepared_dispatch(adapter, (valid,)) + base = request["assignments"][0] + scopes = [f"scope{index:02d}/" + "a" * 240 for index in range(32)] + oversized = [] + for _index in range(16): + assignment = json.loads(json.dumps(base)) + assignment["write_scope"] = scopes + oversized.append(assignment) + request["assignments"] = oversized + canonical_called = False + + def forbidden_canonical(_value: object) -> bytes: + nonlocal canonical_called + canonical_called = True + raise AssertionError("oversized request reached canonical JSON") + + monkeypatch.setattr(adapter, "_canonical_bytes", forbidden_canonical) + try: + result = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="high", + verified_host_facts=prepared, + ) + finally: + for bridge in bridges: + bridge.close() + + assert result == adapter.NO_SAFE_WORK + assert canonical_called is False + + +def test_overlapping_parallel_scopes_fail_closed_but_serial_scopes_are_ordered() -> ( + None +): + adapter = _adapter() + first = _dispatch_fact(adapter, task="task-a", scope="src/shared") + overlapping = replace( + _dispatch_fact(adapter, task="task-b", scope="src/shared/child.py"), + concurrency_mode="parallel", + ) + request, prepared, bridges = _prepared_dispatch(adapter, (first, overlapping)) + try: + rejected = adapter.compile_fast_lane_with_host_facts( + request, reasoning_effort="high", verified_host_facts=prepared + ) + finally: + for bridge in bridges: + bridge.close() + assert rejected == adapter.NO_SAFE_WORK + with pytest.raises(ValueError, match="parallel write scopes overlap"): + adapter._validate_batch_fences( + (replace(first, concurrency_mode="serial", dispatch_order=1), overlapping) + ) + + serial_facts = ( + replace(first, concurrency_mode="serial", dispatch_order=1), + replace(overlapping, concurrency_mode="serial", dispatch_order=2), + ) + request, prepared, bridges = _prepared_dispatch(adapter, serial_facts) + try: + accepted = adapter.compile_fast_lane_with_host_facts( + request, reasoning_effort="high", verified_host_facts=prepared + ) + finally: + for bridge in bridges: + bridge.close() + assert [item["dispatch_order"] for item in accepted["assignments"]] == [1, 2] + + def test_adapter_exposes_no_forgeable_verified_host_facts_marker() -> None: adapter = _adapter() diff --git a/mcp-tools/tests/test_host_session.py b/mcp-tools/tests/test_host_session.py index 9d7eff3..97f0b32 100644 --- a/mcp-tools/tests/test_host_session.py +++ b/mcp-tools/tests/test_host_session.py @@ -75,12 +75,16 @@ def _compiler_binding( reasoning_effort: str = "high", route_hashes: tuple[str, ...] = (_HASH_PREFIX + "2" * 64,), lease_hashes: tuple[str, ...] = (_HASH_PREFIX + "3" * 64,), + dispatch_facts: tuple[object, ...] = (), + dispatch_binding_hashes: tuple[str, ...] = (), ) -> object: return host_session._CompilerInvocationBinding( request_hash=request_hash, reasoning_effort=reasoning_effort, verified_route_result_hashes=route_hashes, verified_lease_scope_bindings=lease_hashes, + dispatch_facts=dispatch_facts, + dispatch_binding_hashes=dispatch_binding_hashes, ) @@ -1050,6 +1054,37 @@ def provider(preparation: object) -> object: assert session.consume_compiler_evidence(handle) == "NO_SAFE_WORK" +def test_compiler_invocation_binding_hash_binds_ordered_dispatch_hashes() -> None: + dispatch_hashes = (_HASH_PREFIX + "4" * 64, _HASH_PREFIX + "5" * 64) + session, child, host = _compiler_session( + provider=lambda preparation: preparation, + resolver=lambda _preparation_id: _compiler_binding( + dispatch_facts=("fact-a", "fact-b"), + dispatch_binding_hashes=dispatch_hashes, + ), + ) + try: + handle = session.prepare_compiler_evidence(preparation_id="prep-dispatch") + invocation = session.consume_compiler_evidence(handle) + finally: + child.close() + host.close() + + assert invocation.dispatch_binding_hashes == dispatch_hashes + assert invocation.binding_hash == _hash( + { + "preparation_id": "prep-dispatch", + "request_hash": invocation.request_hash, + "reasoning_effort": invocation.reasoning_effort, + "verified_route_result_hashes": invocation.verified_route_result_hashes, + "verified_lease_scope_bindings": invocation.verified_lease_scope_bindings, + "issued_at": invocation.issued_at, + "expires_at": invocation.expires_at, + "dispatch_binding_hashes": dispatch_hashes, + } + ) + + def test_compiler_evidence_fails_closed_without_provider_or_binding() -> None: child, host = _pipe_pair() try: From d51c99c6ff0267cd5bc77ed2864a6b12e9d758f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Fri, 28 Aug 2026 23:07:11 +0800 Subject: [PATCH 3/8] fix: secure the Windows host bridge rendezvous --- CHANGELOG.md | 8 + README.md | 13 + README.zh-CN.md | 11 +- mcp-tools/devkit_runtime/host_bridge.py | 204 ++++-- .../tests/test_relay_runtime_registry.py | 584 ++++++++++++------ 5 files changed, 586 insertions(+), 234 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edd23e6..40b0f93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ only after the CI and artifact checks pass. ## [Unreleased] +### Fixed + +- Replaced inherited numeric Windows host-bridge handles with a strict local + named-pipe selector bound to the exact launcher PID and process creation + FILETIME while preserving the Unix inherited-FD contract. Untagged, + path-like, remote, malformed, PID-mismatched, or creation-mismatched selectors + now fail closed before any session key is sent. + ## [1.1.2] - 2026-08-27 ### Fixed diff --git a/README.md b/README.md index 753a0bf..5b7ede7 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,19 @@ private host-bridge selector names and optional project/thread scope identifiers These are selector or identity names, not values to invent or copy into a task message. The latter identifiers keep durable state scoped to one project or thread instead of leaking it into another workspace. +On Windows, CODEX_DEVKIT_HOST_BRIDGE_HANDLE is intentionally named for +compatibility but accepts only +`pipe:codex-devkit---` (a +launcher PID, its 16-hex Windows creation FILETIME, and a launcher-generated +128-bit lowercase hex token). The runtime +maps that opaque selector only into the local `\\.\pipe\` namespace, verifies +that the connected pipe server has exactly the encoded launcher PID and process +creation time before sending the session key, and rejects inherited numeric +handles, paths, remote UNC names, and untagged values. The launcher remains +responsible for CSPRNG +generation, first-instance creation, remote-client rejection, and an owner-only +ACL. Unix continues to accept only a numeric inherited descriptor through +CODEX_DEVKIT_HOST_BRIDGE_FD. Relay lifecycle mutations that need the private host capability broker or proof registry fail closed when the host does not provide an attested capability, using RELAY_CAPABILITY_BROKER_UNAVAILABLE. The server never exposes raw diff --git a/README.zh-CN.md b/README.zh-CN.md index 0550350..46f82b9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -169,7 +169,16 @@ Fast Lane 不含额度协调器合同;公共编译器和 CLI 不读取、协 - CODEX_PROJECT_ID、CODEX_WORKSPACE_ID、CODEX_THREAD_ID 这些是 selector 或 identity 名称,不是应该自行编造或塞进任务消息的值。后五项 -会把持久化状态限定在单个项目或线程,避免投影到另一个工作区。需要私有 +会把持久化状态限定在单个项目或线程,避免投影到另一个工作区。 +在 Windows 上,CODEX_DEVKIT_HOST_BRIDGE_HANDLE 为兼容既有环境变量名而保留, +但值只能是 `pipe:codex-devkit-<宿主PID>-<创建FILETIME>-<128位小写十六进制令牌>`, +其中创建时间是 16 位小写十六进制 Windows FILETIME。运行时只会把该不透明 +selector 映射到本机 `\\.\pipe\` 命名空间,并在发送 session key 之前证明 +已连接管道的 server PID 与进程创建时间都与 selector 完全相同;数字 HANDLE、 +路径、远程 UNC 名称及未加标签的值都会被拒绝。可信 launcher 负责用 CSPRNG 生成令牌、 +以 first-instance 与拒绝远程客户端模式创建管道,并设置仅 owner 可访问的 ACL。 +Unix 仍只通过 CODEX_DEVKIT_HOST_BRIDGE_FD 接受数字形式的继承描述符。 +需要私有 宿主 capability broker 或 proof registry 的 Relay 生命周期变更,在宿主 没有提供可证明能力时会失败关闭,并返回 RELAY_CAPABILITY_BROKER_UNAVAILABLE。服务器不会暴露原始 handle,也不会 diff --git a/mcp-tools/devkit_runtime/host_bridge.py b/mcp-tools/devkit_runtime/host_bridge.py index de31385..67db108 100644 --- a/mcp-tools/devkit_runtime/host_bridge.py +++ b/mcp-tools/devkit_runtime/host_bridge.py @@ -1,9 +1,9 @@ -"""Framed host-private capability traffic over one inherited OS handle. +"""Framed host-private capability traffic over one private OS transport. The bridge deliberately has no listener, socket bootstrap, file mailbox, or -environment-provided secret. A launcher may pass only its dedicated inherited -descriptor/handle selector; all capability material stays in authenticated -frames on that private handle. +environment-provided secret. A launcher passes either a Unix inherited +descriptor or a high-entropy local Windows named-pipe selector; all capability +material stays in authenticated frames on that private transport. """ from __future__ import annotations @@ -41,7 +41,12 @@ _ENDPOINT = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}\Z") _DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z") _MAC = re.compile(r"[0-9a-f]{64}\Z") -_HANDLE_SELECTOR = re.compile(r"[0-9]{1,18}\Z") +_FD_SELECTOR = re.compile(r"[0-9]{1,18}\Z") +_WINDOWS_PIPE_SELECTOR = re.compile( + r"pipe:(?Pcodex-devkit-(?P[1-9][0-9]{0,9})-" + r"(?P[0-9a-f]{16})-(?P[0-9a-f]{32}))\Z" +) +_MAX_WINDOWS_PIPE_NAME: Final = 96 _MESSAGE_KINDS: Final = frozenset( { "session_open", @@ -149,12 +154,12 @@ def receipt(self, action_id: str) -> CapabilityDeliveryReceipt: class InheritedHandleHostBridge: - """Concrete non-listening bridge backed by a dedicated inherited handle. + """Concrete non-listening bridge backed by one private duplex descriptor. - ``from_environment`` is the launch-path constructor. It accepts only the - numeric inherited handle selector frozen in the design lock. The direct - descriptor constructor exists for an already-established host session and - for process-local harnesses; it does not open any listener. + ``from_environment`` accepts a Unix inherited descriptor or a strict local + Windows named-pipe selector. The direct descriptor constructor exists for + an already-established host session and for process-local harnesses; this + module never opens a listener. """ def __init__( @@ -222,7 +227,7 @@ def from_environment( *, platform: str | None = None, ) -> InheritedHandleHostBridge | None: - """Open only the dedicated inherited handle selected by the launcher. + """Open only the dedicated private transport selected by the launcher. Absence is intentionally non-fatal so read-only Relay operations remain available. An invalid selector is fail-closed and is never reflected @@ -239,19 +244,34 @@ def from_environment( raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") if selector is None or selector == "": return None - if type(selector) is not str or _HANDLE_SELECTOR.fullmatch(selector) is None: - raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") - handle = int(selector) - if handle in {0, 1, 2}: - raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") descriptor = -1 + unavailable = False try: if target_platform == "nt": - _assert_windows_private_duplex_ipc_handle(handle) import msvcrt - descriptor = msvcrt.open_osfhandle(handle, os.O_BINARY | os.O_RDWR) + ( + pipe_path, + expected_server_pid, + expected_creation_filetime, + ) = _windows_named_pipe_target(selector) + descriptor = os.open(pipe_path, os.O_BINARY | os.O_RDWR) + windows_handle = msvcrt.get_osfhandle(descriptor) + _assert_windows_private_duplex_ipc_handle(windows_handle) + _assert_windows_named_pipe_server( + windows_handle, + expected_server_pid=expected_server_pid, + expected_creation_filetime=expected_creation_filetime, + ) else: + if ( + type(selector) is not str + or _FD_SELECTOR.fullmatch(selector) is None + ): + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + handle = int(selector) + if handle in {0, 1, 2}: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") descriptor = os.dup(handle) _assert_private_duplex_ipc_descriptor(descriptor, platform=target_platform) os.set_inheritable(descriptor, False) @@ -261,15 +281,17 @@ def from_environment( OSError, OverflowError, ValueError, - ) as error: + ): if descriptor >= 0: try: os.close(descriptor) except OSError: pass - if isinstance(error, HostBridgeError): - raise - raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") from error + unavailable = True + if unavailable: + # Raise outside the handler so a path-bearing OSError is not retained + # in __cause__, __context__, or formatted traceback state. + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") return cls( read_fd=descriptor, write_fd=descriptor, @@ -586,9 +608,7 @@ def send_terminal_result( normalized_predecessor = _normalize_operation_receipt(predecessor, now=now) if normalized_predecessor.kind != "coordinator_assignment": raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") - _assert_terminal_predecessor( - normalized, normalized_predecessor, now=now - ) + _assert_terminal_predecessor(normalized, normalized_predecessor, now=now) registered_predecessor = self._received_operations.get( normalized_predecessor.envelope_hash ) @@ -732,7 +752,7 @@ def request_proof_attestation( def send_private( self, *, kind: str, action_id: str, payload: Mapping[str, object] ) -> None: - """Write one canonical authenticated frame to the inherited handle.""" + """Write one canonical authenticated frame to the private transport.""" if kind in _VALIDATED_PRIVATE_KINDS: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") @@ -1009,7 +1029,10 @@ def _normalize_capability_hashes( normalized: dict[str, str] = {} for name in capability_names: capability_hash = value.get(name) - if type(capability_hash) is not str or _DIGEST.fullmatch(capability_hash) is None: + if ( + type(capability_hash) is not str + or _DIGEST.fullmatch(capability_hash) is None + ): raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") normalized[name] = capability_hash return dict(sorted(normalized.items())) @@ -1130,9 +1153,7 @@ def _parse_capability_report( return normalized -def _operation_receipt( - envelope: Mapping[str, object], *, now: int -) -> OperationReceipt: +def _operation_receipt(envelope: Mapping[str, object], *, now: int) -> OperationReceipt: normalized = host_envelopes.validate_envelope(envelope, now=now) payload = normalized["payload"] assert type(payload) is dict @@ -1182,14 +1203,19 @@ def _operation_replay_identity_from_key( return key[:-1] -def _normalize_operation_receipt(value: OperationReceipt, *, now: int) -> OperationReceipt: +def _normalize_operation_receipt( + value: OperationReceipt, *, now: int +) -> OperationReceipt: if type(value) is not OperationReceipt: raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") if value.kind not in {"coordinator_assignment", "peer_evidence_handoff"}: raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") _validate_action_id(value.task_id) _validate_action_id(value.correlation_id) - if type(value.envelope_hash) is not str or _DIGEST.fullmatch(value.envelope_hash) is None: + if ( + type(value.envelope_hash) is not str + or _DIGEST.fullmatch(value.envelope_hash) is None + ): raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") try: binding = _normalize_bridge_binding(value.binding, now=now) @@ -1380,17 +1406,30 @@ def _assert_private_duplex_ipc_descriptor(descriptor: int, *, platform: str) -> raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") from error +def _windows_named_pipe_target(selector: str) -> tuple[str, int, int]: + """Map a process-identity-bound selector to the local Windows pipe namespace.""" + + if type(selector) is not str: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + matched = _WINDOWS_PIPE_SELECTOR.fullmatch(selector) + if matched is None: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + name = matched.group("name") + server_pid = int(matched.group("server_pid")) + creation_filetime = int(matched.group("creation_filetime"), 16) + if len(name) > _MAX_WINDOWS_PIPE_NAME: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + if server_pid > 0xFFFF_FFFF: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + return rf"\\.\pipe\{name}", server_pid, creation_filetime + + def _assert_windows_private_duplex_ipc_handle(handle: int) -> None: """Reject console, disk, and one-way Windows handles before CRT adoption.""" try: import _winapi - for standard_handle in (-10, -11, -12): - if not _windows_handles_are_provably_distinct( - handle, _winapi.GetStdHandle(standard_handle) - ): - raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") if _winapi.GetFileType(handle) != 3: # FILE_TYPE_PIPE raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") if not _windows_named_pipe_info_available(handle): @@ -1411,19 +1450,90 @@ def _assert_windows_private_duplex_ipc_handle(handle: int) -> None: raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") from error -def _windows_handles_are_provably_distinct(first: int, second: int) -> bool: - """Accept only a verified distinct kernel object, never an ambiguous comparison.""" +def _assert_windows_named_pipe_server( + handle: int, + *, + expected_server_pid: int, + expected_creation_filetime: int, +) -> None: + """Bind the opened pipe to the selector's exact launcher process identity.""" + + try: + import ctypes + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + get_server_pid = kernel32.GetNamedPipeServerProcessId + get_server_pid.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_ulong), + ) + get_server_pid.restype = ctypes.c_int + observed_server_pid = ctypes.c_ulong() + if not get_server_pid(handle, ctypes.byref(observed_server_pid)): + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + if observed_server_pid.value != expected_server_pid: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + if ( + _windows_process_creation_filetime(expected_server_pid) + != expected_creation_filetime + ): + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + except HostBridgeError: + raise + except ( + AttributeError, + ImportError, + OSError, + OverflowError, + TypeError, + ValueError, + ) as error: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") from error + + +def _windows_process_creation_filetime(process_id: int) -> int: + """Read one process creation identity without retaining a process handle.""" import ctypes - kernelbase = ctypes.WinDLL("kernelbase", use_last_error=True) - compare_object_handles = kernelbase.CompareObjectHandles - compare_object_handles.argtypes = (ctypes.c_void_p, ctypes.c_void_p) - compare_object_handles.restype = ctypes.c_int - ctypes.set_last_error(0) - if compare_object_handles(first, second): - return False - return ctypes.get_last_error() == 1656 # ERROR_NOT_SAME_OBJECT + class _FileTime(ctypes.Structure): + _fields_ = [("low", ctypes.c_ulong), ("high", ctypes.c_ulong)] + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + open_process = kernel32.OpenProcess + open_process.argtypes = (ctypes.c_ulong, ctypes.c_int, ctypes.c_ulong) + open_process.restype = ctypes.c_void_p + get_process_times = kernel32.GetProcessTimes + get_process_times.argtypes = ( + ctypes.c_void_p, + ctypes.POINTER(_FileTime), + ctypes.POINTER(_FileTime), + ctypes.POINTER(_FileTime), + ctypes.POINTER(_FileTime), + ) + get_process_times.restype = ctypes.c_int + close_handle = kernel32.CloseHandle + close_handle.argtypes = (ctypes.c_void_p,) + close_handle.restype = ctypes.c_int + process = open_process(0x1000, False, process_id) # QUERY_LIMITED_INFORMATION + if not process: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + try: + creation = _FileTime() + exit_time = _FileTime() + kernel_time = _FileTime() + user_time = _FileTime() + if not get_process_times( + process, + ctypes.byref(creation), + ctypes.byref(exit_time), + ctypes.byref(kernel_time), + ctypes.byref(user_time), + ): + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + return (creation.high << 32) | creation.low + finally: + close_handle(process) def _windows_named_pipe_info_available(handle: int) -> bool: diff --git a/mcp-tools/tests/test_relay_runtime_registry.py b/mcp-tools/tests/test_relay_runtime_registry.py index 2c3faa9..d75c6c3 100644 --- a/mcp-tools/tests/test_relay_runtime_registry.py +++ b/mcp-tools/tests/test_relay_runtime_registry.py @@ -7,6 +7,8 @@ import socket import struct import sys +import threading +import traceback from collections.abc import Callable from pathlib import Path @@ -88,6 +90,84 @@ def test_inherited_handle_bridge_rejects_missing_or_invalid_selectors() -> None: assert "not-a-handle" not in str(caught.value) +@pytest.mark.parametrize( + "selector", + [ + "1234", + "handle:1234", + "pipe:", + "pipe:other-product-0123456789abcdef0123456789abcdef", + "pipe:codex-devkit-not-a-pid-0123456789abcdef-0123456789abcdef0123456789abcdef", + "pipe:codex-devkit-0-0123456789abcdef-0123456789abcdef0123456789abcdef", + "pipe:codex-devkit-01-0123456789abcdef-0123456789abcdef0123456789abcdef", + "pipe:codex-devkit-4294967296-0123456789abcdef-0123456789abcdef0123456789abcdef", + "pipe:codex-devkit-42-not-filetime-0123456789abcdef0123456789abcdef", + "pipe:codex-devkit-42-0123456789abcdef-0123456789abcdef0123456789abcdeg", + "pipe:codex-devkit-42-0123456789abcdef-0123456789abcdef0123456789abcdef/child", + r"pipe:codex-devkit-42-0123456789abcdef-0123456789abcdef0123456789abcdef\child", + r"pipe:\\server\pipe\codex-devkit-42-0123456789abcdef-0123456789abcdef0123456789abcdef", + "pipe:codex-devkit-42-0123456789abcdef-0123456789abcdef0123456789abcdef.", + "pipe:codex-devkit-" + "a" * 97, + ], +) +def test_windows_named_pipe_selector_rejects_untagged_or_unsafe_values( + selector: str, monkeypatch: pytest.MonkeyPatch +) -> None: + opened = False + + def unexpected_open(*_args: object, **_kwargs: object) -> int: + nonlocal opened + opened = True + raise AssertionError("invalid selector reached os.open") + + monkeypatch.setattr(host_bridge_module.os, "open", unexpected_open) + with pytest.raises(HostBridgeError) as caught: + InheritedHandleHostBridge.from_environment( + {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": selector}, platform="nt" + ) + + assert caught.value.code == "HOST_BRIDGE_UNAVAILABLE" + assert selector not in str(caught.value) + assert opened is False + + +def test_windows_named_pipe_selector_maps_only_to_local_namespace() -> None: + name = "codex-devkit-42-0123456789abcdef-0123456789abcdef0123456789abcdef" + + assert host_bridge_module._windows_named_pipe_target(f"pipe:{name}") == ( + rf"\\.\pipe\{name}", + 42, + 0x0123456789ABCDEF, + ) + + +def test_windows_named_pipe_open_failure_drops_sensitive_exception_chain( + monkeypatch: pytest.MonkeyPatch, +) -> None: + token = "fedcba9876543210fedcba9876543210" + selector = f"pipe:codex-devkit-42-0123456789abcdef-{token}" + + def fail_open(path: str, _flags: int) -> int: + raise OSError(f"cannot open {path}") + + monkeypatch.setattr(host_bridge_module.os, "open", fail_open) + with pytest.raises(HostBridgeError) as caught: + InheritedHandleHostBridge.from_environment( + {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": selector}, platform="nt" + ) + + error = caught.value + rendered = "".join( + traceback.format_exception(type(error), error, error.__traceback__) + ) + assert error.code == "HOST_BRIDGE_UNAVAILABLE" + assert error.__cause__ is None + assert error.__context__ is None + for sensitive in (selector, token, r"\\.\pipe"): + assert sensitive not in str(error) + assert sensitive not in rendered + + @pytest.mark.parametrize( ("platform", "selector_name"), [ @@ -121,169 +201,6 @@ def test_inherited_handle_bridge_rejects_regular_file_selector(tmp_path: Path) - assert mailbox.read_bytes() == b"" -@pytest.mark.skipif(os.name != "nt", reason="requires Win32 handle semantics") -def test_inherited_handle_bridge_rejects_windows_regular_file_handle( - tmp_path: Path, -) -> None: - import _winapi - import msvcrt - - mailbox = tmp_path / "not-a-private-windows-ipc-handle" - descriptor = os.open(mailbox, os.O_RDWR | os.O_CREAT, 0o600) - duplicated_handle: int | None = None - bridge: InheritedHandleHostBridge | None = None - try: - duplicated_handle = _winapi.DuplicateHandle( - _winapi.GetCurrentProcess(), - msvcrt.get_osfhandle(descriptor), - _winapi.GetCurrentProcess(), - 0, - False, - _winapi.DUPLICATE_SAME_ACCESS, - ) - with pytest.raises(HostBridgeError) as caught: - bridge = InheritedHandleHostBridge.from_environment( - {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": str(duplicated_handle)}, - platform="nt", - ) - finally: - if bridge is not None: - bridge.close() - elif duplicated_handle is not None: - try: - _winapi.CloseHandle(duplicated_handle) - except OSError: - pass - os.close(descriptor) - - assert caught.value.code == "HOST_BRIDGE_UNAVAILABLE" - assert mailbox.read_bytes() == b"" - - -@pytest.mark.skipif(os.name != "nt", reason="requires Win32 handle semantics") -@pytest.mark.parametrize("endpoint_name", ["read", "write"]) -def test_inherited_handle_bridge_rejects_windows_one_way_pipe_handle( - endpoint_name: str, -) -> None: - import _winapi - import msvcrt - - read_fd, write_fd = os.pipe() - duplicated_handle: int | None = None - bridge: InheritedHandleHostBridge | None = None - try: - duplicated_handle = _winapi.DuplicateHandle( - _winapi.GetCurrentProcess(), - msvcrt.get_osfhandle(read_fd if endpoint_name == "read" else write_fd), - _winapi.GetCurrentProcess(), - 0, - False, - _winapi.DUPLICATE_SAME_ACCESS, - ) - with pytest.raises(HostBridgeError) as caught: - bridge = InheritedHandleHostBridge.from_environment( - {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": str(duplicated_handle)}, - platform="nt", - ) - finally: - if bridge is not None: - bridge.close() - elif duplicated_handle is not None: - try: - _winapi.CloseHandle(duplicated_handle) - except OSError: - pass - os.close(read_fd) - os.close(write_fd) - - assert caught.value.code == "HOST_BRIDGE_UNAVAILABLE" - - -@pytest.mark.skipif(os.name != "nt", reason="requires Win32 handle semantics") -@pytest.mark.parametrize( - "standard_handle", [-10, -11, -12], ids=["stdin", "stdout", "stderr"] -) -def test_inherited_handle_bridge_rejects_windows_standard_handle_object_alias( - standard_handle: int, -) -> None: - import _winapi - import ctypes - from multiprocessing import Pipe - - kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) - set_std_handle = kernel32.SetStdHandle - set_std_handle.argtypes = (ctypes.c_ulong, ctypes.c_void_p) - set_std_handle.restype = ctypes.c_int - standard_handle_selector = ctypes.c_ulong(standard_handle).value - peer, inherited = Pipe(duplex=True) - standard_alias = _winapi.DuplicateHandle( - _winapi.GetCurrentProcess(), - inherited.fileno(), - _winapi.GetCurrentProcess(), - 0, - False, - _winapi.DUPLICATE_SAME_ACCESS, - ) - candidate_alias = _winapi.DuplicateHandle( - _winapi.GetCurrentProcess(), - standard_alias, - _winapi.GetCurrentProcess(), - 0, - False, - _winapi.DUPLICATE_SAME_ACCESS, - ) - original_standard_handle = _winapi.GetStdHandle(standard_handle) - bridge: InheritedHandleHostBridge | None = None - standard_handle_replaced = False - try: - assert candidate_alias != standard_alias - if not set_std_handle( - standard_handle_selector, ctypes.c_void_p(standard_alias) - ): - raise OSError(ctypes.get_last_error(), "SetStdHandle failed") - standard_handle_replaced = True - assert _winapi.GetStdHandle(standard_handle) == standard_alias - - caught: HostBridgeError | None = None - try: - bridge = InheritedHandleHostBridge.from_environment( - {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": str(candidate_alias)}, - platform="nt", - ) - except HostBridgeError as error: - caught = error - - if bridge is not None: - bridge.prepare_capability( - action_id="standard-alias", - endpoint="bridge/standard-alias", - capabilities={"heartbeat": "test-private-capability"}, - ) - assert bridge is None - assert caught is not None - assert caught.code == "HOST_BRIDGE_UNAVAILABLE" - assert peer.poll(0) is False - finally: - if standard_handle_replaced: - if not set_std_handle( - standard_handle_selector, ctypes.c_void_p(original_standard_handle) - ): - raise OSError(ctypes.get_last_error(), "SetStdHandle restore failed") - if bridge is not None: - bridge.close() - else: - try: - _winapi.CloseHandle(candidate_alias) - except OSError: - pass - try: - _winapi.CloseHandle(standard_alias) - except OSError: - pass - peer.close() - inherited.close() - - @pytest.mark.parametrize( ("corruption", "first_error"), [ @@ -363,32 +280,14 @@ def test_bad_bootstrap_closes_owned_transport_before_reaccept( os.close(sender_reply_fd) +@pytest.mark.skipif(os.name == "nt", reason="requires POSIX socket inheritance") def test_inherited_handle_bridge_accepts_only_duplex_inherited_ipc_selector() -> None: - closers: list[Callable[[], None]] - if os.name == "nt": - import _winapi - from multiprocessing import Pipe - - peer, inherited = Pipe(duplex=True) - handle = _winapi.DuplicateHandle( - _winapi.GetCurrentProcess(), - inherited.fileno(), - _winapi.GetCurrentProcess(), - 0, - False, - _winapi.DUPLICATE_SAME_ACCESS, - ) - environ = {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": str(handle)} - platform = "nt" - closers = [peer.close, inherited.close] - else: - peer, inherited = socket.socketpair() - environ = {"CODEX_DEVKIT_HOST_BRIDGE_FD": str(inherited.fileno())} - platform = "posix" - closers = [peer.close, inherited.close] + peer, inherited = socket.socketpair() + environ = {"CODEX_DEVKIT_HOST_BRIDGE_FD": str(inherited.fileno())} + closers: list[Callable[[], None]] = [peer.close, inherited.close] bridge: InheritedHandleHostBridge | None = None try: - bridge = InheritedHandleHostBridge.from_environment(environ, platform=platform) + bridge = InheritedHandleHostBridge.from_environment(environ, platform="posix") assert bridge is not None assert bridge.is_available finally: @@ -398,6 +297,319 @@ def test_inherited_handle_bridge_accepts_only_duplex_inherited_ipc_selector() -> close() +@pytest.mark.skipif(os.name != "nt", reason="requires Windows named pipes") +@pytest.mark.parametrize("reply_mode", ["pong", "silent"]) +def test_windows_named_pipe_selector_opens_duplex_and_exchanges_frames( + reply_mode: str, +) -> None: + import _winapi + import ctypes + import msvcrt + + creation_filetime = host_bridge_module._windows_process_creation_filetime( + os.getpid() + ) + pipe_name = ( + f"codex-devkit-{os.getpid()}-{creation_filetime:016x}-{os.urandom(16).hex()}" + ) + selector = f"pipe:{pipe_name}" + pipe_path = rf"\\.\pipe\{pipe_name}" + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + create_named_pipe = kernel32.CreateNamedPipeW + create_named_pipe.argtypes = ( + ctypes.c_wchar_p, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_void_p, + ) + create_named_pipe.restype = ctypes.c_void_p + connect_named_pipe = kernel32.ConnectNamedPipe + connect_named_pipe.argtypes = (ctypes.c_void_p, ctypes.c_void_p) + connect_named_pipe.restype = ctypes.c_int + open_thread = kernel32.OpenThread + open_thread.argtypes = (ctypes.c_ulong, ctypes.c_int, ctypes.c_ulong) + open_thread.restype = ctypes.c_void_p + cancel_synchronous_io = kernel32.CancelSynchronousIo + cancel_synchronous_io.argtypes = (ctypes.c_void_p,) + cancel_synchronous_io.restype = ctypes.c_int + close_handle = kernel32.CloseHandle + close_handle.argtypes = (ctypes.c_void_p,) + close_handle.restype = ctypes.c_int + local_free = kernel32.LocalFree + local_free.argtypes = (ctypes.c_void_p,) + local_free.restype = ctypes.c_void_p + advapi32 = ctypes.WinDLL("advapi32", use_last_error=True) + convert_sddl = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW + convert_sddl.argtypes = ( + ctypes.c_wchar_p, + ctypes.c_ulong, + ctypes.POINTER(ctypes.c_void_p), + ctypes.c_void_p, + ) + convert_sddl.restype = ctypes.c_int + + class SecurityAttributes(ctypes.Structure): + _fields_ = [ + ("nLength", ctypes.c_ulong), + ("lpSecurityDescriptor", ctypes.c_void_p), + ("bInheritHandle", ctypes.c_int), + ] + + security_descriptor = ctypes.c_void_p() + if not convert_sddl("D:P(A;;GA;;;OW)", 1, ctypes.byref(security_descriptor), None): + raise OSError(ctypes.get_last_error(), "owner-only SDDL conversion failed") + security_attributes = SecurityAttributes( + ctypes.sizeof(SecurityAttributes), security_descriptor, False + ) + server_handle = create_named_pipe( + pipe_path, + 0x00000003 | 0x00080000, # PIPE_ACCESS_DUPLEX | FIRST_PIPE_INSTANCE + 0x00000008, # byte mode, blocking, and PIPE_REJECT_REMOTE_CLIENTS + 1, + 65_536, + 65_536, + 0, + ctypes.byref(security_attributes), + ) + local_free(security_descriptor) + if server_handle == ctypes.c_void_p(-1).value: + raise OSError(ctypes.get_last_error(), "CreateNamedPipeW failed") + + errors: list[BaseException] = [] + release_silent_server = threading.Event() + + def serve() -> None: + nonlocal server_handle + descriptor = -1 + host: InheritedHandleHostBridge | None = None + try: + if not connect_named_pipe(server_handle, None): + error = ctypes.get_last_error() + if error != 535: # ERROR_PIPE_CONNECTED + raise OSError(error, "ConnectNamedPipe failed") + descriptor = msvcrt.open_osfhandle( + int(server_handle), os.O_BINARY | os.O_RDWR + ) + server_handle = None + host = InheritedHandleHostBridge.accept_from_file_descriptors( + read_fd=descriptor, write_fd=descriptor + ) + descriptor = -1 + ping = host.receive() + assert (ping.kind, ping.action_id, ping.payload) == ( + "capability_ack", + "pipe-ping", + {}, + ) + if reply_mode == "pong": + host.send_private( + kind="capability_ack", action_id="pipe-pong", payload={} + ) + else: + release_silent_server.wait(timeout=4) + except BaseException as error: + errors.append(error) + finally: + if host is not None: + host.close() + elif descriptor >= 0: + os.close(descriptor) + + thread = threading.Thread(target=serve, daemon=True) + thread.start() + child: InheritedHandleHostBridge | None = None + client_thread: threading.Thread | None = None + client_errors: list[BaseException] = [] + try: + child = InheritedHandleHostBridge.from_environment( + {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": selector}, platform="nt" + ) + assert child is not None + + def exchange() -> None: + try: + assert child is not None + child.send_private( + kind="capability_ack", action_id="pipe-ping", payload={} + ) + pong = child.receive() + assert (pong.kind, pong.action_id, pong.payload) == ( + "capability_ack", + "pipe-pong", + {}, + ) + except BaseException as error: + client_errors.append(error) + + client_thread = threading.Thread(target=exchange, daemon=True) + client_thread.start() + client_thread.join(timeout=2) + if client_thread.is_alive(): + assert client_thread.native_id is not None + thread_handle = open_thread(0x0001, False, client_thread.native_id) + if not thread_handle: + raise OSError(ctypes.get_last_error(), "OpenThread failed") + try: + if not cancel_synchronous_io(thread_handle): + raise OSError(ctypes.get_last_error(), "CancelSynchronousIo failed") + finally: + close_handle(thread_handle) + client_thread.join(timeout=2) + release_silent_server.set() + if reply_mode == "pong": + assert client_errors == [] + else: + assert len(client_errors) == 1 + assert isinstance(client_errors[0], HostBridgeError) + assert child.is_available is False + rendered = "".join( + traceback.format_exception( + type(client_errors[0]), + client_errors[0], + client_errors[0].__traceback__, + ) + ) + for sensitive in (selector, pipe_name.rsplit("-", 1)[-1], pipe_path): + assert sensitive not in rendered + assert not client_thread.is_alive() + thread.join(timeout=2) + assert not thread.is_alive() + assert errors == [] + finally: + release_silent_server.set() + if child is not None: + child.close() + if server_handle is not None: + if thread.is_alive() and thread.native_id is not None: + thread_handle = open_thread(0x0001, False, thread.native_id) + if thread_handle: + try: + cancel_synchronous_io(thread_handle) + finally: + close_handle(thread_handle) + _winapi.CloseHandle(server_handle) + thread.join(timeout=2) + if client_thread is not None: + client_thread.join(timeout=2) + assert not thread.is_alive() + assert client_thread is None or not client_thread.is_alive() + + +@pytest.mark.skipif(os.name != "nt", reason="requires Windows named pipes") +@pytest.mark.parametrize("mismatch", ["pid", "creation_filetime"]) +def test_windows_named_pipe_rejects_reused_server_identity_before_bootstrap( + mismatch: str, +) -> None: + import _winapi + import ctypes + import msvcrt + + actual_pid = os.getpid() + creation_filetime = host_bridge_module._windows_process_creation_filetime( + actual_pid + ) + encoded_pid = ( + actual_pid + 1 + if mismatch == "pid" and actual_pid < 0xFFFF_FFFF + else actual_pid - 1 + if mismatch == "pid" + else actual_pid + ) + encoded_creation = ( + creation_filetime ^ 1 if mismatch == "creation_filetime" else creation_filetime + ) + pipe_name = ( + f"codex-devkit-{encoded_pid}-{encoded_creation:016x}-{os.urandom(16).hex()}" + ) + selector = f"pipe:{pipe_name}" + pipe_path = rf"\\.\pipe\{pipe_name}" + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + create_named_pipe = kernel32.CreateNamedPipeW + create_named_pipe.argtypes = ( + ctypes.c_wchar_p, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_ulong, + ctypes.c_void_p, + ) + create_named_pipe.restype = ctypes.c_void_p + connect_named_pipe = kernel32.ConnectNamedPipe + connect_named_pipe.argtypes = (ctypes.c_void_p, ctypes.c_void_p) + connect_named_pipe.restype = ctypes.c_int + open_thread = kernel32.OpenThread + open_thread.argtypes = (ctypes.c_ulong, ctypes.c_int, ctypes.c_ulong) + open_thread.restype = ctypes.c_void_p + cancel_synchronous_io = kernel32.CancelSynchronousIo + cancel_synchronous_io.argtypes = (ctypes.c_void_p,) + cancel_synchronous_io.restype = ctypes.c_int + server_handle = create_named_pipe( + pipe_path, + 0x00000003 | 0x00080000, + 0x00000008, + 1, + 65_536, + 65_536, + 0, + None, + ) + if server_handle == ctypes.c_void_p(-1).value: + raise OSError(ctypes.get_last_error(), "CreateNamedPipeW failed") + + observed: list[bytes] = [] + errors: list[BaseException] = [] + + def serve() -> None: + nonlocal server_handle + descriptor = -1 + try: + if not connect_named_pipe(server_handle, None): + error = ctypes.get_last_error() + if error != 535: + raise OSError(error, "ConnectNamedPipe failed") + descriptor = msvcrt.open_osfhandle( + int(server_handle), os.O_BINARY | os.O_RDWR + ) + server_handle = None + observed.append(os.read(descriptor, 1)) + except BaseException as error: + errors.append(error) + finally: + if descriptor >= 0: + os.close(descriptor) + + thread = threading.Thread(target=serve, daemon=True) + thread.start() + try: + with pytest.raises(HostBridgeError) as caught: + InheritedHandleHostBridge.from_environment( + {"CODEX_DEVKIT_HOST_BRIDGE_HANDLE": selector}, platform="nt" + ) + assert caught.value.code == "HOST_BRIDGE_UNAVAILABLE" + thread.join(timeout=2) + assert not thread.is_alive() + assert errors == [] + assert observed == [b""] + finally: + if server_handle is not None: + if thread.is_alive() and thread.native_id is not None: + thread_handle = open_thread(0x0001, False, thread.native_id) + if thread_handle: + try: + cancel_synchronous_io(thread_handle) + finally: + _winapi.CloseHandle(thread_handle) + _winapi.CloseHandle(server_handle) + thread.join(timeout=2) + assert not thread.is_alive() + + @pytest.mark.skipif( not hasattr(os, "set_blocking"), reason="Python runtime does not expose os.set_blocking on this platform", From 0f00b5df75c6dd862fbd72470314220a4c51dbc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Sat, 29 Aug 2026 21:29:42 +0800 Subject: [PATCH 4/8] fix: finalize 1.1.2 host-attested fast lane --- README.md | 21 +- README.zh-CN.md | 15 +- .../devkit_fastlane/FASTLANE_CONTRACT.md | 23 +- .../assets/fastlane-routing-policy-v5.json | 2 +- .../scripts/authenticated_v5_planner.py | 415 ++++ .../scripts/authenticated_v5_projection.py | 318 +++ .../scripts/fastlane_routing.py | 2 +- .../scripts/team_efficiency.py | 209 +- .../tests/test_fastlane_routing.py | 24 + .../tests/test_team_efficiency.py | 269 +++ .../devkit_runtime/fastlane_host_adapter.py | 339 ++- .../devkit_runtime/fastlane_host_intent.py | 5 +- .../fastlane_terminal_protocol.py | 274 +++ mcp-tools/devkit_runtime/host_bridge.py | 2118 ++++++++++++++++- mcp-tools/devkit_runtime/host_envelopes.py | 242 +- mcp-tools/devkit_runtime/host_session.py | 717 +++++- .../project_index_attestation_protocol.py | 223 ++ mcp-tools/project_index/service.py | 180 +- mcp-tools/server.py | 468 +++- mcp-tools/tests/compiler_evidence_vector.json | 303 +++ mcp-tools/tests/test_fastlane_host_adapter.py | 663 +++++- mcp-tools/tests/test_mcp_contract.py | 31 +- .../tests/test_project_index_host_material.py | 107 + 23 files changed, 6785 insertions(+), 183 deletions(-) create mode 100644 mcp-tools/devkit_fastlane/scripts/authenticated_v5_planner.py create mode 100644 mcp-tools/devkit_fastlane/scripts/authenticated_v5_projection.py create mode 100644 mcp-tools/devkit_runtime/fastlane_terminal_protocol.py create mode 100644 mcp-tools/devkit_runtime/project_index_attestation_protocol.py create mode 100644 mcp-tools/tests/compiler_evidence_vector.json create mode 100644 mcp-tools/tests/test_project_index_host_material.py diff --git a/README.md b/README.md index 5b7ede7..ceebf1b 100644 --- a/README.md +++ b/README.md @@ -327,17 +327,22 @@ The Fast Lane compiler is in mcp-tools/devkit_fastlane/scripts/fastlane_routing.py and mcp-tools/devkit_fastlane/scripts/team_efficiency.py. The public MCP entry is `fastlane_compile`; every current invocation is deliberately blocked with -`NO_SAFE_WORK` and zero assignments. +`NO_SAFE_WORK` and zero assignments unless the request is the closed +`fastlane-host-dispatch-request-v1` shape and this MCP process owns an +authenticated inherited host bridge. In that private case the host supplies +one-time registry-bound compiler evidence and receives a typed dispatch batch. -- `ultra` and `--enable` only select the shape of the blocked result; they do - not activate scheduling. +- `reasoning_effort` is required and accepts only `low`, `medium`, `high`, + `xhigh`, or `max`; worker dispatch never accepts `ultra`. - The public compiler/CLI does not consume host status, account usage, index evidence, or a worktree root. -- It never dispatches a session, creates a worktree, refills a slot, or runs a - command. No in-repository execution path exists for those actions. -- An external Desktop-host bridge may later provide attested project authority - and execution. That is a future contract, not a - shipped implementation or a claim that any Desktop host source exists. +- The compiler never creates a worktree, selects a route, or runs a command. + The authenticated session ACKs terminal slots and requests refill only at the + next host boundary. The compiler can only commit the fully hash-bound batch to the private + host bridge; the host remains the execution authority. +- Missing, stale, mismatched, replayed, or caller-supplied evidence keeps the + result at `NO_SAFE_WORK`. Filesystem paths are never accepted as compiler + evidence. ### Account-usage boundary diff --git a/README.zh-CN.md b/README.zh-CN.md index 46f82b9..7d7c06b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -284,13 +284,18 @@ Fast Lane 编译器位于 mcp-tools/devkit_fastlane/scripts/fastlane_routing.py 和 mcp-tools/devkit_fastlane/scripts/team_efficiency.py。公共 MCP 入口为 `fastlane_compile`;当前每一次调用都会刻意以 `NO_SAFE_WORK` 和零 assignments -被阻断。 +被阻断;唯一例外是 exact-key 的 `fastlane-host-dispatch-request-v1`,且当前 MCP +进程确实持有经过认证的 inherited host bridge。此时宿主通过一次性、registry-bound +的 compiler evidence 回传精确事实,并接收 typed dispatch batch。 -- `ultra` 和 `--enable` 只选择被阻断结果的形状,不会激活调度。 +- `reasoning_effort` 必填且只接受 `low`、`medium`、`high`、`xhigh` 或 + `max`;worker 调度永不接受 `ultra`。 - 公共编译器/CLI 不消费 host-status、账号用量、index evidence 或 worktree root。 -- 它不会派发会话、创建 worktree、补位或运行命令;仓库内不存在这些动作的执行路径。 -- 外部 Desktop-host bridge 未来可以提供经证明的项目权限和执行能力。 - 这只是未来合同,不是已交付实现,也不是任何 Desktop host 源码已经存在的声明。 +- 编译器不会创建 worktree、选择路由或运行命令;认证 session 只在 terminal ACK + 后请求下一宿主边界补位。编译器只能把完整 hash-bound batch 提交给私有宿主桥, + 真正执行权限仍属于宿主。 +- evidence 缺失、过期、错配、重放或来自 caller 自报时仍保持 `NO_SAFE_WORK`; + filesystem path 永远不能充当 compiler evidence。 ### 账号用量边界 diff --git a/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md b/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md index 59c74eb..08947b6 100644 --- a/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md +++ b/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md @@ -106,8 +106,9 @@ does not weaken host capability, lease, worktree, review, or safety gates. source-plan hash 必须包含该整个 binding,因此相同 task/workflow 在不同项目、workspace 或输入 snapshot 下不能共用计划、lease、receipt 或恢复状态。 -manifest 中的 fence 只是可验证的结构与 hash 输入,绝不是 authority。当前仓库没有 Desktop-host -durable registry 或真正私有的跨边界 authority bridge,因此没有任何同进程 provider、module +manifest 中的 fence 只是可验证的结构与 hash 输入,绝不是 authority。DevKit 不拥有 Desktop-host +durable registry;已认证 inherited host bridge 只能按一次性 nonce/expiry 向宿主 registry 请求 +compiler evidence。因此没有任何同进程 provider、module attribute、closure、环境变量、请求 JSON、repo/task root、路径名或 caller-supplied ID 可被当作 live authority。公开 `compile_fast_lane` 与 `fast-lane` CLI 对 structurally valid V2 一律产出 `NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE`,零本地 assignment、零队列、零外部派发;V2 @@ -117,18 +118,18 @@ envelope/hash 无效、其内层 canonical v1 `package` 不能完成纯诊断解 公开 MCP request 若试图携带明确的 host-private 字段(如 `host_status`、账号用量或 index evidence),则是适配器输入违规,必须在编译前以 `FASTLANE_REQUEST_INVALID` 拒绝,而不是把 该值当作可诊断的计划输入。 -增加 -Desktop-host durable registry、跨进程 authority 传递或公开 MCP 参数属于后续外部 host 合同,不能由 -工作包 JSON 或 Python 私有命名假装已经存在。 +`compiler-evidence-request-v1/response-v1` 与 typed dispatch batch 是唯一跨进程 +authority 通道:request/response 必须 exact-key、同 bridge generation、一次性且完整绑定 route、 +lease、scope、context、predecessor、worktree identity 与 registry hash。它不接受 actual path, +也不能由工作包 JSON、环境变量值或 Python 私有命名伪造。 同一限制覆盖 `bootstrap --apply` 及 import-callable `apply_bootstrap_plan`:当前公开入口在构建 caller-supplied bootstrap plan 或调用 worktree mutation 前,无条件以 `NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE` 失败关闭,因而不能到达 `git worktree add`。不带 `--apply` 的 `bootstrap` 仍只输出 dry-run 诊断计划;其中的 project、 -root、worktree 和任何 JSON 都不是 sealed V2 execution context。仓库当前不存在可执行的 -host-authorized worktree path:没有 module-private capability、runner、Git probe 或 adapter 可绕过 -该关闭结果。Desktop host registry 与真正私有的跨边界 execution bridge 是外部前置条件;它们尚未在 -本仓库实现,也不能用 Python module attribute、closure 或 caller-supplied JSON 伪装。 +root、worktree 和任何 JSON 都不是 sealed V2 execution context。DevKit 仍不存在自行创建 worktree +的可执行路径;authenticated compiler evidence 只允许将 typed batch 提交给宿主,不能绕过宿主的 +worktree broker、Git probe 或 coordinator gate。 ### 4. 接地后再写 @@ -140,13 +141,13 @@ host-authorized worktree path:没有 module-private capability、runner、Git #### Ultra Fast Lane -下面是未来外部 Desktop bridge 的 host 合同形状: +下面的 CLI host-status 仍是未启用的外部合同形状;已交付的 inherited bridge 不读取该文件: ```text python scripts/team_efficiency.py fast-lane --input --host-status --reasoning-effort ultra ``` -`ultra` 自动激活(Ultra automatic activation);低于 Ultra 的 effort 必须由 host 显式传入 `--enable`,否则得到 inactive plan。当前仓库的公开 `fast-lane` CLI/API 不消费 host-status、额度或 index 输入来激活该合同:在外部 Desktop authority bridge 实现并验收前,它始终输出 `NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE` 的零 assignment/队列预览。下文的 descriptor、route 与 host dispatch 约束只定义未来 bridge 的接入要求,不是本仓库存在的执行通路。`fast-lane` 本身不调用模型、不启动 agent、不创建会话或工作树、不运行 gate、不改写 Git、不领取或完成 workflow。协调器 lane 保有设计、集成、风险决策和最终验收责任;是否需要 Sol 设计/独立终审由精确的 host-attested route 决定,编译器不硬锁某个模型。 +`ultra` 自动激活(Ultra automatic activation);低于 Ultra 的 effort 必须由 host 显式传入 `--enable`,否则得到 inactive plan。公开 `fast-lane` CLI/API 不消费 host-status、额度或 index 输入,因此仍输出 `NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE` 的零 assignment/队列预览。只有 MCP 进程持有已认证 inherited bridge 且宿主返回 exact registry binding 时,私有 adapter 才能机械提交 `dispatch_all`;worker effort 禁止 `ultra`。编译器本身不调用模型、不启动 agent、不创建会话或工作树、不运行 gate、不改写 Git、不领取或完成 workflow。协调器 lane 保有设计、集成、风险决策和最终验收责任。 host 通过不超过 3 MiB、有 exact-key 的 `--host-status` 传入 `workflow_id`、当前 lease/binding 与 `routing_context`。后者按 `(task_id, scheduler_role)` 唯一关联完整 diff --git a/mcp-tools/devkit_fastlane/assets/fastlane-routing-policy-v5.json b/mcp-tools/devkit_fastlane/assets/fastlane-routing-policy-v5.json index b5e01c7..56b8cca 100644 --- a/mcp-tools/devkit_fastlane/assets/fastlane-routing-policy-v5.json +++ b/mcp-tools/devkit_fastlane/assets/fastlane-routing-policy-v5.json @@ -31,7 +31,7 @@ "maximum_cache_entries": 128, "maximum_gate_reason_codes": 16, "maximum_host_models": 8, - "maximum_total_slots": 8, + "maximum_total_slots": 9, "maximum_scope_items": 8, "maximum_dependency_items": 32 }, diff --git a/mcp-tools/devkit_fastlane/scripts/authenticated_v5_planner.py b/mcp-tools/devkit_fastlane/scripts/authenticated_v5_planner.py new file mode 100644 index 0000000..8916d2b --- /dev/null +++ b/mcp-tools/devkit_fastlane/scripts/authenticated_v5_planner.py @@ -0,0 +1,415 @@ +"""Pure authenticated Fast Lane V5 request and compiler-evidence helpers.""" + +from __future__ import annotations + +import hmac +import json +from collections.abc import Mapping, Sequence +from typing import Any + +_UNIT_FIELDS = frozenset( + { + "task", + "dependency_state", + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "predecessor_hash", + } +) +_CONTEXTUAL_UNIT_FIELDS = (_UNIT_FIELDS - {"predecessor_hash"}) | {"workflow_id_hash"} +_ATTESTATION_ITEM_FIELDS = frozenset({"task_id", "request_binding_hash", "attestation"}) +_CONCURRENCY_MODES = frozenset({"parallel", "serial", "isolated_worktree"}) + + +def owned_scope_hash(api: Any, task_id: object, write_scope: object) -> str: + normalized_task_id = api._task_id(task_id, "authenticated V5 task_id") + normalized_scope = api._normalised_scopes( + write_scope, "authenticated V5 write_scope" + ) + return api._sha256_json( + { + "schema": "2718lab-devkit/owned-write-scope-v1", + "task_id": normalized_task_id, + "write_scope": normalized_scope, + } + ) + + +def normalize_units( + api: Any, units: Sequence[Mapping[str, Any]] +) -> list[dict[str, Any]]: + if ( + not isinstance(units, Sequence) + or isinstance(units, (str, bytes, bytearray)) + or not 1 <= len(units) <= 16 + ): + raise ValueError("authenticated V5 units are out of bounds") + normalized: list[dict[str, Any]] = [] + for index, raw_unit in enumerate(units): + unit = api._mapping(raw_unit, f"authenticated V5 units[{index}]") + if set(unit) not in {_UNIT_FIELDS, _CONTEXTUAL_UNIT_FIELDS}: + raise ValueError(f"authenticated V5 units[{index}] has unsupported fields") + task = dict(api._mapping(unit["task"], f"authenticated V5 units[{index}].task")) + api._task_id( + task.get("task_id"), f"authenticated V5 units[{index}].task.task_id" + ) + write_scope = api._normalised_scopes( + unit["write_scope"], f"authenticated V5 units[{index}].write_scope" + ) + if ( + task.get("schema") != "2718lab-devkit/task-routing-profile-v5" + or task.get("role") != "execution" + or task.get("access") != "workspace_write" + or task.get("write_scope_count") != len(write_scope) + or task.get("overlap_risk") != "none" + or task.get("overlap_count") != 0 + ): + raise ValueError("authenticated V5 task is not an owned writer") + concurrency_mode = api._text( + unit["concurrency_mode"], + f"authenticated V5 units[{index}].concurrency_mode", + maximum=32, + ) + dispatch_order = unit["dispatch_order"] + if ( + concurrency_mode not in _CONCURRENCY_MODES + or type(dispatch_order) is not int + or not 0 <= dispatch_order < 16 + ): + raise ValueError("authenticated V5 dispatch facts are invalid") + normalized.append( + { + "task": task, + "dependency_state": json.loads( + api._canonical_json(unit["dependency_state"]) + ), + "write_scope": write_scope, + "concurrency_mode": concurrency_mode, + "dispatch_order": dispatch_order, + "index_context_hash": api._hash( + unit["index_context_hash"], + f"authenticated V5 units[{index}].index_context_hash", + ), + "predecessor_hash": ( + api._hash( + unit["predecessor_hash"], + f"authenticated V5 units[{index}].predecessor_hash", + ) + if "predecessor_hash" in unit + else None + ), + "workflow_id_hash": ( + api._hash( + unit["workflow_id_hash"], + f"authenticated V5 units[{index}].workflow_id_hash", + ) + if "workflow_id_hash" in unit + else None + ), + } + ) + task_ids = [str(item["task"]["task_id"]) for item in normalized] + orders = [int(item["dispatch_order"]) for item in normalized] + if ( + len(set(task_ids)) != len(task_ids) + or len(set(orders)) != len(orders) + or orders != sorted(orders) + ): + raise ValueError("authenticated V5 units contain duplicate bindings") + return normalized + + +def prepare_requests( + api: Any, + units: Sequence[Mapping[str, Any]], + *, + source_plan_hash: object, + host_capabilities: Mapping[str, Any], + scheduler_facts: Mapping[str, Any], +) -> list[dict[str, Any]]: + api._hash(source_plan_hash, "source_plan_hash") + core = api._fast_lane_routing_core() + if core is None: + raise ValueError("authenticated V5 routing core is unavailable") + normalized_units = normalize_units(api, units) + policy = core.load_policy_v5() + policy_hash = core.policy_hash_v5(policy) + canonical_host = json.loads(api._canonical_json(host_capabilities)) + canonical_scheduler = json.loads(api._canonical_json(scheduler_facts)) + requests: list[dict[str, Any]] = [] + for unit in normalized_units: + task_id = str(unit["task"]["task_id"]) + request = { + "schema": "2718lab-devkit/fastlane-routing-request-v5", + "policy_hash": policy_hash, + "task": unit["task"], + "dependency_state": unit["dependency_state"], + "scope_state": { + "schema": "2718lab-devkit/scope-state-v1", + "scope_epoch": canonical_scheduler.get("route_epoch"), + "owned_scope_hash": owned_scope_hash(api, task_id, unit["write_scope"]), + "conflicting_task_ids": [], + "active_writer_task_ids": [], + }, + "scheduler_facts": canonical_scheduler, + "host_capabilities": canonical_host, + "child_route_attestation": None, + "legacy": None, + } + try: + normalized_request = core._normalise_request_v5(request, policy) + except Exception as error: + raise ValueError("authenticated V5 routing request is invalid") from error + if request != normalized_request: + raise ValueError("authenticated V5 routing request is not canonical") + requests.append(json.loads(api._canonical_json(normalized_request))) + return requests + + +def compile_skeletons( + api: Any, + units: Sequence[Mapping[str, Any]], + *, + source_plan_hash: object, + routing_requests: Sequence[Mapping[str, Any]], + attestation_items: Sequence[Mapping[str, Any]], +) -> dict[str, list[dict[str, Any]]]: + source_hash = api._hash(source_plan_hash, "source_plan_hash") + normalized_units = normalize_units(api, units) + if ( + not isinstance(routing_requests, Sequence) + or isinstance(routing_requests, (str, bytes, bytearray)) + or len(routing_requests) != len(normalized_units) + or not isinstance(attestation_items, Sequence) + or isinstance(attestation_items, (str, bytes, bytearray)) + or len(attestation_items) != len(normalized_units) + ): + raise ValueError("authenticated V5 routing response is incomplete") + core = api._fast_lane_routing_core() + if core is None: + raise ValueError("authenticated V5 routing core is unavailable") + policy = core.load_policy_v5() + request_by_task: dict[str, dict[str, Any]] = {} + for raw_request in routing_requests: + request = json.loads(api._canonical_json(raw_request)) + try: + normalized_request = core._normalise_request_v5(request, policy) + except Exception as error: + raise ValueError("authenticated V5 routing request is invalid") from error + task = api._mapping(normalized_request["task"], "authenticated V5 request.task") + task_id = api._task_id(task.get("task_id"), "authenticated V5 request.task_id") + if ( + request != normalized_request + or request.get("child_route_attestation") is not None + or task_id in request_by_task + ): + raise ValueError( + "authenticated V5 pre-attestation request is not canonical" + ) + request_by_task[task_id] = normalized_request + + attestation_by_task: dict[str, dict[str, Any]] = {} + for index, raw_item in enumerate(attestation_items): + item = api._mapping(raw_item, f"authenticated V5 attestations[{index}]") + api._exact_keys( + item, + _ATTESTATION_ITEM_FIELDS, + f"authenticated V5 attestations[{index}]", + ) + task_id = api._task_id( + item["task_id"], f"authenticated V5 attestations[{index}].task_id" + ) + request = request_by_task.get(task_id) + if request is None or task_id in attestation_by_task: + raise ValueError("authenticated V5 attestation task binding is invalid") + binding_hash = api._hash( + item["request_binding_hash"], + f"authenticated V5 attestations[{index}].request_binding_hash", + ) + if not hmac.compare_digest(binding_hash, core.v5_request_binding_hash(request)): + raise ValueError("authenticated V5 request binding is invalid") + attestation = dict( + api._mapping( + item["attestation"], + f"authenticated V5 attestations[{index}].attestation", + ) + ) + supplied_hash = api._hash( + attestation.get("attestation_hash"), + f"authenticated V5 attestations[{index}].attestation_hash", + ) + expected_hash = api._sha256_json( + { + key: value + for key, value in attestation.items() + if key != "attestation_hash" + } + ) + if not hmac.compare_digest( + supplied_hash, expected_hash + ) or not hmac.compare_digest( + binding_hash, str(attestation.get("request_binding_hash")) + ): + raise ValueError("authenticated V5 Host attestation is invalid") + attestation_by_task[task_id] = attestation + + skeletons: list[dict[str, Any]] = [] + route_pairs: set[tuple[str, str]] = set() + for unit in normalized_units: + task_id = str(unit["task"]["task_id"]) + request = request_by_task.get(task_id) + attestation = attestation_by_task.get(task_id) + if request is None or attestation is None: + raise ValueError("authenticated V5 routing response is incomplete") + attested_request = {**request, "child_route_attestation": attestation} + try: + normalized_request = core._normalise_request_v5(attested_request, policy) + result = core.route_v5(normalized_request, policy=policy) + except Exception as error: + raise ValueError("authenticated V5 Host route is invalid") from error + if ( + attested_request != normalized_request + or result.get("schema") != "2718lab-devkit/fastlane-routing-result-v5" + or result.get("status") != "resolved" + or result.get("task_id") != task_id + ): + raise ValueError("authenticated V5 Host route did not resolve") + route = api._mapping(result.get("route"), "authenticated V5 result.route") + model = api._text( + route.get("model"), "authenticated V5 result.route.model", maximum=64 + ) + effort = api._text( + route.get("effort"), "authenticated V5 result.route.effort", maximum=16 + ) + if route.get("inherit_current_session_model") is not False or effort == "ultra": + raise ValueError("authenticated V5 route is not explicit") + context_hash = api._sha256_json( + { + "schema": "team-efficiency/fast-lane-routing-context-binding-v1", + "source_plan_hash": source_hash, + "task_id": task_id, + "scheduler_role": normalized_request["task"]["role"], + "routing_request_hash": api._sha256_json(normalized_request), + "scheduler_facts_hash": api._sha256_json( + normalized_request["scheduler_facts"] + ), + } + ) + result_hash = api._sha256_json(result) + predecessor_hash = unit["predecessor_hash"] + if predecessor_hash is None: + predecessor_hash = api._sha256_json( + { + "schema": "team-efficiency/fast-lane-external-lease-predecessor-v1", + "source_plan_hash": source_hash, + "workflow_id_hash": unit["workflow_id_hash"], + "task_id": task_id, + "role": normalized_request["task"]["role"], + "context_hash": context_hash, + "routing_result_hash": result_hash, + } + ) + skeletons.append( + { + "task_id": task_id, + "routing_proof": { + "request": normalized_request, + "result": result, + "request_binding_hash": core.v5_request_binding_hash(request), + "attestation_hash": attestation["attestation_hash"], + "routing_context_hash": context_hash, + "routing_result_hash": result_hash, + }, + "write_scope": unit["write_scope"], + "concurrency_mode": unit["concurrency_mode"], + "dispatch_order": unit["dispatch_order"], + "index_context_hash": unit["index_context_hash"], + "predecessor_hash": predecessor_hash, + "source_plan_hash": source_hash, + } + ) + route_pairs.add((model, effort)) + return { + "assignment_skeletons": skeletons, + "requested_route_pairs": [ + {"model": model, "effort": effort} for model, effort in sorted(route_pairs) + ], + } + + +def validate_skeleton_package( + api: Any, + source_plan_units: Sequence[Mapping[str, Any]], + initial_skeletons: Sequence[Mapping[str, Any]], + remaining_skeletons: Sequence[Mapping[str, Any]], + *, + source_plan_hash: object, +) -> str: + """Validate the complete package before a wave is registered. + + A wave is allowed to be sparse, but the package boundary is not. Keep + this check separate from ``normalize_units`` because that helper is also + used for the intentionally sparse initial/refill slices. + """ + + source_hash = api._hash(source_plan_hash, "source_plan_hash") + source_ids: list[str] = [] + for index, raw_unit in enumerate(source_plan_units): + unit = api._mapping(raw_unit, f"authenticated V5 source plan units[{index}]") + task = api._mapping( + unit.get("task"), f"authenticated V5 source plan units[{index}].task" + ) + source_ids.append( + api._task_id( + task.get("task_id"), + f"authenticated V5 source plan units[{index}].task.task_id", + ) + ) + if not 1 <= len(source_ids) <= 16 or len(set(source_ids)) != len(source_ids): + raise ValueError("authenticated V5 source plan task coverage is invalid") + + combined: list[dict[str, Any]] = [] + for wave_name, wave in ( + ("initial", initial_skeletons), + ("remaining", remaining_skeletons), + ): + if not isinstance(wave, Sequence) or isinstance(wave, (str, bytes, bytearray)): + raise ValueError(f"authenticated V5 {wave_name} skeletons are invalid") + for index, raw_skeleton in enumerate(wave): + skeleton = dict( + api._mapping(raw_skeleton, f"authenticated V5 {wave_name} skeletons[{index}]") + ) + if skeleton.get("source_plan_hash") != source_hash: + raise ValueError("authenticated V5 skeleton source hash is invalid") + task_id = api._task_id( + skeleton.get("task_id"), + f"authenticated V5 {wave_name} skeletons[{index}].task_id", + ) + order = skeleton.get("dispatch_order") + if type(order) is not int or not 0 <= order < len(source_ids): + raise ValueError("authenticated V5 package dispatch order is invalid") + combined.append(skeleton) + + if len(combined) != len(source_ids): + raise ValueError("authenticated V5 skeleton package is incomplete") + task_ids = [str(item["task_id"]) for item in combined] + orders = [int(item["dispatch_order"]) for item in combined] + if ( + len(set(task_ids)) != len(task_ids) + or set(task_ids) != set(source_ids) + or len(set(orders)) != len(orders) + or set(orders) != set(range(len(source_ids))) + ): + raise ValueError("authenticated V5 skeleton package coverage is invalid") + + ordered = sorted(combined, key=lambda item: int(item["dispatch_order"])) + return api._sha256_json( + { + "schema": "2718lab-devkit/authenticated-v5-skeleton-package-v1", + "source_plan_hash": source_hash, + "task_ids": source_ids, + "assignment_skeletons": ordered, + } + ) diff --git a/mcp-tools/devkit_fastlane/scripts/authenticated_v5_projection.py b/mcp-tools/devkit_fastlane/scripts/authenticated_v5_projection.py new file mode 100644 index 0000000..91f5a94 --- /dev/null +++ b/mcp-tools/devkit_fastlane/scripts/authenticated_v5_projection.py @@ -0,0 +1,318 @@ +"""Mechanical raw Fast Lane request projection into authenticated V5 units.""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from typing import Any + + +def project_units( + api: Any, + request: Mapping[str, Any], + *, + index_context_hash: object, + scheduler_facts: Mapping[str, Any], + host_capabilities: Mapping[str, Any] | None = None, +) -> tuple[str, list[dict[str, Any]]]: + """Project the first host-admitted wave. + + The public helper keeps its historical two-value return shape. The + authenticated server path uses :func:`project_units_with_waves` so route + attestations can cover the whole bounded package while dispatch remains + limited to the live first-wave capacity. + """ + + source_plan_hash, initial, _remaining = project_units_with_waves( + api, + request, + index_context_hash=index_context_hash, + scheduler_facts=scheduler_facts, + host_capabilities=host_capabilities, + ) + return source_plan_hash, initial + + +def project_units_with_waves( + api: Any, + request: Mapping[str, Any], + *, + index_context_hash: object, + scheduler_facts: Mapping[str, Any], + host_capabilities: Mapping[str, Any] | None = None, +) -> tuple[str, list[dict[str, Any]], list[dict[str, Any]]]: + """Project a package into an initial wave and a host-owned refill queue. + + The package is bounded to sixteen units. The first wave is capped at + ``min(host_available, config_capacity, 3)``. Each emitted slice retains + its source-plan dispatch coordinates; the complete package remains the + authority for checking a hole-free global order. + """ + + candidate = api._mapping(request, "fast-lane request") + api._exact_keys(candidate, api._FAST_LANE_REQUEST_FIELDS, "fast-lane request") + if ( + candidate.get("schema") != "team-efficiency/fast-lane-request-v1" + or len(api._json_bytes(candidate)) > api.MAX_MANIFEST_INPUT_BYTES + or candidate.get("remediation_request") is not None + ): + raise ValueError("authenticated V5 raw request is unsupported") + source_plan = api.decompose(candidate["work_package"]) + if source_plan.get("status") != "planned": + raise ValueError("authenticated V5 source plan is not schedulable") + source_plan_hash = api._sha256_json(source_plan) + project_binding = api._validated_project_binding(candidate["project_binding"]) + project_authority = api._mapping( + source_plan.get("project_authority"), "source plan.project_authority" + ) + attestation = api._mapping( + project_binding["attestation"], "project binding.attestation" + ) + if ( + project_binding["mode"] != "indexed" + or project_binding["project_id"] + != f"sha256:{project_authority.get('project_id')}" + or project_binding["workspace_id"] != project_authority.get("workspace_id") + or attestation["attested_input_snapshot_id"] + != project_authority.get("input_snapshot_id") + ): + raise ValueError("authenticated V5 project binding is stale") + target_gates = api._validated_fast_lane_target_gates( + candidate["target_gates"], source_plan + ) + execution_contexts, read_contexts = api._validated_fast_lane_contexts( + candidate["execution_contexts"], + candidate["read_contexts"], + source_plan, + candidate["scheduler_state"], + ) + state, remediation = api._validated_fast_lane_scheduler_state( + candidate["scheduler_state"], + source_plan=source_plan, + source_plan_hash=source_plan_hash, + execution_contexts=execution_contexts, + read_contexts=read_contexts, + target_gates=target_gates, + remediation_request_value=None, + routing_context={}, + ) + if remediation is not None or state["phase"] != "execution": + raise ValueError("authenticated V5 scheduler phase is unsupported") + + units_by_task = api._fast_lane_unit_index(source_plan) + if not 1 <= len(units_by_task) <= 16: + raise ValueError("authenticated V5 source plan exceeds the bounded queue") + config_capacity = source_plan.get("capacity") + if type(config_capacity) is not int or not 1 <= config_capacity <= 16: + raise ValueError("authenticated V5 source plan capacity is invalid") + host_capacity = _host_available_capacity( + scheduler_facts, host_capabilities=host_capabilities + ) + first_wave_capacity = min(3, config_capacity, host_capacity) + if first_wave_capacity < 1: + raise ValueError("authenticated V5 Host has no available writer slot") + + completed = api._fast_lane_completed_ids(state) + running = frozenset(item["task_id"] for item in state["running_assignments"]) + candidates = frozenset(item["task_id"] for item in state["review_ready_candidates"]) + reviewed = frozenset(item["task_id"] for item in state["reviewed_candidates"]) + graph = api._fast_lane_conflict_graph( + units_by_task, + lane0_scopes=state["lane0_state"]["owned_write_scopes"], + running=state["running_assignments"], + ) + ready = api._fast_lane_ready_items( + units_by_task, + completed, + frozenset(state["blocked_task_ids"]), + running, + candidates, + reviewed, + conflict_graph=graph, + ) + selected_ids = api._maximal_ready_wave(ready, graph, first_wave_capacity) + if not selected_ids: + raise ValueError("authenticated V5 has no ready owned writers") + package_order = api._fast_lane_topology_index(units_by_task) + ordered_selected_ids = sorted(selected_ids, key=package_order.__getitem__) + selected_set = set(ordered_selected_ids) + ordered_remaining_ids = [ + task_id + for task_id, _order in sorted(package_order.items(), key=lambda item: item[1]) + if ( + task_id not in selected_set + and task_id not in completed + and task_id not in running + ) + ] + index_hash = api._hash(index_context_hash, "index_context_hash") + context_by_task = {item["task_id"]: item for item in execution_contexts} + target_by_task = {item["task_id"]: item for item in target_gates} + workflow_hash = api._sha256_json({"workflow_id": project_binding["workflow_id"]}) + + ancestor_cache: dict[str, int] = {} + + def ancestor_depth(task_id: str) -> int: + cached = ancestor_cache.get(task_id) + if cached is not None: + return cached + dependencies = list(units_by_task[task_id].get("depends_on", [])) + depth = ( + 0 + if not dependencies + else 1 + max(ancestor_depth(item) for item in dependencies) + ) + ancestor_cache[task_id] = depth + return depth + + downstream_cache: dict[str, frozenset[str]] = {} + + def downstream(task_id: str) -> frozenset[str]: + cached = downstream_cache.get(task_id) + if cached is not None: + return cached + direct = { + other_id + for other_id, other in units_by_task.items() + if task_id in other.get("depends_on", []) + } + result = frozenset( + direct | {item for child in direct for item in downstream(child)} + ) + downstream_cache[task_id] = result + return result + + maximum_downstream = max(len(downstream(task_id)) for task_id in units_by_task) + + def project_slice(task_ids: Sequence[str]) -> list[dict[str, Any]]: + projected_slice: list[dict[str, Any]] = [] + for task_id in task_ids: + # dispatch_order is a source-plan coordinate, not a batch-local + # ordinal. A refill wave can therefore contain a legitimate + # sparse slice (for example [1, 3]) while the concatenated plan + # remains the complete 0..N-1 sequence. + dispatch_order = package_order[task_id] + source_unit = units_by_task[task_id] + if ( + context_by_task.get(task_id) is None + or target_by_task.get(task_id) is None + ): + raise ValueError("authenticated V5 execution context is incomplete") + target = target_by_task[task_id] + write_scope = api._normalised_scopes(source_unit.get("write_scope", [])) + top_levels = {path.split("/", 1)[0] for path in write_scope} + breadth = ( + "single_file" + if len(write_scope) == 1 + else "single_module" + if len(top_levels) == 1 + else "multi_module" + ) + direct_dependencies = sorted(source_unit.get("depends_on", [])) + dependency_without_hash = { + "schema": "2718lab-devkit/dependency-state-v1", + "graph_epoch": scheduler_facts.get("route_epoch"), + "direct_dependency_ids": direct_dependencies, + "completed_dependency_ids": sorted( + set(direct_dependencies).intersection(completed) + ), + } + dependency_state = { + **dependency_without_hash, + "dependency_state_hash": api._sha256_json(dependency_without_hash), + } + criticality = { + "Terra High": "normal", + "Terra Max": "high", + "Sol High": "critical", + }.get(str(source_unit.get("recommended_route")), "critical") + profile_material = { + "schema": "team-efficiency/fast-lane-v5-profile-evidence-v1", + "source_plan_hash": source_plan_hash, + "source_unit": source_unit, + "target_gates": target, + "dependency_state": dependency_state, + } + task = { + "schema": "2718lab-devkit/task-routing-profile-v5", + "task_id": task_id, + "role": "execution", + "access": "workspace_write", + "write_scope_count": len(write_scope), + "write_scope_breadth": breadth, + "read_scope_count": 0, + "read_scope_breadth": "none", + "overlap_risk": "none", + "overlap_count": 0, + "dependency_depth": ancestor_depth(task_id), + "downstream_critical_count": len(downstream(task_id)), + "critical_path": maximum_downstream > 0 + and len(downstream(task_id)) == maximum_downstream, + "criticality": criticality, + "cross_module": len(top_levels) > 1, + "database_work": False, + "migration": False, + "security_sensitive": False, + "destructive": False, + "external_boundary": False, + "architecture_conflict": False, + "design_ambiguity": False, + "verification_cost": "focused" + if len(target["gates"]) == 1 + else "multi_gate", + "blocker_severity": "none", + "authorization": "not_required", + "authorization_evidence_hash": None, + "narrow_decoupling_eligible": False, + "strike": None, + "gate_matrix_hash": api._sha256_json(target), + "profile_evidence_hash": api._sha256_json(profile_material), + } + projected_slice.append( + { + "task": task, + "dependency_state": dependency_state, + "write_scope": write_scope, + "concurrency_mode": "parallel", + "dispatch_order": dispatch_order, + "index_context_hash": index_hash, + "workflow_id_hash": workflow_hash, + } + ) + return projected_slice + + return ( + source_plan_hash, + project_slice(ordered_selected_ids), + project_slice(ordered_remaining_ids), + ) + + +def _host_available_capacity( + scheduler_facts: Mapping[str, Any], + *, + host_capabilities: Mapping[str, Any] | None, +) -> int: + """Extract an attested available writer count without guessing a route.""" + + fields = ( + "available_writer_slots", + "available_slots", + "host_available", + "writer_capacity", + "available_capacity", + ) + for facts in (scheduler_facts, host_capabilities): + if not isinstance(facts, Mapping): + continue + for field in fields: + value = facts.get(field) + if type(value) is int and 0 <= value <= 16: + return value + if facts is host_capabilities: + value = facts.get("total_slots") + if type(value) is int and 0 <= value <= 16: + return value + # The pure helper predates the bridge capability argument. Its callers do + # not have a Host snapshot, so retain a conservative test-only three-slot + # cap; the authenticated server always supplies the Host snapshot. + return 3 diff --git a/mcp-tools/devkit_fastlane/scripts/fastlane_routing.py b/mcp-tools/devkit_fastlane/scripts/fastlane_routing.py index 7fac034..04fd74a 100644 --- a/mcp-tools/devkit_fastlane/scripts/fastlane_routing.py +++ b/mcp-tools/devkit_fastlane/scripts/fastlane_routing.py @@ -2410,7 +2410,7 @@ def _validate_policy_v5(policy: object) -> dict[str, Any]: "maximum_cache_entries": 128, "maximum_gate_reason_codes": 16, "maximum_host_models": 8, - "maximum_total_slots": 8, + "maximum_total_slots": 9, "maximum_scope_items": 8, "maximum_dependency_items": 32, } diff --git a/mcp-tools/devkit_fastlane/scripts/team_efficiency.py b/mcp-tools/devkit_fastlane/scripts/team_efficiency.py index ce4ee28..3c2595c 100644 --- a/mcp-tools/devkit_fastlane/scripts/team_efficiency.py +++ b/mcp-tools/devkit_fastlane/scripts/team_efficiency.py @@ -11,6 +11,7 @@ import re import stat import sys +import unicodedata from collections.abc import Callable, Iterable, Mapping, Sequence from datetime import UTC, datetime from pathlib import Path @@ -62,7 +63,7 @@ Path("mcp-tools/devkit_fastlane/tests/test_team_efficiency.py"), ) -_TASK_ID = re.compile(r"^(?:[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+|FLR1-[0-9a-f]{24})$") +_TASK_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,95}$") _BRANCH = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]*$") _GIT_ID = re.compile(r"^[0-9a-fA-F]{40}(?:[0-9a-fA-F]{24})?$") _SHA256 = re.compile(r"^sha256:[0-9a-fA-F]{64}$") @@ -70,6 +71,16 @@ _PROJECT_FENCE_PROJECT_ID = re.compile(r"^[0-9a-f]{64}$") _UTC_Z = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$") _PATH_PART = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") +_WINDOWS_RESERVED_NAMES = frozenset( + { + "con", + "prn", + "aux", + "nul", + *(f"com{index}" for index in range(1, 10)), + *(f"lpt{index}" for index in range(1, 10)), + } +) _LABEL = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]*$") _ENDPOINT = re.compile( r"^/[A-Za-z0-9][A-Za-z0-9._/-]*(?:/[A-Za-z0-9][A-Za-z0-9._-]*)*$" @@ -1194,10 +1205,21 @@ def _project_execution_block_details( def _relative_scope(value: object, field: str) -> str: text = _text(value, field, maximum=256) - if text.startswith("/") or "\\" in text or ":" in text: + if ( + text.startswith("/") + or "\\" in text + or ":" in text + or any(ord(character) < 32 for character in text) + ): raise ValueError(f"{field} must be a bounded relative path") - parts = text.split("/") - if any(not _PATH_PART.fullmatch(part) for part in parts): + parts = unicodedata.normalize("NFC", text).casefold().split("/") + if any( + not _PATH_PART.fullmatch(part) + or part in {".", ".."} + or part.endswith((".", " ")) + or part.rstrip(" .").split(".", 1)[0] in _WINDOWS_RESERVED_NAMES + for part in parts + ): raise ValueError(f"{field} must be a bounded relative path") return "/".join(parts) @@ -3041,7 +3063,7 @@ def _episode_units( ] if not episode_ids: raise AtlasEvidenceError("ATLAS_TASK_EPISODE_MISSING") - if len(episode_ids) >= MAX_MANIFEST_UNITS: + if len(episode_ids) > MAX_MANIFEST_UNITS: raise AtlasEvidenceError("ATLAS_UNIT_BUDGET_EXCEEDED") change_edges: dict[str, list[dict[str, Any]]] = { @@ -3282,9 +3304,9 @@ def _ensure_acyclic(units: Mapping[str, Mapping[str, Any]]) -> None: def _scope_conflicts(left: Sequence[str], right: Sequence[str]) -> bool: for first in left: - first_parts = first.split("/") + first_parts = [part.casefold() for part in first.split("/")] for second in right: - second_parts = second.split("/") + second_parts = [part.casefold() for part in second.split("/")] shared = min(len(first_parts), len(second_parts)) if first_parts[:shared] == second_parts[:shared]: return True @@ -4722,6 +4744,179 @@ def _fast_lane_routing_core() -> Any | None: return None +def _authenticated_v5_helper_module(module_name: str) -> Any: + """Load one sibling V5 helper without changing public import topology.""" + + qualified_name = f"_team_efficiency_{module_name}" + module_path = Path(__file__).with_name(f"{module_name}.py").resolve() + loaded = sys.modules.get(qualified_name) + if ( + loaded is not None + and Path(str(getattr(loaded, "__file__", ""))).resolve() == module_path + ): + return loaded + spec = importlib.util.spec_from_file_location(qualified_name, module_path) + if spec is None or spec.loader is None: + raise ValueError("authenticated V5 helper is unavailable") + module = importlib.util.module_from_spec(spec) + sys.modules[qualified_name] = module + try: + spec.loader.exec_module(module) + except Exception: + sys.modules.pop(qualified_name, None) + raise + return module + + +class _AuthenticatedV5Api: + """Late-bound view over this module, including spec-loaded test imports.""" + + def __getattr__(self, name: str) -> Any: + try: + return globals()[name] + except KeyError as error: + raise AttributeError(name) from error + + +def authenticated_v5_owned_scope_hash(task_id: object, write_scope: object) -> str: + planner = _authenticated_v5_helper_module("authenticated_v5_planner") + return planner.owned_scope_hash(_AuthenticatedV5Api(), task_id, write_scope) + + +def _authenticated_v5_units( + units: Sequence[Mapping[str, Any]], +) -> list[dict[str, Any]]: + planner = _authenticated_v5_helper_module("authenticated_v5_planner") + return planner.normalize_units(_AuthenticatedV5Api(), units) + + +def _authenticated_v5_raw_projection( + request: Mapping[str, Any], + *, + index_context_hash: object, + scheduler_facts: Mapping[str, Any], + host_capabilities: Mapping[str, Any] | None = None, +) -> tuple[str, list[dict[str, Any]]]: + projection = _authenticated_v5_helper_module("authenticated_v5_projection") + return projection.project_units( + _AuthenticatedV5Api(), + request, + index_context_hash=index_context_hash, + scheduler_facts=scheduler_facts, + host_capabilities=host_capabilities, + ) + + +def prepare_authenticated_v5_routing_from_request( + request: Mapping[str, Any], + *, + index_context_hash: object, + host_capabilities: Mapping[str, Any], + scheduler_facts: Mapping[str, Any], +) -> dict[str, Any]: + projection = _authenticated_v5_helper_module("authenticated_v5_projection") + ( + source_plan_hash, + units, + remaining_units, + ) = projection.project_units_with_waves( + _AuthenticatedV5Api(), + request, + index_context_hash=index_context_hash, + scheduler_facts=scheduler_facts, + host_capabilities=host_capabilities, + ) + routing_requests = prepare_authenticated_v5_routing_requests( + units, + source_plan_hash=source_plan_hash, + host_capabilities=host_capabilities, + scheduler_facts=scheduler_facts, + ) + remaining_routing_requests = ( + prepare_authenticated_v5_routing_requests( + remaining_units, + source_plan_hash=source_plan_hash, + host_capabilities=host_capabilities, + scheduler_facts=scheduler_facts, + ) + if remaining_units + else [] + ) + all_units = sorted( + [*units, *remaining_units], key=lambda unit: int(unit["dispatch_order"]) + ) + order_by_task = { + str(unit["task"]["task_id"]): int(unit["dispatch_order"]) + for unit in all_units + } + all_routing_requests = sorted( + [*routing_requests, *remaining_routing_requests], + key=lambda request: order_by_task[str(request["task"]["task_id"])], + ) + return { + "source_plan_hash": source_plan_hash, + "units": units, + "routing_requests": routing_requests, + "remaining_units": remaining_units, + "remaining_routing_requests": remaining_routing_requests, + "all_units": all_units, + "all_routing_requests": all_routing_requests, + } + + +def prepare_authenticated_v5_routing_requests( + units: Sequence[Mapping[str, Any]], + *, + source_plan_hash: object, + host_capabilities: Mapping[str, Any], + scheduler_facts: Mapping[str, Any], +) -> list[dict[str, Any]]: + planner = _authenticated_v5_helper_module("authenticated_v5_planner") + return planner.prepare_requests( + _AuthenticatedV5Api(), + units, + source_plan_hash=source_plan_hash, + host_capabilities=host_capabilities, + scheduler_facts=scheduler_facts, + ) + + +def compile_authenticated_v5_assignment_skeletons( + units: Sequence[Mapping[str, Any]], + *, + source_plan_hash: object, + routing_requests: Sequence[Mapping[str, Any]], + attestation_items: Sequence[Mapping[str, Any]], +) -> dict[str, list[dict[str, Any]]]: + planner = _authenticated_v5_helper_module("authenticated_v5_planner") + return planner.compile_skeletons( + _AuthenticatedV5Api(), + units, + source_plan_hash=source_plan_hash, + routing_requests=routing_requests, + attestation_items=attestation_items, + ) + + +def validate_authenticated_v5_skeleton_package( + source_plan_units: Sequence[Mapping[str, Any]], + initial_skeletons: Sequence[Mapping[str, Any]], + remaining_skeletons: Sequence[Mapping[str, Any]], + *, + source_plan_hash: object, +) -> str: + """Validate and hash the complete package at the wave boundary.""" + + planner = _authenticated_v5_helper_module("authenticated_v5_planner") + return planner.validate_skeleton_package( + _AuthenticatedV5Api(), + source_plan_units, + initial_skeletons, + remaining_skeletons, + source_plan_hash=source_plan_hash, + ) + + def _fast_lane_failure_reason(value: object) -> str: """Project only bounded core failures into the scheduler's idle vocabulary.""" diff --git a/mcp-tools/devkit_fastlane/tests/test_fastlane_routing.py b/mcp-tools/devkit_fastlane/tests/test_fastlane_routing.py index 383b5ee..f7f58b1 100644 --- a/mcp-tools/devkit_fastlane/tests/test_fastlane_routing.py +++ b/mcp-tools/devkit_fastlane/tests/test_fastlane_routing.py @@ -1022,6 +1022,30 @@ def test_v5_uses_a_fresh_host_attested_child_tuple_above_terra_without_dispatchi assert result["capability_resolution"]["state"] == "host_attested" +def test_v5_accepts_nine_host_slots_without_inflating_lane_limits() -> None: + core = _load_core() + policy = core.load_policy_v5() + assert policy["limits"]["maximum_total_slots"] == 9 + + request = _request_v5() + host = request["host_capabilities"] + assert isinstance(host, dict) + host["total_slots"] = 9 + normalized = core._normalise_host_v5(host, policy) + assert normalized["total_slots"] == 9 + assert normalized["model_slot_limits"] == { + "luna": 4, + "terra": 4, + "sol": 4, + "spark": 1, + } + + host["total_slots"] = 10 + with pytest.raises(core.RoutingError) as exc: + core._normalise_host_v5(host, policy) + assert exc.value.code == "invalid_bounds" + + @pytest.mark.parametrize( ("mutate", "status", "reason"), [ diff --git a/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py b/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py index afcd1fe..c3bf462 100644 --- a/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py +++ b/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py @@ -8697,6 +8697,275 @@ def identity(name: str) -> str: ], ) + def test_authenticated_v5_planner_emits_exact_proofs_and_skeletons(self) -> None: + helper = load_efficiency() + core = load_fastlane_routing() + hash_a = helper._sha256_json({"fixture": "a"}) + hash_b = helper._sha256_json({"fixture": "b"}) + source_plan_hash = helper._sha256_json({"fixture": "plan"}) + dependency = { + "schema": "2718lab-devkit/dependency-state-v1", + "graph_epoch": 1, + "direct_dependency_ids": [], + "completed_dependency_ids": [], + } + dependency["dependency_state_hash"] = helper._sha256_json(dependency) + scheduler = { + "event_seq": 1, + "route_epoch": 1, + "override_epoch": 0, + "recovery_epoch": 0, + "ready_event_seq": 1, + "dispatch_cause": "task_ready", + "transport_state": "connected", + "execution_state": "unknown", + "lease_state": "unclaimed", + "evidence_state": "none", + "lease_epoch": 0, + "recovery_probe_count_epoch": 0, + "fence_count_epoch": 0, + "fenced_replacement_count_task": 0, + } + host = { + "schema": "2718lab-devkit/host-capabilities-v1", + "host_id_hash": hash_a, + "capability_epoch": 1, + "total_slots": 4, + "model_slot_limits": {"luna": 4, "terra": 0, "sol": 0, "spark": 0}, + "models": [ + { + "model_id": "gpt-5.6-luna", + "status": "available", + "efforts": ["max"], + } + ], + "entitlements": [], + } + unit = { + "task": { + "schema": "2718lab-devkit/task-routing-profile-v5", + "task_id": "TASK-V5", + "role": "execution", + "access": "workspace_write", + "write_scope_count": 1, + "write_scope_breadth": "single_file", + "read_scope_count": 0, + "read_scope_breadth": "none", + "overlap_risk": "none", + "overlap_count": 0, + "dependency_depth": 0, + "downstream_critical_count": 0, + "critical_path": False, + "criticality": "low", + "cross_module": False, + "database_work": False, + "migration": False, + "security_sensitive": False, + "destructive": False, + "external_boundary": False, + "architecture_conflict": False, + "design_ambiguity": False, + "verification_cost": "none", + "blocker_severity": "none", + "authorization": "not_required", + "authorization_evidence_hash": None, + "narrow_decoupling_eligible": False, + "strike": None, + "gate_matrix_hash": hash_a, + "profile_evidence_hash": hash_b, + }, + "dependency_state": dependency, + "write_scope": ["src/task_v5.py"], + "concurrency_mode": "parallel", + "dispatch_order": 0, + "index_context_hash": hash_a, + "predecessor_hash": hash_b, + } + requests = helper.prepare_authenticated_v5_routing_requests( + [unit], + source_plan_hash=source_plan_hash, + host_capabilities=host, + scheduler_facts=scheduler, + ) + self.assertIsNone(requests[0]["child_route_attestation"]) + binding_hash = core.v5_request_binding_hash(requests[0]) + attestation = { + "schema": "2718lab-devkit/host-child-route-attestation-v1", + "status": "attested", + "request_binding_hash": binding_hash, + "host_id_hash": hash_a, + "capability_epoch": 1, + "lease_epoch": 0, + "issued_event_seq": 1, + "expires_event_seq": 1, + "route": { + "lane": "luna", + "model": "gpt-5.6-luna", + "effort": "max", + "rank": 40, + }, + "inherit_current_session_model": False, + "refusal_code": None, + } + attestation["attestation_hash"] = helper._sha256_json(attestation) + compiled = helper.compile_authenticated_v5_assignment_skeletons( + [unit], + source_plan_hash=source_plan_hash, + routing_requests=requests, + attestation_items=[ + { + "task_id": "TASK-V5", + "request_binding_hash": binding_hash, + "attestation": attestation, + } + ], + ) + self.assertEqual( + [{"model": "gpt-5.6-luna", "effort": "max"}], + compiled["requested_route_pairs"], + ) + skeleton = compiled["assignment_skeletons"][0] + self.assertEqual( + { + "task_id", + "routing_proof", + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "predecessor_hash", + "source_plan_hash", + }, + set(skeleton), + ) + self.assertEqual( + { + "request", + "result", + "request_binding_hash", + "attestation_hash", + "routing_context_hash", + "routing_result_hash", + }, + set(skeleton["routing_proof"]), + ) + + def test_authenticated_v5_raw_request_projects_ready_units(self) -> None: + helper = load_efficiency() + request = self.fast_lane_request(helper) + source_plan = helper.decompose(request["work_package"]) + ready_ids = sorted(unit["task_id"] for unit in source_plan["waves"][0]) + index_context_hash = helper._sha256_json({"index": "workspace-query"}) + scheduler = { + "event_seq": 1, + "route_epoch": 1, + "override_epoch": 0, + "recovery_epoch": 0, + "ready_event_seq": 1, + "dispatch_cause": "task_ready", + "transport_state": "connected", + "execution_state": "unknown", + "lease_state": "unclaimed", + "evidence_state": "none", + "lease_epoch": 0, + "recovery_probe_count_epoch": 0, + "fence_count_epoch": 0, + "fenced_replacement_count_task": 0, + } + host = { + "schema": "2718lab-devkit/host-capabilities-v1", + "host_id_hash": helper._sha256_json({"host": "raw-projection"}), + "capability_epoch": 1, + "total_slots": 4, + "model_slot_limits": {"luna": 4, "terra": 0, "sol": 0, "spark": 0}, + "models": [ + { + "model_id": "gpt-5.6-luna", + "status": "available", + "efforts": ["max"], + } + ], + "entitlements": [], + } + + prepared = helper.prepare_authenticated_v5_routing_from_request( + request, + index_context_hash=index_context_hash, + host_capabilities=host, + scheduler_facts=scheduler, + ) + + self.assertEqual(helper._sha256_json(source_plan), prepared["source_plan_hash"]) + self.assertEqual( + ready_ids, [unit["task"]["task_id"] for unit in prepared["units"]] + ) + self.assertEqual( + ready_ids, + [item["task"]["task_id"] for item in prepared["routing_requests"]], + ) + for unit, routing_request in zip( + prepared["units"], prepared["routing_requests"], strict=True + ): + self.assertNotIn("predecessor_hash", unit) + self.assertIn("workflow_id_hash", unit) + self.assertIsNone(routing_request["child_route_attestation"]) + self.assertEqual(index_context_hash, unit["index_context_hash"]) + + core = load_fastlane_routing() + attestation_items = [] + for routing_request in prepared["routing_requests"]: + binding_hash = core.v5_request_binding_hash(routing_request) + attestation = { + "schema": "2718lab-devkit/host-child-route-attestation-v1", + "status": "attested", + "request_binding_hash": binding_hash, + "host_id_hash": host["host_id_hash"], + "capability_epoch": 1, + "lease_epoch": 0, + "issued_event_seq": 1, + "expires_event_seq": 1, + "route": { + "lane": "luna", + "model": "gpt-5.6-luna", + "effort": "max", + "rank": 40, + }, + "inherit_current_session_model": False, + "refusal_code": None, + } + attestation["attestation_hash"] = helper._sha256_json(attestation) + attestation_items.append( + { + "task_id": routing_request["task"]["task_id"], + "request_binding_hash": binding_hash, + "attestation": attestation, + } + ) + compiled = helper.compile_authenticated_v5_assignment_skeletons( + prepared["units"], + source_plan_hash=prepared["source_plan_hash"], + routing_requests=prepared["routing_requests"], + attestation_items=attestation_items, + ) + for skeleton in compiled["assignment_skeletons"]: + proof = skeleton["routing_proof"] + predecessor = { + "schema": "team-efficiency/fast-lane-external-lease-predecessor-v1", + "source_plan_hash": prepared["source_plan_hash"], + "workflow_id_hash": next( + unit["workflow_id_hash"] + for unit in prepared["units"] + if unit["task"]["task_id"] == skeleton["task_id"] + ), + "task_id": skeleton["task_id"], + "role": "execution", + "context_hash": proof["routing_context_hash"], + "routing_result_hash": proof["routing_result_hash"], + } + self.assertEqual( + helper._sha256_json(predecessor), skeleton["predecessor_hash"] + ) + if __name__ == "__main__": unittest.main() diff --git a/mcp-tools/devkit_runtime/fastlane_host_adapter.py b/mcp-tools/devkit_runtime/fastlane_host_adapter.py index 57cfb43..9227f12 100644 --- a/mcp-tools/devkit_runtime/fastlane_host_adapter.py +++ b/mcp-tools/devkit_runtime/fastlane_host_adapter.py @@ -9,10 +9,12 @@ import hashlib import json import re -from collections.abc import Sequence -from dataclasses import dataclass -from typing import Final +import unicodedata +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass, field +from typing import Final, cast +from . import host_envelopes from .host_session import ( HostCapabilityFact, HostRoute, @@ -25,6 +27,16 @@ _HASH: Final = re.compile(r"sha256:[0-9a-f]{64}\Z") _LABEL: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z") _PATH_PART: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*\Z") +_WINDOWS_RESERVED_NAMES: Final = frozenset( + { + "con", + "prn", + "aux", + "nul", + *(f"com{index}" for index in range(1, 10)), + *(f"lpt{index}" for index in range(1, 10)), + } +) _TRANSFER_ROLES: Final = { "coordinator_to_worker": ("coordinator", "worker"), "worker_to_coordinator": ("worker", "coordinator"), @@ -35,6 +47,9 @@ ) _DISPATCH_MODES: Final = frozenset({"parallel", "serial", "isolated_worktree"}) _DISPATCH_REQUEST_FIELDS: Final = frozenset({"schema", "action", "assignments"}) +_PLANNER_REQUEST_FIELDS: Final = frozenset( + {"schema", "action", "assignment_skeletons", "project_index_attestation_refs"} +) _DISPATCH_ASSIGNMENT_FIELDS: Final = frozenset( { "task_id", @@ -67,6 +82,7 @@ } ) _MAX_DISPATCH_REQUEST_BYTES: Final = 65_536 +_MAX_DISPATCH_JSON_DEPTH: Final = 12 @dataclass(frozen=True) @@ -85,7 +101,7 @@ class _HostDispatchFact: lease_id: str lease_epoch: int task_version: int - assignment_token: str + assignment_token: str = field(repr=False) write_scope: tuple[str, ...] concurrency_mode: str dispatch_order: int @@ -104,13 +120,22 @@ class _PreparedHostFacts: session: HostSession evidence: object capability_facts: tuple[HostCapabilityFact, ...] + bridge_attested: bool = False + evidence_expires_at: int | None = None + preparation_id: str | None = None + call_intent_hash: str | None = None def prepare_verified_host_facts( session: object, *, - capability_facts: Sequence[HostCapabilityFact] | object, + capability_facts: Sequence[HostCapabilityFact] | object = (), preparation_id: object = None, + call_intent_hash: object = None, + routing_registry_binding_hash: object = None, + request: object = None, + reasoning_effort: object = None, + requested_routes: Sequence[HostRoute] | object = None, ) -> _PreparedHostFacts | str: """Accept no public substitute for session-owned compiler evidence.""" @@ -123,18 +148,84 @@ def prepare_verified_host_facts( ): return NO_SAFE_WORK try: - scheduling = session.scheduling_facts(tuple(capability_facts)) - if type(scheduling) is not HostSchedulingFacts: - return NO_SAFE_WORK + bridge_attested = False + if request is not None or reasoning_effort is not None: + normalized_request = _planner_request(request) + request_bytes = _canonical_bytes(normalized_request) + if ( + type(reasoning_effort) is not str + or len(request_bytes) > _MAX_DISPATCH_REQUEST_BYTES + ): + return NO_SAFE_WORK + if requested_routes is None: + requested_route_set: set[HostRoute] = set() + for assignment in cast( + list[dict[str, object]], normalized_request["assignment_skeletons"] + ): + proof = cast(dict[str, object], assignment["routing_proof"]) + result = cast(dict[str, object], proof["result"]) + route = cast(dict[str, object], result["route"]) + requested_route_set.add( + HostRoute( + model=cast(str, route["model"]), + effort=cast(str, route["effort"]), + ) + ) + requested_routes = tuple( + sorted( + requested_route_set, + key=lambda route: (route.model, route.effort), + ) + ) + elif not isinstance(requested_routes, Sequence) or isinstance( + requested_routes, (str, bytes, bytearray) + ): + return NO_SAFE_WORK + else: + requested_routes = tuple(requested_routes) + bridge_attested = session.bind_compiler_request( + preparation_id=normalized_preparation_id, + call_intent_hash=cast(str, call_intent_hash), + request_hash=_hash_bytes(request_bytes), + reasoning_effort=reasoning_effort, + requested_routes=requested_routes, + assignment_skeletons=tuple( + cast(list[dict[str, object]], normalized_request["assignment_skeletons"]) + ), + project_index_attestation_refs=tuple( + cast( + list[dict[str, object]], + normalized_request["project_index_attestation_refs"], + ) + ), + routing_registry_binding_hash=cast( + str, routing_registry_binding_hash + ), + ) + if not bridge_attested: + return NO_SAFE_WORK + else: + scheduling = session.scheduling_facts(tuple(capability_facts)) + if type(scheduling) is not HostSchedulingFacts: + return NO_SAFE_WORK evidence = session.prepare_compiler_evidence( preparation_id=normalized_preparation_id ) if evidence == NO_SAFE_WORK: return NO_SAFE_WORK + expires_at = session.compiler_evidence_expires_at(evidence) + if expires_at is None: + return NO_SAFE_WORK return _PreparedHostFacts( session=session, evidence=evidence, capability_facts=tuple(capability_facts), + bridge_attested=bridge_attested, + evidence_expires_at=expires_at, + preparation_id=normalized_preparation_id, + call_intent_hash=( + cast(str, call_intent_hash) if bridge_attested else None + ), ) except Exception: return NO_SAFE_WORK @@ -152,7 +243,11 @@ def compile_fast_lane_with_host_facts( return NO_SAFE_WORK prepared = verified_host_facts try: - normalized_request = _dispatch_request(request) + normalized_request = ( + _planner_request(request) + if prepared.bridge_attested + else _dispatch_request(request) + ) if _bounded_json_size(normalized_request) > _MAX_DISPATCH_REQUEST_BYTES: return NO_SAFE_WORK request_bytes = _canonical_bytes(normalized_request) @@ -169,7 +264,37 @@ def compile_fast_lane_with_host_facts( return NO_SAFE_WORK facts = _normalized_dispatch_facts(material.dispatch_facts) fact_mappings = [_dispatch_fact_mapping(fact) for fact in facts] - if normalized_request["assignments"] != fact_mappings: + if prepared.bridge_attested: + skeletons = cast( + list[dict[str, object]], normalized_request["assignment_skeletons"] + ) + if len(skeletons) != len(fact_mappings) or any( + mapping[field_name] != skeleton[field_name] + for mapping, skeleton in zip(fact_mappings, skeletons, strict=True) + for field_name in ( + "task_id", + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "predecessor_hash", + "source_plan_hash", + ) + ): + return NO_SAFE_WORK + for mapping, skeleton in zip(fact_mappings, skeletons, strict=True): + proof = cast(dict[str, object], skeleton["routing_proof"]) + result = cast(dict[str, object], proof["result"]) + route = cast(dict[str, object], result["route"]) + if mapping["route"] != { + "model": route["model"], + "reasoning_effort": route["effort"], + "routing_context_hash": proof["routing_context_hash"], + "routing_result_hash": proof["routing_result_hash"], + "require_explicit_route": True, + }: + return NO_SAFE_WORK + elif normalized_request["assignments"] != fact_mappings: return NO_SAFE_WORK if tuple(sorted(fact.route.routing_result_hash for fact in facts)) != tuple( material.verified_route_result_hashes @@ -184,10 +309,18 @@ def compile_fast_lane_with_host_facts( ) if dispatch_binding_hashes != material.dispatch_binding_hashes: return NO_SAFE_WORK - scheduling = prepared.session.scheduling_facts(prepared.capability_facts) - if type(scheduling) is not HostSchedulingFacts: - return NO_SAFE_WORK - attested_routes = set(scheduling.routes) + if prepared.bridge_attested: + if material.registry_binding_hash is None: + return NO_SAFE_WORK + attested_routes = { + HostRoute(model=fact.route.model, effort=fact.route.reasoning_effort) + for fact in facts + } + else: + scheduling = prepared.session.scheduling_facts(prepared.capability_facts) + if type(scheduling) is not HostSchedulingFacts: + return NO_SAFE_WORK + attested_routes = set(scheduling.routes) if any( HostRoute( model=fact.route.model, @@ -215,6 +348,63 @@ def compile_fast_lane_with_host_facts( return NO_SAFE_WORK +def dispatch_fast_lane_with_host_facts( + request: object, + *, + reasoning_effort: object, + verified_host_facts: object, + correlation_id: object, + now: object, + refill_callback: object = None, +) -> object | str: + """Compile and commit one batch to the authenticated bridge, never publicly.""" + + if ( + type(verified_host_facts) is not _PreparedHostFacts + or not verified_host_facts.bridge_attested + or type(correlation_id) is not str + or type(now) is not int + or (refill_callback is not None and not callable(refill_callback)) + ): + return NO_SAFE_WORK + prepared = verified_host_facts + batch = compile_fast_lane_with_host_facts( + request, + reasoning_effort=reasoning_effort, + verified_host_facts=prepared, + ) + if type(batch) is not dict or prepared.evidence_expires_at is None: + return NO_SAFE_WORK + try: + assignments = batch["assignments"] + assert type(assignments) is list and assignments + first = assignments[0] + assert type(first) is dict + route = first["route"] + assert type(route) is dict + binding = host_envelopes.EnvelopeBinding( + task_id=first["task_id"], + lease_epoch=first["lease_epoch"], + assignment_token=first["assignment_token"], + dispatch_context_hash=first["dispatch_binding_hash"], + route_hash=route["routing_result_hash"], + expires_at=prepared.evidence_expires_at, + ) + return prepared.session.send_fast_lane_dispatch_batch( + batch=batch, + binding=binding, + correlation_id=correlation_id, + now=now, + call_intent_hash=prepared.call_intent_hash, + preparation_id=prepared.preparation_id, + refill_callback=cast( + Callable[[Mapping[str, object]], object] | None, refill_callback + ), + ) + except Exception: + return NO_SAFE_WORK + + def project_role_transfer( *, kind: object, @@ -295,6 +485,43 @@ def _digest_list(value: object, *, maximum: int) -> list[str] | None: return digests +def _planner_request(value: object) -> dict[str, object]: + if type(value) is not dict or set(value) != _PLANNER_REQUEST_FIELDS: + raise ValueError("planner request is invalid") + skeletons = value.get("assignment_skeletons") + references = value.get("project_index_attestation_refs") + if ( + value.get("schema") != "2718lab-devkit/fastlane-host-planner-request-v1" + or value.get("action") != "plan_dispatch" + or type(skeletons) is not list + or not skeletons + or len(skeletons) > 16 + or type(references) is not list + or len(references) != len(skeletons) + ): + raise ValueError("planner request is invalid") + from .host_bridge import ( + _normalize_assignment_skeleton, + _normalize_project_index_attestation_ref, + ) + + normalized_skeletons = [_normalize_assignment_skeleton(item) for item in skeletons] + normalized_references = [ + _normalize_project_index_attestation_ref(item) for item in references + ] + _validate_dispatch_order_sequence(normalized_skeletons) + if [item["task_id"] for item in normalized_skeletons] != [ + item["task_id"] for item in normalized_references + ]: + raise ValueError("planner request is invalid") + return { + "schema": value["schema"], + "action": value["action"], + "assignment_skeletons": normalized_skeletons, + "project_index_attestation_refs": normalized_references, + } + + def _dispatch_request(value: object) -> dict[str, object]: if ( type(value) is not dict @@ -311,12 +538,14 @@ def _dispatch_request(value: object) -> dict[str, object]: or len(assignments) > 16 ): raise ValueError("dispatch request is invalid") + normalized_assignments = [ + _bounded_dispatch_assignment(assignment) for assignment in assignments + ] + _validate_dispatch_order_sequence(normalized_assignments) return { "schema": value["schema"], "action": value["action"], - "assignments": [ - _bounded_dispatch_assignment(assignment) for assignment in assignments - ], + "assignments": normalized_assignments, } @@ -362,7 +591,7 @@ def _bounded_dispatch_assignment(value: object) -> dict[str, object]: if ( not 0 < value["lease_epoch"] <= 2**63 - 1 or not 0 <= value["task_version"] <= 2**63 - 1 - or not 0 <= value["dispatch_order"] <= 16 + or not 0 <= value["dispatch_order"] < 16 or not 0 < value["ledger_epoch"] <= 2**63 - 1 ): raise ValueError("dispatch integer field is out of bounds") @@ -415,7 +644,7 @@ def _bounded_string(value: object, *, maximum: int) -> str: def _bounded_json_size(value: object, *, depth: int = 0) -> int: """Count exact compact-JSON bytes without constructing the whole document.""" - if depth > 4: + if depth > _MAX_DISPATCH_JSON_DEPTH: raise ValueError("dispatch request is too deep") if type(value) is str: return len( @@ -423,6 +652,8 @@ def _bounded_json_size(value: object, *, depth: int = 0) -> int: ) if type(value) is bool: return 4 if value else 5 + if value is None: + return 4 if type(value) is int: return len(str(value)) if type(value) is list: @@ -451,6 +682,7 @@ def _normalized_dispatch_facts(value: object) -> tuple[_HostDispatchFact, ...]: facts = tuple(_normalized_dispatch_fact(fact) for fact in value) if len({fact.task_id for fact in facts}) != len(facts): raise ValueError("dispatch tasks are duplicated") + _validate_dispatch_order_sequence(facts) return facts @@ -477,7 +709,6 @@ def _normalized_dispatch_fact(value: object) -> _HostDispatchFact: or value.concurrency_mode not in _DISPATCH_MODES or type(value.dispatch_order) is not int or value.dispatch_order < 0 - or (value.concurrency_mode == "serial") != (value.dispatch_order > 0) or _digest(value.index_context_hash) is None or _digest(value.worktree_identity) is None or _digest(value.worktree_base) is None @@ -504,17 +735,20 @@ def _canonical_write_scope(value: object) -> tuple[str, ...]: or item != item.strip() or "\\" in item or item.startswith("/") + or any(ord(character) < 32 for character in item) ): raise ValueError("write scope is invalid") - parts = item.split("/") + canonical_item = unicodedata.normalize("NFC", item).casefold() + parts = canonical_item.split("/") if any( _PATH_PART.fullmatch(part) is None or part in {".", ".."} or part.endswith((".", " ")) + or part.rstrip(" .").split(".", 1)[0] in _WINDOWS_RESERVED_NAMES for part in parts ): raise ValueError("write scope is invalid") - normalized.append(item) + normalized.append("/".join(parts)) if tuple(sorted(normalized)) != tuple(normalized): raise ValueError("write scope is not canonical") if len(set(normalized)) != len(normalized): @@ -553,6 +787,43 @@ def _dispatch_fact_mapping(value: object) -> dict[str, object]: return mapping +def _dispatch_fact_from_mapping(value: object) -> _HostDispatchFact: + """Elevate one closed wire mapping only after every scalar has normalized.""" + + normalized = _bounded_dispatch_assignment(value) + route = cast(dict[str, object], normalized["route"]) + fact = _HostDispatchFact( + task_id=cast(str, normalized["task_id"]), + route=_HostDispatchRoute( + model=cast(str, route["model"]), + reasoning_effort=cast(str, route["reasoning_effort"]), + routing_context_hash=cast(str, route["routing_context_hash"]), + routing_result_hash=cast(str, route["routing_result_hash"]), + require_explicit_route=cast(bool, route["require_explicit_route"]), + ), + lease_id=cast(str, normalized["lease_id"]), + lease_epoch=cast(int, normalized["lease_epoch"]), + task_version=cast(int, normalized["task_version"]), + assignment_token=cast(str, normalized["assignment_token"]), + write_scope=tuple(cast(list[str], normalized["write_scope"])), + concurrency_mode=cast(str, normalized["concurrency_mode"]), + dispatch_order=cast(int, normalized["dispatch_order"]), + index_context_hash=cast(str, normalized["index_context_hash"]), + worktree_identity=cast(str, normalized["worktree_identity"]), + worktree_base=cast(str, normalized["worktree_base"]), + integration_head=cast(str, normalized["integration_head"]), + predecessor_hash=cast(str, normalized["predecessor_hash"]), + source_plan_hash=cast(str, normalized["source_plan_hash"]), + ledger_epoch=cast(int, normalized["ledger_epoch"]), + active_lease_set_hash=cast(str, normalized["active_lease_set_hash"]), + ) + if normalized["dispatch_binding_hash"] != _dispatch_fact_mapping(fact)[ + "dispatch_binding_hash" + ]: + raise ValueError("dispatch binding hash is invalid") + return fact + + def _lease_scope_binding_hash(value: object) -> str: fact = _normalized_dispatch_fact(value) return _canonical_hash( @@ -578,6 +849,7 @@ def _validate_batch_fences(facts: tuple[_HostDispatchFact, ...]) -> None: raise ValueError("ledger epochs are mixed") if len({fact.active_lease_set_hash for fact in facts}) != 1: raise ValueError("active lease sets are mixed") + _validate_dispatch_order_sequence(facts) serial_orders = [ fact.dispatch_order for fact in facts if fact.concurrency_mode == "serial" ] @@ -597,6 +869,28 @@ def _validate_batch_fences(facts: tuple[_HostDispatchFact, ...]) -> None: raise ValueError("parallel write scopes overlap") +def _validate_dispatch_order_sequence(values: Sequence[object]) -> None: + """Validate source-plan coordinates without coupling a wave to ordering. + + ``dispatch_order`` remains an authenticated source-plan coordinate. A + refill is a sparse slice, so batch admission must not require the slice to + be sorted; the projection layer owns global dependency/order validation. + """ + + orders: list[object] = [] + for value in values: + if type(value) is _HostDispatchFact: + orders.append(value.dispatch_order) + elif type(value) is dict: + orders.append(value.get("dispatch_order")) + else: + raise ValueError("dispatch order is invalid") + if any(type(order) is not int or not 0 <= order < 16 for order in orders): + raise ValueError("dispatch order is invalid") + if len(set(orders)) != len(orders): + raise ValueError("dispatch order is duplicated") + + def _scopes_overlap(left: tuple[str, ...], right: tuple[str, ...]) -> bool: for left_item in left: left_folded = left_item.casefold() @@ -632,6 +926,7 @@ def _hash_bytes(value: bytes) -> str: __all__ = [ "NO_SAFE_WORK", "compile_fast_lane_with_host_facts", + "dispatch_fast_lane_with_host_facts", "prepare_verified_host_facts", "project_role_transfer", ] diff --git a/mcp-tools/devkit_runtime/fastlane_host_intent.py b/mcp-tools/devkit_runtime/fastlane_host_intent.py index 63df173..fb7901d 100644 --- a/mcp-tools/devkit_runtime/fastlane_host_intent.py +++ b/mcp-tools/devkit_runtime/fastlane_host_intent.py @@ -11,6 +11,7 @@ import json import re from dataclasses import dataclass +from dataclasses import field as dataclass_field from typing import Final, Literal, cast NO_SAFE_WORK: Final = "NO_SAFE_WORK" @@ -210,7 +211,7 @@ class HostExecutionExpectationProjection: source_plan_hash: str workflow_hash: str assignment_id: str - assignment_token: str + assignment_token: str = dataclass_field(repr=False) predecessor_hash: str task_id: str role: str @@ -251,7 +252,7 @@ class ParsedHostExecutionIntent: source_plan_hash: str workflow_hash: str assignment_id: str - assignment_token: str + assignment_token: str = dataclass_field(repr=False) assignment_binding_hash: str predecessor_hash: str task_id: str diff --git a/mcp-tools/devkit_runtime/fastlane_terminal_protocol.py b/mcp-tools/devkit_runtime/fastlane_terminal_protocol.py new file mode 100644 index 0000000..4b53445 --- /dev/null +++ b/mcp-tools/devkit_runtime/fastlane_terminal_protocol.py @@ -0,0 +1,274 @@ +"""Exact Fast Lane worker terminal-result and acknowledgement protocol. + +This module owns only the data-plane contract. Transport sequencing and replay +state remain in :mod:`devkit_runtime.host_bridge`. +""" + +from __future__ import annotations + +import hashlib +import hmac +import json +import math +import re +from collections.abc import Mapping +from typing import Final, cast + +from . import host_envelopes + +TERMINAL_RESULT_SCHEMA: Final = ( + "2718lab-devkit/fastlane-worker-terminal-result-v1" +) +TERMINAL_ACK_SCHEMA: Final = "2718lab-devkit/fastlane-worker-terminal-ack-v1" +TERMINAL_BINDING_FIELDS: Final = frozenset( + { + "call_intent_hash", + "preparation_id", + "batch_hash", + "task_id", + "lease_id", + "lease_epoch", + "task_version", + "assignment_token", + "dispatch_binding_hash", + "routing_result_hash", + "worktree_identity", + "worktree_base", + "integration_head", + "predecessor_hash", + } +) + +_IDENTIFIER = re.compile(r"[a-z0-9][a-z0-9._-]{0,127}\Z") +_FAST_LANE_TASK_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,95}\Z") +_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z") +_MAX_JSON_DEPTH: Final = 12 +_MAX_JSON_NODES: Final = 4_096 +_MAX_TERMINAL_PACKET_BYTES: Final = 24 * 1024 +_TERMINAL_TTL_SECONDS: Final = 120 +_INVALID: Final = "HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID" +_FRAME_INVALID: Final = "HOST_BRIDGE_FRAME_INVALID" + + +class FastLaneTerminalProtocolError(ValueError): + """Stable protocol failure translated by the host bridge boundary.""" + + def __init__(self, code: str) -> None: + super().__init__(code) + self.code = code + + +def normalize_worker_terminal_result( + value: object, + *, + expected: Mapping[str, object], + expires_at: int | None = None, + now: int, +) -> dict[str, object]: + """Validate and normalize one exact23 authenticated terminal result.""" + + fields = { + "schema", + *TERMINAL_BINDING_FIELDS, + "terminal", + "result", + "risk", + "artifact_refs", + "digest_refs", + "event_seq", + "expires_at", + "terminal_receipt_hash", + } + terminal_expires_at = value.get("expires_at") if type(value) is dict else None + if ( + type(value) is not dict + or set(value) != fields + or value.get("schema") != TERMINAL_RESULT_SCHEMA + or set(expected) != TERMINAL_BINDING_FIELDS + or type(now) is not int + or type(expires_at) is not int + or expires_at <= now + or type(terminal_expires_at) is not int + or not now < cast(int, terminal_expires_at) <= now + _TERMINAL_TTL_SECONDS + or cast(int, terminal_expires_at) > expires_at + or any(value.get(field) != expected.get(field) for field in expected) + or value.get("terminal") not in {"succeeded", "failed", "blocked"} + or type(value.get("event_seq")) is not int + or not 0 < cast(int, value["event_seq"]) <= 2**63 - 1 + ): + _raise_invalid() + if ( + type(value.get("call_intent_hash")) is not str + or len(cast(str, value["call_intent_hash"])) != 64 + or any( + character not in "0123456789abcdef" + for character in cast(str, value["call_intent_hash"]) + ) + or type(value.get("preparation_id")) is not str + or _IDENTIFIER.fullmatch(cast(str, value["preparation_id"])) is None + ): + _raise_invalid() + for field_name in ( + "batch_hash", + "dispatch_binding_hash", + "routing_result_hash", + "worktree_identity", + "worktree_base", + "integration_head", + "predecessor_hash", + ): + item = value.get(field_name) + if type(item) is not str or _DIGEST.fullmatch(item) is None: + _raise_invalid() + task_id = value.get("task_id") + if type(task_id) is not str or _FAST_LANE_TASK_ID.fullmatch(task_id) is None: + _raise_invalid() + lease_id = value.get("lease_id") + assignment_token = value.get("assignment_token") + if ( + type(lease_id) is not str + or _IDENTIFIER.fullmatch(lease_id) is None + or type(assignment_token) is not str + or _DIGEST.fullmatch(assignment_token) is None + ): + _raise_invalid() + for field_name in ("lease_epoch", "task_version"): + item = value.get(field_name) + if type(item) is not int or not 0 < item <= 2**63 - 1: + _raise_invalid() + try: + normalized_result = host_envelopes._required_text_items(value, "result") + normalized_risk = host_envelopes._required_risks(value) + normalized_artifacts = host_envelopes._required_refs(value, "artifact_refs", 16) + normalized_digests = host_envelopes._required_refs(value, "digest_refs", 32) + except host_envelopes.HostEnvelopeError as error: + raise FastLaneTerminalProtocolError(_INVALID) from error + unsigned = dict(value) + receipt_hash = unsigned.pop("terminal_receipt_hash") + if ( + value["result"] != normalized_result + or value["risk"] != normalized_risk + or value["artifact_refs"] != normalized_artifacts + or value["digest_refs"] != normalized_digests + or type(receipt_hash) is not str + or _DIGEST.fullmatch(receipt_hash) is None + or not hmac.compare_digest(receipt_hash, _private_payload_hash(unsigned)) + ): + _raise_invalid() + _validate_private_packet_size(value, _MAX_TERMINAL_PACKET_BYTES) + return dict(value) + + +def normalize_worker_terminal_ack( + value: object, *, terminal_result: Mapping[str, object] +) -> dict[str, object]: + """Validate and normalize an exact terminal acknowledgement.""" + + fields = { + "schema", + "call_intent_hash", + "preparation_id", + "batch_hash", + "task_id", + "terminal_receipt_hash", + "accepted_event_seq", + "refill_trigger_hash", + "ack_hash", + } + unsigned = dict(value) if type(value) is dict else {} + ack_hash = unsigned.pop("ack_hash", None) + if ( + type(value) is not dict + or set(value) != fields + or value.get("schema") != TERMINAL_ACK_SCHEMA + or any( + value.get(field) != terminal_result.get(field) + for field in ( + "call_intent_hash", + "preparation_id", + "batch_hash", + "task_id", + "terminal_receipt_hash", + ) + ) + or type(value.get("accepted_event_seq")) is not int + or cast(int, value["accepted_event_seq"]) + < cast(int, terminal_result.get("event_seq")) + or type(value.get("refill_trigger_hash")) is not str + or _DIGEST.fullmatch(cast(str, value["refill_trigger_hash"])) is None + or type(ack_hash) is not str + or _DIGEST.fullmatch(ack_hash) is None + or not hmac.compare_digest(ack_hash, _private_payload_hash(unsigned)) + ): + _raise_invalid() + return dict(value) + + +def validate_terminal_correlation(value: object) -> None: + """Require the opaque ``terminal-`` correlation namespace.""" + + if ( + type(value) is not str + or len(value) != 73 + or not value.startswith("terminal-") + or any(character not in "0123456789abcdef" for character in value[9:]) + ): + _raise_invalid() + + +def _raise_invalid() -> None: + raise FastLaneTerminalProtocolError(_INVALID) + + +def _private_payload_hash(payload: object) -> str: + return "sha256:" + hashlib.sha256(_canonical_bytes(payload)).hexdigest() + + +def _validate_private_packet_size(payload: Mapping[str, object], maximum: int) -> None: + if len(_canonical_bytes(payload)) > maximum: + raise FastLaneTerminalProtocolError(_FRAME_INVALID) + + +def _canonical_bytes(value: object) -> bytes: + try: + _validate_json_value(value) + return json.dumps( + value, + ensure_ascii=False, + allow_nan=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + except FastLaneTerminalProtocolError: + raise + except (TypeError, ValueError, UnicodeError, RecursionError) as error: + raise FastLaneTerminalProtocolError(_FRAME_INVALID) from error + + +def _validate_json_value(value: object) -> None: + pending: list[tuple[object, int]] = [(value, 0)] + nodes = 0 + while pending: + item, depth = pending.pop() + nodes += 1 + if depth > _MAX_JSON_DEPTH or nodes > _MAX_JSON_NODES: + raise FastLaneTerminalProtocolError(_FRAME_INVALID) + if item is None or type(item) in {bool, int, str}: + continue + if type(item) is float: + if math.isfinite(item): + continue + raise FastLaneTerminalProtocolError(_FRAME_INVALID) + if type(item) is list: + if len(item) > _MAX_JSON_NODES - nodes: + raise FastLaneTerminalProtocolError(_FRAME_INVALID) + pending.extend((child, depth + 1) for child in item) + continue + if type(item) is dict: + if len(item) > _MAX_JSON_NODES - nodes: + raise FastLaneTerminalProtocolError(_FRAME_INVALID) + if any(type(key) is not str for key in item): + raise FastLaneTerminalProtocolError(_FRAME_INVALID) + pending.extend((child, depth + 1) for child in item.values()) + continue + raise FastLaneTerminalProtocolError(_FRAME_INVALID) diff --git a/mcp-tools/devkit_runtime/host_bridge.py b/mcp-tools/devkit_runtime/host_bridge.py index 67db108..de04139 100644 --- a/mcp-tools/devkit_runtime/host_bridge.py +++ b/mcp-tools/devkit_runtime/host_bridge.py @@ -15,32 +15,82 @@ import math import os import re +import secrets +import select import stat import struct +import time from collections.abc import Mapping, Sequence from dataclasses import dataclass, field -from typing import Final +from threading import Event, Lock, RLock +from typing import Final, cast -from . import host_envelopes +from . import ( + fastlane_terminal_protocol, + host_envelopes, + project_index_attestation_protocol, +) _FRAME_SCHEMA: Final = "2718lab-devkit/host-bridge-v1" _CAPABILITY_PROBE_SCHEMA: Final = "2718lab-devkit/host-capability-probe-v1" _CAPABILITY_REPORT_SCHEMA: Final = "2718lab-devkit/host-capability-report-v1" +_CAPABILITY_PROBE_SCHEMA_V2: Final = "2718lab-devkit/host-capability-probe-v2" +_CAPABILITY_REPORT_SCHEMA_V2: Final = "2718lab-devkit/host-capability-report-v2" _OPERATION_REQUEST_SCHEMA: Final = "2718lab-devkit/host-operation-request-v1" _TERMINAL_RESULT_SCHEMA: Final = "2718lab-devkit/host-terminal-result-v1" _PROOF_CONTINUATION_SCHEMA: Final = "2718lab-devkit/host-proof-continuation-v1" +_COMPILER_EVIDENCE_REQUEST_SCHEMA: Final = ( + "2718lab-devkit/compiler-evidence-request-v1" +) +_COMPILER_EVIDENCE_RESPONSE_SCHEMA: Final = ( + "2718lab-devkit/compiler-evidence-response-v1" +) +_PROJECT_INDEX_ATTESTATION_SCHEMA: Final = ( + project_index_attestation_protocol.ATTESTATION_SCHEMA +) +_ROUTING_ATTESTATION_REQUEST_SCHEMA: Final = ( + "2718lab-devkit/routing-attestation-request-v1" +) +_ROUTING_ATTESTATION_RESPONSE_SCHEMA: Final = ( + "2718lab-devkit/routing-attestation-response-v1" +) +_FAST_LANE_TERMINAL_RESULT_SCHEMA: Final = ( + fastlane_terminal_protocol.TERMINAL_RESULT_SCHEMA +) +_FAST_LANE_TERMINAL_ACK_SCHEMA: Final = fastlane_terminal_protocol.TERMINAL_ACK_SCHEMA +_FAST_LANE_REFILL_REGISTRY_SCHEMA: Final = ( + "2718lab-devkit/fast_lane_refill_registry-v1" +) +_FAST_LANE_REFILL_REGISTRY_ACTION_PREFIX: Final = "refill-registry-" _FRAME_FIELDS: Final = frozenset( {"schema", "kind", "action_id", "session_nonce", "sequence", "payload", "mac"} ) _MAX_FRAME_BYTES: Final = 65_536 +_MAX_JSON_DEPTH: Final = 12 +_MAX_JSON_NODES: Final = 4_096 _MAX_CAPABILITY_PACKET_BYTES: Final = 8 * 1024 _MAX_OPERATION_PACKET_BYTES: Final = 40 * 1024 _MAX_PROOF_CONTINUATION_BYTES: Final = 2 * 1024 _MAX_TERMINAL_OPERATION_TOMBSTONES: Final = 256 +_MAX_COMPILER_EVIDENCE_BYTES: Final = 40 * 1024 +_COMPILER_EVIDENCE_TTL_SECONDS: Final = 120 +_CAPABILITY_V2_TTL_SECONDS: Final = 120 +_MAX_PROJECT_INDEX_ATTESTATION_BYTES: Final = ( + project_index_attestation_protocol.MAX_ATTESTATION_BYTES +) +_PROJECT_INDEX_ATTESTATION_TTL_SECONDS: Final = ( + project_index_attestation_protocol.ATTESTATION_TTL_SECONDS +) +_MAX_ROUTING_ATTESTATION_BYTES: Final = 40 * 1024 +_ROUTING_ATTESTATION_TTL_SECONDS: Final = 120 _IDENTIFIER = re.compile(r"[a-z0-9][a-z0-9._-]{0,127}\Z") +_FAST_LANE_TASK_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,95}\Z") _ENDPOINT = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}\Z") _DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z") _MAC = re.compile(r"[0-9a-f]{64}\Z") +_FAST_LANE_REFILL_REGISTRY_ACTION = re.compile( + r"refill-registry-[0-9a-f]{64}\Z" +) _FD_SELECTOR = re.compile(r"[0-9]{1,18}\Z") _WINDOWS_PIPE_SELECTOR = re.compile( r"pipe:(?Pcodex-devkit-(?P[1-9][0-9]{0,9})-" @@ -61,6 +111,14 @@ "operation_request", "operation_result", "proof_continuation", + "compiler_evidence_request", + "compiler_evidence_response", + "project_index_attestation", + "routing_attestation_request", + "routing_attestation_response", + "fast_lane_worker_terminal_result", + "fast_lane_worker_terminal_ack", + "fast_lane_refill_registry", } ) _VALIDATED_PRIVATE_KINDS: Final = frozenset( @@ -70,6 +128,14 @@ "operation_request", "operation_result", "proof_continuation", + "compiler_evidence_request", + "compiler_evidence_response", + "project_index_attestation", + "routing_attestation_request", + "routing_attestation_response", + "fast_lane_worker_terminal_result", + "fast_lane_worker_terminal_ack", + "fast_lane_refill_registry", } ) _BINDING_FIELDS: Final = frozenset( @@ -127,6 +193,28 @@ class CapabilityProbe: probe_hash: str +@dataclass(frozen=True) +class CapabilityProbeV2: + """Generation-local request for the exact V5 Host and scheduler snapshots.""" + + call_intent_hash: str + preparation_id: str + requested_capability_schemas: tuple[str, ...] + probe_hash: str + expires_at: int = field(repr=False) + + +@dataclass(frozen=True) +class RoutingAttestationRequest: + """Generation-local set of normalized V5 requests awaiting Host proof.""" + + call_intent_hash: str + preparation_id: str + routing_requests: tuple[dict[str, object], ...] = field(repr=False) + routing_request_set_hash: str + expires_at: int = field(repr=False) + + @dataclass(frozen=True) class OperationReceipt: """Predecessor receipt a terminal result must bind exactly.""" @@ -138,6 +226,44 @@ class OperationReceipt: binding: host_envelopes.EnvelopeBinding +@dataclass(frozen=True) +class CompilerEvidenceRequest: + """One authenticated, generation-local request for compiler authority facts.""" + + preparation_id: str + call_intent_hash: str + request_hash: str + reasoning_effort: str + requested_route_pairs: tuple[tuple[str, str], ...] + assignment_skeletons: tuple[dict[str, object], ...] = field(repr=False) + project_index_attestation_refs: tuple[dict[str, object], ...] = field(repr=False) + routing_registry_binding_hash: str + nonce: str = field(repr=False) + expires_at: int + + +@dataclass(frozen=True) +class FastLaneRefillRegistryRequest: + """One authenticated queue of remaining V5 skeletons. + + The queue is a host-owned handoff: the compiler only submits the exact + remaining skeletons and their index references, while the host decides if + and when a successor wave can be admitted. + """ + + correlation_id: str + call_intent_hash: str + preparation_id: str + source_plan_hash: str + index_context_hash: str + routing_registry_binding_hash: str + remaining_skeletons: tuple[dict[str, object], ...] = field(repr=False) + index_attestation_refs: tuple[dict[str, object], ...] = field(repr=False) + queue_registry_hash: str + skeleton_package_hash: str + expires_at: int = field(repr=False) + + @dataclass class _CapabilityDelivery: endpoint: str @@ -182,6 +308,20 @@ def __init__( raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") self._read_fd = read_fd self._write_fd = write_fd + # A private wake pipe lets a HostSession cancel a receiver that is + # waiting for the next frame. The transport itself may be an inherited + # named pipe/anonymous pipe and closing it from another thread is not a + # portable way to interrupt a blocked read. + try: + self._cancel_read_fd, self._cancel_write_fd = os.pipe() + except OSError as error: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") from error + self._cancel_event = Event() + self._close_lock = Lock() + # A session has one framed sequence in each direction. Serialize all + # bridge I/O so concurrent Fast Lane waves cannot interleave bytes or + # consume one another's sequence slot. + self._io_lock = RLock() self._session_key = session_key self._session_nonce = _b64encode(session_nonce) self._owns_descriptors = owns_descriptors @@ -192,8 +332,19 @@ def __init__( self._deliveries: dict[str, _CapabilityDelivery] = {} self._pending_capability_probes: set[str] = set() self._received_capability_probes: set[str] = set() + self._pending_capability_v2: dict[str, CapabilityProbeV2] = {} + self._received_capability_v2: dict[str, CapabilityProbeV2] = {} + self._pending_routing_attestations: dict[str, RoutingAttestationRequest] = {} + self._received_routing_attestations: dict[str, RoutingAttestationRequest] = {} + self._pending_fast_lane_terminals: dict[str, dict[str, object]] = {} + self._received_fast_lane_terminals: set[str] = set() self._pending_operations: dict[str, OperationReceipt] = {} self._received_operations: dict[str, OperationReceipt] = {} + self._pending_compiler_evidence: dict[str, CompilerEvidenceRequest] = {} + self._received_compiler_evidence: set[str] = set() + self._sent_fast_lane_refill_registries: set[str] = set() + self._received_fast_lane_refill_registries: set[str] = set() + self._received_project_index_attestations: set[str] = set() self._terminal_operation_tombstones: dict[ tuple[str, str, str, str, str, str], int ] = {} @@ -429,6 +580,470 @@ def send_acknowledgement(self, action_id: str) -> None: _validate_action_id(action_id) self.send_private(kind="capability_ack", action_id=action_id, payload={}) + def send_capability_probe_v2( + self, *, call_intent_hash: str, preparation_id: str, now: int + ) -> CapabilityProbeV2: + schemas = ( + "fastlane-host-dispatch-request-v1", + "fastlane-routing-request-v5", + "project-index-attestation-v1", + ) + unsigned: dict[str, object] = { + "schema": _CAPABILITY_PROBE_SCHEMA_V2, + "call_intent_hash": call_intent_hash, + "preparation_id": preparation_id, + "requested_capability_schemas": list(schemas), + } + payload = {**unsigned, "probe_hash": _private_payload_hash(unsigned)} + probe = _normalize_capability_probe_v2(payload, now=now) + if probe.probe_hash in self._pending_capability_v2: + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + _validate_private_packet_size(payload, _MAX_CAPABILITY_PACKET_BYTES) + self._send_validated_private( + kind="capability_probe", action_id=preparation_id, payload=payload + ) + self._pending_capability_v2[probe.probe_hash] = probe + return probe + + def receive_capability_probe_v2(self, *, now: int) -> CapabilityProbeV2: + try: + message = self._receive_private() + if message.kind != "capability_probe": + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + probe = _normalize_capability_probe_v2(message.payload, now=now) + if ( + message.action_id != probe.preparation_id + or probe.probe_hash in self._received_capability_v2 + ): + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + except HostBridgeError: + self._poison() + raise + self._received_capability_v2[probe.probe_hash] = probe + return probe + + def send_capability_report_v2( + self, + *, + probe: CapabilityProbeV2, + host_capabilities: Mapping[str, object], + scheduler_facts: Mapping[str, object], + now: int, + ) -> dict[str, object]: + if self._received_capability_v2.get(probe.probe_hash) != probe: + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + unsigned: dict[str, object] = { + "schema": _CAPABILITY_REPORT_SCHEMA_V2, + "call_intent_hash": probe.call_intent_hash, + "preparation_id": probe.preparation_id, + "probe_hash": probe.probe_hash, + "host_capabilities": dict(host_capabilities), + "scheduler_facts": dict(scheduler_facts), + } + payload = {**unsigned, "report_hash": _private_payload_hash(unsigned)} + normalized = _normalize_capability_report_v2(payload, probe=probe, now=now) + self._send_validated_private( + kind="capability_report", action_id=probe.preparation_id, payload=normalized + ) + del self._received_capability_v2[probe.probe_hash] + return normalized + + def receive_capability_report_v2( + self, *, probe: CapabilityProbeV2, now: int + ) -> dict[str, object]: + if self._pending_capability_v2.get(probe.probe_hash) != probe: + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + try: + message = self._receive_private() + if ( + message.kind != "capability_report" + or message.action_id != probe.preparation_id + ): + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + normalized = _normalize_capability_report_v2( + message.payload, probe=probe, now=now + ) + except HostBridgeError: + self._poison() + raise + del self._pending_capability_v2[probe.probe_hash] + return normalized + + def send_routing_attestation_request( + self, + *, + call_intent_hash: str, + preparation_id: str, + routing_requests: Sequence[Mapping[str, object]], + now: int, + ) -> RoutingAttestationRequest: + """Send one ordered, canonical V5 request set over this generation.""" + + request_set = [dict(item) for item in routing_requests] + payload: dict[str, object] = { + "schema": _ROUTING_ATTESTATION_REQUEST_SCHEMA, + "call_intent_hash": call_intent_hash, + "preparation_id": preparation_id, + "routing_requests": request_set, + "routing_request_set_hash": _private_payload_hash(request_set), + } + request = _normalize_routing_attestation_request(payload, now=now) + if request.routing_request_set_hash in self._pending_routing_attestations: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + _validate_private_packet_size(payload, _MAX_ROUTING_ATTESTATION_BYTES) + self._send_validated_private( + kind="routing_attestation_request", + action_id=preparation_id, + payload=payload, + ) + self._pending_routing_attestations[request.routing_request_set_hash] = request + return request + + def receive_routing_attestation_request( + self, *, now: int + ) -> RoutingAttestationRequest: + try: + message = self._receive_private() + if message.kind != "routing_attestation_request": + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + request = _normalize_routing_attestation_request(message.payload, now=now) + if ( + message.action_id != request.preparation_id + or request.routing_request_set_hash + in self._received_routing_attestations + ): + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + except HostBridgeError: + self._poison() + raise + self._received_routing_attestations[request.routing_request_set_hash] = request + return request + + def send_routing_attestation_response( + self, + *, + request: RoutingAttestationRequest, + attestations: Sequence[Mapping[str, object]], + now: int, + ) -> dict[str, object]: + if self._received_routing_attestations.get( + request.routing_request_set_hash + ) != request: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + unsigned: dict[str, object] = { + "schema": _ROUTING_ATTESTATION_RESPONSE_SCHEMA, + "call_intent_hash": request.call_intent_hash, + "preparation_id": request.preparation_id, + "routing_request_set_hash": request.routing_request_set_hash, + "attestations": [dict(item) for item in attestations], + } + payload = { + **unsigned, + "routing_registry_binding_hash": _private_payload_hash(unsigned), + } + normalized = _normalize_routing_attestation_response( + payload, request=request, now=now + ) + _validate_private_packet_size(normalized, _MAX_ROUTING_ATTESTATION_BYTES) + self._send_validated_private( + kind="routing_attestation_response", + action_id=request.preparation_id, + payload=normalized, + ) + del self._received_routing_attestations[request.routing_request_set_hash] + return normalized + + def receive_routing_attestation_response( + self, *, request: RoutingAttestationRequest, now: int + ) -> dict[str, object]: + if self._pending_routing_attestations.get( + request.routing_request_set_hash + ) != request: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + try: + message = self._receive_private() + if ( + message.kind != "routing_attestation_response" + or message.action_id != request.preparation_id + ): + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + normalized = _normalize_routing_attestation_response( + message.payload, request=request, now=now + ) + except HostBridgeError: + self._poison() + raise + del self._pending_routing_attestations[request.routing_request_set_hash] + return normalized + + def send_fast_lane_worker_terminal_result( + self, + *, + terminal_result: Mapping[str, object], + correlation_id: str, + expected: Mapping[str, object], + expires_at: int, + now: int, + ) -> dict[str, object]: + if type(expires_at) is not int or expires_at <= now: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + normalized = _normalize_fast_lane_worker_terminal_result( + terminal_result, expected=expected, expires_at=expires_at, now=now + ) + receipt_hash = cast(str, normalized["terminal_receipt_hash"]) + if receipt_hash in self._pending_fast_lane_terminals: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + _validate_terminal_correlation(correlation_id) + self._send_validated_private( + kind="fast_lane_worker_terminal_result", + action_id=correlation_id, + payload=normalized, + ) + self._pending_fast_lane_terminals[receipt_hash] = normalized + return normalized + + def receive_fast_lane_worker_terminal_result( + self, + *, + correlation_id: str, + expected: Mapping[str, object], + expires_at: int | None = None, + now: int, + ) -> dict[str, object]: + _validate_terminal_correlation(correlation_id) + try: + if expires_at is not None and ( + type(expires_at) is not int or expires_at <= now + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + message = self._receive_private() + if ( + message.kind != "fast_lane_worker_terminal_result" + or message.action_id != correlation_id + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + normalized = _normalize_fast_lane_worker_terminal_result( + message.payload, expected=expected, expires_at=expires_at, now=now + ) + receipt_hash = cast(str, normalized["terminal_receipt_hash"]) + if receipt_hash in self._received_fast_lane_terminals: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + except HostBridgeError: + self._poison() + raise + self._received_fast_lane_terminals.add(receipt_hash) + return normalized + + def receive_next_fast_lane_worker_terminal_result( + self, + *, + expected_by_assignment: Mapping[tuple[str, str], Mapping[str, object]], + expires_at_by_assignment: Mapping[tuple[str, str], int] | None = None, + now: int, + ) -> tuple[str, dict[str, object]]: + """Receive the next generation-bound terminal and resolve its stored batch binding.""" + + try: + message = self._receive_private() + if message.kind != "fast_lane_worker_terminal_result": + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + _validate_terminal_correlation(message.action_id) + payload = message.payload + batch_hash = payload.get("batch_hash") + task_id = payload.get("task_id") + key = (cast(str, batch_hash), cast(str, task_id)) + expected = expected_by_assignment.get(key) + if expected is None: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + lease_expires_at = ( + None + if expires_at_by_assignment is None + else expires_at_by_assignment.get(key) + ) + if lease_expires_at is not None and ( + type(lease_expires_at) is not int or lease_expires_at <= now + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + normalized = _normalize_fast_lane_worker_terminal_result( + payload, + expected=expected, + expires_at=lease_expires_at, + now=now, + ) + receipt_hash = cast(str, normalized["terminal_receipt_hash"]) + if receipt_hash in self._received_fast_lane_terminals: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + self._received_fast_lane_terminals.add(receipt_hash) + except HostBridgeError: + self._poison() + raise + return message.action_id, normalized + + def send_fast_lane_worker_terminal_ack( + self, + *, + terminal_result: Mapping[str, object], + correlation_id: str, + accepted_event_seq: int, + refill_trigger_hash: str, + ) -> dict[str, object]: + receipt_hash = terminal_result.get("terminal_receipt_hash") + if receipt_hash not in self._received_fast_lane_terminals: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + unsigned: dict[str, object] = { + "schema": _FAST_LANE_TERMINAL_ACK_SCHEMA, + "call_intent_hash": terminal_result.get("call_intent_hash"), + "preparation_id": terminal_result.get("preparation_id"), + "batch_hash": terminal_result.get("batch_hash"), + "task_id": terminal_result.get("task_id"), + "terminal_receipt_hash": receipt_hash, + "accepted_event_seq": accepted_event_seq, + "refill_trigger_hash": refill_trigger_hash, + } + ack = {**unsigned, "ack_hash": _private_payload_hash(unsigned)} + normalized = _normalize_fast_lane_worker_terminal_ack( + ack, terminal_result=terminal_result + ) + _validate_terminal_correlation(correlation_id) + self._send_validated_private( + kind="fast_lane_worker_terminal_ack", + action_id=correlation_id, + payload=normalized, + ) + self._received_fast_lane_terminals.remove(cast(str, receipt_hash)) + return normalized + + def send_fast_lane_refill_registry_request( + self, + *, + call_intent_hash: str, + preparation_id: str, + source_plan_hash: str, + index_context_hash: str, + routing_registry_binding_hash: str, + source_plan_task_ids: Sequence[str], + initial_skeletons: Sequence[Mapping[str, object]], + remaining_skeletons: Sequence[Mapping[str, object]], + index_attestation_refs: Sequence[Mapping[str, object]], + skeleton_package_hash: str, + now: int, + ) -> FastLaneRefillRegistryRequest: + """Register the authenticated remaining V5 skeletons with the Host. + + The queue hash is derived from the complete unsigned payload. It is + also used as the action suffix, making retries/replays visible to the + host without exposing any task content outside the authenticated frame. + """ + + normalized_initial = [ + _normalize_assignment_skeleton(item) for item in initial_skeletons + ] + normalized_remaining = [ + _normalize_assignment_skeleton(item) for item in remaining_skeletons + ] + expected_package_hash = _validate_skeleton_package_coverage( + normalized_initial, + normalized_remaining, + source_plan_hash=source_plan_hash, + source_plan_task_ids=source_plan_task_ids, + ) + if ( + type(skeleton_package_hash) is not str + or _DIGEST.fullmatch(skeleton_package_hash) is None + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + if not hmac.compare_digest(skeleton_package_hash, expected_package_hash): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + payload_without_hash: dict[str, object] = { + "schema": _FAST_LANE_REFILL_REGISTRY_SCHEMA, + "call_intent_hash": call_intent_hash, + "preparation_id": preparation_id, + "source_plan_hash": source_plan_hash, + "index_context_hash": index_context_hash, + "routing_registry_binding_hash": routing_registry_binding_hash, + "remaining_skeletons": normalized_remaining, + "index_attestation_refs": [dict(item) for item in index_attestation_refs], + } + queue_registry_hash = _private_payload_hash(payload_without_hash) + payload = { + **payload_without_hash, + "queue_registry_hash": queue_registry_hash, + } + request = _normalize_fast_lane_refill_registry_request( + payload, + now=now, + initial_skeletons=normalized_initial, + source_plan_task_ids=source_plan_task_ids, + skeleton_package_hash=expected_package_hash, + ) + if request.queue_registry_hash in self._sent_fast_lane_refill_registries: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + _validate_private_packet_size(payload, _MAX_COMPILER_EVIDENCE_BYTES) + action_id = _FAST_LANE_REFILL_REGISTRY_ACTION_PREFIX + queue_registry_hash[7:] + self._send_validated_private( + kind="fast_lane_refill_registry", action_id=action_id, payload=payload + ) + self._sent_fast_lane_refill_registries.add(request.queue_registry_hash) + return request + + def receive_fast_lane_refill_registry_request( + self, + *, + source_plan_task_ids: Sequence[str], + initial_skeletons: Sequence[Mapping[str, object]], + now: int, + ) -> FastLaneRefillRegistryRequest: + """Receive and consume one authenticated refill registry request.""" + + try: + message = self._receive_private() + request = _normalize_fast_lane_refill_registry_request( + message.payload, + now=now, + initial_skeletons=initial_skeletons, + source_plan_task_ids=source_plan_task_ids, + ) + expected_action = ( + _FAST_LANE_REFILL_REGISTRY_ACTION_PREFIX + + request.queue_registry_hash[7:] + ) + if ( + message.kind != "fast_lane_refill_registry" + or message.action_id != expected_action + or request.queue_registry_hash in self._received_fast_lane_refill_registries + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + except HostBridgeError: + self._poison() + raise + self._received_fast_lane_refill_registries.add(request.queue_registry_hash) + return request + + def receive_fast_lane_worker_terminal_ack( + self, *, terminal_result: Mapping[str, object], correlation_id: str + ) -> dict[str, object]: + receipt_hash = terminal_result.get("terminal_receipt_hash") + if self._pending_fast_lane_terminals.get(cast(str, receipt_hash)) != dict( + terminal_result + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + _validate_terminal_correlation(correlation_id) + try: + message = self._receive_private() + if ( + message.kind != "fast_lane_worker_terminal_ack" + or message.action_id != correlation_id + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID") + normalized = _normalize_fast_lane_worker_terminal_ack( + message.payload, terminal_result=terminal_result + ) + except HostBridgeError: + self._poison() + raise + del self._pending_fast_lane_terminals[cast(str, receipt_hash)] + return normalized + def send_capability_probe( self, *, @@ -570,6 +1185,204 @@ def send_operation( self._pending_operations[receipt.envelope_hash] = receipt return receipt + def send_fast_lane_dispatch_batch( + self, + *, + batch: Mapping[str, object], + binding: host_envelopes.EnvelopeBinding, + correlation_id: str, + now: int, + ) -> OperationReceipt: + """Send dispatch only through its closed, compiler-authorized envelope.""" + + try: + envelope = host_envelopes.build_fast_lane_dispatch_envelope( + batch=batch, + binding=binding, + correlation_id=correlation_id, + now=now, + ) + receipt = _operation_receipt(envelope, now=now) + payload = { + "schema": _OPERATION_REQUEST_SCHEMA, + "correlation_id": receipt.correlation_id, + "envelope": envelope, + "envelope_hash": receipt.envelope_hash, + } + _validate_private_packet_size(payload, _MAX_OPERATION_PACKET_BYTES) + except host_envelopes.HostEnvelopeError as error: + raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") from error + if self._operation_is_registered_or_tombstoned(receipt, now=now): + raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") + self._ensure_terminal_operation_tombstone_capacity(receipt, now=now) + self._send_validated_private( + kind="operation_request", action_id=receipt.task_id, payload=payload + ) + # The host owns execution/terminal lifecycle for this batch. Retain only + # an expiry-bounded replay tombstone in the compiler process. + self._remember_terminal_operation(receipt, now=now) + return receipt + + def send_compiler_evidence_request( + self, + *, + preparation_id: str, + call_intent_hash: str, + request_hash: str, + reasoning_effort: str, + requested_route_pairs: Sequence[Mapping[str, object]], + assignment_skeletons: Sequence[Mapping[str, object]], + project_index_attestation_refs: Sequence[Mapping[str, object]], + routing_registry_binding_hash: str, + now: int, + ) -> CompilerEvidenceRequest: + """Request one one-time compiler binding over this authenticated session.""" + + nonce = _b64encode(secrets.token_bytes(32)) + request = _normalize_compiler_evidence_request( + { + "schema": _COMPILER_EVIDENCE_REQUEST_SCHEMA, + "preparation_id": preparation_id, + "call_intent_hash": call_intent_hash, + "request_hash": request_hash, + "reasoning_effort": reasoning_effort, + "requested_route_pairs": list(requested_route_pairs), + "assignment_skeletons": list(assignment_skeletons), + "project_index_attestation_refs": list( + project_index_attestation_refs + ), + "routing_registry_binding_hash": routing_registry_binding_hash, + "nonce": nonce, + "expires_at": now + _COMPILER_EVIDENCE_TTL_SECONDS, + }, + now=now, + ) + if request.preparation_id in self._pending_compiler_evidence: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + payload = _compiler_evidence_request_payload(request) + _validate_private_packet_size(payload, _MAX_COMPILER_EVIDENCE_BYTES) + self._send_validated_private( + kind="compiler_evidence_request", + action_id=request.preparation_id, + payload=payload, + ) + self._pending_compiler_evidence[request.preparation_id] = request + return request + + def send_project_index_attestation( + self, *, attestation: Mapping[str, object], now: int + ) -> dict[str, object]: + """Send one persisted Project Index fact through the authenticated sideband.""" + + normalized = _normalize_project_index_attestation(attestation, now=now) + attestation_hash = normalized["attestation_hash"] + assert type(attestation_hash) is str + if attestation_hash in self._received_project_index_attestations: + raise HostBridgeError("HOST_BRIDGE_PROJECT_INDEX_ATTESTATION_INVALID") + _validate_private_packet_size( + normalized, _MAX_PROJECT_INDEX_ATTESTATION_BYTES + ) + self._send_validated_private( + kind="project_index_attestation", + action_id=cast(str, normalized["correlation_id"]), + payload=normalized, + ) + # This process never needs the actual root after deriving the opaque facts. + return normalized + + def receive_project_index_attestation(self, *, now: int) -> dict[str, object]: + """Receive one exact, expiry-bounded Project Index attestation once.""" + + try: + message = self._receive_private() + if message.kind != "project_index_attestation": + raise HostBridgeError( + "HOST_BRIDGE_PROJECT_INDEX_ATTESTATION_INVALID" + ) + normalized = _normalize_project_index_attestation( + message.payload, now=now + ) + if message.action_id != normalized["correlation_id"]: + raise HostBridgeError( + "HOST_BRIDGE_PROJECT_INDEX_ATTESTATION_INVALID" + ) + attestation_hash = normalized["attestation_hash"] + assert type(attestation_hash) is str + if attestation_hash in self._received_project_index_attestations: + raise HostBridgeError( + "HOST_BRIDGE_PROJECT_INDEX_ATTESTATION_INVALID" + ) + except HostBridgeError: + self._poison() + raise + self._received_project_index_attestations.add(attestation_hash) + return normalized + + def receive_compiler_evidence_request(self, *, now: int) -> CompilerEvidenceRequest: + """Receive one exact compiler request for a host-side registry lookup.""" + + try: + message = self._receive_private() + request = _parse_compiler_evidence_request(message, now=now) + if request.preparation_id in self._received_compiler_evidence: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + except HostBridgeError: + self._poison() + raise + self._received_compiler_evidence.add(request.preparation_id) + return request + + def send_compiler_evidence_response( + self, + *, + request: CompilerEvidenceRequest, + response: Mapping[str, object], + now: int, + ) -> None: + """Return only registry-bound facts for a request received on this session.""" + + normalized_request = _normalize_compiler_evidence_request( + _compiler_evidence_request_payload(request), now=now + ) + if normalized_request.preparation_id not in self._received_compiler_evidence: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + normalized = _normalize_compiler_evidence_response( + response, request=normalized_request, now=now + ) + _validate_private_packet_size(normalized, _MAX_COMPILER_EVIDENCE_BYTES) + self._send_validated_private( + kind="compiler_evidence_response", + action_id=normalized_request.preparation_id, + payload=normalized, + ) + self._received_compiler_evidence.remove(normalized_request.preparation_id) + + def receive_compiler_evidence_response( + self, *, request: CompilerEvidenceRequest, now: int + ) -> dict[str, object]: + """Consume one exact response bound to the still-pending request and nonce.""" + + normalized_request = _normalize_compiler_evidence_request( + _compiler_evidence_request_payload(request), now=now + ) + if self._pending_compiler_evidence.get(request.preparation_id) != request: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + try: + message = self._receive_private() + if ( + message.kind != "compiler_evidence_response" + or message.action_id != request.preparation_id + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + normalized = _normalize_compiler_evidence_response( + message.payload, request=normalized_request, now=now + ) + except HostBridgeError: + self._poison() + raise + del self._pending_compiler_evidence[request.preparation_id] + return normalized + def receive_operation( self, *, @@ -580,7 +1393,12 @@ def receive_operation( try: message = self._receive_private() - receipt = _parse_operation_request(message, now=now, expected=expected) + receipt = _parse_operation_request( + message, + now=now, + expected=expected, + allowed_kinds={"coordinator_assignment", "peer_evidence_handoff"}, + ) if self._operation_is_registered_or_tombstoned(receipt, now=now): raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") except host_envelopes.HostEnvelopeError as error: @@ -592,6 +1410,27 @@ def receive_operation( self._received_operations[receipt.envelope_hash] = receipt return receipt + def receive_fast_lane_dispatch_batch(self, *, now: int) -> OperationReceipt: + """Receive only the typed Fast Lane operation; generic receive stays closed.""" + + try: + message = self._receive_private() + receipt = _parse_operation_request( + message, + now=now, + expected=None, + allowed_kinds={"fast_lane_dispatch_batch"}, + ) + if self._operation_is_registered_or_tombstoned(receipt, now=now): + raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") + except (host_envelopes.HostEnvelopeError, HostBridgeError) as error: + self._poison() + if isinstance(error, HostBridgeError): + raise + raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") from error + self._received_operations[receipt.envelope_hash] = receipt + return receipt + def send_terminal_result( self, *, @@ -770,32 +1609,37 @@ def _send_validated_private( def _send_private( self, *, kind: str, action_id: str, payload: Mapping[str, object] ) -> None: - if kind not in _MESSAGE_KINDS or kind == "session_open": - raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") - _validate_action_id(action_id) - encoded_payload = _json_object(payload) - self._ensure_open() - bootstrap = ( - self._frame_bytes( - kind="session_open", - action_id="session", - sequence=0, - payload={"session_key": _b64encode(self._session_key)}, - ) - if not self._bootstrap_sent - else None - ) - private_frame = self._frame_bytes( - kind=kind, - action_id=action_id, - sequence=self._next_out, - payload=encoded_payload, - ) - if bootstrap is not None: - self._write_complete(bootstrap) - self._bootstrap_sent = True - self._write_complete(private_frame) - self._next_out += 1 + with self._io_lock: + if kind not in _MESSAGE_KINDS or kind == "session_open": + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") + _validate_frame_action_id(kind, action_id, payload) + self._ensure_open() + try: + encoded_payload = _json_object(payload) + bootstrap = ( + self._frame_bytes( + kind="session_open", + action_id="session", + sequence=0, + payload={"session_key": _b64encode(self._session_key)}, + ) + if not self._bootstrap_sent + else None + ) + private_frame = self._frame_bytes( + kind=kind, + action_id=action_id, + sequence=self._next_out, + payload=encoded_payload, + ) + except (HostBridgeError, RecursionError): + self._poison() + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") from None + if bootstrap is not None: + self._write_complete(bootstrap) + self._bootstrap_sent = True + self._write_complete(private_frame) + self._next_out += 1 def receive(self) -> PrivateHostMessage: """Read only legacy private messages; typed packets need typed receivers.""" @@ -809,48 +1653,72 @@ def receive(self) -> PrivateHostMessage: def _receive_private(self) -> PrivateHostMessage: """Read one framed message for a typed private validator.""" - self._ensure_open() - try: - frame = _decode_frame(self._read_raw_frame(self._read_fd)) - self._verify_frame(frame, expected_sequence=self._next_in) - except HostBridgeError: - self._poison() - raise - self._next_in += 1 - kind = frame["kind"] - action_id = frame["action_id"] - payload = frame["payload"] - assert type(kind) is str - assert type(action_id) is str - assert type(frame["sequence"]) is int - assert type(payload) is dict - if kind == "capability_ack" and payload == {}: - delivery = self._deliveries.get(action_id) - if delivery is not None: - delivery.state = "acknowledged" - return PrivateHostMessage( - kind=kind, - action_id=action_id, - sequence=frame["sequence"], - payload=payload, - ) + with self._io_lock: + self._ensure_open() + try: + frame = _decode_frame( + self._read_raw_frame( + self._read_fd, + cancel_event=self._cancel_event, + cancel_fd=self._cancel_read_fd, + ) + ) + self._verify_frame(frame, expected_sequence=self._next_in) + except HostBridgeError: + self._poison() + raise + self._next_in += 1 + kind = frame["kind"] + action_id = frame["action_id"] + payload = frame["payload"] + assert type(kind) is str + assert type(action_id) is str + assert type(frame["sequence"]) is int + assert type(payload) is dict + if kind == "capability_ack" and payload == {}: + delivery = self._deliveries.get(action_id) + if delivery is not None: + delivery.state = "acknowledged" + return PrivateHostMessage( + kind=kind, + action_id=action_id, + sequence=frame["sequence"], + payload=payload, + ) def close(self) -> None: """Close only the descriptor(s) this bridge owns.""" - if self._closed: - return - self._closed = True - if not self._owns_descriptors: - return - descriptors = {self._read_fd, self._write_fd} - self._read_fd = -1 - self._write_fd = -1 - for descriptor in descriptors: + with self._close_lock: + if self._closed: + return + self._closed = True + self._cancel_event.set() try: - os.close(descriptor) + os.write(self._cancel_write_fd, b"\\x00") except OSError: pass + cancel_descriptors = { + self._cancel_read_fd, + self._cancel_write_fd, + } + self._cancel_read_fd = -1 + self._cancel_write_fd = -1 + for descriptor in cancel_descriptors: + try: + os.close(descriptor) + except OSError: + pass + if not self._owns_descriptors: + return + descriptors = {self._read_fd, self._write_fd} + self._read_fd = -1 + self._write_fd = -1 + for descriptor in descriptors: + try: + os.close(descriptor) + except OSError: + pass def _frame_bytes( self, *, kind: str, action_id: str, sequence: int, payload: dict[str, object] @@ -889,8 +1757,7 @@ def _verify_frame( schema != _FRAME_SCHEMA or type(kind) is not str or kind not in _MESSAGE_KINDS - or type(action_id) is not str - or _IDENTIFIER.fullmatch(action_id) is None + or not _is_valid_frame_action_id(kind, action_id, payload) or type(session_nonce) is not str or session_nonce != self._session_nonce or type(sequence) is not int @@ -917,12 +1784,21 @@ def _verify_frame( raise HostBridgeError("HOST_BRIDGE_SEQUENCE_INVALID") @staticmethod - def _read_raw_frame(descriptor: int) -> bytes: - header = _read_exact(descriptor, 4) + def _read_raw_frame( + descriptor: int, + *, + cancel_event: Event | None = None, + cancel_fd: int | None = None, + ) -> bytes: + header = _read_exact( + descriptor, 4, cancel_event=cancel_event, cancel_fd=cancel_fd + ) size = struct.unpack("!I", header)[0] if size == 0 or size > _MAX_FRAME_BYTES: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") - return _read_exact(descriptor, size) + return _read_exact( + descriptor, size, cancel_event=cancel_event, cancel_fd=cancel_fd + ) def _write_complete(self, payload: bytes) -> None: try: @@ -1006,6 +1882,272 @@ def _normalize_bridge_binding( raise host_envelopes.HostEnvelopeError("HOST_ENVELOPE_INVALID") +def _normalize_capability_probe_v2(value: object, *, now: int) -> CapabilityProbeV2: + expected_schemas = [ + "fastlane-host-dispatch-request-v1", + "fastlane-routing-request-v5", + "project-index-attestation-v1", + ] + if ( + type(value) is not dict + or set(value) + != { + "schema", + "call_intent_hash", + "preparation_id", + "requested_capability_schemas", + "probe_hash", + } + or value.get("schema") != _CAPABILITY_PROBE_SCHEMA_V2 + or type(now) is not int + or now < 0 + ): + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + call_intent_hash = value.get("call_intent_hash") + preparation_id = value.get("preparation_id") + probe_hash = value.get("probe_hash") + if ( + type(call_intent_hash) is not str + or len(call_intent_hash) != 64 + or any(character not in "0123456789abcdef" for character in call_intent_hash) + or type(preparation_id) is not str + or _IDENTIFIER.fullmatch(preparation_id) is None + or value.get("requested_capability_schemas") != expected_schemas + or type(probe_hash) is not str + or _DIGEST.fullmatch(probe_hash) is None + ): + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + unsigned = dict(value) + unsigned.pop("probe_hash") + if not hmac.compare_digest(probe_hash, _private_payload_hash(unsigned)): + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + return CapabilityProbeV2( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + requested_capability_schemas=tuple(expected_schemas), + probe_hash=probe_hash, + expires_at=now + _CAPABILITY_V2_TTL_SECONDS, + ) + + +def _normalize_capability_report_v2( + value: object, *, probe: CapabilityProbeV2, now: int +) -> dict[str, object]: + if ( + type(value) is not dict + or set(value) + != { + "schema", + "call_intent_hash", + "preparation_id", + "probe_hash", + "host_capabilities", + "scheduler_facts", + "report_hash", + } + or value.get("schema") != _CAPABILITY_REPORT_SCHEMA_V2 + or value.get("call_intent_hash") != probe.call_intent_hash + or value.get("preparation_id") != probe.preparation_id + or value.get("probe_hash") != probe.probe_hash + or type(now) is not int + or not now < probe.expires_at + ): + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + try: + from devkit_fastlane.scripts import fastlane_routing + + policy = fastlane_routing.load_policy_v5() + host = fastlane_routing._normalise_host_v5(value["host_capabilities"], policy) + scheduler = fastlane_routing._normalise_scheduler(value["scheduler_facts"]) + except Exception as error: + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") from error + report_hash = value.get("report_hash") + unsigned = dict(value) + unsigned.pop("report_hash") + if ( + value["host_capabilities"] != host + or value["scheduler_facts"] != scheduler + or type(report_hash) is not str + or _DIGEST.fullmatch(report_hash) is None + or not hmac.compare_digest(report_hash, _private_payload_hash(unsigned)) + ): + raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") + _validate_private_packet_size(value, _MAX_CAPABILITY_PACKET_BYTES) + return dict(value) + + +def _normalize_routing_attestation_request( + value: object, *, now: int +) -> RoutingAttestationRequest: + if ( + type(value) is not dict + or set(value) + != { + "schema", + "call_intent_hash", + "preparation_id", + "routing_requests", + "routing_request_set_hash", + } + or value.get("schema") != _ROUTING_ATTESTATION_REQUEST_SCHEMA + or type(now) is not int + or now < 0 + ): + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + call_intent_hash = value.get("call_intent_hash") + preparation_id = value.get("preparation_id") + request_set_hash = value.get("routing_request_set_hash") + candidates = value.get("routing_requests") + if ( + type(call_intent_hash) is not str + or len(call_intent_hash) != 64 + or any(character not in "0123456789abcdef" for character in call_intent_hash) + or type(preparation_id) is not str + or _IDENTIFIER.fullmatch(preparation_id) is None + or type(request_set_hash) is not str + or _DIGEST.fullmatch(request_set_hash) is None + or type(candidates) is not list + or not 1 <= len(candidates) <= 16 + ): + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + try: + from devkit_fastlane.scripts import fastlane_routing + + policy = fastlane_routing.load_policy_v5() + normalized = [ + fastlane_routing._normalise_request_v5(candidate, policy) + for candidate in candidates + ] + except Exception as error: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") from error + task_ids = [request["task"]["task_id"] for request in normalized] + if ( + candidates != normalized + or any(request["child_route_attestation"] is not None for request in normalized) + or task_ids != sorted(task_ids) + or len(set(task_ids)) != len(task_ids) + or not hmac.compare_digest(request_set_hash, _private_payload_hash(normalized)) + ): + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + _validate_private_packet_size(value, _MAX_ROUTING_ATTESTATION_BYTES) + return RoutingAttestationRequest( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + routing_requests=tuple(dict(request) for request in normalized), + routing_request_set_hash=request_set_hash, + expires_at=now + _ROUTING_ATTESTATION_TTL_SECONDS, + ) + + +def _normalize_routing_attestation_response( + value: object, *, request: RoutingAttestationRequest, now: int +) -> dict[str, object]: + if ( + type(value) is not dict + or set(value) + != { + "schema", + "call_intent_hash", + "preparation_id", + "routing_request_set_hash", + "attestations", + "routing_registry_binding_hash", + } + or value.get("schema") != _ROUTING_ATTESTATION_RESPONSE_SCHEMA + or value.get("call_intent_hash") != request.call_intent_hash + or value.get("preparation_id") != request.preparation_id + or value.get("routing_request_set_hash") != request.routing_request_set_hash + or type(now) is not int + or not now < request.expires_at + ): + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + items = value.get("attestations") + registry_hash = value.get("routing_registry_binding_hash") + unsigned = dict(value) + unsigned.pop("routing_registry_binding_hash") + if ( + type(items) is not list + or len(items) != len(request.routing_requests) + or type(registry_hash) is not str + or _DIGEST.fullmatch(registry_hash) is None + or not hmac.compare_digest(registry_hash, _private_payload_hash(unsigned)) + ): + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + normalized_items: list[dict[str, object]] = [] + try: + from devkit_fastlane.scripts import fastlane_routing + + policy = fastlane_routing.load_policy_v5() + for original_request, item in zip(request.routing_requests, items, strict=True): + if type(item) is not dict or set(item) != { + "task_id", + "request_binding_hash", + "attestation", + }: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + task_id = original_request["task"]["task_id"] + binding_hash = fastlane_routing.v5_request_binding_hash(original_request) + if item.get("task_id") != task_id or item.get( + "request_binding_hash" + ) != binding_hash: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + routed_request = dict(original_request) + routed_request["child_route_attestation"] = item.get("attestation") + normalized_request = fastlane_routing._normalise_request_v5( + routed_request, policy + ) + fastlane_routing.route_v5(normalized_request, policy=policy) + if routed_request != normalized_request: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + normalized_items.append(dict(item)) + except HostBridgeError: + raise + except Exception as error: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") from error + if items != normalized_items: + raise HostBridgeError("HOST_BRIDGE_ROUTING_ATTESTATION_INVALID") + _validate_private_packet_size(value, _MAX_ROUTING_ATTESTATION_BYTES) + return dict(value) + + +_FAST_LANE_TERMINAL_BINDING_FIELDS: Final = ( + fastlane_terminal_protocol.TERMINAL_BINDING_FIELDS +) + + +def _normalize_fast_lane_worker_terminal_result( + value: object, + *, + expected: Mapping[str, object], + expires_at: int | None = None, + now: int, +) -> dict[str, object]: + try: + return fastlane_terminal_protocol.normalize_worker_terminal_result( + value, expected=expected, expires_at=expires_at, now=now + ) + except fastlane_terminal_protocol.FastLaneTerminalProtocolError as error: + raise HostBridgeError(error.code) from error + + +def _normalize_fast_lane_worker_terminal_ack( + value: object, *, terminal_result: Mapping[str, object] +) -> dict[str, object]: + try: + return fastlane_terminal_protocol.normalize_worker_terminal_ack( + value, terminal_result=terminal_result + ) + except fastlane_terminal_protocol.FastLaneTerminalProtocolError as error: + raise HostBridgeError(error.code) from error + + +def _validate_terminal_correlation(value: object) -> None: + try: + fastlane_terminal_protocol.validate_terminal_correlation(value) + except fastlane_terminal_protocol.FastLaneTerminalProtocolError as error: + raise HostBridgeError(error.code) from error + + def _normalize_capability_names(value: object) -> tuple[str, ...]: if type(value) is not list and type(value) is not tuple: raise HostBridgeError("HOST_BRIDGE_CAPABILITY_INVALID") @@ -1153,6 +2295,685 @@ def _parse_capability_report( return normalized +def _normalize_compiler_evidence_request( + value: object, *, now: int +) -> CompilerEvidenceRequest: + if ( + type(value) is not dict + or set(value) + != { + "schema", + "preparation_id", + "call_intent_hash", + "request_hash", + "reasoning_effort", + "requested_route_pairs", + "assignment_skeletons", + "project_index_attestation_refs", + "routing_registry_binding_hash", + "nonce", + "expires_at", + } + or value.get("schema") != _COMPILER_EVIDENCE_REQUEST_SCHEMA + or type(now) is not int + or now < 0 + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + preparation_id = value.get("preparation_id") + call_intent_hash = value.get("call_intent_hash") + request_hash = value.get("request_hash") + reasoning_effort = value.get("reasoning_effort") + route_pairs = value.get("requested_route_pairs") + assignment_skeletons = value.get("assignment_skeletons") + attestation_refs = value.get("project_index_attestation_refs") + routing_registry_binding_hash = value.get("routing_registry_binding_hash") + nonce = value.get("nonce") + expires_at = value.get("expires_at") + if ( + type(preparation_id) is not str + or _IDENTIFIER.fullmatch(preparation_id) is None + or type(call_intent_hash) is not str + or len(call_intent_hash) != 64 + or any(character not in "0123456789abcdef" for character in call_intent_hash) + or type(request_hash) is not str + or _DIGEST.fullmatch(request_hash) is None + or reasoning_effort not in {"low", "medium", "high", "xhigh", "max"} + or type(route_pairs) is not list + or not route_pairs + or len(route_pairs) > 16 + or type(assignment_skeletons) is not list + or not assignment_skeletons + or len(assignment_skeletons) > 16 + or type(attestation_refs) is not list + or len(attestation_refs) != len(assignment_skeletons) + or type(routing_registry_binding_hash) is not str + or _DIGEST.fullmatch(routing_registry_binding_hash) is None + or type(nonce) is not str + or type(expires_at) is not int + or not now < expires_at <= now + _COMPILER_EVIDENCE_TTL_SECONDS + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + try: + if len(_b64decode(nonce)) != 32 or _b64encode(_b64decode(nonce)) != nonce: + raise ValueError + except ValueError as error: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") from error + normalized_pairs: list[tuple[str, str]] = [] + for pair in route_pairs: + if type(pair) is not dict or set(pair) != {"model", "effort"}: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + model = pair.get("model") + effort = pair.get("effort") + if ( + type(model) is not str + or _IDENTIFIER.fullmatch(model) is None + or type(effort) is not str + or _IDENTIFIER.fullmatch(effort) is None + or effort == "ultra" + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + normalized_pairs.append((model, effort)) + if normalized_pairs != sorted(set(normalized_pairs)): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + normalized_skeletons = [ + _normalize_assignment_skeleton(item) for item in assignment_skeletons + ] + normalized_refs = [ + _normalize_project_index_attestation_ref(item) for item in attestation_refs + ] + task_ids = [cast(str, item["task_id"]) for item in normalized_skeletons] + if len(set(task_ids)) != len(task_ids): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + if [item["task_id"] for item in normalized_refs] != task_ids: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + receipt_bindings = { + _canonical_bytes( + {key: value for key, value in item.items() if key != "task_id"} + ) + for item in normalized_refs + } + if len(receipt_bindings) != 1: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + skeleton_pairs: list[tuple[str, str]] = [] + for item in normalized_skeletons: + proof = cast(dict[str, object], item["routing_proof"]) + result = cast(dict[str, object], proof["result"]) + route = cast(dict[str, object], result["route"]) + skeleton_pairs.append((cast(str, route["model"]), cast(str, route["effort"]))) + skeleton_pairs = sorted(set(skeleton_pairs)) + if skeleton_pairs != normalized_pairs: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + for skeleton, reference in zip(normalized_skeletons, normalized_refs, strict=True): + if skeleton["index_context_hash"] != reference["index_context_hash"]: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + if len({item["source_plan_hash"] for item in normalized_skeletons}) != 1: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + planner_preimage = { + "schema": "2718lab-devkit/fastlane-host-planner-request-v1", + "action": "plan_dispatch", + "assignment_skeletons": normalized_skeletons, + "project_index_attestation_refs": normalized_refs, + } + if not hmac.compare_digest(request_hash, _private_payload_hash(planner_preimage)): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + return CompilerEvidenceRequest( + preparation_id=preparation_id, + call_intent_hash=call_intent_hash, + request_hash=request_hash, + reasoning_effort=reasoning_effort, + requested_route_pairs=tuple(normalized_pairs), + assignment_skeletons=tuple(normalized_skeletons), + project_index_attestation_refs=tuple(normalized_refs), + routing_registry_binding_hash=routing_registry_binding_hash, + nonce=nonce, + expires_at=expires_at, + ) + + +def _compiler_evidence_request_payload( + request: CompilerEvidenceRequest, +) -> dict[str, object]: + if type(request) is not CompilerEvidenceRequest: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + return { + "schema": _COMPILER_EVIDENCE_REQUEST_SCHEMA, + "preparation_id": request.preparation_id, + "call_intent_hash": request.call_intent_hash, + "request_hash": request.request_hash, + "reasoning_effort": request.reasoning_effort, + "requested_route_pairs": [ + {"model": model, "effort": effort} + for model, effort in request.requested_route_pairs + ], + "assignment_skeletons": [dict(item) for item in request.assignment_skeletons], + "project_index_attestation_refs": [ + dict(item) for item in request.project_index_attestation_refs + ], + "routing_registry_binding_hash": request.routing_registry_binding_hash, + "nonce": request.nonce, + "expires_at": request.expires_at, + } + + +def _parse_compiler_evidence_request( + message: PrivateHostMessage, *, now: int +) -> CompilerEvidenceRequest: + if message.kind != "compiler_evidence_request": + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + request = _normalize_compiler_evidence_request(message.payload, now=now) + if message.action_id != request.preparation_id: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + _validate_private_packet_size(message.payload, _MAX_COMPILER_EVIDENCE_BYTES) + return request + + +def _normalize_assignment_skeleton(value: object) -> dict[str, object]: + fields = { + "task_id", + "routing_proof", + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "predecessor_hash", + "source_plan_hash", + } + if type(value) is not dict or set(value) != fields: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + proof = value.get("routing_proof") + if type(proof) is not dict or set(proof) != { + "request", + "result", + "request_binding_hash", + "attestation_hash", + "routing_context_hash", + "routing_result_hash", + }: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + task_id = value.get("task_id") + scope = value.get("write_scope") + mode = value.get("concurrency_mode") + order = value.get("dispatch_order") + digest_fields = ( + "request_binding_hash", + "attestation_hash", + "routing_context_hash", + "routing_result_hash", + "index_context_hash", + "predecessor_hash", + "source_plan_hash", + ) + digest_values = { + **{field: proof.get(field) for field in digest_fields[:4]}, + **{field: value.get(field) for field in digest_fields[4:]}, + } + if ( + type(task_id) is not str + or _FAST_LANE_TASK_ID.fullmatch(task_id) is None + or any( + type(item) is not str or _DIGEST.fullmatch(item) is None + for item in digest_values.values() + ) + or mode not in {"parallel", "serial", "isolated_worktree"} + or type(order) is not int + or not 0 <= order < 16 + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + try: + if type(scope) is not list: + raise ValueError + from .fastlane_host_adapter import _canonical_write_scope + + normalized_scope = list(_canonical_write_scope(tuple(scope))) + except (TypeError, ValueError) as error: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") from error + try: + from devkit_fastlane.scripts import fastlane_routing + + request = fastlane_routing._normalise_request_v5( + proof["request"], fastlane_routing.load_policy_v5() + ) + result = fastlane_routing.route_v5(request) + task = cast(dict[str, object], request["task"]) + scheduler = cast(dict[str, object], request["scheduler_facts"]) + attestation = cast(dict[str, object], request["child_route_attestation"]) + expected_context = _private_payload_hash( + { + "schema": "team-efficiency/fast-lane-routing-context-binding-v1", + "source_plan_hash": value["source_plan_hash"], + "task_id": task_id, + "scheduler_role": task["role"], + "routing_request_hash": _private_payload_hash(request), + "scheduler_facts_hash": _private_payload_hash(scheduler), + } + ) + if ( + request != proof["request"] + or result != proof["result"] + or task["task_id"] != task_id + or fastlane_routing.v5_request_binding_hash(request) + != digest_values["request_binding_hash"] + or attestation.get("attestation_hash") != digest_values["attestation_hash"] + or expected_context != digest_values["routing_context_hash"] + or _private_payload_hash(result) != digest_values["routing_result_hash"] + ): + raise ValueError + except Exception as error: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") from error + return { + "task_id": task_id, + "routing_proof": dict(proof), + "write_scope": normalized_scope, + "concurrency_mode": mode, + "dispatch_order": order, + "index_context_hash": digest_values["index_context_hash"], + "predecessor_hash": digest_values["predecessor_hash"], + "source_plan_hash": digest_values["source_plan_hash"], + } + + +def _normalize_project_index_attestation_ref(value: object) -> dict[str, object]: + fields = { + "task_id", + "correlation_id", + "workspace_id", + "workspace_binding_hash", + "root_identity_hash", + "snapshot_id", + "snapshot_attestation_hash", + "query_receipt_hash", + "index_context_hash", + "attestation_hash", + } + if type(value) is not dict or set(value) != fields: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + task_id = value.get("task_id") + if type(task_id) is not str or _FAST_LANE_TASK_ID.fullmatch(task_id) is None: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + correlation_id = value.get("correlation_id") + if not _is_index_correlation(correlation_id): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + for field_name in fields - {"task_id", "correlation_id"}: + item = value.get(field_name) + if type(item) is not str or _DIGEST.fullmatch(item) is None: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + return {field_name: value[field_name] for field_name in sorted(fields)} + + +def _validate_skeleton_package_coverage( + initial_skeletons: Sequence[Mapping[str, object]], + remaining_skeletons: Sequence[Mapping[str, object]], + *, + source_plan_hash: str, + source_plan_task_ids: Sequence[str], +) -> str: + """Bind both waves to one complete, hole-free source-plan package. + + Individual waves retain source dispatch coordinates and may therefore have + holes. Only their union is required to cover every source task exactly + once and to contain every global dispatch coordinate exactly once. + """ + + if ( + type(source_plan_hash) is not str + or _DIGEST.fullmatch(source_plan_hash) is None + or not isinstance(source_plan_task_ids, Sequence) + or isinstance(source_plan_task_ids, (str, bytes, bytearray)) + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + source_ids = list(source_plan_task_ids) + if ( + not 1 <= len(source_ids) <= 16 + or any( + type(task_id) is not str or _FAST_LANE_TASK_ID.fullmatch(task_id) is None + for task_id in source_ids + ) + or len(set(source_ids)) != len(source_ids) + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + if not isinstance(initial_skeletons, Sequence) or isinstance( + initial_skeletons, (str, bytes, bytearray) + ) or not isinstance(remaining_skeletons, Sequence) or isinstance( + remaining_skeletons, (str, bytes, bytearray) + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + combined = [*initial_skeletons, *remaining_skeletons] + if len(combined) != len(source_ids): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + task_ids: list[str] = [] + orders: list[int] = [] + for skeleton in combined: + if type(skeleton) is not dict: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + task_id = skeleton.get("task_id") + order = skeleton.get("dispatch_order") + if ( + type(task_id) is not str + or _FAST_LANE_TASK_ID.fullmatch(task_id) is None + or type(order) is not int + or not 0 <= order < len(source_ids) + or skeleton.get("source_plan_hash") != source_plan_hash + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + task_ids.append(task_id) + orders.append(order) + if ( + len(set(task_ids)) != len(task_ids) + or set(task_ids) != set(source_ids) + or len(set(orders)) != len(orders) + or set(orders) != set(range(len(source_ids))) + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + ordered = sorted(combined, key=lambda item: cast(int, item["dispatch_order"])) + return _private_payload_hash( + { + "schema": "2718lab-devkit/authenticated-v5-skeleton-package-v1", + "source_plan_hash": source_plan_hash, + "task_ids": source_ids, + "assignment_skeletons": ordered, + } + ) + + +def _normalize_fast_lane_refill_registry_request( + value: object, + *, + now: int, + initial_skeletons: Sequence[Mapping[str, object]] = (), + source_plan_task_ids: Sequence[str] | None = None, + skeleton_package_hash: str | None = None, +) -> FastLaneRefillRegistryRequest: + """Validate the exact9-field authenticated successor-wave registry.""" + + fields = { + "schema", + "call_intent_hash", + "preparation_id", + "source_plan_hash", + "index_context_hash", + "routing_registry_binding_hash", + "remaining_skeletons", + "index_attestation_refs", + "queue_registry_hash", + } + if ( + type(value) is not dict + or set(value) != fields + or value.get("schema") != _FAST_LANE_REFILL_REGISTRY_SCHEMA + or type(now) is not int + or now < 0 + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + call_intent_hash = value.get("call_intent_hash") + preparation_id = value.get("preparation_id") + source_plan_hash = value.get("source_plan_hash") + index_context_hash = value.get("index_context_hash") + routing_registry_binding_hash = value.get("routing_registry_binding_hash") + queue_registry_hash = value.get("queue_registry_hash") + if ( + type(call_intent_hash) is not str + or len(call_intent_hash) != 64 + or any(character not in "0123456789abcdef" for character in call_intent_hash) + or type(preparation_id) is not str + or _IDENTIFIER.fullmatch(preparation_id) is None + or any( + type(item) is not str or _DIGEST.fullmatch(item) is None + for item in ( + source_plan_hash, + index_context_hash, + routing_registry_binding_hash, + queue_registry_hash, + ) + ) + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + raw_skeletons = value.get("remaining_skeletons") + raw_refs = value.get("index_attestation_refs") + if ( + type(raw_skeletons) is not list + or not 1 <= len(raw_skeletons) <= 16 + or type(raw_refs) is not list + or len(raw_refs) != len(raw_skeletons) + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + try: + skeletons = [_normalize_assignment_skeleton(item) for item in raw_skeletons] + refs = [_normalize_project_index_attestation_ref(item) for item in raw_refs] + except HostBridgeError as error: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") from error + task_ids = [cast(str, item["task_id"]) for item in skeletons] + ref_task_ids = [cast(str, item["task_id"]) for item in refs] + if ( + len(set(task_ids)) != len(task_ids) + or ref_task_ids != task_ids + or any( + item["source_plan_hash"] != source_plan_hash + or item["index_context_hash"] != index_context_hash + for item in skeletons + ) + or any(item["index_context_hash"] != index_context_hash for item in refs) + or raw_skeletons != skeletons + or raw_refs != refs + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + if source_plan_task_ids is None: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + try: + normalized_initial = [ + _normalize_assignment_skeleton(item) for item in initial_skeletons + ] + expected_package_hash = _validate_skeleton_package_coverage( + normalized_initial, + skeletons, + source_plan_hash=cast(str, source_plan_hash), + source_plan_task_ids=source_plan_task_ids, + ) + except (HostBridgeError, TypeError, ValueError) as error: + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") from error + if skeleton_package_hash is None: + skeleton_package_hash = expected_package_hash + if ( + type(skeleton_package_hash) is not str + or _DIGEST.fullmatch(skeleton_package_hash) is None + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + if not hmac.compare_digest(skeleton_package_hash, expected_package_hash): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + if any( + item["index_context_hash"] != index_context_hash + for item in normalized_initial + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + unsigned = dict(value) + unsigned.pop("queue_registry_hash") + if not hmac.compare_digest( + cast(str, queue_registry_hash), _private_payload_hash(unsigned) + ): + raise HostBridgeError("HOST_BRIDGE_FAST_LANE_REFILL_INVALID") + return FastLaneRefillRegistryRequest( + correlation_id=_FAST_LANE_REFILL_REGISTRY_ACTION_PREFIX + + cast(str, queue_registry_hash)[7:], + call_intent_hash=cast(str, call_intent_hash), + preparation_id=cast(str, preparation_id), + source_plan_hash=cast(str, source_plan_hash), + index_context_hash=cast(str, index_context_hash), + routing_registry_binding_hash=cast(str, routing_registry_binding_hash), + remaining_skeletons=tuple(skeletons), + index_attestation_refs=tuple(refs), + queue_registry_hash=cast(str, queue_registry_hash), + expires_at=now + _COMPILER_EVIDENCE_TTL_SECONDS, + ) + + +def _normalize_project_index_attestation( + value: object, *, now: int +) -> dict[str, object]: + try: + return project_index_attestation_protocol.normalize_attestation(value, now=now) + except ( + project_index_attestation_protocol.ProjectIndexAttestationProtocolError + ) as error: + raise HostBridgeError(error.code) from error + + +def build_project_index_attestation( + *, + operation: str, + correlation_id: str, + material: Mapping[str, object], + now: int, +) -> dict[str, object]: + """Build one closed sideband packet from already persisted index material.""" + try: + return project_index_attestation_protocol.build_attestation( + operation=operation, + correlation_id=correlation_id, + material=material, + now=now, + ) + except ( + project_index_attestation_protocol.ProjectIndexAttestationProtocolError + ) as error: + raise HostBridgeError(error.code) from error + + +def _is_index_correlation(value: object) -> bool: + return project_index_attestation_protocol.is_index_correlation(value) + + +def _normalize_compiler_evidence_response( + value: object, *, request: CompilerEvidenceRequest, now: int +) -> dict[str, object]: + expected_fields = { + "schema", + "preparation_id", + "request_hash", + "reasoning_effort", + "verified_route_result_hashes", + "verified_lease_scope_bindings", + "dispatch_facts", + "dispatch_binding_hashes", + "nonce", + "expires_at", + "registry_binding_hash", + } + if ( + type(value) is not dict + or set(value) != expected_fields + or value.get("schema") != _COMPILER_EVIDENCE_RESPONSE_SCHEMA + or value.get("preparation_id") != request.preparation_id + or value.get("request_hash") != request.request_hash + or value.get("reasoning_effort") != request.reasoning_effort + or value.get("nonce") != request.nonce + or value.get("expires_at") != request.expires_at + or not now < request.expires_at + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + facts = value.get("dispatch_facts") + dispatch_hashes = value.get("dispatch_binding_hashes") + route_hashes = _normalized_sorted_digest_list( + value.get("verified_route_result_hashes") + ) + lease_hashes = _normalized_sorted_digest_list( + value.get("verified_lease_scope_bindings") + ) + if ( + type(facts) is not list + or not facts + or len(facts) > 16 + or type(dispatch_hashes) is not list + or len(dispatch_hashes) != len(facts) + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + from .fastlane_host_adapter import ( + _dispatch_fact_from_mapping, + _dispatch_fact_mapping, + _lease_scope_binding_hash, + _validate_batch_fences, + ) + + try: + normalized_facts = tuple(_dispatch_fact_from_mapping(fact) for fact in facts) + _validate_batch_fences(normalized_facts) + normalized_mappings = [ + _dispatch_fact_mapping(fact) for fact in normalized_facts + ] + expected_dispatch_hashes = [ + mapping["dispatch_binding_hash"] for mapping in normalized_mappings + ] + expected_route_hashes = sorted( + {fact.route.routing_result_hash for fact in normalized_facts} + ) + expected_lease_hashes = sorted( + {_lease_scope_binding_hash(fact) for fact in normalized_facts} + ) + except Exception as error: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") from error + requested_pairs = {(model, effort) for model, effort in request.requested_route_pairs} + fact_pairs = { + (fact.route.model, fact.route.reasoning_effort) for fact in normalized_facts + } + skeletons = request.assignment_skeletons + skeleton_by_task = {item["task_id"]: item for item in skeletons} + if len(skeleton_by_task) != len(skeletons) or len(normalized_facts) != len(skeletons): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + for fact, mapping in zip(normalized_facts, normalized_mappings, strict=True): + skeleton = skeleton_by_task.get(fact.task_id) + if skeleton is None: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + proof = cast(dict[str, object], skeleton["routing_proof"]) + result = cast(dict[str, object], proof["result"]) + result_route = cast(dict[str, object], result["route"]) + expected_route = { + "model": result_route["model"], + "reasoning_effort": result_route["effort"], + "routing_context_hash": proof["routing_context_hash"], + "routing_result_hash": proof["routing_result_hash"], + "require_explicit_route": True, + } + if mapping["route"] != expected_route or any( + mapping[field_name] != skeleton[field_name] + for field_name in ( + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "predecessor_hash", + "source_plan_hash", + ) + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + if ( + facts != normalized_mappings + or dispatch_hashes != expected_dispatch_hashes + or route_hashes != expected_route_hashes + or lease_hashes != expected_lease_hashes + or fact_pairs != requested_pairs + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + registry_binding_hash = value.get("registry_binding_hash") + unsigned = dict(value) + unsigned.pop("registry_binding_hash", None) + if ( + type(registry_binding_hash) is not str + or _DIGEST.fullmatch(registry_binding_hash) is None + or not hmac.compare_digest(registry_binding_hash, _private_payload_hash(unsigned)) + ): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + _validate_private_packet_size(value, _MAX_COMPILER_EVIDENCE_BYTES) + return dict(value) + + +def _normalized_sorted_digest_list(value: object) -> list[str]: + if type(value) is not list or not value or len(value) > 16: + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + if any(type(item) is not str or _DIGEST.fullmatch(item) is None for item in value): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + if value != sorted(set(value)): + raise HostBridgeError("HOST_BRIDGE_COMPILER_EVIDENCE_INVALID") + return list(value) + + def _operation_receipt(envelope: Mapping[str, object], *, now: int) -> OperationReceipt: normalized = host_envelopes.validate_envelope(envelope, now=now) payload = normalized["payload"] @@ -1164,7 +2985,9 @@ def _operation_receipt(envelope: Mapping[str, object], *, now: int) -> Operation assert type(kind) is str assert type(task_id) is str binding = host_envelopes.validate_binding_mapping( - {field: normalized[field] for field in _BINDING_FIELDS}, now=now + {field: normalized[field] for field in _BINDING_FIELDS}, + now=now, + allow_fast_lane=kind == "fast_lane_dispatch_batch", ) return OperationReceipt( kind=kind, @@ -1237,6 +3060,7 @@ def _parse_operation_request( *, now: int, expected: host_envelopes.EnvelopeExpectation | None, + allowed_kinds: set[str], ) -> OperationReceipt: payload = message.payload envelope_value = payload.get("envelope") @@ -1254,7 +3078,7 @@ def _parse_operation_request( envelope = host_envelopes.validate_envelope( envelope_value, now=now, expected=expected ) - if envelope["kind"] not in {"coordinator_assignment", "peer_evidence_handoff"}: + if envelope["kind"] not in allowed_kinds: raise HostBridgeError("HOST_BRIDGE_ENVELOPE_INVALID") receipt = _operation_receipt(envelope, now=now) if ( @@ -1375,7 +3199,7 @@ def _parse_proof_continuation( } -def _private_payload_hash(payload: Mapping[str, object]) -> str: +def _private_payload_hash(payload: object) -> str: return "sha256:" + hashlib.sha256(_canonical_bytes(payload)).hexdigest() @@ -1589,10 +3413,40 @@ class _IoStatusBlock(ctypes.Structure): return access_mask.value -def _read_exact(descriptor: int, size: int) -> bytes: +def _read_exact( + descriptor: int, + size: int, + *, + cancel_event: Event | None = None, + cancel_fd: int | None = None, +) -> bytes: chunks: list[bytes] = [] remaining = size while remaining: + if cancel_event is not None: + if cancel_event.is_set(): + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + # POSIX pipes can be waited on together with the private wake fd. + # Windows' select() only accepts sockets, so fall back to the + # named/anonymous-pipe availability probe below. + readable: list[int] | None + try: + wait_fds = [descriptor] + if cancel_fd is not None and cancel_fd >= 0: + wait_fds.append(cancel_fd) + readable, _, _ = select.select(wait_fds, [], [], 0.1) + except (OSError, ValueError): + readable = None + if readable is not None: + if not readable: + continue + if cancel_fd is not None and cancel_fd in readable: + raise HostBridgeError("HOST_BRIDGE_UNAVAILABLE") + else: + available = _windows_pipe_readable(descriptor) + if available is False: + time.sleep(0.05) + continue try: chunk = os.read(descriptor, remaining) except OSError as error: @@ -1604,18 +3458,56 @@ def _read_exact(descriptor: int, size: int) -> bytes: return b"".join(chunks) +def _windows_pipe_readable(descriptor: int) -> bool | None: + """Return pipe readiness on Windows, or None when the handle is unknown.""" + + if os.name != "nt": + return None + try: + import ctypes + import msvcrt + + available = ctypes.c_ulong() + handle = msvcrt.get_osfhandle(descriptor) + ok = ctypes.windll.kernel32.PeekNamedPipe( + ctypes.c_void_p(handle), + None, + 0, + None, + ctypes.byref(available), + None, + ) + if ok: + return bool(available.value) + # A broken/closed pipe should be handed to os.read so it produces the + # stable unavailable result rather than spinning in the poll loop. + error = ctypes.get_last_error() + if error in {109, 232}: # ERROR_BROKEN_PIPE / ERROR_NO_DATA + return True + except (AttributeError, OSError, OverflowError, ValueError): + pass + return None + + def _decode_frame(raw: bytes) -> dict[str, object]: try: decoded = json.loads(raw.decode("utf-8")) if type(decoded) is not dict or _canonical_bytes(decoded) != raw: raise ValueError return decoded - except (UnicodeDecodeError, ValueError, json.JSONDecodeError, TypeError) as error: + except ( + UnicodeDecodeError, + ValueError, + json.JSONDecodeError, + TypeError, + RecursionError, + ) as error: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") from error def _canonical_bytes(value: object) -> bytes: try: + _validate_json_value(value) return json.dumps( value, ensure_ascii=False, @@ -1623,7 +3515,7 @@ def _canonical_bytes(value: object) -> bytes: sort_keys=True, separators=(",", ":"), ).encode("utf-8") - except (TypeError, ValueError, UnicodeError) as error: + except (TypeError, ValueError, UnicodeError, RecursionError) as error: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") from error @@ -1633,7 +3525,12 @@ def _json_object(value: Mapping[str, object]) -> dict[str, object]: try: encoded = _canonical_bytes(value) decoded = json.loads(encoded.decode("utf-8")) - except (HostBridgeError, UnicodeDecodeError, json.JSONDecodeError) as error: + except ( + HostBridgeError, + UnicodeDecodeError, + json.JSONDecodeError, + RecursionError, + ) as error: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") from error if type(decoded) is not dict: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") @@ -1642,23 +3539,32 @@ def _json_object(value: Mapping[str, object]) -> dict[str, object]: def _validate_json_value(value: object) -> None: - if value is None or type(value) in {bool, int, str}: - return - if type(value) is float: - if math.isfinite(value): - return - raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") - if type(value) is list: - for item in value: - _validate_json_value(item) - return - if type(value) is dict: - for key, item in value.items(): - if type(key) is not str: + pending: list[tuple[object, int]] = [(value, 0)] + nodes = 0 + while pending: + item, depth = pending.pop() + nodes += 1 + if depth > _MAX_JSON_DEPTH or nodes > _MAX_JSON_NODES: + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") + if item is None or type(item) in {bool, int, str}: + continue + if type(item) is float: + if math.isfinite(item): + continue + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") + if type(item) is list: + if len(item) > _MAX_JSON_NODES - nodes: + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") + pending.extend((child, depth + 1) for child in item) + continue + if type(item) is dict: + if len(item) > _MAX_JSON_NODES - nodes: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") - _validate_json_value(item) - return - raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") + if any(type(key) is not str for key in item): + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") + pending.extend((child, depth + 1) for child in item.values()) + continue + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") def _validate_action_id(value: str) -> None: @@ -1666,6 +3572,34 @@ def _validate_action_id(value: str) -> None: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") +def _validate_frame_action_id( + kind: str, action_id: str, payload: Mapping[str, object] +) -> None: + if not _is_valid_frame_action_id(kind, action_id, payload): + raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") + + +def _is_valid_frame_action_id(kind: object, action_id: object, payload: object) -> bool: + if type(action_id) is not str: + return False + if kind == "fast_lane_refill_registry": + return _FAST_LANE_REFILL_REGISTRY_ACTION.fullmatch(action_id) is not None + if _IDENTIFIER.fullmatch(action_id) is not None: + return True + if ( + kind != "operation_request" + or _FAST_LANE_TASK_ID.fullmatch(action_id) is None + or type(payload) is not dict + ): + return False + envelope = payload.get("envelope") + return ( + type(envelope) is dict + and envelope.get("kind") == "fast_lane_dispatch_batch" + and envelope.get("task_id") == action_id + ) + + def _validate_endpoint(value: str) -> None: if type(value) is not str or _ENDPOINT.fullmatch(value) is None: raise HostBridgeError("HOST_BRIDGE_FRAME_INVALID") diff --git a/mcp-tools/devkit_runtime/host_envelopes.py b/mcp-tools/devkit_runtime/host_envelopes.py index e04bd72..6fd9a8e 100644 --- a/mcp-tools/devkit_runtime/host_envelopes.py +++ b/mcp-tools/devkit_runtime/host_envelopes.py @@ -11,13 +11,14 @@ import json import re from collections.abc import Mapping -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Final, NoReturn HOST_ENVELOPE_SCHEMA: Final = "2718lab-devkit/host-envelope-v1" _DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z") _TASK_ID = re.compile(r"task-[1-9][0-9]{0,11}\Z") +_FAST_LANE_TASK_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,95}\Z") _CORRELATION_ID_BY_KIND: Final = { "coordinator_assignment": re.compile(r"operation-[1-9][0-9]{0,11}\Z"), "worker_terminal_result": re.compile(r"operation-[1-9][0-9]{0,11}\Z"), @@ -26,6 +27,7 @@ _RISK_CODES: Final = frozenset({"none", "bounded", "unverified"}) _ROLE_BY_KIND: Final = { "coordinator_assignment": ("coordinator", "worker"), + "fast_lane_dispatch_batch": ("coordinator", "worker"), "worker_terminal_result": ("worker", "coordinator"), "peer_evidence_handoff": ("peer", "peer"), } @@ -46,6 +48,7 @@ ) _MAX_BYTES_BY_KIND: Final = { "coordinator_assignment": 32 * 1024, + "fast_lane_dispatch_batch": 64 * 1024, "worker_terminal_result": 24 * 1024, "peer_evidence_handoff": 16 * 1024, } @@ -85,7 +88,7 @@ class EnvelopeBinding: task_id: str lease_epoch: int - assignment_token: str + assignment_token: str = field(repr=False) dispatch_context_hash: str route_hash: str expires_at: int @@ -111,7 +114,11 @@ def render_envelope( """Render one canonical, role-fixed envelope without performing I/O.""" roles = _roles_for_kind(kind) - normalized_binding = _validate_binding(binding, now=now) + normalized_binding = _validate_binding( + binding, + now=now, + allow_fast_lane=kind == "fast_lane_dispatch_batch", + ) normalized_payload = _validate_payload(kind, payload) envelope = { "schema": HOST_ENVELOPE_SCHEMA, @@ -121,10 +128,40 @@ def render_envelope( **normalized_binding, "payload": normalized_payload, } + if kind == "fast_lane_dispatch_batch": + _validate_fast_lane_outer_binding(envelope) _validate_size(kind, envelope) return envelope +def build_fast_lane_dispatch_envelope( + *, + batch: Mapping[str, object], + binding: EnvelopeBinding, + correlation_id: str, + now: int, +) -> dict[str, object]: + """Build the only envelope allowed to carry a compiler-authorized batch.""" + + return render_envelope( + kind="fast_lane_dispatch_batch", + binding=binding, + payload={"correlation_id": correlation_id, "batch": batch}, + now=now, + ) + + +def validate_fast_lane_dispatch_envelope( + envelope: Mapping[str, object], *, now: int +) -> dict[str, object]: + """Validate a complete dispatch batch and its first-assignment outer fence.""" + + normalized = validate_envelope(envelope, now=now) + if normalized["kind"] != "fast_lane_dispatch_batch": + _invalid() + return normalized + + def validate_envelope( envelope: Mapping[str, object], *, @@ -152,7 +189,11 @@ def validate_envelope( route_hash=_required_str(envelope, "route_hash"), expires_at=_required_int(envelope, "expires_at"), ) - normalized_binding = _validate_binding(binding, now=now) + normalized_binding = _validate_binding( + binding, + now=now, + allow_fast_lane=kind == "fast_lane_dispatch_batch", + ) normalized_payload = _validate_payload(kind, envelope["payload"]) normalized = { "schema": HOST_ENVELOPE_SCHEMA, @@ -162,6 +203,8 @@ def validate_envelope( **normalized_binding, "payload": normalized_payload, } + if kind == "fast_lane_dispatch_batch": + _validate_fast_lane_outer_binding(normalized) _validate_size(kind, normalized) if expected is not None: _validate_expectation(normalized, expected, now=now) @@ -192,14 +235,16 @@ def envelope_hash( ).hexdigest() -def binding_mapping(binding: EnvelopeBinding, *, now: int) -> dict[str, object]: +def binding_mapping( + binding: EnvelopeBinding, *, now: int, allow_fast_lane: bool = False +) -> dict[str, object]: """Return the normalized binding for private bridge packet validation.""" - return _validate_binding(binding, now=now) + return _validate_binding(binding, now=now, allow_fast_lane=allow_fast_lane) def validate_binding_mapping( - value: Mapping[str, object], *, now: int + value: Mapping[str, object], *, now: int, allow_fast_lane: bool = False ) -> EnvelopeBinding: """Validate exact binding data received in a private bridge packet.""" @@ -220,7 +265,7 @@ def validate_binding_mapping( route_hash=_required_str(value, "route_hash"), expires_at=_required_int(value, "expires_at"), ) - _validate_binding(binding, now=now) + _validate_binding(binding, now=now, allow_fast_lane=allow_fast_lane) return binding @@ -230,11 +275,16 @@ def _roles_for_kind(kind: str) -> tuple[str, str]: return _ROLE_BY_KIND[kind] -def _validate_binding(binding: EnvelopeBinding, *, now: int) -> dict[str, object]: +def _validate_binding( + binding: EnvelopeBinding, + *, + now: int, + allow_fast_lane: bool = False, +) -> dict[str, object]: _validate_now(now) if type(binding) is not EnvelopeBinding: _invalid() - _validate_task_id(binding.task_id) + _validate_task_id(binding.task_id, allow_fast_lane=allow_fast_lane) if ( type(binding.lease_epoch) is not int or binding.lease_epoch < 1 @@ -264,7 +314,11 @@ def _validate_expectation( if type(expectation) is not EnvelopeExpectation: _invalid() expected_roles = _roles_for_kind(expectation.kind) - expected_binding = _validate_binding(expectation.binding, now=now) + expected_binding = _validate_binding( + expectation.binding, + now=now, + allow_fast_lane=expectation.kind == "fast_lane_dispatch_batch", + ) if ( envelope["kind"] != expectation.kind or envelope["sender_role"] != expected_roles[0] @@ -307,6 +361,14 @@ def _validate_payload(kind: str, payload: object) -> dict[str, object]: "artifact_refs": _required_refs(payload, "artifact_refs", _MAX_ARTIFACT_REFS), "digest_refs": _required_refs(payload, "digest_refs", _MAX_DIGEST_REFS), } + if kind == "fast_lane_dispatch_batch": + if set(payload) != {"correlation_id", "batch"}: + _invalid() + correlation_id = _required_correlation_id( + payload, "correlation_id", kind="coordinator_assignment" + ) + batch = _validate_fast_lane_dispatch_batch(payload.get("batch")) + return {"correlation_id": correlation_id, "batch": batch} if kind == "worker_terminal_result": if set(payload) != { "correlation_id", @@ -355,6 +417,158 @@ def _validate_payload(kind: str, payload: object) -> dict[str, object]: _invalid() +def _validate_fast_lane_dispatch_batch(value: object) -> dict[str, object]: + """Reuse the compiler's closed assignment validator, then bind every hash.""" + + if type(value) is not dict or set(value) != { + "schema", + "action", + "selection_authority", + "llm_choice", + "source_plan_hash", + "ledger_epoch", + "active_lease_set_hash", + "dispatch_binding_hashes", + "assignments", + "batch_hash", + }: + _invalid() + if ( + value.get("schema") + != "2718lab-devkit/fastlane-host-dispatch-batch-v1" + or value.get("action") != "dispatch_all" + or value.get("selection_authority") != "host_attested_compiler" + or value.get("llm_choice") is not False + ): + _invalid() + assignments = value.get("assignments") + if type(assignments) is not list or not assignments or len(assignments) > 16: + _invalid() + # Lazy import avoids making the pure envelope module part of adapter startup. + from .fastlane_host_adapter import _dispatch_request + + try: + request = _dispatch_request( + { + "schema": "2718lab-devkit/fastlane-host-dispatch-request-v1", + "action": "dispatch_all", + "assignments": assignments, + } + ) + normalized_assignments = request["assignments"] + assert type(normalized_assignments) is list + # Reconstructing trusted facts is intentionally not possible here. The + # request validator still closes every scalar/path field; the following + # checks close all batch-level hashes and fences independently. + if normalized_assignments != assignments: + _invalid() + task_ids = [assignment["task_id"] for assignment in assignments] + if any( + type(task_id) is not str or _FAST_LANE_TASK_ID.fullmatch(task_id) is None + for task_id in task_ids + ) or len(set(task_ids)) != len(task_ids): + _invalid() + source_plan_hash = value.get("source_plan_hash") + active_lease_set_hash = value.get("active_lease_set_hash") + ledger_epoch = value.get("ledger_epoch") + _validate_digest(source_plan_hash) + _validate_digest(active_lease_set_hash) + if type(ledger_epoch) is not int or not 0 < ledger_epoch <= 2**63 - 1: + _invalid() + if any( + assignment["source_plan_hash"] != source_plan_hash + or assignment["ledger_epoch"] != ledger_epoch + or assignment["active_lease_set_hash"] != active_lease_set_hash + for assignment in assignments + ): + _invalid() + dispatch_hashes = value.get("dispatch_binding_hashes") + expected_hashes = [ + assignment["dispatch_binding_hash"] for assignment in assignments + ] + if dispatch_hashes != expected_hashes: + _invalid() + for assignment in assignments: + unsigned = dict(assignment) + received = unsigned.pop("dispatch_binding_hash") + if received != _digest_mapping(unsigned): + _invalid() + _validate_dispatch_assignment_fences(assignments) + unsigned_batch = dict(value) + received_batch_hash = unsigned_batch.pop("batch_hash") + if received_batch_hash != _digest_mapping(unsigned_batch): + _invalid() + except HostEnvelopeError: + raise + except Exception: + _invalid() + return dict(value) + + +def _validate_dispatch_assignment_fences( + assignments: list[dict[str, object]], +) -> None: + serial_orders: set[int] = set() + for assignment in assignments: + mode = assignment["concurrency_mode"] + order = assignment["dispatch_order"] + if mode == "serial": + assert type(order) is int + if order in serial_orders: + _invalid() + serial_orders.add(order) + for index, left in enumerate(assignments): + for right in assignments[index + 1 :]: + if not _scopes_overlap(left["write_scope"], right["write_scope"]): + continue + if left["concurrency_mode"] == right["concurrency_mode"] == "serial": + continue + if ( + left["concurrency_mode"] + == right["concurrency_mode"] + == "isolated_worktree" + and left["worktree_identity"] != right["worktree_identity"] + ): + continue + _invalid() + + +def _validate_fast_lane_outer_binding(envelope: Mapping[str, object]) -> None: + payload = envelope["payload"] + assert type(payload) is dict + batch = payload["batch"] + assert type(batch) is dict + assignments = batch["assignments"] + assert type(assignments) is list and assignments + first = assignments[0] + assert type(first) is dict + route = first["route"] + assert type(route) is dict + if ( + envelope["task_id"] != first["task_id"] + or envelope["lease_epoch"] != first["lease_epoch"] + or envelope["assignment_token"] != first["assignment_token"] + or envelope["dispatch_context_hash"] != first["dispatch_binding_hash"] + or envelope["route_hash"] != route["routing_result_hash"] + ): + raise HostEnvelopeError("HOST_ENVELOPE_BINDING_INVALID") + + +def _scopes_overlap(left: object, right: object) -> bool: + assert type(left) is list and type(right) is list + return any( + left_item.casefold() == right_item.casefold() + or left_item.casefold().startswith(right_item.casefold() + "/") + or right_item.casefold().startswith(left_item.casefold() + "/") + for left_item in left + for right_item in right + ) + + +def _digest_mapping(value: Mapping[str, object]) -> str: + return "sha256:" + hashlib.sha256(_canonical_bytes(value)).hexdigest() + + def _required_risks(payload: Mapping[str, object]) -> list[dict[str, str]]: risks = payload.get("risk") if type(risks) is not list or len(risks) > _MAX_RISKS: @@ -442,8 +656,10 @@ def _required_int(payload: Mapping[str, object], field: str) -> int: return value -def _validate_task_id(value: object) -> None: - if type(value) is not str or _TASK_ID.fullmatch(value) is None: +def _validate_task_id(value: object, *, allow_fast_lane: bool = False) -> None: + if type(value) is not str or _TASK_ID.fullmatch(value) is None and not ( + allow_fast_lane and _FAST_LANE_TASK_ID.fullmatch(value) is not None + ): _invalid() diff --git a/mcp-tools/devkit_runtime/host_session.py b/mcp-tools/devkit_runtime/host_session.py index f258f68..dc6ec9e 100644 --- a/mcp-tools/devkit_runtime/host_session.py +++ b/mcp-tools/devkit_runtime/host_session.py @@ -11,14 +11,15 @@ import json import math import re -from collections.abc import Callable, Mapping +from collections.abc import Callable, Mapping, Sequence from dataclasses import dataclass, field from enum import StrEnum -from threading import RLock -from typing import Final, TypeAlias +from threading import Event, RLock, Thread, current_thread +from typing import Final, TypeAlias, cast from . import host_envelopes from .host_bridge import ( + FastLaneRefillRegistryRequest, HostBridgeError, InheritedHandleHostBridge, OperationReceipt, @@ -147,6 +148,8 @@ class _CompilerInvocationBinding: verified_lease_scope_bindings: tuple[str, ...] dispatch_facts: tuple[object, ...] = () dispatch_binding_hashes: tuple[str, ...] = () + registry_binding_hash: str | None = None + evidence_expires_at: int | None = None CompilerInvocationResolver: TypeAlias = Callable[ @@ -170,6 +173,45 @@ class _CompilerInvocation: issued_at: float expires_at: float binding_hash: str + registry_binding_hash: str | None = None + + +@dataclass(frozen=True) +class _CompilerRequestContext: + call_intent_hash: str + request_hash: str + reasoning_effort: str + requested_routes: tuple[HostRoute, ...] + assignment_skeletons: tuple[dict[str, object], ...] = field(repr=False) + project_index_attestation_refs: tuple[dict[str, object], ...] = field(repr=False) + routing_registry_binding_hash: str + + +@dataclass(frozen=True) +class _HostCapabilitySnapshotV2: + call_intent_hash: str + preparation_id: str + host_capabilities: dict[str, object] = field(repr=False) + scheduler_facts: dict[str, object] = field(repr=False) + report_hash: str + expires_at: int + + +@dataclass(frozen=True) +class _RoutingAttestationSnapshot: + call_intent_hash: str + preparation_id: str + routing_requests: tuple[dict[str, object], ...] = field(repr=False) + attestations: tuple[dict[str, object], ...] = field(repr=False) + routing_request_set_hash: str + routing_registry_binding_hash: str + expires_at: int + + +@dataclass(frozen=True) +class _PendingFastLaneTerminal: + expected: dict[str, object] = field(repr=False) + lease_expires_at: int = field(repr=False) class _CompilerPreparation: @@ -210,6 +252,30 @@ def __init__( ) self._compiler_evidence_lock = RLock() self._compiler_evidence: dict[_CompilerEvidenceHandle, _CompilerInvocation] = {} + self._compiler_request_contexts: dict[str, _CompilerRequestContext] = {} + self._capability_snapshots_v2: dict[ + tuple[str, str], _HostCapabilitySnapshotV2 + ] = {} + self._preparation_expiry_caps: dict[str, int] = {} + self._routing_attestation_snapshots: dict[ + tuple[str, str], _RoutingAttestationSnapshot + ] = {} + self._pending_fast_lane_terminals: dict[ + tuple[str, str], _PendingFastLaneTerminal + ] = {} + # One reader owns the framed inbound sequence for all active batches; + # callbacks are multiplexed by the authenticated batch hash. + # Batch identities share one session-level reader; this set is only + # bookkeeping for callbacks, never a per-batch receiver registry. + self._fast_lane_active_batches: set[str] = set() + self._fast_lane_terminal_thread: Thread | None = None + self._fast_lane_refill_callbacks: dict[ + str, Callable[[Mapping[str, object]], object] + ] = {} + self._fast_lane_terminal_stop = Event() + self._fast_lane_refill_receipts: dict[str, object] = {} + self._fast_lane_refill_registries: dict[str, object] = {} + self._project_index_query_attestations: dict[str, dict[str, object]] = {} self._burned_preparation_ids: set[str] = set() self._clock = clock self._last_trusted_clock: float | None = None @@ -240,10 +306,16 @@ def from_environment( ) except HostBridgeError: bridge = None - return cls( + session = cls( bridge=bridge, clock=clock, ) + if bridge is not None: + session._compiler_evidence_provider = lambda preparation: preparation + session._compiler_invocation_resolver = ( + session._resolve_bridge_compiler_invocation + ) + return session @property def is_available(self) -> bool: @@ -262,6 +334,122 @@ def last_unavailable(self) -> HostUnavailableFacts | None: return self._last_unavailable + @property + def has_active_fast_lane_terminal_receiver(self) -> bool: + """Whether this session currently owns its multiplexed terminal reader.""" + + with self._compiler_evidence_lock: + thread = self._fast_lane_terminal_thread + return thread is not None and thread.is_alive() + + def resolve_capability_snapshot_v2( + self, + *, + call_intent_hash: str, + preparation_id: str, + expires_at_ceiling: int | None = None, + ) -> _HostCapabilitySnapshotV2 | None: + """Resolve and retain one generation-bound V5 Host/scheduler snapshot.""" + + bridge = self._bridge + if ( + bridge is None + or not self.is_available + or type(call_intent_hash) is not str + or len(call_intent_hash) != 64 + or any(character not in "0123456789abcdef" for character in call_intent_hash) + or type(preparation_id) is not str + or _IDENTIFIER.fullmatch(preparation_id) is None + or ( + expires_at_ceiling is not None + and (type(expires_at_ceiling) is not int or expires_at_ceiling <= 0) + ) + ): + return None + key = (call_intent_hash, preparation_id) + if key in self._capability_snapshots_v2: + return None + try: + now = int(self._read_trusted_clock()) + probe = bridge.send_capability_probe_v2( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + now=now, + ) + report = bridge.receive_capability_report_v2(probe=probe, now=now) + host = cast(dict[str, object], report["host_capabilities"]) + scheduler = cast(dict[str, object], report["scheduler_facts"]) + snapshot_expires_at = probe.expires_at + if expires_at_ceiling is not None: + if now >= expires_at_ceiling: + return None + snapshot_expires_at = min(snapshot_expires_at, expires_at_ceiling) + if snapshot_expires_at <= now: + return None + snapshot = _HostCapabilitySnapshotV2( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + host_capabilities=dict(host), + scheduler_facts=dict(scheduler), + report_hash=_required_hash(report["report_hash"]), + expires_at=snapshot_expires_at, + ) + except Exception: + return None + self._capability_snapshots_v2[key] = snapshot + self._preparation_expiry_caps[preparation_id] = snapshot.expires_at + return snapshot + + def resolve_routing_attestations( + self, + *, + call_intent_hash: str, + preparation_id: str, + routing_requests: Sequence[Mapping[str, object]], + ) -> _RoutingAttestationSnapshot | None: + """Resolve one V5 route set after the same-generation capability report.""" + + bridge = self._bridge + key = (call_intent_hash, preparation_id) + capability_snapshot = self._capability_snapshots_v2.get(key) + if ( + bridge is None + or not self.is_available + or capability_snapshot is None + or key in self._routing_attestation_snapshots + ): + return None + try: + now = int(self._read_trusted_clock()) + if now >= capability_snapshot.expires_at: + return None + request = bridge.send_routing_attestation_request( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + routing_requests=routing_requests, + now=now, + ) + response = bridge.receive_routing_attestation_response( + request=request, now=now + ) + raw_attestations = response["attestations"] + assert type(raw_attestations) is list + snapshot = _RoutingAttestationSnapshot( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + routing_requests=tuple(dict(item) for item in request.routing_requests), + attestations=tuple(dict(item) for item in raw_attestations), + routing_request_set_hash=request.routing_request_set_hash, + routing_registry_binding_hash=_required_hash( + response["routing_registry_binding_hash"] + ), + expires_at=request.expires_at, + ) + except Exception: + return None + self._routing_attestation_snapshots[key] = snapshot + return snapshot + def close(self) -> None: """Close the owned private transport once; no session can be revived.""" @@ -271,8 +459,26 @@ def close(self) -> None: self._closed = True self._frozen = True self._compiler_evidence.clear() + self._compiler_request_contexts.clear() + self._capability_snapshots_v2.clear() + self._preparation_expiry_caps.clear() + self._routing_attestation_snapshots.clear() + self._pending_fast_lane_terminals.clear() + self._fast_lane_refill_callbacks.clear() + self._fast_lane_active_batches.clear() + self._project_index_query_attestations.clear() + self._fast_lane_terminal_stop.set() + terminal_thread = self._fast_lane_terminal_thread if self._bridge is not None: + # Closing the descriptor unblocks the single multiplexed receiver; + # join it here so no detached reader can outlive this session. self._bridge.close() + if ( + terminal_thread is not None + and terminal_thread is not current_thread() + and terminal_thread.is_alive() + ): + terminal_thread.join(timeout=0.25) def prepare_compiler_evidence( self, *, preparation_id: str @@ -300,13 +506,22 @@ def prepare_compiler_evidence( issued_at = self._read_trusted_clock() except (TypeError, ValueError): return _NO_SAFE_WORK - expires_at = issued_at + 120 try: binding = _normalized_compiler_invocation_binding( binding_resolver(preparation_id) ) except Exception: return _NO_SAFE_WORK + expires_at = ( + binding.evidence_expires_at + if binding.evidence_expires_at is not None + else issued_at + 120 + ) + expiry_cap = self._preparation_expiry_caps.pop(preparation_id, None) + if not issued_at < expires_at <= issued_at + 120 or ( + expiry_cap is not None and expires_at > expiry_cap + ): + return _NO_SAFE_WORK preparation = _CompilerPreparation() invocation_binding = { "preparation_id": preparation_id, @@ -317,6 +532,10 @@ def prepare_compiler_evidence( "issued_at": issued_at, "expires_at": expires_at, } + if binding.registry_binding_hash is not None: + invocation_binding["registry_binding_hash"] = ( + binding.registry_binding_hash + ) if binding.dispatch_binding_hashes: invocation_binding["dispatch_binding_hashes"] = ( binding.dispatch_binding_hashes @@ -333,6 +552,7 @@ def prepare_compiler_evidence( issued_at=issued_at, expires_at=expires_at, binding_hash=_hash(invocation_binding), + registry_binding_hash=binding.registry_binding_hash, ) material_state = _compiler_invocation_state(material) try: @@ -357,6 +577,153 @@ def prepare_compiler_evidence( self._compiler_evidence[evidence] = material return evidence + def bind_compiler_request( + self, + *, + preparation_id: str, + call_intent_hash: str, + request_hash: str, + reasoning_effort: str, + requested_routes: tuple[HostRoute, ...], + assignment_skeletons: tuple[dict[str, object], ...], + project_index_attestation_refs: tuple[dict[str, object], ...], + routing_registry_binding_hash: str, + ) -> bool: + """Bind public request identity before a private registry round trip.""" + + with self._compiler_evidence_lock: + if ( + self._closed + or self._frozen + or self._compiler_invocation_resolver + != self._resolve_bridge_compiler_invocation + or _IDENTIFIER.fullmatch(preparation_id) is None + or type(call_intent_hash) is not str + or len(call_intent_hash) != 64 + or any(character not in "0123456789abcdef" for character in call_intent_hash) + or not _is_hash(request_hash) + or not _is_hash(routing_registry_binding_hash) + or reasoning_effort not in {"low", "medium", "high", "xhigh", "max"} + ): + return False + try: + normalized_routes = _normalized_routes(requested_routes) + trusted_now = int(self._read_trusted_clock()) + except (TypeError, ValueError): + return False + routing_snapshot = self._routing_attestation_snapshots.get( + (call_intent_hash, preparation_id) + ) + if ( + routing_snapshot is None + or routing_snapshot.routing_registry_binding_hash + != routing_registry_binding_hash + or trusted_now >= routing_snapshot.expires_at + ): + return False + if preparation_id in self._compiler_request_contexts: + return False + self._compiler_request_contexts[preparation_id] = _CompilerRequestContext( + call_intent_hash=call_intent_hash, + request_hash=request_hash, + reasoning_effort=reasoning_effort, + requested_routes=normalized_routes, + assignment_skeletons=assignment_skeletons, + project_index_attestation_refs=project_index_attestation_refs, + routing_registry_binding_hash=routing_registry_binding_hash, + ) + return True + + def _resolve_bridge_compiler_invocation( + self, preparation_id: str + ) -> _CompilerInvocationBinding | None: + context = self._compiler_request_contexts.pop(preparation_id, None) + bridge = self._bridge + if context is None or bridge is None or not self.is_available: + return None + now = int(self._read_trusted_clock()) + request = bridge.send_compiler_evidence_request( + preparation_id=preparation_id, + call_intent_hash=context.call_intent_hash, + request_hash=context.request_hash, + reasoning_effort=context.reasoning_effort, + requested_route_pairs=tuple( + {"model": route.model, "effort": route.effort} + for route in context.requested_routes + ), + assignment_skeletons=context.assignment_skeletons, + project_index_attestation_refs=context.project_index_attestation_refs, + routing_registry_binding_hash=context.routing_registry_binding_hash, + now=now, + ) + + response = bridge.receive_compiler_evidence_response(request=request, now=now) + from .fastlane_host_adapter import _dispatch_fact_from_mapping + + facts_value = response["dispatch_facts"] + assert type(facts_value) is list + route_hashes = cast(list[object], response["verified_route_result_hashes"]) + lease_hashes = cast(list[object], response["verified_lease_scope_bindings"]) + dispatch_hashes = cast(list[object], response["dispatch_binding_hashes"]) + return _CompilerInvocationBinding( + request_hash=_required_hash(response["request_hash"]), + reasoning_effort=str(response["reasoning_effort"]), + verified_route_result_hashes=tuple( + _required_hash(value) + for value in route_hashes + ), + verified_lease_scope_bindings=tuple( + _required_hash(value) + for value in lease_hashes + ), + dispatch_facts=tuple( + _dispatch_fact_from_mapping(value) for value in facts_value + ), + dispatch_binding_hashes=tuple( + _required_hash(value) for value in dispatch_hashes + ), + registry_binding_hash=_required_hash(response["registry_binding_hash"]), + evidence_expires_at=_required_positive_int(response["expires_at"]), + ) + + def send_project_index_attestation( + self, attestation: Mapping[str, object] + ) -> dict[str, object] | None: + """Emit a persisted index attestation only when this bridge is live.""" + + bridge = self._bridge + if bridge is None or not self.is_available: + return None + now = int(self._read_trusted_clock()) + sent = bridge.send_project_index_attestation(attestation=attestation, now=now) + if sent.get("operation") == "query": + correlation_id = sent.get("correlation_id") + if type(correlation_id) is not str: + self._freeze() + return None + self._project_index_query_attestations[correlation_id] = dict(sent) + return sent + + def project_index_query_attestation( + self, *, correlation_id: str + ) -> dict[str, object] | None: + """Resolve a Host-preselected, same-generation query attestation once.""" + + try: + now = int(self._read_trusted_clock()) + except (TypeError, ValueError): + return None + value = self._project_index_query_attestations.pop(correlation_id, None) + if ( + value is None + or value.get("operation") != "query" + or value.get("correlation_id") != correlation_id + or type(value.get("expires_at")) is not int + or not now < cast(int, value["expires_at"]) + ): + return None + return dict(value) + def consume_compiler_evidence(self, evidence: object) -> object | str: """Exchange a session-issued handle once, rejecting public substitutes.""" @@ -378,6 +745,334 @@ def consume_compiler_evidence(self, evidence: object) -> object | str: return _NO_SAFE_WORK return material + def compiler_evidence_expires_at(self, evidence: object) -> int | None: + """Return only the expiry of a still-live opaque session handle.""" + + with self._compiler_evidence_lock: + if type(evidence) is not _CompilerEvidenceHandle: + return None + material = self._compiler_evidence.get(evidence) + if material is None or not math.isfinite(material.expires_at): + return None + return int(material.expires_at) + + def send_fast_lane_dispatch_batch( + self, + *, + batch: Mapping[str, object], + binding: host_envelopes.EnvelopeBinding, + correlation_id: str, + now: int, + call_intent_hash: str | None = None, + preparation_id: str | None = None, + refill_callback: Callable[[Mapping[str, object]], object] | None = None, + ) -> OperationReceipt | str: + """Forward a compiled batch only on this session's authenticated bridge.""" + + bridge = self._bridge + if not self.is_available or bridge is None: + return _NO_SAFE_WORK + try: + # Validate and publish the whole batch under the session lock. A + # second dispatch may arrive while the receiver is blocked, but it + # can never race the pending-map update or the bridge sequence. + with self._compiler_evidence_lock: + if self._closed or self._frozen: + return _NO_SAFE_WORK + if ( + type(call_intent_hash) is not str + or len(call_intent_hash) != 64 + or any(character not in "0123456789abcdef" for character in call_intent_hash) + or type(preparation_id) is not str + or _IDENTIFIER.fullmatch(preparation_id) is None + ): + raise ValueError("missing Fast Lane terminal binding") + assignments = batch.get("assignments") + batch_hash = batch.get("batch_hash") + if ( + type(assignments) is not list + or not assignments + or not _is_hash(batch_hash) + ): + raise ValueError("invalid Fast Lane terminal binding") + pending: list[tuple[tuple[str, str], _PendingFastLaneTerminal]] = [] + seen_tasks: set[str] = set() + for assignment in assignments: + if type(assignment) is not dict: + raise ValueError("invalid Fast Lane terminal binding") + route = assignment.get("route") + if type(route) is not dict: + raise ValueError("invalid Fast Lane terminal binding") + expected = { + "call_intent_hash": call_intent_hash, + "preparation_id": preparation_id, + "batch_hash": batch_hash, + "task_id": assignment.get("task_id"), + "lease_id": assignment.get("lease_id"), + "lease_epoch": assignment.get("lease_epoch"), + "task_version": assignment.get("task_version"), + "assignment_token": assignment.get("assignment_token"), + "dispatch_binding_hash": assignment.get("dispatch_binding_hash"), + "routing_result_hash": route.get("routing_result_hash"), + "worktree_identity": assignment.get("worktree_identity"), + "worktree_base": assignment.get("worktree_base"), + "integration_head": assignment.get("integration_head"), + "predecessor_hash": assignment.get("predecessor_hash"), + } + task_id = assignment.get("task_id") + if type(task_id) is not str or task_id in seen_tasks: + raise ValueError("invalid Fast Lane terminal binding") + seen_tasks.add(task_id) + key = (cast(str, batch_hash), task_id) + if key in self._pending_fast_lane_terminals: + raise ValueError("duplicate Fast Lane terminal binding") + pending.append( + ( + key, + _PendingFastLaneTerminal( + expected=expected, + # The envelope binding is host-issued and is + # the only expiry available on the dispatch + # wire. Terminal receipts cannot extend it. + lease_expires_at=binding.expires_at, + ), + ) + ) + receipt = bridge.send_fast_lane_dispatch_batch( + batch=batch, + binding=binding, + correlation_id=correlation_id, + now=now, + ) + self._pending_fast_lane_terminals.update(pending) + if refill_callback is not None and not self.start_fast_lane_terminal_receiver( + batch_hash=cast(str, batch_hash), refill_callback=refill_callback + ): + raise ValueError("Fast Lane terminal receiver was not started") + return receipt + except Exception: + self._freeze() + return _NO_SAFE_WORK + + def send_fast_lane_refill_registry( + self, + *, + call_intent_hash: str, + preparation_id: str, + source_plan_hash: str, + index_context_hash: str, + routing_registry_binding_hash: str, + source_plan_task_ids: Sequence[str], + initial_skeletons: Sequence[Mapping[str, object]], + remaining_skeletons: Sequence[Mapping[str, object]], + index_attestation_refs: Sequence[Mapping[str, object]], + skeleton_package_hash: str, + now: int, + ) -> FastLaneRefillRegistryRequest | str: + """Publish one host-authenticated queue for successor Fast Lane waves.""" + + bridge = self._bridge + if bridge is None or not self.is_available: + return _NO_SAFE_WORK + try: + with self._compiler_evidence_lock: + if self._closed or self._frozen: + return _NO_SAFE_WORK + routing_snapshot = self._routing_attestation_snapshots.get( + (call_intent_hash, preparation_id) + ) + if ( + routing_snapshot is None + or routing_snapshot.routing_registry_binding_hash + != routing_registry_binding_hash + or type(now) is not int + or now < 0 + or now >= routing_snapshot.expires_at + ): + return _NO_SAFE_WORK + request = bridge.send_fast_lane_refill_registry_request( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + source_plan_hash=source_plan_hash, + index_context_hash=index_context_hash, + routing_registry_binding_hash=routing_registry_binding_hash, + source_plan_task_ids=source_plan_task_ids, + initial_skeletons=initial_skeletons, + remaining_skeletons=remaining_skeletons, + index_attestation_refs=index_attestation_refs, + skeleton_package_hash=skeleton_package_hash, + now=now, + ) + self._fast_lane_refill_registries[request.queue_registry_hash] = request + return request + except Exception: + self._freeze() + return _NO_SAFE_WORK + + def receive_fast_lane_worker_terminal( + self, + *, + correlation_id: str, + batch_hash: str, + task_id: str, + accepted_event_seq: int, + refill_trigger_hash: str, + ) -> dict[str, object] | str: + """Consume and acknowledge one stored batch assignment, retaining peers.""" + + bridge = self._bridge + key = (batch_hash, task_id) + try: + with self._compiler_evidence_lock: + pending = self._pending_fast_lane_terminals.get(key) + if ( + bridge is None + or not self.is_available + or pending is None + or ( + self._fast_lane_terminal_thread is not None + and self._fast_lane_terminal_thread.is_alive() + ) + ): + return _NO_SAFE_WORK + now = int(self._read_trusted_clock()) + terminal = bridge.receive_fast_lane_worker_terminal_result( + correlation_id=correlation_id, + expected=pending.expected, + expires_at=pending.lease_expires_at, + now=now, + ) + ack = bridge.send_fast_lane_worker_terminal_ack( + terminal_result=terminal, + correlation_id=correlation_id, + accepted_event_seq=accepted_event_seq, + refill_trigger_hash=refill_trigger_hash, + ) + except Exception: + self._freeze() + return _NO_SAFE_WORK + with self._compiler_evidence_lock: + self._pending_fast_lane_terminals.pop(key, None) + return ack + + def start_fast_lane_terminal_receiver( + self, + *, + batch_hash: str, + refill_callback: Callable[[Mapping[str, object]], object], + ) -> bool: + """Register a batch on the session's shared terminal receiver. + + There is exactly one inbound framed reader per session. Additional + batches attach their callback to that reader instead of competing for + the transport sequence. + """ + + if not _is_hash(batch_hash) or not callable(refill_callback): + return False + with self._compiler_evidence_lock: + if ( + not self.is_available + or not any(key[0] == batch_hash for key in self._pending_fast_lane_terminals) + or batch_hash in self._fast_lane_refill_callbacks + ): + return False + self._fast_lane_refill_callbacks[batch_hash] = refill_callback + thread = self._fast_lane_terminal_thread + if thread is not None and thread.is_alive(): + self._fast_lane_active_batches.add(batch_hash) + return True + self._fast_lane_active_batches.clear() + thread = Thread( + target=self._run_fast_lane_terminal_receiver, + name="devkit-fastlane-terminal-multiplex", + daemon=False, + ) + self._fast_lane_terminal_thread = thread + self._fast_lane_active_batches.add(batch_hash) + try: + thread.start() + except Exception: + self._fast_lane_terminal_thread = None + self._fast_lane_active_batches.discard(batch_hash) + self._fast_lane_refill_callbacks.pop(batch_hash, None) + raise + return True + + def _run_fast_lane_terminal_receiver(self) -> None: + bridge = self._bridge + if bridge is None: + self._freeze() + return + try: + while not self._fast_lane_terminal_stop.is_set(): + with self._compiler_evidence_lock: + pending = { + key: dict(value.expected) + for key, value in self._pending_fast_lane_terminals.items() + } + expires_at_by_assignment = { + key: value.lease_expires_at + for key, value in self._pending_fast_lane_terminals.items() + } + if not pending: + return + now = int(self._read_trusted_clock()) + correlation_id, terminal = ( + bridge.receive_next_fast_lane_worker_terminal_result( + expected_by_assignment=pending, + expires_at_by_assignment=expires_at_by_assignment, + now=now, + ) + ) + batch_hash = cast(str, terminal["batch_hash"]) + task_id = cast(str, terminal["task_id"]) + event_seq = cast(int, terminal["event_seq"]) + receipt_hash = cast(str, terminal["terminal_receipt_hash"]) + remaining = sorted( + key[1] + for key in pending + if key[0] == batch_hash and key != (batch_hash, task_id) + ) + descriptor: dict[str, object] = { + "schema": "team-efficiency/fast-lane-refill-trigger-v1", + "trigger": "slot_terminal_event", + "dispatch_at": "next_host_dispatch_boundary", + "polling": False, + "batch_hash": batch_hash, + "task_id": task_id, + "terminal_receipt_hash": receipt_hash, + "accepted_event_seq": event_seq, + "remaining_task_ids": remaining, + } + refill_trigger_hash = _hash(descriptor) + bridge.send_fast_lane_worker_terminal_ack( + terminal_result=terminal, + correlation_id=correlation_id, + accepted_event_seq=event_seq, + refill_trigger_hash=refill_trigger_hash, + ) + with self._compiler_evidence_lock: + self._pending_fast_lane_terminals.pop((batch_hash, task_id), None) + refill_callback = self._fast_lane_refill_callbacks.get(batch_hash) + if refill_callback is not None: + result = refill_callback( + {**descriptor, "refill_trigger_hash": refill_trigger_hash} + ) + with self._compiler_evidence_lock: + self._fast_lane_refill_receipts[receipt_hash] = result + except Exception: + if not self._closed: + self._freeze() + finally: + with self._compiler_evidence_lock: + current = self._fast_lane_terminal_thread + if current is current_thread(): + self._fast_lane_terminal_thread = None + self._fast_lane_active_batches.clear() + if not self._pending_fast_lane_terminals: + self._fast_lane_refill_callbacks.clear() + def resolve_scheduler_topology( self, topology: object ) -> HostResolvedSchedulerTopology | str: @@ -969,6 +1664,7 @@ def _freeze(self) -> None: with self._compiler_evidence_lock: self._frozen = True self._compiler_evidence.clear() + self._compiler_request_contexts.clear() if self._bridge is not None: self._bridge.close() @@ -1054,6 +1750,14 @@ def _normalized_compiler_invocation_binding( or len(value.dispatch_facts) > 16 or not _optional_ordered_hash_tuple(value.dispatch_binding_hashes) or len(value.dispatch_facts) != len(value.dispatch_binding_hashes) + or ( + value.registry_binding_hash is not None + and not _is_hash(value.registry_binding_hash) + ) + or ( + value.evidence_expires_at is not None + and type(value.evidence_expires_at) is not int + ) ): raise ValueError("compiler invocation binding is invalid") return _CompilerInvocationBinding( @@ -1063,6 +1767,8 @@ def _normalized_compiler_invocation_binding( verified_lease_scope_bindings=value.verified_lease_scope_bindings, dispatch_facts=value.dispatch_facts, dispatch_binding_hashes=value.dispatch_binding_hashes, + registry_binding_hash=value.registry_binding_hash, + evidence_expires_at=value.evidence_expires_at, ) @@ -1081,6 +1787,7 @@ def _compiler_invocation_state(value: _CompilerInvocation) -> tuple[object, ...] value.issued_at, value.expires_at, value.binding_hash, + value.registry_binding_hash, ) diff --git a/mcp-tools/devkit_runtime/project_index_attestation_protocol.py b/mcp-tools/devkit_runtime/project_index_attestation_protocol.py new file mode 100644 index 0000000..1282c84 --- /dev/null +++ b/mcp-tools/devkit_runtime/project_index_attestation_protocol.py @@ -0,0 +1,223 @@ +"""Exact host-private Project Index attestation protocol.""" + +from __future__ import annotations + +import hashlib +import hmac +import json +import math +import re +from collections.abc import Mapping +from typing import Final + +ATTESTATION_SCHEMA: Final = "2718lab-devkit/project-index-attestation-v1" +MAX_ATTESTATION_BYTES: Final = 8 * 1024 +ATTESTATION_TTL_SECONDS: Final = 120 + +_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z") +_MAX_JSON_DEPTH: Final = 12 +_MAX_JSON_NODES: Final = 4_096 +_INVALID: Final = "HOST_BRIDGE_PROJECT_INDEX_ATTESTATION_INVALID" +_FRAME_INVALID: Final = "HOST_BRIDGE_FRAME_INVALID" + +_COMMON_FIELDS: Final = frozenset( + { + "schema", + "operation", + "correlation_id", + "workspace_id", + "workspace_binding_hash", + "root_identity_hash", + "expires_at", + "attestation_hash", + } +) +_SYNC_FIELDS: Final = _COMMON_FIELDS | { + "snapshot_id", + "snapshot_attestation_hash", + "head_hash", + "manifest_hash", + "parser_set_hash", +} +_QUERY_FIELDS: Final = _SYNC_FIELDS | {"query_receipt_hash", "index_context_hash"} +_FIELDS_BY_OPERATION: Final = { + "register": ( + "workspace_id", + "workspace_binding_hash", + "root_identity_hash", + ), + "sync": ( + "workspace_id", + "workspace_binding_hash", + "root_identity_hash", + "snapshot_id", + "snapshot_attestation_hash", + "head_hash", + "manifest_hash", + "parser_set_hash", + ), + "query": ( + "workspace_id", + "workspace_binding_hash", + "root_identity_hash", + "snapshot_id", + "snapshot_attestation_hash", + "head_hash", + "manifest_hash", + "parser_set_hash", + "query_receipt_hash", + "index_context_hash", + ), +} +_EXPECTED_FIELDS_BY_OPERATION: Final = { + "register": _COMMON_FIELDS, + "sync": _SYNC_FIELDS, + "query": _QUERY_FIELDS, +} + + +class ProjectIndexAttestationProtocolError(ValueError): + """Stable protocol failure translated by the host bridge boundary.""" + + def __init__(self, code: str) -> None: + super().__init__(code) + self.code = code + + +def normalize_attestation(value: object, *, now: int) -> dict[str, object]: + """Validate one exact register, sync, or query attestation packet.""" + + if type(value) is not dict or type(now) is not int or now < 0: + _raise_invalid() + operation = value.get("operation") + if type(operation) is not str: + _raise_invalid() + expected = _EXPECTED_FIELDS_BY_OPERATION.get(operation) + if ( + expected is None + or set(value) != expected + or value.get("schema") != ATTESTATION_SCHEMA + or type(value.get("expires_at")) is not int + or not now < value["expires_at"] <= now + ATTESTATION_TTL_SECONDS + ): + _raise_invalid() + correlation_id = value.get("correlation_id") + if not is_index_correlation(correlation_id): + _raise_invalid() + digest_fields = expected - { + "schema", + "operation", + "correlation_id", + "expires_at", + } + if any( + type(value.get(field_name)) is not str + or _DIGEST.fullmatch(value[field_name]) is None + for field_name in digest_fields + ): + _raise_invalid() + unsigned = dict(value) + attestation_hash = unsigned.pop("attestation_hash") + if not hmac.compare_digest(attestation_hash, _private_payload_hash(unsigned)): + _raise_invalid() + _validate_private_packet_size(value, MAX_ATTESTATION_BYTES) + return dict(value) + + +def build_attestation( + *, + operation: str, + correlation_id: str, + material: Mapping[str, object], + now: int, +) -> dict[str, object]: + """Build a closed sideband packet from already persisted index material.""" + + fields = _FIELDS_BY_OPERATION.get(operation) + if ( + fields is None + or not is_index_correlation(correlation_id) + or type(material) is not dict + or type(now) is not int + or now < 0 + ): + _raise_invalid() + facts = {field_name: material.get(field_name) for field_name in fields} + unsigned: dict[str, object] = { + "schema": ATTESTATION_SCHEMA, + "operation": operation, + "correlation_id": correlation_id, + **facts, + "expires_at": now + ATTESTATION_TTL_SECONDS, + } + unsigned["attestation_hash"] = _private_payload_hash(unsigned) + return normalize_attestation(unsigned, now=now) + + +def is_index_correlation(value: object) -> bool: + """Return whether a value is the exact opaque Project Index correlation.""" + + return ( + type(value) is str + and value.startswith("index-") + and len(value) == 70 + and all(character in "0123456789abcdef" for character in value[6:]) + ) + + +def _raise_invalid() -> None: + raise ProjectIndexAttestationProtocolError(_INVALID) + + +def _private_payload_hash(payload: object) -> str: + return "sha256:" + hashlib.sha256(_canonical_bytes(payload)).hexdigest() + + +def _validate_private_packet_size(payload: Mapping[str, object], maximum: int) -> None: + if len(_canonical_bytes(payload)) > maximum: + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) + + +def _canonical_bytes(value: object) -> bytes: + try: + _validate_json_value(value) + return json.dumps( + value, + ensure_ascii=False, + allow_nan=False, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + except ProjectIndexAttestationProtocolError: + raise + except (TypeError, ValueError, UnicodeError, RecursionError) as error: + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) from error + + +def _validate_json_value(value: object) -> None: + pending: list[tuple[object, int]] = [(value, 0)] + nodes = 0 + while pending: + item, depth = pending.pop() + nodes += 1 + if depth > _MAX_JSON_DEPTH or nodes > _MAX_JSON_NODES: + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) + if item is None or type(item) in {bool, int, str}: + continue + if type(item) is float: + if math.isfinite(item): + continue + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) + if type(item) is list: + if len(item) > _MAX_JSON_NODES - nodes: + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) + pending.extend((child, depth + 1) for child in item) + continue + if type(item) is dict: + if len(item) > _MAX_JSON_NODES - nodes: + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) + if any(type(key) is not str for key in item): + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) + pending.extend((child, depth + 1) for child in item.values()) + continue + raise ProjectIndexAttestationProtocolError(_FRAME_INVALID) diff --git a/mcp-tools/project_index/service.py b/mcp-tools/project_index/service.py index ec7927d..6e7bcde 100644 --- a/mcp-tools/project_index/service.py +++ b/mcp-tools/project_index/service.py @@ -14,7 +14,7 @@ from collections.abc import Mapping, Sequence from dataclasses import asdict, dataclass from pathlib import Path, PurePosixPath, PureWindowsPath -from typing import TYPE_CHECKING, BinaryIO, cast +from typing import TYPE_CHECKING, Any, BinaryIO, cast if TYPE_CHECKING: from devkit_runtime.workspace_authority import WorkspaceRootAuthority @@ -678,6 +678,128 @@ def query_receipt(self, trace_id: str) -> QueryReceipt: """Compatibility alias for fetching a successful query receipt.""" return self.get_query_receipt(trace_id) + def host_attestation_material( + self, + workspace_id: str, + *, + snapshot_id: str | None = None, + trace_id: str | None = None, + ) -> dict[str, str]: + """Project current persisted facts into path-free Host-side digests.""" + + registered_id, root = self._workspace_for_reference(workspace_id) + root_identity_hash = _opaque_hash( + {"root_identity": workspace_identity(root)} + ) + workspace_binding_hash = _opaque_hash( + { + "workspace_id": registered_id, + "root_identity_hash": root_identity_hash, + } + ) + material = { + "workspace_id": registered_id, + "root_identity_hash": root_identity_hash, + "workspace_binding_hash": workspace_binding_hash, + } + if snapshot_id is None: + if trace_id is not None: + raise IndexError("INVALID_QUERY", "query attestation needs a snapshot") + return material + snapshot = self.assert_current(registered_id, snapshot_id) + if ( + not snapshot.head + or not snapshot.manifest_hash + or not snapshot.parser_set_hash + ): + raise IndexError("INDEX_STALE", "snapshot has no provable head") + head_hash = _opaque_hash({"head": snapshot.head}) + snapshot_attestation_hash = _opaque_hash( + { + **material, + "snapshot_id": snapshot.snapshot_id, + "head_hash": head_hash, + "manifest_hash": snapshot.manifest_hash, + "parser_set_hash": snapshot.parser_set_hash, + } + ) + material.update( + { + "snapshot_id": snapshot.snapshot_id, + "snapshot_attestation_hash": snapshot_attestation_hash, + "head_hash": head_hash, + "manifest_hash": snapshot.manifest_hash, + "parser_set_hash": snapshot.parser_set_hash, + } + ) + if trace_id is None: + return material + receipt = self.get_query_receipt(trace_id) + if receipt.snapshot_id != snapshot.snapshot_id: + raise IndexError("INDEX_STALE", "query receipt snapshot changed") + query_projection = self._public_query_projection(registered_id, receipt) + index_context_hash = _opaque_hash(query_projection) + query_receipt_hash = _opaque_hash( + { + "schema": "2718lab-devkit/project-index-query-receipt-binding-v1", + "receipt": asdict(receipt), + "index_context_hash": index_context_hash, + } + ) + material.update( + { + "query_receipt_hash": query_receipt_hash, + "index_context_hash": index_context_hash, + } + ) + return material + + def _public_query_projection( + self, workspace_id: str, receipt: QueryReceipt + ) -> dict[str, object]: + """Rebuild the exact bounded public query facts hashed for the Host.""" + + nodes_by_id = { + node.node_id: node for node in self._store.nodes(receipt.snapshot_id) + } + edges_by_id = { + edge.edge_id: edge for edge in self._store.edges(receipt.snapshot_id) + } + try: + nodes = [ + _public_query_node(nodes_by_id[node_id]) + for node_id in receipt.returned_node_ids + ] + edges = [ + _public_query_edge(edges_by_id[edge_id]) + for edge_id in receipt.returned_edge_ids + ] + except KeyError as exc: + raise IndexError( + "INDEX_CORRUPT", "project index query receipt is corrupt" + ) from exc + return { + "workspace_id": workspace_id, + "snapshot_id": receipt.snapshot_id, + "trace_id": receipt.trace_id, + "nodes": nodes, + "edges": edges, + "source_windows": [ + { + "path": path, + "start_line": start_line, + "end_line": end_line, + "content_hash": content_hash, + } + for path, start_line, end_line, content_hash in receipt.returned_source_windows + ], + "gaps": [ + {"path": gap.path, "code": gap.code, "message": gap.message} + for gap in receipt.gaps + ], + "truncated": receipt.truncated, + } + def diff( self, workspace_id: str, from_snapshot_id: str, to_snapshot_id: str ) -> SnapshotDiff: @@ -1646,11 +1768,65 @@ def _bounded_items( def _encoded_size(value: object) -> int: return len( json.dumps( - asdict(value), ensure_ascii=True, separators=(",", ":"), sort_keys=True + asdict(cast(Any, value)), + ensure_ascii=True, + separators=(",", ":"), + sort_keys=True, ).encode("utf-8") ) +def _opaque_hash(value: object) -> str: + return "sha256:" + hashlib.sha256( + json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + allow_nan=False, + ).encode("utf-8") + ).hexdigest() + + +def _public_query_node(node: IndexNode) -> dict[str, object]: + """Mirror the stable public Project Index node projection.""" + + return { + "node_id": node.node_id, + "kind": node.kind, + "path": node.path, + "name": node.name, + "qualified_name": node.qualified_name, + "start_line": node.start_line, + "end_line": node.end_line, + "content_hash": node.content_hash, + "attributes": [ + {"name": name, "value": value} for name, value in node.attributes + ], + "extractor_id": node.extractor_id, + "extractor_version": node.extractor_version, + "provenance": node.provenance, + } + + +def _public_query_edge(edge: IndexEdge) -> dict[str, object]: + """Mirror the stable public Project Index edge projection.""" + + return { + "edge_id": edge.edge_id, + "source_id": edge.source_id, + "target_id": edge.target_id, + "relation": edge.relation, + "path": edge.path, + "start_line": edge.start_line, + "end_line": edge.end_line, + "content_hash": edge.content_hash, + "extractor_id": edge.extractor_id, + "extractor_version": edge.extractor_version, + "provenance": edge.provenance, + } + + def _node_order(node: IndexNode) -> tuple[object, ...]: return ( node.path, diff --git a/mcp-tools/server.py b/mcp-tools/server.py index 68e945c..5e14003 100644 --- a/mcp-tools/server.py +++ b/mcp-tools/server.py @@ -3,6 +3,11 @@ from __future__ import annotations import atexit +import hashlib +import json +import os +import secrets +import time from collections.abc import Callable, Mapping from pathlib import Path from typing import TYPE_CHECKING, Literal, Protocol, TypeVar, cast @@ -23,6 +28,7 @@ PLUGIN_ROOT = Path(__file__).resolve().parent.parent mcp = FastMCP(name="2718lab-devkit") +_FASTLANE_HOST_SESSION: object | None = None class _StrictModel(BaseModel): @@ -257,6 +263,10 @@ def _shutdown_runtime() -> None: root = _RUNTIME_ROOT if root is not None: root.shutdown() + session = _FASTLANE_HOST_SESSION + close = getattr(session, "close", None) + if callable(close): + close() atexit.register(_shutdown_runtime) @@ -268,6 +278,145 @@ def _failure(code: str) -> dict[str, object]: return envelope_failure(code) +def _host_session() -> object: + global _FASTLANE_HOST_SESSION + if _FASTLANE_HOST_SESSION is None: + from devkit_runtime.host_session import HostSession + + _FASTLANE_HOST_SESSION = HostSession.from_environment( + environ=None, platform=os.name, clock=time.time + ) + return _FASTLANE_HOST_SESSION + + +def _project_index_attestation( + uow: RuntimeUnitOfWork, + operation: str, + *, + workspace_id: str, + snapshot_id: str | None = None, + trace_id: str | None = None, +) -> dict[str, object] | None: + """Send only persisted, path-free Project Index evidence to a live Host.""" + + from devkit_runtime.host_bridge import build_project_index_attestation + from devkit_runtime.host_session import HostSession + + correlation_id = _current_index_correlation() + if correlation_id is None: + return None + session = _host_session() + if type(session) is not HostSession or not session.is_available: + return None + material = uow.project_checkpoint.project_index.host_attestation_material( + workspace_id, + snapshot_id=snapshot_id, + trace_id=trace_id, + ) + now = int(time.time()) + attestation = build_project_index_attestation( + operation=operation, + correlation_id=correlation_id, + material=material, + now=now, + ) + sent = session.send_project_index_attestation(attestation) + if sent is None: + return None + return { + key: value + for key, value in sent.items() + if key + in { + "correlation_id", + "workspace_id", + "workspace_binding_hash", + "root_identity_hash", + "snapshot_id", + "snapshot_attestation_hash", + "query_receipt_hash", + "index_context_hash", + "attestation_hash", + "expires_at", + } + } + + +def _current_index_correlation() -> str | None: + """Read the Host reservation from MCP `_meta`, never from public arguments.""" + + try: + meta = mcp.get_context().request_context.meta + except (LookupError, ValueError): + return None + if meta is None or type(meta.model_extra) is not dict: + return None + value = meta.model_extra.get("2718lab/host-index-correlation") + if ( + type(value) is not str + or len(value) != 70 + or not value.startswith("index-") + or any(character not in "0123456789abcdef" for character in value[6:]) + ): + return None + return value + + +def _current_fastlane_intent_hash() -> str | None: + """Read the Host call intent from current MCP metadata only.""" + + try: + meta = mcp.get_context().request_context.meta + except (LookupError, ValueError): + return None + if meta is None or type(meta.model_extra) is not dict: + return None + value = meta.model_extra.get("2718lab/host-fastlane-intent-hash") + if ( + type(value) is not str + or len(value) != 64 + or any(character not in "0123456789abcdef" for character in value) + ): + return None + return value + + +def _current_fastlane_index_query_correlation() -> str | None: + """Read the Host-selected Fast Lane query receipt from reserved metadata.""" + + try: + meta = mcp.get_context().request_context.meta + except (LookupError, ValueError): + return None + if meta is None or type(meta.model_extra) is not dict: + return None + value = meta.model_extra.get("2718lab/host-fastlane-index-query-correlation") + if ( + type(value) is not str + or len(value) != 70 + or not value.startswith("index-") + or any(character not in "0123456789abcdef" for character in value[6:]) + ): + return None + return value + + +def _sync_result_snapshot_id(value: object) -> str: + from project_index.models import IndexSyncResult + + if type(value) is not IndexSyncResult: + raise TypeError("invalid sync result") + return value.snapshot.snapshot_id + + +def _query_result_trace_id(value: object) -> str: + from project_index.models import QueryResult + + if type(value) is not QueryResult: + raise TypeError("invalid query result") + return value.trace_id + + def _runtime_failure( error: RuntimeConfigError | RelayRuntimeError, ) -> dict[str, object]: @@ -295,12 +444,23 @@ def _invoke( read_only: bool, invalid_code: str = "INVALID_REQUEST", operation: Callable[[RuntimeUnitOfWork], object], + private_success: Callable[[RuntimeUnitOfWork, object], dict[str, object] | None] + | None = None, ) -> dict[str, object]: """Run one operation inside a fresh UoW and project its exact result.""" try: with _runtime_root().open_uow(read_only=read_only) as uow: - return uow.tool_results.project(tool_name, operation(uow)) + value = operation(uow) + projected = uow.tool_results.project(tool_name, value) + if private_success is not None: + attestation = private_success(uow, value) + if attestation is not None: + data = projected.get("data") + if type(data) is not dict: + raise TypeError("successful result has no data") + data["index_attestation"] = attestation + return projected except _RequestError as error: return _failure(error.code) except Exception as error: @@ -486,6 +646,9 @@ def project_index_register(workspace_root: str) -> dict[str, object]: operation=lambda uow: ( uow.project_checkpoint.project_index.project_index_register(workspace_root) ), + private_success=lambda uow, value: _project_index_attestation( + uow, "register", workspace_id=str(value) + ), ) @@ -539,7 +702,17 @@ def operation(uow: RuntimeUnitOfWork) -> object: ) return result - return _invoke("project_index_sync", read_only=False, operation=operation) + return _invoke( + "project_index_sync", + read_only=False, + operation=operation, + private_success=lambda uow, value: _project_index_attestation( + uow, + "sync", + workspace_id=workspace_id, + snapshot_id=_sync_result_snapshot_id(value), + ), + ) @mcp.tool(annotations=_tool_annotations("project_index_status")) @@ -641,7 +814,18 @@ def operation(uow: RuntimeUnitOfWork) -> object: ) return result - return _invoke("project_index_query", read_only=False, operation=operation) + return _invoke( + "project_index_query", + read_only=False, + operation=operation, + private_success=lambda uow, value: _project_index_attestation( + uow, + "query", + workspace_id=workspace_id, + snapshot_id=snapshot_id, + trace_id=_query_result_trace_id(value), + ), + ) @mcp.tool(annotations=_tool_annotations("worktree_checkpoint_create")) @@ -825,8 +1009,8 @@ def relay_compile(request: RelayCompileRequest) -> dict[str, object]: def fastlane_compile( request: dict[str, object], reasoning_effort: Literal[ - "low", "medium", "high", "xhigh", "max", "ultra" - ] = "ultra", + "low", "medium", "high", "xhigh", "max" + ], enable: bool = False, ) -> dict[str, object]: """Compile inert Fast Lane descriptors without receiving host-private evidence. @@ -838,6 +1022,20 @@ def fastlane_compile( if type(request) is not dict or type(enable) is not bool: return _failure("FASTLANE_REQUEST_INVALID") + session = _host_session() + intent_hash = _current_fastlane_intent_hash() + index_query_correlation = _current_fastlane_index_query_correlation() + from devkit_runtime.host_session import HostSession + + if type(session) is HostSession and session.is_available: + if intent_hash is None or index_query_correlation is None or not enable: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + return _fastlane_authenticated_dispatch( + request, + reasoning_effort, + call_intent_hash=intent_hash, + index_query_correlation=index_query_correlation, + ) from devkit_fastlane import compile_fast_lane from devkit_runtime.tool_result import ResultContractError, envelope_success @@ -854,6 +1052,266 @@ def fastlane_compile( return _failure("FASTLANE_REQUEST_INVALID") +def _fastlane_authenticated_dispatch( + request: dict[str, object], + reasoning_effort: str, + *, + call_intent_hash: str, + index_query_correlation: str, +) -> dict[str, object]: + """Use no request-carried authority; the inherited bridge is the only gate.""" + + from devkit_runtime.fastlane_host_adapter import ( + NO_SAFE_WORK, + dispatch_fast_lane_with_host_facts, + prepare_verified_host_facts, + ) + from devkit_runtime.host_bridge import ( + FastLaneRefillRegistryRequest, + OperationReceipt, + ) + from devkit_runtime.host_session import HostRoute, HostSession + from devkit_runtime.tool_result import ResultContractError, envelope_success + + try: + if reasoning_effort == "ultra": + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + session = _host_session() + if ( + type(session) is not HostSession + or not session.is_available + ): + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + index_attestation = session.project_index_query_attestation( + correlation_id=index_query_correlation + ) + if index_attestation is None: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + project_binding = request.get("project_binding") + work_package = request.get("work_package") + if ( + type(project_binding) is not dict + or type(work_package) is not dict + or project_binding.get("workspace_id") + != index_attestation.get("workspace_id") + or work_package.get("input_snapshot_id") + != index_attestation.get("snapshot_id") + ): + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + preparation_id = f"compiler-{secrets.token_hex(16)}" + capability_snapshot = session.resolve_capability_snapshot_v2( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + expires_at_ceiling=cast(int, index_attestation["expires_at"]), + ) + if capability_snapshot is None: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + from devkit_fastlane.scripts.team_efficiency import ( + compile_authenticated_v5_assignment_skeletons, + prepare_authenticated_v5_routing_from_request, + validate_authenticated_v5_skeleton_package, + ) + + projected = prepare_authenticated_v5_routing_from_request( + request, + index_context_hash=index_attestation["index_context_hash"], + host_capabilities=capability_snapshot.host_capabilities, + scheduler_facts=capability_snapshot.scheduler_facts, + ) + routing_snapshot = session.resolve_routing_attestations( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + routing_requests=projected["all_routing_requests"], + ) + if routing_snapshot is None: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + initial_units = projected["units"] + remaining_units = projected["remaining_units"] + initial_task_ids = { + unit["task"]["task_id"] for unit in initial_units + } + initial_requests = [ + item + for item in routing_snapshot.routing_requests + if item["task"]["task_id"] in initial_task_ids + ] + initial_attestations = [ + item + for item in routing_snapshot.attestations + if item["task_id"] in initial_task_ids + ] + if len(initial_requests) != len(initial_units) or len( + initial_attestations + ) != len(initial_units): + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + compiled = compile_authenticated_v5_assignment_skeletons( + initial_units, + source_plan_hash=projected["source_plan_hash"], + routing_requests=initial_requests, + attestation_items=initial_attestations, + ) + compiled_remaining = ( + compile_authenticated_v5_assignment_skeletons( + remaining_units, + source_plan_hash=projected["source_plan_hash"], + routing_requests=[ + item + for item in routing_snapshot.routing_requests + if item["task"]["task_id"] + not in initial_task_ids + ], + attestation_items=[ + item + for item in routing_snapshot.attestations + if item["task_id"] not in initial_task_ids + ], + ) + if remaining_units + else {"assignment_skeletons": [], "requested_route_pairs": []} + ) + attestation_ref_fields = ( + "correlation_id", + "workspace_id", + "workspace_binding_hash", + "root_identity_hash", + "snapshot_id", + "snapshot_attestation_hash", + "query_receipt_hash", + "index_context_hash", + "attestation_hash", + ) + skeletons = compiled["assignment_skeletons"] + skeleton_package_hash = validate_authenticated_v5_skeleton_package( + projected["all_units"], + skeletons, + compiled_remaining["assignment_skeletons"], + source_plan_hash=projected["source_plan_hash"], + ) + index_refs = [ + { + "task_id": skeleton["task_id"], + **{field: index_attestation[field] for field in attestation_ref_fields}, + } + for skeleton in skeletons + ] + planner_request = { + "schema": "2718lab-devkit/fastlane-host-planner-request-v1", + "action": "plan_dispatch", + "assignment_skeletons": skeletons, + "project_index_attestation_refs": index_refs, + } + requested_route_pairs: set[tuple[str, str]] = set() + for item in routing_snapshot.attestations: + route = cast(dict[str, object], item["route"]) + model = route.get("model") + effort = route.get("effort") + if type(model) is not str or type(effort) is not str: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + requested_route_pairs.add((model, effort)) + requested_routes = tuple( + HostRoute(model=model, effort=effort) + for model, effort in sorted(requested_route_pairs) + ) + prepared = prepare_verified_host_facts( + session, + preparation_id=preparation_id, + call_intent_hash=call_intent_hash, + routing_registry_binding_hash=( + routing_snapshot.routing_registry_binding_hash + ), + request=planner_request, + reasoning_effort=reasoning_effort, + requested_routes=requested_routes, + ) + if prepared == NO_SAFE_WORK: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + now = int(time.time()) + correlation_id = f"operation-{secrets.randbelow(999_999_999_999) + 1}" + + queue_registry = None + if remaining_units: + queue_registry = session.send_fast_lane_refill_registry( + call_intent_hash=call_intent_hash, + preparation_id=preparation_id, + source_plan_hash=projected["source_plan_hash"], + index_context_hash=index_attestation["index_context_hash"], + routing_registry_binding_hash=( + routing_snapshot.routing_registry_binding_hash + ), + source_plan_task_ids=[ + unit["task"]["task_id"] for unit in projected["all_units"] + ], + initial_skeletons=skeletons, + remaining_skeletons=compiled_remaining["assignment_skeletons"], + index_attestation_refs=[ + { + "task_id": skeleton["task_id"], + **{ + field: index_attestation[field] + for field in attestation_ref_fields + }, + } + for skeleton in compiled_remaining["assignment_skeletons"] + ], + skeleton_package_hash=skeleton_package_hash, + now=now, + ) + if type(queue_registry) is not FastLaneRefillRegistryRequest: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + + def refill_callback(trigger: Mapping[str, object]) -> dict[str, object]: + """Record the real next-boundary result for this fully dispatched plan.""" + + request_hash = "sha256:" + hashlib.sha256( + json.dumps( + request, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + allow_nan=False, + ).encode("utf-8") + ).hexdigest() + queued_ids = [ + skeleton["task_id"] + for skeleton in compiled_remaining["assignment_skeletons"] + ] + return { + "schema": "2718lab-devkit/fastlane-refill-receipt-v1", + "state": ( + "QUEUED_WAVE_PENDING" if queued_ids else "NO_QUEUED_WORK" + ), + "request_hash": request_hash, + "refill_trigger_hash": trigger["refill_trigger_hash"], + "queue_registry_hash": ( + queue_registry.queue_registry_hash if queue_registry else None + ), + "queued_task_ids": queued_ids, + } + + receipt = dispatch_fast_lane_with_host_facts( + planner_request, + reasoning_effort=reasoning_effort, + verified_host_facts=prepared, + correlation_id=correlation_id, + now=now, + refill_callback=refill_callback, + ) + if type(receipt) is not OperationReceipt: + return _failure("FASTLANE_HOST_DISPATCH_REJECTED") + return envelope_success( + { + "state": "DISPATCH_COMMITTED", + "task_id": receipt.task_id, + "correlation_id": receipt.correlation_id, + "dispatch_envelope_hash": receipt.envelope_hash, + } + ) + except ResultContractError: + return _failure("INTERNAL_ERROR") + except Exception: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + + def _fastlane_public_value(value: object) -> object: """Remove compiler-only null sentinels before the no-null MCP envelope.""" diff --git a/mcp-tools/tests/compiler_evidence_vector.json b/mcp-tools/tests/compiler_evidence_vector.json new file mode 100644 index 0000000..8419dd2 --- /dev/null +++ b/mcp-tools/tests/compiler_evidence_vector.json @@ -0,0 +1,303 @@ +{ + "frame": { + "action_id": "dispatch-vector-1", + "canonical_payload_hash": "sha256:5ed44f2adf7478b0599a483c9690a995d614e9add464afe52a4878a585d0d587", + "kind": "compiler_evidence_request", + "mac": "4093949ca660d2ce1f545e6ad47c3545c3f8999684353ac0dccaef4a59902fd2", + "sequence": 1, + "session_key_hex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "session_nonce_hex": "101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f" + }, + "project_index_attestations": [ + { + "mac": "53cab9ff30fb8fb145e3fdc09faaa6356fc47b52c4c9658aeb8fd9a7d00c2268", + "payload": { + "attestation_hash": "sha256:bda7ebd6721915bfe5b1f275f71fd0bd7e42c6859016c375151d67148dd9ecab", + "correlation_id": "index-cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "expires_at": 1700000120, + "operation": "register", + "root_identity_hash": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "schema": "2718lab-devkit/project-index-attestation-v1", + "workspace_binding_hash": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "workspace_id": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + }, + "sequence": 1 + }, + { + "mac": "c52d0ae1afe3ae4ff976676b9cf934814513d5b06b278bf53b1bd75812172375", + "payload": { + "attestation_hash": "sha256:dfd0a43ac856d250030d111778992eac8938082a90dcd1960c7975573495fd49", + "correlation_id": "index-cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "expires_at": 1700000120, + "head_hash": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + "manifest_hash": "sha256:3333333333333333333333333333333333333333333333333333333333333333", + "operation": "sync", + "parser_set_hash": "sha256:5555555555555555555555555555555555555555555555555555555555555555", + "root_identity_hash": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "schema": "2718lab-devkit/project-index-attestation-v1", + "snapshot_attestation_hash": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "snapshot_id": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "workspace_binding_hash": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "workspace_id": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + }, + "sequence": 2 + }, + { + "mac": "d03860eb971a56bbf97944ebaa19bfa582d0566f75437faa71b2da503c2e711c", + "payload": { + "attestation_hash": "sha256:be52b379bac39a7522eeb43d1c58852d440859df713b98fe7c44b35fedf61c62", + "correlation_id": "index-cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "expires_at": 1700000120, + "head_hash": "sha256:2222222222222222222222222222222222222222222222222222222222222222", + "index_context_hash": "sha256:4444444444444444444444444444444444444444444444444444444444444444", + "manifest_hash": "sha256:3333333333333333333333333333333333333333333333333333333333333333", + "operation": "query", + "parser_set_hash": "sha256:5555555555555555555555555555555555555555555555555555555555555555", + "query_receipt_hash": "sha256:6666666666666666666666666666666666666666666666666666666666666666", + "root_identity_hash": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "schema": "2718lab-devkit/project-index-attestation-v1", + "snapshot_attestation_hash": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "snapshot_id": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "workspace_binding_hash": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "workspace_id": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + }, + "sequence": 3 + } + ], + "request": { + "assignment_skeletons": [ + { + "concurrency_mode": "parallel", + "dispatch_order": 0, + "index_context_hash": "sha256:335e66a9b631f72cfe45f8e239041a57a6631cd9469ae99b37a367e88b8a420d", + "predecessor_hash": "sha256:ac101b412eec57e00c9997e92da946314f7c04319a9553606deb89a6e4d58214", + "routing_proof": { + "attestation_hash": "sha256:04261cd1bcca69437bc6262f726398a4616155f445a7035d7f28e97d660263ba", + "request": { + "child_route_attestation": { + "attestation_hash": "sha256:04261cd1bcca69437bc6262f726398a4616155f445a7035d7f28e97d660263ba", + "capability_epoch": 1, + "expires_event_seq": 1, + "host_id_hash": "sha256:335e66a9b631f72cfe45f8e239041a57a6631cd9469ae99b37a367e88b8a420d", + "inherit_current_session_model": false, + "issued_event_seq": 1, + "lease_epoch": 0, + "refusal_code": null, + "request_binding_hash": "sha256:9a7ac4a9557fdadb1140c6bd55b2b8ed4a1ca9f57792ebc0b23bc1c56988c0a7", + "route": { + "effort": "max", + "lane": "luna", + "model": "gpt-5.6-luna", + "rank": 40 + }, + "schema": "2718lab-devkit/host-child-route-attestation-v1", + "status": "attested" + }, + "dependency_state": { + "completed_dependency_ids": [], + "dependency_state_hash": "sha256:db5f8b37d7802dd65890de8c13e1dbd49bd82fd1886e0c166b1703347167d77e", + "direct_dependency_ids": [], + "graph_epoch": 1, + "schema": "2718lab-devkit/dependency-state-v1" + }, + "host_capabilities": { + "capability_epoch": 1, + "entitlements": [], + "host_id_hash": "sha256:335e66a9b631f72cfe45f8e239041a57a6631cd9469ae99b37a367e88b8a420d", + "model_slot_limits": { + "luna": 4, + "sol": 0, + "spark": 0, + "terra": 0 + }, + "models": [ + { + "efforts": [ + "max" + ], + "model_id": "gpt-5.6-luna", + "status": "available" + } + ], + "schema": "2718lab-devkit/host-capabilities-v1", + "total_slots": 4 + }, + "legacy": null, + "policy_hash": "sha256:2078016c714f889fdfe9d7156a622a1cb8f157b64cc15ead5e328ae1bc40eea5", + "scheduler_facts": { + "dispatch_cause": "task_ready", + "event_seq": 1, + "evidence_state": "none", + "execution_state": "unknown", + "fence_count_epoch": 0, + "fenced_replacement_count_task": 0, + "lease_epoch": 0, + "lease_state": "unclaimed", + "override_epoch": 0, + "ready_event_seq": 1, + "recovery_epoch": 0, + "recovery_probe_count_epoch": 0, + "route_epoch": 1, + "transport_state": "connected" + }, + "schema": "2718lab-devkit/fastlane-routing-request-v5", + "scope_state": { + "active_writer_task_ids": [], + "conflicting_task_ids": [], + "owned_scope_hash": "sha256:5a94938500ddd7a0c9d76bf448af74a0b4b395571755ca88dbc5bc727c997bce", + "schema": "2718lab-devkit/scope-state-v1", + "scope_epoch": 1 + }, + "task": { + "access": "workspace_write", + "architecture_conflict": false, + "authorization": "not_required", + "authorization_evidence_hash": null, + "blocker_severity": "none", + "critical_path": false, + "criticality": "low", + "cross_module": false, + "database_work": false, + "dependency_depth": 0, + "design_ambiguity": false, + "destructive": false, + "downstream_critical_count": 0, + "external_boundary": false, + "gate_matrix_hash": "sha256:335e66a9b631f72cfe45f8e239041a57a6631cd9469ae99b37a367e88b8a420d", + "migration": false, + "narrow_decoupling_eligible": false, + "overlap_count": 0, + "overlap_risk": "none", + "profile_evidence_hash": "sha256:ac101b412eec57e00c9997e92da946314f7c04319a9553606deb89a6e4d58214", + "read_scope_breadth": "none", + "read_scope_count": 0, + "role": "execution", + "schema": "2718lab-devkit/task-routing-profile-v5", + "security_sensitive": false, + "strike": null, + "task_id": "TASK-V5", + "verification_cost": "none", + "write_scope_breadth": "single_file", + "write_scope_count": 1 + } + }, + "request_binding_hash": "sha256:9a7ac4a9557fdadb1140c6bd55b2b8ed4a1ca9f57792ebc0b23bc1c56988c0a7", + "result": { + "access": "workspace_write", + "capability_resolution": { + "attestation_hash": "sha256:04261cd1bcca69437bc6262f726398a4616155f445a7035d7f28e97d660263ba", + "state": "host_attested" + }, + "dispatch": { + "requires_host_execution": true, + "state": "not_dispatched" + }, + "effective_role": "execution", + "policy_hash": "sha256:2078016c714f889fdfe9d7156a622a1cb8f157b64cc15ead5e328ae1bc40eea5", + "reason_codes": [ + "floor_role" + ], + "render_hash": "sha256:ffcd81c377739833799c8f45185cf99520f90aadbc97c6aa1d4371d6ab3e0c77", + "route": { + "effort": "max", + "inherit_current_session_model": false, + "lane": "luna", + "model": "gpt-5.6-luna", + "rank": 40 + }, + "safety_floor": { + "rank": 20 + }, + "schema": "2718lab-devkit/fastlane-routing-result-v5", + "status": "resolved", + "task_fingerprint": "sha256:be2326dd3da066a5b7992c92d97b3bf9d82db4a0fd9186cbeb1f18b15ae2dd22", + "task_id": "TASK-V5" + }, + "routing_context_hash": "sha256:f3755042b0902f7b6af7989a6f2550acbf8619e30109afc1c8dd8287c4be3dfd", + "routing_result_hash": "sha256:de6e37a57ea259de6108650b888eb3154cea3b1ed6ff395498e78c0efc4bbb7e" + }, + "source_plan_hash": "sha256:b95bed09962fdf8ccb165848bb022734d0966c54dc033def24645b2e7a199ea4", + "task_id": "TASK-V5", + "write_scope": [ + "src/task_v5.py" + ] + } + ], + "call_intent_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "expires_at": 1700000120, + "nonce": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8", + "preparation_id": "dispatch-vector-1", + "project_index_attestation_refs": [ + { + "attestation_hash": "sha256:06b5a11446f7267a05390dd404db32f85267a66771535988d6413a974439ec24", + "correlation_id": "index-cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "index_context_hash": "sha256:335e66a9b631f72cfe45f8e239041a57a6631cd9469ae99b37a367e88b8a420d", + "query_receipt_hash": "sha256:6666666666666666666666666666666666666666666666666666666666666666", + "root_identity_hash": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "snapshot_attestation_hash": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "snapshot_id": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "task_id": "TASK-V5", + "workspace_binding_hash": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "workspace_id": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + } + ], + "reasoning_effort": "max", + "request_hash": "sha256:1de656b46197cd4bc5fcb01ca6eeb1b4e7573a8b78310a7c84d6bef63ec87a8a", + "requested_route_pairs": [ + { + "effort": "max", + "model": "gpt-5.6-luna" + } + ], + "routing_registry_binding_hash": "sha256:1b76b3443a306420a571bd3e6cf548e0ce2e71cb3963ce99eda19bca74b182cd", + "schema": "2718lab-devkit/compiler-evidence-request-v1" + }, + "response": { + "dispatch_binding_hashes": [ + "sha256:06ea274ea0d15ca7156ca81ebe097dff145baf7b4ae00b8832e303dc3a2df26e" + ], + "dispatch_facts": [ + { + "active_lease_set_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "assignment_token": "sha256:3333333333333333333333333333333333333333333333333333333333333333", + "concurrency_mode": "parallel", + "dispatch_binding_hash": "sha256:06ea274ea0d15ca7156ca81ebe097dff145baf7b4ae00b8832e303dc3a2df26e", + "dispatch_order": 0, + "index_context_hash": "sha256:335e66a9b631f72cfe45f8e239041a57a6631cd9469ae99b37a367e88b8a420d", + "integration_head": "sha256:7777777777777777777777777777777777777777777777777777777777777777", + "lease_epoch": 1, + "lease_id": "lease-task-v5", + "ledger_epoch": 11, + "predecessor_hash": "sha256:ac101b412eec57e00c9997e92da946314f7c04319a9553606deb89a6e4d58214", + "route": { + "model": "gpt-5.6-luna", + "reasoning_effort": "max", + "require_explicit_route": true, + "routing_context_hash": "sha256:f3755042b0902f7b6af7989a6f2550acbf8619e30109afc1c8dd8287c4be3dfd", + "routing_result_hash": "sha256:de6e37a57ea259de6108650b888eb3154cea3b1ed6ff395498e78c0efc4bbb7e" + }, + "source_plan_hash": "sha256:b95bed09962fdf8ccb165848bb022734d0966c54dc033def24645b2e7a199ea4", + "task_id": "TASK-V5", + "task_version": 1, + "worktree_base": "sha256:6666666666666666666666666666666666666666666666666666666666666666", + "worktree_identity": "sha256:5555555555555555555555555555555555555555555555555555555555555555", + "write_scope": [ + "src/task_v5.py" + ] + } + ], + "expires_at": 1700000120, + "nonce": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8", + "preparation_id": "dispatch-vector-1", + "reasoning_effort": "max", + "registry_binding_hash": "sha256:4cf01212c6065dcc86d09c019c822af669d79c290e800426f1dcf58f7491b3b9", + "request_hash": "sha256:1de656b46197cd4bc5fcb01ca6eeb1b4e7573a8b78310a7c84d6bef63ec87a8a", + "schema": "2718lab-devkit/compiler-evidence-response-v1", + "verified_lease_scope_bindings": [ + "sha256:2d1c461dbcf7144b28b2ffa4fef63e6cf8c2a2042302a2449a0228df46ef6faf" + ], + "verified_route_result_hashes": [ + "sha256:de6e37a57ea259de6108650b888eb3154cea3b1ed6ff395498e78c0efc4bbb7e" + ] + } +} diff --git a/mcp-tools/tests/test_fastlane_host_adapter.py b/mcp-tools/tests/test_fastlane_host_adapter.py index 3161eaf..0132570 100644 --- a/mcp-tools/tests/test_fastlane_host_adapter.py +++ b/mcp-tools/tests/test_fastlane_host_adapter.py @@ -8,7 +8,8 @@ import os import sys import threading -from collections.abc import Sequence +import time +from collections.abc import Mapping, Sequence from dataclasses import replace from pathlib import Path @@ -42,6 +43,215 @@ def _canonical_hash(value: object) -> str: ) +def _authenticated_v5_fixture() -> dict[str, object]: + from devkit_fastlane.scripts import fastlane_routing, team_efficiency + + hash_a = _canonical_hash({"fixture": "a"}) + hash_b = _canonical_hash({"fixture": "b"}) + source_plan_hash = _canonical_hash({"fixture": "plan"}) + dependency: dict[str, object] = { + "schema": "2718lab-devkit/dependency-state-v1", + "graph_epoch": 1, + "direct_dependency_ids": [], + "completed_dependency_ids": [], + } + dependency["dependency_state_hash"] = _canonical_hash(dependency) + scheduler = { + "event_seq": 1, + "route_epoch": 1, + "override_epoch": 0, + "recovery_epoch": 0, + "ready_event_seq": 1, + "dispatch_cause": "task_ready", + "transport_state": "connected", + "execution_state": "unknown", + "lease_state": "unclaimed", + "evidence_state": "none", + "lease_epoch": 0, + "recovery_probe_count_epoch": 0, + "fence_count_epoch": 0, + "fenced_replacement_count_task": 0, + } + host = { + "schema": "2718lab-devkit/host-capabilities-v1", + "host_id_hash": hash_a, + "capability_epoch": 1, + "total_slots": 4, + "model_slot_limits": {"luna": 4, "terra": 0, "sol": 0, "spark": 0}, + "models": [ + { + "model_id": "gpt-5.6-luna", + "status": "available", + "efforts": ["max"], + } + ], + "entitlements": [], + } + unit = { + "task": { + "schema": "2718lab-devkit/task-routing-profile-v5", + "task_id": "TASK-V5", + "role": "execution", + "access": "workspace_write", + "write_scope_count": 1, + "write_scope_breadth": "single_file", + "read_scope_count": 0, + "read_scope_breadth": "none", + "overlap_risk": "none", + "overlap_count": 0, + "dependency_depth": 0, + "downstream_critical_count": 0, + "critical_path": False, + "criticality": "low", + "cross_module": False, + "database_work": False, + "migration": False, + "security_sensitive": False, + "destructive": False, + "external_boundary": False, + "architecture_conflict": False, + "design_ambiguity": False, + "verification_cost": "none", + "blocker_severity": "none", + "authorization": "not_required", + "authorization_evidence_hash": None, + "narrow_decoupling_eligible": False, + "strike": None, + "gate_matrix_hash": hash_a, + "profile_evidence_hash": hash_b, + }, + "dependency_state": dependency, + "write_scope": ["src/task_v5.py"], + "concurrency_mode": "parallel", + "dispatch_order": 0, + "index_context_hash": hash_a, + "predecessor_hash": hash_b, + } + routing_requests = team_efficiency.prepare_authenticated_v5_routing_requests( + [unit], + source_plan_hash=source_plan_hash, + host_capabilities=host, + scheduler_facts=scheduler, + ) + request_binding_hash = fastlane_routing.v5_request_binding_hash( + routing_requests[0] + ) + attestation: dict[str, object] = { + "schema": "2718lab-devkit/host-child-route-attestation-v1", + "status": "attested", + "request_binding_hash": request_binding_hash, + "host_id_hash": hash_a, + "capability_epoch": 1, + "lease_epoch": 0, + "issued_event_seq": 1, + "expires_event_seq": 1, + "route": { + "lane": "luna", + "model": "gpt-5.6-luna", + "effort": "max", + "rank": 40, + }, + "inherit_current_session_model": False, + "refusal_code": None, + } + attestation["attestation_hash"] = _canonical_hash(attestation) + attestation_items = [ + { + "task_id": "TASK-V5", + "request_binding_hash": request_binding_hash, + "attestation": attestation, + } + ] + compiled = team_efficiency.compile_authenticated_v5_assignment_skeletons( + [unit], + source_plan_hash=source_plan_hash, + routing_requests=routing_requests, + attestation_items=attestation_items, + ) + index_query = { + "schema": "2718lab-devkit/project-index-attestation-v1", + "operation": "query", + "correlation_id": "index-" + "c" * 64, + "workspace_id": _hash("d"), + "workspace_binding_hash": _hash("e"), + "root_identity_hash": _hash("f"), + "expires_at": 1_700_000_120, + "snapshot_id": _hash("0"), + "snapshot_attestation_hash": _hash("1"), + "head_hash": _hash("2"), + "manifest_hash": _hash("3"), + "parser_set_hash": _hash("5"), + "query_receipt_hash": _hash("6"), + "index_context_hash": hash_a, + } + index_query["attestation_hash"] = _canonical_hash(index_query) + index_ref = { + key: value + for key, value in index_query.items() + if key + not in { + "schema", + "operation", + "expires_at", + "head_hash", + "manifest_hash", + "parser_set_hash", + } + } + index_ref["task_id"] = "TASK-V5" + planner_request = { + "schema": "2718lab-devkit/fastlane-host-planner-request-v1", + "action": "plan_dispatch", + "assignment_skeletons": compiled["assignment_skeletons"], + "project_index_attestation_refs": [index_ref], + } + return { + "call_intent_hash": "a" * 64, + "preparation_id": "dispatch-v5-1", + "host": host, + "scheduler": scheduler, + "unit": unit, + "source_plan_hash": source_plan_hash, + "routing_requests": routing_requests, + "attestation_items": attestation_items, + "compiled": compiled, + "index_query": index_query, + "index_ref": index_ref, + "planner_request": planner_request, + } + + +def _v5_dispatch_fact(adapter: object, fixture: dict[str, object]) -> object: + skeleton = fixture["compiled"]["assignment_skeletons"][0] + proof = skeleton["routing_proof"] + result_route = proof["result"]["route"] + return adapter._HostDispatchFact( + task_id=skeleton["task_id"], + route=adapter._HostDispatchRoute( + model=result_route["model"], + reasoning_effort=result_route["effort"], + routing_context_hash=proof["routing_context_hash"], + routing_result_hash=proof["routing_result_hash"], + require_explicit_route=True, + ), + lease_id="lease-task-v5", + lease_epoch=1, + task_version=1, + assignment_token=_hash("3"), + write_scope=tuple(skeleton["write_scope"]), + concurrency_mode=skeleton["concurrency_mode"], + dispatch_order=skeleton["dispatch_order"], + index_context_hash=skeleton["index_context_hash"], + worktree_identity=_hash("5"), + worktree_base=_hash("6"), + integration_head=_hash("7"), + predecessor_hash=skeleton["predecessor_hash"], + source_plan_hash=skeleton["source_plan_hash"], + ledger_epoch=11, + active_lease_set_hash=_hash("b"), + ) + + def _pipe_pair() -> tuple[object, object]: from devkit_runtime.host_bridge import InheritedHandleHostBridge @@ -65,7 +275,7 @@ def _pipe_pair() -> tuple[object, object]: ) -def _dispatch_fact(adapter: object, *, task: str, scope: str) -> object: +def _dispatch_fact(adapter: object, *, task: str, scope: str, order: int = 0) -> object: task_hash_character = "a" if task.endswith("a") else "b" return adapter._HostDispatchFact( task_id=task, @@ -82,7 +292,7 @@ def _dispatch_fact(adapter: object, *, task: str, scope: str) -> object: assignment_token=_hash("3"), write_scope=(scope,), concurrency_mode="parallel", - dispatch_order=0, + dispatch_order=order, index_context_hash=_hash("4"), worktree_identity=_hash("5"), worktree_base=_hash("6"), @@ -102,6 +312,50 @@ def _dispatch_request(adapter: object, facts: tuple[object, ...]) -> dict[str, o } +def _planner_request(adapter: object, facts: tuple[object, ...]) -> dict[str, object]: + skeletons = [] + references = [] + for fact in facts: + mapping = adapter._dispatch_fact_mapping(fact) + skeletons.append( + { + key: mapping[key] + for key in ( + "task_id", + "route", + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "predecessor_hash", + "source_plan_hash", + "ledger_epoch", + "active_lease_set_hash", + ) + } + ) + references.append( + { + "task_id": mapping["task_id"], + "correlation_id": "index-" + "c" * 64, + "workspace_id": _hash("d"), + "workspace_binding_hash": _hash("e"), + "root_identity_hash": _hash("f"), + "snapshot_id": _hash("0"), + "snapshot_attestation_hash": _hash("1"), + "query_receipt_hash": _hash("2"), + "index_context_hash": mapping["index_context_hash"], + "attestation_hash": _hash("3"), + } + ) + return { + "schema": "2718lab-devkit/fastlane-host-planner-request-v1", + "action": "plan_dispatch", + "assignment_skeletons": skeletons, + "project_index_attestation_refs": references, + } + + def _prepared_dispatch( adapter: object, facts: tuple[object, ...], @@ -196,7 +450,7 @@ def test_private_host_facts_form_one_mechanical_dispatch_all_request() -> None: adapter = _adapter() facts = ( _dispatch_fact(adapter, task="task-a", scope="src/a.py"), - _dispatch_fact(adapter, task="task-b", scope="src/b.py"), + _dispatch_fact(adapter, task="task-b", scope="src/b.py", order=1), ) request, prepared, bridges = _prepared_dispatch(adapter, facts) try: @@ -552,6 +806,7 @@ def test_overlapping_parallel_scopes_fail_closed_but_serial_scopes_are_ordered() overlapping = replace( _dispatch_fact(adapter, task="task-b", scope="src/shared/child.py"), concurrency_mode="parallel", + dispatch_order=1, ) request, prepared, bridges = _prepared_dispatch(adapter, (first, overlapping)) try: @@ -564,12 +819,12 @@ def test_overlapping_parallel_scopes_fail_closed_but_serial_scopes_are_ordered() assert rejected == adapter.NO_SAFE_WORK with pytest.raises(ValueError, match="parallel write scopes overlap"): adapter._validate_batch_fences( - (replace(first, concurrency_mode="serial", dispatch_order=1), overlapping) + (replace(first, concurrency_mode="serial", dispatch_order=0), overlapping) ) serial_facts = ( - replace(first, concurrency_mode="serial", dispatch_order=1), - replace(overlapping, concurrency_mode="serial", dispatch_order=2), + replace(first, concurrency_mode="serial", dispatch_order=0), + replace(overlapping, concurrency_mode="serial", dispatch_order=1), ) request, prepared, bridges = _prepared_dispatch(adapter, serial_facts) try: @@ -579,7 +834,7 @@ def test_overlapping_parallel_scopes_fail_closed_but_serial_scopes_are_ordered() finally: for bridge in bridges: bridge.close() - assert [item["dispatch_order"] for item in accepted["assignments"]] == [1, 2] + assert [item["dispatch_order"] for item in accepted["assignments"]] == [0, 1] def test_adapter_exposes_no_forgeable_verified_host_facts_marker() -> None: @@ -636,6 +891,398 @@ def test_adapter_contains_no_host_execution_calls() -> None: ) +def test_environment_session_round_trips_evidence_and_typed_dispatch( + monkeypatch: pytest.MonkeyPatch, +) -> None: + adapter = _adapter() + import devkit_runtime.host_session as host_session + from devkit_runtime.host_bridge import InheritedHandleHostBridge, OperationReceipt + + fixture = _authenticated_v5_fixture() + child, host = _pipe_pair() + fact = _v5_dispatch_fact(adapter, fixture) + request = fixture["planner_request"] + fact_mapping = adapter._dispatch_fact_mapping(fact) + lease_hash = adapter._lease_scope_binding_hash(fact) + received: list[OperationReceipt] = [] + + def host_reply() -> None: + assert host.receive_project_index_attestation(now=1_700_000_000) == fixture[ + "index_query" + ] + probe = host.receive_capability_probe_v2(now=1_700_000_000) + host.send_capability_report_v2( + probe=probe, + host_capabilities=fixture["host"], + scheduler_facts=fixture["scheduler"], + now=1_700_000_000, + ) + routing_request = host.receive_routing_attestation_request( + now=1_700_000_000 + ) + assert list(routing_request.routing_requests) == fixture["routing_requests"] + host.send_routing_attestation_response( + request=routing_request, + attestations=fixture["attestation_items"], + now=1_700_000_000, + ) + evidence_request = host.receive_compiler_evidence_request(now=1_700_000_000) + response = { + "schema": "2718lab-devkit/compiler-evidence-response-v1", + "preparation_id": evidence_request.preparation_id, + "request_hash": evidence_request.request_hash, + "reasoning_effort": evidence_request.reasoning_effort, + "verified_route_result_hashes": [fact.route.routing_result_hash], + "verified_lease_scope_bindings": [lease_hash], + "dispatch_facts": [fact_mapping], + "dispatch_binding_hashes": [fact_mapping["dispatch_binding_hash"]], + "nonce": evidence_request.nonce, + "expires_at": evidence_request.expires_at, + } + response["registry_binding_hash"] = _canonical_hash(response) + host.send_compiler_evidence_response( + request=evidence_request, + response=response, + now=1_700_000_000, + ) + received.append(host.receive_fast_lane_dispatch_batch(now=1_700_000_000)) + + thread = threading.Thread(target=host_reply, daemon=True) + thread.start() + monkeypatch.setattr( + InheritedHandleHostBridge, + "from_environment", + classmethod(lambda cls, environ=None, *, platform=None: child), + ) + session = host_session.HostSession.from_environment( + environ={}, platform="posix", clock=lambda: 1_700_000_000 + ) + assert session.send_project_index_attestation(fixture["index_query"]) == fixture[ + "index_query" + ] + capability = session.resolve_capability_snapshot_v2( + call_intent_hash=fixture["call_intent_hash"], + preparation_id=fixture["preparation_id"], + ) + assert capability is not None + routing = session.resolve_routing_attestations( + call_intent_hash=fixture["call_intent_hash"], + preparation_id=fixture["preparation_id"], + routing_requests=fixture["routing_requests"], + ) + assert routing is not None + prepared = adapter.prepare_verified_host_facts( + session, + preparation_id=fixture["preparation_id"], + call_intent_hash=fixture["call_intent_hash"], + routing_registry_binding_hash=routing.routing_registry_binding_hash, + request=request, + reasoning_effort="max", + ) + assert type(prepared).__name__ == "_PreparedHostFacts" + batch = adapter.compile_fast_lane_with_host_facts( + request, + reasoning_effort="max", + verified_host_facts=prepared, + ) + assert type(batch) is dict + first = batch["assignments"][0] + route = first["route"] + receipt = session.send_fast_lane_dispatch_batch( + batch=batch, + binding=host_session.host_envelopes.EnvelopeBinding( + task_id=first["task_id"], + lease_epoch=first["lease_epoch"], + assignment_token=first["assignment_token"], + dispatch_context_hash=first["dispatch_binding_hash"], + route_hash=route["routing_result_hash"], + expires_at=1_700_000_120, + ), + correlation_id="operation-1", + now=1_700_000_000, + refill_callback=lambda trigger: {"trigger": dict(trigger)}, + call_intent_hash=fixture["call_intent_hash"], + preparation_id=fixture["preparation_id"], + ) + thread.join(timeout=2) + try: + assert type(receipt) is OperationReceipt + assert received == [receipt] + finally: + host.close() + child.close() + + +def test_fast_lane_terminal_ack_removes_only_completed_assignment() -> None: + adapter = _adapter() + from devkit_runtime import host_bridge, host_envelopes + from devkit_runtime.host_session import HostSession + + fixture = _authenticated_v5_fixture() + fact_a = _v5_dispatch_fact(adapter, fixture) + fact_b = replace( + fact_a, + task_id="TASK-V5-B", + route=replace( + fact_a.route, + routing_context_hash=_hash("c"), + routing_result_hash=_hash("d"), + ), + lease_id="lease-task-v5-b", + assignment_token=_hash("e"), + write_scope=("src/task_v5_b.py",), + dispatch_order=1, + ) + mappings = [ + adapter._dispatch_fact_mapping(fact) for fact in (fact_a, fact_b) + ] + batch: dict[str, object] = { + "schema": "2718lab-devkit/fastlane-host-dispatch-batch-v1", + "action": "dispatch_all", + "selection_authority": "host_attested_compiler", + "llm_choice": False, + "source_plan_hash": fact_a.source_plan_hash, + "ledger_epoch": fact_a.ledger_epoch, + "active_lease_set_hash": fact_a.active_lease_set_hash, + "dispatch_binding_hashes": [ + mapping["dispatch_binding_hash"] for mapping in mappings + ], + "assignments": mappings, + } + batch["batch_hash"] = _canonical_hash(batch) + assert host_envelopes._validate_fast_lane_dispatch_batch(batch) == batch + child, host = _pipe_pair() + session = HostSession(bridge=child, clock=lambda: 1_700_000_000) + first_route = mappings[0]["route"] + binding = host_envelopes.EnvelopeBinding( + task_id=fact_a.task_id, + lease_epoch=fact_a.lease_epoch, + assignment_token=fact_a.assignment_token, + dispatch_context_hash=mappings[0]["dispatch_binding_hash"], + route_hash=first_route["routing_result_hash"], + expires_at=1_700_000_120, + ) + received_dispatch: list[host_bridge.OperationReceipt] = [] + refill_receipts: list[dict[str, object]] = [] + + def refill_callback(trigger: Mapping[str, object]) -> dict[str, object]: + receipt = {"state": "NO_QUEUED_WORK", **dict(trigger)} + refill_receipts.append(receipt) + return receipt + dispatch_reader = threading.Thread( + target=lambda: received_dispatch.append( + host.receive_fast_lane_dispatch_batch(now=1_700_000_000) + ), + daemon=True, + ) + dispatch_reader.start() + receipt = session.send_fast_lane_dispatch_batch( + batch=batch, + binding=binding, + correlation_id="operation-2", + now=1_700_000_000, + call_intent_hash=fixture["call_intent_hash"], + preparation_id=fixture["preparation_id"], + refill_callback=refill_callback, + ) + assert type(receipt) is host_bridge.OperationReceipt + dispatch_reader.join(timeout=2) + assert received_dispatch == [receipt] + expected = dict( + session._pending_fast_lane_terminals[(batch["batch_hash"], fact_a.task_id)].expected + ) + terminal: dict[str, object] = { + "schema": "2718lab-devkit/fastlane-worker-terminal-result-v1", + **expected, + "terminal": "succeeded", + "result": ["result.verified"], + "risk": [], + "artifact_refs": [], + "digest_refs": [], + "event_seq": 1, + "expires_at": 1_700_000_120, + } + terminal["terminal_receipt_hash"] = _canonical_hash(terminal) + assert len(terminal) == 23 + assert ( + host_bridge._normalize_fast_lane_worker_terminal_result( + terminal, + expected=expected, + expires_at=1_699_999_999, + now=1_700_000_000, + ) + == terminal + ) + expired = {**terminal, "expires_at": 1_700_000_000} + expired["terminal_receipt_hash"] = _canonical_hash( + {key: value for key, value in expired.items() if key != "terminal_receipt_hash"} + ) + with pytest.raises( + host_bridge.HostBridgeError, + match="HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID", + ): + host_bridge._normalize_fast_lane_worker_terminal_result( + expired, expected=expected, now=1_700_000_000 + ) + invalid = {**terminal, "terminal": "cancelled"} + invalid["terminal_receipt_hash"] = _canonical_hash( + {key: value for key, value in invalid.items() if key != "terminal_receipt_hash"} + ) + with pytest.raises( + host_bridge.HostBridgeError, + match="HOST_BRIDGE_FAST_LANE_TERMINAL_INVALID", + ): + host_bridge._normalize_fast_lane_worker_terminal_result( + invalid, + expected=expected, + expires_at=1_700_000_120, + now=1_700_000_000, + ) + correlation_id = "terminal-" + "d" * 64 + host.send_fast_lane_worker_terminal_result( + terminal_result=terminal, + correlation_id=correlation_id, + expected=expected, + expires_at=1_700_000_120, + now=1_700_000_000, + ) + ack = host.receive_fast_lane_worker_terminal_ack( + terminal_result=terminal, correlation_id=correlation_id + ) + assert type(ack) is dict and len(ack) == 9 + deadline = time.monotonic() + 2 + while not refill_receipts and time.monotonic() < deadline: + time.sleep(0.01) + assert len(refill_receipts) == 1 + assert ack["refill_trigger_hash"] == refill_receipts[0]["refill_trigger_hash"] + assert (batch["batch_hash"], fact_a.task_id) not in session._pending_fast_lane_terminals + assert (batch["batch_hash"], fact_b.task_id) in session._pending_fast_lane_terminals + close_started = time.monotonic() + session.close() + assert time.monotonic() - close_started < 0.5 + host.close() + + +def test_compiler_evidence_cross_language_fixed_vector() -> None: + from devkit_runtime import host_bridge + + vector = json.loads( + (MCP_TOOLS / "tests" / "compiler_evidence_vector.json").read_text( + encoding="utf-8" + ) + ) + request = host_bridge._normalize_compiler_evidence_request( + vector["request"], now=1_700_000_000 + ) + assert len(vector["request"]) == 11 + assert set(vector["request"]["assignment_skeletons"][0]) == { + "task_id", + "routing_proof", + "write_scope", + "concurrency_mode", + "dispatch_order", + "index_context_hash", + "predecessor_hash", + "source_plan_hash", + } + assert host_bridge._normalize_compiler_evidence_response( + vector["response"], request=request, now=1_700_000_000 + ) == vector["response"] + assert vector["frame"]["canonical_payload_hash"] == _canonical_hash( + vector["request"] + ) + for item in vector["project_index_attestations"]: + assert host_bridge._normalize_project_index_attestation( + item["payload"], now=1_700_000_000 + ) == item["payload"] + read_fd, write_fd = os.pipe() + bridge = host_bridge.InheritedHandleHostBridge.from_file_descriptors( + read_fd=read_fd, + write_fd=write_fd, + session_key=bytes.fromhex(vector["frame"]["session_key_hex"]), + session_nonce=bytes.fromhex(vector["frame"]["session_nonce_hex"]), + owns_descriptors=True, + ) + try: + frame = bridge._frame_bytes( + kind=vector["frame"]["kind"], + action_id=vector["frame"]["action_id"], + sequence=vector["frame"]["sequence"], + payload=vector["request"], + ) + assert json.loads(frame[4:])["mac"] == vector["frame"]["mac"] + for item in vector["project_index_attestations"]: + sideband = bridge._frame_bytes( + kind="project_index_attestation", + action_id=item["payload"]["correlation_id"], + sequence=item["sequence"], + payload=item["payload"], + ) + assert json.loads(sideband[4:])["mac"] == item["mac"] + finally: + bridge.close() + + +def test_registry_hash_tamper_never_issues_compiler_evidence( + monkeypatch: pytest.MonkeyPatch, +) -> None: + adapter = _adapter() + import devkit_runtime.host_session as host_session + from devkit_runtime.host_bridge import InheritedHandleHostBridge + + child, host = _pipe_pair() + fact = _dispatch_fact(adapter, task="task-1", scope="src/a.py") + request = _planner_request(adapter, (fact,)) + fact_mapping = adapter._dispatch_fact_mapping(fact) + + def host_reply() -> None: + evidence_request = host.receive_compiler_evidence_request(now=1_700_000_000) + host._send_private( + kind="compiler_evidence_response", + action_id=evidence_request.preparation_id, + payload={ + "schema": "2718lab-devkit/compiler-evidence-response-v1", + "preparation_id": evidence_request.preparation_id, + "request_hash": evidence_request.request_hash, + "reasoning_effort": evidence_request.reasoning_effort, + "verified_route_result_hashes": [fact.route.routing_result_hash], + "verified_lease_scope_bindings": [ + adapter._lease_scope_binding_hash(fact) + ], + "dispatch_facts": [fact_mapping], + "dispatch_binding_hashes": [fact_mapping["dispatch_binding_hash"]], + "nonce": evidence_request.nonce, + "expires_at": evidence_request.expires_at, + "registry_binding_hash": _hash("0"), + }, + ) + + thread = threading.Thread(target=host_reply, daemon=True) + thread.start() + monkeypatch.setattr( + InheritedHandleHostBridge, + "from_environment", + classmethod(lambda cls, environ=None, *, platform=None: child), + ) + session = host_session.HostSession.from_environment( + environ={}, platform="posix", clock=lambda: 1_700_000_000 + ) + try: + assert ( + adapter.prepare_verified_host_facts( + session, + preparation_id="dispatch-private-2", + request=request, + reasoning_effort="high", + ) + == adapter.NO_SAFE_WORK + ) + finally: + thread.join(timeout=2) + child.close() + host.close() + + @pytest.mark.parametrize( ("kind", "sender_role", "recipient_role"), ( diff --git a/mcp-tools/tests/test_mcp_contract.py b/mcp-tools/tests/test_mcp_contract.py index 7f5d955..1c40b42 100644 --- a/mcp-tools/tests/test_mcp_contract.py +++ b/mcp-tools/tests/test_mcp_contract.py @@ -52,6 +52,35 @@ } ) + +def test_index_sideband_correlation_is_meta_only(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + server.mcp, + "get_context", + lambda: SimpleNamespace( + request_context=SimpleNamespace( + meta=SimpleNamespace( + model_extra={ + "2718lab/host-index-correlation": "index-" + "a" * 64 + } + ) + ) + ), + ) + assert server._current_index_correlation() == "index-" + "a" * 64 + monkeypatch.setattr( + server.mcp, + "get_context", + lambda: SimpleNamespace( + request_context=SimpleNamespace( + meta=SimpleNamespace( + model_extra={"2718lab/host-index-correlation": "caller-value"} + ) + ) + ), + ) + assert server._current_index_correlation() is None + EXPECTED_PARAMETERS = { "project_index_register": ("workspace_root",), "project_index_sync": ( @@ -210,7 +239,7 @@ def test_tool_signatures_and_top_level_input_schemas_are_exact() -> None: "ingestion_key", }, "relay_compile": {"request"}, - "fastlane_compile": {"request"}, + "fastlane_compile": {"request", "reasoning_effort"}, "relay_start": {"request"}, "relay_status": {"workflow_id"}, "relay_handoff": {"request"}, diff --git a/mcp-tools/tests/test_project_index_host_material.py b/mcp-tools/tests/test_project_index_host_material.py new file mode 100644 index 0000000..bb183a1 --- /dev/null +++ b/mcp-tools/tests/test_project_index_host_material.py @@ -0,0 +1,107 @@ +"""Focused Project Index Host material contract tests.""" + +from __future__ import annotations + +import hashlib +import json +import subprocess +import sys +from dataclasses import asdict +from pathlib import Path + +MCP_TOOLS = Path(__file__).resolve().parents[1] +if str(MCP_TOOLS) not in sys.path: + sys.path.insert(0, str(MCP_TOOLS)) + +from devkit_runtime.bootstrap import RuntimeBootstrap # noqa: E402 +from devkit_runtime.config import RuntimeConfig # noqa: E402 +from devkit_runtime.project_checkpoint import open_project_checkpoint_rw # noqa: E402 +from devkit_runtime.tool_result import _query_data # noqa: E402 + + +def _hash(value: object) -> str: + return "sha256:" + hashlib.sha256( + json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + allow_nan=False, + ).encode("utf-8") + ).hexdigest() + + +def test_query_attestation_hashes_exact_public_projection(tmp_path: Path) -> None: + workspace = tmp_path / "workspace" + workspace.mkdir() + (workspace / "sample.py").write_text( + "def alpha():\n return 1\n", encoding="utf-8" + ) + subprocess.run(["git", "init", "--quiet", str(workspace)], check=True) + subprocess.run( + ["git", "-C", str(workspace), "config", "user.name", "Index Test"], + check=True, + ) + subprocess.run( + [ + "git", + "-C", + str(workspace), + "config", + "user.email", + "index@example.invalid", + ], + check=True, + ) + subprocess.run(["git", "-C", str(workspace), "add", "sample.py"], check=True) + subprocess.run( + ["git", "-C", str(workspace), "commit", "--quiet", "-m", "fixture"], + check=True, + ) + + data_root = tmp_path / "runtime-data" + scratch_root = tmp_path / "runtime-scratch" + scratch_root.mkdir() + config = RuntimeConfig.load( + environ={"PLUGIN_DATA": str(data_root), "CODEX_TASK_TEMP": str(scratch_root)} + ) + RuntimeBootstrap.run(config) + checkpoint = open_project_checkpoint_rw( + config.project_index_database, + config.checkpoint_cas_root, + scratch_root=config.scratch_root, + ) + service = checkpoint.project_index + try: + workspace_id = service.project_index_register(workspace) + register_material = service.host_attestation_material(workspace_id) + assert service.project_index_register(workspace) == workspace_id + assert service.host_attestation_material(workspace_id) == register_material + snapshot = service.sync(workspace_id) + result = service.query( + workspace_id, + snapshot.snapshot_id, + "alpha", + source_lines=2, + byte_budget=4096, + ) + receipt = service.get_query_receipt(result.trace_id) + material = service.host_attestation_material( + workspace_id, + snapshot_id=snapshot.snapshot_id, + trace_id=result.trace_id, + ) + + projection = _query_data(result) + projection.pop("state") + projection["workspace_id"] = workspace_id + assert material["index_context_hash"] == _hash(projection) + assert material["query_receipt_hash"] == _hash( + { + "schema": "2718lab-devkit/project-index-query-receipt-binding-v1", + "receipt": asdict(receipt), + "index_context_hash": material["index_context_hash"], + } + ) + finally: + checkpoint.close() From 61873e78684436fc9e57cef006fe01610fa3d17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Tue, 1 Sep 2026 16:15:51 +0800 Subject: [PATCH 5/8] release: prepare DevKit 1.1.2 --- CHANGELOG.md | 7 ++----- mcp-tools/devkit_fastlane/scripts/team_efficiency.py | 3 +-- mcp-tools/devkit_fastlane/tests/test_team_efficiency.py | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b0f93..f3ad3dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ only after the CI and artifact checks pass. ## [Unreleased] +## [1.1.2] - 2026-08-27 + ### Fixed - Replaced inherited numeric Windows host-bridge handles with a strict local @@ -15,11 +17,6 @@ only after the CI and artifact checks pass. FILETIME while preserving the Unix inherited-FD contract. Untagged, path-like, remote, malformed, PID-mismatched, or creation-mismatched selectors now fail closed before any session key is sent. - -## [1.1.2] - 2026-08-27 - -### Fixed - - Treat a newly opened project without an index as normal cold start: initialize it with one bounded `project_index_register -> project_index_sync` sequence before considering degraded mode, including when README, configuration, or diff --git a/mcp-tools/devkit_fastlane/scripts/team_efficiency.py b/mcp-tools/devkit_fastlane/scripts/team_efficiency.py index 3c2595c..ee616e1 100644 --- a/mcp-tools/devkit_fastlane/scripts/team_efficiency.py +++ b/mcp-tools/devkit_fastlane/scripts/team_efficiency.py @@ -4846,8 +4846,7 @@ def prepare_authenticated_v5_routing_from_request( [*units, *remaining_units], key=lambda unit: int(unit["dispatch_order"]) ) order_by_task = { - str(unit["task"]["task_id"]): int(unit["dispatch_order"]) - for unit in all_units + str(unit["task"]["task_id"]): int(unit["dispatch_order"]) for unit in all_units } all_routing_requests = sorted( [*routing_requests, *remaining_routing_requests], diff --git a/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py b/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py index c3bf462..c6aebc1 100644 --- a/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py +++ b/mcp-tools/devkit_fastlane/tests/test_team_efficiency.py @@ -6353,7 +6353,7 @@ def test_contract_documents_ultra_auto_policy(self) -> None: "NO_SAFE_WORK", "PROJECT_AUTHORITY_UNAVAILABLE", "apply_bootstrap_plan", - "不存在可执行的", + "不存在自行创建 worktree", "worker effort 禁止 `ultra`", "prewarm 始终是独立的只读证据角色", "归档不是 adapter 操作", From 05a6796d0d0925db3f918049b6d0093d4777dbe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Tue, 1 Sep 2026 16:19:57 +0800 Subject: [PATCH 6/8] style: satisfy MCP release formatting gate --- mcp-tools/project_index/service.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/mcp-tools/project_index/service.py b/mcp-tools/project_index/service.py index 6e7bcde..92dc753 100644 --- a/mcp-tools/project_index/service.py +++ b/mcp-tools/project_index/service.py @@ -688,9 +688,7 @@ def host_attestation_material( """Project current persisted facts into path-free Host-side digests.""" registered_id, root = self._workspace_for_reference(workspace_id) - root_identity_hash = _opaque_hash( - {"root_identity": workspace_identity(root)} - ) + root_identity_hash = _opaque_hash({"root_identity": workspace_identity(root)}) workspace_binding_hash = _opaque_hash( { "workspace_id": registered_id, @@ -1777,15 +1775,18 @@ def _encoded_size(value: object) -> int: def _opaque_hash(value: object) -> str: - return "sha256:" + hashlib.sha256( - json.dumps( - value, - ensure_ascii=False, - separators=(",", ":"), - sort_keys=True, - allow_nan=False, - ).encode("utf-8") - ).hexdigest() + return ( + "sha256:" + + hashlib.sha256( + json.dumps( + value, + ensure_ascii=False, + separators=(",", ":"), + sort_keys=True, + allow_nan=False, + ).encode("utf-8") + ).hexdigest() + ) def _public_query_node(node: IndexNode) -> dict[str, object]: From ba5c6e5d97992ab44253da9748dc57d4585ab21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Tue, 1 Sep 2026 16:39:36 +0800 Subject: [PATCH 7/8] fix: narrow authenticated dispatch evidence --- mcp-tools/server.py | 32 +++++++++++-------- mcp-tools/tests/test_fastlane_host_adapter.py | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/mcp-tools/server.py b/mcp-tools/server.py index 5e14003..74a5aa9 100644 --- a/mcp-tools/server.py +++ b/mcp-tools/server.py @@ -1087,6 +1087,9 @@ def _fastlane_authenticated_dispatch( ) if index_attestation is None: return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + index_context_hash = index_attestation.get("index_context_hash") + if type(index_context_hash) is not str: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") project_binding = request.get("project_binding") work_package = request.get("work_package") if ( @@ -1114,7 +1117,7 @@ def _fastlane_authenticated_dispatch( projected = prepare_authenticated_v5_routing_from_request( request, - index_context_hash=index_attestation["index_context_hash"], + index_context_hash=index_context_hash, host_capabilities=capability_snapshot.host_capabilities, scheduler_facts=capability_snapshot.scheduler_facts, ) @@ -1130,11 +1133,19 @@ def _fastlane_authenticated_dispatch( initial_task_ids = { unit["task"]["task_id"] for unit in initial_units } - initial_requests = [ - item - for item in routing_snapshot.routing_requests - if item["task"]["task_id"] in initial_task_ids - ] + initial_requests: list[dict[str, object]] = [] + remaining_requests: list[dict[str, object]] = [] + for item in routing_snapshot.routing_requests: + task = item.get("task") + if type(task) is not dict: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + task_id = task.get("task_id") + if type(task_id) is not str: + return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") + if task_id in initial_task_ids: + initial_requests.append(item) + else: + remaining_requests.append(item) initial_attestations = [ item for item in routing_snapshot.attestations @@ -1154,12 +1165,7 @@ def _fastlane_authenticated_dispatch( compile_authenticated_v5_assignment_skeletons( remaining_units, source_plan_hash=projected["source_plan_hash"], - routing_requests=[ - item - for item in routing_snapshot.routing_requests - if item["task"]["task_id"] - not in initial_task_ids - ], + routing_requests=remaining_requests, attestation_items=[ item for item in routing_snapshot.attestations @@ -1234,7 +1240,7 @@ def _fastlane_authenticated_dispatch( call_intent_hash=call_intent_hash, preparation_id=preparation_id, source_plan_hash=projected["source_plan_hash"], - index_context_hash=index_attestation["index_context_hash"], + index_context_hash=index_context_hash, routing_registry_binding_hash=( routing_snapshot.routing_registry_binding_hash ), diff --git a/mcp-tools/tests/test_fastlane_host_adapter.py b/mcp-tools/tests/test_fastlane_host_adapter.py index 0132570..b49d924 100644 --- a/mcp-tools/tests/test_fastlane_host_adapter.py +++ b/mcp-tools/tests/test_fastlane_host_adapter.py @@ -1108,7 +1108,7 @@ def refill_callback(trigger: Mapping[str, object]) -> dict[str, object]: host_bridge._normalize_fast_lane_worker_terminal_result( terminal, expected=expected, - expires_at=1_699_999_999, + expires_at=1_700_000_120, now=1_700_000_000, ) == terminal From 35700b36447a2dd8bfbb11ca648c0d9218cb91f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Tue, 1 Sep 2026 16:56:38 +0800 Subject: [PATCH 8/8] test: align release gates with plugin and pipe contracts --- mcp-tools/tests/test_bugkiller_metadata.py | 9 +++++++++ .../tests/test_relay_runtime_registry.py | 20 ++++++++----------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/mcp-tools/tests/test_bugkiller_metadata.py b/mcp-tools/tests/test_bugkiller_metadata.py index 684d617..c4c5c5b 100644 --- a/mcp-tools/tests/test_bugkiller_metadata.py +++ b/mcp-tools/tests/test_bugkiller_metadata.py @@ -19,6 +19,11 @@ "python-engineering", "workflow-design", } +MCP_DEPENDENCY_SKILLS = { + "code-atlas", + "fast-lane-routing", + "workflow-design", +} RETIRED_MANUAL_SURFACE = re.compile( r"(?i)(?:^|[^a-z0-9_-])(?:agents|assets|commands|scripts)[\\/]|" r"bugkiller-(?:sol|terra)-" @@ -149,6 +154,10 @@ def test_local_skill_bundle_contains_only_reference_manuals(self) -> None: relative.startswith("references/") and relative.endswith(".md") ) + or ( + skill_name in MCP_DEPENDENCY_SKILLS + and relative == "agents/openai.yaml" + ) for relative in files ), files, diff --git a/mcp-tools/tests/test_relay_runtime_registry.py b/mcp-tools/tests/test_relay_runtime_registry.py index d75c6c3..13ca398 100644 --- a/mcp-tools/tests/test_relay_runtime_registry.py +++ b/mcp-tools/tests/test_relay_runtime_registry.py @@ -381,6 +381,7 @@ class SecurityAttributes(ctypes.Structure): errors: list[BaseException] = [] release_silent_server = threading.Event() + server_received_ping = threading.Event() def serve() -> None: nonlocal server_handle @@ -405,6 +406,7 @@ def serve() -> None: "pipe-ping", {}, ) + server_received_ping.set() if reply_mode == "pong": host.send_private( kind="capability_ack", action_id="pipe-pong", payload={} @@ -447,19 +449,10 @@ def exchange() -> None: client_thread = threading.Thread(target=exchange, daemon=True) client_thread.start() + assert server_received_ping.wait(timeout=2) + if reply_mode == "silent": + release_silent_server.set() client_thread.join(timeout=2) - if client_thread.is_alive(): - assert client_thread.native_id is not None - thread_handle = open_thread(0x0001, False, client_thread.native_id) - if not thread_handle: - raise OSError(ctypes.get_last_error(), "OpenThread failed") - try: - if not cancel_synchronous_io(thread_handle): - raise OSError(ctypes.get_last_error(), "CancelSynchronousIo failed") - finally: - close_handle(thread_handle) - client_thread.join(timeout=2) - release_silent_server.set() if reply_mode == "pong": assert client_errors == [] else: @@ -651,10 +644,13 @@ def test_partial_private_write_poison_session_without_prepared_delivery( ) -> None: child, host = _pipe_pair() original_write = host_bridge_module.os.write + transport_write_fd = child._write_fd writes = 0 def partial_then_transport_error(descriptor: int, payload: object) -> int: nonlocal writes + if descriptor != transport_write_fd: + return original_write(descriptor, payload) writes += 1 if writes == 1: assert isinstance(payload, bytes | memoryview)