feat: goal 断点续跑端点(G10)+ 摘要持久化与推送 outbox(G11/G12)
G10:POST /tasks/{id}/goal-resume,仅 interrupted 的 goal 任务,
走 host.py goal-resume(无 --goal-text)从 goal_rounds_done 下一轮
断点续跑,保留原线程与 worktree,状态回 queued 并置
goal_resume_pending(worker 认领后清掉),写 task_goal_resumed
审计;goal-met/unmet、failed、非 goal 一律 409;/continue 开新
循环语义不动。Local/Docker 两路新增 goal_resume_breakpoint_task,
不支持断点的执行器明确失败,绝不回落成 goal 或开新循环。
G11:digest 非失败事件持久化进 push_digest_events,worker 循环
每小时按(项目、订阅)分组汇总发一条,失败类仍即时发。
G12:webhook/slack 投递失败写 push_outbox,worker 循环后台重试
(最多 3 次、默认 60 秒退避),3 次仍失败删行并记日志,不丢
任务状态;缺凭证与无 sender 不进 outbox;文档写明 at-least-once
下 webhook/slack 可能重复,接收方须去重。
测试:cloud 769(4 跳过)、host 68、deploy 19 全过。
This commit is contained in:
1 parent
ac553509ea
commit
5883169c82
8 files changed
+958
-21
No files matched your search
+38
-12
@@ -226,7 +226,7 @@ webhook 实测)。
|
||||
### 推送凭证清单
|
||||
|
||||
- webhook / slack:已有内置真实发送端(标准库 urllib POST,
|
||||
单次 10 秒超时,不重试,失败记入分发结果的 errors):
|
||||
单次 10 秒超时,失败记入分发结果的 errors):
|
||||
webhook 投 JSON(event_type、project_id、task_id、payload),
|
||||
slack 投 Slack incoming webhook 格式的一行文本(任务 id +
|
||||
事件 + 项目,失败附 error 摘要)。任务终态(completed /
|
||||
@@ -235,6 +235,23 @@ webhook 实测)。
|
||||
落盘失败时发 merge-failed;手动分发走
|
||||
`POST /admin/push/dispatch`,同样经内置发送端。
|
||||
显式注入的 sender 优先于内置端。
|
||||
- 摘要(digest,G11):非失败事件在 digest 偏好下不再只在
|
||||
返回值里列入 skipped_for_digest 就完事——事件持久化进
|
||||
`push_digest_events` 表,worker 循环每小时按(项目、订阅)
|
||||
分组汇总发一条(payload 为 `{"digest": True, "events": [...],
|
||||
"count": n}`),成功后整组标记已发送,失败整组保留待下
|
||||
一次重发。失败类事件(failed/goal-unmet/merge-failed)不
|
||||
进摘要,仍即时发。摘要节奏是“worker 循环每小时一批”,
|
||||
不是“每条事件满 1 小时才发”——一批刚发完、紧接着到的
|
||||
事件要等下一批(最长约 1 小时)。
|
||||
- 推送失败重试(outbox,G12):webhook/slack 发送端抛异常
|
||||
时,事件写进 `push_outbox` 表,worker 循环后台重试(最多
|
||||
3 次、默认 60 秒退避);3 次仍失败则删行并记
|
||||
worker_push_outbox_dropped 日志,不丢任务状态、不炸循环。
|
||||
缺凭证(webpush/apns/fcm)与无 sender 不是瞬时故障,不
|
||||
进 outbox,仍明确报错。at-least-once:webhook/slack 的重
|
||||
试可能重复送达同一事件,接收方必须按 event/task 去重,
|
||||
本模块不保证 exactly-once。
|
||||
- webpush:需要 VAPID 密钥对(私钥绝不进仓库),且需要
|
||||
浏览器端订阅(endpoint + p256dh + auth,现有订阅模型只有
|
||||
一个 target 文本字段,装不下)与标准库没有的 P-256 ECDH +
|
||||
@@ -956,17 +973,26 @@ interrupted 且终态任务 cancel 返回 400、心跳后 sweep 不扫刚心跳
|
||||
NULL,SUM 自动跳过),按任务创建者 `created_by` 归属——一个
|
||||
用户在多个项目里花的钱都算进他自己的月度账单;项目维度的
|
||||
拆分由项目成本接口另行提供。
|
||||
- 准入:建任务、会话分支、重试、续跑在权限校验通过后、写库
|
||||
之前查配额,本月用量 >= 配额时回 402 `quota_exceeded`(附
|
||||
month、quota_usd_micros、used_usd_micros),不建任务、不改
|
||||
状态。老用户与未设配额的用户默认不限额,行为与过去一致。但
|
||||
OAuth 新用户不再是“建号即不限额”:建号(created=True)时
|
||||
写一行默认配额(`CLOUD_DEFAULT_QUOTA_USD_MICROS`,默认
|
||||
10_000_000,即 $10/月;`INSERT OR IGNORE` 幂等,不覆盖管理
|
||||
员后来改过的额度),并记 `quota_default` 审计。重试与续跑
|
||||
按任务创建者查配额(费用本来就记在创建者名下;维护者替
|
||||
他人重试/续跑时,操作人超限不应拦、创建者超限才应拦——
|
||||
旧版重试按操作人查是错位,本轮已改)。后续消息
|
||||
- 准入:建任务、会话分支、重试、续跑、goal 断点续跑在权限
|
||||
校验通过后、写库之前查配额,本月用量 >= 配额时回 402
|
||||
`quota_exceeded`(附 month、quota_usd_micros、used_usd_micros),
|
||||
不建任务、不改状态。老用户与未设配额的用户默认不限额,
|
||||
行为与过去一致。但 OAuth 新用户不再是“建号即不限额”:
|
||||
建号(created=True)时写一行默认配额
|
||||
(`CLOUD_DEFAULT_QUOTA_USD_MICROS`,默认 10_000_000,
|
||||
即 $10/月;`INSERT OR IGNORE` 幂等,不覆盖管理员后来改过
|
||||
的额度),并记 `quota_default` 审计。goal 断点续跑端点
|
||||
(G10):`POST /tasks/{id}/goal-resume`(权限 approve_merge,
|
||||
同重试),仅 interrupted 的 goal 任务可续(goal-met/goal-unmet
|
||||
已终局、failed 按旧规则新建、非 goal 任务一律 409),走
|
||||
host.py goal-resume(无 --goal-text)的断点语义——从
|
||||
goal_rounds_done 的下一轮接着跑同一循环,保留原线程与
|
||||
worktree,状态回 queued 并置 goal_resume_pending(worker
|
||||
认领后清掉),写 `task_goal_resumed` 审计;`/continue`
|
||||
开新循环的语义不动,不要把两者混用。重试与续跑按任务
|
||||
创建者查配额(费用本来就记在创建者名下;维护者替他人
|
||||
重试/续跑时,操作人超限不应拦、创建者超限才应拦——旧版
|
||||
重试按操作人查是错位,本轮已改)。后续消息
|
||||
(followup)创建不查配额:排队本身不花钱,执行时轮次边界
|
||||
的配额门照常生效,不要把 followup 也改成准入校验。系统
|
||||
任务(GitHub webhook 的 /fix-review,created_by 是 'system')
|
||||
|
||||
Reference in new issue
Block a user