@sealkeeper/schema 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +4411 -364
- package/dist/api.js +1753 -46
- package/dist/blocks.d.ts +21 -0
- package/dist/blocks.js +43 -0
- package/dist/cli-version.d.ts +7 -0
- package/dist/cli-version.js +89 -0
- package/dist/conformance.d.ts +4 -0
- package/dist/conformance.js +7 -0
- package/dist/credential.d.ts +763 -21
- package/dist/credential.js +253 -35
- package/dist/dimensions.d.ts +16 -3
- package/dist/dimensions.js +52 -2
- package/dist/fingerprint-conformance.d.ts +17 -0
- package/dist/fingerprint-conformance.js +83 -0
- package/dist/fingerprint.d.ts +133 -0
- package/dist/fingerprint.js +178 -0
- package/dist/game.d.ts +100 -0
- package/dist/game.js +190 -0
- package/dist/goal.d.ts +22 -1
- package/dist/goal.js +50 -7
- package/dist/handshake-conformance.d.ts +18 -0
- package/dist/handshake-conformance.js +276 -0
- package/dist/handshake.d.ts +66 -0
- package/dist/handshake.js +188 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/model-comparison.d.ts +70 -0
- package/dist/model-comparison.js +208 -0
- package/dist/model-name.d.ts +3 -0
- package/dist/model-name.js +48 -0
- package/dist/moderation.d.ts +2 -0
- package/dist/moderation.js +14 -8
- package/dist/policy.d.ts +1 -1
- package/dist/policy.js +1 -1
- package/dist/seal-conformance.js +263 -2
- package/dist/standing.d.ts +90 -0
- package/dist/standing.js +253 -13
- package/dist/task-templates.d.ts +47 -0
- package/dist/task-templates.js +506 -0
- package/dist/tasks.d.ts +97 -0
- package/dist/tasks.js +230 -0
- package/dist/template-conformance.d.ts +10 -0
- package/dist/template-conformance.js +298 -0
- package/dist/top-dimensions.d.ts +3 -3
- package/dist/top-dimensions.js +16 -6
- package/package.json +3 -3
package/dist/api.js
CHANGED
|
@@ -2,15 +2,20 @@ import { z } from 'zod';
|
|
|
2
2
|
import { AgentId } from './agent-id.js';
|
|
3
3
|
import { AgentName } from './agent-name.js';
|
|
4
4
|
import { base64urlDecode, base64urlEncode, utf8Decode, utf8Encode, } from './base64url.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { BLOCK } from './blocks.js';
|
|
6
|
+
import { IssuedSealPayload, SealPayload } from './credential.js';
|
|
7
|
+
import { AcceptedDimension, COMPETENCE_TYPES_MAX, Dimension, TaskType, } from './dimensions.js';
|
|
7
8
|
import { Jws } from './envelope.js';
|
|
8
9
|
import { StoredVersion, Version } from './events.js';
|
|
10
|
+
import { AgentFingerprint, Fingerprint } from './fingerprint.js';
|
|
11
|
+
import { CHALLENGE_TASKS, ChallengeState, DuelOrigin, DuelResult, DuelSeekState, DuelState, GAME, GAME_CAP_MAX, GameBadgeKind, GameCap, IsoWeek, KeeperRank, } from './game.js';
|
|
12
|
+
import { HANDSHAKE_MAX_CHARS, HandshakeAgainst, HandshakeNonce, HandshakeRefusal, HandshakeResult, } from './handshake.js';
|
|
9
13
|
import { boundedJsonObject } from './json-shape.js';
|
|
14
|
+
import { MODEL_NETWORK, ModelVerdict, SuspectedState, } from './model-comparison.js';
|
|
10
15
|
import { OPERATOR_DISPLAY_NAME_MAX, OPERATOR_SLUG_MAX, OperatorDisplayName, OperatorSlug, } from './moderation.js';
|
|
11
16
|
import { Runtime } from './runtime.js';
|
|
12
|
-
import { Level, Standing } from './standing.js';
|
|
13
|
-
import { PublicVerificationSpec, Sha256Hex, ShownVerificationSpec, TaskOutcome, TaskState, VerificationSpec, } from './tasks.js';
|
|
17
|
+
import { DAY_MS, Level, Standing, TRUST_SCORE } from './standing.js';
|
|
18
|
+
import { CheckMethod, Disclosure, PublicVerificationSpec, Sha256Hex, ShownVerificationSpec, STORED_TASK_CATEGORIES, StoredTaskCategory, TASK_CATEGORIES, TASK_DIFFICULTIES, TaskCategory, TaskDifficulty, TaskInputRef, TaskOutcome, TaskOutputShape, TaskSize, TaskState, VerificationSpec, } from './tasks.js';
|
|
14
19
|
export const MAX_EVENTS_PER_BATCH = 500;
|
|
15
20
|
// The window an event's occurred_at must fall in for the API to take it, at
|
|
16
21
|
// most this many days old and this many seconds ahead of the API clock.
|
|
@@ -41,6 +46,17 @@ export const TASK_DEFAULT_TTL_HOURS = 24;
|
|
|
41
46
|
export const TASK_MAX_TTL_DAYS = 7;
|
|
42
47
|
const Timestamp = z.iso.datetime();
|
|
43
48
|
const Limit = z.coerce.number().int().min(1).max(100).default(50);
|
|
49
|
+
// The least difficulties the feed (VOU-554) and the task board (VOU-569)
|
|
50
|
+
// filter on, the hard end of TASK_DIFFICULTIES.
|
|
51
|
+
export const FEED_MIN_DIFFICULTIES = [
|
|
52
|
+
3, 4, 5,
|
|
53
|
+
];
|
|
54
|
+
// A minDifficulty query parameter, one digit that is one of them.
|
|
55
|
+
const MinDifficultyParam = z
|
|
56
|
+
.string()
|
|
57
|
+
.regex(/^\d$/)
|
|
58
|
+
.transform(Number)
|
|
59
|
+
.pipe(z.literal(FEED_MIN_DIFFICULTIES));
|
|
44
60
|
export const AgentParams = z.strictObject({ id: AgentId });
|
|
45
61
|
export const TaskParams = z.strictObject({ id: z.uuid() });
|
|
46
62
|
// The body of a signed task write.
|
|
@@ -48,14 +64,17 @@ export const SignedTaskRequest = z.strictObject({
|
|
|
48
64
|
envelope: Jws.max(MAX_TASK_ENVELOPE_CHARS),
|
|
49
65
|
});
|
|
50
66
|
// runtime is optional, so a CLI from before runtimes registers unchanged
|
|
51
|
-
// and its agent is unknown.
|
|
52
|
-
//
|
|
67
|
+
// and its agent is unknown. gameEnabled is the operator's game switch at
|
|
68
|
+
// init (VOU-469, D-GAME-2), optional too, and absent means off. Both are
|
|
69
|
+
// read on a new agent only. Registering a key that is already registered
|
|
70
|
+
// changes nothing, runtime and game switch included.
|
|
53
71
|
export const RegisterAgentRequest = z.strictObject({
|
|
54
72
|
publicKey: AgentId,
|
|
55
73
|
githubToken: z.string().min(1).max(256),
|
|
56
74
|
name: AgentName,
|
|
57
75
|
version: Version,
|
|
58
76
|
runtime: Runtime.optional(),
|
|
77
|
+
gameEnabled: z.boolean().optional(),
|
|
59
78
|
});
|
|
60
79
|
// A name as the API sends it. Every stored name is an AgentName, this stays
|
|
61
80
|
// loose so a client never refuses an answer over a name.
|
|
@@ -145,14 +164,33 @@ export const AgentResponse = z.strictObject({
|
|
|
145
164
|
// the registration answer for the same reason as operatedBySealKeeper.
|
|
146
165
|
// Optional so an answer from an older API parses.
|
|
147
166
|
runtime: Runtime.optional(),
|
|
148
|
-
// The model
|
|
149
|
-
//
|
|
150
|
-
//
|
|
167
|
+
// The model the agent names, as text. The name it declares beside its
|
|
168
|
+
// fingerprint (VOU-566), or the one its newest usage event names when
|
|
169
|
+
// that reached the API after the declared name last changed, null when
|
|
170
|
+
// neither names one (modelOf in apps/api/src/activity.ts). What the
|
|
171
|
+
// agent says, never guessed, never proof. Plain text wherever it shows,
|
|
172
|
+
// never a link. Sent by GET by id and GET by handle only. Optional so an
|
|
173
|
+
// answer from an older API parses.
|
|
151
174
|
model: Name.nullable().optional(),
|
|
152
175
|
// The agent's avatar, WEB_URL plus avatarPath. Sent by GET by id and GET
|
|
153
176
|
// by handle, left out of registration and rename for the same reason as
|
|
154
177
|
// counts.
|
|
155
178
|
avatarUrl: z.url().optional(),
|
|
179
|
+
// The agent's current fingerprint (VB-4), the one with the latest
|
|
180
|
+
// captured_at any of its signed requests carried, as its hash, when it
|
|
181
|
+
// was captured and each part's state, never a part hash. null when it
|
|
182
|
+
// has declared none. A second record beside version, never the scoring
|
|
183
|
+
// key. Sent by GET by id and GET by handle only, for the same reason as
|
|
184
|
+
// counts.
|
|
185
|
+
fingerprint: AgentFingerprint.nullable().optional(),
|
|
186
|
+
// The agent's current and best streak of UTC days with a server checked
|
|
187
|
+
// pass and no fail (VOU-472, D-GAME-8), as the scoring run last walked
|
|
188
|
+
// them, today never counted. From the agent row. A streak earns nothing
|
|
189
|
+
// and is not in the SEAL. Sent by GET by id and GET by handle only, for
|
|
190
|
+
// the same reason as counts. Optional so an answer from an older API
|
|
191
|
+
// parses.
|
|
192
|
+
currentStreak: z.int().min(0).optional(),
|
|
193
|
+
bestStreak: z.int().min(0).optional(),
|
|
156
194
|
});
|
|
157
195
|
// Old handles keep resolving, as a redirect, for this many days after an
|
|
158
196
|
// agent rename. The profile shows the old name for as long.
|
|
@@ -212,6 +250,120 @@ export const DeleteAgentBody = z.union([
|
|
|
212
250
|
z.strictObject({ envelope: Jws.max(MAX_ENVELOPE_CHARS) }),
|
|
213
251
|
z.strictObject({}),
|
|
214
252
|
]);
|
|
253
|
+
// The game settings of one agent (VOU-469, D-GAME-2, D-GAME-3). enabled is
|
|
254
|
+
// the operator's switch and cap the most game units the agent may use in
|
|
255
|
+
// one UTC day, 0 to GAME_CAP_MAX. Either or both, never neither. The body
|
|
256
|
+
// of PUT /v1/me/agents/:id/game from the web, and with issuedAt the signed
|
|
257
|
+
// payload of PUT /v1/game/settings.
|
|
258
|
+
const gameChange = {
|
|
259
|
+
enabled: z.boolean().optional(),
|
|
260
|
+
cap: GameCap.optional(),
|
|
261
|
+
};
|
|
262
|
+
const someGameChange = (r) => r.enabled !== undefined || r.cap !== undefined;
|
|
263
|
+
const GAME_CHANGE_REQUIRED = { message: 'enabled or cap is required' };
|
|
264
|
+
export const GameSettingsRequest = z
|
|
265
|
+
.strictObject(gameChange)
|
|
266
|
+
.refine(someGameChange, GAME_CHANGE_REQUIRED);
|
|
267
|
+
// A signed settings change or status read is accepted for this long after
|
|
268
|
+
// its issuedAt, the window of rename.
|
|
269
|
+
export const GAME_REQUEST_MAX_AGE_SEC = RENAME_MAX_AGE_SEC;
|
|
270
|
+
// PUT /v1/game/settings, signed by the agent's own key. issuedAt is signed
|
|
271
|
+
// with the change, so an old envelope sent again cannot undo a newer one.
|
|
272
|
+
export const SignedGameSettingsRequest = z
|
|
273
|
+
.strictObject({ ...gameChange, issuedAt: Timestamp })
|
|
274
|
+
.refine(someGameChange, GAME_CHANGE_REQUIRED);
|
|
275
|
+
// The body of a signed game request, POST /v1/game/status, PUT
|
|
276
|
+
// /v1/game/settings and every agent route under /v1/duels. Its payloads
|
|
277
|
+
// are small, so it takes the event envelope cap.
|
|
278
|
+
export const SignedGameRequest = z.strictObject({
|
|
279
|
+
envelope: Jws.max(MAX_ENVELOPE_CHARS),
|
|
280
|
+
});
|
|
281
|
+
// POST /v1/game/status, the agent's signed read of its own game settings
|
|
282
|
+
// and the units it used today. issuedAt bounds how long a captured envelope
|
|
283
|
+
// could be sent again.
|
|
284
|
+
export const GameStatusRequest = z.strictObject({ issuedAt: Timestamp });
|
|
285
|
+
// The game settings as GET /v1/me shows them per agent.
|
|
286
|
+
export const GameSettingsView = z.strictObject({
|
|
287
|
+
enabled: z.boolean(),
|
|
288
|
+
cap: GameCap,
|
|
289
|
+
});
|
|
290
|
+
// The answer of the status read and of both settings writes. usedToday is
|
|
291
|
+
// the game units the agent used in the current UTC day, and resetAt the
|
|
292
|
+
// next 00:00:00 UTC, when it starts again from 0.
|
|
293
|
+
export const GameStatusResponse = z.strictObject({
|
|
294
|
+
enabled: z.boolean(),
|
|
295
|
+
cap: GameCap,
|
|
296
|
+
usedToday: z.int().min(0).max(GAME_CAP_MAX),
|
|
297
|
+
resetAt: Timestamp,
|
|
298
|
+
});
|
|
299
|
+
// GET /v1/game/categories (VOU-474), the categories a duel or a weekly
|
|
300
|
+
// challenge can be in, in the order of TASK_CATEGORIES. A category is
|
|
301
|
+
// duelable when a template in it has a server solver and makes tasks that
|
|
302
|
+
// count as seed tasks (GAME_TEMPLATES in apps/api/src/game/tasks.ts).
|
|
303
|
+
// Wrapped in an object, so a later field can be added beside the list.
|
|
304
|
+
export const GameCategoriesResponse = z.strictObject({
|
|
305
|
+
categories: z
|
|
306
|
+
.array(z.strictObject({ category: TaskCategory }))
|
|
307
|
+
.max(TASK_CATEGORIES.length),
|
|
308
|
+
});
|
|
309
|
+
/*
|
|
310
|
+
* GET /v1/agents/:id/game and GET /v1/agents/:slug/:name/game (VOU-478,
|
|
311
|
+
* GAME-11), the public game summary of one agent, for the profile's Game
|
|
312
|
+
* section. enabled is the operator's game switch. The rest is answered
|
|
313
|
+
* with the game off too, so the agent's history stays visible. streak is
|
|
314
|
+
* the agent answer's currentStreak and bestStreak. keeper lists only the
|
|
315
|
+
* categories with a keeper rank, empty until GAME-6. ratings has one entry
|
|
316
|
+
* per category with a rated duel, provisional below GAME.provisionalDuels.
|
|
317
|
+
* record counts the agent's finished duels, wins and losses without the
|
|
318
|
+
* forfeits, which forfeitWins and forfeitLosses count. recentDuels are the
|
|
319
|
+
* last GAME.recentDuels finished duels, newest decided first, the opponent
|
|
320
|
+
* as its handle and the result from this agent's side. badges are the
|
|
321
|
+
* newest GAME.summaryBadges from weekly challenges. Never a task id, a
|
|
322
|
+
* spec or an answer. Game only, so nothing here is on the SEAL or adds to
|
|
323
|
+
* Trust.
|
|
324
|
+
*/
|
|
325
|
+
export const GameSummaryResponse = z.strictObject({
|
|
326
|
+
enabled: z.boolean(),
|
|
327
|
+
streak: z.strictObject({
|
|
328
|
+
current: z.int().min(0),
|
|
329
|
+
best: z.int().min(0),
|
|
330
|
+
}),
|
|
331
|
+
keeper: z
|
|
332
|
+
.array(z.strictObject({
|
|
333
|
+
category: StoredTaskCategory,
|
|
334
|
+
score: z.number().min(0).max(1),
|
|
335
|
+
rank: KeeperRank,
|
|
336
|
+
}))
|
|
337
|
+
.max(STORED_TASK_CATEGORIES.length),
|
|
338
|
+
ratings: z
|
|
339
|
+
.array(z.strictObject({
|
|
340
|
+
category: TaskCategory,
|
|
341
|
+
rating: z.int(),
|
|
342
|
+
ratedDuels: z.int().min(0),
|
|
343
|
+
provisional: z.boolean(),
|
|
344
|
+
}))
|
|
345
|
+
.max(TASK_CATEGORIES.length),
|
|
346
|
+
record: z.strictObject({
|
|
347
|
+
wins: z.int().min(0),
|
|
348
|
+
losses: z.int().min(0),
|
|
349
|
+
draws: z.int().min(0),
|
|
350
|
+
forfeitWins: z.int().min(0),
|
|
351
|
+
forfeitLosses: z.int().min(0),
|
|
352
|
+
}),
|
|
353
|
+
recentDuels: z
|
|
354
|
+
.array(z.strictObject({
|
|
355
|
+
id: z.uuid(),
|
|
356
|
+
opponent: AgentHandle,
|
|
357
|
+
category: TaskCategory,
|
|
358
|
+
result: z.enum(['win', 'loss', 'draw']),
|
|
359
|
+
forfeit: z.boolean(),
|
|
360
|
+
decidedAt: Timestamp,
|
|
361
|
+
}))
|
|
362
|
+
.max(GAME.recentDuels),
|
|
363
|
+
badges: z
|
|
364
|
+
.array(z.strictObject({ kind: GameBadgeKind, isoWeek: IsoWeek }))
|
|
365
|
+
.max(GAME.summaryBadges),
|
|
366
|
+
});
|
|
215
367
|
// The slug half of a handle as a caller writes it, in a URL, a query or
|
|
216
368
|
// tasks post --for. Letters, digits and hyphens, at most OPERATOR_SLUG_MAX.
|
|
217
369
|
// Slugs are lowercase and the API lowercases this before it looks one up,
|
|
@@ -256,9 +408,9 @@ function keysetCursor(id, isId = () => true) {
|
|
|
256
408
|
return { encode, decode };
|
|
257
409
|
}
|
|
258
410
|
// A query parameter that holds a cursor, 400 for one the API did not make.
|
|
259
|
-
const cursorParam = (decode) => z
|
|
411
|
+
const cursorParam = (decode, max = 128) => z
|
|
260
412
|
.string()
|
|
261
|
-
.max(
|
|
413
|
+
.max(max)
|
|
262
414
|
.transform((raw, ctx) => {
|
|
263
415
|
const cursor = decode(raw);
|
|
264
416
|
if (cursor)
|
|
@@ -266,7 +418,9 @@ const cursorParam = (decode) => z
|
|
|
266
418
|
ctx.addIssue({ code: 'custom', message: 'Invalid cursor' });
|
|
267
419
|
return z.NEVER;
|
|
268
420
|
});
|
|
269
|
-
|
|
421
|
+
// An agent id's text, as a cursor holds it.
|
|
422
|
+
const AGENT_ID_SOURCE = '[A-Za-z0-9_-]{43}';
|
|
423
|
+
const agentsKeyset = keysetCursor(new RegExp(AGENT_ID_SOURCE), (id) => AgentId.safeParse(id).success);
|
|
270
424
|
export function encodeAgentsCursor(cursor) {
|
|
271
425
|
return agentsKeyset.encode({ micros: cursor.createdAtMicros, id: cursor.id });
|
|
272
426
|
}
|
|
@@ -275,21 +429,100 @@ export function decodeAgentsCursor(raw) {
|
|
|
275
429
|
const c = agentsKeyset.decode(raw);
|
|
276
430
|
return c && { createdAtMicros: c.micros, id: c.id };
|
|
277
431
|
}
|
|
278
|
-
|
|
279
|
-
|
|
432
|
+
// The orders of the directory (VOU-568, UI-34). newest, the default, is
|
|
433
|
+
// newest registration first on (created_at, id). name is by name, then id,
|
|
434
|
+
// A to Z, since a name is unique per operator only. trust is by the
|
|
435
|
+
// agent's overall Trust Score, highest first and ties to the lower id, the
|
|
436
|
+
// all time board's order (trust_boards), then the agents with no Trust
|
|
437
|
+
// Score, newest first.
|
|
438
|
+
export const AgentsSort = z.enum(['trust', 'name', 'newest']);
|
|
439
|
+
const NAME_CURSOR = new RegExp(`^name\\.(${AGENT_ID_SOURCE})\\.(.+)$`, 's');
|
|
440
|
+
const BOARD_CURSOR = new RegExp(`^trust\\.([0-9.e+-]{1,32})\\.(${AGENT_ID_SOURCE})$`);
|
|
441
|
+
const REST_CURSOR = new RegExp(`^trust-rest\\.(\\d{1,18})\\.(${AGENT_ID_SOURCE})$`);
|
|
442
|
+
export function encodeAgentsListCursor(cursor) {
|
|
443
|
+
switch (cursor.sort) {
|
|
444
|
+
case 'newest':
|
|
445
|
+
return encodeAgentsCursor(cursor);
|
|
446
|
+
case 'name':
|
|
447
|
+
return base64urlEncode(utf8Encode(`name.${cursor.id}.${cursor.name}`));
|
|
448
|
+
case 'trust':
|
|
449
|
+
return base64urlEncode(utf8Encode(cursor.half === 'board'
|
|
450
|
+
? `trust.${cursor.score}.${cursor.id}`
|
|
451
|
+
: `trust-rest.${cursor.createdAtMicros}.${cursor.id}`));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Null for anything that is not a cursor this API made. A score is the
|
|
455
|
+
// shortest text of a positive finite number, as String writes it, so it
|
|
456
|
+
// reads back as the same double.
|
|
457
|
+
export function decodeAgentsListCursor(raw) {
|
|
458
|
+
const newest = decodeAgentsCursor(raw);
|
|
459
|
+
if (newest)
|
|
460
|
+
return { sort: 'newest', ...newest };
|
|
461
|
+
let text;
|
|
462
|
+
try {
|
|
463
|
+
text = utf8Decode(base64urlDecode(raw));
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
const isId = (id) => id !== undefined && AgentId.safeParse(id).success;
|
|
469
|
+
const name = NAME_CURSOR.exec(text);
|
|
470
|
+
if (name) {
|
|
471
|
+
return isId(name[1]) && Name.safeParse(name[2]).success
|
|
472
|
+
? { sort: 'name', id: name[1], name: name[2] }
|
|
473
|
+
: null;
|
|
474
|
+
}
|
|
475
|
+
const board = BOARD_CURSOR.exec(text);
|
|
476
|
+
if (board) {
|
|
477
|
+
const score = Number(board[1]);
|
|
478
|
+
return isId(board[2]) &&
|
|
479
|
+
Number.isFinite(score) &&
|
|
480
|
+
score > 0 &&
|
|
481
|
+
String(score) === board[1]
|
|
482
|
+
? { sort: 'trust', half: 'board', score, id: board[2] }
|
|
483
|
+
: null;
|
|
484
|
+
}
|
|
485
|
+
const rest = REST_CURSOR.exec(text);
|
|
486
|
+
if (rest?.[1] && isId(rest[2])) {
|
|
487
|
+
return {
|
|
488
|
+
sort: 'trust',
|
|
489
|
+
half: 'rest',
|
|
490
|
+
createdAtMicros: rest[1],
|
|
491
|
+
id: rest[2],
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
// Room for a name cursor with a name of 64 characters that are not ASCII.
|
|
497
|
+
export const AgentsCursorParam = cursorParam(decodeAgentsListCursor, 512);
|
|
498
|
+
// sort is left out of the parsed query when the caller sends none, which
|
|
499
|
+
// reads as newest, so an answer to a request from before is unchanged.
|
|
500
|
+
export const AgentsListQuery = z
|
|
501
|
+
.strictObject({
|
|
280
502
|
limit: Limit,
|
|
281
503
|
cursor: AgentsCursorParam.optional(),
|
|
282
504
|
// Only the agents of the operator with this slug. Case does not matter.
|
|
283
505
|
operator: HandleSlug.optional(),
|
|
506
|
+
sort: AgentsSort.optional(),
|
|
507
|
+
})
|
|
508
|
+
.superRefine((q, ctx) => {
|
|
509
|
+
if (q.cursor && q.cursor.sort !== (q.sort ?? 'newest')) {
|
|
510
|
+
ctx.addIssue({
|
|
511
|
+
code: 'custom',
|
|
512
|
+
path: ['cursor'],
|
|
513
|
+
message: 'Invalid cursor',
|
|
514
|
+
});
|
|
515
|
+
}
|
|
284
516
|
});
|
|
285
517
|
export const TopScore = z.strictObject({
|
|
286
|
-
dimension:
|
|
518
|
+
dimension: Dimension,
|
|
287
519
|
value: z.number().min(0).max(1),
|
|
288
520
|
});
|
|
289
521
|
// One agent in the directory. lastSeenAt is the newest received_at of its
|
|
290
|
-
// events, null when it has sent none. topScores holds at most two
|
|
291
|
-
// dimensions on the current version,
|
|
292
|
-
//
|
|
522
|
+
// events, null when it has sent none. topScores holds at most two trust
|
|
523
|
+
// dimensions on the current version, base or competence category
|
|
524
|
+
// (topDimensions), highest first, only those with a value. cost_latency is
|
|
525
|
+
// never one of them. verifiedTasks and seedTasks are the
|
|
293
526
|
// same counts as on AgentResponse. level and standing are as on
|
|
294
527
|
// AgentResponse, left out until the current version has been scored.
|
|
295
528
|
export const AgentSummary = z.strictObject({
|
|
@@ -313,17 +546,31 @@ export const AgentSummary = z.strictObject({
|
|
|
313
546
|
// As on AgentResponse. The API always sends it. Optional so the web reads
|
|
314
547
|
// an older API's answer.
|
|
315
548
|
avatarUrl: z.url().optional(),
|
|
549
|
+
// The Trust Score of the current version, rounded, 0 with none, as trust
|
|
550
|
+
// on TrustResponse (VOU-568). week is the Trust its verified tasks added
|
|
551
|
+
// on each of the last TRUST_SCORE.deltaDays UTC days, oldest first and
|
|
552
|
+
// today last, rounded per day, the days delta7d sums. The API always
|
|
553
|
+
// sends both. Optional so the web reads an older API's answer.
|
|
554
|
+
trust: z.int().min(0).optional(),
|
|
555
|
+
week: z.array(z.int().min(0)).length(TRUST_SCORE.deltaDays).optional(),
|
|
316
556
|
});
|
|
317
557
|
// nextCursor is null on the last page.
|
|
318
558
|
export const AgentsListResponse = z.strictObject({
|
|
319
559
|
agents: z.array(AgentSummary),
|
|
320
560
|
nextCursor: z.string().nullable(),
|
|
321
561
|
});
|
|
562
|
+
// fingerprint is the signing agent's fingerprint as its CLI last computed
|
|
563
|
+
// it (VB-4), a JWS signed by the same agent over FingerprintDeclaration in
|
|
564
|
+
// ./fingerprint.ts. The API refuses one that does not verify, names
|
|
565
|
+
// another agent or whose hash is not the hash of its parts, and keeps it
|
|
566
|
+
// as the agent's current fingerprint when it was captured later than the
|
|
567
|
+
// stored one. Optional, so a CLI from before it syncs unchanged.
|
|
322
568
|
export const EventsBatchRequest = z.strictObject({
|
|
323
569
|
envelopes: z
|
|
324
570
|
.array(Jws.max(MAX_ENVELOPE_CHARS))
|
|
325
571
|
.min(1)
|
|
326
572
|
.max(MAX_EVENTS_PER_BATCH),
|
|
573
|
+
fingerprint: Jws.max(MAX_ENVELOPE_CHARS).optional(),
|
|
327
574
|
});
|
|
328
575
|
// accepted counts rows newly stored. duplicates counts events already stored
|
|
329
576
|
// for this agent (same event_id), which are skipped, so a retry is safe.
|
|
@@ -354,12 +601,44 @@ export const AgentRef = z.union([
|
|
|
354
601
|
// routine anything the unattended daily routine sends. Absent means manual.
|
|
355
602
|
// All three count toward bronze and silver. Gold counts a confirmed task
|
|
356
603
|
// only when its post is manual and neither outcome report is routine.
|
|
357
|
-
export const
|
|
604
|
+
export const TASK_ORIGINS = ['manual', 'template', 'routine'];
|
|
605
|
+
export const TaskOrigin = z.enum(TASK_ORIGINS);
|
|
606
|
+
/*
|
|
607
|
+
* The origins a stored task can have, the three a payload declares and two
|
|
608
|
+
* only the server sets (VOU-468). duel and challenge mark a game task the
|
|
609
|
+
* server makes for a duel or a weekly challenge (D-GAME-5, D-GAME-11). No
|
|
610
|
+
* signed payload can declare them, so an agent cannot mark its own post a
|
|
611
|
+
* game task, and every signed request and task filter reads TaskOrigin.
|
|
612
|
+
* The tasks row and the task answers read this list. GAME_TASK_ORIGINS is
|
|
613
|
+
* the two game origins, which every open pool and public task list leaves
|
|
614
|
+
* out (VOU-474, notGameTask in apps/api/src/routes/tasks.ts).
|
|
615
|
+
*/
|
|
616
|
+
export const GAME_TASK_ORIGINS = ['duel', 'challenge'];
|
|
617
|
+
export const STORED_TASK_ORIGINS = [
|
|
618
|
+
...TASK_ORIGINS,
|
|
619
|
+
...GAME_TASK_ORIGINS,
|
|
620
|
+
];
|
|
621
|
+
export const StoredTaskOrigin = z.enum(STORED_TASK_ORIGINS);
|
|
358
622
|
// taskId is generated by the client and becomes the task id, so a retried
|
|
359
623
|
// post is a no-op. The same poster gets the existing task back, any other
|
|
360
624
|
// poster gets 409. assignee addresses the task to one agent of another
|
|
361
625
|
// operator, resolved to its id at post. Only that agent can claim it, and it
|
|
362
626
|
// counts at reduced weight as evidence. Without it the task is open to all.
|
|
627
|
+
//
|
|
628
|
+
// The real task fields (RT-2) are optional. The API stores what the post
|
|
629
|
+
// says, else what derivedTaskFields gives from the type and the spec, so an
|
|
630
|
+
// older CLI posts without them. disclosure sealed and verdict_only are
|
|
631
|
+
// reserved, and the API refuses them with 400 disclosure_reserved.
|
|
632
|
+
//
|
|
633
|
+
// category reads the stored list, not the six that can be chosen, since a
|
|
634
|
+
// CLI from before D-UI-12 still posts conversation or other and must keep
|
|
635
|
+
// working. This CLI offers the six only.
|
|
636
|
+
//
|
|
637
|
+
// difficulty (D-TS-3) is the poster's, a whole number from 1 to 5, and
|
|
638
|
+
// TASK_DIFFICULTY_DEFAULT when the post leaves it out, or the template's
|
|
639
|
+
// own for a template task type. From DIFFICULTY_CONFIRM_MIN up only a
|
|
640
|
+
// counterparty task may carry it, and the API refuses it on a hash or
|
|
641
|
+
// schema task with 400 difficulty_needs_confirm.
|
|
363
642
|
export const PostTaskRequest = z.strictObject({
|
|
364
643
|
taskId: z.uuid(),
|
|
365
644
|
taskType: TaskType,
|
|
@@ -368,6 +647,55 @@ export const PostTaskRequest = z.strictObject({
|
|
|
368
647
|
expiresAt: Timestamp.optional(),
|
|
369
648
|
assignee: AgentRef.optional(),
|
|
370
649
|
origin: TaskOrigin.optional(),
|
|
650
|
+
category: StoredTaskCategory.optional(),
|
|
651
|
+
size: TaskSize.optional(),
|
|
652
|
+
difficulty: TaskDifficulty.optional(),
|
|
653
|
+
disclosure: Disclosure.optional(),
|
|
654
|
+
inputRef: TaskInputRef.optional(),
|
|
655
|
+
outputShape: TaskOutputShape.optional(),
|
|
656
|
+
});
|
|
657
|
+
/*
|
|
658
|
+
* An adoption (RT-11, D-RT-2), the other payload POST /v1/tasks takes. The
|
|
659
|
+
* agent names a category and the API picks a candidate in it, a task with a
|
|
660
|
+
* known answer SealKeeper made from the shared templates, and posts it as
|
|
661
|
+
* the agent's own open template task. So the payload carries no task type,
|
|
662
|
+
* spec or verification, the spec reaches only the agent that adopted it,
|
|
663
|
+
* and the known answer never leaves the server. origin is template, the
|
|
664
|
+
* default, or routine for a routine run's adoption (RT-12), so an adopted
|
|
665
|
+
* task carries the same origin as any other post of its kind. taskId and
|
|
666
|
+
* expiresAt work as in PostTaskRequest. category is one of the six that
|
|
667
|
+
* can be chosen, since an adoption chooses and no CLI ever had a candidate
|
|
668
|
+
* in conversation or other to adopt.
|
|
669
|
+
*/
|
|
670
|
+
export const AdoptTaskRequest = z.strictObject({
|
|
671
|
+
taskId: z.uuid(),
|
|
672
|
+
category: TaskCategory,
|
|
673
|
+
expiresAt: Timestamp.optional(),
|
|
674
|
+
origin: z.enum(['template', 'routine']).optional(),
|
|
675
|
+
});
|
|
676
|
+
// A task post always has a task type, a spec and a verification, and an
|
|
677
|
+
// adoption has none of them.
|
|
678
|
+
const ADOPTION_ABSENT = ['taskType', 'spec', 'verification'];
|
|
679
|
+
const isAdoption = (value) => typeof value === 'object' &&
|
|
680
|
+
value !== null &&
|
|
681
|
+
ADOPTION_ABSENT.every((key) => !Object.hasOwn(value, key));
|
|
682
|
+
/*
|
|
683
|
+
* The payload POST /v1/tasks parses. One with none of taskType, spec and
|
|
684
|
+
* verification is an adoption and parses as AdoptTaskRequest, any other as
|
|
685
|
+
* PostTaskRequest, so each failure names its own fields rather than a
|
|
686
|
+
* union's. A transform that picks the schema, shown once. The issues of
|
|
687
|
+
* the schema it picked are copied onto this one, paths and all.
|
|
688
|
+
*/
|
|
689
|
+
export const PostTaskPayload = z.unknown().transform((value, ctx) => {
|
|
690
|
+
const parsed = isAdoption(value)
|
|
691
|
+
? AdoptTaskRequest.safeParse(value)
|
|
692
|
+
: PostTaskRequest.safeParse(value);
|
|
693
|
+
if (parsed.success)
|
|
694
|
+
return parsed.data;
|
|
695
|
+
for (const issue of parsed.error.issues) {
|
|
696
|
+
ctx.addIssue({ ...issue, input: value });
|
|
697
|
+
}
|
|
698
|
+
return z.NEVER;
|
|
371
699
|
});
|
|
372
700
|
// The agent an addressed task is for, as it is now.
|
|
373
701
|
export const TaskAssignee = z.strictObject({
|
|
@@ -377,6 +705,28 @@ export const TaskAssignee = z.strictObject({
|
|
|
377
705
|
// from an API before runtimes parses.
|
|
378
706
|
runtime: Runtime.optional(),
|
|
379
707
|
});
|
|
708
|
+
/*
|
|
709
|
+
* The real task fields (RT-1), on TaskResponse and TaskView alike. category,
|
|
710
|
+
* checkMethod, disclosure, size and difficulty always have a value, and the
|
|
711
|
+
* other three are null when the task does not say. The API always sends all
|
|
712
|
+
* eight. Each is optional, so an answer from an API before them parses.
|
|
713
|
+
*/
|
|
714
|
+
const realTaskFields = {
|
|
715
|
+
// Any stored category, conversation and other included, since a task
|
|
716
|
+
// keeps the category it was scored under.
|
|
717
|
+
category: StoredTaskCategory.optional(),
|
|
718
|
+
checkMethod: CheckMethod.optional(),
|
|
719
|
+
disclosure: Disclosure.optional(),
|
|
720
|
+
size: TaskSize.optional(),
|
|
721
|
+
// How hard the task is, 1 to 5, set by its poster (D-TS-3).
|
|
722
|
+
difficulty: TaskDifficulty.optional(),
|
|
723
|
+
// UTF-8 bytes of the task's input.
|
|
724
|
+
inputBytes: z.number().int().min(0).nullable().optional(),
|
|
725
|
+
// A link to a public input the task reads.
|
|
726
|
+
inputRef: z.string().nullable().optional(),
|
|
727
|
+
// A short statement of what the answer looks like.
|
|
728
|
+
outputShape: z.string().nullable().optional(),
|
|
729
|
+
};
|
|
380
730
|
export const TaskResponse = z.strictObject({
|
|
381
731
|
id: z.uuid(),
|
|
382
732
|
posterAgentId: AgentId,
|
|
@@ -396,22 +746,72 @@ export const TaskResponse = z.strictObject({
|
|
|
396
746
|
submittedAt: Timestamp.nullable(),
|
|
397
747
|
verifiedAt: Timestamp.nullable(),
|
|
398
748
|
expiresAt: Timestamp,
|
|
749
|
+
// When the poster of a counterparty task let its response time lapse and
|
|
750
|
+
// the claimant's success report stood, which verified the task (POST-4).
|
|
751
|
+
// Null otherwise. Optional so the CLI still parses an API from before it.
|
|
752
|
+
posterLapsedAt: Timestamp.nullable().optional(),
|
|
399
753
|
// True when the seed agent posted the task, so a CLI tells seed tasks
|
|
400
754
|
// apart without looking the poster up. A CLI reads an API from before it
|
|
401
755
|
// as not saying.
|
|
402
756
|
seed: z.boolean(),
|
|
757
|
+
...realTaskFields,
|
|
758
|
+
// Where the post came from, manual when the poster's CLI did not say, so
|
|
759
|
+
// a routine finds other operators' template tasks (RT-8), and duel or
|
|
760
|
+
// challenge for a game task the server made. The API always sends it.
|
|
761
|
+
// Optional so the CLI still parses an API from before it, and the CLI
|
|
762
|
+
// reads it as any string, so a new origin never fails its parse.
|
|
763
|
+
origin: StoredTaskOrigin.optional(),
|
|
764
|
+
// The poster as it is now, in list answers only (GET /v1/tasks and POST
|
|
765
|
+
// /v1/tasks/open). level is its current version's level from the last
|
|
766
|
+
// scoring run, left out until it is scored, so a routine tells a poster
|
|
767
|
+
// at bronze or above without a lookup (RT-8). Optional so the CLI still
|
|
768
|
+
// parses an API from before it.
|
|
769
|
+
poster: z
|
|
770
|
+
.strictObject({
|
|
771
|
+
handle: AgentHandle,
|
|
772
|
+
level: Level.optional(),
|
|
773
|
+
operatedBySealKeeper: z.boolean(),
|
|
774
|
+
})
|
|
775
|
+
.optional(),
|
|
403
776
|
// Present only in responses to the poster or the claimant.
|
|
404
777
|
submission: z.string().optional(),
|
|
778
|
+
// True in the answer to POST /v1/tasks/:id/release, whose signing agent's
|
|
779
|
+
// claim on the task has ended (VOU-572). Absent everywhere else.
|
|
780
|
+
// Optional, so the CLI still parses every other answer and one from an
|
|
781
|
+
// API before it.
|
|
782
|
+
released: z.literal(true).optional(),
|
|
405
783
|
});
|
|
406
784
|
// Claim and submit payloads name the task they are for, and the server checks
|
|
407
785
|
// it against the path. Without it a signed claim for one task could be
|
|
408
|
-
// replayed against any other.
|
|
409
|
-
|
|
786
|
+
// replayed against any other. origin is the claimant's, TaskOrigin above,
|
|
787
|
+
// routine when the unattended daily routine claims, which waits out the
|
|
788
|
+
// claim age threshold (RT-8). Absent means manual.
|
|
789
|
+
//
|
|
790
|
+
// fingerprint, on claim, submit and outcome, is the signing agent's
|
|
791
|
+
// fingerprint as its CLI last computed it (VB-3), Fingerprint in
|
|
792
|
+
// ./fingerprint.ts. The API stores it with the claim, replaces it with one
|
|
793
|
+
// a submit declares, keeps it when a submit declares none, and keeps the
|
|
794
|
+
// reporter's with each outcome report. It refuses one
|
|
795
|
+
// whose hash is not the hash of its parts. Absent means not declared, as
|
|
796
|
+
// from a CLI from before it.
|
|
797
|
+
export const ClaimTaskRequest = z.strictObject({
|
|
798
|
+
taskId: z.uuid(),
|
|
799
|
+
origin: TaskOrigin.optional(),
|
|
800
|
+
fingerprint: Fingerprint.optional(),
|
|
801
|
+
});
|
|
410
802
|
export const SubmitTaskRequest = z.strictObject({
|
|
411
803
|
taskId: z.uuid(),
|
|
412
804
|
submission: z
|
|
413
805
|
.string()
|
|
414
806
|
.refine((text) => utf8Encode(JSON.stringify(text)).length <= MAX_SUBMISSION_BYTES, `submission must be at most ${MAX_SUBMISSION_BYTES} bytes as JSON`),
|
|
807
|
+
fingerprint: Fingerprint.optional(),
|
|
808
|
+
});
|
|
809
|
+
// POST /v1/tasks/:id/release, the claimant giving its claim back
|
|
810
|
+
// (VOU-572). Named for its task like claim and submit, checked against the
|
|
811
|
+
// path. A captured envelope cannot end a later claim, since the release
|
|
812
|
+
// bars its agent from the task.
|
|
813
|
+
export const ReleaseTaskRequest = z.strictObject({
|
|
814
|
+
taskId: z.uuid(),
|
|
415
815
|
});
|
|
416
816
|
// Named for its task like claim and submit, checked against the path.
|
|
417
817
|
// origin is the reporting side's, TaskOrigin above.
|
|
@@ -420,6 +820,7 @@ export const TaskOutcomeRequest = z.strictObject({
|
|
|
420
820
|
outcome: TaskOutcome,
|
|
421
821
|
evidenceHash: Sha256Hex.optional(),
|
|
422
822
|
origin: TaskOrigin.optional(),
|
|
823
|
+
fingerprint: Fingerprint.optional(),
|
|
423
824
|
});
|
|
424
825
|
// POST /v1/tasks/:id/submission, the poster's signed read of its task with
|
|
425
826
|
// the submission and both outcome reports. The public GET leaves the
|
|
@@ -462,7 +863,9 @@ const QueryFlag = z.union([
|
|
|
462
863
|
// assignee is given. assignee keeps the tasks addressed to that agent, in
|
|
463
864
|
// the state asked for. poster and claimant keep one agent's tasks on that
|
|
464
865
|
// side. seed true keeps only the tasks the seed agent posted and false
|
|
465
|
-
// leaves them out.
|
|
866
|
+
// leaves them out. origin keeps the tasks posted with that origin, so a
|
|
867
|
+
// routine reads other operators' template tasks without paging past every
|
|
868
|
+
// manual one (RT-8). Without these a caller reads one global page and a
|
|
466
869
|
// flood of older tasks can hide the ones it wants (VOU-208).
|
|
467
870
|
export const ListTasksQuery = z.strictObject({
|
|
468
871
|
state: TaskState.default('open'),
|
|
@@ -471,6 +874,7 @@ export const ListTasksQuery = z.strictObject({
|
|
|
471
874
|
poster: AgentId.optional(),
|
|
472
875
|
claimant: AgentId.optional(),
|
|
473
876
|
seed: QueryFlag.optional(),
|
|
877
|
+
origin: TaskOrigin.optional(),
|
|
474
878
|
limit: Limit,
|
|
475
879
|
cursor: TasksCursorParam.optional(),
|
|
476
880
|
});
|
|
@@ -483,6 +887,7 @@ export const ListTasksQuery = z.strictObject({
|
|
|
483
887
|
export const OpenTasksRequest = z.strictObject({
|
|
484
888
|
taskType: TaskType.optional(),
|
|
485
889
|
seed: z.boolean().optional(),
|
|
890
|
+
origin: TaskOrigin.optional(),
|
|
486
891
|
limit: Limit,
|
|
487
892
|
cursor: TasksCursorParam.optional(),
|
|
488
893
|
issuedAt: Timestamp,
|
|
@@ -492,14 +897,209 @@ export const ListTasksResponse = z.strictObject({
|
|
|
492
897
|
tasks: z.array(TaskResponse),
|
|
493
898
|
nextCursor: z.string().nullable(),
|
|
494
899
|
});
|
|
900
|
+
/*
|
|
901
|
+
* Duels (VOU-475, GAME-8, D-GAME-4, D-GAME-7), under /v1/duels. Every
|
|
902
|
+
* agent route is signed with the envelope of the other game routes, body
|
|
903
|
+
* SignedGameRequest { envelope }, and each payload carries issuedAt, which
|
|
904
|
+
* bounds how long a captured envelope could be sent again
|
|
905
|
+
* (GAME_REQUEST_MAX_AGE_SEC). A write names the seek or duel it was signed
|
|
906
|
+
* for, checked against the path. The reads are POSTs, as every signed read
|
|
907
|
+
* is, since a GET has no body for the envelope.
|
|
908
|
+
*/
|
|
909
|
+
// POST /v1/duels/seek, an open call for a duel in one category.
|
|
910
|
+
export const SeekDuelRequest = z.strictObject({
|
|
911
|
+
category: TaskCategory,
|
|
912
|
+
issuedAt: Timestamp,
|
|
913
|
+
});
|
|
914
|
+
// DELETE /v1/duels/seek/:id, the agent cancels its own open seek.
|
|
915
|
+
export const CancelSeekRequest = z.strictObject({
|
|
916
|
+
seekId: z.uuid(),
|
|
917
|
+
issuedAt: Timestamp,
|
|
918
|
+
});
|
|
919
|
+
// POST /v1/duels/challenge, an invite to one agent by id or handle
|
|
920
|
+
// slug/name, as a post's assignee is named.
|
|
921
|
+
export const ChallengeDuelRequest = z.strictObject({
|
|
922
|
+
opponent: AgentRef,
|
|
923
|
+
category: TaskCategory,
|
|
924
|
+
issuedAt: Timestamp,
|
|
925
|
+
});
|
|
926
|
+
// POST /v1/duels/:id/rematch, /accept and /decline, named for the duel.
|
|
927
|
+
export const DuelActionRequest = z.strictObject({
|
|
928
|
+
duelId: z.uuid(),
|
|
929
|
+
issuedAt: Timestamp,
|
|
930
|
+
});
|
|
931
|
+
// POST /v1/duels/mine, the agent's own duels in one state, active by
|
|
932
|
+
// default, from both sides, newest first on (created_at, id), paged with
|
|
933
|
+
// the task list's cursor.
|
|
934
|
+
export const ListDuelsRequest = z.strictObject({
|
|
935
|
+
state: DuelState.default('active'),
|
|
936
|
+
limit: Limit,
|
|
937
|
+
cursor: TasksCursorParam.optional(),
|
|
938
|
+
issuedAt: Timestamp,
|
|
939
|
+
});
|
|
940
|
+
// POST /v1/duels/inbox, the invites to the agent still waiting for its
|
|
941
|
+
// answer, newest first, paged the same way.
|
|
942
|
+
export const DuelInboxRequest = z.strictObject({
|
|
943
|
+
limit: Limit,
|
|
944
|
+
cursor: TasksCursorParam.optional(),
|
|
945
|
+
issuedAt: Timestamp,
|
|
946
|
+
});
|
|
947
|
+
// GET /v1/me/agents/:id/duels (VOU-581), the operator's read of one of its
|
|
948
|
+
// agents' duels with the session cookie, paged as POST /mine. state is
|
|
949
|
+
// one POST /mine takes, active by default, except invited, which is the
|
|
950
|
+
// inbox, the invites to the agent still waiting for its answer.
|
|
951
|
+
export const MyDuelsQuery = z.strictObject({
|
|
952
|
+
state: DuelState.default('active'),
|
|
953
|
+
limit: Limit,
|
|
954
|
+
cursor: TasksCursorParam.optional(),
|
|
955
|
+
});
|
|
956
|
+
export const DuelParams = z.strictObject({ id: z.uuid() });
|
|
957
|
+
// One side of a duel. taskId is the side's own copy of the duel task, sent
|
|
958
|
+
// only in a signed answer to that side once the duel started, so no other
|
|
959
|
+
// agent can find and claim it or read its spec before the duel.
|
|
960
|
+
export const DuelSideView = z.strictObject({
|
|
961
|
+
agentId: AgentId,
|
|
962
|
+
handle: AgentHandle,
|
|
963
|
+
taskId: z.uuid().optional(),
|
|
964
|
+
});
|
|
965
|
+
// A duel as GET /v1/duels/:id shows it to anyone, and the signed routes
|
|
966
|
+
// to a side with its taskId. The timestamps and the result are null until
|
|
967
|
+
// the duel reaches them. deadlineAt is the end of the 48 hour window.
|
|
968
|
+
export const DuelResponse = z.strictObject({
|
|
969
|
+
id: z.uuid(),
|
|
970
|
+
category: TaskCategory,
|
|
971
|
+
state: DuelState,
|
|
972
|
+
origin: DuelOrigin,
|
|
973
|
+
challenger: DuelSideView,
|
|
974
|
+
opponent: DuelSideView,
|
|
975
|
+
rematchOf: z.uuid().nullable(),
|
|
976
|
+
invitedAt: Timestamp.nullable(),
|
|
977
|
+
startedAt: Timestamp.nullable(),
|
|
978
|
+
deadlineAt: Timestamp.nullable(),
|
|
979
|
+
decidedAt: Timestamp.nullable(),
|
|
980
|
+
result: DuelResult.nullable(),
|
|
981
|
+
forfeit: z.boolean(),
|
|
982
|
+
});
|
|
983
|
+
// nextCursor is null on the last page.
|
|
984
|
+
export const ListDuelsResponse = z.strictObject({
|
|
985
|
+
duels: z.array(DuelResponse),
|
|
986
|
+
nextCursor: z.string().nullable(),
|
|
987
|
+
});
|
|
988
|
+
// A seek as its agent sees it. duelId is the duel a match started.
|
|
989
|
+
export const DuelSeekView = z.strictObject({
|
|
990
|
+
id: z.uuid(),
|
|
991
|
+
category: TaskCategory,
|
|
992
|
+
state: DuelSeekState,
|
|
993
|
+
expiresAt: Timestamp,
|
|
994
|
+
duelId: z.uuid().nullable(),
|
|
995
|
+
});
|
|
996
|
+
// The answer of POST /v1/duels/seek and DELETE /v1/duels/seek/:id. duel
|
|
997
|
+
// is the duel the seek started when it matched on the spot, with the
|
|
998
|
+
// agent's taskId.
|
|
999
|
+
export const SeekDuelResponse = z.strictObject({
|
|
1000
|
+
seek: DuelSeekView,
|
|
1001
|
+
duel: DuelResponse.optional(),
|
|
1002
|
+
});
|
|
1003
|
+
/*
|
|
1004
|
+
* Weekly challenges (VOU-479, GAME-12, D-GAME-11), under /v1/challenges.
|
|
1005
|
+
* The agent routes are signed like the duel routes, body SignedGameRequest
|
|
1006
|
+
* { envelope }, each payload with issuedAt within GAME_REQUEST_MAX_AGE_SEC.
|
|
1007
|
+
*/
|
|
1008
|
+
// POST /v1/challenges/current, the agent's signed read of the current
|
|
1009
|
+
// week's challenge, and POST /v1/challenges/current/enter, its entry.
|
|
1010
|
+
export const ChallengeRequest = z.strictObject({ issuedAt: Timestamp });
|
|
1011
|
+
// Where one of an entrant's challenge tasks stands. submitted once its
|
|
1012
|
+
// one answer is in, correct true for a pass and false for a fail, and
|
|
1013
|
+
// null before.
|
|
1014
|
+
export const CHALLENGE_TASK_STATES = [
|
|
1015
|
+
'unclaimed',
|
|
1016
|
+
'claimed',
|
|
1017
|
+
'submitted',
|
|
1018
|
+
];
|
|
1019
|
+
export const ChallengeTaskState = z.enum(CHALLENGE_TASK_STATES);
|
|
1020
|
+
export const ChallengeTaskView = z.strictObject({
|
|
1021
|
+
taskId: z.uuid(),
|
|
1022
|
+
state: ChallengeTaskState,
|
|
1023
|
+
correct: z.boolean().nullable(),
|
|
1024
|
+
});
|
|
1025
|
+
// The current week's challenge as its agent sees it. entered says whether
|
|
1026
|
+
// the agent has an entry, and tasks are its own, empty without one. rank
|
|
1027
|
+
// is its live place, null until it has submitted.
|
|
1028
|
+
export const CurrentChallengeResponse = z.strictObject({
|
|
1029
|
+
isoWeek: IsoWeek,
|
|
1030
|
+
category: TaskCategory,
|
|
1031
|
+
closesAt: Timestamp,
|
|
1032
|
+
entered: z.boolean(),
|
|
1033
|
+
rank: z.int().min(1).nullable(),
|
|
1034
|
+
tasks: z.array(ChallengeTaskView).max(CHALLENGE_TASKS),
|
|
1035
|
+
});
|
|
1036
|
+
// GET /v1/challenges/:isoWeek/leaderboard, public, a week by its ISO week
|
|
1037
|
+
// or current for the week that holds now, at most limit rows.
|
|
1038
|
+
export const ChallengeBoardParams = z.strictObject({
|
|
1039
|
+
isoWeek: z.union([z.literal('current'), IsoWeek]),
|
|
1040
|
+
});
|
|
1041
|
+
export const ChallengeBoardQuery = z.strictObject({ limit: Limit });
|
|
1042
|
+
// One ranked entry, its place, the agent, its correct answers and their
|
|
1043
|
+
// total server time.
|
|
1044
|
+
export const ChallengeBoardRow = z.strictObject({
|
|
1045
|
+
rank: z.int().min(1),
|
|
1046
|
+
agent: z.strictObject({ agentId: AgentId, name: Name, handle: AgentHandle }),
|
|
1047
|
+
correct: z.int().min(0).max(CHALLENGE_TASKS),
|
|
1048
|
+
serverMs: z.int().min(0),
|
|
1049
|
+
});
|
|
1050
|
+
// The week's board, live while it is open and its final ranks once it
|
|
1051
|
+
// closed. entrants counts the entries with a submission, the ranked ones,
|
|
1052
|
+
// at most GAME.challengeRankMax.
|
|
1053
|
+
export const ChallengeBoardResponse = z.strictObject({
|
|
1054
|
+
isoWeek: IsoWeek,
|
|
1055
|
+
category: TaskCategory,
|
|
1056
|
+
state: ChallengeState,
|
|
1057
|
+
closesAt: Timestamp,
|
|
1058
|
+
entrants: z.int().min(0),
|
|
1059
|
+
rows: z.array(ChallengeBoardRow),
|
|
1060
|
+
});
|
|
1061
|
+
// One of the operator's agents with an entry in the week, its rank and its
|
|
1062
|
+
// tasks as CurrentChallengeResponse shows them to the agent. rank is the
|
|
1063
|
+
// live place while the week is open and the final one once it closed, null
|
|
1064
|
+
// for an entry with no submission.
|
|
1065
|
+
export const MyChallengeEntry = z.strictObject({
|
|
1066
|
+
agentId: AgentId,
|
|
1067
|
+
rank: z.int().min(1).nullable(),
|
|
1068
|
+
tasks: z.array(ChallengeTaskView).max(CHALLENGE_TASKS),
|
|
1069
|
+
});
|
|
1070
|
+
// GET /v1/me/challenges/:isoWeek (VOU-584), the signed in operator's
|
|
1071
|
+
// entries in one week, current or an ISO week as the board takes it, one
|
|
1072
|
+
// per agent with an entry, ordered by agent name, empty with none.
|
|
1073
|
+
export const MyChallengeResponse = z.strictObject({
|
|
1074
|
+
isoWeek: IsoWeek,
|
|
1075
|
+
category: TaskCategory,
|
|
1076
|
+
state: ChallengeState,
|
|
1077
|
+
closesAt: Timestamp,
|
|
1078
|
+
entries: z.array(MyChallengeEntry),
|
|
1079
|
+
});
|
|
495
1080
|
// The public task views the web shows, GET /v1/tasks/board, GET
|
|
496
1081
|
// /v1/tasks/:id/view and GET /v1/agents/:id/tasks. TaskResponse above is
|
|
497
1082
|
// the CLI's shape. These carry the two sides as handles and never a
|
|
498
1083
|
// submission. verification is always the public spec.
|
|
1084
|
+
/*
|
|
1085
|
+
* Which tasks the board lists (VOU-569), from the task's timestamps as
|
|
1086
|
+
* taskState reads them, since no column holds a state. open is a live task
|
|
1087
|
+
* nobody claimed. claimed is a live claim, submitted or not, so it keeps
|
|
1088
|
+
* the TaskState values claimed and submitted. verified is every verified
|
|
1089
|
+
* task, newest verified first. all is every task, expired ones included.
|
|
1090
|
+
* The others list newest posted first.
|
|
1091
|
+
*/
|
|
1092
|
+
export const TaskBoardState = z.enum(['open', 'claimed', 'verified', 'all']);
|
|
499
1093
|
// GET /v1/tasks/board. Open tasks, newest first, addressed ones included
|
|
500
|
-
// with their assignee.
|
|
1094
|
+
// with their assignee, unless state asks for others. A cursor pages the
|
|
1095
|
+
// state it came from.
|
|
1096
|
+
// category filters stored tasks, so it takes any stored category.
|
|
1097
|
+
// minDifficulty keeps the tasks at that difficulty or above, 3, 4 or 5.
|
|
501
1098
|
export const TaskBoardQuery = z.strictObject({
|
|
502
1099
|
taskType: TaskType.optional(),
|
|
1100
|
+
category: StoredTaskCategory.optional(),
|
|
1101
|
+
state: TaskBoardState.default('open'),
|
|
1102
|
+
minDifficulty: MinDifficultyParam.optional(),
|
|
503
1103
|
limit: Limit,
|
|
504
1104
|
cursor: TasksCursorParam.optional(),
|
|
505
1105
|
});
|
|
@@ -512,6 +1112,50 @@ export const AgentTasksQuery = z.strictObject({
|
|
|
512
1112
|
limit: Limit,
|
|
513
1113
|
cursor: TasksCursorParam.optional(),
|
|
514
1114
|
});
|
|
1115
|
+
// Why the issuer withholds a SEAL for cause, the classes of the SEAL
|
|
1116
|
+
// standard, section 6 (VOU-85), and why it voids a task's credit (COL-5).
|
|
1117
|
+
// The class is public, on the SEAL routes' 404, in the feed and on a
|
|
1118
|
+
// voided task. The detail never is. The agent_holds.reason and
|
|
1119
|
+
// task_voids.reason check constraints list the same values.
|
|
1120
|
+
export const HoldReason = z.enum([
|
|
1121
|
+
'fraud',
|
|
1122
|
+
'collusion',
|
|
1123
|
+
'safety',
|
|
1124
|
+
'operator_ban',
|
|
1125
|
+
'key_compromise',
|
|
1126
|
+
]);
|
|
1127
|
+
/*
|
|
1128
|
+
* Why a verified task that counts adds less than its weight, or nothing,
|
|
1129
|
+
* named after the step of counted evidence that cut it (VOU-139, VOU-296,
|
|
1130
|
+
* SEAL standard section 4). daily_ceiling when
|
|
1131
|
+
* COUNTED_EVIDENCE.dailyCeiling heavier, more valuable or earlier tasks of
|
|
1132
|
+
* the claimant counted that UTC day, so it adds nothing. diminishing when
|
|
1133
|
+
* it is past the first COUNTED_EVIDENCE.diminishingK of its category (its
|
|
1134
|
+
* seed task type, or its poster's operator). pass_rate when its type
|
|
1135
|
+
* weighed less than 1 on the day it verified (tasks.type_weight).
|
|
1136
|
+
* pair_curve when it is past the first COUNTED_EVIDENCE.pairCurve.free
|
|
1137
|
+
* tasks of its operator pair in the pair window. share_cap when the share
|
|
1138
|
+
* cap took part or all of it. outside_window when it was verified before
|
|
1139
|
+
* the 180 day window. When more than one of diminishing, pass_rate,
|
|
1140
|
+
* pair_curve and share_cap cut a task, the note names the one that kept
|
|
1141
|
+
* the smallest share of what reached it, which is the one that took the
|
|
1142
|
+
* most, the later in the standard's order on a tie. The category curve
|
|
1143
|
+
* counts as a cut only past the first COUNTED_EVIDENCE.diminishingK, so
|
|
1144
|
+
* below that place a later step names the note even when the curve took
|
|
1145
|
+
* more. The scoring run
|
|
1146
|
+
* decides every note but outside_window and stores it on the task
|
|
1147
|
+
* (tasks.counted_note, whose check constraint lists the same values but
|
|
1148
|
+
* outside_window). The released web shows no line for a note it does not
|
|
1149
|
+
* know, so a new value needs no new web first.
|
|
1150
|
+
*/
|
|
1151
|
+
export const CountedNote = z.enum([
|
|
1152
|
+
'daily_ceiling',
|
|
1153
|
+
'diminishing',
|
|
1154
|
+
'pass_rate',
|
|
1155
|
+
'pair_curve',
|
|
1156
|
+
'share_cap',
|
|
1157
|
+
'outside_window',
|
|
1158
|
+
]);
|
|
515
1159
|
// One side of a task, as it is now.
|
|
516
1160
|
export const TaskParty = z.strictObject({
|
|
517
1161
|
id: AgentId,
|
|
@@ -522,9 +1166,18 @@ export const TaskParty = z.strictObject({
|
|
|
522
1166
|
// The level of the agent's current version from the last scoring run,
|
|
523
1167
|
// for the name line. Left out until the agent has been scored.
|
|
524
1168
|
level: Level.optional(),
|
|
525
|
-
// True for an agent SealKeeper runs itself, the seed agent
|
|
1169
|
+
// True for an agent SealKeeper runs itself, the seed agent that posts the
|
|
1170
|
+
// seed tasks or the taker that completes template tasks nobody else took
|
|
1171
|
+
// (TAKER-5). The taker is only ever a claimant.
|
|
526
1172
|
operatedBySealKeeper: z.boolean(),
|
|
527
1173
|
});
|
|
1174
|
+
// What a task can earn, TaskView.worth. min is a routine pass, at the
|
|
1175
|
+
// credit a counterparty task's poster below silver pays (VOU-574), and max
|
|
1176
|
+
// the most any claimant can get at the difficulty the poster gave.
|
|
1177
|
+
export const TaskWorth = z.strictObject({
|
|
1178
|
+
min: z.int().min(0),
|
|
1179
|
+
max: z.int().min(0),
|
|
1180
|
+
});
|
|
528
1181
|
export const TaskView = z.strictObject({
|
|
529
1182
|
id: z.uuid(),
|
|
530
1183
|
taskType: TaskType,
|
|
@@ -538,8 +1191,10 @@ export const TaskView = z.strictObject({
|
|
|
538
1191
|
// The one agent that can claim an addressed task. Null for an open task,
|
|
539
1192
|
// and after the assignee agent was deleted.
|
|
540
1193
|
assignee: TaskParty.nullable(),
|
|
541
|
-
// Where the post came from, manual when the poster's CLI did not say
|
|
542
|
-
|
|
1194
|
+
// Where the post came from, manual when the poster's CLI did not say,
|
|
1195
|
+
// duel or challenge for a game task the server made.
|
|
1196
|
+
origin: StoredTaskOrigin,
|
|
1197
|
+
...realTaskFields,
|
|
543
1198
|
// The scoring rule. True when the poster is the seed agent or an agent of
|
|
544
1199
|
// another operator than the claimant, false when both sides share an
|
|
545
1200
|
// operator or the task is addressed and came past its operator pair's
|
|
@@ -556,26 +1211,160 @@ export const TaskView = z.strictObject({
|
|
|
556
1211
|
// false, null when it is null, and null for a task verified since its
|
|
557
1212
|
// claimant's last scoring run, until that run.
|
|
558
1213
|
counted: z.number().min(0).max(1).nullable(),
|
|
559
|
-
// Why a task that counts adds less than its weight, or nothing
|
|
560
|
-
//
|
|
561
|
-
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
1214
|
+
// Why a task that counts adds less than its weight, or nothing
|
|
1215
|
+
// (CountedNote), as the last scoring run decided it. Null otherwise.
|
|
1216
|
+
countedNote: CountedNote.nullable(),
|
|
1217
|
+
// The base credit the task earned as it verified (VOU-498), 10 times the
|
|
1218
|
+
// multiplier of its difficulty times the novelty bonus (TRUST_CREDIT),
|
|
1219
|
+
// before the counted value and the decay, so not what it adds to Trust
|
|
1220
|
+
// Score. Null while unverified, for a task that counts for nothing and
|
|
1221
|
+
// for one verified before the issuer stored it. Optional so the web
|
|
1222
|
+
// still reads an answer from an older API.
|
|
1223
|
+
baseCredit: z.number().min(0).nullable().optional(),
|
|
1224
|
+
// What the task can earn, whole numbers of Trust (VOU-569, taskWorth in
|
|
1225
|
+
// the API). min is a routine pass, its difficulty's base credit with no
|
|
1226
|
+
// novelty bonus. max is the same with the most novelty bonus a claimant
|
|
1227
|
+
// can get at that difficulty, the one of an agent whose average is 1.
|
|
1228
|
+
// Both at a counted value of 1 and before any decay. It names no agent,
|
|
1229
|
+
// so it is the same for every reader. Optional so the web still reads an
|
|
1230
|
+
// answer from an older API.
|
|
1231
|
+
worth: TaskWorth.optional(),
|
|
1232
|
+
// The Trust the task added as its claimant's last scoring run counted
|
|
1233
|
+
// it, its base credit times its counted value, the amount the roll-up
|
|
1234
|
+
// put on the claimant's day, rounded to a whole number. Absent until the
|
|
1235
|
+
// run stored a counted value, and while counted is null. Optional so the
|
|
1236
|
+
// web still reads an answer from an older API.
|
|
1237
|
+
earned: z.int().min(0).optional(),
|
|
1238
|
+
// The reason class of a void in force on the task (COL-5), which makes it
|
|
1239
|
+
// weigh 0 and count for nothing on either side. Absent without one. Never
|
|
1240
|
+
// the note. Optional so the web still reads an answer from an older API.
|
|
1241
|
+
voided: HoldReason.optional(),
|
|
568
1242
|
postedAt: Timestamp,
|
|
569
1243
|
claimedAt: Timestamp.nullable(),
|
|
570
1244
|
submittedAt: Timestamp.nullable(),
|
|
571
1245
|
verifiedAt: Timestamp.nullable(),
|
|
572
1246
|
expiresAt: Timestamp,
|
|
1247
|
+
// When each side of a counterparty task first reported its outcome, null
|
|
1248
|
+
// until it reports, and always null for a hash or schema task. A later
|
|
1249
|
+
// report replaces the outcome and keeps this time. Never the outcome
|
|
1250
|
+
// itself. Optional so the web still reads an answer from an older API.
|
|
1251
|
+
posterReportedAt: Timestamp.nullable().optional(),
|
|
1252
|
+
claimantReportedAt: Timestamp.nullable().optional(),
|
|
1253
|
+
// When the poster of a counterparty task let its response time lapse and
|
|
1254
|
+
// the claimant's success report stood, which verified the task (POST-4),
|
|
1255
|
+
// so it equals verifiedAt. Null otherwise. Optional so the web still
|
|
1256
|
+
// reads an answer from an older API.
|
|
1257
|
+
posterLapsedAt: Timestamp.nullable().optional(),
|
|
1258
|
+
// How many claims on the task ended before it verified and gave it back,
|
|
1259
|
+
// released by their claimant (VOU-572) or ended at the failed submit
|
|
1260
|
+
// cap, from the issuer's own record. 0 when none. Optional so the web
|
|
1261
|
+
// still reads an answer from an older API.
|
|
1262
|
+
releasedClaims: z.number().int().min(0).optional(),
|
|
573
1263
|
});
|
|
574
1264
|
// nextCursor is null on the last page.
|
|
575
1265
|
export const TaskViewsResponse = z.strictObject({
|
|
576
1266
|
tasks: z.array(TaskView),
|
|
577
1267
|
nextCursor: z.string().nullable(),
|
|
578
1268
|
});
|
|
1269
|
+
/*
|
|
1270
|
+
* One number a block moved (VOU-573), as the scoring run had stored it when
|
|
1271
|
+
* the block opened (before) and once it could take no more tasks (after).
|
|
1272
|
+
* before is absent when it could not be read as the block opened, and a
|
|
1273
|
+
* rank either side when the agent had no place on the board then.
|
|
1274
|
+
*/
|
|
1275
|
+
export const BlockMove = z.strictObject({
|
|
1276
|
+
before: z.int().min(0).optional(),
|
|
1277
|
+
after: z.int().min(0).optional(),
|
|
1278
|
+
});
|
|
1279
|
+
/*
|
|
1280
|
+
* What a block moved (VOU-573, UI project), written once when the block
|
|
1281
|
+
* could take no more tasks and never changed, so every read answers the
|
|
1282
|
+
* same. trust is the agent's Trust Score, whole, rank its place on the
|
|
1283
|
+
* weekly Trust board across all categories of the week the block started,
|
|
1284
|
+
* and streak its day streak. No race lead, since there is no Race yet.
|
|
1285
|
+
*/
|
|
1286
|
+
export const BlockMoved = z.strictObject({
|
|
1287
|
+
trust: BlockMove,
|
|
1288
|
+
rank: BlockMove,
|
|
1289
|
+
streak: BlockMove,
|
|
1290
|
+
});
|
|
1291
|
+
/*
|
|
1292
|
+
* Blocks (D-UI-9, VOU-550, BLOCK in ./blocks.ts), a run of verified tasks
|
|
1293
|
+
* by one agent in one category, close together in time. GET
|
|
1294
|
+
* /v1/blocks/:id, GET /v1/agents/:id/blocks and GET /v1/blocks/latest.
|
|
1295
|
+
* Every time on a block is a server time.
|
|
1296
|
+
*/
|
|
1297
|
+
export const BlockView = z.strictObject({
|
|
1298
|
+
id: z.uuid(),
|
|
1299
|
+
// The agent whose work it is, as it is now.
|
|
1300
|
+
agent: TaskParty,
|
|
1301
|
+
category: StoredTaskCategory,
|
|
1302
|
+
// blockTitle of startedAt (./blocks.ts), worked out by the API, never
|
|
1303
|
+
// stored. Any text, so a band added later reads in an older client.
|
|
1304
|
+
title: z.string().min(1),
|
|
1305
|
+
// The verified_at of its first task and of its latest.
|
|
1306
|
+
startedAt: Timestamp,
|
|
1307
|
+
endedAt: Timestamp,
|
|
1308
|
+
taskCount: z.int().min(1).max(BLOCK.maxTasks),
|
|
1309
|
+
// The base credit its tasks stored as they verified (TaskView.baseCredit),
|
|
1310
|
+
// summed, before the counted value and the decay, so not what the block
|
|
1311
|
+
// adds to Trust Score.
|
|
1312
|
+
creditTotal: z.number().min(0),
|
|
1313
|
+
difficultySum: z.int().min(1),
|
|
1314
|
+
// Rejections of the agent's work in the block's category submitted while
|
|
1315
|
+
// it was open, less those the poster withdrew.
|
|
1316
|
+
rejectedCount: z.int().min(0),
|
|
1317
|
+
// The handles of the posters who confirmed its tasks, each once, in the
|
|
1318
|
+
// order a task of theirs first joined, at most BLOCK.confirmers
|
|
1319
|
+
// (VOU-583). A poster confirms a task with the confirm check it did not
|
|
1320
|
+
// let lapse, so a hash or schema task, which the server checked, and a
|
|
1321
|
+
// task that stood on the claimant's report add no one. Stored as its
|
|
1322
|
+
// tasks join and shown by each poster's handle as it is now. Optional so
|
|
1323
|
+
// the web still reads an answer from an older API.
|
|
1324
|
+
confirmedBy: z.array(AgentHandle).max(BLOCK.confirmers).optional(),
|
|
1325
|
+
// What the block moved (VOU-573), absent until it can take no more tasks
|
|
1326
|
+
// and on a block from before it was stored. Optional, so an answer from
|
|
1327
|
+
// an older API parses.
|
|
1328
|
+
moved: BlockMoved.optional(),
|
|
1329
|
+
// On GET /v1/blocks/latest only, the agent's numbers for the live hero
|
|
1330
|
+
// card (VOU-573), from the stored trust_days rows and the agent row.
|
|
1331
|
+
// streak is its day streak, dayTasks its verified tasks a day over the
|
|
1332
|
+
// last TRUST_SCORE.seriesDays UTC days oldest first, today last, and
|
|
1333
|
+
// monthTasks and monthTrust their totals, Trust whole, before the decay.
|
|
1334
|
+
// Optional, so an answer from an older API parses.
|
|
1335
|
+
streak: z.int().min(0).optional(),
|
|
1336
|
+
dayTasks: z.array(z.int().min(0)).length(TRUST_SCORE.seriesDays).optional(),
|
|
1337
|
+
monthTasks: z.int().min(0).optional(),
|
|
1338
|
+
monthTrust: z.int().min(0).optional(),
|
|
1339
|
+
});
|
|
1340
|
+
export const BlockParams = z.strictObject({ id: z.uuid() });
|
|
1341
|
+
// GET /v1/blocks/:id. The block and its tasks in the order they verified,
|
|
1342
|
+
// at most BLOCK.maxTasks. Each task names its poster and its check method,
|
|
1343
|
+
// so a reader sees who confirmed it, the server for a hash or schema task
|
|
1344
|
+
// and the poster for a counterparty one, or no one when the poster let its
|
|
1345
|
+
// response time lapse (posterLapsedAt).
|
|
1346
|
+
export const BlockResponse = z.strictObject({
|
|
1347
|
+
block: BlockView,
|
|
1348
|
+
tasks: z.array(TaskView),
|
|
1349
|
+
});
|
|
1350
|
+
// GET /v1/agents/:id/blocks, newest started first, paged on
|
|
1351
|
+
// (started_at, id) with the cursor from nextCursor, the task list cursor.
|
|
1352
|
+
export const AgentBlocksQuery = z.strictObject({
|
|
1353
|
+
limit: Limit,
|
|
1354
|
+
cursor: TasksCursorParam.optional(),
|
|
1355
|
+
});
|
|
1356
|
+
// nextCursor is null on the last page.
|
|
1357
|
+
export const AgentBlocksResponse = z.strictObject({
|
|
1358
|
+
blocks: z.array(BlockView),
|
|
1359
|
+
nextCursor: z.string().nullable(),
|
|
1360
|
+
});
|
|
1361
|
+
// GET /v1/blocks/latest takes no query parameters.
|
|
1362
|
+
export const LatestBlocksQuery = z.strictObject({});
|
|
1363
|
+
// GET /v1/blocks/latest. The blocks most recently added to on the network,
|
|
1364
|
+
// newest first, at most BLOCK.latest.
|
|
1365
|
+
export const LatestBlocksResponse = z.strictObject({
|
|
1366
|
+
blocks: z.array(BlockView).max(BLOCK.latest),
|
|
1367
|
+
});
|
|
579
1368
|
// The body of a signed rating. A rating payload is small, so it takes the
|
|
580
1369
|
// event envelope cap.
|
|
581
1370
|
export const SignedRatingRequest = z.strictObject({
|
|
@@ -599,8 +1388,19 @@ export const RatingResponse = z.strictObject({
|
|
|
599
1388
|
value: RatingValue,
|
|
600
1389
|
raterScoreAtTime: z.number().min(0).max(1),
|
|
601
1390
|
});
|
|
1391
|
+
// One task type's competence under its category (RT-3), verified over
|
|
1392
|
+
// claimed for that type on the version's own signal. A breakdown for the
|
|
1393
|
+
// profile, never a dimension and never in a SEAL.
|
|
1394
|
+
export const CompetenceTypeScore = z.strictObject({
|
|
1395
|
+
taskType: TaskType,
|
|
1396
|
+
value: z.number().min(0).max(1),
|
|
1397
|
+
});
|
|
602
1398
|
// A base dimension with no row is still listed, with every field after
|
|
603
|
-
// dimension null. Competence entries appear only where a row exists
|
|
1399
|
+
// dimension null. Competence entries appear only where a row exists, one
|
|
1400
|
+
// per category. types is the per task type breakdown of a competence
|
|
1401
|
+
// entry, highest claimed first, at most COMPETENCE_TYPES_MAX. It is left
|
|
1402
|
+
// out on base entries and on a competence entry the version only
|
|
1403
|
+
// inherited, since a breakdown is never fabricated from no signal.
|
|
604
1404
|
export const ScoreEntry = z.strictObject({
|
|
605
1405
|
version: StoredVersion,
|
|
606
1406
|
dimension: Dimension,
|
|
@@ -608,12 +1408,215 @@ export const ScoreEntry = z.strictObject({
|
|
|
608
1408
|
windowStart: Timestamp.nullable(),
|
|
609
1409
|
windowEnd: Timestamp.nullable(),
|
|
610
1410
|
computedAt: Timestamp.nullable(),
|
|
1411
|
+
types: z.array(CompetenceTypeScore).max(COMPETENCE_TYPES_MAX).optional(),
|
|
611
1412
|
});
|
|
612
1413
|
// The agent's current version only.
|
|
613
1414
|
export const ScoreResponse = z.strictObject({
|
|
614
1415
|
agentId: AgentId,
|
|
615
1416
|
scores: z.array(ScoreEntry),
|
|
616
1417
|
});
|
|
1418
|
+
// An agent's place on the Trust leaderboards (TS-7, VOU-502), each counted
|
|
1419
|
+
// through the board's index (TS-11) up to TRUST_SCORE.rankMax, 1 at the
|
|
1420
|
+
// top. Absent without a row on the board or past rankMax. week is this UTC
|
|
1421
|
+
// week's board and weekChange the places moved against last week's, up
|
|
1422
|
+
// positive, absent without a rank on both.
|
|
1423
|
+
export const TrustRanks = z.strictObject({
|
|
1424
|
+
week: z.int().min(1).optional(),
|
|
1425
|
+
weekChange: z.int().optional(),
|
|
1426
|
+
allTime: z.int().min(1).optional(),
|
|
1427
|
+
});
|
|
1428
|
+
// One category of the current version's Trust Score, as the scoring run
|
|
1429
|
+
// stored it (trust_categories). tasks is its verified tasks that count,
|
|
1430
|
+
// weighed and rounded down as verified_tasks is, which silver's
|
|
1431
|
+
// categories read.
|
|
1432
|
+
export const TrustCategoryView = z.strictObject({
|
|
1433
|
+
category: StoredTaskCategory,
|
|
1434
|
+
trust: z.int().min(0),
|
|
1435
|
+
tasks: z.int().min(0),
|
|
1436
|
+
rank: TrustRanks.omit({ weekChange: true }).optional(),
|
|
1437
|
+
});
|
|
1438
|
+
// One UTC day of the series, what the day's verified tasks added on any
|
|
1439
|
+
// version, each task's base credit times its counted value with no decay,
|
|
1440
|
+
// as the scoring run last saw them (trust_days). A day with no task is 0,
|
|
1441
|
+
// 0 and a null difficulty, the mean of the day's tasks otherwise.
|
|
1442
|
+
export const TrustDayView = z.strictObject({
|
|
1443
|
+
date: z.iso.date(),
|
|
1444
|
+
trust: z.int().min(0),
|
|
1445
|
+
tasks: z.int().min(0),
|
|
1446
|
+
difficulty: z.number().min(1).max(5).nullable(),
|
|
1447
|
+
});
|
|
1448
|
+
/*
|
|
1449
|
+
* GET /v1/agents/:id/trust (TS-7, VOU-502). Public. The rows the scoring
|
|
1450
|
+
* run stored, never a sum of tasks. Whole numbers of Trust, rounded.
|
|
1451
|
+
*
|
|
1452
|
+
* trust is the Trust Score of the current version (standing.trust_score),
|
|
1453
|
+
* 0 for an agent with no verified task, before its first run and on a row
|
|
1454
|
+
* written before Trust Score was stored. delta7d is the Trust the last
|
|
1455
|
+
* TRUST_SCORE.deltaDays days of days added, today included, before
|
|
1456
|
+
* penalties and before the fade of older work, so it is never below 0.
|
|
1457
|
+
* categories are the version's, highest first, empty with no verified
|
|
1458
|
+
* task. days are the last TRUST_SCORE.seriesDays UTC days, oldest first,
|
|
1459
|
+
* each there with zeros when nothing verified. operator is the agent's
|
|
1460
|
+
* operator's number (operator_trust), absent before a run wrote it, with
|
|
1461
|
+
* change7d (VOU-582), that number now less the same roll up over its
|
|
1462
|
+
* agents' Trust seven days back, each agent's Trust less its delta7d from
|
|
1463
|
+
* the same trust_days rows, absent when none of its agents has a day of
|
|
1464
|
+
* trust_days before those seven, so there is no Trust seven days back to
|
|
1465
|
+
* change from. held
|
|
1466
|
+
* is true while a hold withholds the SEAL (holds.ts), read live as the
|
|
1467
|
+
* agent answer reads it, and the numbers are shown as they are.
|
|
1468
|
+
*/
|
|
1469
|
+
export const TrustResponse = z.strictObject({
|
|
1470
|
+
agentId: AgentId,
|
|
1471
|
+
version: StoredVersion,
|
|
1472
|
+
trust: z.int().min(0),
|
|
1473
|
+
delta7d: z.int(),
|
|
1474
|
+
rank: TrustRanks.optional(),
|
|
1475
|
+
categories: z.array(TrustCategoryView).max(STORED_TASK_CATEGORIES.length),
|
|
1476
|
+
days: z.array(TrustDayView).length(TRUST_SCORE.seriesDays),
|
|
1477
|
+
operator: z
|
|
1478
|
+
.strictObject({
|
|
1479
|
+
trust: z.int().min(0),
|
|
1480
|
+
agents: z.int().min(0),
|
|
1481
|
+
change7d: z.int().min(0).optional(),
|
|
1482
|
+
})
|
|
1483
|
+
.optional(),
|
|
1484
|
+
held: z.boolean().optional(),
|
|
1485
|
+
});
|
|
1486
|
+
/*
|
|
1487
|
+
* The models agents run and the changes they declare (VOU-551, UI-23).
|
|
1488
|
+
* Every number here is a report the nightly run stored. None of it moves a
|
|
1489
|
+
* Trust Score, a category score, stored credit or a level, and the model
|
|
1490
|
+
* names are what agents say about themselves, never proof.
|
|
1491
|
+
*/
|
|
1492
|
+
// A model's key, modelKey of a declared name, as the stored rows hold it.
|
|
1493
|
+
const ModelKey = z.string().min(1).max(64);
|
|
1494
|
+
// One side of one category, as the run counted it. verified is the tasks
|
|
1495
|
+
// that count for Trust, failed the claims ended at the failed submit cap
|
|
1496
|
+
// or released after a failed submit (a clean release counts nowhere,
|
|
1497
|
+
// VOU-577), rejected the
|
|
1498
|
+
// rejection penalties, and credit the average stored base credit of the
|
|
1499
|
+
// verified ones, null when none stores one.
|
|
1500
|
+
export const ModelComparisonSideView = z.strictObject({
|
|
1501
|
+
verified: z.int().min(0),
|
|
1502
|
+
failed: z.int().min(0),
|
|
1503
|
+
rejected: z.int().min(0),
|
|
1504
|
+
credit: z.number().nullable(),
|
|
1505
|
+
});
|
|
1506
|
+
// One category of a comparison. Both sides and the verdict, or, below
|
|
1507
|
+
// MODEL_COMPARISON.minTasks verified tasks on either side, the two
|
|
1508
|
+
// verified counts alone and insufficient.
|
|
1509
|
+
export const ModelComparisonCategoryView = z.union([
|
|
1510
|
+
z.strictObject({
|
|
1511
|
+
before: ModelComparisonSideView,
|
|
1512
|
+
after: ModelComparisonSideView,
|
|
1513
|
+
verdict: z.enum(['better', 'worse', 'same']),
|
|
1514
|
+
}),
|
|
1515
|
+
z.strictObject({
|
|
1516
|
+
before: z.strictObject({ verified: z.int().min(0) }),
|
|
1517
|
+
after: z.strictObject({ verified: z.int().min(0) }),
|
|
1518
|
+
verdict: z.literal('insufficient'),
|
|
1519
|
+
}),
|
|
1520
|
+
]);
|
|
1521
|
+
// A window of a comparison, from inclusive and to exclusive.
|
|
1522
|
+
const ModelWindowView = z.strictObject({ from: Timestamp, to: Timestamp });
|
|
1523
|
+
// One declared change of an agent's model. date is the UTC day of at, the
|
|
1524
|
+
// server's time when the change arrived. from and to are the names as the
|
|
1525
|
+
// agent declared them, null where it declared none, and the same name on
|
|
1526
|
+
// both sides for a change only the fingerprint's model part showed.
|
|
1527
|
+
// verdicts is each of the six categories' verdict from the stored
|
|
1528
|
+
// comparison, absent until the nightly run first compares the change.
|
|
1529
|
+
// final is true once the comparison's after window is
|
|
1530
|
+
// MODEL_COMPARISON.afterDays long. Until then a verdict reads the days
|
|
1531
|
+
// since the change so far, and one a later change replaced stays so, so a
|
|
1532
|
+
// page tells a partial verdict from a final one.
|
|
1533
|
+
export const ModelChangeView = z.strictObject({
|
|
1534
|
+
date: z.iso.date(),
|
|
1535
|
+
at: Timestamp,
|
|
1536
|
+
from: Name.nullable(),
|
|
1537
|
+
to: Name.nullable(),
|
|
1538
|
+
final: z.boolean(),
|
|
1539
|
+
verdicts: z.record(TaskCategory, ModelVerdict).optional(),
|
|
1540
|
+
});
|
|
1541
|
+
// GET /v1/agents/:id/model-changes. Public. The agent's declared changes,
|
|
1542
|
+
// newest first, at most MODEL_NETWORK.changes.
|
|
1543
|
+
export const ModelChangesResponse = z.strictObject({
|
|
1544
|
+
changes: z.array(ModelChangeView).max(MODEL_NETWORK.changes),
|
|
1545
|
+
});
|
|
1546
|
+
// GET /v1/agents/:id/model-changes/:date, the date a UTC day. The years
|
|
1547
|
+
// are bounded, since Postgres reads no year 0 and no day past 9999, and
|
|
1548
|
+
// no change is older than SealKeeper.
|
|
1549
|
+
export const ModelChangeParams = z.strictObject({
|
|
1550
|
+
id: AgentId,
|
|
1551
|
+
date: z.iso
|
|
1552
|
+
.date()
|
|
1553
|
+
.refine((d) => d >= '2026-01-01' && d <= '9998-12-31', 'A date from 2026 to 9998'),
|
|
1554
|
+
});
|
|
1555
|
+
/*
|
|
1556
|
+
* GET /v1/agents/:id/model-changes/:date. Public. The agent's newest
|
|
1557
|
+
* change of that UTC day with its comparison as the nightly run stored
|
|
1558
|
+
* it. window and categories are absent until the run first compares the
|
|
1559
|
+
* change, and categories holds all six. final is true once the after
|
|
1560
|
+
* window is MODEL_COMPARISON.afterDays long. The run compares only an
|
|
1561
|
+
* agent's newest change, so one a later change replaced keeps the
|
|
1562
|
+
* comparison it had and may stay not final.
|
|
1563
|
+
*/
|
|
1564
|
+
export const ModelChangeResponse = z.strictObject({
|
|
1565
|
+
date: z.iso.date(),
|
|
1566
|
+
at: Timestamp,
|
|
1567
|
+
from: Name.nullable(),
|
|
1568
|
+
to: Name.nullable(),
|
|
1569
|
+
final: z.boolean(),
|
|
1570
|
+
window: z
|
|
1571
|
+
.strictObject({ before: ModelWindowView, after: ModelWindowView })
|
|
1572
|
+
.optional(),
|
|
1573
|
+
categories: z.record(TaskCategory, ModelComparisonCategoryView).optional(),
|
|
1574
|
+
});
|
|
1575
|
+
// How many agents of a network event read better, worse or same in one
|
|
1576
|
+
// category. An insufficient comparison counts in none.
|
|
1577
|
+
export const ModelVerdictCountsView = z.strictObject({
|
|
1578
|
+
better: z.int().min(0),
|
|
1579
|
+
worse: z.int().min(0),
|
|
1580
|
+
same: z.int().min(0),
|
|
1581
|
+
});
|
|
1582
|
+
const ModelVerdictsView = z.record(TaskCategory, ModelVerdictCountsView);
|
|
1583
|
+
// A model agents run now. name is the newest declared name of the key and
|
|
1584
|
+
// firstSeen the UTC day it was first seen. operators is the distinct
|
|
1585
|
+
// operators of its agents (VOU-578), optional so the web still reads an
|
|
1586
|
+
// answer from an older API. verdicts is the latest network event to it,
|
|
1587
|
+
// present only when the model has MODEL_NETWORK.minAgents agents or more
|
|
1588
|
+
// and such an event exists.
|
|
1589
|
+
export const ModelView = z.strictObject({
|
|
1590
|
+
key: ModelKey,
|
|
1591
|
+
name: Name,
|
|
1592
|
+
agents: z.int().min(1),
|
|
1593
|
+
operators: z.int().min(1).optional(),
|
|
1594
|
+
firstSeen: z.iso.date(),
|
|
1595
|
+
verdicts: ModelVerdictsView.optional(),
|
|
1596
|
+
});
|
|
1597
|
+
// A change of model that MODEL_NETWORK.minAgents agents or more, of
|
|
1598
|
+
// minOperators operators or more, made in the UTC week from weekStart, a
|
|
1599
|
+
// Monday. agents and the verdicts count at most perOperator agents of one
|
|
1600
|
+
// operator (VOU-578). from and to are the two keys, which the models list names.
|
|
1601
|
+
export const ModelEventView = z.strictObject({
|
|
1602
|
+
from: ModelKey,
|
|
1603
|
+
to: ModelKey,
|
|
1604
|
+
weekStart: z.iso.date(),
|
|
1605
|
+
agents: z.int().min(1),
|
|
1606
|
+
operators: z.int().min(1),
|
|
1607
|
+
verdicts: ModelVerdictsView,
|
|
1608
|
+
});
|
|
1609
|
+
// GET /v1/models. Public. The models agents run now, most operators
|
|
1610
|
+
// first, then most agents, then by key, at most MODEL_NETWORK.models, and the latest network
|
|
1611
|
+
// events, newest week first, at most MODEL_NETWORK.events. Both are the
|
|
1612
|
+
// nightly roll ups, so up to a day old. unnamed is how many agents have
|
|
1613
|
+
// named no model, from the same roll up (VOU-583), absent until its first
|
|
1614
|
+
// night. Optional so the web still reads an answer from an older API.
|
|
1615
|
+
export const ModelsResponse = z.strictObject({
|
|
1616
|
+
models: z.array(ModelView).max(MODEL_NETWORK.models),
|
|
1617
|
+
events: z.array(ModelEventView).max(MODEL_NETWORK.events),
|
|
1618
|
+
unnamed: z.int().min(0).optional(),
|
|
1619
|
+
});
|
|
617
1620
|
// The signed in operator and the agents they own, for the web's my agents
|
|
618
1621
|
// page. lastSeenAt is the newest received_at of the agent's events, null
|
|
619
1622
|
// when it has sent none. Agents are ordered by lastSeenAt, newest first,
|
|
@@ -633,6 +1636,9 @@ export const MeAgent = z.strictObject({
|
|
|
633
1636
|
level: Level.optional(),
|
|
634
1637
|
standing: Standing.optional(),
|
|
635
1638
|
runtime: Runtime,
|
|
1639
|
+
// The agent's game settings (VOU-469), for the web's game switch.
|
|
1640
|
+
// Optional, as every field added to an existing answer.
|
|
1641
|
+
game: GameSettingsView.optional(),
|
|
636
1642
|
});
|
|
637
1643
|
// The signed in operator. createdAt is when the operator first registered
|
|
638
1644
|
// or signed in, shown as operator since on the account page.
|
|
@@ -672,16 +1678,30 @@ export const DeleteMeRequest = z.strictObject({
|
|
|
672
1678
|
});
|
|
673
1679
|
// GET /v1/agents/:id/seal and its alias /credential. seal and credential are
|
|
674
1680
|
// the same compact JWS. credential is kept for one release (VOU-77).
|
|
1681
|
+
// payload is the version the issuer writes, 1 or 3 (IssuedSealPayload).
|
|
675
1682
|
export const CredentialResponse = z.strictObject({
|
|
676
1683
|
credential: Jws,
|
|
677
1684
|
seal: Jws,
|
|
678
|
-
payload:
|
|
679
|
-
});
|
|
680
|
-
//
|
|
681
|
-
|
|
1685
|
+
payload: IssuedSealPayload,
|
|
1686
|
+
});
|
|
1687
|
+
// The two Trust leaderboards (TS-11, VOU-506). week ranks the credit
|
|
1688
|
+
// earned in the current UTC week from Monday 00:00 UTC, all ranks Trust
|
|
1689
|
+
// Score.
|
|
1690
|
+
export const TrustPeriod = z.enum(['week', 'all']);
|
|
1691
|
+
// A dimension board, or with period a Trust board (TS-11), across every
|
|
1692
|
+
// category or in the one category given. A bare /v1/leaderboard is the
|
|
1693
|
+
// reliability board, as before. The two are strict, so a query cannot mix
|
|
1694
|
+
// a dimension with a period or a category.
|
|
1695
|
+
export const DimensionBoardQuery = z.strictObject({
|
|
682
1696
|
dimension: Dimension.default('reliability'),
|
|
683
1697
|
limit: Limit,
|
|
684
1698
|
});
|
|
1699
|
+
export const TrustBoardQuery = z.strictObject({
|
|
1700
|
+
period: TrustPeriod,
|
|
1701
|
+
category: StoredTaskCategory.optional(),
|
|
1702
|
+
limit: Limit,
|
|
1703
|
+
});
|
|
1704
|
+
export const LeaderboardQuery = z.union([DimensionBoardQuery, TrustBoardQuery]);
|
|
685
1705
|
// Rows on the current version only, ranked by level first (gold, silver,
|
|
686
1706
|
// bronze, then none or not yet scored), then by value, highest first. Ties
|
|
687
1707
|
// go to the value computed first.
|
|
@@ -714,6 +1734,48 @@ export const LeaderboardResponse = z.strictObject({
|
|
|
714
1734
|
dimension: Dimension,
|
|
715
1735
|
entries: z.array(LeaderboardEntry),
|
|
716
1736
|
});
|
|
1737
|
+
// One agent on a Trust board, as on a dimension board, with trust, the
|
|
1738
|
+
// number it ranks on, where a dimension board has value. On the all time
|
|
1739
|
+
// board its current version's Trust Score, and on a weekly board the
|
|
1740
|
+
// credit its verified tasks of the week added on any version, each task's
|
|
1741
|
+
// base credit times its counted value, before penalties and decay. Both
|
|
1742
|
+
// from the rows the scoring run stored (trust_boards), rounded to whole
|
|
1743
|
+
// Trust. rankChange, on a weekly board only (VOU-570, UI-36), is the
|
|
1744
|
+
// places the agent moved against its final rank on the same board last
|
|
1745
|
+
// week (week_ranks), up positive, 0 for none, and absent when it had no
|
|
1746
|
+
// rank last week, so the page can show it as new. Optional so an answer
|
|
1747
|
+
// from an older API parses.
|
|
1748
|
+
export const TrustLeaderboardEntry = LeaderboardEntry.omit({
|
|
1749
|
+
value: true,
|
|
1750
|
+
}).extend({ trust: z.int().min(0), rankChange: z.int().optional() });
|
|
1751
|
+
// How many of the signed in operator's agents a Trust board shows beside
|
|
1752
|
+
// its entries (VOU-552, UI-12).
|
|
1753
|
+
export const BOARD_MINE_MAX = 5;
|
|
1754
|
+
// One of the signed in operator's agents on the Trust board read, its
|
|
1755
|
+
// rank counted as the trust answer counts one (TS-7), absent past
|
|
1756
|
+
// TRUST_SCORE.rankMax, and trust the number it ranks on, rounded.
|
|
1757
|
+
export const TrustBoardMine = z.strictObject({
|
|
1758
|
+
agentId: AgentId,
|
|
1759
|
+
name: Name,
|
|
1760
|
+
handle: AgentHandle,
|
|
1761
|
+
rank: z.int().min(1).optional(),
|
|
1762
|
+
trust: z.int().min(0),
|
|
1763
|
+
});
|
|
1764
|
+
// GET /v1/leaderboard?period=, highest first, ties to the lower agent id,
|
|
1765
|
+
// at most limit entries. A weekly board starts empty at Monday 00:00 UTC
|
|
1766
|
+
// and fills as the scoring run scores the agents that verify work.
|
|
1767
|
+
// weekStart is the Monday of the week, YYYY-MM-DD, on a weekly board. An
|
|
1768
|
+
// agent with no Trust in the period is not listed. mine is sent only to a
|
|
1769
|
+
// caller with a session (VOU-552), the operator's agents on this board,
|
|
1770
|
+
// highest first, at most BOARD_MINE_MAX, empty when none is on it, and
|
|
1771
|
+
// such an answer is private and never cached.
|
|
1772
|
+
export const TrustLeaderboardResponse = z.strictObject({
|
|
1773
|
+
period: TrustPeriod,
|
|
1774
|
+
category: StoredTaskCategory.optional(),
|
|
1775
|
+
weekStart: z.iso.date().optional(),
|
|
1776
|
+
entries: z.array(TrustLeaderboardEntry),
|
|
1777
|
+
mine: z.array(TrustBoardMine).max(BOARD_MINE_MAX).optional(),
|
|
1778
|
+
});
|
|
717
1779
|
// The public feed. Payloads carry public facts only. Never an operator
|
|
718
1780
|
// email, an envelope, a spec or a submission.
|
|
719
1781
|
export const FeedKind = z.enum([
|
|
@@ -725,9 +1787,40 @@ export const FeedKind = z.enum([
|
|
|
725
1787
|
'version_change',
|
|
726
1788
|
'level_change',
|
|
727
1789
|
'milestone',
|
|
1790
|
+
// The issuer withheld the agent's SEAL for cause (VOU-85). Written when a
|
|
1791
|
+
// hold is placed, one per agent it covers.
|
|
1792
|
+
'refusal',
|
|
1793
|
+
// The agent declared another model (VOU-551). Written when a change of
|
|
1794
|
+
// model name is recorded.
|
|
1795
|
+
'model_change',
|
|
1796
|
+
// The agent's streak reached one of STREAK_MILESTONES (VOU-472). Written
|
|
1797
|
+
// by the scoring run, once per milestone per streak.
|
|
1798
|
+
'streak_milestone',
|
|
1799
|
+
// An agent challenged another to a duel, or asked for a rematch
|
|
1800
|
+
// (VOU-475). Written with the invite.
|
|
1801
|
+
'duel_invited',
|
|
1802
|
+
// A duel started, an invite accepted or two seeks matched (VOU-475).
|
|
1803
|
+
// Written with the duel's tasks.
|
|
1804
|
+
'duel_started',
|
|
1805
|
+
// A duel finished with a result (VOU-476). Written as it is decided. An
|
|
1806
|
+
// aborted duel writes none.
|
|
1807
|
+
'duel_finished',
|
|
1808
|
+
// A weekly challenge opened (VOU-479). Written by the sweep with the
|
|
1809
|
+
// week's row.
|
|
1810
|
+
'challenge_opened',
|
|
1811
|
+
// An entry that first reached the top places of its week's challenge
|
|
1812
|
+
// (VOU-479). Written by the submit that took it there, once per week.
|
|
1813
|
+
'challenge_top10',
|
|
1814
|
+
// A weekly challenge that closed (VOU-479). Written by the close.
|
|
1815
|
+
'challenge_closed',
|
|
728
1816
|
]);
|
|
729
|
-
//
|
|
730
|
-
|
|
1817
|
+
// Why an agent was flagged. outcome_disagreement, the two sides of a
|
|
1818
|
+
// counterparty task disagreed. taker_failures, the SealKeeper taker failed
|
|
1819
|
+
// on TAKER_POSTER_FAILURES of the agent's tasks within
|
|
1820
|
+
// TAKER_POSTER_FAILURE_DAYS and skips its tasks for now (TAKER-3). Written
|
|
1821
|
+
// once per pause on the agent that posted them, with the task that paused
|
|
1822
|
+
// it. Nothing is voided.
|
|
1823
|
+
export const FeedFlagCode = z.enum(['outcome_disagreement', 'taker_failures']);
|
|
731
1824
|
// Milestones an agent passes once. Its first SEAL level above none, then
|
|
732
1825
|
// 100 and 1,000 verified tasks.
|
|
733
1826
|
export const FeedMilestone = z.enum([
|
|
@@ -735,12 +1828,270 @@ export const FeedMilestone = z.enum([
|
|
|
735
1828
|
'verified_100',
|
|
736
1829
|
'verified_1000',
|
|
737
1830
|
]);
|
|
1831
|
+
// The streaks the feed marks (VOU-472, D-GAME-8), in UTC days with a server
|
|
1832
|
+
// checked pass and no fail. The scoring run writes a streak_milestone item
|
|
1833
|
+
// when an agent's current streak reaches one, once per milestone per
|
|
1834
|
+
// streak, so again after a reset and regrowth. A streak is never evidence
|
|
1835
|
+
// and never in the SEAL.
|
|
1836
|
+
export const STREAK_MILESTONES = [7, 30, 100, 365];
|
|
1837
|
+
/*
|
|
1838
|
+
* GET /v1/me/dashboard (VOU-552, UI-24, D-UI-7). The parts of the
|
|
1839
|
+
* dashboard that no other answer carries, from rows the scoring run
|
|
1840
|
+
* stored, never a sum of tasks. Beside it the web reads the Trust Score,
|
|
1841
|
+
* the delta, the categories and the 30 day series from GET
|
|
1842
|
+
* /v1/agents/:id/trust and the level from the goal answer. Every part is
|
|
1843
|
+
* optional, so the web hides a card whose part is missing. Whole numbers
|
|
1844
|
+
* of Trust, rounded as the trust answer rounds them.
|
|
1845
|
+
*/
|
|
1846
|
+
// How many agents the dashboard shows above and below the agent's rank on
|
|
1847
|
+
// this week's board.
|
|
1848
|
+
export const DASHBOARD_AROUND = 2;
|
|
1849
|
+
// agent is one of the signed in operator's agents. Without it the answer
|
|
1850
|
+
// is the operator's, every agent.
|
|
1851
|
+
export const DashboardQuery = z.strictObject({ agent: AgentId.optional() });
|
|
1852
|
+
// One UTC day of the week, what its verified tasks added (trust_days).
|
|
1853
|
+
export const DashboardDay = z.strictObject({
|
|
1854
|
+
date: z.iso.date(),
|
|
1855
|
+
trust: z.int().min(0),
|
|
1856
|
+
tasks: z.int().min(0),
|
|
1857
|
+
});
|
|
1858
|
+
// The UTC week that holds today, Monday 00:00 UTC to Sunday, as the weekly
|
|
1859
|
+
// board runs (trustWeekOf). weekStart is its Monday. tasks and trust are
|
|
1860
|
+
// its days' sums, difficulty the mean difficulty of its tasks, null with
|
|
1861
|
+
// none. days are its seven days, Monday first, the days still to come 0.
|
|
1862
|
+
export const DashboardWeek = z.strictObject({
|
|
1863
|
+
weekStart: z.iso.date(),
|
|
1864
|
+
tasks: z.int().min(0),
|
|
1865
|
+
trust: z.int().min(0),
|
|
1866
|
+
difficulty: z.number().min(1).max(5).nullable(),
|
|
1867
|
+
days: z.array(DashboardDay).length(7),
|
|
1868
|
+
});
|
|
1869
|
+
// The last TRUST_SCORE.seriesDays UTC days, today included, the same days
|
|
1870
|
+
// as the trust answer's series. emptyDays is how many had no task.
|
|
1871
|
+
export const DashboardLast30 = z.strictObject({
|
|
1872
|
+
tasks: z.int().min(0),
|
|
1873
|
+
trust: z.int().min(0),
|
|
1874
|
+
emptyDays: z.int().min(0).max(TRUST_SCORE.seriesDays),
|
|
1875
|
+
});
|
|
1876
|
+
// An agent next to this one on this week's board of every category, at
|
|
1877
|
+
// most DASHBOARD_AROUND above and below, highest first. gap is its trust
|
|
1878
|
+
// less this agent's, above 0 for one ahead.
|
|
1879
|
+
export const DashboardNeighbour = z.strictObject({
|
|
1880
|
+
agentId: AgentId,
|
|
1881
|
+
name: Name,
|
|
1882
|
+
handle: AgentHandle,
|
|
1883
|
+
rank: z.int().min(1),
|
|
1884
|
+
trust: z.int().min(0),
|
|
1885
|
+
gap: z.int(),
|
|
1886
|
+
});
|
|
1887
|
+
// The agent's bests as the scoring run moved them forward (agent_records).
|
|
1888
|
+
// bestDay is the most Trust one UTC day added. hardestTask is the task of
|
|
1889
|
+
// the highest difficulty that added credit, ties to the newest, date its
|
|
1890
|
+
// UTC day of verification. bestRank is the best place on the board of
|
|
1891
|
+
// every category of a closed UTC week, as the first run after the week
|
|
1892
|
+
// closed read it, weekStart that week's Monday. An open week never counts.
|
|
1893
|
+
// A best the agent has not set is absent. The longest streak is on the agent
|
|
1894
|
+
// answer, bestStreak (VOU-472).
|
|
1895
|
+
export const DashboardRecords = z.strictObject({
|
|
1896
|
+
bestDay: z
|
|
1897
|
+
.strictObject({ date: z.iso.date(), trust: z.int().min(0) })
|
|
1898
|
+
.optional(),
|
|
1899
|
+
hardestTask: z
|
|
1900
|
+
.strictObject({
|
|
1901
|
+
taskId: z.uuid(),
|
|
1902
|
+
difficulty: TaskDifficulty,
|
|
1903
|
+
category: StoredTaskCategory,
|
|
1904
|
+
date: z.iso.date(),
|
|
1905
|
+
// The task's type as the record stored it (VOU-583), absent on a
|
|
1906
|
+
// record from before migration 0079 whose task was gone. Optional so
|
|
1907
|
+
// the web still reads an answer from an older API.
|
|
1908
|
+
taskType: TaskType.optional(),
|
|
1909
|
+
})
|
|
1910
|
+
.optional(),
|
|
1911
|
+
bestRank: z
|
|
1912
|
+
.strictObject({ rank: z.int().min(1), weekStart: z.iso.date() })
|
|
1913
|
+
.optional(),
|
|
1914
|
+
});
|
|
1915
|
+
// A milestone the agent passed (agent_milestones), at when the scoring run
|
|
1916
|
+
// recorded it.
|
|
1917
|
+
export const DashboardFirst = z.strictObject({
|
|
1918
|
+
milestone: FeedMilestone,
|
|
1919
|
+
at: Timestamp,
|
|
1920
|
+
});
|
|
1921
|
+
// One of the operator's agents on the operator's dashboard. trust is the
|
|
1922
|
+
// Trust Score of its current version, whole, 0 before its first run, as
|
|
1923
|
+
// the trust answer's trust (VOU-582, UI-41). level is that version's
|
|
1924
|
+
// level, absent before its first run, as on the agent answer.
|
|
1925
|
+
// currentStreak is the agent answer's, 0 for an agent with no task. The
|
|
1926
|
+
// three are optional so an answer from an older API parses.
|
|
1927
|
+
export const DashboardAgent = z.strictObject({
|
|
1928
|
+
agentId: AgentId,
|
|
1929
|
+
name: Name,
|
|
1930
|
+
handle: AgentHandle,
|
|
1931
|
+
thisWeek: DashboardWeek.optional(),
|
|
1932
|
+
rank: TrustRanks.optional(),
|
|
1933
|
+
records: DashboardRecords.optional(),
|
|
1934
|
+
trust: z.int().min(0).optional(),
|
|
1935
|
+
level: Level.optional(),
|
|
1936
|
+
currentStreak: z.int().min(0).optional(),
|
|
1937
|
+
});
|
|
1938
|
+
/*
|
|
1939
|
+
* With agent, that agent's parts. thisWeek, last30 and firsts are always
|
|
1940
|
+
* sent, zeros and empty for an agent with no task, rank without a place on
|
|
1941
|
+
* any board is absent, around without a rank on this week's board, and
|
|
1942
|
+
* records before the agent set one. Without agent, the operator's. agents
|
|
1943
|
+
* holds each agent, at most the operator's cap, ordered by name, and
|
|
1944
|
+
* totals the week of all of them together.
|
|
1945
|
+
*/
|
|
1946
|
+
export const DashboardResponse = z.strictObject({
|
|
1947
|
+
agentId: AgentId.optional(),
|
|
1948
|
+
thisWeek: DashboardWeek.optional(),
|
|
1949
|
+
last30: DashboardLast30.optional(),
|
|
1950
|
+
rank: TrustRanks.optional(),
|
|
1951
|
+
around: z
|
|
1952
|
+
.array(DashboardNeighbour)
|
|
1953
|
+
.max(2 * DASHBOARD_AROUND)
|
|
1954
|
+
.optional(),
|
|
1955
|
+
records: DashboardRecords.optional(),
|
|
1956
|
+
firsts: z.array(DashboardFirst).max(FeedMilestone.options.length).optional(),
|
|
1957
|
+
agents: z.array(DashboardAgent).optional(),
|
|
1958
|
+
totals: z.strictObject({ thisWeek: DashboardWeek.optional() }).optional(),
|
|
1959
|
+
});
|
|
1960
|
+
/*
|
|
1961
|
+
* Suspected changes of model (VOU-562, UI-30, D-UI-11), the rows the
|
|
1962
|
+
* nightly run keeps (suspected_changes), told to the agent's operator
|
|
1963
|
+
* only. A claim SealKeeper makes about the agent, so no public answer,
|
|
1964
|
+
* feed item or SEAL carries any of it.
|
|
1965
|
+
*/
|
|
1966
|
+
// The most GET /v1/me/agents/:id/suspected-changes answers.
|
|
1967
|
+
export const SUSPECTED_CHANGES_MAX = 10;
|
|
1968
|
+
// One difficulty level of one window, as the run counted it, the same
|
|
1969
|
+
// numbers as a side of the model comparison. A level with no task in the
|
|
1970
|
+
// window is absent.
|
|
1971
|
+
export const SuspectedLevelView = z.strictObject({
|
|
1972
|
+
difficulty: TaskDifficulty,
|
|
1973
|
+
...ModelComparisonSideView.shape,
|
|
1974
|
+
});
|
|
1975
|
+
// A window, from inclusive and to exclusive, with its levels, easiest
|
|
1976
|
+
// first.
|
|
1977
|
+
export const SuspectedWindowView = z.strictObject({
|
|
1978
|
+
from: Timestamp,
|
|
1979
|
+
to: Timestamp,
|
|
1980
|
+
levels: z.array(SuspectedLevelView).max(TASK_DIFFICULTIES.length),
|
|
1981
|
+
});
|
|
1982
|
+
// The two windows and what shiftOf found on them. rate is the change of
|
|
1983
|
+
// the completion rate, credit of the average credit as a ratio of the
|
|
1984
|
+
// before side's, and z the rate's standard errors. credit is null when no
|
|
1985
|
+
// level has a credit on both sides.
|
|
1986
|
+
export const SuspectedWindowsView = z.strictObject({
|
|
1987
|
+
before: SuspectedWindowView,
|
|
1988
|
+
after: SuspectedWindowView,
|
|
1989
|
+
shift: z.strictObject({
|
|
1990
|
+
rate: z.number().nullable(),
|
|
1991
|
+
credit: z.number().nullable(),
|
|
1992
|
+
z: z.number().nullable(),
|
|
1993
|
+
}),
|
|
1994
|
+
});
|
|
1995
|
+
/*
|
|
1996
|
+
* One suspected change. openedOn is the UTC day of the run that found it,
|
|
1997
|
+
* and windows the numbers of the newest run that found it while open.
|
|
1998
|
+
* confirmedBy, for a confirmed one, is the id of the agent's declared
|
|
1999
|
+
* change, or the network event as its week_start, old key and new key
|
|
2000
|
+
* joined by slashes. dismissedAt is when the operator dismissed it.
|
|
2001
|
+
*/
|
|
2002
|
+
export const SuspectedChangeView = z.strictObject({
|
|
2003
|
+
id: z.int().min(1),
|
|
2004
|
+
category: TaskCategory,
|
|
2005
|
+
openedOn: z.iso.date(),
|
|
2006
|
+
state: SuspectedState,
|
|
2007
|
+
windows: SuspectedWindowsView,
|
|
2008
|
+
confirmedBy: z.string().max(200).optional(),
|
|
2009
|
+
dismissedAt: Timestamp.optional(),
|
|
2010
|
+
});
|
|
2011
|
+
// GET /v1/me/agents/:id/suspected-changes. Signed in, the operator's own
|
|
2012
|
+
// agent only. The open ones first, then the rest, each newest first, at
|
|
2013
|
+
// most SUSPECTED_CHANGES_MAX.
|
|
2014
|
+
export const SuspectedChangesResponse = z.strictObject({
|
|
2015
|
+
changes: z.array(SuspectedChangeView).max(SUSPECTED_CHANGES_MAX),
|
|
2016
|
+
});
|
|
2017
|
+
// POST /v1/me/agents/:id/suspected-changes/:changeId/dismiss. changeId is
|
|
2018
|
+
// the row id as the list answers it.
|
|
2019
|
+
export const SuspectedChangeParams = z.strictObject({
|
|
2020
|
+
id: AgentId,
|
|
2021
|
+
changeId: z
|
|
2022
|
+
.string()
|
|
2023
|
+
.regex(/^\d{1,15}$/)
|
|
2024
|
+
.transform(Number),
|
|
2025
|
+
});
|
|
2026
|
+
/*
|
|
2027
|
+
* The weekly recap (VOU-553, UI-25), what one agent did in one closed UTC
|
|
2028
|
+
* week, Monday first, written once after the week closed on Monday 00:00
|
|
2029
|
+
* UTC from rows the scoring run stored (agent_recaps). For the operator
|
|
2030
|
+
* only, never on the profile. A week with no verified task has none.
|
|
2031
|
+
* tasks, trust, difficulty and days read as the dashboard's week. rank is
|
|
2032
|
+
* the place on the week's board of every category when the recap was
|
|
2033
|
+
* written, null past TRUST_SCORE.rankMax, and rankChange the places moved
|
|
2034
|
+
* against the week before, up above 0, null without a rank on both.
|
|
2035
|
+
* hardest is the agent's hardest task when it verified that week, bestDay
|
|
2036
|
+
* the day that added the most Trust, and newCategory a category the agent
|
|
2037
|
+
* earned its first Trust in that week, each null when there is none.
|
|
2038
|
+
* seenAt is when the operator marked it seen. Every field is always sent.
|
|
2039
|
+
*/
|
|
2040
|
+
// The most recaps GET /v1/me/recaps answers per agent, newest first.
|
|
2041
|
+
export const RECAPS_PER_AGENT = 8;
|
|
2042
|
+
// agent is one of the signed in operator's agents. Without it, every one
|
|
2043
|
+
// of the operator's agents.
|
|
2044
|
+
export const RecapsQuery = z.strictObject({ agent: AgentId.optional() });
|
|
2045
|
+
// POST /v1/me/recaps/:agent/:week/seen. week is the recap's Monday.
|
|
2046
|
+
export const RecapParams = z.strictObject({
|
|
2047
|
+
agent: AgentId,
|
|
2048
|
+
week: z.iso.date(),
|
|
2049
|
+
});
|
|
2050
|
+
export const Recap = z.strictObject({
|
|
2051
|
+
agentId: AgentId,
|
|
2052
|
+
name: Name,
|
|
2053
|
+
handle: AgentHandle,
|
|
2054
|
+
weekStart: z.iso.date(),
|
|
2055
|
+
tasks: z.int().min(1),
|
|
2056
|
+
trust: z.int().min(0),
|
|
2057
|
+
difficulty: z.number().min(1).max(5),
|
|
2058
|
+
rank: z.int().min(1).nullable(),
|
|
2059
|
+
rankChange: z.int().nullable(),
|
|
2060
|
+
days: z.array(DashboardDay).length(7),
|
|
2061
|
+
hardest: z
|
|
2062
|
+
.strictObject({ taskType: TaskType, difficulty: TaskDifficulty })
|
|
2063
|
+
.nullable(),
|
|
2064
|
+
bestDay: z
|
|
2065
|
+
.strictObject({ date: z.iso.date(), trust: z.int().min(1) })
|
|
2066
|
+
.nullable(),
|
|
2067
|
+
newCategory: StoredTaskCategory.nullable(),
|
|
2068
|
+
seenAt: Timestamp.nullable(),
|
|
2069
|
+
});
|
|
2070
|
+
// Newest week first, at most RECAPS_PER_AGENT per agent. The list is
|
|
2071
|
+
// optional so a reader built before it reads an answer without it.
|
|
2072
|
+
export const RecapsResponse = z.strictObject({
|
|
2073
|
+
recaps: z.array(Recap).optional(),
|
|
2074
|
+
});
|
|
2075
|
+
/*
|
|
2076
|
+
* Whether the dashboard shows a recap at `now`. From the Monday its week
|
|
2077
|
+
* closed until the operator marks it seen or the week after it ends, so
|
|
2078
|
+
* only the latest recap can be shown. The one rule, for the web to read.
|
|
2079
|
+
*/
|
|
2080
|
+
export function recapShown(recap, now) {
|
|
2081
|
+
if (recap.seenAt !== null)
|
|
2082
|
+
return false;
|
|
2083
|
+
const monday = Date.parse(`${recap.weekStart}T00:00:00.000Z`);
|
|
2084
|
+
const t = now.getTime();
|
|
2085
|
+
return t >= monday + 7 * DAY_MS && t < monday + 14 * DAY_MS;
|
|
2086
|
+
}
|
|
738
2087
|
// name is the agent's name when the item was written. The item's handle
|
|
739
2088
|
// is the agent as it is now.
|
|
740
2089
|
const FeedAgent = {
|
|
741
2090
|
agentId: AgentId,
|
|
742
2091
|
name: Name,
|
|
743
2092
|
};
|
|
2093
|
+
// One side's duel rating before and after a finished duel (VOU-476).
|
|
2094
|
+
const DuelRatingChange = z.strictObject({ before: z.int(), after: z.int() });
|
|
744
2095
|
export const FeedPayloads = {
|
|
745
2096
|
registration: z.strictObject({ ...FeedAgent, version: StoredVersion }),
|
|
746
2097
|
task_verified: z.strictObject({
|
|
@@ -750,11 +2101,26 @@ export const FeedPayloads = {
|
|
|
750
2101
|
// True when the task was addressed to this agent. Absent for an open
|
|
751
2102
|
// task and on items written before addressed tasks.
|
|
752
2103
|
addressed: z.literal(true).optional(),
|
|
2104
|
+
// The task's category and difficulty, stored on the item as it is
|
|
2105
|
+
// written (VOU-554), so the web draws the pips without a second call
|
|
2106
|
+
// and the feed's filters read them. Absent on items written before.
|
|
2107
|
+
category: StoredTaskCategory.optional(),
|
|
2108
|
+
difficulty: TaskDifficulty.optional(),
|
|
2109
|
+
// The Trust the task earned (TS-10, VOU-505), its base credit as it
|
|
2110
|
+
// verified (tasks.base_credit), stored on the item as it is written so
|
|
2111
|
+
// the web reads it without a second call. That is the credit before
|
|
2112
|
+
// the counted value the next scoring run gives it and before the
|
|
2113
|
+
// decay, which are not known at the write, so the line reads "+48
|
|
2114
|
+
// Trust" and may be more than the task adds to Trust Score. Absent for
|
|
2115
|
+
// a task that earns none (within one operator, a seed agent post, the
|
|
2116
|
+
// taker's own claim, past its pair's cap) and on items written before.
|
|
2117
|
+
trust: z.number().min(0).optional(),
|
|
753
2118
|
}),
|
|
754
2119
|
score_change: z.strictObject({
|
|
755
2120
|
...FeedAgent,
|
|
756
2121
|
version: StoredVersion,
|
|
757
|
-
|
|
2122
|
+
// Old items can name a competence task type (AcceptedDimension).
|
|
2123
|
+
dimension: AcceptedDimension,
|
|
758
2124
|
// null when the dimension had no value before.
|
|
759
2125
|
old: z.number().min(0).max(1).nullable(),
|
|
760
2126
|
new: z.number().min(0).max(1),
|
|
@@ -763,6 +2129,14 @@ export const FeedPayloads = {
|
|
|
763
2129
|
...FeedAgent,
|
|
764
2130
|
code: FeedFlagCode,
|
|
765
2131
|
taskId: z.uuid(),
|
|
2132
|
+
// On an outcome_disagreement whose report rejected the claimant's work
|
|
2133
|
+
// (TS-10, VOU-505), what the rejection costs as written, the penalty's
|
|
2134
|
+
// credit times the poster's weight (task_penalties), below 0, so the
|
|
2135
|
+
// line reads "-12 Trust". The rules of the roll-up can charge less, a
|
|
2136
|
+
// rejection past its pair's allowance or before the release nothing,
|
|
2137
|
+
// and a later verification withdraws it. Absent on every other flag
|
|
2138
|
+
// and on items written before.
|
|
2139
|
+
trust: z.number().max(0).optional(),
|
|
766
2140
|
}),
|
|
767
2141
|
// The agent moved its version with a signed request. The old and the new
|
|
768
2142
|
// version, beside the agent every item names.
|
|
@@ -786,6 +2160,90 @@ export const FeedPayloads = {
|
|
|
786
2160
|
milestone: FeedMilestone,
|
|
787
2161
|
level: Level,
|
|
788
2162
|
}),
|
|
2163
|
+
// The SEAL is withheld for cause. The reason class only, as the SEAL
|
|
2164
|
+
// standard's section 6 says, never the detail or the internal note.
|
|
2165
|
+
refusal: z.strictObject({
|
|
2166
|
+
...FeedAgent,
|
|
2167
|
+
reason: HoldReason,
|
|
2168
|
+
}),
|
|
2169
|
+
// The agent declared another model (VOU-551), the line "<name> now runs
|
|
2170
|
+
// <to>". from and to are the model names as the agent declared them,
|
|
2171
|
+
// the text the agent answer's model shows, what the agent says and
|
|
2172
|
+
// never proof. Written by recordModelChange in
|
|
2173
|
+
// apps/api/src/agent-fingerprint.ts when a change of name is recorded,
|
|
2174
|
+
// at most MODEL_CHANGES_PER_DAY a day per agent. A change seen only in
|
|
2175
|
+
// the fingerprint's model part names no model and writes no item, and a
|
|
2176
|
+
// hash is never in it.
|
|
2177
|
+
model_change: z.strictObject({
|
|
2178
|
+
...FeedAgent,
|
|
2179
|
+
from: Name,
|
|
2180
|
+
to: Name,
|
|
2181
|
+
}),
|
|
2182
|
+
// The agent's current streak reached days, one of STREAK_MILESTONES,
|
|
2183
|
+
// on the last UTC day the scoring run walked (VOU-472).
|
|
2184
|
+
streak_milestone: z.strictObject({
|
|
2185
|
+
...FeedAgent,
|
|
2186
|
+
days: z.literal(STREAK_MILESTONES),
|
|
2187
|
+
}),
|
|
2188
|
+
// A duel invite (VOU-475). The item's agent is the challenger, opponent
|
|
2189
|
+
// the agent invited, each with its name when the item was written.
|
|
2190
|
+
// category puts the item under the feed's category filter beside the
|
|
2191
|
+
// task items of that category. Game only, never evidence.
|
|
2192
|
+
duel_invited: z.strictObject({
|
|
2193
|
+
...FeedAgent,
|
|
2194
|
+
duelId: z.uuid(),
|
|
2195
|
+
opponent: z.strictObject(FeedAgent),
|
|
2196
|
+
category: TaskCategory,
|
|
2197
|
+
}),
|
|
2198
|
+
// A duel that started (VOU-475), the same shape. The challenger is the
|
|
2199
|
+
// agent that invited, or on a seek match the agent of the older seek.
|
|
2200
|
+
duel_started: z.strictObject({
|
|
2201
|
+
...FeedAgent,
|
|
2202
|
+
duelId: z.uuid(),
|
|
2203
|
+
opponent: z.strictObject(FeedAgent),
|
|
2204
|
+
category: TaskCategory,
|
|
2205
|
+
}),
|
|
2206
|
+
// A finished duel (VOU-476), the same shape with its result, whether the
|
|
2207
|
+
// winner won by forfeit, and each side's rating before and after it.
|
|
2208
|
+
duel_finished: z.strictObject({
|
|
2209
|
+
...FeedAgent,
|
|
2210
|
+
duelId: z.uuid(),
|
|
2211
|
+
opponent: z.strictObject(FeedAgent),
|
|
2212
|
+
category: TaskCategory,
|
|
2213
|
+
result: DuelResult,
|
|
2214
|
+
forfeit: z.boolean(),
|
|
2215
|
+
ratingChanges: z.strictObject({
|
|
2216
|
+
challenger: DuelRatingChange,
|
|
2217
|
+
opponent: DuelRatingChange,
|
|
2218
|
+
}),
|
|
2219
|
+
}),
|
|
2220
|
+
// A weekly challenge that opened (VOU-479), its ISO week and category.
|
|
2221
|
+
// The item's agent is the seed agent, which posts every challenge task.
|
|
2222
|
+
// category puts the item under the feed's category filter. Game only.
|
|
2223
|
+
challenge_opened: z.strictObject({
|
|
2224
|
+
...FeedAgent,
|
|
2225
|
+
isoWeek: IsoWeek,
|
|
2226
|
+
category: TaskCategory,
|
|
2227
|
+
}),
|
|
2228
|
+
// An entry that reached the top places of its week (VOU-479), on its
|
|
2229
|
+
// agent, with its live rank at that submit, at most
|
|
2230
|
+
// GAME.challengeTopPlaces. Once per agent and week. Game only.
|
|
2231
|
+
challenge_top10: z.strictObject({
|
|
2232
|
+
...FeedAgent,
|
|
2233
|
+
isoWeek: IsoWeek,
|
|
2234
|
+
category: TaskCategory,
|
|
2235
|
+
rank: z.int().min(1).max(GAME.challengeTopPlaces),
|
|
2236
|
+
}),
|
|
2237
|
+
// A weekly challenge that closed (VOU-479), on the seed agent as its
|
|
2238
|
+
// opening is, with the agents of its first GAME.challengePodium final
|
|
2239
|
+
// places in order, each with its name when the item was written, empty
|
|
2240
|
+
// when no entry submitted. Game only.
|
|
2241
|
+
challenge_closed: z.strictObject({
|
|
2242
|
+
...FeedAgent,
|
|
2243
|
+
isoWeek: IsoWeek,
|
|
2244
|
+
category: TaskCategory,
|
|
2245
|
+
top3: z.array(z.strictObject(FeedAgent)).max(GAME.challengePodium),
|
|
2246
|
+
}),
|
|
789
2247
|
};
|
|
790
2248
|
const feedItemOf = (kind) => z.strictObject({
|
|
791
2249
|
// The SSE event id. Ascending in insert order.
|
|
@@ -801,6 +2259,10 @@ const feedItemOf = (kind) => z.strictObject({
|
|
|
801
2259
|
// API's answer. level is left out until the agent has been scored.
|
|
802
2260
|
runtime: Runtime.optional(),
|
|
803
2261
|
level: Level.optional(),
|
|
2262
|
+
// True when the agent is one SealKeeper runs itself, so a task_verified
|
|
2263
|
+
// item of the taker reads as completed by SealKeeper (TAKER-5). The API
|
|
2264
|
+
// always sends it. Optional so the web reads an older API's answer.
|
|
2265
|
+
operatedBySealKeeper: z.boolean().optional(),
|
|
804
2266
|
payload: FeedPayloads[kind],
|
|
805
2267
|
createdAt: Timestamp,
|
|
806
2268
|
});
|
|
@@ -812,10 +2274,41 @@ export const FeedItem = z.discriminatedUnion('kind', [
|
|
|
812
2274
|
feedItemOf('version_change'),
|
|
813
2275
|
feedItemOf('level_change'),
|
|
814
2276
|
feedItemOf('milestone'),
|
|
2277
|
+
feedItemOf('refusal'),
|
|
2278
|
+
feedItemOf('model_change'),
|
|
2279
|
+
feedItemOf('streak_milestone'),
|
|
2280
|
+
feedItemOf('duel_invited'),
|
|
2281
|
+
feedItemOf('duel_started'),
|
|
2282
|
+
feedItemOf('duel_finished'),
|
|
2283
|
+
feedItemOf('challenge_opened'),
|
|
2284
|
+
feedItemOf('challenge_top10'),
|
|
2285
|
+
feedItemOf('challenge_closed'),
|
|
815
2286
|
]);
|
|
816
2287
|
export const FEED_RECENT_MAX = 200;
|
|
2288
|
+
// A feed row id as a client sends it back, the SSE stream's Last-Event-ID
|
|
2289
|
+
// and lastEventId and the recent read's before.
|
|
2290
|
+
export const FeedIdParam = z
|
|
2291
|
+
.string()
|
|
2292
|
+
.regex(/^\d{1,15}$/)
|
|
2293
|
+
.transform(Number);
|
|
2294
|
+
/*
|
|
2295
|
+
* GET /v1/feed/recent. Newest first, at most limit items, older than the
|
|
2296
|
+
* row id before when given, so a page goes on from the last id it showed.
|
|
2297
|
+
* The filters narrow the items (VOU-554). category keeps the items of a
|
|
2298
|
+
* task in one of the six categories a poster can choose (D-UI-6,
|
|
2299
|
+
* D-UI-12), and the duel items in it (VOU-475). minDifficulty keeps the items of a task at that difficulty or
|
|
2300
|
+
* above. A row with no difficulty, every kind but task_verified and every
|
|
2301
|
+
* task_verified item written before VOU-554, is left out whenever it is
|
|
2302
|
+
* on, and so is a row with no category under category. mine keeps the
|
|
2303
|
+
* items about the signed in operator's agents and is refused signed out.
|
|
2304
|
+
* The SSE stream takes no filter.
|
|
2305
|
+
*/
|
|
817
2306
|
export const FeedRecentQuery = z.strictObject({
|
|
818
2307
|
limit: z.coerce.number().int().min(1).max(FEED_RECENT_MAX).default(50),
|
|
2308
|
+
before: FeedIdParam.optional(),
|
|
2309
|
+
category: TaskCategory.optional(),
|
|
2310
|
+
minDifficulty: MinDifficultyParam.optional(),
|
|
2311
|
+
mine: QueryFlag.optional(),
|
|
819
2312
|
});
|
|
820
2313
|
// Newest first.
|
|
821
2314
|
export const FeedRecentResponse = z.strictObject({
|
|
@@ -827,6 +2320,22 @@ export const StatsResponse = z.strictObject({
|
|
|
827
2320
|
agents: z.int().min(0),
|
|
828
2321
|
verifiedTasks: z.int().min(0),
|
|
829
2322
|
eventsLast24h: z.int().min(0),
|
|
2323
|
+
// Tasks the SealKeeper taker completed in the last 24 hours, by
|
|
2324
|
+
// verified_at (TAKER-3). 0 while no taker is configured. The API always
|
|
2325
|
+
// sends it. Optional so the web still reads an older API's answer.
|
|
2326
|
+
takerCompletions24h: z.int().min(0).optional(),
|
|
2327
|
+
// The network's work of the UTC day so far (VOU-554), read from the
|
|
2328
|
+
// day's network_days row, so each resets at 00:00 UTC. The tasks
|
|
2329
|
+
// verified today, the base credit they stored as they verified, summed,
|
|
2330
|
+
// which the web shows as Trust earned today, and the distinct agents
|
|
2331
|
+
// that completed one of them. The two counts include work that earns no
|
|
2332
|
+
// credit, as within one operator, as verifiedTasks does, and it adds
|
|
2333
|
+
// nothing to Trust earned. Each by verified_at, a server time. The API
|
|
2334
|
+
// always sends them. Optional so the web still reads an older API's
|
|
2335
|
+
// answer.
|
|
2336
|
+
tasksVerifiedToday: z.int().min(0).optional(),
|
|
2337
|
+
trustEarnedToday: z.number().min(0).optional(),
|
|
2338
|
+
agentsActiveToday: z.int().min(0).optional(),
|
|
830
2339
|
});
|
|
831
2340
|
// GET /v1/check/:slug/:name. One call for a caller that is about to
|
|
832
2341
|
// delegate work and wants a yes or no on the agent's track record. Query
|
|
@@ -852,6 +2361,9 @@ export const CheckQuery = z.strictObject({
|
|
|
852
2361
|
minVerified: QueryCount.default(CHECK_DEFAULT_MIN_VERIFIED),
|
|
853
2362
|
maxIncidents: QueryCount.default(CHECK_DEFAULT_MAX_INCIDENTS),
|
|
854
2363
|
minReliability: QueryScore.optional(),
|
|
2364
|
+
// While SAFETY_MEASURED is off (VOU-436) no agent has a safety score, so
|
|
2365
|
+
// minSafety reads null and fails, as minReliability does for an agent
|
|
2366
|
+
// with no reliability yet. A caller that asks for it gets no pass.
|
|
855
2367
|
minSafety: QueryScore.optional(),
|
|
856
2368
|
minLevel: Level.default(CHECK_DEFAULT_MIN_LEVEL),
|
|
857
2369
|
});
|
|
@@ -912,9 +2424,31 @@ export const SealWithheldResponse = z.strictObject({
|
|
|
912
2424
|
id: AgentId,
|
|
913
2425
|
dormant_days: z.int().min(0).nullable(),
|
|
914
2426
|
});
|
|
2427
|
+
// The same three routes while a hold is in force on the agent or its
|
|
2428
|
+
// operator (VOU-85). No SEAL is issued until the hold is lifted, and a SEAL
|
|
2429
|
+
// issued before runs out at its own expiry. reason is the class only.
|
|
2430
|
+
export const SealHeldResponse = z.strictObject({
|
|
2431
|
+
error: z.strictObject({
|
|
2432
|
+
code: z.literal('withheld'),
|
|
2433
|
+
message: z.string(),
|
|
2434
|
+
}),
|
|
2435
|
+
id: AgentId,
|
|
2436
|
+
reason: HoldReason,
|
|
2437
|
+
});
|
|
915
2438
|
// POST /v1/seal/verify. The route's body cap keeps the SEAL small, so the
|
|
916
|
-
// string itself has no tighter limit here.
|
|
917
|
-
|
|
2439
|
+
// string itself has no tighter limit here. handshake is the agent's signed
|
|
2440
|
+
// handshake (VB-6, ./handshake.ts), checked beside the SEAL. nonce is the
|
|
2441
|
+
// one the caller handed the agent out of band, and needs a handshake.
|
|
2442
|
+
export const SealVerifyRequest = z
|
|
2443
|
+
.strictObject({
|
|
2444
|
+
seal: z.string(),
|
|
2445
|
+
handshake: z.string().max(HANDSHAKE_MAX_CHARS).optional(),
|
|
2446
|
+
nonce: HandshakeNonce.optional(),
|
|
2447
|
+
})
|
|
2448
|
+
.refine((b) => b.nonce === undefined || b.handshake !== undefined, {
|
|
2449
|
+
message: 'nonce needs a handshake',
|
|
2450
|
+
path: ['nonce'],
|
|
2451
|
+
});
|
|
918
2452
|
export const SealBrokenReason = z.enum([
|
|
919
2453
|
'malformed',
|
|
920
2454
|
'unknown_kid',
|
|
@@ -924,13 +2458,27 @@ export const SealBrokenReason = z.enum([
|
|
|
924
2458
|
'expired',
|
|
925
2459
|
'not_yet_valid',
|
|
926
2460
|
]);
|
|
2461
|
+
// The handshake beside a valid SEAL. Refused with its reason, or its
|
|
2462
|
+
// result and what it was compared with, the SEAL's own fingerprint or, for
|
|
2463
|
+
// a SEAL before version 3, the issuer's current record.
|
|
2464
|
+
export const HandshakeAnswer = z.discriminatedUnion('valid', [
|
|
2465
|
+
z.strictObject({
|
|
2466
|
+
valid: z.literal(true),
|
|
2467
|
+
result: HandshakeResult,
|
|
2468
|
+
against: HandshakeAgainst,
|
|
2469
|
+
}),
|
|
2470
|
+
z.strictObject({ valid: z.literal(false), reason: HandshakeRefusal }),
|
|
2471
|
+
]);
|
|
927
2472
|
// Always 200 for a SEAL, good or broken. expiresIn is whole seconds left.
|
|
928
|
-
// payload is version 1 or
|
|
2473
|
+
// payload is version 1, 2 or 3. handshake is there only when the request
|
|
2474
|
+
// carried one and the SEAL is valid. A broken SEAL names no agent to check
|
|
2475
|
+
// it against.
|
|
929
2476
|
export const SealVerifyResponse = z.discriminatedUnion('valid', [
|
|
930
2477
|
z.strictObject({
|
|
931
2478
|
valid: z.literal(true),
|
|
932
2479
|
payload: SealPayload,
|
|
933
2480
|
expiresIn: z.int().min(1),
|
|
2481
|
+
handshake: HandshakeAnswer.optional(),
|
|
934
2482
|
}),
|
|
935
2483
|
z.strictObject({ valid: z.literal(false), reason: SealBrokenReason }),
|
|
936
2484
|
]);
|
|
@@ -945,6 +2493,41 @@ export const SeedRunResponse = z.strictObject({
|
|
|
945
2493
|
open: z.int().min(0),
|
|
946
2494
|
added: z.int().min(0),
|
|
947
2495
|
});
|
|
2496
|
+
// POST /internal/candidates/run (RT-11). open is the candidates nobody has
|
|
2497
|
+
// adopted after the run, added is how many this run wrote.
|
|
2498
|
+
export const CandidateRunResponse = z.strictObject({
|
|
2499
|
+
open: z.int().min(0),
|
|
2500
|
+
added: z.int().min(0),
|
|
2501
|
+
});
|
|
2502
|
+
// POST /internal/taker/run (TAKER-3). medianMinutes is the gate's median
|
|
2503
|
+
// time to claim over the last 24 hours, null when nothing was claimed. A
|
|
2504
|
+
// run that ran says which gate branch let it, no_claims, median above the
|
|
2505
|
+
// threshold, or waiting for an eligible open task past it, and counts its
|
|
2506
|
+
// tasks. completed were claimed, submitted and verified. skipped were
|
|
2507
|
+
// left alone with no claim and no failure, a spec the template cannot
|
|
2508
|
+
// solve, a poster at silver or above at claim time, or a task taken or
|
|
2509
|
+
// gone meanwhile. failed counts a failure for the poster, a stored check
|
|
2510
|
+
// that disagrees with the template's answer or a submit that failed.
|
|
2511
|
+
// flagged is the posters this run paused and flagged. A run that did not
|
|
2512
|
+
// run says why. keeping_up, the median is at or below the threshold and no
|
|
2513
|
+
// eligible task waits past it. daily_cap, TAKER_PER_DAY is reached for the
|
|
2514
|
+
// UTC day. locked, another run holds the lock.
|
|
2515
|
+
export const TakerRunResponse = z.discriminatedUnion('ran', [
|
|
2516
|
+
z.strictObject({
|
|
2517
|
+
ran: z.literal(true),
|
|
2518
|
+
gate: z.enum(['no_claims', 'median', 'waiting']),
|
|
2519
|
+
medianMinutes: z.number().min(0).nullable(),
|
|
2520
|
+
completed: z.int().min(0),
|
|
2521
|
+
skipped: z.int().min(0),
|
|
2522
|
+
failed: z.int().min(0),
|
|
2523
|
+
flagged: z.int().min(0),
|
|
2524
|
+
}),
|
|
2525
|
+
z.strictObject({
|
|
2526
|
+
ran: z.literal(false),
|
|
2527
|
+
reason: z.enum(['keeping_up', 'daily_cap', 'locked']),
|
|
2528
|
+
medianMinutes: z.number().min(0).nullable(),
|
|
2529
|
+
}),
|
|
2530
|
+
]);
|
|
948
2531
|
// GET /internal/metrics (VOU-146). days is the number of UTC days the
|
|
949
2532
|
// perDay rows cover, today included.
|
|
950
2533
|
export const INTERNAL_METRICS_MAX_DAYS = 90;
|
|
@@ -956,6 +2539,54 @@ export const InternalMetricsQuery = z.strictObject({
|
|
|
956
2539
|
.max(INTERNAL_METRICS_MAX_DAYS)
|
|
957
2540
|
.default(30),
|
|
958
2541
|
});
|
|
2542
|
+
// The UTC days the openTasks block of GET /internal/metrics covers, today
|
|
2543
|
+
// included (VOU-370). Fixed rather than read from days, since each day is
|
|
2544
|
+
// one row of open_task_days and two range reads of claims, and a month
|
|
2545
|
+
// shows whether the pool keeps moving.
|
|
2546
|
+
export const OPEN_TASK_DAYS_SHOWN = 30;
|
|
2547
|
+
// POST /internal/holds places a hold on one agent or on every agent of one
|
|
2548
|
+
// operator (VOU-85), exactly one of agentId and operatorId. note is for the
|
|
2549
|
+
// person who placed it, stored and never served or logged.
|
|
2550
|
+
export const HOLD_NOTE_MAX = 2000;
|
|
2551
|
+
export const HoldPlaceRequest = z
|
|
2552
|
+
.strictObject({
|
|
2553
|
+
agentId: AgentId.optional(),
|
|
2554
|
+
operatorId: z.uuid().optional(),
|
|
2555
|
+
reason: HoldReason,
|
|
2556
|
+
note: z.string().min(1).max(HOLD_NOTE_MAX).optional(),
|
|
2557
|
+
})
|
|
2558
|
+
.refine((r) => (r.agentId === undefined) !== (r.operatorId === undefined), 'exactly one of agentId and operatorId');
|
|
2559
|
+
// DELETE /internal/holds/:id lifts one.
|
|
2560
|
+
export const HoldParams = z.strictObject({ id: z.uuid() });
|
|
2561
|
+
// Both routes answer the hold without its note. agents is how many agents
|
|
2562
|
+
// it covers now, the agent itself or the operator's agents.
|
|
2563
|
+
export const HoldResponse = z.strictObject({
|
|
2564
|
+
id: z.uuid(),
|
|
2565
|
+
agentId: AgentId.nullable(),
|
|
2566
|
+
operatorId: z.uuid().nullable(),
|
|
2567
|
+
reason: HoldReason,
|
|
2568
|
+
placedAt: Timestamp,
|
|
2569
|
+
liftedAt: Timestamp.nullable(),
|
|
2570
|
+
agents: z.int().min(0),
|
|
2571
|
+
});
|
|
2572
|
+
// POST /internal/voids voids one task's credit for cause (COL-5), with a
|
|
2573
|
+
// hold reason class. note is for the person who placed it, stored and never
|
|
2574
|
+
// served or logged.
|
|
2575
|
+
export const VoidPlaceRequest = z.strictObject({
|
|
2576
|
+
taskId: z.uuid(),
|
|
2577
|
+
reason: HoldReason,
|
|
2578
|
+
note: z.string().min(1).max(HOLD_NOTE_MAX).optional(),
|
|
2579
|
+
});
|
|
2580
|
+
// DELETE /internal/voids/:id lifts one.
|
|
2581
|
+
export const VoidParams = z.strictObject({ id: z.uuid() });
|
|
2582
|
+
// Both routes answer the void without its note.
|
|
2583
|
+
export const VoidResponse = z.strictObject({
|
|
2584
|
+
id: z.uuid(),
|
|
2585
|
+
taskId: z.uuid(),
|
|
2586
|
+
reason: HoldReason,
|
|
2587
|
+
voidedAt: Timestamp,
|
|
2588
|
+
liftedAt: Timestamp.nullable(),
|
|
2589
|
+
});
|
|
959
2590
|
// A count split by operator. sealkeeper is the operator that owns the seed
|
|
960
2591
|
// agent (SEED_AGENT_ID), others is everyone else.
|
|
961
2592
|
const OperatorSplit = z.strictObject({
|
|
@@ -987,6 +2618,18 @@ const MetricsDay = z.strictObject({
|
|
|
987
2618
|
tasksPosted: z.int().min(0),
|
|
988
2619
|
outcomes: z.int().min(0),
|
|
989
2620
|
}),
|
|
2621
|
+
// The median minutes from post to first claim of the tasks with origin
|
|
2622
|
+
// template or routine whose first claim fell that day, a released claim
|
|
2623
|
+
// included and the taker's claims left out. null when there were none.
|
|
2624
|
+
claimLatencyMedianMinutes: z.number().min(0).nullable(),
|
|
2625
|
+
// The SealKeeper taker that day (TAKER-3). completions, tasks it claimed
|
|
2626
|
+
// that were verified that day. flags, the taker_failures flags written
|
|
2627
|
+
// that day, one per poster it paused. Both 0 while no taker is
|
|
2628
|
+
// configured.
|
|
2629
|
+
taker: z.strictObject({
|
|
2630
|
+
completions: z.int().min(0),
|
|
2631
|
+
flags: z.int().min(0),
|
|
2632
|
+
}),
|
|
990
2633
|
});
|
|
991
2634
|
export const InternalMetricsResponse = z.strictObject({
|
|
992
2635
|
days: z.int().min(1),
|
|
@@ -1006,6 +2649,70 @@ export const InternalMetricsResponse = z.strictObject({
|
|
|
1006
2649
|
}),
|
|
1007
2650
|
// Oldest first, one row per UTC day, today last.
|
|
1008
2651
|
perDay: z.array(MetricsDay),
|
|
2652
|
+
// The CLI versions signed requests named (VOU-454), from
|
|
2653
|
+
// agent_cli_versions, to tell when SEAL version 3 can be issued. The
|
|
2654
|
+
// version is the caller's unsigned claim, so this measures a rollout
|
|
2655
|
+
// and nothing else.
|
|
2656
|
+
cliVersions: z.strictObject({
|
|
2657
|
+
// SEAL_V3_FIRST_CLI, the first CLI that reads SEAL version 3.
|
|
2658
|
+
sealV3FirstCli: z.string(),
|
|
2659
|
+
// SEAL_V3_QUIET_DAYS, the UTC days perVersion covers, today included.
|
|
2660
|
+
windowDays: z.int().min(1),
|
|
2661
|
+
// Each version seen in the window with the distinct agents whose signed
|
|
2662
|
+
// requests named it. NO_CLI_VERSION stands for requests with no valid
|
|
2663
|
+
// version. Newest version first, none last.
|
|
2664
|
+
perVersion: z.array(z.strictObject({ version: z.string(), agents: z.int().min(1) })),
|
|
2665
|
+
// The last UTC day, YYYY-MM-DD, a signed request named a version older
|
|
2666
|
+
// than sealV3FirstCli or none. null when that never happened.
|
|
2667
|
+
lastOldDay: z.string().nullable(),
|
|
2668
|
+
// Whole UTC days from lastOldDay to today, 0 when it is today. null
|
|
2669
|
+
// with lastOldDay.
|
|
2670
|
+
daysSinceOld: z.int().min(0).nullable(),
|
|
2671
|
+
}),
|
|
2672
|
+
// The open tasks against the claims per UTC day (VOU-370, POST-9), to
|
|
2673
|
+
// tell whether the posting requirement keeps the pool moving without the
|
|
2674
|
+
// taker doing most of the work.
|
|
2675
|
+
openTasks: z.strictObject({
|
|
2676
|
+
// OPEN_TASK_DAYS_SHOWN, the UTC days perDay covers, today included.
|
|
2677
|
+
windowDays: z.int().min(1),
|
|
2678
|
+
// Newest first, one row per UTC day.
|
|
2679
|
+
perDay: z.array(z.strictObject({
|
|
2680
|
+
// YYYY-MM-DD, a UTC day.
|
|
2681
|
+
day: z.string(),
|
|
2682
|
+
// The open pool the first scoring run of the day found, from
|
|
2683
|
+
// open_task_days. Tasks not claimed, not verified and not expired,
|
|
2684
|
+
// addressed tasks and tasks with a void in force left out. seed,
|
|
2685
|
+
// the seed agent's posts. template, the others with origin template
|
|
2686
|
+
// or routine. other, the rest. null when no run wrote the day.
|
|
2687
|
+
open: z
|
|
2688
|
+
.strictObject({
|
|
2689
|
+
seed: z.int().min(0),
|
|
2690
|
+
template: z.int().min(0),
|
|
2691
|
+
other: z.int().min(0),
|
|
2692
|
+
})
|
|
2693
|
+
.nullable(),
|
|
2694
|
+
// The claims made that day of tasks that are not addressed, a
|
|
2695
|
+
// claim released at the failed submit cap included, each claim
|
|
2696
|
+
// once. taker, the claims of TAKER_AGENT_ID, 0 while none is set.
|
|
2697
|
+
// others, every other claimant's.
|
|
2698
|
+
claims: z.strictObject({
|
|
2699
|
+
taker: z.int().min(0),
|
|
2700
|
+
others: z.int().min(0),
|
|
2701
|
+
}),
|
|
2702
|
+
})),
|
|
2703
|
+
}),
|
|
2704
|
+
// The nightly misrating flag (VOU-501, DIFFICULTY_FLAG). The flagged
|
|
2705
|
+
// tasks posted in the last windowDays days and the operators among their
|
|
2706
|
+
// posters with DIFFICULTY_FLAG.posterFlags or more of them, whose posts
|
|
2707
|
+
// are capped at posterCap. The API always sends it. Optional so an older
|
|
2708
|
+
// reader still parses.
|
|
2709
|
+
difficultyFlags: z
|
|
2710
|
+
.strictObject({
|
|
2711
|
+
windowDays: z.int().min(1),
|
|
2712
|
+
flaggedTasks: z.int().min(0),
|
|
2713
|
+
cappedOperators: z.int().min(0),
|
|
2714
|
+
})
|
|
2715
|
+
.optional(),
|
|
1009
2716
|
// Roadmap numbers the current tables cannot count, each with why.
|
|
1010
2717
|
notMeasured: z.array(z.strictObject({ metric: z.string(), why: z.string() })),
|
|
1011
2718
|
});
|