# 人工事项定义（IssueDefinition）开发

创建人工事项定义并配置 human，支持前置节点用户数组、单人自动分配和多人领取，并说明原有候选主体配置。

IssueDefinition 是 Bundle 拥有的 `ISSUE_DEFINITION` 资源，定义人工事项的输入、输出和处理规则。
**没有满足业务需求的现成定义时，应在自己的业务 Bundle 中创建定义，不需要等待平台提供专用类型。**
`workflow.human_task` 只是一个可能已安装的通用定义，不是 human 节点的必经依赖，也不会由平台自动创建。
看不到它的输出合同，不妨碍开发者为自己的业务建立明确的合同。

## 资源与类型的区别

| 对象                         | 所有者与用途                                                                                                          |
| -------------------------- | --------------------------------------------------------------------------------------------------------------- |
| IssueDefinition            | 属于 Bundle；使用 Owner 返回的 `issueDefinitionId` 标识，发布精确不可变版本，随 Bundle 安装。                                            |
| `inputType` / `outputType` | IssueDefinition 的输入、结果类型；使用平台统一 Type，可内联匿名 `DataType`。                                                          |
| Type Definition            | 工作区独立拥有的可复用命名类型资源，不属于 Bundle，也不进入安装台账；参见[类型定义开发](/development/bundle-development/type-definition-development/)。 |
| human 节点                   | 引用 IssueDefinition 精确版本，映射输入、指定候选主体和结果写入路径，不另写一份输出类型。                                                           |

仅为一个业务事项定义输入输出时，可以直接内联匿名类型，不必先创建独立 Type Definition。
Bundle 版本、IssueDefinition 版本与 Workflow 版本相互独立；更新草稿不会改变任何已发布版本。

## 1. 确认工作区与业务 Bundle

先核对本机命令和当前授权，显式传入目标工作区，避免误用 CLI 默认工作区：

```bash
baijimu --version
baijimu auth status --verify
baijimu bundle object create --help
baijimu bundle object publish --help
baijimu bundle definition list --workspace-id <WORKSPACE_ID> --json
```

创建、修改和发布需要业务 Bundle 所有者或所属工作区 owner/admin 权限。安装基础套件不代表拥有其
作者权限；应选择自己的业务 Bundle。没有 Bundle 时，先按[创建项目与定义](/development/bundle-development/version-and-delivery/)创建并绑定 `BUNDLE` 项目。

下文的 `business.review`、`example-operations` 和版本只用于示例。工作区、事项定义身份、用户 ID 和
实际版本必须从当前授权目录和发布结果取得；不能原样套用。

## 2. 编写输入输出合同

将以下完整资源定义保存为 `issue-definition.json`。例子要求人工提交审核结果和意见；这两个字段是本业务的
选择，平台不预置 `approved`、`decision` 或其他审批字段。

```json
{
  "name": "业务审核",
  "description": "核对业务摘要并提交审核结果",
  "tags": ["review"],
  "inputType": {
    "@type": "DataType",
    "type": "object",
    "properties": {
      "summary": { "@type": "DataType", "type": "string" }
    },
    "required": ["summary"],
    "additionalProperties": false
  },
  "outputType": {
    "@type": "DataType",
    "type": "object",
    "properties": {
      "accepted": { "@type": "DataType", "type": "boolean" },
      "comment": { "@type": "DataType", "type": "string" }
    },
    "required": ["accepted", "comment"],
    "additionalProperties": false
  },
  "processActions": ["manual_process"],
  "enabled": true
}
```

- `name` 必填，最多 200 个字符；`description` 可省略，填写时最多 4000 个字符；文本不得为空或有首尾空白。
- `tags` 和 `processActions` 可省略，默认为空数组；每项最多 100 个字符。处理动作不能代替输出字段定义。
- `inputType`、`outputType` 和可选的 `statusType` 使用同一平台 Type 合同。用于工作流 human 的定义必须声明 `outputType`。
- `enabled` 默认 `true`。正式引用应使用已发布、启用且随目标 Bundle 安装的资源版本。
- 对象身份由 Owner 创建并返回，不放进这个 JSON；也不填写工作区、Bundle ID、资源版本或安装状态。
- 不使用旧的 `inputSchema/outputSchema/statusSchema` 字段；平台 Type 也不是直接填写 JSON Schema。

## 3. 创建并发布资源版本

```bash
baijimu bundle object create <bundle> issue-definition --workspace-id <WORKSPACE_ID> --definition @issue-definition.json --json

baijimu bundle object publish <bundle> issue-definition --workspace-id <WORKSPACE_ID> \
  --object-id <ownerReturnedIssueId> --version <ISSUE_VERSION> --json
```

