Skip to content
Closed
2 changes: 2 additions & 0 deletions .github/workflows/macos-recovery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
run: |
npm run build -w @maka/core
npm run build -w @maka/storage
npm run build -w @maka/runtime
npm run build -w @maka/runtime-host

- name: Verify managed workspace crash recovery
run: node scripts/run-recovery-test-inventory.mjs managed-workspace
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# Managed Workspace Git Mutation Candidate Owner v1

- 状态:M2.2 核心实现完成;已在最新 main 上重建并仅叠加 M2.1,保持 Draft 等待 M2.3/M2.4 消费者
- 更新日期:2026-08-17
- 主要不变量:只有 Maka 的 Git artifact owner 能把 owned worktree 中一次声明路径的变化发布成 operation-bound candidate
- 状态:M2.2 核心 owner,M2.4 将其输入收紧为 Runtime-owned exact result content;API-only Draft
- 更新日期:2026-08-19
- 主要不变量:只有 Maka 的 Git artifact owner 能把 Runtime-owned transform result 写入 private index 并发布成 operation-bound candidate;worktree 不是 candidate 输入
- owner:既有 `GitWorkspaceService`;不新建第二个 repository/ref writer
- canonical artifact:candidate Git commit/ref + strict durable receipt
- 不做:不写 T2、不推进 SQLite workspace head、不更新 worktree `HEAD`、不接 Desktop/CLI

## 1. 为什么是 candidate,而不是直接提交 head

工具执行后的文件状态还不能直接成为 canonical workspace version。M2.2 先将它冻结成候选:
工具的 result content 还不能直接成为 canonical workspace version。candidate owner 先将它冻结成候选:

```text
accepted base commit
+ declared changed paths
+ exact resulting blob for the sole path
+ fixed candidate policy
+ execution profile digest
Expand All @@ -24,7 +25,7 @@ operation-bound candidate ref
durable candidate receipt
```

candidate 只证明“Git owner 看到了什么”。只有后续 M2.4 将它与 T1 identity、工具语义和 M2.1 SQLite
candidate 只证明“Git owner 对哪一份 exact result content 重算并写入了什么”。只有 M2.4 将它与 T1 identity、工具语义和 M2.1 SQLite
bundle 一起验证并提交后,它才成为 accepted workspace version。

## 2. 权限边界
Expand All @@ -38,7 +39,8 @@ config 和 storage-root writer lock。M2.2 通过 storage-internal `WeakMap` cap
- binding、repository、epoch artifact 和 Git runtime identity 一致;
- worktree 是非 symlink 目录,common-dir 指向 Maka repository,worktree lock 仍存在;
- worktree `HEAD`、managed head ref、base commit/tree 同时匹配;
- status 只有声明的路径,没有 ignored、rename/copy 或额外变化;
- worktree status 必须 clean;projection drift 只能 fail closed 或在 accept 时整体保留,绝不进入 candidate;
- 首版单文件 mutation 的 result content 必须与 worker result blob 匹配,owner 再用 verified Git `hash-object` 重算 OID并写入 private index(删除为 absent);
- candidate 全树只有普通 blob mode `100644/100755`,没有 symlink、submodule、special mode、属性文件或大小写冲突;
- candidate commit 只有一个 parent,且 commit identity/message 使用固定协议;
- receipt 的 ref、commit、tree、parent、递归文件级 delta digest 和路径集合可从 Git object database 重算;
Expand All @@ -55,11 +57,11 @@ sequenceDiagram
participant R as Maka Git Repository
participant D as Durable Receipt Directory

C->>G: capture(binding, operation, baseHead, expectedPaths, profile)
C->>G: capture(binding, operation, baseHead, expectedPath, expectedBlob/content, profile)
G->>G: acquire storage-root writer lock
G->>G: verify binding, worktree owner, exact base and status
G->>R: read-tree(base) into private temporary index
G->>R: add declared paths and write-tree
G->>R: hash exact result content; update-index declared path; write-tree
G->>R: commit-tree(tree, parent=base)
G->>R: CAS create operation-bound candidate ref
G->>R: recompute tree modes and delta
Expand All @@ -71,7 +73,7 @@ sequenceDiagram
因此合同依靠可重放状态,而不是宣称不存在中间状态:

