# Agent Codex Workspace 文件管理 Sidecar PRD

创建日期：2026-09-02

状态：Draft

产品范围：Agent Sidecar、Agent Gateway 管理 API、Management Portal、dev 环境部署

## 1. 背景

当前 `agent-codex` 以 Pod 运行 Codex app-server，并将两个需要长期保留的目录挂载到
独立 PVC：

- `/data/projects`：Agent 执行任务时使用的 Projects 工作目录；
- `/home/node/.codex`：`CODEX_HOME`，保存 Skills、全局指令和 Codex 运行数据。

管理员目前只能进入 Pod 或直接操作 PVC 才能创建 Project、检查文件或调整 Skills。
这种方式缺少统一入口、路径隔离、并发写保护和操作审计，也无法从 Management Portal
查看多个 Agent Codex 实例的实际文件状态。

本产品为每个 Agent Codex Pod 增加一个 `agent-sidecar`。Sidecar 与
Agent Codex 共享 Projects 和 CODEX_HOME 卷，只提供受限的文件系统能力；
Agent Gateway 负责实例注册、管理员 API 和请求路由；Management Portal 提供目录树、
文件查看和在线编辑界面。

`agent-sidecar` 首期提供面向运行实例当前 PVC 的命令式文件管理能力，后续可继续扩展
其他 Agent Pod 内管理能力。它不复用、
也不替代 Agent Sandbox Manager 中基于 ProjectBundle desired state 的
`agent-sandbox-sidecar`。

## 2. 产品目标

### 2.1 目标

1. 管理员可以在 Management Portal 选择一个 Agent Codex 实例，并查看其可管理状态。
2. 管理员可以创建 Project；系统创建一级目录并执行 Git 初始化。
3. 管理员可以永久删除 Project。
4. 管理员可以管理 Project 内普通文件和目录，并在线查看、编辑 UTF-8 文本文件。
5. 管理员可以管理 `$CODEX_HOME/skills` 的内容和 `$CODEX_HOME/AGENTS.md`，但不能访问
   CODEX_HOME 的其他内容。
6. 系统从服务端过滤并保护 Git metadata、大型依赖目录、缓存目录和符号链接。
7. 并发编辑时不能静默覆盖其他管理员或 Agent 已经写入的新内容。
8. 所有变更操作都有不包含文件正文和 Secret 的结构化审计记录。

### 2.2 成功标准

- 管理员无需登录 Kubernetes 或进入 Pod，即可完成本 PRD 定义的 Project 与 Global
  文件管理操作。
- 在至少两个已配置实例之间切换时，Project、目录树和文件内容不会串实例。
- Create Project 成功后目标目录包含 `.git`，但 `.git` 不出现在目录树中，且不能通过
  直接构造路径访问。
- Projects 和 Global 允许范围外的路径不能通过 UI 或 API 读取、修改或删除。
- 两个客户端基于同一旧 revision 保存时，只有第一个成功，第二个收到明确冲突。
- Sidecar 不可用时，Portal 明确展示不可用状态，原有 PVC 内容不被修改。
- 每次创建、更新、移动和删除都有 operator、agent、workspace、path、result 和
  request ID 可供审计。

## 3. 非目标

MVP 不包含：

- Git clone、remote、fetch、pull、push、commit、branch、tag 或凭证管理；
- Project 重命名以及跨 Project、Projects 与 Global 之间的移动；
- 二进制文件上传、下载或在线编辑；
- 超过 1 MiB 的文本文件在线查看或编辑；
- 全文搜索、跨目录搜索、实时文件系统 watch 或多人协同编辑；
- 回收站、软删除、版本历史或删除恢复；
- 查询 Agent Gateway Task 状态，或在 Agent 执行期间阻止文件修改；
- 动态实例注册、Kubernetes Pod 自动发现或实例信息数据库；
- 直接向浏览器暴露 Sidecar；
- Agent Sandbox Manager 的 ProjectBundle、Apply journal、发布状态或 Reconcile；
- staging、prod 的首期发布。

## 4. 用户与权限

### 4.1 Workspace 管理员

Workspace 管理员是 Management Portal 中具有 admin 权限的内部用户，可以：

