feat(monitoring): requeue automatic tasks after a manual human-verification run succeeds

When a manual, desktop-owned collect task succeeds after clearing a human
verification gate, requeue the automatic monitoring tasks that were
previously skipped for the same tenant/business-date/platform/client with
the human-verification skip reason. Restored tasks are reset to pending
(clearing lease, callback, and completion fields), stamped with a
recovery marker in their payload, and their brand projections are
rebuilt after commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-22 23:57:28 +08:00
parent fc7495aba0
commit 6aea051c19
3 changed files with 196 additions and 2 deletions
@@ -763,6 +763,75 @@ func TestFailRemainingMonitoringTasksAfterFinalAccountChallengeScopesToModelAndC
assert.Equal(t, monitoringPlatformHumanVerificationSkipReason, runtimeError["cancellation_reason"])
}
func TestShouldRecoverHumanVerificationTasksAfterManualSuccess(t *testing.T) {
task := &monitoringCollectTask{
CollectorType: monitoringCollectorType,
ExecutionOwner: "desktop_tasks",
TriggerSource: "manual",
}
assert.True(t, shouldRecoverHumanVerificationTasks(task, MonitoringTaskResultRequest{Status: "succeeded"}))
assert.False(t, shouldRecoverHumanVerificationTasks(task, MonitoringTaskResultRequest{Status: "failed"}))
automatic := *task
automatic.TriggerSource = "automatic"
assert.False(t, shouldRecoverHumanVerificationTasks(&automatic, MonitoringTaskResultRequest{Status: "succeeded"}))
}
func TestRestoreHumanVerificationTasksRequeuesOnlySkippedAutomaticTasks(t *testing.T) {
tx := &captureDailyMonitorTaskTx{}
task := &monitoringCollectTask{
ID: 901,
TenantID: 77,
AIPlatformID: "doubao",
CollectorType: monitoringCollectorType,
BusinessDate: time.Date(2026, 7, 22, 0, 0, 0, 0, time.UTC),
ExecutionOwner: "desktop_tasks",
TriggerSource: "manual",
}
brandIDs, err := (&MonitoringCallbackService{}).restoreHumanVerificationTasks(
context.Background(),
tx,
task,
"00000000-0000-0000-0000-000000000123",
)
require.NoError(t, err)
assert.Empty(t, brandIDs)
require.True(t, tx.queryCalled)
query := normalizeSQLWhitespace(tx.querySQL)
for _, fragment := range []string{
"SET status = 'pending'",
"callback_received_at = NULL",
"completed_at = NULL",
"skip_reason = NULL",
"dispatch_after = NOW()",
"last_dispatched_at = NULL",
"tenant_id = $1",
"business_date = $2::date",
"ai_platform_id = $3",
"collector_type = $4",
"trigger_source = 'automatic'",
"status = 'skipped'",
"skip_reason = $6",
"target_client_id::text = $7",
"'recovered_by_monitoring_task_id', $8::bigint",
"RETURNING brand_id",
} {
assert.Contains(t, query, fragment)
}
require.Len(t, tx.queryArgs, 8)
assert.Equal(t, int64(77), tx.queryArgs[0])
assert.Equal(t, "2026-07-22", tx.queryArgs[1])
assert.Equal(t, "doubao", tx.queryArgs[2])
assert.Equal(t, monitoringCollectorType, tx.queryArgs[3])
assert.Equal(t, "desktop_tasks", tx.queryArgs[4])
assert.Equal(t, monitoringPlatformHumanVerificationSkipReason, tx.queryArgs[5])
assert.Equal(t, "00000000-0000-0000-0000-000000000123", tx.queryArgs[6])
assert.Equal(t, int64(901), tx.queryArgs[7])
}
func TestBuildMonitoringRawPayloadQwenIncludesSearchResultsInCitationInputs(t *testing.T) {
searchTitle := "搜索网页结果"