Skip to content
Draft
13 changes: 4 additions & 9 deletions apps/desktop/src/main/__tests__/create-session-input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,22 @@ describe('resolveCreateSessionRequest', () => {
kind: 'project',
projectId: 'project-1',
}),
'managed-coding-v1',
'managed-coding-v2',
);
assert.equal(
resolveAutomaticWorkspaceToolProfile(ordinary, {
kind: 'host_path',
path: '/workspace/non-git',
}),
'managed-coding-v1',
'managed-coding-v2',
);
assert.equal(
resolveAutomaticWorkspaceToolProfile(
ordinary,
{ kind: 'project', projectId: 'project-1' },
[
'managed-coding-v1',
'managed-coding-v2',
'managed-coding-v3',
'managed-coding-v4',
],
['managed-coding-v2'],
),
'managed-coding-v4',
'managed-coding-v2',
);
assert.throws(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test('Desktop main automatically admits ordinary project sessions to managed cod
client: {
async queryHostExecutionProfiles() {
return {
profiles: ['managed-coding-v1', 'managed-coding-v2'] as const,
profiles: ['managed-coding-v2'] as const,
};
},
async createSession(input: SessionCreateInput) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ test('maps Runtime Host live run state without collapsing unknown and known-empt

test('keeps the immutable managed product identity in the Desktop summary', () => {
const managed = toDesktopHostSessionSummary(
projection({ toolProfile: 'managed-coding-v1' }),
projection({ toolProfile: 'managed-coding-v2' }),
);

assert.equal(managed.toolProfile, 'managed-coding-v1');
assert.equal(managed.toolProfile, 'managed-coding-v2');
});

function projection(overrides: Partial<SessionCatalogProjection> = {}): SessionCatalogProjection {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ test('managed Review reads the accepted tree from Runtime Host', async () => {
allowLocalWorkspace: false,
client: {
async getSession() {
return sessionProjection('managed-coding-v1');
return sessionProjection('managed-coding-v2');
},
async readManagedWorkspaceReview(sessionId: string) {
managedReads += 1;
Expand Down Expand Up @@ -162,7 +162,7 @@ test('managed Review fails closed instead of reading the attached checkout', asy
ipcMain: ipc as never,
client: {
async getSession() {
return sessionProjection('managed-coding-v1');
return sessionProjection('managed-coding-v2');
},
async readManagedWorkspaceReview() {
throw new Error('accepted review unavailable');
Expand Down Expand Up @@ -210,7 +210,7 @@ test('managed workspace Publish delegates one immutable accepted snapshot to Run
ipcMain: ipc as never,
client: {
async getSession() {
return sessionProjection('managed-coding-v1');
return sessionProjection('managed-coding-v2');
},
async readManagedWorkspaceReview() {
throw new Error('not used');
Expand Down Expand Up @@ -258,7 +258,7 @@ test('managed workspace source branch Publish delegates one exact branch request
ipcMain: ipc as never,
client: {
async getSession() {
return sessionProjection('managed-coding-v1');
return sessionProjection('managed-coding-v2');
},
async readManagedWorkspaceReview() {
throw new Error('not used');
Expand Down Expand Up @@ -341,7 +341,7 @@ test('managed workspace Restore delegates one isolated accepted snapshot to Runt
ipcMain: ipc as never,
client: {
async getSession() {
return sessionProjection('managed-coding-v1');
return sessionProjection('managed-coding-v2');
},
async readManagedWorkspaceReview() {
throw new Error('not used');
Expand Down Expand Up @@ -454,7 +454,7 @@ test('managed workspace maintenance delegates one bounded quiet cleanup', async
ipcMain: ipc as never,
client: {
async getSession() {
return sessionProjection('managed-coding-v1');
return sessionProjection('managed-coding-v2');
},
async maintainManagedWorkspace(sessionId: string) {
calls += 1;
Expand Down Expand Up @@ -488,7 +488,7 @@ test('managed workspace lifecycle commands stay bound to the same session', asyn
ipcMain: ipc as never,
client: {
async getSession() {
return sessionProjection('managed-coding-v1');
return sessionProjection('managed-coding-v2');
},
async readManagedWorkspaceHistory(sessionId: string, limit: number) {
assert.equal(sessionId, 'session-managed');
Expand Down Expand Up @@ -589,7 +589,7 @@ test('managed workspace lifecycle commands stay bound to the same session', asyn
});

function sessionProjection(
toolProfile?: 'managed-coding-v1' | 'managed-coding-v2',
toolProfile?: 'managed-coding-v2',
hostCwd = process.cwd(),
): SessionCatalogProjection {
return {
Expand Down
5 changes: 1 addition & 4 deletions apps/desktop/src/main/create-session-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,14 @@ export function resolveCreateSessionRequest(
export function resolveAutomaticWorkspaceToolProfile(
request: ResolvedCreateSessionRequest,
workspace: WorkspaceTarget,
availableProfiles: readonly SessionToolProfile[] = ['managed-coding-v1'],
availableProfiles: readonly SessionToolProfile[] = ['managed-coding-v2'],
): SessionToolProfile | undefined {
if (request.mode !== undefined) return undefined;

switch (workspace.kind) {
case 'project':
case 'host_path': {
if (availableProfiles.includes('managed-coding-v4')) return 'managed-coding-v4';
if (availableProfiles.includes('managed-coding-v3')) return 'managed-coding-v3';
if (availableProfiles.includes('managed-coding-v2')) return 'managed-coding-v2';
if (availableProfiles.includes('managed-coding-v1')) return 'managed-coding-v1';
throw new Error('Managed coding is unavailable in the active Runtime Host.');
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

## 主要不变量

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

## Owner 与权限

- Desktop renderer 只提交 `productIntent: 'managed_coding'`,没有签发 `toolProfile` 的权限;Desktop main
是唯一把该产品意图映射为 `managed-coding-v1` 的 owner,并在 Session 创建前拒绝未知意图或与其它
是唯一把该产品意图映射为 `managed-coding-v2` 的 owner,并在 Session 创建前拒绝未知意图或与其它
start mode 混用;
- product release owner 生成 helper binary、strict manifest 和锁定 Cargo graph 的第三方 notices;
- packaged-resource resolver 只接受 `process.resourcesPath` 内的固定路径,并绑定 platform、arch、bytes、SHA-256、
Expand All @@ -24,7 +24,7 @@ profile 的工具上限只有 Write/Edit。普通 Session 不进入该数据面

## 原子性边界

1. Session header 在任何 T1 以前持久化 `managed-coding-v1`;
1. Session header 在任何 T1 以前持久化 `managed-coding-v2`;
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;
Expand Down Expand Up @@ -55,7 +55,7 @@ electron-builder 读取这些资源。最终包验证必须要求 binary、manif
| managed Run 无法取得 workspace boundary | provider dispatch 前失败;不静默退回普通 Run |
| T1 后进程退出 | 下层 SQLite/Gitoxide owner 按 durable evidence 恢复,不重跑 Write/Edit |

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

## 平台能力矩阵
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

本切片只证明一个主要不变量:

> managed-coding-v1 的 continuation 必须同时绑定不可变 RuntimeEvent 前缀与同一 SQLite 事务读取到的 accepted Git workspace head;任一侧漂移都不得继续 provider dispatch。
> managed-coding-v2 的 continuation 必须同时绑定不可变 RuntimeEvent 前缀与同一 SQLite 事务读取到的 accepted Git workspace head;任一侧漂移都不得继续 provider dispatch。

普通 continuation 仍使用 `continuation_claim_v1`。Managed continuation 使用
`continuation_claim_v2` 和 `continuation_source_v3`,不存在从 v2 静默降级为 v1 的路径。
Expand Down Expand Up @@ -69,8 +69,8 @@ RuntimeEvents + 可重建 workspace authority projection。

## 产品范围

该能力只对显式 `managed-coding-v1` 生效。普通会话行为不变。Desktop 新任务 Composer 已提供显式
该能力只对 canonical `managed-coding-v2` 生效。普通会话行为不变。Desktop 新任务 Composer 已提供显式
`Managed workspace` 产品意图;renderer 不能直接签发 profile,Desktop main 在 Session 创建前把该意图
映射为 immutable `managed-coding-v1`。Catalog 会把 profile 投影回 Desktop,已有 Session 只显示状态、
映射为 immutable `managed-coding-v2`。Catalog 会把 profile 投影回 Desktop,已有 Session 只显示状态、
不能切换。Managed Run 与手动 Resume 不依赖普通 Session 使用的实验开关,但所有 safety observation 仍需
fail closed。Accepted-tree Read/Glob/Grep 尚未接入,因此这只关闭产品入口,不代表完整 M3 已完成。
88 changes: 35 additions & 53 deletions docs/architecture/managed-coding-v2-product-composition.zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,56 @@
# Managed Coding v2 Product Composition

## 1. 为什么是 v2
## 1. Canonical contract

`managed-coding-v1` 已经是持久化 Session 合同:它只有 accepted-world `Read/Glob/Grep/Write/Edit`。直接把
新工具塞进 v1,会让同一 durable profile 在不同版本拥有不同权限,也会让旧 Session 因新 toolchain/sandbox 缺失而
突然无法打开。
`managed-coding-v2` 是第一版、也是当前唯一的 managed coding Session 合同。此前 integration stack 中出现过的
`managed-coding-v1/v3/v4` 从未发布、没有生产消费者,也没有需要迁移的用户数据;它们不再是可读或可协商协议。

因此 v1 保持冻结,v2 只增加一个能力
固定工具集合为

```text
ManagedNodeTest(explicit sorted .js/.mjs/.cjs files)
Read / Glob / Grep / Write / Edit
ManagedNodeTest / ManagedNodeRun / ManagedNodeTransform
```

它不是 Bash、npm script 或任意 command;它只能观察同一个 accepted Git tree。
- Read/Glob/Grep:只读取 accepted Git tree;
- Write/Edit/ManagedNodeTransform:`reconcile + managed_mutation_v2`,用 `operationKind` 区分转换;
- ManagedNodeTest/ManagedNodeRun:`replay_safe + managed_observation_v2`,用 `operationKind` 区分测试与命令;
- Bash、PATH executable、联网安装和 attached checkout 不在 profile 内。

## 2. 主要不变量

> `managed-coding-v2` 只有在一个 Runtime Host 同时拥有 accepted Gitoxide session、current-process Node
> toolchain、enforcing sandbox 与 storage-root execution capability 时才可组合;缺一项必须在 T1 前明确不可用。
> Host 只有同时拥有 Gitoxide accepted-world authority、canonical managed toolchain v2、enforcing sandbox、
> dependency/execution-root authority 和对应 admission owner 时,才能在 T1 前宣告 `managed-coding-v2`;缺少任何
> 能力都必须让整个 profile unavailable,禁止回退到旧 profile 或普通工具。

v2 工具集合固定为:

```text
Read / Glob / Grep / Write / Edit / ManagedNodeTest
```

- Read/Glob/Grep:`replay_safe`,读取 accepted tree;
- Write/Edit:`reconcile + managed_mutation_v2`;
- ManagedNodeTest:`replay_safe + managed_observation_v2`;dependency 输入只能是显式 `none` 或 owner-bound
immutable snapshot lease,禁止从 checkout `node_modules` 回退;
- Bash、npm、package script、PATH executable 与 attached checkout 均不在 profile 内。
Session header、Host handshake、Runtime admission、durable dispatch 和 release manifest 都只接受 canonical v2。
旧 Draft 标识明确 fail closed,不提供 dual reader、migration 或 downgrade。

## 3. Owner 与组合顺序

1. Host boot 尝试 admission packaged Gitoxide helper 与 current-process managed toolchain;缺失只让对应 profile
unavailable,不让普通 Session 获得 fallback;manifest 损坏仍 fail Host boot。
2. Session run 开始时,Gitoxide owner读取 durable epoch/head/version。
3. v2 additionally 组合 command sandbox owner、execution-root owner 与 Node-test admission owner。
4. Run composer 将 exact profile 工具投影给模型,同时把 mutation/observation admission 分别交给 Runtime。
5. Runtime 在 T1 前冻结 mode;T1 后不允许换回 v1、普通 test runner 或 generic T2。

## 4. 失败与兼容

- 旧 `managed-coding-v1` Session 永远不要求 Node toolchain;
- v2 缺 Gitoxide/toolchain/sandbox:run 在 provider 请求前以
`managed_workspace_profile_unavailable` 失败;
- v2 test admission 失败:没有 T1;
- T1 后 helper/Host 失败:按 `managed_observation_v2` exact accepted-tree + dependency boundary recovery 收敛;
- profile 是 Session immutable identity,不允许运行中从 v2 降级 v1。

本切片建立 Host 产品 composition,但不立即把 Desktop 默认创建策略从 v1 切到 v2。默认切换必须与 packaged
Host/helper kill-reopen 和三平台 enforcing sandbox gate 同一交付完成,避免用户拿到未经证明的默认能力。
1. Host boot 验证 Gitoxide helper 与 managed-command toolchain release v2;manifest 损坏 fail Host boot。
2. Desktop 在创建 Session、任何 T1 之前读取 resident Host 的 exact capability set。
3. Gitoxide owner读取 durable epoch/head/version;toolchain、sandbox 和 dependency owner签发不透明 capability。
4. Run composer把固定工具集合投影给模型,并把 mutation/observation admission交给 Runtime。
5. Runtime 在 T1 前冻结 mode、accepted head、operation kind、args 和 execution profile;T1 后禁止 generic fallback。

## 5. Production-shaped crash gate
## 4. 失败与数据断代

默认切换前的 crash gate 不使用同进程异常模拟:
- capability 不完整:Session 创建或 Run admission 明确返回 `managed_workspace_profile_unavailable`;
- preflight 失败:不跨 T1;
- T1 后失败:只按 canonical mutation/observation proof 收敛;
- 旧 Draft SQLite schema 15–17、旧 profile/manifest/payload 不迁移;开发数据库必须备份后清理;
- 正式 main schema 14 只通过一个 migration 15 进入 canonical managed workspace epoch。

1. 使用仓库锁定的 Electron/Node 24 启动真实 Runtime Host;
2. 使用真实 packaged Gitoxide helper、managed-command manifest 与平台 sandbox;
3. provider 发出一次 `ManagedNodeTest`,并在看到 durable tool result 后挂起;
4. 测试强杀整个 Host root process;
5. 第二个 Host 从 SQLite、accepted Git tree 和 continuation facts 自动继续;
6. provider 不得再次发出工具调用,RuntimeEvents 中只能有一对 `function_call/function_response`。
## 5. Crash gate 与平台矩阵

测试由 Gitoxide 三平台 workflow 持有。没有真实 helper 的本地构建只能明确 skip,不能把 skip 计作 crash 证据。
真实 gate 必须启动 packaged/current-process Runtime Host、Gitoxide helper、toolchain 和平台 sandbox,强杀整个 Host
process tree 后从 SQLite + accepted Git facts 继续;已完成工具不得重放。

## 6. 平台矩阵
| 平台 | v2 availability |
| --- | --- |
| Windows | 只有完整 AppContainer/Job 与 Host kill/reopen 证据时可宣告,否则整个 v2 unavailable |
| macOS | signed app + Seatbelt + kill/reopen |
| Linux | distribution authority + Bubblewrap + kill/reopen |

| 平台 | composition 语义 | 默认启用前 gate |
| --- | --- | --- |
| Windows | v2 profile 与 owner graph 可组合 | packaged Electron + AppContainer/Job + kill/reopen |
| macOS | 相同 durable profile | signed app + Seatbelt + kill/reopen |
| Linux | 相同 protocol/build | signed distribution authority + Bubblewrap + kill/reopen |
平台缺能力时采用“v2 或 unavailable”,不保留较弱 profile 作为兼容层。
65 changes: 0 additions & 65 deletions docs/architecture/managed-coding-v3-product-composition.zh-CN.md

This file was deleted.

Loading