# 09 · Fmode 企业 Agent 闭环执行规范

> 这是企业微服务开通后交给 Claude Code、Codex、WorkBuddy 或 FmodeStudio Agent 的长期执行规范。短授权提示词只引用本文件的线上地址；本文件与 `00`–`08` 及 `10-enterprise-delivery-method.md` 共同生效。

### 规则来源与优先级

- `https://repos.fmode.cn/x/fmode-studio/fmode-enterprise-agent-loop.md` 是本文件的线上发布镜像，不是第二套规则。Agent 已读取其中任一份后，同一版本无需重复读取另一份；manifest 安装本文件是为了让后续任务持续生效。
- manifest 中的版本是规则包版本依据。线上镜像与本地文件内容不一致时，先按 manifest 更新规则；仍不一致则以 manifest 下载的本地规则为准，并在交付报告记录版本差异。
- 指令优先级为：用户当前明确需求 > 当前项目代码和配置事实 > 企业规则 `09/10` > 通用规则 `00`–`08`。任何层级都不能放宽密钥保护、租户隔离和发布证据要求。
- `09` 负责运行配置、实现和托管发布闭环；`10` 负责需求分析、UI、阶段切换和验收方法。两者职责互补，不互相覆盖。

## 一、启动顺序

### 1.0 空白 Agent 的规则引导

当当前目录没有 `.claude/` 或没有 FmodeStudio 服务端时，先从线上规则 manifest 补齐规则：

- Manifest：`https://repos.fmode.cn/x/fmode-rules/claude/manifest.json`
- 文件根地址：`https://repos.fmode.cn/x/fmode-rules/claude/`

PowerShell：

```powershell
$manifestUrl = 'https://repos.fmode.cn/x/fmode-rules/claude/manifest.json'
$manifest = Invoke-RestMethod $manifestUrl
foreach ($item in $manifest.files) {
  $target = Join-Path (Get-Location) $item.dest
  New-Item -ItemType Directory -Force (Split-Path $target) | Out-Null
  Invoke-WebRequest "$($manifestUrl -replace '/manifest.json$','')/$($item.src)" -OutFile $target
}
```

Bash：

```bash
manifest_url='https://repos.fmode.cn/x/fmode-rules/claude/manifest.json'
base_url='https://repos.fmode.cn/x/fmode-rules/claude'
manifest="$(curl -fsSL "$manifest_url")"
printf '%s' "$manifest" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{for(const f of JSON.parse(s).files) console.log(`${f.dest}\t${f.src}`)})' |
while IFS=$'\t' read -r dest src; do mkdir -p "$(dirname "$dest")"; curl -fsSL "$base_url/$src" -o "$dest"; done
```

下载完成后必须重新读取 `.claude/CLAUDE.md`、全部规则和项目 `git status`；下载失败、manifest 结构异常或规则版本无法确认时停止实现并报告原因。

每次开始任务都按以下顺序执行：

1. 读取当前项目 `.claude/CLAUDE.md` 和 `.claude/rules/`，再读取本规范及 `11-project-layout.md`。
2. 读取项目 `git status`、工作区目录、`app/`、`agent/`、应用 `package.json`、前端入口、后端入口、`cloud/`、`api/`、`config.json` 和 `docs/`；已有项目没有 `app/` 或 `agent/` 时，记录现状，不擅自迁移。
3. 读取用户级 `~/.fmode/config/user.json`、`~/.fmode/config/enterprise/current.json`；只在需要时读取敏感字段，禁止输出原文。
4. 将用户需求拆成数据模型、页面、接口/云函数、权限租户、测试、发布和验收条件，然后执行，不停留在方案说明。新企业项目按 `docs/prd/`、`docs/design/`、`docs/schema/`、`docs/api/`、`docs/quality/` 留存文档。

本规范自包含技能安装结论，不依赖任何项目内的历史记录文档。当前项目代码、配置和线上接口始终优先于历史说明。

