mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
8.2 KiB
8.2 KiB
ADR-0258:Owner-authenticated Local Trigger Management 与 Current Task Fence
- 状态:Accepted
- 日期:2026-08-01
- 关联 RFC:QL-RFC-0001 D-85、D-175、D-207、D-239、D-241
- 关联 ADR:ADR-0074、ADR-0085、ADR-0089、ADR-0091、ADR-0092、ADR-0256、ADR-0257
背景
ADR-0256 已让 fresh Edge/Standalone 能通过受支持入口创建 TaskDefinition,但部署者仍不能 创建、更新、停用、启用或查询 Trigger,Fresh Setup 因此停在 Task 而不能完成 Task→Trigger→Run。既有 scheduler 还只检查 Trigger current head;Task 更新或停用后,固定到 旧 Task revision 的已启用 Trigger 仍可能继续产生 Run。这会绕过部署者对 Task current head 作出的撤权决定。
该缺口不能通过新增常驻管理服务、每 Trigger timer 或新 workspace package 解决。路由设备的 空闲资源边界必须不变;Cluster 也不能复用本机 SQLite、Owner credential file 或 command file 来绕过 PostgreSQL/RBAC 与多副本围栏。
决策
- 在既有
@qinglong/local-owner-cli增加短生命周期 binaryql3-trigger,只接受run --command-file /absolute/private-command.json。command file 必须位于 deployment root 内、由当前 UID 持有、模式0600且不是 symlink。 - v1 只开放
trigger.put|trigger.inspect|trigger.list。trigger.put以expectedRevision=null创建,以 current revision 更新;enable/disable 都追加 immutable Trigger revision,不提供原地修改或 delete。production registry 只接受qinglong/cron@v1。 - 创建或更新必须绑定 exact
taskId、taskRevision与taskContentDigest。启用的 Trigger 只能绑定同 Project 下当前、内容摘要一致且 enabled 的 Task head。Task 更新、停用或重新启用 后,旧 Trigger 失败关闭,必须由部署者显式提交新 Trigger revision 完成 repin。 - 为避免撤权死锁,
enabled=false的新 Trigger revision 可以继续绑定历史 Task revision; 因此 Task 已变化或停用后,operator 仍能明确停用 stale Trigger。再次启用时必须 repin 当前 Task head。 - scheduler 在两个位置复验 Task current fence:候选发现必须联结 current Task head;最终 创建 Run 的事务必须再次验证 Task current revision、content digest 和 enabled。扫描后发生 Task race 时不得创建 Run,不能只依赖 Trigger 写入时的检查。
- 每次命令建立短生命周期 SQLite connection,复用 Owner pepper/credential authentication
与 Project Policy。put 要求
task.update,inspect/list 要求task.read;principal 必须是 strong User。沿用 Task 权限是因为 Trigger 直接改变该 Task 的调度能力,v1 不另造含义重叠的 permission。 - 写事务在同一
BEGIN IMMEDIATE内重新验证 credential/Identity/pepper、Project 与 RoleBinding fence,并原子提交 allowedtrigger.create|trigger.updateaudit、Trigger head、 immutable revision、mutation replay 和本机 schedule reset。任何认证、Policy、Task、revision、 audit 或 replay 漂移都整体回滚。 mutationId同时是 allowed audit event ID;request、actor、Policy fence、完整 Trigger mutation 与 immutableoccurredAtMs都属于 exact replay。结果未知时只能原样重放同一个 command file。- inspect/list 只返回低敏 current-head 摘要:Project/Trigger、revision、Task binding、schema、
enabled、content digest 与时间;不回显 cron expression、timezone、misfire policy、credential、
pepper、数据库路径或 command-file 路径。list 使用最多 256 条的稳定
triggerIdkeyset。 - 实现只增加既有
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。 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 conflict:inspect Trigger current revision 后人工确认,再生成新 command identity;
- 认证、授权、Task fence、semantic 或 audit conflict:失败关闭,不创建半个 revision 或 Run;
- COMMIT 结果未知:逐字重放原
0600command file;existing表示相同 durable semantic 已提交; - 禁止直接修改 Trigger、schedule 或 audit 表来“恢复”,历史 revision 必须保留解释既有 Run。
被拒绝的替代方案
- 新增
trigger-adminpackage:没有独立交付、依赖或权限域,会重现单文件/小包碎片化,拒绝。 - 让旧 Trigger 永久执行 pinned Task:Task current head 的停用/替换将不能撤销后续 admission, 拒绝;历史 pin 只用于解释已创建 Run。
- 只在 Trigger 写入时检查 Task:扫描与 commit 之间仍有竞态,拒绝;最终 Run admission 必须 重验 current Task fence。
- Task 变化时自动改写 Trigger pin:会在没有 operator intent、audit 和 replay identity 时改变 执行语义,拒绝;恢复必须显式 repin。
- 禁止 stale Trigger 的任何更新:Task 先变化后将无法停用旧 Trigger,形成撤权死锁,拒绝; 只允许追加 disabled revision,重新启用仍须 current pin。
- 每 Trigger 建 timer 或管理 daemon:扩大路由设备空闲 RSS、唤醒与连接数,拒绝;复用唯一 application scheduler cadence。
验证
ql3-trigger真实 SQLite 纵切面 5/5:create、exact replay、disable、inspect/list、operator allow、viewer deny、credential/Policy race、Task fence、audit drift、私有 command file 与低敏输出;- SQLite Trigger/schedule 定向 7/7:Task 更新或停用后候选为空,扫描后 stale commit 不生成 Run, stale Trigger 可停用,Task 重新启用并显式 repin 后才恢复候选;
- fresh 产品链 1/1:
ql3-task创建 Task、ql3-trigger创建 cron Trigger、productionLocalSchedulerCoordinator生成唯一 queued Run;Task 停用后不再产生第二个 Run; - 19 个 QL3 package 清理、顺序构建与完整测试零失败;Owner CLI 88/88,backend 958 pass/
2 条件 skip/0 fail;Trigger 精确依赖边界加入后定向审计 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 bytes,adopted 361/4,064,367--4,064,451,application 415/4,548,022--4,548,166,AI-only 345/4,082,310--4,082,370,application+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 失败关闭”。