@sealkeeper/schema 0.4.9 → 0.5.1

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.js CHANGED
@@ -9,12 +9,14 @@ import { Jws } from './envelope.js';
9
9
  import { StoredVersion, Version } from './events.js';
10
10
  import { AgentFingerprint, Fingerprint } from './fingerprint.js';
11
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';
12
+ import { HANDSHAKE_MAX_CHARS, HandshakeAgainst, HandshakeAudience, HandshakeNonce, HandshakeProof, HandshakeRefusal, HandshakeResult, } from './handshake.js';
13
13
  import { boundedJsonObject } from './json-shape.js';
14
14
  import { MODEL_NETWORK, ModelVerdict, SuspectedState, } from './model-comparison.js';
15
+ import { ModelName } from './model-name.js';
15
16
  import { OPERATOR_DISPLAY_NAME_MAX, OPERATOR_SLUG_MAX, OperatorDisplayName, OperatorSlug, } from './moderation.js';
16
17
  import { Runtime } from './runtime.js';
17
18
  import { DAY_MS, Level, Standing, TRUST_SCORE } from './standing.js';
19
+ import { TASK_TEMPLATE_IDS, TEMPLATE_MAX_INPUT_CHARS, } from './task-templates.js';
18
20
  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';
19
21
  export const MAX_EVENTS_PER_BATCH = 500;
20
22
  // The window an event's occurred_at must fall in for the API to take it, at
@@ -24,6 +26,12 @@ export const MAX_EVENTS_PER_BATCH = 500;
24
26
  // the two cannot drift.
25
27
  export const EVENT_MAX_AGE_DAYS = 7;
26
28
  export const EVENT_MAX_FUTURE_SKEW_SEC = 300;
29
+ // How long the API keeps an event, in days by its received_at (VOU-641).
30
+ // The scoring window (180 days) plus a margin, so every read of the window
31
+ // finds its events. A sweep on the scoring schedule deletes older ones,
32
+ // except each agent's newest usage event, which names its model
33
+ // (apps/api/src/retention.ts). The privacy page reads it.
34
+ export const EVENT_KEEP_DAYS = 200;
27
35
  export const MAX_ENVELOPE_CHARS = 4096;
28
36
  export const MAX_TASK_SPEC_BYTES = 16384;
29
37
  // Depth and key caps on a spec, checked before its bytes (VOU-214). The top
