fix(pr): webhook 入站同步更新 PR 列表 + webhook/poll 诊断(#61 #62) - #66
Conversation
#61: webhook 收到的 PR 现在走 upsert+emit prs:updated(即使 autoReview=off 也进列表),不再只 dispatch。payload_to_candidate 重写为纯函数 parse_delivery → ParseResult(Routable/WrongRepo/Malformed),承载 title/labels/url(不再 gh pr view);新增 registry::update_present(status-only 刷新现有行,不插入)、 commands::webhook_view(复用 should_skip/cooldown_skip 门,对齐 poll 路径)与 AppHandle 绑定的 ingest_webhook(config/ledger fail-closed),删除 gate_dispatchable /gate_candidates;webhook seam 由 set_dispatcher 改为 set_ingestor(WebhookEvent)。 #62: 无新事件类型,命令拉取。webhook.rs 新增 DeliveryStatus/WebhookDelivery + 50 条环形缓冲(每请求恰好记一条);scheduler.rs 新增 PollDiag + PollStatus, discover_emit_dispatch 记录 started/discovered/persist/error;新增命令 webhook_deliveries / poll_status。所有新 wire 类型配 camelCase golden 测试 (Medium 载体,ai-robust.md)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
镜像后端两个诊断 command 的 wire 形状并在 UI 展示: - types.ts: 新增 DeliveryStatus 联合 / WebhookDelivery / PollStatus(切片私有,serde camelCase) - api.ts: webhookDeliveries() / pollStatus(projectId) invoke 包装 - usePrStore.ts: 按项目 pollStatus 分区 + pollStatusActive getter + refreshPollStatus action;subscribe 两分支末尾、init、toggle 后刷新 - PollControls.vue: 轮询循环运行/暂停指示、最近成功时间、失败/停滞告警、间隔提示 + ~10s 兜底刷新 - WebhookPanel.vue: 最近 deliveries 列表(最近优先),状态徽章 + assertNever 穷尽,挂载/启停/手动刷新 + ~5s 兜底刷新 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…测试补全(#61 #62) 六维内置 review 的 small findings 自动修: - 后端:IngestIntent::StatusOnly 改携带 sealed StatusOnlyKind(type-locked, 消除 parse_delivery↔ingest_webhook 的字符串比较耦合,Soft→Hard);webhook mutate_tracked 失败改 emit PrEvent::Error(与 poll 路径对称,不再静默吞); final_status 单点决策 + WriteKind 提到模块级;fork 检测改 eq_ignore_ascii_case; make_dispatcher 去重;record_delivery 委托 record_into;过期模块/类型 doc 订正; 补 poll_status golden 4 个 snake_case 缺席断言 + parse_delivery Malformed/labels-缺失/ WebhookDelivery all-None null 序列化测试。 - 前端:PollControls stalled 首轮误报修复(用 lastStartedEpoch)+ interval 友好格式化; WebhookPanel 独立 deliveryError/deliveryLoading + 时间戳含日期 + listUpdated 无 message 时给固定提示;轮询间隔魔法数常量化。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🛠 ship review + fixreviewer 6(diff 2176 行 → 顶档六维并行)· Findings 24(已修 small 20 · 遗留 large 0 · OUT_OF_SCOPE/declined 4)· 0 P0/P1 可靠性维度确认核心设计健全:
OUT_OF_SCOPE / declined(不阻塞;附理由)
完整详表(根因 + 证据 + 处置;/fix 读此)F1 [P2·small·架构/安全/DX/可靠]
F2 [P2·small·架构/安全/可靠]
F3 [P2·small·可靠]
F4 [P2·small·DX] F5 [P2·small·测试] F6 [P2·small·测试] F7 [P2·small·测试] F8 [P2·small·产品] F9 [P2·small·产品] F10 [P2·small·产品] F11–F20(P3·small):
O1 [安全] head_sha/head_ref/labels/action 无长度上限 → 🚦 DECLINE(HMAC 验证 + GitHub 来源 + body 1 MiB 上限;推测性防御违优雅简洁/不预设未来)。 下一步:切 🤖 PR #66 · Generated with Claude Code · branch |
🟢 CI 收敛提交
run: https://github.com/ghbvf/prmonitor/actions/runs/27715715398 本地另跑(CI 未含): 🤖 PR #66 · Generated with Claude Code |
🔍 pr-review(六维度分级审查)根因簇 5 · Findings 8(P0 0·P1 1·P2 4·P3 3 | small 5·large 3)· 结论 需修复 根因簇
Findings(每条带 file:line,/fix 无损提取)
完整详表(证据 + 建议 + 根因 + 方案种子,/fix 读此)F1 [P1·large·可靠性/生命周期]
F2 [P2·large·可靠性/生命周期]
F3 [P2·small·安全/健壮]
F4 [P2·small·产品/用户体验]
F5 [P3·small·产品/用户体验]
F6 [P3·small·产品/用户体验]
F7 [P2·large·测试/回归]
F8 [P3·small·测试/回归]
复杂度汇总:按根因簇 small: 3 / large: 2;按 Finding small: 5 / large: 3 修复分流:small → 🤖 PR #66 · Generated with Claude Code · branch Fix/61-62-webhook-ingest-diag · worktree /Users/shengming/Documents/code/prmonitor/worktrees/Fix/61-62-webhook-ingest-diag · session — |
…on seam + poll 状态一致性(#61 #62) 根因簇 C2/C5/C3/C4 的 small findings: - F3[安全] parse_delivery:labels 缺失/null/非数组 → ParseResult::Malformed (仅显式 [] 表示无触发 label),坏 payload 不再被当成有效状态更新; 改正上一轮误锁 null/absent→TriggerLabelRemoved 的测试。 - F7[测试] 抽出纯 decide_ingest seam(intent/params/ledger/autoReview → view/ WriteKind/dispatchable/status/message),ingest_webhook 退化为薄 IO 壳; 补全 7 分支单测(clean×autoReview / gated draft+cooldown / conflict / StatusOnly closed+label-removed),移除"未单测"注释。 - F4[产品] refreshPollStatus 成功后同步 polling[id]=running,按钮以后端为源。 - F5[产品] switchTo 切项目后立即刷新 pollStatus。 - F6[产品] listUpdated 文案改 mode-agnostic「未派发自动任务」(check 不再误显 review)。 - F8[测试] usePrStore.test 补 pollStatus mock + refreshPollStatus 全链路断言。 遗留(large·并发/生命周期,需人工决策,见 pm:fix): F1[P1] webhook delivery detached spawn 乱序覆盖+过期派发;F2 spawn 即记 Dispatched 误报。 Refs: PR #66 pm:pr-review F3-F8(Discovered via /fix #66) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔁 fix(findings triage + fix)Findings 8(已修 small 6 · 遗留 large 2 · OUT_OF_SCOPE 0)· 来源 全部 IN_SCOPE(涉及文件均在本分支 diff 内)。small 自动修;C1 的 2 条 large(并发/生命周期语义)按 fix 章程「并发语义变更不自动执行」遗留 + surface。
完整详表(triage 依据 + 证据 + 处置 + 遗留方案种子,下次 fix 读此)F3 [P2·small·安全]
F7 [P2→small·测试]
F4 F1 [P1·large·可靠性/生命周期]
F2 [P2·large·可靠性/生命周期]
下一步:切 🤖 PR #66 · Generated with Claude Code · branch |
🟢 CI 收敛(fix 轮)提交
run: https://github.com/ghbvf/prmonitor/actions/runs/27718713102 本地另跑(CI 未含): 🤖 PR #66 · Generated with Claude Code |
🔍 pr-review --check(修复验证)验证对象 8 条上一轮 findings · 已修复 6 · 未修复 2 · 回归 0 · 部分 0 · 结论 需继续修复 验证表
验证证据F1
F2
F3
F4
F7
F5
F6
F8
验证命令
结论:仍需修复。F1/F2 两个 large 生命周期问题按 fix 留痕遗留,需人工选择方案后继续 🤖 PR #66 · Generated with Claude Code · branch Fix/61-62-webhook-ingest-diag · worktree /Users/shengming/Documents/code/prmonitor/worktrees/Fix/61-62-webhook-ingest-diag · session — |
🔍 pr-review --check(defer 确认)F1/F2 已由人工确认 deferred,不再阻塞 PR #66 本轮合并判断。 本轮状态
结论:通过;本轮阻塞项清零,切 🤖 PR #66 · Generated with Claude Code · branch Fix/61-62-webhook-ingest-diag · worktree /Users/shengming/Documents/code/prmonitor/worktrees/Fix/61-62-webhook-ingest-diag · session — |
Summary
webhook 收到 PR 后同步 upsert 到
prs.json并 emitprs:updated(即便autoReview=false也进列表,只是不 dispatch),修复「GitHub delivery 200 但 App 列表不显示」;并新增 webhook delivery 历史 + poll loop 后端真实状态两类诊断。Why / 背景
排查
ghbvf/gocell#2300时发现:webhook 入站路径(handle_webhook→ 注入闭包)只做 gate + dispatch,从不registry::mutate_trackedupsert、从不 emitprs:updated——upsert+emit 这段尾巴只活在 poll 路径的scheduler.rs::discover_emit_dispatch。所以 polling 暂停/停摆/未到下一轮时,webhook 已收到的 PR 不进左侧列表(#61)。同时缺入站/调度可观测性:delivery 无审计、poll loop 无后端真实状态、跳过原因无法串链路(#62)。预期结果:webhook 收到
opened/labeled/ready_for_review(open + 触发 label)后目标 PR 立即进列表;Webhook 面板能看最近 delivery 处理结果;PollControls 能看后端真实running+ 最近成功/失败时间。Refs
src-tauri/src/pr/scheduler.rs::discover_emit_dispatch(复用的 upsert+emit+dispatch 尾巴)、src-tauri/src/pr/commands.rs::build_view(webhook_view镜像)Risk / 兼容性
ProjectDispatcher→WebhookIngestor;payload_to_candidate→parse_delivery;删除gate_dispatchable/gate_candidates(被webhook_view取代,parity 测试已迁移)。WebhookDelivery/DeliveryStatus/PollStatus↔src/pr/types.ts:切片私有命令返回(同WebhookStatus/GhStatus),各带 golden 锁 wire 形状(camelCase 在 / snake_case 缺 / enum 字面量 pin)。未触跨切片model.rs/events.rs契约。mutate_tracked不变(复用,非另开)。无配置/数据迁移。新增诊断走命令拉取,无新增事件类型。Test plan
pnpm build本地通过(vue-tsc 类型检查 + vite build)cargo build --manifest-path src-tauri/Cargo.toml --locked本地通过cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets --locked -- -D warnings0 告警cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check干净cargo test --manifest-path src-tauri/Cargo.toml --locked通过(218 lib + 3 集成;新增parse_delivery/webhook_view/update_present/PollDiag单测 + 3 个 wire golden)🤖 Generated with Claude Code