From b2b202ef7619e4edfaaf42c495a6c0e4e1d39bea Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sat, 29 Aug 2026 19:43:32 +0800 Subject: [PATCH] refactor: remove dormant Git workspace write path Remove the disconnected Git executable workspace service, write owner, execution admission bridge, and their private tests and CI entries. Keep attached execution on the Runtime filesystem worker and preserve the schema 9 workspace event reader, migration, projections, and rebuild authority for historical databases. The released v0.1.10 and v0.1.11 compositions constructed the owner but had no production caller for baseline open, artifact creation, or managed profile creation, so no artifact-only decoder remains. Generated-by: Codex --- .github/RELEASE_CHECKLIST.md | 4 +- .github/workflows/release.yml | 2 +- .github/workflows/windows-baseline.yml | 1 - .github/workflows/windows-recovery.yml | 17 - ...e-short-lived-helper-admission-v1.zh-CN.md | 2 +- ...d-dependency-storage-authority-v1.zh-CN.md | 2 +- ...anaged-workspace-baseline-open-v1.zh-CN.md | 265 -- ...-workspace-execution-admission-v1.zh-CN.md | 179 - ...untime-managed-workspace-owner-v1.zh-CN.md | 136 - .../runtime-resume-architecture.md | 14 +- .../runtime-resume-architecture.zh-CN.md | 14 +- .../runtime-resume-extraction-ledger.zh-CN.md | 2 +- ...hase4-workspace-checkpoint-design.zh-CN.md | 132 +- ...me-workspace-version-authority-v1.zh-CN.md | 115 +- docs/windows-support.md | 4 +- docs/windows-test-inventory.md | 8 +- .../__tests__/execution-composition.test.ts | 33 - .../workspace-execution-composition.test.ts | 128 +- .../src/server/execution-composition.ts | 12 +- .../server/workspace-execution-composition.ts | 85 +- packages/storage/package.json | 1 - .../dugite-native-environment.test.ts | 72 - .../git-workspace-service-crash-child.ts | 90 - .../__tests__/git-workspace-service.test.ts | 898 ----- .../managed-workspace-baseline.test.ts | 1326 -------- ...space-execution-authority-internal.test.ts | 58 - .../__tests__/managed-workspace-owner.test.ts | 1013 ------ ...d-workspace-worker-bridge-internal.test.ts | 124 - .../src/__tests__/public-entrypoints.test.ts | 2 - .../storage/src/dugite-native-environment.ts | 54 - packages/storage/src/git-workspace-service.ts | 2933 ----------------- ...ged-baseline-receipt-authority-internal.ts | 56 - ...-workspace-execution-authority-internal.ts | 176 - .../storage/src/managed-workspace-owner.ts | 584 ---- ...anaged-workspace-worker-bridge-internal.ts | 144 - packages/storage/src/sqlite-runtime-store.ts | 7 +- .../workspace-version-authority-internal.ts | 79 +- scripts/ci-test-plan.mjs | 3 - 38 files changed, 144 insertions(+), 8631 deletions(-) delete mode 100644 docs/architecture/runtime-managed-workspace-baseline-open-v1.zh-CN.md delete mode 100644 docs/architecture/runtime-managed-workspace-execution-admission-v1.zh-CN.md delete mode 100644 docs/architecture/runtime-managed-workspace-owner-v1.zh-CN.md delete mode 100644 packages/storage/src/__tests__/dugite-native-environment.test.ts delete mode 100644 packages/storage/src/__tests__/fixtures/git-workspace-service-crash-child.ts delete mode 100644 packages/storage/src/__tests__/git-workspace-service.test.ts delete mode 100644 packages/storage/src/__tests__/managed-workspace-baseline.test.ts delete mode 100644 packages/storage/src/__tests__/managed-workspace-execution-authority-internal.test.ts delete mode 100644 packages/storage/src/__tests__/managed-workspace-owner.test.ts delete mode 100644 packages/storage/src/__tests__/managed-workspace-worker-bridge-internal.test.ts delete mode 100644 packages/storage/src/dugite-native-environment.ts delete mode 100644 packages/storage/src/git-workspace-service.ts delete mode 100644 packages/storage/src/managed-baseline-receipt-authority-internal.ts delete mode 100644 packages/storage/src/managed-workspace-execution-authority-internal.ts delete mode 100644 packages/storage/src/managed-workspace-owner.ts delete mode 100644 packages/storage/src/managed-workspace-worker-bridge-internal.ts diff --git a/.github/RELEASE_CHECKLIST.md b/.github/RELEASE_CHECKLIST.md index 72a11d93a0..0f78a82e5c 100644 --- a/.github/RELEASE_CHECKLIST.md +++ b/.github/RELEASE_CHECKLIST.md @@ -33,8 +33,8 @@ Phase 1 requires: - checksums generated after each artifact reaches its final form. The convenience Desktop artifacts must not contain a Git runtime, a bundled-Git manifest, or Git/Dugite -redistribution notices. Managed-workspace execution remains unavailable until a separately reviewed, -ASF-compatible verified runtime is connected before admission/T1. +redistribution notices. The retired Git executable-backed managed-workspace path must not be restored; +future workspace execution requires a separately reviewed Gitoxide production composition before admission/T1. The first product release also requires the exact `maka-agent@` npm package. The product tag and Draft must exist before npm staging, but the Draft must remain unpublished until npm is diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9fe3aeed2..ca1844a0cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -462,7 +462,7 @@ jobs: These files are convenience binaries built from the approved ASF source release; they are not ASF release artifacts. - Known limitations: Computer Use and managed-workspace execution are not included in this release. The Desktop convenience artifacts do not distribute a Git runtime." + Known limitations: Computer Use and Gitoxide-backed workspace execution are not included in this release. The Desktop convenience artifacts do not distribute a Git runtime." if gh release view "$TAG" >/dev/null 2>&1; then is_draft="$(gh release view "$TAG" --json isDraft --jq .isDraft)" diff --git a/.github/workflows/windows-baseline.yml b/.github/workflows/windows-baseline.yml index 06f2dafaaa..8a6fd69cd1 100644 --- a/.github/workflows/windows-baseline.yml +++ b/.github/workflows/windows-baseline.yml @@ -167,7 +167,6 @@ jobs: 'packages/storage/dist/__tests__/write-queue.test.js', 'packages/storage/dist/__tests__/marker-file.test.js', 'packages/storage/dist/__tests__/workspace-root.test.js', - 'packages/storage/dist/__tests__/managed-workspace-owner.test.js', 'packages/storage/dist/__tests__/workspace-identity.test.js' ) node.exe --test --test-concurrency=2 --test-force-exit --test-timeout=60000 ` diff --git a/.github/workflows/windows-recovery.yml b/.github/workflows/windows-recovery.yml index dc9184186f..1c978e38d8 100644 --- a/.github/workflows/windows-recovery.yml +++ b/.github/workflows/windows-recovery.yml @@ -192,20 +192,3 @@ jobs: Write-Error 'Runtime Host recovery gate did not run exactly two passing tests' exit 1 } - - - name: Verify managed workspace crash recovery - shell: pwsh - run: | - $env:MAKA_STORAGE_STRESS = '1' - node.exe --test --test-reporter=tap --test-concurrency=1 ` - --test-name-pattern="real process crash|real crash|real-process crash|crash after baseline ref publication" ` - packages/storage/dist/__tests__/managed-workspace-baseline.test.js ` - packages/storage/dist/__tests__/git-workspace-service.test.js ` - 2>&1 | Tee-Object -FilePath "$env:RUNNER_TEMP/managed-workspace-recovery.tap" - $exitCode = $LASTEXITCODE - if ($exitCode -ne 0) { exit $exitCode } - $output = Get-Content "$env:RUNNER_TEMP/managed-workspace-recovery.tap" - if ($output -notcontains '# tests 12' -or $output -notcontains '# pass 12' -or $output -notcontains '# skipped 0') { - Write-Error 'Managed workspace recovery gate did not run exactly twelve passing tests' - exit 1 - } diff --git a/docs/architecture/gitoxide-short-lived-helper-admission-v1.zh-CN.md b/docs/architecture/gitoxide-short-lived-helper-admission-v1.zh-CN.md index 16eb90514d..9f237c1256 100644 --- a/docs/architecture/gitoxide-short-lived-helper-admission-v1.zh-CN.md +++ b/docs/architecture/gitoxide-short-lived-helper-admission-v1.zh-CN.md @@ -31,7 +31,7 @@ > 打开的 object format 稳定 fail closed,且不得调用或回退到系统 Git。 它不证明 source import、clone、fetch、worktree、candidate、projection、ref CAS、Write/Edit 或 -resume。现有 dormant `GitWorkspaceService` 也没有切换到该 helper。 +resume。已删除的 Git executable-backed workspace service 也不由该 helper 复活。 ## 2. 为什么是 helper,不是常驻 broker diff --git a/docs/architecture/managed-dependency-storage-authority-v1.zh-CN.md b/docs/architecture/managed-dependency-storage-authority-v1.zh-CN.md index 4c9974c8c6..168bead1d7 100644 --- a/docs/architecture/managed-dependency-storage-authority-v1.zh-CN.md +++ b/docs/architecture/managed-dependency-storage-authority-v1.zh-CN.md @@ -38,7 +38,7 @@ base: upstream/main@08bcf324b - bundled npm 的实现与网络策略; - `node_modules/.bin` symlink 的 producer 配额扫描; -- ManagedWorkspaceOwner/worker logical binding; +- workspace execution owner/worker logical binding; - Desktop/CLI/Runtime Host 接线; - release packaging、audit 或 SBOM。 diff --git a/docs/architecture/runtime-managed-workspace-baseline-open-v1.zh-CN.md b/docs/architecture/runtime-managed-workspace-baseline-open-v1.zh-CN.md deleted file mode 100644 index 30271ce748..0000000000 --- a/docs/architecture/runtime-managed-workspace-baseline-open-v1.zh-CN.md +++ /dev/null @@ -1,265 +0,0 @@ - - -# Managed Workspace Baseline Open v1:M0 最终接受门 - -- 状态:已合并;M1 execution admission 在其返回值上增加 owner-bound handle,不改变 M0 durable authority -- 更新日期:2026-08-04 -- 主要不变量:只有经 Git artifact owner 持久化并重新验证的 exact baseline receipt,才能由同一 - storage-root lifecycle owner 提交给 SQLite workspace authority;Git artifact 单独成功不构成 - canonical workspace version -- Git artifact owner:`GitWorkspaceService` -- composition/lifecycle owner:`ManagedWorkspaceOwner` -- canonical history owner:SQLite 内 immutable workspace RuntimeEvents - -## 1. 本切片交付什么 - -前三个 M0 切片已经分别证明: - -1. Maka 可以用 pinned Git 创建、验证、repair 和 quarantine 自有 Git repository/worktree artifacts; -2. SQLite 可以把 epoch-opened、baseline-accepted 与三个 projection 原子提交,并从 RuntimeEvents 重建; -3. 一个 authenticated interactive storage root 只发布一个 managed workspace lifecycle owner。 - -它们之间仍缺一个权限与因果门。如果调用者可以把任意 OID 直接交给 SQLite,那么 Git 验证只是注释; -如果 Git 创建成功就被当作 canonical,那么 SQLite 失败后会出现第二套 workspace truth。本切片只关闭这条 -seam,不接 Desktop、CLI、runtime host 或工具执行。 - -公开入口是: - -```ts -owner.openManagedWorkspaceBaseline(runtimeStore, { - repositoryId, - workspaceId, - workspaceEpochId, - workspaceInstanceId, - sourceRoot, -}); -``` - -调用者只提供业务身份与 source root,不能提供 policy hash、commit/tree OID、version ID、event ID、 -tree delta 或“verified”布尔值。M0 policy 是实现实际执行并内部 canonicalize/hash 的固定协议,不是 -调用者提供的标签。 - -## 2. Owner、原子边界、失败状态与回滚 - -| 项目 | 决策 | -|---|---| -| Git artifact owner | `GitWorkspaceService` 证明 Git artifact,并在未导出的 receipt authority capability 中实现持久化与复验 | -| lifecycle owner | `ManagedWorkspaceOwner` 是 receipt capability 的唯一持有者,独占 Git receipt → SQLite authority 的组合调用 | -| canonical truth | SQLite authority stream 的 immutable RuntimeEvents;receipt 是 admission evidence,不是 canonical head | -| Git durability boundary | `baseline-receipt.json` 以同目录临时文件、file fsync、rename、POSIX parent fsync 持久化 | -| SQLite atomic boundary | epoch fact、baseline fact、epoch/version/head projection 位于同一事务 | -| root binding | authority store 必须是 authenticated storage root 下精确的普通非 symlink `runtime.sqlite`,并保持注册时的文件 identity;该文件必须只有一个 hard link,lease 的真实 DB path 是唯一依据;数据库 singleton rootId 必须等于 authenticated owner rootId;SQLite COMMIT 后、返回前再次验证 pathname、identity 与 durable rootId | -| policy identity | policy version/hash 从固定且实际执行的 M0 policy object 内部派生;覆盖 source cleanliness、tracked/untracked/ignored、UTF-8 lossless path、NFC/case-fold collision、symlink/submodule/attributes/special mode 与 materialization;public API 不接受裸 hash | -| exact retry | version/event IDs 由 binding + artifact + policy 确定性派生;durable receipt 冻结可重新证明的 Git evidence 与 delta | -| pre-accept orphan | receipt 已落盘而 SQLite 未接受时,它是可重试 orphan;不是 canonical workspace version | -| post-accept loss | SQLite 已有 head 但 receipt 缺失、漂移或 Git artifact 不可验证时,报 corruption/unavailable,不生成替代 receipt | -| 回滚 | 停止调用本入口即可;既有 RuntimeEvents 仍可读。不能删除已接受 receipt 来“回滚” canonical history | - -本切片没有尝试跨 Git filesystem 与 SQLite 建立一个不真实的分布式事务。它使用可重放协议收敛:Git -receipt 先 durable,SQLite 后原子接受;唯一允许的半完成状态是“未被接受的 Git orphan”。反方向的 -“SQLite 已接受但 Git receipt 不存在”不是可修复半状态,而是 fail-closed corruption。 - -## 3. Durable receipt 合同 - -receipt 位于 managed instance root,和 `binding.json` 同级: - -```text -managed-workspaces/ - w//e//i// - binding.json - baseline-receipt.json - worktree/ -``` - -v1 receipt 严格包含: - -```ts -{ - schemaVersion: 1, - protocol: 'maka_managed_workspace_baseline_receipt_v1', - binding, - workspaceVersionId, - policyVersion: 1, - policyHash, - epochOpenedEventId, - baselineAcceptedEventId, - treeDeltaDigest, - changedFileCount, - deletedFileCount: 0, -} -``` - -所有字段采用 exact-key strict decoder。receipt 必须重新证明: - -- durable binding 与调用 identity 完全相同; -- repository record、epoch artifact、baseline ref、head ref、commit/tree 与 pinned Git capability 一致; -- worktree registration 仍被 Maka ownership lock 锁定; -- HEAD/tree 等于 baseline,worktree clean; -- policy version/hash 等于实现实际执行的 canonical M0 policy; -- workspaceVersionId、epochOpenedEventId 与 baselineAcceptedEventId 可从 binding、baseline artifact 与 - policy 确定性重新派生;合法 shape 的磁盘篡改也必须被拒绝; -- tree delta 摘要与 baseline tree 重新计算结果一致。 - -`GitWorkspaceService` 的 public interface 不暴露 receipt 的 issue/require/verify,package root 也不导出 -`git-workspace-service` factory。artifact-only create/open 只作为 storage 内部前置实现存在;public -`ManagedWorkspaceOwner` 只暴露 `openManagedWorkspaceBaseline(...)` 与 lifecycle close。对应 receipt capability -位于未被 package root 导出的 internal module,并通过实例绑定的 `WeakMap` 只交给该 owner。普通 package -consumer 既不能预占一个 epoch 的 admission identity,也不能在 canonical acceptance 前取得 executable -worktree path。M1 execution admission 只允许消费由该入口成功返回的 owner-bound handle;handle 的 consumer -是同一 `ManagedWorkspaceOwner.withManagedWorkspaceExecution(...)` 准入门,仍未把 cwd 暴露给 Desktop、CLI -或 ToolRuntime。详细合同见 -[Managed Workspace Execution Admission v1](./runtime-managed-workspace-execution-admission-v1.zh-CN.md)。 - -receipt 不保存 wall-clock `committedAt`:artifact owner 无法从 Git evidence 重新证明该时间,允许它进入 receipt -会让 orphan receipt 篡改污染 canonical event 时间。M0 的 baseline authority 使用协议固定逻辑时间 `0`; -canonical 顺序由 SQLite authority spine 的 `event_seq` 决定。真实 wall-clock acceptance time 若未来用于观测, -必须由 store 自己生成并作为 operational metadata 保存,不进入 Git evidence 或恢复身份。 - -## 4. Tree delta 摘要 - -M0 baseline 的逻辑 parent 是 empty tree,因此: - -- `changedFileCount` 等于 baseline tree 的受支持 blob entry 数; -- `deletedFileCount` 固定为 `0`; -- `treeDeltaDigest` 是 domain-separated canonical manifest 的 SHA-256,而不是 caller 自报值。 - -manifest 以 Buffer 读取 Git `ls-tree -r -z`,按 Git canonical 顺序记录每个 entry 的 `mode`、 -`objectType`、`oid` 和 `pathBytesBase64`。v1 为了保证 Git path 与 Node/host 路径语义一致,会严格 -拒绝不能无损解码并 round-trip 的非 UTF-8 path;不会把 replacement character 写进摘要。M0 只接受 -普通 blob mode `100644` / `100755`;symlink、submodule、special mode 与 case collision 继续由 Git -Workspace Service 在 receipt 生成前拒绝。现有 Git CLI adapter 仍拒绝 `.gitattributes`;替代它的 -Gitoxide data plane 只接受 managed tree policy v2 已验证的确定性 attributes 子集,不能把两条路径的 -receipt 或 materialization profile 混用。 - -该摘要证明的是 Git object delta,不是 checkout 文件字节扫描,也不把 ignored/untracked 内容纳入 -canonical baseline。 - -## 5. 正常时序 - -```mermaid -sequenceDiagram - participant C as "Storage composition caller" - participant O as "ManagedWorkspaceOwner" - participant G as "GitWorkspaceService" - participant R as "baseline-receipt.json" - participant S as "SqliteRuntimeStore" - participant E as "Workspace RuntimeEvents" - - C->>O: openManagedWorkspaceBaseline(store, identity) - O->>O: authenticate root lease; assert exact runtime.sqlite identity - O->>S: bind/verify singleton durable storage-root ID - O->>G: create/adopt and verify managed workspace - G->>G: verify binding/repository/epoch/ref/worktree - G->>G: derive canonical policy + IDs + empty-tree → baseline delta - alt receipt already exists - G->>R: strict read + exact revalidation - else no canonical head and receipt absent - G->>R: atomic durable write of frozen receipt - end - G-->>O: verified durable receipt - O->>S: internal commitWorkspaceBaseline(receipt-derived input) - S->>S: BEGIN IMMEDIATE - S->>E: epoch-opened + baseline-accepted - S->>S: epoch/version/head projections - S->>S: COMMIT - S-->>O: created or exact-existing head - O->>O: post-commit revalidate exact runtime.sqlite identity - O->>S: revalidate exact durable storage-root ID - O->>G: post-commit reverify exact receipt and Git artifacts - G-->>O: verified - O->>O: re-authenticate root owner and durable root marker - O-->>C: usable canonical baseline -``` - -只有最后一步成功返回以后,调用者才得到可用的 managed baseline。SQLite commit 后的 reverify 失败 -不会撤销 canonical history;它阻止当前 instance 被交给后续工具,并要求 repair/quarantine 流程处理。 -最终 root-owner 复验与 Git receipt 复验是两个独立闸门:receipt 证明 managed artifact,root-owner -复验证明返回瞬间的 marker、lease、database binding 仍属于同一个 authenticated storage root。 - -## 6. Crash 与并发矩阵 - -| 崩溃/并发点 | durable Git 状态 | canonical SQLite 状态 | 重启结果 | -|---|---|---|---| -| receipt durable 前退出 | 无 receipt 或原子旧值 | absent | 重新验证 Git 后重新生成 receipt | -| receipt durable 后、SQLite 前退出 | exact orphan receipt | absent | exact reuse receipt,再提交 authority | -| epoch/version/projection 事务中退出 | exact receipt | 全回滚 | exact retry;不生成新 IDs | -| SQLite COMMIT 后、post-verify 前退出 | exact receipt | accepted | 重读 head + require existing receipt;exact existing | -| accepted 后 receipt 缺失 | missing | accepted | fail closed;禁止创建 replacement receipt | -| accepted 后 receipt 篡改 | invalid/drifted | accepted | fail closed;canonical history 不变 | -| accepted 后 Git ref/object/worktree 漂移 | unverifiable | accepted | fail closed,后续 quarantine/repair;不返回 cwd | -| 同一 root owner 内两个并发 open | artifact lock/SQLite writer 串行 | 一个 transaction 创建 | 一个 created,一个 exact existing | -| 两个进程竞争同一 root owner | loser 不能越过 root-owner admission | winner 独占 composition | winner 关闭后 loser 重试并得到 exact existing | -| owner 传入另一 storage root 的 DB | 不写 receipt | 不写错误 DB | admission 前拒绝 | -| 两个 storage root 以 hard link 共享 `runtime.sqlite` inode | 不写 receipt | 不接受共享 DB;拒绝 `nlink != 1` | fail closed,避免 WAL/SHM 跨目录分裂 | -| 单独复制或移动已绑定的 `runtime.sqlite` 到另一 root | 不写/不读取错误 root 的 receipt | DB rootId 与 owner rootId 冲突 | fail closed;必须使用 whole-root import/adopt 协议 | -| 正式复制整个 storage root 后 adopt | storage-root marker 与 DB 随 root 保留 | DB rootId 与 marker rootId 保持一致 | `adoptStorageRootOnImport` 只恢复 storage-root identity;不证明旧路径下已 materialize 的 linked worktree 可迁移 | -| imported root 含既有 managed workspace instance | binding 与 Git admin metadata 可能仍引用旧绝对路径 | canonical history 保留 | fail closed;首版必须显式 relocation/adoption 或重新 materialize 新 instance,不能直接返回旧 cwd | -| unbound DB 已含 Session、RuntimeEvent、claim 或 workspace fact 等逻辑数据 | 不生成新 receipt | 禁止静默认领 | 要求独立、显式、可备份和可审计的 legacy root-binding adoption;M0 baseline open 不承担迁移 | -| 初次 DB identity 检查后 canonical pathname 被替换 | receipt 可能成为 orphan | 已打开连接的提交视为 detached,不返回 usable baseline | post-commit DB identity 复验拒绝;新 canonical DB 不获得错误 head | -| post-commit artifact 复验后 root marker 被替换 | exact receipt 与 accepted head 保留 | canonical history 不回滚 | 最终 root-owner identity 复验拒绝;不返回 usable baseline | -| owned quarantine/instance parent 被替换为 symlink | physical layout revalidation 拒绝 | canonical history 不变 | fail closed,不读取外部 control record、不移动到外部目录 | - -真实进程 crash harness 分别覆盖 receipt durable 后、SQLite authority 前,以及 SQLite COMMIT 后、 -post-verify 前的 `SIGKILL`;两者重启后都使用同一 receipt/identity 收敛。SQLite 事务内部的 -process-kill/rollback 证明继续由 Workspace Version Authority 测试拥有;M0 只增加跨 Git、root owner、 -SQLite 与 post-verify 的 composition crash proof。 - -## 7. 平台能力矩阵 - -| 能力 | Linux | macOS | Windows | -|---|---|---|---| -| pinned Git artifact revalidation | 支持 | 支持 | 支持 | -| receipt file fsync + atomic rename | 支持 | 支持 | 进程崩溃支持;不承诺断电级 parent fsync | -| SQLite atomic baseline acceptance | 支持 | 支持 | 支持 | -| raw Git path | 非 UTF-8 fail closed | 非 UTF-8 fail closed | Node/Git UTF-8 path policy | -| real-process crash test | 发布门槛 | 发布门槛 | 有限支持;依赖 Node/Git 的 SIGKILL 等价行为 | -| power-loss durability | 不承诺 | 不承诺 | 不承诺 | - -Windows 上不虚构 POSIX directory fsync 承诺。M0 保证进程崩溃后的可重试状态收敛;机器断电、磁盘缓存 -或文件系统损坏不在 v1 发布证明内。 - -## 8. 明确延期 - -- Desktop、CLI、runtime-host 与设置入口; -- 工具 cwd 切换、ignored dependency/scratch provisioning; -- mutation candidate ref、T1/T2 + workspace version 原子接受、conditional redo; -- continuation 绑定 workspace version 与自动 resume; -- orphan receipt/ref/object GC; -- whole-root import 后既有 managed worktree 的 relocation/adoption 或重新 materialize; -- 非空 legacy DB 的显式备份、授权、root-binding adoption 与完整重扫维护入口; -- replication、跨端同步、publish、undo 与 multi-agent merge。 - -这些能力必须分别由后续 PR 证明自己的 owner、原子边界、失败状态与回滚方式。M0 完成只代表系统拥有 -一个可验证、可重试、canonical 的 managed baseline,不代表 agent 已经在该 worktree 中执行工具。 - -## 9. 验收门槛 - -- 首次 open 与 exact retry 返回同一 receipt/head; -- receipt-after-write crash 与 SQLite rollback 都能 exact retry; -- SQLite COMMIT 后、post-verify 前真实进程 crash 返回 exact existing; -- accepted 后 receipt missing/tampered fail closed; -- post-commit Git 复验后 root marker 变化时,保留 canonical head 但拒绝返回 usable workspace; -- lease 伪 path、同 root 第二 DB、memory DB、DB symlink 与文件 identity 变化均在 admission 前拒绝; -- public Git service 无 receipt issuer,canonical policy/hash 与 deterministic IDs 有 literal/篡改测试; -- raw path digest 使用字节编码,非 UTF-8 source path fail closed; -- 同 owner 并发 open 收敛为一个 created、一个 existing;跨进程首先由 root-owner lock 仲裁; -- Git service、owner、workspace authority 的 focused real-Git/crash suites 全绿; -- `@maka/core` / `@maka/storage` build 与 `git diff --check` 通过; -- 不包含 Desktop/CLI/runtime-host 生产接线。 diff --git a/docs/architecture/runtime-managed-workspace-execution-admission-v1.zh-CN.md b/docs/architecture/runtime-managed-workspace-execution-admission-v1.zh-CN.md deleted file mode 100644 index 9cb4f52268..0000000000 --- a/docs/architecture/runtime-managed-workspace-execution-admission-v1.zh-CN.md +++ /dev/null @@ -1,179 +0,0 @@ - - -# Managed Workspace Execution Admission v1:M1.1 可撤销 scope 门 - -- 状态:M1.1 execution admission 与 M1.2 owner-bound worker / Runtime Host composition 已实现;尚未由 Desktop/CLI 默认启用 -- 更新日期:2026-08-27 -- 主要不变量:同一个 `ManagedWorkspaceOwner` 只能用其亲自签发的进程内 execution handle 创建 active - execution scope;一次 admission 只签发一个 scope,但同一 handle 允许多个只读 admission 并发。每次创建 - scope 前重新证明 exact SQLite workspace head 与 exact Git artifact,公共 API 永不发布 raw cwd,callback - 退出后 scope 立即失效 -- lifecycle / admission owner:`ManagedWorkspaceOwner` -- durable truth:SQLite immutable workspace RuntimeEvents -- artifact evidence:Maka-owned Git binding、baseline receipt、HEAD/tree 与 ownership lock - -## 1. 本切片交付什么 - -M0 已经能创建或 exact-adopt 一个 managed baseline,并把 Git receipt 与 SQLite canonical head 组合接受。 -但 raw `worktreePath` 是不可撤销字符串,调用者可以永久缓存并绕过 owner close、后续 revalidation 和未来 -workspace version 变化。因此 M1.1 使用两级进程内 capability:baseline 返回 owner-bound execution handle; -每次 admission callback 只得到 active execution scope,不得到 path。 - -```ts -const accepted = await owner.openManagedWorkspaceBaseline(store, identity); - -await owner.withManagedWorkspaceExecution(accepted.executionHandle, async (scope) => { - // M1.2 的 storage-internal worker bridge 才能消费 active scope。 -}); -``` - -`openManagedWorkspaceBaseline(...)` 不再公开 raw binding、receipt 或 `worktreePath`。handle 的内部证据保存在 -未从 package root 导出的模块中,并使用 `WeakMap` 与 owner token 绑定;复制相同字段或使用另一个 owner 的 -handle 都不能获得执行权限。scope 同样由 `WeakMap` 保存真实状态,callback 退出后即使被闭包保留也只能得到 -typed `ManagedWorkspaceExecutionAuthorityError`,其稳定 code 为 -`managed_workspace_execution_scope_expired`;伪造 scope 的 code 为 -`managed_workspace_execution_scope_invalid`。 - -M1.1 没有接入 Desktop、CLI 或 ToolRuntime,只建立 host 后续接线必须消费的唯一准入 API。M1.2 已把该 -authority 接入 Runtime Host 的只读 worker 与 lifecycle composition,但仍未由 Desktop/CLI 默认启用。 - -## 2. Owner、原子性边界、失败状态与回滚 - -| 项目 | 决策 | -|---|---| -| execution admission owner | 只有签发 handle 的同一个 `ManagedWorkspaceOwner` 可以消费它 | -| durable authority | handle/scope 都不是 durable fact;canonical head 通过注册时捕获的 storage-internal reader 读取,不调用可被 caller shadow 的 public method | -| 原子性边界 | 不虚构 Git 与 SQLite 之外的新事务;在 owner/root lease residency 内先完成 exact receipt/binding/Git verification,再以 immutable SQLite workspace head 作为 scope 签发前最后一次 durable reread;随后执行 DB pathname/inode guard 和纯内存 identity compare,不再运行慢速 Git 命令 | -| 执行期间 | 每个 callback 分别计入 owner active operation;同一 handle 可并发多个 `workspaceEffect: none` scope;`close()` 等全部 scope drain,新 admission 在 closing 后被拒绝 | -| invalid handle | `managed_workspace_execution_handle_invalid`;不签发 scope | -| foreign / expired scope | `managed_workspace_execution_scope_invalid` / `managed_workspace_execution_scope_expired`;bridge 不解析 cwd、不 dispatch worker | -| worker 缺失 | `managed_workspace_worker_unavailable`;不从 managed mode fallback 到 host-local I/O | -| mutation / unknown operation | `managed_workspace_operation_denied`;Write/Edit/Format/Bash/未知 operation 在 worker dispatch 前 fail closed | -| canonical head 漂移/缺失 | fail closed;旧 handle 不能自行选择新 head | -| artifact drift | 不签发 scope;所有 Git verification 阶段的 `managed_workspace_drifted` 统一 quarantine | -| mutation permission | scope 固定 `workspaceEffect: none`;M2 前没有 raw cwd consumer,Write/Edit/Bash/未知工具不得接入 | -| 回滚 | 无新增 schema 或 durable admission row;停止调用 API 即回滚能力,已接受的 baseline history 与 artifact 不变 | - -## 3. 每次执行的时序 - -```mermaid -sequenceDiagram - participant H as "Runtime host(后续消费者)" - participant O as "ManagedWorkspaceOwner" - participant S as "SQLite workspace authority" - participant G as "Git artifact owner" - participant T as "Scope callback" - - H->>O: withManagedWorkspaceExecution(handle, callback) - O->>O: 验证 handle 属于本 owner,进入 drain residency - O->>S: internal reader 读取 exact workspace/epoch canonical head - S-->>O: accepted WorkspaceHead - O->>O: exact compare frozen expected head + full cross-plane identity - O->>G: one-shot exact receipt/binding/HEAD/tree/lock verification - G-->>O: verified artifact - O->>S: final internal head reread - O->>O: runtime.sqlite pathname+inode guard + pure identity compare - O->>T: callback(active opaque scope) - T-->>O: result / error - O->>O: revoke scope;释放 active residency;允许 close 收敛 -``` - -这不是跨 SQLite/Git/filesystem 的共同事务。它关闭 cooperating Maka writer 的 proof-to-scope seam;任意外部 -进程仍可能在最后一次 filesystem observation 后制造 drift,后续 admission 会 fail closed。普通生产路径只执行 -图中的一次 exact Git verification,随后以 SQLite head 作为最后一次 durable reread;只有配置 production-shaped -crash failpoint 的测试路径才先执行一次 preliminary verification,以便在真实 proof 完成后 `SIGKILL`,随后仍必须 -再次执行 exact Git verification 并完成 final head reread 才能签发 scope。M1.2 的 worker/ -sandbox 建立实际 I/O 隔离,M2 才负责 mutating tool 产生 successor candidate 并接受新 workspace version。 - -## 4. Provisioning 与实际可用性边界 - -本切片 scope 的 provisioning 固定为 `canonical_tree_only_v1`,且 `workspaceEffect` 固定为 `none`。底层 -artifact 只包含 accepted Git tree: - -- 不复制 source checkout 的 ignored/untracked 文件; -- 不复制 `.env`、credential、`node_modules` 或 build cache; -- 不自动运行安装命令; -- 不把 scratch/build output 偷偷写入 canonical baseline; -- 不从 managed profile 静默 fallback 到 attached checkout。 - -因此 M1.1 可用于验证纯 tracked-tree 读取和后续受控工具接线,但还不宣称一般开发任务已经可用。 -ignored dependency、secret 与 scratch overlay 必须作为独立 M1 provisioning 切片,写明数据来源、生命周期、 -泄露边界和清理方式后才能接入。 - -## 5. Crash、并发与外部 drift 矩阵 - -| 场景 | 必须结果 | -|---|---| -| baseline 已接受,签发 handle 前崩溃 | 重启后从 canonical head 与 receipt 重新签发新 handle | -| execution verification 中崩溃 | 没有 durable “half admission”;旧 handle 随进程消失 | -| crash-test preliminary verification 后 drift | final exact receipt/Git verification 检测并 quarantine;scope 不签发 | -| 同一 handle 的两个只读 callback 并发 | 两个 scope 可同时 active,彼此独立 revoke | -| 多个 callback 运行时 owner close | close 等全部 callback drain;不取消已 admission operation | -| closing 后新 execution 请求 | `managed_workspace_owner_closing` | -| forged / cross-owner handle | `managed_workspace_execution_handle_invalid` | -| callback 保存 scope 后再次使用 | scope 已 expired,internal consumer 必须拒绝 | -| callback 抛错 | scope 仍在 finally 中 revoke,owner residency 正常释放 | -| worker 在返回结果前失败 | worker error 结束本次 operation;callback 的 finally revoke scope,owner residency 被释放,后续 close 可收敛 | -| worker timeout / abort | production `FilesystemWorkerClient` 终止并等待 one-shot worker process tree 与 I/O drain 后才 reject;scope 在此之后 revoke | -| worker 已启动时 host process crash | M1.2 不承诺跨平台 parent-death kill;worker 仍受 read-only sandbox 与单次请求约束,不能留下 workspace mutation,且完成或超时后退出;新 host 必须重新 admission。Shell/Write/Edit 不得借此 seam 执行 | -| 用户直接编辑 Maka-owned worktree | 系统不能物理阻止;下一次 admission 检测 drift 并 fail closed/quarantine | -| 重启后恢复 | 新 root owner、新 managed owner、新 handle;不可恢复旧进程内 capability | - -production-shaped 测试使用真实 pinned Git、真实 SQLite、真实子进程和 `SIGKILL`,证明 execution artifact -verification 中断后,重启只能经完整 reopen/revalidate 获得新 scope authority;真实 Git 并发测试证明两个 -只读 scope 可以并行,且 `close()` 必须等待二者全部退出。 - -filesystem worker 是 one-shot request/response process。它的 `execute()` 合同要求 Promise 只有在该次操作及其 -拥有的 process lifecycle 已终止后才能 settle;storage owner 因而以这个 Promise 作为 execution residency, -不再增加一个无法约束真实进程的装饰性 lease。正常返回、worker error、timeout 与 abort 都必须先由现有 -`FilesystemWorkerClient` / process runner 收敛,再退出 callback 并 revoke scope。host 被 `SIGKILL` 时无法执行 -JavaScript finally,因此本切片的安全保证来自“只读 operation allowlist + read-only sandbox”,而不是虚构所有平台 -都具备 parent-death cleanup。需要 durable handle 的 ShellRun 继续留在后续独立阶段。 - -## 6. 平台能力矩阵 - -| 能力 | Linux | macOS | Windows | -|---|---|---|---| -| owner-bound opaque handle | 支持 | 支持 | 支持 | -| exact SQLite/Git/head/tree revalidation | 支持 | 支持 | 支持 | -| callback drain | 支持 | 支持 | 支持 | -| process crash 后重新准入 | 支持 | 支持 | 支持(进程级;不宣称断电 durability) | -| external drift quarantine | 支持 | 支持 | 有限支持,沿用 Git artifact owner 的 Windows 保证 | -| managed Read/Glob/Grep worker bridge | 需要可用的 Linux sandbox backend;缺失时 fail closed | 支持 seatbelt worker | 当前不支持;managed Host composition fail closed,不回退 host-local | -| runtime-host lifecycle composition | 支持 | 支持 | 支持生命周期与 typed profile,但 managed I/O 因 worker 不可用而拒绝 | -| power-loss durability | 不承诺 | 不承诺 | 不承诺 | - -## 7. 已交付边界与后续切片 - -1. M1.2(已实现):owner-bound storage worker bridge 消费 active scope 并在内部解析 cwd;公共 caller 仍不获得 path。 - managed profile 在 M2 前只允许 `workspaceEffect: none` 的 Read/Glob/Grep;Write/Edit/Format/Bash/未知工具在 - worker dispatch 前 fail closed。无 ownerToken 的 inspect API 仅保留在显式 test support 中;callback 内 - reentrant `owner.close()` 由 execution-context guard 拒绝。runtime-host 使用不可混淆的 attached/managed - typed profile;managed profile 只携带 opaque handle,绝不携带 attached cwd,也绝不 fallback。Host 先 drain - tool operations,再关闭 workspace composition/managed owner,最后由 kernel 关闭 root owner。只有显式提供 - verified Git runtime 且 sandbox filesystem worker 可用时才组合 managed owner;否则保持未启用或 fail closed。 -2. M1.3:显式 dependency/secret/scratch provisioning;首版若无法安全提供则保持 - `canonical_tree_only_v1`,不能 silent fallback。 -3. M2:mutation candidate capture/accept;T1 前冻结 profile/base version,SQLite 原子接受 tool outcome - 与 successor workspace version。 - -M1.2 提供真实 runtime-host composition seam,但本切片不修改 Desktop/CLI 默认配置,因此不默认开启 managed -execution,也不改变 attached mode。ASF Desktop 已停止分发 bundled Git;在兼容许可证的 verified runtime -接入前,managed execution 必须在 admission/T1 前保持不可用,且不得回退系统 Git。 diff --git a/docs/architecture/runtime-managed-workspace-owner-v1.zh-CN.md b/docs/architecture/runtime-managed-workspace-owner-v1.zh-CN.md deleted file mode 100644 index 5502e57085..0000000000 --- a/docs/architecture/runtime-managed-workspace-owner-v1.zh-CN.md +++ /dev/null @@ -1,136 +0,0 @@ - - -# Managed Workspace Owner v1:M0 生命周期门 - -- 状态:已合并;M1 execution admission 继续复用本 owner 的 lifecycle/drain 门 -- 更新日期:2026-08-04 -- 主要不变量:一个 authenticated interactive storage-root owner 在其生命周期内至多发布一个 - managed workspace owner;已经 admission 的 workspace 操作必须在关闭前 drain -- artifact owner:`GitWorkspaceService` -- lifecycle owner:`ManagedWorkspaceOwner` -- canonical workspace history:仍由 Workspace Version Authority 拥有;M0 composition 只允许 owner 的 - `openManagedWorkspaceBaseline(...)` 通过 storage-internal writer 写入 baseline RuntimeEvents - -## 1. 为什么需要独立 owner - -`GitWorkspaceService` 能创建、验证、repair 与 quarantine Maka-owned Git artifacts,但它是一个 -operation-scoped service;仅有它还不能回答: - -- 哪一个 host 有权在当前 storage root 上驱动这些操作; -- 初始化进行中或失败时,是否可能发布半个可用 owner; -- shutdown 与进行中的 Git 操作谁先完成; -- Desktop、CLI 与 runtime-host 是否可能各自绕过同一生命周期门。 - -本切片把现有 authenticated `InteractiveRootOwner` 作为上层 lease authority。它不增加第二个 OS -owner lock,也不通过路径自行证明 ownership。 - -## 2. Owner、边界、失败状态与回滚 - -| 项目 | 决策 | -|---|---| -| 唯一 owner | 一个真实 `InteractiveRootOwner` 对象只能组合一个 `ManagedWorkspaceOwner` | -| 初始化边界 | pinned Git digest 验证与 storage-root authority probe 全部运行在 root write lease 内 | -| operation admission | 仅 `ready` 可 admission;每项操作同时持有 managed-owner residency 与 root lease operation | -| shutdown | `ready -> closing -> closed`;`closing` 拒绝新操作并等待已 admission 操作 drain | -| 初始化失败 | 返回 `managed_workspace_owner_unavailable`,释放未发布 claim,允许同一 root owner 修正后重试 | -| 重复组合 | 返回 `managed_workspace_owner_conflict` | -| drift | 不返回 drifted cwd;receipt/artifact 复验发现 drift 时 fail closed;复验后 reopen 竞态发现 drift 时 durable quarantine | -| 回滚 | 不接 Desktop/CLI/runtime-host,不改变 attached mode;可删除本 owner 而不改变 Git artifacts 或 RuntimeEvents | - -owner 不关闭外层 `InteractiveRootOwner`。Runtime Host 仍拥有 root owner 的最终关闭顺序;managed owner -必须先关闭。反过来,如果 root owner 已开始关闭,lease revalidation 会阻止新的 managed operation。 - -## 3. 公开状态机 - -```mermaid -stateDiagram-v2 - [*] --> opening - opening --> ready: pinned Git + root lease probe 成功 - opening --> failed: 初始化失败,释放 unpublished claim - ready --> closing: close() - closing --> closing: 拒绝新 operation,等待 residency drain - closing --> closed: active operations = 0 - closed --> closed: repeated close() -``` - -`opening` 与 `failed` 不作为已发布 owner 的可见状态。factory 只有在初始化完成并再次确认 root owner -仍然有效后才返回 `ready` owner。 - -## 4. Workspace gate - -owner 的 public surface 开放两个连续、不可绕过的 workspace admission 操作: - -1. `openManagedWorkspaceBaseline(store, identity)` 从 eligible clean source 创建/exact-adopt artifact, - 持久化并复验 receipt,再由 storage-internal writer 接受 canonical baseline,返回 owner-bound execution handle; -2. `withManagedWorkspaceExecution(handle, callback)` 在每次执行前重新证明 canonical head、receipt、HEAD/tree、 - ownership 与 root identity,只在 drain-managed callback 中签发可撤销 opaque scope;raw cwd 不进入 - public API。 - -artifact-only create/open 和 `GitWorkspaceService` factory 不从 package root 导出。调用者不能在 SQLite -acceptance 前取得 `worktreePath` 或裸 `ManagedWorkspaceBinding`。入口返回前必须验证 worktree、index、 -HEAD、tree、ownership lock、canonical `runtime.sqlite` pathname/inode、durable receipt,以及最终时刻的 -`InteractiveRootOwner`/root marker identity;任何失败都不能把 cwd 交给工具。SQLite 已提交而最终 owner -复验失败时,canonical history 保留,但本次调用不得发布 usable workspace。 - -本切片不扫描目录来猜测 workspace identity。Baseline Open Bundle 通过 Git artifact owner 的 durable -receipt 与 canonical workspace authority 绑定 exact identity;未接受 Git artifact 属于 orphan GC 范畴。 - -M1 execution admission 的详细合同见 -[Managed Workspace Execution Admission v1](./runtime-managed-workspace-execution-admission-v1.zh-CN.md)。 - -## 5. Crash 与并发证明 - -| 场景 | 必须结果 | -|---|---| -| 同一 root owner 两次 open | 一个 ready;另一个 owner conflict | -| pinned Git 初始化失败 | 不发布 owner;修正 digest 后可重试 | -| operation admission 后 close | close 等待 operation;新 operation 被拒绝 | -| root owner 同时 close | root close 与 managed close 都等待同一 lease-bound operation | -| external drift 后 reopen | receipt/artifact 复验 fail closed;若发生在复验与 reopen 之间则 durable quarantine | -| post-commit artifact 复验后 root marker 被替换 | admission 时捕获的 lease identity guard 最终复验并拒绝;保留 canonical head,不发布 cwd;owner closing 只阻止新 admission,不误杀正在 drain 的操作 | -| repeated close | exact no-op,不重复释放外层 root owner | - -Git artifact create/quarantine 的进程崩溃矩阵继续由 `GitWorkspaceService` 负责;本 owner 不复制第二套 -repair 状态机。Baseline Open Bundle 将补充“startup 时先验证 canonical receipt,再按 exact binding -reopen/repair,最后才允许 baseline authority read”的组合顺序。 - -## 6. 平台能力矩阵 - -| 能力 | Linux | macOS | Windows | -|---|---|---|---| -| owner uniqueness / lifecycle | 支持 | 支持 | 支持 | -| root lease-bound operation drain | 支持 | 支持 | 支持 | -| pinned Git initialization | 支持 | 支持 | 支持 | -| external drift quarantine | 支持 | 支持 | 有限支持,沿用 Git service 的 Windows 承诺 | -| power-loss durability | 不承诺 | 不承诺 | 不承诺 | - -## 7. 明确延期 - -- Desktop、CLI、runtime-host 接线与 managed-mode 设置;execution admission API 已建立,但尚无生产 host consumer; -- filesystem worker、mutation coordinator 与工具 cwd 切换; -- candidate refs、mutation repair、GC、replication outbox; -- ignored dependencies、build/test environment provisioning; -- Durable Write、workspace-bound continuation 与自动 resume。 -- whole-root import 后既有 linked worktree 的 relocation/adoption; -- 非空 legacy database 的显式备份与 root-binding migration 工具。 - -这些能力不能借 owner lifecycle PR 顺手接入。Baseline Open Bundle 已作为本 owner 的第一个 -canonical-fact consumer 完成组合,证明 Git baseline 与 RuntimeEvent baseline 不会只成功一半;后续 -M1 execution admission 已从该 bundle 返回的 owner-bound handle 进入,未重新开放 artifact-only 旁路。 diff --git a/docs/architecture/runtime-resume-architecture.md b/docs/architecture/runtime-resume-architecture.md index 3d5754d026..32cede1e3c 100644 --- a/docs/architecture/runtime-resume-architecture.md +++ b/docs/architecture/runtime-resume-architecture.md @@ -7,7 +7,7 @@ counterpart: ./runtime-resume-architecture.zh-CN.md implementation_status: phase_0_2_and_phase_3a_authority_current document_status: current translation_status: synced -last_verified: 2026-07-28 +last_verified: 2026-08-29 owners: - maka-backend --- @@ -777,18 +777,6 @@ Layer responsibilities: The Runtime host uses strict recovery stores. It does not silently turn an unreadable ledger into best-effort fallback before admitting new writes. -### Managed workspace execution admission and read bridge (M1.1–M1.2) - -The workspace plane now has storage-owned execution admission and an owner-bound, read-only Runtime Host bridge: - -- baseline open returns an opaque handle bound to its `ManagedWorkspaceOwner`, never a raw cwd; -- every admission reproves storage-root identity, the exact Git receipt/binding/HEAD/tree/ownership, and the exact SQLite canonical head; the ordinary path performs its slow Git verification first, then makes the immutable SQLite head the final durable reread before the DB identity guard and pure in-memory comparisons; -- one admission issues one callback-scoped opaque scope with `workspaceEffect: none`; the same handle may have multiple concurrent read-only scopes; -- `close()` rejects new admissions and drains every active scope; a scope expires when its callback exits, with typed `managed_workspace_execution_scope_invalid` and `managed_workspace_execution_scope_expired` codes for forged and retained scopes; -- the crash harness may enable a preliminary-verification failpoint, but that test path must still pass the final verification before any scope is issued. - -M1.2 wires that authority into the owner-bound storage worker bridge and Runtime Host lifecycle composition. It limits managed execution to Read/Glob/Grep, demotes unchecked scope inspection to explicit test support, rejects reentrant owner close, keeps attached and managed profiles structurally distinct, and orders shutdown as tool operations → managed owner → root owner. Desktop and CLI do not enable it by default; Write/Edit/Format/Bash/unknown tools fail closed, and managed mode never silently falls back to the attached checkout. See [Managed Workspace Execution Admission v1](./runtime-managed-workspace-execution-admission-v1.zh-CN.md) for the detailed contract. - ### Eval Eval does not resume or reconstruct Runtime execution. It asks Runtime Host to execute a Maka subject. Infrastructure replacement appends a new attempt to the same experiment cell; Runtime continuation stays inside that subject and is not observable as a repetition or retry. diff --git a/docs/architecture/runtime-resume-architecture.zh-CN.md b/docs/architecture/runtime-resume-architecture.zh-CN.md index 4e8d97f03e..c0dbccaf6f 100644 --- a/docs/architecture/runtime-resume-architecture.zh-CN.md +++ b/docs/architecture/runtime-resume-architecture.zh-CN.md @@ -7,7 +7,7 @@ counterpart: ./runtime-resume-architecture.md implementation_status: phase_0_2_and_phase_3a_authority_current document_status: current translation_status: synced -last_verified: 2026-07-28 +last_verified: 2026-08-29 owners: - maka-backend --- @@ -803,18 +803,6 @@ flowchart LR Runtime host 使用 strict recovery stores 执行 startup repair。严格模式不会把 unreadable ledger 吞成 best-effort fallback,适合服务端 composition 在接收新写入前建立清楚的恢复边界。 -### Managed workspace execution admission 与只读桥(M1.1–M1.2) - -Resume 的 workspace plane 已经有 storage-owned execution admission,并已接入 owner-bound、只读的 Runtime Host bridge: - -- baseline open 只返回绑定 `ManagedWorkspaceOwner` 的 opaque handle,不公开 raw cwd; -- 每次 admission 都重新证明 storage-root identity、exact SQLite canonical head 和 exact Git receipt/binding/HEAD/tree/ownership;普通路径先完成一次慢速 Git verification,再以 immutable SQLite head 作为最后一次 durable reread,随后只执行 DB identity guard 与纯内存比较; -- 一次 admission 签发一个 callback-scoped、`workspaceEffect: none` 的 opaque scope;同一 handle 可以并发多个只读 scope; -- `close()` 拒绝新 admission,并等待所有 active scope drain;callback 退出后 scope 立即失效,伪造与过期分别返回 typed `managed_workspace_execution_scope_invalid` / `managed_workspace_execution_scope_expired`; -- crash harness 可配置 preliminary-verification failpoint,但该测试路径仍须再次通过 final verification 才能签发 scope。 - -M1.2 已完成 owner-bound storage worker bridge 与 Runtime Host lifecycle composition:只允许 Read/Glob/Grep,把无 owner 校验的 inspect 降为显式 test support,拒绝 active callback 内 reentrant `owner.close()`,用不可混淆的 attached/managed typed profile 阻止 silent fallback,并固定 tool operations → managed owner → root owner 的关闭顺序。Desktop/CLI 仍不默认开启 managed execution;Write/Edit/Format/Bash/未知工具必须 fail closed。详细合同见 [Managed Workspace Execution Admission v1](./runtime-managed-workspace-execution-admission-v1.zh-CN.md)。 - ### Eval Eval 不恢复或重建 Runtime execution,只请求 Runtime Host 执行 Maka subject。基础设施替换会向同一个 experiment cell 追加新 attempt;Runtime continuation 留在该 subject 内部,不成为 repetition 或 retry。 diff --git a/docs/architecture/runtime-resume-extraction-ledger.zh-CN.md b/docs/architecture/runtime-resume-extraction-ledger.zh-CN.md index c0da7e8c3e..de04bc6a98 100644 --- a/docs/architecture/runtime-resume-extraction-ledger.zh-CN.md +++ b/docs/architecture/runtime-resume-extraction-ledger.zh-CN.md @@ -468,7 +468,7 @@ Schema 9 进一步要求该 authority stream 在首次写入前绑定 authentica - SQLite/JSONL/tool/recovery/continuation/copy writer bypass; - 两进程 exact/conflicting baseline arbitration; - 两进程 schema 6/7→8 migration; -- DB rootId exact binding、跨 root 单文件复制拒绝、whole-root storage authority import/adopt 与 unbound operational data fail closed;既有 managed worktree relocation 和 legacy DB binding 仍是独立维护协议,不由 M0 baseline open 暗中完成; +- DB rootId exact binding、跨 root 单文件复制拒绝、whole-root storage authority import/adopt 与 unbound operational data fail closed;legacy DB binding 仍是独立维护协议;旧 baseline-open producer 已删除,后续 workspace artifact admission 由 Gitoxide data plane 单独建立; - Linux/macOS process-kill crash harness; - workspace fact 不进入 UI/provider message projection。 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 aecb8def28..86b726c66e 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 @@ -19,14 +19,14 @@ # Runtime Resume Phase 3–4 实施路线 -> 路线更新(2026-08-02):workspace 主线已从“通用 checkpoint 抽象优先”切换为 -> **Git-native managed workspace**。本文保留 Recovery/Continuation 已落地协议与旧路线的历史论证; -> 新实现不再以通用 per-file checkpoint、native manifest 或 CAS object store 为前置。 -> 当前 M0 的 baseline-only 权威合同见 +> 路线更新(2026-08-29):旧 Git executable managed-workspace path 已删除,后续 workspace 主线从 +> **Gitoxide data plane** 重新建立 production admission。本文保留 Recovery/Continuation 已落地协议与旧路线的历史论证; +> 新实现不再以通用 per-file checkpoint、native manifest、CAS object store 或旧 Git CLI owner 为前置。 +> 已保留的 baseline authority 合同见 > [Workspace Version Authority v1](./runtime-workspace-version-authority-v1.zh-CN.md)。 - 状态:Implementation tracked -- 更新日期:2026-08-02 +- 更新日期:2026-08-29 - 事实权威:immutable RuntimeEvents - 主要平台:Linux、macOS;Windows 有限支持 - 拆分审计:`runtime-resume-extraction-ledger.zh-CN.md` @@ -47,7 +47,7 @@ RuntimeEvent 是语义事实的唯一权威,但不能替代执行所有权的 | recovery semantics | immutable RuntimeEvents | call、dispatch、outcome、observation、decision | | execution ownership | admission/claim CAS | 一个 source boundary 只能有一个执行者 | | projection | SQLite tables | 可删除、可从事件重建 | -| workspace artifact | managed Git artifact owner(强模式) | 保存/验证 workspace 状态,不能单独授权 continuation | +| workspace artifact | 当前无生产 writer;后续 Gitoxide data plane | 保存/验证 workspace 状态,不能单独授权 continuation | ## 2. Phase 3A:工具恢复 @@ -539,95 +539,23 @@ Attached 模式继续服务现有用户目录与兼容场景,但不冒充强 w - 旧的 generic checkpoint provider、per-file carrier 与 observe-only user-repository Git 路线只保留为 attached/legacy research,不再是 `managed_worktree` 的实现前置。 -### Git-native managed workspace +### Gitoxide managed workspace -强模式以 Maka-owned repository/worktree 为 artifact carrier。Git 证明文件世界,SQLite immutable -RuntimeEvents 决定 Maka 是否接受某个文件版本;Git commit、ref、receipt 与 projection 都不是第二套真相。 -内置并校验的 Git CLI 是该模式的运行能力,不要求用户自行安装 Git,也不修改用户 repository 的 index/ref。 +旧 Git executable-backed managed workspace 从未获得生产 baseline/profile 调用方,其 service、owner、 +receipt、worktree materialization 与 Runtime Host admission path 已删除。schema 9 RuntimeEvents、workspace +projection reader/rebuild 与升级合同继续保留;它们是历史事实 authority,不是可执行 Git path。 -## 4. Git-native managed workspace 演进 +## 4. Gitoxide managed workspace 后续边界 -### M0 — Baseline admission(已合并) +后续强模式必须从 Gitoxide data plane 建立新的 production composition,并分别证明: -M0 只证明一条接受链: +- packaged helper 的 trust root、artifact identity 与 bounded invocation; +- repository admission 与 schema 9 workspace authority 的单一写入边界; +- mutation T1/T2、workspace version 与 tool outcome 的原子接受; +- workspace-bound continuation、restore、rebaseline 与 product workflow。 -```text -authenticated storage-root owner -→ create/reopen Maka-owned managed Git workspace -→ durable exact receipt -→ SQLite atomic epoch + baseline acceptance -→ post-commit DB/Git/root-owner revalidation -→ publish usable baseline -``` - -M0 不接 Desktop、CLI、runtime host 或工具执行。Whole-root import 当前只恢复 storage-root identity 与 -SQLite ownership;既有 linked worktree 的路径迁移需要独立 relocation/rematerialization 协议。非空且未绑定的 -legacy DB 继续 fail closed,未来由显式、备份后、可审计的 root-binding maintenance flow 处理,不能藏在首次 -baseline open 中。 - -### M1 — Execution admission 与环境可用性 - -只允许 host 从 M0 成功返回的 accepted handle 取得工具 cwd,并证明: - -- managed owner 与 runtime host 的启动、drain、关闭顺序; -- 每次执行前 exact instance/HEAD/tree/ownership revalidation; -- ignored dependencies、`.env` 与 build scratch 使用明确 provisioning/overlay policy,不污染 canonical tree; -- 外部修改 Maka-owned worktree 时检测 drift、fail closed 并 quarantine; -- attached 与 managed execution profile 在类型和配置上不可静默互相 fallback。 - -M1 拆成独立不变量,避免一次 PR 同时跨越 host lifecycle、runtime protocol 与 platform I/O: - -1. **M1.1 execution scope admission(已合并)**:M0 只返回 owner-bound opaque handle;同一 owner 每次 - execution 都在 drain residency 内重新证明 canonical head、Git receipt、HEAD/tree/ownership 与 root - identity,最后只签发 callback 生命周期内有效的 opaque scope,不公开 raw cwd。provisioning 固定为 - `canonical_tree_only_v1`,`workspaceEffect` 固定为 `none`。同一 handle 可以并发多个只读 admission, - `close()` 必须等待全部 active scope drain;普通 production admission 先做一次 exact Git verification,再以 - SQLite head 作为最后一次 durable reread;preliminary verification 仅存在于配置 crash failpoint 的 - production-shaped 测试路径;过期或伪造 scope 通过 typed - `ManagedWorkspaceExecutionAuthorityError` 的稳定 code fail closed; -2. **M1.2 owner-bound worker 与 runtime-host composition(已合并)**:storage-internal bridge 只能用同 owner 的 active - scope 解析 cwd,M2 前只允许 Read/Glob/Grep;建立不可混淆的 managed/attached typed profile 与 - startup/drain/shutdown 顺序,关闭顺序固定为 tool operations → managed owner → root owner; -3. **M1.3 environment provisioning**:单独设计 ignored dependency、secret 与 scratch overlay。M1.1 不复制 - `.env`、`node_modules` 或 build output,也不以 attached checkout silent fallback 掩盖能力缺失。 - -M1.1 合同见 -[Managed Workspace Execution Admission v1](./runtime-managed-workspace-execution-admission-v1.zh-CN.md)。 - -### M2 — Mutation version acceptance - -每个 mutating tool 使用 candidate ref/version 协议: - -1. T1 前冻结 execution profile、base workspace version 与 mutation identity; -2. 工具只在 owned worktree 中执行; -3. Git artifact owner 捕获并验证 candidate commit/tree; -4. SQLite 在一个 authority transaction 中接受 tool outcome 与 successor workspace version; -5. artifact orphan 可回收;canonical fact 已接受而 artifact 缺失时 fail closed; -6. crash matrix 覆盖执行前后、candidate durable 前后、SQLite commit 前后与返回前复验。 - -这一步取代旧的“先做通用 checkpoint contract,再接 observe-only Git carrier”。不得同时维护两套 -managed workspace version writer。 - -### M3 — Workspace-bound continuation / resume - -Continuation boundary 同时绑定 immutable RuntimeEvent cursor 与 accepted workspace version/epoch: - -- planner 与 execution revalidation 读取同一 canonical boundary; -- startup reopen 必须重新证明 exact Git artifact、storage root 与 owner; -- operation effect 无法证明、artifact 漂移或 execution profile 不匹配时 park; -- 自动 resume 仍由独立用户设置控制,默认关闭;手动恢复与自动恢复不能共享一个含糊开关。 - -### M4 — Product workflows - -在 M0–M3 的事实与 owner 边界稳定后,再分别交付: - -- isolated restore,不覆盖用户当前目录; -- explicit rebaseline,创建新 epoch 并要求模型重新读取; -- publish/merge/undo、retention 与 orphan GC; -- whole-root managed-workspace relocation; -- replication/cross-device 与 multi-agent merge。 - -每项都必须是独立 PR;不能借“恢复产品化”一次跨越 schema、runtime protocol、host lifecycle 与 platform I/O。 +不能恢复旧 Git CLI、receipt、quarantine、write owner 或 `managed_worktree_v1` profile,也不能让 Gitoxide +foundation 在尚无 Desktop/CLI/T1 consumer 时冒充产品能力。 ## 5. 依赖顺序 @@ -635,25 +563,15 @@ Continuation boundary 同时绑定 immutable RuntimeEvent cursor 与 accepted wo PR A recovery persistence authority (merged) └─> PR B continuation correctness (merged) -Git-native workspace: -M0.1 Git artifact owner (merged) - + M0.2 workspace version authority (merged) - + M0.3 managed owner lifecycle (merged) - └─> M0.4 baseline open bundle (merged) - └─> M1.1 execution scope admission (merged) - └─> M1.2 runtime-host composition (merged) - └─> M1.3 explicit environment provisioning - └─> M2 mutation version acceptance - └─> M3 workspace-bound continuation - └─> M4 restore / rebaseline / publish / replication - -Independent maintenance gates before broad production enablement: - - legacy non-empty DB root-binding adoption - - whole-root managed-workspace relocation/rematerialization +Gitoxide foundation/data plane +└─> production artifact admission + └─> schema 9 workspace authority writer composition + └─> mutation acceptance + └─> workspace-bound continuation and product workflows ``` -旧 PR C–H 的论证可作为历史材料,但不再代表 `managed_worktree` 的施工依赖图;其中不具备生产消费者的 -generic checkpoint、observe-only carrier 与预设抽象不得迁入新主线。 +旧 PR C–H 与 Git-CLI managed-workspace 路线只作为历史论证;其中没有真实消费者的 checkpoint、carrier、 +receipt 与 owner 抽象不得迁入新主线。 ## 6. 工程门槛 diff --git a/docs/architecture/runtime-workspace-version-authority-v1.zh-CN.md b/docs/architecture/runtime-workspace-version-authority-v1.zh-CN.md index 5fd0d508e1..3757c4cfce 100644 --- a/docs/architecture/runtime-workspace-version-authority-v1.zh-CN.md +++ b/docs/architecture/runtime-workspace-version-authority-v1.zh-CN.md @@ -19,12 +19,11 @@ # Workspace Version Authority v1:Baseline 事实权威 -- 状态:authority foundation 已由 M0 Baseline Open Bundle 接入;仍待前置 PR 合并后从最新 `main` - 平铺并完成最终 CI -- 更新日期:2026-08-02 +- 状态:authority foundation 已合并;schema 9 reader/migration、RuntimeEvents、projection reader/rebuild 继续受支持,当前没有生产 baseline writer consumer +- 更新日期:2026-08-29 - 主要不变量:经专用 writer 提交的一个 workspace epoch,其 baseline canonical facts 与三个 SQLite projection 对外只能全可见或全不可见 - 事实权威:immutable RuntimeEvents -- artifact owner:后续 `GitWorkspaceService`;本切片不执行 Git 命令 +- artifact owner:后续 Gitoxide data plane;本切片不执行 Git 命令 - 主要发布证明平台:Linux、macOS;Windows 当前仅验证 SQLite 事务与多进程路径 ## 1. 本切片为什么存在 @@ -48,21 +47,20 @@ epoch opened RuntimeEvent 可删除、可重建、每次读取都要与 canonical facts 交叉验证的 projection。若维护操作或外部损坏只删除 projection,公开 reader 会 fail closed,直到显式 rebuild;这种损坏态不被伪装成合法的“全不可见”。 -本切片只接受 baseline,不接受 mutation。它不创建 internal repository、不创建 worktree、不调用工具, -也不改变 Desktop/CLI 行为。这种收缩是刻意的:一个 PR 只证明一个主要不变量。 +本节定义的 baseline transaction 只接受 baseline;独立的 successor/mutation authority extension 不属于这个 +历史切片。它不创建 internal repository、不创建 worktree、不调用工具,也不改变 Desktop/CLI 行为。 -authority slice 本身只证明事实合同、SQLite 原子写入与 projection 可重建性;M0 Baseline Open Bundle -在其上增加了生产 composition seam:`ManagedWorkspaceOwner` 持有未导出的 Git receipt capability, -先持久化并复验 exact receipt,再调用 storage-internal raw writer,SQLite COMMIT 后还会再次复验 Git -artifact。裸 OID、TypeScript brand、caller 自报的 `verified: true` 或 caller 提供的 policy hash 都不 -构成证据。 +authority slice 本身只证明事实合同、SQLite 原子写入与 projection 可重建性。旧 Git-CLI-shaped +Baseline Open composition 从未获得生产调用方,现已删除;后续生产 writer 必须由 Gitoxide data plane +建立新的窄 admission 边界。裸 OID、TypeScript brand、caller 自报的 `verified: true` 或 caller 提供的 +policy hash 都不构成证据。 ## 2. Owner、边界、失败状态与回滚 | 项目 | 决策 | |---|---| | 协议 owner | `@maka/core` 的 strict fact contract 与 pure scanner | -| 写入 owner | storage-internal WeakMap writer;不属于 package API,只能由 `ManagedWorkspaceOwner` composition seam 到达 | +| baseline 写入 owner | storage-internal WeakMap writer;不属于 package API,当前没有 production baseline producer;独立 successor/mutation writer 不由旧 owner 提供 | | 原子性边界 | 单个 `BEGIN IMMEDIATE ... COMMIT` SQLite transaction | | canonical source | store-owned authority stream 中的两条 immutable RuntimeEvents | | disposable state | `runtime_workspace_epochs`、`runtime_workspace_versions`、`runtime_workspace_heads` | @@ -192,45 +190,15 @@ Writer reservation 同时覆盖: storage 内部 writer 接受 typed baseline input,由 store 自己构造 RuntimeEvents。它不接受 caller 拼好的 event,因此 caller 没有机会夹带另一条 semantic lane。该 seam 所在模块不从 `@maka/storage` 导出; -公开 store 只暴露 capability、reader 与 projection rebuild。当前唯一生产入口由 -`ManagedWorkspaceOwner` 重新验证 durable Git receipt 后调用 raw seam;receipt issuer 与 raw writer -都不进入 package root exports。 - -## 6. Baseline Open Bundle 时序 - -```mermaid -sequenceDiagram - participant G as GitWorkspaceService - participant O as ManagedWorkspaceOwner - participant S as SqliteRuntimeStore - participant E as immutable RuntimeEvents - participant P as workspace projections - - G->>G: 验证 source repository、commit/tree、fixed materialization profile - G->>G: 导入并验证 internal baseline commit/tree - G->>O: verified durable receipt (internal capability) - O->>S: commitWorkspaceBaselineInternal(receipt-derived input) - S->>S: BEGIN IMMEDIATE - S->>S: 扫描全部 canonical workspace facts - alt exact bundle 已存在 - S-->>O: created=false - else identity 或 payload 冲突 - S-->>O: fail closed / rollback - else authority 为空 - S->>E: append epoch event (seq=1) - S->>E: append baseline event (seq=2) - S->>P: insert epoch - S->>P: insert version - S->>P: set head=baseline - S->>S: COMMIT - S-->>O: created=true - end -``` +公开 store 只暴露 capability、reader 与 projection rebuild。baseline raw writer 不进入 package root exports, +当前没有生产 composition 可以调用它。 + +## 6. Baseline writer 状态 -Git 验证与 tree-delta 计算故意不放进 SQLite transaction。Baseline Open Bundle 在调用 writer 前读取 -并重新验证 durable receipt,COMMIT 后再次复验;SQLite writer 负责冻结已验证 identity,并保证 -facts/projections 原子提交。该 composition 仍未接 Desktop、CLI、tool 或自动恢复入口。 -`treeDeltaDigest` 必须是 canonical empty-tree → baseline-tree delta 的摘要,不能由 caller 随意填写。 +`commitWorkspaceBaselineInternal` 继续作为 schema 9 authority 的 storage-internal 测试 seam,用于证明 +atomic bundle、exact retry、conflict、crash rollback 与 projection rebuild。旧 Git executable receipt → +writer composition 已删除;后续 Gitoxide producer 必须重新建立 artifact admission,不能复用或恢复旧 +owner、receipt 或 worktree materialization path。 ## 7. Schema 7–8 与 projection @@ -307,9 +275,9 @@ SQLite read transaction/snapshot;否则并发 writer 可能让读者拼接两 本表只描述 authority persistence,不能推导 managed worktree 已跨平台可用。 -## 10. 明确延期 +## 10. Baseline slice 之外的扩展 -以下能力不属于 M0 baseline authority: +以下能力不由本文的 baseline authority slice 定义;它们可以由独立 authority extension 提供: - verified Git runtime 探测、source eligibility 与 internal bare repository; - managed worktree 创建、owner lifecycle、quarantine 与 repair; @@ -321,17 +289,13 @@ SQLite read transaction/snapshot;否则并发 writer 可能让读者拼接两 - continuation boundary 绑定 workspace version; - Desktop/CLI 设置、默认启用或自动恢复。 -尤其不能在本协议中提前定义无生产消费者的 mutation 抽象。Mutation fact 必须与真实 Durable Write -的 T1/T2、tool outcome 引用、session retention 和 head CAS 一起冻结。 - -## 11. 后续 M0 交付顺序 +本文不定义 mutation。Successor/mutation fact 必须由其独立 authority 与真实 Durable Write 的 T1/T2、 +tool outcome 引用、session retention 和 head CAS 一起冻结。 -### Slice 2:Managed Workspace Owner(已完成) +## 11. 后续生产接线 -只证明:Maka 能用一个显式注入且经过校验的 Git runtime 创建并独占 private internal -repository/worktree lifecycle;外部 drift 被检测后 quarantine。ASF Desktop 不再提供该 runtime,后续实现将 -验证 Apache-2.0/MIT 的 gitoxide backend。旧 Git-CLI-shaped service 仅作为历史/测试实现,不能成为 -新生产 backend 的 identity owner。 +旧 Git-CLI-shaped service、owner、receipt 与 worktree materialization path 已删除,不能成为新生产 +backend 的 identity owner。 当前 Gitoxide 验证栈已拆成三个窄、可独立合并的 enabling-infrastructure 层:isolated short-lived Rust helper、exact helper artifact → opaque invocation capability、bounded invocation → opaque repository admission capability。分别见: @@ -342,29 +306,6 @@ opaque invocation capability、bounded invocation → opaque repository admissio - [`gitoxide-repository-admission-capability-v1.zh-CN.md`](./gitoxide-repository-admission-capability-v1.zh-CN.md) 这些基础设施尚未建立 signed packaged-release trust root,也没有 Desktop/CLI/T1 消费者,因此不能据此 -恢复 managed mode。后续生产接线仍需先拍板 ignored dependencies/scratch、identity marker、 -symlink/LFS/submodule/case/filemode 平台政策。 - -### Slice 3:Baseline Open Bundle(实现中) - -只证明:从 eligible clean source HEAD 导入 Maka-owned objects、materialize baseline、验证 tree/cleanliness, -然后调用本切片的 atomic authority writer。Git artifact 在失败时可作为 orphan GC;只有 RuntimeEvent -接受后的 commit 才是 canonical workspace version。 - -完成这两片以后,系统才拥有一个可供工具使用的 managed baseline。真正的 Durable Write 与 -workspace-version/T2 原子接受仍是下一组独立 PR,不能在 host 接线中顺手补入。 - -Baseline Open 的详细 receipt 合同、组合 owner、crash matrix 与平台承诺见 -[`runtime-managed-workspace-baseline-open-v1.zh-CN.md`](./runtime-managed-workspace-baseline-open-v1.zh-CN.md)。 - -## 12. 当前验收记录 - -本分支完成的短门禁: - -- core/storage 定向集合:93/93; -- 其中 workspace authority:3 个 pure contract + 12 个 persistence + 4 个新增多进程/升级场景; -- runtime read-model 定向集合:59/59; -- `@maka/storage` 与 `@maka/runtime` build 通过; -- `git diff --check` 通过。 - -未运行全仓测试;Windows 本地没有执行被 suite 明确跳过的 Unix process-kill crash proof。 +恢复 managed mode。后续 production writer 必须从真实 Gitoxide artifact admission 出发,并继续复用本 +文档定义的 schema 9 RuntimeEvents 与 projection authority;接线前仍需先拍板 ignored dependencies/scratch、 +identity marker、symlink/LFS/submodule/case/filemode 平台政策。 diff --git a/docs/windows-support.md b/docs/windows-support.md index 5d72aae23f..07865a1436 100644 --- a/docs/windows-support.md +++ b/docs/windows-support.md @@ -185,9 +185,8 @@ parent-directory update through volatile device caches. | Surface | Current Windows guarantee | Boundary | |---|---|---| -| SQLite operational state | Databases use WAL journaling with `synchronous=FULL`. Real-process failpoint tests verify that committed runtime, continuation, memory, and managed-workspace facts survive owner death while incomplete transactions do not become authoritative. | The guarantee is the SQLite and Windows filesystem contract on supported local storage. Maka does not claim protection from storage hardware or drivers that acknowledge flushes before data is stable. | +| SQLite operational state | Databases use WAL journaling with `synchronous=FULL`. Real-process failpoint tests verify that committed runtime, continuation, and memory facts survive owner death while incomplete transactions do not become authoritative. Workspace RuntimeEvents and projections retain their schema 9 reader/rebuild contract. | The guarantee is the SQLite and Windows filesystem contract on supported local storage. Maka does not claim protection from storage hardware or drivers that acknowledge flushes before data is stable. | | Artifact payload publication | Payload bytes are written to a same-directory staging file and the file is synchronized before publication. Recovery reconciles staged payloads, metadata, deletes, and owner death without accepting uncommitted residue. | Windows evidence covers forced process termination and restart. It does not establish a separately forced parent-directory entry after sudden system power loss. | -| Marker and managed-workspace control files | Writers synchronize temporary file contents before same-directory `link` or `rename` publication. Readers validate file and root identity and fail closed on unsupported replacement. | Node can synchronize the file on Windows, but the repository's directory synchronization barrier is intentionally unavailable on Windows. | | Root and open-database replacement | Live owners retain exclusive authority and cleanup closes stores and leases before deleting their roots. | Windows does not permit the POSIX test technique of renaming or unlinking an open SQLite database or replacing a directory that contains open files. Those tests remain classified as platform contracts rather than portable recovery gates. | On POSIX, stable-storage paths synchronize changed parent directories after publishing or removing a @@ -206,7 +205,6 @@ Environment: Windows 11 x64, Node.js 22.23.1, npm 11, Git for Windows. |---|---:|---| | Workspace build | PASS | All root `build:test` workspace builds completed. | | Repository script tests | 110 pass, 0 fail, 1 skip | The skip is a real macOS `pgrep` probe. | -| Managed workspace baseline tests | 17 pass, 0 fail, 5 skip | Passed after enabling Git for Windows long paths. | | Storage suite | 514 pass, 100 fail, 40 skip | Failures are dominated by `EBUSY` cleanup while SQLite files remain open. | | Complete repository test plan | TIMEOUT | `npm test` did not exit within 10 minutes and left the workspace test runner alive. | diff --git a/docs/windows-test-inventory.md b/docs/windows-test-inventory.md index 53ffc8563a..c218eb71b2 100644 --- a/docs/windows-test-inventory.md +++ b/docs/windows-test-inventory.md @@ -17,9 +17,9 @@ Locations intentionally omit line numbers so unrelated edits do not invalidate t |---|---:| | windows-backend-gap | 25 | | portable-candidate | 10 | -| platform-contract | 35 | +| platform-contract | 31 | -Total Windows-excluded declarations: **70** +Total Windows-excluded declarations: **66** ## Inventory @@ -74,10 +74,6 @@ Total Windows-excluded declarations: **70** | platform-contract | `packages/runtime/src/__tests__/shell-run-manager.test.ts` keeps SIGTERM final output and escalates an ignored SIGTERM without leaking slots | `process.platform === 'win32' ? 'Windows tree termination has no graceful SIGTERM phase' : false` | | portable-candidate | `packages/storage/src/__tests__/managed-dependency-environment.test.ts` accepts a POSIX package bin symlink whose target remains inside the dependency root | `process.platform === 'win32'` | | portable-candidate | `packages/storage/src/__tests__/managed-dependency-environment.test.ts` isolates published POSIX content from a producer-retained writable handle | `process.platform === 'win32'` | -| platform-contract | `packages/storage/src/__tests__/managed-workspace-baseline.test.ts` rejects an authority database whose file identity changes after registration | `process.platform === 'win32' ? 'Windows cannot rename an open SQLite database to replace its file identity' : false` | -| platform-contract | `packages/storage/src/__tests__/managed-workspace-baseline.test.ts` does not return an accepted baseline when runtime.sqlite is replaced after the initial root check | `process.platform === 'win32' ? 'Windows cannot rename an open SQLite database during the verification race' : false` | -| platform-contract | `packages/storage/src/__tests__/managed-workspace-baseline.test.ts` rejects a source tree containing a non-UTF-8 Git path | `process.platform === 'win32'` | -| platform-contract | `packages/storage/src/__tests__/managed-workspace-owner.test.ts` rejects execution when runtime.sqlite detaches from its canonical path after verification | `process.platform === 'win32' ? 'Open SQLite files cannot be renamed reliably on Windows' : false` | | platform-contract | `packages/storage/src/__tests__/operational-state-store.test.ts` does not classify a SQLite write failure as a migration blocker | `process.platform === 'win32' ? 'POSIX permissions are required to make the SQLite database read-only' : false` | | platform-contract | `packages/storage/src/__tests__/pet-pack-store.test.ts` detects sprite sheets redirected outside the installed pack | `process.platform === 'win32' ? 'Windows file-symlink permissions are not guaranteed in CI' : false` | | platform-contract | `packages/storage/src/__tests__/root-authority.test.ts` preserves unexpected marker I/O failures at the public authority boundary | `process.platform === 'win32' ? 'POSIX permissions are required to make the marker unreadable' : typeof process.getuid === 'function' && process.getuid() === 0` | diff --git a/packages/runtime-host/src/__tests__/execution-composition.test.ts b/packages/runtime-host/src/__tests__/execution-composition.test.ts index ad3b9db508..12c83148e5 100644 --- a/packages/runtime-host/src/__tests__/execution-composition.test.ts +++ b/packages/runtime-host/src/__tests__/execution-composition.test.ts @@ -19,7 +19,6 @@ import assert from 'node:assert/strict'; import { parseNoRealConnectionError } from '@maka/core/connection-error-copy'; -import { execFile } from 'node:child_process'; import { createRequire } from 'node:module'; import { mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; @@ -55,10 +54,6 @@ import { createExecutionRuntimeHostComposition, runtimeHostFilesystemWorkerRuntime, } from '../server/execution-composition.js'; -import { - createManagedWorkspaceExecutionProfile, - RuntimeHostWorkspaceExecutionError, -} from '../server/workspace-execution-composition.js'; const require = createRequire(import.meta.url); const FAKE_CONNECTION_ID = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'; @@ -68,34 +63,6 @@ test('filesystem worker follows the candidate executable runtime', () => { assert.equal(runtimeHostFilesystemWorkerRuntime({}), 'node'); }); -test('production composition never discovers PATH Git when no runtime is admitted', async () => { - await new Promise((resolve, reject) => { - execFile('git', ['--version'], (error) => (error ? reject(error) : resolve())); - }); - - await withCompositionRoot(async ({ owner }) => { - const composition = await createExecutionRuntimeHostComposition(compositionContext(owner)); - try { - assert.equal(composition.workspaceExecution.state, 'ready'); - const managed = createManagedWorkspaceExecutionProfile( - Object.freeze({ kind: 'managed_workspace_execution_handle_v1' }), - ); - await assert.rejects( - () => - composition.workspaceExecution.executeReadOnly(managed, { - kind: 'read', - path: 'README.md', - }), - (error) => - error instanceof RuntimeHostWorkspaceExecutionError && - error.code === 'managed_workspace_profile_unavailable', - ); - } finally { - await composition.close(); - } - }); -}); - test('production composition owns the long-term memory database lifecycle', async () => { await withCompositionRoot(async ({ root, owner }) => { const databasePath = join(root, LONG_TERM_MEMORY_DATABASE_NAME); diff --git a/packages/runtime-host/src/__tests__/workspace-execution-composition.test.ts b/packages/runtime-host/src/__tests__/workspace-execution-composition.test.ts index a9e958f016..a94dea9c6f 100644 --- a/packages/runtime-host/src/__tests__/workspace-execution-composition.test.ts +++ b/packages/runtime-host/src/__tests__/workspace-execution-composition.test.ts @@ -19,74 +19,33 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import type { - ManagedWorkspaceExecutionHandle, - ManagedWorkspaceExecutionScope, - ManagedWorkspaceOwner, -} from '@maka/storage/managed-workspace-owner'; import { createAttachedWorkspaceExecutionProfile, - createManagedWorkspaceExecutionProfile, createRuntimeHostWorkspaceExecutionComposition, RuntimeHostWorkspaceExecutionError, } from '../server/workspace-execution-composition.js'; -test('keeps attached and managed execution profiles explicit', async () => { +test('executes read-only operations in the attached checkout', async () => { const calls: string[] = []; - const handle = Object.freeze({ kind: 'managed_workspace_execution_handle_v1' as const }); - const scope = Object.freeze({ kind: 'managed_workspace_execution_scope_v1' as const }); - const managedOwner = fakeManagedOwner({ handle, scope, calls }); const composition = createRuntimeHostWorkspaceExecutionComposition({ filesystemWorker: { async execute(input) { - calls.push(`worker:${input.cwd}:${input.operation.kind}`); + calls.push(`${input.cwd}:${input.operation.kind}`); return { kind: 'read', content: 'attached' }; }, }, - managedOwner, }); - const attached = createAttachedWorkspaceExecutionProfile('/attached'); + const profile = createAttachedWorkspaceExecutionProfile('/attached'); assert.deepEqual( - await composition.executeReadOnly(attached, { kind: 'read', path: 'README.md' }), + await composition.executeReadOnly(profile, { kind: 'read', path: 'README.md' }), { kind: 'read', content: 'attached' }, ); - - const managed = createManagedWorkspaceExecutionProfile(handle); - assert.deepEqual( - await composition.executeReadOnly(managed, { kind: 'read', path: 'README.md' }), - { kind: 'read', content: 'managed' }, - ); - assert.deepEqual(calls, ['worker:/attached:read', 'managed:admit', 'managed:read']); - + assert.deepEqual(calls, ['/attached:read']); await composition.close(); }); -test('never falls back a managed profile to attached execution', async () => { - let workerCalls = 0; - const composition = createRuntimeHostWorkspaceExecutionComposition({ - filesystemWorker: { - async execute() { - workerCalls += 1; - return { kind: 'read', content: 'unsafe fallback' }; - }, - }, - }); - const managed = createManagedWorkspaceExecutionProfile( - Object.freeze({ kind: 'managed_workspace_execution_handle_v1' }), - ); - - await assert.rejects( - () => composition.executeReadOnly(managed, { kind: 'read', path: 'README.md' }), - (error) => - error instanceof RuntimeHostWorkspaceExecutionError && - error.code === 'managed_workspace_profile_unavailable', - ); - assert.equal(workerCalls, 0); - await composition.close(); -}); - -test('rejects forged or malformed profiles before worker dispatch', async () => { +test('rejects malformed profiles and mutating operations before worker dispatch', async () => { let workerCalls = 0; const composition = createRuntimeHostWorkspaceExecutionComposition({ filesystemWorker: { @@ -102,34 +61,43 @@ test('rejects forged or malformed profiles before worker dispatch', async () => { kind: 'attached_checkout_v1', cwd: '' }, ]) { await assert.rejects( - () => - composition.executeReadOnly(profile as never, { - kind: 'read', - path: 'README.md', - }), + () => composition.executeReadOnly(profile as never, { kind: 'read', path: 'README.md' }), (error) => error instanceof RuntimeHostWorkspaceExecutionError && error.code === 'workspace_operation_denied', ); } + await assert.rejects( + () => + composition.executeReadOnly(createAttachedWorkspaceExecutionProfile('/attached'), { + kind: 'write', + path: 'unsafe.txt', + } as never), + (error) => + error instanceof RuntimeHostWorkspaceExecutionError && + error.code === 'workspace_operation_denied', + ); assert.equal(workerCalls, 0); await composition.close(); }); -test('drains tool operations before closing the managed owner', async () => { - const calls: string[] = []; +test('drains active attached operations before closing', async () => { let releaseWorker!: () => void; const workerBlocked = new Promise((resolve) => { releaseWorker = resolve; }); - const handle = Object.freeze({ kind: 'managed_workspace_execution_handle_v1' as const }); - const scope = Object.freeze({ kind: 'managed_workspace_execution_scope_v1' as const }); - const managedOwner = fakeManagedOwner({ handle, scope, calls, workerBlocked }); - const composition = createRuntimeHostWorkspaceExecutionComposition({ managedOwner }); - const profile = createManagedWorkspaceExecutionProfile(handle); + const composition = createRuntimeHostWorkspaceExecutionComposition({ + filesystemWorker: { + async execute() { + await workerBlocked; + return { kind: 'read', content: 'attached' }; + }, + }, + }); + const profile = createAttachedWorkspaceExecutionProfile('/attached'); const executing = composition.executeReadOnly(profile, { kind: 'read', path: 'README.md' }); - await waitFor(() => calls.includes('managed:read')); + await new Promise((resolve) => setImmediate(resolve)); const closing = composition.close(); await assert.rejects( () => composition.executeReadOnly(profile, { kind: 'read', path: 'other.md' }), @@ -137,46 +105,10 @@ test('drains tool operations before closing the managed owner', async () => { error instanceof RuntimeHostWorkspaceExecutionError && error.code === 'workspace_execution_draining', ); - assert.equal(calls.includes('managed:close'), false); + assert.equal(composition.state, 'draining'); releaseWorker(); await executing; await closing; - assert.deepEqual(calls, ['managed:admit', 'managed:read', 'managed:close']); + assert.equal(composition.state, 'closed'); }); - -function fakeManagedOwner(input: { - handle: ManagedWorkspaceExecutionHandle; - scope: ManagedWorkspaceExecutionScope; - calls: string[]; - workerBlocked?: Promise; -}): ManagedWorkspaceOwner { - return { - state: 'ready', - async openManagedWorkspaceBaseline() { - throw new Error('not used'); - }, - async withManagedWorkspaceExecution(handle, operation) { - assert.equal(handle, input.handle); - input.calls.push('managed:admit'); - return await operation(input.scope); - }, - async executeReadOnlyFilesystemOperation(scope) { - assert.equal(scope, input.scope); - input.calls.push('managed:read'); - await input.workerBlocked; - return { kind: 'read', content: 'managed' }; - }, - async close() { - input.calls.push('managed:close'); - }, - }; -} - -async function waitFor(predicate: () => boolean): Promise { - const deadline = Date.now() + 1_000; - while (!predicate()) { - if (Date.now() >= deadline) throw new Error('condition was not reached'); - await new Promise((resolve) => setTimeout(resolve, 1)); - } -} diff --git a/packages/runtime-host/src/server/execution-composition.ts b/packages/runtime-host/src/server/execution-composition.ts index bbfd74ac82..2468419635 100644 --- a/packages/runtime-host/src/server/execution-composition.ts +++ b/packages/runtime-host/src/server/execution-composition.ts @@ -86,7 +86,6 @@ import { runWithStorageRootLease } from '@maka/storage/root-authority'; import { openStorageWriterComposition } from '@maka/storage/storage-writer-composition'; import type { RuntimePolicyStoresWriter } from '@maka/storage/runtime-policy-stores'; import { resolveWorkspaceIdentity } from '@maka/storage/workspace-identity'; -import { type ManagedWorkspaceFilesystemWorker } from '@maka/storage/managed-workspace-owner'; import { CanonicalSessionProjectionReader } from './canonical-session-projection.js'; import { bindHostChildAgentBackend, @@ -190,6 +189,7 @@ import { createRuntimeHostWorkspaceExecutionComposition, RuntimeHostWorkspaceExecutionError, type RuntimeHostWorkspaceExecutionComposition, + type RuntimeHostWorkspaceFilesystemWorker, } from './workspace-execution-composition.js'; export interface ExecutionRuntimeHostComposition extends RuntimeHostComposition { @@ -328,11 +328,11 @@ export async function createExecutionRuntimeHostComposition( getLaunchSpec: filesystemWorkerLaunchSpecProvider, }) : undefined; - const managedFilesystemWorker = filesystemWorker - ? adaptManagedWorkspaceFilesystemWorker(filesystemWorker) + const workspaceFilesystemWorker = filesystemWorker + ? adaptWorkspaceFilesystemWorker(filesystemWorker) : undefined; workspaceExecution = createRuntimeHostWorkspaceExecutionComposition({ - ...(managedFilesystemWorker ? { filesystemWorker: managedFilesystemWorker } : {}), + ...(workspaceFilesystemWorker ? { filesystemWorker: workspaceFilesystemWorker } : {}), }); const taskLedger = new HostTaskLedgerCoordinator( taskLedgerStore, @@ -1817,9 +1817,9 @@ function requireWorkspaceExecution( return composition; } -function adaptManagedWorkspaceFilesystemWorker( +function adaptWorkspaceFilesystemWorker( worker: Pick, -): ManagedWorkspaceFilesystemWorker { +): RuntimeHostWorkspaceFilesystemWorker { return { async execute(input) { // Read-only operations never participate in CAS; the adapter says so diff --git a/packages/runtime-host/src/server/workspace-execution-composition.ts b/packages/runtime-host/src/server/workspace-execution-composition.ts index 570532efbb..2142bbd8ce 100644 --- a/packages/runtime-host/src/server/workspace-execution-composition.ts +++ b/packages/runtime-host/src/server/workspace-execution-composition.ts @@ -18,29 +18,42 @@ */ import { createReadOnlyPermissionProfile } from '@maka/core/permission-profile'; -import { createManagedExecutionBoundary } from '@maka/core/sandbox-boundary'; +import { + createManagedExecutionBoundary, + type ExecutionBoundary, +} from '@maka/core/sandbox-boundary'; import type { - ManagedWorkspaceExecutionHandle, - ManagedWorkspaceFilesystemWorker, - ManagedWorkspaceOwner, - ManagedWorkspaceReadOnlyOperation, - ManagedWorkspaceReadOnlyResult, -} from '@maka/storage/managed-workspace-owner'; + FilesystemWorkerClient, + FilesystemWorkerClientOperation, +} from '@maka/runtime/filesystem-worker'; -export type RuntimeHostWorkspaceExecutionProfile = - | { - readonly kind: 'attached_checkout_v1'; - readonly cwd: string; - } - | { - readonly kind: 'managed_worktree_v1'; - readonly executionHandle: ManagedWorkspaceExecutionHandle; - }; +export interface RuntimeHostWorkspaceExecutionProfile { + readonly kind: 'attached_checkout_v1'; + readonly cwd: string; +} + +export type RuntimeHostWorkspaceReadOnlyOperation = Extract< + FilesystemWorkerClientOperation, + { kind: 'read' | 'glob' | 'grep' } +>; + +export type RuntimeHostWorkspaceReadOnlyResult = Extract< + Awaited>, + { kind: 'read' | 'read_image' | 'glob' | 'grep' } +>; + +export interface RuntimeHostWorkspaceFilesystemWorker { + execute(input: { + readonly operation: RuntimeHostWorkspaceReadOnlyOperation; + readonly cwd: string; + readonly executionBoundary: ExecutionBoundary; + readonly abortSignal?: AbortSignal; + }): Promise; +} export type RuntimeHostWorkspaceExecutionErrorCode = | 'workspace_execution_draining' | 'filesystem_worker_unavailable' - | 'managed_workspace_profile_unavailable' | 'workspace_operation_denied'; export class RuntimeHostWorkspaceExecutionError extends Error { @@ -57,16 +70,15 @@ export interface RuntimeHostWorkspaceExecutionComposition { readonly state: 'ready' | 'draining' | 'closed'; executeReadOnly( profile: RuntimeHostWorkspaceExecutionProfile, - operation: ManagedWorkspaceReadOnlyOperation, + operation: RuntimeHostWorkspaceReadOnlyOperation, abortSignal?: AbortSignal, - ): Promise; + ): Promise; beginDrain(): void; close(): Promise; } export interface CreateRuntimeHostWorkspaceExecutionCompositionInput { - readonly filesystemWorker?: ManagedWorkspaceFilesystemWorker; - readonly managedOwner?: ManagedWorkspaceOwner; + readonly filesystemWorker?: RuntimeHostWorkspaceFilesystemWorker; } export function createAttachedWorkspaceExecutionProfile( @@ -81,12 +93,6 @@ export function createAttachedWorkspaceExecutionProfile( return Object.freeze({ kind: 'attached_checkout_v1', cwd }); } -export function createManagedWorkspaceExecutionProfile( - executionHandle: ManagedWorkspaceExecutionHandle, -): RuntimeHostWorkspaceExecutionProfile { - return Object.freeze({ kind: 'managed_worktree_v1', executionHandle }); -} - export function createRuntimeHostWorkspaceExecutionComposition( input: CreateRuntimeHostWorkspaceExecutionCompositionInput, ): RuntimeHostWorkspaceExecutionComposition { @@ -134,19 +140,6 @@ export function createRuntimeHostWorkspaceExecutionComposition( } activeOperations += 1; try { - if (profile.kind === 'managed_worktree_v1') { - if (!input.managedOwner) { - throw new RuntimeHostWorkspaceExecutionError( - 'managed_workspace_profile_unavailable', - 'Managed workspace execution is not composed for this Runtime Host', - ); - } - return await input.managedOwner.withManagedWorkspaceExecution( - profile.executionHandle, - (scope) => - input.managedOwner!.executeReadOnlyFilesystemOperation(scope, operation, abortSignal), - ); - } if (!input.filesystemWorker) { throw new RuntimeHostWorkspaceExecutionError( 'filesystem_worker_unavailable', @@ -168,7 +161,6 @@ export function createRuntimeHostWorkspaceExecutionComposition( closeTask ??= (async () => { beginDrain(); await waitForDrain(); - await input.managedOwner?.close(); state = 'closed'; })(); return closeTask; @@ -176,7 +168,7 @@ export function createRuntimeHostWorkspaceExecutionComposition( }; } -function isReadOnlyOperation(input: unknown): input is ManagedWorkspaceReadOnlyOperation { +function isReadOnlyOperation(input: unknown): input is RuntimeHostWorkspaceReadOnlyOperation { if (!input || typeof input !== 'object') return false; const kind = (input as { kind?: unknown }).kind; return kind === 'read' || kind === 'glob' || kind === 'grep'; @@ -189,13 +181,10 @@ function isWorkspaceExecutionProfile( const candidate = input as { kind?: unknown; cwd?: unknown; - executionHandle?: { kind?: unknown }; }; - if (candidate.kind === 'attached_checkout_v1') { - return typeof candidate.cwd === 'string' && candidate.cwd.length > 0; - } return ( - candidate.kind === 'managed_worktree_v1' && - candidate.executionHandle?.kind === 'managed_workspace_execution_handle_v1' + candidate.kind === 'attached_checkout_v1' && + typeof candidate.cwd === 'string' && + candidate.cwd.length > 0 ); } diff --git a/packages/storage/package.json b/packages/storage/package.json index 47ea5c4206..2c4bc7853d 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -25,7 +25,6 @@ "./interaction-store": "./dist/interaction-store-public.js", "./long-term-memory-store": "./dist/long-term-memory-store.js", "./managed-secret-store": "./dist/managed-secret-store.js", - "./managed-workspace-owner": "./dist/managed-workspace-owner.js", "./mcp-config-store": "./dist/mcp-config-store.js", "./memory-bundle-store": "./dist/memory-bundle-store.js", "./model-call-ledger": "./dist/model-call-ledger.js", diff --git a/packages/storage/src/__tests__/dugite-native-environment.test.ts b/packages/storage/src/__tests__/dugite-native-environment.test.ts deleted file mode 100644 index f9f06697b8..0000000000 --- a/packages/storage/src/__tests__/dugite-native-environment.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { bundledGitEnvironment } from '../dugite-native-environment.js'; - -test('constructs a Windows MinGit environment without a system PATH', () => { - assert.deepEqual( - bundledGitEnvironment({ - platform: 'win32', - arch: 'arm64', - rootPath: 'C:\\Maka\\resources\\git', - executablePath: 'C:\\Maka\\resources\\git\\cmd\\git.exe', - }), - { - PATH: [ - 'C:\\Maka\\resources\\git\\cmd', - 'C:\\Maka\\resources\\git\\clangarm64\\bin', - 'C:\\Maka\\resources\\git\\clangarm64\\usr\\bin', - ].join(';'), - GIT_EXEC_PATH: 'C:\\Maka\\resources\\git\\clangarm64\\libexec\\git-core', - }, - ); -}); - -test('constructs relocatable macOS and Linux Git environments', () => { - assert.deepEqual( - bundledGitEnvironment({ - platform: 'darwin', - arch: 'arm64', - rootPath: '/Applications/Maka.app/Contents/Resources/git', - executablePath: '/Applications/Maka.app/Contents/Resources/git/bin/git', - }), - { - PATH: '/Applications/Maka.app/Contents/Resources/git/bin', - GIT_EXEC_PATH: '/Applications/Maka.app/Contents/Resources/git/libexec/git-core', - GIT_TEMPLATE_DIR: '/Applications/Maka.app/Contents/Resources/git/share/git-core/templates', - }, - ); - assert.deepEqual( - bundledGitEnvironment({ - platform: 'linux', - arch: 'x64', - rootPath: '/opt/maka/resources/git', - executablePath: '/opt/maka/resources/git/bin/git', - }), - { - PATH: '/opt/maka/resources/git/bin', - GIT_EXEC_PATH: '/opt/maka/resources/git/libexec/git-core', - GIT_TEMPLATE_DIR: '/opt/maka/resources/git/share/git-core/templates', - PREFIX: '/opt/maka/resources/git', - GIT_SSL_CAINFO: '/opt/maka/resources/git/ssl/cacert.pem', - }, - ); -}); diff --git a/packages/storage/src/__tests__/fixtures/git-workspace-service-crash-child.ts b/packages/storage/src/__tests__/fixtures/git-workspace-service-crash-child.ts deleted file mode 100644 index 5491beef18..0000000000 --- a/packages/storage/src/__tests__/fixtures/git-workspace-service-crash-child.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { writeFileSync, writeSync } from 'node:fs'; -import { join } from 'node:path'; -import { createGitWorkspaceService } from '../../git-workspace-service.js'; -import { openManagedWorkspaceOwner } from '../../managed-workspace-owner.js'; -import { resolveStorageRoot, tryAcquireInteractiveRootOwner } from '../../root-authority.js'; -import { createSqliteRuntimeStore } from '../../sqlite-runtime-store.js'; - -const storageRoot = requiredEnv('MAKA_GIT_WORKSPACE_STORAGE'); -const gitRuntime = { - executablePath: requiredEnv('MAKA_GIT_WORKSPACE_EXECUTABLE'), - expectedSha256: requiredEnv('MAKA_GIT_WORKSPACE_SHA256') as `sha256:${string}`, -}; -const failpoint = (point: string) => { - if (point !== requiredEnv('MAKA_GIT_WORKSPACE_FAILPOINT')) return; - writeSync(1, 'READY\n'); - Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0); -}; - -const request = { - repositoryId: 'repository_11111111111111111111111111111111', - workspaceId: 'workspace_22222222222222222222222222222222', - workspaceEpochId: 'epoch_33333333333333333333333333333333', - workspaceInstanceId: 'instance_44444444444444444444444444444444', - sourceRoot: requiredEnv('MAKA_GIT_WORKSPACE_SOURCE'), -} as const; - -if (process.env.MAKA_GIT_WORKSPACE_ACTION === 'baseline-receipt') { - const capability = await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - if (!rootOwner) throw new Error('Unable to acquire crash-child root owner'); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - const owner = await openManagedWorkspaceOwner({ rootOwner, gitRuntime, failpoint }); - await owner.openManagedWorkspaceBaseline(runtimeStore, request); - throw new Error('Managed baseline crash child missed its failpoint'); -} - -if (process.env.MAKA_GIT_WORKSPACE_ACTION === 'execution-admission') { - const capability = await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - if (!rootOwner) throw new Error('Unable to acquire crash-child root owner'); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - const owner = await openManagedWorkspaceOwner({ rootOwner, gitRuntime, failpoint }); - const accepted = await owner.openManagedWorkspaceBaseline(runtimeStore, request); - await owner.withManagedWorkspaceExecution(accepted.executionHandle, async () => undefined); - throw new Error('Managed execution admission crash child missed its failpoint'); -} - -const service = createGitWorkspaceService({ - storageRoot, - gitRuntime, - failpoint, -}); - -const binding = await service.createManagedWorkspaceFromSource(request); - -if (process.env.MAKA_GIT_WORKSPACE_ACTION === 'quarantine') { - writeFileSync( - requiredEnv('MAKA_GIT_WORKSPACE_BINDING_OUTPUT'), - `${JSON.stringify(binding)}\n`, - 'utf8', - ); - await service.quarantineManagedWorkspace(binding, 'crash_convergence_test'); -} - -throw new Error('Git workspace crash child missed its failpoint'); - -function requiredEnv(name: string): string { - const value = process.env[name]; - if (!value) throw new Error(`Missing ${name}`); - return value; -} diff --git a/packages/storage/src/__tests__/git-workspace-service.test.ts b/packages/storage/src/__tests__/git-workspace-service.test.ts deleted file mode 100644 index 831b88d97f..0000000000 --- a/packages/storage/src/__tests__/git-workspace-service.test.ts +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'node:assert/strict'; -import { execFile, spawn } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { createReadStream, existsSync } from 'node:fs'; -import { - mkdir, - mkdtemp, - readFile, - readdir, - realpath, - rename, - rm, - stat, - symlink, - writeFile, -} from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { dirname, join, normalize } from 'node:path'; -import { promisify } from 'node:util'; -import { afterEach, before, describe, test } from 'node:test'; -import { fileURLToPath } from 'node:url'; -import { - GitWorkspaceServiceError, - createGitWorkspaceService, - type GitWorkspaceService, - type ManagedWorkspaceBinding, -} from '../git-workspace-service.js'; - -const execFileAsync = promisify(execFile); -const cleanup: string[] = []; -let gitExecutablePath: string; -let gitExecutableSha256: `sha256:${string}`; - -// These probes fork a second Node process, stop it at a durable failpoint, and -// kill it to verify restart convergence. Keep the full crash matrix on this -// service's owning stress route; the default suite still covers recovery, -// quarantine, corruption, ownership, and symlink boundaries in-process. -const RUN_REAL_PROCESS_CRASH_TESTS = process.env.MAKA_STORAGE_STRESS === '1'; - -before(async () => { - gitExecutablePath = await findGitExecutable(); - gitExecutableSha256 = await sha256File(gitExecutablePath); -}); - -afterEach(async () => { - await Promise.all(cleanup.splice(0).map((path) => rm(path, { recursive: true, force: true }))); -}); - -describe('Git workspace service', () => { - test('fails before creating storage when the explicit Git artifact digest is wrong', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const service = createGitWorkspaceService({ - storageRoot, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: `sha256:${'0'.repeat(64)}`, - }, - }); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('git_runtime_integrity_mismatch'), - ); - assert.equal(existsSync(storageRoot), false); - }); - - test('imports only the clean source HEAD tree into an independent fixed-config workspace', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - await git(sourceRoot, 'config', 'core.hooksPath', join(root, 'attacker-hooks')); - await git(sourceRoot, 'config', 'core.sshCommand', 'attacker-ssh-command'); - await git(sourceRoot, 'config', 'credential.helper', '!attacker-credential-helper'); - const sourceSnapshot = await snapshotSource(sourceRoot); - const service = await serviceAt(join(root, 'storage')); - - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - const inspection = await service.inspectManagedWorkspace(binding); - - assert.equal(inspection.state, 'ready'); - assert.equal(inspection.commitOid, binding.baselineCommitOid); - assert.equal(inspection.treeOid, binding.baselineTreeOid); - assert.equal(binding.sourceHeadCommitOid, sourceSnapshot.head); - assert.equal(binding.sourceTreeOid, sourceSnapshot.tree); - assert.equal(binding.baselineTreeOid, sourceSnapshot.tree); - assert.match(binding.materializationProfileDigest, /^sha256:[a-f0-9]{64}$/u); - assert.notEqual(normalize(binding.repositoryPath), normalize(sourceSnapshot.commonDir)); - assert.equal(await readFile(join(binding.worktreePath, 'tracked.txt'), 'utf8'), 'tracked\n'); - assert.equal(existsSync(join(binding.worktreePath, 'ignored.env')), false); - assert.equal(existsSync(join(binding.worktreePath, '.maka-workspace.json')), false); - assert.equal(await git(binding.worktreePath, 'status', '--porcelain=v1'), ''); - assert.equal( - await gitBare( - binding.repositoryPath, - 'rev-list', - '--parents', - '-n', - '1', - binding.baselineCommitOid, - ), - binding.baselineCommitOid, - ); - assert.equal( - await gitBare(binding.repositoryPath, 'config', '--local', '--get', 'core.autocrlf'), - 'false', - ); - assert.equal( - await gitBare(binding.repositoryPath, 'config', '--local', '--get', 'core.hooksPath'), - binding.hooksPath, - ); - assert.equal( - await gitBare(binding.repositoryPath, 'config', '--local', '--get', 'core.sshCommand'), - '', - ); - assert.equal( - await gitBare(binding.repositoryPath, 'config', '--local', '--get', 'protocol.allow'), - 'never', - ); - assert.equal(existsSync(join(binding.repositoryPath, 'objects', 'info', 'alternates')), false); - await assert.rejects(gitBare(binding.repositoryPath, 'cat-file', '-e', sourceSnapshot.head)); - await assertSourceUnchanged(sourceRoot, sourceSnapshot); - }); - - test('does not inherit a poisoned process-level Git config path', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const poisonedConfig = join(root, 'poisoned-global-gitconfig'); - await writeFile(poisonedConfig, 'this is not valid Git config\n'); - const previous = process.env.GIT_CONFIG_GLOBAL; - process.env.GIT_CONFIG_GLOBAL = poisonedConfig; - try { - const service = await serviceAt(join(root, 'storage')); - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - assert.equal((await service.inspectManagedWorkspace(binding)).state, 'ready'); - } finally { - if (previous === undefined) delete process.env.GIT_CONFIG_GLOBAL; - else process.env.GIT_CONFIG_GLOBAL = previous; - } - }); - - test('adopts the same binding across service restart and concurrent duplicate opens', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const storageRoot = join(root, 'storage'); - const request = openRequest(sourceRoot); - const firstService = await serviceAt(storageRoot); - const [left, right] = await Promise.all([ - firstService.createManagedWorkspaceFromSource(request), - firstService.createManagedWorkspaceFromSource(request), - ]); - const restarted = await serviceAt(storageRoot); - const adopted = await restarted.openManagedWorkspaceFromBinding(left); - - assert.deepEqual(right, left); - assert.deepEqual(adopted, left); - assert.equal((await restarted.inspectManagedWorkspace(adopted)).state, 'ready'); - }); - - test('reopens an existing managed epoch after the source checkout advances', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const storageRoot = join(root, 'storage'); - const request = openRequest(sourceRoot); - const firstService = await serviceAt(storageRoot); - const created = await firstService.createManagedWorkspaceFromSource(request); - - await writeFile(join(sourceRoot, 'tracked.txt'), 'source advanced\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'advance source after managed epoch creation', - ); - - const restarted = await serviceAt(storageRoot); - const reopened = await restarted.openManagedWorkspaceFromBinding(created); - - assert.deepEqual(reopened, created); - assert.equal((await restarted.inspectManagedWorkspace(reopened)).state, 'ready'); - assert.equal(await readFile(join(reopened.worktreePath, 'tracked.txt'), 'utf8'), 'tracked\n'); - }); - - test('creates a second epoch with a different baseline in the same managed repository', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const service = await serviceAt(join(root, 'storage')); - const first = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - - await writeFile(join(sourceRoot, 'tracked.txt'), 'second epoch\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'second epoch source', - ); - const secondRequest = { - ...openRequest(sourceRoot), - workspaceEpochId: 'epoch_55555555555555555555555555555555', - workspaceInstanceId: 'instance_66666666666666666666666666666666', - } as const; - - const second = await service.createManagedWorkspaceFromSource(secondRequest); - - assert.equal(second.repositoryId, first.repositoryId); - assert.equal(second.repositoryPath, first.repositoryPath); - assert.notEqual(second.baselineCommitOid, first.baselineCommitOid); - assert.notEqual(second.baselineTreeOid, first.baselineTreeOid); - assert.equal(await readFile(join(first.worktreePath, 'tracked.txt'), 'utf8'), 'tracked\n'); - assert.equal( - await readFile(join(second.worktreePath, 'tracked.txt'), 'utf8'), - 'second epoch\n', - ); - assert.equal((await service.inspectManagedWorkspace(first)).state, 'ready'); - assert.equal((await service.inspectManagedWorkspace(second)).state, 'ready'); - }); - - test('removes an unpublished baseline ref when the source changes and permits retry', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const storageRoot = join(root, 'storage'); - let changed = false; - const service = createGitWorkspaceService({ - storageRoot, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - failpoint: async (point) => { - if (point !== 'after_baseline_ref_created' || changed) return; - changed = true; - await writeFile(join(sourceRoot, 'tracked.txt'), 'raced source\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'source changed during import', - ); - }, - }); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('source_changed_during_baseline_import'), - ); - const repositoryPath = await onlyManagedRepositoryPath(storageRoot); - await assert.rejects( - gitBare( - repositoryPath, - 'rev-parse', - '--verify', - 'refs/maka/baselines/epoch_33333333333333333333333333333333', - ), - ); - - const retried = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - assert.equal( - await readFile(join(retried.worktreePath, 'tracked.txt'), 'utf8'), - 'raced source\n', - ); - assert.equal((await service.inspectManagedWorkspace(retried)).state, 'ready'); - }); - - test('repairs a crash after baseline ref publication even when the source later advances', { - skip: !RUN_REAL_PROCESS_CRASH_TESTS, - timeout: 60_000, - }, async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const storageRoot = join(root, 'storage'); - const child = spawn( - process.execPath, - [fileURLToPath(new URL('./fixtures/git-workspace-service-crash-child.js', import.meta.url))], - { - env: { - ...process.env, - MAKA_GIT_WORKSPACE_STORAGE: storageRoot, - MAKA_GIT_WORKSPACE_SOURCE: sourceRoot, - MAKA_GIT_WORKSPACE_EXECUTABLE: gitExecutablePath, - MAKA_GIT_WORKSPACE_SHA256: gitExecutableSha256, - MAKA_GIT_WORKSPACE_FAILPOINT: 'after_baseline_ref_created', - }, - stdio: ['ignore', 'pipe', 'pipe'], - }, - ); - try { - await waitForReady(child, 30_000); - child.kill('SIGKILL'); - await waitForExit(child); - await writeFile(join(sourceRoot, 'tracked.txt'), 'advanced after crash\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'advance after baseline publication crash', - ); - - const service = await serviceAt(storageRoot); - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - assert.equal( - await readFile(join(binding.worktreePath, 'tracked.txt'), 'utf8'), - 'advanced after crash\n', - ); - assert.equal((await service.inspectManagedWorkspace(binding)).state, 'ready'); - } finally { - if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); - } - }); - - for (const failpoint of [ - 'after_worktree_materialized', - 'after_worktree_locked', - 'after_head_ref_updated', - ] as const) { - test(`repairs a real-process crash at ${failpoint} without accepting residue`, { - skip: !RUN_REAL_PROCESS_CRASH_TESTS, - timeout: 60_000, - }, async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const sourceSnapshot = await snapshotSource(sourceRoot); - const storageRoot = join(root, 'storage'); - const child = spawn( - process.execPath, - [ - fileURLToPath( - new URL('./fixtures/git-workspace-service-crash-child.js', import.meta.url), - ), - ], - { - env: { - ...process.env, - MAKA_GIT_WORKSPACE_STORAGE: storageRoot, - MAKA_GIT_WORKSPACE_SOURCE: sourceRoot, - MAKA_GIT_WORKSPACE_EXECUTABLE: gitExecutablePath, - MAKA_GIT_WORKSPACE_SHA256: gitExecutableSha256, - MAKA_GIT_WORKSPACE_FAILPOINT: failpoint, - }, - stdio: ['ignore', 'pipe', 'pipe'], - }, - ); - try { - await waitForReady(child, 30_000); - child.kill('SIGKILL'); - await waitForExit(child); - - const service = await serviceAt(storageRoot); - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - assert.equal((await service.inspectManagedWorkspace(binding)).state, 'ready'); - const quarantineEntries = await readdir( - join(storageRoot, 'managed-workspaces', 'quarantine'), - ); - assert.ok(quarantineEntries.some((entry) => entry.startsWith('incomplete-'))); - await assertSourceUnchanged(sourceRoot, sourceSnapshot); - } finally { - if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); - } - }); - } - - for (const failpoint of [ - 'after_quarantine_intent', - 'after_quarantine_unlock', - 'after_quarantine_move', - 'after_quarantine_binding_removed', - 'after_quarantine_pruned', - ] as const) { - test(`converges quarantine after a real-process crash at ${failpoint}`, { - skip: !RUN_REAL_PROCESS_CRASH_TESTS, - timeout: 60_000, - }, async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const sourceSnapshot = await snapshotSource(sourceRoot); - const storageRoot = join(root, 'storage'); - const bindingOutput = join(root, 'binding.json'); - const child = spawn( - process.execPath, - [ - fileURLToPath( - new URL('./fixtures/git-workspace-service-crash-child.js', import.meta.url), - ), - ], - { - env: { - ...process.env, - MAKA_GIT_WORKSPACE_STORAGE: storageRoot, - MAKA_GIT_WORKSPACE_SOURCE: sourceRoot, - MAKA_GIT_WORKSPACE_EXECUTABLE: gitExecutablePath, - MAKA_GIT_WORKSPACE_SHA256: gitExecutableSha256, - MAKA_GIT_WORKSPACE_FAILPOINT: failpoint, - MAKA_GIT_WORKSPACE_ACTION: 'quarantine', - MAKA_GIT_WORKSPACE_BINDING_OUTPUT: bindingOutput, - }, - stdio: ['ignore', 'pipe', 'pipe'], - }, - ); - try { - await waitForReady(child, 30_000); - child.kill('SIGKILL'); - await waitForExit(child); - const binding = JSON.parse( - await readFile(bindingOutput, 'utf8'), - ) as ManagedWorkspaceBinding; - const service = await serviceAt(storageRoot); - - await assert.rejects( - service.openManagedWorkspaceFromBinding(binding), - isWorkspaceError('managed_workspace_unavailable'), - ); - const first = await service.quarantineManagedWorkspace(binding, 'crash_convergence_test'); - const retry = await service.quarantineManagedWorkspace(binding, 'crash_convergence_test'); - - assert.deepEqual(retry, first); - assert.equal((await stat(first.quarantinePath)).isDirectory(), true); - assert.equal(existsSync(binding.worktreePath), false); - await assertSourceUnchanged(sourceRoot, sourceSnapshot); - } finally { - if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); - } - }); - } - - test('rejects dirty or unsupported source state without falling back to attached execution', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - await writeFile(join(sourceRoot, 'untracked.txt'), 'not committed\n', 'utf8'); - const service = await serviceAt(join(root, 'storage')); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('repository_ineligible'), - ); - }); - - test('rejects nested attributes that could change fixed Git materialization', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - await mkdir(join(sourceRoot, 'nested')); - await writeFile(join(sourceRoot, 'nested', '.gitattributes'), '*.txt filter=custom\n', 'utf8'); - await git(sourceRoot, 'add', 'nested/.gitattributes'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'unsupported attributes', - ); - const service = await serviceAt(join(root, 'storage')); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('repository_ineligible'), - ); - }); - - test('detects external managed-worktree drift and quarantines it without touching source', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const sourceSnapshot = await snapshotSource(sourceRoot); - const service = await serviceAt(join(root, 'storage')); - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - await writeFile(join(binding.worktreePath, 'tracked.txt'), 'external writer\n', 'utf8'); - - const drifted = await service.inspectManagedWorkspace(binding); - assert.equal(drifted.state, 'drifted'); - const quarantined = await service.quarantineManagedWorkspace(binding, 'external_drift'); - - assert.equal(existsSync(binding.worktreePath), false); - assert.equal((await stat(quarantined.quarantinePath)).isDirectory(), true); - assert.equal( - await readFile(join(quarantined.quarantinePath, 'tracked.txt'), 'utf8'), - 'external writer\n', - ); - await assertSourceUnchanged(sourceRoot, sourceSnapshot); - await assert.rejects( - service.inspectManagedWorkspace(binding), - isWorkspaceError('managed_workspace_unavailable'), - ); - }); - - test('rejects quarantine resume when its owned root is replaced by a symlink', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - let interruptOnce = true; - const interrupted = createGitWorkspaceService({ - storageRoot, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - failpoint(point) { - if (point === 'after_quarantine_intent' && interruptOnce) { - interruptOnce = false; - throw new Error('stop after durable quarantine intent'); - } - }, - }); - const binding = await interrupted.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - await assert.rejects( - interrupted.quarantineManagedWorkspace(binding, 'owned_root_symlink'), - /stop after durable quarantine intent/u, - ); - const quarantineRoot = join(storageRoot, 'managed-workspaces', 'quarantine'); - const externalRoot = join(root, 'external-quarantine'); - await rm(quarantineRoot, { recursive: true, force: true }); - await mkdir(externalRoot, { recursive: true }); - await symlink(externalRoot, quarantineRoot, process.platform === 'win32' ? 'junction' : 'dir'); - - const service = await serviceAt(storageRoot); - await assert.rejects( - service.quarantineManagedWorkspace(binding, 'owned_root_symlink'), - isWorkspaceError('managed_workspace_identity_conflict'), - ); - assert.equal(existsSync(binding.worktreePath), true); - assert.deepEqual(await readdir(externalRoot), []); - }); - - test('rejects control records reached through a replaced instance-root symlink', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const service = await serviceAt(storageRoot); - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - const instanceRoot = dirname(binding.worktreePath); - const externalRoot = join(root, 'external-instance'); - await mkdir(externalRoot, { recursive: true }); - const movedInstance = join(externalRoot, 'instance'); - await rename(instanceRoot, movedInstance); - await symlink(movedInstance, instanceRoot, process.platform === 'win32' ? 'junction' : 'dir'); - - await assert.rejects( - service.inspectManagedWorkspace(binding), - isWorkspaceError('managed_workspace_identity_conflict'), - ); - }); - - test('rejects a source repository that delegates object lookup through alternates', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const commonDirRaw = await git(sourceRoot, 'rev-parse', '--git-common-dir'); - const commonDir = await realpath(join(sourceRoot, commonDirRaw)); - const alternateObjects = join(root, 'alternate-objects'); - await mkdir(alternateObjects, { recursive: true }); - await mkdir(join(commonDir, 'objects', 'info'), { recursive: true }); - await writeFile( - join(commonDir, 'objects', 'info', 'alternates'), - `${alternateObjects}\n`, - 'utf8', - ); - const service = await serviceAt(join(root, 'storage')); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('repository_ineligible'), - ); - }); - - test('rejects unsafe worktree-scoped source configuration', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - await git(sourceRoot, 'config', 'extensions.worktreeConfig', 'true'); - await git(sourceRoot, 'config', '--worktree', 'core.fsmonitor', 'untrusted-monitor'); - const service = await serviceAt(join(root, 'storage')); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('repository_ineligible'), - ); - }); - - test('rejects a local includeIf source configuration after Git key normalization', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - await git( - sourceRoot, - 'config', - '--local', - 'includeIf.gitdir:/definitely-never/.path', - '../untrusted-config', - ); - const service = await serviceAt(join(root, 'storage')); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('repository_ineligible'), - ); - }); - - test('rejects a worktree partial-clone source configuration after key normalization', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - await git(sourceRoot, 'config', 'extensions.worktreeConfig', 'true'); - await git(sourceRoot, 'config', '--worktree', 'extensions.partialClone', 'origin'); - const service = await serviceAt(join(root, 'storage')); - - await assert.rejects( - service.createManagedWorkspaceFromSource(openRequest(sourceRoot)), - isWorkspaceError('repository_ineligible'), - ); - }); - - test('rejects adoption when the Maka worktree ownership lock is removed', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const service = await serviceAt(join(root, 'storage')); - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - await gitBare(binding.repositoryPath, 'worktree', 'unlock', binding.worktreePath); - - await assert.rejects( - service.inspectManagedWorkspace(binding), - isWorkspaceError('managed_workspace_identity_conflict'), - ); - }); - - test('does not overwrite a conflicting managed head ref', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const service = await serviceAt(join(root, 'storage')); - const first = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - const conflictingEpochId = 'epoch_55555555555555555555555555555555'; - const conflictingHeadRef = `refs/maka/workspaces/${first.workspaceId}/epochs/${conflictingEpochId}/head`; - await gitBare(first.repositoryPath, 'update-ref', conflictingHeadRef, first.baselineCommitOid); - await writeFile(join(sourceRoot, 'tracked.txt'), 'conflicting second epoch\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'conflicting second epoch source', - ); - const secondRequest = { - ...openRequest(sourceRoot), - workspaceEpochId: conflictingEpochId, - workspaceInstanceId: 'instance_66666666666666666666666666666666', - } as const; - - await assert.rejects( - service.createManagedWorkspaceFromSource(secondRequest), - isWorkspaceError('managed_workspace_identity_conflict'), - ); - assert.equal( - await gitBare(first.repositoryPath, 'rev-parse', '--verify', conflictingHeadRef), - first.baselineCommitOid, - ); - }); - - test('fails closed when a durable binding gains unknown or self-authenticating fields', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const service = await serviceAt(join(root, 'storage')); - const binding = await service.createManagedWorkspaceFromSource(openRequest(sourceRoot)); - const bindingPath = join(dirname(binding.worktreePath), 'binding.json'); - const corrupted = { - ...JSON.parse(await readFile(bindingPath, 'utf8')), - acceptedByCaller: true, - }; - await writeFile(bindingPath, `${JSON.stringify(corrupted)}\n`, 'utf8'); - - await assert.rejects( - service.inspectManagedWorkspace(binding), - isWorkspaceError('managed_workspace_identity_conflict'), - ); - }); -}); - -async function serviceAt(storageRoot: string): Promise { - return createGitWorkspaceService({ - storageRoot, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); -} - -async function onlyManagedRepositoryPath(storageRoot: string): Promise { - const repositoryRoots = await readdir(join(storageRoot, 'managed-workspaces', 'r')); - assert.equal(repositoryRoots.length, 1); - return join(storageRoot, 'managed-workspaces', 'r', repositoryRoots[0]!, 'repository.git'); -} - -function openRequest(sourceRoot: string) { - return { - repositoryId: 'repository_11111111111111111111111111111111', - workspaceId: 'workspace_22222222222222222222222222222222', - workspaceEpochId: 'epoch_33333333333333333333333333333333', - workspaceInstanceId: 'instance_44444444444444444444444444444444', - sourceRoot, - } as const; -} - -async function createEligibleSource(sourceRoot: string): Promise { - await mkdir(sourceRoot, { recursive: true }); - await git(sourceRoot, 'init', '--quiet'); - await writeFile(join(sourceRoot, 'tracked.txt'), 'tracked\n', 'utf8'); - await writeFile(join(sourceRoot, '.gitignore'), 'ignored.env\n.maka-workspace.json\n', 'utf8'); - await writeFile(join(sourceRoot, 'ignored.env'), 'SECRET=not-imported\n', 'utf8'); - await writeFile(join(sourceRoot, '.maka-workspace.json'), '{"host":"metadata"}\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt', '.gitignore'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'source baseline', - ); - return realpath(sourceRoot); -} - -interface SourceSnapshot { - readonly root: string; - readonly commonDir: string; - readonly head: string; - readonly tree: string; - readonly branch: string; - readonly status: string; - readonly refs: string; - readonly indexDigest: `sha256:${string}`; - readonly trackedContents: string; -} - -async function snapshotSource(sourceRoot: string): Promise { - const commonDirRaw = await git(sourceRoot, 'rev-parse', '--git-common-dir'); - const commonDir = await realpath(join(sourceRoot, commonDirRaw)); - return { - root: await realpath(sourceRoot), - commonDir, - head: await git(sourceRoot, 'rev-parse', 'HEAD'), - tree: await git(sourceRoot, 'rev-parse', 'HEAD^{tree}'), - branch: await git(sourceRoot, 'branch', '--show-current'), - status: await git(sourceRoot, 'status', '--porcelain=v1', '--untracked-files=all'), - refs: await git(sourceRoot, 'for-each-ref', '--format=%(refname) %(objectname)'), - indexDigest: await sha256File(join(commonDir, 'index')), - trackedContents: await readFile(join(sourceRoot, 'tracked.txt'), 'utf8'), - }; -} - -async function assertSourceUnchanged(sourceRoot: string, before: SourceSnapshot): Promise { - const after = await snapshotSource(sourceRoot); - assert.deepEqual(after, before); -} - -function isWorkspaceError(code: string): (error: unknown) => boolean { - return (error) => error instanceof GitWorkspaceServiceError && error.code === code; -} - -async function temporaryRoot(): Promise { - const root = await mkdtemp(join(tmpdir(), 'maka-git-workspace-service-')); - cleanup.push(root); - return root; -} - -async function findGitExecutable(): Promise { - const command = process.platform === 'win32' ? 'where.exe' : 'which'; - const { stdout } = await execFileAsync(command, ['git'], { - encoding: 'utf8', - }); - const first = stdout - .split(/\r?\n/u) - .map((line) => line.trim()) - .find(Boolean); - if (!first) throw new Error('Git executable is unavailable for integration tests'); - return realpath(first); -} - -async function sha256File(path: string): Promise<`sha256:${string}`> { - const hash = createHash('sha256'); - for await (const chunk of createReadStream(path)) hash.update(chunk); - return `sha256:${hash.digest('hex')}`; -} - -async function git(cwd: string, ...args: string[]): Promise { - const { stdout } = await execFileAsync('git', args, { - cwd, - encoding: 'utf8', - maxBuffer: 8 * 1024 * 1024, - }); - return stdout.trim(); -} - -async function gitBare(repositoryPath: string, ...args: string[]): Promise { - const { stdout } = await execFileAsync( - 'git', - ['-c', 'core.longpaths=true', '--git-dir', repositoryPath, ...args], - { - cwd: dirname(repositoryPath), - encoding: 'utf8', - maxBuffer: 8 * 1024 * 1024, - }, - ); - return stdout.trim(); -} - -function waitForReady(child: ReturnType, timeoutMs: number): Promise { - return new Promise((resolve, reject) => { - let stdout = ''; - let stderr = ''; - const timeout = setTimeout(() => { - cleanupListeners(); - reject(new Error(`Git workspace crash child did not become ready: ${stderr}`)); - }, timeoutMs); - const onStdout = (chunk: unknown) => { - stdout += String(chunk); - if (stdout.includes('READY\n')) { - cleanupListeners(); - resolve(); - } - }; - const onStderr = (chunk: unknown) => { - stderr += String(chunk); - }; - const onExit = (code: number | null, signal: NodeJS.Signals | null) => { - cleanupListeners(); - reject(new Error(`Git workspace crash child exited early: ${code}/${signal} ${stderr}`)); - }; - const onError = (error: Error) => { - cleanupListeners(); - reject(error); - }; - const cleanupListeners = () => { - clearTimeout(timeout); - child.stdout?.off('data', onStdout); - child.stderr?.off('data', onStderr); - child.off('exit', onExit); - child.off('error', onError); - }; - child.stdout?.on('data', onStdout); - child.stderr?.on('data', onStderr); - child.once('exit', onExit); - child.once('error', onError); - }); -} - -function waitForExit(child: ReturnType): Promise { - if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(); - return new Promise((resolve, reject) => { - child.once('exit', () => resolve()); - child.once('error', reject); - }); -} diff --git a/packages/storage/src/__tests__/managed-workspace-baseline.test.ts b/packages/storage/src/__tests__/managed-workspace-baseline.test.ts deleted file mode 100644 index eb8b008b0b..0000000000 --- a/packages/storage/src/__tests__/managed-workspace-baseline.test.ts +++ /dev/null @@ -1,1326 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'node:assert/strict'; -import { execFile, execFileSync, spawn } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { createReadStream } from 'node:fs'; -import { - cp, - copyFile, - link, - mkdir, - mkdtemp, - readFile, - realpath, - rename, - rm, - symlink, - writeFile, -} from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { dirname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { promisify } from 'node:util'; -import { afterEach, before, test } from 'node:test'; -import { openManagedWorkspaceOwner } from '../managed-workspace-owner.js'; -import { managedWorkspaceExecutionAuthorityTestSupport } from '../managed-workspace-execution-authority-internal.js'; -import { createGitWorkspaceService } from '../git-workspace-service.js'; -import { acquireOperationalStateDatabase } from '../operational-state-store.js'; -import { - adoptStorageRootOnImport, - resolveStorageRoot, - STORAGE_ROOT_MARKER_FILE, - tryAcquireInteractiveRootOwner, -} from '../root-authority.js'; -import { createSqliteRuntimeStore } from '../sqlite-runtime-store.js'; -import { - removeTrackedControlDirectories, - trackControlDirectory, -} from './fixtures/control-directory-hygiene.js'; - -const execFileAsync = promisify(execFile); -const cleanup: string[] = []; -const { - inspectHandle: inspectManagedWorkspaceExecutionHandleInternal, - inspectScope: inspectManagedWorkspaceExecutionScopeInternal, -} = managedWorkspaceExecutionAuthorityTestSupport; -let gitExecutablePath: string; -let gitExecutableSha256: `sha256:${string}`; - -before(async () => { - gitExecutablePath = await findGitExecutable(); - gitExecutableSha256 = await sha256File(gitExecutablePath); -}); - -test('does not expose baseline receipt issuance on the public Git workspace service', () => { - const service = createGitWorkspaceService({ - storageRoot: join(tmpdir(), 'maka-public-receipt-authority-test'), - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - assert.equal('openManagedWorkspaceBaselineReceipt' in service, false); - assert.equal('requireManagedWorkspaceBaselineReceipt' in service, false); - assert.equal('verifyManagedWorkspaceBaselineReceipt' in service, false); -}); - -test('does not expose artifact-only workspace creation through the public storage API', async () => { - // The package has no barrel: reachability is decided by the `exports` map. - // Asserting on the map's targets alone would miss a published entrypoint - // re-exporting an internal module, so this loads every published entrypoint - // and checks the union of the symbols a consumer can actually reach. - const manifest = JSON.parse( - await readFile(new URL('../../package.json', import.meta.url), 'utf8'), - ) as { exports: Record }; - const reachable = new Set(); - await Promise.all( - Object.values(manifest.exports).map(async (target) => { - const entrypoint = (await import(new URL(`../../${target}`, import.meta.url).href)) as object; - for (const name of Object.keys(entrypoint)) reachable.add(name); - }), - ); - for (const internalSymbol of [ - 'createGitWorkspaceService', - 'GitWorkspaceServiceError', - 'createManagedWorkspaceWorkerBridgeInternal', - 'ManagedWorkspaceWorkerBridgeError', - 'ManagedWorkspaceExecutionAuthorityError', - 'issueManagedWorkspaceExecutionHandleInternal', - 'requireManagedWorkspaceExecutionHandleInternal', - 'issueManagedWorkspaceExecutionScopeInternal', - 'revokeManagedWorkspaceExecutionScopeInternal', - 'requireManagedWorkspaceExecutionScopeInternal', - 'managedWorkspaceExecutionAuthorityTestSupport', - 'createSqliteArtifactStoreWriteAuthority', - 'migrateOperationalStateDatabaseInternal', - 'inspectOperationalStateSchema', - 'OperationalStateMigrationBlockedError', - ]) { - assert.equal(reachable.has(internalSymbol), false, internalSymbol); - } - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - assert.equal('createManagedWorkspaceFromSource' in owner, false); - assert.equal('openManagedWorkspaceFromBinding' in owner, false); - await owner.close(); - } finally { - await rootOwner.close(); - } -}); - -afterEach(async () => { - await Promise.all( - cleanup.splice(0).map(async (path) => { - await rm(path, { recursive: true, force: true }); - }), - ); - // Control directories live outside the temporary roots, so they outlive the - // removal above and have to be reclaimed from the recorded rootIds. - await removeTrackedControlDirectories(); -}); - -test('opens one canonical baseline from a durable verified Git receipt', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const input = openRequest(sourceRoot); - - const first = await owner.openManagedWorkspaceBaseline(runtimeStore, input); - const retry = await owner.openManagedWorkspaceBaseline(runtimeStore, input); - const firstEvidence = inspectManagedWorkspaceExecutionHandleInternal(first.executionHandle); - const retryEvidence = inspectManagedWorkspaceExecutionHandleInternal(retry.executionHandle); - - assert.equal(first.created, true); - assert.equal(retry.created, false); - assert.equal('committedAt' in firstEvidence.receipt, false); - assert.deepEqual(retryEvidence.receipt, firstEvidence.receipt); - assert.equal(first.head.commitOid, firstEvidence.binding.baselineCommitOid); - assert.equal(first.head.treeOid, firstEvidence.binding.baselineTreeOid); - assert.equal(firstEvidence.receipt.changedFileCount, 2); - assert.equal(firstEvidence.receipt.deletedFileCount, 0); - assert.equal(firstEvidence.receipt.policyVersion, 1); - assert.equal( - firstEvidence.receipt.policyHash, - 'sha256:48eb80c9c8dd6c4d1e7a635dcc187488e0e0b20e2296b81bbaae0be7c5467341', - ); - assert.match(firstEvidence.receipt.treeDeltaDigest, /^sha256:[a-f0-9]{64}$/u); - assert.equal( - firstEvidence.receipt.treeDeltaDigest, - 'sha256:84a37411a467c40c9fd763ab128a6b23e56ec74ab3da2bd54de8d61e375244e8', - ); - assert.deepEqual( - await runtimeStore.readWorkspaceHead(input.workspaceId, input.workspaceEpochId), - first.head, - ); - const unrelatedStore = createSqliteRuntimeStore(join(root, 'other-root', 'runtime.sqlite')); - try { - await assert.rejects( - owner.openManagedWorkspaceBaseline(unrelatedStore, input), - /belongs to a different storage root/u, - ); - } finally { - unrelatedStore.close(); - } - - const receiptPath = join(dirname(firstEvidence.binding.worktreePath), 'baseline-receipt.json'); - await writeFile( - receiptPath, - `${JSON.stringify({ - ...firstEvidence.receipt, - treeDeltaDigest: `sha256:${'f'.repeat(64)}`, - })}\n`, - 'utf8', - ); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, input), - /does not match its verified Git boundary/u, - ); - await writeFile(receiptPath, `${JSON.stringify(firstEvidence.receipt)}\n`, 'utf8'); - - await writeFile( - receiptPath, - `${JSON.stringify({ - ...firstEvidence.receipt, - workspaceVersionId: `version_${'a'.repeat(32)}`, - epochOpenedEventId: `workspace_epoch_opened_${'b'.repeat(64)}`, - baselineAcceptedEventId: `workspace_baseline_accepted_${'c'.repeat(64)}`, - policyHash: `sha256:${'d'.repeat(64)}`, - })}\n`, - 'utf8', - ); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, input), - /does not match its verified Git boundary/u, - ); - await writeFile(receiptPath, `${JSON.stringify(firstEvidence.receipt)}\n`, 'utf8'); - - await writeFile( - receiptPath, - `${JSON.stringify({ ...firstEvidence.receipt, committedAt: Date.now() })}\n`, - 'utf8', - ); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, input), - /Invalid managed workspace baseline receipt/u, - ); - await writeFile(receiptPath, `${JSON.stringify(firstEvidence.receipt)}\n`, 'utf8'); - - const bindingPath = join(dirname(firstEvidence.binding.worktreePath), 'binding.json'); - await rm(receiptPath); - await rm(bindingPath); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, input), - /baseline receipt is unavailable/u, - ); - await assert.rejects(readFile(bindingPath, 'utf8'), { code: 'ENOENT' }); - assert.deepEqual( - await runtimeStore.readWorkspaceHead(input.workspaceId, input.workspaceEpochId), - first.head, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('reuses an orphan receipt after interruption before SQLite acceptance', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let interruptOnce = true; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - failpoint(point) { - if (point === 'after_baseline_receipt' && interruptOnce) { - interruptOnce = false; - throw new Error('simulated interruption after durable baseline receipt'); - } - }, - }); - const input = openRequest(sourceRoot); - - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, input), - /simulated interruption/u, - ); - assert.equal( - await runtimeStore.readWorkspaceHead(input.workspaceId, input.workspaceEpochId), - undefined, - ); - - const recovered = await owner.openManagedWorkspaceBaseline(runtimeStore, input); - const recoveredEvidence = inspectManagedWorkspaceExecutionHandleInternal( - recovered.executionHandle, - ); - assert.equal(recovered.created, true); - assert.equal(recoveredEvidence.receipt.baselineAcceptedEventId, recovered.head.acceptedEventId); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects a receipt read through a replaced instance-root symlink before parsing it', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - const acceptedEvidence = inspectManagedWorkspaceExecutionHandleInternal( - accepted.executionHandle, - ); - const instanceRoot = dirname(acceptedEvidence.binding.worktreePath); - const externalRoot = join(root, 'external-instance'); - await mkdir(externalRoot, { recursive: true }); - const movedInstance = join(externalRoot, 'instance'); - await rename(instanceRoot, movedInstance); - await symlink(movedInstance, instanceRoot, process.platform === 'win32' ? 'junction' : 'dir'); - await writeFile(join(movedInstance, 'baseline-receipt.json'), '{invalid-json', 'utf8'); - - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(sourceRoot)), - /owned directory escaped or changed identity/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('serializes concurrent baseline opens under the same managed workspace owner', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const [left, right] = await Promise.all([ - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(sourceRoot)), - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(sourceRoot)), - ]); - assert.deepEqual([left.created, right.created].sort(), [false, true]); - assert.deepEqual( - inspectManagedWorkspaceExecutionHandleInternal(left.executionHandle).receipt, - inspectManagedWorkspaceExecutionHandleInternal(right.executionHandle).receipt, - ); - assert.deepEqual(left.head, right.head); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects a leased authority database whose real root differs from its claimed path', async () => { - const root = await temporaryRoot(); - const actualRoot = join(root, 'actual-storage'); - const claimedRoot = join(root, 'claimed-storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: claimedRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const databaseLease = acquireOperationalStateDatabase(actualRoot); - const runtimeStore = createSqliteRuntimeStore(join(claimedRoot, 'runtime.sqlite'), { - databaseLease, - }); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, { - ...openRequest(sourceRoot), - }), - /belongs to a different storage root/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects a non-canonical SQLite file in the authenticated storage root', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'other.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, { - ...openRequest(join(root, 'unused-source')), - }), - /unavailable for this storage root/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects an in-memory SQLite authority store', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(':memory:'); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, { - ...openRequest(join(root, 'unused-source')), - }), - /unavailable for this storage root/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects a cross-root hard link to the canonical SQLite authority database', async () => { - const root = await temporaryRoot(); - const sourceStorageRoot = join(root, 'source-storage'); - const claimedStorageRoot = join(root, 'claimed-storage'); - await mkdir(claimedStorageRoot, { recursive: true }); - const sourceDatabasePath = join(sourceStorageRoot, 'runtime.sqlite'); - const sourceStore = createSqliteRuntimeStore(sourceDatabasePath); - sourceStore.close(); - await link(sourceDatabasePath, join(claimedStorageRoot, 'runtime.sqlite')); - - const capability = trackControlDirectory( - await resolveStorageRoot({ path: claimedStorageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(claimedStorageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, { - ...openRequest(join(root, 'unused-source')), - }), - /unavailable for this storage root/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects a canonical SQLite database copied from a different durable storage root', async () => { - const root = await temporaryRoot(); - const sourceStorageRoot = join(root, 'storage-a'); - const claimedStorageRoot = join(root, 'storage-b'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const request = openRequest(sourceRoot); - const sourceCapability = trackControlDirectory( - await resolveStorageRoot({ path: sourceStorageRoot, kind: 'interactive' }), - ); - const sourceRootOwner = await tryAcquireInteractiveRootOwner(sourceCapability); - assert.ok(sourceRootOwner); - const sourceStore = createSqliteRuntimeStore(join(sourceStorageRoot, 'runtime.sqlite')); - try { - const sourceOwner = await openManagedWorkspaceOwner({ - rootOwner: sourceRootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await sourceOwner.openManagedWorkspaceBaseline(sourceStore, request); - await sourceOwner.close(); - } finally { - sourceStore.close(); - await sourceRootOwner.close(); - } - - const claimedCapability = trackControlDirectory( - await resolveStorageRoot({ path: claimedStorageRoot, kind: 'interactive' }), - ); - await copyFile( - join(sourceStorageRoot, 'runtime.sqlite'), - join(claimedStorageRoot, 'runtime.sqlite'), - ); - const claimedRootOwner = await tryAcquireInteractiveRootOwner(claimedCapability); - assert.ok(claimedRootOwner); - const claimedStore = createSqliteRuntimeStore(join(claimedStorageRoot, 'runtime.sqlite')); - try { - const claimedOwner = await openManagedWorkspaceOwner({ - rootOwner: claimedRootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - claimedOwner.openManagedWorkspaceBaseline(claimedStore, request), - /different durable storage root/u, - ); - await claimedOwner.close(); - } finally { - claimedStore.close(); - await claimedRootOwner.close(); - } -}); - -test('preserves the durable database root binding across formal whole-root import', async () => { - const root = await temporaryRoot(); - const sourceStorageRoot = join(root, 'storage-a'); - const importedStorageRoot = join(root, 'storage-imported'); - const sourceCapability = trackControlDirectory( - await resolveStorageRoot({ path: sourceStorageRoot, kind: 'interactive' }), - ); - const sourceRootOwner = await tryAcquireInteractiveRootOwner(sourceCapability); - assert.ok(sourceRootOwner); - const sourceStore = createSqliteRuntimeStore(join(sourceStorageRoot, 'runtime.sqlite')); - try { - const sourceOwner = await openManagedWorkspaceOwner({ - rootOwner: sourceRootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - sourceOwner.openManagedWorkspaceBaseline( - sourceStore, - openRequest(join(root, 'missing-source')), - ), - /Directory is unavailable/u, - ); - await sourceOwner.close(); - } finally { - sourceStore.close(); - await sourceRootOwner.close(); - } - - await cp(sourceStorageRoot, importedStorageRoot, { recursive: true }); - await rm(sourceStorageRoot, { recursive: true, force: true }); - const importedCapability = await adoptStorageRootOnImport({ - path: importedStorageRoot, - kind: 'interactive', - expectedRootId: sourceCapability.rootId, - }); - const importedRootOwner = await tryAcquireInteractiveRootOwner(importedCapability); - assert.ok(importedRootOwner); - const importedStore = createSqliteRuntimeStore(join(importedStorageRoot, 'runtime.sqlite')); - try { - const importedOwner = await openManagedWorkspaceOwner({ - rootOwner: importedRootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - importedOwner.openManagedWorkspaceBaseline( - importedStore, - openRequest(join(root, 'missing-source')), - ), - /Directory is unavailable/u, - ); - await importedOwner.close(); - } finally { - importedStore.close(); - await importedRootOwner.close(); - } -}); - -test('rejects an authenticated owner after its durable root marker id is replaced', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - const markerPath = join(storageRoot, STORAGE_ROOT_MARKER_FILE); - const originalMarker = await readFile(markerPath, 'utf8'); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const marker = JSON.parse(originalMarker) as { rootId: string }; - marker.rootId = `${marker.rootId.startsWith('0') ? '1' : '0'}${marker.rootId.slice(1)}`; - await writeFile(markerPath, `${JSON.stringify(marker)}\n`, 'utf8'); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(join(root, 'missing-source'))), - /storage root|root identity|marker/u, - ); - await writeFile(markerPath, originalMarker, 'utf8'); - await owner.close(); - } finally { - await writeFile(markerPath, originalMarker, 'utf8'); - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects final admission when the root marker changes after post-commit artifact verification', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const request = openRequest(sourceRoot); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - const markerPath = join(storageRoot, STORAGE_ROOT_MARKER_FILE); - const originalMarker = await readFile(markerPath, 'utf8'); - let replaceOnce = true; - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - async failpoint(point) { - if ((point as string) !== 'after_post_commit_artifact_verification' || !replaceOnce) return; - replaceOnce = false; - const marker = JSON.parse(originalMarker) as { rootId: string }; - marker.rootId = `${marker.rootId.startsWith('0') ? '1' : '0'}${marker.rootId.slice(1)}`; - await writeFile(markerPath, `${JSON.stringify(marker)}\n`, 'utf8'); - }, - }); - try { - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, request), - /storage root|root identity|marker/u, - ); - assert.ok(await runtimeStore.readWorkspaceHead(request.workspaceId, request.workspaceEpochId)); - } finally { - await writeFile(markerPath, originalMarker, 'utf8'); - await owner.close(); - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects an authority database whose file identity changes after registration', { - skip: - process.platform === 'win32' - ? 'Windows cannot rename an open SQLite database to replace its file identity' - : false, -}, async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const databasePath = join(storageRoot, 'runtime.sqlite'); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(databasePath); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await rename(databasePath, `${databasePath}.replaced`); - await writeFile(databasePath, 'replacement database identity\n', 'utf8'); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(join(root, 'unused-source'))), - /database file identity changed/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('does not return an accepted baseline when runtime.sqlite is replaced after the initial root check', { - skip: - process.platform === 'win32' - ? 'Windows cannot rename an open SQLite database during the verification race' - : false, -}, async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const databasePath = join(storageRoot, 'runtime.sqlite'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const request = openRequest(sourceRoot); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(databasePath); - let replaceOnce = true; - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - async failpoint(point) { - if ((point as string) !== 'after_initial_store_root_validation' || !replaceOnce) return; - replaceOnce = false; - await rename(databasePath, `${databasePath}.detached`); - await writeFile(databasePath, 'replacement database identity\n', 'utf8'); - }, - }); - try { - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, request), - /database file identity changed/u, - ); - } finally { - await owner.close(); - runtimeStore.close(); - await rootOwner.close(); - } - - await Promise.all([ - rm(databasePath, { force: true }), - rm(`${databasePath}-wal`, { force: true }), - rm(`${databasePath}-shm`, { force: true }), - ]); - // SQLite may keep the accepted transaction in a WAL that remains associated - // with the original pathname, so the detached main file is not a portable - // authority read after this adversarial rename. The safety invariant is that - // the replacement canonical database never receives that head and no usable - // baseline was returned above. - const replacementStore = createSqliteRuntimeStore(databasePath); - try { - assert.equal( - await replacementStore.readWorkspaceHead(request.workspaceId, request.workspaceEpochId), - undefined, - ); - } finally { - replacementStore.close(); - } -}); - -test('rejects runtime.sqlite when the canonical database path is a symlink', async (context) => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const externalRoot = join(root, 'external'); - await mkdir(storageRoot, { recursive: true }); - const externalPath = join(externalRoot, 'runtime.sqlite'); - createSqliteRuntimeStore(externalPath).close(); - try { - await symlink(externalPath, join(storageRoot, 'runtime.sqlite'), 'file'); - } catch (error) { - const code = (error as NodeJS.ErrnoException).code; - if (process.platform === 'win32' && (code === 'EPERM' || code === 'EACCES')) { - context.skip('File symlinks require Windows developer mode or elevated privileges'); - return; - } - throw error; - } - - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, { - ...openRequest(join(root, 'unused-source')), - }), - /unavailable for this storage root/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('keeps an orphan receipt across SQLite rollback and accepts it on retry', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - let interruptOnce = true; - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite'), { - failpoint(point) { - if (point === 'after_workspace_version_event_insert' && interruptOnce) { - interruptOnce = false; - throw new Error('simulated SQLite baseline rollback'); - } - }, - }); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const input = openRequest(sourceRoot); - - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, input), - /simulated SQLite baseline rollback/u, - ); - assert.equal( - await runtimeStore.readWorkspaceHead(input.workspaceId, input.workspaceEpochId), - undefined, - ); - - const recovered = await owner.openManagedWorkspaceBaseline(runtimeStore, input); - const recoveredEvidence = inspectManagedWorkspaceExecutionHandleInternal( - recovered.executionHandle, - ); - assert.equal(recovered.created, true); - assert.equal(recoveredEvidence.receipt.workspaceVersionId, recovered.head.workspaceVersionId); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('accepts the same durable receipt after a real process crash before SQLite authority', { - timeout: 60_000, -}, async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const child = spawn( - process.execPath, - [fileURLToPath(new URL('./fixtures/git-workspace-service-crash-child.js', import.meta.url))], - { - env: { - ...process.env, - MAKA_GIT_WORKSPACE_STORAGE: storageRoot, - MAKA_GIT_WORKSPACE_SOURCE: sourceRoot, - MAKA_GIT_WORKSPACE_EXECUTABLE: gitExecutablePath, - MAKA_GIT_WORKSPACE_SHA256: gitExecutableSha256, - MAKA_GIT_WORKSPACE_FAILPOINT: 'after_baseline_receipt', - MAKA_GIT_WORKSPACE_ACTION: 'baseline-receipt', - }, - stdio: ['ignore', 'pipe', 'pipe'], - }, - ); - try { - await waitForReady(child, 30_000); - const durableBeforeCrash = JSON.parse( - await readFile(baselineReceiptPath(storageRoot, openRequest(sourceRoot)), 'utf8'), - ); - child.kill('SIGKILL'); - await waitForExit(child); - - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline(runtimeStore, { - ...openRequest(sourceRoot), - }); - const acceptedEvidence = inspectManagedWorkspaceExecutionHandleInternal( - accepted.executionHandle, - ); - assert.equal(accepted.created, true); - assert.equal(acceptedEvidence.receipt.baselineAcceptedEventId, accepted.head.acceptedEventId); - assert.deepEqual(acceptedEvidence.receipt, durableBeforeCrash); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } - } finally { - if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); - } -}); - -test('reopens the exact accepted baseline after a real crash before post-commit verification', { - timeout: 60_000, -}, async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const child = spawn( - process.execPath, - [fileURLToPath(new URL('./fixtures/git-workspace-service-crash-child.js', import.meta.url))], - { - env: { - ...process.env, - MAKA_GIT_WORKSPACE_STORAGE: storageRoot, - MAKA_GIT_WORKSPACE_SOURCE: sourceRoot, - MAKA_GIT_WORKSPACE_EXECUTABLE: gitExecutablePath, - MAKA_GIT_WORKSPACE_SHA256: gitExecutableSha256, - MAKA_GIT_WORKSPACE_FAILPOINT: 'after_baseline_authority_commit', - MAKA_GIT_WORKSPACE_ACTION: 'baseline-receipt', - }, - stdio: ['ignore', 'pipe', 'pipe'], - }, - ); - try { - await waitForReady(child, 30_000); - const durableBeforeCrash = JSON.parse( - await readFile(baselineReceiptPath(storageRoot, openRequest(sourceRoot)), 'utf8'), - ); - child.kill('SIGKILL'); - await waitForExit(child); - - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const reopened = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - const reopenedEvidence = inspectManagedWorkspaceExecutionHandleInternal( - reopened.executionHandle, - ); - assert.equal(reopened.created, false); - assert.deepEqual(reopenedEvidence.receipt, durableBeforeCrash); - assert.equal(reopened.head.acceptedEventId, reopenedEvidence.receipt.baselineAcceptedEventId); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } - } finally { - if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); - } -}); - -test('reissues execution authority after a real process crash during admission verification', { - timeout: 60_000, -}, async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const child = spawn( - process.execPath, - [fileURLToPath(new URL('./fixtures/git-workspace-service-crash-child.js', import.meta.url))], - { - env: { - ...process.env, - MAKA_GIT_WORKSPACE_STORAGE: storageRoot, - MAKA_GIT_WORKSPACE_SOURCE: sourceRoot, - MAKA_GIT_WORKSPACE_EXECUTABLE: gitExecutablePath, - MAKA_GIT_WORKSPACE_SHA256: gitExecutableSha256, - MAKA_GIT_WORKSPACE_FAILPOINT: 'after_execution_artifact_verification', - MAKA_GIT_WORKSPACE_ACTION: 'execution-admission', - }, - stdio: ['ignore', 'pipe', 'pipe'], - }, - ); - try { - await waitForReady(child, 30_000); - child.kill('SIGKILL'); - await waitForExit(child); - - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const reopened = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - let observedCwd: string | undefined; - await owner.withManagedWorkspaceExecution(reopened.executionHandle, async (scope) => { - const context = inspectManagedWorkspaceExecutionScopeInternal(scope); - observedCwd = context.cwd; - assert.equal(await readFile(join(context.cwd, 'tracked.txt'), 'utf8'), 'tracked\n'); - }); - assert.equal( - observedCwd, - inspectManagedWorkspaceExecutionHandleInternal(reopened.executionHandle).binding - .worktreePath, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } - } finally { - if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL'); - } -}); - -test('rejects a source tree containing a non-UTF-8 Git path', { - skip: process.platform === 'win32', -}, async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - await commitInvalidUtf8Path(sourceRoot); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(sourceRoot)), - /requires Git paths to be valid UTF-8/u, - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -async function temporaryRoot(): Promise { - const root = await mkdtemp(join(tmpdir(), 'maka-managed-workspace-baseline-')); - cleanup.push(root); - return root; -} - -async function findGitExecutable(): Promise { - const command = process.platform === 'win32' ? 'where.exe' : 'which'; - const { stdout } = await execFileAsync(command, ['git'], { encoding: 'utf8' }); - const first = stdout - .split(/\r?\n/u) - .map((line) => line.trim()) - .find(Boolean); - if (!first) throw new Error('Git executable is unavailable for integration tests'); - return realpath(first); -} - -async function sha256File(path: string): Promise<`sha256:${string}`> { - const hash = createHash('sha256'); - for await (const chunk of createReadStream(path)) hash.update(chunk); - return `sha256:${hash.digest('hex')}`; -} - -async function createEligibleSource(sourceRoot: string): Promise { - await mkdir(sourceRoot, { recursive: true }); - await git(sourceRoot, 'init', '--quiet'); - await writeFile(join(sourceRoot, 'tracked.txt'), 'tracked\n', 'utf8'); - await writeFile(join(sourceRoot, '.gitignore'), '.maka-workspace.json\n', 'utf8'); - await writeFile(join(sourceRoot, '.maka-workspace.json'), '{"host":"metadata"}\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt', '.gitignore'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'source baseline', - ); - return realpath(sourceRoot); -} - -async function commitInvalidUtf8Path(sourceRoot: string): Promise { - const invalidPath = Buffer.from([0xff]); - const blobOid = gitWithInput(sourceRoot, ['hash-object', '-w', '--stdin'], 'invalid\n') - .toString('ascii') - .trim(); - const existingEntries = gitWithInput(sourceRoot, ['ls-tree', '-z', 'HEAD']); - const invalidEntry = Buffer.concat([ - Buffer.from(`100644 blob ${blobOid}\t`, 'ascii'), - invalidPath, - Buffer.from([0]), - ]); - const treeOid = gitWithInput( - sourceRoot, - ['mktree', '-z'], - Buffer.concat([existingEntries, invalidEntry]), - ) - .toString('ascii') - .trim(); - const parentOid = await git(sourceRoot, 'rev-parse', 'HEAD'); - const commitOid = gitWithInput( - sourceRoot, - [ - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit-tree', - treeOid, - '-p', - parentOid, - ], - 'add invalid path\n', - ) - .toString('ascii') - .trim(); - - await git(sourceRoot, 'update-ref', 'HEAD', commitOid, parentOid); - await git(sourceRoot, 'read-tree', treeOid); - // Keep Git status clean without asking the filesystem to materialize the invalid path. - gitWithInput( - sourceRoot, - ['update-index', '--skip-worktree', '-z', '--stdin'], - Buffer.concat([invalidPath, Buffer.from([0])]), - ); -} - -function openRequest(sourceRoot: string) { - return { - repositoryId: 'repository_11111111111111111111111111111111', - workspaceId: 'workspace_22222222222222222222222222222222', - workspaceEpochId: 'epoch_33333333333333333333333333333333', - workspaceInstanceId: 'instance_44444444444444444444444444444444', - sourceRoot, - } as const; -} - -function baselineReceiptPath( - storageRoot: string, - identity: ReturnType, -): string { - const compact = (value: string) => createHash('sha256').update(value).digest('hex').slice(0, 20); - return join( - storageRoot, - 'managed-workspaces', - 'w', - compact(identity.workspaceId), - 'e', - compact(identity.workspaceEpochId), - 'i', - compact(identity.workspaceInstanceId), - 'baseline-receipt.json', - ); -} - -async function git(cwd: string, ...args: string[]): Promise { - const { stdout } = await execFileAsync('git', args, { - cwd, - encoding: 'utf8', - maxBuffer: 8 * 1024 * 1024, - }); - return stdout.trim(); -} - -function gitWithInput( - cwd: string, - args: readonly string[], - input: string | Buffer = Buffer.alloc(0), -): Buffer { - return execFileSync('git', args, { - cwd, - input, - maxBuffer: 8 * 1024 * 1024, - }); -} - -function waitForReady(child: ReturnType, timeoutMs: number): Promise { - return new Promise((resolve, reject) => { - let stdout = ''; - let stderr = ''; - const timeout = setTimeout(() => { - cleanup(); - reject(new Error(`Managed baseline crash child did not become ready: ${stderr}`)); - }, timeoutMs); - const onStdout = (chunk: unknown) => { - stdout += String(chunk); - if (stdout.includes('READY\n')) { - cleanup(); - resolve(); - } - }; - const onStderr = (chunk: unknown) => { - stderr += String(chunk); - }; - const onExit = (code: number | null, signal: NodeJS.Signals | null) => { - cleanup(); - reject(new Error(`Managed baseline crash child exited early: ${code}/${signal} ${stderr}`)); - }; - const cleanup = () => { - clearTimeout(timeout); - child.stdout?.off('data', onStdout); - child.stderr?.off('data', onStderr); - child.off('exit', onExit); - }; - child.stdout?.on('data', onStdout); - child.stderr?.on('data', onStderr); - child.once('exit', onExit); - }); -} - -function waitForExit(child: ReturnType): Promise { - if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(); - return new Promise((resolve, reject) => { - child.once('exit', () => resolve()); - child.once('error', reject); - }); -} diff --git a/packages/storage/src/__tests__/managed-workspace-execution-authority-internal.test.ts b/packages/storage/src/__tests__/managed-workspace-execution-authority-internal.test.ts deleted file mode 100644 index 4c8a603404..0000000000 --- a/packages/storage/src/__tests__/managed-workspace-execution-authority-internal.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { - issueManagedWorkspaceExecutionScopeInternal, - ManagedWorkspaceExecutionAuthorityError, - requireManagedWorkspaceExecutionScopeInternal, - revokeManagedWorkspaceExecutionScopeInternal, -} from '../managed-workspace-execution-authority-internal.js'; - -test('resolves an active execution scope only for its issuing owner', () => { - const issuer = {}; - const otherOwner = {}; - const scope = issueManagedWorkspaceExecutionScopeInternal(issuer, { - provisioning: 'canonical_tree_only_v1', - workspaceEffect: 'none', - cwd: '/managed/worktree', - binding: Object.freeze({}) as never, - head: Object.freeze({}) as never, - }); - - assert.equal( - requireManagedWorkspaceExecutionScopeInternal(issuer, scope).cwd, - '/managed/worktree', - ); - assert.throws( - () => requireManagedWorkspaceExecutionScopeInternal(otherOwner, scope), - (error) => - error instanceof ManagedWorkspaceExecutionAuthorityError && - error.code === 'managed_workspace_execution_scope_invalid', - ); - - revokeManagedWorkspaceExecutionScopeInternal(issuer, scope); - assert.throws( - () => requireManagedWorkspaceExecutionScopeInternal(issuer, scope), - (error) => - error instanceof ManagedWorkspaceExecutionAuthorityError && - error.code === 'managed_workspace_execution_scope_expired', - ); -}); diff --git a/packages/storage/src/__tests__/managed-workspace-owner.test.ts b/packages/storage/src/__tests__/managed-workspace-owner.test.ts deleted file mode 100644 index 91073a12a2..0000000000 --- a/packages/storage/src/__tests__/managed-workspace-owner.test.ts +++ /dev/null @@ -1,1013 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'node:assert/strict'; -import { execFile } from 'node:child_process'; -import { createHash } from 'node:crypto'; -import { createReadStream, existsSync } from 'node:fs'; -import { mkdir, mkdtemp, realpath, rename, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { setTimeout as delay } from 'node:timers/promises'; -import { promisify } from 'node:util'; -import { afterEach, before, test } from 'node:test'; -import { - ManagedWorkspaceOwnerError, - openManagedWorkspaceOwner, - type ManagedWorkspaceExecutionHandle, - type ManagedWorkspaceExecutionScope, -} from '../managed-workspace-owner.js'; -import { - managedWorkspaceExecutionAuthorityTestSupport, - ManagedWorkspaceExecutionAuthorityError, -} from '../managed-workspace-execution-authority-internal.js'; -import { resolveStorageRoot, tryAcquireInteractiveRootOwner } from '../root-authority.js'; -import { createSqliteRuntimeStore } from '../sqlite-runtime-store.js'; -import { - removeTrackedControlDirectories, - trackControlDirectory, -} from './fixtures/control-directory-hygiene.js'; - -const execFileAsync = promisify(execFile); -const cleanup: string[] = []; -const { - inspectHandle: inspectManagedWorkspaceExecutionHandleInternal, - inspectScope: inspectManagedWorkspaceExecutionScopeInternal, -} = managedWorkspaceExecutionAuthorityTestSupport; -let gitExecutablePath: string; -let gitExecutableSha256: `sha256:${string}`; - -before(async () => { - gitExecutablePath = await findGitExecutable(); - gitExecutableSha256 = await sha256File(gitExecutablePath); -}); - -afterEach(async () => { - await Promise.all( - cleanup.splice(0).map(async (path) => { - await rm(path, { recursive: true, force: true }); - }), - ); - // Control directories live outside the temporary roots, so they outlive the - // removal above and have to be reclaimed from the recorded rootIds. - await removeTrackedControlDirectories(); -}); - -test('admits exactly one managed workspace owner for an interactive root owner', async () => { - const storageRoot = await temporaryRoot(); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - try { - const managedOwner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - - await assert.rejects( - openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }), - isOwnerError('managed_workspace_owner_conflict'), - ); - await managedOwner.close(); - await managedOwner.close(); - } finally { - await rootOwner.close(); - } -}); - -test('releases a partial owner claim when pinned Git initialization fails', async () => { - const storageRoot = await temporaryRoot(); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - try { - await assert.rejects( - openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: `sha256:${'0'.repeat(64)}`, - }, - }), - isOwnerError('managed_workspace_owner_unavailable'), - ); - - const retry = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - assert.equal(retry.state, 'ready'); - await retry.close(); - } finally { - await rootOwner.close(); - } -}); - -test('creates an accepted managed baseline only through the active owner', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - const { binding, receipt } = inspectManagedWorkspaceExecutionHandleInternal( - accepted.executionHandle, - ); - - assert.equal(binding.sourceTreeOid, binding.baselineTreeOid); - assert.notEqual(binding, receipt.binding); - assert.deepEqual(binding, receipt.binding); - assert.equal(existsSync(join(binding.worktreePath, '.maka-workspace.json')), false); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('publishes only a revocable execution scope through its accepted handle', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - - assert.equal('binding' in accepted, false); - assert.equal('receipt' in accepted, false); - let retainedScope: unknown; - const execution = await owner.withManagedWorkspaceExecution( - accepted.executionHandle, - async (scope) => { - retainedScope = scope; - assert.equal('cwd' in scope, false); - assert.equal(scope.kind, 'managed_workspace_execution_scope_v1'); - await assert.rejects( - () => - owner.executeReadOnlyFilesystemOperation(scope, { - kind: 'read', - path: 'README.md', - }), - (error) => - error instanceof ManagedWorkspaceOwnerError && - error.code === 'managed_workspace_worker_unavailable', - ); - return 'admitted'; - }, - ); - - assert.equal(execution, 'admitted'); - assert.deepEqual(retainedScope, { kind: 'managed_workspace_execution_scope_v1' }); - assert.throws( - () => - inspectManagedWorkspaceExecutionScopeInternal({ - kind: 'managed_workspace_execution_scope_v1', - }), - (error) => - error instanceof ManagedWorkspaceExecutionAuthorityError && - error.code === 'managed_workspace_execution_scope_invalid', - ); - assert.throws( - () => - inspectManagedWorkspaceExecutionScopeInternal( - retainedScope as ManagedWorkspaceExecutionScope, - ), - (error) => - error instanceof ManagedWorkspaceExecutionAuthorityError && - error.code === 'managed_workspace_execution_scope_expired', - ); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('does not let caller-mutated head state or a shadowed public reader forge execution authority', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let callbackCalled = false; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - const forgedHead = { - ...accepted.head, - workspaceVersionId: `version_${'a'.repeat(32)}`, - commitOid: 'a'.repeat(40), - treeOid: 'b'.repeat(40), - revision: accepted.head.revision + 1, - }; - assert.throws(() => Object.assign(accepted.head, forgedHead), TypeError); - Object.defineProperty(runtimeStore, 'readWorkspaceHead', { - configurable: true, - value: async () => forgedHead, - }); - - await owner.withManagedWorkspaceExecution(accepted.executionHandle, async (scope) => { - callbackCalled = true; - const context = inspectManagedWorkspaceExecutionScopeInternal(scope); - assert.equal(context.head.workspaceVersionId, accepted.head.workspaceVersionId); - assert.equal(context.head.commitOid, accepted.head.commitOid); - assert.equal(context.head.treeOid, accepted.head.treeOid); - }); - assert.equal(callbackCalled, true); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('quarantines drift introduced after execution artifact verification before publishing cwd', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let executionFailpointArmed = false; - let callbackCalled = false; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - async failpoint(point) { - if ( - executionFailpointArmed && - (point as string) === 'after_execution_artifact_verification' - ) { - executionFailpointArmed = false; - const acceptedEvidence = inspectManagedWorkspaceExecutionHandleInternal( - accepted.executionHandle, - ); - await writeFile( - join(acceptedEvidence.binding.worktreePath, 'tracked.txt'), - 'external drift after verification\n', - 'utf8', - ); - } - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - executionFailpointArmed = true; - - await assert.rejects( - owner.withManagedWorkspaceExecution(accepted.executionHandle, async () => { - callbackCalled = true; - }), - isOwnerError('managed_workspace_quarantined'), - ); - - assert.equal(callbackCalled, false); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects execution when runtime.sqlite detaches from its canonical path after verification', { - skip: - process.platform === 'win32' - ? 'Open SQLite files cannot be renamed reliably on Windows' - : false, -}, async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const databasePath = join(storageRoot, 'runtime.sqlite'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(databasePath); - let executionFailpointArmed = false; - let callbackCalled = false; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - async failpoint(point) { - if ( - executionFailpointArmed && - (point as string) === 'after_execution_artifact_verification' - ) { - executionFailpointArmed = false; - await rename(databasePath, `${databasePath}.detached`); - } - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - executionFailpointArmed = true; - - await assert.rejects( - owner.withManagedWorkspaceExecution(accepted.executionHandle, async () => { - callbackCalled = true; - }), - /database file identity changed|belongs to a different storage root/u, - ); - assert.equal(callbackCalled, false); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects a forged execution handle before invoking the tool callback', async () => { - const storageRoot = await temporaryRoot(); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - let callbackCalled = false; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - - await assert.rejects( - owner.withManagedWorkspaceExecution( - Object.freeze({ - kind: 'managed_workspace_execution_handle_v1', - }) as ManagedWorkspaceExecutionHandle, - async () => { - callbackCalled = true; - }, - ), - isOwnerError('managed_workspace_execution_handle_invalid'), - ); - - assert.equal(callbackCalled, false); - await owner.close(); - } finally { - await rootOwner.close(); - } -}); - -test('rejects an execution handle issued by another managed workspace owner', async () => { - const root = await temporaryRoot(); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const leftCapability = trackControlDirectory( - await resolveStorageRoot({ path: join(root, 'left-storage'), kind: 'interactive' }), - ); - const rightCapability = trackControlDirectory( - await resolveStorageRoot({ path: join(root, 'right-storage'), kind: 'interactive' }), - ); - const leftRootOwner = await tryAcquireInteractiveRootOwner(leftCapability); - const rightRootOwner = await tryAcquireInteractiveRootOwner(rightCapability); - assert.ok(leftRootOwner); - assert.ok(rightRootOwner); - const leftStore = createSqliteRuntimeStore(join(leftCapability.canonicalPath, 'runtime.sqlite')); - try { - const leftOwner = await openManagedWorkspaceOwner({ - rootOwner: leftRootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const rightOwner = await openManagedWorkspaceOwner({ - rootOwner: rightRootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await leftOwner.openManagedWorkspaceBaseline( - leftStore, - openRequest(sourceRoot), - ); - - await assert.rejects( - rightOwner.withManagedWorkspaceExecution(accepted.executionHandle, async () => {}), - isOwnerError('managed_workspace_execution_handle_invalid'), - ); - - await leftOwner.close(); - await rightOwner.close(); - } finally { - leftStore.close(); - await leftRootOwner.close(); - await rightRootOwner.close(); - } -}); - -test('drains an admitted managed execution before owner close completes', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let releaseExecution!: () => void; - const executionMayFinish = new Promise((resolve) => { - releaseExecution = resolve; - }); - let executionAdmitted!: () => void; - const executionStarted = new Promise((resolve) => { - executionAdmitted = resolve; - }); - let executing: Promise | undefined; - let closing: Promise | undefined; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - executing = owner.withManagedWorkspaceExecution(accepted.executionHandle, async () => { - executionAdmitted(); - await executionMayFinish; - }); - await executionStarted; - - let closeSettled = false; - closing = owner.close().then(() => { - closeSettled = true; - }); - assert.equal( - await Promise.race([closing.then(() => 'closed'), delay(250, 'pending')]), - 'pending', - ); - assert.equal(closeSettled, false); - await assert.rejects( - owner.withManagedWorkspaceExecution(accepted.executionHandle, async () => {}), - isOwnerError('managed_workspace_owner_closing'), - ); - - releaseExecution(); - await executing; - await closing; - assert.equal(owner.state, 'closed'); - } finally { - releaseExecution(); - await Promise.allSettled([executing, closing].filter((value) => value !== undefined)); - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('routes read-only execution through the owner-bound worker bridge', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - const workerCalls: Array<{ cwd: string; operation: { kind: string } }> = []; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - filesystemWorker: { - async execute(input) { - workerCalls.push(input); - return { kind: 'read', content: 'ok' }; - }, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - - await owner.withManagedWorkspaceExecution(accepted.executionHandle, async (scope) => { - assert.deepEqual( - await owner.executeReadOnlyFilesystemOperation(scope, { - kind: 'read', - path: 'README.md', - }), - { kind: 'read', content: 'ok' }, - ); - }); - - assert.equal(workerCalls.length, 1); - assert.equal(workerCalls[0]?.operation.kind, 'read'); - assert.equal(workerCalls[0]?.cwd.endsWith('worktree'), true); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('revokes the scope and releases owner residency when the filesystem worker fails', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let retainedScope: ManagedWorkspaceExecutionScope | undefined; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - filesystemWorker: { - async execute() { - throw new Error('simulated filesystem worker crash'); - }, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - - await assert.rejects( - owner.withManagedWorkspaceExecution(accepted.executionHandle, async (scope) => { - retainedScope = scope; - return await owner.executeReadOnlyFilesystemOperation(scope, { - kind: 'read', - path: 'README.md', - }); - }), - /simulated filesystem worker crash/u, - ); - - if (!retainedScope) throw new Error('Execution callback did not expose its scope'); - const expiredScope = retainedScope; - assert.throws( - () => inspectManagedWorkspaceExecutionScopeInternal(expiredScope), - /execution scope has expired/u, - ); - await owner.close(); - assert.equal(owner.state, 'closed'); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects owner close reentrancy from an active execution callback', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - - await owner.withManagedWorkspaceExecution(accepted.executionHandle, async () => { - const disposition = await Promise.race([ - owner.close().then( - () => 'closed' as const, - (error: unknown) => error, - ), - delay(250, 'pending' as const), - ]); - assert.ok(isOwnerError('managed_workspace_owner_reentrant_close')(disposition)); - }); - - assert.equal(owner.state, 'ready'); - await owner.close(); - assert.equal(owner.state, 'closed'); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('allows concurrent read-only scopes for one handle and close drains both', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let releaseFirst!: () => void; - const firstReleased = new Promise((resolve) => { - releaseFirst = resolve; - }); - let releaseSecond!: () => void; - const secondReleased = new Promise((resolve) => { - releaseSecond = resolve; - }); - let bothAdmitted!: () => void; - const bothScopesActive = new Promise((resolve) => { - bothAdmitted = resolve; - }); - let activeScopes = 0; - let executions: Promise[] = []; - let closing: Promise | undefined; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - const execute = (released: Promise) => - owner.withManagedWorkspaceExecution(accepted.executionHandle, async (scope) => { - assert.equal(inspectManagedWorkspaceExecutionScopeInternal(scope).workspaceEffect, 'none'); - activeScopes += 1; - if (activeScopes === 2) bothAdmitted(); - await released; - activeScopes -= 1; - }); - - executions = [execute(firstReleased), execute(secondReleased)]; - assert.equal( - await Promise.race([bothScopesActive.then(() => 'active'), delay(20_000, 'timeout')]), - 'active', - ); - assert.equal(activeScopes, 2); - - closing = owner.close(); - assert.equal( - await Promise.race([closing.then(() => 'closed'), delay(250, 'pending')]), - 'pending', - ); - releaseFirst(); - assert.equal( - await Promise.race([closing.then(() => 'closed'), delay(250, 'pending')]), - 'pending', - ); - releaseSecond(); - await Promise.all(executions); - await closing; - assert.equal(owner.state, 'closed'); - } finally { - releaseFirst(); - releaseSecond(); - await Promise.allSettled([...executions, closing].filter((value) => value !== undefined)); - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('expires the execution scope and releases owner residency when its callback rejects', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let retainedScope: ManagedWorkspaceExecutionScope | undefined; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - - await assert.rejects( - owner.withManagedWorkspaceExecution(accepted.executionHandle, async (scope) => { - retainedScope = scope; - throw new Error('simulated managed tool failure'); - }), - /simulated managed tool failure/u, - ); - if (!retainedScope) throw new Error('Execution callback did not expose its scope'); - const expiredScope = retainedScope; - assert.throws( - () => inspectManagedWorkspaceExecutionScopeInternal(expiredScope), - /execution scope has expired/u, - ); - await owner.close(); - assert.equal(owner.state, 'closed'); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('drains an admitted workspace operation before closing and rejects new work', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - let releaseOperation!: () => void; - const operationMayFinish = new Promise((resolve) => { - releaseOperation = resolve; - }); - let operationAdmitted!: () => void; - const operationReachedFailpoint = new Promise((resolve) => { - operationAdmitted = resolve; - }); - let creating: - | ReturnType< - Awaited>['openManagedWorkspaceBaseline'] - > - | undefined; - let closing: Promise | undefined; - let rootClosing: Promise | undefined; - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - failpoint: async (point) => { - if (point !== 'after_worktree_materialized') return; - operationAdmitted(); - await operationMayFinish; - }, - }); - creating = owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(sourceRoot)); - await operationReachedFailpoint; - - let closeSettled = false; - closing = owner.close().then(() => { - closeSettled = true; - }); - let rootCloseSettled = false; - rootClosing = rootOwner.close().then(() => { - rootCloseSettled = true; - }); - const rootCloseDisposition = await Promise.race([ - rootClosing.then(() => 'closed' as const), - delay(250, 'pending' as const), - ]); - - assert.equal(owner.state, 'closing'); - assert.equal(closeSettled, false); - assert.equal(rootCloseSettled, false); - assert.equal(rootCloseDisposition, 'pending'); - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(sourceRoot)), - isOwnerError('managed_workspace_owner_closing'), - ); - - releaseOperation(); - await creating; - await closing; - await rootClosing; - assert.equal(owner.state, 'closed'); - } finally { - releaseOperation(); - await Promise.allSettled( - [creating, closing, rootClosing].filter((value) => value !== undefined), - ); - runtimeStore.close(); - await rootOwner.close(); - } -}); - -test('rejects external drift instead of reopening a non-ready workspace', async () => { - const root = await temporaryRoot(); - const storageRoot = join(root, 'storage'); - const sourceRoot = await createEligibleSource(join(root, 'source')); - const capability = trackControlDirectory( - await resolveStorageRoot({ path: storageRoot, kind: 'interactive' }), - ); - const rootOwner = await tryAcquireInteractiveRootOwner(capability); - assert.ok(rootOwner); - const runtimeStore = createSqliteRuntimeStore(join(storageRoot, 'runtime.sqlite')); - try { - const owner = await openManagedWorkspaceOwner({ - rootOwner, - gitRuntime: { - executablePath: gitExecutablePath, - expectedSha256: gitExecutableSha256, - }, - }); - const accepted = await owner.openManagedWorkspaceBaseline( - runtimeStore, - openRequest(sourceRoot), - ); - const { binding } = inspectManagedWorkspaceExecutionHandleInternal(accepted.executionHandle); - await writeFile(join(binding.worktreePath, 'tracked.txt'), 'external drift\n', 'utf8'); - - await assert.rejects( - owner.openManagedWorkspaceBaseline(runtimeStore, openRequest(sourceRoot)), - (error: unknown) => - error instanceof Error && - error.name === 'GitWorkspaceServiceError' && - 'code' in error && - error.code === 'managed_workspace_drifted', - ); - - assert.equal(existsSync(binding.worktreePath), true); - await owner.close(); - } finally { - runtimeStore.close(); - await rootOwner.close(); - } -}); - -function isOwnerError(code: string): (error: unknown) => boolean { - return (error) => error instanceof ManagedWorkspaceOwnerError && error.code === code; -} - -async function temporaryRoot(): Promise { - const root = await mkdtemp(join(tmpdir(), 'maka-managed-workspace-owner-')); - cleanup.push(root); - return root; -} - -async function findGitExecutable(): Promise { - const command = process.platform === 'win32' ? 'where.exe' : 'which'; - const { stdout } = await execFileAsync(command, ['git'], { encoding: 'utf8' }); - const first = stdout - .split(/\r?\n/u) - .map((line) => line.trim()) - .find(Boolean); - if (!first) throw new Error('Git executable is unavailable for integration tests'); - return realpath(first); -} - -async function sha256File(path: string): Promise<`sha256:${string}`> { - const hash = createHash('sha256'); - for await (const chunk of createReadStream(path)) hash.update(chunk); - return `sha256:${hash.digest('hex')}`; -} - -async function createEligibleSource(sourceRoot: string): Promise { - await mkdir(sourceRoot, { recursive: true }); - await git(sourceRoot, 'init', '--quiet'); - await writeFile(join(sourceRoot, 'tracked.txt'), 'tracked\n', 'utf8'); - await writeFile(join(sourceRoot, '.gitignore'), '.maka-workspace.json\n', 'utf8'); - await writeFile(join(sourceRoot, '.maka-workspace.json'), '{"host":"metadata"}\n', 'utf8'); - await git(sourceRoot, 'add', 'tracked.txt', '.gitignore'); - await git( - sourceRoot, - '-c', - 'user.name=Maka Test', - '-c', - 'user.email=test@maka.invalid', - 'commit', - '--quiet', - '-m', - 'source baseline', - ); - return realpath(sourceRoot); -} - -function openRequest(sourceRoot: string) { - return { - repositoryId: 'repository_11111111111111111111111111111111', - workspaceId: 'workspace_22222222222222222222222222222222', - workspaceEpochId: 'epoch_33333333333333333333333333333333', - workspaceInstanceId: 'instance_44444444444444444444444444444444', - sourceRoot, - } as const; -} - -async function git(cwd: string, ...args: string[]): Promise { - const { stdout } = await execFileAsync('git', args, { - cwd, - encoding: 'utf8', - maxBuffer: 8 * 1024 * 1024, - }); - return stdout.trim(); -} diff --git a/packages/storage/src/__tests__/managed-workspace-worker-bridge-internal.test.ts b/packages/storage/src/__tests__/managed-workspace-worker-bridge-internal.test.ts deleted file mode 100644 index 188271d2ae..0000000000 --- a/packages/storage/src/__tests__/managed-workspace-worker-bridge-internal.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { createReadOnlyPermissionProfile } from '@maka/core/permission-profile'; -import { - issueManagedWorkspaceExecutionScopeInternal, - revokeManagedWorkspaceExecutionScopeInternal, -} from '../managed-workspace-execution-authority-internal.js'; -import { - createManagedWorkspaceWorkerBridgeInternal, - ManagedWorkspaceWorkerBridgeError, -} from '../managed-workspace-worker-bridge-internal.js'; - -function scopeFor(ownerToken: object) { - return issueManagedWorkspaceExecutionScopeInternal(ownerToken, { - provisioning: 'canonical_tree_only_v1', - workspaceEffect: 'none', - cwd: '/managed/worktree', - binding: Object.freeze({}) as never, - head: Object.freeze({}) as never, - }); -} - -test('injects the owner-bound cwd and read-only boundary for allowed operations', async () => { - const ownerToken = {}; - const calls: unknown[] = []; - const bridge = createManagedWorkspaceWorkerBridgeInternal(ownerToken, { - async execute(input) { - calls.push(input); - switch (input.operation.kind) { - case 'read': - return { kind: 'read', content: 'ok' }; - case 'glob': - return { kind: 'glob', files: [] }; - case 'grep': - return { kind: 'grep', matches: [] }; - } - }, - }); - const scope = scopeFor(ownerToken); - - assert.deepEqual(await bridge.execute(scope, { kind: 'read', path: 'README.md' }), { - kind: 'read', - content: 'ok', - }); - assert.deepEqual(await bridge.execute(scope, { kind: 'glob', path: '.', pattern: '**/*.ts' }), { - kind: 'glob', - files: [], - }); - assert.deepEqual( - await bridge.execute(scope, { - kind: 'grep', - path: '.', - pattern: 'TODO', - maxCountPerFile: 10, - limit: 100, - timeoutMs: 1_000, - }), - { kind: 'grep', matches: [] }, - ); - - for (const call of calls as Array<{ - cwd: string; - executionBoundary: unknown; - }>) { - assert.equal(call.cwd, '/managed/worktree'); - assert.deepEqual(call.executionBoundary, { - kind: 'managed', - profile: createReadOnlyPermissionProfile(), - revision: 0, - }); - } -}); - -test('rejects foreign, expired, mutating, and unknown operations before worker dispatch', async () => { - const ownerToken = {}; - let dispatches = 0; - const bridge = createManagedWorkspaceWorkerBridgeInternal(ownerToken, { - async execute() { - dispatches += 1; - return { kind: 'read', content: 'unexpected' }; - }, - }); - const foreignScope = scopeFor({}); - await assert.rejects(() => bridge.execute(foreignScope, { kind: 'read', path: 'README.md' })); - - const expiredScope = scopeFor(ownerToken); - revokeManagedWorkspaceExecutionScopeInternal(ownerToken, expiredScope); - await assert.rejects(() => bridge.execute(expiredScope, { kind: 'read', path: 'README.md' })); - - const activeScope = scopeFor(ownerToken); - for (const operation of [ - { kind: 'write', path: 'a.txt', content: 'unsafe' }, - { kind: 'edit', path: 'a.txt', oldString: 'a', newString: 'b' }, - { kind: 'format_json', path: 'a.json', sortKeys: true }, - { kind: 'unknown', path: '.' }, - ]) { - await assert.rejects( - () => bridge.execute(activeScope, operation as never), - (error) => - error instanceof ManagedWorkspaceWorkerBridgeError && - error.code === 'managed_workspace_operation_denied', - ); - } - assert.equal(dispatches, 0); -}); diff --git a/packages/storage/src/__tests__/public-entrypoints.test.ts b/packages/storage/src/__tests__/public-entrypoints.test.ts index d3d185cb93..bdbb85bf7c 100644 --- a/packages/storage/src/__tests__/public-entrypoints.test.ts +++ b/packages/storage/src/__tests__/public-entrypoints.test.ts @@ -55,7 +55,6 @@ const SQLITE_BACKED_ENTRYPOINTS = [ './git-worktree-child-executor', './goal-authority', './interaction-store', - './managed-workspace-owner', './model-call-ledger', './operational-state-store', './plan-authority', @@ -68,7 +67,6 @@ const SQLITE_BACKED_ENTRYPOINTS = [ './session-store', './shell-run-authority', './shell-run-store', - './sqlite-runtime-store', './sqlite-session-metadata-store', './storage-writer-composition', './task-ledger-authority', diff --git a/packages/storage/src/dugite-native-environment.ts b/packages/storage/src/dugite-native-environment.ts deleted file mode 100644 index 2797207445..0000000000 --- a/packages/storage/src/dugite-native-environment.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { posix, win32 } from 'node:path'; - -export interface BundledGitEnvironmentInput { - readonly platform: NodeJS.Platform; - readonly arch: string; - readonly rootPath: string; - readonly executablePath: string; -} - -export function bundledGitEnvironment(input: BundledGitEnvironmentInput): NodeJS.ProcessEnv { - const path = input.platform === 'win32' ? win32 : posix; - const env: NodeJS.ProcessEnv = { - PATH: path.dirname(input.executablePath), - }; - if (input.platform === 'win32') { - const platformRoot = path.join( - input.rootPath, - input.arch === 'x64' ? 'mingw64' : input.arch === 'arm64' ? 'clangarm64' : 'mingw32', - ); - env.PATH = [ - path.dirname(input.executablePath), - path.join(platformRoot, 'bin'), - path.join(platformRoot, 'usr', 'bin'), - ].join(';'); - env.GIT_EXEC_PATH = path.join(platformRoot, 'libexec', 'git-core'); - return env; - } - env.GIT_EXEC_PATH = path.join(input.rootPath, 'libexec', 'git-core'); - env.GIT_TEMPLATE_DIR = path.join(input.rootPath, 'share', 'git-core', 'templates'); - if (input.platform === 'linux') { - env.PREFIX = input.rootPath; - env.GIT_SSL_CAINFO = path.join(input.rootPath, 'ssl', 'cacert.pem'); - } - return env; -} diff --git a/packages/storage/src/git-workspace-service.ts b/packages/storage/src/git-workspace-service.ts deleted file mode 100644 index 9d4954f5bb..0000000000 --- a/packages/storage/src/git-workspace-service.ts +++ /dev/null @@ -1,2933 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { execFile, spawn, type ChildProcess } from 'node:child_process'; -import { createHash, randomBytes, randomUUID } from 'node:crypto'; -import { createReadStream } from 'node:fs'; -import { - lstat, - mkdir, - open as openFile, - readFile, - realpath, - rename, - rm, - stat, -} from 'node:fs/promises'; -import { dirname, isAbsolute, join, normalize, relative, resolve } from 'node:path'; -import { promisify } from 'node:util'; -import { withArtifactWriterLock } from './artifact-writer-lock.js'; -import { bundledGitEnvironment } from './dugite-native-environment.js'; -import { registerManagedBaselineReceiptAuthorityInternal } from './managed-baseline-receipt-authority-internal.js'; - -const execFileAsync = promisify(execFile); -const GIT_TIMEOUT_MS = 2 * 60 * 1_000; -const GIT_MAX_BUFFER_BYTES = 16 * 1024 * 1024; -const BINDING_SCHEMA_VERSION = 1; -const REPOSITORY_SCHEMA_VERSION = 1; -const EPOCH_ARTIFACT_SCHEMA_VERSION = 1; -const QUARANTINE_INTENT_SCHEMA_VERSION = 1; -const BASELINE_RECEIPT_SCHEMA_VERSION = 1; -const IDENTIFIER_PATTERN = /^(repository|workspace|epoch|instance)_[a-f0-9]{32}$/u; -const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/u; -const OID_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u; -const BINDING_KEYS = [ - 'schemaVersion', - 'protocol', - 'repositoryId', - 'workspaceId', - 'workspaceEpochId', - 'workspaceInstanceId', - 'sourceRoot', - 'sourceGitCommonDir', - 'sourceHeadCommitOid', - 'sourceTreeOid', - 'repositoryPath', - 'worktreePath', - 'hooksPath', - 'baselineCommitOid', - 'baselineTreeOid', - 'headRef', - 'gitRuntimeSha256', - 'objectFormat', - 'materializationProfileDigest', - 'materializationSemantics', -] as const; -const REPOSITORY_KEYS = [ - 'schemaVersion', - 'protocol', - 'repositoryId', - 'repositoryPath', - 'hooksPath', - 'gitRuntimeSha256', - 'objectFormat', - 'repositoryCapabilityDigest', -] as const; -const EPOCH_ARTIFACT_KEYS = [ - 'schemaVersion', - 'protocol', - 'repositoryId', - 'workspaceId', - 'workspaceEpochId', - 'sourceRoot', - 'sourceGitCommonDir', - 'sourceHeadCommitOid', - 'sourceTreeOid', - 'baselineCommitOid', - 'baselineTreeOid', - 'baselineRef', - 'headRef', - 'gitRuntimeSha256', - 'objectFormat', - 'materializationProfileDigest', - 'materializationSemantics', -] as const; -const QUARANTINE_INTENT_KEYS = [ - 'schemaVersion', - 'protocol', - 'reason', - 'quarantinePath', - 'binding', -] as const; -const QUARANTINE_RECORD_KEYS = ['protocol', 'reason', 'binding'] as const; -const BASELINE_RECEIPT_KEYS = [ - 'schemaVersion', - 'protocol', - 'binding', - 'workspaceVersionId', - 'policyVersion', - 'policyHash', - 'epochOpenedEventId', - 'baselineAcceptedEventId', - 'treeDeltaDigest', - 'changedFileCount', - 'deletedFileCount', -] as const; -const MATERIALIZATION_SEMANTICS = 'git_tree_materialized_with_fixed_config_v1'; -const MANAGED_BASELINE_POLICY_V1 = { - protocol: 'maka_managed_workspace_baseline_policy_v1', - source: 'source_head_tree', - sourceWorkingTree: 'clean_tracked_and_untracked_except_ignored', - trackedFiles: 'include', - untrackedFiles: 'exclude', - ignoredFiles: 'exclude', - pathEncoding: 'utf8_lossless_roundtrip_required', - caseCollisions: 'reject_nfc_casefold_v1', - symlinks: 'reject', - submodules: 'reject', - attributes: 'reject', - specialModes: 'reject', - materialization: MATERIALIZATION_SEMANTICS, -} as const; -const MANAGED_BASELINE_POLICY_HASH_V1 = hashCanonicalJson(MANAGED_BASELINE_POLICY_V1); -const BASELINE_MESSAGE = 'maka managed workspace baseline v1\n'; -const BASELINE_DATE = '2000-01-01T00:00:00Z'; - -export type GitWorkspaceServiceErrorCode = - | 'git_runtime_unavailable' - | 'git_runtime_integrity_mismatch' - | 'git_workspace_operation_failed' - | 'repository_ineligible' - | 'source_changed_during_baseline_import' - | 'managed_workspace_identity_conflict' - | 'managed_workspace_unavailable' - | 'managed_workspace_drifted'; - -export class GitWorkspaceServiceError extends Error { - constructor( - readonly code: GitWorkspaceServiceErrorCode, - message: string, - options?: ErrorOptions, - ) { - super(message, options); - this.name = 'GitWorkspaceServiceError'; - } -} - -interface GitRuntimeExecutableIdentity { - readonly executablePath: string; - readonly expectedSha256: `sha256:${string}`; -} - -export type VerifiedGitRuntimeInput = GitRuntimeExecutableIdentity & - ( - | { - readonly distribution?: undefined; - readonly runtimeIdentitySha256?: undefined; - } - | { - readonly distribution: { - readonly kind: 'dugite_native_v1'; - readonly rootPath: string; - }; - /** Stable identity of the complete declared distribution. */ - readonly runtimeIdentitySha256: `sha256:${string}`; - } - ); - -export interface CreateGitWorkspaceServiceInput { - readonly storageRoot: string; - readonly gitRuntime: VerifiedGitRuntimeInput; - readonly failpoint?: (point: GitWorkspaceServiceFailpoint) => void | Promise; -} - -export type GitWorkspaceServiceFailpoint = - | 'after_repository_record' - | 'after_baseline_ref_created' - | 'after_worktree_materialized' - | 'after_worktree_locked' - | 'after_head_ref_updated' - | 'after_quarantine_intent' - | 'after_quarantine_unlock' - | 'after_quarantine_move' - | 'after_quarantine_binding_removed' - | 'after_quarantine_pruned' - | 'after_baseline_receipt'; - -export interface ManagedWorkspaceIdentity { - /** - * Identifies the Maka-owned Git object universe. It is not the identity of the - * source checkout; sourceRoot, sourceGitCommonDir, HEAD, and tree OIDs carry - * source provenance and must be validated independently. - */ - readonly repositoryId: string; - readonly workspaceId: string; - readonly workspaceEpochId: string; - readonly workspaceInstanceId: string; -} - -export interface CreateManagedWorkspaceFromSourceInput extends ManagedWorkspaceIdentity { - readonly sourceRoot: string; -} - -export interface ManagedWorkspaceBinding { - readonly schemaVersion: 1; - readonly protocol: 'git_managed_workspace_v1'; - readonly repositoryId: string; - readonly workspaceId: string; - readonly workspaceEpochId: string; - readonly workspaceInstanceId: string; - readonly sourceRoot: string; - readonly sourceGitCommonDir: string; - readonly sourceHeadCommitOid: string; - readonly sourceTreeOid: string; - readonly repositoryPath: string; - readonly worktreePath: string; - readonly hooksPath: string; - readonly baselineCommitOid: string; - readonly baselineTreeOid: string; - readonly headRef: string; - readonly gitRuntimeSha256: `sha256:${string}`; - readonly objectFormat: 'sha1' | 'sha256'; - readonly materializationProfileDigest: `sha256:${string}`; - readonly materializationSemantics: typeof MATERIALIZATION_SEMANTICS; -} - -export type ManagedWorkspaceInspection = - | { - readonly state: 'ready'; - readonly commitOid: string; - readonly treeOid: string; - } - | { - readonly state: 'drifted'; - readonly commitOid?: string; - readonly treeOid?: string; - readonly status: string; - }; - -export interface ManagedWorkspaceQuarantine { - readonly quarantinePath: string; - readonly reason: string; -} - -export interface ManagedWorkspaceBaselineReceiptV1 { - readonly schemaVersion: 1; - readonly protocol: 'maka_managed_workspace_baseline_receipt_v1'; - readonly binding: ManagedWorkspaceBinding; - readonly workspaceVersionId: string; - readonly policyVersion: 1; - readonly policyHash: `sha256:${string}`; - readonly epochOpenedEventId: string; - readonly baselineAcceptedEventId: string; - readonly treeDeltaDigest: `sha256:${string}`; - readonly changedFileCount: number; - readonly deletedFileCount: 0; -} - -export interface GitWorkspaceService { - assertAvailable(): Promise; - createManagedWorkspaceFromSource( - input: CreateManagedWorkspaceFromSourceInput, - ): Promise; - openManagedWorkspaceFromBinding( - input: ManagedWorkspaceIdentity, - ): Promise; - inspectManagedWorkspace(binding: ManagedWorkspaceBinding): Promise; - quarantineManagedWorkspace( - binding: ManagedWorkspaceBinding, - reason: string, - ): Promise; -} - -export function createGitWorkspaceService( - input: CreateGitWorkspaceServiceInput, -): GitWorkspaceService { - return new GitWorkspaceServiceImpl(input); -} - -interface SourceRepositoryInspection { - readonly sourceRoot: string; - readonly gitCommonDir: string; - readonly headCommitOid: string; - readonly treeOid: string; - readonly objectFormat: 'sha1' | 'sha256'; -} - -interface GitTreeEntry { - readonly mode: string; - readonly objectType: string; - readonly oid: string; - readonly path: string; - readonly pathBytesBase64: string; -} - -interface BaselineTreeSummary { - readonly treeDeltaDigest: `sha256:${string}`; - readonly changedFileCount: number; -} - -interface ManagedRepositoryRecord { - readonly schemaVersion: 1; - readonly protocol: 'maka_managed_git_repository_v1'; - readonly repositoryId: string; - readonly repositoryPath: string; - readonly hooksPath: string; - readonly gitRuntimeSha256: `sha256:${string}`; - readonly objectFormat: 'sha1' | 'sha256'; - readonly repositoryCapabilityDigest: `sha256:${string}`; -} - -interface ManagedWorkspaceEpochArtifact { - readonly schemaVersion: 1; - readonly protocol: 'maka_managed_workspace_epoch_artifact_v1'; - readonly repositoryId: string; - readonly workspaceId: string; - readonly workspaceEpochId: string; - readonly sourceRoot: string; - readonly sourceGitCommonDir: string; - readonly sourceHeadCommitOid: string; - readonly sourceTreeOid: string; - readonly baselineCommitOid: string; - readonly baselineTreeOid: string; - readonly baselineRef: string; - readonly headRef: string; - readonly gitRuntimeSha256: `sha256:${string}`; - readonly objectFormat: 'sha1' | 'sha256'; - readonly materializationProfileDigest: `sha256:${string}`; - readonly materializationSemantics: typeof MATERIALIZATION_SEMANTICS; -} - -interface ManagedWorkspaceQuarantineIntent { - readonly schemaVersion: 1; - readonly protocol: 'maka_managed_workspace_quarantine_intent_v1'; - readonly reason: string; - readonly quarantinePath: string; - readonly binding: ManagedWorkspaceBinding; -} - -interface ManagedWorkspaceQuarantineRecord { - readonly protocol: 'maka_managed_workspace_quarantine_v1'; - readonly reason: string; - readonly binding: ManagedWorkspaceBinding; -} - -interface WorkspaceLayout { - readonly managedRoot: string; - readonly repositoryRoot: string; - readonly repositoryPath: string; - readonly repositoryRecordPath: string; - readonly hooksPath: string; - readonly homePath: string; - readonly epochRoot: string; - readonly epochArtifactPath: string; - readonly instanceRoot: string; - readonly bindingPath: string; - readonly baselineReceiptPath: string; - readonly worktreePath: string; - readonly quarantineRoot: string; - readonly quarantineIntentRoot: string; - readonly quarantineIntentPath: string; -} - -class GitWorkspaceServiceImpl implements GitWorkspaceService { - private readonly runtime: VerifiedGitRuntime; - - constructor(private readonly input: CreateGitWorkspaceServiceInput) { - if (!isAbsolute(input.storageRoot)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace storage root must be absolute', - ); - } - this.runtime = new VerifiedGitRuntime(input.gitRuntime); - registerManagedBaselineReceiptAuthorityInternal(this, { - issue: (binding) => this.#createOrReuseManagedWorkspaceBaselineReceipt(binding), - require: (request) => this.#requireManagedWorkspaceBaselineReceipt(request), - verify: (receipt) => this.#verifyManagedWorkspaceBaselineReceipt(receipt), - }); - } - - async assertAvailable(): Promise { - await this.runtime.verify(); - await withArtifactWriterLock(this.input.storageRoot, async () => undefined); - } - - async createManagedWorkspaceFromSource( - input: CreateManagedWorkspaceFromSourceInput, - ): Promise { - assertOpenIdentity(input); - const runtime = await this.runtime.verify(); - - return withArtifactWriterLock(this.input.storageRoot, async (canonicalStorageRoot) => { - const layout = workspaceLayout(canonicalStorageRoot, input); - await ensureOwnedDirectory(layout.managedRoot, canonicalStorageRoot); - await ensureOwnedDirectory(layout.quarantineRoot, layout.managedRoot); - await ensureOwnedDirectory(layout.homePath, layout.managedRoot); - await assertOwnedManagedWorkspaceLayout(canonicalStorageRoot, layout); - - const quarantined = await this.resumePendingQuarantine(input, layout, runtime.digest); - if (quarantined) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace instance was quarantined: ${input.workspaceInstanceId}`, - ); - } - - const existingBinding = await readBinding(layout.bindingPath); - if (existingBinding) { - assertBindingMatches(existingBinding, input, layout, runtime.digest); - return this.adoptStoredBinding(input, existingBinding, layout); - } - - const sourceRoot = await canonicalDirectory(input.sourceRoot, 'repository_ineligible'); - const source = await this.inspectSourceRepository(sourceRoot); - const repository = await this.openRepository(input, source, layout, runtime.digest); - const epoch = await this.openEpochArtifact(input, source, repository, layout); - await this.clearIncompleteInstance(input, epoch, layout); - await ensureOwnedDirectory(layout.instanceRoot, layout.managedRoot); - await this.runtime.run( - [ - '--git-dir', - repository.repositoryPath, - 'worktree', - 'add', - '--quiet', - '--detach', - layout.worktreePath, - epoch.baselineCommitOid, - ], - layout.homePath, - ); - await this.input.failpoint?.('after_worktree_materialized'); - - await this.runtime.run( - [ - '--git-dir', - repository.repositoryPath, - 'worktree', - 'lock', - '--reason', - worktreeLockReason(input), - layout.worktreePath, - ], - layout.homePath, - ); - await this.input.failpoint?.('after_worktree_locked'); - await this.updateRefCas( - repository.repositoryPath, - epoch.headRef, - epoch.baselineCommitOid, - repository.objectFormat, - layout.homePath, - ); - await this.input.failpoint?.('after_head_ref_updated'); - - const binding: ManagedWorkspaceBinding = { - schemaVersion: BINDING_SCHEMA_VERSION, - protocol: 'git_managed_workspace_v1', - repositoryId: input.repositoryId, - workspaceId: input.workspaceId, - workspaceEpochId: input.workspaceEpochId, - workspaceInstanceId: input.workspaceInstanceId, - sourceRoot: epoch.sourceRoot, - sourceGitCommonDir: epoch.sourceGitCommonDir, - sourceHeadCommitOid: epoch.sourceHeadCommitOid, - sourceTreeOid: epoch.sourceTreeOid, - repositoryPath: repository.repositoryPath, - worktreePath: normalize(layout.worktreePath), - hooksPath: repository.hooksPath, - baselineCommitOid: epoch.baselineCommitOid, - baselineTreeOid: epoch.baselineTreeOid, - headRef: epoch.headRef, - gitRuntimeSha256: runtime.digest, - objectFormat: repository.objectFormat, - materializationProfileDigest: epoch.materializationProfileDigest, - materializationSemantics: MATERIALIZATION_SEMANTICS, - }; - const inspection = await this.inspectBinding(binding, layout); - if (inspection.state !== 'ready') { - await this.beginQuarantine(binding, layout, 'initial_materialization_drift'); - throw new GitWorkspaceServiceError( - 'managed_workspace_drifted', - 'Git materialization did not produce a clean managed workspace', - ); - } - await atomicWriteJson(layout.bindingPath, binding); - return binding; - }); - } - - async openManagedWorkspaceFromBinding( - input: ManagedWorkspaceIdentity, - ): Promise { - assertOpenIdentity(input); - const runtime = await this.runtime.verify(); - return withArtifactWriterLock(this.input.storageRoot, async (canonicalStorageRoot) => { - const layout = workspaceLayout(canonicalStorageRoot, input); - await assertOwnedManagedWorkspaceLayout(canonicalStorageRoot, layout); - const quarantined = await this.resumePendingQuarantine(input, layout, runtime.digest); - if (quarantined) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace instance was quarantined: ${input.workspaceInstanceId}`, - ); - } - const binding = await readBinding(layout.bindingPath); - if (!binding) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace binding is unavailable: ${input.workspaceInstanceId}`, - ); - } - assertBindingIdentity(binding, input, layout, runtime.digest); - return this.adoptStoredBinding(input, binding, layout); - }); - } - - private async adoptStoredBinding( - input: ManagedWorkspaceIdentity, - binding: ManagedWorkspaceBinding, - layout: WorkspaceLayout, - ): Promise { - const repository = await this.requireRepository(input, layout); - assertBindingRepository(binding, repository); - const epoch = await this.requireEpochArtifact(input, repository, layout); - assertBindingEpoch(binding, epoch); - const inspection = await this.inspectBinding(binding, layout); - if (inspection.state !== 'ready') { - throw new GitWorkspaceServiceError( - 'managed_workspace_drifted', - `Managed workspace contains unaccepted changes: ${binding.worktreePath}`, - ); - } - return binding; - } - - async inspectManagedWorkspace( - binding: ManagedWorkspaceBinding, - ): Promise { - const runtime = await this.runtime.verify(); - assertBindingShape(binding); - assertOpenIdentity(binding); - return withArtifactWriterLock(this.input.storageRoot, async (canonicalStorageRoot) => { - const layout = workspaceLayout(canonicalStorageRoot, binding); - await assertOwnedManagedWorkspaceLayout(canonicalStorageRoot, layout); - assertBindingPaths(binding, layout); - const quarantined = await this.resumePendingQuarantine(binding, layout, runtime.digest); - if (quarantined) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace instance was quarantined: ${binding.workspaceInstanceId}`, - ); - } - const stored = await readBinding(layout.bindingPath); - if (!stored || !sameBinding(stored, binding)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace binding is unavailable: ${binding.workspaceInstanceId}`, - ); - } - const repository = await readRepositoryRecord(layout.repositoryRecordPath); - if (!repository) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed Git repository record is unavailable: ${binding.repositoryId}`, - ); - } - assertBindingRepository(binding, repository); - await this.assertRepositoryArtifact(repository); - const epoch = await this.requireEpochArtifact(binding, repository, layout); - assertBindingEpoch(binding, epoch); - return this.inspectBinding(binding, layout); - }); - } - - async #requireManagedWorkspaceBaselineReceipt( - input: CreateManagedWorkspaceFromSourceInput, - ): Promise { - const runtime = await this.runtime.verify(); - assertOpenIdentity(input); - return withArtifactWriterLock(this.input.storageRoot, async (canonicalStorageRoot) => { - const layout = workspaceLayout(canonicalStorageRoot, input); - await assertOwnedManagedWorkspaceLayout(canonicalStorageRoot, layout); - const receipt = await readBaselineReceipt(layout.baselineReceiptPath); - if (!receipt) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - 'Canonical workspace baseline receipt is unavailable', - ); - } - assertBindingMatches(receipt.binding, input, layout, runtime.digest); - const summary = await this.requireVerifiedBaselineContext( - receipt.binding, - layout, - runtime.digest, - ); - assertBaselineReceiptMatches(receipt, receipt.binding, summary); - return receipt; - }); - } - - async #createOrReuseManagedWorkspaceBaselineReceipt( - binding: ManagedWorkspaceBinding, - ): Promise { - const runtime = await this.runtime.verify(); - assertBindingShape(binding); - assertOpenIdentity(binding); - return withArtifactWriterLock(this.input.storageRoot, async (canonicalStorageRoot) => { - const layout = workspaceLayout(canonicalStorageRoot, binding); - const summary = await this.requireVerifiedBaselineContext(binding, layout, runtime.digest); - const existing = await readBaselineReceipt(layout.baselineReceiptPath); - if (existing) { - assertBaselineReceiptMatches(existing, binding, summary); - return existing; - } - const identities = deriveBaselineReceiptIdentities(binding); - const receipt: ManagedWorkspaceBaselineReceiptV1 = { - schemaVersion: BASELINE_RECEIPT_SCHEMA_VERSION, - protocol: 'maka_managed_workspace_baseline_receipt_v1', - binding, - workspaceVersionId: identities.workspaceVersionId, - policyVersion: 1, - policyHash: MANAGED_BASELINE_POLICY_HASH_V1, - epochOpenedEventId: identities.epochOpenedEventId, - baselineAcceptedEventId: identities.baselineAcceptedEventId, - treeDeltaDigest: summary.treeDeltaDigest, - changedFileCount: summary.changedFileCount, - deletedFileCount: 0, - }; - await atomicWriteJson(layout.baselineReceiptPath, receipt); - await this.input.failpoint?.('after_baseline_receipt'); - const durable = await readBaselineReceipt(layout.baselineReceiptPath); - if (!durable) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - 'Managed workspace baseline receipt was not durable', - ); - } - assertBaselineReceiptMatches(durable, binding, summary); - return durable; - }); - } - - async #verifyManagedWorkspaceBaselineReceipt( - receipt: ManagedWorkspaceBaselineReceiptV1, - ): Promise { - const runtime = await this.runtime.verify(); - assertBaselineReceiptShape(receipt); - assertOpenIdentity(receipt.binding); - await withArtifactWriterLock(this.input.storageRoot, async (canonicalStorageRoot) => { - const layout = workspaceLayout(canonicalStorageRoot, receipt.binding); - const summary = await this.requireVerifiedBaselineContext( - receipt.binding, - layout, - runtime.digest, - ); - const durable = await readBaselineReceipt(layout.baselineReceiptPath); - if (!durable || !sameBaselineReceipt(durable, receipt)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace baseline receipt does not match its durable artifact', - ); - } - assertBaselineReceiptMatches(durable, receipt.binding, summary); - }); - } - - async quarantineManagedWorkspace( - binding: ManagedWorkspaceBinding, - reason: string, - ): Promise { - const runtime = await this.runtime.verify(); - assertBindingShape(binding); - assertOpenIdentity(binding); - if (!reason.trim()) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace quarantine reason is required', - ); - } - return withArtifactWriterLock(this.input.storageRoot, async (canonicalStorageRoot) => { - const layout = workspaceLayout(canonicalStorageRoot, binding); - await assertOwnedManagedWorkspaceLayout(canonicalStorageRoot, layout); - assertBindingPaths(binding, layout); - const pending = await readQuarantineIntent(layout.quarantineIntentPath); - if (pending) { - assertQuarantineIntentMatches(pending, binding, reason, layout); - await this.assertQuarantineBindingAuthority(binding, layout, runtime.digest); - return this.resumeQuarantineIntent(pending, layout); - } - const stored = await readBinding(layout.bindingPath); - if (!stored || !sameBinding(stored, binding)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace binding is unavailable: ${binding.workspaceInstanceId}`, - ); - } - const repository = await readRepositoryRecord(layout.repositoryRecordPath); - if (!repository) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed Git repository record is unavailable: ${binding.repositoryId}`, - ); - } - assertBindingRepository(binding, repository); - await this.assertRepositoryArtifact(repository); - const epoch = await this.requireEpochArtifact(binding, repository, layout); - assertBindingEpoch(binding, epoch); - return this.beginQuarantine(binding, layout, reason); - }); - } - - private async requireVerifiedBaselineContext( - binding: ManagedWorkspaceBinding, - layout: WorkspaceLayout, - runtimeDigest: `sha256:${string}`, - ): Promise { - assertBindingPaths(binding, layout); - const quarantined = await this.resumePendingQuarantine(binding, layout, runtimeDigest); - if (quarantined) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace instance was quarantined: ${binding.workspaceInstanceId}`, - ); - } - const stored = await readBinding(layout.bindingPath); - if (!stored || !sameBinding(stored, binding)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace binding is unavailable: ${binding.workspaceInstanceId}`, - ); - } - const repository = await this.requireRepository(binding, layout); - assertBindingRepository(binding, repository); - const epoch = await this.requireEpochArtifact(binding, repository, layout); - assertBindingEpoch(binding, epoch); - const inspection = await this.inspectBinding(binding, layout); - if (inspection.state !== 'ready') { - throw new GitWorkspaceServiceError( - 'managed_workspace_drifted', - `Managed workspace contains unaccepted changes: ${binding.worktreePath}`, - ); - } - return this.readBaselineTreeSummary(binding, layout); - } - - private async readBaselineTreeSummary( - binding: ManagedWorkspaceBinding, - layout: WorkspaceLayout, - ): Promise { - const entries = parseTreeEntries( - await this.runtime.runBuffer( - ['--git-dir', binding.repositoryPath, 'ls-tree', '-r', '-z', binding.baselineCommitOid], - layout.homePath, - ), - ); - assertSupportedTree(entries); - return baselineTreeSummary(entries); - } - - private async inspectSourceRepository(sourceRoot: string): Promise { - try { - const topLevelRaw = await this.runtime.run([ - '-C', - sourceRoot, - 'rev-parse', - '--show-toplevel', - ]); - const topLevel = await realpath(topLevelRaw.trim()); - if (!samePath(topLevel, sourceRoot)) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Managed workspace source must be the Git worktree root', - ); - } - const status = await this.runtime.run([ - '-C', - sourceRoot, - 'status', - '--porcelain=v1', - '--untracked-files=all', - '--ignore-submodules=none', - ]); - if (status.trim()) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Managed workspace source must have no tracked or untracked changes', - ); - } - const sparse = await this.runtime.runOptional( - ['-C', sourceRoot, 'config', '--bool', '--get', 'core.sparseCheckout'], - 1, - ); - if (sparse?.trim() === 'true') { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Sparse Git worktrees are not supported by managed workspace v1', - ); - } - const entries = parseTreeEntries( - await this.runtime.runBuffer(['-C', sourceRoot, 'ls-tree', '-r', '-z', 'HEAD']), - ); - assertSupportedTree(entries); - - const gitCommonDirRaw = ( - await this.runtime.run(['-C', sourceRoot, 'rev-parse', '--git-common-dir']) - ).trim(); - const gitCommonDir = await realpath(resolveGitPath(sourceRoot, gitCommonDirRaw)); - await this.assertSourceConfigurationFences(sourceRoot, gitCommonDir); - const [headCommitOid, treeOid, objectFormat] = await Promise.all([ - this.runtime.run(['-C', sourceRoot, 'rev-parse', '--verify', 'HEAD']), - this.runtime.run(['-C', sourceRoot, 'rev-parse', '--verify', 'HEAD^{tree}']), - this.runtime.run(['-C', sourceRoot, 'rev-parse', '--show-object-format']), - ]); - const normalizedObjectFormat = objectFormat.trim(); - if (normalizedObjectFormat !== 'sha1' && normalizedObjectFormat !== 'sha256') { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - `Unsupported Git object format: ${normalizedObjectFormat}`, - ); - } - return { - sourceRoot: normalize(sourceRoot), - gitCommonDir: normalize(gitCommonDir), - headCommitOid: headCommitOid.trim(), - treeOid: treeOid.trim(), - objectFormat: normalizedObjectFormat, - }; - } catch (error) { - if (error instanceof GitWorkspaceServiceError) throw error; - throw new GitWorkspaceServiceError( - 'repository_ineligible', - `Unable to inspect managed workspace source: ${sourceRoot}`, - { cause: error }, - ); - } - } - - private async openRepository( - input: CreateManagedWorkspaceFromSourceInput, - source: SourceRepositoryInspection, - layout: WorkspaceLayout, - runtimeDigest: `sha256:${string}`, - ): Promise { - const existing = await readRepositoryRecord(layout.repositoryRecordPath); - if (existing) { - assertRepositoryMatches(existing, input, source.objectFormat, layout, runtimeDigest); - await this.assertRepositoryArtifact(existing); - return existing; - } - - if (await pathExists(layout.repositoryRoot)) { - await moveToQuarantine( - layout.repositoryRoot, - layout.quarantineRoot, - `${input.repositoryId}-incomplete-repository`, - ); - } - await ensureOwnedDirectory(layout.repositoryRoot, layout.managedRoot); - await ensureOwnedDirectory(layout.hooksPath, layout.repositoryRoot); - const stagingRepository = join( - layout.repositoryRoot, - `r.tmp-${randomBytes(6).toString('hex')}`, - ); - try { - const templatePath = join(layout.repositoryRoot, 'empty-template'); - await mkdir(templatePath, { recursive: true }); - await this.runtime.run( - [ - 'init', - '--quiet', - '--bare', - `--object-format=${source.objectFormat}`, - `--template=${templatePath}`, - stagingRepository, - ], - layout.homePath, - ); - await this.configureManagedRepository(stagingRepository, layout.hooksPath); - await rename(stagingRepository, layout.repositoryPath); - - const record: ManagedRepositoryRecord = { - schemaVersion: REPOSITORY_SCHEMA_VERSION, - protocol: 'maka_managed_git_repository_v1', - repositoryId: input.repositoryId, - repositoryPath: normalize(layout.repositoryPath), - hooksPath: normalize(layout.hooksPath), - gitRuntimeSha256: runtimeDigest, - objectFormat: source.objectFormat, - repositoryCapabilityDigest: repositoryCapabilityDigest(runtimeDigest, source.objectFormat), - }; - await this.assertRepositoryArtifact(record); - await atomicWriteJson(layout.repositoryRecordPath, record); - await this.input.failpoint?.('after_repository_record'); - return record; - } catch (error) { - await rm(stagingRepository, { recursive: true, force: true }); - if (error instanceof GitWorkspaceServiceError) throw error; - throw new GitWorkspaceServiceError( - 'git_workspace_operation_failed', - 'Unable to create the Maka-owned Git repository', - { cause: error }, - ); - } - } - - private async assertSourceConfigurationFences( - sourceRoot: string, - gitCommonDir: string, - ): Promise { - if (await pathExists(join(gitCommonDir, 'objects', 'info', 'alternates'))) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Managed workspace source must not use Git object alternates', - ); - } - const unsafeConfig = await this.runtime.runOptional( - ['-C', sourceRoot, 'config', '--no-includes', '--local', '--name-only', '--get-regexp', '.*'], - 1, - ); - const worktreeConfigEnabled = ( - await this.runtime.runOptional( - [ - '-C', - sourceRoot, - 'config', - '--no-includes', - '--local', - '--bool', - '--get', - 'extensions.worktreeConfig', - ], - 1, - ) - )?.trim(); - const unsafeWorktreeConfig = - worktreeConfigEnabled === 'true' - ? await this.runtime.runOptional( - [ - '-C', - sourceRoot, - 'config', - '--no-includes', - '--worktree', - '--name-only', - '--get-regexp', - '.*', - ], - 1, - ) - : undefined; - const replaceRefs = await this.runtime.run( - ['-C', sourceRoot, 'for-each-ref', '--format=%(refname)', 'refs/replace/'], - undefined, - undefined, - [1], - ); - if ( - hasUnsafeSourceConfig(unsafeConfig) || - hasUnsafeSourceConfig(unsafeWorktreeConfig) || - replaceRefs.trim() - ) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Managed workspace source contains unsupported Git indirection or runtime configuration', - ); - } - } - - private async openEpochArtifact( - input: CreateManagedWorkspaceFromSourceInput, - source: SourceRepositoryInspection, - repository: ManagedRepositoryRecord, - layout: WorkspaceLayout, - ): Promise { - const existing = await readEpochArtifact(layout.epochArtifactPath); - if (existing) { - assertEpochArtifactMatches(existing, input, source, repository); - await this.assertEpochArtifact(existing, repository, layout); - return existing; - } - - const baselineRef = managedBaselineRef(input.workspaceEpochId); - await this.clearIncompleteBaselineRef(repository.repositoryPath, baselineRef, layout.homePath); - await ensureOwnedDirectory(layout.epochRoot, layout.managedRoot); - await this.runtime.importTree( - source.sourceRoot, - source.treeOid, - repository.repositoryPath, - layout.homePath, - ); - const importedTree = ( - await this.runtime.run( - ['--git-dir', repository.repositoryPath, 'rev-parse', `${source.treeOid}^{tree}`], - layout.homePath, - ) - ).trim(); - if (importedTree !== source.treeOid) { - throw new GitWorkspaceServiceError( - 'git_workspace_operation_failed', - 'Imported baseline tree does not match the source HEAD tree', - ); - } - const baselineCommitOid = ( - await this.runtime.run( - [ - '--git-dir', - repository.repositoryPath, - 'commit-tree', - importedTree, - '-m', - BASELINE_MESSAGE.trim(), - ], - layout.homePath, - baselineCommitEnvironment(), - ) - ).trim(); - const observedBeforeRef = await this.inspectSourceRepository(source.sourceRoot); - assertSameSourceObservation(source, observedBeforeRef); - await this.updateRefCas( - repository.repositoryPath, - baselineRef, - baselineCommitOid, - repository.objectFormat, - layout.homePath, - ); - - await this.input.failpoint?.('after_baseline_ref_created'); - try { - const observedAgain = await this.inspectSourceRepository(source.sourceRoot); - assertSameSourceObservation(source, observedAgain); - } catch (error) { - await this.deleteRefCas( - repository.repositoryPath, - baselineRef, - baselineCommitOid, - layout.homePath, - ); - throw error; - } - - const artifact: ManagedWorkspaceEpochArtifact = { - schemaVersion: EPOCH_ARTIFACT_SCHEMA_VERSION, - protocol: 'maka_managed_workspace_epoch_artifact_v1', - repositoryId: input.repositoryId, - workspaceId: input.workspaceId, - workspaceEpochId: input.workspaceEpochId, - sourceRoot: source.sourceRoot, - sourceGitCommonDir: source.gitCommonDir, - sourceHeadCommitOid: source.headCommitOid, - sourceTreeOid: source.treeOid, - baselineCommitOid, - baselineTreeOid: importedTree, - baselineRef, - headRef: managedHeadRef(input.workspaceId, input.workspaceEpochId), - gitRuntimeSha256: repository.gitRuntimeSha256, - objectFormat: repository.objectFormat, - materializationProfileDigest: materializationProfileDigest( - repository.gitRuntimeSha256, - repository.objectFormat, - ), - materializationSemantics: MATERIALIZATION_SEMANTICS, - }; - await this.assertEpochArtifact(artifact, repository, layout); - await atomicWriteJson(layout.epochArtifactPath, artifact); - return artifact; - } - - private async requireRepository( - input: ManagedWorkspaceIdentity, - layout: WorkspaceLayout, - ): Promise { - const repository = await readRepositoryRecord(layout.repositoryRecordPath); - if (!repository) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed Git repository record is unavailable: ${input.repositoryId}`, - ); - } - if ( - repository.repositoryId !== input.repositoryId || - !samePath(repository.repositoryPath, layout.repositoryPath) || - !samePath(repository.hooksPath, layout.hooksPath) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed Git repository does not match the requested repository identity', - ); - } - await this.assertRepositoryArtifact(repository); - return repository; - } - - private async requireEpochArtifact( - input: Pick, - repository: ManagedRepositoryRecord, - layout: WorkspaceLayout, - ): Promise { - const epoch = await readEpochArtifact(layout.epochArtifactPath); - if (!epoch) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace epoch artifact is unavailable: ${input.workspaceEpochId}`, - ); - } - if ( - epoch.repositoryId !== input.repositoryId || - epoch.workspaceId !== input.workspaceId || - epoch.workspaceEpochId !== input.workspaceEpochId - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace epoch artifact does not match the requested identity', - ); - } - await this.assertEpochArtifact(epoch, repository, layout); - return epoch; - } - - private async assertEpochArtifact( - epoch: ManagedWorkspaceEpochArtifact, - repository: ManagedRepositoryRecord, - layout: WorkspaceLayout, - ): Promise { - if ( - epoch.repositoryId !== repository.repositoryId || - epoch.gitRuntimeSha256 !== repository.gitRuntimeSha256 || - epoch.objectFormat !== repository.objectFormat || - epoch.materializationProfileDigest !== - materializationProfileDigest(repository.gitRuntimeSha256, repository.objectFormat) || - epoch.baselineRef !== managedBaselineRef(epoch.workspaceEpochId) || - epoch.headRef !== managedHeadRef(epoch.workspaceId, epoch.workspaceEpochId) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace epoch artifact does not match its repository capability', - ); - } - const [commit, tree, baselineRef] = await Promise.all([ - this.runtime.run( - ['--git-dir', repository.repositoryPath, 'rev-parse', '--verify', epoch.baselineCommitOid], - layout.homePath, - ), - this.runtime.run( - [ - '--git-dir', - repository.repositoryPath, - 'rev-parse', - '--verify', - `${epoch.baselineCommitOid}^{tree}`, - ], - layout.homePath, - ), - this.runtime.run( - ['--git-dir', repository.repositoryPath, 'rev-parse', '--verify', epoch.baselineRef], - layout.homePath, - ), - ]); - if ( - commit.trim() !== epoch.baselineCommitOid || - tree.trim() !== epoch.baselineTreeOid || - baselineRef.trim() !== epoch.baselineCommitOid - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace epoch baseline identity changed', - ); - } - } - - private async updateRefCas( - repositoryPath: string, - ref: string, - desiredOid: string, - objectFormat: string, - homePath: string, - ): Promise { - const current = ( - await this.runtime.runOptional( - ['--git-dir', repositoryPath, 'rev-parse', '--verify', '--quiet', ref], - 1, - ) - )?.trim(); - if (current === desiredOid) return; - if (current) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed Git ref already points at a different object: ${ref}`, - ); - } - const expected = '0'.repeat(objectFormat === 'sha256' ? 64 : 40); - try { - await this.runtime.run( - ['--git-dir', repositoryPath, 'update-ref', ref, desiredOid, expected], - homePath, - ); - } catch (error) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed Git ref changed concurrently: ${ref}`, - { cause: error }, - ); - } - } - - private async deleteRefCas( - repositoryPath: string, - ref: string, - expectedOid: string, - homePath: string, - ): Promise { - try { - await this.runtime.run( - ['--git-dir', repositoryPath, 'update-ref', '-d', ref, expectedOid], - homePath, - ); - } catch (error) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed Git ref changed before cleanup: ${ref}`, - { cause: error }, - ); - } - } - - private async clearIncompleteBaselineRef( - repositoryPath: string, - baselineRef: string, - homePath: string, - ): Promise { - const current = ( - await this.runtime.runOptional( - ['--git-dir', repositoryPath, 'rev-parse', '--verify', '--quiet', baselineRef], - 1, - ) - )?.trim(); - if (!current) return; - const commitBody = await this.runtime.run( - ['--git-dir', repositoryPath, 'cat-file', 'commit', current], - homePath, - ); - if (!isOwnedBaselineCommit(commitBody)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Incomplete managed baseline ref is not owned by this protocol: ${baselineRef}`, - ); - } - await this.deleteRefCas(repositoryPath, baselineRef, current, homePath); - } - - private async configureManagedRepository( - repositoryPath: string, - hooksPath: string, - ): Promise { - const entries: readonly [string, string][] = [ - ['core.autocrlf', 'false'], - ['core.safecrlf', 'true'], - ['core.hooksPath', normalize(hooksPath)], - ['core.sshCommand', ''], - ['credential.helper', ''], - ['credential.interactive', 'never'], - ['protocol.allow', 'never'], - ['gc.auto', '0'], - ]; - for (const [key, value] of entries) { - await this.runtime.run( - ['--git-dir', repositoryPath, 'config', '--local', key, value], - dirname(repositoryPath), - ); - } - } - - private async assertRepositoryArtifact(record: ManagedRepositoryRecord): Promise { - if (!(await isNonSymlinkDirectory(record.repositoryPath))) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed Git repository is unavailable: ${record.repositoryPath}`, - ); - } - if (!(await isNonSymlinkDirectory(record.hooksPath))) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed Git hooks fence is unavailable: ${record.hooksPath}`, - ); - } - const objectFormat = ( - await this.runtime.run([ - '--git-dir', - record.repositoryPath, - 'rev-parse', - '--show-object-format', - ]) - ).trim(); - if ( - objectFormat !== record.objectFormat || - record.repositoryCapabilityDigest !== - repositoryCapabilityDigest(record.gitRuntimeSha256, record.objectFormat) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed Git repository capability identity changed', - ); - } - if (await pathExists(join(record.repositoryPath, 'objects', 'info', 'alternates'))) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed Git repository must not use object alternates', - ); - } - } - - private async inspectBinding( - binding: ManagedWorkspaceBinding, - layout: WorkspaceLayout, - ): Promise { - assertBindingPaths(binding, layout); - if ( - !(await isNonSymlinkDirectory(binding.worktreePath)) || - !(await isNonSymlinkDirectory(binding.repositoryPath)) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Managed workspace is unavailable: ${binding.worktreePath}`, - ); - } - try { - const commonDirRaw = ( - await this.runtime.run( - ['-C', binding.worktreePath, 'rev-parse', '--git-common-dir'], - layout.homePath, - ) - ).trim(); - const commonDir = await realpath(resolveGitPath(binding.worktreePath, commonDirRaw)); - if (!samePath(commonDir, binding.repositoryPath)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed worktree is attached to a different Git repository', - ); - } - const [commitRaw, treeRaw, statusRaw, headRefRaw, worktreeListRaw] = await Promise.all([ - this.runtime.run( - ['-C', binding.worktreePath, 'rev-parse', '--verify', 'HEAD'], - layout.homePath, - ), - this.runtime.run( - ['-C', binding.worktreePath, 'rev-parse', '--verify', 'HEAD^{tree}'], - layout.homePath, - ), - this.runtime.run( - [ - '-C', - binding.worktreePath, - 'status', - '--porcelain=v1', - '--untracked-files=all', - '--ignored=matching', - ], - layout.homePath, - ), - this.runtime.run( - ['--git-dir', binding.repositoryPath, 'rev-parse', '--verify', binding.headRef], - layout.homePath, - ), - this.runtime.run( - ['--git-dir', binding.repositoryPath, 'worktree', 'list', '--porcelain'], - layout.homePath, - ), - ]); - const commitOid = commitRaw.trim(); - const treeOid = treeRaw.trim(); - const status = statusRaw.trim(); - const headRef = headRefRaw.trim(); - assertWorktreeRegistrationLocked( - worktreeListRaw, - binding.worktreePath, - worktreeLockReason(binding), - ); - if ( - commitOid !== binding.baselineCommitOid || - treeOid !== binding.baselineTreeOid || - headRef !== binding.baselineCommitOid || - status - ) { - return { state: 'drifted', commitOid, treeOid, status }; - } - return { state: 'ready', commitOid, treeOid }; - } catch (error) { - if (error instanceof GitWorkspaceServiceError) throw error; - throw new GitWorkspaceServiceError( - 'managed_workspace_unavailable', - `Unable to inspect managed workspace: ${binding.worktreePath}`, - { cause: error }, - ); - } - } - - private async clearIncompleteInstance( - input: ManagedWorkspaceIdentity, - epoch: ManagedWorkspaceEpochArtifact, - layout: WorkspaceLayout, - ): Promise { - const registration = findWorktreeRegistration( - await this.runtime.run( - ['--git-dir', layout.repositoryPath, 'worktree', 'list', '--porcelain'], - layout.homePath, - ), - layout.worktreePath, - ); - if (registration) { - if ( - registration.headOid !== epoch.baselineCommitOid || - (registration.lockReason !== undefined && - registration.lockReason !== worktreeLockReason(input)) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Incomplete managed worktree registration does not match its durable identity', - ); - } - // Git cannot unlock a registration after its worktree path has been moved - // (notably on Windows), so unlock only after exact identity validation and - // immediately before quarantining the path under the global writer lock. - if (registration.lockReason !== undefined) { - await this.runtime.run( - ['--git-dir', layout.repositoryPath, 'worktree', 'unlock', layout.worktreePath], - layout.homePath, - ); - } - } - if (await pathExists(layout.instanceRoot)) { - await moveToQuarantine( - layout.instanceRoot, - layout.quarantineRoot, - `incomplete-${randomUUID()}`, - ); - } - await this.runtime.run( - ['--git-dir', layout.repositoryPath, 'worktree', 'prune', '--expire=now'], - layout.homePath, - ); - const remaining = findWorktreeRegistration( - await this.runtime.run( - ['--git-dir', layout.repositoryPath, 'worktree', 'list', '--porcelain'], - layout.homePath, - ), - layout.worktreePath, - ); - if (remaining) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Incomplete managed worktree registration could not be removed safely', - ); - } - } - - private async resumePendingQuarantine( - input: ManagedWorkspaceIdentity, - layout: WorkspaceLayout, - runtimeDigest: `sha256:${string}`, - ): Promise { - await assertOwnedManagedWorkspaceLayout(this.input.storageRoot, layout); - const intent = await readQuarantineIntent(layout.quarantineIntentPath); - if (!intent) return undefined; - assertQuarantineIntentMatches(intent, intent.binding, intent.reason, layout); - assertBindingIdentity(intent.binding, input, layout, runtimeDigest); - await this.assertQuarantineBindingAuthority(intent.binding, layout, runtimeDigest); - return this.resumeQuarantineIntent(intent, layout); - } - - private async assertQuarantineBindingAuthority( - binding: ManagedWorkspaceBinding, - layout: WorkspaceLayout, - runtimeDigest: `sha256:${string}`, - ): Promise { - assertBindingIdentity(binding, binding, layout, runtimeDigest); - const repository = await this.requireRepository(binding, layout); - assertBindingRepository(binding, repository); - const epoch = await this.requireEpochArtifact(binding, repository, layout); - assertBindingEpoch(binding, epoch); - } - - private async beginQuarantine( - binding: ManagedWorkspaceBinding, - layout: WorkspaceLayout, - reason: string, - ): Promise { - await ensureOwnedDirectory(layout.quarantineIntentRoot, layout.managedRoot); - const existing = await readQuarantineIntent(layout.quarantineIntentPath); - if (existing) { - assertQuarantineIntentMatches(existing, binding, reason, layout); - return this.resumeQuarantineIntent(existing, layout); - } - const intent: ManagedWorkspaceQuarantineIntent = { - schemaVersion: QUARANTINE_INTENT_SCHEMA_VERSION, - protocol: 'maka_managed_workspace_quarantine_intent_v1', - reason, - quarantinePath: join( - layout.quarantineRoot, - `${compactIdentity(binding.workspaceInstanceId)}-${sanitizeReason(reason)}-${randomUUID()}`, - ), - binding, - }; - assertQuarantineIntentMatches(intent, binding, reason, layout); - await atomicWriteJson(layout.quarantineIntentPath, intent); - await this.input.failpoint?.('after_quarantine_intent'); - return this.resumeQuarantineIntent(intent, layout); - } - - private async resumeQuarantineIntent( - intent: ManagedWorkspaceQuarantineIntent, - layout: WorkspaceLayout, - ): Promise { - await assertOwnedManagedWorkspaceLayout(this.input.storageRoot, layout); - const { binding, reason, quarantinePath } = intent; - assertQuarantineIntentMatches(intent, binding, reason, layout); - const registration = findWorktreeRegistration( - await this.runtime.run( - ['--git-dir', binding.repositoryPath, 'worktree', 'list', '--porcelain'], - layout.homePath, - ), - binding.worktreePath, - ); - if ( - registration && - (registration.headOid !== binding.baselineCommitOid || - (registration.lockReason !== undefined && - registration.lockReason !== worktreeLockReason(binding))) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Quarantine worktree registration does not match its durable intent', - ); - } - if (registration?.lockReason !== undefined) { - await this.runtime.run( - ['--git-dir', binding.repositoryPath, 'worktree', 'unlock', binding.worktreePath], - layout.homePath, - ); - } - await this.input.failpoint?.('after_quarantine_unlock'); - - const sourceExists = await pathEntryExists(binding.worktreePath); - const targetExists = await pathEntryExists(quarantinePath); - if (sourceExists && targetExists) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Quarantine source and target both exist', - ); - } - if (sourceExists) { - await ensureOwnedDirectory(layout.quarantineRoot, layout.managedRoot); - await assertOwnedManagedWorkspaceLayout(this.input.storageRoot, layout); - await rename(binding.worktreePath, quarantinePath); - } else if (!targetExists) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Quarantine intent has neither a source worktree nor a target artifact', - ); - } - await assertOwnedDirectoryEntry(quarantinePath, layout.quarantineRoot, true); - if (!samePath(await realpath(dirname(quarantinePath)), await realpath(layout.quarantineRoot))) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Quarantine target parent does not match the owned quarantine root', - ); - } - await this.input.failpoint?.('after_quarantine_move'); - - const stored = await readBinding(layout.bindingPath); - if (stored && !sameBinding(stored, binding)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Quarantine binding changed after its durable intent was recorded', - ); - } - await rm(layout.bindingPath, { force: true }); - await this.input.failpoint?.('after_quarantine_binding_removed'); - await this.runtime.run( - ['--git-dir', binding.repositoryPath, 'worktree', 'prune', '--expire=now'], - layout.homePath, - ); - const remaining = findWorktreeRegistration( - await this.runtime.run( - ['--git-dir', binding.repositoryPath, 'worktree', 'list', '--porcelain'], - layout.homePath, - ), - binding.worktreePath, - ); - if (remaining) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Quarantine could not remove the managed worktree registration', - ); - } - await this.input.failpoint?.('after_quarantine_pruned'); - - const record: ManagedWorkspaceQuarantineRecord = { - protocol: 'maka_managed_workspace_quarantine_v1', - reason, - binding, - }; - const existingRecord = await readQuarantineRecord(`${quarantinePath}.json`); - if (existingRecord && !sameQuarantineRecord(existingRecord, record)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Quarantine record conflicts with its durable intent', - ); - } - if (!existingRecord) await atomicWriteJson(`${quarantinePath}.json`, record); - return { quarantinePath, reason }; - } -} - -class VerifiedGitRuntime { - constructor(private readonly input: VerifiedGitRuntimeInput) { - if ( - !isAbsolute(input.executablePath) || - !SHA256_PATTERN.test(input.expectedSha256) || - (input.distribution !== undefined && !SHA256_PATTERN.test(input.runtimeIdentitySha256 ?? '')) - ) { - throw new GitWorkspaceServiceError( - 'git_runtime_unavailable', - 'Managed workspace requires an absolute Git executable and SHA-256 digest', - ); - } - if (input.distribution) { - const rootPath = input.distribution.rootPath; - const executableRelativePath = relative(rootPath, input.executablePath); - if ( - !isAbsolute(rootPath) || - executableRelativePath === '' || - executableRelativePath === '..' || - executableRelativePath.startsWith(`..${process.platform === 'win32' ? '\\' : '/'}`) || - isAbsolute(executableRelativePath) - ) { - throw new GitWorkspaceServiceError( - 'git_runtime_unavailable', - 'Bundled Git executable must belong to its declared distribution root', - ); - } - } - } - - verify(): Promise<{ executablePath: string; digest: `sha256:${string}` }> { - return this.verifyOnce(); - } - - async run( - args: readonly string[], - homePath?: string, - extraEnv?: NodeJS.ProcessEnv, - acceptedExitCodes: readonly number[] = [], - ): Promise { - const runtime = await this.verify(); - const hooksPath = homePath ? join(homePath, 'empty-hooks') : dirname(runtime.executablePath); - if (homePath) { - await mkdir(homePath, { recursive: true }); - await mkdir(hooksPath, { recursive: true }); - } - const env = isolatedGitEnvironment(this.input, homePath ?? dirname(runtime.executablePath)); - try { - const { stdout } = await execFileAsync( - runtime.executablePath, - [...fixedGitArguments(hooksPath), ...args], - { - cwd: homePath ?? dirname(runtime.executablePath), - // Operation-scoped values (currently only deterministic commit - // identity) cannot override the hermetic Git execution profile. - env: { ...extraEnv, ...env }, - encoding: 'utf8', - maxBuffer: GIT_MAX_BUFFER_BYTES, - timeout: GIT_TIMEOUT_MS, - windowsHide: true, - }, - ); - return stdout; - } catch (error) { - if (acceptedExitCodes.includes(exitCode(error) ?? -1)) return ''; - throw error; - } - } - - async runBuffer( - args: readonly string[], - homePath?: string, - extraEnv?: NodeJS.ProcessEnv, - ): Promise { - const runtime = await this.verify(); - const hooksPath = homePath ? join(homePath, 'empty-hooks') : dirname(runtime.executablePath); - if (homePath) { - await mkdir(homePath, { recursive: true }); - await mkdir(hooksPath, { recursive: true }); - } - const env = isolatedGitEnvironment(this.input, homePath ?? dirname(runtime.executablePath)); - const { stdout } = await execFileAsync( - runtime.executablePath, - [...fixedGitArguments(hooksPath), ...args], - { - cwd: homePath ?? dirname(runtime.executablePath), - env: { ...extraEnv, ...env }, - encoding: 'buffer', - maxBuffer: GIT_MAX_BUFFER_BYTES, - timeout: GIT_TIMEOUT_MS, - windowsHide: true, - }, - ); - return stdout; - } - - async runOptional( - args: readonly string[], - acceptedMissingExitCode: number, - ): Promise { - try { - return await this.run(args); - } catch (error) { - if (exitCode(error) === acceptedMissingExitCode) return undefined; - throw error; - } - } - - async importTree( - sourceRoot: string, - treeOid: string, - repositoryPath: string, - homePath: string, - ): Promise { - const packRuntime = await this.verify(); - const hooksPath = join(homePath, 'empty-hooks'); - await mkdir(hooksPath, { recursive: true }); - const fixed = fixedGitArguments(hooksPath); - const pack = spawn( - packRuntime.executablePath, - [...fixed, '-C', sourceRoot, 'pack-objects', '--stdout', '--revs'], - { - cwd: homePath, - env: isolatedGitEnvironment(this.input, homePath), - stdio: ['pipe', 'pipe', 'pipe'], - windowsHide: true, - }, - ); - let indexRuntime: Awaited>; - try { - // Each Git process is an independent trust decision. Re-check the - // executable immediately before index-pack instead of extending the - // pack-objects verification across a second invocation. - indexRuntime = await this.verify(); - } catch (error) { - pack.kill('SIGKILL'); - await waitForChildExit(pack).catch(() => undefined); - throw error; - } - const index = spawn( - indexRuntime.executablePath, - [...fixed, '--git-dir', repositoryPath, 'index-pack', '--stdin', '--fix-thin'], - { - cwd: homePath, - env: isolatedGitEnvironment(this.input, homePath), - stdio: ['pipe', 'ignore', 'pipe'], - windowsHide: true, - }, - ); - if (!pack.stdin || !pack.stdout || !index.stdin) { - pack.kill('SIGKILL'); - index.kill('SIGKILL'); - throw new Error('Git tree import did not create the required process pipes'); - } - const packStderr = collectBoundedStderr(pack.stderr); - const indexStderr = collectBoundedStderr(index.stderr); - index.stdin.on('error', () => { - // The index process exit status/stderr is the authoritative pipeline failure. - }); - pack.stdout.pipe(index.stdin); - pack.stdin.end(`${treeOid}\n`); - const timeout = setTimeout(() => { - pack.kill('SIGKILL'); - index.kill('SIGKILL'); - }, GIT_TIMEOUT_MS); - try { - const [packCode, indexCode] = await Promise.all([ - waitForChildExit(pack), - waitForChildExit(index), - ]); - if (packCode !== 0 || indexCode !== 0) { - throw new Error( - `Git tree import failed: pack=${packCode} index=${indexCode} ` + - `${await packStderr} ${await indexStderr}`, - ); - } - } catch (error) { - pack.kill('SIGKILL'); - index.kill('SIGKILL'); - throw error; - } finally { - clearTimeout(timeout); - } - } - - private async verifyOnce(): Promise<{ - executablePath: string; - digest: `sha256:${string}`; - }> { - try { - const executablePath = normalize(await realpath(this.input.executablePath)); - const info = await stat(executablePath); - if (!info.isFile()) throw new Error('not a regular file'); - const executableDigest = await sha256File(executablePath); - if (executableDigest !== this.input.expectedSha256) { - throw new GitWorkspaceServiceError( - 'git_runtime_integrity_mismatch', - `Git executable digest mismatch: ${executablePath}`, - ); - } - return { - executablePath, - digest: this.input.runtimeIdentitySha256 ?? executableDigest, - }; - } catch (error) { - if (error instanceof GitWorkspaceServiceError) throw error; - throw new GitWorkspaceServiceError( - 'git_runtime_unavailable', - `Git runtime is unavailable: ${this.input.executablePath}`, - { cause: error }, - ); - } - } -} - -function workspaceLayout( - canonicalStorageRoot: string, - identity: Pick< - ManagedWorkspaceIdentity, - 'repositoryId' | 'workspaceId' | 'workspaceEpochId' | 'workspaceInstanceId' - >, -): WorkspaceLayout { - const managedRoot = join(canonicalStorageRoot, 'managed-workspaces'); - const quarantineIntentRoot = join(managedRoot, 'quarantine-intents'); - const repositoryRoot = join(managedRoot, 'r', compactIdentity(identity.repositoryId)); - const epochRoot = join( - managedRoot, - 'w', - compactIdentity(identity.workspaceId), - 'e', - compactIdentity(identity.workspaceEpochId), - ); - const instanceRoot = join(epochRoot, 'i', compactIdentity(identity.workspaceInstanceId)); - return { - managedRoot, - repositoryRoot, - repositoryPath: join(repositoryRoot, 'repository.git'), - repositoryRecordPath: join(repositoryRoot, 'repository.json'), - hooksPath: join(repositoryRoot, 'hooks'), - homePath: join(managedRoot, 'git-home'), - epochRoot, - epochArtifactPath: join(epochRoot, 'epoch.json'), - instanceRoot, - bindingPath: join(instanceRoot, 'binding.json'), - baselineReceiptPath: join(instanceRoot, 'baseline-receipt.json'), - worktreePath: join(instanceRoot, 'worktree'), - quarantineRoot: join(managedRoot, 'quarantine'), - quarantineIntentRoot, - quarantineIntentPath: join( - quarantineIntentRoot, - `${compactIdentity(identity.workspaceInstanceId)}.json`, - ), - }; -} - -function assertOpenIdentity(input: ManagedWorkspaceIdentity): void { - for (const [name, value] of Object.entries({ - repositoryId: input.repositoryId, - workspaceId: input.workspaceId, - workspaceEpochId: input.workspaceEpochId, - workspaceInstanceId: input.workspaceInstanceId, - })) { - if (!IDENTIFIER_PATTERN.test(value)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Invalid ${name}: ${value}`, - ); - } - } -} - -function assertBindingShape(value: unknown): asserts value is ManagedWorkspaceBinding { - if (!isBinding(value)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Invalid managed workspace binding', - ); - } -} - -function assertBindingMatches( - binding: ManagedWorkspaceBinding, - input: CreateManagedWorkspaceFromSourceInput, - layout: WorkspaceLayout, - runtimeDigest: `sha256:${string}`, -): void { - assertBindingIdentity(binding, input, layout, runtimeDigest); - if (!samePath(binding.sourceRoot, input.sourceRoot)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace binding does not match the requested source provenance', - ); - } -} - -function assertBindingIdentity( - binding: ManagedWorkspaceBinding, - input: ManagedWorkspaceIdentity, - layout: WorkspaceLayout, - runtimeDigest: `sha256:${string}`, -): void { - assertBindingShape(binding); - assertBindingPaths(binding, layout); - if ( - binding.repositoryId !== input.repositoryId || - binding.workspaceId !== input.workspaceId || - binding.workspaceEpochId !== input.workspaceEpochId || - binding.workspaceInstanceId !== input.workspaceInstanceId || - binding.gitRuntimeSha256 !== runtimeDigest || - binding.materializationProfileDigest !== - materializationProfileDigest(runtimeDigest, binding.objectFormat) || - binding.headRef !== managedHeadRef(input.workspaceId, input.workspaceEpochId) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace binding does not match the requested managed identity', - ); - } -} - -function assertBindingRepository( - binding: ManagedWorkspaceBinding, - repository: ManagedRepositoryRecord, -): void { - if ( - binding.repositoryId !== repository.repositoryId || - !samePath(binding.repositoryPath, repository.repositoryPath) || - !samePath(binding.hooksPath, repository.hooksPath) || - binding.gitRuntimeSha256 !== repository.gitRuntimeSha256 || - binding.objectFormat !== repository.objectFormat || - repository.repositoryCapabilityDigest !== - repositoryCapabilityDigest(repository.gitRuntimeSha256, repository.objectFormat) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace binding does not match its Git repository record', - ); - } -} - -function assertBindingEpoch( - binding: ManagedWorkspaceBinding, - epoch: ManagedWorkspaceEpochArtifact, -): void { - if ( - binding.repositoryId !== epoch.repositoryId || - binding.workspaceId !== epoch.workspaceId || - binding.workspaceEpochId !== epoch.workspaceEpochId || - !samePath(binding.sourceRoot, epoch.sourceRoot) || - !samePath(binding.sourceGitCommonDir, epoch.sourceGitCommonDir) || - binding.sourceHeadCommitOid !== epoch.sourceHeadCommitOid || - binding.sourceTreeOid !== epoch.sourceTreeOid || - binding.baselineCommitOid !== epoch.baselineCommitOid || - binding.baselineTreeOid !== epoch.baselineTreeOid || - binding.headRef !== epoch.headRef || - binding.materializationProfileDigest !== epoch.materializationProfileDigest - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace binding does not match its epoch artifact', - ); - } -} - -function assertBindingPaths(binding: ManagedWorkspaceBinding, layout: WorkspaceLayout): void { - if ( - !samePath(binding.repositoryPath, layout.repositoryPath) || - !samePath(binding.worktreePath, layout.worktreePath) || - !samePath(binding.hooksPath, layout.hooksPath) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace binding escapes its owned storage namespace', - ); - } -} - -function assertRepositoryMatches( - record: ManagedRepositoryRecord, - input: CreateManagedWorkspaceFromSourceInput, - objectFormat: string, - layout: WorkspaceLayout, - runtimeDigest: `sha256:${string}`, -): void { - if ( - record.repositoryId !== input.repositoryId || - record.gitRuntimeSha256 !== runtimeDigest || - record.objectFormat !== objectFormat || - record.repositoryCapabilityDigest !== repositoryCapabilityDigest(runtimeDigest, objectFormat) || - !samePath(record.repositoryPath, layout.repositoryPath) || - !samePath(record.hooksPath, layout.hooksPath) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed Git repository does not match the requested repository capability', - ); - } -} - -async function readBinding(path: string): Promise { - const value = await readJson(path); - if (value === undefined) return undefined; - assertBindingShape(value); - return value; -} - -async function readBaselineReceipt( - path: string, -): Promise { - const value = await readJson(path); - if (value === undefined) return undefined; - assertBaselineReceiptShape(value); - return value; -} - -async function readRepositoryRecord(path: string): Promise { - const value = await readJson(path); - if (value === undefined) return undefined; - if (!isRepositoryRecord(value)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Invalid managed Git repository record: ${path}`, - ); - } - return value; -} - -function assertEpochArtifactMatches( - artifact: ManagedWorkspaceEpochArtifact, - input: CreateManagedWorkspaceFromSourceInput, - source: SourceRepositoryInspection, - repository: ManagedRepositoryRecord, -): void { - if ( - artifact.repositoryId !== input.repositoryId || - artifact.workspaceId !== input.workspaceId || - artifact.workspaceEpochId !== input.workspaceEpochId || - !samePath(artifact.sourceRoot, source.sourceRoot) || - !samePath(artifact.sourceGitCommonDir, source.gitCommonDir) || - artifact.sourceHeadCommitOid !== source.headCommitOid || - artifact.sourceTreeOid !== source.treeOid || - artifact.baselineTreeOid !== source.treeOid || - artifact.gitRuntimeSha256 !== repository.gitRuntimeSha256 || - artifact.objectFormat !== repository.objectFormat - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace epoch artifact does not match the requested source boundary', - ); - } -} - -function assertSameSourceObservation( - captured: SourceRepositoryInspection, - current: SourceRepositoryInspection, -): void { - if ( - !samePath(captured.sourceRoot, current.sourceRoot) || - !samePath(captured.gitCommonDir, current.gitCommonDir) || - captured.headCommitOid !== current.headCommitOid || - captured.treeOid !== current.treeOid || - captured.objectFormat !== current.objectFormat - ) { - throw new GitWorkspaceServiceError( - 'source_changed_during_baseline_import', - 'Managed workspace source changed while the baseline was being imported', - ); - } -} - -async function readEpochArtifact(path: string): Promise { - const value = await readJson(path); - if (value === undefined) return undefined; - if (!isEpochArtifact(value)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Invalid managed workspace epoch artifact: ${path}`, - ); - } - return value; -} - -async function readQuarantineIntent( - path: string, -): Promise { - const value = await readJson(path); - if (value === undefined) return undefined; - if (!isQuarantineIntent(value)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Invalid managed workspace quarantine intent: ${path}`, - ); - } - return value; -} - -async function readQuarantineRecord( - path: string, -): Promise { - const value = await readJson(path); - if (value === undefined) return undefined; - if (!isQuarantineRecord(value)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Invalid managed workspace quarantine record: ${path}`, - ); - } - return value; -} - -function assertQuarantineIntentMatches( - intent: ManagedWorkspaceQuarantineIntent, - binding: ManagedWorkspaceBinding, - reason: string, - layout: WorkspaceLayout, -): void { - assertBindingShape(intent.binding); - assertBindingPaths(intent.binding, layout); - if ( - !sameBinding(intent.binding, binding) || - intent.reason !== reason || - !isPathWithin(intent.quarantinePath, layout.quarantineRoot) || - !samePath(dirname(intent.quarantinePath), layout.quarantineRoot) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace quarantine intent does not match its binding or owned namespace', - ); - } -} - -function isBinding(value: unknown): value is ManagedWorkspaceBinding { - if (!isRecord(value)) return false; - return ( - hasExactKeys(value, BINDING_KEYS) && - value.schemaVersion === BINDING_SCHEMA_VERSION && - value.protocol === 'git_managed_workspace_v1' && - typeof value.repositoryId === 'string' && - IDENTIFIER_PATTERN.test(value.repositoryId) && - typeof value.workspaceId === 'string' && - IDENTIFIER_PATTERN.test(value.workspaceId) && - typeof value.workspaceEpochId === 'string' && - IDENTIFIER_PATTERN.test(value.workspaceEpochId) && - typeof value.workspaceInstanceId === 'string' && - IDENTIFIER_PATTERN.test(value.workspaceInstanceId) && - typeof value.sourceRoot === 'string' && - isAbsolute(value.sourceRoot) && - typeof value.sourceGitCommonDir === 'string' && - isAbsolute(value.sourceGitCommonDir) && - typeof value.sourceHeadCommitOid === 'string' && - OID_PATTERN.test(value.sourceHeadCommitOid) && - typeof value.sourceTreeOid === 'string' && - OID_PATTERN.test(value.sourceTreeOid) && - typeof value.repositoryPath === 'string' && - isAbsolute(value.repositoryPath) && - typeof value.worktreePath === 'string' && - isAbsolute(value.worktreePath) && - typeof value.hooksPath === 'string' && - isAbsolute(value.hooksPath) && - typeof value.baselineCommitOid === 'string' && - OID_PATTERN.test(value.baselineCommitOid) && - typeof value.baselineTreeOid === 'string' && - OID_PATTERN.test(value.baselineTreeOid) && - typeof value.headRef === 'string' && - value.headRef === managedHeadRef(value.workspaceId, value.workspaceEpochId) && - typeof value.gitRuntimeSha256 === 'string' && - SHA256_PATTERN.test(value.gitRuntimeSha256) && - (value.objectFormat === 'sha1' || value.objectFormat === 'sha256') && - typeof value.materializationProfileDigest === 'string' && - SHA256_PATTERN.test(value.materializationProfileDigest) && - value.materializationSemantics === MATERIALIZATION_SEMANTICS - ); -} - -function assertBaselineReceiptShape( - value: unknown, -): asserts value is ManagedWorkspaceBaselineReceiptV1 { - if (!isBaselineReceipt(value)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Invalid managed workspace baseline receipt', - ); - } -} - -function isBaselineReceipt(value: unknown): value is ManagedWorkspaceBaselineReceiptV1 { - if (!isRecord(value)) return false; - return ( - hasExactKeys(value, BASELINE_RECEIPT_KEYS) && - value.schemaVersion === BASELINE_RECEIPT_SCHEMA_VERSION && - value.protocol === 'maka_managed_workspace_baseline_receipt_v1' && - isBinding(value.binding) && - typeof value.workspaceVersionId === 'string' && - /^version_[a-f0-9]{32}$/u.test(value.workspaceVersionId) && - value.policyVersion === 1 && - typeof value.policyHash === 'string' && - SHA256_PATTERN.test(value.policyHash) && - typeof value.epochOpenedEventId === 'string' && - /^[A-Za-z0-9_-]{1,128}$/u.test(value.epochOpenedEventId) && - typeof value.baselineAcceptedEventId === 'string' && - /^[A-Za-z0-9_-]{1,128}$/u.test(value.baselineAcceptedEventId) && - value.epochOpenedEventId !== value.baselineAcceptedEventId && - typeof value.treeDeltaDigest === 'string' && - SHA256_PATTERN.test(value.treeDeltaDigest) && - typeof value.changedFileCount === 'number' && - Number.isSafeInteger(value.changedFileCount) && - value.changedFileCount >= 0 && - value.deletedFileCount === 0 - ); -} - -function assertBaselineReceiptMatches( - receipt: ManagedWorkspaceBaselineReceiptV1, - binding: ManagedWorkspaceBinding, - summary: BaselineTreeSummary, -): void { - const identities = deriveBaselineReceiptIdentities(binding); - if ( - !sameBinding(receipt.binding, binding) || - receipt.workspaceVersionId !== identities.workspaceVersionId || - receipt.epochOpenedEventId !== identities.epochOpenedEventId || - receipt.baselineAcceptedEventId !== identities.baselineAcceptedEventId || - receipt.policyVersion !== 1 || - receipt.policyHash !== MANAGED_BASELINE_POLICY_HASH_V1 || - receipt.treeDeltaDigest !== summary.treeDeltaDigest || - receipt.changedFileCount !== summary.changedFileCount || - receipt.deletedFileCount !== 0 - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed workspace baseline receipt does not match its verified Git boundary', - ); - } -} - -function deriveBaselineReceiptIdentities(binding: ManagedWorkspaceBinding): { - readonly workspaceVersionId: string; - readonly epochOpenedEventId: string; - readonly baselineAcceptedEventId: string; -} { - const identity = { - repositoryId: binding.repositoryId, - workspaceId: binding.workspaceId, - workspaceEpochId: binding.workspaceEpochId, - workspaceInstanceId: binding.workspaceInstanceId, - baselineCommitOid: binding.baselineCommitOid, - baselineTreeOid: binding.baselineTreeOid, - policyVersion: 1, - policyHash: MANAGED_BASELINE_POLICY_HASH_V1, - } as const; - const derive = (purpose: string): string => - createHash('sha256') - .update( - JSON.stringify({ - protocol: 'maka_managed_workspace_baseline_identity_v1', - purpose, - identity, - }), - ) - .digest('hex'); - return { - workspaceVersionId: `version_${derive('workspace_version').slice(0, 32)}`, - epochOpenedEventId: `workspace_epoch_opened_${derive('epoch_opened').slice(0, 64)}`, - baselineAcceptedEventId: `workspace_baseline_accepted_${derive('baseline_accepted').slice(0, 64)}`, - }; -} - -function hashCanonicalJson(value: unknown): `sha256:${string}` { - return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`; -} - -function isRepositoryRecord(value: unknown): value is ManagedRepositoryRecord { - if (!isRecord(value)) return false; - return ( - hasExactKeys(value, REPOSITORY_KEYS) && - value.schemaVersion === REPOSITORY_SCHEMA_VERSION && - value.protocol === 'maka_managed_git_repository_v1' && - typeof value.repositoryId === 'string' && - IDENTIFIER_PATTERN.test(value.repositoryId) && - typeof value.repositoryPath === 'string' && - isAbsolute(value.repositoryPath) && - typeof value.hooksPath === 'string' && - isAbsolute(value.hooksPath) && - typeof value.gitRuntimeSha256 === 'string' && - SHA256_PATTERN.test(value.gitRuntimeSha256) && - (value.objectFormat === 'sha1' || value.objectFormat === 'sha256') && - typeof value.repositoryCapabilityDigest === 'string' && - SHA256_PATTERN.test(value.repositoryCapabilityDigest) - ); -} - -function isEpochArtifact(value: unknown): value is ManagedWorkspaceEpochArtifact { - if (!isRecord(value)) return false; - return ( - hasExactKeys(value, EPOCH_ARTIFACT_KEYS) && - value.schemaVersion === EPOCH_ARTIFACT_SCHEMA_VERSION && - value.protocol === 'maka_managed_workspace_epoch_artifact_v1' && - typeof value.repositoryId === 'string' && - IDENTIFIER_PATTERN.test(value.repositoryId) && - typeof value.workspaceId === 'string' && - IDENTIFIER_PATTERN.test(value.workspaceId) && - typeof value.workspaceEpochId === 'string' && - IDENTIFIER_PATTERN.test(value.workspaceEpochId) && - typeof value.sourceRoot === 'string' && - isAbsolute(value.sourceRoot) && - typeof value.sourceGitCommonDir === 'string' && - isAbsolute(value.sourceGitCommonDir) && - typeof value.sourceHeadCommitOid === 'string' && - OID_PATTERN.test(value.sourceHeadCommitOid) && - typeof value.sourceTreeOid === 'string' && - OID_PATTERN.test(value.sourceTreeOid) && - typeof value.baselineCommitOid === 'string' && - OID_PATTERN.test(value.baselineCommitOid) && - typeof value.baselineTreeOid === 'string' && - OID_PATTERN.test(value.baselineTreeOid) && - value.baselineRef === managedBaselineRef(value.workspaceEpochId) && - value.headRef === managedHeadRef(value.workspaceId, value.workspaceEpochId) && - typeof value.gitRuntimeSha256 === 'string' && - SHA256_PATTERN.test(value.gitRuntimeSha256) && - (value.objectFormat === 'sha1' || value.objectFormat === 'sha256') && - typeof value.materializationProfileDigest === 'string' && - SHA256_PATTERN.test(value.materializationProfileDigest) && - value.materializationSemantics === MATERIALIZATION_SEMANTICS - ); -} - -function isQuarantineIntent(value: unknown): value is ManagedWorkspaceQuarantineIntent { - if (!isRecord(value)) return false; - return ( - hasExactKeys(value, QUARANTINE_INTENT_KEYS) && - value.schemaVersion === QUARANTINE_INTENT_SCHEMA_VERSION && - value.protocol === 'maka_managed_workspace_quarantine_intent_v1' && - typeof value.reason === 'string' && - value.reason.length > 0 && - typeof value.quarantinePath === 'string' && - isAbsolute(value.quarantinePath) && - isBinding(value.binding) - ); -} - -function isQuarantineRecord(value: unknown): value is ManagedWorkspaceQuarantineRecord { - if (!isRecord(value)) return false; - return ( - hasExactKeys(value, QUARANTINE_RECORD_KEYS) && - value.protocol === 'maka_managed_workspace_quarantine_v1' && - typeof value.reason === 'string' && - value.reason.length > 0 && - isBinding(value.binding) - ); -} - -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function hasExactKeys(value: Record, expected: readonly string[]): boolean { - const actual = Object.keys(value).sort(); - const canonical = [...expected].sort(); - return ( - actual.length === canonical.length && actual.every((key, index) => key === canonical[index]) - ); -} - -function sameBinding(left: ManagedWorkspaceBinding, right: ManagedWorkspaceBinding): boolean { - return JSON.stringify(left) === JSON.stringify(right); -} - -function sameBaselineReceipt( - left: ManagedWorkspaceBaselineReceiptV1, - right: ManagedWorkspaceBaselineReceiptV1, -): boolean { - return JSON.stringify(left) === JSON.stringify(right); -} - -function sameQuarantineRecord( - left: ManagedWorkspaceQuarantineRecord, - right: ManagedWorkspaceQuarantineRecord, -): boolean { - return JSON.stringify(left) === JSON.stringify(right); -} - -async function readJson(path: string): Promise { - try { - const info = await lstat(path); - if (!info.isFile() || info.isSymbolicLink()) { - throw new Error('control record is not one regular non-symlink file'); - } - return JSON.parse(await readFile(path, 'utf8')) as unknown; - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined; - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Invalid managed workspace control record: ${path}`, - { cause: error }, - ); - } -} - -async function atomicWriteJson(path: string, value: unknown): Promise { - await mkdir(dirname(path), { recursive: true }); - const temporaryPath = `${path}.tmp-${randomUUID()}`; - try { - const file = await openFile(temporaryPath, 'wx', 0o600); - try { - await file.writeFile(`${JSON.stringify(value, null, 2)}\n`, 'utf8'); - await file.sync(); - } finally { - await file.close(); - } - await rename(temporaryPath, path); - if (process.platform !== 'win32') { - const directory = await openFile(dirname(path), 'r'); - try { - await directory.sync(); - } finally { - await directory.close(); - } - } - } finally { - await rm(temporaryPath, { force: true }); - } -} - -async function moveToQuarantine( - source: string, - quarantineRoot: string, - label: string, -): Promise { - await mkdir(quarantineRoot, { recursive: true }); - const target = join(quarantineRoot, `${sanitizeReason(label)}-${Date.now()}-${randomUUID()}`); - await rename(source, target); - return target; -} - -async function canonicalDirectory( - path: string, - code: GitWorkspaceServiceErrorCode, -): Promise { - try { - const canonical = normalize(await realpath(path)); - if (!(await stat(canonical)).isDirectory()) throw new Error('not a directory'); - return canonical; - } catch (error) { - throw new GitWorkspaceServiceError(code, `Directory is unavailable: ${path}`, { cause: error }); - } -} - -async function ensureOwnedDirectory(path: string, ownerRoot: string): Promise { - const requested = normalize(resolve(path)); - const canonicalOwner = normalize(await realpath(ownerRoot)); - const relation = relative(normalize(resolve(ownerRoot)), requested); - if (relation.startsWith('..') || isAbsolute(relation)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed workspace control path escapes its owner root: ${path}`, - ); - } - let current = normalize(resolve(ownerRoot)); - for (const segment of relation.split(/[\\/]/u).filter(Boolean)) { - current = join(current, segment); - try { - await mkdir(current); - } catch (error) { - if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error; - } - const info = await lstat(current); - const canonical = normalize(await realpath(current)); - if (!info.isDirectory() || info.isSymbolicLink() || !isPathWithin(canonical, canonicalOwner)) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed workspace control path is not one owned directory: ${current}`, - ); - } - } -} - -async function assertOwnedManagedWorkspaceLayout( - storageRoot: string, - layout: WorkspaceLayout, -): Promise { - const canonicalStorageRoot = normalize(await realpath(storageRoot)); - await assertOwnedDirectoryEntry(layout.managedRoot, canonicalStorageRoot, true); - await assertOwnedDirectoryEntry(layout.quarantineRoot, layout.managedRoot, true); - await assertOwnedDirectoryEntry(layout.homePath, layout.managedRoot, true); - await assertOwnedDirectoryEntry(layout.quarantineIntentRoot, layout.managedRoot, false); - await assertOwnedDirectoryEntry(layout.repositoryRoot, layout.managedRoot, false); - await assertOwnedDirectoryEntry(layout.repositoryPath, layout.repositoryRoot, false); - await assertOwnedDirectoryEntry(layout.hooksPath, layout.repositoryRoot, false); - await assertOwnedDirectoryEntry(layout.epochRoot, layout.managedRoot, false); - await assertOwnedDirectoryEntry(layout.instanceRoot, layout.epochRoot, false); - await assertOwnedDirectoryEntry(layout.worktreePath, layout.instanceRoot, false); -} - -async function assertOwnedDirectoryEntry( - path: string, - ownerRoot: string, - required: boolean, -): Promise { - let info; - try { - info = await lstat(path); - } catch (error) { - if (!required && (error as NodeJS.ErrnoException).code === 'ENOENT') return; - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed workspace owned directory is unavailable: ${path}`, - { cause: error }, - ); - } - let canonicalOwner: string; - let canonicalPath: string; - try { - canonicalOwner = normalize(await realpath(ownerRoot)); - canonicalPath = normalize(await realpath(path)); - } catch (error) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed workspace owned directory cannot be canonicalized: ${path}`, - { cause: error }, - ); - } - if ( - !info.isDirectory() || - info.isSymbolicLink() || - !isPathWithin(canonicalPath, canonicalOwner) - ) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - `Managed workspace owned directory escaped or changed identity: ${path}`, - ); - } -} - -async function pathExists(path: string): Promise { - try { - await stat(path); - return true; - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false; - throw error; - } -} - -async function pathEntryExists(path: string): Promise { - try { - await lstat(path); - return true; - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false; - throw error; - } -} - -async function isNonSymlinkDirectory(path: string): Promise { - try { - const info = await lstat(path); - return info.isDirectory() && !info.isSymbolicLink(); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false; - throw error; - } -} - -function resolveGitPath(cwd: string, path: string): string { - return isAbsolute(path) ? path : resolve(cwd, path); -} - -function samePath(left: string, right: string): boolean { - const normalizedLeft = normalize(resolve(left)); - const normalizedRight = normalize(resolve(right)); - return process.platform === 'win32' - ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() - : normalizedLeft === normalizedRight; -} - -function isPathWithin(path: string, root: string): boolean { - const candidate = normalize(resolve(path)); - const owner = normalize(resolve(root)); - const comparisonCandidate = process.platform === 'win32' ? candidate.toLowerCase() : candidate; - const comparisonOwner = process.platform === 'win32' ? owner.toLowerCase() : owner; - const relation = relative(comparisonOwner, comparisonCandidate); - return relation === '' || (!relation.startsWith('..') && !isAbsolute(relation)); -} - -function sanitizeReason(value: string): string { - return ( - value - .toLowerCase() - .replace(/[^a-z0-9_-]+/gu, '-') - .replace(/^-+|-+$/gu, '') - .slice(0, 64) || 'unknown' - ); -} - -function hasUnsafeSourceConfig(raw: string | undefined): boolean { - if (!raw) return false; - return raw.split(/\r?\n/u).some((line) => { - const key = line.trim().toLowerCase(); - return ( - key.startsWith('include.') || - key.startsWith('includeif.') || - key === 'extensions.objectformat' || - key === 'extensions.partialclone' || - key === 'core.fsmonitor' || - (key.startsWith('remote.') && key.endsWith('.promisor')) - ); - }); -} - -function compactIdentity(value: string): string { - return createHash('sha256').update(value).digest('hex').slice(0, 20); -} - -function managedHeadRef(workspaceId: string, workspaceEpochId: string): string { - return `refs/maka/workspaces/${workspaceId}/epochs/${workspaceEpochId}/head`; -} - -function managedBaselineRef(workspaceEpochId: string): string { - return `refs/maka/baselines/${workspaceEpochId}`; -} - -function worktreeLockReason( - identity: Pick, -): string { - return `maka managed workspace ${identity.workspaceInstanceId}`; -} - -function assertWorktreeRegistrationLocked( - porcelain: string, - worktreePath: string, - expectedReason: string, -): void { - const registration = findWorktreeRegistration(porcelain, worktreePath); - if (!registration || registration.lockReason !== expectedReason) { - throw new GitWorkspaceServiceError( - 'managed_workspace_identity_conflict', - 'Managed worktree registration or Maka ownership lock is unavailable', - ); - } -} - -interface WorktreeRegistration { - readonly headOid?: string; - readonly lockReason?: string; -} - -function findWorktreeRegistration( - porcelain: string, - worktreePath: string, -): WorktreeRegistration | undefined { - for (const block of porcelain.split(/\r?\n\r?\n/u)) { - const lines = block.split(/\r?\n/u); - const pathLine = lines.find((line) => line.startsWith('worktree ')); - if (!pathLine || !samePath(pathLine.slice('worktree '.length), worktreePath)) continue; - const headLine = lines.find((line) => line.startsWith('HEAD ')); - const lockedLine = lines.find((line) => line === 'locked' || line.startsWith('locked ')); - return { - ...(headLine ? { headOid: headLine.slice('HEAD '.length) } : {}), - ...(lockedLine ? { lockReason: lockedLine.slice('locked'.length).trim() } : {}), - }; - } - return undefined; -} - -function repositoryCapabilityDigest( - gitRuntimeSha256: `sha256:${string}`, - objectFormat: string, -): `sha256:${string}` { - const capability = JSON.stringify({ - protocol: 'maka_managed_git_repository_capability_v1', - gitRuntimeSha256, - objectFormat, - hooks: 'disabled_v1', - credentials: 'disabled_v1', - autoGc: false, - alternates: 'forbidden_v1', - }); - return `sha256:${createHash('sha256').update(capability).digest('hex')}`; -} - -function materializationProfileDigest( - gitRuntimeSha256: `sha256:${string}`, - objectFormat: string, -): `sha256:${string}` { - const profile = JSON.stringify({ - protocol: 'git_materialization_profile_v1', - gitRuntimeSha256, - objectFormat, - platform: process.platform, - autocrlf: false, - safecrlf: true, - attributes: 'reject_v1', - symlinks: 'reject_v1', - submodules: 'reject_v1', - caseCollisions: 'reject_v1', - ignoredInputs: 'exclude_v1', - }); - return `sha256:${createHash('sha256').update(profile).digest('hex')}`; -} - -function parseTreeEntries(output: Buffer): GitTreeEntry[] { - const entries: GitTreeEntry[] = []; - let start = 0; - while (start < output.length) { - const end = output.indexOf(0, start); - if (end < 0) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Git source tree contains an unterminated entry', - ); - } - const record = output.subarray(start, end); - start = end + 1; - if (record.length === 0) continue; - const tab = record.indexOf(0x09); - const metadata = tab >= 0 ? record.subarray(0, tab).toString('ascii') : ''; - const pathBytes = tab >= 0 ? record.subarray(tab + 1) : Buffer.alloc(0); - const [mode = '', objectType = '', oid = ''] = metadata.split(' '); - let path = ''; - try { - path = new TextDecoder('utf-8', { fatal: true }).decode(pathBytes); - } catch { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Managed workspace v1 requires Git paths to be valid UTF-8', - ); - } - if ( - !mode || - !objectType || - !OID_PATTERN.test(oid) || - pathBytes.length === 0 || - !Buffer.from(path, 'utf8').equals(pathBytes) - ) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Git source tree contains an unreadable entry', - ); - } - entries.push({ mode, objectType, oid, path, pathBytesBase64: pathBytes.toString('base64') }); - } - return entries; -} - -function baselineTreeSummary(entries: readonly GitTreeEntry[]): BaselineTreeSummary { - const manifest = JSON.stringify({ - protocol: 'maka_git_empty_tree_delta_v1', - entries: entries.map(({ mode, objectType, oid, pathBytesBase64 }) => ({ - mode, - objectType, - oid, - pathBytesBase64, - })), - }); - return { - treeDeltaDigest: `sha256:${createHash('sha256').update(manifest).digest('hex')}`, - changedFileCount: entries.length, - }; -} - -function assertSupportedTree(entries: readonly GitTreeEntry[]): void { - const caseFolded = new Set(); - for (const entry of entries) { - if ((entry.mode !== '100644' && entry.mode !== '100755') || entry.objectType !== 'blob') { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Symlinks, submodules, and special Git modes are not supported by managed workspace v1', - ); - } - if (entry.path === '.gitattributes' || entry.path.endsWith('/.gitattributes')) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Tracked .gitattributes require a materialization profile not supported by v1', - ); - } - const folded = entry.path.normalize('NFC').toLocaleLowerCase('en-US'); - if (caseFolded.has(folded)) { - throw new GitWorkspaceServiceError( - 'repository_ineligible', - 'Case-colliding Git paths are not supported by managed workspace v1', - ); - } - caseFolded.add(folded); - } -} - -function baselineCommitEnvironment(): NodeJS.ProcessEnv { - return { - GIT_AUTHOR_NAME: 'Maka Workspace Service', - GIT_AUTHOR_EMAIL: 'workspace@maka.invalid', - GIT_AUTHOR_DATE: BASELINE_DATE, - GIT_COMMITTER_NAME: 'Maka Workspace Service', - GIT_COMMITTER_EMAIL: 'workspace@maka.invalid', - GIT_COMMITTER_DATE: BASELINE_DATE, - GIT_WORKSPACE_BASELINE_MESSAGE: BASELINE_MESSAGE, - }; -} - -function isOwnedBaselineCommit(raw: string): boolean { - const normalized = raw.replace(/\r\n/gu, '\n'); - const separator = normalized.indexOf('\n\n'); - if (separator < 0) return false; - const headers = normalized.slice(0, separator).split('\n'); - const message = normalized.slice(separator + 2).trimEnd(); - return ( - headers.length === 3 && - /^tree (?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(headers[0] ?? '') && - headers[1] === 'author Maka Workspace Service 946684800 +0000' && - headers[2] === 'committer Maka Workspace Service 946684800 +0000' && - message === BASELINE_MESSAGE.trimEnd() - ); -} - -function isolatedGitEnvironment( - input: VerifiedGitRuntimeInput, - homePath: string, -): NodeJS.ProcessEnv { - const executablePath = input.executablePath; - const env: NodeJS.ProcessEnv = { - HOME: homePath, - XDG_CONFIG_HOME: join(homePath, 'xdg'), - GIT_CONFIG_NOSYSTEM: '1', - GIT_TERMINAL_PROMPT: '0', - GCM_INTERACTIVE: 'never', - GIT_OPTIONAL_LOCKS: '0', - GIT_NO_REPLACE_OBJECTS: '1', - GIT_NO_LAZY_FETCH: '1', - LANG: 'C', - LC_ALL: 'C', - PATH: dirname(executablePath), - }; - if (input.distribution?.kind === 'dugite_native_v1') { - Object.assign( - env, - bundledGitEnvironment({ - platform: process.platform, - arch: process.arch, - rootPath: input.distribution.rootPath, - executablePath, - }), - ); - } - for (const name of ['SystemRoot', 'WINDIR', 'COMSPEC', 'TMP', 'TEMP', 'TMPDIR']) { - if (process.env[name]) env[name] = process.env[name]; - } - return env; -} - -function fixedGitArguments(hooksPath: string): string[] { - return [ - '--no-pager', - '-c', - 'core.autocrlf=false', - '-c', - 'core.safecrlf=true', - '-c', - 'core.fsmonitor=false', - '-c', - 'core.longpaths=true', - '-c', - `core.hooksPath=${hooksPath}`, - '-c', - 'credential.helper=', - '-c', - 'credential.interactive=never', - '-c', - 'core.sshCommand=', - '-c', - 'protocol.allow=never', - '-c', - 'gc.auto=0', - '-c', - 'submodule.recurse=false', - ]; -} - -function waitForChildExit(child: ChildProcess): Promise { - return new Promise((resolve, reject) => { - child.once('error', reject); - child.once('exit', (code) => resolve(code)); - }); -} - -function collectBoundedStderr(stream: NodeJS.ReadableStream | null): Promise { - if (!stream) return Promise.resolve(''); - return new Promise((resolve) => { - let output = ''; - stream.on('data', (chunk) => { - if (output.length < 64 * 1024) output += String(chunk); - }); - stream.on('end', () => resolve(output.slice(0, 64 * 1024).trim())); - }); -} - -async function sha256File(path: string): Promise<`sha256:${string}`> { - const hash = createHash('sha256'); - for await (const chunk of createReadStream(path)) hash.update(chunk); - return `sha256:${hash.digest('hex')}`; -} - -function exitCode(error: unknown): number | undefined { - if (!isRecord(error)) return undefined; - return typeof error.code === 'number' ? error.code : undefined; -} diff --git a/packages/storage/src/managed-baseline-receipt-authority-internal.ts b/packages/storage/src/managed-baseline-receipt-authority-internal.ts deleted file mode 100644 index e1c18ee458..0000000000 --- a/packages/storage/src/managed-baseline-receipt-authority-internal.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import type { - CreateManagedWorkspaceFromSourceInput, - GitWorkspaceService, - ManagedWorkspaceBaselineReceiptV1, - ManagedWorkspaceBinding, -} from './git-workspace-service.js'; - -export interface ManagedBaselineReceiptAuthorityInternal { - issue(binding: ManagedWorkspaceBinding): Promise; - require(input: CreateManagedWorkspaceFromSourceInput): Promise; - verify(receipt: ManagedWorkspaceBaselineReceiptV1): Promise; -} - -const receiptAuthorities = new WeakMap< - GitWorkspaceService, - ManagedBaselineReceiptAuthorityInternal ->(); - -export function registerManagedBaselineReceiptAuthorityInternal( - service: GitWorkspaceService, - authority: ManagedBaselineReceiptAuthorityInternal, -): void { - if (receiptAuthorities.has(service)) { - throw new Error('Managed baseline receipt authority is already registered'); - } - receiptAuthorities.set(service, authority); -} - -export function requireManagedBaselineReceiptAuthorityInternal( - service: GitWorkspaceService, -): ManagedBaselineReceiptAuthorityInternal { - const authority = receiptAuthorities.get(service); - if (!authority) { - throw new Error('Managed baseline receipt authority is unavailable'); - } - return authority; -} diff --git a/packages/storage/src/managed-workspace-execution-authority-internal.ts b/packages/storage/src/managed-workspace-execution-authority-internal.ts deleted file mode 100644 index 19676b9cde..0000000000 --- a/packages/storage/src/managed-workspace-execution-authority-internal.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import type { RuntimeWorkspaceVersionAuthorityStore } from '@maka/core/runtime-event-store'; -import type { WorkspaceHeadRecordV1 } from '@maka/core/workspace-version-authority'; -import type { - ManagedWorkspaceBaselineReceiptV1, - ManagedWorkspaceBinding, -} from './git-workspace-service.js'; - -export interface ManagedWorkspaceExecutionHandle { - readonly kind: 'managed_workspace_execution_handle_v1'; -} - -export interface ManagedWorkspaceExecutionScope { - readonly kind: 'managed_workspace_execution_scope_v1'; -} - -export type ManagedWorkspaceExecutionAuthorityErrorCode = - | 'managed_workspace_execution_scope_invalid' - | 'managed_workspace_execution_scope_expired'; - -export class ManagedWorkspaceExecutionAuthorityError extends Error { - constructor( - readonly code: ManagedWorkspaceExecutionAuthorityErrorCode, - message: string, - ) { - super(message); - this.name = 'ManagedWorkspaceExecutionAuthorityError'; - } -} - -export interface ManagedWorkspaceExecutionAuthorityStateInternal { - readonly store: RuntimeWorkspaceVersionAuthorityStore; - readonly binding: Readonly; - readonly receipt: Readonly; - readonly head: Readonly; -} - -const states = new WeakMap< - object, - ManagedWorkspaceExecutionAuthorityStateInternal & { - readonly ownerToken: object; - } ->(); - -export interface ManagedWorkspaceExecutionScopeStateInternal { - readonly provisioning: 'canonical_tree_only_v1'; - readonly workspaceEffect: 'none'; - readonly cwd: string; - readonly binding: Readonly; - readonly head: Readonly; -} - -const scopes = new WeakMap< - object, - ManagedWorkspaceExecutionScopeStateInternal & { - readonly ownerToken: object; - active: boolean; - } ->(); - -export function issueManagedWorkspaceExecutionHandleInternal( - ownerToken: object, - state: ManagedWorkspaceExecutionAuthorityStateInternal, -): ManagedWorkspaceExecutionHandle { - const handle = Object.freeze({ kind: 'managed_workspace_execution_handle_v1' as const }); - states.set(handle, { ...state, ownerToken }); - return handle; -} - -export function requireManagedWorkspaceExecutionHandleInternal( - ownerToken: object, - handle: ManagedWorkspaceExecutionHandle, -): ManagedWorkspaceExecutionAuthorityStateInternal { - const state = states.get(handle); - if (!state || state.ownerToken !== ownerToken) { - throw new Error('Managed workspace execution handle is invalid for this owner'); - } - return state; -} - -function inspectManagedWorkspaceExecutionHandleForTest( - handle: ManagedWorkspaceExecutionHandle, -): ManagedWorkspaceExecutionAuthorityStateInternal { - const state = states.get(handle); - if (!state) throw new Error('Managed workspace execution handle is invalid'); - return state; -} - -export function issueManagedWorkspaceExecutionScopeInternal( - ownerToken: object, - state: ManagedWorkspaceExecutionScopeStateInternal, -): ManagedWorkspaceExecutionScope { - const scope = Object.freeze({ kind: 'managed_workspace_execution_scope_v1' as const }); - scopes.set(scope, { ...state, ownerToken, active: true }); - return scope; -} - -export function revokeManagedWorkspaceExecutionScopeInternal( - ownerToken: object, - scope: ManagedWorkspaceExecutionScope, -): void { - const state = scopes.get(scope); - if (!state || state.ownerToken !== ownerToken) { - throw new ManagedWorkspaceExecutionAuthorityError( - 'managed_workspace_execution_scope_invalid', - 'Managed workspace execution scope is invalid for this owner', - ); - } - state.active = false; -} - -export function requireManagedWorkspaceExecutionScopeInternal( - ownerToken: object, - scope: ManagedWorkspaceExecutionScope, -): ManagedWorkspaceExecutionScopeStateInternal { - const state = scopes.get(scope); - if (!state || state.ownerToken !== ownerToken) { - throw new ManagedWorkspaceExecutionAuthorityError( - 'managed_workspace_execution_scope_invalid', - 'Managed workspace execution scope is invalid for this owner', - ); - } - if (!state.active) { - throw new ManagedWorkspaceExecutionAuthorityError( - 'managed_workspace_execution_scope_expired', - 'Managed workspace execution scope has expired', - ); - } - return state; -} - -function inspectManagedWorkspaceExecutionScopeForTest( - scope: ManagedWorkspaceExecutionScope, -): ManagedWorkspaceExecutionScopeStateInternal { - const state = scopes.get(scope); - if (!state) { - throw new ManagedWorkspaceExecutionAuthorityError( - 'managed_workspace_execution_scope_invalid', - 'Managed workspace execution scope is invalid', - ); - } - if (!state.active) { - throw new ManagedWorkspaceExecutionAuthorityError( - 'managed_workspace_execution_scope_expired', - 'Managed workspace execution scope has expired', - ); - } - return state; -} - -/** - * Test-only evidence access. Production consumers must use the owner-bound - * require* functions and may never decode a handle or scope without its owner token. - */ -export const managedWorkspaceExecutionAuthorityTestSupport = Object.freeze({ - inspectHandle: inspectManagedWorkspaceExecutionHandleForTest, - inspectScope: inspectManagedWorkspaceExecutionScopeForTest, -}); diff --git a/packages/storage/src/managed-workspace-owner.ts b/packages/storage/src/managed-workspace-owner.ts deleted file mode 100644 index 5ba63d7293..0000000000 --- a/packages/storage/src/managed-workspace-owner.ts +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { AsyncLocalStorage } from 'node:async_hooks'; -import { - createGitWorkspaceService, - type CreateManagedWorkspaceFromSourceInput, - GitWorkspaceServiceError, - type GitWorkspaceService, - type GitWorkspaceServiceFailpoint, - type ManagedWorkspaceBaselineReceiptV1, - type ManagedWorkspaceBinding, - type VerifiedGitRuntimeInput, -} from './git-workspace-service.js'; -import { - requireManagedBaselineReceiptAuthorityInternal, - type ManagedBaselineReceiptAuthorityInternal, -} from './managed-baseline-receipt-authority-internal.js'; -import { - issueManagedWorkspaceExecutionHandleInternal, - issueManagedWorkspaceExecutionScopeInternal, - requireManagedWorkspaceExecutionHandleInternal, - revokeManagedWorkspaceExecutionScopeInternal, - type ManagedWorkspaceExecutionHandle, - type ManagedWorkspaceExecutionScope, -} from './managed-workspace-execution-authority-internal.js'; -import { - createManagedWorkspaceWorkerBridgeInternal, - type ManagedWorkspaceFilesystemWorker, - type ManagedWorkspaceReadOnlyOperation, - type ManagedWorkspaceReadOnlyResult, - type ManagedWorkspaceWorkerBridgeInternal, -} from './managed-workspace-worker-bridge-internal.js'; -import type { RuntimeWorkspaceVersionAuthorityStore } from '@maka/core/runtime-event-store'; -import type { WorkspaceHeadRecordV1 } from '@maka/core/workspace-version-authority'; -import { - assertInteractiveRootOwner, - authenticateInteractiveRootOwner, - createStorageRootLeaseIdentityGuard, - runWithStorageRootLease, - type InteractiveRootOwner, -} from './root-authority.js'; -import { - assertWorkspaceBaselineAuthorityStoreRootInternal, - bindWorkspaceBaselineAuthorityStoreRootInternal, - commitWorkspaceBaselineInternal, - readWorkspaceHeadInternal, -} from './workspace-version-authority-internal.js'; - -// RuntimeEvent order is assigned by the SQLite authority spine. M0 therefore -// uses a protocol-fixed logical timestamp and keeps unauthenticated wall-clock -// time out of the durable Git receipt. -const MANAGED_BASELINE_LOGICAL_TIMESTAMP_V1 = 0; - -export type ManagedWorkspaceOwnerErrorCode = - | 'managed_workspace_owner_conflict' - | 'managed_workspace_owner_unavailable' - | 'managed_workspace_owner_closing' - | 'managed_workspace_owner_reentrant_close' - | 'managed_workspace_worker_unavailable' - | 'managed_workspace_quarantined' - | 'managed_workspace_execution_handle_invalid'; - -export class ManagedWorkspaceOwnerError extends Error { - constructor( - readonly code: ManagedWorkspaceOwnerErrorCode, - message: string, - options?: ErrorOptions, - ) { - super(message, options); - this.name = 'ManagedWorkspaceOwnerError'; - } -} - -export interface OpenManagedWorkspaceOwnerInput { - readonly rootOwner: InteractiveRootOwner; - readonly gitRuntime: VerifiedGitRuntimeInput; - readonly failpoint?: (point: ManagedWorkspaceOwnerFailpoint) => void | Promise; - readonly filesystemWorker?: ManagedWorkspaceFilesystemWorker; -} - -export type ManagedWorkspaceOwnerFailpoint = - | GitWorkspaceServiceFailpoint - | 'after_initial_store_root_validation' - | 'after_baseline_authority_commit' - | 'after_post_commit_artifact_verification' - | 'after_execution_artifact_verification'; - -export type OpenManagedWorkspaceBaselineInput = CreateManagedWorkspaceFromSourceInput; - -export interface OpenManagedWorkspaceBaselineResult { - readonly created: boolean; - readonly head: WorkspaceHeadRecordV1; - readonly executionHandle: ManagedWorkspaceExecutionHandle; -} - -export interface ManagedWorkspaceOwner { - readonly state: 'ready' | 'closing' | 'closed'; - openManagedWorkspaceBaseline( - store: RuntimeWorkspaceVersionAuthorityStore, - input: OpenManagedWorkspaceBaselineInput, - ): Promise; - withManagedWorkspaceExecution( - handle: ManagedWorkspaceExecutionHandle, - operation: (scope: ManagedWorkspaceExecutionScope) => Promise, - ): Promise; - executeReadOnlyFilesystemOperation( - scope: ManagedWorkspaceExecutionScope, - operation: ManagedWorkspaceReadOnlyOperation, - abortSignal?: AbortSignal, - ): Promise; - close(): Promise; -} - -export type { - ManagedWorkspaceExecutionHandle, - ManagedWorkspaceExecutionScope, - ManagedWorkspaceFilesystemWorker, - ManagedWorkspaceReadOnlyOperation, - ManagedWorkspaceReadOnlyResult, - VerifiedGitRuntimeInput, -}; - -const owners = new WeakMap(); - -export async function openManagedWorkspaceOwner( - input: OpenManagedWorkspaceOwnerInput, -): Promise { - const rootOwner = authenticateInteractiveRootOwner(input.rootOwner); - await assertInteractiveRootOwner(rootOwner); - if (owners.has(rootOwner)) { - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_owner_conflict', - 'This storage root owner already has a managed workspace owner', - ); - } - - const claim = {}; - owners.set(rootOwner, claim); - try { - const service = createGitWorkspaceService({ - storageRoot: rootOwner.capability.canonicalPath, - gitRuntime: input.gitRuntime, - ...(input.failpoint ? { failpoint: input.failpoint } : {}), - }); - await runWithStorageRootLease(rootOwner.lease, 'interactive', 'write', () => - service.assertAvailable(), - ); - // The root owner may begin closing while capability initialization is in - // flight. Revalidate after the lease-bound operation so a stale lifecycle - // owner is never published as ready. - await assertInteractiveRootOwner(rootOwner); - const owner = new ManagedWorkspaceOwnerImpl( - rootOwner, - service, - requireManagedBaselineReceiptAuthorityInternal(service), - input.failpoint, - input.filesystemWorker, - ); - owners.set(rootOwner, owner); - return owner; - } catch (error) { - if (owners.get(rootOwner) === claim) owners.delete(rootOwner); - if (error instanceof ManagedWorkspaceOwnerError) throw error; - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_owner_unavailable', - 'Unable to initialize the managed workspace owner', - { cause: error }, - ); - } -} - -class ManagedWorkspaceOwnerImpl implements ManagedWorkspaceOwner { - #state: 'ready' | 'closing' | 'closed' = 'ready'; - #activeOperations = 0; - readonly #drainWaiters = new Set<() => void>(); - readonly #assertCurrentRootIdentity: () => Promise; - readonly #executionContext = new AsyncLocalStorage(); - #closeTask: Promise | undefined; - readonly #executionOwnerToken = {}; - readonly #workerBridge: ManagedWorkspaceWorkerBridgeInternal | undefined; - - constructor( - private readonly rootOwner: InteractiveRootOwner, - private readonly service: GitWorkspaceService, - private readonly receiptAuthority: ManagedBaselineReceiptAuthorityInternal, - private readonly failpoint?: (point: ManagedWorkspaceOwnerFailpoint) => void | Promise, - filesystemWorker?: ManagedWorkspaceFilesystemWorker, - ) { - // Capture the identity guard while the lease is active. Unlike a fresh - // admission check, this guard remains valid for an already-admitted - // operation while owner.close() waits for that operation to drain. - this.#assertCurrentRootIdentity = createStorageRootLeaseIdentityGuard( - rootOwner.lease, - 'interactive', - 'write', - ); - this.#workerBridge = filesystemWorker - ? createManagedWorkspaceWorkerBridgeInternal(this.#executionOwnerToken, filesystemWorker) - : undefined; - } - - get state(): 'ready' | 'closing' | 'closed' { - return this.#state; - } - - async openManagedWorkspaceBaseline( - store: RuntimeWorkspaceVersionAuthorityStore, - input: OpenManagedWorkspaceBaselineInput, - ): Promise { - return this.#run(async () => { - await assertWorkspaceBaselineAuthorityStoreRootInternal( - store, - this.rootOwner.capability.canonicalPath, - ); - bindWorkspaceBaselineAuthorityStoreRootInternal(store, this.rootOwner.capability.rootId); - await this.failpoint?.('after_initial_store_root_validation'); - const existingHead = await readWorkspaceHeadInternal( - store, - input.workspaceId, - input.workspaceEpochId, - ); - const receipt = existingHead ? await this.receiptAuthority.require(input) : undefined; - const binding = receipt - ? await this.#openReadyBinding(receipt.binding) - : await this.#requireReady(await this.service.createManagedWorkspaceFromSource(input)); - const durableReceipt = receipt ?? (await this.receiptAuthority.issue(binding)); - if ( - existingHead && - (existingHead.workspaceVersionId !== durableReceipt.workspaceVersionId || - existingHead.acceptedEventId !== durableReceipt.baselineAcceptedEventId || - existingHead.commitOid !== binding.baselineCommitOid || - existingHead.treeOid !== binding.baselineTreeOid) - ) { - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_owner_unavailable', - 'Canonical workspace head does not match its durable Git baseline receipt', - ); - } - const committed = await commitWorkspaceBaselineInternal(store, { - epochOpenedEventId: durableReceipt.epochOpenedEventId, - baselineAcceptedEventId: durableReceipt.baselineAcceptedEventId, - committedAt: MANAGED_BASELINE_LOGICAL_TIMESTAMP_V1, - epoch: { - repositoryId: binding.repositoryId, - workspaceId: binding.workspaceId, - workspaceEpochId: binding.workspaceEpochId, - workspaceInstanceId: binding.workspaceInstanceId, - mode: 'managed_worktree', - objectFormat: binding.objectFormat, - sourceCommitOid: binding.sourceHeadCommitOid, - sourceTreeOid: binding.sourceTreeOid, - materializationProfileDigest: binding.materializationProfileDigest, - materializationSemantics: binding.materializationSemantics, - policyHash: durableReceipt.policyHash, - }, - baseline: { - workspaceVersionId: durableReceipt.workspaceVersionId, - commitOid: binding.baselineCommitOid, - treeOid: binding.baselineTreeOid, - treeDeltaDigest: durableReceipt.treeDeltaDigest, - changedFileCount: durableReceipt.changedFileCount, - deletedFileCount: durableReceipt.deletedFileCount, - }, - }); - await this.failpoint?.('after_baseline_authority_commit'); - // The transaction writes through the already-open SQLite handle. Rebind - // that handle to the canonical pathname after COMMIT so an external - // rename/replacement cannot make an orphan database look accepted. - await assertWorkspaceBaselineAuthorityStoreRootInternal( - store, - this.rootOwner.capability.canonicalPath, - ); - bindWorkspaceBaselineAuthorityStoreRootInternal(store, this.rootOwner.capability.rootId); - // Canonical acceptance never makes a missing Git artifact acceptable. - // Reverify after the SQLite transaction so post-accept artifact loss is - // reported fail-closed instead of returning a usable workspace head. - await this.receiptAuthority.verify(durableReceipt); - await this.failpoint?.('after_post_commit_artifact_verification'); - // The root marker is mutable host state and is not covered by receipt - // verification. Revalidate its identity at the final return gate without - // rejecting an operation that owner.close() is legitimately draining. - await this.#assertCurrentRootIdentity(); - const internalHead = freezeWorkspaceHead(committed.head); - const internalBinding = freezeManagedWorkspaceBinding(binding); - const internalReceipt = freezeManagedWorkspaceReceipt(durableReceipt); - return { - created: committed.created, - head: freezeWorkspaceHead(committed.head), - executionHandle: issueManagedWorkspaceExecutionHandleInternal(this.#executionOwnerToken, { - store, - binding: internalBinding, - receipt: internalReceipt, - head: internalHead, - }), - }; - }); - } - - async withManagedWorkspaceExecution( - handle: ManagedWorkspaceExecutionHandle, - operation: (scope: ManagedWorkspaceExecutionScope) => Promise, - ): Promise { - return this.#run(async () => { - let accepted; - try { - accepted = requireManagedWorkspaceExecutionHandleInternal( - this.#executionOwnerToken, - handle, - ); - } catch (error) { - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_execution_handle_invalid', - 'Managed workspace execution handle is invalid for this owner', - { cause: error }, - ); - } - assertExecutionCrossPlaneIdentity(accepted.binding, accepted.receipt, accepted.head); - // Test builds with a failpoint retain a preliminary proof so crash tests - // can stop after a real, completed artifact verification. The ordinary - // production path performs only the final proof below. - if (this.failpoint) { - await this.#verifyExecutionArtifactOrQuarantine(accepted.binding, accepted.receipt); - await this.failpoint('after_execution_artifact_verification'); - } - - // The root write lease excludes cooperating Maka writers across this - // proof bundle. Verify the durable Git artifact first, then make the - // immutable workspace head the final durable reread before scope issue. - await this.#assertCurrentRootIdentity(); - await this.#verifyExecutionArtifactOrQuarantine(accepted.binding, accepted.receipt); - const currentHead = await readWorkspaceHeadInternal( - accepted.store, - accepted.binding.workspaceId, - accepted.binding.workspaceEpochId, - ); - // Rebind only after the head read. This catches a database pathname - // detach at the admission boundary without putting a mutable DB guard - // ahead of the durable head evidence it protects. - await assertWorkspaceBaselineAuthorityStoreRootInternal( - accepted.store, - this.rootOwner.capability.canonicalPath, - ); - bindWorkspaceBaselineAuthorityStoreRootInternal( - accepted.store, - this.rootOwner.capability.rootId, - ); - if (!currentHead || !sameWorkspaceHead(currentHead, accepted.head)) { - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_owner_unavailable', - 'Managed workspace execution handle no longer matches the canonical workspace head', - ); - } - assertExecutionCrossPlaneIdentity(accepted.binding, accepted.receipt, currentHead); - const binding = accepted.binding; - const scope = issueManagedWorkspaceExecutionScopeInternal(this.#executionOwnerToken, { - provisioning: 'canonical_tree_only_v1', - workspaceEffect: 'none', - cwd: binding.worktreePath, - binding: Object.freeze({ ...binding }), - head: freezeWorkspaceHead(currentHead), - }); - try { - return await this.#executionContext.run(this.#executionOwnerToken, () => operation(scope)); - } finally { - revokeManagedWorkspaceExecutionScopeInternal(this.#executionOwnerToken, scope); - } - }); - } - - async executeReadOnlyFilesystemOperation( - scope: ManagedWorkspaceExecutionScope, - operation: ManagedWorkspaceReadOnlyOperation, - abortSignal?: AbortSignal, - ): Promise { - if (!this.#workerBridge) { - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_worker_unavailable', - 'Managed workspace filesystem worker is unavailable', - ); - } - return await this.#workerBridge.execute(scope, operation, abortSignal); - } - - close(): Promise { - if (this.#executionContext.getStore() === this.#executionOwnerToken) { - return Promise.reject( - new ManagedWorkspaceOwnerError( - 'managed_workspace_owner_reentrant_close', - 'Managed workspace owner cannot close from its own execution callback', - ), - ); - } - this.#closeTask ??= (async () => { - this.#state = 'closing'; - await this.#waitForDrain(); - this.#state = 'closed'; - })(); - return this.#closeTask; - } - - #assertReady(): void { - if (this.#state !== 'ready') { - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_owner_closing', - 'Managed workspace owner is closing or closed', - ); - } - } - - async #run(operation: () => Promise): Promise { - this.#assertReady(); - this.#activeOperations += 1; - try { - return await runWithStorageRootLease(this.rootOwner.lease, 'interactive', 'write', operation); - } finally { - this.#activeOperations -= 1; - if (this.#activeOperations === 0) { - for (const resolve of this.#drainWaiters) resolve(); - this.#drainWaiters.clear(); - } - } - } - - #waitForDrain(): Promise { - if (this.#activeOperations === 0) return Promise.resolve(); - return new Promise((resolve) => this.#drainWaiters.add(resolve)); - } - - async #requireReady(binding: ManagedWorkspaceBinding): Promise { - const inspection = await this.service.inspectManagedWorkspace(binding); - if (inspection.state === 'ready') return binding; - const quarantine = await this.service.quarantineManagedWorkspace( - binding, - 'external_workspace_drift', - ); - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_quarantined', - `Managed workspace drift was quarantined at ${quarantine.quarantinePath}`, - ); - } - - async #openReadyBinding(binding: ManagedWorkspaceBinding): Promise { - try { - return await this.#requireReady(await this.service.openManagedWorkspaceFromBinding(binding)); - } catch (error) { - if ( - !(error instanceof GitWorkspaceServiceError) || - error.code !== 'managed_workspace_drifted' - ) { - throw error; - } - const quarantine = await this.service.quarantineManagedWorkspace( - binding, - 'external_workspace_drift', - ); - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_quarantined', - `Managed workspace drift was quarantined at ${quarantine.quarantinePath}`, - { cause: error }, - ); - } - } - - async #verifyExecutionArtifactOrQuarantine( - binding: ManagedWorkspaceBinding, - receipt: ManagedWorkspaceBaselineReceiptV1, - ): Promise { - try { - await this.receiptAuthority.verify(receipt); - } catch (error) { - if ( - !(error instanceof GitWorkspaceServiceError) || - error.code !== 'managed_workspace_drifted' - ) { - throw error; - } - const quarantine = await this.service.quarantineManagedWorkspace( - binding, - 'external_workspace_drift', - ); - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_quarantined', - `Managed workspace drift was quarantined at ${quarantine.quarantinePath}`, - { cause: error }, - ); - } - } -} - -function sameWorkspaceHead(left: WorkspaceHeadRecordV1, right: WorkspaceHeadRecordV1): boolean { - return ( - left.workspaceId === right.workspaceId && - left.repositoryId === right.repositoryId && - left.workspaceEpochId === right.workspaceEpochId && - left.workspaceVersionId === right.workspaceVersionId && - left.acceptedEventId === right.acceptedEventId && - left.commitOid === right.commitOid && - left.treeOid === right.treeOid && - left.revision === right.revision - ); -} - -function freezeWorkspaceHead(head: WorkspaceHeadRecordV1): Readonly { - return Object.freeze({ ...head }); -} - -function freezeManagedWorkspaceBinding( - binding: ManagedWorkspaceBinding, -): Readonly { - return Object.freeze({ ...binding }); -} - -function freezeManagedWorkspaceReceipt( - receipt: ManagedWorkspaceBaselineReceiptV1, -): Readonly { - return Object.freeze({ ...receipt, binding: freezeManagedWorkspaceBinding(receipt.binding) }); -} - -function assertExecutionCrossPlaneIdentity( - binding: ManagedWorkspaceBinding, - receipt: ManagedWorkspaceBaselineReceiptV1, - head: Readonly, -): void { - if ( - !sameManagedWorkspaceBinding(binding, receipt.binding) || - head.repositoryId !== binding.repositoryId || - head.workspaceId !== binding.workspaceId || - head.workspaceEpochId !== binding.workspaceEpochId || - head.workspaceVersionId !== receipt.workspaceVersionId || - head.acceptedEventId !== receipt.baselineAcceptedEventId || - head.commitOid !== binding.baselineCommitOid || - head.treeOid !== binding.baselineTreeOid - ) { - throw new ManagedWorkspaceOwnerError( - 'managed_workspace_owner_unavailable', - 'Managed workspace execution evidence does not identify one exact accepted boundary', - ); - } -} - -function sameManagedWorkspaceBinding( - left: ManagedWorkspaceBinding, - right: ManagedWorkspaceBinding, -): boolean { - return ( - left.schemaVersion === right.schemaVersion && - left.protocol === right.protocol && - left.repositoryId === right.repositoryId && - left.workspaceId === right.workspaceId && - left.workspaceEpochId === right.workspaceEpochId && - left.workspaceInstanceId === right.workspaceInstanceId && - left.sourceRoot === right.sourceRoot && - left.sourceGitCommonDir === right.sourceGitCommonDir && - left.sourceHeadCommitOid === right.sourceHeadCommitOid && - left.sourceTreeOid === right.sourceTreeOid && - left.repositoryPath === right.repositoryPath && - left.worktreePath === right.worktreePath && - left.hooksPath === right.hooksPath && - left.baselineCommitOid === right.baselineCommitOid && - left.baselineTreeOid === right.baselineTreeOid && - left.headRef === right.headRef && - left.gitRuntimeSha256 === right.gitRuntimeSha256 && - left.objectFormat === right.objectFormat && - left.materializationProfileDigest === right.materializationProfileDigest && - left.materializationSemantics === right.materializationSemantics - ); -} diff --git a/packages/storage/src/managed-workspace-worker-bridge-internal.ts b/packages/storage/src/managed-workspace-worker-bridge-internal.ts deleted file mode 100644 index bf07b53e29..0000000000 --- a/packages/storage/src/managed-workspace-worker-bridge-internal.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { - createManagedExecutionBoundary, - type ExecutionBoundary, -} from '@maka/core/sandbox-boundary'; -import { createReadOnlyPermissionProfile } from '@maka/core/permission-profile'; -import { - requireManagedWorkspaceExecutionScopeInternal, - type ManagedWorkspaceExecutionScope, -} from './managed-workspace-execution-authority-internal.js'; - -export type ManagedWorkspaceReadOnlyOperation = - | { - readonly kind: 'read'; - readonly path: string; - readonly offset?: number; - readonly limit?: number; - } - | { - readonly kind: 'glob'; - readonly path: string; - readonly pattern: string; - readonly limit?: number; - } - | { - readonly kind: 'grep'; - readonly path: string; - readonly pattern: string; - readonly glob?: string; - readonly maxCountPerFile: number; - readonly limit: number; - readonly timeoutMs: number; - }; - -interface ManagedWorkspaceFilesystemWorkerInput { - readonly operation: ManagedWorkspaceReadOnlyOperation; - readonly cwd: string; - readonly executionBoundary: ExecutionBoundary; - readonly abortSignal?: AbortSignal; -} - -export type ManagedWorkspaceReadOnlyResult = - | { readonly kind: 'read'; readonly content: string } - | { - readonly kind: 'read_image'; - readonly base64: string; - readonly mimeType: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp'; - } - | { readonly kind: 'glob'; readonly files: readonly string[] } - | { readonly kind: 'grep'; readonly matches: readonly string[] }; - -export interface ManagedWorkspaceFilesystemWorker { - /** - * Resolves only after the one-shot filesystem operation and every process it - * owns have reached a terminal lifecycle state. Implementations must not - * return a detached filesystem effect to the caller. The production adapter - * satisfies this contract through FilesystemWorkerClient; M1.2 admits only - * read-only operations, so a host crash cannot leave a workspace mutation. - */ - execute(input: ManagedWorkspaceFilesystemWorkerInput): Promise; -} - -export type ManagedWorkspaceWorkerBridgeErrorCode = 'managed_workspace_operation_denied'; - -export class ManagedWorkspaceWorkerBridgeError extends Error { - constructor( - readonly code: ManagedWorkspaceWorkerBridgeErrorCode, - message: string, - ) { - super(message); - this.name = 'ManagedWorkspaceWorkerBridgeError'; - } -} - -export interface ManagedWorkspaceWorkerBridgeInternal { - execute( - scope: ManagedWorkspaceExecutionScope, - operation: ManagedWorkspaceReadOnlyOperation, - abortSignal?: AbortSignal, - ): Promise; -} - -/** - * Owner-bound bridge from an opaque, revocable scope to the filesystem worker. - * The caller never supplies or receives cwd. Runtime validation intentionally - * repeats the TypeScript allowlist so JavaScript/forged inputs fail closed. - */ -export function createManagedWorkspaceWorkerBridgeInternal( - ownerToken: object, - worker: ManagedWorkspaceFilesystemWorker, -): ManagedWorkspaceWorkerBridgeInternal { - const bridge: ManagedWorkspaceWorkerBridgeInternal = { - async execute( - scope: ManagedWorkspaceExecutionScope, - operation: ManagedWorkspaceReadOnlyOperation, - abortSignal?: AbortSignal, - ) { - if (!isReadOnlyOperation(operation)) { - throw new ManagedWorkspaceWorkerBridgeError( - 'managed_workspace_operation_denied', - 'Managed workspace execution currently permits only Read, Glob, and Grep operations', - ); - } - const state = requireManagedWorkspaceExecutionScopeInternal(ownerToken, scope); - if (state.workspaceEffect !== 'none' || state.provisioning !== 'canonical_tree_only_v1') { - throw new ManagedWorkspaceWorkerBridgeError( - 'managed_workspace_operation_denied', - 'Managed workspace execution scope does not permit filesystem mutation', - ); - } - return await worker.execute({ - operation, - cwd: state.cwd, - executionBoundary: createManagedExecutionBoundary(createReadOnlyPermissionProfile(), 0), - ...(abortSignal ? { abortSignal } : {}), - }); - }, - }; - return Object.freeze(bridge); -} - -function isReadOnlyOperation(input: unknown): input is ManagedWorkspaceReadOnlyOperation { - if (!input || typeof input !== 'object') return false; - const kind = (input as { kind?: unknown }).kind; - return kind === 'read' || kind === 'glob' || kind === 'grep'; -} diff --git a/packages/storage/src/sqlite-runtime-store.ts b/packages/storage/src/sqlite-runtime-store.ts index 4ebc1b4832..91633b4197 100644 --- a/packages/storage/src/sqlite-runtime-store.ts +++ b/packages/storage/src/sqlite-runtime-store.ts @@ -280,7 +280,7 @@ export class SqliteRuntimeStore assertContinuationAuthorityCapability(this.db); assertWorkspaceVersionAuthorityCapability(this.db); if (!options.readOnly) { - this.registerWorkspaceBaselineAuthorityWriter(options.databaseLease.databasePath); + this.registerWorkspaceBaselineAuthorityWriter(); this.refreshToolLedgerHealth(); } return; @@ -306,7 +306,7 @@ export class SqliteRuntimeStore assertContinuationAuthorityCapability(this.db); assertWorkspaceVersionAuthorityCapability(this.db); if (!options.readOnly) { - this.registerWorkspaceBaselineAuthorityWriter(path); + this.registerWorkspaceBaselineAuthorityWriter(); this.refreshToolLedgerHealth(); } } catch (error) { @@ -1230,11 +1230,10 @@ export class SqliteRuntimeStore }); } - private registerWorkspaceBaselineAuthorityWriter(databasePath: string): void { + private registerWorkspaceBaselineAuthorityWriter(): void { const readWorkspaceHead = this.readWorkspaceHead.bind(this); registerWorkspaceBaselineAuthorityWriterInternal( this, - databasePath, (input, rootId) => this.#commitWorkspaceBaseline(input, rootId), (rootId) => this.#bindWorkspaceStorageRoot(rootId), readWorkspaceHead, diff --git a/packages/storage/src/workspace-version-authority-internal.ts b/packages/storage/src/workspace-version-authority-internal.ts index ed77104ee1..9346d8c1ce 100644 --- a/packages/storage/src/workspace-version-authority-internal.ts +++ b/packages/storage/src/workspace-version-authority-internal.ts @@ -22,10 +22,6 @@ import type { WorkspaceBaselineCommitResult, WorkspaceHeadRecordV1, } from '@maka/core/workspace-version-authority'; -import { lstatSync } from 'node:fs'; -import { realpath } from 'node:fs/promises'; -import { basename, dirname, join, normalize, resolve } from 'node:path'; -import { OPERATIONAL_STATE_DATABASE_NAME } from './operational-state-store.js'; type WorkspaceBaselineAuthorityWriter = ( input: WorkspaceBaselineAuthorityInput, @@ -41,8 +37,6 @@ interface WorkspaceBaselineAuthorityRegistration { readonly writer: WorkspaceBaselineAuthorityWriter; readonly readHead: WorkspaceHeadReader; readonly bindStorageRoot: WorkspaceStorageRootBinder; - readonly databasePath: string; - readonly databaseFileIdentity?: string; boundRootId?: string; } @@ -53,7 +47,6 @@ const workspaceBaselineAuthorityWriters = new WeakMap< export function registerWorkspaceBaselineAuthorityWriterInternal( store: object, - databasePath: string, writer: WorkspaceBaselineAuthorityWriter, bindStorageRoot: WorkspaceStorageRootBinder, readHead: WorkspaceHeadReader, @@ -61,13 +54,10 @@ export function registerWorkspaceBaselineAuthorityWriterInternal( if (workspaceBaselineAuthorityWriters.has(store)) { throw new Error('Workspace baseline authority writer is already registered'); } - const resolvedDatabasePath = resolve(databasePath); workspaceBaselineAuthorityWriters.set(store, { writer, readHead, bindStorageRoot, - databasePath: resolvedDatabasePath, - databaseFileIdentity: captureRegularFileIdentity(resolvedDatabasePath), }); } @@ -83,9 +73,10 @@ export function readWorkspaceHeadInternal( /** * Storage-internal authority seam. This module is deliberately absent from the - * @maka/storage package exports. Production composition reaches it only through - * ManagedWorkspaceOwner after durable Git receipt verification; focused - * persistence/crash tests use it directly to prove the SQLite transaction. + * @maka/storage package exports. The schema-9 reader, migration, and projection + * rebuild remain supported even though no production baseline writer is + * currently composed. Focused persistence tests use this seam to prove the + * SQLite transaction and historical read contract. */ export function commitWorkspaceBaselineInternal( store: object, @@ -111,65 +102,3 @@ export function bindWorkspaceBaselineAuthorityStoreRootInternal( registration.bindStorageRoot(rootId); registration.boundRootId = rootId; } - -export async function assertWorkspaceBaselineAuthorityStoreRootInternal( - store: object, - storageRoot: string, -): Promise { - const registration = workspaceBaselineAuthorityWriters.get(store); - if ( - !registration || - !registration.databaseFileIdentity || - basename(registration.databasePath) !== OPERATIONAL_STATE_DATABASE_NAME - ) { - throw new Error('Workspace baseline authority store is unavailable for this storage root'); - } - const expectedDatabasePath = join(storageRoot, OPERATIONAL_STATE_DATABASE_NAME); - const currentIdentity = captureRegularFileIdentity(registration.databasePath); - if (currentIdentity !== registration.databaseFileIdentity) { - throw new Error('Workspace baseline authority database file identity changed'); - } - let databasePath: string; - let expectedPath: string; - let expectedRoot: string; - try { - [databasePath, expectedPath, expectedRoot] = await Promise.all([ - realpath(registration.databasePath), - realpath(expectedDatabasePath), - realpath(storageRoot), - ]); - } catch (error) { - throw new Error('Workspace baseline authority store belongs to a different storage root', { - cause: error, - }); - } - const canonicalDatabasePath = normalize(databasePath); - const canonicalExpectedPath = normalize(expectedPath); - const canonicalExpectedRoot = normalize(expectedRoot); - if ( - !sameFilesystemPath(canonicalDatabasePath, canonicalExpectedPath) || - !sameFilesystemPath(dirname(canonicalDatabasePath), canonicalExpectedRoot) - ) { - throw new Error('Workspace baseline authority store belongs to a different storage root'); - } -} - -function captureRegularFileIdentity(path: string): string | undefined { - try { - const info = lstatSync(path, { bigint: true }); - // SQLite sidecars are pathname-scoped. Opening the same main database - // inode through a second hard-linked storage root can split its WAL/SHM - // coordination across two directories, so a canonical authority database - // must have exactly one directory entry. - if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1n) return undefined; - return `${info.dev}:${info.ino}:${info.nlink}`; - } catch { - return undefined; - } -} - -function sameFilesystemPath(left: string, right: string): boolean { - return process.platform === 'win32' - ? left.toLocaleLowerCase('en-US') === right.toLocaleLowerCase('en-US') - : left === right; -} diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index b6fd9431e6..3b19f8616f 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -249,7 +249,6 @@ function isE2eProductPath(path) { const STORAGE_STRESS_FILES = new Set([ 'packages/storage/src/agent-run-store.ts', - 'packages/storage/src/git-workspace-service.ts', 'packages/storage/src/runtime-event-invariants.ts', 'packages/storage/src/root-authority.ts', 'packages/storage/src/operational-state-store.ts', @@ -260,10 +259,8 @@ const STORAGE_STRESS_FILES = new Set([ 'packages/storage/src/sqlite-usage-schema.ts', 'packages/storage/src/sqlite-workflow-schema.ts', 'packages/storage/src/__tests__/agent-run-store.test.ts', - 'packages/storage/src/__tests__/git-workspace-service.test.ts', 'packages/storage/src/__tests__/root-authority.test.ts', 'packages/storage/src/__tests__/sqlite-recovery-concurrency.test.ts', - 'packages/storage/src/__tests__/fixtures/git-workspace-service-crash-child.ts', 'packages/storage/src/__tests__/fixtures/sqlite-recovery-concurrency-child.ts', 'packages/storage/src/__tests__/fixtures/root-lock-holder.ts', 'packages/storage/src/__tests__/fixtures/root-resolver.ts',