@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/CHANGELOG.md CHANGED
@@ -1,5 +1,221 @@
1
1
  # @domino-sdk/relay
2
2
 
3
+ ## 1.63.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1fa78d2: `limits` reads Relay's platform limits as `number`, so a 1.x release can raise one or add one (released SDKs keep checking the values they shipped with, so Relay answers them within those): `limits.quests`, `limits.questTypes`, `limits.collections`, `limits.sdkRuntime`, `limits.memberCompletions`, `limits.dashboardPanels`, `limits.dashboards`, `limits.defer`, `limits.retryCooldown`, `limits.analyticsQueryRows`, `limits.apps` (secrets, KV and SQL stores, migrations, storage browsing and schedules), `limits.stagedDeployment`, `limits.capabilityCalls`, `limits.trackerData`, `limits.trackerPreview`, `limits.walletsPerChain`, `limits.walletEventLogs` and `limits.walletTransactions`. It is exported from `@domino-sdk/relay/schema`, `/authoring`, `/server` and `/testing`, with its `Limits` type.
8
+
9
+ Deprecated, with unchanged values and types: the literal-typed limit constants `limits` replaces, which can never change in 1.x. They are `questScaleLimits`, `sdkRuntimeLimits`, `maxMemberCompletions`, `dashboardLimits`, `deferPolicy`, `retryCooldownPolicy`, `analyticsQueryLimit`, `APP_SECRET_LIMIT`, `APP_KV_LIMIT`, `APP_SQL_LIMIT`, `APP_MIGRATION_LIMIT`, `APP_MIGRATION_FILE_BYTES`, `APP_MIGRATION_TOTAL_BYTES`, `APP_STORAGE_ROW_LIMIT`, `APP_STORAGE_PREVIEW_CHARACTERS`, `APP_STORAGE_CELL_CHARACTERS`, `stagedDeploymentLimits`, `APP_SCHEDULE_LIMIT`, `APP_SCHEDULE_MIN_INTERVAL`, `APP_SCHEDULE_TIMEOUT`, `APP_SCHEDULE_RESULT_BYTES`, `maxCapabilityCalls`, `trackerDataMaxBranches`, `trackerDataMaxEntries`, `trackerPreviewLimits`, `WALLETS_PER_CHAIN`, `maxWalletEventLogs` and `maxWalletTransactions`.
10
+
11
+ Deprecated and removed in 2.0.0: Relay's own implementation helpers, which apps and quests have no use for. From `@domino-sdk/relay/server`: `participantRoutes`, `participantRoutePattern`, `sequenceCursorSchema`, `sourceDataSchema`, `trackerDataIssue`, `trackerDataIssues`, `whereSchemaIssue`, `validateQuestShape`, `containsReferralIntegrationReward`, `REFERRAL_INTEGRATION_REWARD_ERROR` and `nextScheduleRun`. From `@domino-sdk/relay/schema`: `dashboardIssue`, `normalizeDashboard`, `compatibleSettingKinds`, `isBoundSetting`, `whereSettings` and `sourceContent`. They keep working until then.
12
+
13
+ `z` from `@domino-sdk/relay/authoring` stays zod 4 for all of 1.x.
14
+
15
+ - 052744c: `inspectIntegrationKey({ baseUrl, integrationKey })` from `@domino-sdk/relay/portal-proxy` answers which app login key a `di_` key is: its `id`, `name`, `organization`, `project`, `environment`, `origin`, `identityKind`, `vouchesAccounts`, `secretExpiresAt` and `appOrigins`, the addresses Domino serves the project's app from in that environment. It calls the new `GET /v1/auth/integration`, never returns a secret, and does not count as a use of the key. An unknown, expired or revoked key throws `ParticipantExchangeError` with 401 `invalid-credential`. `identityIntegrationKeySchema` and the `IdentityIntegrationKey` type are exported from `@domino-sdk/relay/server`.
16
+
17
+ Behavior change: `domino secrets set` now checks a value that starts with `di_` before storing it. It refuses, storing nothing and exiting 2 with `INVALID_ARGUMENT`, a key that is unknown, expired or revoked, or that belongs to another project or environment, such as a Live key set as Test's secret. It stores a key whose origin is not the app's own address for that environment with a warning, and a key it could not check (network failure, or a Relay without the route) with a warning. A checked key is named in the result's new `identityIntegration` field. Other values are stored as before.
18
+
19
+ - e71245d: `createParticipantBackend` accepts `onExchangeError(error, request)`, called with each refused sign-in exchange as Domino answered it (a `ParticipantExchangeError` with `status`, `code`, Domino's `message` and `retryAfter`) before the browser gets its answer, so the app's server can log why sign-in failed. The browser's answer is unchanged, also when the callback throws.
20
+
21
+ Behavior change: creating an app login key for one of Domino's own app addresses in the environment that address does not serve is now refused with 400 `invalid-request`, and the message names the right environment. A `<project-slug>--staging.domino.page` or preview address belongs in Test, a live `<project-slug>.domino.page` address in Live. Other origins, custom domains included, can still use either environment, and existing keys keep working. An origin that is a URL but not an exact HTTPS origin is refused with a message that names the origin it accepts ("Use the exact origin https://app.example.com, without a path, query or trailing slash.") or "Use an HTTPS origin." instead of "Use an exact HTTPS application origin"; over HTTP the management API now answers that message instead of "Invalid management request". The app login guide and the Domino skill references gain a troubleshooting section with the exchange refusal codes.
22
+
23
+ - eaaae2a: Test members that `reset` refuses because they redeemed an offer, hold a reservation or a started reward handover, or took part in a referral can now be purged: `management.testMembers.purge({ actionId, members, reason })` and `domino test-members purge MEMBER... --reason TEXT` delete up to 500 test members in one transaction, in the test environment only. The stock unit each redemption, reservation and pickup handover took goes back to its stock item, so the offer's stock and redeemed count stay consistent, and the members' referrals and referral accounting go with them. List both sides of every referral: one with a member outside the list refuses the call with `test-member-reset-blocked` (`referrals affect another member`, naming the unlisted member in `outside`). Every other reason reset refuses still applies, and a refused purge changes nothing. The audit records one event with the operator, the members, the reason and the stock returned. `reset` is unchanged. See [Purge members reset refuses](https://console.domino.run/docs/use/quests#purge-members-reset-refuses).
24
+ - 69e017d: Erase a member on the person's request: `management.members.erase(member, { reason, actionId? })` and `domino members erase MEMBER --reason TEXT [--yes]` (`POST /management/v1/members/:member/erase`, `publish` permission, live and test). It can't be undone. The member's profile and uploaded avatar, sign-in identities, linked accounts and their tokens, app identities, wallets, sessions, consent, leaderboard aliases, referral code, the evidence of every attempt and the stored content of the tracker records their accounts authored are deleted; reasons people typed on the rows that stay read `[erased]`. The member ID keeps its points, completions, rewards and referrals. The result is `{ member, erasedAt, deleted }`; while an attempt waits for a decision or a reward handover is in progress it is refused with 409 `member-erase-blocked`. See [Erase a member on request](https://console.domino.run/docs/use/member-erasure).
25
+
26
+ Member reads (`members.get`, `members.list`) carry a new `erasedAt` on an erased member, and `members.get` answers `profile: null` for it. After erasure, writes naming the member (points, badges, groups, campaigns, consent, profile changes) are refused with the new 409 code `member-erased`, records naming its ID attribute to no one, and the same person signing in again becomes a new member. A member import's revert passes over an erased member's row and reports it in `keptLinks` with reason `member-erased`. The CLI command asks for confirmation in a terminal and needs `--yes` otherwise.
27
+
28
+ - bbc3623: Klaviyo is the first destination plugin: connected through Klaviyo's OAuth, it keeps opted-in members' Klaviyo profiles current with `domino_` properties (points, ranks, quests completed, unredeemed rewards, campaigns), subscribes and unsubscribes them as their email consent changes (a backfill or resync never resubscribes someone Klaviyo has unsubscribed), and records Domino's events as `Domino …` metrics. It never calls Klaviyo's send, campaign or flow APIs. See the new guide, "Send reminders from Klaviyo".
29
+
30
+ `management.connections` gains, experimental like the rest of it:
31
+
32
+ - `connect(plugin, { config, dryRun: true })` connects a destination that supports a dry run, in the test environment, without secrets or the vendor's account. It records what it would have sent instead of sending it. `Connection.dryRun` and `PluginInfo.dryRun` say which. Refusals: 409 `dry-run-not-supported`, `dry-run-test-only`.
33
+ - `activity(plugin)` reads a destination's delivery record: last sync, failing state, members synced and skipped per reason, the latest backfill or resync with its progress, and the latest 20 errors.
34
+ - `resync(plugin)` sends every member's profile and attributes again, never past events; one runs at a time.
35
+ - `dryRunLog(plugin, { cursor, limit })` pages through a dry run's requests, newest first.
36
+
37
+ These answer 409 `not-a-destination` for other plugins. The CLI adds `domino connections connect --dry-run`, `connections activity`, `connections resync` and `connections log`.
38
+
39
+ - 9cb68e2: A text setting can be secret: `settings.text({ label, secret: true })`. Operators set its value in Console or with `management.quests.updateSettings`, and the quest's code reads it as usual, but no management response returns it. Releases leave secret settings out of `values` and `configuration`, and list the ones that hold a value in the new optional `withheldSettings`. So `domino deploy --json`, `--preview --json`, `quests list`, `releases get`, deployments, attempts and member reads no longer print a shared code a guest must not see. Console shows a secret as **Set** or **Not set**, with a field to replace it and **Clear**. A secret setting takes no default: one with a non-empty `default`, or a value from a deploy entry's `settings`, is refused, and reusable quest types refuse secret settings. A secret's entry in a release's `fields` says `secret: true`. Settings without `secret` behave as before. Withholding follows each release's own fields, so releases and deployments from before a setting became secret still show its old value: set a new one after marking an existing setting secret.
40
+ - 8ee88a9: Test members can redeem an offer: `management.testMembers.redeem(member, { offer })`, with the offer's key or ID, and `domino test-members redeem MEMBER OFFER`. The redemption runs as a signed-in member's would, with the same price, stock, per-member limit, requirements and refusals, writes the same redemption and entitlement, answers the redemption, and replays by action ID. It works in the test environment only, and the audit names the operator. A test member with a redemption can't be reset.
41
+ - 6fe6f22: The testing kit lists and replays every capability reader, and a few small defaults change.
42
+
43
+ - **`capabilityCalls`.** `runQuest`, `createTestContext` and `replayAttempt` results gain `capabilityCalls`: every capability call as `{ sequence, request, outcome }`, with an open `CapabilityCallRequest` whose `kind` names the reader. It includes the calls `calls` left out: Telegram, NFT, contract call, delegation, wallet event and transaction reads, and the member's state. `calls` is deprecated.
44
+ - **Replay divergences of any reader.** `replayAttempt` reports a call of any reader the capture cannot answer in the new `capabilityDivergence`, and stops there unless `facts` answers it. Before, such a call of a newer reader failed the replay with `TestSetupError`; now the replay resolves with `decision: null`. `divergence` is deprecated, and is `null` when the first divergence is a newer reader's call.
45
+ - **Tolerant replay.** A captured answer with a field or an enum value a later Relay added, such as a new field on a Discord message, now replays instead of failing. Answers in `facts` are still checked strictly.
46
+ - **Reader fakes.** `fakeWalletReader(facts)` and `fakeTelegramReader(facts)` from `@domino-sdk/relay/testing` build readers with every method Relay supplies, for tests that assemble their own evaluation context. The readers added after 1.0 stay optional in their types; quest code calls them with a non-null assertion, as the snippets now do (`ctx.wallet.nfts!(…)`, `ctx.telegram!.connection!()`), instead of guarding against a runtime that lacks them.
47
+ - **Behavior change: Telegram trackers fire once per record.** `automatic()` on a `telegram.reactions()` or `telegram.messages()` tracker now defaults to `on: "new-record"` instead of `"new-observation"`, so a reaction removed and added again no longer runs the quest again. Published quests keep the mode they were published with; republishing one with this SDK switches it unless it sets `on`.
48
+ - **`domino check` warns `TRIGGER_REFIRES_ON_REFRESH`** for a non-content quest on a YouTube Shorts or TikTok tracker that runs on every observation: each refresh evaluates it again for every video. Use `content({ tracker })` with `performance` for a metric threshold.
49
+ - **`RelayProvider` takes `followWallet`.** `followWallet={false}` keeps a wallet sign-in's session when the wallet switches account or locks. The default, `true`, signs out as before.
50
+ - **Behavior change: `unauthenticated` ends the session.** A participant backend's (`createParticipantBackend`) 401 `unauthenticated` now ends the member's session in the SDK's experience, progress and activity observers, as `not-signed-in` does, instead of reporting an error. `RelayErrorCode.Unauthenticated` names it.
51
+ - **`enrollment` on `not-enrolled` skips.** Tracker trigger outcomes, `relay.quests.outcomes()` and automatic outcomes carry an optional `enrollment` on `not-enrolled` skips: `"quest"` for the quest's own enrollment, `"campaign"` for its campaign's. A campaign skip on a tracker trigger now says "Member has not enrolled in the quest's campaign".
52
+ - **Naming.** A leaderboard's `balance` is the ledger it ranks and its points pool's `ledger` the one the pool pays into; both are documented on the type. New fields that name a ledger are called `ledger`; existing `balance` fields keep their name in 1.x.
53
+
54
+ - 561f4ab: Tiered rewards (`defineTieredReward`) can be listed, archived and restored. `management.resources.list()` and `domino resources list` list them in a new `tieredRewards` array (`managedTieredRewardSchema`), beside `offers`, and `domino tiered-rewards list` lists them alone. The experimental `management.tieredRewards.archive` and `unarchive` (`POST /management/v1/tiered-rewards/REWARD/archive|unarchive`) and `domino tiered-rewards archive|unarchive KEY --reason` archive and restore one in test, by key or ID, with `publish` permission; a promotion carries it to live, and Console **Settings → Resources** lists them with the same controls. Each returns `{ resource, changed }`, typed by `tieredRewardLifecycleResultSchema`. While one is archived, a deploy or promotion that would make a quest, offer or leaderboard prize newly grant it fails with 422 `tiered-reward-archived`, naming it in `reward`. What already granted it keeps granting it, its entitlements and handover keep working, and its key stays taken. Console **Rewards** leaves archived tiered rewards out of its rewards. Promotions now list tiered rewards as definitions of kind `tiered-reward`, and a rollback archives one the earlier promotion did not carry.
55
+ - 8d79fd6: Four operations that had two public paths with different results now agree, or say how they differ:
56
+
57
+ - `management.deployments.promote`, `submit` and `reject` are deprecated, removed in 2.0.0. `promote` prepares a promotion of all of test and publishes nothing; its JSDoc now says so, and that the answered `id` is the promotion's, read with `promotions.get` and rejected by `deployments.get` and `publish`. Use `management.promotions.prepare()` with a client for test, and `promotions.reject` to decline. `submit` and `reject` act only on live deployments made before live refused them. `domino deploy --promote` is deprecated in favor of `domino promote --environment test`, and prints a warning naming it.
58
+ - `management.testMembers.acceptAnyway(member, { attempt, rules: "live", reason })` applies live `attempts.acceptAnyway`'s rules in test: `reason` is required, and an archived quest, a superseded attempt, a completion cooldown, an exhausted budget, an existing or revoked completion and a used key are refused with live's 409 codes, leaving the attempt and balances unchanged. The default (`rules: "test"`) still accepts those, with no reason, so a tester can get a test member unstuck. A `reason` is recorded with the decision either way. `domino test-members accept-anyway MEMBER ATTEMPT --live-rules --reason TEXT` sends it.
59
+ - `GET /v1/reward-deliveries` and `GET /management/v1/reward-deliveries` answer `items` beside `deliveries`, with the same rows, so `paginate((cursor) => relay.me.rewardDeliveries({ cursor }))` and `management.rewardDeliveries({ cursor })` read every page. Both clients fill `items` from `deliveries` when an older Relay sends only `deliveries`. `useExperience().deliveries` reads the role deliveries again on each `delivery.updated` activity event, so a role an operator revoked shows without waiting for focus.
60
+ - `defineLeaderboard` prints a warning, and `domino check` reports a `LEADERBOARD_TIES_DIFFER` warning, when a board's live `ties` differs from its `settle.ties`, including a board that sets only `settle.ties: "earliest"`, whose standings share ranks while its prizes pay first-to-reach. `leaderboardTiesWarning(board)` from `@domino-sdk/relay/authoring` returns that message, or null. `domino check`'s `diagnostics` now include warnings from data definitions beside its errors; only errors fail it.
61
+
62
+ ### Patch Changes
63
+
64
+ - cc04dae: Browser apps bundle less of the SDK: an app's bundler now drops the schemas it never reads, such as tracker, wallet-read and management schemas, instead of keeping every schema in each module the client imports. `@domino-sdk/relay/react` adds about 41 KB less minified JavaScript (235 KB instead of 276 KB), and `@domino-sdk/relay/browser` about 42 KB less. Exports and behavior are unchanged.
65
+ - 4842e30: Wording only. The app identity kind example is now `app:game`, including the validation message "Name an app identity kind like app:game: …", since a Telegram account is vouched for rather than modelled as an app identity. The JSDoc of `progress()`, `quests.claim`, `me.claim` and the quest interactions says which meaning of "progress" and "claim" each uses, and `domino test-members pass` help calls `progress(...)` quests counter quests. Examples use the `@XDevelopers` X account. The changelogs mark the versions that were never published to npm.
66
+ - 425e4df: Internal: the "HTTPS, or plain HTTP on a loopback host" check for API URLs, sign-in URLs and portal proxy origins now has one implementation, shared through `@domino-sdk/relay/internal`. Published behavior is unchanged.
67
+ - 8515f3f: `participantResponses` and `capabilityResponses` from `@domino-sdk/relay/schema` now build each reader on first use, as `managementResponses` and the field-tolerant tables already do. Each reader is built once and stays writable; types and parse results are unchanged.
68
+ - 8278803: Relay now refuses a malformed personal API key ID with 400 `invalid-request` ("Use the key's ID, such as ak_…") on `DELETE /management/v1/tokens/:id` and `PUT /management/v1/tokens/:id/scope`. A key ID is `ak_` followed by letters, digits or `_`. IDs from `tokens.list()` and `domino tokens list` already have that form, so no working call changes: an unknown `ak_…` ID still revokes as a silent success, and a key that belongs to someone else is still refused with 403.
69
+ - eb8572b: The testing kit's participant API answers an activity cursor it cannot read as Relay does: 400 `invalid-cursor`, "Invalid cursor. Start from the first page.", instead of `invalid-request`.
70
+
71
+ ## 1.62.0
72
+
73
+ ### Minor Changes
74
+
75
+ - a341b16: Analytics counts Relay's own `key-used` rejection, of an attempt whose completion key another member's completion holds, as its own rejection source. `rejectionsBySource` gains `keyUsed`, and `analytics.query`'s `source` breakdown a `keyUsed` row; `evaluator` no longer includes them, while an evaluator's own `key-used` still counts there. Each `topReasons` item carries `codeSource` (`relay`, `evaluator`, or null without a code), and Relay's `key-used` and an evaluator's are separate rows, in `topReasons` and in the `reason` breakdown. Both fields are optional in the schema, since older servers don't send them. `domino analytics get` marks the code of a top reason Relay wrote, such as `[relay key-used]` or `[relay budget-exhausted]`.
76
+ - bd622ae: An app login's server can vouch for its members' X, Telegram and Discord accounts and wallets when it signs them in. `createParticipantBackend`'s `authenticate` may return `accounts` next to `subject`, and `onAccounts` receives each account's outcome (`attached`, `already-linked`, or `refused` with a member import refusal code). The new `exchangeParticipant` from `@domino-sdk/relay/portal-proxy` runs the same exchange from a server without a browser and throws `ParticipantExchangeError`, a `RequestError` with the refusal's `status`, `code` and `retryAfter` in milliseconds (`isParticipantExchangeError` recognizes it). The key needs **Vouches accounts**, which `management.identityIntegrations.setAccountVouching(id, { enabled, reason })` turns on from a Console session with publish permission; identity integrations report it as `vouchesAccounts`. Vouched accounts and wallets carry `vouchedBy: { integration, integrationName }` next to `imported: true`.
77
+ - 76be180: An attempt carries `codeSource`: `relay` when Relay wrote its `code`, `evaluator` when the evaluator's `decision.reject({ reason, code })` did, and null without a code (`AttemptCodeSource`, open). `reservedAttemptCodes` no longer grows, so a quest built with any 1.x SDK keeps working when Relay adds a code: `key-used` stays usable as an evaluator's reject code, and `codeSource` tells it apart from Relay's own shared-key rejection. Members still read a rejected attempt's code only when Relay rejected it.
78
+ - 5233be5: The experimental billing results read the plan as `BillingPlan` (`KnownBillingPlan | (string & {})`): `subscription.plan` and `scheduledChange.plan` in `management.billing.read()`, `cancel()`, `resume()` and `organization()`, `billingSubscriptionSchema`, `projectBillingSchema` and `organizationBillingSchema`, and the `--json` results of `domino billing show`, `billing organization`, `billing cancel` and `billing resume`, so a plan Relay adds no longer fails to parse. Handle a plan name this SDK does not know. The stable `management.usage.plan()` and `domino usage plan` are unchanged: the `subscription` they carry keeps the plans this SDK knows (`launch`, `growth`, `scale`).
79
+ - 3665a4e: Billing is experimental while it is open to Domino staff only: the `management.billing` methods, the billing schemas and types in `@domino-sdk/relay/server`, and the `domino billing` commands may change in a minor release, plan names included. `domino billing` now appears under **Experimental commands**. `management.billing.checkout()` and `changePlan()` take any plan name (`BillingPlan`, `KnownBillingPlan | (string & {})`), and `domino billing checkout` and `billing change` take any `--plan`; Relay refuses a plan it does not offer with 400, as before. `management.usage.plan()` and `domino usage plan` stay stable.
80
+
81
+ The CLI's JSON result schemas now leave objects open, as documented, so a JSON Schema validator such as Ajv accepts fields a later release adds; only the variants of a `oneOf` stay closed. `webhooks deliveries`, `storage rows` and `storage keys` results declare the optional `nextCursor` the API already sent. `secrets`, `storage` and `schedules` results include `environment`, and these commands warn on stderr when `RELAY_ENVIRONMENT` or `relay.json` names live while they use test. `contract.json` marks deprecated options with `deprecated`, as it does commands, and `capabilities.json` no longer lists deprecated commands. `management.referrals.control` and `referralControlSchema` now point to `pause` and `resume` instead of the deprecated `suspend`.
82
+
83
+ - 361a5b1: A deployment keeps the active release of a quest it doesn't change, instead of publishing an equal one with a new ID. Deployment previews and results carry `questChanges`: each quest by key, `created`, `updated` (with the release it replaces) or `unchanged`. `domino deploy` lists the created and updated quests in its confirmation and in the `--preview --json` summary, so a deploy that would replace a quest you didn't touch shows before it publishes.
84
+ - 154af63: Discord message and reaction records name a thread's parent channel: `DiscordMessageData` and `DiscordReactionData` gain `parentChannelId`, the channel a thread belongs to (a forum post's forum), or `null` outside a thread. It is absent when it is unknown, because Discord's channel read failed. `channelId` stays the thread's own ID. A trigger's `where: { path: "parentChannelId", equals }` selects a channel's threads; to count a channel and its threads, compare both fields in the evaluator. `management.testMembers.record` and `domino test-members record --data` take `parentChannelId` for Discord records.
85
+ - 79b7c68: `keyed(fn, { across: "members" })` makes a quest's completion keys unique across members: each key completes the quest once in the project and environment, for whichever member's completion commits first, such as a single-use code or a post that pays once. The release's completion is `{ kind: "keyed", across: "members" }`, and quest progress and experience reads carry it. Another member's attempt with a used key is rejected with `key-used` and `codeSource: "relay"`, which members see; `attempts.acceptAnyway` refuses it with 409 `key-used` while the key is held. Revoking the holder's completion with `freeOccurrence: true` frees the key. Member imports refuse a row with `MemberImportRefusalCode.KeyUsed` when another member or an earlier row already uses one of its keys.
86
+ - 56a0601: `fakeEvaluationMember(methods)` from `@domino-sdk/relay/testing` builds an `EvaluationMember` for a test that assembles its own evaluation context. Pass the methods your code reads; the rest answer for a member with no linked accounts, completions, groups or enrollments, and `balance` and `referrals` fail with a `CapabilityError` of code `author-error`. A member written by hand stops compiling when a minor release adds a method; one from `fakeEvaluationMember` does not.
87
+
88
+ The CLI's automation contract (`cli/contract/contract.json`) lists the JSON Schemas of `relay.json` and the checkout's `domino.json` under `files`, so a later release is checked to accept every file an older CLI wrote.
89
+
90
+ - 5cc0d4d: Revoking a completion with `freeOccurrence: true` now returns the budget it used. Each quest budget the completion was charged gets that charge back, in the same transaction and in the budget period it was charged to, so the freed occurrence can be completed again within the cap. A point-priced budget gets back its charge less the points the revoke couldn't reverse. `CompletionRevocation` reports this in a new optional `budgets` (`budget`, `released`). Revoking without freeing the occurrence leaves budgets spent, as before. Completions recorded before this release give no budget back and report `budgets: []`.
91
+
92
+ Restoring such a revocation charges that budget again and reports it in a new optional `CompletionRestoration.budgets` (`budget`, `charged`). The charge is checked against the budget's current limit, or the limit it was first charged under if the quest removed the budget or changed its period or cost. Restore now also refuses with 409 `budget-exhausted`, naming the `budget`, when a budget has no room left for the charge, and then changes nothing. This is a behaviour change: until now, budgets stayed spent after any revoke.
93
+
94
+ - f9c9f34: `defineOffer`, `defineLeaderboard` (prizes) and `defineReferral` (inviter, invitee and milestone rewards) now throw when given a `badge()` or `groupMembership()` reward, so `domino check` reports it before a deploy. These definitions type-checked but a deployment always refused them; now they fail when defined, with `Badge rewards can only be granted by quests or manually, not by offers` (or `leaderboard prizes`, `referral programs`), or `Group membership rewards can only be granted by quests, not by …`. A deployment refuses them with the same message and 422 `validation-failed`, instead of claiming a tier reward was needed. `badgeReward` and `groupReward` from `@domino-sdk/relay/schema` are deprecated: use `badge()` and `groupMembership()` from `@domino-sdk/relay/authoring`. `defineOffer` from `@domino-sdk/relay/schema` is deprecated too and keeps its old behaviour; import it from `@domino-sdk/relay/authoring`, which does the check.
95
+ - dcc32da: `ctx.wallet.events()` takes `newest`, from 1 to 1,000: it returns at most that many of the newest matching events, reading back from the newest block, and stops at the 3,000,000-block or 1,000-log cap instead of throwing `author-error`. `WalletEvents` gains an optional `complete`, `false` when a `newest` read left older matching events out or stopped before `since`. A busy wallet, or a long window on a fast chain such as Arbitrum One (3,000,000 blocks is about 9 days there), no longer has to fail the attempt. A call without `newest` behaves as before: it returns every matching event, now with `complete: true`, or throws `author-error`. A read whose start-block estimate overshot 3,000,000 blocks while the span itself fit is no longer refused. The `onchain-action` snippet reads with `newest: maxWalletEventLogs`.
96
+ - 98ad384: A member import carries its real state in a new `state` field: `open`, `checking`, `checked`, `applying`, `applied`, `stopped`, `reverting`, `reverted` or `canceled`, typed open (`KnownMemberImportState | (string & {})`, with `MemberImportState` listing the known values), so Relay may add states. `status` is unchanged and still reads `applied` for a stopped, reverting or reverted import, so code filtering on `status === "applied"` counts those too; read `state` instead. Import rows gain `reverted`, true once the import's revert went through the row. Both fields are optional in the schema, since older servers don't send them. `domino import` and `domino imports` results include `state`, and the CLI polls on it.
97
+
98
+ `MemberSummary` (`relay.me()`, `useMe()`) now types `email`, `emails` and `clock` as required, since Relay always sends them: code no longer needs to check them for `undefined`. The SDK reads a `/v1/me` response from a Relay older than these fields with `email: null`, `emails: []` and a `clock` at real time (offset 0), the same as code treating them as absent did. `memberTime()` still accepts an object without `clock`.
99
+
100
+ `MemberSummary`, `LeaderboardPage`, `LeaderboardHistoryPage`, `Badge`, `MemberBadge` and `MemberBadges` are marked as response types Relay may add properties to.
101
+
102
+ - 4bf82e5: More management writes are safe to retry. `memberImports.check`, `apply`, `cancel` and `stop`, and `promotions.approve`, take an optional `{ actionId }` before the call options (`check(id, { actionId })`; `check(id, call)` still works), and so does `webhooks.replay(id, delivery, { actionId })`. `promotions.reject`, `members.unlinkCreatorAccount` and `identityIntegrations.setAccountVouching` take `actionId` in their input. With an action ID, a retry answers with the first result instead of a refusal for the state the first call left. These methods now generate an action ID when you pass none, as other writes do, so the client retries them after a lost answer.
103
+
104
+ Inputs fenced by `revision` also take it as `expectedRevision`, the name newer methods use: quest and redemption `pause` and `resume`, `offers.control`, `offers.cancelRedemption`, `handover.begin` and `cancel`, `entitlements.void`, `restore` and `correctCollection`, the pickup policy, `members.unlinkCreatorAccount` and the deprecated account unlinks. Pass either; a call that names two different revisions is refused. `expectedRevision()` and the `ExpectedRevision` type are exported from `@domino-sdk/relay/server`.
105
+
106
+ `attempts.acceptAnyway`, `attempts.redactEvidence`, `completions.revoke` and `completions.restore` take their path ID first, as the route names it: `acceptAnyway(attempt, { reason })`. The object form, `acceptAnyway({ attempt, reason })`, keeps working.
107
+
108
+ - 4bf82e5: `relay.badges.catalog()` reads the project's active badge catalog as `{ items }`, like `relay.me.badges()`, from the new `GET /v1/badges/catalog`. `relay.badges.list()` and `GET /v1/badges` keep answering the bare array and are deprecated. `useBadgeCatalog()` now reads the new route and still returns the array. `createFakeRelay` takes a `badges` seed and answers both routes from it.
109
+
110
+ `relay.auth.exchange()`, `ExchangeInput` and `exchangeSchema` are deprecated: `POST /v1/auth/exchange` refuses the body they send with 400, because the route takes `{ subject, accounts?, previousToken? }` with an identity integration server key. Sign a user in from a server with `exchangeParticipant` from `@domino-sdk/relay/portal-proxy`.
111
+
112
+ The activity feed's JSDoc, and `paginate()`'s, now say that `me.activity.list` never answers a null `nextCursor`, so a loop over it stops on its own condition, such as an empty page or a count.
113
+
114
+ - 4bf82e5: ISO times in management responses carry their epoch milliseconds beside them, named `<field>Ms`. `analytics.read` and `analytics.query` (and so each `dashboards.read` panel) send `range.fromMs`, `range.toMs`, each bucket's `startMs` and `endMs`, `previous.fromMs` and `previous.toMs`, and `testEnvironment.lastResetAtMs`; custom domains (`management.domains.list|get|add|recheck`) send `checkedAtMs`, `nextCheckAtMs`, `createdAtMs` and `cloudflare.certificateExpiresAtMs`. The ISO fields are unchanged, and the new fields are optional in the schemas, since older servers don't send them. `management.testMembers.advance(member, { actionId, to })` takes `to` as epoch milliseconds as well as an ISO 8601 instant with an offset; `testMemberClockAdvanceSchema` parses epoch milliseconds to that instant as an ISO string in UTC, so its parsed `to` stays a string.
115
+ - ea2c9be: A retention job can delete the evidence of a quest's attempts decided before a cutoff: `management.attempts.redactEvidenceBefore({ quest, decidedBefore, reason })` (`review` permission; in live it also requires an organization administrator's Console session or personal key, or a service key, and refuses any other caller with `403 permission-required`, because the deletion can't be undone) deletes up to 100 attempts' evidence per call, oldest decision first, exactly as `attempts.redactEvidence` does for one, and answers `more: true` while others remain. Attempts still waiting for a decision are skipped, and a `decidedBefore` in the future is refused with `400 invalid-request`. Each call records one `evidence-redacted-before` audit event. `domino attempts redact-evidence-before --quest QUEST --decided-before MS --reason TEXT` repeats the call until nothing is left and prints the total.
116
+ - 2de5d71: `@domino-sdk/relay/testing` adds `createFakeRelay(options)`, an experimental in-memory participant API for UI tests of apps built on `@domino-sdk/relay/react`. Seed it with a `member` (or `null` for signed out), `quests`, `balances`, `activity`, `leaderboards` and the rest; it answers the session, logout, experience, points, point history, point ledgers, activity, leaderboard standings and the server integration exchange, under `/v1/...` and `/relay/v1/...`, and returns `handle(request)` for a Playwright or MSW route handler, `fetch` for `createRelayClient`, and `requests`, every request it answered. Each answer is checked against the SDK's response schema, so a seed Relay could never send throws a `TestSetupError` naming the route. Another participant route answers `501` with code `not-faked` until `routes` answers it. The testing guide lists the requests `RelayProvider` makes on mount and while polling. `createFakeRelay` and its `FakeRelay`, `FakeRelayOptions` and `FakeRelayRequest` types are tagged `@experimental`, outside the 1.x compatibility promise: a minor release can change them.
117
+ - e58c2ae: Name a member's account the way a member import does, everywhere an operator command takes one: `{ kind: "x" | "discord" | "telegram", subject }` with the numeric user ID, `{ kind: "app:<name>", subject }`, or `{ kind: "wallet", chain, address }` (`identityReferenceSchema`, `IdentityReference`).
118
+
119
+ - `members.identityStates.unlink` and `relink` take it as `identity` (`memberIdentityStateChangeInputSchema`, `MemberIdentityStateChangeInput`); `setSignIn` also takes a Cosmos wallet with its `network`. An X or Discord subject names the member's selected account with that subject, else the only one they hold. A subject the member holds through several issuers, none of them selected, fails with 409 `identity-ambiguous`; name it by `issuer` then.
120
+ - `members.lookup` takes it, and sends it as the `provider` and `subject` every Relay version reads.
121
+ - `testMembers.linkAccount` and `unlinkAccount` take `{ actionId, kind, subject }` for an X or Discord account.
122
+ - The CLI takes `--kind` and `--subject` on `domino members lookup` (with `--chain` and `--address` for a wallet) and on `domino test-members link-account` and `unlink-account`.
123
+
124
+ Deprecated, removed in 2.0.0: the `{ kind: "oauth", provider, issuer, subject }` and `{ kind: "app", appKind, subject }` input selectors, `memberIdentityLinkChangeInputSchema` and `MemberIdentityLinkChangeInput`, `members.lookup({ provider, subject })`, the test-member `{ provider, id }`, and the CLI's `members lookup --provider` and `test-members link-account|unlink-account --provider|--id`. They keep working through 1.x; `contract.json` marks the deprecated options with `deprecated`.
125
+
126
+ Identity reads gain an optional `source`: `member`, `import`, `app-login` or `test-link`, an open string (`IdentitySource`, `KnownIdentitySource`), and `import`, the ID of the member import that attached the identity when Relay recorded it. Identity states of every kind carry them, and so do `members.get`'s `wallets` and `identities` and the deprecated `oauthAccounts` and `telegramAccount`. `imported` is unchanged.
127
+
128
+ - 3a3feef: A leaderboard can rank its live standings first-to-reach. `defineLeaderboard({ ..., ties: "earliest" })` orders equal scores by each member's latest qualifying entry, earliest first, then by member ID, and gives every member who scored their own rank: the order a settlement with `settle.ties: "earliest"` freezes. Standings pages, `me`, `nearby`, placement, and the management and CLI standings reads all follow it, so a tie-broken board is one paged read.
129
+
130
+ `ties` is optional. Omitted or `"shared"`, equal scores share a rank as before, so published boards rank unchanged. It is independent of `settle.ties`; set both on a board that settles so members see the order prizes follow. Members listed through `includeUnscored` still share one rank after every member who scored.
131
+
132
+ - 7a93f76: `createParticipantBackend` from `@domino-sdk/relay/portal-proxy` accepts a loopback HTTP app origin (`http://localhost`, `http://127.0.0.1` or `http://[::1]`, any port), so a browser test can run the real adapter, identity exchange included, at a local address against `createFakeRelay`'s `fetch`. On such an origin the `domino_app_session` cookie omits `Secure`. Any other `http:` origin still throws, `baseUrl` must still be HTTPS, and Relay still registers only HTTPS app origins.
133
+ - 1a70098: `management.members.lookup({ provider: "app:<name>", subject })` finds the member holding a linked app identity, so a server can map one of its user IDs to the member. `members.people()`, `members.list()`, `members.lookup()` and `members.get()` list each member's linked `appIdentities` (`{ kind, subject }`), and `members.list({ q })` also matches part of an app identity's subject. Identities an operator unlinked are neither found nor listed.
134
+ - dd7ced3: Experimental: a member import row can carry the member's marketing consent from the platform they come from. The optional `consent` list on `memberImportRowSchema` takes one `{ channel, state: "granted" | "withdrawn", at, wording? }` per channel; applying the row records it with its original time and wording, marked `source: "imported"`, in the row's transaction, unless the member chose on that channel in Relay (their choice always stays) or an earlier import recorded consent as recent or more recent. A check refuses the row with `future-action` for a time in the future, `unsupported-consent-channel` for a channel other than `email`, `consent-wording-required`, `consent-wording-too-long` or `consent-wording-invalid` for the wording, and `duplicate-entry` for a channel named twice. `KnownConsentSource` gains `imported`. The CLI imports reference describes the field.
135
+ - 5a34b36: Experimental: members record their marketing consent per channel. `relay.consent.get()` lists the signed-in member's current consent, one entry per channel they chose on (`member`, `channel`, `state`, `at`, `wording`, `source`), and `relay.consent.set({ channel, granted, wording, actionId? })` grants or withdraws one channel. The `useConsent()` React hook returns the query with `grant(channel, wording)` and `withdraw(channel, wording?)`. Relay stamps the time and stores the wording verbatim; a repeat of the current state and wording records nothing. Only `email` is accepted for now: other channels are refused with `unsupported-consent-channel`, a grant without wording with `consent-wording-required`, wording over 2,000 characters with `consent-wording-too-long`, and wording with a NUL or a lone surrogate with `consent-wording-invalid`. Channel, state and source are open strings in results. The consent API is `@experimental` and can change in a minor release. The participant API reference and `capabilities.json` describe it.
136
+ - 4b7c062: Integration modules, reward kinds and webhook event types now have open types beside SDK 1.x's closed ones, which keep their exact shapes.
137
+
138
+ - `defineQuest` accepts an integration module whose provider the SDK does not know, such as `{ provider: "future-chat" }`. `IntegrationModule.provider` is `IntegrationModuleProvider`, `KnownIntegrationModuleProvider | (string & {})`. The quest builds; its description keeps `integrations` to the modules the SDK knows and adds `integrationModules`, every declared module. Publishing a quest that declares a module Relay does not have is refused with 422 `unknown-integration-module`. Discord and X quests behave as before. Evaluator contexts type readers only for the modules the SDK knows, now named `KnownIntegrationModule`: `EvaluationContext`'s module parameter takes `readonly KnownIntegrationModule[]`, so a helper that passed `readonly IntegrationModule[]` to it explicitly passes `readonly KnownIntegrationModule[]` instead.
139
+ - Release reads (`management.releases.get`, `releases.list`, publish results and every other `PublishedRelease`) add `integrationModules` and `rewardEntries`. `rewardEntries` lists every reward as a `RewardEntry`, `{ type, data }`, with an open `RewardType`; a reward kind the SDK does not know, which a later Relay adds, will appear only there and never in `rewards`. `management.quests.previewAutomaticCatchUp()` adds the same `rewardEntries` beside `rewards`. Older Relay versions omit these fields.
140
+ - `ActivityType` (`KnownActivityType | (string & {})`) from `@domino-sdk/relay/server` names the webhook and activity event types for your own handlers; `WebhookEvent.type` stays a `string`. Ignore a type you don't handle: Relay adds event types.
141
+
142
+ - de99154: Every sign-in provider Relay has, Telegram included, now has open surfaces beside the closed `AuthProvider` lists, which keep their exact shapes. `SignInProvider` is `KnownSignInProvider | (string & {})`, so handle a provider you do not know.
143
+
144
+ - `relay.auth.providers()` adds `signInProviders`, `[{ provider, configured }]` for every sign-in provider. `management.participantSignIn()` adds the same `signInProviders`. Older Relay versions omit both.
145
+ - `relay.auth.signInAccounts()` (`GET /v1/auth/sign-in-accounts`) returns the member's account on every sign-in provider as `SignInAccount`s, shaped like `AccountConnection` with an open `provider` and `status`.
146
+ - `relay.auth.start({ provider, mode })` and `relay.auth.disconnect(provider)` take any `SignInProvider`, such as `"telegram"`.
147
+ - `useAccount(provider)` from `@domino-sdk/relay/react` reads one provider's account, with `connect()`, `disconnect()` and `isConnected`.
148
+ - `management.members.accounts(member)` (`GET /members/:member/accounts`) returns `{ accounts }`, the member's X, Discord and Telegram selections as `ManagedAccount`s with an open `provider`. `management.members.unlinkAccount(member, { provider, subject, expectedRevision, reason, actionId? })` (`POST /members/:member/accounts/unlink`, `review` permission) unlinks one and returns that provider's selection; a retry with the same `actionId` returns the first result.
149
+
150
+ - 80e09c2: Experimental: operators read a member's marketing consent. `management.members.consent(member)` lists the member's current consent, one entry per channel they chose on, and `management.members.consentHistory(member, { cursor, limit })` pages through every change on every channel, newest first, as `{ items, nextCursor }`; each entry is `{ member, channel, state, at, wording, source }`, and `memberConsentHistorySchema` and `memberConsentHistoryQuerySchema` are exported from `@domino-sdk/relay/schema`. Both need `read` permission (`GET /management/v1/members/:member/consent` and `/consent/history`); a member who never chose reads as empty, and an unknown member fails with 404 `not-found`. `domino members consent MEMBER` prints both, with `--limit` and `--cursor` for the history. The Console member page shows the same, read-only. There is no management write: only the member grants or withdraws consent. The consent API is `@experimental` and can change in a minor release.
151
+ - 423b6fc: Plugin connections, experimental. `management.connections` (`list`, `get`, `connect`, `update`, `disconnect`) connects a project to the plugins Domino writes, separately in test and live; a promotion never copies a connection. A connection's config is checked against the plugin's JSON Schema (422 `invalid-config` names the failing `path`), its secrets are write-only and sealed at rest, every change takes an `actionId`, and `update` and `disconnect` take an `expectedRevision` (409 `connection-changed`). The new exports (`connectionSchema`, `connectionPageSchema`, `Connection`, `ConnectionStatus` and the connect, update and disconnect inputs) are experimental and may change in a minor release. The CLI adds the experimental `domino connections list|get|connect|update|disconnect`, test unless `--environment live`, reading secrets from stdin as JSON or at hidden prompts, never from arguments.
152
+ - 01b789e: Experimental `management.plugins.list()` answers the plugins a deployment runs, each with the public part of its manifest: `name`, `slots`, the config's JSON Schema (`configSchema`), its secret fields and the Domino event types a destination receives. The new exports (`pluginSchema`, `pluginPageSchema`, `PluginInfo`, `PluginPage`, `PluginSlot`, `KnownPluginSlot` and `knownPluginSlots`) are experimental and may change in a minor release. Console builds each plugin's connection form on **Integrations** from it.
153
+ - 2a83792: Plugin OAuth connections, experimental. A plugin whose `plugins.list()` entry has `oauth` connects through its vendor's OAuth instead of entered secrets: `management.connections.authorize(plugin, { config, returnTo?, actionId? })` answers `{ authorizationUrl, expiresAt }`, a person opens the URL and grants access, and the vendor's callback makes the connection `ready`. The URL works once, for ten minutes. Domino refreshes the tokens before they expire and never answers them; when the vendor refuses a refresh, the connection is `attention` with the new `reconnectReason` (`revoked` or `expired`), and authorizing again reconnects it. Disconnecting deletes the tokens, then revokes them at the vendor; a failed revocation is logged. `connect` refuses an OAuth plugin with 409 `oauth-required`, `authorize` refuses one connected with secrets with 409 `oauth-not-supported`, and a deployment without the vendor's OAuth app with 409 `plugin-not-configured` (`oauth.configured` is false). New experimental exports: `connectionAuthorizeInputSchema`, `connectionAuthorizationSchema`, `ConnectionAuthorizeInput`, `ConnectionAuthorization`, `ConnectionReconnectReason`, `KnownConnectionReconnectReason` and `knownConnectionReconnectReasons`. The CLI adds the experimental `domino connections authorize <plugin> --config-json …`, which prints the authorization URL.
154
+ - 172c3b5: Experimental: a `viewer` project role and grant end dates, to share a project's dashboards with a sponsor or client. A viewer holds one permission, `dashboards`, which reads `dashboards.list` and `dashboards.read` and nothing else: no members, evidence, quests or settings. Every other role holds it too, and `read` keeps reading dashboards, so no existing person or key loses access.
155
+
156
+ `management.grants.set` takes `projectGrantSchema`: `roles` may name `viewer`, and an optional `expiresAt` (epoch ms, or null for no end) ends the grant's access without anyone removing it. An ended grant stays in `grants.list()`, and the access history records each end date. Released SDKs keep reading grants: a grant's `roles` keeps the roles 1.x names, so a viewer's is empty, and the new `projectRoles` lists every role. Likewise a project in `management.access()` keeps `dashboards` out of `permissions` and lists it in the new `projectPermissions`. `projectRoleSchema`, `projectPermissionSchema`, `projectRolePermissions`, `projectGrantSchema` and these fields are experimental.
157
+
158
+ Console grants the viewer role and an end date under **Settings → Access → Team**, and shows an ended grant as **Expired**. A viewer's Console shows only **Analytics**, opened on the project's first dashboard. The CLI's agent references describe the role.
159
+
160
+ - b2b8127: A full deployment to test warns `quests-not-in-bundle` with the keys of quests published from code that it leaves out, such as a starter's sample quests whose files were deleted: they stay as published until archived with `domino quests archive <key> --reason "…"`. Archived quests and quests created in Console are not listed. A deployment input with `partial: true` doesn't warn, and `domino deploy --only` sends it. `domino stage` now prints the warnings of the deployment its build published, and its `--json` result carries them in `warnings`; a build's status (`GET /builds/{id}`) names that deployment in `deployment`.
161
+ - a381dde: Staff can delete the evidence a member submitted for a decided attempt: `management.attempts.redactEvidence({ attempt, reason })` and `domino attempts redact-evidence ATTEMPT --reason TEXT` (`review` permission, test and live). The photo, text, URL or quiz answers, and what the quest's capabilities read from them, are deleted; the attempt keeps its outcome, completion and rewards. Attempts that submitted the same photo are redacted together. `management.attempts.get` answers the new `redaction` (`{ at, actor, reason }`). Undecided attempts are refused with `attempt-not-decided`, and `attempts.retry` refuses a redacted attempt with `evidence-redacted`.
162
+ - 4443124: Quest code can read whether the member linked Telegram: `ctx.telegram.connection()` resolves to `{ userId }`, the numeric Telegram user ID, or `null`. It needs no connected chat and doesn't ask Telegram; an account an app login vouched for or a member import brought in counts. It reads the account the attempt pinned, like `member()`, and is captured for replay. The testing kit answers it from `facts.telegram.connection`, and `TelegramConnection` is exported from `@domino-sdk/relay/authoring`.
163
+ - b9270ff: Telegram Login, the member's Telegram account and its unlink now go through the generic sign-in surfaces, and Telegram's own members for them are deprecated. They keep working, with their routes, through SDK 1.x and are removed in 2.0.0:
164
+
165
+ - `relay.auth.telegram.start(mode)` → `relay.auth.start({ provider: "telegram", mode })`.
166
+ - `relay.auth.telegram.account()` → the `"telegram"` entry of `relay.auth.signInAccounts()`.
167
+ - `relay.auth.telegram.disconnect()` → `relay.auth.disconnect("telegram")`.
168
+ - `useTelegramAccount()` → `useAccount("telegram")`. Inside a Mini App, link with `relay.auth.telegram.miniApp.connect(initData)`, then call `invalidate([relayKeys.accounts, relayKeys.experience])` from `useInvalidate()`.
169
+ - `useSignIn().withTelegram()` → `useSignIn().withProvider("telegram")`; `withProvider` now takes any `SignInProvider`.
170
+ - `SignInOptions.telegram` and `useSignInOptions().telegram`, `TelegramSignInOption` and `SignInActions.telegram` → `signInProviders`, below.
171
+ - `relay.auth.providers().telegram.login` and `management.participantSignIn().telegram.login` → the `"telegram"` entry of `signInProviders`. `TelegramSignInMethods` and the new `ParticipantSignInTelegram` are written-out types, so editors show these two deprecations.
172
+ - `management.members.telegramAccount(member)` and `unlinkTelegramAccount(member, input)` → `members.accounts(member)` and `members.unlinkAccount(member, { provider: "telegram", subject, expectedRevision, reason })`.
173
+
174
+ `useSignInOptions()` and `signInOptions(relay)` add `signInProviders`, the open sibling of the `oauth` entries of `options`: every sign-in provider Domino configured, Telegram included, in Relay's order, each a `ProviderSignInOption` (`{ kind: "provider", id, label, icon, start() }`). A newer Relay may list a provider this SDK does not know; its label is its name. Against a Relay without `signInProviders`, the list holds its providers and Telegram Login. `SignInMethods` adds `signInProviders` and `SignInActions` an optional `provider(provider)` action. The starters render their provider buttons from `signInProviders`.
175
+
176
+ Mini App sign-in (`relay.auth.telegram.miniApp`, `useSignIn().withTelegramMiniApp`, `providers().telegram.miniApp`, `participantSignIn().telegram.miniAppBotId`) is not deprecated.
177
+
178
+ - 90a20c6: Telegram is now a plugin: `management.connections.list()` and `domino connections get telegram` include it, `ready` while the environment has a connected chat, `attention` with the chat's issue while Domino's bot can't read one, and `disconnected` without chats. Chats still connect only through the bot (`management.telegram.connect()`, unchanged), so `connections.connect`, `update` and `disconnect` answer 409 `plugin-managed` for Telegram and for any plugin that connects through its own flow. `knownPluginSlots` adds `announcement`, the slot Telegram posts quest announcements through. `management.plugins.list()` items gain `console`: `{ component }` when Console shows the plugin's own component instead of the generated card, otherwise null; the schema defaults it to null, since older servers don't send it.
179
+ - 8e8e24a: Telegram Mini App sign-in has its own entrypoint, `@domino-sdk/relay/telegram` (experimental): `withTelegram(relay)` adds `relay.telegram` to a client and answers the same client, `relay.telegram.miniApp.signIn(initData)` and `.connect(initData)` do what `relay.auth.telegram.miniApp` did, and `telegramMiniAppInitData()` reads the launch data. Apps that don't import it don't carry it. `relay.auth.telegram.miniApp` and the `telegramMiniAppInitData` exports of `@domino-sdk/relay/browser` and `@domino-sdk/relay/react` are deprecated, keep working through 1.x and are removed in 2.0.0.
180
+
181
+ Telegram Login and Mini App sign-in now run in the Telegram plugin, with no change to what they accept or answer: members linked before keep signing in as the same member, and `/v1/auth/telegram/mini-app` takes the same requests. `management.plugins.list()` and `domino connections get telegram` list one more slot for Telegram, which `knownPluginSlots` adds: `identity` (members sign in with it).
182
+
183
+ - 52ed037: Telegram's trackers and `ctx.telegram` now come from the Telegram plugin, with no change to what they record or read: `telegram.messages()` and `telegram.reactions()` trackers, the bot's webhook at `/v1/integrations/telegram/updates`, and `ctx.telegram.connection()` and `member()` behave as before, and published quests keep working. `management.plugins.list()` and `domino connections get telegram` list two more slots for Telegram, which `knownPluginSlots` adds: `tracker-source` (the plugin writes its trackers' records) and `evaluator-capability` (quest code reads it as `ctx.<plugin>`). A quest may now declare the `telegram` integration module, `{ provider: "telegram" }`, and publishes; before, publishing refused it with `unknown-integration-module`. Declaring it is optional, since every quest gets `ctx.telegram`.
184
+ - 2fe2d72: `management.testMembers.history()` and `domino test-members history` name each attempt's and skipped tracker outcome's quest by its authoring key as `questKey`, next to the quest ID in `quest`. It is absent for a quest created without a key. A script can filter a test member's history by quest key without calling `quests get` first.
185
+ - 7fa2a06: Test members can submit photo quests. `management.testMembers.uploadEvidence(member, file, { actionId })` uploads a JPEG or PNG of at most 5 MB as the test member's photo, and `act` submits its `id` as `evidence`. `domino test-members act MEMBER QUEST --photo FILE` does both, so the quest's real image check and the review queue can be proven from the CLI. The campaign-testing reference now points photo evaluator tests at `facts.images.review`.
186
+ - 06046f1: X trackers can pay the member whose post a collected post quotes or reposts. `automatic({ tracker, beneficiary: "quoted" })` pays the quoted post's author and `beneficiary: "reposted"` the reposted post's author, so a "get noticed" quest can reward members an account picks up: `x.search({ query: "from:examplelabs filter:nativeretweets" })` with `beneficiary: "reposted"`. `xAccountRoles` gains both. A post that is no quote or repost is skipped with `no-account` ("Post is not a quote" / "Post is not a repost"), and a quote or repost of the author's own post with `beneficiary-is-author`. Like `inReplyTo`, these quests don't reward posting and don't raise `x-post-rewards-ungated`. `xAccountRoles` and `SourceRoles` list the roles this SDK knows, and a later release may add roles to them, so code that switches over them needs a default branch.
187
+
188
+ ### Patch Changes
189
+
190
+ - ee3f488: Bulk management writes (`catalog.deploy`, catalog previews and drafts, `deployments.preview`, `releases.publishBatch`, tracker record ingest and `memberImports.addRows`) refuse a body with more than 2,000,000 JSON values with 413 `payload-too-large` before parsing it, whatever its byte size; other management writes refuse one past 500,000. A server handles one body over 4 MiB or 500,000 values at a time and answers another 503 with `Retry-After` when it has waited 10 seconds. `memberImports.addRows` now takes at most 64,000,000 bytes, like the other bulk writes, so a single batch above 64 MB from an older CLI gets 413. `domino import` now splits rows into batches of at most 500 rows, 16 MB and 1,000,000 JSON values, sends a row larger than that in a batch of its own, and refuses a row too large for one call (63 MB or 1,990,000 values) before uploading anything, naming its line.
191
+
192
+ The management client, the CLI and the participant client now send any call again after a 503 with `Retry-After`, within its deadline and attempts, including writes without an action ID: Relay answers such a 503 only to a request it refused before doing anything. Before, those writes were sent once.
193
+
194
+ - f13cfb6: A `domino login` CLI session creates and scopes personal API keys in test only. `management.tokens.create` with `environment: "live"`, and `management.tokens.scope` on a live key, now answer 403 `console-approval-required` from a CLI session, whatever `X-Relay-Environment` selects: a live key would carry the live operations (points, attempt confirmations, reviews, pauses) that the CLI session itself is refused, and would outlive `domino logout`. Create and change live API keys in Console (**Settings → API keys**). A CLI session still lists its keys and revokes any of them, live included, and Console sessions are unchanged.
195
+
196
+ This changes a 1.x behaviour, an exception Peter Szoke granted on 2026-10-15: no request or response shape changes, and the refusal is a typed 403 that says where to go. `domino tokens create --environment live` prints that refusal, and `domino tokens --help` says live keys come from Console.
197
+
198
+ - eed1808: Participant cookies on HTTPS now use `__Host-` names (`__Host-relay_session` and the sign-in binding cookies), so one app on `domino.page` can no longer set them for another, and a cookie name the browser sends twice reads as absent. `createParticipantBackend` forwards the OAuth return binding as `__Host-relay_auth_return` when the browser has it, otherwise `relay_auth_return`, and forwards neither when a name appears twice.
199
+
200
+ Members signed in when this deploys are signed out once and sign in again, and a sign-in started just before the deploy fails once and starts over. Older SDK versions keep working: through an identity integration, Relay still sets and reads `relay_auth_return`.
201
+
202
+ - 94ca2b5: Unlinking an imported or vouched account now releases it. `management.members.identityStates.unlink` (and `domino members unlink-identity` and the deprecated per-kind unlink methods) on an X, Discord or Telegram account or wallet that a member import or an app login vouched for, and that the member never signed in with or proved, removes it from the member instead of keeping it bound: its owner can then link it and sign in with it. The answer shows it unlinked with `canRelink: false`, and later imports and app-login vouching refuse it for that member with `identity-unlinked`. Accounts the member signed in with or proved keep the previous behaviour: the link is cleared, ownership stays, and it can be relinked.
203
+ - d4fcade: Role delivery helpers ignore announcement rows. `useExperience().deliveries`, `roleDeliveryStatus` and the watch behind them count only `grant-role` deliveries, so a quest that only posts an announcement in a Telegram chat or Discord channel no longer shows "Role delivery pending" or "Role granted", and its pending post is no longer polled every 5 seconds. A quest with a Discord role reward reports its role as before, also when it announces. `roleDeliveryStatus` applies the same rule to raw `me.rewardDeliveries()` rows, and `RoleDelivery` gains an optional `action`. `me.rewardDeliveries()` itself still returns every delivery.
204
+ - 8064983: Deployment previews, records and history pages (`deploymentPreviewSchema`, `deploymentRecordSchema`, `deploymentHistoryPageSchema`, and the `management.deployments` methods that return them), `collectionSlotSchema` and `questCollectionSchema` read collections without rejecting unknown fields, so a field a newer Relay adds to a deployment collection no longer fails the response. Collection inputs (`collectionSlotDeclarationSchema`, `catalogDeploymentSchema` and saved draft changes) still reject unknown keys.
205
+ - 980e6ca: Sign-in rate limits count an IPv6 client by its /64, so every address in one /64 shares the per-IP limits on email, wallet, OAuth and Telegram sign-in starts, `domino login` device starts and staging password attempts. An IPv4-mapped IPv6 address counts as its IPv4 address. Per-address limits now count per client: three sign-in codes per email address and ten wallet challenges per wallet address from one IP address every ten minutes, so another client hitting them no longer refuses that address's own sign-in, on this project or another. Each project and environment also caps an address at ten sign-in codes and thirty wallet challenges every ten minutes from all clients together.
206
+ - d683d00: Sign-in rate limits per IP address count each member of a Domino-hosted app on their own. Hosted apps reach the API from Cloudflare's shared Worker address, so until now every member of every hosted app shared one set of per-IP limits. Domino now attests the visitor's IP on every request a hosted app's server sends to the API, with no app change or rebuild. A request from Cloudflare's shared Worker address without Domino's attestation, such as from an app you run on your own Cloudflare Workers, is counted per project and environment instead of with every other Worker.
207
+ - 081806c: Personal keys (`ak_…`) and MCP OAuth tokens that Clerk does not recognize now count against the client that presented them, on the management API, `git` and the MCP server: after 30 in 10 minutes from one IPv4 address or IPv6 /64, counted by each API server on its own, every `ak_` and MCP token from it answers 429 `rate-limited` with `Retry-After` until the window ends. Expired, revoked and other refused credentials Clerk knows are not counted, and neither is Clerk refusing Domino's own secret key. A refused value presented again within a minute can be refused without asking Clerk, and a value no Clerk credential can be (under 16 characters after its prefix, over 4096, or with a character a bearer token cannot hold) is refused with 401 `invalid-credential` without asking Clerk. When Domino is checking many new credentials at once, a key or token it has not accepted recently can answer 503 with `Retry-After: 1`. Clerk's own rate limit now answers 503 with Clerk's `Retry-After`.
208
+ - 114e0c7: Telegram and Discord install links now tell whoever opens them which Domino project they connect to. The Telegram bot's reply to a link, before the chat picker, names the organization, the project's name and slug, and the environment, and so does its `Connected …` reply. A Discord install link sent to a server's administrator (**Send to your server admin**) first opens a page naming the same, with a **Continue to Discord** link; the page changes nothing, so a link preview that fetches it stays harmless, and the page shown after connecting names the project too. Creating either link (`management.telegram.connect()`, `POST /management/v1/integrations/discord/links`) now reads the organization's name from Clerk, and answers 503 rather than issue a link that names nobody when Clerk can't answer. Links made before this change still work and name no project. Console's own Discord installation is unchanged.
209
+ - 92ee725: Connecting a Discord server or Telegram chat where Domino's shared bot already holds rights now takes someone who holds those rights too. In Discord, installing the bot (Console's **Connect Discord** or a shared install link) refuses a user who is neither the server's owner nor an Administrator when the bot already has a permission there they lack, such as Manage Roles another organization gave it; the install page says "Domino's bot already has permissions in this server that you don't have. Ask a server administrator to connect it." and nothing is connected. A fresh install, where the bot holds nothing beyond `@everyone`, still needs only Manage Server. In Telegram, an administrator who lacks an administrator right the bot holds in the chat, such as posting messages, is refused with a reply naming the missing rights; the chat's creator always connects it. Reusing a Discord server the organization already connected is unchanged, and existing connections are not revisited.
210
+ - 10826dc: An organization administrator's CLI session lists the project's identity integrations (app login keys) and their claimed kinds: `domino identity-integrations list` and `domino identity-integrations kinds`, or `management.identityIntegrations.list()` and `kinds()`. They return each key's origin, identity kind and secrets' expiry and last use, never a secret. Creating, rotating and revoking keys and their secrets stays in Console; non-admin CLI sessions and every key type are refused.
211
+ - c391c86: Documentation only: the JSDoc for `pointLedgers.create`, `pointLedgers.update`, `calendar.configure`, `profiles.configure` and the `campaigns` methods now says what live refuses. In live, every definition write is refused for every credential, Console included, unless it applies an approved promotion; `campaigns.enroll` and `campaigns.withdraw` are operations that need `enroll` and work in live with a personal API key.
212
+ - 1c61073: `domino tokens list|create|scope|revoke` manage your personal API keys from a `domino login` session, as `management.tokens` does. `domino tokens create --name NAME --permission P [--ingest-tracker T] [--days N]` issues a key for the selected project and environment and prints its one-time `token` and expiry; `--json` prints the SDK's answer, so `domino tokens create … --json | jq -r .token | domino secrets set NAME` stores it. A personal key expires after `days`, 1 to 90, default 30; `--days 91` is refused before anything is sent. `domino tokens list` shows each key's expiry. API keys still cannot manage keys (`credential-not-allowed`), and a credential that must not expire is a service key, which an organization admin creates in Console.
213
+ - aaf28f9: Quest submissions are limited to 120 an hour per member in each project environment, counted together across `POST /v1/me/attempts`, `POST /v1/attempts`, `POST /v1/me/submissions/:id/submit` and `POST /v1/attempts/:id/retry`. Past it they answer 429 `rate-limited` with `Retry-After`, before the request is read. The quest controllers keep the saved draft, so `resume()` continues it. The participant reference in the Domino skill states the limit.
214
+ - 9f39ea4: An operator's reversal of a referral payout stays reversed. Reversing all or part of a referral payout (Console point history **Reverse**, or `management.points.record({ kind: "reverse", entry })`) writes those points off on the referral account, so no later reconcile pays them again; earlier, the inviter's next invitee activity or a new acceptance of their code paid the reversed amount back. Later bonus growth still pays, and restoring a revoked qualification does not repay the reversed points. `earned` in the referral summary and the management referral accounts excludes the reversed points, so a reversed payout no longer reads as earned.
215
+ - b866593: Behavior change: completing a referral program's qualifying quest no longer closes the member's invitation. A friend who claimed an entry quest at sign-in, for example through `management.passes.verify`, can still accept an invitation afterwards; accepting qualifies them in each enabled program whose quest they already completed, on their earliest unrevoked completion, and pays the inviter and the friend once. Completing any other quest still makes `referrals.accept` and `testMembers.acceptReferral` refuse with `invitation-closed`, and a revoked qualifying completion neither closes the invitation nor qualifies. Points the friend earned before accepting don't earn the inviter a percentage bonus.
216
+ - 1cdc64b: Staging a commit the project's hosted repository doesn't have (a mistyped SHA, or a commit not pushed yet) now answers a certain 422 `commit-not-found` before any build is reserved, instead of an uncertain 502 `upstream-failed`. `domino stage` reports it as `REJECTED` with `apiCode: "commit-not-found"`, saves no operation to resume and prints no retry command: push the commit, then stage again. Other GitHub failures during the commit check still answer 502 and stay uncertain.
217
+ - 3d2f654: Staging's participant API is now private, like staging itself. A participant request that names a staging address (`<slug>--staging.domino.page`) in `X-Relay-App-Origin` answers only when it carries the staging pass, which the staging gateway gives browsers signed in to staging; anything else is refused with 403 `staging-access-required`. Apps on staging keep working with no change: the gateway adds the pass to the requests it hands the app and to legacy `/relay/*` calls, and `proxyParticipant` forwards it with the browser's other cookies. Only callers that sent a staging origin straight to the API, outside the staging address, see the new refusal. The retired `staging-<slug>.domino.run` origin now selects nothing (403 "Application origin is not registered or is offline"). Previews, live apps and identity integrations are unchanged.
218
+
3
219
  ## 1.61.0
4
220
 
5
221
  ### Minor Changes
@@ -70,6 +286,8 @@
70
286
 
71
287
  ## 1.58.0
72
288
 
289
+ _Not published to npm. Its changes first reached npm in 1.59.0._
290
+
73
291
  ### Minor Changes
74
292
 
75
293
  - 321763b: `management.discordRoles.list()` lists every Discord role the project's reward deliveries have granted in the environment, with the quests that requested it and its deliveries counted by status (`pending`, `delivered`, `failed`). It reads the whole delivery history, so a role granted only before the newest page of `management.rewardDeliveries` still appears. The response schema is `discordRoleRewardsSchema`.
@@ -80,6 +298,8 @@
80
298
 
81
299
  ## 1.57.0
82
300
 
301
+ _Not published to npm. Its changes first reached npm in 1.59.0._
302
+
83
303
  ### Minor Changes
84
304
 
85
305
  - 7012d1c: Contract state and Cosmos staking reads in quest evaluators.
@@ -218,6 +438,8 @@ No changes in this release.
218
438
 
219
439
  ## 1.53.0
220
440
 
441
+ _Not published to npm. Its changes first reached npm in 1.53.1._
442
+
221
443
  ### Minor Changes
222
444
 
223
445
  - 1b789ce: Test members can hold real X and Discord accounts. `management.testMembers.linkAccount(member, { actionId, provider, id })`, or `{ actionId, provider: "x", username }` resolved through X, links a public account to a test member without OAuth, and `unlinkAccount(member, { actionId, provider, id })` unlinks and frees it (`domino test-members link-account` and `unlink-account`). Quest code's `x.profile()`, `follows()`, `reposted()` and Discord role reads then answer for the real account, tracker records it authors are attributed to the member, and `testMembers.record` posts as it instead of refusing with `conflict`. An account another member holds is refused with `account-linked-elsewhere`; when its owner signs in with it or connects it, it moves to them. Reset unlinks it. Relay never writes to a test-linked account: a Discord role reward for one creates no delivery and is never sent, and the attempt's `history` entry carries `rewardsNotSent: [{ reason }]`. Test member results include an optional `accounts` list; managed member identities and `members.oauthAccounts` mark these accounts with an optional `testLinked` flag.
@@ -268,12 +490,16 @@ No changes in this release.
268
490
 
269
491
  ## 1.48.0
270
492
 
493
+ _Not published to npm. Its changes first reached npm in 1.49.0._
494
+
271
495
  ### Minor Changes
272
496
 
273
497
  - 3213a87: Add group membership quest rewards. `groupMembership(group)` from `@domino-sdk/relay/authoring` takes a `defineGroup` handle and adds the member to that group when the completion's rewards are delivered, recording the completion as the reason in the audit history and checking prerequisite-only automatic quests. Publishing refuses a reward for a group that does not exist or is archived. Revoking the completion removes the membership unless an operator's add or another unrevoked completion also backs it; `CompletionRevocation.groups` reports each group with `stillMember`. Archiving a group that a quest rewards fails with `group-in-use`, naming the quests. SDK 1.x keeps the existing `kind: "tier"` reward envelope with an optional `reward.group` key (`groupReward` and `groupRewardSchema` in `@domino-sdk/relay/schema`), and quest views add an optional `reward.group` with the group's key and title.
274
498
 
275
499
  ## 1.47.0
276
500
 
501
+ _Not published to npm. Its changes first reached npm in 1.49.0._
502
+
277
503
  ### Minor Changes
278
504
 
279
505
  - 7db52a2: Reward X posts only for approved creators. X does not allow apps that reward users for posting, so a deployment that publishes a quest rewarding X posts without an `inGroup` prerequisite now returns the warning `x-post-rewards-ungated` in `warnings`, naming the quests by key. It applies to an X tracker trigger that pays the post's author and to a content quest on an X tracker, when the quest declares rewards or an engagement rule credits it; `beneficiary: "inReplyTo"` quests are not affected. The deployment still publishes, `domino deploy` prints the warning, and Console shows it on the deployment with a link to **Groups**. Add `requires: [inGroup(group)]` with a group of approved creators you manage in Console **Groups**, with `domino groups`, or with `management.groups`.
@@ -325,12 +551,16 @@ No changes in this release.
325
551
 
326
552
  ## 1.41.0
327
553
 
554
+ _Not published to npm. Its changes first reached npm in 1.42.0._
555
+
328
556
  ### Minor Changes
329
557
 
330
558
  - c8e46ee: `RelayProvider` reads the session and every query again when `window` receives a `domino:test-state-changed` event. Domino's staging test toolbar fires it after it changes test data under the page, such as moving a test member to the next day or sending a tracker record, so a staging app shows the new state without a reload. Apps that read Domino without `RelayProvider` can listen for the same event. See [trying quests on your staging site](https://console.domino.run/docs/use/quests#try-quests-on-your-staging-site).
331
559
 
332
560
  ## 1.40.0
333
561
 
562
+ _Not published to npm. Its changes first reached npm in 1.42.0._
563
+
334
564
  ### Minor Changes
335
565
 
336
566
  - 68bf9eb: Promote test to live as one reviewed request ([ADR 0017](https://github.com/GetDomino/relay/blob/main/docs/adr/0017-live-changes-only-through-promotion.md)). A promotion makes live match test for every definition, code-defined and Console-authored, matched by authoring key, and carries the hosted app's active staged build. It lists environment bindings live lacks (integrations, webhooks, participant sign-in, app secrets) as warnings and never copies them.
@@ -373,6 +603,8 @@ No changes in this release.
373
603
 
374
604
  ## 1.37.0
375
605
 
606
+ _Not published to npm. Its changes first reached npm in 1.37.1._
607
+
376
608
  ### Minor Changes
377
609
 
378
610
  - 701a0d8: Leaderboards can list every member. `defineLeaderboard({ includeUnscored: true })` adds the board's eligible members who have no qualifying points to its standings, `me`, `nearby` and `placement` reads, with a score of 0. They share one rank after every member who scored and are ordered by member ID; the board's group and exclusion group still apply. Settlements leave them out, so they win no prizes. Boards without the option rank as before.
@@ -388,6 +620,8 @@ No changes in this release.
388
620
 
389
621
  ## 1.35.0
390
622
 
623
+ _Not published to npm. Its changes first reached npm in 1.36.0._
624
+
391
625
  ### Minor Changes
392
626
 
393
627
  - f7ba8d6: Create and change X search trackers without a deployment. `management.trackers.create({ key, title, query, window, refreshSeconds?, maxPages? })` (`POST /trackers`, `domino trackers create`) creates one as data in test, with the same validation as `defineTracker`; its first collection searches from `window.start`. `management.trackers.update(tracker, changes)` (`POST /trackers/:tracker/update`, `domino trackers update`) changes any field but the key, and a new query or window never searches the past. Both need `publish` permission, and live refuses them with `promotion-required`: a data tracker reaches live through a promotion from test, which is not available yet. Console **Trackers** creates and edits them too.
@@ -462,6 +696,8 @@ No changes in this release.
462
696
 
463
697
  ## 1.28.0
464
698
 
699
+ _Not published to npm. Its changes first reached npm in 1.29.0._
700
+
465
701
  ### Minor Changes
466
702
 
467
703
  - 932e3c1: Add kind-keyed operator identity commands. `management.members.identityStates.list(member)` reads every identity a member proved (wallets, X, Discord and Telegram accounts, and `app:<name>` identities) with its link, revision, sign-in and allowed actions. `identityStates.unlink` and `relink` take an `identity` selector, an `expectedRevision` and a reason, and `identityStates.setSignIn` turns a wallet's sign-in on or off on its own. Each kind keeps its rules: wallet and account unlinks keep sign-in, app identity unlinks end that identity's sessions, and a Telegram account can now be relinked. The CLI adds `domino members identities`, `unlink-identity`, `relink-identity` and `identity-sign-in`.
@@ -472,10 +708,14 @@ No changes in this release.
472
708
 
473
709
  ## 1.27.1
474
710
 
711
+ _Not published to npm. Its changes first reached npm in 1.29.0._
712
+
475
713
  No changes in this release.
476
714
 
477
715
  ## 1.27.0
478
716
 
717
+ _Not published to npm. Its changes first reached npm in 1.29.0._
718
+
479
719
  ### Minor Changes
480
720
 
481
721
  - 072493d: Leaderboard exclusions are a group (DOM-205):
@@ -488,6 +728,8 @@ No changes in this release.
488
728
 
489
729
  ## 1.26.0
490
730
 
731
+ _Not published to npm. Its changes first reached npm in 1.29.0._
732
+
491
733
  ### Minor Changes
492
734
 
493
735
  - 91d7ad0: Relay keeps quest submissions (DOM-268). See [ADR 0014](https://github.com/GetDomino/relay/blob/main/docs/adr/0014-relay-keeps-quest-submissions.md).
@@ -499,10 +741,14 @@ No changes in this release.
499
741
 
500
742
  ## 1.25.1
501
743
 
744
+ _Not published to npm. Its changes first reached npm in 1.29.0._
745
+
502
746
  No changes in this release.
503
747
 
504
748
  ## 1.25.0
505
749
 
750
+ _Not published to npm. Its changes first reached npm in 1.29.0._
751
+
506
752
  ### Minor Changes
507
753
 
508
754
  - 55d08ae: Client SDK paper cuts (DOM-160):
@@ -594,6 +840,8 @@ No changes in this release.
594
840
 
595
841
  ## 1.17.0
596
842
 
843
+ _Not published to npm. Its changes first reached npm in 1.18.0._
844
+
597
845
  ### Minor Changes
598
846
 
599
847
  - d8a5731: Add ordered quest presentation steps with HTTPS link actions, images and alt text, and account or wallet hints. Collection declarations now accept description, image and alt text. Participant reads and React hooks return these optional fields in the published collection order. Console can edit and reset quest presentation and edit collection details. Presentation changes do not change verification.
@@ -608,6 +856,8 @@ No changes in this release.
608
856
 
609
857
  ## 1.15.1
610
858
 
859
+ _Not published to npm. Its changes first reached npm in 1.16.0._
860
+
611
861
  No changes in this release.
612
862
 
613
863
  ## 1.15.0
@@ -628,6 +878,8 @@ No changes in this release.
628
878
 
629
879
  ## 1.13.0
630
880
 
881
+ _Not published to npm. Its changes first reached npm in 1.14.0._
882
+
631
883
  ### Minor Changes
632
884
 
633
885
  - c3601dd: `RelayProvider` now follows the signed-in member's activity feed instead of polling the whole quest experience every 1.5 seconds. Each event reads again only the hooks it changes: an accepted attempt reads the experience, outcomes, rewards, balances, leaderboards, profiles, referral progress and offers; a points award reads balances, leaderboards, offers and deliveries; and so on. The whole experience is read every 30 seconds for changes no event announces, and as the fallback when the feed is unavailable. An idle signed-in tab drops from about 124 requests a minute to about 14. Pass `polling={{ activityMs, snapshotMs }}` to change either interval. `useEntitlements` and `useDeliveries` now poll every 30 seconds instead of 5, since feed events refresh them.
@@ -642,12 +894,16 @@ No changes in this release.
642
894
 
643
895
  ## 1.12.0
644
896
 
897
+ _Not published to npm. Its changes first reached npm in 1.14.0._
898
+
645
899
  ### Minor Changes
646
900
 
647
901
  - 80f823a: Add `@domino-sdk/relay/testing`, a kit for unit-testing quests locally. `runQuest(quest, { input, tracker, settings, facts, now, seed })` evaluates a quest the way Relay's hosted runtime does: its published settings, rewards and review switch, a frozen clock, seeded randomness, fake answers for `ctx.member`, `ctx.wallet`, `ctx.x`, `ctx.discord`, `ctx.trackers`, `ctx.images` and `ctx.review` checked against Relay's capability contracts, and the reward bounds Relay enforces. `trackerObservation` and `trackerPage` build typed tracker input, `expectAccept`, `expectReject`, `expectReview`, `expectDefer` and `expectProgress` assert decisions, and `createTestContext` returns the host context `quest.evaluate` takes. `buildQuest` now refuses a quest bundle that imports the testing kit. Starters include quest unit tests in `tests/quests.test.ts`.
648
902
 
649
903
  ## 1.11.2
650
904
 
905
+ _Not published to npm. Its changes first reached npm in 1.14.0._
906
+
651
907
  ### Patch Changes
652
908
 
653
909
  - 7160db6: Include points received before a leaderboard cutoff even when their transaction commits just afterward. New point receipts use database time, and freeze waits for in-flight point writers. Existing frozen settlements remain unchanged.
@@ -690,6 +946,8 @@ No changes in this release.
690
946
 
691
947
  ## 1.8.0
692
948
 
949
+ _Not published to npm. Its changes first reached npm in 1.9.0._
950
+
693
951
  ### Minor Changes
694
952
 
695
953
  - f19da0d: Add canonical archive, unarchive, pause and resume methods and CLI commands for management resources. Keep older lifecycle and feature-specific publication forms working as deprecated 1.x APIs.
@@ -971,7 +1229,7 @@ No changes in this release.
971
1229
 
972
1230
  Stable APIs follow semantic versioning throughout the supported v1 lifetime. Compatibility exemptions apply only to explicitly experimental entrypoints.
973
1231
 
974
- - ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 2 is the first stable contract, and all pre-v1 bundles must be rebuilt and republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See [Remove stored pre-v1 state before cutover](https://console.domino.run/docs/reference/upgrade-to-v1#remove-stored-pre-v1-state-before-cutover) in the v1 upgrade guide.
1232
+ - ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 2 is the first stable contract, and all pre-v1 bundles must be rebuilt and republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See [Wallet reads return every linked wallet](https://console.domino.run/docs/reference/upgrade-to-v1#wallet-reads-return-every-linked-wallet) in the v1 upgrade guide.
975
1233
  - bdb89a4: Evaluator readers follow one rule: a missing subject is `null`, a failure throws `CapabilityError`, linked accounts are pinned once per evaluation run, and tracker reads are point in time. Retries replay the same run; scheduled deferred evaluations get fresh captures. Pre-v1 releases must be rebuilt and republished during cutover.
976
1234
 
977
1235
  - **Missing is `null`.** `ctx.x.profile()`, `follows()` and `reposted()` return `null` without a linked X account instead of throwing; `ctx.x.post()` returns `null` for a post that does not exist and needs no linked account. `ctx.discord.member()` returns `null` without a linked Discord account. `ctx.wallet.list()` returns `null` instead of `[]`, and `ctx.wallet.balance()` returns `null` instead of `total: "0"` when the member has no wallet of that family. `ctx.trackers.content()` returns `null` for a missing record or observation. See [Readers return `null` when an account is not linked or a subject is missing](https://console.domino.run/docs/reference/upgrade-to-v1#readers-return-null-when-an-account-is-not-linked-or-a-subject-is-missing).
@@ -993,7 +1251,7 @@ No changes in this release.
993
1251
 
994
1252
  - b0bffa0: Add native distinct-action progress counters with calendar and rolling windows, durable achievements, participant summaries and audited operator correction. Participant state follows settlement order for late-arriving repeat batches, and achievement alerts remain scoped to their calendar window when the next window reopens. Add qualified-referral and continuous-membership examples. See [counter rules](https://console.domino.run/docs/concepts/quests#distinct-action-counters). Update custom attempt renderers using the [counter upgrade steps](https://console.domino.run/docs/reference/upgrade-to-v1#counter-progress).
995
1253
  - bac9ca4: Declare provider modules to enable evaluator readers, use `discord.grantRole()` for role rewards, and replace Solana-only wallet readers with `wallet.list()` and `wallet.balance()`. See the [upgrade guide](/docs/reference/upgrade-to-v1#declared-integrations).
996
- - e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose `management.webhooks`, `verifyWebhook`, shared activity constants, and `domino webhooks` commands. This is additive; existing integrations need no changes. See [Outbound webhooks in the v1 upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#outbound-webhooks) for an optional polling migration.
1254
+ - e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose `management.webhooks`, `verifyWebhook`, shared activity constants, and `domino webhooks` commands. This is additive; existing integrations need no changes. See [Receive outbound webhooks](https://console.domino.run/docs/build/webhooks) for an optional polling migration.
997
1255
  - 8465d40: Operators can correct a member's tiered reward, at the revision they saw and with an audited reason. `management.handover.cancel(entitlement, { revision, reason })` (`POST /management/v1/entitlements/<id>/cancel`, `handover` permission) returns a handover in progress to `available` and puts its pickup size back into stock. `management.entitlements.correctCollection` (`…/correct`, `review`) does the same for a collection recorded by mistake, and `management.entitlements.void` (`…/void`, `review`) voids an available entitlement. A stale revision fails with 409 `entitlement-changed`, the wrong starting status with 409 `entitlement-status`. API keys with the permission can make these corrections in live. Console Rewards and member detail offer them, and Audit history lists each one with its reason. `domino capabilities --json` lists them as `entitlement-corrections`.
998
1256
 
999
1257
  Breaking: `Entitlement` gains the status `"void"`, with `voidedAt`. A voided entitlement cannot be collected; a quest that grants the same reward to the member again makes it `available`, as a fresh grant at that quest's tier; a voided referral reward stays void. SDKs from before this release fail the whole progress read once a member has a voided entitlement. Reads now parse entitlements with a tolerant `entitlementSchema` (fields a newer server adds are kept, and an unknown status reads as `"unsupported"` with `reportedStatus`); the strict stored shape is `entitlementStateSchema`, and the management client's `entitlementRecordSchema` is removed in favor of `entitlementSchema`. To upgrade, see [Entitlements can be `void`, and reads tolerate statuses they do not know](https://console.domino.run/docs/reference/upgrade-to-v1#entitlements-can-be-void-and-reads-tolerate-statuses-they-do-not-know).
@@ -1020,7 +1278,7 @@ No changes in this release.
1020
1278
 
1021
1279
  - c320586: Replace untyped prerequisite objects with typed rules. `completed(questHandle)` creates a completion rule with `scope: "ever"`; `completed(questHandle, { scope: "period", period: "day" })` checks the current project-calendar period. Balance and group requirements use `balanceAtLeast(ledgerHandle, minimum)` and `inGroup(groupHandle)`. Persisted rules carry an explicit `kind`; bare `{ quest }` and string prerequisites are not canonical v1 input.
1022
1280
 
1023
- See [Prerequisites no longer carry an untyped scope](https://console.domino.run/docs/reference/upgrade-to-v1#prerequisites-no-longer-carry-a-scope) and [Authoring references use typed handles](https://console.domino.run/docs/reference/upgrade-to-v1#primitive-handles) in the upgrade guide.
1281
+ See [Prerequisites are typed eligibility rules](https://console.domino.run/docs/reference/upgrade-to-v1#prerequisites-are-typed-eligibility-rules) and [Authoring references use typed handles](https://console.domino.run/docs/reference/upgrade-to-v1#primitive-handles) in the upgrade guide.
1024
1282
 
1025
1283
  - 569989e: Reconcile Discord role membership across projects and environments, preserve external assignments, and expose operator recovery plus current membership alongside delivery history. Revoking the last accepted grant now removes Relay-owned assignments. Read the [SDK v1 upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#discord-role-ownership-and-reconciliation) before upgrading.
1026
1284
 
@@ -1029,7 +1287,7 @@ No changes in this release.
1029
1287
  Publish initial pending delivery events in the grant transaction so observers can see queued rewards before reconciliation starts.
1030
1288
 
1031
1289
  - 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.
1032
- - 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the [upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#explicit-resource-archive-and-restore).
1290
+ - 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the [resource lifecycle](https://console.domino.run/docs/concepts/quests#resource-lifecycle).
1033
1291
  - ddfc526: The participant client, the management client and the CLI share one set of deadline, cancellation and retry rules.
1034
1292
 
1035
1293
  - Each call has one deadline covering every attempt, the waits between them and the answer's body: 15 seconds for the participant client and 30 seconds for the management client and the CLI. `createRelayClient({ timeout })` and the new per-call `{ timeout }` option of every participant method set it, as `timeout` already does for the management client. A timeout is never retried.
@@ -1106,7 +1364,7 @@ No changes in this release.
1106
1364
 
1107
1365
  **Who should act.** Apps that enroll members or read trigger outcomes, and quests whose members link accounts after the activity they expect to be paid for.
1108
1366
 
1109
- - ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 1 remains supported, while protocol-less bundles must be republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See [Remove stored pre-v1 state before cutover](https://console.domino.run/docs/reference/upgrade-to-v1#remove-stored-pre-v1-state-before-cutover) in the v1 upgrade guide.
1367
+ - ae047ca: Remove the pre-v1 runtime, release, single-wallet and tracker compatibility shims. Published quests must carry an explicit evaluator protocol; protocol 1 remains supported, while protocol-less bundles must be republished. Participant and management reads now expose only canonical wallet lists, and tracker policies use canonical identity roles and an explicit observation mode. See [Wallet reads return every linked wallet](https://console.domino.run/docs/reference/upgrade-to-v1#wallet-reads-return-every-linked-wallet) in the v1 upgrade guide.
1110
1368
 
1111
1369
  ### Minor Changes
1112
1370
 
@@ -1120,7 +1378,7 @@ No changes in this release.
1120
1378
  Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.
1121
1379
 
1122
1380
  - 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.
1123
- - 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the [upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#explicit-resource-archive-and-restore).
1381
+ - 11ab1d8: Add explicit archive and restore for code-declared resources through the management client, durable CLI operations and Console. Keep resource identities and history while stopping new activity, and preserve archive state across publication. See the [resource lifecycle](https://console.domino.run/docs/concepts/quests#resource-lifecycle).
1124
1382
  - 0a89168: Operators can archive point ledgers and correct points from Console member detail. `management.pointLedgers.list()` includes nullable `archivedAt`, and `management.pointLedgers.archive(id)` archives a ledger. Archived ledgers remain in balance and history reads and existing published rewards keep working, but new reward pickers and published definitions reject them.
1125
1383
  - 1e23d8f: Type quest completion and prerequisites from the trigger, preserve source-specific beneficiary roles, and share shape validation with publication. Reusable prerequisite-only types can receive prerequisites from their instances. See the [upgrade guide](/docs/reference/upgrade-to-v1#typed-trigger-policies).
1126
1384
 
@@ -1186,7 +1444,7 @@ No changes in this release.
1186
1444
 
1187
1445
  ### Minor Changes
1188
1446
 
1189
- - e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose `management.webhooks`, `verifyWebhook`, shared activity constants, and `domino webhooks` commands. This is additive; existing integrations need no changes. See [Outbound webhooks in the v1 upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#outbound-webhooks) for an optional polling migration.
1447
+ - e7f81ff: Add signed outbound webhooks for operator backends, with scoped subscriptions, bounded retries, delivery logs, replay, and overlapping secret rotation. Expose `management.webhooks`, `verifyWebhook`, shared activity constants, and `domino webhooks` commands. This is additive; existing integrations need no changes. See [Receive outbound webhooks](https://console.domino.run/docs/build/webhooks) for an optional polling migration.
1190
1448
 
1191
1449
  ## 1.0.0-next.26
1192
1450
 
@@ -1455,7 +1713,7 @@ No changes in this release.
1455
1713
 
1456
1714
  - c320586: A prerequisite is the quest key alone. `completed(quest)` returned `{ quest, scope: "ever" }`, and `scope` was declared but never read: a prerequisite has always meant a required quest completion from any prior occurrence, with no same-period variant. `completed()`, the quest description schema (`requires[]`) and published releases no longer carry `scope`. Releases stored with it still read, and the field is dropped. Code that reads `requires[].scope`, or builds `{ quest, scope: "ever" }` by hand, should use `{ quest }` or `completed(quest)`.
1457
1715
 
1458
- See [Prerequisites no longer carry a scope](/docs/reference/upgrade-to-v1#prerequisites-no-longer-carry-a-scope) in the upgrade guide.
1716
+ See [Prerequisites are typed eligibility rules](/docs/reference/upgrade-to-v1#prerequisites-are-typed-eligibility-rules) in the upgrade guide.
1459
1717
 
1460
1718
  ### Patch Changes
1461
1719
 
@@ -1692,6 +1950,8 @@ No changes in this release.
1692
1950
 
1693
1951
  ## 0.23.0
1694
1952
 
1953
+ _Not published to npm. Its changes first reached npm in 0.23.1._
1954
+
1695
1955
  ### Minor Changes
1696
1956
 
1697
1957
  - 49fa0fd: Apps can now match the ledger and tiered reward IDs that reads return to the keys they declared.
@@ -1713,6 +1973,8 @@ No changes in this release.
1713
1973
 
1714
1974
  ## 0.22.0
1715
1975
 
1976
+ _Not published to npm. Its changes first reached npm in 0.23.1._
1977
+
1716
1978
  ### Minor Changes
1717
1979
 
1718
1980
  - 2614ad4: Add `ctx.images` for quest evaluators: `inspect`, `compare` (perceptual hash distances), `review` (AI judgement over up to four images), `extract` (typed fields read from a screenshot) and `seen` (other participants' near-duplicate images). Images are named by reference: the attempt's photo evidence, the linked X profile picture, an X post's attached image, or a reference image listed under the new `images` key in `relay.json` (JPEG, PNG or WebP, up to 5 MiB each and 10 per deployment) and published with `domino deploy`, which uploads new files directly to storage and sends only their hashes. New snippets `x-profile-picture`, `unique-photo` and `community-screenshot` install complete quests with operator-tunable similarity thresholds. `ctx.x.post()` results now include attached `media`.
@@ -1721,10 +1983,14 @@ No changes in this release.
1721
1983
 
1722
1984
  ## 0.21.0
1723
1985
 
1986
+ _Not published to npm. Its changes first reached npm in 0.23.1._
1987
+
1724
1988
  No changes in this release.
1725
1989
 
1726
1990
  ## 0.20.0
1727
1991
 
1992
+ _Not published to npm. Its changes first reached npm in 0.23.1._
1993
+
1728
1994
  ### Minor Changes
1729
1995
 
1730
1996
  - 2ff82fb: Remove `reviews.list` and `members.list` from `createRelayClient` and their `ReviewListOptions` and `MemberListOptions` types. Since the 0.10.0 release the participant API no longer serves `GET /v1/reviews` or `GET /v1/members`, so these calls fail with every SDK version; removing the methods turns that runtime failure into a type error.
@@ -1826,6 +2092,8 @@ No changes in this release.
1826
2092
 
1827
2093
  ## 0.16.0
1828
2094
 
2095
+ _Not published to npm. Its changes first reached npm in 0.16.1._
2096
+
1829
2097
  ### Minor Changes
1830
2098
 
1831
2099
  - abc6358: Add `ctx.review.text({ text, criteria })` with typed pass, fail, and unclear results for authored quest decisions. Review verified content such as X tracker posts using the existing backend AI configuration. Validated results are captured and reused across retries; provider failures remain errors.