# Timer 定义、发布与运行态

Bundle Timer 的机器定义、Manifest 引用、安装收敛和执行记录合同。

Timer 是 Bundle 拥有的版本化资源。作者先维护一个以 `bundleId + timerKey` 标识的草稿，
再发布不可变的精确 SemVer，最后在 Bundle Manifest 中引用该版本。安装方不直接创建平台
Timer；Runtime 根据已安装 Bundle 的目标资源集合统一物化和收敛。

## 机器可读定义

当前公开定义合同是 `1.0.0`：

- [JSON Schema](https://docs.baijimu.com/contracts/timer-definition/1.0.0/schema.json)
- [CRON 完整示例](https://docs.baijimu.com/contracts/timer-definition/1.0.0/examples/cron.json)
- [EVERY 完整示例](https://docs.baijimu.com/contracts/timer-definition/1.0.0/examples/every.json)
- [AT 完整示例](https://docs.baijimu.com/contracts/timer-definition/1.0.0/examples/at.json)

`timerKey` 不在 Definition JSON 内；它是 Bundle 内稳定且不可修改的资源 Key，由命令参数提供。
Definition 只包含下列字段，未知字段会被拒绝：

```json
{
  "name": "每日门店运营汇总",
  "description": "每天 09:00 生成并分发门店运营日报",
  "target": {
    "businessId": "flower-operation.report-service",
    "method": "generateDailyReport",
    "params": {
      "scope": "ALL_STORES"
    }
  },
  "schedule": {
    "type": "CRON",
    "expression": "0 0 9 * * * *",
    "timezone": "Asia/Shanghai"
  },
  "enabled": true
}
```

字段规则：

- `name` 为 1–200 个 Unicode 字符；`description` 可省略或为 `null`，字符串时为 1–2000 个字符；两者不能有首尾空白。
- `target.businessId` 是安装后要调用的模块完整 `businessId`，`target.method` 是该模块公开的方法；`params` 可为任意 JSON 值，也可省略或为 `null`。
- `CRON` 使用 cron crate 的 7 段表达式（秒、分、时、日、月、周、年），并要求合法 IANA 时区。
- `EVERY.everyMs` 是毫秒整数，最小值为 `300000`。
- `AT.at` 是 RFC 3339 `date-time`，只执行一次；完成后保持禁用，不会在下一次相同目标态收敛时重复执行。
- 所有循环型 Timer 的相邻触发间隔不得小于 5 分钟。JSON Schema 能表达 `EVERY` 的下限；CRON 的间隔由 Definition 服务和 Runtime 再做语义校验。
- `enabled: false` 表示资源仍随 Bundle 安装和升级物化，但运行态保持禁用；它不等价于从 Manifest 删除资源。

## 草稿和不可变版本

以下命令中的 `<workspace>` 接受工作区 ID 或精确名称，`<bundle>` 接受 `bundleId` 或精确名称：

```bash
baijimu bundle timer list <workspace> <bundle> --json
baijimu bundle timer get <workspace> <bundle> <timerKey> --json
baijimu bundle timer create <workspace> <bundle> <timerKey> --definition @timer.json --json
baijimu bundle timer update <workspace> <bundle> <timerKey> --definition @timer.json --json
baijimu bundle timer publish <workspace> <bundle> <timerKey> <semanticVersion> --json
```

`list` 和 `get` 返回当前草稿，而不是已发布版本的可变视图。成功响应的 `data` 结构分别是
`TimerDefinitionView[]` 和 `TimerDefinitionView`：

```json
{
  "bundleId": "flower-operation",
  "timerKey": "daily-report-trigger",
  "workspaceId": 1430,
  "definition": {
    "name": "每日门店运营汇总",
    "description": null,
    "target": {
      "businessId": "flower-operation.report-service",
      "method": "generateDailyReport",
      "params": null
    },
    "schedule": {
      "type": "EVERY",
      "everyMs": 86400000
    },
    "enabled": true
  }
}
```

两个查询都校验当前用户对 Bundle 所属工作区的读取权限。发布后，该
`timerKey + semanticVersion` 的内容不可修改；继续编辑草稿不会改变已发布版本。

## 加入 Bundle Manifest

Bundle Manifest schema `8.0.0` 使用统一 `resources` 数组。一个可安装的 Timer 引用是：

```json
{
  "schemaVersion": "8.0.0",
  "resources": [
    {
      "resourceType": "TIMER",
      "resourceKey": "daily-report-trigger",
      "semanticVersion": "1.0.0"
    }
  ],
  "dependencies": []
}
```

用 CLI 离线修改和校验 Manifest：

```bash
baijimu bundle manifest timer list @bundle.json --json
baijimu bundle manifest timer add @bundle.json --timer-key daily-report-trigger --semantic-version 1.0.0 --json
baijimu bundle manifest timer remove @bundle.json --timer-key daily-report-trigger --json
baijimu bundle manifest validate @bundle.json --json
```

`resourceType` 必须是大写 `TIMER`，`semanticVersion` 必须是完整、精确的严格 SemVer。
Manifest 不携带 Definition、工作区 ID、数据库 ID、运行态 Timer ID 或安装实例信息。

## 安装、升级和卸载收敛

Runtime 以每个 Application Runtime 中全部有效 Bundle 安装账本为权威目标集合，并使用完整
`ResourceLocator = environmentKey/bundleId/TIMER/resourceKey` 区分资源：

| Bundle 操作        | Timer 目标态                                           |
| ---------------- | --------------------------------------------------- |
| 安装               | 解析 Manifest 中的精确 Timer 版本，物化目标调用、调度和 Bundle 来源身份    |
| 升级               | 按新旧资源集合增加、更新或移除 Timer；相同 `ResourceLocator` 切换到新精确版本 |
| 从新 Manifest 删除   | 从目标集合移除，停止调度并软删除运行态任务                               |
| 卸载 Bundle        | 移除该安装实例拥有的 Timer，停止调度并软删除运行态任务                      |
| `enabled: false` | 保留资源和来源身份，但运行态不调度                                   |
| 整个 Runtime 删除    | 用空目标集合执行 Runtime teardown                           |

每次安装、升级或卸载在提交 Bundle 最终状态前先执行 Timer replace 收敛。收敛失败会使当前
Bundle Operation 失败，不会把安装记录错误地提交为已完成。运行态任务保存来源 Bundle、版本、
安装实例和 `ResourceLocator`；这些字段由平台生成，作者不能在 Definition 或 Manifest 中填写。

## 查询实际状态和执行记录

先取得目标工作区的 `applicationRuntimeId`，再查询当前已物化状态：

```bash
baijimu runtime app timer status <workspaceId> <applicationRuntimeId> --json
```

成功响应的 `data` 包含 `appId`、`version`、`versionNumber`、`apiGatewayUrl`、
`enabledTimerCount` 和 `timers`。每个运行态 Timer 都包含 `id`、`resourceLocator`、
`resourceKey`、`resourceVersion`、`enabled`、调度字段以及
`sourceBundleId`、`sourceBundleVersionId`、`sourceBundleInstallId`。排障时应从这里复制
完整 `resourceLocator`，不要只猜 `timerKey`。

查询执行记录：

```bash
baijimu runtime app timer executions <workspaceId> <applicationRuntimeId> <timerId> \
  --resource-locator <environmentKey/bundleId/TIMER/resourceKey> \
  --status success --limit 50 --offset 0 --json
```

`resourceLocator` 是推荐的精确身份。为兼容既有运行态可以省略；如果同一 Runtime 中存在多个
同名 `timerId`，则必须提供 `resourceLocator`，或在旧运行态中使用 `--business-id` 消歧。
`status` 可选值为 `pending`、`running`、`success`、`failed`、`timeout`、`cancelled`。
`limit` 取值为 1–1000，默认 50；`offset` 从 0 开始。
响应 `data` 包含 `taskId`、`resourceLocator`、`timerId`、`count`、`limit`、`offset` 和
`executions`。执行项沿用 Timer Runtime 的 `snake_case` 审计字段，例如 `execution_id`、
`attempt_number`、`scheduled_at`、`started_at`、`completed_at`、`response_status_code`、
`duration_ms`、`error_type` 和 `error_message`。

两个运行态查询都校验当前用户对 `applicationRuntimeId` 的访问权限。Timer 被 Manifest 删除或
Bundle 卸载后不再出现在活动配置中；已产生的执行审计记录按平台保留策略保存，但活动 Timer 查询
不承诺在资源移除后仍能用原 `timerId` 继续定位它。
