@domino-sdk/relay 1.61.0 → 1.63.0

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +276 -8
  2. package/README.md +3 -2
  3. package/dist/{schema-CZ6Rj0ZF.d.ts → accounts-nz3mzfRQ.d.ts} +3146 -1765
  4. package/dist/action-9Xl0StBg.d.ts +33 -0
  5. package/dist/{attempt-captures-DYnJVkfg.d.ts → attempt-captures-DMi8oR9T.d.ts} +1 -1
  6. package/dist/authoring.d.ts +171 -49
  7. package/dist/authoring.js +158 -49
  8. package/dist/{automatic-outcomes-If88LR9f.d.ts → automatic-outcomes-C_LLQU1p.d.ts} +15 -1
  9. package/dist/avatar.d.ts +1 -1
  10. package/dist/browser.d.ts +15 -11
  11. package/dist/browser.js +18 -12
  12. package/dist/capability-error-CuI_F7yM.d.ts +296 -0
  13. package/dist/{pages-K7-KjSXw.d.ts → catalog-BaTOKpNw.d.ts} +56 -567
  14. package/dist/{chunk-XBV2CK2H.js → chunk-3RFP6GT5.js} +15 -3
  15. package/dist/{chunk-VURRFVNP.js → chunk-4GQW43UU.js} +239 -361
  16. package/dist/chunk-4NOFSGAL.js +320 -0
  17. package/dist/{chunk-24I4R732.js → chunk-5XLZ34PV.js} +34 -19
  18. package/dist/{chunk-L77O64I4.js → chunk-7X67QLPB.js} +43 -43
  19. package/dist/{chunk-RHBS5FJL.js → chunk-7Z4SZ6YM.js} +3 -3
  20. package/dist/{chunk-QEAS4ZGS.js → chunk-AQOL3SK5.js} +1 -1
  21. package/dist/chunk-EHUSD3O2.js +377 -0
  22. package/dist/chunk-FO2LCZTL.js +19 -0
  23. package/dist/{chunk-DYXDZFKA.js → chunk-FU5CGNTR.js} +166 -178
  24. package/dist/chunk-FWLQ5XCS.js +21 -0
  25. package/dist/chunk-GLH6MEI2.js +193 -0
  26. package/dist/chunk-ICXZZNPS.js +199 -0
  27. package/dist/{chunk-ZDJLPN4L.js → chunk-ID3BZXFZ.js} +2696 -2693
  28. package/dist/chunk-KP2MCX3R.js +587 -0
  29. package/dist/{chunk-SKGTNTLF.js → chunk-LZWXCBUB.js} +213 -297
  30. package/dist/chunk-MTKRIQEU.js +31 -0
  31. package/dist/{chunk-4OC7REYX.js → chunk-MWMCQLCG.js} +3 -3
  32. package/dist/{chunk-PPB7HALD.js → chunk-N4BDKS6Y.js} +27 -26
  33. package/dist/{chunk-5I54XBK2.js → chunk-N4LNEGQX.js} +2 -2
  34. package/dist/chunk-NQDEWJWE.js +554 -0
  35. package/dist/chunk-QR6HH4DI.js +508 -0
  36. package/dist/chunk-TLSQETTY.js +41 -0
  37. package/dist/{chunk-MHPKEVEW.js → chunk-U66GUVSG.js} +7 -2
  38. package/dist/{chunk-KDR2VAJP.js → chunk-UYVJZR33.js} +2 -2
  39. package/dist/chunk-UZ765UGH.js +18 -0
  40. package/dist/{chunk-7A4J67ZL.js → chunk-VWQGWGCH.js} +7 -7
  41. package/dist/chunk-W7YSMSGC.js +725 -0
  42. package/dist/{client-C51w7fMb.d.ts → client-DHmG7rn6.d.ts} +126 -93
  43. package/dist/{announcements-CHx3-HBw.d.ts → dashboards-CLemT6kj.d.ts} +14 -137
  44. package/dist/{deployments-kWoHFaqj.d.ts → deployments-DTULi7dF.d.ts} +450 -18
  45. package/dist/identity-integration-DOeNZaxF.d.ts +1271 -0
  46. package/dist/index.d.ts +13 -11
  47. package/dist/index.js +16 -11
  48. package/dist/internal.d.ts +29 -13
  49. package/dist/internal.js +57 -17
  50. package/dist/{integrations-B3y36OaB.d.ts → limits-kNzYdZ00.d.ts} +464 -53
  51. package/dist/{offers-BoV4ryUc.d.ts → offers-CYeN_I0Y.d.ts} +22 -4
  52. package/dist/{participant-activity-DHL8QYks.d.ts → participant-activity-D-Drp24G.d.ts} +16 -3
  53. package/dist/portal-proxy.d.ts +75 -2
  54. package/dist/portal-proxy.js +153 -25
  55. package/dist/react/index.d.ts +214 -27
  56. package/dist/react/index.js +196 -42
  57. package/dist/{action-DBzILFx9.d.ts → request-error-D_dUPLwo.d.ts} +1 -31
  58. package/dist/reward-deliveries-drp-x1G-.d.ts +782 -0
  59. package/dist/runtime/authoring.js +1 -1
  60. package/dist/runtime/chunks/UGDV5W4N.js +1 -0
  61. package/dist/runtime/manifest.json +3 -3
  62. package/dist/runtime/schema.js +1 -1
  63. package/dist/schema.d.ts +330 -31
  64. package/dist/schema.js +147 -91
  65. package/dist/server/index.d.ts +14 -12
  66. package/dist/server/index.js +6425 -6260
  67. package/dist/sign-in-providers-B1Q9eDRm.d.ts +44 -0
  68. package/dist/submission-input-DZCbjQUH.d.ts +343 -0
  69. package/dist/{telegram-browser-BUceZIGZ.d.ts → telegram-browser-NL0Asmsi.d.ts} +46 -6
  70. package/dist/telegram.d.ts +64 -0
  71. package/dist/telegram.js +23 -0
  72. package/dist/testing.d.ts +326 -27
  73. package/dist/testing.js +440 -90
  74. package/dist/tracker-data-esm3WG8R.d.ts +1881 -0
  75. package/dist/{transport-BrX9pIGN.d.ts → transport-Czht_irp.d.ts} +43483 -38512
  76. package/dist/{wallet-CIqFoxdm.d.ts → wallet-B7e06v4m.d.ts} +32 -9
  77. package/package.json +5 -1
  78. package/dist/accounts-DoabriBX.d.ts +0 -306
  79. package/dist/capability-error-DufwG3G0.d.ts +0 -159
  80. package/dist/chunk-3Y3L2F54.js +0 -982
  81. package/dist/chunk-DHXDBAJF.js +0 -544
  82. package/dist/chunk-EN3M5Z4J.js +0 -13
  83. package/dist/chunk-QNUHWK7R.js +0 -602
  84. package/dist/chunk-SE4PFJPT.js +0 -247
  85. package/dist/groups-CqcA2Ycs.d.ts +0 -2827
  86. package/dist/runtime/chunks/5QVFB4TU.js +0 -1
  87. package/dist/submission-input-C54Q8yAD.d.ts +0 -146
  88. package/dist/x-D1PGi8Sq.d.ts +0 -331