- object 已写但 ref 未写:unreachable object,Git GC 可回收;
- ref 已写但 receipt 未写:相同 operation/base/worktree 重试生成相同 commit 并补 receipt;
- ref 已写但 receipt 未写:相同 operation/base/result content 重试生成相同 commit 并补 receipt;
- receipt 已写:重启后严格重验 Git artifact 和 delta,exact retry 返回同一 receipt;
- ref 与当前 candidate 不一致:fail closed,不覆盖。

Expand All @@ -94,7 +96,8 @@ tombstone 在删除 ref 前落盘,所以崩溃后不会把“外部删 ref”
| 情况 | 结果 | 后续 |
|---|---|---|
| base/head/worktree owner 漂移 | `managed_workspace_drifted` 或 identity conflict | park;不发布 ref |
| undeclared、ignored、dependency/control path | `managed_mutation_candidate_rejected` | 不发布 ref |
| worktree projection drift、undeclared/ignored residue | `managed_mutation_candidate_rejected` | 不读取其内容,不发布 ref |
| candidate 目标 blob 与 worker result blob 不同 | `managed_mutation_candidate_rejected` | 不发布 ref;park/quarantine |
| symlink/submodule/special mode/attributes/case collision | reject | 不发布 receipt;已生成 object 可 GC |
| 同 operation 已有不同 ref/receipt | identity conflict | fail closed |
| ref 后崩溃 | ref 保留、receipt 缺失 | exact capture 重放 |
Expand All @@ -109,23 +112,35 @@ tombstone 在删除 ref 前落盘,所以崩溃后不会把“外部删 ref”
| private temporary index | 支持 | 支持 | 支持 |
| symlink candidate 拒绝 | Git tree mode 验证;实测 | Git tree mode 验证;实测 | 同一 tree-mode 验证;创建 symlink fixture 可能跳过 |
| operation path identity | case-sensitive | case-sensitive | filesystem 路径比较 case-insensitive;Git path 仍严格 |
| process-crash convergence | 承诺;Linux storage stress CI | 实现预期支持,但 v1 未持续验证、不作承诺 | 承诺;Windows recovery CI |
| process-crash convergence | 承诺;Linux recovery inventory | 承诺;macOS recovery inventory | 承诺;Windows recovery inventory |
| power-loss ordering | v1 不承诺 | v1 不承诺 | v1 不承诺 |

普通 `fsync`、Git ref 的平台实现和设备缓存不足以构成统一的断电证明,所以 v1 只声明进程崩溃收敛。

进程崩溃承诺由真实 child-process kill/reopen 测试约束:capture 在 ref publication 后被杀,新进程补齐同一
receipt;discard 在 ref deletion 后被杀,新进程依 tombstone 幂等完成清理。统一的
`scripts/recovery-test-inventory.mjs` 拥有 recovery suite 和 Linux/Windows 期望数量,Windows workflow 直接
消费该 inventory;Linux storage stress 全量测试包含相同用例。仓库当前没有 macOS runner,因此 v1 不把 macOS
process-crash convergence 写成已证明能力。嵌套目录的新增、修改与删除均以递归 `diff-tree -r` 的文件路径作为
receipt;discard 在 ref deletion 后被杀,新进程依 tombstone 幂等完成清理;projection rotation 在保存旧目录及
发布新目录两个点被杀,新进程只收敛投影且保留外部内容。rotation intent 同时绑定旧 worktree 根目录的
device/inode identity;恢复拒绝预置 symlink/Windows junction,并且 projection owner 不再通过可替换 quarantine
子路径删除 `.git`。