- 查看配置了 Agent Sidecar 的 Agent 实例；
- 管理任一可用实例的 Projects 和 Global 内容；
- 执行不可恢复的删除操作；
- 查看错误和冲突提示。

MVP 不定义实例级、Project 级或只读角色。非 admin 用户看不到 Agents 菜单，也不能访问
对应管理 API。

### 4.2 服务身份

- Management Portal 认证入口负责确认用户身份和 admin 权限，并向 Agent Gateway
  传递可信 operator；外部请求不能覆盖该身份。
- Agent Gateway 通过集群内地址调用 Sidecar。
- Sidecar 信任内部网络，不验证 token，也不自行实现调用方认证或用户权限模型。
- Sidecar 不接受 Management Portal 浏览器的直接请求。

## 5. 核心概念

### 5.1 Managed Agent

`agentRuntime.agents` 中配置了 Agent Sidecar 的 Agent Provider 是一个
Managed Agent：

- 配置项名称是稳定且区分大小写的 `agent_id`；
- Agent Gateway 配置同时保存 app-server endpoint 和 Sidecar endpoint；
- 不配置 Sidecar 的 Provider 仍出现在 Agent 列表中，但 `project_management_enabled` 为 false；
- 配置变化在 Agent Gateway 重新部署后生效，不写数据库。

不同 Managed Agent 可以包含同名 Project，它们仍是完全独立的文件空间。

### 5.2 Project

Project 是 Projects Root 下的一个一级目录。默认 Projects Root 为
`/data/projects`。

- Projects Root 下每个未被过滤的一级普通目录都作为 Project 返回；
- 既有目录即使没有 `.git` 也仍是 Project，并返回 `git_initialized=false`；
- 通过管理 API 创建的 Project 必须完成 Git 初始化；
- Project 名称是目录名和 API 主键，MVP 不支持修改；
- 普通文件、符号链接或过滤目录不能作为 Project。

### 5.3 Workspace

文件 API 使用以下 Workspace 引用：

```text
WorkspaceRef {
  scope: PROJECT | GLOBAL
  project_name: string
}
```

- `PROJECT` 必须提供 `project_name`，根目录是对应 Project 目录；
- `GLOBAL` 不得提供 `project_name`，它是受限虚拟根；
- 一个操作只能在一个 Workspace 内完成。

### 5.4 Global 虚拟根

Global 并不等于整个 CODEX_HOME。它只映射：

```text
Global
├── skills/       -> $CODEX_HOME/skills/
└── AGENTS.md     -> $CODEX_HOME/AGENTS.md
```

规则：

- Sidecar 启动时确保 `skills` 普通目录存在；
- `skills` 根目录不能删除、重命名或移动，其内部内容可正常管理；
- `AGENTS.md` 是可选普通文件，可创建、查看、编辑和删除，但不能改成其他根级名称；
- Global 根不能创建第三个根级条目；
- `auth.json`、`config.toml`、`model-catalog.local.json`、session/history 等内容不在
  Global namespace 中，不能通过相对路径或符号链接访问。

## 6. 整体架构与职责

请求链路固定为：

```text
Management Portal
        |
        | authenticated admin request
        v
Management API ingress / proxy
        |
        v
Agent Gateway
        |
        | trusted in-cluster gRPC
        v
agent-sidecar
        |
        +-- /data/projects PVC
        +-- $CODEX_HOME PVC
```

### 6.1 Management Portal

- 提供 Agents 管理页面和交互确认；
- 使用 Agent Gateway OpenAPI 生成的客户端；
- 不拼接 Pod 地址；
- 不把 UI 过滤作为安全边界。

### 6.2 Agent Gateway

- 从配置列出 Managed Agent，并按 `agent_id` 选择 Sidecar Client；
- 校验外部请求结构、admin 身份和可信 operator；
- 将文件操作转发到 Sidecar，并统一映射 gRPC-Gateway 错误；
- 记录管理操作审计；
- 不读取本地文件系统，不持久化或缓存文件正文；
- 不查询 Task 是否运行，也不因为 Task 状态拒绝管理操作。

### 6.3 Agent Sidecar

- 与 Agent Codex 位于同一 Pod，并以 UID/GID 1000 运行；
- 同时挂载 Projects PVC 和 CODEX_HOME PVC；
- 执行路径校验、Global allowlist、denylist、文本识别、revision 校验和文件系统操作；
- 在进程内串行化全部变更操作；读取可以并发；
- 不感知 Agent Gateway Session、Task、Codex app-server 或 Git 远端；
- 不保存独立业务数据库。