保存目录返回的 `issueDefinitionId`、所属 `bundleId` 和发布的精确 `semanticVersion`，供 human 节点的 `issueDefinition` 引用。
继续修改同一业务定义时使用完整更新，再发布一个符合兼容性语义的新版本：

```bash
baijimu bundle object update <bundle> issue-definition --workspace-id <WORKSPACE_ID> \
  --object-id <ownerReturnedIssueId> --definition @issue-definition.json --json
```

版本必须是严格 SemVer。不要把示例版本当作实际发布授权；发布前分别确定 IssueDefinition、Workflow 和
Bundle 的版本及兼容性影响。不能覆盖旧版本，也不能把资源发布成功当作安装完成。

使用 `bundle manifest catalog <bundle> issue-definition` 查询目录，`bundle object get` 读取 Owner 草稿。
定义 JSON 保存在业务源码中，连同精确版本一起评审；安装台账不代替定义正文。

## 4. 配置 human 节点

下例与上面的输入输出合同配套。替换目录返回的事项定义身份、版本和候选用户后，将节点加入完整 Workflow：

```json
{
  "type": "human",
  "id": "review",
  "name": "人工审核",
  "input": {
    "summary": "{{input.summary}}"
  },
  "assignment": {
    "mode": "candidates",
    "candidates": [
      {
        "identityType": "user",
        "identityId": "{{input.reviewerUserId}}"
      }
    ]
  },
  "result": {
    "path": "reviewResult"
  },
  "executionPolicy": {
    "completionTimeoutSecs": 86400
  },
  "issueDefinition": {
    "bundleId": "example-operations",
    "issueDefinitionId": "business.review",
    "semanticVersion": "1.0.0"
  }
}
```

工作流输入需要声明 `summary` 和 `reviewerUserId`。`reviewerUserId` 必须来自当前工作区有效用户目录，
不能将用户姓名、设备 ID 或其他工作区用户 ID 当作候选用户。

- `input` 显式映射到事项的 `inputPayload`；不会隐式发送整个 Workflow 输入或上下文。
- `assignment.mode: "candidates"` 必须有非空候选列表。`identityType` 支持 `user / department / group / role`；角色填写稳定 `roleKey`，参见[角色模板开发](/development/bundle-development/role-template-development/)。
- 明确用户列表校验、去重后，单人直接分配到 `Issue.assignee`，多人由其中一人领取。含角色、部门或群组时始终是候选范围；领取或转交后的唯一处理者是 `Issue.assignee`。
- 对工作区成员开放领取时显式使用 `assignment: {"mode":"workspace_members"}`，不能用空候选列表表达。
- `result.path` 只决定结果位置，例子写入 `context.reviewResult`；输出类型始终归 IssueDefinition，不配置 `result.type` 或节点级 `outputSchema`。
- `completionTimeoutSecs` 是人工等待期限，必须大于零；它不是 HTTP 请求超时，省略表示不配置期限。

人工完成时提交与 `outputType` 一致的结果，例如 `{"accepted":true,"comment":"核对通过"}`。
Issue 校验通过后，由平台回传结果并推进 human 节点；下游据 `context.reviewResult.accepted` 判断业务分支。
`accepted:false` 仍是合法业务结果，不自动等于 Workflow 执行失败。

### 从前置节点读取候选人

前置节点若使用 `outputPath: "ownerLookup"`，其结果写入 `context.ownerLookup`。
例如实际输出为以下对象，`reviewerUserId` 的值必须是当前工作区有效用户目录中的真实 ID：

```json
{
  "summary": "待审核的报告摘要",
  "reviewerUserId": "<VALID_WORKSPACE_USER_ID>"
}
```

把上面完整 human 节点中的 `input` 和 `assignment` 替换为以下片段，保留原有的
`issueDefinition`、`result` 和执行策略。连线必须让前置节点先完成，再执行 human：

```json
{
  "input": {
    "summary": "{{context.ownerLookup.summary}}"
  },
  "assignment": {
    "mode": "candidates",
    "candidates": [
      {
        "identityType": "user",
        "identityId": "{{context.ownerLookup.reviewerUserId}}"
      }
    ]
  }
}
```

路径必须按实际节点输出合同填写；若返回值还有外层 `data`，应引用真实的嵌套路径，不能只凭节点 ID
猜输出位置。`identityId` 引用的是一个主体 ID，不是用户对象、用户姓名或数组。运行时解析为空或用户身份无效会报错，不会自动开放给全工作区；相同类型与 ID 的重复候选主体会去重。

### 动态用户列表：单人分配，多人领取

需要使用前置节点返回的任意长度用户列表时，继续使用 `assignment.mode: "candidates"`，将整组引用直接放在 `candidates`。
前置节点应返回真实的当前工作区用户 ID **字符串数组**，例如：