### 1.1 技能与规则安装要求

- **规则包是强制项**：必须按 manifest 下载 `.claude/CLAUDE.md`、`.claude/settings.local.json` 和全部 `.claude/rules/`，并在实现前重新读取；不能只读取本文件。
- **技能分为两类**：需求分析、架构、测试和交付所需的方法技能属于默认基线，缺少时必须安装；音视频、VOC、提号等业务技能按用户需求安装。先检查当前环境已安装的 `.claude/skills/`，缺少基线技能时优先项目级安装到当前项目。
- **当前 Fmode 业务技能清单**（来源 `Skill` 库 `type=claude`，按需安装）：

  | 技能 | 适用场景 | 项目级安装命令 |
  |---|---|---|
  | `fmode-listen` | 录音转写、多语种和说话人识别 | `npx --yes fmode-listen@latest workspace` |
  | `fmode-ffmpeg` | 音视频转码、抽帧、压缩和媒体信息 | `npx --yes fmode-ffmpeg@latest workspace` |
  | `fmode-vision` | 图片/视频理解、批量视觉分析 | `npx --yes fmode-vision@latest workspace` |
  | `tihao` | 达人/博主筛选和候选名单 | `npx --yes @vocmarket/tihao@latest workspace --smoke` |
  | `voc-skill` | VOC 趋势、问题池和选题脚本 | `npx --yes @vocmarket/voc-skill@latest workspace --smoke` |

- 安装前先说明技能用途；安装失败不阻塞项目实现，但最终报告要列出技能、命令和失败原因。`voc-skill` 的扫码/账户激活只能在用户明确需要 VOC 且确认后执行，默认不得使用 `--activate`。
- **默认方法技能基线**（来源 `mattpocock/skills`，虽然历史上从 Fmode `Skill` 表撤回，但仍是 Agent 默认安装项）：

  | 技能 | 用途 | 项目级安装命令 |
  |---|---|---|
  | `tdd` | 测试驱动开发 | `npx --yes skills@latest add mattpocock/skills --skill tdd codebase-design --agent claude-code --yes --copy` |
  | `diagnosing-bugs` | 系统化故障诊断 | `npx --yes skills@latest add mattpocock/skills --skill diagnosing-bugs --agent claude-code --yes --copy` |
  | `grill-me` | 需求/方案深度追问 | `npx --yes skills@latest add mattpocock/skills --skill grill-me grilling --agent claude-code --yes --copy` |
  | `grill-with-docs` | 追问与文档沉淀 | `npx --yes skills@latest add mattpocock/skills --skill grill-with-docs grilling domain-modeling --agent claude-code --yes --copy` |
  | `improve-codebase-architecture` | 代码库架构改进 | `npx --yes skills@latest add mattpocock/skills --skill improve-codebase-architecture codebase-design grilling domain-modeling --agent claude-code --yes --copy` |

- “已撤回”只表示这些技能记录曾从 Fmode 自有 `Skill` 表删除，是内部数据操作备注，不表示 Agent 禁用。Agent 仍按上表默认安装；不得把它们误报为 Fmode `Skill` 库当前在线条目。
- `brainstorming`、`to-spec`、`ui-new`、`ui-audit` 等其他方法技能按 `10` 的技能路由检查环境后按需安装；不存在时使用 `10` 的内嵌规则完成工作。
- 技能安装失败不影响 Parse/云函数项目实现，但必须在最终报告列出失败技能和原因；规则下载失败则停止实现。

## 二、本机配置契约

### 2.1 用户级配置

企业开通后，维护以下文件，保留已有字段并采用原子写入：

