Files
qinglong/docs/adr/ADR-0377-profile-aware-run-attempt-log-range-read.md
T

104 lines
8.9 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-0377Profile-aware Run Attempt 日志范围读取
- 状态:Accepted
- 日期:2026-08-12
- 关联 RFCQL-RFC-0001 D-289
- 前置决策:ADR-0027、ADR-0367、ADR-0376
## 上下文
Task Start 已能在 Local 与 Cluster 创建可执行 Run,产品入口也能读取 Run、Event、Step 并请求取消,但调用方仍无法从同一 Project-scoped 产品面读取某个 Attempt 的执行日志。早期 `back/runtime` 已验证本机 metadata-first 授权、私有文件 Range 与截断三态,但它不在 QingLong 3.0 package 生产图中,也没有 Cluster 对象存储实现。
直接恢复 2.x 的 `path + file` 会把路径当作外部 capability;把整份日志编码进 JSON 会让内存与日志大小线性增长;把 Local 与 Cluster 强行放到同一存储 adapter,又会使控制面依赖 Worker 本地路径。低配路由设备和集群节点需要共享语义,但必须拥有不同的单次窗口与存储实现。
## 决策
### 1. 同一产品路由与只读权限
Local 与 Cluster 提供同构入口:
`GET /api/v3/projects/{projectId}/runs/{runId}/attempts/{attemptId}/log?offset={n}&length={n}`
- operation`run.log.read`
- permission`artifact.read`
- response schema`qinglong/run-attempt-log-read-result@v1`
- `offset` 默认为 0`length` 默认为当前 Profile 窗口
- 请求只接受 Project、Run、Attempt identity 和有界范围,不接受 Artifact ID、路径、URI、bucket 或 object key
Policy、durable security audit 与 credential confirmation 必须先于 Run/Attempt metadata 和 Artifact 存储访问。对非可信 HTTP 调用方,Policy deny、require-approval、跨 Project、不属于该 Run、legacy owner、错误 executor 和不存在均返回相同的 404,不形成存在性 oracle;真实结果只进入低敏审计。
### 2. 共享 application contract,不共享存储 adapter
`runtime-core` 定义唯一的 Run Attempt 日志读取服务和 byte-range port。服务复用现有 `RunRepositoryReader.findRunById/findAttemptById`,依次验证:
1. Run 属于请求 Project 且 `executionOwner=runtime`
2. Attempt 属于请求 Run
3. executor 与当前 Profile 一致;
4. Attempt 绑定 canonical `local-*``wlog-*` Artifact ID
5. range 规范化且不超过调用 Profile 的上限;
6. 只有上述数据库事实成立后才调用存储 reader。
Local adapter 从 opaque `local-*` ID 派生私有 shard,拒绝 symlink、非普通文件、非当前进程 owner、多 hard link、异常短读和 identity 不一致的 truncation fact。Cluster adapter 先用现有 immutable S3 HEAD 校验 identity digest、length、content type、checksum metadata,再执行单次有界 Range GET;控制面从不拼接 Worker 路径。
S3-compatible provider 必须在最终对象 COPY 后,通过启用 checksum 的 HEAD 返回 canonical `ChecksumSHA256`;只返回调用方可写 metadata 而不能独立证明对象内容的实现不在兼容集合内,adapter 必须失败关闭,不能退化为信任 metadata 或 ETag。
### 3. Profile 窗口和资源上限
共享协议硬上限为 256 KiB,但部署档位收紧实际窗口:
- Local Edge:默认 16 KiB,最大 32 KiB
- Local Standalone:默认/最大 32 KiB
- Cluster Control:默认 64 KiB,最大 256 KiB。
当前 HTTP transport 只返回 JSON,因此 content 使用 Base64,并同时返回 byte range、总长度和下一 offset。Local 的 32 KiB 上限保证编码和元数据仍低于既有 64 KiB response hard limitCluster 保持低于既有 1 MiB 默认 response limit。reader 只分配请求窗口,不启动 watcher、tail timer、cache、额外 listener、Pool 或 S3 client。
### 4. 状态与失败语义
- `200 available`:返回当前文件/对象快照内的有界 bytes、`start``endExclusive``totalBytes` 和可选 `nextOffset`
- `202 pending`:合法远端 Attempt 已绑定日志,但对象尚未在 upload-before-completion 协议中发布,或 Attempt 尚未产生 Artifact identity
- `404 artifact_not_found`:不存在、越权或 identity/executor 不匹配的统一外部遮蔽;
- `503 artifact_unavailable`:数据库、文件安全检查、对象存储或证据一致性无法证明。
`truncated` 保持 `true | false | unknown`。Local 只相信 launcher canonical factCluster 只相信 immutable S3 metadata;禁止从 size、尾部文本或退出状态推断。offset 等于或超过当前快照末尾时返回空的 available range,并把 start 收敛到 totalBytes,便于轮询而不制造 416 竞态。
### 5. 本批明确不闭环 retention
本批不新增 tombstone table、delete marker、retention lifecycle 或对象删除权限。ADR-0027 的 retained/missing 竞态和 ADR-0026 retention 仍是独立后续切片。没有 tombstone 时,Local 的已绑定文件缺失和终态 Cluster 对象缺失都按 503 处理,不能伪装成已执行 retention;因此 ADR-0027 在 retention 完成前保持 Proposed。
### 6. package 与部署边界
- 不新增 workspace package、第三方依赖、migration、table/index、数据库连接或常驻资源;
- contract 放入 `runtime-core` 既有 Run 能力目录;Local reader 放入 `local-execution` 既有 Artifact 能力目录;S3 reader 扩展 `cluster-control` 既有 immutable store
- 默认 headless Local 不加载文件 reader;只有可选 Local API product surface 通过 application authority 获得已构造 capability
- Cluster 复用 production Worker artifact binding 的同一 S3 client/store;未配置对象存储时 route 保持存在并失败关闭为 503;
- MCP 本批不读取日志,避免在 Agent 面扩大敏感数据能力。
## 被否决的替代方案
1. **恢复 `path + file`**:路径成为跨 Project capability,拒绝。
2. **整文件返回或服务端 tail 长连接**:内存、连接和后台资源无界,不适合 Edge,拒绝。
3. **固定所有 Profile 为 256 KiB**Base64 后突破 Local 64 KiB transport 门,拒绝。
4. **Cluster 读取 Worker 共享挂载**:绕过 immutable upload、checksum 与 fencing,拒绝。
5. **为日志读取新建微包或 metadata 表**:既有 Run/Attempt authority 足够,本批不制造薄 package,拒绝。
6. **将 missing 当 retention**:没有 durable tombstone 无法证明,拒绝。
## 验收
1. shared contract 覆盖 identity/range、Project/Run/Attempt/executor 绑定、pending/available/missing 与截断三态;
2. Local adapter 覆盖 symlink、owner/link count、append snapshot、短读、空/越尾 range 和 fact identity 冲突;
3. S3 adapter 覆盖 HEAD identity/checksum、精确 Range、abort、404、响应长度/Content-Range 漂移和终态缺失;
4. Local/Cluster HTTP 覆盖 route/query、`artifact.read`、durable audit、deny/not-found 遮蔽、202/200/400/404/503
5. 默认 headless Edge closure 不增加;Local API 增量仍满足 64 KiB response、4 in-flight 与 128 MiB/64 PID 门;
6. 完整 package/backend、dependency/package boundary、Profile artifact、真实 SQLite+HTTP、S3-compatible integration、PostgreSQL HA 与 Local image 门全部通过后,状态才可改为 Accepted。
## Accepted 证据
- Runtime Core 494/494、Local Execution 35/35、Local API 45/45、Local Application 45 pass/4 platform skip、Cluster Control 215 pass/2 conditional skip;完整 18-package build/test 退出 0。
- backend 1,163 pass/2 skip/0 failpackage boundary、dependency boundary 与 Edge import audit 全部 compatible。workspace 仍为 18 package、1,045 source、1,027 nested/18 reviewed root entry,无 single-source 或 shallow package。
- 真实 SQLite+HTTP 已覆盖认证、Policy、durable audit、credential confirmation、Attempt metadata 与本地私有文件 Range 的完整顺序;Local 文件 adapter 的 symlink、权限、hard-link、短读、append snapshot、越尾空读与 truncation identity drift 全部通过。
- S3 单元门覆盖 checksum/metadata/ETag/Content-Range 漂移、短体与超长体;固定摘要 `minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e` 的真实 KMS/SSE、条件 promotion、重放、HEAD 与 Range GET 通过。不能在 COPY 后返回 `ChecksumSHA256` 的旧 provider 被实测失败关闭。
- PostgreSQL 18.4 arm64 HA 通过 112 gatestimeline `1→2`,报告 SHA-256 为 `61bea38e3a4f66884b9642c2fc1944dd7084f70ceb358d6d3fea8c11d5d33b65`
- 14 个 Edge/Standalone Profile artifact 全部 compatible。Edge Application API 为 3,596,457 bytes/423 files/84 loaded modules,距 6 MiB 门仍有 2,694,999 bytes;依赖闭包不包含 Cluster、PostgreSQL 或 AWS SDK。
- 当前源码构建的 arm64 Local image 为 10 package/385 files/3,331,225 bytes。Edge 128 MiB/64 PIDs 与 Standalone 256 MiB/256 PIDs 均在 non-root、read-only root、network-none 下 active、20 events、graceful stopSQLite integrity 为 `ok`
- 本批没有新增 package、第三方依赖、migration、table/index、连接、listener、timer、watcher 或 cacheArtifact retention/tombstone 仍由 ADR-0027 后续闭环。