```json
{
  "summary": "待审核的报告摘要",
  "userIds": ["<VALID_WORKSPACE_USER_ID>", "<ANOTHER_VALID_WORKSPACE_USER_ID>"]
}
```

假设该节点的 `outputPath` 是 `ownerLookup`，完整 human 节点可以写成：

```json
{
  "id": "reviewReport",
  "type": "human",
  "name": "审核报告",
  "issueDefinition": {
    "bundleId": "<BUNDLE_ID>",
    "issueDefinitionId": "<ISSUE_DEFINITION_ID>",
    "semanticVersion": "1.0.0"
  },
  "input": {"summary": "{{context.ownerLookup.summary}}"},
  "assignment": {
    "mode": "candidates",
    "candidates": "{{context.ownerLookup.userIds}}"
  },
  "result": {"path": "reviewResult"}
}
```

替换示例中的身份占位符；事项定义的 `inputType` / `outputType` 与本页定义保持一致。
Bundle 保存的是运行时数据引用，实际员工来自执行时所在工作区的用户目录。
若名单由 Event Trigger 传入 Workflow，改用 `"candidates": "{{input.userIds}}"`，并在 Workflow `inputSchema`
声明对应的字符串数组。也可直接提供真实用户 ID 的非空 JSON 字符串数组；跨工作区复用的 Bundle 应使用引用。

平台使用固定规则，无需配置“直接分配”或“领取”策略：

| 校验并去重后的名单             | 行为                                        |
| --------------------- | ----------------------------------------- |
| 1 人                   | 创建 Issue 时直接设置 `Issue.assignee`，该用户无需先领取。 |
| 多人                    | 创建一个候选池；其中一人领取后成为唯一处理者，其他候选人不能再领取。        |
| 空名单、缺失路径、`null` 或错误类型 | human 执行失败，不创建开放事项。                       |
| 任一用户不属于当前工作区          | 整组失败；不会丢弃无效用户后继续分配。                       |

字符串 ID 去掉首尾空白后必须是标准正整数格式；重复 ID 只保留一份。`["7","7"]` 的有效人数为 1，
适用单人自动分配。数值数组、用户对象数组、姓名数组和 JSON 字符串化数组均不接受；前置节点需要先输出
字符串 ID 数组。这里的数值仅说明格式，实际配置必须使用目录返回的身份。

`candidates` 整组引用必须完整指向 `input` 或 `context` 中的数组；不支持拼接、隐式取第一人或在此处执行 `=CEL`。
多候选不代表会签，也不会创建多份事项。human 重试复用原 Issue，不覆盖已领取或转派后的负责人。

### 一个候选入口，统一分配规则

`assignment.candidates` 接受三种形式：

| 形式          | 示例                                                  | 用途                                                               |
| ----------- | --------------------------------------------------- | ---------------------------------------------------------------- |
| 用户 ID 字符串数组 | `["7", "8"]`                                        | 已知的当前工作区用户列表。                                                    |
| 完整数组引用      | `"{{context.ownerLookup.userIds}}"`                 | 读取前置节点返回的用户 ID 字符串数组，也可引用 `input`。                               |
| 候选主体对象数组    | `[{"identityType":"role","identityId":"reviewer"}]` | 指定 `user / department / group / role` 主体；每项 `identityId` 可引用单个值。 |

前两种形式解析为 `user` 候选主体，与直接填写用户主体对象使用相同的校验、去重和分配规则。
示例数字只说明格式，必须替换为目录返回的真实用户身份。字符串数组与主体对象不能混在同一个数组中。
整组引用的结果必须是用户 ID 字符串数组，不能是用户对象数组或候选主体对象数组。

纯用户候选去重后只有一人时直接分配；多人时由其中一人领取。如果包含角色、部门或群组，保留候选范围，
即使只配置一个角色，也不会将其当成一个用户直接分配。混合用户与角色的主体对象数组同样需要领取，
其中显式用户仍必须通过工作区成员校验。`workspace_members` 继续表示显式开放工作区领取。

不再使用独立的 `mode: "users"` / `assignment.userIds`；也不添加 `mode: "direct"`、分配策略开关或节点级
`assignee`。不要把用户数组放进某一项 `identityId`，该字段仍只表示一个主体 ID。

### 从旧配置升级

旧 `users` 配置应把 `mode` 改为 `candidates`，将 `assignment.userIds` 的原值移到
`assignment.candidates`，然后发布新的 Workflow 版本。该收敛合同拒绝旧 `users` 模式，平台升级前需核对
已发布资源、运行时投影和执行快照；不可变版本不能原地改写。

