From 82cce4b57e4d3d98df606ff24acbcb0ae5ba1850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=93=80=E6=B4=9B=E8=8A=99?= Date: Fri, 4 Sep 2026 21:35:03 +0800 Subject: [PATCH] feat(fastlane): add hostless compile-only planning --- .codex-plugin/plugin.json | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- CHANGELOG.md | 25 + README.md | 87 +-- README.zh-CN.md | 76 ++- .../devkit_fastlane/FASTLANE_CONTRACT.md | 55 +- .../references/efficiency-automation.md | 40 +- .../scripts/team_efficiency.py | 643 ++++++++++++++++++ mcp-tools/project_index/service.py | 56 ++ mcp-tools/pyproject.toml | 2 +- mcp-tools/server.py | 401 +---------- mcp-tools/tests/test_bugkiller_metadata.py | 2 +- mcp-tools/tests/test_fastlane_runtime.py | 542 ++++++++++++++- mcp-tools/tests/test_primary_artifact.py | 4 +- mcp-tools/uv.lock | 2 +- 15 files changed, 1445 insertions(+), 494 deletions(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 706b1e7..60c8949 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "2718lab-devkit", - "version": "1.1.3", + "version": "1.1.4", "description": "Local MCP server for developer workflow coordination, indexing, and evidence handling.", "author": { "name": "2718lab", diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b4e8d9f..a94ae82 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -28,7 +28,7 @@ body: id: version attributes: label: DevKit version - placeholder: "v1.1.3" + placeholder: "v1.1.4" validations: required: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 84801d9..ccf7473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,31 @@ only after the CI and artifact checks pass. ## [Unreleased] +## [1.1.4] - 2026-09-04 + +### Added + +- Added a hostless, compile-only MCP Fast Lane planning path. Caller-supplied + workspace and snapshot identifiers remain selectors: a read-only local + RuntimeRoot resolves the registered workspace, requires the exact current + `INDEX_READY` snapshot and Git binding, and emits a bounded, exact-key + `team-efficiency/fast-lane-plan-v2` descriptor. + +### Fixed + +- Normalize the no-null public plan before calculating assignment and plan + identities, so `plan_item_id` and `plan_hash` can be recomputed exactly from + inactive plans and read-only as well as writer assignments. +- Fail closed with the snapshot's precise stable error whenever local planning + observes any Project Index state other than `INDEX_READY`. + +### Security + +- Plan-v2 is planning data only: it fixes `plan_only=true`, + `dispatch_state="not_dispatched"`, and `execution_authorized=false`. It does + not spawn an agent, claim a lease, create a worktree, mutate Git, or dispatch + work; those actions remain outside the compiler contract. + ## [1.1.3] - 2026-09-01 ### Added diff --git a/README.md b/README.md index af285d6..66888db 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,26 @@ [简体中文](README.zh-CN.md) -# 2718lab DevKit — Codex + MCP v1.1.3 +# 2718lab DevKit — Codex + MCP v1.1.4 -[![version](https://img.shields.io/badge/version-v1.1.3-blue)](./.codex-plugin/plugin.json) +[![version](https://img.shields.io/badge/version-v1.1.4-blue)](./.codex-plugin/plugin.json) [![license](https://img.shields.io/badge/license-AGPL--3.0-blue)](LICENSE) 2718lab DevKit is a Codex-first engineering toolkit: a local, stdio-only MCP runtime for bounded project indexing, Atlas evidence, Relay lifecycle coordination, and deterministic Fast Lane planning, plus a compact Skill bundle -of reference manuals. This repository carries the versioned v1.1.3 package. +of reference manuals. This repository carries the versioned v1.1.4 package. The checked-in manifest and allowlist define the executable runtime surface; the manual map, install, build, and verification sections below describe the supported workflow. -The current release retains a deliberately fail-closed Fast Lane preview. The public compiler -and CLI return `NO_SAFE_WORK` with zero assignments: they do not consume host -status or live-account inputs, and have no worktree execution path. Host -execution remains an external Desktop-host bridge requirement. +The public Python compiler and CLI retain the deliberately fail-closed +`team-efficiency/fast-lane-plan-v1` preview. The MCP `fastlane_compile` tool +instead resolves caller-supplied workspace/snapshot selectors through its local +RuntimeRoot and returns a verified `team-efficiency/fast-lane-plan-v2` planning +descriptor. That descriptor is `plan_only`: it never dispatches, claims a +lease, creates a worktree, or authorizes execution. -For storage-governed execution, v1.1.3 references compatible Host source only +The prior v1.1.3 storage-governance integration references compatible Host source only on Ayleovelle's user-fork [`codex/host-1.1.3-storage-governance-upstream`](https://github.com/Ayleovelle/codex/tree/codex/host-1.1.3-storage-governance-upstream) branch, pinned to immutable commit @@ -59,12 +61,12 @@ and continue to fail closed. - The optional Codex Skill bundle is part of DevKit's documentation surface. It provides short, module-specific manuals without becoming a second runtime or an executable prompt/agent surface. -- Fast Lane is a pure MCP runtime compiler. Its public surface is presently - authority-inert and fail-closed: it emits no assignments and never spawns - agents, edits Git, runs commands, or executes worktrees. Host execution is - reserved for a future external Desktop-host bridge contract. The RuntimeRoot - host-private V2/V3 bootstrap path is covered with injected test doubles only; - no external host embedding or operational/host-integrated GO is claimed. +- Fast Lane is a pure planning compiler. The MCP tool can emit bounded + `team-efficiency/fast-lane-plan-v2` assignments only after its private + RuntimeRoot verifies a registered `INDEX_READY` snapshot, Git HEAD, and full + write-scope coverage. Every result remains `plan_only`; no agent, Git command, + lease, or worktree action is executed. The public Python compiler/CLI remains + the authority-inert plan-v1 diagnostic surface. ## Module overview @@ -82,11 +84,11 @@ and continue to fail closed. ## Overall workflow The repository workflow defaults to Fast Lane. `workflow-design` prepares a -bounded input; `fastlane_compile` or `team_efficiency.py` then returns an -authority-inert, fail-closed plan. `fast-lane-routing` documents the intended -future host-consumption boundary; neither a skill nor the current compiler -starts agents or creates/executes cross-session worktrees. The current path is -to inspect the blocked plan and retain authority outside this repository. +bounded input. MCP `fastlane_compile` verifies the persisted local project +snapshot and returns a plan-v2 descriptor; `team_efficiency.py` retains the +blocked plan-v1 diagnostic path. Neither path starts agents or creates/executes +cross-session worktrees. A coordinator must separately validate route +availability, claim a lease, and dispatch any selected work. ```mermaid flowchart TD @@ -95,9 +97,9 @@ flowchart TD C["mcp-tools/server.py
stdio entry"] --> D["Project Index / Checkpoint
Atlas / Relay"] --> E["Bounded result
host action"] end subgraph FAST["Fast Lane"] - F["fast-lane request"] --> G["team_efficiency.py
public compiler"] - G --> X["Fail closed
NO_SAFE_WORK, zero assignments"] - H["Future external Desktop-host bridge
contract only"] -. "not shipped or invoked" .-> G + F["fast-lane request"] --> G["MCP fastlane_compile
private RuntimeRoot verification"] + G --> X["plan-v2
plan_only, not dispatched"] + F --> H["public Python / CLI"] --> I["plan-v1
NO_SAFE_WORK"] end B -->|MCP tools| C B -->|Fast Lane| F @@ -177,7 +179,7 @@ source of record remains `main` and immutable release tags. Maintainers build that snapshot with the dedicated marketplace allowlist: - python .codex-plugin/build_main_artifact.py --plugin-root . --allowlist .codex-plugin/marketplace-artifact-allowlist.json --output /2718lab-devkit-marketplace-v1.1.3.zip + python .codex-plugin/build_main_artifact.py --plugin-root . --allowlist .codex-plugin/marketplace-artifact-allowlist.json --output /2718lab-devkit-marketplace-v1.1.4.zip ## Install and run locally @@ -224,7 +226,7 @@ handles or falls back to an unrelated local start. The allowlisted builder creates a deterministic ZIP outside the plugin source tree. Choose an output directory outside the source tree: - python .codex-plugin/build_main_artifact.py --plugin-root . --output /2718lab-devkit-v1.1.3.zip + python .codex-plugin/build_main_artifact.py --plugin-root . --output /2718lab-devkit-v1.1.4.zip The artifact contains the manifest, .mcp.json, LICENSE, the locked Python project, and the runtime files selected by @@ -334,24 +336,27 @@ cleanup target. 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 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. +mcp-tools/devkit_fastlane/scripts/team_efficiency.py. The MCP `fastlane_compile` +entry returns `team-efficiency/fast-lane-plan-v2` only after a read-only +RuntimeRoot unit of work resolves the registered workspace and snapshot. The +snapshot must be current, `INDEX_READY`, bound to the repository Git HEAD, and +its persisted `include_paths` must fully cover every writer `write_scope`. - `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. -- 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. + `xhigh`, or `max`; the MCP output never dispatches a worker. +- `team-efficiency/fast-lane-plan-v2` fixes `plan_only=true`, + `dispatch_state="not_dispatched"`, and `execution_authorized=false`. + Assignments use `team-efficiency/local-writer-plan-v1`; path-free + `index_evidence` includes snapshot/binding hashes, `include_paths_hash`, and + the compiled `scope_hash`. +- `INDEX_STALE` rejects filesystem or Git drift. `INDEX_PARTIAL` rejects either + a partial snapshot or any snapshot whose include roots omit a writer scope. +- Caller IDs are selectors, not authority material. The public MCP request and + public `compile_fast_lane` Python API accept no local index root, Git HEAD, or + private planning material. +- The public Python compiler/CLI still does not consume host status, account + usage, index evidence, or a worktree root and remains the blocked plan-v1 + diagnostic boundary. ### Account-usage boundary @@ -390,7 +395,7 @@ freeze a transient regression count. ## Version -This repository represents the versioned v1.1.3 package. Release notes are +This repository represents the versioned v1.1.4 package. Release notes are in [CHANGELOG.md](CHANGELOG.md); build and install from the checked-in manifest, artifact allowlist, and locked dependency set. A maintainer dispatches Release from current `main`; it validates all declared gates, creates the annotated tag, diff --git a/README.zh-CN.md b/README.zh-CN.md index 5f2d4e5..be8c255 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,21 +1,23 @@ [English](README.md) -# 2718lab DevKit —— Codex + MCP v1.1.3 +# 2718lab DevKit —— Codex + MCP v1.1.4 -[![版本](https://img.shields.io/badge/version-v1.1.3-blue)](./.codex-plugin/plugin.json) +[![版本](https://img.shields.io/badge/version-v1.1.4-blue)](./.codex-plugin/plugin.json) [![许可证](https://img.shields.io/badge/license-AGPL--3.0-blue)](LICENSE) 2718lab DevKit 是一个 Codex-first 工程工具包:它包含一个本地、仅 stdio 传输的 MCP 运行时,用于有边界的项目索引、Atlas 证据、Relay 生命周期协调和 确定性的 Fast Lane 规划;同时还包含一组精简的 Skill 说明书。本仓库承载版本化的 -v1.1.3 包;已提交的 manifest 和 allowlist 定义可执行运行时范围,说明书导航、 +v1.1.4 包;已提交的 manifest 和 allowlist 定义可执行运行时范围,说明书导航、 安装、构建和验证章节共同给出支持的工作流。 -当前版本保留刻意 fail-closed 的 Fast Lane 预览。公共编译器和 CLI 固定返回 -`NO_SAFE_WORK` 与零 assignments:不会消费 host-status 或实时账号输入,也没有 -worktree 执行路径。宿主执行属于未来外部 Desktop-host bridge 合同的要求。 +公共 Python 编译器和 CLI 继续保留刻意 fail-closed 的 +`team-efficiency/fast-lane-plan-v1` 预览。MCP `fastlane_compile` 则通过本地 +RuntimeRoot 解析 caller 提供的 workspace/snapshot selector,并返回已验证的 +`team-efficiency/fast-lane-plan-v2` 规划描述符。该描述符固定为 `plan_only`:不会 +派发、领取 lease、创建 worktree 或授权执行。 -对于受存储治理的执行,v1.1.3 只引用 Ayleovelle 用户 fork 上的兼容 Host 源码: +此前 v1.1.3 的存储治理集成只引用 Ayleovelle 用户 fork 上的兼容 Host 源码: [`codex/host-1.1.3-storage-governance-upstream`](https://github.com/Ayleovelle/codex/tree/codex/host-1.1.3-storage-governance-upstream) 分支,固定到不可变提交 [`c3dde23bec21c45d10740f2eec09d9a1b87cd329`](https://github.com/Ayleovelle/codex/commit/c3dde23bec21c45d10740f2eec09d9a1b87cd329)。 @@ -45,11 +47,11 @@ stock Codex Host 没有经过证明的 protected broker,继续 fail-closed。 静态 prompt agent 或模型运行器。 - 可选的 Codex Skill bundle 是 DevKit 的说明书表面,提供简短的模块化手册; 它不构成第二个运行时,也不是可执行的 prompt/agent 表面。 -- Fast Lane 是 MCP runtime 中的纯本地编译器。其公共面当前没有调度权限并且 - fail-closed:不会产生 assignment,也不会 spawn agent、修改 Git、运行命令或 - 执行 worktree。宿主执行预留给未来的外部 Desktop-host bridge 合同。 - RuntimeRoot 的 host-private V2/V3 bootstrap 仅由注入的测试替身覆盖; - 没有 external host embedding 实证,也不声称 operational/host-integrated GO。 +- Fast Lane 是纯规划编译器。只有 server 私有 RuntimeRoot 验证已注册的 + `INDEX_READY` snapshot、Git HEAD 和完整 writer scope 覆盖后,MCP 工具才会 + 产出有界的 `team-efficiency/fast-lane-plan-v2` assignments。所有结果仍为 + `plan_only`,不执行 agent、Git 命令、lease 或 worktree 动作。公共 Python + 编译器/CLI 继续作为无 authority 的 plan-v1 诊断面。 ## 核心模块速览 @@ -66,11 +68,11 @@ stock Codex Host 没有经过证明的 protected broker,继续 fail-closed。 ## 整体工作流 -仓库级默认工作流是 Fast Lane。`workflow-design` 准备有界输入; -`fastlane_compile` 或 `team_efficiency.py` 随后只返回无权限、fail-closed 的计划。 -`fast-lane-routing` 记录的是预期的未来宿主消费边界;skill 和当前编译器均不会 -启动 agent,也不会创建或执行跨会话工作树。当前路径只用于检查被阻断的计划, -权限仍保留在本仓库之外。 +仓库级默认工作流是 Fast Lane。`workflow-design` 准备有界输入;MCP +`fastlane_compile` 验证持久化本地项目 snapshot 后返回 plan-v2 描述符, +`team_efficiency.py` 则保留被阻断的 plan-v1 诊断路径。两条路径都不会启动 agent, +也不会创建或执行跨会话工作树。协调器必须另行验证 route availability、领取 lease +并派发所选工作。 ```mermaid flowchart TD @@ -79,9 +81,9 @@ flowchart TD C["mcp-tools/server.py
stdio 入口"] --> D["Project Index / Checkpoint
Atlas / Relay"] --> E["有界结果
宿主动作"] end subgraph FAST["Fast Lane"] - F["fast-lane request"] --> G["team_efficiency.py
公共编译器"] - G --> X["失败关闭
NO_SAFE_WORK,零 assignments"] - H["未来外部 Desktop-host bridge
仅合同"] -. "未交付或调用" .-> G + F["fast-lane request"] --> G["MCP fastlane_compile
私有 RuntimeRoot 验证"] + G --> X["plan-v2
plan_only,未派发"] + F --> H["公共 Python / CLI"] --> I["plan-v1
NO_SAFE_WORK"] end B -->|MCP 工具| C B -->|Fast Lane| F @@ -155,7 +157,7 @@ Fast Lane 不含额度协调器合同;公共编译器和 CLI 不读取、协 维护者使用专用的 marketplace allowlist 构建该快照: - python .codex-plugin/build_main_artifact.py --plugin-root . --allowlist .codex-plugin/marketplace-artifact-allowlist.json --output /2718lab-devkit-marketplace-v1.1.3.zip + python .codex-plugin/build_main_artifact.py --plugin-root . --allowlist .codex-plugin/marketplace-artifact-allowlist.json --output /2718lab-devkit-marketplace-v1.1.4.zip ## 本地安装与运行 @@ -195,7 +197,7 @@ RELAY_CAPABILITY_BROKER_UNAVAILABLE。服务器不会暴露原始 handle,也 allowlist builder 会在插件源码树之外生成确定性的 ZIP。请选择源码树之外的输出目录: - python .codex-plugin/build_main_artifact.py --plugin-root . --output /2718lab-devkit-v1.1.3.zip + python .codex-plugin/build_main_artifact.py --plugin-root . --output /2718lab-devkit-v1.1.4.zip 产物包含 manifest、.mcp.json、LICENSE、锁定的 Python 项目,以及 .codex-plugin/main-artifact-allowlist.json 选中的运行时文件。它的可执行运行时 @@ -289,20 +291,24 @@ embedding 或本地路径放宽的证据。 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。 +mcp-tools/devkit_fastlane/scripts/team_efficiency.py。MCP `fastlane_compile` 只有在 +只读 RuntimeRoot UoW 解析已注册 workspace/snapshot 后才返回 +`team-efficiency/fast-lane-plan-v2`。snapshot 必须 current、处于 `INDEX_READY`、 +绑定仓库 Git HEAD,且持久化 `include_paths` 完整覆盖每个 writer `write_scope`。 - `reasoning_effort` 必填且只接受 `low`、`medium`、`high`、`xhigh` 或 - `max`;worker 调度永不接受 `ultra`。 -- 公共编译器/CLI 不消费 host-status、账号用量、index evidence 或 worktree root。 -- 编译器不会创建 worktree、选择路由或运行命令;认证 session 只在 terminal ACK - 后请求下一宿主边界补位。编译器只能把完整 hash-bound batch 提交给私有宿主桥, - 真正执行权限仍属于宿主。 -- evidence 缺失、过期、错配、重放或来自 caller 自报时仍保持 `NO_SAFE_WORK`; - filesystem path 永远不能充当 compiler evidence。 + `max`;MCP 输出永不派发 worker。 +- `team-efficiency/fast-lane-plan-v2` 固定 `plan_only=true`、 + `dispatch_state="not_dispatched"`、`execution_authorized=false`。 + assignment 使用 `team-efficiency/local-writer-plan-v1`;无路径的 + `index_evidence` 包含 snapshot/binding hashes、`include_paths_hash` 与已编译 + `scope_hash`。 +- filesystem 或 Git 漂移返回 `INDEX_STALE`;snapshot 本身 partial,或其 include + roots 遗漏任一 writer scope,均返回 `INDEX_PARTIAL`。 +- caller ID 只是 selector,不是 authority material。公共 MCP request 与公共 + `compile_fast_lane` Python API 均不接受本地 index root、Git HEAD 或私有规划材料。 +- 公共 Python 编译器/CLI 仍不消费 host-status、账号用量、index evidence 或 + worktree root,并继续作为被阻断的 plan-v1 诊断边界。 ### 账号用量边界 @@ -336,7 +342,7 @@ CI 和全新产物检查才是当前测试计数的唯一来源。它们验证 ## 版本 -本仓库代表版本化的 v1.1.3 包。发布说明见 +本仓库代表版本化的 v1.1.4 包。发布说明见 [CHANGELOG.md](CHANGELOG.md);构建和安装请以已提交的 manifest、产物 allowlist 和锁定依赖为准。维护者从 current `main` 手动 dispatch Release;它通过全部 gates 后才创建注释 tag 并发布匹配的 GitHub Release。单独 push tag 不会触发发布。 diff --git a/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md b/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md index 08947b6..59a374b 100644 --- a/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md +++ b/mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md @@ -106,30 +106,48 @@ 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。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 +manifest 中的 fence 只是可验证的结构与 hash 输入,绝不是执行 authority。没有任何 module +attribute、closure、环境变量、请求 JSON、repo/task root、路径名或 caller-supplied ID 可以授权 +spawn、lease、worktree 或 Git mutation。公开 Python `compile_fast_lane` 与 `fast-lane` CLI 对 +structurally valid V2 继续产出 `team-efficiency/fast-lane-plan-v1` 的 +`NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE`,零 assignment、零队列、零外部派发;V2 envelope/hash 无效、其内层 canonical v1 `package` 不能完成纯诊断解析,或 fast-lane request 壳的 schema/key/字节边界无效时,必须是 `PROJECT_BINDING_INVALID`;v1 保持 -`LEGACY_PROJECT_UNBOUND`。这些结构预检不读取 host、账号用量或 index 输入,也不触及 scheduler。 -公开 MCP request 若试图携带明确的 host-private 字段(如 `host_status`、账号用量或 index -evidence),则是适配器输入违规,必须在编译前以 `FASTLANE_REQUEST_INVALID` 拒绝,而不是把 -该值当作可诊断的计划输入。 -`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 私有命名伪造。 +`LEGACY_PROJECT_UNBOUND`。 + +MCP `fastlane_compile` 使用不同的 server-private planning path。公开 request 中的 +`workspace_id` 与 `input_snapshot_id` 只作为 selector;server 在只读 RuntimeRoot UoW 内解析 +持久 registry,并由 Project Index service 读取 current snapshot、canonical `include_paths` 与 +真实 Git HEAD,再把这些事实直接交给 module-private 编译入口。该材料入口不是公开 Python API +参数,也不从 `devkit_fastlane/__init__.py` 导出。公开 MCP request 若携带 `host_status`、账号 +用量、index evidence、root 或 Git HEAD 等 caller 自报材料,必须在编译前以 +`FASTLANE_REQUEST_INVALID` 拒绝。 + +server-private 编译在创建任何 planned assignment 前必须同时证明:workspace/snapshot binding +匹配 selector;snapshot 为 current `INDEX_READY`;snapshot head、scheduler integration commit +与每个 bootstrap base commit 都等于真实 Git HEAD;bootstrap repo 等于 registry root;持久化 +`include_paths` 为 canonical 且完整覆盖 source plan 中每个 writer `write_scope`。空 +`include_paths` 表示完整 workspace snapshot;非空 include root 必须等于 scope 或是 scope 的 +祖先。snapshot 自身有 gap,或任一 writer scope 未被覆盖,均精确返回 `INDEX_PARTIAL`;文件或 +Git 漂移返回 `INDEX_STALE`;binding、root、snapshot 或 HEAD 错配必须 fail closed,不能产生 +部分计划。 + +成功响应 schema 为 `team-efficiency/fast-lane-plan-v2`。顶层固定 +`plan_only=true`、`dispatch_state="not_dispatched"`、`execution_authorized=false`;assignment +使用 `team-efficiency/local-writer-plan-v1`,固定 `execution_state="plan_only"`、 +`lease_state="unclaimed"` 与 `worktree.state="planned"`。path-free `index_evidence` 使用 +`team-efficiency/local-index-evidence-v1`,包含 workspace/snapshot/head/manifest/parser binding +hash、`include_paths_hash`、所有 writer scopes 的 `scope_hash` 与最终 `evidence_hash`,但不返回 +registry root、raw Git HEAD 或 include path 文本。`workflow_policy` 只说明协调器后续必须显式 +验证 route、领取 lease 并调用 dispatch tool;它不是执行许可,编译器也不调用该工具。 同一限制覆盖 `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。DevKit 仍不存在自行创建 worktree -的可执行路径;authenticated compiler evidence 只允许将 typed batch 提交给宿主,不能绕过宿主的 -worktree broker、Git probe 或 coordinator gate。 +的可执行路径;plan-v2 不是 typed dispatch batch,也不能绕过外部宿主的 worktree broker、Git +probe 或 coordinator gate。 ### 4. 接地后再写 @@ -147,7 +165,10 @@ worktree broker、Git probe 或 coordinator gate。 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 输入,因此仍输出 `NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE` 的零 assignment/队列预览。只有 MCP 进程持有已认证 inherited bridge 且宿主返回 exact registry binding 时,私有 adapter 才能机械提交 `dispatch_all`;worker effort 禁止 `ultra`。编译器本身不调用模型、不启动 agent、不创建会话或工作树、不运行 gate、不改写 Git、不领取或完成 workflow。协调器 lane 保有设计、集成、风险决策和最终验收责任。 +`ultra` 自动激活(Ultra automatic activation);低于 Ultra 的 effort 必须显式传入 `--enable`,否则得到 inactive plan。这个 CLI/API 不消费 host-status、额度或 index 输入,因此仍输出 plan-v1 的 `NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE` 零 assignment/队列预览。MCP 工具只接受 `low`、`medium`、`high`、`xhigh`、`max`,并在 `enable=true` 时返回上述 plan-v2;`enable=false` 返回同 schema 的 inactive、零 assignment 计划,但仍先验证 registry/index/Git binding。两种编译路径都不调用模型、不启动 agent、不创建会话或工作树、不运行 gate、不改写 Git、不领取或完成 workflow。协调器 lane 保有设计、集成、风险决策和最终验收责任;同时负责 route availability 与 dispatch。 + +以下 host-status、`host_dispatch`、cross-session projection 与 refill 规则只描述 plan-v1 的外部 +host contract;它们不由当前 MCP `fastlane_compile` 调用,也不能把 plan-v2 变成 dispatch receipt。 host 通过不超过 3 MiB、有 exact-key 的 `--host-status` 传入 `workflow_id`、当前 lease/binding 与 `routing_context`。后者按 `(task_id, scheduler_role)` 唯一关联完整 diff --git a/mcp-tools/devkit_fastlane/references/efficiency-automation.md b/mcp-tools/devkit_fastlane/references/efficiency-automation.md index b26f017..dc343d0 100644 --- a/mcp-tools/devkit_fastlane/references/efficiency-automation.md +++ b/mcp-tools/devkit_fastlane/references/efficiency-automation.md @@ -158,23 +158,26 @@ still canonical diagnostic input and the required future external-host contract: } ``` -The diagnostic decomposition and inert public blocked-plan hash incorporate -this full binding into `source_plan_hash`. The manifest provides structural fence data, never +The diagnostic decomposition and plan hash incorporate this full binding into +`source_plan_hash`. The manifest provides structural fence data, never execution authority; no environment project ID, root path, worktree path, task-root component, caller-supplied identifier, Python module attribute, or closure can -replace a live host record. This repository has no Desktop-host durable -registry or external private bridge, so its public `compile_fast_lane` and -`fast-lane` CLI do not compare against an in-process provider and cannot -activate V2: a structurally valid V2 request always yields -`NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE` with zero assignments, local -queues, and external-session assignments. An invalid V2 envelope/hash yields +authorize execution. The public Python `compile_fast_lane` and `fast-lane` CLI +remain plan-v1 diagnostics: a structurally valid V2 request yields +`NO_SAFE_WORK/PROJECT_AUTHORITY_UNAVAILABLE` with zero assignments, local queues, +and external-session assignments. An invalid V2 envelope/hash yields `PROJECT_BINDING_INVALID`; so does an inner canonical-v1 `package` that fails pure diagnostic parsing, or an invalid fast-lane request schema/key/byte shell. -Those checks occur before any host, account-usage, or index input is read. V1 yields -`NO_SAFE_WORK/LEGACY_PROJECT_UNBOUND`. -An MCP request that explicitly carries host-private fields such as `host_status`, -account-usage, or index evidence is not a diagnostic plan shell: the public adapter must -reject it as `FASTLANE_REQUEST_INVALID` before compilation. +V1 yields `NO_SAFE_WORK/LEGACY_PROJECT_UNBOUND`. + +MCP `fastlane_compile` resolves the same caller IDs only as selectors through a +read-only RuntimeRoot UoW. Its server-private path requires a current, +`INDEX_READY`, Git-HEAD-bound snapshot whose persisted `include_paths` fully +cover every writer `write_scope`, then emits +`team-efficiency/fast-lane-plan-v2`. The private index/root/Git material is not a +public Python argument or package export. An MCP request that explicitly carries +`host_status`, account usage, index evidence, a root, or Git HEAD is rejected as +`FASTLANE_REQUEST_INVALID` before compilation. Public `bootstrap --apply` and import-callable `apply_bootstrap_plan` are both blocked before any caller plan, path, provider override, closure, or JSON can reach a worktree helper, because no such helper exists in this repository. @@ -319,7 +322,7 @@ workspace, trace id, or snapshot id from Atlas evidence. ## Ultra Fast Lane -`fast-lane` compiles the exact +The public Python/CLI `fast-lane` path compiles the exact `team-efficiency/fast-lane-request-v1` request into a deterministic `team-efficiency/fast-lane-plan-v1` result. It is a pure compiler: all target gates, contexts, receipts, tokens, and workflow operations are inert dispatch descriptors. The @@ -327,6 +330,15 @@ helper performs no model call, agent spawn, remote service contact, gate run, Git mutation, workflow call, lease claim, endpoint bind, or workflow completion. +The MCP tool response uses `team-efficiency/fast-lane-plan-v2`. It fixes +`plan_only=true`, `dispatch_state="not_dispatched"`, and +`execution_authorized=false`; each `team-efficiency/local-writer-plan-v1` +assignment is planned/unclaimed, and path-free +`team-efficiency/local-index-evidence-v1` exposes only binding hashes including +`include_paths_hash` and the compiled writer `scope_hash`. `INDEX_PARTIAL` +rejects both an incomplete snapshot and omitted writer-scope coverage; +`INDEX_STALE` rejects filesystem or Git drift. + The host invokes `fast-lane` with an explicit reasoning effort. `ultra` is the automatic activation path (`ultra_auto`); lower efforts require explicit `--enable` (`explicit_opt_in`) and otherwise return an inactive plan. diff --git a/mcp-tools/devkit_fastlane/scripts/team_efficiency.py b/mcp-tools/devkit_fastlane/scripts/team_efficiency.py index 99938ee..591f33c 100644 --- a/mcp-tools/devkit_fastlane/scripts/team_efficiency.py +++ b/mcp-tools/devkit_fastlane/scripts/team_efficiency.py @@ -708,6 +708,120 @@ "complex": "Terra Max", "exceptional": "Sol High", } +_LOCAL_ROUTE_MODELS = { + "Luna": "gpt-5.6-luna", + "Terra": "gpt-5.6-terra", + "Sol": "gpt-5.6-sol", +} +_LOCAL_ROUTE_FLOOR_RANKS = { + "low": 20, + "medium": 40, + "high": 60, + "xhigh": 80, + "max": 100, +} +_LOCAL_PLAN_MATERIAL_FIELDS = frozenset( + { + "workspace_id", + "root_identity_hash", + "workspace_binding_hash", + "snapshot_id", + "snapshot_attestation_hash", + "head_hash", + "manifest_hash", + "parser_set_hash", + "workspace_root", + "git_head", + "include_paths", + "include_paths_hash", + } +) +_LOCAL_PLAN_V2_FIELDS = frozenset( + { + "schema", + "status", + "decision_code", + "plan_only", + "dispatch_state", + "execution_authorized", + "activation", + "source_plan_hash", + "phase", + "subagent_capacity", + "assignments", + "idle_slots", + "index_evidence", + "workflow_policy", + "plan_hash", + } +) +_LOCAL_PLAN_ASSIGNMENT_FIELDS = frozenset( + { + "schema", + "slot_id", + "task_id", + "role", + "goal", + "output_boundary", + "execution_state", + "dispatch_state", + "execution_authorized", + "lease_state", + "worktree", + "model", + "reasoning_effort", + "routing_context_hash", + "routing_result_hash", + "task_fingerprint", + "routing_reason_codes", + "depends_on", + "required_evidence", + "index_evidence", + "plan_item_id", + } +) +_LOCAL_PLAN_WORKTREE_FIELDS = frozenset( + { + "state", + "identity_hash", + "base_commit", + "branch", + "write_scope", + "write_scope_hash", + } +) +_LOCAL_PLAN_READ_WORKTREE_FIELDS = frozenset( + {"state", "identity_hash", "write_scope", "write_scope_hash"} +) +_LOCAL_PLAN_POLICY_FIELDS = frozenset( + { + "schema", + "consumer", + "dispatch_tool", + "explicit_route_required", + "lease_claim_required_before_execution", + "compiler_side_effects", + } +) +_LOCAL_PLAN_INDEX_EVIDENCE_FIELDS = frozenset( + { + "schema", + "workspace_id", + "root_identity_hash", + "workspace_binding_hash", + "snapshot_id", + "snapshot_attestation_hash", + "head_hash", + "manifest_hash", + "parser_set_hash", + "include_paths_hash", + "scope_hash", + "evidence_hash", + } +) +_LOCAL_PLAN_ACTIVATION_FIELDS = frozenset({"reasoning_effort", "reason"}) +_LOCAL_PLAN_INACTIVE_ACTIVATION_FIELDS = frozenset({"reasoning_effort"}) +_LOCAL_PLAN_IDLE_SLOT_FIELDS = frozenset({"slot_id", "reason_code"}) _ARTIFACT_SOURCE_KINDS = frozenset({"explicit_artifact_boundaries"}) _ATLAS_SOURCE_KINDS = frozenset({"code_atlas_packet", "task_episode_graph"}) _ATLAS_PACKET_FIELDS = frozenset( @@ -9443,6 +9557,535 @@ def _fast_lane_apply_index_evidence( return bound +class _FastLaneLocalPlanError(ValueError): + """One precise local planning failure safe to expose at the MCP boundary.""" + + def __init__(self, code: str) -> None: + self.code = code + super().__init__(code) + + +def _fast_lane_local_selector(request: Mapping[str, Any]) -> tuple[str, str]: + """Return registry selectors from a fully validated indexed V2 envelope. + + The opaque values select persisted local state only. They do not grant + execution, lease, worktree, or dispatch authority. + """ + + reason_code, source_identity = _project_execution_block_details(request) + if reason_code != "PROJECT_AUTHORITY_UNAVAILABLE" or source_identity is None: + raise _FastLaneLocalPlanError(reason_code) + authority = _mapping( + source_identity.get("project_authority"), "project authority selector" + ) + return ( + _hash(authority.get("workspace_id"), "project authority.workspace_id"), + _hash( + authority.get("input_snapshot_id"), + "project authority.input_snapshot_id", + ), + ) + + +def _fast_lane_public_value(value: object) -> Any: + """Apply the MCP no-null projection before any public identity is hashed.""" + + if type(value) is dict: + return { + key: _fast_lane_public_value(item) + for key, item in value.items() + if item is not None + } + if type(value) is list: + return [_fast_lane_public_value(item) for item in value if item is not None] + return value + + +def _validated_fast_lane_local_index_evidence(value: object) -> dict[str, Any]: + evidence = _mapping(value, "local plan index evidence") + _exact_keys( + evidence, + _LOCAL_PLAN_INDEX_EVIDENCE_FIELDS, + "local plan index evidence", + ) + if evidence["schema"] != "team-efficiency/local-index-evidence-v1": + raise ValueError("local plan index evidence schema is invalid") + supplied_hash = _hash( + evidence["evidence_hash"], "local plan index evidence.evidence_hash" + ) + normalized = {key: item for key, item in evidence.items() if key != "evidence_hash"} + if supplied_hash != _sha256_json(normalized): + raise ValueError("local plan index evidence hash is invalid") + for field in _LOCAL_PLAN_INDEX_EVIDENCE_FIELDS - {"schema", "evidence_hash"}: + _hash(evidence[field], f"local plan index evidence.{field}") + return dict(evidence) + + +def _validated_fast_lane_local_worktree(value: object, *, role: str) -> dict[str, Any]: + worktree = _mapping(value, "local planned worktree") + expected = ( + _LOCAL_PLAN_WORKTREE_FIELDS + if role == "execution" + else _LOCAL_PLAN_READ_WORKTREE_FIELDS + ) + _exact_keys(worktree, expected, "local planned worktree") + if worktree["state"] != "planned": + raise ValueError("local planned worktree state is invalid") + scopes = _normalised_list( + worktree["write_scope"], + "local planned worktree.write_scope", + _relative_scope, + maximum=MAX_WRITE_SCOPES, + ) + if worktree["write_scope"] != scopes: + raise ValueError("local planned worktree write scope is not canonical") + _hash( + worktree["write_scope_hash"], + "local planned worktree.write_scope_hash", + ) + _hash(worktree["identity_hash"], "local planned worktree.identity_hash") + if role == "execution": + _git_id(worktree["base_commit"], "local planned worktree.base_commit") + _branch(worktree["branch"]) + return dict(worktree) + + +def _validated_fast_lane_local_assignment(value: object) -> dict[str, Any]: + assignment = _mapping(value, "local planned assignment") + _exact_keys( + assignment, + _LOCAL_PLAN_ASSIGNMENT_FIELDS, + "local planned assignment", + ) + if assignment["schema"] != "team-efficiency/local-writer-plan-v1": + raise ValueError("local planned assignment schema is invalid") + role = _text(assignment["role"], "local planned assignment.role", maximum=32) + if role not in _FAST_LANE_ROLES: + raise ValueError("local planned assignment role is invalid") + if ( + assignment["execution_state"] != "plan_only" + or assignment["dispatch_state"] != "not_dispatched" + or assignment["execution_authorized"] is not False + or assignment["lease_state"] != "unclaimed" + ): + raise ValueError("local planned assignment execution state is invalid") + _validated_fast_lane_local_worktree(assignment["worktree"], role=role) + _validated_fast_lane_local_index_evidence(assignment["index_evidence"]) + supplied_id = _hash( + assignment["plan_item_id"], "local planned assignment.plan_item_id" + ) + normalized = { + key: item for key, item in assignment.items() if key != "plan_item_id" + } + if supplied_id != _sha256_json(normalized): + raise ValueError("local planned assignment identity is invalid") + return dict(assignment) + + +def _validated_fast_lane_local_policy(value: object) -> dict[str, Any]: + policy = _mapping(value, "local plan workflow policy") + _exact_keys(policy, _LOCAL_PLAN_POLICY_FIELDS, "local plan workflow policy") + if dict(policy) != { + "schema": "team-efficiency/local-plan-consumer-policy-v1", + "consumer": "main_coordinator", + "dispatch_tool": "collaboration.spawn_agent", + "explicit_route_required": True, + "lease_claim_required_before_execution": True, + "compiler_side_effects": False, + }: + raise ValueError("local plan workflow policy is invalid") + return dict(policy) + + +def _validated_fast_lane_local_plan(value: object) -> dict[str, Any]: + plan = _mapping(value, "fast-lane local plan") + _exact_keys(plan, _LOCAL_PLAN_V2_FIELDS, "fast-lane local plan") + if plan["schema"] != "team-efficiency/fast-lane-plan-v2": + raise ValueError("fast-lane local plan schema is invalid") + if ( + plan["plan_only"] is not True + or plan["dispatch_state"] != "not_dispatched" + or plan["execution_authorized"] is not False + ): + raise ValueError("fast-lane local plan execution state is invalid") + + activation = _mapping(plan["activation"], "fast-lane local plan activation") + activation_fields = ( + _LOCAL_PLAN_ACTIVATION_FIELDS + if "reason" in activation + else _LOCAL_PLAN_INACTIVE_ACTIVATION_FIELDS + ) + _exact_keys(activation, activation_fields, "fast-lane local plan activation") + + index_evidence = _validated_fast_lane_local_index_evidence(plan["index_evidence"]) + assignments = plan["assignments"] + if type(assignments) is not list or len(assignments) > len(FAST_LANE_SLOT_IDS): + raise ValueError("fast-lane local plan assignments are invalid") + for assignment_value in assignments: + assignment = _validated_fast_lane_local_assignment(assignment_value) + if assignment["index_evidence"] != index_evidence: + raise ValueError("local planned assignment index evidence is not bound") + + idle_slots = plan["idle_slots"] + if type(idle_slots) is not list or len(idle_slots) > len(FAST_LANE_SLOT_IDS): + raise ValueError("fast-lane local plan idle slots are invalid") + for index, idle_value in enumerate(idle_slots): + idle = _mapping(idle_value, f"fast-lane local plan idle_slots[{index}]") + _exact_keys( + idle, + _LOCAL_PLAN_IDLE_SLOT_FIELDS, + f"fast-lane local plan idle_slots[{index}]", + ) + + _validated_fast_lane_local_policy(plan["workflow_policy"]) + supplied_hash = _hash(plan["plan_hash"], "fast-lane local plan.plan_hash") + normalized = {key: item for key, item in plan.items() if key != "plan_hash"} + if supplied_hash != _sha256_json(normalized): + raise ValueError("fast-lane local plan hash is invalid") + return dict(plan) + + +def _fast_lane_local_routing_context( + source_plan: Mapping[str, Any], source_plan_hash: str +) -> dict[str, Any]: + """Resolve the package-owned route policy without capability claims. + + These are coordinator inputs, not observations that a model or worker is + currently available. The collaboration boundary remains responsible for + rejecting an unavailable explicit route. + """ + + decisions: dict[tuple[str, str], dict[str, Any]] = {} + reasons: dict[tuple[str, str], str] = {} + for task_id, unit in sorted(_fast_lane_unit_index(source_plan).items()): + route_text = _text( + unit.get("recommended_route"), + f"source unit {task_id}.recommended_route", + maximum=64, + ) + try: + family, effort_text = route_text.split(" ", 1) + model = _LOCAL_ROUTE_MODELS[family] + effort = effort_text.casefold() + floor_rank = _LOCAL_ROUTE_FLOOR_RANKS[effort] + except (KeyError, ValueError) as error: + raise _FastLaneLocalPlanError("FASTLANE_ROUTE_UNAVAILABLE") from error + for role in sorted(_FAST_LANE_ROLES): + route_input = { + "schema": "team-efficiency/local-route-input-v1", + "source_plan_hash": source_plan_hash, + "task_id": task_id, + "role": role, + "recommended_route": route_text, + } + route_result = { + "schema": "team-efficiency/local-route-result-v1", + "task_id": task_id, + "role": role, + "model": model, + "reasoning_effort": effort, + "source": "package_policy", + } + decisions[(task_id, role)] = { + "model": model, + "reasoning_effort": effort, + "routing_context_hash": _sha256_json(route_input), + "routing_result_hash": _sha256_json(route_result), + "task_fingerprint": _sha256_json( + { + "task_id": task_id, + "goal": unit.get("goal"), + "write_scope": unit.get("write_scope", []), + } + ), + "routing_reason_codes": ["package_policy_route"], + "routing_safety_floor_rank": floor_rank, + "routing_render_hash": _sha256_json( + {"model": model, "reasoning_effort": effort} + ), + "routing_input": route_input, + } + reasons[(task_id, role)] = "" + return { + "decisions": decisions, + "reasons": reasons, + "default_reason": "routing_context_missing", + "global_failure_reason": None, + } + + +def _validated_fast_lane_local_material( + material_value: Mapping[str, Any], + *, + request: Mapping[str, Any], + validated: Mapping[str, Any], +) -> tuple[dict[str, str], Path]: + material = _mapping(material_value, "local plan material") + _exact_keys(material, _LOCAL_PLAN_MATERIAL_FIELDS, "local plan material") + workspace_id = _hash(material["workspace_id"], "local material.workspace_id") + snapshot_id = _hash(material["snapshot_id"], "local material.snapshot_id") + root_identity_hash = _hash( + material["root_identity_hash"], "local material.root_identity_hash" + ) + workspace_binding_hash = _hash( + material["workspace_binding_hash"], "local material.workspace_binding_hash" + ) + snapshot_attestation_hash = _hash( + material["snapshot_attestation_hash"], + "local material.snapshot_attestation_hash", + ) + head_hash = _hash(material["head_hash"], "local material.head_hash") + manifest_hash = _hash(material["manifest_hash"], "local material.manifest_hash") + parser_set_hash = _hash( + material["parser_set_hash"], "local material.parser_set_hash" + ) + git_head = _git_id(material["git_head"], "local material.git_head").casefold() + workspace_root = _absolute_path( + material["workspace_root"], "local material.workspace_root" + ).resolve(strict=True) + + selected_workspace_id, selected_snapshot_id = _fast_lane_local_selector(request) + if workspace_id != selected_workspace_id: + raise _FastLaneLocalPlanError("FASTLANE_WORKSPACE_MISMATCH") + if snapshot_id != selected_snapshot_id: + raise _FastLaneLocalPlanError("FASTLANE_SNAPSHOT_MISMATCH") + + include_paths_value = material["include_paths"] + if not isinstance(include_paths_value, Sequence) or isinstance( + include_paths_value, (str, bytes, bytearray) + ): + raise _FastLaneLocalPlanError("FASTLANE_INDEX_BINDING_INVALID") + try: + include_paths = tuple( + _relative_scope(value, "local material.include_paths") + for value in include_paths_value + ) + except (TypeError, ValueError) as error: + raise _FastLaneLocalPlanError("FASTLANE_INDEX_BINDING_INVALID") from error + if include_paths != tuple(sorted(set(include_paths))): + raise _FastLaneLocalPlanError("FASTLANE_INDEX_BINDING_INVALID") + include_paths_hash = _hash( + material["include_paths_hash"], "local material.include_paths_hash" + ) + if include_paths_hash != _sha256_json({"include_paths": include_paths}): + raise _FastLaneLocalPlanError("FASTLANE_INDEX_BINDING_INVALID") + + base_material = { + "workspace_id": workspace_id, + "root_identity_hash": root_identity_hash, + "workspace_binding_hash": workspace_binding_hash, + } + if workspace_binding_hash != _sha256_json( + { + "workspace_id": workspace_id, + "root_identity_hash": root_identity_hash, + } + ): + raise _FastLaneLocalPlanError("FASTLANE_INDEX_BINDING_INVALID") + if head_hash != _sha256_json({"head": git_head}): + raise _FastLaneLocalPlanError("FASTLANE_GIT_BINDING_INVALID") + if snapshot_attestation_hash != _sha256_json( + { + **base_material, + "snapshot_id": snapshot_id, + "head_hash": head_hash, + "manifest_hash": manifest_hash, + "parser_set_hash": parser_set_hash, + } + ): + raise _FastLaneLocalPlanError("FASTLANE_INDEX_BINDING_INVALID") + + state = _mapping(validated["scheduler_state"], "scheduler state") + if state["integration_state"]["commit"].casefold() != git_head: + raise _FastLaneLocalPlanError("FASTLANE_GIT_HEAD_MISMATCH") + for context in validated["execution_contexts"]: + plan = context["bootstrap_plan"] + repo = _absolute_path(plan["repo"], "bootstrap plan.repo").resolve(strict=True) + if _fast_lane_path_identity(repo) != _fast_lane_path_identity(workspace_root): + raise _FastLaneLocalPlanError("FASTLANE_WORKSPACE_MISMATCH") + if plan["base_commit"].casefold() != git_head: + raise _FastLaneLocalPlanError("FASTLANE_GIT_HEAD_MISMATCH") + + scopes = sorted( + { + scope + for unit in validated["source_plan"].get("units", []) + for scope in unit.get("write_scope", []) + } + ) + if include_paths and any( + not any( + scope == include or scope.startswith(f"{include}/") + for include in include_paths + ) + for scope in scopes + ): + raise _FastLaneLocalPlanError("INDEX_PARTIAL") + public_evidence = { + "schema": "team-efficiency/local-index-evidence-v1", + **base_material, + "snapshot_id": snapshot_id, + "snapshot_attestation_hash": snapshot_attestation_hash, + "head_hash": head_hash, + "manifest_hash": manifest_hash, + "parser_set_hash": parser_set_hash, + "include_paths_hash": include_paths_hash, + "scope_hash": _sha256_json(scopes), + } + public_evidence["evidence_hash"] = _sha256_json(public_evidence) + return public_evidence, workspace_root + + +def _fast_lane_local_assignment_output( + validated: Mapping[str, Any], + assignment: Mapping[str, Any], + *, + index_evidence: Mapping[str, str], +) -> dict[str, Any]: + task_id = _task_id(assignment["task_id"], "planned assignment.task_id") + role = _text(assignment["role"], "planned assignment.role", maximum=32) + unit = _fast_lane_unit_index(validated["source_plan"])[task_id] + context = _mapping(assignment.get("_context"), "planned assignment context") + execution_context = next( + ( + item + for item in validated["execution_contexts"] + if item["task_id"] == task_id + ), + None, + ) + if role == "execution" and execution_context is None: + raise _FastLaneLocalPlanError("FASTLANE_EXECUTION_CONTEXT_MISSING") + read_context = next( + ( + item + for item in validated["read_contexts"] + if item["task_id"] == task_id and item["role"] == role + ), + None, + ) + if role != "execution" and read_context is None: + raise _FastLaneLocalPlanError("FASTLANE_READ_CONTEXT_MISSING") + bootstrap = ( + None if execution_context is None else execution_context["bootstrap_plan"] + ) + worktree_path = ( + bootstrap["worktree"] + if role == "execution" and bootstrap is not None + else read_context["worktree"] + ) + worktree_value = { + "state": "planned", + "identity_hash": _sha256_json({"worktree": worktree_path}), + "base_commit": context["base_commit"], + "branch": context["branch"], + "write_scope": list(unit.get("write_scope", [])), + "write_scope_hash": context["write_scope_hash"], + } + worktree = _fast_lane_public_value(worktree_value) + if type(worktree) is not dict: + raise ValueError("local planned worktree is invalid") + item_value = { + "schema": "team-efficiency/local-writer-plan-v1", + "slot_id": assignment["slot_id"], + "task_id": task_id, + "role": role, + "goal": unit["goal"], + "output_boundary": unit["output_boundary"], + "execution_state": "plan_only", + "dispatch_state": "not_dispatched", + "execution_authorized": False, + "lease_state": "unclaimed", + "worktree": worktree, + "model": assignment["model"], + "reasoning_effort": assignment["reasoning_effort"], + "routing_context_hash": assignment["routing_context_hash"], + "routing_result_hash": assignment["routing_result_hash"], + "task_fingerprint": assignment["task_fingerprint"], + "routing_reason_codes": list(assignment["routing_reason_codes"]), + "depends_on": list(unit.get("depends_on", [])), + "required_evidence": list(unit.get("required_evidence", [])), + "index_evidence": dict(index_evidence), + } + item = _fast_lane_public_value(item_value) + if type(item) is not dict: + raise ValueError("local planned assignment is invalid") + item["plan_item_id"] = _sha256_json(item) + return _validated_fast_lane_local_assignment(item) + + +def _compile_fast_lane_local_plan( + request: Mapping[str, Any], + *, + reasoning_effort: str, + enable: bool, + local_plan_material: Mapping[str, Any], +) -> dict[str, Any]: + activation = _fast_lane_activation(reasoning_effort, enable) + validated = _validated_fast_lane_request(request) + validated = { + **validated, + "routing_context": _fast_lane_local_routing_context( + validated["source_plan"], validated["source_plan_hash"] + ), + } + if validated["scheduler_state"]["running_assignments"]: + raise _FastLaneLocalPlanError("FASTLANE_RUNTIME_STATE_UNSUPPORTED") + index_evidence, _workspace_root = _validated_fast_lane_local_material( + local_plan_material, + request=request, + validated=validated, + ) + if activation["reason"] is None: + planned_assignments: list[dict[str, Any]] = [] + idle_slots = _fast_lane_idle_slots("OPT_IN_REQUIRED") + status = "inactive" + decision_code = "EXPLICIT_OPT_IN_REQUIRED" + else: + selected, idle_slots = _fast_lane_select_actions(validated, activation) + planned_assignments = [ + _fast_lane_local_assignment_output( + validated, assignment, index_evidence=index_evidence + ) + for assignment in selected + ] + status = "planned" if planned_assignments else "blocked" + decision_code = ( + "FAST_LANE_PLANNED" if planned_assignments else "NO_SAFE_LOCAL_PLAN" + ) + result_value: dict[str, Any] = { + "schema": "team-efficiency/fast-lane-plan-v2", + "status": status, + "decision_code": decision_code, + "plan_only": True, + "dispatch_state": "not_dispatched", + "execution_authorized": False, + "activation": dict(activation), + "source_plan_hash": validated["source_plan_hash"], + "phase": validated["scheduler_state"]["phase"], + "subagent_capacity": len(FAST_LANE_SLOT_IDS), + "assignments": planned_assignments, + "idle_slots": list(idle_slots), + "index_evidence": index_evidence, + "workflow_policy": { + "schema": "team-efficiency/local-plan-consumer-policy-v1", + "consumer": "main_coordinator", + "dispatch_tool": "collaboration.spawn_agent", + "explicit_route_required": True, + "lease_claim_required_before_execution": True, + "compiler_side_effects": False, + }, + } + result = _fast_lane_public_value(result_value) + if type(result) is not dict: + raise ValueError("fast-lane local plan is invalid") + result["plan_hash"] = _sha256_json(result) + result = _validated_fast_lane_local_plan(result) + if len(_json_bytes(result)) > MAX_MANIFEST_BYTES: + raise ValueError("fast-lane plan exceeds its byte budget") + return result + + def compile_fast_lane( request: Mapping[str, Any], *, diff --git a/mcp-tools/project_index/service.py b/mcp-tools/project_index/service.py index 92dc753..d214fae 100644 --- a/mcp-tools/project_index/service.py +++ b/mcp-tools/project_index/service.py @@ -752,6 +752,62 @@ def host_attestation_material( ) return material + def local_plan_material( + self, + workspace_id: str, + *, + snapshot_id: str, + ) -> dict[str, object]: + """Return current registry, index, and Git facts for local planning. + + Unlike ``host_attestation_material``, this projection is consumed only + inside the MCP process. The root and raw Git head are deliberately + private inputs used to bind caller selectors and dry-run worktree + descriptors to the registered workspace; callers receive only the + path-free evidence projection produced by the Fast Lane compiler. + """ + + registered_id, root = self._workspace_for_reference(workspace_id) + material = self.host_attestation_material( + registered_id, + snapshot_id=snapshot_id, + ) + snapshot = self._require_snapshot(registered_id, snapshot_id) + if snapshot.state is not IndexState.INDEX_READY: + raise IndexError( + snapshot.state.name, + "Fast Lane planning requires an INDEX_READY project index snapshot", + ) + try: + include_paths = self._store.include_paths(snapshot.snapshot_id) + normalized_include_paths = self._normalize_paths(include_paths) + except (IndexError, StoreError, sqlite3.DatabaseError, ValueError) as exc: + raise IndexError( + "INDEX_CORRUPT", "project index include paths are corrupt" + ) from exc + if include_paths != normalized_include_paths: + raise IndexError( + "INDEX_CORRUPT", "project index include paths are not canonical" + ) + git_head = _git_head(root) + if git_head is None: + raise IndexError( + "GIT_WORKSPACE_REQUIRED", + "Fast Lane planning requires a registered Git workspace", + ) + if snapshot.head is None or snapshot.head.casefold() != git_head: + raise IndexError( + "INDEX_STALE", + "project index snapshot does not match the Git HEAD", + ) + return { + **material, + "workspace_root": str(root), + "git_head": git_head, + "include_paths": include_paths, + "include_paths_hash": _opaque_hash({"include_paths": include_paths}), + } + def _public_query_projection( self, workspace_id: str, receipt: QueryReceipt ) -> dict[str, object]: diff --git a/mcp-tools/pyproject.toml b/mcp-tools/pyproject.toml index fd9823d..e22f84b 100644 --- a/mcp-tools/pyproject.toml +++ b/mcp-tools/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "2718lab-devkit-mcp" -version = "1.1.3" +version = "1.1.4" description = "MCP runtime for the 2718lab DevKit primary plugin." requires-python = ">=3.11" dependencies = [ diff --git a/mcp-tools/server.py b/mcp-tools/server.py index 1a6c546..4d04780 100644 --- a/mcp-tools/server.py +++ b/mcp-tools/server.py @@ -3,10 +3,7 @@ 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 @@ -362,45 +359,6 @@ def _current_index_correlation() -> str | 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 @@ -1011,346 +969,51 @@ def fastlane_compile( reasoning_effort: Literal["low", "medium", "high", "xhigh", "max"], enable: bool = False, ) -> dict[str, object]: - """Compile inert Fast Lane descriptors without receiving host-private evidence. + """Compile a locally verified, side-effect-free Fast Lane writer plan. - The host remains responsible for capability attestations, worktree/lease fencing, - model dispatch, terminal receipts, and execution. This MCP boundary only - validates and renders the deterministic local scheduling plan. + Caller workspace and snapshot identifiers are selectors only. A read-only + runtime UoW resolves them through the local registry, verifies the current + index and Git HEAD, and supplies private facts to the compiler. The result + never dispatches, creates a worktree, claims a lease, or authorizes execution. """ 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 - - try: - plan = compile_fast_lane( - request, - reasoning_effort=reasoning_effort, - enable=enable, - ) - return envelope_success(_fastlane_public_value(plan)) - except ResultContractError: - return _failure("INTERNAL_ERROR") - except (TypeError, ValueError): - 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_fastlane.scripts.team_efficiency import ( + _compile_fast_lane_local_plan, + _fast_lane_local_selector, + _FastLaneLocalPlanError, ) - from devkit_runtime.host_bridge import ( - FastLaneRefillRegistryRequest, - OperationReceipt, - ) - from devkit_runtime.host_session import HostRoute, HostSession + from devkit_runtime.config import RuntimeConfigError from devkit_runtime.tool_result import ResultContractError, envelope_success + from project_index.models import IndexError as ProjectIndexError 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") - 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 ( - 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_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_storage_budgets = { - unit["task"]["task_id"]: unit["storage_budget"] - for unit in initial_units - if "storage_budget" in unit - } - # The compiler/profile exchange can attest only the live initial - # skeletons. Remaining work is materialized later by the Host-owned - # refill registry, which has no storage-profile proof channel yet. - # Budgeted successors therefore fail closed before publication. - if len(initial_storage_budgets) not in {0, len(initial_units)} or any( - "storage_budget" in unit for unit in remaining_units - ): - return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") - initial_task_ids = {unit["task"]["task_id"] for unit in initial_units} - 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 - 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=remaining_requests, - attestation_items=[ - item - for item in routing_snapshot.attestations - if item["task_id"] not in initial_task_ids - ], + workspace_id, snapshot_id = _fast_lane_local_selector(request) + with _runtime_root().open_uow(read_only=True) as uow: + local_plan_material = ( + uow.project_checkpoint.project_index.local_plan_material( + workspace_id, + snapshot_id=snapshot_id, + ) ) - 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", - ) - raw_skeletons = compiled.get("assignment_skeletons") - raw_remaining_skeletons = compiled_remaining.get("assignment_skeletons") - if type(raw_skeletons) is not list or type(raw_remaining_skeletons) is not list: - return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") - skeletons: list[dict[str, object]] = [] - remaining_skeletons: list[dict[str, object]] = [] - for raw_skeleton, destination in ( - (raw_skeletons, skeletons), - (raw_remaining_skeletons, remaining_skeletons), - ): - for skeleton in raw_skeleton: - if ( - type(skeleton) is not dict - or type(skeleton.get("task_id")) is not str - ): - return _failure("FASTLANE_HOST_AUTHORITY_UNAVAILABLE") - destination.append(skeleton) - skeleton_package_hash = validate_authenticated_v5_skeleton_package( - projected["all_units"], - skeletons, - remaining_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, - storage_budgets=initial_storage_budgets, - ) - 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_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=remaining_skeletons, - index_attestation_refs=[ - { - "task_id": skeleton["task_id"], - **{ - field: index_attestation[field] - for field in attestation_ref_fields - }, - } - for skeleton in remaining_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() + plan = _compile_fast_lane_local_plan( + request, + reasoning_effort=reasoning_effort, + enable=enable, + local_plan_material=local_plan_material, ) - queued_ids = [skeleton["task_id"] for skeleton in remaining_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, - } - ) + return envelope_success(plan) + except _FastLaneLocalPlanError as error: + return _failure(error.code) + except ProjectIndexError as error: + return _failure(error.code) + except RuntimeConfigError as error: + return _runtime_failure(error) 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.""" - - if type(value) is dict: - return { - key: _fastlane_public_value(item) - for key, item in value.items() - if item is not None - } - if type(value) is list: - return [_fastlane_public_value(item) for item in value if item is not None] - return value + except (TypeError, ValueError): + return _failure("FASTLANE_REQUEST_INVALID") @mcp.tool(annotations=_tool_annotations("relay_start")) diff --git a/mcp-tools/tests/test_bugkiller_metadata.py b/mcp-tools/tests/test_bugkiller_metadata.py index 6192c4b..a7ff2bf 100644 --- a/mcp-tools/tests/test_bugkiller_metadata.py +++ b/mcp-tools/tests/test_bugkiller_metadata.py @@ -39,7 +39,7 @@ def test_primary_plugin_manifest_is_stable_v1_and_has_no_prompt_runtime_surface( self, ) -> None: codex = load_json(".codex-plugin/plugin.json") - self.assertEqual("1.1.3", codex["version"]) + self.assertEqual("1.1.4", codex["version"]) self.assertEqual("./.mcp.json", codex["mcpServers"]) for legacy_surface in ("skills", "agents", "commands", "hooks"): self.assertNotIn(legacy_surface, codex) diff --git a/mcp-tools/tests/test_fastlane_runtime.py b/mcp-tools/tests/test_fastlane_runtime.py index 25c946e..ed642fe 100644 --- a/mcp-tools/tests/test_fastlane_runtime.py +++ b/mcp-tools/tests/test_fastlane_runtime.py @@ -1,13 +1,28 @@ from __future__ import annotations +import copy +import inspect +import subprocess import sys +from contextlib import nullcontext +from dataclasses import replace from pathlib import Path +from types import SimpleNamespace +from typing import Any + +import pytest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "mcp-tools")) +import devkit_fastlane # noqa: E402 import server # noqa: E402 from devkit_fastlane.scripts import team_efficiency # noqa: E402 +from devkit_runtime.bootstrap import RuntimeBootstrap # noqa: E402 +from devkit_runtime.composition import RuntimeRoot # noqa: E402 +from devkit_runtime.config import RuntimeConfig # noqa: E402 +from project_index.models import IndexError as ProjectIndexError # noqa: E402 +from project_index.models import IndexState # noqa: E402 sys.path.insert(0, str(ROOT / "mcp-tools" / "devkit_fastlane" / "tests")) @@ -23,6 +38,212 @@ def _sample_request() -> tuple[object, dict[str, object], object]: return helper, helper.fast_lane_request(team_efficiency), tests +def _install_local_plan_runtime(monkeypatch, helper, request) -> str: + """Install one read-only registry/index/Git projection for adapter tests.""" + + project_binding = request["project_binding"] + work_package = request["work_package"] + scheduler_state = request["scheduler_state"] + assert isinstance(project_binding, dict) + assert isinstance(work_package, dict) + assert isinstance(scheduler_state, dict) + integration_state = scheduler_state["integration_state"] + assert isinstance(integration_state, dict) + workspace_id = project_binding["workspace_id"] + snapshot_id = work_package["input_snapshot_id"] + git_head = integration_state["commit"] + assert isinstance(workspace_id, str) + assert isinstance(snapshot_id, str) + assert isinstance(git_head, str) + + marker = team_efficiency._sha256_json + root_identity_hash = marker({"root": workspace_id}) + workspace_binding_hash = marker( + { + "workspace_id": workspace_id, + "root_identity_hash": root_identity_hash, + } + ) + head_hash = marker({"head": git_head}) + manifest_hash = marker({"manifest": snapshot_id}) + parser_set_hash = marker({"parsers": snapshot_id}) + material = { + "workspace_id": workspace_id, + "root_identity_hash": root_identity_hash, + "workspace_binding_hash": workspace_binding_hash, + "snapshot_id": snapshot_id, + "snapshot_attestation_hash": marker( + { + "workspace_id": workspace_id, + "root_identity_hash": root_identity_hash, + "workspace_binding_hash": workspace_binding_hash, + "snapshot_id": snapshot_id, + "head_hash": head_hash, + "manifest_hash": manifest_hash, + "parser_set_hash": parser_set_hash, + } + ), + "head_hash": head_hash, + "manifest_hash": manifest_hash, + "parser_set_hash": parser_set_hash, + "workspace_root": str(helper.repo), + "git_head": git_head, + "include_paths": (), + "include_paths_hash": marker({"include_paths": ()}), + } + + class LocalIndex: + def local_plan_material( + self, supplied_workspace_id: str, *, snapshot_id: str + ) -> dict[str, object]: + assert supplied_workspace_id == workspace_id + assert snapshot_id == material["snapshot_id"] + return dict(material) + + unit_of_work = SimpleNamespace( + project_checkpoint=SimpleNamespace(project_index=LocalIndex()) + ) + + def open_uow(*, read_only: bool): + assert read_only is True + return nullcontext(unit_of_work) + + def host_session_forbidden() -> object: + raise AssertionError("Fast Lane must not consult an inherited Host session") + + monkeypatch.setattr( + server, "_runtime_root", lambda: SimpleNamespace(open_uow=open_uow) + ) + monkeypatch.setattr(server, "_host_session", host_session_forbidden, raising=False) + return snapshot_id + + +def _git(repo: Path, *args: str) -> str: + completed = subprocess.run( + ["git", "-C", str(repo), *args], + check=True, + capture_output=True, + text=True, + ) + return completed.stdout.strip() + + +def _assert_no_nulls(value: object) -> None: + if isinstance(value, dict): + assert all(item is not None for item in value.values()) + for item in value.values(): + _assert_no_nulls(item) + elif isinstance(value, list): + assert all(item is not None for item in value) + for item in value: + _assert_no_nulls(item) + + +def _request_for_persisted_snapshot( + helper, + *, + workspace_id: str, + snapshot_id: str, + git_head: str, +) -> dict[str, object]: + authority = dict(helper.project_binding(team_efficiency)) + authority["workspace_id"] = workspace_id + authority["input_snapshot_id"] = snapshot_id + original_project_binding = helper.project_binding + helper.project_binding = lambda _module, **_kwargs: dict(authority) + try: + request = helper.fast_lane_request(team_efficiency) + finally: + helper.project_binding = original_project_binding + + source_plan = team_efficiency.decompose(request["work_package"]) + units = {unit["task_id"]: unit for unit in source_plan["units"]} + scheduler_state = request["scheduler_state"] + assert isinstance(scheduler_state, dict) + integration_state = scheduler_state["integration_state"] + assert isinstance(integration_state, dict) + integration_state["commit"] = git_head + integration_state["tree"] = _git(helper.repo, "rev-parse", "HEAD^{tree}") + execution_contexts = request["execution_contexts"] + assert isinstance(execution_contexts, list) + for context in execution_contexts: + assert isinstance(context, dict) + task_id = context["task_id"] + unit = units[task_id] + bootstrap = context["bootstrap_plan"] + assert isinstance(bootstrap, dict) + context["bootstrap_plan"] = team_efficiency.build_bootstrap_plan( + task_id=task_id, + base_commit=git_head, + branch=bootstrap["branch"], + write_scope=unit["write_scope"], + repo=helper.repo, + project=bootstrap["project"], + worktree=bootstrap["worktree"], + temp_target=bootstrap["temp_target"], + ) + context["workspace_input_snapshot_id"] = snapshot_id + return request + + +def _install_persistent_local_plan_runtime( + monkeypatch, + helper, + *, + include_paths: list[str] | None = None, + partial: bool = False, +) -> tuple[RuntimeRoot, dict[str, object], str]: + manifest = helper.decomposition_manifest() + source_plan = team_efficiency.decompose(manifest) + for unit in source_plan["units"]: + for scope in unit["write_scope"]: + target = helper.repo / scope + target.parent.mkdir(parents=True, exist_ok=True) + body = ( + "def indexed_scope() -> bool:\n return True\n" + if target.suffix == ".py" + else "# Indexed scope\n" + ) + target.write_text(body, encoding="utf-8") + if partial: + (helper.repo / "unsupported.js").write_text( + "export const unsupported = true;\n", encoding="utf-8" + ) + _git(helper.repo, "init", "--quiet") + _git(helper.repo, "config", "user.name", "Fast Lane Test") + _git(helper.repo, "config", "user.email", "fastlane@example.invalid") + _git(helper.repo, "add", ".") + _git(helper.repo, "commit", "--quiet", "-m", "fixture") + git_head = _git(helper.repo, "rev-parse", "HEAD") + + scratch = helper.temp / "runtime-scratch" + scratch.mkdir() + config = RuntimeConfig.load( + environ={ + "PLUGIN_DATA": str(helper.temp / "runtime-data"), + "CODEX_TASK_TEMP": str(scratch), + } + ) + RuntimeBootstrap.run(config) + root = RuntimeRoot(config) + monkeypatch.setattr(server, "_RUNTIME_ROOT", root) + registered = server.project_index_register(str(helper.repo)) + assert registered["ok"] is True + workspace_id = registered["data"]["workspace_id"] + assert isinstance(workspace_id, str) + synced = server.project_index_sync(workspace_id, include_paths=include_paths) + assert synced["ok"] is True + snapshot_id = synced["data"]["snapshot_id"] + assert isinstance(snapshot_id, str) + request = _request_for_persisted_snapshot( + helper, + workspace_id=workspace_id, + snapshot_id=snapshot_id, + git_head=git_head, + ) + return root, request, snapshot_id + + def test_work_methodology_is_not_a_discoverable_skill() -> None: """Fast Lane contract lives in the MCP runtime, not prompt-discovered skills.""" @@ -32,6 +253,43 @@ def test_work_methodology_is_not_a_discoverable_skill() -> None: assert (runtime / "scripts" / "team_efficiency.py").is_file() +def test_fastlane_docs_describe_the_mcp_plan_v2_boundary() -> None: + contract = (ROOT / "mcp-tools/devkit_fastlane/FASTLANE_CONTRACT.md").read_text( + encoding="utf-8" + ) + readmes = [ + (ROOT / "README.md").read_text(encoding="utf-8"), + (ROOT / "README.zh-CN.md").read_text(encoding="utf-8"), + ] + + for document in (contract, *readmes): + assert "team-efficiency/fast-lane-plan-v2" in document + assert "plan_only" in document + assert "include_paths" in contract + assert "INDEX_PARTIAL" in contract + assert "local_plan_material" not in contract + + +def test_public_compiler_api_cannot_receive_local_authority_material() -> None: + assert ( + "local_plan_material" + not in inspect.signature(team_efficiency.compile_fast_lane).parameters + ) + assert not hasattr(devkit_fastlane, "fast_lane_local_selector") + + helper, request, _ = _sample_request() + try: + with pytest.raises(TypeError): + devkit_fastlane.compile_fast_lane( + request, + reasoning_effort="max", + enable=True, + local_plan_material={}, + ) + finally: + helper.tearDown() + + def test_fastlane_tool_rejects_host_private_inputs() -> None: """Host attestations stay private; public MCP receives only an inert request.""" @@ -47,26 +305,288 @@ def test_fastlane_tool_rejects_host_private_inputs() -> None: assert result["error"]["code"] == "FASTLANE_REQUEST_INVALID" -def test_fastlane_tool_never_spawns_or_executes() -> None: +def test_fastlane_tool_never_spawns_or_executes(monkeypatch) -> None: """The MCP compiler emits descriptors; the host owns execution and refill.""" helper, request, _ = _sample_request() + _install_local_plan_runtime(monkeypatch, helper, request) try: result = server.fastlane_compile( - request=request, reasoning_effort="ultra", enable=True + request=request, reasoning_effort="max", enable=False ) finally: helper.tearDown() assert result["ok"] is True data = result["data"] - assert data["schema"] == "team-efficiency/fast-lane-plan-v1" + assert data["schema"] == "team-efficiency/fast-lane-plan-v2" + assert data["status"] == "inactive" + assert data["assignments"] == [] + assert data["plan_only"] is True + assert data["dispatch_state"] == "not_dispatched" + assert data["execution_authorized"] is False assert "host_actions" not in data - assert data["workflow_policy"]["dispatch_protocol"] == { - "schema": "team-efficiency/fast-lane-dispatch-protocol-v1", - "tool": "collaboration.spawn_agent", - "model_source": "assignment.host_dispatch.model", - "reasoning_effort_source": "assignment.host_dispatch.reasoning_effort", - "inherit_current_session_model": False, - "require_explicit_route": True, - "missing_route_action": "reject", + assert data["workflow_policy"]["compiler_side_effects"] is False + + +def test_fastlane_tool_hashes_the_exact_normalized_inactive_plan(monkeypatch) -> None: + helper, request, _ = _sample_request() + _install_local_plan_runtime(monkeypatch, helper, request) + try: + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=False + ) + finally: + helper.tearDown() + + assert result["ok"] is True + data = result["data"] + _assert_no_nulls(data) + assert data["plan_hash"] == team_efficiency._sha256_json( + {key: value for key, value in data.items() if key != "plan_hash"} + ) + + +def test_fastlane_tool_hashes_the_exact_normalized_read_only_assignment( + monkeypatch, +) -> None: + helper, _, _ = _sample_request() + request = helper.fast_lane_schedule_request(team_efficiency) + _install_local_plan_runtime(monkeypatch, helper, request) + try: + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=True + ) + finally: + helper.tearDown() + + assert result["ok"] is True, result + data = result["data"] + read_only = next( + assignment + for assignment in data["assignments"] + if assignment["role"] != "execution" + ) + _assert_no_nulls(read_only) + assert read_only["plan_item_id"] == team_efficiency._sha256_json( + {key: value for key, value in read_only.items() if key != "plan_item_id"} + ) + assert data["plan_hash"] == team_efficiency._sha256_json( + {key: value for key, value in data.items() if key != "plan_hash"} + ) + + +def test_fastlane_plan_v2_rejects_unknown_keys_at_each_contract_layer( + monkeypatch, +) -> None: + helper, request, _ = _sample_request() + _install_local_plan_runtime(monkeypatch, helper, request) + try: + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=True + ) + finally: + helper.tearDown() + + assert result["ok"] is True + plan = result["data"] + + def add_plan_key(value: dict[str, Any]) -> None: + value["unexpected"] = True + + def add_assignment_key(value: dict[str, Any]) -> None: + value["assignments"][0]["unexpected"] = True + + def add_worktree_key(value: dict[str, Any]) -> None: + value["assignments"][0]["worktree"]["unexpected"] = True + + def add_policy_key(value: dict[str, Any]) -> None: + value["workflow_policy"]["unexpected"] = True + + for mutate in ( + add_plan_key, + add_assignment_key, + add_worktree_key, + add_policy_key, + ): + tampered = copy.deepcopy(plan) + mutate(tampered) + with pytest.raises(ValueError, match="unsupported fields"): + team_efficiency._validated_fast_lane_local_plan(tampered) + + +def test_fastlane_tool_plans_writers_from_current_local_index_without_host( + monkeypatch, +) -> None: + """A current local index is sufficient for a non-dispatched writer plan.""" + + helper, request, _ = _sample_request() + snapshot_id = _install_local_plan_runtime(monkeypatch, helper, request) + try: + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=True + ) + finally: + helper.tearDown() + + assert result["ok"] is True + data = result["data"] + assert data["schema"] == "team-efficiency/fast-lane-plan-v2" + assert data["status"] == "planned" + assert data["plan_only"] is True + assert data["dispatch_state"] == "not_dispatched" + assert data["execution_authorized"] is False + writers = [ + assignment + for assignment in data["assignments"] + if assignment["role"] == "execution" + ] + assert writers + assert all(assignment["execution_state"] == "plan_only" for assignment in writers) + assert all( + assignment["dispatch_state"] == "not_dispatched" for assignment in writers + ) + assert all(assignment["execution_authorized"] is False for assignment in writers) + assert all(assignment["lease_state"] == "unclaimed" for assignment in writers) + assert all(assignment["worktree"]["state"] == "planned" for assignment in writers) + assert all( + assignment["index_evidence"]["snapshot_id"] == snapshot_id + for assignment in writers + ) + forbidden = { + "action", + "host_dispatch", + "assignment_token", + "dispatch_receipt", + "terminal_receipt", } + assert all(forbidden.isdisjoint(assignment) for assignment in writers) + assert "host_status_unavailable" not in str(data) + + +def test_fastlane_tool_uses_persisted_runtime_registry_index_and_git_head( + monkeypatch, +) -> None: + helper, _, _ = _sample_request() + root = None + try: + root, request, snapshot_id = _install_persistent_local_plan_runtime( + monkeypatch, helper + ) + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=True + ) + finally: + if root is not None: + root.shutdown() + helper.tearDown() + + assert result["ok"] is True + data = result["data"] + assert data["schema"] == "team-efficiency/fast-lane-plan-v2" + assert data["status"] == "planned" + assert data["index_evidence"]["snapshot_id"] == snapshot_id + assert data["index_evidence"]["include_paths_hash"].startswith("sha256:") + assert all(item["execution_state"] == "plan_only" for item in data["assignments"]) + + +def test_fastlane_tool_rejects_stale_persisted_snapshot(monkeypatch) -> None: + helper, _, _ = _sample_request() + root = None + try: + root, request, _ = _install_persistent_local_plan_runtime(monkeypatch, helper) + changed = helper.repo / "skills/work-methodology/scripts/team_efficiency.py" + changed.write_text( + "def changed_after_sync():\n return True\n", encoding="utf-8" + ) + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=True + ) + finally: + if root is not None: + root.shutdown() + helper.tearDown() + + assert result["ok"] is False + assert result["error"]["code"] == "INDEX_STALE" + + +def test_fastlane_tool_rejects_partial_persisted_snapshot(monkeypatch) -> None: + helper, _, _ = _sample_request() + root = None + try: + root, request, _ = _install_persistent_local_plan_runtime( + monkeypatch, helper, partial=True + ) + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=True + ) + finally: + if root is not None: + root.shutdown() + helper.tearDown() + + assert result["ok"] is False + assert result["error"]["code"] == "INDEX_PARTIAL" + + +@pytest.mark.parametrize( + "state", + [ + IndexState.INDEX_PARTIAL, + IndexState.INDEX_STALE, + IndexState.INDEX_UNAVAILABLE, + IndexState.INDEX_CORRUPT, + IndexState.HISTORICAL_UNVERIFIED, + ], +) +def test_project_index_local_plan_material_requires_exact_ready_state( + monkeypatch, state: IndexState +) -> None: + helper, _, _ = _sample_request() + root = None + try: + root, request, snapshot_id = _install_persistent_local_plan_runtime( + monkeypatch, helper + ) + project_binding = request["project_binding"] + assert isinstance(project_binding, dict) + workspace_id = project_binding["workspace_id"] + assert isinstance(workspace_id, str) + with root.open_uow(read_only=True) as uow: + index = uow.project_checkpoint.project_index + ready = index._require_snapshot(workspace_id, snapshot_id) + non_ready = replace(ready, state=state) + monkeypatch.setattr( + index, + "_require_snapshot", + lambda supplied_workspace_id, supplied_snapshot_id: non_ready, + ) + with pytest.raises(ProjectIndexError) as captured: + index.local_plan_material(workspace_id, snapshot_id=snapshot_id) + finally: + if root is not None: + root.shutdown() + helper.tearDown() + + assert captured.value.code == state.name + + +def test_fastlane_tool_rejects_snapshot_that_omits_a_writer_scope(monkeypatch) -> None: + helper, _, _ = _sample_request() + source_plan = team_efficiency.decompose(helper.decomposition_manifest()) + first_scope = source_plan["units"][0]["write_scope"][0] + root = None + try: + root, request, _ = _install_persistent_local_plan_runtime( + monkeypatch, helper, include_paths=[first_scope] + ) + result = server.fastlane_compile( + request=request, reasoning_effort="max", enable=True + ) + finally: + if root is not None: + root.shutdown() + helper.tearDown() + + assert result["ok"] is False + assert result["error"]["code"] == "INDEX_PARTIAL" diff --git a/mcp-tools/tests/test_primary_artifact.py b/mcp-tools/tests/test_primary_artifact.py index 0f59b97..8bc1a1d 100644 --- a/mcp-tools/tests/test_primary_artifact.py +++ b/mcp-tools/tests/test_primary_artifact.py @@ -231,14 +231,14 @@ def test_python_project_and_lock_use_pep440_stable_v1_metadata() -> None: with project_path.open("rb") as project_file: project = tomllib.load(project_file) - assert project["project"]["version"] == "1.1.3" + assert project["project"]["version"] == "1.1.4" assert project["project"]["dependencies"] == ["mcp[cli]>=1,<2"] assert "devkit_atlas" in project["tool"]["pyright"]["include"] assert "devkit_runtime" in project["tool"]["pyright"]["include"] assert "code_atlas" not in project["tool"]["pyright"]["include"] lock_text = lock_path.read_text(encoding="utf-8") assert 'name = "2718lab-devkit-mcp"' in lock_text - assert 'version = "1.1.3"' in lock_text + assert 'version = "1.1.4"' in lock_text def test_two_builds_are_byte_identical_with_normalized_zip_metadata( diff --git a/mcp-tools/uv.lock b/mcp-tools/uv.lock index 96cd914..b33ed59 100644 --- a/mcp-tools/uv.lock +++ b/mcp-tools/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "2718lab-devkit-mcp" -version = "1.1.3" +version = "1.1.4" source = { virtual = "." } dependencies = [ { name = "mcp", extra = ["cli"] },