timers.mjs
6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env node
/**
* 时间类规则的真实链路脚本,由真实 voko-worker 触发(运行前须已启动 `pnpm --filter @voko/server worker`)。
* 用时间规则为秒级的公会(产品配置,不是测试开关)覆盖 TM-01 – TM-05;TM-04 会真实调用大模型。
* TM-06、F0-06 在 W4。
*/
import { runScenario } from './_lib.mjs'
await runScenario('tm', async ({ db, check, call, createOwner, createGuild, createAgent, publish, waitForTask, wallet, submit, claim }) => {
const alice = await createOwner('alice')
const bob = await createOwner('bob')
// fast:检查点间隔 4 秒、两类验收窗口 6 秒;slow:默认规则,只用短截止。
const fast = await createGuild('fast', alice.user_id, { checkpoint_interval_sec: 4, piece_review_window_sec: 6, contest_review_window_sec: 6 })
const slow = await createGuild('slow', alice.user_id)
const fastPublisher = await createAgent('fast-publisher', alice, fast)
const slowPublisher = await createAgent('slow-publisher', alice, slow)
const fastTaker = await createAgent('fast-taker', bob, fast)
const slowTaker = await createAgent('slow-taker', bob, slow)
const eventsOf = async (agent) => (await call('GET', '/v1/events?since=0', { token: agent.key })).json.data.events
// TM-05 open 过截止无人接 → cancelled 退款。
const expiring = (await publish(slowPublisher, { reward: 40, deadline: new Date(Date.now() + 4000).toISOString() })).json.data
const expired = await waitForTask(slowPublisher, expiring.id, (task) => task.status !== 'open')
check('TM-05 open 过截止 → worker 取消并全额退款', expired.status === 'cancelled' && JSON.stringify(await wallet(slowPublisher)) === JSON.stringify({ available: 100, held: 0 }), { expired, wallet: await wallet(slowPublisher) })
// TM-01 截止未提交 → 释放回 open、发 task.released,截止按原工期顺延。
const late = (await publish(slowPublisher, { reward: 20, deadline: new Date(Date.now() + 6000).toISOString() })).json.data
await claim(slowTaker, late.id)
const reopened = await waitForTask(slowPublisher, late.id, (task) => task.status === 'open')
const lateHold = await db('vk_task_holds').where({ task_id: late.id }).first()
check('TM-01 截止未提交 → open、hold 记 timeout、发 task.released', reopened.status === 'open' && reopened.holder_agent_id === null && lateHold.end_reason === 'timeout' && (await eventsOf(slowTaker)).some((e) => e.type === 'task.released' && e.task_id === late.id), { reopened, lateHold })
check('TM-01 截止顺延,任务可以重新被接', new Date(reopened.deadline) > new Date(late.deadline) && (await claim(slowTaker, late.id)).status === 200, { old: late.deadline, next: reopened.deadline })
// TM-02 缺 1 个检查点只告警,连续缺 2 个自动释放。
const idle = (await publish(fastPublisher, { reward: 20 })).json.data
const idleClaim = (await claim(fastTaker, idle.id)).json.data
const warned = await waitForTask(fastPublisher, idle.id, (task) => task.next_checkpoint_due !== idleClaim.next_checkpoint_due || task.status !== 'claimed')
check('TM-02 缺 1 个检查点 → 仍为 claimed,到期时间顺延,并发过 task.checkpoint_due', warned.status === 'claimed' && new Date(warned.next_checkpoint_due) > new Date(idleClaim.next_checkpoint_due) && (await eventsOf(fastTaker)).some((e) => e.type === 'task.checkpoint_due' && e.task_id === idle.id), warned)
const dropped = await waitForTask(fastPublisher, idle.id, (task) => task.status === 'open')
const idleHold = await db('vk_task_holds').where({ task_id: idle.id }).first()
check('TM-02 连续缺 2 个检查点 → 自动释放回 open', dropped.status === 'open' && idleHold.end_reason === 'timeout' && idleHold.has_valid_checkpoint === 0, { dropped, idleHold })
// TM-03 计件活验收窗口到期 → 自动结算,发 task.auto_accepted。
const silent = (await publish(fastPublisher, { reward: 30 })).json.data
await claim(fastTaker, silent.id)
await submit(fastTaker, silent.id)
const auto = await waitForTask(fastPublisher, silent.id, (task) => task.status === 'settled')
const takerEvents = await eventsOf(fastTaker)
check('TM-03 验收窗口到期 → 自动结算,接单方 +30,双方收到 task.auto_accepted 与到期提醒', auto.status === 'settled' && (await wallet(fastTaker)).available === 130 && takerEvents.some((e) => e.type === 'task.auto_accepted' && e.task_id === silent.id) && (await eventsOf(fastPublisher)).some((e) => e.type === 'task.review_due' && e.task_id === silent.id), { auto, wallet: await wallet(fastTaker) })
// TM-04 擂台赛截止 → reviewing;验收窗口到期 → 大模型按清单选赢家并结算;没有任何提交 → 退款。
const finished = [{ id: 'c1', text: 'The submission summary says the work is finished.' }]
const judged = (await publish(fastPublisher, { reward: 20, template: 'contest', acceptance: finished, deadline: new Date(Date.now() + 5000).toISOString() })).json.data
const empty = (await publish(fastPublisher, { reward: 10, template: 'contest', acceptance: finished, deadline: new Date(Date.now() + 5000).toISOString() })).json.data
for (const contest of [judged, empty]) await call('POST', `/v1/tasks/${contest.id}/enter`, { token: fastTaker.key })
await submit(fastTaker, judged.id, 'The work is finished. The result is in the linked document.')
const publisherBefore = (await wallet(fastPublisher)).available
const takerBefore = (await wallet(fastTaker)).available
const decided = await waitForTask(fastPublisher, judged.id, (task) => task.status === 'settled' || task.status === 'refunded', 90)
const autoReview = await db('vk_reviews').where({ task_id: judged.id }).first()
check('TM-04 擂台赛到期 → 大模型选赢家并结算,记一条 auto 验收', decided.status === 'settled' && (await wallet(fastTaker)).available === takerBefore + 20 && autoReview !== undefined && autoReview.reviewer_kind === 'auto', { decided, autoReview })
const unanswered = await waitForTask(fastPublisher, empty.id, (task) => task.status === 'settled' || task.status === 'refunded', 60)
check('TM-04 擂台赛无人提交 → 到期退款', unanswered.status === 'refunded' && (await wallet(fastPublisher)).available === publisherBefore + 10, { unanswered, wallet: await wallet(fastPublisher) })
})