# OKR 目标管理（okr）

`okr` 是 E10 OKR 目标管理的聚合命令，覆盖**目标分页查询、目标详情、关键成果列表、关键成果保存、目标对齐、目标评论、目标详情页链接、链路判定** 8 类能力，共 16 个 operation。

```text
weaver-work-cli okr --help
weaver-work-cli okr schema
weaver-work-cli --json okr run <operation> --input-json '{"key":"value"}'
```

`schema` 是能力清单的唯一事实来源，包含每个 operation 的 `inputSchema`、`risk`、`requiresConfirmation`。

## 实现位置

| 路径 | 职责 |
| --- | --- |
| `src/shortcuts/okr/index.ts` | 命令注册与 JSON envelope，`--input` / `--input-json` 输入解析 |
| `src/shortcuts/okr/manifest.ts` | `schema` 契约、链路路由描述、禁用边界 |
| `src/shortcuts/okr/host.ts` | E10 会话桥接、请求发号、租户缓存、`datajson` 成功判定 |
| `src/shortcuts/okr/continuation.ts` | 写操作确认链的 HMAC-SHA256 token |
| `src/shortcuts/okr/operations/link.ts` | 链路判定与缓存、eb 表单 id 解析、详情页链接、标准版请求体构造 |
| `src/shortcuts/okr/operations/read.ts` | 读 operation（version.check / eb.form.resolve / detail.link / viewlink / list / get / kr.list / comment.list） |
| `src/shortcuts/okr/operations/write.ts` | 写 operation（create / update / kr.save / align.create 的 prepare→apply） |
| `src/shortcuts/okr/operations/registry.ts` | operation 注册表与去重校验 |
| `src/shortcuts/okr/operations/shared.ts` | 周期归一化、人员 ID 校验、凭证字段拦截、目标结构归一化、固定四列表格 |

## 认证

复用 `src/internal/e10` 的 E10 会话，请求自动携带 `Cookie`、`eteamsid`、`User-Agent`。业务代码不接触认证目录。所有 `datajson` 结构接口的 `header.operator` 由 CLI 自动填充为当前登录用户。

## operation 清单

| operation | 类型 | 用途 |
| --- | --- | --- |
| `okr.version.check` | read | 链路判定（`okr_searchAppver` 的 `ver_name`，租户缓存 1 天） |
| `okr.eb.form.resolve` | read | 解析 ebuilder 目标详情表单 id（仅 ebuilder 链路） |
| `okr.detail.link` | read | 返回当前链路的详情页地址模板 |
| `okr.viewlink` | read | 由目标 ID 生成详情页链接（`path` + 绝对 `url`） |
| `okr.list` | read | 分页查询目标，输出固定四列表格 |
| `okr.get` | read | 目标详情（标准版附带评论条数提示，不输出评论正文） |
| `okr.kr.list` | read | 关键成果分页查询（仅 ebuilder 链路） |
| `okr.comment.list` | read | 目标评论分页查询（仅标准版链路） |
| `okr.create.prepare` / `.apply` | high-risk-write | 新建目标 |
| `okr.update.prepare` / `.apply` | high-risk-write | 编辑目标（全量合并） |
| `okr.kr.save.prepare` / `.apply` | high-risk-write | 关键成果新建/修改（按有无 `kr_id` 区分） |
| `okr.align.create.prepare` / `.apply` | high-risk-write | 添加目标对齐（仅 ebuilder 链路） |

## 双链路路由

OKR 目标在不同租户走两套互不兼容的接口链路，由 `okr.version.check` 读取 `ver_name` 判定：

- `ver_name >= V2` → **e10-ebuilder 链路**（`/api/ebuilder/form/formdata/v2/...`）。
- 其它，或版本接口不可用 → **e10 标准版链路**（`/api/workrelate/goal/...`、`/api/goal/common/comment/...`）。

判定结果按租户隔离缓存 1 天（key 前缀 `okr:version:`）。版本接口不可用时降级为标准版并**不写缓存**，避免把「接口缺失」误固化。判定后严禁跨链路混用接口。

