Skip to content
Draft
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
23 changes: 23 additions & 0 deletions .github/workflows/gitoxide-helper-admission.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,19 @@ on:
- 'packages/runtime-host/src/server/gitoxide-repository-admission-authority-internal.ts'
- 'packages/runtime-host/src/server/gitoxide-mutation-candidate-receipt-authority-internal.ts'
- 'packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts'
- 'packages/runtime-host/src/server/gitoxide-managed-session-owner-internal.ts'
- 'packages/runtime-host/src/server/packaged-gitoxide-helper-internal.ts'
- 'packages/runtime-host/src/server/hosted-execution-tool-profile.ts'
- 'packages/runtime-host/src/server/execution-composition.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-repository-admission-authority-internal.test.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-managed-write-edit-owner-internal.test.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-managed-session-owner-internal.test.ts'
- 'packages/runtime-host/src/__tests__/packaged-gitoxide-helper.test.ts'
- 'packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts'
- 'scripts/prepare-gitoxide-helper*'
- 'packages/runtime-host/src/__tests__/fixtures/gitoxide-candidate-receipt-crash-child.ts'
- 'packages/runtime-host/src/__tests__/fixtures/gitoxide-managed-write-edit-owner-crash-child.ts'
- 'packages/runtime-host/src/__tests__/fixtures/gitoxide-managed-session-owner-crash-child.ts'
- 'packages/storage/src/execution-stores-workspace-authority-internal.ts'
- 'packages/storage/src/workspace-version-authority-internal.ts'
- 'packages/storage/src/sqlite-runtime-store.ts'
Expand All @@ -47,10 +56,19 @@ on:
- 'packages/runtime-host/src/server/gitoxide-repository-admission-authority-internal.ts'
- 'packages/runtime-host/src/server/gitoxide-mutation-candidate-receipt-authority-internal.ts'
- 'packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts'
- 'packages/runtime-host/src/server/gitoxide-managed-session-owner-internal.ts'
- 'packages/runtime-host/src/server/packaged-gitoxide-helper-internal.ts'
- 'packages/runtime-host/src/server/hosted-execution-tool-profile.ts'
- 'packages/runtime-host/src/server/execution-composition.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-repository-admission-authority-internal.test.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-managed-write-edit-owner-internal.test.ts'
- 'packages/runtime-host/src/__tests__/gitoxide-managed-session-owner-internal.test.ts'
- 'packages/runtime-host/src/__tests__/packaged-gitoxide-helper.test.ts'
- 'packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts'
- 'scripts/prepare-gitoxide-helper*'
- 'packages/runtime-host/src/__tests__/fixtures/gitoxide-candidate-receipt-crash-child.ts'
- 'packages/runtime-host/src/__tests__/fixtures/gitoxide-managed-write-edit-owner-crash-child.ts'
- 'packages/runtime-host/src/__tests__/fixtures/gitoxide-managed-session-owner-crash-child.ts'
- 'packages/storage/src/execution-stores-workspace-authority-internal.ts'
- 'packages/storage/src/workspace-version-authority-internal.ts'
- 'packages/storage/src/sqlite-runtime-store.ts'
Expand Down Expand Up @@ -107,3 +125,8 @@ jobs:
packages/runtime-host/dist/__tests__/gitoxide-helper-invocation-internal.test.js
packages/runtime-host/dist/__tests__/gitoxide-repository-admission-authority-internal.test.js
packages/runtime-host/dist/__tests__/gitoxide-managed-write-edit-owner-internal.test.js
packages/runtime-host/dist/__tests__/gitoxide-managed-session-owner-internal.test.js
packages/runtime-host/dist/__tests__/packaged-gitoxide-helper.test.js
packages/runtime-host/dist/__tests__/hosted-execution-tool-profile.test.js
- name: Test packaged helper preparation
run: node --test scripts/prepare-gitoxide-helper.test.mjs
1 change: 1 addition & 0 deletions apps/desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Playwright E2E run artifacts: traces, videos, screenshots, last-run state.
test-results/
resources/workers/
.generated/
.maka-dev/
.maka-dev.staging/
# Written by the pre-opt-in dev launcher, which ran unconditionally on macOS.
Expand Down
12 changes: 12 additions & 0 deletions apps/desktop/electron-builder.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ const baseDesktopBuilderConfig = {
'dist/renderer/computer-use-overlay/**',
],
extraResources: [
{
from: '.generated/gitoxide-helper/gitoxide',
to: 'gitoxide',
},
{
from: '.generated/gitoxide-helper/gitoxide-helper.json',
to: 'gitoxide-helper.json',
},
{
from: '.generated/gitoxide-helper/THIRD_PARTY_NOTICES.txt',
to: 'licenses/gitoxide-helper/THIRD_PARTY_NOTICES.txt',
},
{
from: 'bundled-tools.json',
to: 'bundled-tools.json',
Expand Down
57 changes: 57 additions & 0 deletions docs/architecture/gitoxide-managed-product-composition-v1.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Gitoxide managed product composition v1

## 主要不变量

只有显式选择 `managed-coding-v1` 的 Session,才能使用 packaged Gitoxide helper 打开 managed epoch,且该
profile 的工具上限只有 Write/Edit。普通 Session 不进入该数据面;helper 缺失或身份不匹配时,managed Session
在 provider dispatch 前失败,禁止从 PATH 发现 Git、回退 attached checkout 或改走普通文件工具。

## Owner 与权限

- product release owner 生成 helper binary、strict manifest 和锁定 Cargo graph 的第三方 notices;
- packaged-resource resolver 只接受 `process.resourcesPath` 内的固定路径,并绑定 platform、arch、bytes、SHA-256、
protocol version 与完整 operation allowlist;
- artifact authority 将 release claim 转为 Runtime Host 私有的 invocation capability;调用者不能提交 executable path;
- managed session owner 从 source root 与 Session ID 派生 durable identity,并向 ToolRuntime 提供 owner-bound
`admitManagedMutation`;
- ToolRuntime 保持 provider result 的唯一 owner,Gitoxide owner 只提交 terminal proof 与 durable successor。

外层已签名应用/安装包是 v1 的发布信任根。本合同防止误打包、manifest/helper 漂移、PATH 注入和普通 Session
误入 managed 数据面;不把本机恶意管理员或可任意改写完整安装目录的进程纳入密码学攻击模型。

## 原子性边界

1. Session header 在任何 T1 以前持久化 `managed-coding-v1`;
2. backend creation 在 provider dispatch 前要求 packaged helper capability,并打开 exact source epoch;
3. Write/Edit tool 在 T1 前取得 managed admission;
4. T1 后只允许 managed mutation state machine 提交 terminal outcome,禁止 generic T2 fallback;
5. Git candidate、SQLite successor 与 accepted ref projection 继续由下层 owner 按既有恢复协议收敛。

package build 的边界是:锁定 Cargo build成功后,复制到 `.generated`、计算 manifest、生成 notices,最后才允许
electron-builder 读取这些资源。最终包验证必须要求 binary、manifest 与 notices 同时存在。

## 失败状态与回滚

| 状态 | 行为 |
| --- | --- |
| dev/CLI Host 没有 packaged helper | 普通 Session 正常;managed Session 明确 `managed_workspace_profile_unavailable` |
| manifest 缺字段、operation list 漂移 | Host 不授予 capability |
| helper bytes/path/platform/arch 不匹配 | fail closed;不从 PATH 替代 |
| source admission/import 失败 | provider dispatch 前失败,不产生 managed T1 |
| T1 后进程退出 | 下层 SQLite/Gitoxide owner 按 durable evidence 恢复,不重跑 Write/Edit |

回滚本切片会删除 `managed-coding-v1` 产品入口和 packaged resource composition;现有普通 Session、SQLite 数据与
attached execution 行为不需要迁移。

## 平台能力矩阵

Linux、macOS、Windows 使用相同 manifest、artifact digest、operation allowlist 与 short-lived helper protocol。
正式 Desktop 当前打包 Windows x64 与 macOS arm64 helper;Linux workflow 继续证明 helper/owner 合同,但本切片
不新增 Linux Desktop 发布物。进程崩溃恢复由三平台 helper workflow 证明;硬件断电与恶意本机管理员不在 v1
新增承诺中。

## 非目标

- 不接入 npm dependency environment;纯 Write/Edit 不依赖 npm。
- 不向 managed profile 暴露 Bash、Read、Glob、Grep 或 attached filesystem worker。
- 不自动迁移普通 Session,也不把 managed capability 当作 resident Host 的隐式 fallback。
51 changes: 51 additions & 0 deletions docs/architecture/gitoxide-managed-session-owner-v1.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Gitoxide managed session owner v1

## 主要不变量

一个显式 managed session 只能从一次受权的 source observation 打开一个 durable workspace epoch。source
import、SQLite baseline 与后续 Write/Edit owner 必须绑定同一 source commit/tree、helper artifact、managed
tree policy 和 session identity。

## Owner 与权限

- Gitoxide repository admission owner 观察 source HEAD,并签发不可伪造的 admission capability;
- Gitoxide import owner 只从该 capability 导入 exact source commit;
- session owner 从 canonical source root 与 session ID 派生 repository/workspace/epoch/instance/version ID,调用者
不能自报 durable identity;
- Execution Stores baseline capability 各自持有一个 process-local imported-repository verifier。SQLite writer
只接收 verifier 返回的 baseline descriptor,不接受裸 commit/tree/epoch;
- Write/Edit owner 只在 durable baseline 成立后签发 mutation admission。

## 原子性与恢复

Git repository import 与 SQLite baseline 不能组成一个物理事务。v1 使用以下有序提交:

1. Gitoxide 将 exact source HEAD 导入 Maka-owned bare repository,并发布 `refs/maka/accepted`;
2. SQLite 原子提交 epoch、baseline version、head 和所有投影;
3. session owner 创建 Write/Edit owner,并验证 accepted ref 与 durable head 一致。

进程若在 1 与 2 之间退出,新 owner重新观察 source,并只允许 exact source commit/tree。Gitoxide import 做 exact
retry,Execution Stores 为新的 process-local proof 重新签发 baseline capability,SQLite 再做 exact retry。不会导入
另一个 source HEAD,也不会覆盖已接受 epoch。

## 失败状态

| 状态 | 行为 |
| --- | --- |
| source 不是 policy-v3、SHA-1 repository | T1/baseline 前拒绝 |
| source 在已打开 epoch 后前进 | fail closed;不会静默 rebaseline |
| helper artifact digest 或 workspace policy 改变 | fail closed;新能力必须显式开新 epoch |
| import 已完成、baseline 未提交 | exact import + baseline retry |
| epoch/head/version 只有部分或彼此不一致 | corruption,拒绝签发 Write/Edit owner |
| accepted ref 落后 durable successor | Write/Edit projection recovery 只重放 candidate/ref CAS |

## 平台能力矩阵

Linux、macOS、Windows 使用同一个 short-lived Gitoxide helper、source admission、import 和 SQLite baseline
协议。三平台 workflow 会在 import 后直接终止子进程,并由新进程证明 exact retry。该合同只覆盖进程崩溃;
macOS/Windows 的硬件断电持久性不由本切片扩大承诺。

## 当前交付边界

本切片是 packaged/Host composition 的 session owner。它不从 PATH 发现 helper,不接受公开 helper 路径,也不
自动把普通 Desktop session 升级成 managed session。发布资源 authority 与显式产品入口由后续组合切片提供。
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,7 @@ Gitoxide helper 与 npm producer 分别保留自己的 trust root。packaged Hos
helper workflow。
- R3:accepted-ref projection 已实现为 accepted truth 的派生 CAS;filesystem checkout projection 仍保持
独立延期,不参与 canonical Write/Edit read/write。
- R4、R5:尚未在新基线上重建。
- R4:session owner 已完成 source admission/import、durable baseline、helper identity/source drift 校验和
import 后进程退出的 exact retry;packaged resource 与 Host 产品入口仍待接线。npm producer 不再作为纯
Write/Edit 恢复链的前置。
- R5:尚未在新基线上重建。
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
"test:dist": "node scripts/run-workspace-tests-parallel.mjs --concurrency=3",
"test:dist:serial": "node scripts/run-workspace-tests-parallel.mjs --serial",
"test:gitoxide-helper": "cargo +1.98.0 test --locked --manifest-path native/gitoxide-helper/Cargo.toml",
"build:gitoxide-helper": "cargo +1.98.0 build --release --locked --manifest-path native/gitoxide-helper/Cargo.toml",
"prepare:gitoxide-helper": "node scripts/prepare-gitoxide-helper.mjs",
"generate:gitoxide-helper-notices": "node scripts/generate-gitoxide-helper-notices.mjs",
"build:runtime-host-peer": "node native/runtime-host-peer/build.mjs",
"lint:runtime-host-peer": "cargo clippy --locked --all-targets --manifest-path native/runtime-host-peer/Cargo.toml -- -D warnings",
"dev": "npm --workspace @maka/desktop run dev:hmr --",
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,11 @@ export function isTurnStatus(value: unknown): value is TurnStatus {
// Header (JSONL line 1)
// ============================================================================

export const SESSION_TOOL_PROFILES = ['headless-coding-v1', 'workhub-coordination-v1'] as const;
export const SESSION_TOOL_PROFILES = [
'headless-coding-v1',
'managed-coding-v1',
'workhub-coordination-v1',
] as const;
export type SessionToolProfile = (typeof SESSION_TOOL_PROFILES)[number];

export function isSessionToolProfile(value: unknown): value is SessionToolProfile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,36 @@ test('hosted execution freezes the headless coding provider wire contract', asyn
);
assert.equal(stableHash(profiled.body.tools), HEADLESS_CODING_V1_TOOLS_HASH);
}

const requestCountBeforeManagedAdmission = provider.requests.length;
const managedOutcome = await composition.handlers['hosted.execution.start'](
{
executionId: '00000000-0000-4000-8000-000000000779',
session: {
workspace: { kind: 'host_path', path: root },
modelTarget: {
kind: 'explicit',
connectionId: connection.connectionId,
connectionSlug: 'profile-deepseek',
model: 'deepseek-v4-flash',
},
permissionMode: 'bypass',
collaborationMode: 'agent',
orchestrationMode: 'default',
toolProfile: 'managed-coding-v1',
},
content: { text: 'Modify the managed workspace.' },
},
context,
);
assert.equal(managedOutcome.ok, true);
if (!managedOutcome.ok || managedOutcome.result.kind !== 'settled') return;
assert.equal(managedOutcome.result.status, 'failed');
assert.match(
managedOutcome.result.failureReason ?? '',
/managed_workspace_profile_unavailable/u,
);
assert.equal(provider.requests.length, requestCountBeforeManagedAdmission);
} finally {
try {
await composition?.close();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { createHash } from 'node:crypto';
import { readFile, realpath, stat } from 'node:fs/promises';
import { openInteractiveExecutionStoresForWrite } from '@maka/storage/execution-stores';
import {
discoverMarkedStorageRoot,
tryAcquireInteractiveRootOwner,
} from '@maka/storage/root-authority';
import {
admitGitoxideHelperArtifactInternal,
issueGitoxideHelperReleaseArtifactClaimInternal,
} from '../../server/gitoxide-helper-artifact-authority-internal.js';
import { openGitoxideManagedSessionOwnerInternal } from '../../server/gitoxide-managed-session-owner-internal.js';

interface Fixture {
readonly storageRoot: string;
readonly sourceRoot: string;
readonly sessionId: string;
readonly helperPath: string;
}

const fixturePath = process.argv[2];
if (!fixturePath) throw new Error('Missing managed session crash fixture path');
const fixture = JSON.parse(await readFile(fixturePath, 'utf8')) as Fixture;
const rootCapability = await discoverMarkedStorageRoot({ path: fixture.storageRoot });
if (rootCapability.kind !== 'interactive') throw new Error('Crash fixture root kind is invalid');
const rootOwner = await tryAcquireInteractiveRootOwner(rootCapability);
if (!rootOwner) throw new Error('Crash fixture could not acquire the storage root');
const stores = await openInteractiveExecutionStoresForWrite(rootOwner.lease);
const helperPath = await realpath(fixture.helperPath);
const helperBytes = await readFile(helperPath);
const helperInfo = await stat(helperPath);
const releaseOwnerToken = {};
const invocationOwnerToken = {};
const helperCapability = await admitGitoxideHelperArtifactInternal({
releaseOwnerToken,
invocationOwnerToken,
claim: issueGitoxideHelperReleaseArtifactClaimInternal(releaseOwnerToken, {
executablePath: helperPath,
expectedSha256: `sha256:${createHash('sha256').update(helperBytes).digest('hex')}`,
expectedBytes: helperInfo.size,
platform: process.platform,
arch: process.arch,
protocolVersion: 1,
supportedOperations: [
'inspect_repository',
'import_source_head',
'create_candidate',
'promote_candidate',
'observe_accepted_ref',
'read_tree_file',
],
}),
});
await openGitoxideManagedSessionOwnerInternal({
storageRootLease: rootOwner.lease,
stores,
invocationOwnerToken,
helperCapability,
sourceRoot: fixture.sourceRoot,
sessionId: fixture.sessionId,
failpoint(point) {
if (point === 'after_repository_import') process.exit(74);
},
});
throw new Error('Crash fixture did not stop after repository import');
Loading
Loading