# 迁移指引(BREAKING 变更)

> 版本策略:`@sema-agent/server` 1.x = 快速迭代期,行为破坏性变更可能落在 minor 版本
> (内部多 AI 协作节奏,当日黑板通告+实解)。**生产部署请锁精确版本**;GA 后 2.0 起严格 semver
> (BREAKING → major)。本文件只记录会影响存量部署行为的变更。
>
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。

## SQL 存储面 BREAKING(3.0.0 之后的三个窗)

**常设口径**:本仓**不出 `ALTER TABLE` 增量迁移**(成文裁定:预生产期零存量用户窗口,schema 变更
一律删表/删库重建);boot 对旧形 schema **拒启并带恢复动作文案**,不会静默跑在错形表上。只用
file/local 存储形(未配 SQL 后端)的部署不受本节任何条目影响。

| 窗 | 变更 | 升级动作 |
|---|---|---|
| **7.6.0**(2026-08-08) | SQL 双端归一化第一刀:隔离键排序规则钉死(MySQL 腿表级 `COLLATE utf8mb4_bin`/PG 腿逐列 `COLLATE "C"`)+索引名 36 条+列名/宽度收窄 | **删库重建**(两方言) |
| **7.8.0**(2026-08-09) | SQL 命名三轴归一化第二刀:9 张表名单数化、epoch 毫秒列补 `_ms` 后缀 5 列、approval 两表 `version→rev`;wire 面零变化 | **删库重建**(两方言) |
| **7.14.0**(2026-08-12) | `tool_result` 换代(随 core 5.26.0 的 ref 格式换代):ref 四段单射形、主键 `VARCHAR(190)→518`、新增出处两列 `owner_session_id`/`owner_task_id` | 升级前两方言 **`DROP TABLE tool_result`**(不删=boot 拒启;offload 产物是可恢复窗缓存,转录内联预览不受影响) |

## server 3.0.0 —— BREAKING 四条(2026-07-31)

> 上线前的唯一兼容窗口(clay 令「不做兼容」):旧形**直接消失或 fail-loud**,不留静默兼容层。
> 理由统一:静默兼容会让下游的冒烟测试**测不出问题**,把升级风险推迟到生产;拒启/改键让问题在最早
> 且最清楚的地方现形。

| # | 变更 | 旧形(3.0.0 之前) | 新形 | 你要做什么 |
|---|---|---|---|---|
| ① | HTTP 错误体摘 legacy `code` 键 | `{ error, errorCode, code }` 双键 | `{ error, errorCode }` | 读 `body.code` 的消费端改读 `body.errorCode`(同值,一行) |
| ② | SSE `error` 帧三形归一 | `{code,…}` / `{errorCode,…}` / `{message}` 三种 | 一律 `{ type:"error", errorCode, message }` | 改读 `data.errorCode`;可用 `data.type === "error"` 判帧;workflow 流的错误帧此前**无机器码**,现为 `workflow.stream_error` |
| ③ | D 族负名 env = fail-loud 墓碑 | `X_DISABLED=true` 生效(负名) | 设了(**任何值**)即拒启,文案指路新名 | 按下表把旧名换成正名 |
| ④ | `MODEL_ID` 无出厂缺省 | 未设 = 用烤死的内网模型名 | 未设 = boot fail-loud | 显式配 `MODEL_ID=<你的网关真正提供的模型名>`,或改由 sema-registry 控制面下发目录 |

**③ 的迁移表**(旧名在场即拒启——值是 `true` 还是 `false` 都一样,因为设 `false` 的人同样以为它还生效):

| 🪦 旧名(删掉) | 改设 | 缺省 | 这个旋钮管什么 |
|---|---|---|---|
| `PROJECT_MEMORY_DISABLED` | `PROJECT_MEMORY_ENABLED=false` | 开 | host lane 项目记忆注入(CLAUDE.md + git 叙事) |
| `CONFIG_LKG_DISABLED` | `CONFIG_LKG_ENABLED=false` | 开 | 中心 effective 配置的 LKG 落盘(读写双关) |
| `HOST_BG_DISABLED` | `HOST_BG_ENABLED=false` | 开 | host lane 后台 shell 能力总闸 |
| `HOST_EXEC_SPOOL_DISABLED` | `HOST_EXEC_SPOOL_ENABLED=false` | 开 | host lane exec 的 spool 形 stdio |
| `LSP_ENABLED=false`(拆分前用来关 host 腿) | `LSP_HOST_ENABLED=false` | 开 | host lane 本地 LSP。⚠️ `LSP_ENABLED=true` **不受影响**——那是沙箱腿自己的 opt-in |

