mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
114 lines
8.2 KiB
Markdown
114 lines
8.2 KiB
Markdown
# 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 与多副本围栏。
|
||
|
||
## 决策
|
||
|
||
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 ID;request、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 conflict:inspect 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/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、production
|
||
`LocalSchedulerCoordinator` 生成唯一 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 失败关闭”。
|