### 6.4 部署边界

- 每个 Managed Agent 由独立 Agent Codex Pod、独立 RWO PVC 和稳定 Sidecar endpoint
  组成；
- Sidecar endpoint 只在集群内可达；
- Agent Gateway 与 Sidecar 之间使用内部可信网络，不配置或校验调用 token；
- Sidecar container 重启不清理、不初始化或覆盖既有 Project 内容；
- Sidecar 故障不主动停止 Agent Codex container；Gateway 在实际 Project 请求失败时返回
  `Unavailable`。

## 7. 功能需求

### 7.1 Agent 列表

Agent Gateway 返回所有静态配置的 Agent，按 `agent_id` 排序。

每项至少包含：

| 字段 | 含义 |
| --- | --- |
| `agent_id` | `agentRuntime.agents` 配置 key |
| `provider` | Agent 使用的 Provider 类型 |
| `project_management_enabled` | 是否配置 Agent Sidecar |
| `is_default` | 是否为默认 Agent |

列表只返回静态配置派生信息，不探测 Sidecar，也不暴露 token、endpoint 或本地路径。

### 7.2 List Projects

- 只扫描 Projects Root 的直接子项，不递归；
- 只返回普通目录；
- 过滤 denylist 命中的目录和符号链接；
- 返回 `name`、`modified_at`、`git_initialized`；
- 先按名称升序排序，再使用 cursor 分页；
- 单页默认 100 项，最大 500 项。

`git_initialized` 只表示 `<project>/.git` 是一个真实目录，不读取或验证仓库状态。

### 7.3 Create Project

请求包含：

```text
agent_id
name
```

项目名称必须满足：

```regexp
^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$
```

处理顺序：

1. 校验名称不是 `.`、`..` 或 denylist 项；
2. 以单次 mkdir 创建目标目录，目录权限为 `0755`；
3. 使用参数化进程调用执行 `git init --initial-branch=main <target>`，不得通过 shell
   拼接命令；
4. 成功后读取并返回 Project；
5. Git 初始化失败时删除本次新建目录并返回失败，不留下半初始化 Project。

如果目标目录或任意同名文件已存在，返回 AlreadyExists，不修改已有内容。

### 7.4 Delete Project

请求包含 `name` 和 `confirm_name`。两者必须完全相同。

- 删除整个 Project 目录，包括未受管文件和隐藏的 `.git`；
- 删除是永久递归删除，成功后无法恢复；
- 删除前重新确认目标仍是 Projects Root 下的同名普通目录；
- 拒绝删除符号链接、过滤目录、Projects Root 或其他 Project；
- Project 不存在时返回 NotFound；
- 删除期间有 Agent 正在使用该目录时，不做 Task 检查，底层文件系统错误直接返回。

### 7.5 List Directory

目录树按需列举一个目录的直接子项，不递归展开。

每个 Entry 返回：

| 字段 | 含义 |
| --- | --- |
| `name` | 当前目录内名称 |
| `path` | 相对 Workspace Root 的规范路径 |
| `type` | `file` 或 `directory` |
| `size` | 文件字节数；目录为 0 |
| `modified_at` | 最后修改时间 |
| `revision` | Sidecar 生成的 opaque revision |
| `editable` | 是否是可在线编辑文本 |

排序和分页规则：

- 目录在文件之前；
- 同类型按名称升序；
- 默认 100 项，最大 500 项；
- cursor 是不透明值，只能用于相同 Agent、Workspace 和 path 的下一页；
- 目录内容变化使旧 cursor 失效时返回 Aborted，客户端从第一页刷新。

### 7.6 Read File

- 只允许读取普通文件；
- 文件不超过 1 MiB、内容是合法 UTF-8 且不包含 NUL 时，返回正文并标记
  `editable=true`；
- 二进制、非法 UTF-8 或超限文件只返回元信息、revision 和 `editable=false`，不返回
  部分正文或 Base64 内容；
- 前端不得为不可编辑文件提供保存或下载入口。

### 7.7 Create Entry

支持在 Project 任意可管理目录或 `Global/skills` 内创建：