旧投影与新 canonical projection 必须是两个独立的 linked worktree registration。Git owner 不把 porcelain
`worktree add/move` 视为原子事务:staging 以 `worktree add --no-checkout` 分离 registration 与 materialization;
半完成 path/registration 由 durable intent 驱动的 reconciler 整体保留或 prune 后重建。旧目录与 staging 的发布只用
原子 rename,随后对实际存在的路径幂等执行 `worktree repair` 并重验 HEAD/index/lock。因此 Git command 内部中断不会
要求重新执行 Write/Edit,也不会把 partial checkout 当成完整 artifact。

两份 projection 的 per-worktree gitdir、HEAD 与 index 不同;从 quarantine 执行 `reset`/`add` 不得改变 canonical
HEAD/index 或 managed ref。quarantine GC 将来必须先由 Git owner 撤销 registration,再清理目录,不能把 locked
quarantine 或 partial staging 当普通缓存目录递归删除。统一的
`scripts/recovery-test-inventory.mjs` 拥有 recovery suite 和三平台期望数量,Linux/macOS/Windows workflow 消费
同一 inventory。嵌套目录的新增、修改与删除均以递归 `diff-tree -r` 的文件路径作为
receipt 证据,不能退化成顶层目录名。

## 7. 留给 M2.3a/M2.3b/M2.4 的边界

M2.2 不证明变化一定由某一次 Write/Edit 造成。M2.3a 必须把 T1 identity 与 durable exclusive reservation
M2.2 单独不证明调用者提供的 expected blob 一定由某一次 Write/Edit 造成。M2.3a 必须把 T1 identity 与 durable exclusive reservation
原子绑定并把 exact changed paths 带入 accepted truth;M2.3b 在 T1 前冻结 owner-bound execution admission;
M2.4 必须核对正常工具 transform 的 expected result,调用 M2.1 原子 bundle,并在真实 Host kill/reopen
M2.4 让真实 worker 从 Git immutable base content 执行生产 transform、返回 result content/blob,再由 candidate owner 重算并核对 immutable tree,调用 M2.1 原子 bundle,并在真实 Host kill/reopen
测试中证明唯一 accepted successor。

因此本切片保持 Draft;它没有生产 consumer,也不提升当前 Desktop 的 resume 能力。
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,28 @@ Runtime settlement hook 属于 M2.3b,其余三项由 M2.4 的真实 owner/cons
| `docs/architecture/runtime-managed-workspace-mutation-execution-admission-v1.zh-CN.md` | M2.3b | Runtime owner、失败状态、平台证据和 M2.4 seam |

M2.3b 不迁入 `ManagedWorkspaceOwner` mutation lease/profile、built-in Write/Edit 标记、filesystem worker mutation、
Git candidate capture/discard、successor/error bundle composition 或 Desktop/CLI wiring;这些共同构成 M2.4 的首个
生产消费者。只读 worker 的存在不能签发 mutation profile,caller callback 也不能自证执行能力。
Git candidate capture/discard、successor/error bundle composition 或 Desktop/CLI wiring;前四项共同构成 M2.4 的
Runtime Host API composition,Desktop/CLI 产品入口仍未接入。只读 worker 的存在不能签发 mutation profile,caller
callback 也不能自证执行能力。

## M2.4 增量归属

| 路径 | 主要不变量 |
|---|---|
| `packages/core/src/session.ts` | 显式 `managed-coding-v1` profile;attached profile 不静默升级 |
| `packages/runtime/src/tool-runtime.ts` | Runtime-owned result snapshot;managed terminal 只采用 exact durable proof |
| `packages/runtime/src/filesystem-worker/{protocol,client,operations}.ts` | worker protocol v7;immutable Git content transform,不授予 worktree read/write authority |
| `packages/storage/src/managed-workspace-owner.ts` | 同一 execution handle 绑定 head/path/worker profile/candidate/settlement |
| `packages/storage/src/managed-workspace-worker-bridge-internal.ts` | 把 base content 交给受限 worker并收回 exact result content/blob,不暴露 host-local write seam |
| `packages/storage/src/sqlite-runtime-store.ts` | successor 或 no-effect terminal 的专用原子 writer;generic T2 继续被拒绝 |
| `packages/storage/src/git-workspace-service.ts` | result content 直接进入 private index;candidate accept 用 crash-convergent projection rotation,禁止 `reset --hard` |
| `packages/runtime-host/src/server/managed-workspace-mutation-session.ts` | production Host 将 Read/Glob/Grep/Write/Edit 路由到同一 owner-bound workspace |
| `packages/runtime-host/src/server/execution-composition.ts` | 仅显式 managed profile 取得 managed admission + mutation worker |
| `scripts/recovery-test-inventory.mjs` | successor SQLite commit 与 projection rotation 中间点真实 process kill/reopen 的三平台证据 |
| `runtime-managed-workspace-write-edit-production-v1.zh-CN.md` | M2.4 owner、原子边界、失败状态和平台矩阵 |

