Skip to content

Commit aa6982e

Browse files
authored
Add captcha task-start and challenge-result events (#332)
## Summary - add task-level `captcha_solve_started` and challenge-level `captcha_challenge_result` telemetry - distinguish challenge outcomes as `solved`, `failure`, `timeout`, or `abandoned` - require `data` on both new events and `duration_ms` on challenge results - preserve the existing generated captcha type and solve-result status symbols for downstream Go consumers ## Contract `captcha_solve_started` records that a solver accepted one task. `task_id` pairs that start with its terminal `captcha_solve_result`, while `challenge_id` groups every task from one visible challenge. Delivery remains best-effort and unordered, so event arrival or absence does not indicate current solve state. `captcha_challenge_result` is emitted once for the visible challenge. `failure` means the producer reached an explicit terminal solver failure or exhausted its attempts while the challenge remained. `timeout` is reserved for an actual challenge-level wait budget. `abandoned` covers observation ending without either a solved signal or an explicit terminal solver outcome. The shared captcha taxonomy keeps the legacy generated `BrowserCaptchaSolveResultEventDataCaptchaType` name and constants through a schema alias. Existing relay code therefore compiles unchanged when it updates this module. ## Rollout Schema and generated image API types only; no producer behavior changes here. The public API schema and extension producer are updated in their corresponding PRs before consumers rely on the new challenge event. ## Testing - `go test ./lib/oapi ./lib/events/...` — pass, including generated-symbol and required-field compatibility coverage - `go build ./...` — pass - `go vet ./...` — pass - OpenAPI generation run twice — byte-identical generated output - `go test ./lib/capmonsterrelay` on `kernel/kernel#3432` with this module replacement — pass without relay changes - GitHub `test-server-e2e` — all cases except `TestReplayRecordingIncludesAudioTrack`; that unrelated test failed twice because the generated audio track ended 0.38s / 1.29s before its recording-duration threshold. The base branch has a green full e2e run on the same base commit. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a summary for commit 1183b8d. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 4cfb000 commit aa6982e

5 files changed

Lines changed: 1064 additions & 585 deletions

File tree

server/lib/events/category_gen.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/lib/events/category_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ func TestCategoryMapsAgreeOnAPICallSplit(t *testing.T) {
2121
assert.Equal(t, Platform, cat)
2222
}
2323

24+
func TestCaptchaCategories(t *testing.T) {
25+
for _, eventType := range []string{"captcha_solve_started", "captcha_challenge_result"} {
26+
t.Run(eventType, func(t *testing.T) {
27+
cat, ok := CategoryForType(eventType)
28+
require.True(t, ok)
29+
assert.Equal(t, Captcha, cat)
30+
})
31+
}
32+
}
33+
2434
func TestCategoryForOperation(t *testing.T) {
2535
require.NotEmpty(t, categoryByOperationID, "generator produced no operations")
2636

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package oapi
2+
3+
import "testing"
4+
5+
func TestCaptchaGeneratedNamesRemainCompatible(t *testing.T) {
6+
var captchaType BrowserCaptchaSolveResultEventDataCaptchaType = BrowserCaptchaSolveResultEventDataCaptchaTypeHcaptcha
7+
var sharedType BrowserCaptchaType = captchaType
8+
if sharedType != captchaType {
9+
t.Fatalf("shared captcha type = %q, want %q", sharedType, captchaType)
10+
}
11+
12+
statuses := []BrowserCaptchaSolveResultEventDataStatus{
13+
Success,
14+
Failure,
15+
Timeout,
16+
Abandoned,
17+
}
18+
for _, status := range statuses {
19+
if !status.Valid() {
20+
t.Fatalf("solve status %q is invalid", status)
21+
}
22+
}
23+
for _, status := range []BrowserCaptchaChallengeResultEventDataStatus{
24+
ChallengeSolved,
25+
ChallengeFailure,
26+
ChallengeTimeout,
27+
ChallengeAbandoned,
28+
} {
29+
if !status.Valid() {
30+
t.Fatalf("challenge status %q is invalid", status)
31+
}
32+
}
33+
34+
started := BrowserCaptchaSolveStartedEvent{
35+
Data: BrowserCaptchaSolveStartedEventData{CaptchaType: captchaType},
36+
}
37+
challenge := BrowserCaptchaChallengeResultEvent{
38+
Data: BrowserCaptchaChallengeResultEventData{
39+
CaptchaType: captchaType,
40+
ChallengeId: "challenge-id",
41+
DurationMs: 1,
42+
Status: ChallengeFailure,
43+
},
44+
}
45+
if started.Data.CaptchaType != challenge.Data.CaptchaType {
46+
t.Fatal("captcha event types are inconsistent")
47+
}
48+
}

0 commit comments

Comments
 (0)