- 空文本文件，权限为 `0644`；
- 空目录，权限为 `0755`。

Global 根只允许创建尚不存在的 `AGENTS.md`；`skills` 由系统确保存在。名称必须是单个
路径 segment，不能为空、`.`、`..`、denylist 名称或包含 `/`、NUL。

目标已存在时返回 AlreadyExists，不覆盖内容。

### 7.8 Update File

请求包含：

```text
workspace
path
content
expected_revision
```

规则：

- 目标必须是当前可编辑的普通文件；
- 新正文必须是不超过 1 MiB 的合法 UTF-8 文本且不包含 NUL；
- `expected_revision` 必须与 Sidecar 当前 revision 一致，否则返回 Aborted；
- 写入同目录临时文件，设置与原文件一致的权限后，通过原子 rename 替换；
- 任一步失败都不得暴露半写正文；
- 成功返回新的 revision 和文件元信息。

MVP 不自动合并内容，也不支持忽略 revision 的强制覆盖。

### 7.9 Move Entry

Move 同时承担重命名能力。

- source 与 destination 必须属于同一个 Workspace；
- Project 根、Global 根、`Global/skills` 根和 Project 自身不能移动；
- Global 的 `AGENTS.md` 不能移动或改名；
- destination parent 必须已存在且是可管理普通目录；
- destination 已存在时返回 AlreadyExists；
- 使用同一文件系统内的 rename，不提供 copy + delete fallback；
- 目录不能移动到自身或自己的后代；
- source revision 不一致时返回 Aborted。

### 7.10 Delete Entry

- 文件和空目录可直接删除；
- 非空目录要求 `recursive=true`；
- Project 根、Global 根和 `Global/skills` 根不能通过 Entry API 删除；
- `Global/AGENTS.md` 可删除；
- 所有删除都是永久删除；
- expected revision 不一致时返回 Aborted。

前端对任何 Entry 删除都必须二次确认，并明确提示不可恢复。

## 8. 路径与文件系统安全

### 8.1 路径规则

所有 API path 都是相对 Workspace Root 的 UTF-8 路径。Sidecar 必须拒绝：

- 绝对路径；
- 空 segment；
- `.`、`..`；
- NUL；
- 超出系统配置的最大长度或深度；
- 清理或解析后落到 Workspace Root 外的路径；
- 任意已有路径 segment 是符号链接的路径。

默认最大路径长度为 4096 bytes，最大深度为 64。

仅使用 `filepath.Clean` 或字符串前缀判断不足以构成安全边界。实现必须逐层检查实际文件
类型且不跟随符号链接，并在最终操作前再次确认目标仍位于已解析的 Workspace Root 内。

### 8.2 Denylist

Sidecar 配置提供精确目录名 denylist，默认包含：

```text
.git
.hg
.svn
node_modules
.venv
venv
vendor
dist
build
.next
.cache
__pycache__
```

规则：

- 匹配路径中的任意目录 segment；
- 目录树不返回命中项；
- 直接读取、创建、移动或删除命中路径返回 PermissionDenied；
- `.git` 始终强制加入，即使配置遗漏也不能解除；
- denylist 不影响 Delete Project 删除整个 Project，因为该操作以 Project 为整体永久删除。

### 8.3 符号链接

MVP 不支持符号链接：

- List 不返回符号链接；
- Read、Create、Update、Move、Delete 不跟随也不操作符号链接；
- 通过符号链接逃逸 Workspace 的请求返回 PermissionDenied；
- Sidecar 不提供创建符号链接的 API。

## 9. Revision 与并发

revision 是 Sidecar 根据当前文件系统状态生成的不透明字符串。客户端不得解析或自行
构造。文件 revision 至少绑定当前内容和关键 stat 信息；目录 revision 绑定当前目录项
状态。

并发规则：

- Read/List 返回 revision；
- Update、Move、Delete 必须提交读取到的 expected revision；
- revision 已变化时返回 Aborted，并带不含文件正文的冲突原因；
- Sidecar 在单实例内用同一写锁串行执行 Create Project、Delete Project 和全部 Entry
  mutation；
- Sidecar 不为 Agent Codex 进程加锁。Agent 进程在检查后、提交前写入文件仍可能造成
  冲突；Sidecar 必须在最终替换前再次验证 revision。

