Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Copilot managed release

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
channel:
description: Update channel (prereleases must use preview)
type: choice
options: [stable, preview]
default: stable

permissions:
contents: write

# Publication order also determines the signed monotonic feed sequence.
concurrency:
group: copilot-release-publication
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install release tools
run: python -m pip install "uv==0.11.8" "cryptography>=42" "packaging>=24"
- name: Install test dependencies
run: python -m pip install -e ".[dev,mcp,kb,updater]"
- name: Verify release source before signing
run: python -m pytest -q
- name: Select immutable release and channel
env:
REQUESTED_CHANNEL: ${{ inputs.channel }}
run: |
python - <<'PY'
import os, re
from pathlib import Path
from packaging.version import Version
version = re.search(r'^__version__ = "([^"]+)"', Path("src/infermatrix_copilot/__init__.py").read_text(), re.M).group(1)
parsed = Version(version)
channel = os.environ.get("REQUESTED_CHANNEL") or ("preview" if parsed.is_prerelease or parsed.is_devrelease else "stable")
tag = f"v{version}"
if os.environ["GITHUB_REF_TYPE"] == "tag" and os.environ["GITHUB_REF_NAME"] != tag:
raise SystemExit("Git tag and package version must match")
with open(os.environ["GITHUB_ENV"], "a") as env:
env.write(f"RELEASE_TAG={tag}\nRELEASE_CHANNEL={channel}\n")
PY
- name: Build signed immutable bundle
env:
COPILOT_RELEASE_SIGNING_KEY: ${{ secrets.COPILOT_RELEASE_SIGNING_KEY }}
COPILOT_RELEASE_KEY_ID: ${{ vars.COPILOT_RELEASE_KEY_ID }}
run: |
python scripts/build_copilot_release.py \
--commit "$GITHUB_SHA" \
--channel "$RELEASE_CHANNEL" \
--sequence "$GITHUB_RUN_NUMBER" \
--key-id "$COPILOT_RELEASE_KEY_ID" \
--artifact-base-url "https://github.com/$GITHUB_REPOSITORY/releases/download/$RELEASE_TAG"
- name: Verify clean installation and startup
run: |
uv venv /tmp/copilot-release-smoke
uv pip install --python /tmp/copilot-release-smoke/bin/python \
--require-hashes --only-binary :all: -r dist/release/requirements.txt
uv pip install --python /tmp/copilot-release-smoke/bin/python \
--no-deps dist/release/*.whl
python - <<'PY'
import json, os, subprocess
from pathlib import Path
manifest = json.loads(Path("dist/release/release.json").read_text())["manifest"]
env = os.environ.copy()
env["COPILOT_RELEASE_ID"] = manifest["release_id"]
subprocess.run([
"/tmp/copilot-release-smoke/bin/python", "-m", "infermatrix_copilot.upgrade.health",
"--expected-version", manifest["version"],
"--expected-release", manifest["release_id"],
], check=True, cwd="/tmp", env=env, timeout=60)
PY
- name: Publish immutable release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# No --clobber: publishing the same version twice must fail.
if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
echo "Release already exists; publish a new version instead." >&2
exit 1
fi
flags=(--latest)
if [ "$RELEASE_CHANNEL" = preview ]; then flags=(--prerelease --latest=false); fi
gh release create "$RELEASE_TAG" dist/release/* \
--target "$GITHUB_SHA" --title "InferMatrixCopilot $RELEASE_TAG" \
--generate-notes "${flags[@]}"
- name: Advance signed channel feed
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
feed_tag="auto-update-$RELEASE_CHANNEL"
if ! gh release view "$feed_tag" >/dev/null 2>&1; then
gh release create "$feed_tag" --target "$GITHUB_SHA" \
--title "Copilot $RELEASE_CHANNEL update feed" \
--notes "Signed channel pointer for managed installations." \
--prerelease --latest=false
fi
gh release upload "$feed_tag" "dist/release/copilot-$RELEASE_CHANNEL.json" --clobber
22 changes: 20 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
if "vllm-gr" not in caps.supported_repositories:
sys.exit("installed SDK omits vLLM-GR review support")
dist_version = importlib.metadata.version("infermatrix-copilot")
if not (__version__ == dist_version == caps.distribution_version == "0.2.0"):
if not (__version__ == dist_version == caps.distribution_version):
sys.exit(
"version mismatch: "
f"module={__version__}, metadata={dist_version}, "
Expand Down Expand Up @@ -176,7 +176,7 @@ jobs:
with StrictRuntime(settings_overrides={
"_env_file": None, "run_root": run_root,
}) as strict:
if strict.capabilities().distribution_version != "0.2.0":
if strict.capabilities().distribution_version != dist_version:
sys.exit("Strict facade capabilities mismatch")
print("packaged runtime data resolves via _resource_dir:",
", ".join(_RESOURCE_MARKERS))
Expand Down Expand Up @@ -205,3 +205,21 @@ jobs:
python knowledge/tools/check_wiki_lint.py
python -m pytest -q knowledge/tools/test_check_knowledge_tree.py \
knowledge/tools/test_check_review_report.py

windows-upgrade:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install upgrade test dependencies
run: python -m pip install -e ".[dev,mcp,kb,updater]"
- name: Verify Windows file locks, launcher and upgrade persistence
run: >-
python -m pytest -q
test/test_upgrade_runtime.py
test/test_upgrade_bootstrap.py
test/test_upgrade_manager.py
test/test_upgrade_releases.py
test/test_upgrade_workflows.py
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,16 @@ Use InferMatrixCopilot in Direct mode to review this PR.
**Strict / CLI(独立运行):**

```bash
# 创建 venv、editable 安装和 ./infermatrix-copilot 包装器
# 安装托管运行时、自动升级入口和 ./infermatrix-copilot 包装器
bash install.sh
./infermatrix-copilot doctor # 预检:每个 ✗ 都打印确切的修复命令
```

`install.sh` 会在仓库根从模板生成 `.env`(已被 git 忽略,永不覆盖已有的);
默认安装使用托管启动器;通过签名验证和自检的新版本用于后续会话,运行中的任务
保留原版本。签名信任配置、版本固定与回滚见[自动升级指南](doc/guide/auto-upgrades.md)。
开发者可用 `bash install.sh --development` 保留 editable 安装。

`install.sh` 默认建立 `~/.infermatrix-copilot/.env`,保留已有配置和密钥;
Strict 还要在其中配置 `REPO_PATHS`。使用 API 后端时,填写
`ANTHROPIC_API_KEY` 或 `OPENAI_API_KEY`,各自支持可选的 `*_BASE_URL`;
也可以改用订阅制 harness 后端。API 配置、常用 flag 与排查见
Expand All @@ -85,9 +89,10 @@ Strict 还要在其中配置 `REPO_PATHS`。使用 API 后端时,填写

## 配置 .env

`install.sh` / `install-mcp.sh` 会从 [`.env.template`](.env.template) 生成
`.env`(CLI 用仓库根的 `.env`,Direct/MCP 宿主用
`~/.infermatrix-copilot/.env`)。`.env` 已被 git 忽略——**绝不提交**,token
`install.sh` / `install-mcp.sh` 默认建立 `~/.infermatrix-copilot/.env`,
CLI 与 MCP 共用它。`bash install.sh --development` 从
[`.env.template`](.env.template) 生成仓库根的 `.env`;已有配置和密钥均保留。
`.env` 已被 git 忽略——**绝不提交**,token
和机器本地的绝对路径只放这里。

最小可用配置分三层:Direct 只需要仓库映射;Strict/CLI 加模型;rebase 的
Expand Down
20 changes: 18 additions & 2 deletions doc/architecture/SPEC/app/core.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# app/core.py —— 规范

<!-- verified-against: 2026-10-29 -->
<!-- verified-against: 2026-10-30 -->

`Copilot` is the headless workflow application. It resolves a TaskSpec,
delegates reviewer verdict policy to `review.plan_gate`, confirms when needed,
Expand All @@ -13,7 +13,9 @@ Repository checkout, capabilities, and adapter policy come from
use the same context contract. A missing adapter retains the legacy
unknown-capabilities path when a checkout exists; a broken known adapter
blocks the run. Execution rechecks the context after planning and refuses a
changed checkout or policy before starting the executor.
changed checkout or policy before creating run lock files or artifacts. The
validated context selects the shared checkout lock and is checked again after
waiting for that lock, including on resume.

`Copilot` keeps reservation and run-path methods as compatibility delegates
to `app.reservation.RunReservation`. It owns workflow resolution, confirmation,
Expand Down Expand Up @@ -51,3 +53,17 @@ run(`_contained_run_dir` 拒绝越界路径);`--repo/--playbook/--task-par
`workflow_execution._interrupted` 已经写下 `interrupted`,`128 + signal` 的
退出码不得被这里改标成 `failed`(那会让 reconciler 把一个可恢复的 run 当作
失败收尾)。

## Managed runtime identity (2026-10-30)

New CLI and reserved runs record their runtime before execution. Resume selects
one exact run, then delegates to its pinned physical interpreter while retaining
repo/playbook/parameter assertions and rerun checkpoints. Delegated parameters
use lossless JSON; the original runtime validates the saved task. Missing pinned
runtimes block execution instead of substituting current code.

Execution acquires the per-run lock before the shared checkout/PR lock, passes
the held run lock to WorkflowExecution, and releases both after the lifecycle.
The worker holds the shared lock, so restarting its MCP parent does not permit
a second release to modify the same checkout/PR concurrently. Signal-driven
interrupted terminal states retain their existing ownership rules.
10 changes: 9 additions & 1 deletion doc/architecture/SPEC/app/reservation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# app/reservation.py — durable run reservation

<!-- verified-against: 2026-09-26 -->
<!-- verified-against: 2026-10-30 -->

`RunReservation` owns run ID generation and containment, explicit checkout
authorization, request persistence, initial queued status, and idempotent
Expand All @@ -18,3 +18,11 @@ request again before executing it.
Polling may request `must_exist=False` to report a valid but unknown ID;
execution requires an existing directory. `RunService` calls this service
directly, and `Copilot` preserves its old reservation methods as delegates.

## Managed runtime pin (2026-10-30)

A new reservation writes `release.json` before publishing its queued status.
It records the release identity, immutable release directory and physical venv
interpreter without resolving its Python symlink. Reclaimed/idempotent
reservations retain the existing pin. Runtime identity is independent of the
knowledge snapshot pin and never starts an updater.
10 changes: 9 additions & 1 deletion doc/architecture/SPEC/app/run_service.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# app/run_service.py —— 规范

<!-- verified-against: 2026-10-08 -->
<!-- verified-against: 2026-10-30 -->

`RunService` is the durable application boundary shared by the embedded
Strict SDK and MCP transport. It owns policy-checked reserve/start, a queue
Expand Down Expand Up @@ -84,3 +84,11 @@ this change and is independent of the worker count.
## 2026-10-08 Signed containment

When containment is explicitly enabled, reservation issues a private provider receipt for the pinned repository/shared snapshot scope into `knowledge.json`. It identifies conservative publication dependencies; it does not claim every scoped unit was injected. Launch rechecks the receipt and forwards only explicit maintenance configuration to the child. Old reservations without issuance fail closed until reassessed.

## Managed release launches (2026-10-30)

`_launch` selects the interpreter from the reservation's `release.json` and
forwards its runtime identity. It preserves all explicit child settings, scope
receipts, knowledge pins and platform process-isolation behavior. A missing
pinned runtime fails the run; activation never rewrites queued or live workers.
Existing runs without a pin retain their legacy interpreter selection.
14 changes: 12 additions & 2 deletions doc/architecture/SPEC/cli.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# cli/ —— 规范

<!-- verified-against: 2026-10-29 -->
<!-- verified-against: 2026-10-30 -->

`LOC ~1420(6 个文件) · 接口 + 编排门面 · refactor-status: ok`

Expand Down Expand Up @@ -93,7 +93,7 @@ flag CLI 与 `Copilot` 门面:解析 → 过门 → 执行;并持有 run 目
## 依赖(允许)
`engine/*`、`playbooks/*`、`intent`、`task_spec`、`adapters/base`、`push`、
`review/reviewer`、`notify`、`run_trace`、`config`、`ui`、`chat`。
独立服务入口允许惰性委托 `kb_service.cli`、`rfc_service.cli`;服务自身的授权、
独立服务入口允许惰性委托 `kb_service.cli`、`rfc_service.cli`、`upgrade.cli`;服务自身的授权、
状态与执行契约分别属于对应服务规范。
**任何下层都不得 import 它**(**§ARCH.4.2**)。

Expand Down Expand Up @@ -131,3 +131,13 @@ RFC 子命令原样转发与共享入口:`test_rfc_integration.py`;服务契
`--resume` 现在接受可选 run id;`--repo`、`--playbook`、`--task-param` 与
`--resume` 同用时是对保存任务的断言(见 `app/core`)。
`--rerun-from STEP_ID`(2026-10-29)只能与 `--resume` 同用,否则 argparse 报错。

## 自动升级接入(2026-10-30)

`infermatrix-copilot upgrade …` 在构造 `Copilot` 或模型之前分派到
`upgrade.cli.main`;CLI 包惰性导出 `Copilot`,损坏应用的升级状态与恢复命令仍可用。
`--resume` 固定选中的 run 后,向原 release 的物理解释器转发 `--resume RUN_ID`、
身份断言与 `--rerun-from`。内部 `--resume-params-json` 无损传递保存任务参数的类型。
保留原始 run,不自动重放已经完成的任务。托管安装的执行还在 worker 内持有
跨进程 checkout/PR 锁,以覆盖不同 MCP 进程和 release;运行时固定与控制面契约见
[upgrade.md](upgrade.md)。
13 changes: 11 additions & 2 deletions doc/architecture/SPEC/config.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# config.py —— 规范

<!-- verified-against: 2026-10-27 -->
<!-- verified-against: 2026-10-30 -->

`LOC ~658 · 配置 · refactor-status: oversized`

Expand Down Expand Up @@ -47,7 +47,7 @@ confirmed 依赖及源码/目录 pin 一同验证,空配置只允许当前仓
键一律排除,dotenv 读取 `interpolate=False`——manifest 路径永远不能把凭据拉进
错误信息或日志;Settings 字段在重名时获胜(已体现进程 env 优先)。
- **`imx_knowledge_runtime` 按仓库 fail-closed**:列出的仓库在 resolve 时校验
`MIGRATION_COMPLETE.json`,缺标记即失败;未列出的仓库保持 legacy 路径逐字节不变。
`MIGRATION_COMPLETE.json`,缺标记即失败;未托管安装中未列出的仓库保持 legacy 路径逐字节不变。
- `github_token` 只服务 v3 远端 CI 推送的 **header 认证**(推送传输按设计禁用
credential helper,绝不落共享凭据存储)。
- **`STRICT_BACKEND` 在前面就被校验**,失败时列出合法集合。未知后端**绝不能**到达
Expand Down Expand Up @@ -111,3 +111,12 @@ debug harness 会话的墙钟预算,取代 `ModuleRunConfig.harness_timeout_s`
## 自主进化接入

自主进化新增 `improve_evaluation_mode=objective`、`improve_promotion_mode=automatic`、`improve_evolve_model`;固定单模型,无需判官。总开关与周预算默认不变。

## Managed installation home (2026-10-30)

`INFERMATRIX_COPILOT_HOME` selects the stable user configuration, default run
directory and memory database; it defaults to `~/.infermatrix-copilot`.
The managed CLI can additionally select its existing authoring checkout's
`.env` through `COPILOT_SOURCE_CONFIG`. Source configuration, managed user
configuration, cwd overrides and process environment retain their precedence.
Background upgrades never rewrite model credentials or repository bindings.
12 changes: 10 additions & 2 deletions doc/architecture/SPEC/memory/paths.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# memory/paths.py —— 规范

<!-- verified-against: 2026-08-25 -->
<!-- verified-against: 2026-10-30 -->

`LOC ~262 · 记忆(可变知识位置的唯一解析器 + run/迁移锁) · refactor-status: ok`

Expand All @@ -21,7 +21,7 @@ checkpoint、backups、两把锁)。切换未激活 → 逐消费者的历史
`KnowledgeRunLock(.acquire_shared/.acquire_exclusive/.release)`。

## 不变量(**D4/E2**)
- **切换未激活 = 字节恒等**:未列名仓库解析出的每个位置都与本模块存在前
- **未托管且切换未激活 = 字节恒等**:未列名仓库解析出的每个位置都与本模块存在前
各消费者用的完全一致(含每个 no-adapter 回退)—— 纯重构,由测试钉死。
debug 三成员(`rebase_backend_db`/`shared_write_db`/`debug_read_layers`)
刻意镜像历史的**逐消费者**接线,切换前必须继续互不相同。
Expand Down Expand Up @@ -58,3 +58,11 @@ stdlib(`os`/`dataclasses`/`pathlib`,`fcntl` 带 POSIX 守卫);marker 验
`_require_migration_marker` 是浓缩的 FTS 取证(每个分支对应一条评审发现)——
保持"marker 的声明不是证据"这个立场,别为省事削弱任何一个探针。
PR4d 全量切换落地后,逐消费者的三个 debug 成员应收敛并删掉分叉注释。

## Managed installation paths (2026-10-30)

When `COPILOT_RELEASE_ROOT` identifies a managed installation, immutable adapter
stores remain read fallbacks. Learned debug-memory writes and repository-map
caches use the stable per-repository state directory outside the release.
Explicit knowledge-runtime cutover still validates its migration marker.
Unmanaged installations retain the existing legacy-path contract.
Loading
Loading