**② 的完整码表**见 `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A;机器执行面是 `test/error-code-key-gate.test.ts`
(`src/` 全树的 error 帧 + `src/http/` 全树的 4xx/5xx 错误体,两面都不许漏码)。

## core 引擎侧 BREAKING(经 `@sema-agent/core` 依赖传入)

这些是 core 的行为翻转,server 通过版本 pin 传导给你的部署。滚版前请读对应条目。

### 终局因由 / 门结算记录 / 分类器标签(core 7.6.x,server 7.64.0 起)

- **`TaskResult` 只留 `terminal`**(同步 200 体、SSE `done.result`、`GET /v1/runs/:id` 的 `result` blob):`status` / `errorCode` / `errorMessage` / `blockedReason` / `checkpointToken` / `checkpointId` / `checkpointGate` / `workspaceRestoreMode` 八键删,换 `terminal: {kind:"completed"} | {kind:"failed", code?, message?, nestedPause?} | {kind:"blocked", reason} | {kind:"paused", gate, checkpointId?, restoreMode?}`(wire 上 `paused` 不带 `token`)。客户端改读 `terminal.kind`;run 行的 `status`/`errorCode` **列**、`/decide` 200 体、fleet 终态词不变。
- **`tool_end` 帧四个结算词删**(`settledBy` / `resolution` / `autoDenied` / `approver`),换一条 `gate: {disposition, settlement?, origin?}`;`permission_denied_total` 的标签 `source` → `deniedBy`(仪表盘/告警要改)。
- **`TaskSpec.checkpointStore`**:关断从 `null` 改 `"disabled"`,`null` 被 400 `config.invalid_checkpoint_store` 拒。
- **升级前必做:把待决审批决完或取消**(`GET /v1/approvals` 逐条 `/v1/approvals/:sessionId/decide`)。core 7.6.0 起,升级前铸的待决审批行(无 `origin` 词)在第一次 `/decide` 上会被拒并把那张卡孤儿化(core 7.6.2 补 `reason` 词后改为响亮拒,卡仍需手工取消)。
- **单向迁移面**:7.63.0 写下的 workflow resume journal 行在 7.64.0 下恢复被响亮拒(`workflow.journal_incompatible`,零派发),新起 run 不受影响;历史 `tool_end` 的四个归因词在冷回放里读不出来(core 词表已删,server 不代折)。

### 后台 bash 会话驻留(core 1.269.0,server 1.169.0 起)
- **变更**:后台 `run_in_background` bash **不再随父 run 结束被杀**——改为 session 驻留;终止锚点=
  session 收尾(server 的 E21 DELETE wiring)+ 引擎 hardShutdown 末端全量收割。
- **影响**:如果你的集成依赖「父任务结束 → 后台进程自动死」,现在需要显式走 session 收尾或
  `TaskStop`。SIGKILL 路径不可救(内核直杀,记档)。
- **动作**:审查有无长驻后台 shell 的编排假设;TOC/壳形态退出时靠 SIGHUP→drain 路径收割。

### Agent 工具默认后台化(core 1.272.0,server 1.173.0 起)
- **变更**:挂 background surface 的委派工具(Agent/子代理),**省略 `run_in_background` 参数 = 后台执行**
  (async_launched 即回 + 完成通知);要同步取结果须显式传 `run_in_background: false`。
- **影响**:舰队/集成里「委派后同步等结果」且省略了该参数的调用点,行为从阻塞变异步。
- **动作**:同步依赖的委派调用显式加 `run_in_background: false`。

### Glob `details.numFiles` 语义翻转(core 1.275.0,server 1.176.0 起)
- **变更**:`numFiles` = **截断后**返回数(与 Grep 统一);要总数改用新增的
  `totalMatches` + `countIsComplete`。
- **影响**:只影响直接消费 Glob 工具 `details.numFiles` 当「总数」读的下游(server 本体零消费)。
- **动作**:把 numFiles 当总数读的地方迁 `totalMatches`。

## server 侧兼容性说明

- **HTTP 契约**:`/v1/tasks` body 字段=`objective`(必填)+ 可选 `model`(catalog id)/`scenario`/
  `sandboxImageProfile`(仅 k8s lane)。字段名稳定,新增字段一律 additive。
- **env 旋钮**:全部 ship-dark(缺省=旧行为),半配置一律 fail-loud(不静默降级)——升版不会因为
  没配新 env 而改变现有行为。近期新旋钮:`MEMORY_ENGINE_BACKEND` / `MEMORY_SYNC_*` /
  `WORKFLOW_SIZE_GUIDELINE` / `EXPERIMENTAL_OBSERVER_AGENTS` / sealed-box 托管(boot 自动建本机密钥,
  不改现有 env-NAME 通道)。
- **镜像默认**:k8s 沙箱默认镜像 2026-07-13 起从 code-full(7.75GB)瘦身为 code-node(0.33GB,
  git+node+python);重环境用 per-task `sandboxImageProfile` 按需选。存量部署显式配了
  `K8S_SANDBOX_IMAGE` 的不受影响。
- **沙箱运行时装包源默认海外化(2026-07-13 起)**:`SANDBOX_PKG_SOURCE` 缺省从「不注入(吃镜像烤死的
  CN 源)」翻转为 **`global`**(pip/uv/npm/go/rustup/flutter 等官方源,以 pod/sandbox env 压过镜像内
  默认,存量镜像无需重烤)。**中国大陆部署请显式配 `SANDBOX_PKG_SOURCE=cn`**(恢复国内镜像源);
  `SANDBOX_PKG_SOURCE=none`=完全不注入(2026-07-13 之前的 unset 行为,字节级不变)。这是 env 旋钮
  「缺省=旧行为」惯例的唯一例外(产品面向海外分发,官方源是正确缺省)。
- **内网坐标默认值清理(1.180.0,npm 包卫生)**:`MODEL_GATEWAY_BASEURL` 缺省从内部网关 IP 改为
  `http://127.0.0.1:8000/v1` 占位——**依赖旧缺省的部署必须显式配置**;配了 `OA_ISSUE_TOKEN` 的部署
  现在必须同时给 `OA_ISSUE_BASEURL` 或 `GIT_API_BASEURL`(不再有烤死的内网主机兜底,缺失=启动即错)。