## 10. API 合约

跨组件接口必须先定义在 `packages/contracts/`，再生成 Go SDK、gRPC-Gateway 和前端使用
的 OpenAPI Client；不得在 Agent Gateway 与 Sidecar 中分别维护手写重复结构。

### 10.1 Agent Gateway 对外服务

建议新增 `WorkspaceManagementService`，HTTP 资源前缀为：

```text
/api/v1/agent-gateway/agents
```

接口：

| RPC | HTTP | 用途 |
| --- | --- | --- |
| `ListAgents` | `GET /agents` | 返回配置中静态注册的 Agent 信息 |
| `ListProjects` | `GET /agents/{agent_id}/projects` | 列出 Projects |
| `CreateProject` | `POST /agents/{agent_id}/projects` | 创建并 Git 初始化 |
| `DeleteProject` | `DELETE /agents/{agent_id}/projects/{name}` | 永久删除 Project |
| `ListProjectEntries` | `GET /agents/{agent_id}/projects/entries` | 懒加载目录 |
| `GetProjectFile` | `GET /agents/{agent_id}/projects/files` | 获取正文或元信息 |
| `CreateProjectEntry` | `POST /agents/{agent_id}/projects/entries` | 新建文件或目录 |
| `UpdateProjectFile` | `PUT /agents/{agent_id}/projects/files` | 保存文本 |
| `MoveProjectEntry` | `POST /agents/{agent_id}/projects/entries:move` | 移动或重命名 |
| `DeleteProjectEntry` | `DELETE /agents/{agent_id}/projects/entries` | 永久删除 Entry |

WorkspaceRef、path、cursor 等复杂参数使用生成合约定义的 query encoding；写操作的正文
使用 JSON body。

### 10.2 Sidecar 内部服务

Sidecar 提供与上述文件系统能力一一对应的 gRPC Service，但不包含 `agent_id`。Agent
Gateway 根据 `agent_id` 选择 Client 后再发起请求。

每次内部请求携带：

- request ID；
- operator；
- 调用 deadline。

Sidecar 信任内部网络，不执行 token 校验。operator 仅用于审计上下文，不参与文件权限
判断。

### 10.3 错误语义

| gRPC code | 场景 |
| --- | --- |
| `InvalidArgument` | 名称、Workspace、path、cursor、文本或确认参数非法 |
| `NotFound` | Agent、Project、目录或文件不存在 |
| `AlreadyExists` | Create 或 Move 的目标已存在 |
| `PermissionDenied` | 非 admin、访问 denylist/Global allowlist 外路径、符号链接 |
| `Aborted` | revision 冲突或目录 cursor 已失效 |
| `ResourceExhausted` | 文件、路径、深度或分页限制超出 |
| `FailedPrecondition` | 目标类型不允许、非空目录未指定 recursive 等 |
| `Unavailable` | Sidecar 不可达或请求超时 |
| `Internal` | 未分类文件系统错误 |

错误消息只描述可行动原因，不回显 Sidecar 内部 endpoint、物理根路径、文件正文或
Secret。

## 11. Management Portal 体验

### 11.1 导航与权限

- 在 admin 菜单增加 `Agents`；
- 路由仅对 admin 注册；
- 非 admin 直接访问路由时沿用现有无权限处理；
- 页面使用 Agent Gateway 生成 API Client，不手写协议类型。

### 11.2 页面结构

```text
+-------------------------------------------------------------+
| Agents  [Agent instance selector]                           |
+-------------------------------------------------------------+
| [Projects] [Global]                                         |
+--------------------------+----------------------------------+
| Project list / file tree | File information                 |
|                          | CodeMirror editor                |
|                          | [Cancel] [Save]                  |
+--------------------------+----------------------------------+
```

- 首次进入优先选择 `is_default` Agent；默认 Agent 不支持 Project 管理时选择第一个支持的 Agent；
- 切换实例时重新加载 Project 和 Global 状态，不复用上一个实例的缓存；
- Project 请求返回 `Unavailable` 时保留实例可见性并提供重试；
- 所有树节点按需展开，加载下一页时追加到当前目录。

### 11.3 Projects 页签

