fix traffic alert source mapping and claim-stall gating
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 9s

This commit is contained in:
OG T
2026-06-07 20:35:09 +08:00
parent 0b07ae4344
commit 3b665e4cbe
3 changed files with 62 additions and 6 deletions

View File

@@ -287,6 +287,8 @@ export async function GET(request: Request) {
actorType: actor.actorType,
actorId: actor.actorId,
taskId: "open-tasks",
sourceIp,
userAgent: request.headers.get("user-agent") ?? "unknown",
message: `External discovery call for open tasks (${publicPayload.length} items)`,
metadata: {
...trace,
@@ -361,6 +363,8 @@ export async function GET(request: Request) {
actorType: actor.actorType,
actorId: actor.actorId,
taskId: "open-tasks",
sourceIp,
userAgent: request.headers.get("user-agent") ?? "unknown",
message: `open-tasks 查詢失敗: ${errorName}: ${errorMessage}`,
metadata: {
...trace,

View File

@@ -8,6 +8,8 @@ type FunnelSummary = {
submitEvents: number;
judgePassEvents: number;
judgeFailEvents: number;
openTaskCount: number;
sampleOpenTasks: string[];
externalOpenedActors: number;
externalClaimingActors: number;
externalSubmittingActors: number;
@@ -25,6 +27,7 @@ type MonitorInput = {
const DEFAULT_PERIOD_MINUTES = 10;
const ALERT_TTL_SECONDS = 600;
let redisDedupeFailureLogged = false;
function asRecordJson(value: unknown): Record<string, unknown> | undefined {
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
@@ -77,7 +80,7 @@ function isMissingLedgerTableError(error: unknown) {
async function fetchFunnelSummary(minutes: number): Promise<FunnelSummary> {
const since = new Date(Date.now() - minutes * 60 * 1000);
const [summaryRows, actorRows, judgeRows] = await Promise.all([
const [summaryRows, actorRows, judgeRows, openTaskRows, openTaskCount] = await Promise.all([
prisma.auditEvent.groupBy({
by: ["action"],
where: {
@@ -106,6 +109,33 @@ async function fetchFunnelSummary(minutes: number): Promise<FunnelSummary> {
},
select: { metadata: true },
}),
prisma.task.findMany({
where: {
status: "OPEN",
title: {
not: {
startsWith: "GitHub Issue:",
},
},
},
orderBy: {
created_at: "desc",
},
select: {
id: true,
},
take: 5,
}),
prisma.task.count({
where: {
status: "OPEN",
title: {
not: {
startsWith: "GitHub Issue:",
},
},
},
}),
]);
let payoutCaptured = 0;
@@ -192,6 +222,7 @@ async function fetchFunnelSummary(minutes: number): Promise<FunnelSummary> {
actorId: item.actorId,
opens: item.opens,
}));
const sampleOpenTasks = openTaskRows.map((task) => task.id);
return {
discoveryEvents,
@@ -199,6 +230,8 @@ async function fetchFunnelSummary(minutes: number): Promise<FunnelSummary> {
submitEvents,
judgePassEvents,
judgeFailEvents,
openTaskCount,
sampleOpenTasks,
externalOpenedActors,
externalClaimingActors,
externalSubmittingActors,
@@ -218,6 +251,8 @@ function buildAlertMessage(rule: string, summary: FunnelSummary) {
submitEvents,
judgePassEvents,
payoutCaptured,
openTaskCount,
sampleOpenTasks,
externalOpenedActors,
externalClaimingActors,
externalSubmittingActors,
@@ -227,7 +262,9 @@ function buildAlertMessage(rule: string, summary: FunnelSummary) {
switch (rule) {
case "EXTERNAL_FUNNEL_CLAIM_STALL":
return `外部曝光已達 ${discoveryEvents}(最近 ${periodMinutes} 分鐘但尚無接案EXTERNAL_CLAIM_TASK_SUCCESS = ${claimEvents})。請檢查任務是否包含可直接執行的 npx 指令與明確交付條件。`;
return `外部曝光已達 ${discoveryEvents}(最近 ${periodMinutes} 分鐘),待接任務 ${openTaskCount} 筆,但尚無接案EXTERNAL_CLAIM_TASK_SUCCESS = ${claimEvents})。` +
`${sampleOpenTasks.length > 0 ? `可用任務樣本: ${sampleOpenTasks.join(", ")}` : ""}` +
`請檢查任務是否包含可直接執行的 npx 指令與明確交付條件。`;
case "EXTERNAL_FUNNEL_SUBMIT_STALL":
return `外部已有 ${claimEvents} 筆接案,但近期 ${periodMinutes} 分鐘無任何提交EXTERNAL_SUBMIT_SOLUTION_SUCCESS = ${submitEvents})。請先加速回傳格式與驗收測試規格。`;
case "EXTERNAL_FUNNEL_PASS_STALL":
@@ -248,14 +285,14 @@ function buildAlertMessage(rule: string, summary: FunnelSummary) {
function alertRules(summary: FunnelSummary): Array<{ action: string; message: string }> {
const alerts: Array<{ action: string; message: string }> = [];
if (summary.discoveryEvents > 0 && summary.claimEvents === 0) {
if (summary.discoveryEvents >= 3 && summary.openTaskCount > 0 && summary.claimEvents === 0) {
alerts.push({
action: "EXTERNAL_FUNNEL_CLAIM_STALL",
message: buildAlertMessage("EXTERNAL_FUNNEL_CLAIM_STALL", summary),
});
}
if (summary.externalOnlyOpenActors >= 3 && summary.discoveryEvents >= 10) {
if (summary.externalOnlyOpenActors >= 3 && summary.discoveryEvents >= 10 && summary.openTaskCount > 0) {
alerts.push({
action: "EXTERNAL_FUNNEL_OPEN_COLD_STANDBY",
message: buildAlertMessage("EXTERNAL_FUNNEL_OPEN_COLD_STANDBY", summary),
@@ -289,9 +326,18 @@ function alertRules(summary: FunnelSummary): Array<{ action: string; message: st
async function shouldEmitAlert(key: string): Promise<boolean> {
try {
const result = await redis.set(key, String(Date.now()), "EX", ALERT_TTL_SECONDS, "NX");
if (redisDedupeFailureLogged) {
redisDedupeFailureLogged = false;
}
return result === "OK";
} catch (error) {
console.warn("[traffic-monitor] redis dedupe failed", error);
if (!redisDedupeFailureLogged) {
console.warn(
"[traffic-monitor] redis dedupe failed, fallback no-dedupe mode",
error instanceof Error ? error.message : error
);
redisDedupeFailureLogged = true;
}
return true;
}
}
@@ -322,6 +368,8 @@ export async function evaluateExternalFunnelHealth(input: MonitorInput): Promise
submit_events: summary.submitEvents,
judge_pass_events: summary.judgePassEvents,
judge_fail_events: summary.judgeFailEvents,
open_task_count: summary.openTaskCount,
sample_open_tasks: summary.sampleOpenTasks,
external_opened_actors: summary.externalOpenedActors,
external_claiming_actors: summary.externalClaimingActors,
external_submitting_actors: summary.externalSubmittingActors,

View File

@@ -1,6 +1,10 @@
#!/usr/bin/expect -f
set timeout -1
spawn ssh -J wooo@192.168.0.110 wooo@192.168.0.188 "cd /home/wooo/deployments/agent-bounty-protocol && git pull origin main && docker compose exec -T web npx tsx seed.ts"
set jump_host "wooo@192.168.0.110"
set target_host "ollama@192.168.0.188"
set repo_dir "/home/ollama/vibework-git"
spawn ssh -J $jump_host $target_host "cd $repo_dir && git pull origin main && docker compose pull && docker compose up -d --build db web scout-bot && docker compose exec -T web npx prisma@6.4.1 db push --schema=apps/web/prisma/schema.prisma --skip-generate && docker compose exec -T web npx tsx apps/web/seed.ts"
expect {
"*assword:*" {
send "0936223270\r"