- `~/.fmode/config/user.json`：用户身份、`fmodeApiToken`、`newapiToken`、兼容字段 `fmode_newapi_token`。
- `~/.fmode/config/enterprise/current.json`：当前企业 ID、名称、订阅、`appId`、`masterKey`、`serverURL`、`functionsURL`。数据库连接地址仅供 Fmode 平台内部审核，不下发给用户或 Agent，也不写入本机企业配置。
- `~/.fmode/config/enterprise/<companyId>.json`：同一企业的历史/切换记录。
- `~/.fmode/config.json`：仅作为旧版兼容镜像；不能覆盖新配置中的额外字段。

如果本机 FmodeStudio 服务可用，优先调用 `POST /api/system/enterprise-runtime-config` 完成企业配置和全局技能写入；失败后才由 Agent 按上述契约落盘。

### 2.2 项目级配置

如果当前项目已有 `.fmode/`，可写入 `.fmode/config.json` 作为项目入口；该目录必须加入 `.gitignore`，不得把 `masterKey`、New API token 或 session token 提交到 Git。没有 `.fmode/` 时不要为了配置强行新增目录，优先使用用户级配置。

### 2.3 敏感信息

- `fmode_newapi_token` 只用于 Fmode New API / 模型调用授权，不得写入源码、截图、日志、PR、终端回显或最终报告。
- `masterKey`、session token 同样只允许在本机配置和服务端使用。提示词中的 `FMODE_PARSE_SESSION_TOKEN` 仅在发布前端项目时用于平台主 Parse 的 Project 创建/校验，以及调用 `https://server.fmode.cn/api/apig/deploy/huaweicloud` 和 `https://server.fmode.cn/api/apig/deploy/huaweicloud-cdn-refresh`；本地 `/api/obs/*` 只是一种可选的构建/上传执行器。普通需求、编码、企业 Parse/动态云函数和数据库操作不得使用或长期保存该平台 session。Agent 不得把它写入源码、提交、截图、日志或最终报告，也不得将它当作企业 `masterKey`、本地 Code UI API Key 或华为 OBS `securityToken` 使用。
- 禁止向用户或 Agent 暴露、索取、推断或使用数据库 URI；数据查询、写入、Schema 和迁移统一通过 Parse API/SDK、Cloud Code 或动态 Function，并在需要时使用 `masterKey` 完成服务端授权。
- 展示配置时只显示字段名、是否存在和脱敏尾部；不得打印完整值。

### 2.4 平台 sessionToken 失效时的对话式恢复

`FMODE_PARSE_SESSION_TOKEN` 失效与华为 STS 过期是两种不同情况。平台接口返回 401/209、Parse `Invalid session token` 或无法通过 `/parse/users/me` 校验时，不得索取用户华为云账号，也不得要求用户跳转 FmodeStudio 或其他管理后台。Agent 必须暂停当前发布动作，通过当前对话完成一次手机号验证码登录：

1. 先向用户说明“平台登录 session 已过期，需要手机号验证码重新登录”，只请求手机号，不请求密码、masterKey 或华为云凭据。手机号必须符合中国大陆手机号格式；不得在日志、项目文件或最终报告中保存完整手机号。
2. Agent 不得依赖用户项目中的 Parse/FmodeParse SDK。直接调用平台短信接口发送验证码：

```http
POST https://server.fmode.cn/api/apig/message
Content-Type: application/json
X-Parse-Application-Id: ncloudmaster

{ "mobile": "<手机号>", "company": "E4KpGvTEto" }
```

只有响应 `code == 1` 才算发送成功；失败时报告脱敏错误，不得重复发送超过接口允许频率。该接口是 Agent 与平台之间的登录辅助接口，不要求用户项目安装任何 SDK。
3. 等待用户在对话中主动回复短信验证码。不得自动猜测、读取用户短信或把验证码写入文件、日志、命令历史。
4. 收到验证码后调用平台登录接口：

```http
GET https://server.fmode.cn/api/fmode/mobile?mobile=<手机号>&code=<验证码>
Content-Type: application/json
```