| 能力 | 标准版链路 | ebuilder 链路 |
| --- | --- | --- |
| 目标分页查询 | `POST /api/workrelate/goal/getSearchList` | `POST .../getFormDataList/okr_pageQueryGoal` |
| 目标详情 | `GET /api/workrelate/goal/getObjectDetail/{id}` | `POST .../getFormDataByPk/okr_queryGoal` |
| 关键成果列表 | 随目标列表/详情返回 | `POST .../getFormDataList/okr_pageQueryGoalKeyResults` |
| 关键成果保存 | `POST /api/workrelate/goal/fs/krForm/saveForm` | `.../saveFormData/okr_createKeyResult` / `.../updateFormData/okr_updateKeyResult` |
| 目标评论 | `POST /api/goal/common/comment/commentPage` | 无接口 |
| 目标对齐 | 无接口 | `.../saveFormData/okr_createGoalAlign` |
| 新建 / 编辑目标 | `POST /api/workrelate/goal/create` / `/update` | `.../saveFormData/okr_createGoal` / `.../updateFormData/okr_updateGoal` |

## 详情页链接

- 标准版：`/sp/goal/openDetail?id={goalId}`。
- ebuilder：`/sp/ebdfpage/card/0/{okr目标详情eb表单id}/{goalId}`，其中表单 id 由 `getAppIds`（appTag `weaver-wr-goal-eb`）+ `getObjsByTag`（formTag `uf_wrgm_baseinfo`）解析并按租户长期缓存；已知固定租户 `tqasclatif` 直接内置表单 id。解析失败只让链接降级为「暂不可用」并写入 `warnings`，不影响主操作。

列表表格 `table.markdown` 与写入回执的 `detailUrl` 均为**绝对地址**。

## 写操作确认链

新建/编辑目标、保存关键成果、添加目标对齐全部走 `prepare -> apply`：

1. `prepare` 只归一化、补默认值、查当前数据（编辑时）、生成预览和 continuation，不写入。
2. `apply` 必须带 `confirm=true` 与 prepare 返回的 continuation。
3. continuation 绑定 `baseUrl`/`profile`/`userId`，10 分钟有效；变化触发 `context_mismatch`，字段指纹变化触发 `target_changed`。
4. 网络中断抛 `partial/write_uncertain`，禁止自动重试，先做只读回查。

## 关键业务规则

- **链路专属能力明确报错**：eb 链路列表不支持按名称/状态筛选（`link_filter_unsupported`）；标准版无 `okr.kr.list`/`okr.align.create`；ebuilder 无 `okr.comment.list`（均返回 `policy/link_unsupported`）。
- **新建目标默认草稿**：标准版未传 `status` 时新建默认 `status=0`；标准版编辑必须显式传 `status`（否则 `status_required`）。
- **编辑为全量合并**：`update.prepare` 先查详情再合并，只读字段不回写。
- **ebuilder 新建默认值**：`m_status=1`（进行中）、`progress=0`。
- **部门目标**：标准版要 `scope_data_id`，ebuilder 要 `principal_dept`，缺失在 prepare 阶段阻断。
- **详情不直出评论正文**：`okr.get` 只返回评论总数与提示，正文需用户确认后调 `okr.comment.list`。
- **人员 ID 强制校验**：`principalid`/`partners`/`participants` 必须是人员 ID（数字字符串），传姓名返回 `person_id_invalid` 并指向 weaver-e10-hrm。
- **凭证字段拦截**：业务输入出现 `cookie`/`eteamsid`/`token` 等字段直接返回 `field_forbidden`。

## 调用示例

Windows PowerShell：

```powershell
weaver-work-cli --json okr run okr.list --input-json '{"query_scope":"self","period_type":"3","period_range":"2026-09","page_size":10}'
```

macOS/Linux（bash/zsh）：

```bash
printf '%s\n' '{"query_scope":"self","period_type":"3","period_range":"2026-09","page_size":10}' | weaver-work-cli --json okr run okr.list --input -
```

链路判定与详情：

```text
weaver-work-cli --json okr route
weaver-work-cli --json okr get --id <目标ID>
weaver-work-cli --json okr viewlink --ids <目标ID1>,<目标ID2>
```

## 测试

```text
node --test --import tsx "test/business/okr-*.test.ts"
```

覆盖 manifest/registry 一致性、schema 约束、链路判定与缓存、双链路请求形状与响应归一化、固定四列表格与可点击链接、写操作确认链（含 network → partial/write_uncertain）、人员 ID 与凭证边界、链路专属能力的报错语义，以及 `source-manifest.json` 源资料与产出基线的一致性（产出文件指纹漂移即失败）。