- 显示 Project 名称和 Git 初始化状态；
- 提供 Create Project；
- 选中 Project 后显示其目录树；
- Delete Project 弹窗要求输入完整 Project 名称，文案明确“永久删除且无法恢复”；
- 删除成功后清空当前编辑器并刷新列表。

### 11.4 Global 页签

- 固定显示 `skills`；
- `AGENTS.md` 不存在时显示创建入口，存在时作为文件节点显示；
- 不展示 CODEX_HOME 物理路径和其他文件；
- `skills` 根节点不提供删除、移动或重命名操作。

### 11.5 文件查看与编辑

- 使用仓库已有 CodeMirror；
- 根据扩展名选择已安装语言支持，未知文本使用 plain text；
- 编辑器显示文件 path、大小、modified time 和 dirty 状态；
- 不自动保存；支持保存按钮和常用保存快捷键；
- Cancel 恢复本次读取到的服务器正文；
- 不可编辑文件只显示元信息和原因；
- 不提供 binary preview、Base64、上传或下载。

### 11.6 未保存内容

存在 dirty editor 时，以下操作先确认：

- 选择其他文件；
- 切换 Project；
- 切换 Projects/Global；
- 切换 Agent；
- 离开当前路由或关闭页面。

保存发生 revision 冲突时：

- 保留本地编辑内容；
- 显示“文件已被其他操作修改”；
- 提供“重新加载服务器版本”和“复制当前内容”；
- 不提供忽略冲突的强制保存。

### 11.7 Entry 操作

目录树上下文操作支持：

- 新建文件；
- 新建目录；
- 重命名；
- 移动到同 Workspace 内的目标目录；
- 删除。

文件和目录删除都要求二次确认；非空目录确认文案明确其内容将递归删除。

## 12. 审计与可观测性

### 12.1 结构化审计

Agent Gateway 对每次 mutation 记录：

```text
request_id
operator
agent_id
workspace_scope
project_name
action
source_path
destination_path
result
grpc_code
duration
```

Create/Delete Project 记录 Project 名称。审计不得包含：

- 文件正文；
- Sidecar 内部 endpoint；
- CODEX_HOME 其他文件名；
- Secret 值。

读取操作保留普通访问日志和 metrics，不要求产品级审计历史页面。

### 12.2 Metrics

Agent Gateway 和 Sidecar 至少提供：

- 各 action 请求数、成功数、错误数和 duration；
- revision conflict 次数；
- denylist/path/symlink 拒绝次数；
- Sidecar unavailable 次数；
- Create Project Git 初始化失败次数；
- 文件大小限制拒绝次数。

### 12.3 Agent 列表

- `ListAgents` 返回按 `agent_id` 排序的静态 Agent 信息，包括 Provider、Project 管理能力和 `is_default`；
- 接口不探测 Sidecar 健康状态，也不要求服务层 `X-Operator`；
- Sidecar 可用性在实际 Project 请求中按调用结果体现。

## 13. 配置

### 13.1 Agent Gateway

每个可管理 Agent Provider 增加通用 Sidecar 配置。配置名称不绑定 Workspace 语义，
便于 Sidecar 后续扩展其他能力：

```yaml
agentRuntime:
  agents:
    codex-a:
      provider: codex_app_server
      codexAppServer:
        endpoint: ws://agent-codex-a:4500
      sidecar:
        endpoint: agent-codex-a-sidecar:9091
        timeout: 10s
```

`sidecar` 缺失表示该 Provider 没有 Agent Sidecar，因而不支持本 PRD 的文件管理能力。

### 13.2 Sidecar

```yaml
rpcServer:
  address: :9091

services:
  project:
    projectsRootDir: /data/projects
    codexHomeDir: /home/node/.codex
    maxEditableFileSize: 1048576
    maxPageSize: 500
    maxPathLength: 4096
    maxPathDepth: 64
    deniedDirectories:
      - .git
      - .hg
      - .svn
      - node_modules
      - .venv
      - venv
      - vendor
      - dist
      - build
      - .next
      - .cache
      - __pycache__
```

## 14. 数据与兼容性