只有响应 `code == 200` 且 `data.token` 存在才算成功。`data.token` 就是新的 `FMODE_PARSE_SESSION_TOKEN`，`data.newapiToken` 是 New API token；两者都由登录接口直接返回，Agent 不需要、也不得调用 `Parse.User.become` 或其他项目 SDK 来换取 session。将新 sessionToken 只保存在当前 Agent 进程内存中。
5. 使用新 sessionToken 重新调用 `/parse/users/me` 验证，再回到原发布步骤；重新校验 Project 权限并重新申请 STS。不得复用旧 STS、旧 session 或旧失败响应。
6. 登录失败时只报告平台返回的脱敏错误；验证码最多按用户主动提供的次数尝试，不得暴力重试。新 session 仍不得写入 Git、源码、`.fmode/deploy.json`、终端历史、日志、截图或最终报告。

## 三、需求到实现

### 3.1 数据与租户

1. 优先复用 `_User`、`Company`、`Department`、`Profile`、`Account` 等预设类。
2. 新业务表必须包含 `company: Pointer<Company>` 和 `isDeleted`，并在 `docs/schema/` 维护 PlantUML/字段说明。
3. 创建或迁移 Schema 使用 FmodeSchema/平台脚本，操作必须幂等；删除默认软删除。
4. 所有查询默认按当前企业过滤；管理员跨租户操作必须显式说明并使用服务端权限。
5. 服务端重新校验 user、company、角色、资源 ID 和输入长度，不能相信前端传来的企业上下文。

### 3.2 前端

1. Angular 优先使用 `fmode-ng/core` 的 `FmodeParse`，应用入口只初始化一次，页面复用实例。
2. `serverURL` 只在初始化配置处维护；不得在多个组件散落硬编码地址。
3. 普通 CRUD 走 Parse SDK；加载、空态、错误、重试、权限和网络中断必须有可见状态。
4. 表单和按钮必须防重复提交；轮询必须有成功、失败、超时三个终止条件。
5. 前端绝不保存或打包 `masterKey`；需要管理员动作时由服务端完成。

### 3.3 后端、云函数与 API

1. Parse Claude Code（`cloud/main.js`）：钩子、聚合、简单服务逻辑。
2. 动态云函数（`fmode-functions-api`）：需要即时生效的业务函数，遵守 `functions.md` 的 Function 表、鉴权、参数和返回契约。
3. 自定义 API：第三方对接或独立服务放入 `api/module/*/routes`（或当前项目等价模块），统一错误格式、CORS、鉴权和限流。
4. 每个写接口都要考虑幂等键、并发重复请求、失败重试和部分成功；状态机不能卡死在 running/pending。
5. 外部服务错误要保留可恢复状态并记录脱敏错误；不能吞异常后返回假成功。

## 四、测试与发布

### 4.1 测试顺序

1. 静态检查：格式、类型、语法、`git diff --check`。
2. 单元/模块测试：Schema、序列化、状态机、鉴权、错误映射和提示词生成。
3. 本地冒烟：登录 → Parse `/users/me` → 核心 API/云函数 → 前端状态 → 失败重试。
4. 构建：`npm run build` 或 `ng build --configuration production`；后端执行 `node --check`/类型检查。
5. 线上回归：健康检查、Parse、Functions、核心页面、关键写操作和回滚路径。

测试不得默认真实扣费、真实删除、群发消息或大规模取数；需要这类动作时先得到用户明确授权。

### 4.2 发布门禁

发布前必须确认：

- 目标 `serverURL`、Functions 地址、域名和 CORS 正确；
- Schema、云函数、API 路由和环境变量已在目标环境存在；
- `.env`、`config.json`、masterKey、token 不入 Git，数据库 URI 不进入用户项目或提示词；
- 前端生产构建通过，后端可启动，核心接口冒烟通过；
- 当前变更、回滚点和发布版本可追踪。