M2.4 不绑定 continuation cursor,也不在 attached checkout 自动 redo。前者属于 M3,restore/rebaseline/publish
属于 M4。

## Commit 映射

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Managed Workspace Mutation Runtime Settlement v1

- 阶段:M2.3b
- 状态:实现切片;保持 Draft,等待 M2.4 Write/Edit 生产消费者
- 状态:实现切片;M2.4 Runtime Host API 已消费,Desktop/CLI session creator 尚未接入
- owner:Tool Runtime managed settlement seam
- durable 真相:M2.3a SQLite reservation 与 owner 已提交的 immutable outcome

Expand All @@ -16,11 +16,12 @@ owner 最多调用一次;`execute()` 返回或抛错时 capability 立即关
Runtime 必须先 join 该 operation,再将整个结算判为 unsettled,确保 Runtime 返回后不存在仍可产生副作用的 detached
execution。

Runtime 只接受三种 owner 结算:
Runtime 只接受四种 owner 结算:

1. `workspace_successor_committed`:M2.1 的成功 T2、successor 与 head 已经原子提交;
2. `safely_discarded`:owner 已证明 candidate 未被接受,并已提交 exact error outcome;
3. `unsettled`:副作用或结算状态不可证明,M2.3a reservation 保留给恢复流程。
2. `no_workspace_change_committed`:成功但 tree 未变化,success outcome 与 terminal fact 已提交;
3. `operation_failed_no_effect_committed`:失败且 owner 已证明 workspace 无副作用,error outcome 与 terminal fact 已提交;
4. `unsettled`:副作用或结算状态不可证明,M2.3a reservation 保留给恢复流程。