- 不新增数据库表；
- 不迁移或重写已有 Projects、Skills、AGENTS.md；
- Sidecar 首次启动只确保 `$CODEX_HOME/skills` 存在；
- 既有非 Git Project 保持原样并标记 `git_initialized=false`；
- 既有符号链接和 denylist 目录保留在磁盘上，但产品不可见、不可操作；
- 下线 Sidecar 不影响 Agent Codex 直接使用原 PVC；
- API revision 是不透明实现细节，不承诺跨 Sidecar 版本稳定。

## 15. 测试策略

### 15.1 Sidecar 单元测试

- 合法 Project 创建并产生 `.git`；
- 名称非法、denylist、同名文件或目录时拒绝创建；
- Git 初始化失败时回滚新目录；
- 既有非 Git 目录正常列出；
- Delete Project 确认名不匹配时不删除，匹配时永久递归删除；
- Global 只允许 `skills/**` 与 `AGENTS.md`；
- `skills` 根不可移动或删除；
- absolute、`.`、`..`、空 segment、NUL、超长和超深路径被拒绝；
- denylist 目录不展示且直接访问被拒绝；
- 各层符号链接和并发替换符号链接不能逃逸 root；
- 文本、二进制、非法 UTF-8、含 NUL 和超过 1 MiB 文件识别；
- Create、Move、Delete 的类型和边界；
- 原子写成功及临时写失败不破坏原文件；
- expected revision 一致时成功，不一致时 Aborted；
- 并发 mutation 串行化；
- cursor 分页、排序和目录变化后的 cursor 失效。

### 15.2 Agent Gateway 单元与集成测试

- 返回全部配置的 Provider，并正确标记 Project 管理能力和默认 Agent；
- 多实例 Client 按 agent_id 路由且相互隔离；
- 未知 agent_id 返回 NotFound；
- ListAgents 不调用 Sidecar，单个 Sidecar 不可达时实际操作返回 Unavailable；
- Gateway 按实例调用正确的内部 Sidecar endpoint，且不发送认证 token；
- operator 只能来自认证上下文；
- gRPC error 到 HTTP error 映射稳定；
- Gateway 不保存或记录文件正文；
- mutation 产生完整审计字段；
- 存在 Processing Task 时仍不查询 Task Service，文件请求照常转发；
- Proto、gRPC-Gateway、Swagger 和生成 SDK 一致。

### 15.3 Management Portal 测试

- Agents 菜单和路由仅 admin 可见；
- 静态实例列表、默认 Agent、Project 管理能力和操作失败重试；
- 切换实例不复用另一个实例的 Project 或文件缓存；
- Projects/Global 双页签和目录懒加载；
- Create Project、名称校验和 Delete Project 输入确认；
- 文件/目录 CRUD、移动和删除确认；
- UTF-8 文本使用 CodeMirror，binary/超限文件只显示元信息；
- dirty 状态下切换目标和离开页面的确认；
- Save/Cancel；
- revision conflict 保留本地内容并提供重新加载、复制；
- Sidecar/API 错误提示不展示内部 endpoint 或 root path。

### 15.4 部署验证

- Sidecar 和 Agent Codex 以 UID/GID 1000 访问两个 PVC；
- Sidecar 端口只在集群内暴露；
- Sidecar endpoint 仅在内部可信网络可达，不配置调用 token；
- OPENAI_API_KEY 等现有 Secret 不进入 ConfigMap 或 Git；
- Sidecar restart 后文件保持不变；
- Sidecar unavailable 不清空目录或触发 Project 初始化；
- RWO PVC 下 Deployment 继续采用单副本和 Recreate 策略。

### 15.5 端到端验收

准备两个 Managed Agent `codex-a` 和 `codex-b`：

1. 分别创建同名 Project `demo`，验证两个 PVC 均有独立目录和 `.git`；
2. 验证两个实例的目录树均不显示 `.git`；
3. 在 `codex-a/demo` 新建并编辑文本，刷新后读取到新内容，`codex-b` 不出现该文件；
4. 用旧 revision 再次保存，验证冲突且服务端内容不被覆盖；
5. 创建 denylist 目录并验证 UI 不展示、直接 API 访问被拒绝；
6. 在 Global 创建/编辑 `AGENTS.md` 和 `skills/example/SKILL.md`；
7. 尝试访问 `auth.json`、`config.toml` 和符号链接目标，验证 PermissionDenied；
8. 删除文件、目录、AGENTS.md 和 Project，验证永久删除确认与审计；
9. 停止 `codex-b` Sidecar，验证列表仍返回静态信息、`codex-b` 操作返回 Unavailable，且 `codex-a` 仍可管理；
10. 验证审计记录完整且不含任何文件正文或 Secret。