企业微服务领取页默认使用 Fmode 托管能力：前端通过 Parse SDK 或动态云函数读写数据；只有发布前端项目时，才临时使用当前用户 sessionToken 调用 FmodeStudio 托管发布接口，再按项目已有流程使用华为云 OBS/CDN 发布配置。Fmode 登录 sessionToken 与华为 OBS 临时 `securityToken` 不是同一个凭据，不能混用。Agent 不要求用户进行任何服务器级运维；只有用户明确提供自托管需求时，才将其作为额外项目范围并先确认授权。发布后必须重新访问线上 URL 验证真实链路，不以本地构建通过代替线上验证。

### 4.3 前端 Project 创建与托管发布协议

本节是前端发布的固定协议，适用于 FmodeStudio 内置发布页，也适用于没有安装或没有启动 FmodeStudio 的空白 Agent。不得因为本机没有 `localhost:16666`、`/api/obs/*` 或 Code UI API Key 就停在排查阶段；这些本地接口只是可选执行器，平台 Project、STS 和 CDN 接口均可独立调用。

#### 4.3.1 凭据与系统边界

发布涉及四类配置，必须严格区分：

| 配置 | 用途 | 禁止用途 |
|---|---|---|
| `FMODE_PARSE_SESSION_TOKEN` | 平台主 Parse 登录、创建/校验 Project、申请 STS、刷新 CDN | 不用于企业业务数据，不传给 OBS |
| 企业 `FMODE_PARSE_APP_ID/SERVER_URL/MASTER_KEY` | 企业业务 Schema、数据、动态云函数 | 不用于平台 Project 创建或前端发布鉴权 |
| 本地 Code UI API Key | 可选的 `localhost:16666/api/obs/*` Authorization | 不传给 Parse、STS 或 OBS |
| STS `accessKey/secretKey/securityToken` | 在有效期内上传接口返回的唯一 `obsPath` | 不用于 Fmode 登录，不得跨 Project 使用 |

平台主 Parse 固定为：

- Server URL：`https://server.fmode.cn/parse`
- Application ID：`ncloudmaster`

Project 创建和发布接口只使用平台主 Parse；不要用企业试用开通后得到的独立 Parse 地址和 `masterKey` 替代。

#### 4.3.2 第一步：查找、校验或创建 Project

1. 先读取项目根目录 `.fmode/deploy.json`。存在 `projectId` 时，必须用当前平台 session 调用 STS 接口校验项目存在且有权限；成功则复用，禁止重复创建。
2. 没有 `projectId` 时，先以当前用户、Git URL 和项目名查询 `Project`。只有结果唯一且字段与当前项目一致时才复用；零条则创建，多条或归属不明确时不得猜测 objectId。
3. 先获取当前平台用户，确认 session 有效并取得 `_User.objectId`：

```http
GET https://server.fmode.cn/parse/users/me
X-Parse-Application-Id: ncloudmaster
X-Parse-Session-Token: <FMODE_PARSE_SESSION_TOKEN>
```

4. 创建 Project 使用平台 Parse REST（FmodeStudio 弹窗的 `Parse.Object('Project').save()` 与此等价）：

```http
POST https://server.fmode.cn/parse/classes/Project
Content-Type: application/json
X-Parse-Application-Id: ncloudmaster
X-Parse-Session-Token: <FMODE_PARSE_SESSION_TOKEN>

{
  "name": "<package.json.name；没有则使用项目目录名>",
  "version": "<package.json.version；没有则 1.0.0>",
  "gitUrl": "<存在 Git remote 时填写；不存在则省略该字段>",
  "user": { "__type": "Pointer", "className": "_User", "objectId": "<users/me.objectId>" }
}
```

5. HTTP 成功且响应含 `objectId` 才算创建成功；该 `objectId` 就是后续所有接口的 `projectId`，不是目录名、App ID、Company ID 或随机字符串。
6. 立即原子写入不含密钥的 `.fmode/deploy.json`，至少保存以下字段；不得写入 session、masterKey、AK/SK 或 securityToken：

