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

8.2 KiB
Raw Blame History

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.listtrigger.putexpectedRevision=null 创建,以 current revision 更新;enable/disable 都追加 immutable Trigger revision,不提供原地修改或 delete。production registry 只接受 qinglong/cron@v1
  3. 创建或更新必须绑定 exact taskIdtaskRevisiontaskContentDigest。启用的 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.updateinspect/list 要求 task.readprincipal 必须是 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-corelocal-sqlitelocal-adminlocal-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 fileexisting 表示相同 durable semantic 已提交;
  • 禁止直接修改 Trigger、schedule 或 audit 表来“恢复”,历史 revision 必须保留解释既有 Run。

被拒绝的替代方案

  1. 新增 trigger-admin package:没有独立交付、依赖或权限域,会重现单文件/小包碎片化,拒绝。
  2. 让旧 Trigger 永久执行 pinned TaskTask 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/1ql3-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=trueEdge 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 失败关闭”。