原有纯用户 `candidates` 也使用上述人数规则：新建事项的单一用户无需领取。已经创建的 Issue 不会因此
重新分配，重试继续保留现有负责人和领取状态。需要维持旧行为的在途流程应按旧合同完成后再切换，
不能只更新文档或平台镜像而跳过存量检查。

事件绑定、输入映射和两种表达式语法见 [Event Trigger 教程](/development/bundle-development/event-trigger-development/)。
不要用公开 `wait.signal` 接口绕过人工事项处理。

Workflow 按[工作流开发](/features/workflows/)创建、更新并发布精确版本。当前 human 使用上述
`issueDefinition/input/assignment/result` 字段；历史 Workflow Schema 5.0.0 的 human 示例
采用旧字段，不能用它校验或生成本页的新节点。历史合同保持原样，遇到 CLI 字段不兼容时应先核对版本，不能混用两套合同。

## 5. 纳入 Bundle，发布并安装

资源版本和 Workflow 版本均发布后，将它们加入绑定项目的 `baijimu.bundle.json`。
以下命令会在线预检并原子更新本地作者文件：

```bash
baijimu bundle manifest include <bundle> issue-definition --workspace-id <WORKSPACE_ID> \
  --file baijimu.bundle.json --object-id <ownerReturnedIssueId> --version <ISSUE_VERSION>
baijimu bundle manifest include <bundle> workflow --workspace-id <WORKSPACE_ID> \
  --file baijimu.bundle.json --object-id <ownerReturnedWorkflowId> --version <WORKFLOW_VERSION>
baijimu bundle manifest validate @baijimu.bundle.json
```

`definition.issueDefinitions` 和 `definition.workflows` 分别保存领域对象与精确版本；同一对象重新纳入时更新版本。
跨 Bundle 引用必须通过已声明依赖，不能改挂到当前 Bundle。

按项目分支策略提交并推送源码，再从该完整 Git 提交创建 Bundle 版本：

```bash
baijimu bundle version create <bundle> --workspace-id <WORKSPACE_ID> \
  --version <BUNDLE_VERSION> --git-commit-id <40-character-commit> --json
```

使用返回的 Bundle 版本记录 ID 安装；已经安装时执行 upgrade：

```bash
baijimu bundle install <bundle> --workspace-id <TARGET_WORKSPACE_ID> \
  --version-id <BUNDLE_VERSION_ID> --json
baijimu bundle upgrade <bundle> --workspace-id <TARGET_WORKSPACE_ID> \
  --version-id <BUNDLE_VERSION_ID> --json
baijimu bundle resources <bundle> --workspace-id <TARGET_WORKSPACE_ID> --json
```

install 与 upgrade 按当前安装状态二选一。所属工作区可以走 `OWNER_ONLY` 直接安装，不要求先上公共市场；
跨工作区按[分发与交付](/development/bundle-development/version-and-delivery/)授权。若 CLI 要求确认未验证版本或
跨 Bundle 方法权限，应先核对安装预览，再按当前命令帮助确认。

## 6. 验证结果和定位阻塞

1. Bundle 安装记录为目标版本，台账包含预期 `ISSUE_DEFINITION` 和 `WORKFLOW` 的精确版本及成功状态。
2. 启动测试 Workflow，确认人工事项收到显式映射的输入；`users` 分别验证单人无需领取、多人只允许一人领取、重复 ID 去重和空/无效名单失败。原有 `candidates` 仍按领取流程验证。
3. 提交符合合同的结果，确认 human 完成，下游收到预期字段；用缺失必填字段或错误类型的结果确认校验生效。
4. 验证业务拒绝分支和配置的人工等待超时；不能只以 Bundle 版本创建成功作为流程验收。

| 现象                                          | 应做的判断                                                  |
| ------------------------------------------- | ------------------------------------------------------ |
| 没有合适的 IssueDefinition                       | 在自己的业务 Bundle 中创建，不是等待平台补通用资源。                         |
| 台账有 `workflow.human_task`，但 `detailJson` 为空 | 台账只证明资源版本及安装状态，不能推断它没有输出类型，也不能推断它满足业务要求。可以创建自有定义。      |
| 创建或发布失败                                     | 保留 CLI 版本、脱敏命令和原始错误，区分权限、定义字段、类型或发布问题；不能仅凭空台账断言不支持创建。  |
| 草稿已创建、资源已发布，运行时仍找不到                         | 检查 Manifest 是否引用、Bundle 是否创建版本并安装、human 是否引用同一精确版本。    |
| 结果提交失败                                      | 对照所引用版本的 `outputType` 检查字段、必填性和类型，不在 human 节点另加一份类型兜底。 |
