From 1932823f1d1f4129134e90f05ea1aa9b26d32231 Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 15:02:42 +0800 Subject: [PATCH 1/9] refactor(managed): converge unpublished protocols on v2 --- .../__tests__/create-session-input.test.ts | 13 +- ...ost-session-catalog-auto-admission.test.ts | 2 +- ...time-host-session-catalog-ipc-main.test.ts | 4 +- .../runtime-host-workspace-ipc-main.test.ts | 16 +- apps/desktop/src/main/create-session-input.ts | 5 +- ...de-managed-product-composition-v1.zh-CN.md | 8 +- ...e-workspace-bound-continuation-v1.zh-CN.md | 6 +- ...ged-coding-v2-product-composition.zh-CN.md | 88 ++--- ...ged-coding-v3-product-composition.zh-CN.md | 65 ---- ...ged-coding-v4-product-composition.zh-CN.md | 56 ---- ...naged-hermetic-command-sandbox-v1.zh-CN.md | 6 +- ...d-hermetic-node-command-kernel-v1.zh-CN.md | 4 +- ...hermetic-node-test-observation-v1.zh-CN.md | 2 +- ...aged-node-test-admission-owner-v1.zh-CN.md | 6 +- .../managed-node-transform-kernel-v1.zh-CN.md | 2 +- ...d-toolchain-artifact-authority-v1.zh-CN.md | 2 +- ...time-durable-coding-m3-m5-roadmap.zh-CN.md | 21 +- ...e-managed-observation-boundary-v1.zh-CN.md | 4 +- ...me-managed-profile-negotiation-v1.zh-CN.md | 10 +- ...aged-workspace-desktop-history-v1.zh-CN.md | 2 +- ...aged-workspace-desktop-publish-v1.zh-CN.md | 2 +- ...aged-workspace-desktop-restore-v1.zh-CN.md | 2 +- .../runtime-resume-architecture.zh-CN.md | 2 +- ...hase4-workspace-checkpoint-design.zh-CN.md | 4 +- .../core/src/__tests__/runtime-event.test.ts | 108 +----- packages/core/src/runtime-event.ts | 314 ++++-------------- packages/core/src/session.ts | 15 +- ...process-managed-toolchain-internal.test.ts | 22 +- .../execution-model-composition.test.ts | 2 +- ...itoxide-managed-continuation-crash.test.ts | 2 +- ...-managed-write-edit-owner-internal.test.ts | 1 + .../host-execution-profiles-protocol.test.ts | 23 +- .../src/__tests__/host-kernel.test.ts | 4 +- .../hosted-execution-tool-profile.test.ts | 122 ++----- .../__tests__/managed-coding-v2-crash.test.ts | 43 +-- ...ged-command-sandbox-owner-internal.test.ts | 4 +- ...e-command-admission-owner-internal.test.ts | 14 +- ...transform-admission-owner-internal.test.ts | 3 +- ...lchain-artifact-authority-internal.test.ts | 6 +- .../session-catalog-coordinator.test.ts | 4 +- .../session-catalog-protocol.test.ts | 2 +- .../runtime-host/src/protocol/host-status.ts | 16 +- ...rent-process-managed-toolchain-internal.ts | 25 +- .../src/server/execution-composition.ts | 31 +- ...oxide-managed-write-edit-owner-internal.ts | 9 +- .../server/hosted-execution-tool-profile.ts | 56 +--- .../managed-command-sandbox-owner-internal.ts | 35 +- ...d-node-command-admission-owner-internal.ts | 36 +- ...node-transform-admission-owner-internal.ts | 8 +- ...d-toolchain-artifact-authority-internal.ts | 2 - .../src/__tests__/session-manager.test.ts | 15 +- .../tool-runtime-durable-boundary.test.ts | 40 +-- packages/runtime/src/tool-runtime.ts | 58 +--- .../sqlite-recovery-concurrency-child.ts | 1 + .../sqlite-recovery-concurrency.test.ts | 6 + .../__tests__/sqlite-runtime-crash.test.ts | 1 + .../__tests__/sqlite-runtime-store.test.ts | 48 ++- ...pace-version-authority-persistence.test.ts | 16 +- packages/storage/src/sqlite-runtime-schema.ts | 20 +- packages/storage/src/sqlite-runtime-store.ts | 21 +- scripts/prepare-managed-command-toolchain.mjs | 10 +- ...prepare-managed-command-toolchain.test.mjs | 10 +- 62 files changed, 451 insertions(+), 1034 deletions(-) delete mode 100644 docs/architecture/managed-coding-v3-product-composition.zh-CN.md delete mode 100644 docs/architecture/managed-coding-v4-product-composition.zh-CN.md diff --git a/apps/desktop/src/main/__tests__/create-session-input.test.ts b/apps/desktop/src/main/__tests__/create-session-input.test.ts index 93c1cd4831..d132146c4b 100644 --- a/apps/desktop/src/main/__tests__/create-session-input.test.ts +++ b/apps/desktop/src/main/__tests__/create-session-input.test.ts @@ -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( () => diff --git a/apps/desktop/src/main/__tests__/runtime-host-session-catalog-auto-admission.test.ts b/apps/desktop/src/main/__tests__/runtime-host-session-catalog-auto-admission.test.ts index 3b5e9daf5f..26dd892cc7 100644 --- a/apps/desktop/src/main/__tests__/runtime-host-session-catalog-auto-admission.test.ts +++ b/apps/desktop/src/main/__tests__/runtime-host-session-catalog-auto-admission.test.ts @@ -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) { diff --git a/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts index bb5b55e96c..c76ce1a904 100644 --- a/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts +++ b/apps/desktop/src/main/__tests__/runtime-host-session-catalog-ipc-main.test.ts @@ -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 { diff --git a/apps/desktop/src/main/__tests__/runtime-host-workspace-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-workspace-ipc-main.test.ts index 4554f10c70..50e91e2278 100644 --- a/apps/desktop/src/main/__tests__/runtime-host-workspace-ipc-main.test.ts +++ b/apps/desktop/src/main/__tests__/runtime-host-workspace-ipc-main.test.ts @@ -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; @@ -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'); @@ -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'); @@ -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'); @@ -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'); @@ -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; @@ -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'); @@ -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 { diff --git a/apps/desktop/src/main/create-session-input.ts b/apps/desktop/src/main/create-session-input.ts index fe591794ab..0cd4678ce3 100644 --- a/apps/desktop/src/main/create-session-input.ts +++ b/apps/desktop/src/main/create-session-input.ts @@ -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.'); } } diff --git a/docs/architecture/gitoxide-managed-product-composition-v1.zh-CN.md b/docs/architecture/gitoxide-managed-product-composition-v1.zh-CN.md index f8ec490cb0..7f29cd9e36 100644 --- a/docs/architecture/gitoxide-managed-product-composition-v1.zh-CN.md +++ b/docs/architecture/gitoxide-managed-product-composition-v1.zh-CN.md @@ -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、 @@ -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; @@ -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 行为不需要迁移。 ## 平台能力矩阵 diff --git a/docs/architecture/gitoxide-workspace-bound-continuation-v1.zh-CN.md b/docs/architecture/gitoxide-workspace-bound-continuation-v1.zh-CN.md index 0c66affb23..bc3cd3ea5e 100644 --- a/docs/architecture/gitoxide-workspace-bound-continuation-v1.zh-CN.md +++ b/docs/architecture/gitoxide-workspace-bound-continuation-v1.zh-CN.md @@ -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 的路径。 @@ -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 已完成。 diff --git a/docs/architecture/managed-coding-v2-product-composition.zh-CN.md b/docs/architecture/managed-coding-v2-product-composition.zh-CN.md index 99fa4101d8..25b411503c 100644 --- a/docs/architecture/managed-coding-v2-product-composition.zh-CN.md +++ b/docs/architecture/managed-coding-v2-product-composition.zh-CN.md @@ -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 作为兼容层。 diff --git a/docs/architecture/managed-coding-v3-product-composition.zh-CN.md b/docs/architecture/managed-coding-v3-product-composition.zh-CN.md deleted file mode 100644 index cd8cd19ca3..0000000000 --- a/docs/architecture/managed-coding-v3-product-composition.zh-CN.md +++ /dev/null @@ -1,65 +0,0 @@ -# Managed Coding v3 产品组合 - -status: stacked Draft - -milestone: M5 foreground command loop - -## 1. 主要不变量 - -Runtime Host 只有同时持有 Gitoxide accepted-world authority、经过 release admission 的 Node 24 toolchain、 -enforcing command sandbox、`ManagedNodeTest` owner 与 `ManagedNodeRun` owner 时,才允许宣告 -`managed-coding-v3`。Desktop 必须在创建 Session、写入任何 T1 以前查询 resident Host 的 capability set, -并冻结最高可用 profile;Session 创建以后禁止回落到 v2、v1、普通 Node、Bash 或 `PATH`。 - -本切片把 `ManagedNodeRun` kernel 接入真实 packaged Host,但仍不是通用 shell:调用者只能选择 accepted tree -中的显式 JavaScript 入口和 exact argv,不能选择 executable、cwd、environment、Node flags、网络策略、 -依赖目录或输出上限。 - -## 2. Owner 与权限边界 - -- release owner:v3 首次使用 `maka_managed_command_toolchain_release_v3` 绑定 test/command effect class; - 后续 v4 release manifest 是严格 superset。旧 v3 Session 仍只获得 v3 工具集合,不会因为读取新 manifest 而获得 - workspace transform 权限; -- current-process toolchain owner:严格验证 manifest、Node 版本、entry bytes/hash 后签发 opaque capability; -- Gitoxide session owner:提供 immutable accepted tree 与 workspace boundary; -- sandbox/process owner:提供 read-only input、disposable scratch、空 `PATH`、无 child process 和受限网络; -- Runtime:拥有 durable T1/T2 与 provider result; -- Desktop:只能从 Host 公布的有序 capability set 中选择 profile,不能自行声称 v3。 - -manifest v3 是新的 packaged release envelope,不是 SQLite schema。旧 v2 manifest 会 fail closed;新构建必须重新 -生成资源,不做 silent compatibility fallback。 - -## 3. 原子性边界 - -pre-Session: - -1. Host 完成 Gitoxide、toolchain、sandbox 与 tool declaration composition; -2. Host 返回 canonical profile set; -3. Desktop 选择并写入一个 immutable Session profile。 - -per-command: - -1. admission 在 T1 前冻结 accepted boundary、entry identity、exact argv、toolchain 与 execution profile; -2. Runtime 写入 `managed_observation_v3`; -3. sandbox 只读取 accepted input,并只允许写 disposable scratch; -4. Runtime 将有界 observation 写入 T2; -5. response 丢失时从 durable outcome 恢复,不重新执行命令。 - -## 4. 失败状态与回滚 - -- capability 缺失或 manifest 不匹配:Session 创建前报告 profile unavailable,不产生 durable operation; -- T1 前 entry/toolchain/sandbox 失败:清理临时 roots,拒绝 dispatch; -- T1 后命令未形成合法 observation:保留 T1,按 replay-safe observation 协议恢复; -- T2 已提交、Host 随后死亡:新 Host 采用 durable response,禁止再次 spawn; -- cleanup 失败:不改变 T2,由 execution-root maintenance 回收 disposable roots。 - -## 5. 平台矩阵 - -| 平台 | v3 产品能力 | 持续证据 | -| --- | --- | --- | -| Linux | 支持 | packaged Electron Host + Gitoxide helper kill/restart gate | -| macOS | 支持 | packaged Electron Host + Gitoxide helper kill/restart gate | -| Windows | 暂不宣告 | 当前只宣告 v1;在独立 Node runtime 与完整网络隔离证据形成前 fail closed | - -平台能力不允许通过执行时 fallback 获得。Windows 的缺口是明确的 capability unavailability,不影响既有 v1 -managed Write/Edit。 diff --git a/docs/architecture/managed-coding-v4-product-composition.zh-CN.md b/docs/architecture/managed-coding-v4-product-composition.zh-CN.md deleted file mode 100644 index 31f4476e3a..0000000000 --- a/docs/architecture/managed-coding-v4-product-composition.zh-CN.md +++ /dev/null @@ -1,56 +0,0 @@ -# Managed coding v4 product composition - -status: packaged product gate - -milestone: M5 workspace transform - -## 1. 主要不变量 - -Desktop 只能在 resident Runtime Host 于 Session 创建前宣告 `managed-coding-v4` 时选择该 profile。v4 精确包含 -v3 的 Read/Glob/Grep/Write/Edit/ManagedNodeTest/ManagedNodeRun,再加入 `ManagedNodeTransform`。旧 v1/v2/v3 -Session 的工具集合和 durable protocol 不变;运行过程中禁止从 v4 降级到早期 profile。 - -## 2. Authority chain - -```text -packaged release v4 manifest - -> current-process toolchain capability - -> enforcing command sandbox owner - -> v4 Gitoxide managed session owner - -> ManagedNodeTransform admission - -> managed_mutation_v3 T1 - -> canonical mutation execution profile v2 - -> Gitoxide candidate - -> SQLite successor acceptance -``` - -release manifest v4 同时绑定 `hermetic_observation_v2`、`hermetic_observation_v3` 和 -`workspace_transform_v1`。v3 或更旧 manifest 必须 fail closed;caller 不能只靠路径或 environment 打开 transform -effect class。 - -Write/Edit 的 `managed_mutation_v2` payload 与 transform 的 `managed_mutation_v3` payload 只区分 operation proof -字段;二者统一绑定 canonical mutation execution profile v2 digest。旧 mutation profile v1 没有生产消费者,本交付 -不读取、不迁移,也不保留 profile-set fallback。 - -## 3. Crash boundary - -production-shaped gate 使用真实 packaged Electron Host、Gitoxide helper、Node sandbox、provider tool call 与 SQLite: - -1. transform 生成 exact output; -2. SQLite 已提交 T2/successor、provider 已观察 tool result,但 assistant turn 尚未结束; -3. kill 整个 Host; -4. 新 Host 自动 continuation; -5. 只能观察到一个 call、一个 response、一个 successor、零 active reservation; -6. transformer 不得第二次执行。 - -accepted ref promotion 若在 kill 时尚未完成,只允许从 durable successor 重放 projection。 - -## 4. 平台矩阵 - -| 平台 | v4 availability | -| --- | --- | -| Linux | packaged manifest v4 + enforcing sandbox + real Host crash gate 后可宣告 | -| macOS | packaged manifest v4 + enforcing sandbox + real Host crash gate 后可宣告 | -| Windows | 当前 Electron runtime 不作为独立 admitted Node runtime;只宣告 v1,v4 明确 unavailable | - -本合同只承诺 process-crash convergence,不承诺硬件断电 exactly-once。 diff --git a/docs/architecture/managed-hermetic-command-sandbox-v1.zh-CN.md b/docs/architecture/managed-hermetic-command-sandbox-v1.zh-CN.md index 1ac1722198..eab5a8d6aa 100644 --- a/docs/architecture/managed-hermetic-command-sandbox-v1.zh-CN.md +++ b/docs/architecture/managed-hermetic-command-sandbox-v1.zh-CN.md @@ -6,7 +6,7 @@ ## 主要不变量 -一次 `hermetic_observation_v1` 必须同时满足: +一次 `hermetic_observation_v2` 必须同时满足: - 使用 M5.1 owner-bound toolchain capability; - `SandboxManager` 必须选择真实 enforcing backend,`none` 或 unavailable 都 fail closed; @@ -30,8 +30,8 @@ Node 官方明确说明 Permission Model 是防止受信代码意外越权的 se ## 平台矩阵 -- Linux:以 bubblewrap + 当前发布 Electron Node-mode runtime 证明 `hermetic_observation_v1`。 -- macOS:以 Seatbelt + 当前发布 Electron Node-mode runtime 证明 `hermetic_observation_v1`。 +- Linux:以 bubblewrap + 当前发布 Electron Node-mode runtime 证明 `hermetic_observation_v2`。 +- macOS:以 Seatbelt + 当前发布 Electron Node-mode runtime 证明 `hermetic_observation_v2`。 - Windows:v1 明确不可用。Electron.exe 在 AppContainer 内无法形成已证明的短生命周期 Node command owner;Host 因而不签发 capability,并在 T1 前返回 `managed_workspace_profile_unavailable`。后续支持必须引入独立校验的 standalone Node artifact,禁止从 diff --git a/docs/architecture/managed-hermetic-node-command-kernel-v1.zh-CN.md b/docs/architecture/managed-hermetic-node-command-kernel-v1.zh-CN.md index f48123d0c0..621f489ece 100644 --- a/docs/architecture/managed-hermetic-node-command-kernel-v1.zh-CN.md +++ b/docs/architecture/managed-hermetic-node-command-kernel-v1.zh-CN.md @@ -12,7 +12,7 @@ execution profile 必须在 T1 前由 admission owner 冻结;T1 后禁止切 依赖发现或 generic tool boundary。 本切片只建立 kernel 与 admission;后续产品组合切片负责 packaged toolchain claim、Host profile negotiation -与 production-shaped crash test。该组合见 `managed-coding-v3-product-composition.zh-CN.md`,只有通过组合边界的 +与 production-shaped crash test。该组合见 `managed-coding-v2-product-composition.zh-CN.md`,只有通过组合边界的 Linux/macOS Host 才允许 Desktop 创建 v3 Session。 ## 2. Owner 与权限 @@ -45,7 +45,7 @@ T1 前: 3. 观察 entry identity; 4. 验证 Node 24 toolchain capability; 5. 冻结 entry 与 argv; -6. 持久化 `managed_observation_v3`。 +6. 持久化 canonical `managed_observation_v2` 的 `node_command_v2` operation。 T1 后: diff --git a/docs/architecture/managed-hermetic-node-test-observation-v1.zh-CN.md b/docs/architecture/managed-hermetic-node-test-observation-v1.zh-CN.md index 770188b8e1..90707fbb00 100644 --- a/docs/architecture/managed-hermetic-node-test-observation-v1.zh-CN.md +++ b/docs/architecture/managed-hermetic-node-test-observation-v1.zh-CN.md @@ -10,7 +10,7 @@ v1 不安装依赖,不读取系统 `PATH`,也不发现用户 checkout 的 `n 一次 `run_node_tests_v1` observation 必须满足: -- 复用 owner-bound `hermetic_observation_v1` toolchain capability; +- 复用 owner-bound `hermetic_observation_v2` toolchain capability; - `SandboxManager` 必须提供 enforcing backend,`none` 或 unavailable 都 fail closed; - 已验证 helper 在一个短生命周期沙箱根进程内直接导入精确文件,由 Node 内建 harness 运行注册的 tests;不启用文件 discovery、不调用 programmatic `run()`、不派生 test child process,并固定使用 `--test-force-exit`; - Node Permission Model 不授予 child process、worker、native addon、WASI 或 accepted input 写权限; diff --git a/docs/architecture/managed-node-test-admission-owner-v1.zh-CN.md b/docs/architecture/managed-node-test-admission-owner-v1.zh-CN.md index 3df4c627d9..015546f3ad 100644 --- a/docs/architecture/managed-node-test-admission-owner-v1.zh-CN.md +++ b/docs/architecture/managed-node-test-admission-owner-v1.zh-CN.md @@ -4,7 +4,7 @@ 本切片把 durable `ManagedNodeTest` protocol 连接到两个已经存在的 authority:Gitoxide managed session 的 accepted head,以及 managed command sandbox 的 opaque toolchain capability。它只建立 Runtime Host admission; -不会把工具加入现有 `managed-coding-v1`,不会改变普通 Session,也不会在缺少 enforcing sandbox 时回退到 +不会改变普通 Session,也不会在缺少 enforcing sandbox 时回退到 `node`、`npm`、package script 或 `PATH`。 ## 2. 主要不变量 @@ -69,6 +69,6 @@ Admission 不接收裸 `storageRoot`。execution-root owner 必须持有不可 ## 6. 后续产品接线 -不要静默扩大 `managed-coding-v1`。后续独立切片应定义版本化 product profile,只有在 packaged/current-process -toolchain authority、enforcing sandbox 和 production-shaped crash test 同时可用时,才把 `ManagedNodeTest` 暴露给 +canonical `managed-coding-v2` 只有在 packaged/current-process toolchain authority、enforcing sandbox 和 +production-shaped crash test 同时可用时,才把 `ManagedNodeTest` 暴露给 Desktop。旧 profile 与旧 session 的工具集合保持冻结。 diff --git a/docs/architecture/managed-node-transform-kernel-v1.zh-CN.md b/docs/architecture/managed-node-transform-kernel-v1.zh-CN.md index bf35c2e1fc..e64195cac6 100644 --- a/docs/architecture/managed-node-transform-kernel-v1.zh-CN.md +++ b/docs/architecture/managed-node-transform-kernel-v1.zh-CN.md @@ -34,7 +34,7 @@ sandbox、timeout 与 candidate publication 都由 owner 决定。 - accepted truth owner:SQLite RuntimeEvents 与 workspace successor transaction; - provider result owner:Runtime 的有界 immutable snapshot。 -`managed_mutation_v3` 在 T1 前绑定 exact accepted head、workspace epoch、output path、entry bytes/hash、argv、 +canonical `managed_mutation_v2` 的 `node_transform_v2` operation 在 T1 前绑定 exact accepted head、workspace epoch、output path、entry bytes/hash、argv、 toolchain identity 与 execution profile。`managed_mutation_v2` 继续表达 Write/Edit 的参数形状;两种 RuntimeEvent payload 都只允许同一个 canonical mutation execution profile v2 digest。payload protocol 用于区分 operation proof 的字段,不再形成两套 execution authority,也没有 v1 digest 兼容路径。 diff --git a/docs/architecture/managed-toolchain-artifact-authority-v1.zh-CN.md b/docs/architecture/managed-toolchain-artifact-authority-v1.zh-CN.md index 4b1e7a1a97..df50dde4c9 100644 --- a/docs/architecture/managed-toolchain-artifact-authority-v1.zh-CN.md +++ b/docs/architecture/managed-toolchain-artifact-authority-v1.zh-CN.md @@ -13,7 +13,7 @@ v1 capability 同时绑定: - profile version; - 允许的 effect class。 -当前只允许 `hermetic_observation_v1` 与 `workspace_transform_v1`。`external_effect_v1` 不在 capability 中,因此后续代码不能通过同一入口静默获得网络、凭据或远端副作用权限。 +当前只允许 `hermetic_observation_v2` 与 `workspace_transform_v1`。`external_effect_v1` 不在 capability 中,因此后续代码不能通过同一入口静默获得网络、凭据或远端副作用权限。 ## Owner 与权限边界 diff --git a/docs/architecture/runtime-durable-coding-m3-m5-roadmap.zh-CN.md b/docs/architecture/runtime-durable-coding-m3-m5-roadmap.zh-CN.md index 392a8c4f4a..3439581c3f 100644 --- a/docs/architecture/runtime-durable-coding-m3-m5-roadmap.zh-CN.md +++ b/docs/architecture/runtime-durable-coding-m3-m5-roadmap.zh-CN.md @@ -79,7 +79,7 @@ immutable publish ref 与“应用到用户 checkout”必须分开: 5. 崩溃后只 reconcile receipt/ref/worktree evidence,不重新计算 accepted mutation。 当前 Desktop 已接入第 1 项:Review 面板签发稳定 `publishId`,Runtime Host 只允许 -`managed-coding-v1` session 创建 `refs/maka/published/`。失败重试复用同一个 ID,ref 不存在或精确指向 +`managed-coding-v2` session 创建 `refs/maka/published/`。失败重试复用同一个 ID,ref 不存在或精确指向 accepted commit 是仅有的两种可收敛状态。这个动作不接触 source checkout;Apply 仍由后续独立 owner 负责。 ### M4.3 Restore / Undo / Time travel @@ -116,7 +116,7 @@ M5 不把普通 Bash 直接标成可恢复。命令在 T1 前必须被划分到 | Effect class | 权限 | 恢复策略 | | --- | --- | --- | -| `hermetic_observation_v1` | 无网络、只读 accepted input、仅写 disposable scratch | 可从同一 boundary 重建或重跑 | +| `hermetic_observation_v2` | 无网络、只读 accepted input、仅写 disposable scratch | 可从同一 boundary 重建或重跑 | | `workspace_transform_v1` | 只写 owner-owned output tree | 固化 candidate,SQLite 接受后投影;不原地改 accepted tree | | `external_effect_v1` | 网络、凭据、远端 API 或不可观察系统状态 | 需要外部 idempotency/acceptance evidence;否则 park,禁止自动重放 | @@ -153,16 +153,15 @@ exit status、test summary 与 artifact digest。缓存是 projection;test out T2; 3. Host admission owner 已只从 Gitoxide accepted-world 与 toolchain opaque capability 签发 envelope,并用一次性 input/scratch roots 执行显式 Node tests; -4. `managed-coding-v2` Host composition 已定义版本化工具集合,并保持 v1 不变;真实 Electron Host/helper - kill-reopen 已进入平台 gate;Desktop 在 Session/T1 前查询 resident Host capability,Linux/macOS 选择 v2, - Windows 当前只获得 v1,禁止执行时降级; +4. `managed-coding-v2` 是唯一产品合同;真实 Electron Host/helper kill-reopen 已进入平台 gate;Desktop 在 + Session/T1 前查询 resident Host capability,平台不满足完整条件时整个 v2 unavailable,禁止降级; 5. 需要外部包的项目在 M5.3 capability 可用前明确 unavailable,禁止静默降级。 `ManagedNodeRun` kernel 只运行 accepted tree 中的显式 Node 入口,exact argv 在 T1 前冻结,写入仅限 disposable scratch。它不等同于 Bash,也不发现 package scripts 或 `PATH` 工具。独立产品组合切片已经把 -`managed-coding-v3` 加入 Host negotiation,并用真实 Host kill/restart 证明完成结果不重放;Linux/macOS -可宣告 v3,Windows 在完整证据形成前只宣告 v1。详见 `managed-hermetic-node-command-kernel-v1.zh-CN.md` -与 `managed-coding-v3-product-composition.zh-CN.md`。 +Node command 与 Node test 共用 canonical `managed_observation_v2`,通过 `operationKind` 区分;平台只能宣告完整的 +`managed-coding-v2` 或 unavailable。详见 `managed-hermetic-node-command-kernel-v1.zh-CN.md` 与 +`managed-coding-v2-product-composition.zh-CN.md`。 ### M5.5 External-effect fencing @@ -194,8 +193,8 @@ Host 后,新的 Run 只采用 durable outcome/candidate/evidence;已完成 每个 PR 必须列出 owner、原子性边界、失败状态、回滚/收敛方式和平台矩阵。CI 全绿只表示已布置用例通过; 并发、崩溃与数据安全仍需单独论证。 -当前 workspace-transform kernel 已采用 `managed_mutation_v3`:受限 Node 进程只产生一个 owner-selected UTF-8 +当前 workspace-transform kernel 已采用 canonical `managed_mutation_v2` 的 `node_transform_v2` operation:受限 Node 进程只产生一个 owner-selected UTF-8 输出,Gitoxide candidate 与 SQLite successor 才能把它纳入 accepted history。它不直接写 worktree,也不让 -caller 选择 executable/environment。packaged profile v4、Desktop negotiation 与 Host crash gate 已形成独立 +caller 选择 executable/environment。canonical profile v2、Desktop negotiation 与 Host crash gate 已形成独立 产品组合切片;下一步推进 external-effect fencing。详见 `managed-node-transform-kernel-v1.zh-CN.md` 与 -`managed-coding-v4-product-composition.zh-CN.md`。 +`managed-coding-v2-product-composition.zh-CN.md`。 diff --git a/docs/architecture/runtime-managed-observation-boundary-v1.zh-CN.md b/docs/architecture/runtime-managed-observation-boundary-v1.zh-CN.md index 427663e8d9..7608658a19 100644 --- a/docs/architecture/runtime-managed-observation-boundary-v1.zh-CN.md +++ b/docs/architecture/runtime-managed-observation-boundary-v1.zh-CN.md @@ -10,7 +10,7 @@ 一次 durable managed observation 必须满足: -1. durable mode 在 T1 前确定为 `replay_safe`,effect class 固定为 `hermetic_observation_v1`; +1. durable mode 在 T1 前确定为 `replay_safe`,effect class 固定为 `hermetic_observation_v2`; 2. T1 同时绑定 repository/workspace/epoch/instance、accepted version/event/revision/commit/tree、排序后的测试文件 bytes/SHA-256、toolchain identity digest 与固定 execution-profile digest; 3. persisted function-call `relativePaths` 必须与 owner 冻结的文件集合完全一致,不能在 T1 后换路径; 4. Runtime 独占 operation capability;它只能在 `open -> running -> settled -> closed` 生命周期内调用一次,Host 不能保留 callback 后迟到执行; @@ -59,7 +59,7 @@ SQLite T1 和 T2 各自是单数据库事务;Git/object store、toolchain obse - tool name:`ManagedNodeTest`; - recovery mode:`replay_safe`; - object format:`sha1`; -- operation/effect:`node_test_v1` / `hermetic_observation_v1`; +- operation/effect:`node_test_v2` / `hermetic_observation_v2`; - 固定 profile digest; - 1–64 个 canonical、排序、唯一的 `.js/.mjs/.cjs` 文件。 diff --git a/docs/architecture/runtime-managed-profile-negotiation-v1.zh-CN.md b/docs/architecture/runtime-managed-profile-negotiation-v1.zh-CN.md index 1272d4eb78..b7ad1055b4 100644 --- a/docs/architecture/runtime-managed-profile-negotiation-v1.zh-CN.md +++ b/docs/architecture/runtime-managed-profile-negotiation-v1.zh-CN.md @@ -6,7 +6,7 @@ > 的实际 capability set 中冻结一个精确 execution profile;T1 后不得因平台、打包资源或 sandbox 不可用而 > 静默降级。 -这个协议只决定“当前 Host 能提供什么”,不决定某次工具是否成功。`managed-coding-v1` 代表 accepted-world +这个协议只决定“当前 Host 能提供什么”,不决定某次工具是否成功。`managed-coding-v2` 代表完整 accepted-world Read/Glob/Grep/Write/Edit;`managed-coding-v2` 在 v1 上增加受控 Node test observation。 ## 2. Owner 与权限边界 @@ -51,15 +51,15 @@ Session,也不回退 attached execution。 | --- | --- | --- | --- | | Linux | 可用 | Bubblewrap 证明后可用 | production-shaped Host crash gate | | macOS | 可用 | Seatbelt 证明后可用 | production-shaped Host crash gate | -| Windows | 可用 | 当前不可用 | Host 只发布 v1;不会先创建 v2 再降级 | +| Windows | Gitoxide kernel 可用 | 当前完整 profile 不可用 | Host 发布空集合;不会降级到旧 profile | Windows 的限制是当前 sandbox capability 合同,不是 Gitoxide/Write/Edit 的限制。将来加入可证明的 Windows -managed Node sandbox 时,由 composition 增加 v2;协议和 Desktop selection 不需要改变。 +managed Node sandbox 时,由 composition 直接发布 canonical v2;协议和 Desktop selection 不需要改变。 ## 6. 测试合同 1. protocol 拒绝未知、重复、乱序 profile; 2. Host kernel 返回 composition 实际集合; -3. Desktop 在 v1/v2 共存时选择 v2,只存在 v1 时选择 v1,空集合时拒绝创建; -4. packaged managed-coding-v2 gate 在 Linux/macOS 观察 v1+v2,在 Windows 只观察 v1; +3. Desktop 只接受 exact v2,空集合或旧 Draft profile 时拒绝创建; +4. packaged managed-coding-v2 gate 在具备完整 sandbox 的平台观察 v2,否则观察空集合; 5. compatibility epoch 阻止不了解该冻结协议的旧 Client/Host 混用。 diff --git a/docs/architecture/runtime-managed-workspace-desktop-history-v1.zh-CN.md b/docs/architecture/runtime-managed-workspace-desktop-history-v1.zh-CN.md index 7fff83d3cb..c80e0306e1 100644 --- a/docs/architecture/runtime-managed-workspace-desktop-history-v1.zh-CN.md +++ b/docs/architecture/runtime-managed-workspace-desktop-history-v1.zh-CN.md @@ -9,7 +9,7 @@ Desktop 展示的 workspace history 必须是某一次 durable accepted head 的 - SQLite workspace-version authority 拥有 immutable version records 与 current head; - Gitoxide managed history owner 先捕获 current head,再沿 `parents[0]` 读取版本; -- Runtime Host 只为 `managed-coding-v1` session 打开 history owner; +- Runtime Host 只为 `managed-coding-v2` session 打开 history owner; - Desktop 只消费结构化、bounded 的 newest-first lineage; - historical restore 仍由 time-travel/restore owner 执行,history query 没有文件系统写权限。 diff --git a/docs/architecture/runtime-managed-workspace-desktop-publish-v1.zh-CN.md b/docs/architecture/runtime-managed-workspace-desktop-publish-v1.zh-CN.md index 698c2b12a5..db11b88d86 100644 --- a/docs/architecture/runtime-managed-workspace-desktop-publish-v1.zh-CN.md +++ b/docs/architecture/runtime-managed-workspace-desktop-publish-v1.zh-CN.md @@ -14,7 +14,7 @@ Publish 描述成 Apply。 ## 2. Owner 与权限 - UI 只生成一个稳定、可重试的 `publishId`; -- Desktop main 只允许 `managed-coding-v1` session 进入该 IPC; +- Desktop main 只允许 `managed-coding-v2` session 进入该 IPC; - Runtime Host coordinator 重新打开该 session 的 managed owner,不接受 repository path、commit 或 ref 作为 caller 输入; - Gitoxide managed publish owner 从 durable session identity 读取 accepted commit/tree,并拥有 ref CAS; diff --git a/docs/architecture/runtime-managed-workspace-desktop-restore-v1.zh-CN.md b/docs/architecture/runtime-managed-workspace-desktop-restore-v1.zh-CN.md index a56c835404..f2d62e36f5 100644 --- a/docs/architecture/runtime-managed-workspace-desktop-restore-v1.zh-CN.md +++ b/docs/architecture/runtime-managed-workspace-desktop-restore-v1.zh-CN.md @@ -14,7 +14,7 @@ durable accepted commit/tree ## 2. Owner 与权限 - renderer 只签发稳定 `restoreId`,不能提交 repository path、commit、tree 或 destination; -- Desktop main 只允许 `managed-coding-v1` session 进入 restore IPC; +- Desktop main 只允许 `managed-coding-v2` session 进入 restore IPC; - Runtime Host 从 session header 重新打开 managed session owner; - restore owner 从 SQLite accepted head/version 读取 exact commit/tree; - Gitoxide helper 只向 owner 选择的 staging path 物化 accepted tree; diff --git a/docs/architecture/runtime-resume-architecture.zh-CN.md b/docs/architecture/runtime-resume-architecture.zh-CN.md index 8f3c5f715e..f31ed257f6 100644 --- a/docs/architecture/runtime-resume-architecture.zh-CN.md +++ b/docs/architecture/runtime-resume-architecture.zh-CN.md @@ -426,7 +426,7 @@ sequenceDiagram 如果在两次提交之间再次崩溃,下次启动仍能从 terminal RuntimeEvent 修好 header。反过来先写 header,就会出现一个没有语义事实支持的“完成”状态。 -Desktop 还会恢复 Graph coordinator 和 supervisor wake。只有这些 startup repair 完成后,Runtime Host 才会尝试 continuation。普通 Session 仍要求 safe-boundary flag;durable `managed-coding-v1` Session 则按其 T1 前冻结的产品合同自动规划,不允许用运行时 flag 把它静默降级为较弱模式。 +Desktop 还会恢复 Graph coordinator 和 supervisor wake。只有这些 startup repair 完成后,Runtime Host 才会尝试 continuation。普通 Session 仍要求 safe-boundary flag;durable `managed-coding-v2` Session 则按其 T1 前冻结的产品合同自动规划,不允许用运行时 flag 把它静默降级为较弱模式。 ## Phase 1:安全边界上创建新的执行 diff --git a/docs/architecture/runtime-resume-phase3-phase4-workspace-checkpoint-design.zh-CN.md b/docs/architecture/runtime-resume-phase3-phase4-workspace-checkpoint-design.zh-CN.md index aa6fbc314b..9451ba97aa 100644 --- a/docs/architecture/runtime-resume-phase3-phase4-workspace-checkpoint-design.zh-CN.md +++ b/docs/architecture/runtime-resume-phase3-phase4-workspace-checkpoint-design.zh-CN.md @@ -553,14 +553,14 @@ Filesystem snapshot v1 只接受 portable regular files/directories,拒绝 sym `.git` 控制路径和超出 path/file/tree/byte/depth 配额的输入。它不宣称拥有跨整棵目录的原子物理快照; 只承诺两次完整有界观察得到同一个 immutable Git tree,否则在 destination publication 前 fail closed。 -Desktop 普通 project/host-path coding task 默认请求 `managed-coding-v1`,不向用户暴露 Git/非 Git模式开关; +Desktop 普通 project/host-path coding task 默认请求 canonical `managed-coding-v2`,不向用户暴露 Git/非 Git模式开关; source 类型只能由上述 admission owner 判定。Deep Research 等拥有独立执行合同的产品模式不自动转换。 Session 创建后 UI 只能展示只读的 managed identity,不能在第一个 T1 之后关闭或切换 durable mode。 显式 Resume 读取同一个持久化 tool profile 与 continuation evidence;feature flag 不能把已经创建的 managed task 静默降级成 attached task。 Runtime Host startup recovery 在普通 recovery/interaction repair 完成后,才允许 automatic managed resume -owner 工作。它只处理持久化为 `managed-coding-v1` 且 strict recovery 已经证明可继续的 Session: +owner 工作。它只处理持久化为 `managed-coding-v2` 且 strict recovery 已经证明可继续的 Session: - 先重验 immutable RuntimeEvent prefix、accepted commit/tree、workspace epoch、source identity、tool catalog 与 background quiescence; diff --git a/packages/core/src/__tests__/runtime-event.test.ts b/packages/core/src/__tests__/runtime-event.test.ts index 6b143c940d..b2c3e3effa 100644 --- a/packages/core/src/__tests__/runtime-event.test.ts +++ b/packages/core/src/__tests__/runtime-event.test.ts @@ -32,12 +32,8 @@ import { INTERACTION_ID_MAX_BYTES, INTERACTION_TOOL_NAME_MAX_BYTES } from '../in import { decodeRuntimeEvent, isTerminalRuntimeEvent, - MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST, - MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_SPEC, MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_SPEC, - MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST, - MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_SPEC, MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, MANAGED_MUTATION_EXECUTION_PROFILE_V2_SPEC, runtimeEventHasModelVisibleContent, @@ -505,83 +501,6 @@ describe('RuntimeEvent content variants', () => { }); describe('RuntimeEvent actions', () => { - test('decodes one exact accepted-world observation identity at T1', () => { - const managedObservation = { - protocol: 'managed_observation_v1', - repositoryId: 'repository_11111111111111111111111111111111', - workspaceId: 'workspace_22222222222222222222222222222222', - workspaceEpochId: 'epoch_33333333333333333333333333333333', - workspaceInstanceId: 'instance_44444444444444444444444444444444', - objectFormat: 'sha1', - acceptedWorkspaceVersionId: 'version_55555555555555555555555555555555', - acceptedEventId: 'accepted-event-1', - acceptedHeadRevision: 2, - acceptedCommitOid: '1'.repeat(40), - acceptedTreeOid: '2'.repeat(40), - operationKind: 'node_test_v1', - effectClass: 'hermetic_observation_v1', - executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST, - toolchainIdentityDigest: `sha256:${'3'.repeat(64)}`, - files: [ - { - relativePath: 'src/a.test.mjs', - bytes: 123, - sha256: `sha256:${'4'.repeat(64)}`, - }, - ], - } as const; - const toolDispatch = { - protocol: 't1_after_preflight_v1', - operationId: 'operation-1', - providerToolCallId: 'call-1', - toolName: 'ManagedNodeTest', - canonicalArgsHash: `sha256:${'b'.repeat(64)}`, - recoveryMode: 'replay_safe', - managedObservation, - } as const; - - assert.deepEqual( - decodeRuntimeEvent(baseEvent({ role: 'system', author: 'system', actions: { toolDispatch } })) - .actions?.toolDispatch?.managedObservation, - managedObservation, - ); - for (const invalid of [ - { ...managedObservation, acceptedHeadRevision: 0 }, - { ...managedObservation, acceptedTreeOid: 'not-an-oid' }, - { ...managedObservation, operationKind: 'bash_v1' }, - { ...managedObservation, effectClass: 'external_effect_v1' }, - { ...managedObservation, executionProfileDigest: `sha256:${'0'.repeat(64)}` }, - { ...managedObservation, files: [] }, - { - ...managedObservation, - files: [{ ...managedObservation.files[0], relativePath: '../escape.test.mjs' }], - }, - { - ...managedObservation, - files: [managedObservation.files[0], managedObservation.files[0]], - }, - { ...managedObservation, extra: true }, - ]) { - assert.throws(() => - decodeRuntimeEvent( - baseEvent({ - role: 'system', - author: 'system', - actions: { toolDispatch: { ...toolDispatch, managedObservation: invalid } as never }, - }), - ), - ); - } - }); - - test('binds managed observation identity to one canonical execution profile', () => { - const canonical = JSON.stringify(MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_SPEC); - assert.equal( - MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST, - `sha256:${createHash('sha256').update(canonical).digest('hex')}`, - ); - }); - test('binds one exact dependency lease into managed observation v2', () => { const managedObservation = { protocol: 'managed_observation_v2', @@ -668,9 +587,9 @@ describe('RuntimeEvent actions', () => { ); }); - test('binds one exact accepted-world Node command into managed observation v3', () => { + test('binds one exact accepted-world Node command into managed observation v2', () => { const managedObservation = { - protocol: 'managed_observation_v3', + protocol: 'managed_observation_v2', repositoryId: 'repository_11111111111111111111111111111111', workspaceId: 'workspace_22222222222222222222222222222222', workspaceEpochId: 'epoch_33333333333333333333333333333333', @@ -681,9 +600,9 @@ describe('RuntimeEvent actions', () => { acceptedHeadRevision: 2, acceptedCommitOid: '1'.repeat(40), acceptedTreeOid: '2'.repeat(40), - operationKind: 'node_command_v3', - effectClass: 'hermetic_observation_v3', - executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST, + operationKind: 'node_command_v2', + effectClass: 'hermetic_observation_v2', + executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, toolchainIdentityDigest: `sha256:${'3'.repeat(64)}`, entry: { relativePath: 'scripts/check.mjs', @@ -709,7 +628,7 @@ describe('RuntimeEvent actions', () => { ); for (const invalid of [ { ...managedObservation, operationKind: 'node_test_v2' }, - { ...managedObservation, effectClass: 'hermetic_observation_v2' }, + { ...managedObservation, effectClass: 'hermetic_observation_v3' }, { ...managedObservation, executionProfileDigest: `sha256:${'0'.repeat(64)}` }, { ...managedObservation, @@ -742,14 +661,6 @@ describe('RuntimeEvent actions', () => { ); }); - test('binds managed observation v3 to its canonical command semantics', () => { - const canonical = JSON.stringify(MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_SPEC); - assert.equal( - MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST, - `sha256:${createHash('sha256').update(canonical).digest('hex')}`, - ); - }); - test('binds every managed mutation to one canonical v2 execution profile', () => { const canonicalProfile = JSON.stringify(MANAGED_MUTATION_EXECUTION_PROFILE_V2_SPEC); assert.equal( @@ -778,6 +689,7 @@ describe('RuntimeEvent actions', () => { baseTreeOid: '2'.repeat(40), expectedPath: 'src/a.ts', pathPolicyVersion: 3, + operationKind: 'write_edit_v2', executionProfileDigest: MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, } as const; const toolDispatch = { @@ -826,7 +738,7 @@ describe('RuntimeEvent actions', () => { `sha256:${createHash('sha256').update(canonicalProfile).digest('hex')}`, ); const managedMutation = { - protocol: 'managed_mutation_v3', + protocol: 'managed_mutation_v2', repositoryId: 'repository_11111111111111111111111111111111', workspaceId: 'workspace_22222222222222222222222222222222', workspaceEpochId: 'epoch_33333333333333333333333333333333', @@ -839,7 +751,7 @@ describe('RuntimeEvent actions', () => { baseTreeOid: '2'.repeat(40), expectedPath: 'generated/output.json', pathPolicyVersion: 3, - operationKind: 'node_transform_v1', + operationKind: 'node_transform_v2', executionProfileDigest: MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, toolchainIdentityDigest: `sha256:${'a'.repeat(64)}`, entry: { @@ -864,7 +776,7 @@ describe('RuntimeEvent actions', () => { managedMutation, ); for (const invalid of [ - { ...managedMutation, operationKind: 'node_command_v3' }, + { ...managedMutation, operationKind: 'node_command_v2' }, { ...managedMutation, executionProfileDigest: `sha256:${'d'.repeat(64)}`, diff --git a/packages/core/src/runtime-event.ts b/packages/core/src/runtime-event.ts index 991b8c2d74..25858ece3f 100644 --- a/packages/core/src/runtime-event.ts +++ b/packages/core/src/runtime-event.ts @@ -260,41 +260,9 @@ export interface RuntimeEventToolDispatch { managedObservation?: RuntimeEventManagedWorkspaceObservation; } -export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_SPEC = Object.freeze({ - protocol: 'managed_observation_execution_profile_v1', - operationKind: 'node_test_v1', - effectClass: 'hermetic_observation_v1', - objectFormat: 'sha1', - acceptedInput: 'read_only_accepted_tree_v1', - testRunner: Object.freeze({ - runtime: 'node_24', - api: 'node_test_run_v1', - isolation: 'none', - concurrency: false, - maxFiles: 64, - maxFileBytes: 16_777_216, - }), - sandbox: Object.freeze({ - required: true, - network: 'restricted', - scratch: 'disposable_write_v1', - childProcess: 'forbidden', - }), - result: Object.freeze({ - format: 'strict_json_v1', - maxBytes: 65_536, - nondeterministicFields: 'forbidden', - }), - replay: 'same_accepted_tree_replay_safe_v1', - executionFallback: 'forbidden', -} as const); - -export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST = - 'sha256:816111c078084a460fad2d6d78a545d127b158d8089237e3a238878936d86e6e' as const; - export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_SPEC = Object.freeze({ protocol: 'managed_observation_execution_profile_v2', - operationKind: 'node_test_v2', + operationKinds: Object.freeze(['node_test_v2', 'node_command_v2'] as const), effectClass: 'hermetic_observation_v2', objectFormat: 'sha1', acceptedInput: 'read_only_accepted_tree_v1', @@ -304,13 +272,24 @@ export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_SPEC = Object.freeze({ fallback: 'forbidden', pathTransport: 'forbidden', }), - testRunner: Object.freeze({ - runtime: 'node_24', - api: 'node_test_run_v1', - isolation: 'none', - concurrency: false, - maxFiles: 64, - maxFileBytes: 16_777_216, + operations: Object.freeze({ + nodeTest: Object.freeze({ + runtime: 'node_24', + api: 'node_test_run_v1', + isolation: 'none', + concurrency: false, + maxFiles: 64, + maxFileBytes: 16_777_216, + }), + nodeCommand: Object.freeze({ + runtime: 'node_24', + api: 'node_entrypoint_v1', + extensions: Object.freeze(['.cjs', '.js', '.mjs'] as const), + maxArgs: 64, + maxArgBytes: 4096, + maxTotalArgBytes: 32_768, + dependencyInput: 'none', + }), }), sandbox: Object.freeze({ required: true, @@ -319,54 +298,21 @@ export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_SPEC = Object.freeze({ dependencyTree: 'read_only_when_bound', scratch: 'disposable_write_v1', childProcess: 'forbidden', - }), - result: Object.freeze({ - format: 'strict_json_v1', - maxBytes: 65_536, - nondeterministicFields: 'forbidden', - }), - replay: 'same_accepted_tree_and_dependency_replay_safe_v1', - executionFallback: 'forbidden', -} as const); - -export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST = - 'sha256:be3ca7af72a0d35cda471a6de71eed7dd260890624f11c8b5d71cccb2067c333' as const; - -export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_SPEC = Object.freeze({ - protocol: 'managed_observation_execution_profile_v3', - operationKind: 'node_command_v3', - effectClass: 'hermetic_observation_v3', - objectFormat: 'sha1', - acceptedInput: 'read_only_accepted_tree_v1', - dependencyInput: 'none', - command: Object.freeze({ - runtime: 'node_24', - api: 'node_entrypoint_v1', - extensions: Object.freeze(['.cjs', '.js', '.mjs'] as const), - maxArgs: 64, - maxArgBytes: 4096, - maxTotalArgBytes: 32_768, - }), - sandbox: Object.freeze({ - required: true, - network: 'restricted', - acceptedTree: 'read_only', - scratch: 'disposable_write_v1', - childProcess: 'forbidden', path: 'empty', }), result: Object.freeze({ format: 'strict_json_v1', maxBytes: 65_536, + nondeterministicFields: 'forbidden', stdoutBytes: 32_768, stderrBytes: 32_768, }), - replay: 'same_accepted_tree_replay_safe_v1', + replay: 'same_accepted_tree_and_declared_inputs_replay_safe_v2', executionFallback: 'forbidden', } as const); -export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST = - 'sha256:8cde26b9e1b475fac75f0980baf04d09baed94184757bf02c3cc12fc5df2b50e' as const; +export const MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST = + 'sha256:3702995e2893e5a4a813998665fd0ff6758d68e8faf64685fcc6319e250c0a46' as const; export interface RuntimeEventManagedObservationFileV1 { readonly relativePath: string; @@ -374,8 +320,8 @@ export interface RuntimeEventManagedObservationFileV1 { readonly sha256: `sha256:${string}`; } -export interface RuntimeEventManagedWorkspaceObservationV1 { - readonly protocol: 'managed_observation_v1'; +interface RuntimeEventManagedWorkspaceObservationV2Base { + readonly protocol: 'managed_observation_v2'; readonly repositoryId: string; readonly workspaceId: string; readonly workspaceEpochId: string; @@ -386,11 +332,9 @@ export interface RuntimeEventManagedWorkspaceObservationV1 { readonly acceptedHeadRevision: number; readonly acceptedCommitOid: string; readonly acceptedTreeOid: string; - readonly operationKind: 'node_test_v1'; - readonly effectClass: 'hermetic_observation_v1'; - readonly executionProfileDigest: typeof MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST; + readonly effectClass: 'hermetic_observation_v2'; + readonly executionProfileDigest: typeof MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST; readonly toolchainIdentityDigest: `sha256:${string}`; - readonly files: readonly RuntimeEventManagedObservationFileV1[]; } export type RuntimeEventManagedDependencyObservationV1 = @@ -405,50 +349,25 @@ export type RuntimeEventManagedDependencyObservationV1 = arch: string; }>; -export interface RuntimeEventManagedWorkspaceObservationV2 { - readonly protocol: 'managed_observation_v2'; - readonly repositoryId: string; - readonly workspaceId: string; - readonly workspaceEpochId: string; - readonly workspaceInstanceId: string; - readonly objectFormat: 'sha1'; - readonly acceptedWorkspaceVersionId: string; - readonly acceptedEventId: string; - readonly acceptedHeadRevision: number; - readonly acceptedCommitOid: string; - readonly acceptedTreeOid: string; +export interface RuntimeEventManagedWorkspaceNodeTestObservationV2 + extends RuntimeEventManagedWorkspaceObservationV2Base { readonly operationKind: 'node_test_v2'; - readonly effectClass: 'hermetic_observation_v2'; - readonly executionProfileDigest: typeof MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST; - readonly toolchainIdentityDigest: `sha256:${string}`; readonly dependency: RuntimeEventManagedDependencyObservationV1; readonly files: readonly RuntimeEventManagedObservationFileV1[]; } -export interface RuntimeEventManagedWorkspaceObservationV3 { - readonly protocol: 'managed_observation_v3'; - readonly repositoryId: string; - readonly workspaceId: string; - readonly workspaceEpochId: string; - readonly workspaceInstanceId: string; - readonly objectFormat: 'sha1'; - readonly acceptedWorkspaceVersionId: string; - readonly acceptedEventId: string; - readonly acceptedHeadRevision: number; - readonly acceptedCommitOid: string; - readonly acceptedTreeOid: string; - readonly operationKind: 'node_command_v3'; - readonly effectClass: 'hermetic_observation_v3'; - readonly executionProfileDigest: typeof MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST; - readonly toolchainIdentityDigest: `sha256:${string}`; +export interface RuntimeEventManagedWorkspaceNodeCommandObservationV2 + extends RuntimeEventManagedWorkspaceObservationV2Base { + readonly operationKind: 'node_command_v2'; readonly entry: RuntimeEventManagedObservationFileV1; readonly args: readonly string[]; } -export type RuntimeEventManagedWorkspaceObservation = - | RuntimeEventManagedWorkspaceObservationV1 - | RuntimeEventManagedWorkspaceObservationV2 - | RuntimeEventManagedWorkspaceObservationV3; +export type RuntimeEventManagedWorkspaceObservationV2 = + | RuntimeEventManagedWorkspaceNodeTestObservationV2 + | RuntimeEventManagedWorkspaceNodeCommandObservationV2; + +export type RuntimeEventManagedWorkspaceObservation = RuntimeEventManagedWorkspaceObservationV2; /** * Canonical semantics bound by the managed mutation execution-profile digest. @@ -508,11 +427,12 @@ export interface RuntimeEventManagedWorkspaceMutationV2 { baseTreeOid: string; expectedPath: string; pathPolicyVersion: 3; + operationKind: 'write_edit_v2'; executionProfileDigest: typeof MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST; } -export interface RuntimeEventManagedWorkspaceMutationV3 { - protocol: 'managed_mutation_v3'; +export interface RuntimeEventManagedWorkspaceNodeTransformMutationV2 { + protocol: 'managed_mutation_v2'; repositoryId: string; workspaceId: string; workspaceEpochId: string; @@ -525,7 +445,7 @@ export interface RuntimeEventManagedWorkspaceMutationV3 { baseTreeOid: string; expectedPath: string; pathPolicyVersion: 3; - operationKind: 'node_transform_v1'; + operationKind: 'node_transform_v2'; executionProfileDigest: typeof MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST; toolchainIdentityDigest: `sha256:${string}`; entry: RuntimeEventManagedObservationFileV1; @@ -534,7 +454,7 @@ export interface RuntimeEventManagedWorkspaceMutationV3 { export type RuntimeEventManagedWorkspaceMutation = | RuntimeEventManagedWorkspaceMutationV2 - | RuntimeEventManagedWorkspaceMutationV3; + | RuntimeEventManagedWorkspaceNodeTransformMutationV2; export interface RuntimeEventManagedMutationTerminalV1 { protocol: 'managed_mutation_terminal_v1'; @@ -838,30 +758,8 @@ const RUNTIME_TOOL_DISPATCH_SHAPE = defineObjectShape( ], ['managedMutation', 'managedObservation'], ); -const RUNTIME_MANAGED_WORKSPACE_OBSERVATION_SHAPE = - defineObjectShape()( - [ - 'protocol', - 'repositoryId', - 'workspaceId', - 'workspaceEpochId', - 'workspaceInstanceId', - 'objectFormat', - 'acceptedWorkspaceVersionId', - 'acceptedEventId', - 'acceptedHeadRevision', - 'acceptedCommitOid', - 'acceptedTreeOid', - 'operationKind', - 'effectClass', - 'executionProfileDigest', - 'toolchainIdentityDigest', - 'files', - ], - [], - ); -const RUNTIME_MANAGED_WORKSPACE_OBSERVATION_V2_SHAPE = - defineObjectShape()( +const RUNTIME_MANAGED_WORKSPACE_NODE_TEST_OBSERVATION_V2_SHAPE = + defineObjectShape()( [ 'protocol', 'repositoryId', @@ -883,8 +781,8 @@ const RUNTIME_MANAGED_WORKSPACE_OBSERVATION_V2_SHAPE = ], [], ); -const RUNTIME_MANAGED_WORKSPACE_OBSERVATION_V3_SHAPE = - defineObjectShape()( +const RUNTIME_MANAGED_WORKSPACE_NODE_COMMAND_OBSERVATION_V2_SHAPE = + defineObjectShape()( [ 'protocol', 'repositoryId', @@ -934,12 +832,13 @@ const RUNTIME_MANAGED_WORKSPACE_MUTATION_SHAPE = 'baseTreeOid', 'expectedPath', 'pathPolicyVersion', + 'operationKind', 'executionProfileDigest', ], [], ); -const RUNTIME_MANAGED_WORKSPACE_MUTATION_V3_SHAPE = - defineObjectShape()( +const RUNTIME_MANAGED_WORKSPACE_NODE_TRANSFORM_MUTATION_V2_SHAPE = + defineObjectShape()( [ 'protocol', 'repositoryId', @@ -1253,26 +1152,18 @@ function isRuntimeToolDispatch(value: unknown): value is RuntimeEventToolDispatc (value.managedObservation === undefined || (value.recoveryMode === 'replay_safe' && ((value.toolName === 'ManagedNodeTest' && - (value.managedObservation.protocol === 'managed_observation_v1' || - value.managedObservation.protocol === 'managed_observation_v2')) || + value.managedObservation.operationKind === 'node_test_v2') || (value.toolName === 'ManagedNodeRun' && - value.managedObservation.protocol === 'managed_observation_v3')))) + value.managedObservation.operationKind === 'node_command_v2')))) ); } function isRuntimeManagedWorkspaceObservation( value: unknown, ): value is RuntimeEventManagedWorkspaceObservation { - if (isRecord(value) && value.protocol === 'managed_observation_v2') { - return isRuntimeManagedWorkspaceObservationV2(value); - } - if (isRecord(value) && value.protocol === 'managed_observation_v3') { - return isRuntimeManagedWorkspaceObservationV3(value); - } + if (!isRecord(value)) return false; if ( - !isRecord(value) || - !hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_OBSERVATION_SHAPE) || - value.protocol !== 'managed_observation_v1' || + value.protocol !== 'managed_observation_v2' || typeof value.repositoryId !== 'string' || !/^repository_[0-9a-f]{32}$/u.test(value.repositoryId) || typeof value.workspaceId !== 'string' || @@ -1292,87 +1183,25 @@ function isRuntimeManagedWorkspaceObservation( !/^[0-9a-f]{40}$/u.test(value.acceptedCommitOid) || typeof value.acceptedTreeOid !== 'string' || !/^[0-9a-f]{40}$/u.test(value.acceptedTreeOid) || - value.operationKind !== 'node_test_v1' || - value.effectClass !== 'hermetic_observation_v1' || - value.executionProfileDigest !== MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST || - !isSha256Digest(value.toolchainIdentityDigest) || - !Array.isArray(value.files) || - value.files.length === 0 || - value.files.length > 64 + value.effectClass !== 'hermetic_observation_v2' || + value.executionProfileDigest !== MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST || + !isSha256Digest(value.toolchainIdentityDigest) ) { return false; } - return areRuntimeManagedObservationFiles(value.files); -} - -function isRuntimeManagedWorkspaceObservationV2( - value: unknown, -): value is RuntimeEventManagedWorkspaceObservationV2 { - if (!isRecord(value)) return false; - return ( - hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_OBSERVATION_V2_SHAPE) && - value.protocol === 'managed_observation_v2' && - typeof value.repositoryId === 'string' && - /^repository_[0-9a-f]{32}$/u.test(value.repositoryId) && - typeof value.workspaceId === 'string' && - /^workspace_[0-9a-f]{32}$/u.test(value.workspaceId) && - typeof value.workspaceEpochId === 'string' && - /^epoch_[0-9a-f]{32}$/u.test(value.workspaceEpochId) && - typeof value.workspaceInstanceId === 'string' && - /^instance_[0-9a-f]{32}$/u.test(value.workspaceInstanceId) && - value.objectFormat === 'sha1' && - typeof value.acceptedWorkspaceVersionId === 'string' && - /^version_[0-9a-f]{32}$/u.test(value.acceptedWorkspaceVersionId) && - typeof value.acceptedEventId === 'string' && - /^[A-Za-z0-9_-]{1,128}$/u.test(value.acceptedEventId) && - Number.isSafeInteger(value.acceptedHeadRevision) && - (value.acceptedHeadRevision as number) >= 1 && - typeof value.acceptedCommitOid === 'string' && - /^[0-9a-f]{40}$/u.test(value.acceptedCommitOid) && - typeof value.acceptedTreeOid === 'string' && - /^[0-9a-f]{40}$/u.test(value.acceptedTreeOid) && - value.operationKind === 'node_test_v2' && - value.effectClass === 'hermetic_observation_v2' && - value.executionProfileDigest === MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST && - isSha256Digest(value.toolchainIdentityDigest) && - isRuntimeManagedDependencyObservation(value.dependency) && - Array.isArray(value.files) && - value.files.length > 0 && - value.files.length <= 64 && - areRuntimeManagedObservationFiles(value.files) - ); -} - -function isRuntimeManagedWorkspaceObservationV3( - value: unknown, -): value is RuntimeEventManagedWorkspaceObservationV3 { - if (!isRecord(value)) return false; + if (value.operationKind === 'node_test_v2') { + return ( + hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_NODE_TEST_OBSERVATION_V2_SHAPE) && + isRuntimeManagedDependencyObservation(value.dependency) && + Array.isArray(value.files) && + value.files.length > 0 && + value.files.length <= 64 && + areRuntimeManagedObservationFiles(value.files) + ); + } return ( - hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_OBSERVATION_V3_SHAPE) && - value.protocol === 'managed_observation_v3' && - typeof value.repositoryId === 'string' && - /^repository_[0-9a-f]{32}$/u.test(value.repositoryId) && - typeof value.workspaceId === 'string' && - /^workspace_[0-9a-f]{32}$/u.test(value.workspaceId) && - typeof value.workspaceEpochId === 'string' && - /^epoch_[0-9a-f]{32}$/u.test(value.workspaceEpochId) && - typeof value.workspaceInstanceId === 'string' && - /^instance_[0-9a-f]{32}$/u.test(value.workspaceInstanceId) && - value.objectFormat === 'sha1' && - typeof value.acceptedWorkspaceVersionId === 'string' && - /^version_[0-9a-f]{32}$/u.test(value.acceptedWorkspaceVersionId) && - typeof value.acceptedEventId === 'string' && - /^[A-Za-z0-9_-]{1,128}$/u.test(value.acceptedEventId) && - Number.isSafeInteger(value.acceptedHeadRevision) && - (value.acceptedHeadRevision as number) >= 1 && - typeof value.acceptedCommitOid === 'string' && - /^[0-9a-f]{40}$/u.test(value.acceptedCommitOid) && - typeof value.acceptedTreeOid === 'string' && - /^[0-9a-f]{40}$/u.test(value.acceptedTreeOid) && - value.operationKind === 'node_command_v3' && - value.effectClass === 'hermetic_observation_v3' && - value.executionProfileDigest === MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST && - isSha256Digest(value.toolchainIdentityDigest) && + value.operationKind === 'node_command_v2' && + hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_NODE_COMMAND_OBSERVATION_V2_SHAPE) && areRuntimeManagedObservationFiles([value.entry]) && isRuntimeManagedNodeCommandArgs(value.args) ); @@ -1438,11 +1267,11 @@ function isRuntimeManagedWorkspaceMutation( value: unknown, ): value is RuntimeEventManagedWorkspaceMutation { if (!isRecord(value)) return false; - if (value.protocol === 'managed_mutation_v3') { + if (value.operationKind === 'node_transform_v2') { return ( - hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_MUTATION_V3_SHAPE) && + value.protocol === 'managed_mutation_v2' && + hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_NODE_TRANSFORM_MUTATION_V2_SHAPE) && hasManagedMutationBaseIdentity(value) && - value.operationKind === 'node_transform_v1' && value.executionProfileDigest === MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST && isSha256Digest(value.toolchainIdentityDigest) && isRecord(value.entry) && @@ -1455,6 +1284,7 @@ function isRuntimeManagedWorkspaceMutation( if ( !hasExactShape(value, RUNTIME_MANAGED_WORKSPACE_MUTATION_SHAPE) || value.protocol !== 'managed_mutation_v2' || + value.operationKind !== 'write_edit_v2' || !hasManagedMutationBaseIdentity(value) || value.executionProfileDigest !== MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST ) { diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 1d73336f8d..c9fe7b23bd 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -207,17 +207,11 @@ export function isTurnStatus(value: unknown): value is TurnStatus { export const SESSION_TOOL_PROFILES = [ 'headless-coding-v1', - 'managed-coding-v1', 'managed-coding-v2', - 'managed-coding-v3', - 'managed-coding-v4', 'workhub-coordination-v1', ] as const; export type SessionToolProfile = (typeof SESSION_TOOL_PROFILES)[number]; -export type ManagedCodingSessionToolProfile = Extract< - SessionToolProfile, - 'managed-coding-v1' | 'managed-coding-v2' | 'managed-coding-v3' | 'managed-coding-v4' ->; +export type ManagedCodingSessionToolProfile = Extract; export function isSessionToolProfile(value: unknown): value is SessionToolProfile { return typeof value === 'string' && (SESSION_TOOL_PROFILES as readonly string[]).includes(value); @@ -226,12 +220,7 @@ export function isSessionToolProfile(value: unknown): value is SessionToolProfil export function isManagedCodingSessionToolProfile( value: unknown, ): value is ManagedCodingSessionToolProfile { - return ( - value === 'managed-coding-v1' || - value === 'managed-coding-v2' || - value === 'managed-coding-v3' || - value === 'managed-coding-v4' - ); + return value === 'managed-coding-v2'; } export interface SessionExternalOrigin { diff --git a/packages/runtime-host/src/__tests__/current-process-managed-toolchain-internal.test.ts b/packages/runtime-host/src/__tests__/current-process-managed-toolchain-internal.test.ts index 5820d35e9b..258a1a24bc 100644 --- a/packages/runtime-host/src/__tests__/current-process-managed-toolchain-internal.test.ts +++ b/packages/runtime-host/src/__tests__/current-process-managed-toolchain-internal.test.ts @@ -55,7 +55,7 @@ test('rejects a command entrypoint changed after its release manifest was writte await assert.rejects(runElectronChild(fixture.resourcesRoot), /failed release admission/u); }); -test('rejects the superseded v3 release envelope instead of silently weakening v4', { +test('rejects an abandoned draft v4 release envelope instead of weakening canonical v2', { skip: process.platform === 'win32' ? 'Windows does not admit Electron as the managed Node runtime' @@ -68,9 +68,13 @@ test('rejects the superseded v3 release envelope instead of silently weakening v manifestPath, `${JSON.stringify({ ...manifest, - schemaVersion: 3, - protocol: 'maka_managed_command_toolchain_release_v3', - allowedEffectClasses: ['hermetic_observation_v2', 'hermetic_observation_v3'], + schemaVersion: 4, + protocol: 'maka_managed_command_toolchain_release_v4', + allowedEffectClasses: [ + 'hermetic_observation_v2', + 'hermetic_observation_v3', + 'workspace_transform_v1', + ], })}\n`, 'utf8', ); @@ -95,8 +99,8 @@ async function createFixture(t: test.TestContext) { await writeFile( join(resourcesRoot, 'managed-command-toolchain.json'), `${JSON.stringify({ - schemaVersion: 4, - protocol: 'maka_managed_command_toolchain_release_v4', + schemaVersion: 2, + protocol: 'maka_managed_command_toolchain_release_v2', provider: 'maka/managed-command-toolchain', platform: process.platform, arch: process.arch, @@ -105,11 +109,7 @@ async function createFixture(t: test.TestContext) { entrypointRelativePath: 'managed-command/managed-command-helper-main.js', entrypointBytes: (await stat(entrypointPath)).size, entrypointSha256: `sha256:${createHash('sha256').update(entrypoint).digest('hex')}`, - allowedEffectClasses: [ - 'hermetic_observation_v2', - 'hermetic_observation_v3', - 'workspace_transform_v1', - ], + allowedEffectClasses: ['hermetic_observation_v2', 'workspace_transform_v1'], distributionReady: true, })}\n`, 'utf8', diff --git a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts index c3770a4b39..cf3ed8c3b6 100644 --- a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts +++ b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts @@ -1425,7 +1425,7 @@ test('hosted execution freezes the headless coding provider wire contract', asyn permissionMode: 'bypass', collaborationMode: 'agent', orchestrationMode: 'default', - toolProfile: 'managed-coding-v1', + toolProfile: 'managed-coding-v2', }, content: { text: 'Modify the managed workspace.' }, }, diff --git a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts index aed054c13e..7cd2e7f4fc 100644 --- a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts +++ b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts @@ -419,7 +419,7 @@ async function withManagedContinuationFixture( llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', - toolProfile: 'managed-coding-v1', + toolProfile: 'managed-coding-v2', }); sessionId = session.id; const helper = await admitRealHelper(helperInputPath); diff --git a/packages/runtime-host/src/__tests__/gitoxide-managed-write-edit-owner-internal.test.ts b/packages/runtime-host/src/__tests__/gitoxide-managed-write-edit-owner-internal.test.ts index 78fa899871..37a7812e16 100644 --- a/packages/runtime-host/src/__tests__/gitoxide-managed-write-edit-owner-internal.test.ts +++ b/packages/runtime-host/src/__tests__/gitoxide-managed-write-edit-owner-internal.test.ts @@ -222,6 +222,7 @@ test('does not report a current projection while a durable mutation reservation baseTreeOid: treeOid, expectedPath: args.path, pathPolicyVersion: 3, + operationKind: 'write_edit_v2', executionProfileDigest: MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, }, }, diff --git a/packages/runtime-host/src/__tests__/host-execution-profiles-protocol.test.ts b/packages/runtime-host/src/__tests__/host-execution-profiles-protocol.test.ts index f3404305c9..060682ffdc 100644 --- a/packages/runtime-host/src/__tests__/host-execution-profiles-protocol.test.ts +++ b/packages/runtime-host/src/__tests__/host-execution-profiles-protocol.test.ts @@ -35,24 +35,13 @@ test('Host execution profiles are a closed canonical pre-session capability set' }, ); assert.deepEqual( - decodeHostFrame( - response([ - 'managed-coding-v1', - 'managed-coding-v2', - 'managed-coding-v3', - 'managed-coding-v4', - ]), - ), - response(['managed-coding-v1', 'managed-coding-v2', 'managed-coding-v3', 'managed-coding-v4']), + decodeHostFrame(response(['managed-coding-v2'])), + response(['managed-coding-v2']), ); - assert.deepEqual( - decodeHostFrame(response(['managed-coding-v1'])), - response(['managed-coding-v1']), - ); - assert.throws(() => - decodeHostFrame(response(['managed-coding-v3', 'managed-coding-v2', 'managed-coding-v1'])), - ); - assert.throws(() => decodeHostFrame(response(['managed-coding-v1', 'managed-coding-v1']))); + assert.throws(() => decodeHostFrame(response(['managed-coding-v1']))); + assert.throws(() => decodeHostFrame(response(['managed-coding-v3']))); + assert.throws(() => decodeHostFrame(response(['managed-coding-v4']))); + assert.throws(() => decodeHostFrame(response(['managed-coding-v2', 'managed-coding-v2']))); assert.throws(() => decodeHostFrame(response(['managed-coding-v5']))); }); diff --git a/packages/runtime-host/src/__tests__/host-kernel.test.ts b/packages/runtime-host/src/__tests__/host-kernel.test.ts index abe5320e75..1bc026b15e 100644 --- a/packages/runtime-host/src/__tests__/host-kernel.test.ts +++ b/packages/runtime-host/src/__tests__/host-kernel.test.ts @@ -108,7 +108,7 @@ const STARTUP_ATTEMPT_B = '00000000-0000-4000-8000-000000000002'; const KERNEL_CANDIDATE_ENTRYPOINT = new URL('./fixtures/kernel-candidate.js', import.meta.url); const KERNEL_COMPOSITION = defineInteractiveRuntimeHostComposition(async () => ({ handlers: createUnavailableDomainOperationHandlers(), - executionProfiles: ['managed-coding-v1'] as const, + executionProfiles: ['managed-coding-v2'] as const, beginDrain() {}, async recover() {}, async close() {}, @@ -750,7 +750,7 @@ describe('non-serving Runtime Host kernel', () => { if (connected.kind !== 'connected') return; assert.equal(connected.registration.lifecycleMode, 'service'); assert.deepEqual(await connected.connection.request('host.execution-profiles.query', {}), { - profiles: ['managed-coding-v1'], + profiles: ['managed-coding-v2'], }); await connected.connection.close(); diff --git a/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts b/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts index 20b12d342a..802c230cb5 100644 --- a/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts +++ b/packages/runtime-host/src/__tests__/hosted-execution-tool-profile.test.ts @@ -43,13 +43,6 @@ test('hosted execution tool profiles are durable Session creation inputs', () => content: { text: 'solve' }, }); assert.equal(decoded.session.toolProfile, 'headless-coding-v1'); - assert.equal( - decodeHostedExecutionStartInput({ - ...decoded, - session: { ...decoded.session, toolProfile: 'managed-coding-v1' }, - }).session.toolProfile, - 'managed-coding-v1', - ); assert.equal( decodeHostedExecutionStartInput({ ...decoded, @@ -57,6 +50,14 @@ test('hosted execution tool profiles are durable Session creation inputs', () => }).session.toolProfile, 'managed-coding-v2', ); + assert.throws( + () => + decodeHostedExecutionStartInput({ + ...decoded, + session: { ...decoded.session, toolProfile: 'managed-coding-v1' }, + }), + /Invalid Session tool profile/u, + ); assert.throws( () => decodeHostedExecutionStartInput({ @@ -154,63 +155,9 @@ test('the WorkHub coordination profile has conversational authority but zero too assert.deepEqual(projectHostedExecutionTools([productTool], 'workhub-coordination-v1'), []); }); -test('the managed coding profile reads and mutates only the accepted Git tree', () => { - const profile = hostedExecutionRunProfile('managed-coding-v1'); - assert.ok(profile); - assert.deepEqual(profile.toolNames, ['Read', 'Glob', 'Grep', 'Write', 'Edit']); - assert.equal(profile.memoryExtraction, false); - assert.match(profile.systemPrompt, /managed Git workspace/u); - assert.match(profile.systemPrompt, /Read, Glob, and Grep/u); - - const tools = ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'Bash'].map( - (name): MakaTool => ({ - name, - description: name, - parameters: z.object({}), - impl: async () => 'not used by managed mutation execution', - }), - ); - const selected = projectHostedExecutionTools(tools, 'managed-coding-v1'); - assert.deepEqual( - selected.map(({ name }) => name), - ['Read', 'Glob', 'Grep', 'Write', 'Edit'], - ); - for (const tool of selected.slice(0, 3)) { - assert.equal(tool.recoveryMode, 'replay_safe'); - assert.equal(tool.durableExecutionProfile, undefined); - } - for (const tool of selected.slice(3)) { - assert.equal(tool.recoveryMode, 'reconcile'); - assert.equal(tool.durableExecutionProfile, 'managed_mutation_v2'); - } -}); - -test('managed coding v2 adds only the durable accepted-world Node test', () => { +test('managed coding v2 is the only complete durable coding profile', () => { const profile = hostedExecutionRunProfile('managed-coding-v2'); assert.ok(profile); - assert.deepEqual(profile.toolNames, ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'ManagedNodeTest']); - assert.match(profile.systemPrompt, /immutable read-only dependency snapshot/u); - - const tools = ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'ManagedNodeTest', 'Bash'].map( - (name): MakaTool => ({ - name, - description: name, - parameters: z.object({}), - impl: async () => 'not used', - }), - ); - const selected = projectHostedExecutionTools(tools, 'managed-coding-v2'); - assert.deepEqual( - selected.map(({ name }) => name), - ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'ManagedNodeTest'], - ); - assert.equal(selected.at(-1)?.recoveryMode, 'replay_safe'); - assert.equal(selected.at(-1)?.durableExecutionProfile, 'managed_observation_v2'); -}); - -test('managed coding v3 adds only an explicit hermetic accepted-world Node entrypoint', () => { - const profile = hostedExecutionRunProfile('managed-coding-v3'); - assert.ok(profile); assert.deepEqual(profile.toolNames, [ 'Read', 'Glob', @@ -219,19 +166,15 @@ test('managed coding v3 adds only an explicit hermetic accepted-world Node entry 'Edit', 'ManagedNodeTest', 'ManagedNodeRun', + 'ManagedNodeTransform', ]); + assert.equal(profile.memoryExtraction, false); + assert.match(profile.systemPrompt, /managed Git workspace/u); + assert.match(profile.systemPrompt, /immutable read-only dependency snapshot/u); assert.match(profile.systemPrompt, /explicit accepted-workspace Node entrypoint/u); + assert.match(profile.systemPrompt, /one bounded UTF-8 workspace file/u); - const tools = [ - 'Read', - 'Glob', - 'Grep', - 'Write', - 'Edit', - 'ManagedNodeTest', - 'ManagedNodeRun', - 'Bash', - ].map( + const tools = [...profile.toolNames, 'Bash'].map( (name): MakaTool => ({ name, description: name, @@ -239,38 +182,15 @@ test('managed coding v3 adds only an explicit hermetic accepted-world Node entry impl: async () => 'not used', }), ); - const selected = projectHostedExecutionTools(tools, 'managed-coding-v3'); + const selected = projectHostedExecutionTools(tools, 'managed-coding-v2'); assert.deepEqual( selected.map(({ name }) => name), - ['Read', 'Glob', 'Grep', 'Write', 'Edit', 'ManagedNodeTest', 'ManagedNodeRun'], - ); - assert.equal(selected.at(-1)?.recoveryMode, 'replay_safe'); - assert.equal(selected.at(-1)?.durableExecutionProfile, 'managed_observation_v3'); -}); - -test('managed coding v4 adds one owner-controlled accepted-world workspace transform', () => { - const profile = hostedExecutionRunProfile('managed-coding-v4'); - assert.ok(profile); - assert.deepEqual(profile.toolNames, [ - 'Read', - 'Glob', - 'Grep', - 'Write', - 'Edit', - 'ManagedNodeTest', - 'ManagedNodeRun', - 'ManagedNodeTransform', - ]); - assert.match(profile.systemPrompt, /one bounded UTF-8 workspace file/u); - const tools = [...profile.toolNames, 'Bash'].map( - (name): MakaTool => ({ - name, - description: name, - parameters: z.object({}), - impl: async () => 'not used', - }), + [...profile.toolNames], ); - const selected = projectHostedExecutionTools(tools, 'managed-coding-v4'); + assert.equal(selected[5]?.recoveryMode, 'replay_safe'); + assert.equal(selected[5]?.durableExecutionProfile, 'managed_observation_v2'); + assert.equal(selected[6]?.recoveryMode, 'replay_safe'); + assert.equal(selected[6]?.durableExecutionProfile, 'managed_observation_v2'); assert.equal(selected.at(-1)?.name, 'ManagedNodeTransform'); assert.equal(selected.at(-1)?.recoveryMode, 'reconcile'); assert.equal(selected.at(-1)?.durableExecutionProfile, 'managed_mutation_v2'); diff --git a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts index 7779f4f66d..ba6cdfcca3 100644 --- a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts +++ b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts @@ -125,10 +125,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying a co }); const firstClient = await connectClient(root); assert.deepEqual(await firstClient.request('host.execution-profiles.query', {}), { - profiles: - process.platform === 'win32' - ? ['managed-coding-v1'] - : ['managed-coding-v1', 'managed-coding-v2', 'managed-coding-v3', 'managed-coding-v4'], + profiles: process.platform === 'win32' ? [] : ['managed-coding-v2'], }); const startRequest = firstClient.request('hosted.execution.start', { executionId, @@ -252,7 +249,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying a co } }); -test('packaged managed-coding-v3 resumes after Host death without replaying a completed Node command', { +test('packaged managed-coding-v2 resumes after Host death without replaying a completed Node command', { timeout: 90_000, }, async (t) => { const helperPath = process.env.MAKA_GITOXIDE_HELPER_PATH; @@ -301,10 +298,7 @@ test('packaged managed-coding-v3 resumes after Host death without replaying a co }); const firstClient = await connectClient(root); assert.deepEqual(await firstClient.request('host.execution-profiles.query', {}), { - profiles: - process.platform === 'win32' - ? ['managed-coding-v1'] - : ['managed-coding-v1', 'managed-coding-v2', 'managed-coding-v3', 'managed-coding-v4'], + profiles: process.platform === 'win32' ? [] : ['managed-coding-v2'], }); const startRequest = firstClient.request('hosted.execution.start', { executionId, @@ -319,7 +313,7 @@ test('packaged managed-coding-v3 resumes after Host death without replaying a co permissionMode: 'bypass', collaborationMode: 'agent', orchestrationMode: 'default', - toolProfile: 'managed-coding-v3', + toolProfile: 'managed-coding-v2', }, content: { text: 'Run scripts/check.mjs with the exact requested arguments.' }, }); @@ -424,15 +418,15 @@ test('packaged managed-coding-v3 resumes after Host death without replaying a co } }); -test('packaged managed-coding-v4 resumes after Host death without replaying an accepted workspace transform', { +test('packaged managed-coding-v2 resumes after Host death without replaying an accepted workspace transform', { timeout: 90_000, }, async (t) => { const helperPath = process.env.MAKA_GITOXIDE_HELPER_PATH; if (!helperPath) { - t.skip('MAKA_GITOXIDE_HELPER_PATH is required for the packaged v4 crash gate'); + t.skip('MAKA_GITOXIDE_HELPER_PATH is required for the packaged v2 crash gate'); return; } - const base = await realpath(await mkdtemp(join(tmpdir(), 'maka-managed-v4-crash-'))); + const base = await realpath(await mkdtemp(join(tmpdir(), 'maka-managed-v2-crash-'))); const root = join(base, 'root'); const executionId = randomUUID(); await mkdir(join(root, 'scripts'), { recursive: true }); @@ -456,7 +450,7 @@ test('packaged managed-coding-v4 resumes after Host death without replaying an a 'commit', '--quiet', '-m', - 'managed v4 baseline', + 'managed v2 baseline', ]); const electronExecutable = resolveElectronExecutable(); @@ -474,10 +468,7 @@ test('packaged managed-coding-v4 resumes after Host death without replaying an a }); const firstClient = await connectClient(root); assert.deepEqual(await firstClient.request('host.execution-profiles.query', {}), { - profiles: - process.platform === 'win32' - ? ['managed-coding-v1'] - : ['managed-coding-v1', 'managed-coding-v2', 'managed-coding-v3', 'managed-coding-v4'], + profiles: process.platform === 'win32' ? [] : ['managed-coding-v2'], }); const startRequest = firstClient.request('hosted.execution.start', { executionId, @@ -486,13 +477,13 @@ test('packaged managed-coding-v4 resumes after Host death without replaying an a modelTarget: { kind: 'explicit', connectionId, - connectionSlug: 'managed-v4-provider', + connectionSlug: 'managed-v2-provider', model: MODEL_ID, }, permissionMode: 'bypass', collaborationMode: 'agent', orchestrationMode: 'default', - toolProfile: 'managed-coding-v4', + toolProfile: 'managed-coding-v2', }, content: { text: 'Generate one accepted workspace output.' }, }); @@ -562,7 +553,7 @@ test('packaged managed-coding-v4 resumes after Host death without replaying an a assert.match(JSON.stringify(provider.requests[2]), /ManagedNodeTransform/u); const readerOwner = await tryAcquireInteractiveRootReader(capability); assert.ok(readerOwner); - if (!readerOwner) throw new Error('Unable to read managed v4 fixture root'); + if (!readerOwner) throw new Error('Unable to read managed v2 fixture root'); const reader = await openInteractiveExecutionStoresForRead(readerOwner.lease); try { const runs = await reader.agentRunStore.listSessionRuns(executionId); @@ -1037,8 +1028,8 @@ async function preparePackagedResources( await writeFile( join(resourcesRoot, 'managed-command-toolchain.json'), `${JSON.stringify({ - schemaVersion: 4, - protocol: 'maka_managed_command_toolchain_release_v4', + schemaVersion: 2, + protocol: 'maka_managed_command_toolchain_release_v2', provider: 'maka/managed-command-toolchain', platform: process.platform, arch: process.arch, @@ -1047,11 +1038,7 @@ async function preparePackagedResources( entrypointRelativePath: 'managed-command/managed-command-helper-main.js', entrypointBytes: (await stat(entrypointPath)).size, entrypointSha256: `sha256:${createHash('sha256').update(entrypoint).digest('hex')}`, - allowedEffectClasses: [ - 'hermetic_observation_v2', - 'hermetic_observation_v3', - 'workspace_transform_v1', - ], + allowedEffectClasses: ['hermetic_observation_v2', 'workspace_transform_v1'], distributionReady: true, })}\n`, 'utf8', diff --git a/packages/runtime-host/src/__tests__/managed-command-sandbox-owner-internal.test.ts b/packages/runtime-host/src/__tests__/managed-command-sandbox-owner-internal.test.ts index 4cd9323e5b..e6edcd730a 100644 --- a/packages/runtime-host/src/__tests__/managed-command-sandbox-owner-internal.test.ts +++ b/packages/runtime-host/src/__tests__/managed-command-sandbox-owner-internal.test.ts @@ -444,7 +444,7 @@ test('runs one explicit accepted-tree Node entrypoint without PATH, network, or platform: process.platform, arch: process.arch, profileVersion: 1, - allowedEffectClasses: ['hermetic_observation_v3'], + allowedEffectClasses: ['hermetic_observation_v2'], }), }); let transformedRequest: SandboxTransformRequest | undefined; @@ -553,7 +553,7 @@ test('runs one accepted-tree transform into the owner-selected single output fil platform: process.platform, arch: process.arch, profileVersion: 1, - allowedEffectClasses: ['hermetic_observation_v3', 'workspace_transform_v1'], + allowedEffectClasses: ['hermetic_observation_v2', 'workspace_transform_v1'], }), }); const transformedRequests: SandboxTransformRequest[] = []; diff --git a/packages/runtime-host/src/__tests__/managed-node-command-admission-owner-internal.test.ts b/packages/runtime-host/src/__tests__/managed-node-command-admission-owner-internal.test.ts index f3dc52b4ef..8a4fc8975d 100644 --- a/packages/runtime-host/src/__tests__/managed-node-command-admission-owner-internal.test.ts +++ b/packages/runtime-host/src/__tests__/managed-node-command-admission-owner-internal.test.ts @@ -23,7 +23,7 @@ import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import test from 'node:test'; -import { MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST } from '@maka/core/runtime-event'; +import { MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST } from '@maka/core/runtime-event'; import { resolveStorageRoot, tryAcquireInteractiveRootOwner } from '@maka/storage/root-authority'; import { createManagedNodeCommandAdmissionOwnerInternal } from '../server/managed-node-command-admission-owner-internal.js'; import { createManagedNodeTestExecutionRootOwnerInternal } from '../server/managed-node-test-admission-owner-internal.js'; @@ -68,7 +68,7 @@ test('admits one exact accepted-tree Node entrypoint and freezes its arguments b }, commandOwner: { readToolchainIdentity: async (effectClass) => { - assert.equal(effectClass, 'hermetic_observation_v3'); + assert.equal(effectClass, 'hermetic_observation_v2'); return { identityDigest: `sha256:${'8'.repeat(64)}`, nodeVersion: '24.13.1', @@ -80,7 +80,7 @@ test('admits one exact accepted-tree Node entrypoint and freezes its arguments b throw new Error('dependencies are not part of managed Node command v1'); }, inspectFile: async (request) => { - assert.equal(request.effectClass, 'hermetic_observation_v3'); + assert.equal(request.effectClass, 'hermetic_observation_v2'); const bytes = await readFile(join(request.inputRoot, ...request.relativePath.split('/'))); return { protocolVersion: 1, @@ -134,12 +134,12 @@ test('admits one exact accepted-tree Node entrypoint and freezes its arguments b abortSignal, }); assert.deepEqual(admission.durableDispatch, { - protocol: 'managed_observation_v3', + protocol: 'managed_observation_v2', ...BOUNDARY, objectFormat: 'sha1', - operationKind: 'node_command_v3', - effectClass: 'hermetic_observation_v3', - executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST, + operationKind: 'node_command_v2', + effectClass: 'hermetic_observation_v2', + executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, toolchainIdentityDigest: `sha256:${'8'.repeat(64)}`, entry: { relativePath: 'scripts/check.mjs', diff --git a/packages/runtime-host/src/__tests__/managed-node-transform-admission-owner-internal.test.ts b/packages/runtime-host/src/__tests__/managed-node-transform-admission-owner-internal.test.ts index 6dc48230b2..33ee3bb168 100644 --- a/packages/runtime-host/src/__tests__/managed-node-transform-admission-owner-internal.test.ts +++ b/packages/runtime-host/src/__tests__/managed-node-transform-admission-owner-internal.test.ts @@ -162,7 +162,8 @@ test('freezes one accepted-tree transformer and returns one owner-bound output p head: HEAD, epoch: EPOCH, }); - assert.equal(prepared.durableDispatch.protocol, 'managed_mutation_v3'); + assert.equal(prepared.durableDispatch.protocol, 'managed_mutation_v2'); + assert.equal(prepared.durableDispatch.operationKind, 'node_transform_v2'); assert.equal( prepared.durableDispatch.executionProfileDigest, MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, diff --git a/packages/runtime-host/src/__tests__/managed-toolchain-artifact-authority-internal.test.ts b/packages/runtime-host/src/__tests__/managed-toolchain-artifact-authority-internal.test.ts index 4a430247d8..08cfd74f5e 100644 --- a/packages/runtime-host/src/__tests__/managed-toolchain-artifact-authority-internal.test.ts +++ b/packages/runtime-host/src/__tests__/managed-toolchain-artifact-authority-internal.test.ts @@ -52,13 +52,13 @@ test('release owner admits one owner-bound toolchain and re-verifies both artifa const verified = await verifyManagedToolchainForInvocationInternal( invocationOwnerToken, capability, - 'hermetic_observation_v1', + 'hermetic_observation_v2', ); assert.equal(verified.executablePath, executablePath); assert.equal(verified.entrypointPath, entrypointPath); assert.match(verified.identityDigest, /^sha256:[0-9a-f]{64}$/u); await assert.rejects( - verifyManagedToolchainForInvocationInternal({}, capability, 'hermetic_observation_v1'), + verifyManagedToolchainForInvocationInternal({}, capability, 'hermetic_observation_v2'), (error) => error instanceof ManagedToolchainArtifactAuthorityError && error.code === 'managed_toolchain_invocation_capability_invalid', @@ -111,7 +111,7 @@ async function releaseState(executablePath: string, entrypointPath: string) { platform: process.platform, arch: process.arch, profileVersion: 1 as const, - allowedEffectClasses: ['hermetic_observation_v1', 'workspace_transform_v1'] as const, + allowedEffectClasses: ['hermetic_observation_v2', 'workspace_transform_v1'] as const, }; } diff --git a/packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts b/packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts index 9e5dd514c8..3798a3fe96 100644 --- a/packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts +++ b/packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts @@ -89,7 +89,7 @@ test('projects only bounded execution boundary presentation facts', async () => }); test('projects the immutable managed coding profile from the durable Session header', async () => { - const fixture = createFixture({ header: { toolProfile: 'managed-coding-v1' } }); + const fixture = createFixture({ header: { toolProfile: 'managed-coding-v2' } }); const outcome = await fixture.coordinator.handlers['session.catalog.query']( { kind: 'get', sessionId: fixture.sessionId }, @@ -101,7 +101,7 @@ test('projects the immutable managed coding profile from the durable Session hea assert.fail('Catalog query did not return the managed Session'); } if ('kind' in outcome.result.session) assert.fail('Managed Session was not representable'); - assert.equal(outcome.result.session.toolProfile, 'managed-coding-v1'); + assert.equal(outcome.result.session.toolProfile, 'managed-coding-v2'); }); test('reduces turn pages to their encoded wire budget without skipping contributions', async () => { diff --git a/packages/runtime-host/src/__tests__/session-catalog-protocol.test.ts b/packages/runtime-host/src/__tests__/session-catalog-protocol.test.ts index 6a5a9afdc3..9a60ec108c 100644 --- a/packages/runtime-host/src/__tests__/session-catalog-protocol.test.ts +++ b/packages/runtime-host/src/__tests__/session-catalog-protocol.test.ts @@ -39,7 +39,7 @@ describe('Session catalog protocol', () => { }); test('preserves the immutable managed coding profile in catalog projections', () => { - const catalog = projection({ toolProfile: 'managed-coding-v1' }); + const catalog = projection({ toolProfile: 'managed-coding-v2' }); assert.deepEqual(decodeSessionCatalogItem(catalog), catalog); }); diff --git a/packages/runtime-host/src/protocol/host-status.ts b/packages/runtime-host/src/protocol/host-status.ts index a2f516db93..fa42b2fa1b 100644 --- a/packages/runtime-host/src/protocol/host-status.ts +++ b/packages/runtime-host/src/protocol/host-status.ts @@ -118,22 +118,14 @@ function decodeHostExecutionProfilesResult(value: unknown): HostExecutionProfile throw invalidProtocolFrame('Invalid Runtime Host execution profiles'); } const profiles = record.profiles.map((profile) => { - if ( - profile !== 'managed-coding-v1' && - profile !== 'managed-coding-v2' && - profile !== 'managed-coding-v3' && - profile !== 'managed-coding-v4' - ) { + if (profile !== 'managed-coding-v2') { throw invalidProtocolFrame('Invalid Runtime Host execution profile'); } return profile; }); - const canonical = [ - 'managed-coding-v1', - 'managed-coding-v2', - 'managed-coding-v3', - 'managed-coding-v4', - ].filter((profile) => profiles.includes(profile as SessionToolProfile)); + const canonical = ['managed-coding-v2'].filter((profile) => + profiles.includes(profile as SessionToolProfile), + ); if ( new Set(profiles).size !== profiles.length || canonical.length !== profiles.length || diff --git a/packages/runtime-host/src/server/current-process-managed-toolchain-internal.ts b/packages/runtime-host/src/server/current-process-managed-toolchain-internal.ts index 995c399478..284890faab 100644 --- a/packages/runtime-host/src/server/current-process-managed-toolchain-internal.ts +++ b/packages/runtime-host/src/server/current-process-managed-toolchain-internal.ts @@ -152,9 +152,9 @@ export async function resolveCurrentProcessManagedToolchainInternal(input: { } } -interface ManagedCommandToolchainManifestV4 { - readonly schemaVersion: 4; - readonly protocol: 'maka_managed_command_toolchain_release_v4'; +interface ManagedCommandToolchainManifestV2 { + readonly schemaVersion: 2; + readonly protocol: 'maka_managed_command_toolchain_release_v2'; readonly provider: 'maka/managed-command-toolchain'; readonly platform: NodeJS.Platform; readonly arch: string; @@ -163,23 +163,19 @@ interface ManagedCommandToolchainManifestV4 { readonly entrypointRelativePath: 'managed-command/managed-command-helper-main.js'; readonly entrypointBytes: number; readonly entrypointSha256: `sha256:${string}`; - readonly allowedEffectClasses: readonly [ - 'hermetic_observation_v2', - 'hermetic_observation_v3', - 'workspace_transform_v1', - ]; + readonly allowedEffectClasses: readonly ['hermetic_observation_v2', 'workspace_transform_v1']; readonly distributionReady: true; } -function decodeManifest(input: unknown): ManagedCommandToolchainManifestV4 { +function decodeManifest(input: unknown): ManagedCommandToolchainManifestV2 { if (!input || typeof input !== 'object' || Array.isArray(input)) { throw invalidManifest('Managed command toolchain manifest must be an object'); } const value = input as Record; if ( Object.keys(value).sort().join('\0') !== [...MANIFEST_KEYS].sort().join('\0') || - value.schemaVersion !== 4 || - value.protocol !== 'maka_managed_command_toolchain_release_v4' || + value.schemaVersion !== 2 || + value.protocol !== 'maka_managed_command_toolchain_release_v2' || value.provider !== 'maka/managed-command-toolchain' || !['win32', 'darwin', 'linux'].includes(value.platform as string) || typeof value.arch !== 'string' || @@ -194,15 +190,14 @@ function decodeManifest(input: unknown): ManagedCommandToolchainManifestV4 { typeof value.entrypointSha256 !== 'string' || !SHA256_PATTERN.test(value.entrypointSha256) || !Array.isArray(value.allowedEffectClasses) || - value.allowedEffectClasses.length !== 3 || + value.allowedEffectClasses.length !== 2 || value.allowedEffectClasses[0] !== 'hermetic_observation_v2' || - value.allowedEffectClasses[1] !== 'hermetic_observation_v3' || - value.allowedEffectClasses[2] !== 'workspace_transform_v1' || + value.allowedEffectClasses[1] !== 'workspace_transform_v1' || value.distributionReady !== true ) { throw invalidManifest('Managed command toolchain manifest is invalid'); } - return value as unknown as ManagedCommandToolchainManifestV4; + return value as unknown as ManagedCommandToolchainManifestV2; } async function fileIdentity( diff --git a/packages/runtime-host/src/server/execution-composition.ts b/packages/runtime-host/src/server/execution-composition.ts index b6863e2db6..9a0d453dfe 100644 --- a/packages/runtime-host/src/server/execution-composition.ts +++ b/packages/runtime-host/src/server/execution-composition.ts @@ -838,7 +838,7 @@ export async function createExecutionRuntimeHostComposition( sourceRoot: backendContext.header.cwd, sessionId: backendContext.sessionId, abortSignal: backendContext.abortSignal, - ...(backendContext.header.toolProfile === 'managed-coding-v4' && + ...(backendContext.header.toolProfile === 'managed-coding-v2' && managedCommandOwner ? { managedNodeTransform: { @@ -851,9 +851,7 @@ export async function createExecutionRuntimeHostComposition( })() : undefined; const managedNodeTestAdmission = - backendContext.header.toolProfile === 'managed-coding-v2' || - backendContext.header.toolProfile === 'managed-coding-v3' || - backendContext.header.toolProfile === 'managed-coding-v4' + backendContext.header.toolProfile === 'managed-coding-v2' ? await (async () => { const dependencySnapshotAuthority = await openManagedDependencySnapshotAuthority?.(); @@ -874,8 +872,7 @@ export async function createExecutionRuntimeHostComposition( })() : undefined; const managedNodeCommandAdmission = - backendContext.header.toolProfile === 'managed-coding-v3' || - backendContext.header.toolProfile === 'managed-coding-v4' + backendContext.header.toolProfile === 'managed-coding-v2' ? await (async () => { if (!managedCommandOwner || !managedSession) { throw new Error( @@ -1046,18 +1043,13 @@ export async function createExecutionRuntimeHostComposition( modelId: header.model, hostTools: [ ...hostTools, - ...((header.toolProfile === 'managed-coding-v2' || - header.toolProfile === 'managed-coding-v3' || - header.toolProfile === 'managed-coding-v4') && - managedNodeTestToolDeclaration + ...(header.toolProfile === 'managed-coding-v2' && managedNodeTestToolDeclaration ? [managedNodeTestToolDeclaration] : []), - ...((header.toolProfile === 'managed-coding-v3' || - header.toolProfile === 'managed-coding-v4') && - managedNodeCommandToolDeclaration + ...(header.toolProfile === 'managed-coding-v2' && managedNodeCommandToolDeclaration ? [managedNodeCommandToolDeclaration] : []), - ...(header.toolProfile === 'managed-coding-v4' && managedNodeTransformToolDeclaration + ...(header.toolProfile === 'managed-coding-v2' && managedNodeTransformToolDeclaration ? [managedNodeTransformToolDeclaration] : []), ...graphTools, @@ -2096,20 +2088,11 @@ export async function createExecutionRuntimeHostComposition( handlers, moduleIds: Object.freeze(domainModules.map(({ id }) => id)), executionProfiles: Object.freeze([ - ...(gitoxideHelperCapability ? (['managed-coding-v1'] as const) : []), - ...(gitoxideHelperCapability && managedNodeTestToolDeclaration - ? (['managed-coding-v2'] as const) - : []), - ...(gitoxideHelperCapability && - managedNodeTestToolDeclaration && - managedNodeCommandToolDeclaration - ? (['managed-coding-v3'] as const) - : []), ...(gitoxideHelperCapability && managedNodeTestToolDeclaration && managedNodeCommandToolDeclaration && managedNodeTransformToolDeclaration - ? (['managed-coding-v4'] as const) + ? (['managed-coding-v2'] as const) : []), ]), workspaceExecution: requireWorkspaceExecution(workspaceExecution), diff --git a/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts b/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts index 652e66a19c..bc67d939db 100644 --- a/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts +++ b/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts @@ -25,7 +25,7 @@ import { type RuntimeEvent, type RuntimeEventManagedWorkspaceMutation, type RuntimeEventManagedWorkspaceMutationV2, - type RuntimeEventManagedWorkspaceMutationV3, + type RuntimeEventManagedWorkspaceNodeTransformMutationV2, } from '@maka/core/runtime-event'; import { canonicalToolArgsHash } from '@maka/core/tool-args-identity'; import type { @@ -106,7 +106,7 @@ export interface GitoxideManagedNodeTransformAdmissionInternal { readonly epoch: WorkspaceEpochRecordV1; }): Promise< Readonly<{ - durableDispatch: RuntimeEventManagedWorkspaceMutationV3; + durableDispatch: RuntimeEventManagedWorkspaceNodeTransformMutationV2; dispose(): Promise; }> >; @@ -553,6 +553,7 @@ function freezeManagedDispatch(input: { baseTreeOid: input.head.treeOid, expectedPath: input.expectedPath, pathPolicyVersion: 3 as const, + operationKind: 'write_edit_v2' as const, executionProfileDigest: MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, }); } @@ -825,9 +826,9 @@ function managedMutationMatchesToolName( toolName: string, managed: RuntimeEventManagedWorkspaceMutation | undefined, ): boolean { - return managed?.protocol === 'managed_mutation_v2' + return managed?.operationKind === 'write_edit_v2' ? toolName === 'Write' || toolName === 'Edit' - : managed?.protocol === 'managed_mutation_v3' && toolName === 'ManagedNodeTransform'; + : managed?.operationKind === 'node_transform_v2' && toolName === 'ManagedNodeTransform'; } function parentMatchesSuccessor( diff --git a/packages/runtime-host/src/server/hosted-execution-tool-profile.ts b/packages/runtime-host/src/server/hosted-execution-tool-profile.ts index 6f90e7c76f..01c1c2dd58 100644 --- a/packages/runtime-host/src/server/hosted-execution-tool-profile.ts +++ b/packages/runtime-host/src/server/hosted-execution-tool-profile.ts @@ -31,35 +31,29 @@ const HEADLESS_CODING_V1_TOOL_NAMES = [ 'apply_patch', ] as const; -const MANAGED_CODING_V1_TOOL_NAMES = ['Read', 'Glob', 'Grep', 'Write', 'Edit'] as const; -const MANAGED_CODING_V2_TOOL_NAMES = [...MANAGED_CODING_V1_TOOL_NAMES, 'ManagedNodeTest'] as const; -const MANAGED_CODING_V3_TOOL_NAMES = [...MANAGED_CODING_V2_TOOL_NAMES, 'ManagedNodeRun'] as const; -const MANAGED_CODING_V4_TOOL_NAMES = [ - ...MANAGED_CODING_V3_TOOL_NAMES, +const MANAGED_CODING_V2_TOOL_NAMES = [ + 'Read', + 'Glob', + 'Grep', + 'Write', + 'Edit', + 'ManagedNodeTest', + 'ManagedNodeRun', 'ManagedNodeTransform', ] as const; -const MANAGED_CODING_V1_SYSTEM_PROMPT = [ +const MANAGED_CODING_V2_SYSTEM_PROMPT = [ 'Inspect the managed Git workspace with Read, Glob, and Grep.', 'Modify it with Write and Edit.', 'All five tools consume the same immutable accepted Git tree.', 'These tools transform immutable accepted Git content and publish an owner-verified successor.', 'No shell, attached-workspace read, or unmanaged filesystem authority is available in this profile.', - 'Stop when the requested changes are complete.', -].join('\n'); -const MANAGED_CODING_V2_SYSTEM_PROMPT = [ - MANAGED_CODING_V1_SYSTEM_PROMPT, 'Run only explicit Node tests with ManagedNodeTest.', 'The test consumes the same immutable accepted Git tree and, when present, an immutable read-only dependency snapshot. It cannot install dependencies, use package scripts, PATH, network, or the attached checkout.', -].join('\n'); -const MANAGED_CODING_V3_SYSTEM_PROMPT = [ - MANAGED_CODING_V2_SYSTEM_PROMPT, 'Run an explicit accepted-workspace Node entrypoint with ManagedNodeRun only when a direct script check is useful.', 'ManagedNodeRun has no PATH, network, child-process, package-script, dependency-installation, or attached-checkout authority. Its writes are limited to disposable scratch.', -].join('\n'); -const MANAGED_CODING_V4_SYSTEM_PROMPT = [ - MANAGED_CODING_V3_SYSTEM_PROMPT, 'Use ManagedNodeTransform only when one accepted-tree JavaScript transformer should produce one bounded UTF-8 workspace file.', 'ManagedNodeTransform cannot write the managed worktree directly; Gitoxide and SQLite must accept its exact output as a new successor.', + 'Stop when the requested changes are complete.', ].join('\n'); const HEADLESS_CODING_V1_SYSTEM_PROMPT = [ @@ -104,13 +98,6 @@ export function hostedExecutionRunProfile( memoryExtraction: false, }; } - if (profile === 'managed-coding-v1') { - return { - toolNames: MANAGED_CODING_V1_TOOL_NAMES, - systemPrompt: MANAGED_CODING_V1_SYSTEM_PROMPT, - memoryExtraction: false, - }; - } if (profile === 'managed-coding-v2') { return { toolNames: MANAGED_CODING_V2_TOOL_NAMES, @@ -118,20 +105,6 @@ export function hostedExecutionRunProfile( memoryExtraction: false, }; } - if (profile === 'managed-coding-v3') { - return { - toolNames: MANAGED_CODING_V3_TOOL_NAMES, - systemPrompt: MANAGED_CODING_V3_SYSTEM_PROMPT, - memoryExtraction: false, - }; - } - if (profile === 'managed-coding-v4') { - return { - toolNames: MANAGED_CODING_V4_TOOL_NAMES, - systemPrompt: MANAGED_CODING_V4_SYSTEM_PROMPT, - memoryExtraction: false, - }; - } if (profile === 'workhub-coordination-v1') { return { toolNames: [], @@ -156,12 +129,7 @@ export function projectHostedExecutionTools( throw new Error(`Hosted tool profile is unavailable: ${missing.join(', ')}`); } return (selected as MakaTool[]).map((tool) => { - if ( - profile === 'managed-coding-v1' || - profile === 'managed-coding-v2' || - profile === 'managed-coding-v3' || - profile === 'managed-coding-v4' - ) { + if (profile === 'managed-coding-v2') { if (tool.name === 'Read' || tool.name === 'Glob' || tool.name === 'Grep') { return { ...tool, recoveryMode: 'replay_safe' }; } @@ -176,7 +144,7 @@ export function projectHostedExecutionTools( return { ...tool, recoveryMode: 'replay_safe', - durableExecutionProfile: 'managed_observation_v3', + durableExecutionProfile: 'managed_observation_v2', }; } if (tool.name === 'ManagedNodeTransform') { diff --git a/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts b/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts index 9fe718e1ff..fd6ff0846e 100644 --- a/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts +++ b/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts @@ -89,7 +89,7 @@ export interface ManagedNodeTransformResultInternal { export interface ManagedCommandSandboxOwnerInternal { readToolchainIdentity( - effectClass?: 'hermetic_observation_v2' | 'hermetic_observation_v3' | 'workspace_transform_v1', + effectClass?: 'hermetic_observation_v2' | 'workspace_transform_v1', ): Promise; readDependencyIdentity( lease: ManagedDependencySnapshotLease, @@ -128,10 +128,7 @@ export interface ManagedCommandInspectFileInputInternal { readonly relativePath: string; readonly inputRoot: string; readonly scratchRoot: string; - readonly effectClass?: - | 'hermetic_observation_v2' - | 'hermetic_observation_v3' - | 'workspace_transform_v1'; + readonly effectClass?: 'hermetic_observation_v2' | 'workspace_transform_v1'; readonly abortSignal?: AbortSignal; } @@ -176,10 +173,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { readonly kind: 'helper'; readonly operation: 'inspect_file_v1' | 'inspect_files_v1'; readonly relativePaths: readonly string[]; - readonly effectClass?: - | 'hermetic_observation_v2' - | 'hermetic_observation_v3' - | 'workspace_transform_v1'; + readonly effectClass?: 'hermetic_observation_v2' | 'workspace_transform_v1'; } | { readonly kind: 'node_tests'; @@ -218,10 +212,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { ? 'workspace_transform_v1' : invocation.kind === 'helper' && invocation.effectClass === 'workspace_transform_v1' ? 'workspace_transform_v1' - : invocation.kind === 'node_entrypoint' || - (invocation.kind === 'helper' && invocation.effectClass === 'hermetic_observation_v3') - ? 'hermetic_observation_v3' - : 'hermetic_observation_v2', + : 'hermetic_observation_v2', ); request.abortSignal?.throwIfAborted(); const profile = hermeticObservationProfile(inputRoot, scratchRoot, dependency?.dependencyRoot); @@ -323,10 +314,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { } return Object.freeze({ async readToolchainIdentity( - effectClass: - | 'hermetic_observation_v2' - | 'hermetic_observation_v3' - | 'workspace_transform_v1' = 'hermetic_observation_v2', + effectClass: 'hermetic_observation_v2' | 'workspace_transform_v1' = 'hermetic_observation_v2', ) { const toolchain = await verifyManagedToolchainForInvocationInternal( input.invocationOwnerToken, @@ -429,7 +417,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { kind: 'helper', operation: 'inspect_file_v1', relativePaths: [request.entryPath], - effectClass: 'hermetic_observation_v3', + effectClass: 'hermetic_observation_v2', }); const entry = decodeObservation(before.stdout, request.entryPath, before.nodeVersion); const result = await execute(request, { @@ -441,7 +429,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { kind: 'helper', operation: 'inspect_file_v1', relativePaths: [request.entryPath], - effectClass: 'hermetic_observation_v3', + effectClass: 'hermetic_observation_v2', }); const afterEntry = decodeObservation(after.stdout, request.entryPath, after.nodeVersion); if ( @@ -493,7 +481,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { kind: 'helper', operation: 'inspect_file_v1', relativePaths: [request.entryPath], - effectClass: 'hermetic_observation_v3', + effectClass: 'hermetic_observation_v2', }); const entry = decodeObservation(before.stdout, request.entryPath, before.nodeVersion); const result = await execute(request, { @@ -506,7 +494,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { kind: 'helper', operation: 'inspect_file_v1', relativePaths: [request.entryPath], - effectClass: 'hermetic_observation_v3', + effectClass: 'hermetic_observation_v2', }); const afterEntry = decodeObservation(after.stdout, request.entryPath, after.nodeVersion); if ( @@ -599,10 +587,7 @@ function invocationLabel( readonly kind: 'helper'; readonly operation: 'inspect_file_v1' | 'inspect_files_v1'; readonly relativePaths: readonly string[]; - readonly effectClass?: - | 'hermetic_observation_v2' - | 'hermetic_observation_v3' - | 'workspace_transform_v1'; + readonly effectClass?: 'hermetic_observation_v2' | 'workspace_transform_v1'; } | { readonly kind: 'node_tests'; readonly relativePaths: readonly string[] } | { diff --git a/packages/runtime-host/src/server/managed-node-command-admission-owner-internal.ts b/packages/runtime-host/src/server/managed-node-command-admission-owner-internal.ts index 5bb9258fc3..56a5e0d41d 100644 --- a/packages/runtime-host/src/server/managed-node-command-admission-owner-internal.ts +++ b/packages/runtime-host/src/server/managed-node-command-admission-owner-internal.ts @@ -19,9 +19,9 @@ import { isCanonicalManagedMutationPathV1, - MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST, + MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, type RuntimeEventManagedObservationFileV1, - type RuntimeEventManagedWorkspaceObservationV3, + type RuntimeEventManagedWorkspaceNodeCommandObservationV2, } from '@maka/core/runtime-event'; import { mkdir } from 'node:fs/promises'; import type { MakaTool, RuntimeManagedObservationAdmission } from '@maka/runtime/tool-runtime'; @@ -86,7 +86,7 @@ export function createManagedNodeCommandAdmissionOwnerInternal(input: { parameters: MANAGED_NODE_COMMAND_PARAMETERS, categoryHint: 'custom_tool', recoveryMode: 'replay_safe', - durableExecutionProfile: 'managed_observation_v3', + durableExecutionProfile: 'managed_observation_v2', executionSemantics: 'exclusive_step', nesting: 'direct_only', impl: async () => { @@ -137,7 +137,7 @@ export function createManagedNodeCommandAdmissionOwnerInternal(input: { const normalized = requireManagedNodeCommandArgs(request.persistedArgs); const [boundary, toolchain] = await Promise.all([ input.sourceOwner.readAcceptedBoundary(request.abortSignal), - input.commandOwner.readToolchainIdentity('hermetic_observation_v3'), + input.commandOwner.readToolchainIdentity('hermetic_observation_v2'), ]); assertAcceptedBoundary(boundary); if ( @@ -167,7 +167,7 @@ export function createManagedNodeCommandAdmissionOwnerInternal(input: { relativePath: normalized.entryPath, inputRoot: executionRoot.inputRoot, scratchRoot: executionRoot.scratchRoot, - effectClass: 'hermetic_observation_v3', + effectClass: 'hermetic_observation_v2', abortSignal: request.abortSignal, }); const entry = Object.freeze({ @@ -178,17 +178,19 @@ export function createManagedNodeCommandAdmissionOwnerInternal(input: { if (entry.relativePath !== normalized.entryPath) { throw new Error('Managed Node command entry identity is invalid'); } - const durableDispatch: RuntimeEventManagedWorkspaceObservationV3 = Object.freeze({ - protocol: 'managed_observation_v3', - ...boundary, - objectFormat: 'sha1', - operationKind: 'node_command_v3', - effectClass: 'hermetic_observation_v3', - executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST, - toolchainIdentityDigest: toolchain.identityDigest, - entry, - args: normalized.args, - }); + const durableDispatch: RuntimeEventManagedWorkspaceNodeCommandObservationV2 = Object.freeze( + { + protocol: 'managed_observation_v2', + ...boundary, + objectFormat: 'sha1', + operationKind: 'node_command_v2', + effectClass: 'hermetic_observation_v2', + executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, + toolchainIdentityDigest: toolchain.identityDigest, + entry, + args: normalized.args, + }, + ); let operation: Promise | undefined; let state: 'ready' | 'running' | 'complete' | 'disposed' = 'ready'; admittedByInputRoot.set( @@ -241,7 +243,7 @@ export function createManagedNodeCommandToolDeclarationInternal(): MakaTool< parameters: MANAGED_NODE_COMMAND_PARAMETERS, categoryHint: 'custom_tool', recoveryMode: 'replay_safe', - durableExecutionProfile: 'managed_observation_v3', + durableExecutionProfile: 'managed_observation_v2', executionSemantics: 'exclusive_step', nesting: 'direct_only', impl: ownerUnavailable, diff --git a/packages/runtime-host/src/server/managed-node-transform-admission-owner-internal.ts b/packages/runtime-host/src/server/managed-node-transform-admission-owner-internal.ts index 681712a993..5f886bdef3 100644 --- a/packages/runtime-host/src/server/managed-node-transform-admission-owner-internal.ts +++ b/packages/runtime-host/src/server/managed-node-transform-admission-owner-internal.ts @@ -21,7 +21,7 @@ import { isCanonicalManagedMutationPathV1, MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, type RuntimeEventManagedObservationFileV1, - type RuntimeEventManagedWorkspaceMutationV3, + type RuntimeEventManagedWorkspaceNodeTransformMutationV2, } from '@maka/core/runtime-event'; import type { WorkspaceEpochRecordV1, @@ -206,8 +206,8 @@ export function createManagedNodeTransformOwnerInternal(input: { }; prepared.set(request.operationId, state); installed = true; - const durableDispatch: RuntimeEventManagedWorkspaceMutationV3 = Object.freeze({ - protocol: 'managed_mutation_v3', + const durableDispatch: RuntimeEventManagedWorkspaceNodeTransformMutationV2 = Object.freeze({ + protocol: 'managed_mutation_v2', repositoryId: head.repositoryId, workspaceId: head.workspaceId, workspaceEpochId: head.workspaceEpochId, @@ -220,7 +220,7 @@ export function createManagedNodeTransformOwnerInternal(input: { baseTreeOid: head.treeOid, expectedPath: args.path, pathPolicyVersion: 3, - operationKind: 'node_transform_v1', + operationKind: 'node_transform_v2', executionProfileDigest: MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, toolchainIdentityDigest: toolchain.identityDigest, entry, diff --git a/packages/runtime-host/src/server/managed-toolchain-artifact-authority-internal.ts b/packages/runtime-host/src/server/managed-toolchain-artifact-authority-internal.ts index 718f08e767..e04f00afcd 100644 --- a/packages/runtime-host/src/server/managed-toolchain-artifact-authority-internal.ts +++ b/packages/runtime-host/src/server/managed-toolchain-artifact-authority-internal.ts @@ -28,9 +28,7 @@ const MAX_ENTRYPOINT_BYTES = 4 * 1024 * 1024; const HASH_BUFFER_BYTES = 64 * 1024; export const MANAGED_TOOLCHAIN_EFFECT_CLASSES_INTERNAL = Object.freeze([ - 'hermetic_observation_v1', 'hermetic_observation_v2', - 'hermetic_observation_v3', 'workspace_transform_v1', ] as const); export type ManagedToolchainEffectClassInternal = diff --git a/packages/runtime/src/__tests__/session-manager.test.ts b/packages/runtime/src/__tests__/session-manager.test.ts index 7ec007be84..254a2dc071 100644 --- a/packages/runtime/src/__tests__/session-manager.test.ts +++ b/packages/runtime/src/__tests__/session-manager.test.ts @@ -4533,7 +4533,7 @@ describe('SessionManager permission mode updates', () => { newId: nextId(), now: nextNow(6_528), }); - const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v1' })); + const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v2' })); await collectSessionEvents( manager.sendMessage(session.id, { @@ -4564,7 +4564,7 @@ describe('SessionManager permission mode updates', () => { newId: nextId(), now: nextNow(6_529), }); - const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v1' })); + const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v2' })); await expectRejects( collectSessionEvents( @@ -4723,12 +4723,7 @@ describe('SessionManager permission mode updates', () => { expect(plan.rejectionReasons).toEqual(['safety_observation_unavailable']); }); - for (const toolProfile of [ - 'managed-coding-v1', - 'managed-coding-v2', - 'managed-coding-v3', - 'managed-coding-v4', - ] as const) { + for (const toolProfile of ['managed-coding-v2'] as const) { test(`never downgrades a ${toolProfile} session when its workspace boundary is unavailable`, async () => { const store = new MemorySessionStore(); const runStore = new MemoryAgentRunStore(); @@ -4826,7 +4821,7 @@ describe('SessionManager permission mode updates', () => { newId: nextId(), now: nextNow(6_536), }); - const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v1' })); + const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v2' })); const plan = await manager.planAuthoritativeSafeBoundaryContinuation(session.id, { sourceRunId: 'source-run-managed-manual-resume', @@ -4847,7 +4842,7 @@ describe('SessionManager permission mode updates', () => { newId: nextId(), now: nextNow(6_537), }); - const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v1' })); + const session = await manager.createSession(makeInput({ toolProfile: 'managed-coding-v2' })); const header = await store.readHeader(session.id); await runStore.createRun( makeRunHeader({ diff --git a/packages/runtime/src/__tests__/tool-runtime-durable-boundary.test.ts b/packages/runtime/src/__tests__/tool-runtime-durable-boundary.test.ts index 2bbe40bd4c..78d788f19c 100644 --- a/packages/runtime/src/__tests__/tool-runtime-durable-boundary.test.ts +++ b/packages/runtime/src/__tests__/tool-runtime-durable-boundary.test.ts @@ -75,7 +75,7 @@ describe('ToolRuntime durable boundary', () => { throw new Error('ordinary implementation must not execute'); }); managedTest.name = 'ManagedNodeTest'; - managedTest.durableExecutionProfile = 'managed_observation_v1'; + managedTest.durableExecutionProfile = 'managed_observation_v2'; managedTest.managedObservationImpl = async (_args, _ctx, execution) => { order.push('observe'); assert.deepEqual(execution, { inputRoot: '/accepted', scratchRoot: '/scratch' }); @@ -103,7 +103,7 @@ describe('ToolRuntime durable boundary', () => { assert.equal(outcomes.length, 1); }); - it('binds an explicit Node entrypoint and arguments before managed observation v3 T1', async () => { + it('binds an explicit Node entrypoint and arguments before managed observation v2 T1', async () => { const prepared: ToolPreparedCommit[] = []; const harness = makeHarness( { @@ -117,7 +117,7 @@ describe('ToolRuntime durable boundary', () => { 'run-1', { admitManagedObservation: async () => ({ - durableDispatch: managedObservationDispatchV3(), + durableDispatch: managedCommandObservationDispatchV2(), execute: async (operation) => await operation({ inputRoot: '/accepted', scratchRoot: '/scratch' }), dispose: async () => undefined, @@ -128,7 +128,7 @@ describe('ToolRuntime durable boundary', () => { throw new Error('ordinary implementation must not execute'); }); managedRun.name = 'ManagedNodeRun'; - managedRun.durableExecutionProfile = 'managed_observation_v3'; + managedRun.durableExecutionProfile = 'managed_observation_v2'; managedRun.managedObservationImpl = async (_args, _ctx, execution) => { assert.deepEqual(execution, { inputRoot: '/accepted', scratchRoot: '/scratch' }); return { exitCode: 0, stdout: 'ok\n', stderr: '' }; @@ -143,7 +143,7 @@ describe('ToolRuntime durable boundary', () => { ); assert.deepEqual( prepared[0]?.dispatchRuntimeEvent.actions?.toolDispatch?.managedObservation, - managedObservationDispatchV3(), + managedCommandObservationDispatchV2(), ); }); @@ -340,7 +340,7 @@ describe('ToolRuntime durable boundary', () => { throw new Error('ordinary implementation must not execute'); }); managedTest.name = 'ManagedNodeTest'; - managedTest.durableExecutionProfile = 'managed_observation_v1'; + managedTest.durableExecutionProfile = 'managed_observation_v2'; managedTest.managedObservationImpl = async () => { observations += 1; return { passed: 1, failed: 0 }; @@ -2165,6 +2165,7 @@ function managedMutationDispatch(expectedPath = 'notes.txt') { baseTreeOid: '2'.repeat(40), expectedPath, pathPolicyVersion: 3 as const, + operationKind: 'write_edit_v2' as const, executionProfileDigest: 'sha256:7ff4eb75e8833f7bf97eaa252f47316f609093d89aa32acdeae7fc6caaa11a92' as const, }; @@ -2172,7 +2173,7 @@ function managedMutationDispatch(expectedPath = 'notes.txt') { function managedMutationDispatchV3() { return { - protocol: 'managed_mutation_v3' as const, + protocol: 'managed_mutation_v2' as const, repositoryId: 'repository_11111111111111111111111111111111', workspaceId: 'workspace_22222222222222222222222222222222', workspaceEpochId: 'epoch_33333333333333333333333333333333', @@ -2185,7 +2186,7 @@ function managedMutationDispatchV3() { baseTreeOid: '2'.repeat(40), expectedPath: 'generated/output.txt', pathPolicyVersion: 3 as const, - operationKind: 'node_transform_v1' as const, + operationKind: 'node_transform_v2' as const, executionProfileDigest: 'sha256:7ff4eb75e8833f7bf97eaa252f47316f609093d89aa32acdeae7fc6caaa11a92' as const, toolchainIdentityDigest: `sha256:${'3'.repeat(64)}` as const, @@ -2200,7 +2201,7 @@ function managedMutationDispatchV3() { function managedObservationDispatch() { return { - protocol: 'managed_observation_v1' as const, + protocol: 'managed_observation_v2' as const, repositoryId: 'repository_11111111111111111111111111111111', workspaceId: 'workspace_22222222222222222222222222222222', workspaceEpochId: 'epoch_33333333333333333333333333333333', @@ -2211,10 +2212,10 @@ function managedObservationDispatch() { acceptedHeadRevision: 2, acceptedCommitOid: '1'.repeat(40), acceptedTreeOid: '2'.repeat(40), - operationKind: 'node_test_v1' as const, - effectClass: 'hermetic_observation_v1' as const, + operationKind: 'node_test_v2' as const, + effectClass: 'hermetic_observation_v2' as const, executionProfileDigest: - 'sha256:816111c078084a460fad2d6d78a545d127b158d8089237e3a238878936d86e6e' as const, + 'sha256:3702995e2893e5a4a813998665fd0ff6758d68e8faf64685fcc6319e250c0a46' as const, toolchainIdentityDigest: `sha256:${'3'.repeat(64)}` as const, files: [ { @@ -2223,6 +2224,7 @@ function managedObservationDispatch() { sha256: `sha256:${'4'.repeat(64)}` as const, }, ], + dependency: { kind: 'none' as const }, }; } @@ -2233,7 +2235,7 @@ function managedObservationDispatchV2() { operationKind: 'node_test_v2' as const, effectClass: 'hermetic_observation_v2' as const, executionProfileDigest: - 'sha256:be3ca7af72a0d35cda471a6de71eed7dd260890624f11c8b5d71cccb2067c333' as const, + 'sha256:3702995e2893e5a4a813998665fd0ff6758d68e8faf64685fcc6319e250c0a46' as const, dependency: { kind: 'managed_dependency_snapshot_v1' as const, environmentId: `sha256:${'5'.repeat(64)}` as const, @@ -2246,15 +2248,15 @@ function managedObservationDispatchV2() { }; } -function managedObservationDispatchV3() { - const { files: _files, ...base } = managedObservationDispatch(); +function managedCommandObservationDispatchV2() { + const { files: _files, dependency: _dependency, ...base } = managedObservationDispatch(); return { ...base, - protocol: 'managed_observation_v3' as const, - operationKind: 'node_command_v3' as const, - effectClass: 'hermetic_observation_v3' as const, + protocol: 'managed_observation_v2' as const, + operationKind: 'node_command_v2' as const, + effectClass: 'hermetic_observation_v2' as const, executionProfileDigest: - 'sha256:8cde26b9e1b475fac75f0980baf04d09baed94184757bf02c3cc12fc5df2b50e' as const, + 'sha256:3702995e2893e5a4a813998665fd0ff6758d68e8faf64685fcc6319e250c0a46' as const, entry: { relativePath: 'scripts/check.mjs', bytes: 123, diff --git a/packages/runtime/src/tool-runtime.ts b/packages/runtime/src/tool-runtime.ts index 3bc8ecc729..5d4cbde1e9 100644 --- a/packages/runtime/src/tool-runtime.ts +++ b/packages/runtime/src/tool-runtime.ts @@ -66,17 +66,13 @@ import type { ToolInvocationRecord } from '@maka/core/usage-stats/types'; import { redactSecrets } from '@maka/core/redaction'; import { decodeRuntimeEvent, - MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST, MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, - MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST, MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST, MANAGED_MUTATION_EXECUTION_PROFILE_V2_SPEC, TOOL_BOUNDARY_PROTOCOL_V1, type RuntimeEvent, type RuntimeEventManagedWorkspaceObservation, - type RuntimeEventManagedWorkspaceObservationV1, type RuntimeEventManagedWorkspaceObservationV2, - type RuntimeEventManagedWorkspaceObservationV3, type RuntimeEventManagedWorkspaceMutation, } from '@maka/core/runtime-event'; import { isDeepStrictEqual } from 'node:util'; @@ -181,11 +177,7 @@ export interface MakaTool

