Files
qinglong/docs/adr/ADR-0258-owner-authenticated-local-trigger-management-and-current-task-fence.md
T

114 lines
8.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-0258Owner-authenticated Local Trigger Management 与 Current Task Fence
- 状态:Accepted
- 日期:2026-08-01
- 关联 RFCQL-RFC-0001 D-85、D-175、D-207、D-239、D-241
- 关联 ADRADR-0074、ADR-0085、ADR-0089、ADR-0091、ADR-0092、ADR-0256、ADR-0257
## 背景
ADR-0256 已让 fresh Edge/Standalone 能通过受支持入口创建 TaskDefinition,但部署者仍不能
创建、更新、停用、启用或查询 TriggerFresh Setup 因此停在 Task 而不能完成
Task→Trigger→Run。既有 scheduler 还只检查 Trigger current headTask 更新或停用后,固定到
旧 Task revision 的已启用 Trigger 仍可能继续产生 Run。这会绕过部署者对 Task current head
作出的撤权决定。
该缺口不能通过新增常驻管理服务、每 Trigger timer 或新 workspace package 解决。路由设备的
空闲资源边界必须不变;Cluster 也不能复用本机 SQLite、Owner credential file 或 command file
来绕过 PostgreSQL/RBAC 与多副本围栏。
## 决策
1. 在既有 `@qinglong/local-owner-cli` 增加短生命周期 binary `ql3-trigger`,只接受
`run --command-file /absolute/private-command.json`。command file 必须位于 deployment root
内、由当前 UID 持有、模式 `0600` 且不是 symlink。
2. v1 只开放 `trigger.put|trigger.inspect|trigger.list``trigger.put`
`expectedRevision=null` 创建,以 current revision 更新;enable/disable 都追加 immutable
Trigger revision,不提供原地修改或 delete。production registry 只接受
`qinglong/cron@v1`
3. 创建或更新必须绑定 exact `taskId``taskRevision``taskContentDigest`。启用的 Trigger
只能绑定同 Project 下当前、内容摘要一致且 enabled 的 Task head。Task 更新、停用或重新启用
后,旧 Trigger 失败关闭,必须由部署者显式提交新 Trigger revision 完成 repin。
4. 为避免撤权死锁,`enabled=false` 的新 Trigger revision 可以继续绑定历史 Task revision
因此 Task 已变化或停用后,operator 仍能明确停用 stale Trigger。再次启用时必须 repin 当前
Task head。
5. scheduler 在两个位置复验 Task current fence:候选发现必须联结 current Task head;最终
创建 Run 的事务必须再次验证 Task current revision、content digest 和 enabled。扫描后发生
Task race 时不得创建 Run,不能只依赖 Trigger 写入时的检查。
6. 每次命令建立短生命周期 SQLite connection,复用 Owner pepper/credential authentication
与 Project Policy。put 要求 `task.update`inspect/list 要求 `task.read`principal 必须是
strong User。沿用 Task 权限是因为 Trigger 直接改变该 Task 的调度能力,v1 不另造含义重叠的
permission。
7. 写事务在同一 `BEGIN IMMEDIATE` 内重新验证 credential/Identity/pepper、Project 与
RoleBinding fence,并原子提交 allowed `trigger.create|trigger.update` audit、Trigger head、
immutable revision、mutation replay 和本机 schedule reset。任何认证、Policy、Task、revision、
audit 或 replay 漂移都整体回滚。
8. `mutationId` 同时是 allowed audit event IDrequest、actor、Policy fence、完整 Trigger
mutation 与 immutable `occurredAtMs` 都属于 exact replay。结果未知时只能原样重放同一个
command file。
9. inspect/list 只返回低敏 current-head 摘要:Project/Trigger、revision、Task binding、schema、
enabled、content digest 与时间;不回显 cron expression、timezone、misfire policy、credential、
pepper、数据库路径或 command-file 路径。list 使用最多 256 条的稳定 `triggerId` keyset。
10. 实现只增加既有 `runtime-core``local-sqlite``local-admin``local-owner-cli` 的 source
module 与 subpath。workspace 保持 19 个 package,不新增 production dependency、migration、
daemon、timer、watcher、listener、cache、Pool、连接或端口。只有 operator 执行命令时产生
一个短进程和一个 SQLite connection。
11. `ql3-trigger` 只支持 Edge/Standalone。Cluster 必须使用后续 PostgreSQL 管理 repository、
集群 credential/RBAC、强认证 transport、durable audit 与多副本 fence;不得把本机文件挂进
Pod 伪装集群管理入口。
## 失败与恢复
- Task 已更新或停用:scheduler 自动停止 admission;先 inspect Task current head,再明确停用
stale Trigger,或以新 mutation 将 Trigger repin 到已启用的 current Task
- revision conflictinspect Trigger current revision 后人工确认,再生成新 command identity
- 认证、授权、Task fence、semantic 或 audit conflict:失败关闭,不创建半个 revision 或 Run
- COMMIT 结果未知:逐字重放原 `0600` command file`existing` 表示相同 durable semantic 已提交;
- 禁止直接修改 Trigger、schedule 或 audit 表来“恢复”,历史 revision 必须保留解释既有 Run。
## 被拒绝的替代方案
1. **新增 `trigger-admin` package**:没有独立交付、依赖或权限域,会重现单文件/小包碎片化,拒绝。
2. **让旧 Trigger 永久执行 pinned Task**Task current head 的停用/替换将不能撤销后续 admission
拒绝;历史 pin 只用于解释已创建 Run。
3. **只在 Trigger 写入时检查 Task**:扫描与 commit 之间仍有竞态,拒绝;最终 Run admission 必须
重验 current Task fence。
4. **Task 变化时自动改写 Trigger pin**:会在没有 operator intent、audit 和 replay identity 时改变
执行语义,拒绝;恢复必须显式 repin。
5. **禁止 stale Trigger 的任何更新**:Task 先变化后将无法停用旧 Trigger,形成撤权死锁,拒绝;
只允许追加 disabled revision,重新启用仍须 current pin。
6. **每 Trigger 建 timer 或管理 daemon**:扩大路由设备空闲 RSS、唤醒与连接数,拒绝;复用唯一
application scheduler cadence。
## 验证
- `ql3-trigger` 真实 SQLite 纵切面 5/5create、exact replay、disable、inspect/list、operator
allow、viewer deny、credential/Policy race、Task fence、audit drift、私有 command file 与低敏输出;
- SQLite Trigger/schedule 定向 7/7Task 更新或停用后候选为空,扫描后 stale commit 不生成 Run
stale Trigger 可停用,Task 重新启用并显式 repin 后才恢复候选;
- fresh 产品链 1/1`ql3-task` 创建 Task、`ql3-trigger` 创建 cron Trigger、production
`LocalSchedulerCoordinator` 生成唯一 queued RunTask 停用后不再产生第二个 Run;
- 19 个 QL3 package 清理、顺序构建与完整测试零失败;Owner CLI 88/88backend 958 pass/
2 条件 skip/0 failTrigger 精确依赖边界加入后定向审计 41/41,全仓 19-importer audit
`findings=[]/compatible=true`Edge import 和 local image inventory 均 compatible
- 十档 exact offline artifact 全绿:storage 322 files/3,491,202--3,491,250 bytesadopted
361/4,064,367--4,064,451application 415/4,548,022--4,548,166AI-only
345/4,082,310--4,082,370application+AI 438/5,139,202--5,139,358;最大 import RSS delta
20,480,000 bytes,全部低于既有分层门;
- PostgreSQL 18.4 arm64 physical HA 重新完成 `remote_apply`、timeline 1→2、旧主先 fencing、
`pg_rewind` 只读同步重入、两个 fresh control replicas、scheduler exactly-once 与全部业务门,
`gates.passed=true`;测试容器、网络和卷零残留。它只证明 Cluster 基线未回归,不把本机
`ql3-trigger` 宣称为 Cluster 管理入口;
- 实现没有新增 workspace package、第三方依赖、migration 或常驻资源;GitNexus 刷新后为
37,577 nodes/85,540 edges/280 flows,相对 `develop` 与整个工作树的 change detection 均为
LOW、0 affected process。
## 后续约束
- 下一步为 Cluster 提供 PostgreSQL/RBAC 对等的 Task/Trigger 管理 transport,不能共享本机
credential 或 SQLite authority
- HTTP/UI 只能调用同一 administration service 语义,不能复制 Policy、Task current fence、
audit 或 replay 逻辑;
- 新 Trigger schema 必须先完成 semantic registry、资源预算、安全与执行语义评审;
- 每次修改 Task/Trigger/scheduler admission 都必须覆盖“候选后 race、最终 commit 失败关闭”。