```json
{
  "projectId": "<Project.objectId>",
  "projectName": "<项目名>",
  "version": "<版本>",
  "gitUrl": "<可选>",
  "createdAt": "<ISO 时间>",
  "updatedAt": "<ISO 时间>"
}
```

直接查询 Project 时使用同一平台 Parse `/classes/Project`、同一 Application ID 和 session header；查询条件至少包含当前用户 Pointer，并结合 `gitUrl` 或 `name`。不得使用企业 `masterKey` 绕过 Project 归属关系。创建请求可能已成功但客户端超时时，先查询再重试，避免生成重复 Project。

#### 4.3.3 第二步：静态化、构建和本地验证

1. 检查 `package.json`、锁文件、框架配置、路由、环境变量、SSR/API Routes 和构建脚本，确定唯一生产产物目录。只发布纯静态产物，根目录必须存在 `index.html`。
2. 发布地址固定为 `https://app.fmode.cn/dev/<projectId>/`。构建前将 Angular `base-href`、Vite `base`、React/Vue public path、Next/Nuxt 静态导出路径或等价配置设为 `/dev/<projectId>/`。
3. OBS 不提供应用服务端路由、SSR、Server Actions、API Routes 或进程托管。动态数据改为浏览器端 Parse SDK；敏感逻辑改为 Parse Cloud/动态 Function。客户端路由优先使用 hash；使用 history 时必须验证深链刷新策略。
4. 使用锁文件对应的包管理器安装依赖并执行生产构建。Angular 新构建器通常上传 `dist/<name>/browser/`，旧 Angular 通常为 `dist/<name>/`，Vite/React/Vue 通常为 `dist/`；必须以实际存在 `index.html` 的目录为准，禁止按通配符盲传。
5. 从产物目录启动本地静态 HTTP 服务，验证首页、主要 JS/CSS、字体图片、核心路由和 Parse 请求；同时扫描产物，确认不含 session、masterKey、数据库 URI、永久 AK/SK、localhost API 或源码环境文件。

#### 4.3.4 第三步：申请 Project 专属 OBS 临时凭据

紧邻上传前调用，禁止提前申请后长时间构建：

```http
POST https://server.fmode.cn/api/apig/deploy/huaweicloud
Content-Type: application/json

{ "token": "<FMODE_PARSE_SESSION_TOKEN>", "projectId": "<Project.objectId>" }
```

该接口由 Fmode 平台后端使用平台自己的华为云授权，为当前用户有权访问的 Project 签发项目隔离、短时有效的临时 STS。用户不需要注册、登录或提供自己的华为云账户，也不需要提供永久 AK/SK；Agent 严禁向用户索取这些信息。该接口不会创建 Project，也不会构建或上传文件。只有 HTTP 成功、顶层 `code == 200` 且 `data.success == true` 才继续。必须使用返回的 `data.accessKey`、`data.secretKey`、`data.securityToken`、`data.expiresAt` 和 `data.obsPath`；这三个字段就是 4.3.5 中 obsutil/OBS SDK 所需的 STS 三元组，来源不是用户配置。目标预期为 `obs://nova-cloud/dev/<projectId>/`。若返回 401，表示平台 `FMODE_PARSE_SESSION_TOKEN` 失效，应从当前已登录平台会话重新取得 session 后再申请 STS；这不是 STS 过期，禁止向用户索取华为云凭据。403 表示 Project 归属不匹配；404 表示 Project 不存在，应回到 4.3.2，禁止循环重试 STS。

STS 必须在生产构建和本地验证完成后、紧邻 OBS 操作时申请。Agent 收到响应后解析 `data.expiresAt`；若已过期、无法解析，或距离过期不足 5 分钟，不得开始清理或上传，必须立即使用同一 `FMODE_PARSE_SESSION_TOKEN` 和同一 `projectId` 再调用本接口，直到获得有效期足够的新 STS。每次重新申请都必须整组替换 accessKey、secretKey、securityToken 和 expiresAt，禁止混用新旧字段，并确认新响应的 `data.obsPath` 仍严格等于当前 Project 的目标路径。

