mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
104 lines
7.2 KiB
Markdown
104 lines
7.2 KiB
Markdown
# ADR-0256:Owner-authenticated Local TaskDefinition Management
|
||
|
||
- 状态:Accepted
|
||
- 日期:2026-08-01
|
||
- 关联 RFC:QL-RFC-0001 D-85、D-175、D-207、D-239
|
||
- 关联 ADR:ADR-0074、ADR-0085、ADR-0089、ADR-0091、ADR-0092、ADR-0217
|
||
|
||
## 背景
|
||
|
||
Fresh Edge/Standalone 已具备版本化 TaskDefinition、内建 `qinglong/command@v1`
|
||
语义注册表、原子 execution revision 物化、Owner credential 和 Project Policy,但产品入口
|
||
仍只能通过测试或直接 Repository 写入 TaskDefinition。部署者无法用受支持命令完成
|
||
create/update/enable/disable/inspect/list,也就无法在 fresh 安装后建立第一个可调度任务。
|
||
|
||
这个缺口不能靠向常驻 application 暴露写 Repository 解决。路由设备不应因此增加管理
|
||
daemon、第二数据库连接、timer 或新的 workspace package;Cluster 也不能复用本机文件
|
||
credential 和 SQLite authority 来绕过 PostgreSQL/RBAC/多副本管理边界。
|
||
|
||
## 决策
|
||
|
||
1. 在既有 `@qinglong/local-owner-cli` 增加唯一短生命周期 binary `ql3-task`,只接受
|
||
`run --command-file /absolute/private-command.json`。command file 必须位于 deployment
|
||
root 内、由当前 UID 持有、模式 `0600`、不是 symlink;不接受参数拼装的 Task spec。
|
||
2. v1 只开放 `task.put|task.inspect|task.list`。`task.put` 以
|
||
`expectedRevision=null` 创建,以当前 revision 更新;`enabled=false|true` 分别表达
|
||
disable/enable 的新 revision,不提供 update-in-place 或 delete。
|
||
3. 写入只接受已冻结 registry 中的 Task spec;当前 production registry 仍只有
|
||
`qinglong/command@v1`。CLI 不因为 envelope 支持 `script|workflow|agent|tool` 就猜测其语义。
|
||
4. 每次命令建立短生命周期 SQLite connection,使用现有 Owner pepper/credential
|
||
authentication 和 Project Policy。create/update 分别要求 `task.create|task.update`,查询
|
||
要求 `task.read`;principal 必须是 strong User,owner/admin/operator 可写,viewer 只读。
|
||
5. 写事务在同一 `BEGIN IMMEDIATE` 内重新验证 credential/Identity/pepper fence、Project
|
||
version/status、RoleBinding version/state,并原子提交 allowed audit、Task head、immutable
|
||
revision、mutation replay 与适用的 local execution revision。任何 fence、semantic、revision
|
||
或 audit replay 冲突整体回滚。
|
||
6. `mutationId` 同时是 allowed audit event ID;`requestId`、actor、Policy fence、operation 和
|
||
immutable `occurredAtMs` 都属于 exact replay 语义。调用结果未知时只能原样重放 command
|
||
file。复用 mutation 但改变 Task 或 audit 语义必须失败关闭。
|
||
7. inspect/list 使用当前 head、最多 256 条的稳定 `taskId` keyset;每页使用新的
|
||
request/audit identity。输出只包含 Project/task、revision、name、kind/schema、enabled、
|
||
content digest 与时间,不回显 spec/config/command 参数、SecretRef、credential、pepper、
|
||
数据库路径或 command-file 路径。
|
||
8. 实现只增加现有 `runtime-core`、`local-sqlite`、`local-admin`、`local-owner-cli` 的显式
|
||
subpath;workspace 保持 19 个 package,不新增第三方或 production dependency、migration、
|
||
daemon、timer、watcher、listener、cache、Pool 或端口。Edge/Standalone 空闲成本为零,只有
|
||
operator 发起命令时产生一个短进程和一个 SQLite connection。
|
||
9. `ql3-task` 明确不支持 Cluster。Cluster TaskDefinition 管理必须后续通过 PostgreSQL 管理
|
||
repository、独立 credential/RBAC、强认证 transport、持久审计和多副本 fence 提供;不能
|
||
挂载本机 Owner credential 或把 SQLite command file 当集群管理协议。
|
||
|
||
## 失败与恢复
|
||
|
||
- 配置、路径、权限或 Task semantic 失败:修正后创建全新的 command identity;
|
||
- 认证、授权或 fence 失败:恢复 credential/RoleBinding 后创建新命令,不复用已改变语义的
|
||
mutation;
|
||
- revision conflict:先 inspect 当前 revision,再以新 mutation/时间提交明确更新;
|
||
- COMMIT 结果未知:保留原 `0600` command file 并逐字重放;`existing` 表示 durable semantic
|
||
已存在,不重新创建 revision;
|
||
- audit 与 Task replay 不一致:停止自动重试并人工检查,禁止直接 SQL 修补或删除历史。
|
||
|
||
## 被拒绝的替代方案
|
||
|
||
1. **新建 `task-admin` workspace package**:没有独立部署、依赖或权限域,只会让 19 包拓扑
|
||
再次碎片化,拒绝。
|
||
2. **把写入口放进常驻 local application**:扩大空闲 authority 和攻击面,也让路由设备为偶发
|
||
管理持续付费,拒绝。
|
||
3. **直接使用 SQLite/Repository 脚本**:绕过 authentication、Policy、audit、semantic registry
|
||
和 revision fence,拒绝。
|
||
4. **用 `task.enable|disable` 修改 head bool**:破坏 append-only 历史、pinned Run 解释和 exact
|
||
replay,拒绝;状态变化必须是新 revision。
|
||
5. **让 Cluster 复用本机 CLI**:SQLite 单 authority 和本机 credential 不具备 PostgreSQL HA、
|
||
多副本竞争或集群 RBAC 语义,拒绝。
|
||
|
||
## 验证
|
||
|
||
- `ql3-task` 真实 SQLite 纵切面覆盖 create、exact replay、disable、inspect、keyset list、
|
||
operator allow、viewer deny、credential/Policy race rollback、Task semantic drift、audit semantic
|
||
drift、私有 command file 与低敏输出;当前 5/5;
|
||
- `@qinglong/local-owner-cli` 完整 dependency closure strict TypeScript 通过;
|
||
- Owner CLI 83/83、local-admin 81/81、local-sqlite 189/189、backend 956 pass/2 条件
|
||
skip/0 fail;19 个 QL3 package 清理后顺序构建并完成全量测试,所有已执行 suite 零失败;
|
||
- dependency boundary 40/40 且 repository audit `findings=[]`,Edge import 与 local image
|
||
静态 inventory audit 均 compatible;GitNexus compare-to-develop 为 LOW、0 条受影响执行流;
|
||
- PostgreSQL 18.4 arm64 physical HA 同轮重新通过 `remote_apply`、timeline 1→2、旧主先
|
||
fencing、`pg_rewind` 只读同步重入、两个 fresh control replica 与全部业务 gate,
|
||
`gates.passed=true`;测试容器、网络、卷零残留。此项只证明 Cluster 基线未回归,不把
|
||
本机 `ql3-task` 宣称为 Cluster 产品入口;
|
||
- 完整 package/backend/dependency/artifact 回归是合入前门禁。
|
||
|
||
D-239 验证时曾发现 storage-only 超过 512 files、adopted assembly 漏收 `local-secret`,且
|
||
application 为 741 files、约 6.40 MiB、import RSS delta 约 20.2--20.35 MiB,因此本 ADR
|
||
当时没有把 Profile gate 标记为通过。后续 ADR-0257/D-240 已通过内部运行制品裁剪、精确 adopted
|
||
闭包和 eager import 收口修复该 blocker;十档门现已全部通过。D-239 没有自行放宽预算,完整
|
||
application 的 24 MiB import delta 分层也不改变 Edge 96 MiB 总物理进程预算。
|
||
|
||
## 后续约束
|
||
|
||
后续 ADR-0258/D-241 已完成 Trigger create/update/enable/disable/inspect/list 和
|
||
Task→Trigger→Run 的可操作 fresh 用户闭环,并在 Trigger 写入、scheduler candidate 与最终 Run
|
||
commit 三处执行 Task current-head fence。下一步为 Cluster 实现 PostgreSQL/RBAC 对等管理入口。
|
||
HTTP/UI 只能调用同一服务语义,不能复制 Task/Trigger semantic、Policy、current-head fence 或
|
||
replay 规则。非 command kind 仍须各自完成 semantic、权限、编译与执行评审后才能进入
|
||
production registry。
|