## 16. 发布计划

### 16.1 第一阶段：Sidecar 与合约

- 定义共享 Proto/OpenAPI contract；
- 实现 Sidecar 文件系统边界和测试；
- 构建独立 `agent-sidecar` 镜像；
- 在 dev 为测试 Agent 挂载相同 Projects/CODEX_HOME PVC。

### 16.2 第二阶段：Agent Gateway

- 扩展 Provider 配置和 per-agent Client；
- 提供 WorkspaceManagementService；
- 接入 operator、审计、metrics 和静态 Agent 列表；
- 使用至少两个测试实例完成隔离验证。

### 16.3 第三阶段：Management Portal

- 接入生成客户端；
- 增加 admin Agents 页面；
- 完成 Projects、Global、目录树和编辑器交互；
- 在 dev 完成端到端验收。

MVP 验收完成前不扩展至 staging/prod。后续环境发布需要单独确认实例清单、PVC、Secret、
入口授权和审计保留策略。

## 17. 风险与处理

| 风险 | MVP 处理 | 后续方向 |
| --- | --- | --- |
| Agent 执行期间文件被修改或删除 | 明确不检查 Task；文件系统错误直接返回 | 增加按 Project/Global 的 Task 协调锁 |
| Sidecar 权限可读取 CODEX_HOME Secret | API 使用 Global allowlist，路径逐层校验，不记录内容 | 独立卷拆分 Skills 与 Codex 私有状态 |
| 路径校验与 symlink 存在 TOCTOU | 不跟随 symlink，操作前再次验证，使用安全文件描述符操作 | Linux `openat2` confinement |
| 永久删除误操作 | Project 输入名称确认，Entry 二次确认，结构化审计 | 回收站与保留策略 |
| RWO PVC 限制横向扩展 | 每实例单副本、Recreate | 独立 Workspace 服务或支持 RWX 存储 |
| 大目录导致 UI/Sidecar 压力 | denylist、懒加载、500 项上限和 cursor | 搜索索引、虚拟滚动和目录摘要 |
| 多管理员同时编辑 | opaque revision，冲突时拒绝覆盖 | diff/merge 和协同编辑 |
| 静态配置的实例上下线不及时 | Gateway 重部署生效 | Kubernetes discovery 或实例注册中心 |
| 内部网络中的非预期服务调用 Sidecar | 仅提供 ClusterIP、限制网络可达范围并保留审计 | 需要跨信任域时再引入 ServiceAccount JWT 或 mTLS |

## 18. 最终决策摘要

- 产品名称为 Agent Codex Workspace 文件管理，Pod 内通用组件命名为
  `agent-sidecar`，后续可扩展其他管理能力；
- 管理链路为 Management Portal → Agent Gateway → Sidecar；
- `agentRuntime.agents` 配置 key 是 `agent_id`，配置是多实例清单；
- Sidecar 与 Agent Codex 共享 `/data/projects` 和 `$CODEX_HOME` PVC；
- Projects Root 的每个一级普通目录都是 Project，Create 执行
  `git init --initial-branch=main`；
- Global 只开放 `skills/**` 和 `AGENTS.md`；
- 支持 Project 创建/永久删除，以及 Workspace 内文件目录 CRUD、重命名和移动；
- 只在线编辑不超过 1 MiB 的 UTF-8 文本，不支持 binary 上传下载；
- 服务端 denylist 同时负责隐藏和禁止访问，`.git` 永远受保护；
- 符号链接不展示、不创建、不跟随、不操作；
- revision + 原子替换防止静默覆盖和半写；
- Sidecar 串行 mutation，但不感知运行中的 Agent Task；
- Management Portal 提供 admin-only Agents 页面、实例选择、Projects/Global 双页签、
  懒加载目录树和 CodeMirror 编辑器；
- 删除不可恢复；Project 删除要求输入名称，Entry 删除要求二次确认；
- Gateway 不持久化文件正文，所有 mutation 记录脱敏结构化审计；
- MVP 仅在 dev、RWO PVC、单实例单副本模型下交付。