#### 4.3.5 第四步：上传，FmodeStudio 可选而非必需

有 FmodeStudio 本地服务时，可依次调用以下接口，Header 为 `Authorization: Bearer <本地 Code UI API Key>`：

1. `POST http://localhost:16666/api/obs/deploy/detect`，body：`{ "projectPath": "<绝对路径>" }`。
2. `POST http://localhost:16666/api/obs/deploy/build`，body：`{ "projectPath": "<绝对路径>", "projectId": "<id>", "autoDetect": true }`。
3. `POST http://localhost:16666/api/obs/deploy/upload`，body 至少包含：

```json
{
  "projectPath": "<项目绝对路径>",
  "localDir": "<根目录含 index.html 的产物绝对路径>",
  "bucket": "nova-cloud",
  "prefix": "dev/<projectId>",
  "accessKeyId": "<STS data.accessKey>",
  "secretAccessKey": "<STS data.secretKey>",
  "sessionToken": "<STS data.securityToken>",
  "endpoint": "https://obs.cn-south-1.myhuaweicloud.com",
  "region": "cn-south-1",
  "force": true,
  "concurrency": 10
}
```

这里 upload body 的 `sessionToken` 是华为 STS `securityToken`，不是 `FMODE_PARSE_SESSION_TOKEN`。只有响应 `success == true` 且 `stats.failed == 0` 才算上传成功。

没有 FmodeStudio、连接失败、没有本地 API Key，或本地接口返回失败时，Agent 必须直接使用华为云官方 `obsutil` 或 OBS SDK 上传，不得继续寻找本地路由：

1. 从 `data.obsPath` 解析 bucket 与前缀，且再次校验前缀严格等于 `dev/<projectId>/`。
2. 每次命令同时传 STS accessKey、secretKey、securityToken 和 endpoint `obs.cn-south-1.myhuaweicloud.com`；不得省略临时 token，不得改用永久密钥。
3. 更新部署时只清理接口返回的 `data.obsPath`，然后把产物目录的内容同步到该路径根部并设置 `public-read`。确认远端键为 `dev/<projectId>/index.html`，不是多套一层 `dist` 或项目目录。
4. 上传前再次检查 `expiresAt`。上传过程中若 OBS/obsutil 明确返回 token expired、`ExpiredToken`、`SecurityTokenExpired`、`InvalidToken` 等临时凭据过期错误，或当前时间已经达到 `expiresAt`，必须丢弃整组旧 STS，按 4.3.4 使用同一平台 session 和同一 `projectId` 重新申请，重建 OBS 客户端/obsutil 临时配置，然后对同一 `data.obsPath` 重新执行幂等 `sync`。已完成的文件允许由 sync 跳过，不得重新构建、重复创建 Project、扩大目标前缀或改用永久密钥。
5. 单次发布因 STS 过期最多自动续签并重试上传 2 次；仍失败则停止并报告脱敏错误，不得宣称已上线。普通 403、路径无权限、签名错误或网络错误在没有 `expiresAt`/明确过期证据时不得伪装成过期循环续签，应按实际原因处理。
6. STS 临时凭据只放内存或临时配置，完成后立即清除；终端和最终报告不得回显完整值。CDN 刷新使用 `FMODE_PARSE_SESSION_TOKEN`，与 STS 生命周期无关；上传完成后即使 STS 过期，也不需要为刷新 CDN 重新申请 STS。

等价的 `obsutil` 操作语义是：限定目标执行 `rm <data.obsPath> -r -f`，然后执行 `sync <实际产物目录> <data.obsPath> -acl=public-read`；两条命令都必须携带 `-i/-k/-t/-e` 对应 STS 三元组和 endpoint。下载 obsutil 时只使用华为云官方发行地址，并按当前操作系统和 CPU 架构选择版本。