owner 返回值首先经过运行时结构校验,规范化成内部 terminal union。managed/generic lane 只由 T1 前已经确定的
`managedMutationAdmission` 决定,绝不再用 `durableOutcome` 是否 truthy 选择 writer;terminal settlement 缺失
Expand Down Expand Up @@ -66,15 +67,17 @@ flowchart TD
Q --> N["normalize terminal proof"]
N --> S{"terminal state proven?"}
S -->|"successor committed"| A["compare exact durable success envelope"]
S -->|"safely discarded"| E["compare exact durable error envelope"]
S -->|"no change committed"| N0["compare exact durable success envelope"]
S -->|"failed no effect committed"| E["compare exact durable error envelope"]
S -->|"unknown / throw / invalid"| P["fail-stop; reservation remains"]
A --> R["publish provider result"]
N0 --> R
E --> R
```

`safely_discarded` 只携带一个 exact `providerResult`。Runtime 从该值生成 canonical content,并执行与普通工具
相同的 `maxResultBytes` 检查。getter、serialization、canonicalization 或 size-check 的任何失败都变成 managed
unsettled,不写 generic T2。
两个 no-effect committed 状态与 successor success 一样只携带 terminal proof 和 exact `durableOutcome`,不允许 Host 重新提交
`providerResult`。Runtime 在 operation 边界持有唯一 bounded strict-JSON snapshot;getter、serialization、
canonicalization 或 size-check 的任何失败都变成 managed unsettled,不写 generic T2。

`workspace_successor_committed` 只携带 `durableOutcome`,不得重新提交 provider value。Runtime 在调用真实 operation
完成边界用一次 bounded strict-JSON walker 同时执行 byte budget、类型校验、plain snapshot 和递归冻结。walker
Expand All @@ -96,7 +99,8 @@ parent refs 和 duration。缺字段、多字段或任意值不同都 fail-stop
| Host admission seam 缺失或 admission 在 T1 前失败 | 无 T1、不执行工具 |
| T1 commit 失败 | dispose admission,不执行工具 |
| owner 已原子接受 successor | Runtime 采用 exact durable success outcome |
| owner 已安全 discard | Runtime 采用 exact durable error outcome |
| owner 已提交成功 no-op | Runtime 采用 exact durable success outcome |
| owner 已证明 operation failure 无副作用 | Runtime 采用 exact durable error outcome |
| owner 返回 unsettled、抛错或响应丢失 | 无 generic T2、无 provider result,reservation 保留 |
| owner 结算后再次调用 operation | capability 已关闭,不执行工具 |
| owner 在 operation 运行中提前结算 | join operation 后 fail-stop,不接受 terminal settlement |
Expand All @@ -110,11 +114,12 @@ parent refs 和 duration。缺字段、多字段或任意值不同都 fail-stop
| T1 reservation kill/reopen | CI 证明 | CI 证明 | CI 证明 |
| 跨进程唯一 mutation reservation | CI 证明 | CI 证明 | CI 证明 |
| Runtime managed settlement fail-stop | 平台无关测试 | 平台无关测试 | 平台无关测试 |
| mutation worker/profile/candidate | M2.4 | M2.4 | M2.4 |
| mutation worker/profile/candidate | CI 证明 | CI 证明 | 边界测试证明;完整 Host crash lane 等待发布 broker |
| power-loss convergence | 不在本切片 | 不在本切片 | 不在本切片 |

统一 recovery inventory 同时包含 managed baseline/candidate、`sqlite-runtime-crash` 与
`sqlite-recovery-concurrency`。Linux、macOS、Windows 使用相同文件清单、name pattern 和严格测试数量。
统一 recovery inventory 同时包含 managed baseline/candidate、`sqlite-runtime-crash`、
`sqlite-recovery-concurrency` 与真实 Host/worker kill-reopen。Linux、macOS 完整执行 30 条;Windows 使用同一清单,
其中 29 条通过,完整 Host/worker 用例因当前 runner 不打包 Rust broker 而明确 skip。

## 7. 验证

Expand All @@ -125,8 +130,8 @@ parent refs 和 duration。缺字段、多字段或任意值不同都 fail-stop
- terminal settlement 撤销 operation capability;detached operation 必须 join 后 fail-stop;
- 工具事后修改原始 result 不得改变 durable、message、event 或 provider snapshot;
- oversized result 必须在遍历越过 budget 前终止;strict-JSON 非法值不得进入 durable event;
- safe-discard live result 与 durable content 不一致时 fail-stop;
- getter/canonicalization 异常和超大 safe-discard 均不写 generic T2、不发布结果;
- no-effect terminal 的 live result 与 durable content 不一致时 fail-stop;
- getter/canonicalization 异常和超大 no-effect result 均不写 generic T2、不发布结果;
- code-mode response 的 origin、hidden visibility、parent refs 与 duration 必须完整匹配;
- explicit unsettled、owner throw 和 T1 后任意异常均 fail-stop;
- real-process kill-after-T1 与双进程 reservation 竞争进入三平台 recovery inventory。
Expand All @@ -139,5 +144,5 @@ parent refs 和 duration。缺字段、多字段或任意值不同都 fail-stop
- 不 capture、discard 或 accept Git candidate;
- 不接 Desktop/CLI,不改变当前用户可见 resume 能力。

M2.3b 没有生产消费者,因此即使测试通过也保持 Draft。M2.4 必须由同一个 owner 同时签发并执行真实 mutation
profile,不能重新引入 caller digest、静态标签或 callback 自证
M2.4 现在由同一个 owner 同时签发并执行真实 mutation profile,详细合同见
[Managed Workspace Write/Edit Production Composition v1](./runtime-managed-workspace-write-edit-production-v1.zh-CN.md)
Loading