{ /** Crash-recovery contract used by the durable tool boundary. */ recoveryMode?: ToolRecoveryMode; /** Durable execution profile selected by the Host before T1. */ - durableExecutionProfile?: - | 'managed_mutation_v2' - | 'managed_observation_v1' - | 'managed_observation_v2' - | 'managed_observation_v3'; + durableExecutionProfile?: 'managed_mutation_v2' | 'managed_observation_v2'; /** * Pure Write/Edit transform for managed mutation mode. It receives only the * frozen arguments and must not read or mutate the live workspace. @@ -1482,15 +1474,9 @@ export class ToolRuntime { return this.errorReturn(reason); } } - if ( - tool.durableExecutionProfile === 'managed_observation_v1' || - tool.durableExecutionProfile === 'managed_observation_v2' || - tool.durableExecutionProfile === 'managed_observation_v3' - ) { + if (tool.durableExecutionProfile === 'managed_observation_v2') { const expectedToolName = - tool.durableExecutionProfile === 'managed_observation_v3' - ? 'ManagedNodeRun' - : 'ManagedNodeTest'; + tool.name === 'ManagedNodeRun' ? 'ManagedNodeRun' : 'ManagedNodeTest'; if ( tool.name !== expectedToolName || tool.recoveryMode !== 'replay_safe' || @@ -2399,12 +2385,8 @@ export class ToolRuntime { } if (input.managedObservation) { const profileMatches = - (input.tool.durableExecutionProfile === 'managed_observation_v1' && - isManagedObservationV1(input.managedObservation)) || - (input.tool.durableExecutionProfile === 'managed_observation_v2' && - isManagedObservationV2(input.managedObservation)) || - (input.tool.durableExecutionProfile === 'managed_observation_v3' && - isManagedObservationV3(input.managedObservation)); + input.tool.durableExecutionProfile === 'managed_observation_v2' && + isManagedObservationV2(input.managedObservation); const callMatches = isManagedObservationCallMatch( input.tool.name, input.persistedArgs, @@ -3360,7 +3342,7 @@ function managedMutationDispatchMatchesToolCall( ) { return false; } - if (mutation.protocol === 'managed_mutation_v2') { + if (mutation.operationKind === 'write_edit_v2') { return ( tool.durableExecutionProfile === 'managed_mutation_v2' && (tool.name === 'Write' || tool.name === 'Edit') && @@ -3370,7 +3352,7 @@ function managedMutationDispatchMatchesToolCall( return ( tool.durableExecutionProfile === 'managed_mutation_v2' && tool.name === 'ManagedNodeTransform' && - mutation.operationKind === 'node_transform_v1' && + mutation.operationKind === 'node_transform_v2' && mutation.executionProfileDigest === MANAGED_MUTATION_EXECUTION_PROFILE_V2_DIGEST && args.entryPath === mutation.entry.relativePath && isDeepStrictEqual(args.args ?? [], mutation.args) @@ -4084,39 +4066,17 @@ async function disposeManagedObservationAdmission( } } -function isManagedObservationV1( - value: RuntimeEventManagedWorkspaceObservation, -): value is RuntimeEventManagedWorkspaceObservationV1 { - return ( - value.protocol === 'managed_observation_v1' && - value.operationKind === 'node_test_v1' && - value.effectClass === 'hermetic_observation_v1' && - value.executionProfileDigest === MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST - ); -} - function isManagedObservationV2( value: RuntimeEventManagedWorkspaceObservation, ): value is RuntimeEventManagedWorkspaceObservationV2 { return ( value.protocol === 'managed_observation_v2' && - value.operationKind === 'node_test_v2' && + (value.operationKind === 'node_test_v2' || value.operationKind === 'node_command_v2') && value.effectClass === 'hermetic_observation_v2' && value.executionProfileDigest === MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST ); } -function isManagedObservationV3( - value: RuntimeEventManagedWorkspaceObservation, -): value is RuntimeEventManagedWorkspaceObservationV3 { - return ( - value.protocol === 'managed_observation_v3' && - value.operationKind === 'node_command_v3' && - value.effectClass === 'hermetic_observation_v3' && - value.executionProfileDigest === MANAGED_OBSERVATION_EXECUTION_PROFILE_V3_DIGEST - ); -} - function isManagedObservationCallMatch( toolName: string, persistedArgs: unknown, @@ -4125,7 +4085,7 @@ function isManagedObservationCallMatch( if (!persistedArgs || typeof persistedArgs !== 'object' || Array.isArray(persistedArgs)) { return false; } - if (isManagedObservationV3(observation)) { + if (observation.operationKind === 'node_command_v2') { const record = persistedArgs as { entryPath?: unknown; args?: unknown }; const args = record.args === undefined ? [] : record.args; return ( diff --git a/packages/storage/src/__tests__/fixtures/sqlite-recovery-concurrency-child.ts b/packages/storage/src/__tests__/fixtures/sqlite-recovery-concurrency-child.ts index 61b8118f0b..0479638dbd 100644 --- a/packages/storage/src/__tests__/fixtures/sqlite-recovery-concurrency-child.ts +++ b/packages/storage/src/__tests__/fixtures/sqlite-recovery-concurrency-child.ts @@ -374,6 +374,7 @@ function managedMutationPreparedCommit(variant: 'a' | 'b') { baseTreeOid: '2'.repeat(40), expectedPath: 'notes.txt', pathPolicyVersion: 3 as const, + operationKind: 'write_edit_v2' as const, executionProfileDigest: 'sha256:7ff4eb75e8833f7bf97eaa252f47316f609093d89aa32acdeae7fc6caaa11a92' as const, }, diff --git a/packages/storage/src/__tests__/sqlite-recovery-concurrency.test.ts b/packages/storage/src/__tests__/sqlite-recovery-concurrency.test.ts index 0f13e96866..6456a22c30 100644 --- a/packages/storage/src/__tests__/sqlite-recovery-concurrency.test.ts +++ b/packages/storage/src/__tests__/sqlite-recovery-concurrency.test.ts @@ -341,6 +341,12 @@ describe('SQLite recovery authority multi-process races', () => { db.exec(` DROP TABLE runtime_managed_mutation_reservations; DROP TABLE runtime_session_event_ordinals; + DROP TABLE runtime_workspace_active_epochs; + DELETE FROM runtime_capabilities + WHERE capability IN ( + 'runtime_workspace_bound_continuation_authority', + 'runtime_managed_workspace_canonical_v2' + ); PRAGMA user_version = 10; UPDATE operational_schema_migrations SET version = 10 WHERE scope = 'runtime'; `); diff --git a/packages/storage/src/__tests__/sqlite-runtime-crash.test.ts b/packages/storage/src/__tests__/sqlite-runtime-crash.test.ts index 84cd6a36b0..a5857a0923 100644 --- a/packages/storage/src/__tests__/sqlite-runtime-crash.test.ts +++ b/packages/storage/src/__tests__/sqlite-runtime-crash.test.ts @@ -622,6 +622,7 @@ function workspaceSuccessorPreparedCommit(operationId = 'workspace-successor-ope baseTreeOid: '2'.repeat(40), expectedPath: 'notes.txt', pathPolicyVersion: 3 as const, + operationKind: 'write_edit_v2' as const, executionProfileDigest: 'sha256:7ff4eb75e8833f7bf97eaa252f47316f609093d89aa32acdeae7fc6caaa11a92' as const, }, diff --git a/packages/storage/src/__tests__/sqlite-runtime-store.test.ts b/packages/storage/src/__tests__/sqlite-runtime-store.test.ts index 0368479dfc..1579cb6254 100644 --- a/packages/storage/src/__tests__/sqlite-runtime-store.test.ts +++ b/packages/storage/src/__tests__/sqlite-runtime-store.test.ts @@ -24,7 +24,7 @@ import { join } from 'node:path'; import { DatabaseSync } from 'node:sqlite'; import { describe, it } from 'node:test'; import { - MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST, + MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, type RuntimeEvent, } from '@maka/core/runtime-event'; import { RunSealedError } from '@maka/core/runtime-event-store'; @@ -61,6 +61,38 @@ import { const TEST_STORAGE_ROOT_ID = 'a'.repeat(64); describe('SqliteRuntimeStore', () => { + it('uses one canonical post-main schema epoch and rejects experimental lookalikes', async () => { + const root = await mkdtemp(join(tmpdir(), 'maka-canonical-managed-schema-')); + const dbPath = join(root, 'runtime.sqlite'); + try { + const store = createSqliteRuntimeStore(dbPath); + try { + assert.equal(store.schemaVersion(), 15); + } finally { + store.close(); + } + + const raw = new DatabaseSync(dbPath); + try { + raw + .prepare( + `DELETE FROM runtime_capabilities + WHERE capability = 'runtime_managed_workspace_canonical_v2'`, + ) + .run(); + } finally { + raw.close(); + } + + assert.throws( + () => createSqliteRuntimeStore(dbPath), + /runtime managed workspace capability .* is unavailable/u, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + it('applies versioned migrations and reopens the same database without rewriting schema', async () => { await withStore(async (store, dbPath) => { assert.equal(store.schemaVersion(), SQLITE_RUNTIME_SCHEMA_VERSION); @@ -2375,7 +2407,10 @@ function rewindContinuationClaimsToSchema14(db: DatabaseSync): void { DROP TABLE runtime_continuation_claims_schema_15; DROP TABLE runtime_workspace_active_epochs; DELETE FROM runtime_capabilities - WHERE capability = 'runtime_workspace_bound_continuation_authority'; + WHERE capability IN ( + 'runtime_workspace_bound_continuation_authority', + 'runtime_managed_workspace_canonical_v2' + ); PRAGMA user_version = 14; COMMIT; PRAGMA foreign_keys = ON; @@ -2554,7 +2589,7 @@ function toolDispatchEvent(overrides: Partial = {}): RuntimeEvent function managedObservationDispatch() { return { - protocol: 'managed_observation_v1', + protocol: 'managed_observation_v2', repositoryId: `repository_${'1'.repeat(32)}`, workspaceId: `workspace_${'2'.repeat(32)}`, workspaceEpochId: `epoch_${'3'.repeat(32)}`, @@ -2565,9 +2600,9 @@ function managedObservationDispatch() { acceptedHeadRevision: 7, acceptedCommitOid: '6'.repeat(40), acceptedTreeOid: '7'.repeat(40), - operationKind: 'node_test_v1', - effectClass: 'hermetic_observation_v1', - executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V1_DIGEST, + operationKind: 'node_test_v2', + effectClass: 'hermetic_observation_v2', + executionProfileDigest: MANAGED_OBSERVATION_EXECUTION_PROFILE_V2_DIGEST, toolchainIdentityDigest: `sha256:${'8'.repeat(64)}`, files: [ { @@ -2576,6 +2611,7 @@ function managedObservationDispatch() { sha256: `sha256:${'9'.repeat(64)}`, }, ], + dependency: { kind: 'none' }, } as const; } diff --git a/packages/storage/src/__tests__/workspace-version-authority-persistence.test.ts b/packages/storage/src/__tests__/workspace-version-authority-persistence.test.ts index 20d5168c4e..25dc30858e 100644 --- a/packages/storage/src/__tests__/workspace-version-authority-persistence.test.ts +++ b/packages/storage/src/__tests__/workspace-version-authority-persistence.test.ts @@ -37,6 +37,7 @@ import { canonicalToolArgsHash } from '@maka/core/tool-args-identity'; import { createConversationOperationalStateStore } from '../conversation-operational-state.js'; import { createSqliteRuntimeStore, + SQLITE_RUNTIME_SCHEMA_VERSION, type SqliteRuntimeStoreFailpoint, } from '../sqlite-runtime-store.js'; import { @@ -290,7 +291,7 @@ describe('workspace version persistence authority', () => { }); }); - it('rebuilds one managed Node transform reservation from its immutable v3 T1', async () => { + it('rebuilds one managed Node transform reservation from its canonical v2 T1', async () => { await withDatabase(async ({ dbPath, store }) => { const baseline = baselineInput(); const opened = await commitWorkspaceBaselineInternal(store, baseline); @@ -831,7 +832,7 @@ describe('workspace version persistence authority', () => { bindWorkspaceBaselineAuthorityStoreRootInternal(upgraded, TEST_STORAGE_ROOT_ID); registerWorkspaceSuccessorCandidateVerifierInternal(upgraded, verifyTestCandidate); try { - assert.equal(upgraded.schemaVersion(), 17); + assert.equal(upgraded.schemaVersion(), SQLITE_RUNTIME_SCHEMA_VERSION); assert.equal( ( await upgraded.readWorkspaceHead( @@ -1303,6 +1304,7 @@ async function prepareSuccessorCommit( baseTreeOid: opened.head.treeOid, expectedPath: 'notes.txt', pathPolicyVersion: 3 as const, + operationKind: 'write_edit_v2' as const, executionProfileDigest: 'sha256:7ff4eb75e8833f7bf97eaa252f47316f609093d89aa32acdeae7fc6caaa11a92' as const, }, @@ -1433,6 +1435,7 @@ function managedPreparedCommit( baseTreeOid: head.treeOid, expectedPath: 'notes.txt', pathPolicyVersion: 3 as const, + operationKind: 'write_edit_v2' as const, executionProfileDigest: 'sha256:7ff4eb75e8833f7bf97eaa252f47316f609093d89aa32acdeae7fc6caaa11a92' as const, }, @@ -1500,7 +1503,7 @@ function managedTransformPreparedCommit( canonicalArgsHash: argsHash, recoveryMode: 'reconcile' as const, managedMutation: { - protocol: 'managed_mutation_v3' as const, + protocol: 'managed_mutation_v2' as const, repositoryId: baseline.epoch.repositoryId, workspaceId: baseline.epoch.workspaceId, workspaceEpochId: baseline.epoch.workspaceEpochId, @@ -1513,7 +1516,7 @@ function managedTransformPreparedCommit( baseTreeOid: head.treeOid, expectedPath: 'generated/output.txt', pathPolicyVersion: 3 as const, - operationKind: 'node_transform_v1' as const, + operationKind: 'node_transform_v2' as const, executionProfileDigest: 'sha256:7ff4eb75e8833f7bf97eaa252f47316f609093d89aa32acdeae7fc6caaa11a92' as const, toolchainIdentityDigest: `sha256:${'3'.repeat(64)}` as const, @@ -1608,7 +1611,10 @@ function recreateWorkspaceTablesAsSchema12(database: DatabaseSync): void { DROP TABLE runtime_managed_mutation_reservations; DROP TABLE runtime_workspace_active_epochs; DELETE FROM runtime_capabilities - WHERE capability = 'runtime_workspace_bound_continuation_authority'; + WHERE capability IN ( + 'runtime_workspace_bound_continuation_authority', + 'runtime_managed_workspace_canonical_v2' + ); PRAGMA user_version = 12; COMMIT; PRAGMA foreign_keys = ON; diff --git a/packages/storage/src/sqlite-runtime-schema.ts b/packages/storage/src/sqlite-runtime-schema.ts index 57577066c8..d4d06a3e83 100644 --- a/packages/storage/src/sqlite-runtime-schema.ts +++ b/packages/storage/src/sqlite-runtime-schema.ts @@ -19,7 +19,7 @@ import type { DatabaseSync } from 'node:sqlite'; -export const SQLITE_RUNTIME_SCHEMA_VERSION = 17; +export const SQLITE_RUNTIME_SCHEMA_VERSION = 15; export const RUNTIME_RECOVERY_AUTHORITY_CAPABILITY = 'runtime_recovery_authority'; export const RUNTIME_RECOVERY_AUTHORITY_CAPABILITY_VERSION = 1; export const RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY = 'runtime_continuation_authority'; @@ -29,6 +29,9 @@ export const RUNTIME_WORKSPACE_BOUND_CONTINUATION_AUTHORITY_CAPABILITY = export const RUNTIME_WORKSPACE_BOUND_CONTINUATION_AUTHORITY_CAPABILITY_VERSION = 1; export const RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY = 'runtime_workspace_version_authority'; export const RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY_VERSION = 1; +export const RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY = + 'runtime_managed_workspace_canonical_v2'; +export const RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY_VERSION = 1; const SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS = 5_000; const SQLITE_INITIALIZATION_RETRY_DELAY_MS = 10; const initializationRetryGate = new Int32Array(new SharedArrayBuffer(4)); @@ -540,11 +543,7 @@ const MIGRATIONS: ReadonlyMap = new Map([ INSERT INTO runtime_capabilities(capability, version) VALUES ('runtime_workspace_bound_continuation_authority', 1); - `, - ], - [ - 16, - ` + ALTER TABLE runtime_workspace_heads RENAME TO runtime_workspace_heads_v15; ALTER TABLE runtime_workspace_versions RENAME TO runtime_workspace_versions_v15; @@ -635,11 +634,7 @@ const MIGRATIONS: ReadonlyMap = new Map([ INSERT INTO runtime_workspace_heads SELECT * FROM runtime_workspace_heads_v15; DROP TABLE runtime_workspace_heads_v15; DROP TABLE runtime_workspace_versions_v15; - `, - ], - [ - 17, - ` + CREATE TABLE runtime_workspace_active_epochs ( workspace_id TEXT PRIMARY KEY, repository_id TEXT NOT NULL, @@ -673,6 +668,9 @@ const MIGRATIONS: ReadonlyMap = new Map([ AND json_extract(payload_json, '$.actions.workspaceFact.kind') = 'maka.workspace.epoch_activated' AND json_extract(payload_json, '$.actions.workspaceFact.payload.previousWorkspaceEpochId') IS NULL AND json_extract(payload_json, '$.actions.workspaceFact.payload.rebaselineId') IS NULL; + + INSERT INTO runtime_capabilities(capability, version) + VALUES ('runtime_managed_workspace_canonical_v2', 1); `, ], ]); diff --git a/packages/storage/src/sqlite-runtime-store.ts b/packages/storage/src/sqlite-runtime-store.ts index 45631b7943..596fe5882c 100644 --- a/packages/storage/src/sqlite-runtime-store.ts +++ b/packages/storage/src/sqlite-runtime-store.ts @@ -108,6 +108,8 @@ import { RUNTIME_RECOVERY_AUTHORITY_CAPABILITY_VERSION, RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY, RUNTIME_CONTINUATION_AUTHORITY_CAPABILITY_VERSION, + RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY, + RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY_VERSION, RUNTIME_WORKSPACE_BOUND_CONTINUATION_AUTHORITY_CAPABILITY, RUNTIME_WORKSPACE_BOUND_CONTINUATION_AUTHORITY_CAPABILITY_VERSION, RUNTIME_WORKSPACE_VERSION_AUTHORITY_CAPABILITY, @@ -322,6 +324,7 @@ export class SqliteRuntimeStore assertContinuationAuthorityCapability(this.db); assertWorkspaceBoundContinuationAuthorityCapability(this.db); assertWorkspaceVersionAuthorityCapability(this.db); + assertManagedWorkspaceCanonicalCapability(this.db); if (!options.readOnly) { this.registerWorkspaceBaselineAuthorityWriter(); this.refreshToolLedgerHealth(); @@ -349,6 +352,7 @@ export class SqliteRuntimeStore assertContinuationAuthorityCapability(this.db); assertWorkspaceBoundContinuationAuthorityCapability(this.db); assertWorkspaceVersionAuthorityCapability(this.db); + assertManagedWorkspaceCanonicalCapability(this.db); if (!options.readOnly) { this.registerWorkspaceBaselineAuthorityWriter(); this.refreshToolLedgerHealth(); @@ -4841,6 +4845,17 @@ function assertWorkspaceVersionAuthorityCapability(db: DatabaseSync): void { } } +function assertManagedWorkspaceCanonicalCapability(db: DatabaseSync): void { + const row = db + .prepare('SELECT version FROM runtime_capabilities WHERE capability = ?') + .get(RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY) as { version?: unknown } | undefined; + if (row?.version !== RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY_VERSION) { + throw new Error( + `SQLite runtime managed workspace capability ${RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY}@${RUNTIME_MANAGED_WORKSPACE_CANONICAL_CAPABILITY_VERSION} is unavailable`, + ); + } +} + interface WorkspaceEpochProjectionRow { workspace_id: string; workspace_epoch_id: string; @@ -4992,9 +5007,9 @@ function managedMutationMatchesToolName( toolName: string, mutation: RuntimeEventManagedWorkspaceMutation | undefined, ): boolean { - return mutation?.protocol === 'managed_mutation_v2' + return mutation?.operationKind === 'write_edit_v2' ? toolName === 'Write' || toolName === 'Edit' - : mutation?.protocol === 'managed_mutation_v3' && toolName === 'ManagedNodeTransform'; + : mutation?.operationKind === 'node_transform_v2' && toolName === 'ManagedNodeTransform'; } function managedMutationMatchesCallArgs( @@ -5004,7 +5019,7 @@ function managedMutationMatchesCallArgs( if (!value || typeof value !== 'object' || Array.isArray(value)) return false; const args = value as Record; if (args.path !== mutation.expectedPath) return false; - if (mutation.protocol === 'managed_mutation_v2') return true; + if (mutation.operationKind === 'write_edit_v2') return true; return ( args.entryPath === mutation.entry.relativePath && isDeepStrictEqual(args.args ?? [], mutation.args) diff --git a/scripts/prepare-managed-command-toolchain.mjs b/scripts/prepare-managed-command-toolchain.mjs index 5327993213..36243dd30d 100644 --- a/scripts/prepare-managed-command-toolchain.mjs +++ b/scripts/prepare-managed-command-toolchain.mjs @@ -72,8 +72,8 @@ export async function prepareManagedCommandToolchain({ manifestTempPath, `${JSON.stringify( { - schemaVersion: 4, - protocol: 'maka_managed_command_toolchain_release_v4', + schemaVersion: 2, + protocol: 'maka_managed_command_toolchain_release_v2', provider: 'maka/managed-command-toolchain', platform, arch, @@ -82,11 +82,7 @@ export async function prepareManagedCommandToolchain({ entrypointRelativePath: ENTRYPOINT_RELATIVE_PATH, entrypointBytes: copiedInfo.size, entrypointSha256, - allowedEffectClasses: [ - 'hermetic_observation_v2', - 'hermetic_observation_v3', - 'workspace_transform_v1', - ], + allowedEffectClasses: ['hermetic_observation_v2', 'workspace_transform_v1'], distributionReady: true, }, null, diff --git a/scripts/prepare-managed-command-toolchain.test.mjs b/scripts/prepare-managed-command-toolchain.test.mjs index 0d5dc28f2a..11857adcf1 100644 --- a/scripts/prepare-managed-command-toolchain.test.mjs +++ b/scripts/prepare-managed-command-toolchain.test.mjs @@ -41,8 +41,8 @@ test('prepares one bounded managed-command entrypoint and exact release manifest assert.equal(await readFile(result.entrypointPath, 'utf8'), 'process.stdout.write("ready");\n'); assert.deepEqual(JSON.parse(await readFile(result.manifestPath, 'utf8')), { - schemaVersion: 4, - protocol: 'maka_managed_command_toolchain_release_v4', + schemaVersion: 2, + protocol: 'maka_managed_command_toolchain_release_v2', provider: 'maka/managed-command-toolchain', platform: 'win32', arch: 'x64', @@ -51,11 +51,7 @@ test('prepares one bounded managed-command entrypoint and exact release manifest entrypointRelativePath: 'managed-command/managed-command-helper-main.js', entrypointBytes: 31, entrypointSha256: result.entrypointSha256, - allowedEffectClasses: [ - 'hermetic_observation_v2', - 'hermetic_observation_v3', - 'workspace_transform_v1', - ], + allowedEffectClasses: ['hermetic_observation_v2', 'workspace_transform_v1'], distributionReady: true, }); }); From a4e82723eac89a0c48411f2733a00fd22d900f2b Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 16:59:28 +0800 Subject: [PATCH 2/9] test(runtime-host): run managed continuation with canonical v2 resources --- ...itoxide-managed-continuation-crash.test.ts | 177 +++++++++++++----- 1 file changed, 128 insertions(+), 49 deletions(-) diff --git a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts index 7cd2e7f4fc..8bca5ac99e 100644 --- a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts +++ b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts @@ -33,7 +33,7 @@ import { writeFile, } from 'node:fs/promises'; import { tmpdir } from 'node:os'; -import { join } from 'node:path'; +import { join, resolve } from 'node:path'; import { test } from 'node:test'; import { createSqliteRuntimeStore } from '@maka/storage/sqlite-runtime-store'; import { openInteractiveExecutionStoresForWrite } from '@maka/storage/execution-stores'; @@ -65,12 +65,13 @@ test('a started workspace-bound continuation survives Host death without provide } await withManagedContinuationFixture( helperPath, - async ({ fixture, resourcesRoot, callLog, boundary }) => { + async ({ fixture, resourcesRoot, runtimeExecutablePath, callLog, boundary }) => { const source = await fixture.seedSafeBoundaryContinuationSource(undefined, { failureClass: 'test_manual_resume', }); const crashHost = await fixture.startHost(undefined, true, { packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, providerCallLogPath: callLog, continuationFailpoint: 'after_continuation_start_committed', }); @@ -111,6 +112,7 @@ test('a started workspace-bound continuation survives Host death without provide const successorHost = await fixture.startHost(undefined, true, { packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, providerCallLogPath: callLog, }); const successorClient = await connectClient(fixture.root); @@ -179,13 +181,14 @@ test('an accepted-head continuation never calls the provider twice after Host de } await withManagedContinuationFixture( helperPath, - async ({ fixture, resourcesRoot, callLog, boundary }) => { + async ({ fixture, resourcesRoot, runtimeExecutablePath, callLog, boundary }) => { assert.equal(boundary.revision, 1); const source = await fixture.seedSafeBoundaryContinuationSource(undefined, { failureClass: 'test_manual_resume', }); const crashHost = await fixture.startHost(undefined, true, { packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, providerCallLogPath: callLog, providerFailpointAfterSend: true, }); @@ -218,6 +221,7 @@ test('an accepted-head continuation never calls the provider twice after Host de const successorHost = await fixture.startHost(undefined, true, { packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, providerCallLogPath: callLog, }); const successorClient = await connectClient(fixture.root); @@ -251,51 +255,56 @@ test('Host startup automatically resumes one managed task without an experimenta t.skip('MAKA_GITOXIDE_HELPER_PATH is required for the automatic managed resume test'); return; } - await withManagedContinuationFixture(helperPath, async ({ fixture, resourcesRoot, callLog }) => { - const source = await fixture.seedSafeBoundaryContinuationSource(); - const firstHost = await fixture.startHost(undefined, false, { - packagedResourcesRoot: resourcesRoot, - providerCallLogPath: callLog, - }); - try { - await waitForProviderCalls(callLog, 1); - } finally { - await fixture.stopHost(firstHost); - } - - const firstAdmissions = (await fixture.readAdmissionChain()).filter( - (candidate) => candidate.execution.kind === 'safe_boundary_continuation', - ); - assert.equal(firstAdmissions.length, 1); - const admission = firstAdmissions[0]!; - assert.equal(admission.execution.kind, 'safe_boundary_continuation'); - if (admission.execution.kind !== 'safe_boundary_continuation') { - assert.fail('Automatic managed continuation admission is missing'); - } - assert.equal(admission.execution.sourceRunId, source.sourceRunId); - assert.deepEqual(await fixture.readTurnFootprint(admission.turnId), { - admitted: true, - runCount: 1, - userMessageCount: 0, - }); + await withManagedContinuationFixture( + helperPath, + async ({ fixture, resourcesRoot, runtimeExecutablePath, callLog }) => { + const source = await fixture.seedSafeBoundaryContinuationSource(); + const firstHost = await fixture.startHost(undefined, false, { + packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, + providerCallLogPath: callLog, + }); + try { + await waitForProviderCalls(callLog, 1); + } finally { + await fixture.stopHost(firstHost); + } - const secondHost = await fixture.startHost(undefined, false, { - packagedResourcesRoot: resourcesRoot, - providerCallLogPath: callLog, - }); - try { - await new Promise((resolve) => setTimeout(resolve, 250)); - assert.equal(await providerCallCount(callLog), 1); - assert.equal( - (await fixture.readAdmissionChain()).filter( - (candidate) => candidate.execution.kind === 'safe_boundary_continuation', - ).length, - 1, + const firstAdmissions = (await fixture.readAdmissionChain()).filter( + (candidate) => candidate.execution.kind === 'safe_boundary_continuation', ); - } finally { - await fixture.stopHost(secondHost); - } - }); + assert.equal(firstAdmissions.length, 1); + const admission = firstAdmissions[0]!; + assert.equal(admission.execution.kind, 'safe_boundary_continuation'); + if (admission.execution.kind !== 'safe_boundary_continuation') { + assert.fail('Automatic managed continuation admission is missing'); + } + assert.equal(admission.execution.sourceRunId, source.sourceRunId); + assert.deepEqual(await fixture.readTurnFootprint(admission.turnId), { + admitted: true, + runCount: 1, + userMessageCount: 0, + }); + + const secondHost = await fixture.startHost(undefined, false, { + packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, + providerCallLogPath: callLog, + }); + try { + await new Promise((resolve) => setTimeout(resolve, 250)); + assert.equal(await providerCallCount(callLog), 1); + assert.equal( + (await fixture.readAdmissionChain()).filter( + (candidate) => candidate.execution.kind === 'safe_boundary_continuation', + ).length, + 1, + ); + } finally { + await fixture.stopHost(secondHost); + } + }, + ); }); for (const sourceKind of ['git_repository_v1', 'filesystem_snapshot_v1'] as const) { @@ -307,11 +316,18 @@ for (const sourceKind of ['git_repository_v1', 'filesystem_snapshot_v1'] as cons } await withManagedContinuationFixture( helperPath, - async ({ fixture, resourcesRoot, callLog, sourceKind: admittedSourceKind }) => { + async ({ + fixture, + resourcesRoot, + runtimeExecutablePath, + callLog, + sourceKind: admittedSourceKind, + }) => { assert.equal(admittedSourceKind, sourceKind); const source = await fixture.seedSafeBoundaryContinuationSource(); const firstHost = await fixture.startHost(undefined, false, { packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, providerCallLogPath: callLog, providerFailpointAfterSend: true, }); @@ -330,6 +346,7 @@ for (const sourceKind of ['git_repository_v1', 'filesystem_snapshot_v1'] as cons const secondHost = await fixture.startHost(undefined, false, { packagedResourcesRoot: resourcesRoot, + runtimeExecutablePath, providerCallLogPath: callLog, }); const secondClient = await connectClient(fixture.root); @@ -369,6 +386,7 @@ async function withManagedContinuationFixture( run: (input: { fixture: ExecutionFixture; resourcesRoot: string; + runtimeExecutablePath: string; callLog: string; sourceKind: 'git_repository_v1' | 'filesystem_snapshot_v1'; boundary: NonNullable< @@ -402,7 +420,12 @@ async function withManagedContinuationFixture( } await resolveWorkspaceIdentity({ path: sourceRoot }); - const resourcesRoot = await preparePackagedResources(base, helperInputPath); + const runtimeExecutablePath = resolveElectronExecutable(); + const resourcesRoot = await preparePackagedResources( + base, + helperInputPath, + runtimeExecutablePath, + ); const capability = await resolveStorageRoot({ path: root, kind: 'interactive' }); const owner = await tryAcquireInteractiveRootOwner(capability); assert.ok(owner); @@ -445,6 +468,7 @@ async function withManagedContinuationFixture( await run({ fixture, resourcesRoot, + runtimeExecutablePath, callLog, sourceKind: options.sourceKind ?? 'git_repository_v1', boundary: boundary!, @@ -454,7 +478,11 @@ async function withManagedContinuationFixture( } } -async function preparePackagedResources(base: string, helperInputPath: string): Promise { +async function preparePackagedResources( + base: string, + helperInputPath: string, + runtimeExecutablePath: string, +): Promise { const resourcesRoot = join(base, 'resources'); const helperDirectory = join(resourcesRoot, 'gitoxide'); const executableName = @@ -481,9 +509,60 @@ async function preparePackagedResources(base: string, helperInputPath: string): })}\n`, 'utf8', ); + + const commandRoot = join(resourcesRoot, 'managed-command'); + const entrypointPath = join(commandRoot, 'managed-command-helper-main.js'); + await mkdir(commandRoot); + await copyFile( + resolve(import.meta.dirname, '..', 'server', 'managed-command-helper-main.js'), + entrypointPath, + ); + const entrypoint = await readFile(entrypointPath); + const nodeVersion = execFileSync(runtimeExecutablePath, ['-p', 'process.versions.node'], { + env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, + encoding: 'utf8', + windowsHide: true, + }).trim(); + await writeFile( + join(resourcesRoot, 'managed-command-toolchain.json'), + `${JSON.stringify({ + schemaVersion: 2, + protocol: 'maka_managed_command_toolchain_release_v2', + provider: 'maka/managed-command-toolchain', + platform: process.platform, + arch: process.arch, + nodeVersion, + profileVersion: 1, + entrypointRelativePath: 'managed-command/managed-command-helper-main.js', + entrypointBytes: (await stat(entrypointPath)).size, + entrypointSha256: `sha256:${createHash('sha256').update(entrypoint).digest('hex')}`, + allowedEffectClasses: ['hermetic_observation_v2', 'workspace_transform_v1'], + distributionReady: true, + })}\n`, + 'utf8', + ); + if (process.platform === 'win32') { + const sandboxInputPath = process.env.MAKA_WINDOWS_SANDBOX_PATH; + assert.ok( + sandboxInputPath, + 'MAKA_WINDOWS_SANDBOX_PATH is required for the Windows managed continuation gate', + ); + const sandboxRoot = join(resourcesRoot, 'windows-sandbox'); + await mkdir(sandboxRoot); + await copyFile(await realpath(sandboxInputPath), join(sandboxRoot, 'maka-windows-sandbox.exe')); + } return resourcesRoot; } +function resolveElectronExecutable(): string { + const distributionRoot = resolve(process.cwd(), 'node_modules', 'electron', 'dist'); + if (process.platform === 'win32') return join(distributionRoot, 'electron.exe'); + if (process.platform === 'darwin') { + return join(distributionRoot, 'Electron.app', 'Contents', 'MacOS', 'Electron'); + } + return join(distributionRoot, 'electron'); +} + async function admitRealHelper(helperInputPath: string) { const executablePath = await realpath(helperInputPath); const [bytes, info] = await Promise.all([readFile(executablePath), stat(executablePath)]); From a3fcde65be294c708b74f3dfb0e77c800d240012 Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 17:18:48 +0800 Subject: [PATCH 3/9] test(runtime-host): align managed continuation crash matrix --- ...itoxide-managed-continuation-crash.test.ts | 26 ++++++-------- .../__tests__/managed-coding-v2-crash.test.ts | 36 +++++++++++++++++-- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts index 8bca5ac99e..4f7d445917 100644 --- a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts +++ b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts @@ -386,7 +386,7 @@ async function withManagedContinuationFixture( run: (input: { fixture: ExecutionFixture; resourcesRoot: string; - runtimeExecutablePath: string; + runtimeExecutablePath?: string; callLog: string; sourceKind: 'git_repository_v1' | 'filesystem_snapshot_v1'; boundary: NonNullable< @@ -420,7 +420,11 @@ async function withManagedContinuationFixture( } await resolveWorkspaceIdentity({ path: sourceRoot }); - const runtimeExecutablePath = resolveElectronExecutable(); + // Windows intentionally has no canonical managed-coding-v2 product profile yet. + // Keep the platform in the Git/non-Git continuation matrix without inventing + // a toolchain capability that production refuses to advertise. + const runtimeExecutablePath = + process.platform === 'win32' ? undefined : resolveElectronExecutable(); const resourcesRoot = await preparePackagedResources( base, helperInputPath, @@ -442,7 +446,7 @@ async function withManagedContinuationFixture( llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', - toolProfile: 'managed-coding-v2', + ...(process.platform === 'win32' ? {} : { toolProfile: 'managed-coding-v2' as const }), }); sessionId = session.id; const helper = await admitRealHelper(helperInputPath); @@ -468,7 +472,7 @@ async function withManagedContinuationFixture( await run({ fixture, resourcesRoot, - runtimeExecutablePath, + ...(runtimeExecutablePath ? { runtimeExecutablePath } : {}), callLog, sourceKind: options.sourceKind ?? 'git_repository_v1', boundary: boundary!, @@ -481,7 +485,7 @@ async function withManagedContinuationFixture( async function preparePackagedResources( base: string, helperInputPath: string, - runtimeExecutablePath: string, + runtimeExecutablePath?: string, ): Promise { const resourcesRoot = join(base, 'resources'); const helperDirectory = join(resourcesRoot, 'gitoxide'); @@ -510,6 +514,8 @@ async function preparePackagedResources( 'utf8', ); + if (!runtimeExecutablePath) return resourcesRoot; + const commandRoot = join(resourcesRoot, 'managed-command'); const entrypointPath = join(commandRoot, 'managed-command-helper-main.js'); await mkdir(commandRoot); @@ -541,16 +547,6 @@ async function preparePackagedResources( })}\n`, 'utf8', ); - if (process.platform === 'win32') { - const sandboxInputPath = process.env.MAKA_WINDOWS_SANDBOX_PATH; - assert.ok( - sandboxInputPath, - 'MAKA_WINDOWS_SANDBOX_PATH is required for the Windows managed continuation gate', - ); - const sandboxRoot = join(resourcesRoot, 'windows-sandbox'); - await mkdir(sandboxRoot); - await copyFile(await realpath(sandboxInputPath), join(sandboxRoot, 'maka-windows-sandbox.exe')); - } return resourcesRoot; } diff --git a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts index ba6cdfcca3..b7459ad0c2 100644 --- a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts +++ b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts @@ -343,7 +343,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying a co (error: unknown) => Promise.reject( new Error( - `hosted execution failed before the Node command result: ${error instanceof Error ? error.message : String(error)}`, + `hosted execution failed before the Node command result: ${error instanceof Error ? error.message : String(error)}; runtime evidence: ${readRuntimeFailureEvidence(root, executionId)}`, { cause: error }, ), ), @@ -513,7 +513,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying an a (error: unknown) => Promise.reject( new Error( - `hosted execution failed before the workspace transform result: ${error instanceof Error ? error.message : String(error)}`, + `hosted execution failed before the workspace transform result: ${error instanceof Error ? error.message : String(error)}; runtime evidence: ${readRuntimeFailureEvidence(root, executionId)}`, { cause: error }, ), ), @@ -900,6 +900,38 @@ async function startManagedNodeTransformProvider(): Promise<{ }; } +function readRuntimeFailureEvidence(root: string, executionId: string): string { + try { + const database = new DatabaseSync(join(root, 'runtime.sqlite'), { readOnly: true }); + try { + const rows = database + .prepare( + ` + SELECT event_kind AS eventKind, payload_json AS payloadJson + FROM runtime_events + WHERE session_id = ? + ORDER BY committed_at DESC, event_id DESC + LIMIT 12 + `, + ) + .all(executionId) as unknown as Array<{ + readonly eventKind: string; + readonly payloadJson: string; + }>; + return JSON.stringify( + rows.reverse().map((row) => ({ + eventKind: row.eventKind, + payload: row.payloadJson.slice(0, 2_000), + })), + ); + } finally { + database.close(); + } + } catch (error) { + return `unavailable (${error instanceof Error ? error.message : String(error)})`; + } +} + function readManagedNodeTestResult(request: unknown): Readonly> { if (!request || typeof request !== 'object' || Array.isArray(request)) { throw new Error('Provider request is invalid'); From d6838c224b53de2c3f26b776e7c443d7ba32e3fe Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 17:26:31 +0800 Subject: [PATCH 4/9] test(runtime-host): make managed crash evidence explicit --- ...itoxide-managed-continuation-crash.test.ts | 32 ++++++++++++------- .../__tests__/managed-coding-v2-crash.test.ts | 4 +-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts index 4f7d445917..efc0f3a16e 100644 --- a/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts +++ b/packages/runtime-host/src/__tests__/gitoxide-managed-continuation-crash.test.ts @@ -63,6 +63,10 @@ test('a started workspace-bound continuation survives Host death without provide t.skip('MAKA_GITOXIDE_HELPER_PATH is required for the real helper continuation test'); return; } + if (process.platform === 'win32') { + t.skip('managed-coding-v2 is not a supported Windows product profile yet'); + return; + } await withManagedContinuationFixture( helperPath, async ({ fixture, resourcesRoot, runtimeExecutablePath, callLog, boundary }) => { @@ -179,6 +183,10 @@ test('an accepted-head continuation never calls the provider twice after Host de t.skip('MAKA_GITOXIDE_HELPER_PATH is required for the real provider crash test'); return; } + if (process.platform === 'win32') { + t.skip('managed-coding-v2 is not a supported Windows product profile yet'); + return; + } await withManagedContinuationFixture( helperPath, async ({ fixture, resourcesRoot, runtimeExecutablePath, callLog, boundary }) => { @@ -255,6 +263,10 @@ test('Host startup automatically resumes one managed task without an experimenta t.skip('MAKA_GITOXIDE_HELPER_PATH is required for the automatic managed resume test'); return; } + if (process.platform === 'win32') { + t.skip('managed-coding-v2 is not a supported Windows product profile yet'); + return; + } await withManagedContinuationFixture( helperPath, async ({ fixture, resourcesRoot, runtimeExecutablePath, callLog }) => { @@ -314,6 +326,10 @@ for (const sourceKind of ['git_repository_v1', 'filesystem_snapshot_v1'] as cons t.skip('MAKA_GITOXIDE_HELPER_PATH is required for the automatic resume crash matrix'); return; } + if (process.platform === 'win32') { + t.skip('managed-coding-v2 is not a supported Windows product profile yet'); + return; + } await withManagedContinuationFixture( helperPath, async ({ @@ -386,7 +402,7 @@ async function withManagedContinuationFixture( run: (input: { fixture: ExecutionFixture; resourcesRoot: string; - runtimeExecutablePath?: string; + runtimeExecutablePath: string; callLog: string; sourceKind: 'git_repository_v1' | 'filesystem_snapshot_v1'; boundary: NonNullable< @@ -420,11 +436,7 @@ async function withManagedContinuationFixture( } await resolveWorkspaceIdentity({ path: sourceRoot }); - // Windows intentionally has no canonical managed-coding-v2 product profile yet. - // Keep the platform in the Git/non-Git continuation matrix without inventing - // a toolchain capability that production refuses to advertise. - const runtimeExecutablePath = - process.platform === 'win32' ? undefined : resolveElectronExecutable(); + const runtimeExecutablePath = resolveElectronExecutable(); const resourcesRoot = await preparePackagedResources( base, helperInputPath, @@ -446,7 +458,7 @@ async function withManagedContinuationFixture( llmConnectionSlug: 'fake', model: 'fake-model', permissionMode: 'ask', - ...(process.platform === 'win32' ? {} : { toolProfile: 'managed-coding-v2' as const }), + toolProfile: 'managed-coding-v2', }); sessionId = session.id; const helper = await admitRealHelper(helperInputPath); @@ -472,7 +484,7 @@ async function withManagedContinuationFixture( await run({ fixture, resourcesRoot, - ...(runtimeExecutablePath ? { runtimeExecutablePath } : {}), + runtimeExecutablePath, callLog, sourceKind: options.sourceKind ?? 'git_repository_v1', boundary: boundary!, @@ -485,7 +497,7 @@ async function withManagedContinuationFixture( async function preparePackagedResources( base: string, helperInputPath: string, - runtimeExecutablePath?: string, + runtimeExecutablePath: string, ): Promise { const resourcesRoot = join(base, 'resources'); const helperDirectory = join(resourcesRoot, 'gitoxide'); @@ -514,8 +526,6 @@ async function preparePackagedResources( 'utf8', ); - if (!runtimeExecutablePath) return resourcesRoot; - const commandRoot = join(resourcesRoot, 'managed-command'); const entrypointPath = join(commandRoot, 'managed-command-helper-main.js'); await mkdir(commandRoot); diff --git a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts index b7459ad0c2..b65e095b60 100644 --- a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts +++ b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts @@ -337,7 +337,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying a co (projection) => Promise.reject( new Error( - `hosted execution settled before the Node command result: ${JSON.stringify(projection)}`, + `hosted execution settled before the Node command result: ${JSON.stringify(projection)}; runtime evidence: ${readRuntimeFailureEvidence(root, executionId)}`, ), ), (error: unknown) => @@ -507,7 +507,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying an a (projection) => Promise.reject( new Error( - `hosted execution settled before the workspace transform result: ${JSON.stringify(projection)}`, + `hosted execution settled before the workspace transform result: ${JSON.stringify(projection)}; runtime evidence: ${readRuntimeFailureEvidence(root, executionId)}`, ), ), (error: unknown) => From d97c76ed44d56b821d1d0831b8d600bd58f9595a Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 17:39:24 +0800 Subject: [PATCH 5/9] test(runtime-host): expose managed owner crash diagnostics --- .../src/server/execution-composition.ts | 22 ++++++++++++++----- ...oxide-managed-write-edit-owner-internal.ts | 16 +++++++++++++- .../managed-command-sandbox-owner-internal.ts | 6 ++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/runtime-host/src/server/execution-composition.ts b/packages/runtime-host/src/server/execution-composition.ts index 9a0d453dfe..80ba821b0f 100644 --- a/packages/runtime-host/src/server/execution-composition.ts +++ b/packages/runtime-host/src/server/execution-composition.ts @@ -942,12 +942,22 @@ export async function createExecutionRuntimeHostComposition( : {}), ...(managedNodeTestAdmission || managedNodeCommandAdmission ? { - admitManagedObservation: (input) => - input.toolName === 'ManagedNodeRun' - ? (managedNodeCommandAdmission?.admit(input) ?? - Promise.reject(new Error('Managed Node command admission is unavailable'))) - : (managedNodeTestAdmission?.admit(input) ?? - Promise.reject(new Error('Managed Node test admission is unavailable'))), + admitManagedObservation: async (input) => { + try { + return await (input.toolName === 'ManagedNodeRun' + ? (managedNodeCommandAdmission?.admit(input) ?? + Promise.reject( + new Error('Managed Node command admission is unavailable'), + )) + : (managedNodeTestAdmission?.admit(input) ?? + Promise.reject(new Error('Managed Node test admission is unavailable')))); + } catch (error) { + if (process.env.MAKA_TEST_USE_PRODUCTION_BACKEND === '1') { + console.error('[managed-observation-admission-test-diagnostic]', error); + } + throw error; + } + }, } : {}), requestDrain: context.requestDrain, diff --git a/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts b/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts index bc67d939db..9863a85723 100644 --- a/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts +++ b/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts @@ -398,7 +398,13 @@ async function settleManagedMutation(input: { readonly durableDispatch: RuntimeEventManagedWorkspaceMutation; readonly failpoint?: (point: GitoxideManagedWriteEditOwnerFailpoint) => void | Promise; }): Promise { - const proof = await input.operation(); + let proof: RuntimeManagedMutationOperationProof; + try { + proof = await input.operation(); + } catch (error) { + reportManagedMutationTestDiagnostic('operation', error); + throw error; + } const reservation = await input.persistence.readActiveMutation(input.epoch.workspaceInstanceId); if (!reservationMatchesAdmission(reservation, input)) { return unsettled('Managed Write/Edit operation has no exact durable reservation'); @@ -432,6 +438,7 @@ async function settleManagedMutation(input: { input.durableDispatch.executionProfileDigest, ); } catch (error) { + reportManagedMutationTestDiagnostic('candidate', error); return Object.freeze({ kind: 'unsettled' as const, error }); } @@ -467,10 +474,17 @@ async function settleManagedMutation(input: { durableOutcome: proof.durableOutcome, }); } catch (error) { + reportManagedMutationTestDiagnostic('successor', error); return Object.freeze({ kind: 'unsettled' as const, error }); } } +function reportManagedMutationTestDiagnostic(stage: string, error: unknown): void { + if (process.env.MAKA_TEST_USE_PRODUCTION_BACKEND === '1') { + console.error(`[managed-mutation-${stage}-test-diagnostic]`, error); + } +} + async function commitTerminal( input: Parameters[0], proof: RuntimeManagedMutationOperationProof, diff --git a/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts b/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts index fd6ff0846e..51a3e16ace 100644 --- a/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts +++ b/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts @@ -303,7 +303,11 @@ export function createManagedCommandSandboxOwnerInternal(input: { : invocation.outputPath !== undefined && result.exitCode !== 0) || !result.dispatched ) { - throw new Error(formatManagedCommandFailure(result, invocationLabel(invocation))); + const failure = formatManagedCommandFailure(result, invocationLabel(invocation)); + if (process.env.MAKA_TEST_USE_PRODUCTION_BACKEND === '1') { + console.error(`[managed-command-test-diagnostic] ${failure}`); + } + throw new Error(failure); } return { stdout: result.stdout, From 6e48509c78ae53c3931a87c3fdf5d9fb903a09e1 Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 17:48:38 +0800 Subject: [PATCH 6/9] fix(core): canonicalize frozen runtime events --- .../__tests__/canonical-runtime-event.test.ts | 24 +++++++++++++++++++ packages/core/src/canonical-runtime-event.ts | 9 ++++++- .../src/server/execution-composition.ts | 22 +++++------------ ...oxide-managed-write-edit-owner-internal.ts | 16 +------------ .../managed-command-sandbox-owner-internal.ts | 6 +---- 5 files changed, 40 insertions(+), 37 deletions(-) diff --git a/packages/core/src/__tests__/canonical-runtime-event.test.ts b/packages/core/src/__tests__/canonical-runtime-event.test.ts index 444db1e6ab..208f5cc508 100644 --- a/packages/core/src/__tests__/canonical-runtime-event.test.ts +++ b/packages/core/src/__tests__/canonical-runtime-event.test.ts @@ -112,4 +112,28 @@ describe('canonical RuntimeEvent encoding', () => { /RuntimeEvent is not losslessly serializable/, ); }); + + test('canonicalizes an owner-frozen event without mutating its descriptors', () => { + const content = Object.freeze({ + kind: 'function_response' as const, + id: 'call-1', + name: 'ManagedNodeTransform', + result: Object.freeze({ path: 'generated/output.txt', bytes: 17 }), + isError: false, + }); + const event = Object.freeze( + baseEvent({ + role: 'tool', + author: 'tool', + content, + refs: Object.freeze({ operationId: 'operation-1', toolCallId: 'call-1' }), + }), + ); + + const encoded = encodeCanonicalRuntimeEvent(event); + + assert.deepEqual(encoded.event.content, content); + assert.equal(Object.isFrozen(event), true); + assert.equal(Object.isFrozen(content), true); + }); }); diff --git a/packages/core/src/canonical-runtime-event.ts b/packages/core/src/canonical-runtime-event.ts index ea9b8ddde3..4cfe0da5e9 100644 --- a/packages/core/src/canonical-runtime-event.ts +++ b/packages/core/src/canonical-runtime-event.ts @@ -112,7 +112,14 @@ function omitUndefinedEnvelopeFields(value: object): object { throw new Error('RuntimeEvent is not losslessly serializable'); } if (descriptor.value === undefined) continue; - Object.defineProperty(result, key, descriptor); + // This is a normalization-owned copy. Do not inherit frozen descriptor + // flags from an owner-issued immutable event, because the envelope fields + // below still need to be replaced with their normalized copies. + Object.defineProperty(result, key, { + ...descriptor, + configurable: true, + writable: true, + }); } return result; } diff --git a/packages/runtime-host/src/server/execution-composition.ts b/packages/runtime-host/src/server/execution-composition.ts index 80ba821b0f..9a0d453dfe 100644 --- a/packages/runtime-host/src/server/execution-composition.ts +++ b/packages/runtime-host/src/server/execution-composition.ts @@ -942,22 +942,12 @@ export async function createExecutionRuntimeHostComposition( : {}), ...(managedNodeTestAdmission || managedNodeCommandAdmission ? { - admitManagedObservation: async (input) => { - try { - return await (input.toolName === 'ManagedNodeRun' - ? (managedNodeCommandAdmission?.admit(input) ?? - Promise.reject( - new Error('Managed Node command admission is unavailable'), - )) - : (managedNodeTestAdmission?.admit(input) ?? - Promise.reject(new Error('Managed Node test admission is unavailable')))); - } catch (error) { - if (process.env.MAKA_TEST_USE_PRODUCTION_BACKEND === '1') { - console.error('[managed-observation-admission-test-diagnostic]', error); - } - throw error; - } - }, + admitManagedObservation: (input) => + input.toolName === 'ManagedNodeRun' + ? (managedNodeCommandAdmission?.admit(input) ?? + Promise.reject(new Error('Managed Node command admission is unavailable'))) + : (managedNodeTestAdmission?.admit(input) ?? + Promise.reject(new Error('Managed Node test admission is unavailable'))), } : {}), requestDrain: context.requestDrain, diff --git a/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts b/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts index 9863a85723..bc67d939db 100644 --- a/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts +++ b/packages/runtime-host/src/server/gitoxide-managed-write-edit-owner-internal.ts @@ -398,13 +398,7 @@ async function settleManagedMutation(input: { readonly durableDispatch: RuntimeEventManagedWorkspaceMutation; readonly failpoint?: (point: GitoxideManagedWriteEditOwnerFailpoint) => void | Promise; }): Promise { - let proof: RuntimeManagedMutationOperationProof; - try { - proof = await input.operation(); - } catch (error) { - reportManagedMutationTestDiagnostic('operation', error); - throw error; - } + const proof = await input.operation(); const reservation = await input.persistence.readActiveMutation(input.epoch.workspaceInstanceId); if (!reservationMatchesAdmission(reservation, input)) { return unsettled('Managed Write/Edit operation has no exact durable reservation'); @@ -438,7 +432,6 @@ async function settleManagedMutation(input: { input.durableDispatch.executionProfileDigest, ); } catch (error) { - reportManagedMutationTestDiagnostic('candidate', error); return Object.freeze({ kind: 'unsettled' as const, error }); } @@ -474,17 +467,10 @@ async function settleManagedMutation(input: { durableOutcome: proof.durableOutcome, }); } catch (error) { - reportManagedMutationTestDiagnostic('successor', error); return Object.freeze({ kind: 'unsettled' as const, error }); } } -function reportManagedMutationTestDiagnostic(stage: string, error: unknown): void { - if (process.env.MAKA_TEST_USE_PRODUCTION_BACKEND === '1') { - console.error(`[managed-mutation-${stage}-test-diagnostic]`, error); - } -} - async function commitTerminal( input: Parameters[0], proof: RuntimeManagedMutationOperationProof, diff --git a/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts b/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts index 51a3e16ace..fd6ff0846e 100644 --- a/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts +++ b/packages/runtime-host/src/server/managed-command-sandbox-owner-internal.ts @@ -303,11 +303,7 @@ export function createManagedCommandSandboxOwnerInternal(input: { : invocation.outputPath !== undefined && result.exitCode !== 0) || !result.dispatched ) { - const failure = formatManagedCommandFailure(result, invocationLabel(invocation)); - if (process.env.MAKA_TEST_USE_PRODUCTION_BACKEND === '1') { - console.error(`[managed-command-test-diagnostic] ${failure}`); - } - throw new Error(failure); + throw new Error(formatManagedCommandFailure(result, invocationLabel(invocation))); } return { stdout: result.stdout, From ef1abd2000493f674d1d2d3753f9a1c8e8610c9b Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 18:10:27 +0800 Subject: [PATCH 7/9] test(runtime-host): correct packaged managed profile fixtures --- .../src/__tests__/managed-coding-v2-crash.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts index b65e095b60..3e80b2f58a 100644 --- a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts +++ b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts @@ -307,7 +307,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying a co modelTarget: { kind: 'explicit', connectionId, - connectionSlug: 'managed-v3-provider', + connectionSlug: 'managed-v2-provider', model: MODEL_ID, }, permissionMode: 'bypass', @@ -584,7 +584,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying an a try { const successorCount = database .prepare( - "SELECT COUNT(*) AS count FROM runtime_workspace_versions WHERE origin_kind = 'successor'", + "SELECT COUNT(*) AS count FROM runtime_workspace_versions WHERE origin_kind = 'tool_mutation'", ) .get() as { count: number }; const reservationCount = database From ad2def3a95d0a94d1d2432a04f65301892370e79 Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 18:30:58 +0800 Subject: [PATCH 8/9] test(runtime-host): allow platform Node diagnostics --- .../__tests__/managed-coding-v2-crash.test.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts index 3e80b2f58a..aba4e07a5f 100644 --- a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts +++ b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts @@ -352,13 +352,22 @@ test('packaged managed-coding-v2 resumes after Host death without replaying a co provider.waitForCompletedToolResult(), startFailure, ]); - assert.deepEqual(readManagedNodeRunResult(completedToolResult), { - protocolVersion: 1, - kind: 'node_command_observation', - exitCode: 7, - stdout: '{"argv":["--check","src/index.js"],"path":""}\n', - stderr: '', - }); + const completedNodeRun = readManagedNodeRunResult(completedToolResult); + assert.deepEqual( + { + protocolVersion: completedNodeRun.protocolVersion, + kind: completedNodeRun.kind, + exitCode: completedNodeRun.exitCode, + stdout: completedNodeRun.stdout, + }, + { + protocolVersion: 1, + kind: 'node_command_observation', + exitCode: 7, + stdout: '{"argv":["--check","src/index.js"],"path":""}\n', + }, + ); + assert.equal(typeof completedNodeRun.stderr, 'string'); await fixture.killHost(firstHost); await withTimeout(start, PROCESS_TIMEOUT_MS, 'crashed hosted execution did not close'); await firstClient.close().catch(() => undefined); @@ -382,6 +391,7 @@ test('packaged managed-coding-v2 resumes after Host death without replaying a co assert.equal(provider.requests.length, 3); assert.match(JSON.stringify(provider.requests[1]), /ManagedNodeRun/u); assert.match(JSON.stringify(provider.requests[2]), /ManagedNodeRun/u); + assert.deepEqual(readManagedNodeRunResult(provider.requests[2]), completedNodeRun); const readerOwner = await tryAcquireInteractiveRootReader(capability); assert.ok(readerOwner); if (!readerOwner) throw new Error('Unable to read managed v3 fixture root'); From 4debf43f9bf27ef7a5fb1ec511f1c6594e6428d7 Mon Sep 17 00:00:00 2001 From: zhiiw Date: Mon, 31 Aug 2026 19:05:13 +0800 Subject: [PATCH 9/9] test(runtime-host): decode canonical replay envelopes --- .../src/__tests__/managed-coding-v2-crash.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts index aba4e07a5f..7d2d9d6bea 100644 --- a/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts +++ b/packages/runtime-host/src/__tests__/managed-coding-v2-crash.test.ts @@ -988,7 +988,14 @@ function readManagedNodeRunResult(request: unknown): Readonly; + const envelope = JSON.parse(toolMessage.content) as Record; + const value = + envelope.kind === 'json' && + envelope.value && + typeof envelope.value === 'object' && + !Array.isArray(envelope.value) + ? (envelope.value as Record) + : envelope; return Object.freeze({ protocolVersion: value.protocolVersion, kind: value.kind,