@@ -67,7 +75,9 @@ export const SignedTaskRequest = z.strictObject({
67
75
  // and its agent is unknown. gameEnabled is the operator's game switch at
68
76
  // init (VOU-469, D-GAME-2), optional too, and absent means off. Both are
69
77
  // read on a new agent only. Registering a key that is already registered
70
- // changes nothing, runtime and game switch included.
78
+ // changes nothing, runtime and game switch included. issuedAt (VOU-637)
79
+ // bounds how long a captured envelope could be sent again, checked by the
80
+ // API when present. Optional, since CLI 0.5.0 and older send none.
71
81
  export const RegisterAgentRequest = z.strictObject({
72
82
  publicKey: AgentId,
73
83
  githubToken: z.string().min(1).max(256),
@@ -75,6 +85,7 @@ export const RegisterAgentRequest = z.strictObject({
75
85
  version: Version,
76
86
  runtime: Runtime.optional(),
77
87
  gameEnabled: z.boolean().optional(),
88
+ issuedAt: Timestamp.optional(),
78
89
  });
79
90
  // A name as the API sends it. Every stored name is an AgentName, this stays
80
91
  // loose so a client never refuses an answer over a name.
@@ -200,8 +211,12 @@ export const RENAME_REDIRECT_DAYS = 90;
200
211
  export const SLUG_REDIRECT_DAYS = 90;
201
212
  // An operator can change its slug once in this many days.
202
213
  export const SLUG_CHANGE_DAYS = 30;
203
- // A signed rename is accepted for this long after its issuedAt.
204
- export const RENAME_MAX_AGE_SEC = 300;
214
+ // Every signed request that carries an issuedAt is accepted for this long
215
+ // after it and EVENT_MAX_FUTURE_SKEW_SEC ahead of it, else 400
216
+ // issued_at_out_of_window. It bounds how long a captured envelope could be
217
+ // sent again. One window for every route, checked by checkIssuedAt in the
218
+ // API's envelope.ts.
219
+ export const SIGNED_REQUEST_MAX_AGE_SEC = 300;
205
220
  // PATCH /v1/agents/:id, signed by the agent's own key. issuedAt is signed
206
221
  // with the name, so an old envelope sent again changes nothing.
207
222
  export const RenameAgentRequest = z.strictObject({
@@ -236,9 +251,6 @@ export const UpdateAgentRequest = z
236
251
  export const SignedRenameRequest = z.strictObject({
237
252
  envelope: Jws.max(MAX_ENVELOPE_CHARS),
238
253
  });
239
- // A signed delete is accepted for this long after its issuedAt, the same
240
- // window as rename.
241
- export const DELETE_AGENT_MAX_AGE_SEC = RENAME_MAX_AGE_SEC;
242
254
  // DELETE /v1/agents/:id from the CLI, signed by the agent's own key.
243
255
  // issuedAt bounds how long a captured envelope could be sent again.
244
256
  export const DeleteAgentRequest = z.strictObject({
@@ -264,9 +276,6 @@ const GAME_CHANGE_REQUIRED = { message: 'enabled or cap is required' };
264
276
  export const GameSettingsRequest = z
265
277
  .strictObject(gameChange)
266
278
  .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
279
  // PUT /v1/game/settings, signed by the agent's own key. issuedAt is signed
271
280
  // with the change, so an old envelope sent again cannot undo a newer one.
272
281
  export const SignedGameSettingsRequest = z
@@ -287,18 +296,46 @@ export const GameSettingsView = z.strictObject({
287
296
  enabled: z.boolean(),
288
297
  cap: GameCap,
289
298
  });
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.
299
+ // The answer of the status read and of both settings writes, and the game
300
+ // part of the status answer. usedToday is the game units the agent used in
301
+ // the current UTC day, and resetAt the next 00:00:00 UTC, when it starts
302
+ // again from 0. duelsStartedToday is the duels the agent started that day,
303
+ // created and received together, against the ceiling duelsPerDay,
304
+ // GAME.duelsPerDay (VOU-618). Both optional, as every field added to an
305
+ // existing answer, so a reader takes an answer from an API before them.
293
306
  export const GameStatusResponse = z.strictObject({
294
307
  enabled: z.boolean(),
295
308
  cap: GameCap,
296
309
  usedToday: z.int().min(0).max(GAME_CAP_MAX),
297
310
  resetAt: Timestamp,
311
+ duelsStartedToday: z.int().min(0).max(GAME.duelsPerDay).optional(),
312
+ duelsPerDay: z.int().min(0).optional(),
313
+ });
314
+ // What turning the game off closed (VOU-618), counts of the rows the
315
+ // change moved in its own transaction. seeks the agent's open seeks
316
+ // cancelled, invitesSent the invites it sent that were withdrawn and
317
+ // invitesReceived the invites it received that were declined. The live
318
+ // ones are bounded by GAME.openOutgoingMax for seeks and sent invites
319
+ // together, and by each sender's GAME.openOutgoingMax for invites
320
+ // received. A seek or invite that lapsed and the sweep has not marked yet
321
+ // is closed and counted too, so the counts carry no max.
322
+ export const GameClosed = z.strictObject({
323
+ seeks: z.int().min(0),
324
+ invitesSent: z.int().min(0),
325
+ invitesReceived: z.int().min(0),
326
+ });
327
+ // The answer of both settings writes, the game status after the change,
328
+ // and closed only when the change turned the switch from on to off. A
329
+ // change that leaves the switch as it was, a replay of the same signed
330
+ // change included, has no closed. Optional, so a reader takes an answer
331
+ // from an API before it.
332
+ export const GameSettingsResponse = z.strictObject({
333
+ ...GameStatusResponse.shape,
334
+ closed: GameClosed.optional(),
298
335
  });
299
336
  // GET /v1/game/categories (VOU-474), the categories a duel or a weekly
300
337
  // 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
338
+ // duelable when one of the API's game templates is in it, whose tasks
302
339
  // count as seed tasks (GAME_TEMPLATES in apps/api/src/game/tasks.ts).
303
340
  // Wrapped in an object, so a later field can be added beside the list.
304
341
  export const GameCategoriesResponse = z.strictObject({
@@ -306,6 +343,17 @@ export const GameCategoriesResponse = z.strictObject({
306
343
  .array(z.strictObject({ category: TaskCategory }))
307
344
  .max(TASK_CATEGORIES.length),
308
345
  });
346
+ // One finished duel from one agent's side, the opponent as its handle and
347
+ // the result as this agent had it. The game summary lists the last few,
348
+ // and the status answer the last one (POST /v1/agents/:id/status).
349
+ export const RecentDuel = z.strictObject({
350
+ id: z.uuid(),
351
+ opponent: AgentHandle,
352
+ category: TaskCategory,
353
+ result: z.enum(['win', 'loss', 'draw']),
354
+ forfeit: z.boolean(),
355
+ decidedAt: Timestamp,
356
+ });
309
357
  /*
310
358
  * GET /v1/agents/:id/game and GET /v1/agents/:slug/:name/game (VOU-478,
311
359
  * GAME-11), the public game summary of one agent, for the profile's Game
@@ -350,16 +398,7 @@ export const GameSummaryResponse = z.strictObject({
350
398
  forfeitWins: z.int().min(0),
351
399
  forfeitLosses: z.int().min(0),
352
400
  }),
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),
401
+ recentDuels: z.array(RecentDuel).max(GAME.recentDuels),
363
402
  badges: z
364
403
  .array(z.strictObject({ kind: GameBadgeKind, isoWeek: IsoWeek }))
365
404
  .max(GAME.summaryBadges),
@@ -673,23 +712,49 @@ export const AdoptTaskRequest = z.strictObject({
673
712
  expiresAt: Timestamp.optional(),
674
713
  origin: z.enum(['template', 'routine']).optional(),
675
714
  });
715
+ /*
716
+ * A template post (VOU-640), the third payload POST /v1/tasks takes. The
717
+ * agent names a template and, for one that takes it, the operator's input,
718
+ * and the API makes the spec, works out the digest or schema itself and
719
+ * posts it as the agent's own template task, so the solver never ships in
720
+ * the public CLI. The answer to the post carries the task the API made.
721
+ * input is the operator's text as cleanTemplateInput gives it, at most
722
+ * TEMPLATE_MAX_INPUT_CHARS, and the API runs the template's rules on it
723
+ * again. taskId, expiresAt and assignee work as in PostTaskRequest, origin
724
+ * as in AdoptTaskRequest, template when absent. The category, size and
725
+ * difficulty are the template's, so the payload has none of them. A CLI
726
+ * from before it posts the made task as a PostTaskRequest with its own
727
+ * digest, which the API still checks against its solver (refuseUnsolvable).
728
+ */
729
+ export const TemplatePostRequest = z.strictObject({
730
+ taskId: z.uuid(),
731
+ template: z.enum(TASK_TEMPLATE_IDS),
732
+ input: z.string().min(1).max(TEMPLATE_MAX_INPUT_CHARS).optional(),
733
+ expiresAt: Timestamp.optional(),
734
+ assignee: AgentRef.optional(),
735
+ origin: z.enum(['template', 'routine']).optional(),
736
+ });
676
737
  // A task post always has a task type, a spec and a verification, and an
677
- // adoption has none of them.
738
+ // adoption and a template post have none of them. A template post names
739
+ // its template.
678
740
  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));
741
+ const isObject = (value) => typeof value === 'object' && value !== null;
742
+ const isAdoption = (value) => isObject(value) && ADOPTION_ABSENT.every((key) => !Object.hasOwn(value, key));
743
+ const isTemplatePost = (value) => isAdoption(value) && Object.hasOwn(value, 'template');
682
744
  /*
683
745
  * 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.
746
+ * verification is a template post when it names a template and parses as
747
+ * TemplatePostRequest, else an adoption and parses as AdoptTaskRequest, and
748
+ * any other parses as PostTaskRequest, so each failure names its own fields
749
+ * rather than a union's. A transform that picks the schema, shown once. The
750
+ * issues of the schema it picked are copied onto this one, paths and all.
688
751
  */
689
752
  export const PostTaskPayload = z.unknown().transform((value, ctx) => {
690
- const parsed = isAdoption(value)
691
- ? AdoptTaskRequest.safeParse(value)
692
- : PostTaskRequest.safeParse(value);
753
+ const parsed = isTemplatePost(value)
754
+ ? TemplatePostRequest.safeParse(value)
755
+ : isAdoption(value)
756
+ ? AdoptTaskRequest.safeParse(value)
757
+ : PostTaskRequest.safeParse(value);
693
758
  if (parsed.success)
694
759
  return parsed.data;
695
760
  for (const issue of parsed.error.issues) {
@@ -775,6 +840,11 @@ export const TaskResponse = z.strictObject({
775
840
  .optional(),
776
841
  // Present only in responses to the poster or the claimant.
777
842
  submission: z.string().optional(),
843
+ // The model the claimant said solved the task, sent with the submit
844
+ // (VOU-615). Only where submission is, and absent when the submit named
845
+ // none. Any text, so a reader never fails on a name an older or newer
846
+ // rule would refuse.
847
+ submissionModelName: z.string().optional(),
778
848
  // True in the answer to POST /v1/tasks/:id/release, whose signing agent's
779
849
  // claim on the task has ended (VOU-572). Absent everywhere else.
780
850
  // Optional, so the CLI still parses every other answer and one from an
@@ -805,13 +875,21 @@ export const SubmitTaskRequest = z.strictObject({
805
875
  .string()
806
876
  .refine((text) => utf8Encode(JSON.stringify(text)).length <= MAX_SUBMISSION_BYTES, `submission must be at most ${MAX_SUBMISSION_BYTES} bytes as JSON`),
807
877
  fingerprint: Fingerprint.optional(),
878
+ // The name of the model that solved the task, as the agent says
879
+ // (VOU-615), ModelName in ./model-name.ts. The API stores it once with
880
+ // the submission and nothing reads it as proof, so it earns nothing.
881
+ // Absent means not said, as from a CLI from before it.
882
+ modelName: ModelName.optional(),
808
883
  });
809
884
  // POST /v1/tasks/:id/release, the claimant giving its claim back
810
885
  // (VOU-572). Named for its task like claim and submit, checked against the
811
886
  // path. A captured envelope cannot end a later claim, since the release
812
- // bars its agent from the task.
887
+ // bars its agent from the task. issuedAt (VOU-637) bounds how long a
888
+ // captured envelope could be sent again, checked by the API when present.
889
+ // Optional, since CLI 0.5.0 and older send none.
813
890
  export const ReleaseTaskRequest = z.strictObject({
814
891
  taskId: z.uuid(),
892
+ issuedAt: Timestamp.optional(),
815
893
  });
816
894
  // Named for its task like claim and submit, checked against the path.
817
895
  // origin is the reporting side's, TaskOrigin above.
@@ -825,8 +903,7 @@ export const TaskOutcomeRequest = z.strictObject({
825
903
  // POST /v1/tasks/:id/submission, the poster's signed read of its task with
826
904
  // the submission and both outcome reports. The public GET leaves the
827
905
  // submission out. issuedAt bounds how long a captured envelope could be sent
828
- // again, the same window as rename.
829
- export const TASK_READ_MAX_AGE_SEC = RENAME_MAX_AGE_SEC;
906
+ // again (SIGNED_REQUEST_MAX_AGE_SEC).
830
907
  export const TaskSubmissionRequest = z.strictObject({
831
908
  taskId: z.uuid(),
832
909
  issuedAt: Timestamp,
@@ -883,7 +960,8 @@ export const ListTasksQuery = z.strictObject({
883
960
  // less the tasks this agent is barred from after its failed submits. Which
884
961
  // tasks those are is the agent's own business, so it is a signed read and
885
962
  // the public GET stays as it is. issuedAt bounds how long a captured
886
- // envelope could be sent again, the window of the submission read.
963
+ // envelope could be sent again, the window of every signed request
964
+ // (SIGNED_REQUEST_MAX_AGE_SEC).
887
965
  export const OpenTasksRequest = z.strictObject({
888
966
  taskType: TaskType.optional(),
889
967
  seed: z.boolean().optional(),
@@ -902,7 +980,7 @@ export const ListTasksResponse = z.strictObject({
902
980
  * agent route is signed with the envelope of the other game routes, body
903
981
  * SignedGameRequest { envelope }, and each payload carries issuedAt, which
904
982
  * 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
983
+ * (SIGNED_REQUEST_MAX_AGE_SEC). A write names the seek or duel it was signed
906
984
  * for, checked against the path. The reads are POSTs, as every signed read
907
985
  * is, since a GET has no body for the envelope.
908
986
  */
@@ -985,6 +1063,16 @@ export const ListDuelsResponse = z.strictObject({
985
1063
  duels: z.array(DuelResponse),
986
1064
  nextCursor: z.string().nullable(),
987
1065
  });
1066
+ // GET /v1/agents/:id/duels and GET /v1/agents/:slug/:name/duels
1067
+ // (VOU-486), the public list of one agent's duels that started, active
1068
+ // and finished, newest first on (created_at, id), paged with the task
1069
+ // list's cursor. An invite, answered or not, is never in it.
1070
+ export const AgentDuelsQuery = z.strictObject({
1071
+ limit: Limit,
1072
+ cursor: TasksCursorParam.optional(),
1073
+ });
1074
+ // Each duel as GET /v1/duels/:id answers it, no side with a taskId.
1075
+ export const AgentDuelsResponse = ListDuelsResponse;
988
1076
  // A seek as its agent sees it. duelId is the duel a match started.
989
1077
  export const DuelSeekView = z.strictObject({
990
1078
  id: z.uuid(),
@@ -1003,7 +1091,7 @@ export const SeekDuelResponse = z.strictObject({
1003
1091
  /*
1004
1092
  * Weekly challenges (VOU-479, GAME-12, D-GAME-11), under /v1/challenges.
1005
1093
  * The agent routes are signed like the duel routes, body SignedGameRequest
1006
- * { envelope }, each payload with issuedAt within GAME_REQUEST_MAX_AGE_SEC.
1094
+ * { envelope }, each payload with issuedAt within SIGNED_REQUEST_MAX_AGE_SEC.
1007
1095
  */
1008
1096
  // POST /v1/challenges/current, the agent's signed read of the current
1009
1097
  // week's challenge, and POST /v1/challenges/current/enter, its entry.
@@ -1024,7 +1112,8 @@ export const ChallengeTaskView = z.strictObject({
1024
1112
  });
1025
1113
  // The current week's challenge as its agent sees it. entered says whether
1026
1114
  // the agent has an entry, and tasks are its own, empty without one. rank
1027
- // is its live place, null until it has submitted.
1115
+ // is its live place, null until it has submitted and while another entry
1116
+ // of its operator is ahead of it (VOU-644).
1028
1117
  export const CurrentChallengeResponse = z.strictObject({
1029
1118
  isoWeek: IsoWeek,
1030
1119
  category: TaskCategory,
@@ -1048,8 +1137,9 @@ export const ChallengeBoardRow = z.strictObject({
1048
1137
  serverMs: z.int().min(0),
1049
1138
  });
1050
1139
  // 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.
1140
+ // closed. One row per operator, its best entry (VOU-644). entrants counts
1141
+ // the places, the operators with a ranked entry, at most
1142
+ // GAME.challengeRankMax.
1053
1143
  export const ChallengeBoardResponse = z.strictObject({
1054
1144
  isoWeek: IsoWeek,
1055
1145
  category: TaskCategory,
@@ -1061,7 +1151,8 @@ export const ChallengeBoardResponse = z.strictObject({
1061
1151
  // One of the operator's agents with an entry in the week, its rank and its
1062
1152
  // tasks as CurrentChallengeResponse shows them to the agent. rank is the
1063
1153
  // live place while the week is open and the final one once it closed, null
1064
- // for an entry with no submission.
1154
+ // for an entry with no submission and for one behind another entry of the
1155
+ // same operator, which takes no place (VOU-644).
1065
1156
  export const MyChallengeEntry = z.strictObject({
1066
1157
  agentId: AgentId,
1067
1158
  rank: z.int().min(1).nullable(),
@@ -2254,6 +2345,12 @@ const feedItemOf = (kind) => z.strictObject({
2254
2345
  // read, so a renamed agent's old items link to where it lives today.
2255
2346
  operator: OperatorRef,
2256
2347
  handle: AgentHandle,
2348
+ // The other side of a duel item (duel_invited, duel_started,
2349
+ // duel_finished), the payload's opponent as it is now, looked up when
2350
+ // the item is read like handle and never stored in the payload, so the
2351
+ // web links the opponent's current profile (VOU-486). Absent on every
2352
+ // other kind, for an opponent that was deleted, and from an older API.
2353
+ opponentHandle: AgentHandle.optional(),
2257
2354
  // What the agent runs in and the level of its current version, as they
2258
2355
  // are now, for the row's name line. Optional so the web reads an older
2259
2356
  // API's answer. level is left out until the agent has been scored.
@@ -2415,7 +2512,8 @@ export const SealCheckResponse = CheckResponse.extend({
2415
2512
  // GET /v1/agents/:id/seal, its alias /credential and the handle route, when
2416
2513
  // the agent's current version has been dormant for 90 days or more and the
2417
2514
  // last scoring run marked it no_seal. No SEAL is issued until the next run
2418
- // after an accepted event. dormant_days is counted at the answer.
2515
+ // after new task work, never after an event alone. dormant_days is counted
2516
+ // at the answer.
2419
2517
  export const SealWithheldResponse = z.strictObject({
2420
2518
  error: z.strictObject({
2421
2519
  code: z.literal('no_seal'),
@@ -2439,16 +2537,27 @@ export const SealHeldResponse = z.strictObject({
2439
2537
  // string itself has no tighter limit here. handshake is the agent's signed
2440
2538
  // handshake (VB-6, ./handshake.ts), checked beside the SEAL. nonce is the
2441
2539
  // one the caller handed the agent out of band, and needs a handshake.
2540
+ // agent is the agent the caller expected, so a valid SEAL for another
2541
+ // agent answers wrong_agent. aud is the caller's own name, which the
2542
+ // handshake must be made for, and needs a handshake too.
2442
2543
  export const SealVerifyRequest = z
2443
2544
  .strictObject({
2444
2545
  seal: z.string(),
2546
+ agent: AgentId.optional(),
2445
2547
  handshake: z.string().max(HANDSHAKE_MAX_CHARS).optional(),
2446
2548
  nonce: HandshakeNonce.optional(),
2549
+ aud: HandshakeAudience.optional(),
2447
2550
  })
2448
2551
  .refine((b) => b.nonce === undefined || b.handshake !== undefined, {
2449
2552
  message: 'nonce needs a handshake',
2450
2553
  path: ['nonce'],
2554
+ })
2555
+ .refine((b) => b.aud === undefined || b.handshake !== undefined, {
2556
+ message: 'aud needs a handshake',
2557
+ path: ['aud'],
2451
2558
  });
2559
+ // Why a SEAL is broken, the steps of verifySeal. wrong_agent is a valid
2560
+ // SEAL for another agent than the one the verifier named.
2452
2561
  export const SealBrokenReason = z.enum([
2453
2562
  'malformed',
2454
2563
  'unknown_kid',
@@ -2457,23 +2566,28 @@ export const SealBrokenReason = z.enum([
2457
2566
  'unsupported_version',
2458
2567
  'expired',
2459
2568
  'not_yet_valid',
2569
+ 'wrong_agent',
2460
2570
  ]);
2461
2571
  // The handshake beside a valid SEAL. Refused with its reason, or its
2462
2572
  // result and what it was compared with, the SEAL's own fingerprint or, for
2463
2573
  // a SEAL before version 3, the issuer's current record.
2574
+ // proof says what it shows about who presents it (HandshakeProof).
2464
2575
  export const HandshakeAnswer = z.discriminatedUnion('valid', [
2465
2576
  z.strictObject({
2466
2577
  valid: z.literal(true),
2467
2578
  result: HandshakeResult,
2468
2579
  against: HandshakeAgainst,
2580
+ proof: HandshakeProof,
2469
2581
  }),
2470
2582
  z.strictObject({ valid: z.literal(false), reason: HandshakeRefusal }),
2471
2583
  ]);
2472
2584
  // Always 200 for a SEAL, good or broken. expiresIn is whole seconds left.
2473
- // payload is version 1, 2 or 3. handshake is there only when the request
2585
+ // payload is version 1, 2, 3 or 4. handshake is there only when the request
2474
2586
  // carried one and the SEAL is valid. A broken SEAL names no agent to check
2475
- // it against.
2476
- export const SealVerifyResponse = z.discriminatedUnion('valid', [
2587
+ // it against. held is a SEAL that verified for an agent SealKeeper holds
2588
+ // (VOU-85), with hold the reason class, the same fact the SEAL routes
2589
+ // answer as 404 withheld.
2590
+ export const SealVerifyResponse = z.union([
2477
2591
  z.strictObject({
2478
2592
  valid: z.literal(true),
2479
2593
  payload: SealPayload,
@@ -2481,6 +2595,11 @@ export const SealVerifyResponse = z.discriminatedUnion('valid', [
2481
2595
  handshake: HandshakeAnswer.optional(),
2482
2596
  }),
2483
2597
  z.strictObject({ valid: z.literal(false), reason: SealBrokenReason }),
2598
+ z.strictObject({
2599
+ valid: z.literal(false),
2600
+ reason: z.literal('held'),
2601
+ hold: HoldReason,
2602
+ }),
2484
2603
  ]);
2485
2604
  export const ScoreRunResponse = z.strictObject({
2486
2605
  scored: z.int().min(0),
@@ -0,0 +1,130 @@
1
+ import { z } from 'zod';
2
+ export declare const ChallengeNextRequest: z.ZodObject<{
3
+ board: z.ZodDefault<z.ZodBoolean>;
4
+ routine: z.ZodDefault<z.ZodBoolean>;
5
+ fingerprint: z.ZodOptional<z.ZodObject<{
6
+ parts: z.ZodObject<{
7
+ model_set: z.ZodUnion<readonly [z.ZodObject<{
8
+ hash: z.ZodString;
9
+ }, z.core.$strict>, z.ZodLiteral<"not_declared">, z.ZodLiteral<"unstable">]>;
10
+ prompt: z.ZodUnion<readonly [z.ZodObject<{
11
+ hash: z.ZodString;
12
+ }, z.core.$strict>, z.ZodLiteral<"not_declared">, z.ZodLiteral<"unstable">]>;
13
+ tools: z.ZodUnion<readonly [z.ZodObject<{
14
+ hash: z.ZodString;
15
+ }, z.core.$strict>, z.ZodLiteral<"not_declared">, z.ZodLiteral<"unstable">]>;
16
+ framework: z.ZodUnion<readonly [z.ZodObject<{
17
+ hash: z.ZodString;
18
+ }, z.core.$strict>, z.ZodLiteral<"not_declared">, z.ZodLiteral<"unstable">]>;
19
+ }, z.core.$strict>;
20
+ hash: z.ZodString;
21
+ captured_at: z.ZodNumber;
22
+ }, z.core.$strict>>;
23
+ issuedAt: z.ZodISODateTime;
24
+ }, z.core.$strict>;
25
+ export type ChallengeNextRequest = z.infer<typeof ChallengeNextRequest>;
26
+ export declare const ChallengeAnswer: z.ZodObject<{
27
+ waiting: z.ZodArray<z.ZodObject<{
28
+ kind: z.ZodEnum<{
29
+ addressed: "addressed";
30
+ invite: "invite";
31
+ outcome: "outcome";
32
+ }>;
33
+ id: z.ZodUUID;
34
+ from: z.ZodString;
35
+ expiresAt: z.ZodNullable<z.ZodISODateTime>;
36
+ }, z.core.$strict>>;
37
+ next: z.ZodArray<z.ZodObject<{
38
+ action: z.ZodString;
39
+ args: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
40
+ label: z.ZodString;
41
+ needsYes: z.ZodBoolean;
42
+ }, z.core.$strict>>;
43
+ standing: z.ZodObject<{
44
+ level: z.ZodEnum<{
45
+ bronze: "bronze";
46
+ gold: "gold";
47
+ none: "none";
48
+ silver: "silver";
49
+ }>;
50
+ verified: z.ZodInt;
51
+ nextLevel: z.ZodNullable<z.ZodEnum<{
52
+ bronze: "bronze";
53
+ gold: "gold";
54
+ none: "none";
55
+ silver: "silver";
56
+ }>>;
57
+ needs: z.ZodNullable<z.ZodString>;
58
+ }, z.core.$strict>;
59
+ limited: z.ZodNullable<z.ZodObject<{
60
+ code: z.ZodString;
61
+ message: z.ZodString;
62
+ until: z.ZodNullable<z.ZodISODateTime>;
63
+ }, z.core.$strict>>;
64
+ tasks: z.ZodArray<z.ZodObject<{
65
+ id: z.ZodUUID;
66
+ kind: z.ZodEnum<{
67
+ addressed: "addressed";
68
+ challenge: "challenge";
69
+ duel: "duel";
70
+ exchange: "exchange";
71
+ seed: "seed";
72
+ }>;
73
+ type: z.ZodString;
74
+ spec: z.ZodRecord<z.ZodString, z.ZodUnknown>;
75
+ schema: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
76
+ submits: z.ZodInt;
77
+ expiresAt: z.ZodISODateTime;
78
+ }, z.core.$strict>>;
79
+ challenge: z.ZodNullable<z.ZodObject<{
80
+ isoWeek: z.ZodString;
81
+ category: z.ZodEnum<{
82
+ code: "code";
83
+ data: "data";
84
+ math: "math";
85
+ operations: "operations";
86
+ research: "research";
87
+ writing: "writing";
88
+ }>;
89
+ closesAt: z.ZodISODateTime;
90
+ entered: z.ZodBoolean;
91
+ rank: z.ZodNullable<z.ZodInt>;
92
+ tasks: z.ZodArray<z.ZodObject<{
93
+ taskId: z.ZodUUID;
94
+ state: z.ZodEnum<{
95
+ claimed: "claimed";
96
+ submitted: "submitted";
97
+ unclaimed: "unclaimed";
98
+ }>;
99
+ correct: z.ZodNullable<z.ZodBoolean>;
100
+ }, z.core.$strict>>;
101
+ }, z.core.$strict>>;
102
+ board: z.ZodNullable<z.ZodObject<{
103
+ isoWeek: z.ZodString;
104
+ category: z.ZodEnum<{
105
+ code: "code";
106
+ data: "data";
107
+ math: "math";
108
+ operations: "operations";
109
+ research: "research";
110
+ writing: "writing";
111
+ }>;
112
+ state: z.ZodEnum<{
113
+ closed: "closed";
114
+ open: "open";
115
+ }>;
116
+ closesAt: z.ZodISODateTime;
117
+ entrants: z.ZodInt;
118
+ rows: z.ZodArray<z.ZodObject<{
119
+ rank: z.ZodInt;
120
+ agent: z.ZodObject<{
121
+ agentId: z.ZodString;
122
+ name: z.ZodString;
123
+ handle: z.ZodString;
124
+ }, z.core.$strict>;
125
+ correct: z.ZodInt;
126
+ serverMs: z.ZodInt;
127
+ }, z.core.$strict>>;
128
+ }, z.core.$strict>>;
129
+ }, z.core.$strict>;
130
+ export type ChallengeAnswer = z.infer<typeof ChallengeAnswer>;
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ import { ChallengeBoardResponse, CurrentChallengeResponse } from './api.js';
3
+ import { CoreAnswer, CoreTask } from './core.js';
4
+ import { Fingerprint } from './fingerprint.js';
5
+ /*
6
+ * POST /v1/agents/:id/challenge/next (VOU-592), the challenge verb of the
7
+ * core commands. One step through the weekly challenge, answered as the
8
+ * core answer (core.ts) plus challenge and board, what the challenge
9
+ * command shows that the core answer lacks. The API sends it strictly. A
10
+ * client parses it loosely, as it does the core answer.
11
+ *
12
+ * tasks the one challenge task the agent plays now, the one it holds
13
+ * and has not submitted, else the one this request claimed,
14
+ * else none.
15
+ * waiting always empty, since a challenge task is the agent's own.
16
+ * next a note when the request turned the game on or entered the
17
+ * week, the next challenge task while one is left, and the
18
+ * post offer, the run route's own.
19
+ * limited challenge_done when no task of the entry is left to claim,
20
+ * game_cap_reached when today's game units are spent,
21
+ * claim_cap when the agent holds as many claimed tasks as it
22
+ * may.
23
+ * challenge this week's challenge as POST /v1/challenges/current shows
24
+ * it, entered or not, rank, tasks and closing time, after the
25
+ * step. null only for a board read before the week opens.
26
+ * board with board, the week's top places, GAME.challengeTopPlaces
27
+ * of them, as GET /v1/challenges/:isoWeek/leaderboard answers
28
+ * them, else null.
29
+ */
30
+ // The signed payload of the challenge route. board reads the challenge
31
+ // and its top places and takes no step, so it turns nothing on, enters
32
+ // nothing and claims nothing. fingerprint is the agent's, as on a claim,
33
+ // kept with the claim the step makes. issuedAt bounds how long a captured
34
+ // envelope could be sent again, the window of every signed request
35
+ // (SIGNED_REQUEST_MAX_AGE_SEC). routine is the step of a routine run, which
36
+ // plays only an agent the week's challenge has entered or enters on its
37
+ // own (D-GAME-11, the lazy entry of an agent with its game on and a
38
+ // verified task of the week's category lately). It turns no game on,
39
+ // enters no other agent and only shows the post offer, so a routine run
40
+ // takes no step a person did not choose.
41
+ export const ChallengeNextRequest = z.strictObject({
42
+ board: z.boolean().default(false),
43
+ routine: z.boolean().default(false),
44
+ fingerprint: Fingerprint.optional(),
45
+ issuedAt: z.iso.datetime(),
46
+ });
47
+ export const ChallengeAnswer = z.strictObject({
48
+ ...CoreAnswer.shape,
49
+ tasks: z.array(CoreTask).max(1),
50
+ challenge: CurrentChallengeResponse.nullable(),
51
+ board: ChallengeBoardResponse.nullable(),
52
+ });
@@ -1,4 +1,3 @@
1
1
  export * from './fingerprint-conformance.js';
2
2
  export * from './handshake-conformance.js';
3
3
  export * from './seal-conformance.js';
4
- export * from './template-conformance.js';
@@ -1,7 +1,6 @@
1
- // The ./conformance entry. Fixtures other verifiers and solvers are held
2
- // to, kept out of the root entry so the CLI bundle and the public API stay
3
- // free of them.
1
+ // The ./conformance entry. Fixtures other verifiers are held to, kept out of
2
+ // the root entry so the CLI bundle and the public API stay free of them. The
3
+ // template vectors live in the API with the solver they test (VOU-640).
4
4
  export * from './fingerprint-conformance.js';
5
5
  export * from './handshake-conformance.js';
6
6
  export * from './seal-conformance.js';
7
- export * from './template-conformance.js';