/** * Go-No-Go GD-4 + RPA probe 3/3 验收(task-117 + task-134) * 用法:tsx scripts/go-no-go-check.ts [--mock] */ import "dotenv/config"; import { computeRates, type MetricsCounters, } from "@/modules/metrics/collector"; import { METRIC_COUNTER } from "@/lib/constants/metrics"; import { validateProbeItems } from "@/lib/rpa/probe-validation"; import { executeProbeQuote } from "@/lib/rpa/run-probe"; const THRESHOLDS = { api_success_rate: 99.5, rpa_success_rate: 85, realtime_rate: 90, cache_hit_rate: 90, p95_latency_ms: 30_000, probe_success_count: 3, } as const; const PROBE_ROUNDS = 3; type CheckResult = { name: string; value: number | string; threshold: number | string; pass: boolean; operator: ">=" | "<" | "—"; skipped?: boolean; }; function buildMockCounters(): MetricsCounters { return { [METRIC_COUNTER.POST_TOTAL]: 1000, [METRIC_COUNTER.POST_DONE]: 996, [METRIC_COUNTER.CACHE_HIT_L1]: 200, [METRIC_COUNTER.CACHE_HIT_L2]: 720, [METRIC_COUNTER.RPA_TRIGGERED]: 100, [METRIC_COUNTER.RPA_DONE]: 88, [METRIC_COUNTER.DONE_TOTAL]: 996, [METRIC_COUNTER.DONE_REALTIME]: 910, }; } function isMockMode(): boolean { return ( process.argv.includes("--mock") || process.env.RPA_MOCK_MODE === "true" ); } async function loadSnapshot(): Promise<{ rates: ReturnType; p95: number; }> { if (process.argv.includes("--mock")) { const counters = buildMockCounters(); return { rates: computeRates(counters), p95: 12_000 }; } const { getMetricsSnapshot } = await import("@/modules/metrics/collector"); const snapshot = await getMetricsSnapshot(); return { rates: { api_success_rate: snapshot.api_success_rate, rpa_success_rate: snapshot.rpa_success_rate, realtime_rate: snapshot.realtime_rate, cache_hit_rate: snapshot.cache_hit_rate, }, p95: snapshot.p95_latency_ms, }; } function runMetricChecks( rates: ReturnType, p95: number, ): CheckResult[] { return [ { name: "API success rate", value: rates.api_success_rate, threshold: THRESHOLDS.api_success_rate, pass: rates.api_success_rate >= THRESHOLDS.api_success_rate, operator: ">=", }, { name: "RPA success rate", value: rates.rpa_success_rate, threshold: THRESHOLDS.rpa_success_rate, pass: rates.rpa_success_rate >= THRESHOLDS.rpa_success_rate, operator: ">=", }, { name: "Realtime rate", value: rates.realtime_rate, threshold: THRESHOLDS.realtime_rate, pass: rates.realtime_rate >= THRESHOLDS.realtime_rate, operator: ">=", }, { name: "Cache hit rate", value: rates.cache_hit_rate, threshold: THRESHOLDS.cache_hit_rate, pass: rates.cache_hit_rate >= THRESHOLDS.cache_hit_rate, operator: ">=", }, { name: "P95 latency (ms)", value: p95, threshold: THRESHOLDS.p95_latency_ms, pass: p95 < THRESHOLDS.p95_latency_ms, operator: "<", }, ]; } /** PRD §12.7 CHG-P09:真实 env 下 probe 连续 3 次 exit 0 */ async function runProbeGate(): Promise { if (isMockMode()) { return { name: "RPA probe 3/3", value: "跳过", threshold: PROBE_ROUNDS, pass: true, operator: "—", skipped: true, }; } let success = 0; for (let round = 1; round <= PROBE_ROUNDS; round++) { try { console.log(`[go-no-go] probe 第 ${round}/${PROBE_ROUNDS} 次…`); const result = await executeProbeQuote(); const validation = validateProbeItems(result.items); if (validation.ok) { success += 1; } else { console.error(`[go-no-go] probe 失败: ${validation.message}`); } } catch (error) { console.error(`[go-no-go] probe 异常:`, error); } } return { name: "RPA probe 3/3", value: success, threshold: THRESHOLDS.probe_success_count, pass: success === PROBE_ROUNDS, operator: ">=", }; } async function main(): Promise { const { rates, p95 } = await loadSnapshot(); const metricChecks = runMetricChecks(rates, p95); const probeCheck = await runProbeGate(); const checks = [...metricChecks, probeCheck]; const required = checks.filter((c) => !c.skipped); const allPass = required.every((c) => c.pass); console.log("=== Go-No-Go 验收 ==="); for (const check of checks) { if (check.skipped) { console.log(`[SKIP] ${check.name}: mock 模式跳过 probe 门禁`); continue; } const status = check.pass ? "PASS" : "FAIL"; console.log( `[${status}] ${check.name}: ${check.value} (需 ${check.operator} ${check.threshold})`, ); } console.log(`\n总体判定: ${allPass ? "PASS" : "FAIL"}`); if (!allPass) { process.exit(1); } } main().catch((error) => { console.error("[go-no-go] 执行失败:", error); process.exit(1); });