Files
qinglong/docs/adr/ADR-0094-versioned-trigger-definition-and-pinned-task-binding.md
T

63 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0094:版本化 Trigger 与固定任务修订绑定
- 状态:Accepted(领域契约、本机 SQLite v16、Repository 与 ADR-0098 adoption 共同事务已实现;Scheduler、Run admission 待完成)
- 日期:2026-07-22
- 关联 RFCQL-RFC-0001 D-03、D-04、D-08、D-17、D-23、D-62、D-70、D-88、D-90、D-91、D-92、D-93
- 关联 ADRADR-0022、ADR-0023、ADR-0063、ADR-0087、ADR-0088、ADR-0089、ADR-0091、ADR-0092、ADR-0093
## 背景
TaskDefinition、context recipe 与 local execution revision 已经具备不可变修订、独立摘要和同事务发布,但 Trigger 仍只是 RFC 中的概念联合类型。若 Scheduler 直接读取可变 Crontab 或 Task head,它会在触发时重新解释命令、时区和任务版本,历史 Run 也无法证明由哪个 Trigger revision 与 TaskDefinition revision 创建。
Trigger 还必须同时适应低性能路由器和集群节点:本机存储不能增加 watcher、timer、第二连接或新部署 package,列表与语义 provider 必须有硬上限;未来 PostgreSQL 对等实现又需要明确的 revision、CAS、摘要和任务引用契约,而不能复制 SQLite 偶然行为。
## 决策
### 1. Trigger 是 head 与 immutable revision,不是 Scheduler 配置缓存
新增 `TriggerRecord``TriggerSource``TriggerRepository`。head 只保存 `projectId/triggerId/taskId/currentRevision` 和生命周期时间;每次修改追加 immutable revision,并使用 `expectedRevision` CAS。`mutationId` 全局唯一,相同 mutation 只接受完全一致的重放。
Trigger identity 创建后不能改绑另一个 Task。每个 Trigger revision 固定保存 `taskId``taskRevision``taskContentDigest`;写入与重放都会完整读取并重算对应 TaskDefinition revision。启用 Trigger 不能引用禁用任务,禁用 Trigger 可以保留指向禁用历史任务的可审计关系。
### 2. TriggerSpec 使用版本化 envelope 与冻结语义 registry
TriggerSpec 采用 `{schema, config}` envelope。写入 authority 由 132 个 exact descriptor 一次性构造并冻结,禁止 wildcard、运行期注册、目录扫描和覆盖 `qinglong/` 内建 namespace。历史读取只验证 envelope 与 Trigger content digest,所以移除扩展 provider 不会让历史失读;再次写入仍必须具有当前受信 provider。
首个内建 `qinglong/cron@v1` 只接受五或六个有界 cron field,并要求显式有效 timezone 和 `skip | fire_once` misfire policy。空白被规范化、timezone 使用 Node ICU 规范名持久化;`@daily` 等 macro、隐式主机时区和无界补跑不属于 v1,Legacy adoption 必须逐项诊断而不能猜测。
### 3. Trigger revision 使用独立、域隔离的内容摘要
`contentDigest``qinglong.trigger-definition.v1\0` 域下的 SHA-256,覆盖 Project、Trigger、revision、固定任务 identity/digest、canonical spec 与 enabled,排除 mutation identity 和首写时间。Repository 每次读取都重算摘要,格式正确但语义不匹配的持久化内容同样失败关闭。
### 4. SQLite v16 增加两张正式表,不增加 package 或常驻资源
`0031-trigger-definitions` 创建 `QingLong3Triggers``QingLong3TriggerRevisions`,以 FK 固定 Project、Task head、Trigger identity 与 TaskDefinition revision,并提供 mutation、enabled scan 和 task history 索引。`0032-capability-v16` 仅在前者成功后加入 `trigger_definition=1`,把本机契约推进为 32 条 reviewed migration、capability v16、28 张 owned table。
`LocalSqliteTriggerRepository` 复用唯一的 `LocalSqliteOperationAuthority` 和同一 connection;单次 append 使用既有 `BEGIN IMMEDIATE`,稳定列表最多 256 条。该能力留在 `runtime-core/trigger` subpath 与 `local-sqlite` 内部文件中,不新增 package、sidecar、timer 或 watcher。
### 5. 本 ADR 本身不开放 Scheduler、Run 或 adoption mutation
本 ADR 的 Trigger Repository 只建立正式事实与写入边界。ADR-0095 至 ADR-0098 后续补齐 Legacy Crontab 的有界逐项诊断、review authorization、Policy/audit 与 TaskDefinition、execution facts、Trigger 的同事务 adoption publisher。Trigger 回调幂等键、due-state、misfire 计算、Run admission 和 PostgreSQL 对等 adapter 仍未完成;已发布 current head 仍不得被解释为生产 Scheduler 已接管。
## 被否决的替代方案
1. **Trigger 永远引用 Task current head**:触发时语义可漂移,历史 Run 不可解释,拒绝。
2. **只保存 task revision number,不保存 task digest**:不能证明引用内容,拒绝。
3. **把 cron expression 直接作为 Trigger 类型**:未来语义升级和插件 Trigger 无稳定版本边界,拒绝。
4. **沿用主机默认 timezone**:路由器迁移、容器配置或集群节点差异会改变触发时间,拒绝。
5. **v1 自动兼容所有 crontab macro 和方言**:会把无法证明的 2.x 行为伪装成无损迁移,拒绝。
6. **为 Trigger 新拆 package 或启动独立 Scheduler sidecar**:尚无独立部署/依赖边界,并扩大 edge 常驻成本,拒绝。
7. **建表后立即接管 Crontab**:缺少诊断、共同事务、Run admission 与回滚证据,拒绝。
## 验收证据
1. runtime-core 测试覆盖 cron canonicalization、显式 timezone/misfire、macro 拒绝、extension namespace、摘要损坏与结构预算。
2. local-sqlite contract tests 覆盖创建/更新/精确重放、Task revision pin、digest drift、disabled target、stale CAS、禁止 task rebind、竞争单 winner 与稳定分页。
3. 持久化损坏测试覆盖 Trigger digest 和 pinned TaskDefinition digest;普通 replay 不会修补坏事实。
4. typed schema lockstep 覆盖两张新表的列、索引、CHECK 与 FKfresh migration/readiness 推进到 v16。
5. runtime import、edge import、依赖方向、全包测试和六种 production artifact 门禁继续作为回归条件。
## 后续约束
ADR-0095 至 ADR-0098 已完成 Legacy Crontab 到 `qinglong/command@v1 + qinglong/cron@v1` 的只读分类、显式裁决与 TaskDefinition、execution facts、Trigger 同成同败的 adoption publisher。Scheduler/Run admission 仍必须另外固定 Trigger revision、scheduled time 与 execution revision,不能把本 ADR 的 Repository 或 adoption ledger 误当作已完成接管。