package/dist/testing.js CHANGED
@@ -1,23 +1,40 @@
1
1
  import {
2
2
  attemptCaptureSchema
3
- } from "./chunk-7A4J67ZL.js";
3
+ } from "./chunk-VWQGWGCH.js";
4
4
  import {
5
5
  CapabilityError
6
- } from "./chunk-4OC7REYX.js";
6
+ } from "./chunk-MWMCQLCG.js";
7
7
  import {
8
8
  parseImageResult
9
- } from "./chunk-5I54XBK2.js";
9
+ } from "./chunk-N4LNEGQX.js";
10
10
  import {
11
11
  imageRequestSchema,
12
12
  xConnectionSchema,
13
13
  xPostSchema,
14
14
  xProfileSchema,
15
15
  xRequestSchema
16
- } from "./chunk-L77O64I4.js";
16
+ } from "./chunk-7X67QLPB.js";
17
17
  import {
18
+ isParticipantRoute
19
+ } from "./chunk-U66GUVSG.js";
20
+ import {
21
+ participantActivitySchema,
22
+ questExperienceSchema
23
+ } from "./chunk-4NOFSGAL.js";
24
+ import {
25
+ badgeCatalogSchema,
26
+ badgeSchema,
27
+ leaderboardPageSchema,
18
28
  pointBalanceSchema,
29
+ pointBalancesSchema,
30
+ pointHistoryPageSchema,
31
+ pointLedgerReferenceSchema,
19
32
  profileReferralSchema
20
- } from "./chunk-SE4PFJPT.js";
33
+ } from "./chunk-W7YSMSGC.js";
34
+ import {
35
+ exchangeResultSchema,
36
+ participantSessionSchema
37
+ } from "./chunk-N4BDKS6Y.js";
21
38
  import {
22
39
  contentPerformanceSnapshotSchema,
23
40
  cosmosAddressSchema,
@@ -32,8 +49,9 @@ import {
32
49
  isContractEvent,
33
50
  isNftCollection,
34
51
  isSolanaProgram,
52
+ knownResponse,
53
+ limits,
35
54
  linkedWalletSchema,
36
- maxMemberCompletions,
37
55
  memberCompletionSchema,
38
56
  memberIdentitySchema,
39
57
  photoResultSchema,
@@ -42,6 +60,8 @@ import {
42
60
  solanaAddressSchema,
43
61
  solanaMainnet,
44
62
  telegramChatMemberSchema,
63
+ telegramConnectionInputSchema,
64
+ telegramConnectionSchema,
45
65
  telegramMemberInputSchema,
46
66
  textEvidenceSchema,
47
67
  textResultSchema,
@@ -61,7 +81,10 @@ import {
61
81
  walletEventsSchema,
62
82
  walletNftsSchema,
63
83
  walletTransactionsSchema
64
- } from "./chunk-ZDJLPN4L.js";
84
+ } from "./chunk-ID3BZXFZ.js";
85
+ import {
86
+ RelayErrorCode
87
+ } from "./chunk-GLH6MEI2.js";
65
88
 
66
89
  // src/testing/run.ts
67
90
  import * as z3 from "zod";
@@ -151,12 +174,12 @@ function evaluationCalendar(snapshot, eligibleAt) {
151
174
 
152
175
  // src/testing/capabilities.ts
153
176
  import * as z from "zod";
154
- var trackerInput = {
177
+ var trackerInput = /* @__PURE__ */ (() => ({
155
178
  reference: identifier,
156
179
  query: trackerQuerySchema,
157
180
  asOf: z.number().int().nonnegative().safe()
158
- };
159
- var capabilityRequestSchemas = {
181
+ }))();
182
+ var capabilityRequestSchemas = /* @__PURE__ */ (() => ({
160
183
  "wallet-balance": z.strictObject({
161
184
  kind: z.literal("wallet-balance"),
162
185
  chain: z.union([
@@ -199,39 +222,40 @@ var capabilityRequestSchemas = {
199
222
  "member-identities": z.strictObject({
200
223
  kind: z.literal("member-identities")
201
224
  })
202
- };
203
- var telegramRequestSchema = z.strictObject({
225
+ }))();
226
+ var telegramRequestSchema = /* @__PURE__ */ (() => z.strictObject({
204
227
  kind: z.literal("telegram"),
205
- input: telegramMemberInputSchema
206
- });
207
- var walletNftsRequestSchema = z.strictObject({
228
+ input: z.union([telegramMemberInputSchema, telegramConnectionInputSchema])
229
+ }))();
230
+ var walletNftsRequestSchema = /* @__PURE__ */ (() => z.strictObject({
208
231
  kind: z.literal("wallet-nfts"),
209
232
  collection: z.string().max(200).refine(isNftCollection),
210
233
  address: z.string().min(1).max(100).nullable()
211
- });
212
- var walletEventConditionSchema = z.union([
234
+ }))();
235
+ var walletEventConditionSchema = /* @__PURE__ */ (() => z.union([
213
236
  z.string().min(1).max(200),
214
237
  z.boolean(),
215
238
  z.strictObject({ gte: z.string().regex(/^-?(0|[1-9][0-9]{0,77})$/) })
216
- ]);
217
- var walletEventsRequestSchema = z.strictObject({
239
+ ]))();
240
+ var walletEventsRequestSchema = /* @__PURE__ */ (() => z.strictObject({
218
241
  kind: z.literal("wallet-events"),
219
242
  event: z.string().max(1e3).refine(isContractEvent),
220
243
  account: z.string().min(1).max(100),
221
244
  since: z.number().int().nonnegative().safe(),
222
245
  afterActivation: z.boolean(),
223
246
  where: z.record(z.string().min(1).max(100), walletEventConditionSchema).refine((where) => Object.keys(where).length <= 10).nullable(),
224
- address: z.string().min(1).max(100).nullable()
225
- });
226
- var walletTransactionsRequestSchema = z.strictObject({
247
+ address: z.string().min(1).max(100).nullable(),
248
+ newest: z.number().int().min(1).max(limits.walletEventLogs).optional()
249
+ }))();
250
+ var walletTransactionsRequestSchema = /* @__PURE__ */ (() => z.strictObject({
227
251
  kind: z.literal("wallet-transactions"),
228
252
  program: z.string().max(44).refine(isSolanaProgram),
229
253
  since: z.number().int().nonnegative().safe(),
230
254
  afterActivation: z.boolean(),
231
255
  mint: solanaAddressSchema.nullable(),
232
256
  address: z.string().min(1).max(100).nullable()
233
- });
234
- var walletCallRequestSchema = z.strictObject({
257
+ }))();
258
+ var walletCallRequestSchema = /* @__PURE__ */ (() => z.strictObject({
235
259
  kind: z.literal("wallet-call"),
236
260
  call: z.string().max(400).refine(isContractCall),
237
261
  args: z.array(
@@ -246,14 +270,14 @@ var walletCallRequestSchema = z.strictObject({
246
270
  "Pass the member's wallet ({ wallet: true }) at most once"
247
271
  ),
248
272
  address: z.string().min(1).max(100).nullable()
249
- });
250
- var walletDelegationsRequestSchema = z.strictObject({
273
+ }))();
274
+ var walletDelegationsRequestSchema = /* @__PURE__ */ (() => z.strictObject({
251
275
  kind: z.literal("wallet-delegations"),
252
276
  chain: cosmosNetworkSchema,
253
277
  validator: cosmosAddressSchema.nullable(),
254
278
  address: z.string().min(1).max(100).nullable()
255
- });
256
- var memberStateRequestSchemas = {
279
+ }))();
280
+ var memberStateRequestSchemas = /* @__PURE__ */ (() => ({
257
281
  "member-balance": z.strictObject({
258
282
  kind: z.literal("member-balance"),
259
283
  ledger: identifier
@@ -262,7 +286,7 @@ var memberStateRequestSchemas = {
262
286
  kind: z.literal("member-completions"),
263
287
  quest: identifier.nullable(),
264
288
  since: z.number().int().nonnegative().safe().nullable(),
265
- limit: z.number().int().min(1).max(maxMemberCompletions)
289
+ limit: z.number().int().min(1).max(limits.memberCompletions)
266
290
  }),
267
291
  "member-groups": z.strictObject({ kind: z.literal("member-groups") }),
268
292
  "member-enrolled": z.strictObject({
@@ -273,11 +297,11 @@ var memberStateRequestSchemas = {
273
297
  kind: z.literal("member-referrals"),
274
298
  program: identifier
275
299
  })
276
- };
300
+ }))();
277
301
  function unlistedRequest(request) {
278
302
  return request.kind === "telegram" || request.kind === "wallet-nfts" || request.kind === "wallet-call" || request.kind === "wallet-delegations" || request.kind === "wallet-events" || request.kind === "wallet-transactions" || request.kind === "member-balance" || request.kind === "member-completions" || request.kind === "member-groups" || request.kind === "member-enrolled" || request.kind === "member-referrals";
279
303
  }
280
- var capabilityRequestSchema = z.discriminatedUnion("kind", [
304
+ var capabilityRequestSchema = /* @__PURE__ */ (() => z.discriminatedUnion("kind", [
281
305
  capabilityRequestSchemas["wallet-balance"],
282
306
  capabilityRequestSchemas["tracker-content"],
283
307
  capabilityRequestSchemas.x,
@@ -300,7 +324,7 @@ var capabilityRequestSchema = z.discriminatedUnion("kind", [
300
324
  memberStateRequestSchemas["member-groups"],
301
325
  memberStateRequestSchemas["member-enrolled"],
302
326
  memberStateRequestSchemas["member-referrals"]
303
- ]);
327
+ ]))();
304
328
  function parseCapabilityRequest(raw) {
305
329
  return capabilityRequestSchema.safeParse(raw);
306
330
  }
@@ -342,7 +366,7 @@ function parseCapabilityResult(request, value) {
342
366
  case "member-identities":
343
367
  return z.array(memberIdentitySchema).min(1).nullable().parse(value);
344
368
  case "telegram":
345
- return telegramChatMemberSchema.nullable().parse(value);
369
+ return ("chat" in request.input ? telegramChatMemberSchema : telegramConnectionSchema).nullable().parse(value);
346
370
  case "wallet-nfts":
347
371
  return walletNftsSchema.nullable().parse(value);
348
372
  case "wallet-call":
@@ -365,6 +389,24 @@ function parseCapabilityResult(request, value) {
365
389
  return profileReferralSchema.parse(value);
366
390
  }
367
391
  }
392
+ function newerResultIssue(issue) {
393
+ if (issue.code === "unrecognized_keys" || issue.code === "invalid_value") {
394
+ return true;
395
+ }
396
+ return issue.code === "invalid_union" && issue.errors.some(
397
+ (branch) => branch.length > 0 && branch.every(newerResultIssue)
398
+ );
399
+ }
400
+ function parseCapturedResult(request, value) {
401
+ try {
402
+ return parseCapabilityResult(request, value);
403
+ } catch (error) {
404
+ if (error instanceof z.ZodError && error.issues.every(newerResultIssue)) {
405
+ return value;
406
+ }
407
+ throw error;
408
+ }
409
+ }
368
410
  function imageReferences(input) {
369
411
  return input.operation === "compare" || input.operation === "review" ? input.images : [input.image];
370
412
  }
@@ -414,6 +456,7 @@ var readerFactories = {
414
456
  message: (input) => call({ kind: "discord", input })
415
457
  }),
416
458
  telegram: (call) => ({
459
+ connection: () => call({ kind: "telegram", input: { operation: "connection" } }),
417
460
  member: (input) => call({ kind: "telegram", input })
418
461
  }),
419
462
  x: (call) => ({
@@ -493,7 +536,9 @@ var readerFactories = {
493
536
  since: query.since,
494
537
  afterActivation: query.afterActivation ?? false,
495
538
  where: query.where ?? null,
496
- address: query.address ?? null
539
+ address: query.address ?? null,
540
+ // Only when set, so a call without it sends the request it always did.
541
+ ...query.newest === void 0 ? {} : { newest: query.newest }
497
542
  }),
498
543
  transactions: (query) => call({
499
544
  kind: "wallet-transactions",
@@ -641,21 +686,21 @@ function install(now, seed, restores) {
641
686
  const crypto = globalThis.crypto;
642
687
  if (crypto) {
643
688
  restores.push(
644
- replace(crypto, "getRandomValues", (array3) => {
645
- if (!integerArrays.some((type) => array3 instanceof type)) {
689
+ replace(crypto, "getRandomValues", (array4) => {
690
+ if (!integerArrays.some((type) => array4 instanceof type)) {
646
691
  throw new DOMException(
647
692
  "getRandomValues takes an integer typed array",
648
693
  "TypeMismatchError"
649
694
  );
650
695
  }
651
- if (array3.byteLength > 65536) {
696
+ if (array4.byteLength > 65536) {
652
697
  throw new DOMException(
653
698
  "getRandomValues takes at most 65536 bytes",
654
699
  "QuotaExceededError"
655
700
  );
656
701
  }
657
- fill(new Uint8Array(array3.buffer, array3.byteOffset, array3.byteLength));
658
- return array3;
702
+ fill(new Uint8Array(array4.buffer, array4.byteOffset, array4.byteLength));
703
+ return array4;
659
704
  })
660
705
  );
661
706
  restores.push(
@@ -675,10 +720,10 @@ function install(now, seed, restores) {
675
720
 
676
721
  // src/testing/facts.ts
677
722
  import * as z2 from "zod";
678
- var campaignFactSchema = z2.strictObject({
723
+ var campaignFactSchema = /* @__PURE__ */ (() => z2.strictObject({
679
724
  key: identifier,
680
725
  enrolled: z2.boolean()
681
- });
726
+ }))();
682
727
  var TestSetupError = class extends Error {
683
728
  constructor(message) {
684
729
  super(message);
@@ -796,10 +841,14 @@ function fakeFor(facts, request) {
796
841
  };
797
842
  }
798
843
  case "telegram":
799
- return {
844
+ return "chat" in request.input ? {
800
845
  name: "telegram.member",
801
846
  answer: facts.telegram?.member,
802
847
  args: [request.input]
848
+ } : {
849
+ name: "telegram.connection",
850
+ answer: facts.telegram?.connection,
851
+ args: []
803
852
  };
804
853
  case "tracker-records":
805
854
  case "tracker-summary": {
@@ -1082,6 +1131,9 @@ function factsAnswerer(facts) {
1082
1131
  return "channelId" in request.input || fake.answer !== void 0 ? ask(fake) : { value: null };
1083
1132
  // Without an answer, the member has not linked Telegram.
1084
1133
  case "telegram":
1134
+ if (!("chat" in request.input)) {
1135
+ return pin(request);
1136
+ }
1085
1137
  return fake.answer !== void 0 ? ask(fake) : { value: null };
1086
1138
  case "image": {
1087
1139
  if (imageReferences(request.input).some(
@@ -1158,7 +1210,7 @@ function rewardSelectionIssue(selected, published) {
1158
1210
  }
1159
1211
 
1160
1212
  // src/testing/run.ts
1161
- var defaultNow = Date.UTC(2026, 0, 1);
1213
+ var defaultNow = /* @__PURE__ */ (() => Date.UTC(2026, 0, 1))();
1162
1214
  var defaultMember = "member_test";
1163
1215
  var defaultSeed = "attempt_test";
1164
1216
  var maxCapabilityCalls = 8;
@@ -1266,6 +1318,7 @@ function hostContext(run, answer) {
1266
1318
  const evidence = typeof input.evidence === "string" ? input.evidence : void 0;
1267
1319
  const calendar = evaluationCalendar({ segments: run.segments }, eligibleAt);
1268
1320
  const calls = [];
1321
+ const capabilityCalls = [];
1269
1322
  const diagnostics = [];
1270
1323
  const problems = [];
1271
1324
  const delivered = /* @__PURE__ */ new WeakSet();
@@ -1286,12 +1339,17 @@ function hostContext(run, answer) {
1286
1339
  request.kind === "photo" ? "Photo capability cannot access another attempt\u2019s evidence" : "Image capability cannot access another attempt\u2019s evidence"
1287
1340
  );
1288
1341
  }
1289
- const outcome = await answer(request, sequence2);
1342
+ const { captured, ...outcome } = await answer(request, sequence2);
1290
1343
  if ("error" in outcome) {
1291
1344
  return outcome;
1292
1345
  }
1293
1346
  try {
1294
- const value = z3.json().parse(parseCapabilityResult(request, outcome.value));
1347
+ const value = z3.json().parse(
1348
+ (captured ? parseCapturedResult : parseCapabilityResult)(
1349
+ request,
1350
+ outcome.value
1351
+ )
1352
+ );
1295
1353
  return { value: JSON.parse(JSON.stringify(value)) };
1296
1354
  } catch (error) {
1297
1355
  throw new TestSetupError(
@@ -1301,7 +1359,7 @@ function hostContext(run, answer) {
1301
1359
  }
1302
1360
  const call = async (raw) => {
1303
1361
  const current = sequence++;
1304
- if (current >= maxCapabilityCalls) {
1362
+ if (current >= limits.capabilityCalls) {
1305
1363
  throw capabilityLimitError();
1306
1364
  }
1307
1365
  const parsed = parseCapabilityRequest(raw);
@@ -1321,6 +1379,8 @@ function hostContext(run, answer) {
1321
1379
  problems.push(error instanceof Error ? error : new Error(String(error)));
1322
1380
  throw error;
1323
1381
  }
1382
+ capabilityCalls.push({ sequence: current, request: parsed.data, outcome });
1383
+ capabilityCalls.sort((a, b) => a.sequence - b.sequence);
1324
1384
  if (!unlistedRequest(parsed.data)) {
1325
1385
  calls.push({ sequence: current, request: parsed.data, outcome });
1326
1386
  calls.sort((a, b) => a.sequence - b.sequence);
@@ -1362,6 +1422,7 @@ function hostContext(run, answer) {
1362
1422
  return {
1363
1423
  context,
1364
1424
  calls,
1425
+ capabilityCalls,
1365
1426
  diagnostics,
1366
1427
  problems,
1367
1428
  delivered,
@@ -1395,7 +1456,7 @@ function thrownMessage(error) {
1395
1456
  function capabilityLimitError() {
1396
1457
  return new CapabilityError(
1397
1458
  "capability-limit",
1398
- `An evaluation run makes at most ${maxCapabilityCalls} capability calls, shared by all capabilities`,
1459
+ `An evaluation run makes at most ${limits.capabilityCalls} capability calls, shared by all capabilities`,
1399
1460
  { retryable: false }
1400
1461
  );
1401
1462
  }
@@ -1503,6 +1564,7 @@ function evaluateHosted(quest, host) {
1503
1564
  return {
1504
1565
  decision,
1505
1566
  calls: host.calls,
1567
+ capabilityCalls: host.capabilityCalls,
1506
1568
  release: host.release,
1507
1569
  diagnostics: host.diagnostics
1508
1570
  };
@@ -1518,6 +1580,68 @@ async function runQuest(quest, options = {}) {
1518
1580
  );
1519
1581
  }
1520
1582
 
1583
+ // src/testing/member.ts
1584
+ var keyOf = (resource) => typeof resource === "string" ? resource : resource.key;
1585
+ function fakeEvaluationMember(member = {}) {
1586
+ return {
1587
+ id: defaultMember,
1588
+ identities: async () => null,
1589
+ balance: async (ledger) => {
1590
+ throw new CapabilityError(
1591
+ "author-error",
1592
+ `The fake member has no balance in point ledger ${keyOf(ledger)}; pass balance to fakeEvaluationMember`,
1593
+ { retryable: false }
1594
+ );
1595
+ },
1596
+ completions: async () => [],
1597
+ groups: async () => [],
1598
+ enrolled: async () => false,
1599
+ referrals: async (program) => {
1600
+ throw new CapabilityError(
1601
+ "author-error",
1602
+ `The fake member has no progress in referral program ${keyOf(program)}; pass referrals to fakeEvaluationMember`,
1603
+ { retryable: false }
1604
+ );
1605
+ },
1606
+ ...member
1607
+ };
1608
+ }
1609
+
1610
+ // src/testing/readers.ts
1611
+ import * as z4 from "zod";
1612
+ function factsCall(facts) {
1613
+ const answer = factsAnswerer(facts);
1614
+ return async (raw) => {
1615
+ const request = parseCapabilityRequest(raw);
1616
+ if (!request.success) {
1617
+ throw new CapabilityError(
1618
+ "author-error",
1619
+ z4.prettifyError(request.error),
1620
+ { retryable: false }
1621
+ );
1622
+ }
1623
+ const outcome = await answer(request.data);
1624
+ if ("error" in outcome) {
1625
+ throw CapabilityError.from(outcome.error);
1626
+ }
1627
+ try {
1628
+ return JSON.parse(
1629
+ JSON.stringify(parseCapabilityResult(request.data, outcome.value))
1630
+ );
1631
+ } catch (error) {
1632
+ throw new TestSetupError(
1633
+ `The answer to ${request.data.kind} is not a result Relay delivers: ${error instanceof z4.ZodError ? z4.prettifyError(error) : String(error)}`
1634
+ );
1635
+ }
1636
+ };
1637
+ }
1638
+ function fakeWalletReader(facts = {}) {
1639
+ return readerFactories.wallet(factsCall(facts));
1640
+ }
1641
+ function fakeTelegramReader(facts = {}) {
1642
+ return readerFactories.telegram(factsCall(facts));
1643
+ }
1644
+
1521
1645
  // src/testing/assertions.ts
1522
1646
  var DecisionAssertionError = class extends Error {
1523
1647
  decision;
@@ -1644,7 +1768,7 @@ function trackerPage(tracker, records, options = {}) {
1644
1768
  }
1645
1769
 
1646
1770
  // src/testing/replay.ts
1647
- import * as z4 from "zod";
1771
+ import * as z5 from "zod";
1648
1772
  var ReplayStopped = class extends Error {
1649
1773
  constructor(divergence) {
1650
1774
  super(
@@ -1680,43 +1804,7 @@ function captureAnswerer(facts, fallback, diverged) {
1680
1804
  const fact = facts.find((candidate) => candidate.sequence === sequence);
1681
1805
  const reason = !fact ? "new" : !sameJson(fact.request, request) ? "changed" : !fact.captured || !fact.outcome ? "uncaptured" : null;
1682
1806
  if (fact?.outcome && !reason) {
1683
- return fact.outcome;
1684
- }
1685
- if (unlistedRequest(request)) {
1686
- if (!answerFallback) {
1687
- const [reader, fact2] = {
1688
- telegram: ["ctx.telegram.member()", "facts.telegram"],
1689
- "wallet-nfts": ["ctx.wallet.nfts()", "facts.wallet.nfts"],
1690
- "wallet-call": ["ctx.wallet.call()", "facts.wallet.call"],
1691
- "wallet-delegations": [
1692
- "ctx.wallet.delegations()",
1693
- "facts.wallet.delegations"
1694
- ],
1695
- "wallet-events": ["ctx.wallet.events()", "facts.wallet.events"],
1696
- "wallet-transactions": [
1697
- "ctx.wallet.transactions()",
1698
- "facts.wallet.transactions"
1699
- ],
1700
- "member-balance": ["ctx.member.balance()", "facts.member.balances"],
1701
- "member-completions": [
1702
- "ctx.member.completions()",
1703
- "facts.member.completions"
1704
- ],
1705
- "member-groups": ["ctx.member.groups()", "facts.member.groups"],
1706
- "member-enrolled": [
1707
- "ctx.member.enrolled()",
1708
- "facts.member.campaigns"
1709
- ],
1710
- "member-referrals": [
1711
- "ctx.member.referrals()",
1712
- "facts.member.referrals"
1713
- ]
1714
- }[request.kind];
1715
- throw new TestSetupError(
1716
- `Capability call ${sequence}, ${reader}, is not in the capture (${reason ?? "new"}): pass ${fact2} to answer it`
1717
- );
1718
- }
1719
- return answerFallback(request);
1807
+ return { ...fact.outcome, captured: true };
1720
1808
  }
1721
1809
  const divergence = {
1722
1810
  sequence,
@@ -1724,7 +1812,7 @@ function captureAnswerer(facts, fallback, diverged) {
1724
1812
  recorded: fact?.request ?? null,
1725
1813
  replayed: request
1726
1814
  };
1727
- diverged(divergence);
1815
+ diverged(divergence, !unlistedRequest(request));
1728
1816
  if (!answerFallback) {
1729
1817
  throw new ReplayStopped(divergence);
1730
1818
  }
@@ -1735,7 +1823,7 @@ async function replayAttempt(quest, capture, options = {}) {
1735
1823
  const parsed = attemptCaptureSchema.safeParse(capture);
1736
1824
  if (!parsed.success) {
1737
1825
  throw new TestSetupError(
1738
- `Not an attempt capture: ${z4.prettifyError(parsed.error)}`
1826
+ `Not an attempt capture: ${z5.prettifyError(parsed.error)}`
1739
1827
  );
1740
1828
  }
1741
1829
  const { quest: captured, context, facts, recorded } = parsed.data;
@@ -1745,6 +1833,7 @@ async function replayAttempt(quest, capture, options = {}) {
1745
1833
  );
1746
1834
  }
1747
1835
  let divergence = null;
1836
+ let capabilityDivergence = null;
1748
1837
  const host = hostContext(
1749
1838
  {
1750
1839
  release: {
@@ -1762,8 +1851,17 @@ async function replayAttempt(quest, capture, options = {}) {
1762
1851
  now: parsed.data.now,
1763
1852
  seed: parsed.data.seed
1764
1853
  },
1765
- captureAnswerer(facts, options.facts, (found) => {
1766
- divergence ??= found;
1854
+ captureAnswerer(facts, options.facts, (found, listed) => {
1855
+ if (capabilityDivergence) {
1856
+ return;
1857
+ }
1858
+ capabilityDivergence = found;
1859
+ divergence = listed ? {
1860
+ sequence: found.sequence,
1861
+ reason: found.reason,
1862
+ recorded: found.recorded,
1863
+ replayed: found.replayed
1864
+ } : null;
1767
1865
  })
1768
1866
  );
1769
1867
  let decision = null;
@@ -1779,7 +1877,254 @@ async function replayAttempt(quest, capture, options = {}) {
1779
1877
  recorded,
1780
1878
  changed: decision && recorded ? !sameJson(decision, recorded) : null,
1781
1879
  divergence,
1782
- calls: host.calls
1880
+ capabilityDivergence,
1881
+ calls: host.calls,
1882
+ capabilityCalls: host.capabilityCalls
1883
+ };
1884
+ }
1885
+
1886
+ // src/testing/participant-api.ts
1887
+ import * as z6 from "zod";
1888
+ var defaultScope = {
1889
+ organization: "org_test",
1890
+ project: "project_test",
1891
+ environment: "test"
1892
+ };
1893
+ var sessionLifetimeMs = 24 * 60 * 60 * 1e3;
1894
+ var refuse = (status, code, error) => Response.json({ error, code }, { status });
1895
+ function createFakeRelay(options = {}) {
1896
+ let member = options.member ?? null;
1897
+ const scope = options.scope ?? defaultScope;
1898
+ const requests = [];
1899
+ const activity = options.activity ?? [];
1900
+ const balances = options.balances ?? [];
1901
+ const badges = options.badges ?? [];
1902
+ const ledgers = options.ledgers ?? balances.map((balance) => balance.ledger).filter(
1903
+ (ledger, index, all) => all.findIndex((other) => other.id === ledger.id) === index
1904
+ );
1905
+ const overrides = Object.entries(options.routes ?? {}).map(
1906
+ ([key, answer2]) => {
1907
+ const [method, path, ...rest] = key.trim().split(/\s+/);
1908
+ if (!method || !path?.startsWith("/v1/") || rest.length > 0) {
1909
+ throw new TestSetupError(
1910
+ `routes["${key}"]: name a route as "METHOD /v1/path"`
1911
+ );
1912
+ }
1913
+ const source = path.split("/").map(
1914
+ (segment) => segment.startsWith(":") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
1915
+ ).join("/");
1916
+ return {
1917
+ method: method.toUpperCase(),
1918
+ pattern: new RegExp(`^${source}$`),
1919
+ answer: answer2
1920
+ };
1921
+ }
1922
+ );
1923
+ const session = (current) => ({
1924
+ ...scope,
1925
+ id: `session_${current}`,
1926
+ principal: { kind: "member", member: current },
1927
+ expiresAt: Date.now() + sessionLifetimeMs
1928
+ });
1929
+ const answer = (route, schema, value) => {
1930
+ const wire = JSON.parse(JSON.stringify(value));
1931
+ const parsed = knownResponse(schema).safeParse(wire);
1932
+ if (!parsed.success) {
1933
+ const issue = parsed.error.issues[0];
1934
+ const at = issue?.path.length ? ` at ${issue.path.join(".")}` : "";
1935
+ throw new TestSetupError(
1936
+ `createFakeRelay: the answer to ${route} is not one Relay sends${at}: ${issue?.message ?? parsed.error.message}`
1937
+ );
1938
+ }
1939
+ return Response.json(wire);
1940
+ };
1941
+ const experience = (current) => answer("GET /v1/me/experience", questExperienceSchema, {
1942
+ member: current,
1943
+ quests: options.quests ?? [],
1944
+ collections: options.collections ?? [],
1945
+ entitlements: options.entitlements ?? []
1946
+ });
1947
+ const activityPage = (current, query) => {
1948
+ const cursor = query.get("cursor");
1949
+ const limit = Number(query.get("limit") ?? 50);
1950
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
1951
+ return refuse(400, "invalid-request", "limit must be 1 to 100");
1952
+ }
1953
+ const start = query.get("start") === "latest" ? activity.length : cursor === null ? 0 : Number(cursor.replace(/^fake-/, ""));
1954
+ if (!Number.isInteger(start) || start < 0 || start > activity.length) {
1955
+ return refuse(
1956
+ 400,
1957
+ "invalid-cursor",
1958
+ "Invalid cursor. Start from the first page."
1959
+ );
1960
+ }
1961
+ const items = activity.slice(start, start + limit);
1962
+ return answer("GET /v1/me/activity", participantActivitySchema, {
1963
+ member: current,
1964
+ items,
1965
+ nextCursor: `fake-${start + items.length}`
1966
+ });
1967
+ };
1968
+ const leaderboard = (board) => {
1969
+ const page = options.leaderboards?.[board] ?? Object.values(options.leaderboards ?? {}).find(
1970
+ (seed) => seed.leaderboard === board
1971
+ );
1972
+ return page ? answer("GET /v1/leaderboards/:board", leaderboardPageSchema, page) : refuse(404, RelayErrorCode.NotFound, "Leaderboard not found");
1973
+ };
1974
+ const pointHistory = (ledger) => {
1975
+ const known = ledgers.find(
1976
+ (reference) => reference.id === ledger || reference.key === ledger
1977
+ );
1978
+ const entries = options.pointHistory?.[ledger] ?? (known && (options.pointHistory?.[known.id] ?? (known.key ? options.pointHistory?.[known.key] : void 0)));
1979
+ if (!known && !entries) {
1980
+ return refuse(404, RelayErrorCode.NotFound, "Point ledger not found");
1981
+ }
1982
+ return answer("GET /v1/me/points/:ledger/history", pointHistoryPageSchema, {
1983
+ items: entries ?? [],
1984
+ nextCursor: null
1985
+ });
1986
+ };
1987
+ const exchange = async (request) => {
1988
+ const credential = request.headers.get("Authorization") ?? "";
1989
+ if (!/^Bearer di_\S+$/.test(credential)) {
1990
+ return Response.json(
1991
+ {
1992
+ error: "Use a valid identity integration server key from Console Settings. CLI and management credentials cannot issue participant sessions."
1993
+ },
1994
+ { status: 401 }
1995
+ );
1996
+ }
1997
+ const body = await request.json().catch(() => null);
1998
+ const subject = typeof body === "object" && body && "subject" in body ? body.subject : void 0;
1999
+ if (typeof subject !== "string" || subject.length === 0) {
2000
+ return refuse(400, "invalid-request", "subject is required");
2001
+ }
2002
+ if (member === null && !identifier.safeParse(subject).success) {
2003
+ throw new TestSetupError(
2004
+ `createFakeRelay: POST /v1/auth/exchange signs in subject "${subject}", which is not a member ID; seed options.member`
2005
+ );
2006
+ }
2007
+ member ??= subject;
2008
+ return answer("POST /v1/auth/exchange", exchangeResultSchema, {
2009
+ token: `fake_${member}`,
2010
+ session: session(member)
2011
+ });
2012
+ };
2013
+ const builtIn = (request, method, path, query) => {
2014
+ if (method === "GET" && path === "/v1/auth/session") {
2015
+ return member === null ? Response.json(null) : answer(
2016
+ "GET /v1/auth/session",
2017
+ participantSessionSchema,
2018
+ session(member)
2019
+ );
2020
+ }
2021
+ if (method === "POST" && path === "/v1/auth/logout") {
2022
+ member = null;
2023
+ return answer("POST /v1/auth/logout", z6.object({ ok: z6.boolean() }), {
2024
+ ok: true
2025
+ });
2026
+ }
2027
+ if (method === "POST" && path === "/v1/auth/exchange") {
2028
+ return exchange(request);
2029
+ }
2030
+ if (method === "GET" && path === "/v1/point-ledgers") {
2031
+ return answer(
2032
+ "GET /v1/point-ledgers",
2033
+ z6.array(pointLedgerReferenceSchema),
2034
+ ledgers
2035
+ );
2036
+ }
2037
+ if (method === "GET" && path === "/v1/badges/catalog") {
2038
+ return answer("GET /v1/badges/catalog", badgeCatalogSchema, {
2039
+ items: badges
2040
+ });
2041
+ }
2042
+ if (method === "GET" && path === "/v1/badges") {
2043
+ return answer("GET /v1/badges", z6.array(badgeSchema), badges);
2044
+ }
2045
+ const board = method === "GET" && /^\/v1\/leaderboards\/([\w-]+)$/.exec(path);
2046
+ if (board) {
2047
+ return leaderboard(board[1]);
2048
+ }
2049
+ if (method !== "GET" || !path.startsWith("/v1/me/")) {
2050
+ return void 0;
2051
+ }
2052
+ const history = /^\/v1\/me\/points\/([\w-]+)\/history$/.exec(path);
2053
+ const covered = path === "/v1/me/experience" || path === "/v1/me/points" || path === "/v1/me/activity" || history;
2054
+ if (!covered) {
2055
+ return void 0;
2056
+ }
2057
+ if (member === null) {
2058
+ return refuse(401, RelayErrorCode.NotSignedIn, "Sign in to continue");
2059
+ }
2060
+ if (history) {
2061
+ return pointHistory(history[1]);
2062
+ }
2063
+ switch (path) {
2064
+ case "/v1/me/experience":
2065
+ return experience(member);
2066
+ case "/v1/me/points":
2067
+ return answer("GET /v1/me/points", pointBalancesSchema, {
2068
+ items: balances
2069
+ });
2070
+ default:
2071
+ return activityPage(member, query);
2072
+ }
2073
+ };
2074
+ const handle = async (request) => {
2075
+ const url = new URL(request.url);
2076
+ const path = url.pathname.startsWith("/relay/v1/") ? url.pathname.slice("/relay".length) : url.pathname;
2077
+ const method = request.method.toUpperCase();
2078
+ requests.push({ method, path });
2079
+ const override = overrides.find(
2080
+ (route) => route.method === method && route.pattern.test(path)
2081
+ );
2082
+ if (override) {
2083
+ return override.answer(request);
2084
+ }
2085
+ const response = builtIn(request, method, path, url.searchParams);
2086
+ if (response) {
2087
+ return response;
2088
+ }
2089
+ if (isParticipantRoute(path, method)) {
2090
+ return refuse(
2091
+ 501,
2092
+ "not-faked",
2093
+ `createFakeRelay does not answer ${method} ${path}: answer it with options.routes["${method} ${path}"]`
2094
+ );
2095
+ }
2096
+ return refuse(404, "route-not-found", "Route not found");
2097
+ };
2098
+ experience(member ?? "member_test");
2099
+ answer("GET /v1/me/points", pointBalancesSchema, { items: balances });
2100
+ answer("GET /v1/point-ledgers", z6.array(pointLedgerReferenceSchema), ledgers);
2101
+ answer("GET /v1/badges/catalog", badgeCatalogSchema, { items: badges });
2102
+ answer("GET /v1/me/activity", participantActivitySchema, {
2103
+ member: member ?? "member_test",
2104
+ items: activity,
2105
+ nextCursor: "fake-0"
2106
+ });
2107
+ for (const ledger of Object.keys(options.pointHistory ?? {})) {
2108
+ pointHistory(ledger);
2109
+ }
2110
+ for (const board of Object.keys(options.leaderboards ?? {})) {
2111
+ leaderboard(board);
2112
+ }
2113
+ if (member !== null) {
2114
+ answer("GET /v1/auth/session", participantSessionSchema, session(member));
2115
+ }
2116
+ return {
2117
+ handle,
2118
+ fetch: (input, init) => handle(
2119
+ new Request(
2120
+ input instanceof Request ? input : new URL(
2121
+ String(input),
2122
+ globalThis.location?.href ?? "http://localhost"
2123
+ ),
2124
+ init
2125
+ )
2126
+ ),
2127
+ requests
1783
2128
  };
1784
2129
  }
1785
2130
  export {
@@ -1787,6 +2132,7 @@ export {
1787
2132
  DecisionAssertionError,
1788
2133
  EvaluatorError,
1789
2134
  TestSetupError,
2135
+ createFakeRelay,
1790
2136
  createTestContext,
1791
2137
  defaultMember,
1792
2138
  defaultNow,
@@ -1796,6 +2142,10 @@ export {
1796
2142
  expectProgress,
1797
2143
  expectReject,
1798
2144
  expectReview,
2145
+ fakeEvaluationMember,
2146
+ fakeTelegramReader,
2147
+ fakeWalletReader,
2148
+ limits,
1799
2149
  maxCapabilityCalls,
1800
2150
  replayAttempt,
1801
2151
  runQuest,