#### 4.3.6 第五步：刷新 CDN 并完成线上回归

仅在上传全部成功后调用：

```http
POST https://server.fmode.cn/api/apig/deploy/huaweicloud-cdn-refresh
Content-Type: application/json

{ "token": "<FMODE_PARSE_SESSION_TOKEN>", "projectId": "<Project.objectId>", "path": "/dev/<projectId>/" }
```

`path` 可省略，服务端会默认使用 `/dev/<projectId>/`；显式传入时必须与 Project ID 完全一致。只有顶层 `code == 200` 且 `data.success == true` 才算刷新成功。

刷新后访问 `https://app.fmode.cn/dev/<projectId>/`，并使用时间戳查询参数规避旧缓存。至少验证首页和 `index.html` 为 200、HTML 引用的主要 JS/CSS/字体/图片无 404、浏览器无致命运行时错误、核心路由可访问、企业 Parse/Functions 请求指向正确线上地址。将 `lastDeployTime`、`lastDeployUrl`、`updatedAt` 和可获得的 `deployedKeys` 更新回 `.fmode/deploy.json`。

#### 4.3.7 状态判断与交付门禁

- 创建 Project 成功不等于发布成功；获得 STS 也不等于发布成功；OBS 上传成功但 CDN 刷新失败只能报告“文件已上传，CDN 刷新失败”。
- FmodeStudio 模式与纯接口模式只能择一完成上传，成功后不得用另一模式重复上传。
- 只有同时具备 Project objectId、构建退出码、本地静态预览、STS 成功响应、OBS 上传结果、CDN 刷新结果和线上资源回归证据，才能报告“已上线”。
- 最终报告只列项目 ID、脱敏后的目标路径、构建命令、产物目录、使用的上传模式、CDN 结果、线上 URL 和验证结果；不得出现任何完整 token 或密钥。
- 前端线上回归成功后必须按 `07-git.md` 执行源代码 Git 归档，并单独报告 Git 仓库、分支和 commit SHA。Git 账户和访问密钥通过 `POST https://server.fmode.cn/api/functions` 的 `/gogs/admin/proxy` 流程获取；新建用户名必须使用 `fmode-<稳定用户标识>`，不得使用手机号或每次随机生成的用户名，再按 `07` 完成仓库创建、提交和推送。没有有效登录会话、Git token 或仓库权限时只能报告“Git 归档待授权”，不得伪造已推送。

### 4.4 不依赖 FmodeStudio 的后端与云函数闭环

当没有 FmodeStudio 桌面端时，Agent 仍必须完成业务后端闭环，但不把 FmodeStudio 服务端当作业务后端，也不要求用户操作服务器：

1. **Parse Schema**：使用企业 Parse/FmodeSchema 能力幂等创建 Schema，保存请求结果和 Schema 快照到 `docs/schema/`。
2. **动态云函数**：通过企业 `Function` 表写入/更新函数，使用用户授权调用 Functions 地址回归；源码型 `cloud/main.js` 按项目已有托管能力处理。
3. **业务接口**：优先使用 Parse SDK 和动态云函数；只有确有第三方回调需求才新增项目自定义 API。不得为了普通 CRUD 新建需要用户维护的服务器。
4. **证据门禁**：只有同时拿到 Schema/Function 写入响应、线上 Parse/Functions 响应和前端真实 URL 回归结果，才能报告完整上线；任何一项缺失都必须准确报告当前阶段。

## 五、交付标准

最终报告只包含：完成事项、文件和接口、测试命令/结果、发布版本/地址、剩余风险。敏感信息只报告“已写入/已生效”，不可回显原文。遇到缺失权限、密钥、域名或外部服务时，明确阻塞点，不伪造成功。小白用户只需要确认业务语义和发布授权；其余技术决策、Schema、云函数、构建和回归由 Agent 执行并留存证据。
