Conversation
根因:enqueue 落库后、claim 之前进程崩溃的 queued run,既不在 recoverExpiredClaims (只处理过期租约的 running run) 的恢复集合里,也脱离了触发它的请求进程里那条 auto-pump (只活在那个请求的生命周期内);而恢复调度器的 drain 又以 `requeued > 0` 为闸, requeued 恒为 0 时整轮跳过 drain,且启动时只挂 setInterval、不做首轮 tick, 这类 run 只能空等下一次运气好的 requeued>0 tick,实际等价于永久卡住。 修复: - drain 预算不再以 requeued>0 为闸,无条件循环调用 runNext() 直到队列报空或撞 maxDrainPerTick 硬上限,等价于 min(可 claim 的 queued 存量, 硬上限);死信 run (status='failed') 因 claimNextQueued 谓词固定 status='queued' 天然被排除。 - 调度器 start() 时立即跑一轮完整 tick,不等第一个 interval。 - claimNextQueued(packages/db/src/repositories/agent-runs.ts)已用 `FOR UPDATE SKIP LOCKED` 事务原子认领,多实例并发安全,无需改动。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
自制 DesktopCuuFetchEventSource 只 open() 一次:EOF 静默返回、异常只派发一次 error 就 永久死掉;兜底轮询连续失败 10 次后又永久停摆并假定 SSE 事件会来唤醒——但源已死,约两分钟 断网后网络恢复了 run 卡也不再更新,只能 reload。 - DesktopCuuFetchEventSource 改为可恢复状态机:EOF 与 fetch 异常都派发 error 并调度重连, 指数退避+抖动、封顶 60s;成功建流即复位退避并派发 open。参照 client-tauri sse_worker: 连上=基准、逐次翻倍、封顶。close/abort 后不排程重连、不泄漏定时器/连接。 - 兜底轮询删除「10 次后停摆」死亡分支,改为退避到慢节拍(封顶 60s)继续轮询,成功或 SSE (重)连即复位回正常节拍。 - 订阅层新增 open 监听:(重)连成功复用既有 refresh/getAgentRun 路径做一次终态对账,把断网 期间已推进/终结的 run 卡收敛到服务端真实状态。 - 状态机/兜底轮询均可注入时钟与 fetch,便于假时钟+假 fetch 确定性单测。 新增 4 个根因回归测试(EOF 重连+退避复位、轮询越过 10 次并恢复收敛、重连 open 触发终态对账、 关闭无泄漏)。desktop-webview 1381 测试全绿 + pnpm -r typecheck 全绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
addDependency 的「读边→内存 BFS 判环→插边」原本无项目级串行化:空图上并发
提交 A→B 与 B→A,两事务各自在旧快照判无环、双双提交成环(复合唯一索引只防
重边不防环)。现在在事务内、读边之前取 pg_advisory_xact_lock(键
project-dependency:{projectId},沿用库内 hashtext 模式),事务结束自动释放,
不同项目互不阻塞;读路径不动。
测试:
- 查询记录器单测钉锁语句存在、键绑定 projectId、且先于读边/插入;
- 门控(WORKHUB_R20_TIMELINE_REAL_PG=1 + workhub_r20_rel3_* 草稿库)真 PG
并发复现测试:EXCLUSIVE 表锁做屏障保证两事务都完成判环后才放行插入,
修复前稳定双双成功成环(红),修复后恰一条成功、另一条以 cycle 拒绝且
库内无反向边对(绿)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…livery crash codex P1-11: a ProactiveIntent that crashed between insert-commit (status='created') and delivery was permanently deduped by the suppression_key unique index and never recovered. Make 'created' a recoverable state via two legs, plus migration 0068. - repo recordProactiveIntent: on unique conflict, re-read the existing row's id + status instead of dropping it. Service now distinguishes a pre-delivery-crash row (status='created' -> resume delivery on the same-key retry) from a true duplicate (delivered/suppressed -> idempotent skip). - split recordAndDeliver into record + an internal deliver leg; both the direct path and the recovery scan reuse the single three-channel deliver (no copy-paste). - new pulse task proactive-intent-recovery: scans stale created rows (created_at older than 5m, attempt_count<3), re-delivers via the shared deliver leg, bumps attempt_count each try, and caps at 3 attempts -> suppressed(delivered_via='stalled'). Legacy rows with no delivery_payload are unrecoverable -> stalled. - migration 0068 (additive): proactive_intents.attempt_count int not null default 0 + delivery_payload jsonb (delivery context persisted at record time so the scan can rebuild the intent). schema + schema.test synced; journal tail -> 0068 (when strictly increasing, 1783929000000). Three-channel semantics unchanged (care never degrades, conversation fail-open, etc.). Root-cause tests red on base / green after fix: same-key created resume, recovery scan re-delivery + attempt cap -> stalled, true duplicate still suppressed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0-01(移出不撤访问):
- resolveHumanActor 生产路径 fail-closed——memberships 已接线却解析不到 active
成员行时抛 403 workspace_access_revoked,不再回退默认租户常量(被移出成员的软删
墓碑不再解析出可用 actor)。仅 memberships 整体未接线的单租户/测试路径才走常量。
- identify/desktop-bootstrap 不复活墓碑:ensureDefaultWorkspaceMembership 查到
软删墓碑即 403,只为「从未有过行」的新用户建行(恢复须走管理员显式动作,留 TODO)。
- removeMember 在事务内撤销该用户全部 session + 活跃 device token(复用停用路径
helper),提交后 best-effort 清 presence。与 fail-closed 双保险切断被移出者访问。
P1-09(最后管理员 TOCTOU):
- 移出/改角色把「重查最后管理员不变量 → 写」收进 workspace 级 pg advisory 锁 +
单事务的同一临界区(acquireWorkspaceMemberLock + withWorkspaceLock seam),
杜绝两管理员并发互移致零管理员。
P2-03(审计静默吞):
- createAuditLog 的 .catch(() => {}) 改为结构化 console.warn(带
action/actor/workspace/entity),对齐 routes/auth.ts 的 best-effort 风格。
测试(红→绿):
- auth.test.ts:被移出成员持旧 cookie→403、identify 不复活墓碑→403、
resolveHumanActor fail-closed;旧「无成员回退默认」测试改写成新语义。
- services/workspace-members.test.ts:并发双管理员互移恰一个失败且工作区始终
≥1 管理员(建模 advisory 锁)+ 无锁基线复现零管理员漏洞 + 移出撤 session/device/presence。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts + honest desktop logout SEC P0-02(退出/换号后旧账号私有 SSE 继续 pump): - Rust ShellClientToken 增加身份代际 generation:set/clear 都递增并 notify_waiters (旧实现清空只写 None 后直接 return,连通知都不发——根因)。 - sse_worker pump 循环改为 select!:响应块读取 vs 令牌变更通知,每轮按代际比对—— 代际变化立即中止当前连接(Superseded),令牌清空则挂起等待,换新令牌以新身份重连。 - 可测状态机抽成纯函数/小结构体:decide_token_pump / decide_sse_connect / ShellClientToken::set|snapshot / subscription_has_baked_auth(4 条新单测)。 SEC P0(已建立的 SSE 从不复检 session/device grant): - stream.ts 心跳节拍上按 revalidateMs(默认对齐 heartbeat)重验授权,撤销→写 stream.revoked 终止事件并正常收流;重验抛错 best-effort 不拆健康流。 - push.ts 为每条流请求注入 revalidateGrant(复用 resolveStreamUser 就地重解析同一凭据), 覆盖 device 吊销 / session 撤销 / 用户停用。(push.test.ts 加 2 条:撤销→收尾、抖动→不拆流。) SEC P1-01(桌面 logout 吞错伪装成功): - settings.ts 登出改有序状态机 runDesktopLogout:①await 服务端登出(失败即停、可见、可重试, 绝不静默)②await 清 Rust 壳层令牌(失败同样可见)③清本地→广播→reload;服务端不可达时给 显式"仍要本地退出"兜底(force 跳过①,文案警示服务端凭证可能仍有效)。副作用注入便于单测 (6 条新单测:顺序、失败停位、force、错误面板、点击集成)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
R20 修复流水线 wave1:codex 系统审查 HOLD 结论的解除门槛。含全部两个 P0 安全缺陷 + 四个 P1 可靠性缺陷,六条产线各自独立复验(修复前稳定失败的根因测试→修复后绿),交叉合入后整仓集成终验全绿(pnpm -r typecheck + 全包测试 + cargo test)。
Phase 0 安全封口
resolveHumanActor解析不到 active 成员行即 403workspace_access_revoked(不再回退默认租户),identify 拒绝复活软删墓碑,移出=同事务撤销 session/device token + 提交后清 presence;最后管理员不变量重查收进 workspace 级 pg advisory 锁(杜绝并发互移致零管理员);审计失败改结构化告警不再静默吞。set_client_token(写入与清空)都递增 generation 并 notify,SSE worker pump 用 select! 按代际中止旧身份连接;服务端 SSE 心跳节拍重验 session/device grant,撤销即写stream.revoked收流;桌面 logout 改有序事务(失败可见可重试 + 显式本地兜底),不再吞错伪装成功。Phase 1 可靠性
created变可恢复态——同 key 重试就地恢复投递 + pulse 兜底扫描重投(attempt 封顶 3 判 stalled),崩溃窗口不再永久去重。验证
🤖 Generated with Claude Code