@domino-sdk/relay 1.53.1 → 1.55.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 (66) hide show
  1. package/CHANGELOG.md +1848 -0
  2. package/dist/analytics-URV-mxjH.d.ts +482 -0
  3. package/dist/{attempt-captures-QC4kTA44.d.ts → attempt-captures-CxxO7DeD.d.ts} +1 -1
  4. package/dist/authoring.d.ts +233 -78
  5. package/dist/authoring.js +112 -14
  6. package/dist/{automatic-outcomes-CVyZHxir.d.ts → automatic-outcomes-DxW_v5Wc.d.ts} +5 -2
  7. package/dist/avatar.d.ts +1 -1
  8. package/dist/browser.d.ts +8 -7
  9. package/dist/browser.js +14 -10
  10. package/dist/{capability-error-FQGGb5xS.d.ts → capability-error-DSd8EMQC.d.ts} +76 -2
  11. package/dist/{chunk-53JMTUWG.js → chunk-56DBFNR2.js} +1 -1
  12. package/dist/chunk-C7QZ4UBB.js +937 -0
  13. package/dist/chunk-F6HECAXA.js +661 -0
  14. package/dist/{chunk-VKHSC5MD.js → chunk-GL43VQ5D.js} +16 -16
  15. package/dist/{chunk-GTQJSDBS.js → chunk-IUF7DW76.js} +27 -16
  16. package/dist/{chunk-YBR4BUFK.js → chunk-IZRITLVV.js} +2 -2
  17. package/dist/chunk-KDR2VAJP.js +34 -0
  18. package/dist/{chunk-S2PMKZSX.js → chunk-SABMNPF2.js} +21 -2
  19. package/dist/{chunk-XFAYQFRO.js → chunk-TLK2MD4S.js} +1 -1
  20. package/dist/{chunk-ZJ3FVZRP.js → chunk-UY6KMLQG.js} +10 -3
  21. package/dist/{chunk-JYJAS4E3.js → chunk-VFBQVY4X.js} +1 -1
  22. package/dist/{chunk-YVC3B72P.js → chunk-WHG5WE43.js} +1 -1
  23. package/dist/chunk-WHMXXHPX.js +344 -0
  24. package/dist/{chunk-MP2ZF5U6.js → chunk-WUEQOKJH.js} +4 -4
  25. package/dist/{chunk-7BNCLWAO.js → chunk-XALIFNMD.js} +1 -1
  26. package/dist/{chunk-IELNFM3V.js → chunk-YCOKTQ4E.js} +96 -7
  27. package/dist/{chunk-LPHXTZDA.js → chunk-YE3XYB2F.js} +2 -2
  28. package/dist/{chunk-K6TVMNU4.js → chunk-YEVD4A5X.js} +1 -1
  29. package/dist/{client-CzNtl8UO.d.ts → client-C4zUZKpj.d.ts} +35 -7
  30. package/dist/{deployments-ovtqtAcx.d.ts → deployments-B32QS1CI.d.ts} +1060 -233
  31. package/dist/{groups-B5R1RCsl.d.ts → groups-O6onGPmC.d.ts} +131 -137
  32. package/dist/host-environment-FN4zg3hw.d.ts +30 -0
  33. package/dist/index.d.ts +8 -8
  34. package/dist/index.js +9 -9
  35. package/dist/integrations-C6_uUh-s.d.ts +584 -0
  36. package/dist/internal.d.ts +12 -10
  37. package/dist/internal.js +14 -8
  38. package/dist/{offers-CH1RZekg.d.ts → offers-BMDNcvwB.d.ts} +2 -2
  39. package/dist/{pages-BTlOg8sl.d.ts → pages-BFloMpyQ.d.ts} +25 -1
  40. package/dist/portal-proxy.d.ts +11 -1
  41. package/dist/portal-proxy.js +26 -5
  42. package/dist/{profiles-Cdv4BSd_.d.ts → profiles-BoOK03cR.d.ts} +2 -0
  43. package/dist/react/index.d.ts +488 -13
  44. package/dist/react/index.js +51 -15
  45. package/dist/runtime/authoring.js +1 -1
  46. package/dist/runtime/chunks/WKATKZXE.js +1 -0
  47. package/dist/runtime/manifest.json +3 -3
  48. package/dist/runtime/schema.js +1 -1
  49. package/dist/{schema-BJ9bOgpN.d.ts → schema-C4m9RhCR.d.ts} +166 -6
  50. package/dist/schema.d.ts +392 -334
  51. package/dist/schema.js +84 -40
  52. package/dist/server/index.d.ts +11 -10
  53. package/dist/server/index.js +1604 -838
  54. package/dist/{submission-input-BNah53go.d.ts → submission-input-BbdawqVy.d.ts} +2 -2
  55. package/dist/{telegram-browser-C36j_6v-.d.ts → telegram-browser-CwaxROAZ.d.ts} +1 -1
  56. package/dist/testing.d.ts +85 -12
  57. package/dist/testing.js +322 -37
  58. package/dist/{transport-DuB_fiA2.d.ts → transport-CZJOW5Sr.d.ts} +8018 -3150
  59. package/dist/vite.d.ts +54 -1
  60. package/dist/vite.js +35 -0
  61. package/dist/{x-BfQcsDp0.d.ts → x-1czZFVUW.d.ts} +1 -1
  62. package/package.json +3 -2
  63. package/dist/chunk-2NUD237O.js +0 -102
  64. package/dist/chunk-CCE2JWES.js +0 -1083
  65. package/dist/integrations-CllF-kWg.d.ts +0 -314
  66. package/dist/runtime/chunks/AVRVSLHX.js +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,1848 @@
1
+ # @domino-sdk/relay
2
+
3
+ ## 1.55.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5c70548: Add monthly active member usage. `management.usage.activeMembers()` and `domino usage active-members` (`GET /management/v1/usage/active-members`, `read` permission) return the project's live active members this UTC month (`month`, `activeMembers`), its plan and the plan's `limit` (null when the plan has none), and the six earlier months in `history`, newest first. An active member was active in the month on a UTC day after the day they joined the project: a signed-in participant API request, which includes their attempts and redemptions. Imports, server-reported tracker records and test members never count. `activeMemberUsageSchema` and `activeMemberMonthSchema` are exported from `@domino-sdk/relay/server`, with a reader in `managementResponses`.
8
+ - 65f3a9a: Add four metrics to the analytics catalog. `groups.joins` and `groups.leaves` count members added to and removed from a group, by when the change happened, without repeats that left membership as it was or memberships recorded without a date; their `group` breakdown and filter mean the change's group. `trackers.records` counts tracker trigger outcomes by when the record was observed, as each outcome is now, with an `outcome` (`admitted`, `skipped`) or `tracker` breakdown and a `tracker` filter that keeps the tracker's own outcomes. `leaderboards.settledParticipants` counts the rows of pending and approved leaderboard settlements by each settlement's cutoff, with a `leaderboard` breakdown and filter, and draws as `number`, `bar` or `table`. `analyticsBreakdowns` gains `group`, `tracker`, `outcome` and `leaderboard`, and `analyticsFilters` gains `leaderboard`. A dashboard panel naming a leaderboard the environment doesn't have fails the deployment with `unknown-reference`.
9
+ - 8f12977: Add the closed metric catalog. `management.analytics.catalog()` and `domino analytics metrics` (`GET /management/v1/analytics/catalog`) list every metric with its definition, kind (`count`, `distinct` or `rate`), the breakdowns and filters it accepts, its allowed charts and whether it needs a point ledger; `analyticsCatalog` and the `AnalyticsMetricId`, `AnalyticsBreakdown`, `AnalyticsFilter` and `AnalyticsChart` types are exported from `@domino-sdk/relay/server`. `management.analytics.query({ metric, range, breakdown?, filter?, ledger?, limit? })` and `domino analytics query --metric ID [--range] [--breakdown] [--filter name=a,b]... [--ledger] [--limit]` (`GET /management/v1/analytics/query`, `read` permission) read one metric bucketed like `analytics.read`, and with no breakdown or filter return the same numbers. A breakdown by quest, offer, referral program, rejection source or rejection reason has a row per matching resource (archived ones only with activity), sorted by total and capped by `limit` (1 to 100, default 100) with the rest summed into `other`. Filters narrow to quests, offers or programs by key, the quests a tracker triggers, or members now in a group. The new rates `quests.rejectionRate` and `referrals.qualificationRate` are `null` below 10 in the denominator. A query the catalog doesn't accept is a 422 with `unknown-metric`, `breakdown-not-accepted`, `filter-not-accepted`, `ledger-required`, `ledger-not-accepted`, `invalid-limit` or `unknown-reference`, and a missing group is `group-not-found`. `analyticsCatalogSchema`, `analyticsMetricQuerySchema` and `analyticsMetricResultSchema` are exported, with readers in `managementResponses`.
10
+ - c91ba35: A repeatable quest can space out a member's completions. Set `completionCooldownMs` (1,000 to 604,800,000) on a quest members submit that completes more than once, or **Completion cooldown** on a quest made in Console. After a completion of any key is accepted, a new attempt is refused with 409 `completion-cooldown` (`RelayErrorCode.CompletionCooldown`) until `retryAt`; when the retry cooldown also runs, the later one is named. A quest that completes once refuses the field. Quest progress and `QuestView` carry `nextAvailableAt`, when the member may next submit: the latest of the retry cooldown's end, the completion cooldown's end and an exhausted budget's next reset, omitted when nothing will reopen the quest. `questStatus` reports `completion-cooldown`, `canSubmit` is false until then, and `useQuest` renders again when it ends. Catalog quest instances take an optional `completionCooldownMs` (`null` for none). A quest without the field keeps today's behavior.
11
+ - 6b946d8: Add dashboards: a `relay.json` definition kind built from the metric catalog. Default-export `defineDashboard({ key, title, description?, defaultRange?, position?, sections })` from `@domino-sdk/relay/authoring`, typed so each panel accepts only the breakdowns, filters and charts its metric allows, and list it under `dashboards: [{ entry, exportName? }]` in relay.json. A deployment refuses a dashboard with a coded 422 (`unknown-metric`, `breakdown-not-accepted`, `breakdown-required`, `filter-not-accepted`, `chart-not-accepted`, `ledger-required`, `ledger-not-accepted`, `unknown-reference`, `referral-program-required`, `duplicate-key`, `too-many-panels` or `too-many-dashboards`), publishes a new version only when the definition changed, and reports each one in the preview's `dashboards` as `created`, `updated` or `unchanged`. `management.dashboards.list({ includeArchived? })` and `domino dashboards list` (`GET /management/v1/dashboards`) list them; `management.dashboards.read({ key, range? })` and `domino dashboards get KEY [--range]` (`GET /management/v1/dashboards/KEY`) return the definition and every panel's `analytics.query` result, with a failing panel's own `error` (`group-not-found`, `panel-timeout`) beside the others. `management.dashboards.archive|unarchive(key, { reason })` and `domino dashboards archive|unarchive KEY --reason` (`POST /management/v1/dashboards/KEY/archive|unarchive`) archive and restore one in test, returning `{ dashboard, changed }`, and promotions carry dashboards to live by key as changes of kind `dashboard`. The deployment preview's `dashboards` is optional, so values built for older SDKs still fit. `dashboardDefinitionSchema`, `dashboardAuthoringSchema`, `normalizeDashboard`, `dashboardIssue` and `dashboardPreviewSchema` are exported from `@domino-sdk/relay/schema`, and `dashboardListSchema`, `dashboardReadSchema` and `dashboardLifecycleResultSchema` from `@domino-sdk/relay/server`, with readers in `managementResponses`.
12
+ - 3a8a888: Engagement points land in the leaderboard window they were measured in. Each engagement settlement's point entries now take effect when the content sample that measured their growth was observed, instead of when the daily settlement paid them, and budgets charge each entry to the period of its `effectiveAt`. When the growth between two samples spans a leaderboard window or budget period boundary, Relay splits it there in proportion to time (rounded down, the remainder to the later part), one entry per side. Before, every earned board over engagement points ran about a day late.
13
+
14
+ A settling leaderboard whose score counts an engagement rule's points now takes a closing measurement before it freezes. While it closes, its settlement reads as `pending` with no rows in the review reads (`settlementReviews`, `settlementReview`), with a new optional `closing` field holding `startedAt` and `deadline`: Relay asks scheduled trackers to resample the content, a sample observed within 30 minutes after the cutoff counts as measured at it, and the settlement freezes when everything is measured or the wait ends. Settlements gain `unmeasured`: content frozen at its last good sample, with `content`, `member` and `observedAt`. `management.leaderboards.retrySettlementMeasurement(id, { actionId? })` and `domino leaderboard-settlements retry-measurement SETTLEMENT` (`POST /management/v1/leaderboard-settlements/:id/retry-measurement`, `review` permission) close such a settlement again for another 30 minutes. Approving a closing settlement fails with `settlement-closing`; retrying one with nothing unmeasured fails with `settlement-measured`. The older `settlements` and `settlement` reads omit closing settlements.
15
+
16
+ - 5ae31db: Evaluators read the member's groups, campaign enrollment and referral progress. `ctx.member.groups()` returns the keys of the project's unarchived groups the member belongs to. `ctx.member.enrolled(campaign)` takes a campaign definition or key and returns whether the member's enrollment is active: `false` after withdrawal and without one. `ctx.member.referrals(program)` takes a referral program handle, key or ID and returns the member's progress as inviter, as `relay.profiles.referral()` returns it, never codes or accounts. Each is pinned once per evaluation run at its first call and counts against the run's capability calls; an unknown campaign or program fails with `author-error`. `ReferralHandle` names the handle `defineReferral` returns.
17
+
18
+ The testing kit answers them from `facts.member.groups`, `facts.member.campaigns` (`{ key, enrolled }` per campaign) and `facts.member.referrals`. Like the other member-state calls, they are not listed in a run's `calls`.
19
+
20
+ - d463895: Evaluators read the member's point balances and quest completions. `ctx.member.balance(ledger)` takes a ledger handle or key and returns the member's balance in it, as `relay.me.balances()` lists it. `ctx.member.completions({ quest?, since?, limit? })` returns the member's accepted, unrevoked quest completions in the project, newest first, each with the quest key, completion key and `completedAt`; `limit` defaults to and is capped at 100. Both are pinned once per evaluation run at their first call, count against the run's capability calls, and fail with `author-error` for an unknown ledger or quest. Write rules such as "2 of these 5 quests" or "a balance of 100 or one of these quests" in `evaluate` with them.
21
+
22
+ `EvaluationMember` is now `@compat sealed`: Relay and the testing kit create it, and a 1.x minor release can add members to it.
23
+
24
+ The testing kit answers the readers from `facts.member.balances` and `facts.member.completions`, filtering and ordering completions as Relay does. Like Telegram calls, their calls take sequence numbers but are not listed in a run's `calls`.
25
+
26
+ - 7ff5984: Evaluator failures are coded. A quest bundle that throws while loading, an `evaluate` that throws anything without an escaped capability failure in it, and one that returns no decision now fail the attempt once with code `evaluator-error` and the thrown error's name and message (at most 1,000 characters), instead of retrying and ending with "Execution failed after retries". A retryable capability failure that escapes, rethrown or wrapped as a `cause`, is still retried.
27
+
28
+ An evaluation run's ninth capability call throws `CapabilityError` with the new code `capability-limit`, never retryable, without reaching Relay; before, quest code saw an opaque failure. Like `integration-required`, it is a code `capabilityErrorCodes` does not list. `reservedAttemptCodes` now includes `evaluator-error` and `capability-limit`, so a reject code cannot use them.
29
+
30
+ The testing kit reports both the same way: the ninth call throws the `capability-limit` `CapabilityError` instead of failing the run with a `TestSetupError`, and `runQuest` rejects with the new `EvaluatorError` (code `evaluator-error`, the same message, and what quest code threw as `cause`) for anything quest code throws other than a capability failure it received. A test that expected the thrown error itself reads it from `cause`. `maxCapabilityCalls` is exported from `@domino-sdk/relay/testing`.
31
+
32
+ - 6cc08fa: A member can greet themselves by first name. Each entry of `relay.profiles.mine().options` gains `givenName`: the first name Google sent at sign-in (OIDC `given_name`), null for X, Discord and accounts without one. Older servers' answers still parse, with `givenName: null`. Only the member's own profile carries it; profile reads by ID and leaderboard rows don't. Existing Google accounts get it on their next sign-in. Operator member reads are unchanged.
33
+ - 921e9d7: Add `dominoHosting({ command })` to `@domino-sdk/relay/vite`. It returns `cloudflare`, options for the app's own `cloudflare()` call from `@cloudflare/vite-plugin` (the Worker in Vite's `server` environment, and, only when serving, the variables `domino dev` sets forwarded to the local Worker, empty when unset), and `server`, Vite's server settings (port from `DOMINO_APP_PORT`, 5173 by default, and the `DOMINO_PREVIEW_ORIGIN` host with HTTPS hot reload when it is set). The SDK does not depend on the Cloudflare plugin.
34
+
35
+ Add `domino build --hosting-artifacts`, which writes the quest and catalog artifacts the hosting builder reads to `.domino-build/quests.json` and `.domino-build/catalog.json` beside `relay.json`.
36
+
37
+ The starters use both: their `vite.config.ts` is their own plugins plus `dominoHosting()`, and `scripts/build-domino.mjs` gives way to `vite build && domino build --hosting-artifacts`. Existing apps keep working unchanged.
38
+
39
+ - 6156dec: Automatic attempts that failed with `integration-required` are admitted again. When a project's Discord connection becomes ready, on first connection or after a repair from needs attention, Relay admits a new attempt in the background for each member of an active automatic quest without a tracker that needs Discord whose latest attempt failed that way. `isRetryableAutomaticOutcome` now also returns `true` for an outcome whose linked attempt is `failed` with code `integration-required`, so automatic catch-up (`management.quests.previewAutomaticCatchUp` and `catchUpAutomatic`) accepts these outcomes too. Its signature is unchanged. Console marks published quests that need Discord while it is missing, unavailable or needs attention.
40
+ - a801a1d: Leaderboard settlements can split a pool prize by score. `settle.pool` is `{ amount, unit, ledger?, minScore? }`: `amount` is the whole pool in base units as a decimal string, and `unit` is `"points"` or `{ code, decimals }` for a token or currency. A points pool names the `ledger` it pays into (a ledger handle or key in `defineLeaderboard`) and holds at most 2,147,483,647 points; other units take no ledger. With a pool, `settle.prizes` may be empty (`defineLeaderboard` lets you leave it out), and a board can have both. When the window freezes, Relay splits the pool over the eligible frozen rows in proportion to score, in exact integer math: members scoring 0 or less, or at or below `minScore`, get nothing, each pool share rounds down, and the leftover base units go one each to the largest remainders, earlier positions first, so the shares sum exactly to the pool. With no eligible score the whole pool stays unallocated.
41
+
42
+ Settlement reads (`settlement`, `settlementReview`) gain `pool`, with `allocated` and `unallocated`, and each row gains `share` in base units; both are absent from settlements without a pool. Approval pays a points pool's shares as points in each member's prize completion, and revoking the completion reverses them. Relay pays no other unit: `management.leaderboards.settlementExport(id)` (`GET /management/v1/leaderboard-settlements/:id/export`, `read` permission) and `domino leaderboard-settlements export SETTLEMENT` (CSV, or `--json`) list each member's score, rank, share in base units and whole units, and verified linked wallets, as the operator's payout list. Console shows each winner's pool share before approval.
43
+
44
+ - cbdcdc0: Add the plan limits in test and setup, where plans apply to the organization. A sign-in that would add a real person to a test environment that already has its plan's number of real people fails with `limit-reached` (403, `limit: "real-people-in-test"`, `count`, `maximum`); people who already joined, test members and live sign-ins are unaffected. Creating a project, or transferring a Free project in, fails with `limit-reached` (403, `limit: "free-projects"`) when the organization already holds 10 Free projects. Adding a custom domain needs the Growth plan or above and otherwise fails with `plan-required` (403, `feature: "custom-domain"`). `management.usage.realPeopleInTest()` and `domino usage real-people-in-test` (`GET /management/v1/usage/real-people-in-test`, `read` permission) return the test environment's `realPeople`, the project's `plan`, its `limit` and whether it is `enforced`; `realPeopleInTestUsageSchema` is exported from `@domino-sdk/relay/server`, with a reader in `managementResponses`. The CLI's error JSON keeps a `limit-reached` or `plan-required` refusal's detail in `details`.
45
+ - 823a691: Member accounts that Relay reads in the background with the member's own OAuth tokens now report when those tokens stop working. In management member reads (`management.members.get`, `list`, `lookup`, and the `person` on attempts) such an account carries `status: "connected"`, or `status: "needs-reconnect"` with a `reason` of `revoked`, `expired` or `missing-scope`; accounts Relay keeps no tokens for carry neither field. `domino members get` and the other CLI results include the same optional fields. The participant client's `relay.auth.accounts()` and `useAccounts()` report the account's `status` as `"reconnect"` until the member links it again. Relay refreshes kept tokens before they expire. No provider reads accounts in the background yet.
46
+ - cc459ef: Add `questActionState(quest)` and `useQuestAction(view)` to `@domino-sdk/relay/react`. `questActionState` takes a `useQuest()` result and returns `open` (show the quest's input: the participant can submit now, or is starting a new submission after discarding a failed one on an active quest), `counted` (the submission just sent was accepted on a repeatable quest) and `automatic` (the server owns every run). `useQuestAction(view)` returns `useQuest(view)` with those three fields added.
47
+
48
+ Add `signedInMember(request, env, { apiUrl })` to `@domino-sdk/relay/portal-proxy`. A hosted app's server route calls it to learn the signed-in member's ID, or null for a visitor or a session that is not a member's. It asks Domino's `/v1/auth/session` through the app's participant proxy with the request's cookies.
49
+
50
+ The starters use `questActionState` instead of their own copies of these rules, and the classic starter's `server/participant.ts` gives way to `signedInMember`.
51
+
52
+ - a4c93bc: Add Stripe billing for a project. `management.billing.checkout({ plan, interval })` and `domino billing checkout --plan launch|growth|scale [--interval month|year]` (`POST /management/v1/billing/checkout`) return a Stripe Checkout URL that subscribes the project; `management.billing.portal()` and `domino billing portal` (`POST /management/v1/billing/portal`) return the organization's Stripe customer portal. Both need an organization administrator, not an API key (403 `permission-required`). While billing is staff-only, every organization but Domino's own is refused with 403 `billing-staff-only`. A project whose subscription holds its plan is refused another checkout with 409 `subscription-exists`, and the portal answers 409 `no-customer` before the organization's first checkout; a deployment without Stripe answers 503 `not-configured`. `management.billing.read()` and `domino billing show` (`GET /management/v1/billing`, `read` permission) return whether billing is open to the caller (`available`, `reason`) and, when it is, the project's `subscription`: `plan`, `interval`, Stripe's `status`, `currentPeriodEnd`, `cancelAtPeriodEnd`, `lastInvoice` and `livemode`. `billingCheckoutInputSchema`, `billingSessionSchema`, `billingSubscriptionSchema` and `projectBillingSchema` are exported from `@domino-sdk/relay/server`, with readers in `managementResponses`.
53
+ - b8348fd: Change a project's Stripe plan, cancel and resume. `management.billing.changePlan({ plan, interval })` and `domino billing change --plan launch|growth|scale [--interval month|year]` (`POST /management/v1/billing/plan`) move a paying project's subscription: a higher plan or annual billing applies now with the prorated difference invoiced at once, and a lower plan or monthly billing is held for the renewal. The answer is `billingPlanChangeSchema`: `timing` (`now`, `renewal` or `none`) and `invoiceUrl`, set when the immediate charge needs the customer and nothing changes until it is paid. `management.billing.cancel()` and `management.billing.resume()` (`domino billing cancel`, `domino billing resume`; `POST /management/v1/billing/cancel|resume`) set the subscription to end when its period does, or take that back, and return the project's billing. A project without a subscription that holds its plan is refused with 409 `no-subscription`, and a plan change while the subscription is set to cancel with 409 `subscription-canceling`. The subscription now carries `scheduledChange`, the plan, interval and date of a change held for the renewal, or null. The customer portal no longer changes or cancels plans; it keeps cards, invoices and billing details.
54
+ - c055c43: A project's Stripe subscription follows the project. Deleting a paying project sets its subscription to end with its period, and restoring it resumes the subscription; purging ends it at once. A transferred project keeps its plan until the old organization's period ends, and `management.billing.checkout()` (`domino billing checkout`) subscribes it in the receiving organization with no charge before then. The subscription now carries `endedFor`: `project-deleted`, `project-transferred` or null.
55
+ - f89ea5d: Buy AI builder credit, and see an overdue subscription. `management.billing.topup({ quantity })` and `domino billing topup [--quantity 1-10]` (`POST /management/v1/billing/topup`) return a Stripe Checkout URL buying `quantity` packs of $10 AI builder credit for the project, on any plan; the credit is added once the payment is in, once per purchase. `billingTopupInputSchema` and `billingTopupMaxQuantity` are exported from `@domino-sdk/relay/server`. The subscription now carries `unpaid` while an invoice is overdue: `since` (when it first failed), `promotionsRefusedAt` (14 days later, when the organization's new promotions are refused with `plan-required` and `reason: "unpaid"`) and `offlineAt` (45 days later, when the project's live goes offline until it is paid), or null.
56
+ - 8a5b7d3: Add `settings.amount({ label, default, decimals? })` and `settings.chain({ label, default })` to `@domino-sdk/relay/authoring`. An amount setting holds a non-negative base-unit integer string such as `"1000000000000000000"`; with `decimals`, Console shows and edits it in whole tokens. A tracker filter bound may name an amount setting (`gte: { setting: "minimum" }`) and compares it exactly, so 18-decimal thresholds fit. A chain setting holds `"solana"`, Solana mainnet's CAIP-2 ID or an EVM chain ID such as `"eip155:8453"`, typed as the `ctx.wallet.balance()` query's `chain`. Both are text settings (`picker: "amount"` or `"chain"`; text fields gain an optional `decimals`), and publication and Console edits refuse other values. `@domino-sdk/relay/schema` exports `isAmount`, `isBalanceChain`, `isBoundSetting`, `textSettingSchema` and the `AmountSetting`, `BoundSettings` and `BalanceChain` types. The `token-holder` example and snippet use both settings and need no `WalletBalanceQuery` cast; their minimum is now in base units.
57
+ - c656f61: A quest's `completion` can be a function of the policy builders: `completion: ({ keyed }) => keyed(({ tracker, settings }) => tracker.record.data.messageId)`. Its key callbacks, `keyed(...)`'s key and `progress(...)`'s `distinct`, read a `QuestKeyedContext` typed from the quest: `tracker` is non-null and typed for a tracker trigger, and `settings` has the quest's settings, so the `tracker!` cast is no longer needed. The builders it receives are `once`, `keyed`, `streak` and `progress`. `completion: keyed(...)` built inline keeps working and keeps its `KeyedContext`. Key callbacks now also receive the quest's parsed `settings` at runtime.
58
+
59
+ `runQuest` reports `run.diagnostics` (and `createTestContext` its `diagnostics`): a `completion-key-ignored` entry when the evaluator returns a `completionKey` the completion policy ignores. Only `keyed(...)` uses it; `once()`, `streak(...)`, `progress(...)` and content quests key completions themselves. The decision is unchanged, and hosted attempts record nothing new. The Domino skill's authoring and tracker references describe the builder form and which policies use `completionKey`.
60
+
61
+ - 4aaea1a: X tracker refresh policy. Tracker status gains `pauseReason` (`operator`, `x-budget-exhausted` or `test-window-ended`; null when not paused), `resumesAt` (when a tracker paused on its X budget resumes on its own) and `effectiveRefreshSeconds` (the interval a scheduled tracker actually runs at: `refreshSeconds`, but no shorter than the project's plan allows, 6 hours below Enterprise). All three are optional, so older servers' answers still parse. Known X posts are re-read every 6 hours for 48 hours after they were posted, daily until day 7, then never. A deployment whose X search tracker asks for less than the plan's minimum warns `tracker-refresh-below-plan-minimum` and still publishes. A spent monthly X budget pauses X trackers with `x-budget-exhausted` (live only while plan limits are enforced, test always) until the budget renews, and resuming one meanwhile answers 409 `x-budget-exhausted`; a test X tracker pauses with `test-window-ended` after 14 days until resumed. `domino trackers create|update --refresh-seconds` help names the plan's minimum.
62
+
63
+ ### Patch Changes
64
+
65
+ - 5a8a0a0: CLI paper cuts. `domino stage --commit` accepts a short or uppercase SHA: a short one resolves to the full commit in the checkout, failing with Git's reason when it is unknown or ambiguous; a `--request-id` resume still takes the full SHA the build request printed. `domino api` strips one `/management/v1` the path already starts with, so `domino api POST /management/v1/staging/link` reaches `/staging/link` (a one-line note on stderr says so outside `--json`). A reader that closes stdout early, as in `domino --help | head`, ends the CLI with status 0 instead of an unhandled EPIPE. `domino test-members act --wait [--timeout SECONDS]` reads the attempt until it is no longer queued, evaluating or deferred and prints it with its status, reason and code; without `--wait` it prints the queued attempt as before. The deprecation notes of bare `domino status`, `quests` and `releases` name the typed read for each use, the help says that `--json` stdout carries exactly one JSON value while progress events go to stderr as NDJSON, and `domino build --help` recommends `--out FILE --json` for a summary. Both packages now ship their `CHANGELOG.md`.
66
+
67
+ ## 1.54.0
68
+
69
+ ### Minor Changes
70
+
71
+ - ee9e5f1: Add `management.analytics.read({ range, ledger })` and `domino analytics get [--range 7d|30d|90d|all] [--ledger KEY]`, which read a project's activity in one environment from `GET /management/v1/analytics` with `read` permission. A range ends now and is bucketed by project-calendar day (`7d`, `30d`) or Monday week (`90d`, `all`, which starts on the day of the earliest dated record: the first membership, or an older imported completion). Each metric (sign-ups, active members, attempts, completions, rejections, attempts sent to staff review, one point ledger's points awarded, spent and net and members earning, fulfilled offer redemptions, accepted and qualified referrals) has a total, a value per bucket and the previous range's total. Referrals also report `acceptedQualified`, how many of the range's accepted referrals have qualified since, for a share that never exceeds 100%. Each quest also has its decisions, rejection rate, top 5 rejection reasons and rejections by source. Offers and referrals appear only when the project has them, and a test environment section only in test. `domino analytics get` prints a readable summary, and the read with `--json`; `analyticsReadSchema` and `analyticsQuerySchema` are exported from `@domino-sdk/relay/server`.
72
+
73
+ `managementResponses` from `@domino-sdk/relay/server` now builds each reader on first use, as `managementResponseSchemas` does, so importing the entry retains about 3.5 MiB less heap. Each reader is built once and stays writable.
74
+
75
+ - 63f7a14: Custom domains. An organization admin can attach a domain they control, such as `quests.example.com`, to a project's live hosted app. Domino shows a CNAME to `cname.domino.page` and a `_domino.` TXT record that proves the domain, issues its certificate, and serves the live app there once both are in place. While the domain is active, page loads on `<project-slug>.domino.page` redirect to it.
76
+
77
+ - `domino domains add|list|get|recheck|remove` and Console **Settings → Domains** attach, check and remove the domain, and show each DNS record against what public DNS returned and Cloudflare's statuses word for word.
78
+ - `management.domains.list()`, `.get(id)`, `.add(hostname)`, `.recheck(id)` and `.remove(id)`.
79
+ - Root domains, wildcards, IP addresses and Domino's own domains are refused with `invalid-hostname`. A project has one custom domain.
80
+ - The `hosted-custom-domains` capability is now supported.
81
+
82
+ - 50246d7: `getHostEnvironment()` from `@domino-sdk/relay/browser` tells a hosted app where it is running: it resolves to `{ environment: "test" | "live", kind: "staging" | "preview" | "live" }` from the host's `/__domino/environment`, which Domino answers itself on staging (behind staging access), previews and the live app. It asks once per page load, and resolves to `null` where the host does not answer, such as a local dev server, a host outside Domino, or server rendering. Use it instead of hostname patterns, which are not a supported signal.
83
+ - f5bd728: Test members can accept a referral invitation: `management.testMembers.acceptReferral(member, { code })` or `{ inviter }` (another test member, whose invitation code is used), and `domino test-members accept-referral MEMBER --code CODE` or `--inviter MEMBER`. The acceptance runs as a signed-in member's would, with the same refusals, qualification and rewards, in the test environment only, and the audit names the operator.
84
+
85
+ ## 1.53.1
86
+
87
+ No changes in this release.
88
+
89
+ ## 1.53.0
90
+
91
+ ### Minor Changes
92
+
93
+ - 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.
94
+
95
+ ## 1.52.1
96
+
97
+ No changes in this release.
98
+
99
+ ## 1.52.0
100
+
101
+ ### Minor Changes
102
+
103
+ - 9fadb80: Evaluator rejections take an optional code. `decision.reject({ reason, code })` from `@domino-sdk/relay/authoring` names the cause with a stable key, such as `wrong-answers`, that stays the same when the reason's wording changes. Relay stores it as the attempt's `code`, returned by `management.attempts.get` and `management.attempts.list`, `domino attempts list|get` and Console's attempt detail beside the reason; responses to members carry `code: null` for an evaluator-rejected attempt. A code uses lowercase letters, digits and hyphens, at most 64 characters, and cannot be one of the codes Relay writes itself, now exported as `reservedAttemptCodes` from `@domino-sdk/relay/schema`; an invalid one fails the evaluation, and `runQuest`, with an error naming the rule. A content quest's `performance.evaluate` accepts the same `code`. Rejections without a code are unchanged.
104
+
105
+ ## 1.51.0
106
+
107
+ ### Minor Changes
108
+
109
+ - 4dddeb0: `@domino-sdk/relay/server` exports `customDomainSchema`, `customDomainListSchema`, `customDomainRecordSchema`, `customDomainStateSchema` and `customDomainInputSchema`, the shapes of the management API's custom domain routes (`/management/v1/domains`).
110
+
111
+ ## 1.50.0
112
+
113
+ ### Minor Changes
114
+
115
+ - 2ecde12: An operator can pass a quest for a test member without running its check, or accept a test member's rejected attempt anyway, to reach the quests behind a check that can't be satisfied in test.
116
+
117
+ - New `management.testMembers.pass(member, { actionId, quest, completionKey? })` admits an attempt on a manual quest as a submission would, with no input, then accepts it as the operator. Prerequisites, availability, enrollment and the retry cooldown still refuse it, and the completion policy, budgets and rewards apply as for an accepted check: a progress quest counts one step, and a keyed quest without `completionKey` gets a fresh key. From the CLI, `domino test-members pass MEMBER QUEST [--completion-key KEY]`.
118
+ - New `management.testMembers.acceptAnyway(member, { actionId, attempt })` accepts a rejected attempt as the operator, such as a tracker-triggered attempt its check rejected. The attempt keeps its rejection in its history, and when it is the member's latest attempt on the quest, its retry cooldown ends. Any other status is refused with 409 `attempt-not-rejected`. From the CLI, `domino test-members accept-anyway MEMBER ATTEMPT`.
119
+ - Both answer the decided attempt and record a `test-member-decision` event with the operator as actor, listed in the `test-members` audit category. `testMembers.history` attempts an operator decided carry `operatorDecision: { kind, actor, at }`.
120
+
121
+ Both need `publish` permission in the test environment, like the other test-member methods; live refuses them.
122
+
123
+ ## 1.49.1
124
+
125
+ No changes in this release.
126
+
127
+ ## 1.49.0
128
+
129
+ ### Minor Changes
130
+
131
+ - c262df2: `management.attempts.list` takes `quest` to list one quest's attempts, by its ID, alone or with `q` and `status` (`managedAttemptQuerySchema.quest`). `domino attempts list --quest <id>` does the same from the CLI.
132
+ - 41a3fe9: Hosted apps go live. Approving a promotion that carries a hosted app build deploys that build as the project's public live app at `https://<project-slug>.domino.page`, with live's own management key (`DOMINO_ENVIRONMENT` is `live`), app secrets, KV and SQL stores, and schedule signing key. The previous live build serves until the new one switches in; a failed deployment keeps it, and promoting the build again retries. Before the first such promotion the address answers 404 "Nothing is live here yet".
133
+
134
+ - `domino secrets`, `domino storage` and `domino schedules` take `--environment live` to manage the live app's secrets, stores and jobs. Without the flag they use test, as before; they ignore `RELAY_ENVIRONMENT` and the environment in `relay.json`.
135
+ - A CLI sign-in session can set and delete app secrets in live. Other live writes still need Console or an API key.
136
+ - `management.liveApp.get()` returns the live app's address and state (`none`, `deploying`, `live` or `failed`, with the failure). Console shows it as the **Live app** card in **Deployments**.
137
+ - The `hosted-live-app` capability is now supported.
138
+
139
+ ## 1.48.0
140
+
141
+ ### Minor Changes
142
+
143
+ - 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.
144
+
145
+ ## 1.47.0
146
+
147
+ ### Minor Changes
148
+
149
+ - 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`.
150
+
151
+ The posting-streak and original-post SDK examples, the tracker guides and the CLI skill references now gate X post rewards on an `approved-creators` group.
152
+
153
+ ## 1.46.0
154
+
155
+ ### Minor Changes
156
+
157
+ - fc0fc19: One deployment holds up to 1,000 quests and 200 quest types (was 50 each). `projectDeploymentSchema.releases`, `batchReleaseSchema.releases`, `questDraftChangesSchema` quests and collections, and `catalogDeploymentSchema` collections accept up to 1,000; the limits are exported as `questScaleLimits`. `domino build`, `deploy` and `dev` accept up to 1,000 quests and 200 quest types in one manifest. Other resource kinds keep their limits.
158
+
159
+ SDK clients older than this release check drafts against 50 quests and 50 collections, so they cannot read a draft that holds more. Update the SDK before building drafts larger than that.
160
+
161
+ ## 1.45.0
162
+
163
+ ### Minor Changes
164
+
165
+ - 8bb0a7b: Quest and quest type bundles import the SDK instead of carrying it. The SDK package ships its runtime (`dist/runtime`), and `buildQuest(entry, { runtime: true })` from `@domino-sdk/relay/build` leaves `@domino-sdk/relay/authoring`, `@domino-sdk/relay/schema` and, at the matching version, `zod` out of the bundle, returning the `runtime` the code imports. `domino build`, `deploy`, `dev` and hosted builds use it: releases and types record a `runtime` reference and the deployment carries each runtime once in `runtimes`. A quest bundle shrinks from about 170 KB to a few kilobytes. Apps on an SDK without a runtime keep building self-contained bundles. A Relay that predates runtimes refuses such a deployment, and the CLI says to update it. See [Shared SDK runtime](https://github.com/GetDomino/relay/blob/main/docs/public/concepts/quests.md#shared-sdk-runtime).
166
+
167
+ ## 1.44.0
168
+
169
+ ### Minor Changes
170
+
171
+ - f114841: `management.audit.list` accepts `category: "test-members"`, which lists operators acting as test members in test: creating, submitting a quest as, advancing the clock of, giving and removing wallets for, sending synthetic records as, signing a staging app in and out as, and resetting a test member. Listing every category now includes these records too. The new `auditListQuerySchema` export validates this query and `AuditQuery` is its input type; `auditQuerySchema` keeps the three earlier categories and is deprecated in its favor. Console's **Audit history** shows these records under a new **Test members** filter.
172
+
173
+ ### Patch Changes
174
+
175
+ - f114841: `management.testMembers.reset` and `domino test-members reset` now unlink the accounts a test member connected in your app while a staging site acted as it: X, Discord, Telegram, Google and Apple accounts and wallets. Before, such an account made reset refuse with `test-member-reset-blocked`. Only the member's link is removed; the account itself is untouched and anyone can link it afterwards. Reset still refuses a member with an account that signs in, an app user ID, or an X account a member import attached. See [Reset and create another member](https://console.domino.run/docs/use/quests#6-reset-and-create-another-member).
176
+
177
+ ## 1.43.0
178
+
179
+ ### Minor Changes
180
+
181
+ - 64e8df3: Test members can post on X, send Discord messages and add Discord reactions, so quests on managed X and Discord trackers can be tried in test without real accounts.
182
+
183
+ - New `management.testMembers.record(member, { actionId, tracker, data?, as? })` writes one synthetic record into a managed X or Discord tracker. `data` uses the stored record's field paths (`content` and `likeCount` for X, `channelId` and `text` for messages, `emoji` for reactions), so it exercises quests' `where` filters; Relay sets IDs and real time. `as: "messageAuthor"` makes the member the reacted-to message's author. The answer is `{ tracker, record, account, stored, admissions }`. From the CLI, `domino test-members record MEMBER --tracker TRACKER [--data JSON] [--as author|messageAuthor]`.
184
+ - The first record for X or Discord links the test member a synthetic account of that kind, which attribution, member `identities` and `x.connection()` read like a real link. Reset removes it with the member's synthetic records.
185
+ - Tracker record reads mark these records `synthetic: true`. Collected and ingested records never carry it.
186
+ - Console's **Try** tab sends posts, messages and reactions for X and Discord quests, starting from values that match the quest's filter, and the tracker's record list marks synthetic records.
187
+
188
+ It needs `publish` permission in the test environment, like the other test-member methods; live refuses it.
189
+
190
+ ## 1.42.0
191
+
192
+ ### Minor Changes
193
+
194
+ - b7156c7: Releases and quest types can reference a shared SDK runtime instead of bundling the SDK. `releaseSchema`, `publishedReleaseSchema`, quest type versions and catalog deployment types take an optional `runtime` reference (`{ digest, sdkVersion }`), and project deployments take `runtimes`, each runtime's modules sent once. The new `@domino-sdk/relay/schema` exports are `sdkRuntimeSchema`, `sdkRuntimeReferenceSchema`, `sdkRuntimeModulesSchema`, `sdkRuntimeModulePathSchema`, `sdkRuntimeLimits` and `sdkRuntimeDigest`. Existing self-contained bundles work unchanged.
195
+
196
+ ## 1.41.0
197
+
198
+ ### Minor Changes
199
+
200
+ - 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).
201
+
202
+ ## 1.40.0
203
+
204
+ ### Minor Changes
205
+
206
+ - 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.
207
+
208
+ - `management.promotions.prepare()` (test), `history()`, `get(id)`, `approve(id)` and `reject(id, { reason })` (live, Console session), and `rollback(id)` (live): `POST /promotions`, `GET /promotions/history`, `GET /promotions/:promotion`, and `POST /promotions/:promotion/approve`, `/reject` and `/rollback`.
209
+ - `domino promote --json` prepares one from test; `domino promotions list` and `domino promotions get` read them.
210
+ - New error codes: `promotion-empty` (live already matches), `promotion-changed` (test or live changed since the promotion was prepared; approval changed nothing) and `promotion-decided`.
211
+
212
+ `management.deployments.promote(id)` and `domino deploy --promote` keep their 1.x request and response shapes. Once the test deployment is published they now prepare a promotion of all of test, answered as a pending deployment record with no resources whose first warning, code `environment-promotion`, names the promotion. Approve it as a promotion: `deployments.get` and `deployments.publish` do not accept its ID.
213
+
214
+ Live now refuses direct code deployments. `domino deploy --environment live`, with or without `--preview`, and `management.deployments.preview` with a client for live fail with 422 `promotion-required` before the bundle is checked, whatever the credential; the CLI reports it as `ENVIRONMENT_UNSUPPORTED` (exit 1) and no longer submits anything. Automation that deployed to live should deploy to test, then run `domino promote` (or `management.promotions.prepare()`) and have a person approve the promotion in Console. Live deployments submitted before this change can still be submitted, approved, rejected and rolled back. Request and response shapes are unchanged, so like the other live refusals this ships in a minor release.
215
+
216
+ ## 1.39.1
217
+
218
+ ### Patch Changes
219
+
220
+ - 08f24a3: An operator who signed in to a project's staging app through Console and holds `publish` can now sign that app in as a test member, through the staging host's `/__domino/toolbar/*` endpoints. The test member gets an ordinary 8-hour participant session. Issuing and ending it is audited as the operator, and that audit survives reset. Reset now ends a test member's participant sessions instead of refusing. Live, members that aren't test members, password staging sessions, API keys, hosted keys and CLI sessions can't start one. A password staging session gets the new refusal code `operator-required`, which the CLI skill's error reference now lists.
221
+
222
+ ## 1.39.0
223
+
224
+ ### Minor Changes
225
+
226
+ - a9b11a8: Participants read the point entries behind their score on a leaderboard. `relay.me.leaderboardHistory({ board, cursor? })` (`GET /v1/me/leaderboards/:board/history`) and the React hook `useLeaderboardHistory(board)` page through the signed-in member's own entries that the board counts: its ledger filtered by the board's window, quests and calculation, so every page together sums to the member's score. Entries have the same shape as `relay.me.pointHistory()`, newest recorded first, and the page's `period` is the window the first page counted. A member the board excludes, or one outside its group, gets no entries.
227
+
228
+ ## 1.38.1
229
+
230
+ ### Patch Changes
231
+
232
+ - 9b02ade: `questConfiguration` from `@domino-sdk/relay/server` accepts any release-shaped value with `configuration`, `fields` and `values`, so callers no longer need the release's code to read its configuration.
233
+
234
+ ## 1.38.0
235
+
236
+ ### Minor Changes
237
+
238
+ - 167a840: A quest can make a member wait before trying again after a rejection. Set `retryCooldownMs` (1,000 to 604,800,000) on a quest members submit, or **Retry cooldown** on a quest made in Console, where a new quiz starts at five minutes. After the evaluator or a reviewer rejects the member's latest attempt, a new attempt is refused with 409 `retry-cooldown` until `retryAt` (also `RelayError.retryAt`). Quest progress and `QuestView` carry `retryAt` while the cooldown runs; `questStatus` reports `retry-cooldown`, `canSubmit` is false until then, `isCoolingDown(view)` is new, and `useQuest` renders again when it ends. Catalog quest instances take an optional `retryCooldownMs` (`null` for none). A quest without the field keeps today's behavior: no cooldown.
239
+
240
+ ## 1.37.1
241
+
242
+ No changes in this release.
243
+
244
+ ## 1.37.0
245
+
246
+ ### Minor Changes
247
+
248
+ - 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.
249
+ - e1b3c70: A points reward can pay up to 2,147,483,647 points, the largest 32-bit signed integer, instead of 10,000. The new limit applies wherever `points()` and `pointsRewardSchema` are used: quest rewards, evaluator decisions, leaderboard prizes and referral rewards. Offer prices (`offerPriceSchema`) take the same maximum and still start at 1. Code that declares a larger amount needs this SDK version, and apps that read one need SDK 1.10.0 or later, whose response readers ignore these maximums.
250
+ - ff024c7: Scheduled jobs can report what a run did. A job route answers with `scheduleResult({ outcome, summary?, metrics?, diagnostics? })` from `@domino-sdk/relay/server`, and Relay records it with the run as `result`. An `outcome` of `partial` or `failed` records the run as failed even when the HTTP status is 2xx. Relay reads any `application/json` answer up to 16,384 bytes. An invalid or larger one leaves the status alone and sets the run's `error` to `Result ignored: …`. Each schedule keeps its latest 20 runs: `management.schedules.runs(name)` (`GET /schedules/:name/runs`, `domino schedules runs NAME`, Console **Run history**) returns them newest first, and needs `read` permission.
251
+ - ff024c7: Read a hosted app's SQL value in full where a rows page cuts it at 2,000 characters. `management.storage.cell(name, table, { row, column, cursor? })` (`GET /storage/sql/:name/cell?table=&row=&column=`, `domino storage cell DB TABLE ROW COLUMN [--all]`) returns up to 100,000 characters per page with the value's `type` and `length`, and a `nextCursor` for the rest; `--all` follows it. `row` is the row's position in storage order, the rows page's `offset` plus its index on the page. A BLOB answers with its size and no text. It needs `publish` permission, like the other storage reads. Console's row view has **Show full value**.
252
+
253
+ ## 1.36.0
254
+
255
+ ### Minor Changes
256
+
257
+ - 5da9393: Participants read their own email address. `relay.me.get()` (`GET /v1/me`) and `useMe()` now return `emails`, the verified addresses that sign the member in (each email-code address, then the verified email of each Google or Apple account enabled for sign-in), and `email`, the first of them for "signed in as". A member who signs in only with a wallet, X, Discord, Telegram or an app login has none: `email` is null and `emails` is empty. Participant routes never return another member's email. The classic starter shows the address in its account menu and Settings page.
258
+
259
+ ## 1.35.0
260
+
261
+ ### Minor Changes
262
+
263
+ - 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.
264
+
265
+ A key belongs to code or to data. Tracker status reports it as `origin` (`code` or `data`). A deployment that declares a data tracker's key is refused with 409 `tracker-owned-by-data`, and creating or updating a key a deployment publishes with 409 `tracker-owned-by-code`.
266
+
267
+ ## 1.34.0
268
+
269
+ ### Minor Changes
270
+
271
+ - 81d0037: Definitions change in test and reach live only through a promotion ([ADR 0017](https://github.com/GetDomino/relay/blob/main/docs/adr/0017-live-changes-only-through-promotion.md)). In live, every definition write fails with 422 `promotion-required`, whatever the credential, Console sessions included. These 1.x management routes refuse live:
272
+
273
+ - `POST /releases`, `/releases/batch`, `/releases/settings`, `/releases/lifecycle`, and `/quests/:quest/archive` and `/unarchive`
274
+ - `POST /catalog/deploy`, `/catalog/quests`, `/catalog/drafts` and `/catalog/publish`
275
+ - `POST /engagement/publish` and `/engagement/change`
276
+ - `POST /point-ledgers`, `/point-ledgers/update`, `/point-ledgers/:ledger/update`, `/archive` and `/unarchive`
277
+ - `POST /calendar` and `/profile-settings`
278
+ - `POST /badges`, `/badges/:badge/update`, `/archive` and `/unarchive`
279
+ - `POST /pickup-inventory` and `/leaderboards/:board/exclusion-group`
280
+ - `POST /stock-items`, `/stock-items/:item/rename`, `/archive` and `/unarchive`
281
+ - `POST /resources/lifecycle`, and `/archive` and `/unarchive` under `/trackers`, `/referral-programs`, `/leaderboards`, `/engagement/rules`, `/collections`, `/quest-types` and `/images`
282
+
283
+ API keys used to get 403 `console-approval-required` on most of these. Pickup policy, exclusion-group bindings and stock item creation, renames and archive state accepted API keys in live. Operations keep working in live under the caller's permissions: pauses, points, groups, moderation, reviews, entitlements, redemptions and stock adjustments. Code deployments still reach live through deployment review in Console.
284
+
285
+ This ships as a minor release as an accepted exception to the compatibility policy: no request or response shape changes, the refusal is one new error code, and no project had written a definition to live in production. The CLI reports the refusal as `ENVIRONMENT_UNSUPPORTED` with `apiCode: "promotion-required"`.
286
+
287
+ ## 1.33.0
288
+
289
+ ### Minor Changes
290
+
291
+ - 3706733: Add member imports: bring members, opening point balances and quest progress from another platform into a project and environment. `domino import --file members.jsonl --source NAME --dry-run` uploads a JSONL file, checks every row and prints what applying would create, link, set and refuse; without `--dry-run` it applies the import in test. `domino imports list|get|rows|apply|cancel` and `management.memberImports.create|addRows|check|apply|cancel|get|rows|list` reach the same `/management/v1/member-imports` routes. A live import is applied only from Console. Imported opening balances are `adjust` entries that name their import (`PointsEntry.import`, and `imported` in a member's own point history); they emit no `points.awarded` and count on balance leaderboards only. Imported progress pays no rewards. Accounts an import vouches for carry `imported: true` on `ManagedWallet`, `ManagedOAuthAccount` and `ManagedMemberIdentity`.
292
+
293
+ ## 1.32.0
294
+
295
+ ### Minor Changes
296
+
297
+ - d6ea67b: Service keys give a self-hosted app's backend a management credential that no person owns. An organization admin creates one in Console under **Settings → API keys** for one project and environment, with chosen permissions. It never expires unless an expiry is chosen, and it keeps every live fence a personal key has. `management.serviceKeys` lists, creates and revokes them from an admin's Console session. Rotation overlaps: `addSecret` issues a second secret while the first keeps working, and `revokeSecret` retires the old one. Each secret reports `lastUsedAt`.
298
+
299
+ Identity integration keys follow the same policy. New keys never expire unless `expiresInDays` is given, and existing keys keep their expiry until an admin removes it with `identityIntegrations.removeSecretExpiry`. `identityIntegrations.addSecret` and `revokeSecret` rotate a key without signing members out. Integrations now list their `secrets`. `expiresAt` is deprecated: it reports the latest secret expiry, or 8640000000000000 when a secret never expires. `identityIntegrations.rotate` is deprecated too. It now replaces every secret with one that never expires, instead of extending the key by 90 days.
300
+
301
+ ## 1.31.0
302
+
303
+ ### Minor Changes
304
+
305
+ - 07802d2: Reward deliveries, redemptions and deployments page by `cursor` and `nextCursor`, so long histories no longer stop silently.
306
+
307
+ - `management.rewardDeliveries({ member, cursor, limit })` and `relay.me.rewardDeliveries({ cursor, limit })` answer `nextCursor` beside `deliveries`. A page holds up to 500 deliveries, 500 by default, so calls without a cursor read what they did before. `management.rewardDeliveries(member)` still works.
308
+ - New `management.redemptions.history({ member, cursor, limit })` answers `{ items, nextCursor }`, 25 a page by default and at most 100, and replaces `management.offers.redemptions`, which answers every redemption at once. From the CLI, `domino redemptions history [--member] [--cursor] [--limit]` replaces `domino redemptions list`.
309
+ - New `management.deployments.history({ cursor, limit })` answers `{ items, published, nextCursor }`, 25 a page by default and at most 100. `published` holds the two newest published deployments by revision on every page: the live one and the one `rollback` returns to. It replaces `management.deployments.list`, which stops at the 100 newest.
310
+
311
+ The deprecated forms keep working through 1.x and are removed in 2.0.0.
312
+
313
+ ### Patch Changes
314
+
315
+ - caf698a: To prepare project slugs for hosts on `domino.page`, the server now refuses a slug change to a name Domino reserves (409 `Project slug is reserved`) or to a slug shorter than 3 characters (400), and a new project's derived slug gets a short suffix instead of taking either. Reserved names match exactly: `console` is reserved, `console-tools` is not. `projectSchema` still parses every existing slug; its `slug` field documents the rule, and the agent references and `domino capabilities` describe it.
316
+
317
+ ## 1.30.1
318
+
319
+ No changes in this release.
320
+
321
+ ## 1.30.0
322
+
323
+ ### Minor Changes
324
+
325
+ - cd6d78c: Add `management.x.lookup({ username })` and `domino x lookup HANDLE`, which resolve an X handle (with or without `@`) to the account that holds it, `{ id, username, name }` (read through a cache up to a day old), from `GET /management/v1/x/users/lookup`. Trackers, `--author` filters, custom `x` identities and `ctx.x.follows({ userId })` take the stable `id`, which no longer has to be copied from a post in Preview X search. The lookup needs `read` permission, fails with 404 `not-found` when no account holds the handle, and allows 30 lookups a minute per user and 300 an hour per organization. It resolves the handle only: it does not show who owns the account.
326
+
327
+ ## 1.29.0
328
+
329
+ ### Minor Changes
330
+
331
+ - 272a255: Add `management.members.progress(member)` and `domino members progress MEMBER`, which read a member's progress on each active quest, 25 most recent attempts and point balances from `GET /management/v1/members/:member/progress`. They leave out the quest releases and evaluator code that `members.get` returns beside them. In a project with many quests, member detail runs to megabytes; every list in this read is bounded by the project's configuration, so it suits scheduled jobs and other reads under a deadline. `members.get` is unchanged.
332
+
333
+ ## 1.28.0
334
+
335
+ ### Minor Changes
336
+
337
+ - 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`.
338
+
339
+ Add `management.discordRoles.preserve(delivery, { reason })`, the Discord role reward kind's preservation operation. A delivery of a reward kind without that operation is refused with 409.
340
+
341
+ Deprecated, still supported through 1.x: `members.oauthAccounts`, `unlinkOAuthAccount`, `telegramAccount`, `unlinkTelegramAccount`, `unlinkWallet`, `setWalletSignIn`, `unlinkIdentity`, `relinkIdentity` and `identityLinks` (use `members.identityStates`), `management.preserveRewardRole` (use `management.discordRoles.preserve`), and the CLI's `members identity-links`, `members relink` and `members oauth-accounts`.
342
+
343
+ ## 1.27.1
344
+
345
+ No changes in this release.
346
+
347
+ ## 1.27.0
348
+
349
+ ### Minor Changes
350
+
351
+ - 072493d: Leaderboard exclusions are a group (DOM-205):
352
+
353
+ - A board leaves out the members of its exclusion group. Each board gets its own group, keyed `domino--exclusions--<board ID>`, on its first exclusion. Existing exclusions and their history move into these groups, so standings and settlements leave out the same members at every cutoff.
354
+ - `management.leaderboards.exclusionGroup(board)` returns `{ leaderboard, group, generated, boundAt }`, and `management.leaderboards.bindExclusionGroup(board, { group, reason })` binds the board to another unarchived group, such as one several boards share, or back to its own. Settlements read the group bound at their cutoff.
355
+ - `management.leaderboards.exclude` and `exclusions` keep their inputs, responses, permissions and audit events. They now change and read the board's exclusion group, which `management.groups` can change too.
356
+ - Archiving a group that a leaderboard uses as its exclusion group fails with 409 `group-in-use`, naming the boards in `leaderboards`. Group keys starting with `domino--exclusions--` are reserved: creating one fails with 409 `already-exists`.
357
+ - CLI: `domino leaderboards exclusion-group BOARD` and `domino leaderboards bind-exclusion-group BOARD GROUP --reason …`.
358
+
359
+ ## 1.26.0
360
+
361
+ ### Minor Changes
362
+
363
+ - 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).
364
+
365
+ - Submission handles (`me.submission`) and quest controllers (`quests.open`) save each submission in Relay instead of the browser. A lost answer, a reload, another tab or another device signed in as the same member recovers it from Relay, and submitting again returns the same attempt. The public API is unchanged. Submissions no longer need IndexedDB or Web Locks, so `createRelayClient` without a `submissionStore` submits too. `SubmissionStore` and `browserSubmissionStore` are used only to finish a submission an older SDK version saved in the browser.
366
+ - A member has at most one unfinished attempt per quest. Another claim, or a retry of a failed attempt, is refused with `attempt-pending` until the previous one is decided, including while it waits for human review. The same action sent twice still replays.
367
+ - New participant routes: `GET`/`POST /v1/me/quests/:quest/submission`, `GET`/`POST /v1/me/submissions/:submission`, and `POST /v1/me/submissions/:submission/submit`, `/discard` and `/evidence` (a photo uploaded into the submission). A submit refused because the quest changed keeps the draft with `reviewRequired`, and the handle reports `needs-review`. `@domino-sdk/relay/schema` exports `questSubmissionSchema`, `questSubmissionUpdateSchema`, `questSubmissionResultSchema` and `QuestSubmissionStatus`. A draft expires 7 days after its last change. Refusal code `submission-closed` means the submission was already submitted, discarded or expired.
368
+ - The management client has `quests.submissions(member)` and `quests.discardSubmission(member, submission)`. Console shows a member's open quest submissions and lets reviewers and staff discard one.
369
+
370
+ ## 1.25.1
371
+
372
+ No changes in this release.
373
+
374
+ ## 1.25.0
375
+
376
+ ### Minor Changes
377
+
378
+ - 55d08ae: Client SDK paper cuts (DOM-160):
379
+
380
+ - `createRelayClient` takes `getToken: async () => token | null`, read before every attempt, as the management client does. A failing `getToken` rejects with code `credential` before anything is sent. `token` and `getToken` together are refused.
381
+ - `createRelayClient` and `createManagementClient` take `fetch`, which replaces the global `fetch` for every call, and `headers`, added to every call. The client's own headers win over one of the same name; an invalid header is refused with `invalid-input` when the client is created.
382
+ - Submission handles (`me.submission`) and quest controllers (`quests.open`) take `{ signal?, timeout? }` on `submit`, `resume`, `status` and `discard`. Each request the call sends (session read, upload, claim, retry, and a controller's read of the latest quest after `quest-updated`) gets the signal and what is left of one deadline for the whole call. A cancelled or timed-out call keeps its saved progress for `resume()`. The quiz's `submit(answers, { release?, signal?, timeout? })` makes `release` optional.
383
+ - `auth.start({ provider, mode })` and `auth.emailVerify({ challenge, code })` take an options object, which refuses unknown fields. The positional forms still work and are deprecated, to be removed in 2.0.0.
384
+ - `paginate(read)` from `@domino-sdk/relay` iterates every item of a paged read with `for await`, following `nextCursor`.
385
+ - `RelayErrorCode` lists `OperationRetired` (`operation-retired`) and `Credential` (`credential`).
386
+ - `offers.redeem`, `profiles.badges`, `me.activity.list`, `me.upload`, `auth.telegram.miniApp.connect` and `auth.emailStart` reject invalid input with a `RelayError` of code `invalid-input`, instead of throwing a raw `ZodError` (`offers.redeem`) or throwing synchronously.
387
+ - The browser bundle no longer includes every Zod locale: `createBrowserRelayClient` drops from about 412 KB to 198 KB minified (90 KB to 56 KB gzipped).
388
+
389
+ ## 1.24.1
390
+
391
+ No changes in this release.
392
+
393
+ ## 1.24.0
394
+
395
+ ### Minor Changes
396
+
397
+ - f6e030a: Webhook endpoint creation and secret rotation can be recovered after a lost answer. `management.webhooks.create` takes an optional `actionId` (new `webhookCreateInputSchema`), and `management.webhooks.rotateSecret(id, { actionId })` takes one too (new `webhookRotateInputSchema`). The client generates an ID when you omit it. A retry with the same `actionId` returns the same endpoint and signing secret instead of creating a second endpoint, or being refused during the 24-hour overlap. Once the secret is rotated again, the earlier action's retry is refused with `secret-superseded`. `domino webhooks create` and `domino webhooks rotate-secret` are now saved operations with `--operation-id`; resume one with `domino operations resume <id>`.
398
+
399
+ Relay now refuses webhook subscriptions to event types it never sends, such as `completion.create`, with `unknown-activity-type`; the message lists the valid types. This narrows what the API accepts, but the CLI and SDK already report it as a refusal. An endpoint update can keep a type the endpoint subscribed to before. Event payload `type` stays an open string, so receivers must still accept or ignore types they do not know.
400
+
401
+ ## 1.23.2
402
+
403
+ No changes in this release.
404
+
405
+ ## 1.23.1
406
+
407
+ ### Patch Changes
408
+
409
+ - fd5f211: Members now belong to one project. A person who signs in to two projects of the same organization is a separate member in each, with their own identities, linked X, Discord and Telegram accounts, and wallets; unlinking, relinking and wallet sign-in control act within the project. Project transfer now moves a project with its participants instead of refusing it. No API shape changes: `MemberIdentityLink.scope` keeps the value `"organization-environment"` for OAuth links, which now names a link in the member's project and environment.
410
+
411
+ ## 1.23.0
412
+
413
+ ### Minor Changes
414
+
415
+ - 88c7c00: Add Telegram chats, membership checks and reaction trackers. Operators connect Telegram groups and channels to a project through Domino's bot in Console (**Integrations → Telegram → Add chat**), or with `management.telegram.connect()`, `chats()` and `remove(chat)`. See [Telegram](https://console.domino.run/docs/build/telegram).
416
+
417
+ - `ctx.telegram.member({ chat })` reads whether the member's linked Telegram account is in a connected chat: `{ userId, username, status, isMember, isAdmin, untilDate }`, or null. It needs no `integrations` entry. `ctx.telegram` is optional in `EvaluationContext`, so code that builds contexts keeps compiling. `settings.telegramChat()` adds a setting that Console fills from the connected chats. The testing kit takes `facts.telegram.member`.
418
+ - `telegram.reactions()` is a tracker source of the reactions members add in connected groups. Each record carries Telegram accounts for the reactor (`author`) and the message's author (`messageAuthor`). `data` is `TelegramReactionData`, which includes `reactorStatus` and `reactorIsAdmin` for a trigger's `where`. In SDK 1.x it is a custom source on the reserved namespace `telegram-reactions`, which Relay alone writes.
419
+ - Text settings can carry `picker`, which Console uses to offer a list.
420
+ - The `telegram-membership` snippet is a join quest.
421
+
422
+ ## 1.22.0
423
+
424
+ ### Minor Changes
425
+
426
+ - 81beec3: Personal API keys and `domino login` CLI sessions can now use test members, including giving them wallet addresses, not only Console sessions: the caller needs `publish` permission in the test environment, and hosted app keys stay refused. Add `domino test-members create|list|history|act|advance|link-wallet|unlink-wallet|reset`, which always address the test environment and save their writes as resumable operations, so a coding agent can simulate a campaign end to end.
427
+
428
+ ## 1.21.0
429
+
430
+ ### Minor Changes
431
+
432
+ - 5db81c1: Test members can hold wallet addresses. `management.testMembers.linkWallet(member, { actionId, chain, address, network? })` gives a test member a Solana, EVM or Cosmos address without a signature, and `unlinkWallet(member, { actionId, chain, address })` removes and frees one. Quest code reads these addresses through `ctx.wallet` like a member's linked wallets, so balance-gated quests can be tried in the test environment. When the wallet's owner signs in with it or connects it, the address moves to them. Test member results include an optional `wallets` list. The CLI's capability map and Domino skill reference describe the new methods.
433
+
434
+ ## 1.20.0
435
+
436
+ ### Minor Changes
437
+
438
+ - 2e1eff4: Add Telegram sign-in. Telegram Login signs participants in on the web where Domino configures it, and Mini App sign-in signs them in inside a project's own Telegram Mini App once the project names its bot ID in Console (**Settings → Sign-in**). Both reach one Telegram account. See [Telegram](https://console.domino.run/docs/build/telegram).
439
+
440
+ Telegram is not an `AuthProvider` in SDK 1.x, so code that switches over providers keeps working: `AuthProvider`, `relay.auth.accounts()`, `useAccounts()` and `useSignInOptions().options` never include it. Telegram has its own additions beside them:
441
+
442
+ - `relay.auth.providers()` returns `telegram: { login, miniApp }`.
443
+ - `relay.auth.telegram` has `start(mode)`, `account()`, `disconnect()`, and `miniApp.signIn(initData)` and `miniApp.connect(initData)`. `telegramMiniAppInitData()` reads the Mini App's launch data.
444
+ - `useSignInOptions()` and `signInOptions()` return the Telegram Login option as `telegram`, beside `options`. `useSignIn()` adds `withTelegram()` and `withTelegramMiniApp(initData)`, and `useTelegramAccount()` connects and disconnects the member's Telegram account.
445
+ - In the management client, `participantSignIn()` returns `telegram: { login, miniAppBotId }`, and `configureParticipantSignIn()` takes `telegram: { miniAppBotId }`, which keeps the current bot when left out. `members.telegramAccount(member)` and `members.unlinkTelegramAccount(member, { subject, revision, reason })` read and unlink a member's Telegram account.
446
+
447
+ ## 1.19.0
448
+
449
+ ### Minor Changes
450
+
451
+ - 37a57df: Deploying a quest that needs an unconnected integration, such as Discord, no longer fails. The deployment preview and result include `warnings` (`deploymentWarningSchema`) naming the quests with code `integration-required`, and `domino deploy` prints them. Their attempts still fail with `integration-required` until the integration is connected.
452
+
453
+ ## 1.18.1
454
+
455
+ ### Patch Changes
456
+
457
+ - 0a6ebf1: Allow management deployment responses to include additional fields in direct, promotion and rollback sources. This closes the recorded response compatibility gap without changing known fields or source kinds.
458
+
459
+ ## 1.18.0
460
+
461
+ ### Minor Changes
462
+
463
+ - 7000d41: Add project badge definitions, quest badge rewards, audited manual awards and revokes, participant and profile reads, and React hooks. Author badges with `defineBadge`, include them in `relay.json`, and attach them with `badge(...)`. SDK 1.x retains the existing reward envelope with optional badge metadata. The CLI adds `domino badges` catalog and member commands.
464
+
465
+ ## 1.17.0
466
+
467
+ ### Minor Changes
468
+
469
+ - 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.
470
+
471
+ ## 1.16.0
472
+
473
+ ### Minor Changes
474
+
475
+ - b9d767f: Run background jobs on active hosted Worker builds in test. Declare `app.schedules` in relay.json with an interval or UTC cron, verify signed route calls with `verifyScheduledRequest(request, env.DOMINO_SCHEDULE_KEY)`, and inspect or control jobs with `management.schedules` or `domino schedules`.
476
+
477
+ Jobs have a five-minute minimum cadence, ten schedules per app and a 30-second timeout. Relay makes one attempt per tick; uncertain completion blocks future runs until an operator confirms recovery. See [Run scheduled jobs](https://console.domino.run/docs/build/schedules) for custom tracker ingestion and recovery.
478
+
479
+ ## 1.15.1
480
+
481
+ No changes in this release.
482
+
483
+ ## 1.15.0
484
+
485
+ No changes in this release.
486
+
487
+ ## 1.14.0
488
+
489
+ ### Minor Changes
490
+
491
+ - 0c394de: Replay captured test-environment attempts locally. `management.attempts.capture(attempt)` (`publish` permission) returns what one evaluation run of an attempt read: the release's settings and rewards, the attempt's input, frozen clock, seed and calendar, each captured capability answer, and what quest code decided. `replayAttempt(quest, capture)` from `@domino-sdk/relay/testing` evaluates current quest code against it and reports the decision beside the recorded one, whether it changed (kind, reason, rewards or completion key), and the first call the capture could not answer. `domino quests replay <attempt>` fetches, saves (`--out`) and replays a capture. Captures are served from the test environment only: live attempts are refused with 422 `test-environment-required`, and an attempt that has not been evaluated with 409 `attempt-not-evaluated`.
492
+ - 580a641: Add release history to the management client. `management.releases.list({ quest?, limit?, cursor? })` (`GET /management/v1/releases/list`) pages every release the project's quests have had, newest first, optionally for one quest by ID or key, and `management.releases.get(release)` (`GET /management/v1/releases/:release`) reads one current or historical release. Both need `read` and `publish` permission. They replace the deprecated `page("releases")` and its `detail`.
493
+
494
+ ### Patch Changes
495
+
496
+ - cb252b6: The CLI now sends its management requests through the management client's own transport, so `domino` and `createManagementClient` share one implementation of deadlines, retries and failure classification. CLI exit codes, error codes and results stay the same; some error messages change wording, and network errors no longer carry `details.cause`. A management client write whose connection was refused, never resolved, could not reach the network or host, timed out while connecting, or failed TLS now fails with `uncertain: false`, because nothing was sent, and a 204 answer reads as `null`.
497
+ - 3ccb72a: `Person` records (`members.people()`, and the `people` of attempt, entitlement and page reads) now carry `test` and `name` for Console test members in the test environment, the same fields member reads already had. Console names test members by that name and marks them as test members instead of showing their member ID.
498
+
499
+ ## 1.13.0
500
+
501
+ ### Minor Changes
502
+
503
+ - 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.
504
+
505
+ `quests.observe`, `me.observeProgress` and `me.activity.observe` make one request per poll: they send the session with each read, and read it again only when Relay refuses the session as changed. A shown tab resumes polling on schedule instead of reading at once. `me.activity.list` and `me.activity.observe` accept `start: "latest"` to follow only new events.
506
+
507
+ New React query options: `enabled` and `null` keys for dependent queries, `staleMs`, `select`, `suspense`, and `initialData` for public reads (rendered on the server and while hydrating). `useSetQueryData()` changes a cached value at once, for optimistic updates.
508
+
509
+ Mutation hooks share one shape: `mutate`, `mutateAsync`, `status`, `isPending`, `data`, `error`, `reset` and `settle`, built on `useRelayAction`'s retained keys. `useRelayMutation(mutation, { invalidates })` gives your own writes that shape, and `useRedeemOffer` and `useSignOut` now have it. `useRelayAction` adds `status`, `isPending`, `data` and `reset`, and its failed result carries `error`. `useSignIn` and `useSignInOptions` add `isPending` and `reset`; `useAccounts`, `useWallets` and `useMyProfile` add `isPending` and `resetAction`; `useQuest` adds `isPending`.
510
+
511
+ Deprecated, still working: `busy` on every hook that now has `isPending`; `useRedeemOffer`'s `redeem` and `retry` (use `mutate`, which replays a retained redemption); `useSignOut`'s `signOut` (use `mutateAsync`, which rejects the same way, or `mutate`).
512
+
513
+ ## 1.12.0
514
+
515
+ ### Minor Changes
516
+
517
+ - 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`.
518
+
519
+ ## 1.11.2
520
+
521
+ ### Patch Changes
522
+
523
+ - 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.
524
+
525
+ ## 1.11.1
526
+
527
+ ### Patch Changes
528
+
529
+ - 90e5141: Build tolerant response readers on first use so SDK imports and backend startup retain less heap. Preserve concrete Zod schema APIs, shared readers, and unsupported-variant response envelopes.
530
+
531
+ ## 1.11.0
532
+
533
+ ### Minor Changes
534
+
535
+ - 76e7a45: Add operator quest withdrawal, inventory rename/archive/unarchive/stock correction, webhook deletion with retained history, settlement rejection, and kind-keyed wallet/OAuth relink. Existing 1.x methods and response shapes remain supported. Inventory archive refuses retained offer and pickup references with named blockers; relink applies the current quest's historical policy while preserving admitted attempts.
536
+
537
+ ## 1.10.1
538
+
539
+ ### Patch Changes
540
+
541
+ - fa55eff: Share converted response schema nodes to avoid excessive startup heap allocation in SDK 1.10.0. Tolerant response parsing and the SDK API are unchanged.
542
+
543
+ ## 1.10.0
544
+
545
+ ### Minor Changes
546
+
547
+ - f2724f9: Read added response fields at every depth without relaxing request validation. Add explicit supported/unsupported response schemas and `client.response(...)` for extensible results. Unsupported entities retain their raw data; entity lists retain positions and report an unsupported count. Existing 1.x method return types stay compatible.
548
+
549
+ SDK and CLI calls identify their package, version and runtime with `X-Domino-Client`. Tolerant SDK calls also identify their per-call response capability with `X-Domino-Response: tolerant-v1`. Older and legacy callers still require compatible server wire shapes. See the [participant response reference](https://console.domino.run/docs/reference/participant-api#read-evolving-responses) for the opt-in API.
550
+
551
+ ## 1.9.0
552
+
553
+ ### Minor Changes
554
+
555
+ - 4ae54f9: Add `relay.action()`, `createRelayAction()`, and `useRelayAction()` to retain a mutation key and its original callback across uncertain failures, with explicit replay-safe retries and acknowledgment of confirmed reconciliation and a synchronous guard against duplicate clicks. `useRedeemOffer` now reuses the original redemption key and exposes `retry`.
556
+
557
+ All previously required participant and management mutation action IDs are optional; omitted IDs are generated once per call. Only reusing a key protects separate calls from double application. Evidence upload now generates an action ID; avatar upload accepts `actionId` alongside the existing `uploadId` alias and still requires reconciliation after uncertainty.
558
+
559
+ See [retry after an uncertain answer](https://console.domino.run/docs/reference/participant-api#retry-after-an-uncertain-answer) for helper lifetime, retry behavior, and reload recovery. Existing explicit keys and upload aliases continue to work.
560
+
561
+ ## 1.8.0
562
+
563
+ ### Minor Changes
564
+
565
+ - 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.
566
+
567
+ ## 1.7.0
568
+
569
+ ### Minor Changes
570
+
571
+ - 5662b50: Expose protocol validation helpers through `@domino-sdk/relay/server` for server adapters: identity integration inputs and exchange, participant routes, quest configuration and shape validation, sequence cursors, image extraction, tracker data validation, and referral reward checks. Existing exports and wire formats are unchanged.
572
+
573
+ ## 1.6.0
574
+
575
+ ### Minor Changes
576
+
577
+ - 4651887: Add an optional `window` detail to `outside-window` availability, errors, and automatic outcomes so clients can distinguish tracker and quest windows without branching on reason text.
578
+ - 547600e: Every refusal from the participant and management APIs now carries a machine-readable `code` beside its unchanged message, so clients can branch without matching text. `RelayError.code` and `ManagementError.code` (and the CLI's `apiCode`) are set for every API answer: a specific code when the client can act on it, such as `catalog-changed`, `draft-changed`, `prerequisites-incomplete`, `permission-required` or `not-configured`, or the status's own code otherwise (`not-found`, `conflict`, `forbidden`, …). Structured detail, such as the `missing` prerequisites, comes with the code on every route. The [API error codes](https://console.domino.run/docs/reference/errors) reference lists them.
579
+
580
+ - `RelayErrorCode` adds `PrerequisitesIncomplete`, `QuestClosed`, `QuestPaused`, `ProfileLocked`, `InvitationNotFound`, `InvalidCode`, `CodeExpired`, `ChallengeExpired` and `RateLimited`.
581
+ - New `managementErrorSchema` (`@domino-sdk/relay/server`) parses the management API's error body, for raw `api.raw()` answers.
582
+
583
+ Messages keep their text, and `code` stays an open string, so code written against 1.2 keeps working.
584
+
585
+ The participant proxy now codes its own routing, configuration and authentication refusals. During application identity exchange it also keeps Relay's safe upstream code and `Retry-After` while continuing to replace non-account-conflict messages with its existing generic text.
586
+
587
+ - 6c80795: Groups are their own resource. A group is a named set of members that `inGroup` prerequisites, a leaderboard's `group` and a referral program's `inviterGroup` read by key; before, groups existed only as leaderboard group memberships. Existing memberships and their history move over unchanged.
588
+
589
+ - `management.groups` lists, gets, creates, renames, archives and unarchives groups, and lists, adds and removes members with a reason for the audit history. Lists take `{ cursor, limit }` and return `{ items, nextCursor }`. The schemas and `ManagedGroup`, `GroupPage`, `GroupMember` and `GroupMemberPage` types are exported from `@domino-sdk/relay/schema` and `@domino-sdk/relay/server`.
590
+ - `domino groups list|show|create|update|archive|unarchive|members|add|remove` does the same from the CLI.
591
+ - Groups are archived, never deleted. An archived group's members still count, but cannot change until it is unarchived.
592
+ - `management.leaderboards.group(group, { member, included, reason })` and `leaderboardGroupSchema` are deprecated and removed in 2.0.0. They keep working, and still create a group the first time they add a member to it. The group name must now be a group key (letters, digits, `_` and `-`, at most 100 characters), as definitions already required; a longer name answers 400. Replace them with `management.groups.create({ key, title })` once, then `management.groups.addMember(group, { member, reason })` and `management.groups.removeMember(group, member, { reason })`.
593
+
594
+ `defineGroup` and `inGroup` are unchanged. See [groups](https://console.domino.run/docs/concepts/groups).
595
+
596
+ - 6088922: Every paged management read now takes `cursor` and answers `nextCursor`, as participant reads already did: pass a page's `nextCursor` back as `cursor` until it is `null`.
597
+
598
+ - `management.points.history(member, ledger, { cursor })` and `management.referrals.participants({ cursor })` take an options object; their answers add `nextCursor`. Passing the numeric `before` (a page's `next`) still works and is deprecated.
599
+ - `management.webhooks.deliveries(id, { cursor, limit })` and `domino webhooks deliveries <endpoint> --cursor <cursor>` page by the answer's `nextCursor`. `before`, `--before` and `next` are deprecated.
600
+ - `management.storage.rows(name, table, { cursor })` and `domino storage rows NAME TABLE --cursor C` page by `nextCursor`; `offset` and `--offset` are deprecated. `management.storage.keys` answers `nextCursor`; its `cursor` field is deprecated.
601
+ - New `management.trackers.triggerOutcomes(tracker, { ...filters, cursor })` answers `{ items, nextCursor }` and replaces `management.trackers.outcomes`, whose `before` and `next` stay as they were.
602
+ - New `management.engagement.ruleVersions(rule, { cursor })` answers `{ items, nextCursor }` and replaces `management.engagement.versions({ rule, before })`.
603
+ - `resourceOptionsSchema` (`@domino-sdk/relay/schema`) adds `nextCursor`; its `cursor` field is deprecated.
604
+
605
+ The deprecated forms keep working through 1.x and are removed in 2.0.0. Responses gain only fields, so code written against 1.2 keeps compiling.
606
+
607
+ - 6fa9cd5: New `management.members.list({ q, needsReview, limit, cursor })` (`GET /management/v1/members/list`) and `domino members list [--q TEXT] [--needs-review] [--limit N] [--cursor C]` list a project's members, most recently joined first, with their accounts and attempt, completion and reward counts. The answer is `{ items, total, nextCursor }`: pass `nextCursor` back as `cursor` until it is `null`. `q` finds members by part of their ID, profile name, or an account's subject, name or email, as Console's member search does. Use it instead of `page("members", { q })`.
608
+
609
+ `memberListSchema`, `memberPageSchema` and `MemberPage` (`@domino-sdk/relay/schema`), which no route used, are deprecated in favour of `managedMemberQuerySchema`, `managedMemberPageSchema` and `ManagedMemberPage` (`@domino-sdk/relay/server`), and are removed in 2.0.0.
610
+
611
+ - e3a050e: Management commands on an existing resource now name it in the path, and the client methods take it as their first argument:
612
+
613
+ - `management.deployments.publish(id)`, `submit(id)`, `reject(id, { reason })`, `promote(id, { actionId? })` and `rollback(id, { actionId? })` call `/deployments/:deployment/<verb>`. The `{ id, ... }` forms still work and are deprecated. `domino deploy` uses the new routes.
614
+ - `management.pointLedgers.update(ledger, { name, description })` calls `/point-ledgers/:ledger/update` and replaces the deprecated `pointLedgers.save({ id, name, description })`.
615
+ - `management.referrals.suspend(member, { suspended, reason })`, `qualify(program, member, { qualified, reason })` and `resolveReward(entitlement, { reason })` replace the deprecated `referrals.control({ kind, ... })`. Each generates its `actionId` when omitted.
616
+
617
+ Duplicate and misleading names have clear replacements; the old names stay as deprecated aliases:
618
+
619
+ - `management.points.record(input)` awards, spends, adjusts, reverses or sets a balance, replacing `points.update`.
620
+ - `management.pointLedgers.list()` and `pointLedgers.create(input)` replace `points.ledgers()` and `points.createLedger(input)`.
621
+
622
+ The `{ id, ... }` input schemas (`publishDeploymentSchema`, `submitDeploymentSchema`, `rejectDeploymentSchema`, `promoteDeploymentSchema`, `rollbackDeploymentSchema` and `referralControlSchema`) are deprecated beside them; `deploymentRejectionSchema`, `deploymentActionSchema`, `referralSuspensionSchema`, `referralQualificationSchema` and `referralRewardResolutionSchema` describe the new bodies.
623
+
624
+ The routes that took these IDs in the body keep answering released clients. Deprecated forms are removed in 2.0.0.
625
+
626
+ - b145e16: The management client reads a project's state as resources, which Console now composes its pages from:
627
+
628
+ - `management.metrics.read()`: the project's counts and each quest's completions (`completionsByQuest`).
629
+ - `management.quests.list()`: each quest's current release. `management.operations.list()`: paused and resumed operations. `management.inventory.list()`: stock items with their counts and the offers that sell them.
630
+ - `management.attempts.list({ q, status, limit, cursor })` and `management.attempts.get(attempt)`; `management.entitlements.list({ q, status, limit, cursor })` and `management.entitlements.get(entitlement)`.
631
+ - `management.audit.list({ category, q, limit, cursor })` and, for organization administrators, `management.audit.access({ q, limit, cursor })`.
632
+
633
+ The paged attempt, entitlement and audit reads answer `{ items, total, nextCursor }`; attempt pages also carry the releases and members (`people`) they name, and entitlement pages carry their members. Pass `nextCursor` back as `cursor` until it is `null`.
634
+
635
+ `management.page()`, `pageSchema`, `ManagementPage`, `querySchema` and `ListQuery`, Console's former page-shaped reads, are deprecated: compose the reads above instead. The page routes keep answering, unchanged, through 1.x; they are removed in 2.0.0.
636
+
637
+ - b3d87d0: `points.awarded` webhook events now carry `source`, `{ kind, id }`, naming what paid the award: `quest` or `leaderboard` with the quest or board ID, `engagement` with the engagement rule's key, or `manual` with `id: null` for an operator's point award. `kind` is an open string that can gain values; `PayoutSourceKind` from `@domino-sdk/relay/server` names the known ones. A completion revocation result (`CompletionRevocation`, from `management.completions.revoke`) gains the same optional `source` as completion records, so a revoked leaderboard prize can be told apart from a quest completion; `quest` keeps carrying the board ID for a prize. See the [webhook reference](https://console.domino.run/docs/reference/webhooks).
638
+ - f322ced: `ctx.review.photo()` is deprecated and will be removed in 2.0.0. It still works, and nothing about how it reviews photos changes. In new quest code, review an uploaded photo with `ctx.images.review({ images: [{ evidence: ctx.input.photo }], criteria })` instead. It returns the same `pass`, `fail` or `unclear` result, and it can also review X images, reference images and up to four images at once. See [check images](https://console.domino.run/docs/build/images#review-images-with-ai).
639
+
640
+ The agent references and capability map now list tracker-restricted ingestion keys (`ingestTrackers` on `management.tokens.create` and `management.tokens.scope`) as supported.
641
+
642
+ - 5ddcb62: Approving a leaderboard settlement now sends a `completion.created` webhook event for each winner's completion. Before, only quest attempts sent it. Every `completion.created` event now carries `source` (`{ kind, id }`, where `kind` is `quest` or `leaderboard`) and `key`, the occurrence the completion consumed. A prize event has `attempt: null`, the board ID in `quest` and `source.id`, and the settlement ID in `key`. If your receiver treats every `completion.created` as a quest completion, branch on `source.kind`. Completion records in management snapshots gain the same optional `source`, and `CompletionSourceKind` is exported from `@domino-sdk/relay/server` and `@domino-sdk/relay/schema`. See the [webhook reference](https://console.domino.run/docs/reference/webhooks).
643
+
644
+ ### Patch Changes
645
+
646
+ - 75fa01c: Prerequisite-only automatic quests now try an uncompleted occurrence again after a claim-time budget rejection when a later member event passes current eligibility and budget checks. Evaluator, staff and revoked-occurrence rejections stay final. Automatic outcome reads can include optional `linkedAttempt` details; the legacy `attempt` ID remains available in 1.x but is deprecated. Operators can preview and confirm budget recovery through the existing automatic catch-up API and Console flow.
647
+ - 3831c73: `proxyParticipant` and `createParticipantBackend` (`@domino-sdk/relay/portal-proxy`) now forward each participant route only with the method Relay registers for it, from the same route list Relay's API admits hosted apps by. Before, some routes (sign-in and session, `/v1/me/progress`, `/v1/me/experience`, `/v1/me/attempts`, `/v1/me/evidence`, `/v1/me/collection-pass`, the wallet routes and quest retries) were forwarded with either GET or POST, and Relay answered the wrong one with 405. The proxy now answers it with 404 "Route not found". No client method changes behavior.
648
+
649
+ `relay.evidence.download` is deprecated and will be removed in 2.0.0. No participant gate admits `GET /v1/evidence/:id`, so it has always failed with 404; members do not read evidence back, and reviewers download it through the management API.
650
+
651
+ - f493c48: Referral corrections and operator reversals now consume one shared remainder for each positive payout, so either order settles the payout once. Historical corrections use the referral account's settled amount as a conservative cap. New referral definitions refuse Discord role rewards with a clear error. Stored legacy programs remain readable and recoverable, and skip an unsupported role with an Audit history reason instead of failing qualification. See [referral corrections](https://console.domino.run/docs/build/referrals#how-corrections-work).
652
+ - 6e9b120: Completion revocation now keeps any points it could not take back visible after the command returns. The result and audit event report the remainder for each point ledger. Console shows the remainder on the member and attempt, and operators can open the existing point adjustment to settle it explicitly. Relay never records member debt for the remainder.
653
+ - 0ec4d7d: `RelayClient` and `ManagementClient` are now named interfaces that `createRelayClient`, `createBrowserRelayClient`, `createManagementClient` and `hostedManagementClient` return, so declaration files name them instead of repeating the whole client. `ManagementClient.testMembers` is no longer optional: every client these factories create has it. The clients and the error classes (`RequestError`, `RelayError`, `QuestUpdatedError`, `ManagementError`) are not for implementation or subclassing; minor releases add methods and fields to them. A `@compat` tag in a type's doc comment now says how it may grow in a minor release ([Upgrade to Relay SDK v1](https://console.domino.run/docs/reference/upgrade-to-v1#supported-consumers-and-compatibility)).
654
+ - f6b705a: Revoking a completion now reads the member's recorded tier grants instead of every completion event in the project, so it stays fast as a project grows. `409 completion-grant-history-missing` remains only for older completions whose original record could not be read; contact Domino support to restore their history. See [Revoke a completion](https://console.domino.run/docs/concepts/quests#revoke-a-completion).
655
+ - 65a9765: Deprecated three schema members that Relay no longer uses. They stay in 1.x and are removed in 2.0.0:
656
+
657
+ - `faultAfterReview` on `submitSchema` (`SubmitInput`). Relay refuses a submission that sets it to `true`, so stop sending it.
658
+ - The `fixture-*` values of `storedReviewModeSchema`. They name the retired simulated review providers, and only releases and quest types published before the retirement carry them. Publication already accepts only `workers-ai`.
659
+ - `reservation` on `Redemption`. A redemption is fulfilled in the request that makes it, so the ID names no separate hold. Use `id` to refer to a redemption.
660
+
661
+ Offer redemptions no longer briefly hold the price before spending it, and their response is unchanged. The photo, quiz and staff-confirmation snippet guides no longer suggest `fixture-*` providers, which Relay refuses at publication.
662
+
663
+ ## 1.5.2
664
+
665
+ No changes in this release.
666
+
667
+ ## 1.5.1
668
+
669
+ No changes in this release.
670
+
671
+ ## 1.5.0
672
+
673
+ ### Minor Changes
674
+
675
+ - 625dcbc: Expose project creator IDs and resolved operator details in project access and audit responses. Validate enriched profile IDs against the recorded creator or actor while accepting older unenriched responses. Add `updateProject` for Console organization admins to rename projects or change their slug with name confirmation. Hosted build links follow the current slug without a rebuild, including older builds that used repository-based addresses. Links switch only after the new hostname is confirmed.
676
+
677
+ Slug changes preserve app secrets and hosted stores. New staging addresses are registered before the change commits; old and prepared slugs remain reserved until project purge, so interrupted changes can be retried safely. Reserved slugs cannot be claimed by older servers during a rolling upgrade.
678
+
679
+ ## 1.4.0
680
+
681
+ ### Minor Changes
682
+
683
+ - da46e7b: Add `management.testMembers.advance(member, { actionId, days }, call?)` and `TestMember.offsetDays` for Console sessions in test. Move one member forward 1–30 project-calendar days per write, up to 90 total, to try calendar rules through the ledger. See [test-member clocks](https://console.domino.run/docs/use/quests#3-advance-the-members-clock) for record timestamps, replay, and real-time workflow scheduling.
684
+
685
+ The exported `offsetDays` field and broader `ManagementClient.testMembers.advance` method are optional for compatibility with existing clients. Factory clients always provide `advance`. Shared records retain real timestamps, beneficiary attempts pin their projected activity, and history defaults to the member’s last fourteen days through member now.
686
+
687
+ Test members also expose optional `today?: string` (an ISO calendar date). Each advance captures a fixed duration shift, preserving timestamp order and freshness across DST. Evaluator tracker reads use that frozen shift; observation identity keeps raw timestamps.
688
+
689
+ ## 1.3.0
690
+
691
+ ### Minor Changes
692
+
693
+ - 86114a5: Add `management.testMembers.history(member, { since }?)` for Console sessions in the test environment. Read up to 500 attempts with ledger-credited activity times and 500 skipped tracker outcomes, with a completeness flag. The default window covers the last 14 days.
694
+
695
+ ## 1.2.0
696
+
697
+ ### Minor Changes
698
+
699
+ - 7f69653: Add test-environment KV and D1 storage for Domino-hosted Worker apps through `relay.json` `app.storage`. Stores receive native Worker bindings and retain data when declarations are removed. SQL migration files run before staging activation, with Wrangler-compatible tracking and per-file rollback and safe retries after interrupted responses.
700
+
701
+ Inspect declarations and retained stores with `domino storage list`. Connected development and staging warn when local Wrangler bindings differ from hosted declarations. See [hosted storage and migration recovery](https://console.domino.run/docs/build/storage).
702
+
703
+ - cdea4a0: Browse hosted app storage read-only and delete stores no serving build declares. `domino storage tables`, `rows`, `keys` and `get`, and `management.storage` in the SDK, read SQL tables and KV values with `publish`; `domino storage delete` removes an undeclared store and its data. See [browse and delete stores](https://console.domino.run/docs/build/storage#browse-your-data).
704
+
705
+ ## 1.1.1
706
+
707
+ ### Patch Changes
708
+
709
+ - 1d2a0cc: Recover saved submissions after the server permanently retires an operation. Preserve the terminal state across reloads, expose it for participant review, and allow explicit discard without silently submitting another claim. Starters show a separate restart action; unknown conflicts and lost responses still resume the original action. See the [participant recovery reference](https://console.domino.run/docs/reference/participant-api).
710
+
711
+ ## 1.1.0
712
+
713
+ ### Minor Changes
714
+
715
+ - ead35b5: Add Console-session-only management methods to create, list, act as, and reset test members in the test environment. Member reads expose the test marker and display name. Document the CLI exclusion and reset restrictions.
716
+
717
+ ## 1.0.0
718
+
719
+ ### Major Changes
720
+
721
+ - ad91102: Record prerequisite-only automatic decisions, expose participant and operator outcomes, and add previewed catch-up for paused events with review permission and audited reasons. Point-entry changes schedule current eligibility asynchronously after commit, including refunds and referral or leaderboard awards. Operators can inspect and retry failed scheduling.
722
+
723
+ Remove the permanent silent pause block. Outcome pagination now merges prerequisite and tracker decisions; reset saved cursors and use the `automatic` identity on rows without a tracker record. See the [v1 upgrade guide](https://domino.run/docs/reference/upgrade-to-v1#prerequisite-automatic-outcomes).
724
+
725
+ - 4b10080: Add the protocol-2 `ctx.calendar.day(at?)` evaluator helper for replay-safe project-calendar keys and day bounds. Protocol-1 bundles remain executable. See the [v1 evaluator protocol upgrade guide](https://domino.run/docs/reference/upgrade-to-v1#evaluator-protocol).
726
+ - 4205914: Add code-authored campaigns with half-open availability windows and durable campaign-wide enrollment; typed completion, live balance and project-group prerequisites; typed availability blockers; and live quest-budget capacity on participant views. Participant and management clients can enroll a member in a required campaign, and Console shows campaign rules and member enrollment.
727
+
728
+ `completed(quest)` now returns a discriminated completion prerequisite instead of `{ quest }`, and `prerequisiteSchema` accepts only typed prerequisite definitions. Stored legacy releases remain readable. See [Prerequisites are typed eligibility rules](https://console.domino.run/docs/reference/upgrade-to-v1#prerequisites-are-typed-eligibility-rules) in the v1 upgrade guide.
729
+
730
+ - 6d66f96: Add reversible campaign enrollment. Participants can withdraw and re-enroll themselves; trusted servers and Console operators can withdraw a member with an audited reason and restore them. Withdrawal blocks new admissions while preserving the first enrollment time, admitted attempts, completions, and rewards.
731
+ - a803b30: Evaluators now replay deterministically. Hosted evaluation freezes the clock for each attempt and seeds randomness from the attempt, so a retried or replayed attempt makes the same decision and `keyed()` completion key. This applies to every evaluation Domino runs, including releases published with earlier SDK versions.
732
+
733
+ - `ctx.now` is new: the attempt's evaluation time in epoch milliseconds, the same on every run of the attempt. `keyed()` receives it too. It is distinct from `ctx.eligibleAt`, which is when the activity happened.
734
+ - `Date.now()`, `new Date()` and `Date()` return `ctx.now` and do not advance, even across awaited capability calls. `performance.now()` returns 0.
735
+ - `Math.random()`, `crypto.getRandomValues()` and `crypto.randomUUID()` repeat the same sequence for one attempt and differ between attempts.
736
+
737
+ **Who should act.** Evaluators that measure elapsed time or rely on unpredictable random values. Use `ctx.now` for "as of evaluation" checks and `ctx.eligibleAt` for when the activity happened. See [Evaluators see a frozen clock and seeded randomness](https://console.domino.run/docs/reference/upgrade-to-v1#evaluators-see-a-frozen-clock-and-seeded-randomness) in the v1 upgrade guide.
738
+
739
+ - e1cba88: Observations are removed, and every automatic activity arrives as a tracker record. `automatic({ source, type })`, `ctx.observation`, `management.observations.submit`, `POST /management/v1/observations`, the `Observation` type, `observationSchema`, `observationInputSchema`, `observationReceiptSchema`, `observationTriggerSchema`, `discordReactionSchema`, `QuestView.trigger.observation` (and the release trigger's `observation`) and the `observation` field of the `automatic` availability are gone. `questStatus` and `runsSilently` from `@domino-sdk/relay/react` now label every tracker-triggered quest "Automatic", not only observation and keyed quests. A server that reported observations sends records to a custom tracker with `management.trackers.ingest` and triggers quests with `automatic({ tracker })`. Ingestion never joins a member to a project: a tracker trigger skips a member who has not joined with `not-joined`. Quests published with an observation trigger leave the active set when Relay upgrades. Republish them from code on a tracker; the quest key can stay the same.
740
+
741
+ Discord messages and reactions are two managed tracker sources, `discord.messages()` (with `{ text: true }` to store message text) and `discord.reactions()`, on the `discord` object from `@domino-sdk/relay/authoring`. Domino's Discord gateway writes each new message or reaction into every tracker of that kind in the projects connected to the server, and keeps a record only when one of its accounts is a Discord account a member has linked. Their record data is typed (`DiscordMessageData`, `DiscordMessageTextData`, `DiscordReactionData`), so `where` filters and `ctx.tracker.record.data` are checked. A reaction record names the message's author as the `messageAuthor` account: `automatic({ tracker: reactions, beneficiary: "messageAuthor" })` replaces `reaction.received`, and Relay skips reactions to one's own message with `beneficiary-is-author`. Discord trackers refuse ingestion (403) and refresh (422). Streaks work on these triggers.
742
+
743
+ The `observe` permission is removed. Ingesting needs `ingest`, and enrolling a member from a server (`management.quests.enroll`) needs the new `enroll` permission. Editors hold `read`, `publish`, `inventory`, `points`, `ingest` and `enroll`; hosted-app keys hold `read`, `points` and `ingest` and cannot enroll. A personal API key issued with `observe` keeps its other permissions, and `observe` grants nothing: reissue it with **Ingest** or **Enroll**. The `observation-quests` capability is removed from `capabilities.json`; Discord tracker triggers are part of the `trackers` capability.
744
+
745
+ To upgrade, see [Observation triggers become tracker triggers](https://console.domino.run/docs/reference/upgrade-to-v1#observation-triggers-become-tracker-triggers) and [The `observe` permission is replaced by `enroll`](https://console.domino.run/docs/reference/upgrade-to-v1#the-observe-permission-is-replaced-by-enroll).
746
+
747
+ - 23c85ba: `defineEngagementReward` takes `budgets`, a list of `{ scope: "project" | "member", period: "day" | "week" | "month" | "lifetime", limit }`, instead of `budget: { projectDaily, memberDaily, projectLifetime }`. Periods are those of the project calendar at settlement time, so a rule can now cap weekly or monthly payouts, and accounts settle once per project calendar day instead of once per UTC day. A definition with `budget` fails validation; rules already published keep their limits in the new form.
748
+
749
+ See [Engagement budgets are a list of periods](https://console.domino.run/docs/reference/upgrade-to-v1#engagement-budgets-are-a-list-of-periods) in the v1 upgrade guide.
750
+
751
+ - 6cd0ba7: Make protocol 2 the first stable evaluator contract, including the required pinned project calendar. Remove pre-release protocol 1 from publication and execution, and preserve an SDK-built protocol-2 compatibility fixture. Rebuild and republish pre-v1 bundles during the approved cutover; changing only their protocol field is insufficient. See the [evaluator protocol upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#evaluator-protocol).
752
+ - 3a38259: Run content qualification and performance gates on custom content sources as well as X. Declare each custom source's body schema and metric names, use `content({ tracker })`, and read the captured body through `ctx.tracker.content`. Content quests imply one completion per item. Replace retired content admission and approval calls with quest outcomes, claims and tracker outcome retries.
753
+
754
+ Upgrade steps: [Content stages](https://console.domino.run/docs/reference/upgrade-to-v1#content-stages).
755
+
756
+ - 4626004: Identity kinds are open. Besides `wallet`, `x`, `discord` and `member`, a custom tracker can declare your app's own kinds, `app:<name>` such as `app:telegram`, and records name your app's user IDs. An identity integration created with `identityKind: "app:<name>"` (set only at creation, kept by rotation) links each subject it signs in to the member as an identity of that kind, and tracker triggers attribute records through it, with `{ provider: "app:<name>", subject, revision, linkedAt }` as `ctx.tracker.attribution.account`.
757
+
758
+ A kind belongs to the integration that first claimed it, also after a disconnect, which keeps its links attributing. A new integration takes a kind that has identities only by adopting them: `adoptIdentitiesFrom` (the old integration's ID, from the new `management.identityIntegrations.kinds()`) with a `reason` the audit log keeps; otherwise it is refused with 409. Reviewers unlink and relink a member's app identity (`management.members.unlinkIdentity` and `relinkIdentity`, and Console's **Tracker identities**), each audited with a reason: unlinking stops attribution, ends the member's sessions for that subject, and refuses its sign-in with 409 `APP_IDENTITY_UNLINKED`, which `createParticipantBackend` passes on, until it is relinked. A subject the integration already signs in as another member answers 409 `APP_IDENTITY_CONFLICT`.
759
+
760
+ Evaluators read the member's selected X and Discord accounts and app identities with `ctx.member.identities()`, pinned once per attempt and `null` when there are none. Management member reads return them as `identities`, and Console lists them on the member under **Tracker identities**.
761
+
762
+ Published tracker definitions in status, list, record-page, summary, and deployment responses use `trackerDefinitionResponseSchema` and `TrackerDefinitionResponse`. Authoring keeps the strict `trackerDefinitionSchema` and `TrackerDefinition`.
763
+
764
+ Responses and evaluator contexts read identity kinds as an open set, `OpenIdentityKind`, and ignore fields they do not know, so a kind a later Relay adds no longer fails them.
765
+
766
+ **Breaking:** `IdentityKind` is no longer a closed union, `identityKindSchema` is no longer a `z.enum`, response kinds are `OpenIdentityKind`, `trackerRecordSchema` is a separate response schema, `trackerAttributionSchema`'s account is a plain union with app and generic variants, and `ctx.member` is `{ id, identities() }`. See [Identity kinds are open, and evaluators read `ctx.member.identities()`](https://console.domino.run/docs/reference/upgrade-to-v1#identity-kinds-are-open-and-evaluators-read-ctxmemberidentities).
767
+
768
+ - 2149973: Read published trackers with future source kinds without rejecting a tracker list. Preserve unknown source payloads and expose `isKnownTrackerSource` for supported-source narrowing. Tracker authoring remains closed. Update code that assumes every response source is supported as described in the [v1 upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#handle-future-tracker-sources).
769
+ - 54fcf41: The participant client follows one convention for paging, sessions and errors.
770
+
771
+ - Every method takes an optional last argument, `{ session?, signal? }`. `session` replaces the positional session argument of `wallets.challenge`, `wallets.verify` and `me.collectionPass`. Calls Relay binds to a session (wallet linking, collection passes, `quests.retry`) read the current session when `session` is omitted.
772
+ - Every paged read takes `{ cursor? }` and returns `{ items, nextCursor }`: `referrals.history`, `quests.outcomes`, `content.qualifications`, `content.performance`, `engagement.page` and `engagement.boundaries` changed from `before`, `after` and `next`. The participant routes answer `nextCursor` and read `cursor`, and so do the management routes and client methods that share them (`management.referrals.history(member, { cursor })`, `management.content.*`, `management.engagement.page` and `boundaries`).
773
+ - Every rejection is a `RelayError`, which extends the new shared `RequestError`: `kind` (`http`, `network`, `timeout`, `aborted`, `invalid-response`, `client`), `uncertain` (true only for a write that may have taken effect: a failure after sending, a cut-off body, a 500, 502 or 504, or an unreadable success answer), `status` whenever response headers arrived, an open string `code` (known ones in `RelayErrorCode`), `operationId`, and `recovery` (`replay` with an `operationId`, else `reconcile`) when uncertain. `isRelayError` and `isRequestError` recognize these errors across duplicate copies of the package. `RelayError.status` is now `number | undefined`. Invalid input rejects with `invalid-input` instead of a `ZodError`, a success answer this SDK cannot read with `invalid-response`, a body cut off after its headers with kind `network` and its status, and a wallet signer's own failure with `wallet-signer`. The error body is read field by field, so Relay's message and a code the SDK does not know are always kept. Relay now sends `code: "not-signed-in"` and `code: "stale-session"` on those participant errors, and every paged read answers a malformed cursor with 400, code `invalid-cursor`, and one message.
774
+ - Removed: `submit`, `retry(id)`, `progress(member)`, `upload(member, file)`, `auth.connections`, `me.linkPass`, the `wallet.*` namespace, and the staff calls the participant API refused (`lookupMember`, `snapshot`, `publish`, `publishBatch`, `beginHandover`, `confirmHandover`, `decide`, `journeyTemplate`, `template`).
775
+ - Parameters take Zod input types, so defaulted fields are optional, and cursors are opaque strings the server validates. The session the client returns (`ParticipantSession`) accepts fields and principal kinds a newer Relay adds; `sessionMember(session)` reads its member.
776
+ - Hosted proxies no longer forward the 0.x single-wallet routes (`/v1/me/wallet`, `/challenge`, `/verify`).
777
+
778
+ Upgrade: [One paging, session and error convention in the participant client](https://console.domino.run/docs/reference/upgrade-to-v1#one-paging-session-and-error-convention-in-the-participant-client) in the SDK v1 upgrade guide maps every method and has search commands.
779
+
780
+ - c832384: Replace raw authoring resource keys with typed quest, ledger, tracker, and group handles. Authoring constructors and nested budget, tier, milestone, and bonus definitions now use `key`; ledger declarations publish from modules; point-ledger settings return typed references. Stored releases and management, Console, and participant wire IDs remain compatible. See [Authoring references use typed handles](https://console.domino.run/docs/reference/upgrade-to-v1#primitive-handles) in the v1 upgrade guide.
781
+ - 23c85ba: Each project environment has one project calendar: a timezone, UTC until you choose one, that sets where every day, week and month begins. Streak days, quest budgets, engagement budgets and calendar leaderboard windows all follow it, so a weekly budget and a weekly leaderboard cover the same week.
782
+
783
+ - Set it with `"calendar": { "timeZone": "Europe/Budapest" }` in relay.json, in Console **Settings → General → Project calendar**, or with the new `management.calendar.read()` and `management.calendar.configure({ timeZone, reason })` (`GET` and `POST /calendar`, `publish` permission; in live only from Console). A change takes effect at the next midnight in the current timezone: the open day, week or month finishes in the old zone, and streaks stay consecutive. A deployment applies relay.json's timezone only when it differs from the last one deployed, and the deployment preview's new `calendar` field shows the change it schedules.
784
+ - `streak()` and calendar leaderboard windows no longer take a `timeZone`; both throw if given one. Streaks and boards already published with one still read.
785
+ - Budgets reset at midnight in the project timezone instead of UTC, and accept `period: "month"`.
786
+ - One validator, `timeZoneSchema`, accepts named IANA zones only. Fixed offsets such as `+02:00` and `Etc/GMT-2` are rejected, including in leaderboards, which used to accept them. The shared `calendarPeriodSchema` is `lifetime | day | week | month`.
787
+
788
+ **Who should act.** Projects that pass `timeZone` to `streak()` or a calendar leaderboard window, and projects that rely on budgets resetting at UTC midnight. See [Set the timezone once, on the project calendar](https://console.domino.run/docs/reference/upgrade-to-v1#set-the-timezone-once-on-the-project-calendar) in the v1 upgrade guide.
789
+
790
+ - 2528cec: Every tracker trigger shares the admission rules of ADR 0006: enrollment, the earliest activity counted, and who releases historical activity, with skips decided again when the member event that clears them happens.
791
+
792
+ - **Admission options on `automatic({ tracker })`.** `enrollment: "required"` counts only members who enrolled in the quest, and `ctx.tracker.enrolledAt` carries their enrollment time. `since` is `"activation"` (the default), `"window"`, or, with required enrollment, `"enrollment"`. `historical` is `"staff"` (the default), `"claim"` or `"automatic"`.
793
+ - **Historical activity waits.** A record from before the member linked the account it pays, or with an unknown link time, is skipped as `historical` until a reviewer retries it, unless the quest sets `historical: "claim"` or `"automatic"`. Before, it was admitted. See [Tracker triggers hold activity from before an account was linked](https://console.domino.run/docs/reference/upgrade-to-v1#tracker-triggers-hold-activity-from-before-an-account-was-linked).
794
+ - **Skips are decided again by member events.** Linking an account, removing a conflicting link, joining the project and enrolling decide the matching skips again, through a durable workflow.
795
+ - **Every selected observation has one outcome.** New skip codes `outside-window`, `future-record`, `already-completed`, `not-enrolled`, `before-enrollment` and `historical`; `TrackerSkipCode` is an open string, with `KnownTrackerSkipCode` for the codes this SDK knows. A prerequisite completed after the record occurred no longer counts for it.
796
+ - **Participant APIs.** `relay.quests.enroll({ actionId, quest })`, `relay.quests.outcomes({ quest?, state?, code?, cursor? })` and `relay.quests.claim({ actionId, quest, tracker, record, observedAt })`, and `useQuestOutcomes()` in React. `relay.content.enroll` and `management.content.enroll` are replaced by `relay.quests.enroll` and `management.quests.enroll`. See [Enroll in quests with `relay.quests.enroll`](https://console.domino.run/docs/reference/upgrade-to-v1#enroll-in-quests-with-relayquestsenroll).
797
+ - **Console.** A tracker-triggered quest shows its enrollment, `since` and historical policy, a `not-enrolled` skip offers **Enroll the member**, and a member's claim shows as **Claimed**.
798
+
799
+ **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.
800
+
801
+ - c919948: Follow-ups to the React hooks' shared query layer.
802
+
803
+ - `completionCountLabel(view)` is renamed `completionCountText(view)`, so the name says it returns English like `questStatusText`. For your own words, read `view.completionCount`.
804
+ - `questStatus(view)` returns `QuestStatus`, whose `code` is the open `QuestStatusCode`, and `roleDeliveryStatus(deliveries, quest)` returns the open `RoleDeliveryStatus | null`. A `switch` that lists every code needs a `default`, so a newer SDK can add codes without breaking the signature.
805
+ - `useReferralSummary({ intervalMs })` reads the summary again every 60 seconds by default while the page is visible, so friends who qualify show up without an app-side timer.
806
+ - `useSignOut()` drops the signed-out member's cached values as soon as Relay confirms the sign-out, even when the next session read fails.
807
+ - A `useMyProfile()` change that answers after another member signed in no longer writes into the new member's cache.
808
+ - `useSignIn`, `useSignOut`, `useSignInOptions`, `useInvitation` and `useQuest` return objects and actions that keep their identity until something in them changes; `useQuest`'s actions act on the latest view. `useExperience().deliveries` is a `readonly RoleDelivery[]`, and keeps one frozen empty list while there are none.
809
+ - `progressOf(quests)` is documented: `total` counts locked and paused quests and leaves out archived and repeatable ones.
810
+
811
+ Upgrade: [React hooks share one cache and return one state shape](https://console.domino.run/docs/reference/upgrade-to-v1#react-hooks-share-one-cache-and-return-one-state-shape) in the SDK v1 upgrade guide.
812
+
813
+ - c2390b3: The React hooks in `@domino-sdk/relay/react` share one query cache and return one state shape.
814
+
815
+ - Every read hook returns `{ status, data, error, refresh }`: `status` is `loading`, `ready` or `error`, plus `signed-out` for hooks that need a member; `data` holds the value; `error` is always a `RelayError`, and while `ready` it says why the latest refresh failed and the last value stays. `useMe` and `useSignInMethods` return this shape instead of a bare value, `useMe` no longer swallows errors, and the profile hooks report the uniform 404 as `error` with status 404 instead of `missing`.
816
+ - `<RelayProvider>` keeps one keyed cache. Components that read the same thing share one request. A read that is replaced, invalidated, or belongs to a previous participant or client is cancelled, and its late answer is dropped. Polling, including the quest experience observer and the Discord role-delivery watcher, pauses while the tab is hidden and catches up when it is shown. Reads wait for the first session read, public ones included, and a sign-in attempt that does not change the session drops nothing.
817
+ - Changes invalidate what they affect: every new completion the experience shows, submitted or automatic, reads balances, point history, leaderboards, profiles and referral progress again; an accepted invitation, the referral summary and referral progress; wallet, account and profile changes, their own reads. The `PROFILE_CHANGED` window event is removed. `useInvalidate()` and `relayKeys` invalidate by key prefix after a change made with `useRelay()`.
818
+ - The cache is scoped to the client, organization, project, environment, member and session. Signing in or out or switching accounts, projects or environments drops it, so one member never sees another's cached values. A new `client` or `baseUrl` passed to `RelayProvider` now takes effect: the old observer stops, reads in flight are cancelled, and the hooks start from an empty cache.
819
+ - `useSession()` is new: who is signed in, independent of the experience read, so a failing `/me/experience` no longer blanks the profile, wallet, account and `useMe` hooks. `useExperience()` returns the shared shape with `data.quests` and `data.collections` and `deliveries`; its `member` moved to `useSession()`. `Experience` is replaced by `ExperienceData`, which `questGroups` takes.
820
+ - `useBalances()` returns the `{ items }` answer in `data`, and `usePointHistory(ledger)` returns `data.items` with `loadMore()`; an unknown ledger or a member who has not joined is `error` with code `point-ledger-not-found` or `not-joined` instead of the `missing` and `not-joined` statuses. `BalancesState` becomes `MemberQuery` and `PointHistoryState` becomes `MemberPagedQuery`.
821
+ - `useRelayQuery`, `useMemberQuery`, `useRelayPages` and `useMemberPages` put an app's own reads on the same cache. `Page` is the root package's type; `PagedQuery` (public pages) and `MemberPagedQuery` (member pages) add `loadMore`, `loadingMore` and `loadMoreError`, which belong to one key and participant.
822
+ - Every error the hooks report is a `RelayError`: `useQuest().error`, `useSignIn().error`, `useSignInOptions().error` and `methodsError`, `SignInMethods.error` (also from `signInOptions()` in `@domino-sdk/relay/browser`), and a rejected or failed invitation's `error`, which replaces `reason`.
823
+ - `completionMark`, `hasPending`, `sanitizeDeliveries`, `watchRoleDeliveries`, `PENDING_POLL_MS`, `RETRY_DELAYS_MS`, `runsSilently` and `visibleQuests` are no longer exported from `@domino-sdk/relay/react`.
824
+ - `questStatus(view)` returns `{ tone, code, reason? }`, a stable code to localize (`QuestStatusCode`, an open string like `RelayErrorCode`); `questStatusText(view)` adds the English `label` and `detail`. `roleDeliveryStatus(deliveries, quest)` returns `pending`, `failed`, `granted` or null, and `roleDeliveryLabel(status)` gives its English label.
825
+
826
+ Upgrade: [React hooks share one cache and return one state shape](https://console.domino.run/docs/reference/upgrade-to-v1#react-hooks-share-one-cache-and-return-one-state-shape) in the SDK v1 upgrade guide maps every hook.
827
+
828
+ - 1084288: `@domino-sdk/relay` now exports only the participant runtime client: `createRelayClient`, `RelayError`, `QuestUpdatedError`, `formatUnits`, `parseUnits`, and the types the client returns. Schemas, builders and avatar helpers moved to their own entry points, a few implementation helpers are no longer public, and two are no longer exported at all, so an app that imports the client no longer loads authoring builders, deployment and management schemas, or DiceBear.
829
+
830
+ - Zod schemas (and helpers such as `identifier`, `caip10` and `xPostId`) come from `@domino-sdk/relay/schema`.
831
+ - Builders (`defineTracker`, `defineLeaderboard`, `defineReferral`, `defineEngagementReward`, `content`, `streak`, `settings`, `discord`, `pointLedgers`, `x`, `customSource`) and evaluator reader types (`TrackerReader`, `XReader`, `ImageReader`) come only from `@domino-sdk/relay/authoring`.
832
+ - `avatarSrc`, `defaultAvatar`, `defaultAvatarSvg` and `walletAvatarSeed` come from `@domino-sdk/relay/avatar`, and no longer from `@domino-sdk/relay` or `@domino-sdk/relay/react`.
833
+ - No longer public: `identityIntegrationSchema`, `identityIntegrationInputSchema`, `identityIntegrationSecretSchema`, `participantExchangeSchema`, `questConfiguration`, `parseImageResult`, `parseImageExtraction`, `isContentParticipantRoute`, `isProfileParticipantRoute`, `isWalletParticipantRoute` and `isWalletSignInRoute` are exported only from `@domino-sdk/relay/internal`, which Relay's own services share and which is not a supported API.
834
+ - No longer exported: `questActions` and `reconcileQuestDraft`. Open a quest controller with `relay.quests.open(view)`; its `quest-updated` result already carries the reconciled `draft`.
835
+ - The package declares `sideEffects: false`.
836
+ - `esbuild` is an optional peer dependency, needed only by `@domino-sdk/relay/build`. `@domino-sdk/relay-cli` depends on it directly, so `domino build` and `domino deploy` work as before. Code that calls `buildQuest` without the CLI must install `esbuild`.
837
+
838
+ Upgrade: [Runtime entry exports only the client](https://console.domino.run/docs/reference/upgrade-to-v1#runtime-entry-exports-only-the-client) in the SDK v1 upgrade guide has the import mapping and search commands.
839
+
840
+ - 5d4ec3e: Release Relay SDK and CLI 1.0.0. Install the SDK and CLI together at the same version. This release removes pre-v1 APIs; follow the [SDK v1 upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1) before replacing a 0.x installation.
841
+
842
+ Stable APIs follow semantic versioning throughout the supported v1 lifetime. Compatibility exemptions apply only to explicitly experimental entrypoints.
843
+
844
+ - 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.
845
+ - 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.
846
+
847
+ - **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).
848
+ - **Failures throw `CapabilityError`**, new in `@domino-sdk/relay/authoring` (its wire shape, `capabilityFailureSchema`, is in `@domino-sdk/relay/schema`), with `code` (`"not-linked"`, `"provider-unavailable"`, `"author-error"`) and `retryable`. Invalid capability input is `author-error`; an image check of `{ x: "profile" }` without a linked X account is `not-linked`. Failures that are not retryable are captured and replay. A retryable failure that quest code handles and moves past is final for that evaluation run; one that escapes the evaluation is released, along with every fact captured after it, and retried, so the retry asks the provider and the later calls again. Rethrow a caught failure, or wrap it with `{ cause: error }`, to let it be retried. The failure shape is open: a newer Relay can add codes, and the SDK treats an unknown code as not retryable. See [Capability failures throw `CapabilityError`](https://console.domino.run/docs/reference/upgrade-to-v1#capability-failures-throw-capabilityerror).
849
+ - **Tracker reads are point in time.** `ctx.trackers.records()` and `summary()` take `asOf`, defaulting to `ctx.eligibleAt`, and every tracker read of an evaluation run sees the observations stored at its first tracker read. Pass `asOf: ctx.now` to count everything stored by then. See [Tracker reads are point in time and pinned per attempt](https://console.domino.run/docs/reference/upgrade-to-v1#tracker-reads-are-point-in-time-and-pinned-per-attempt).
850
+ - **Linked accounts stay pinned.** X and Discord readers pin the account on the evaluation run's first read and replay that pin on retries, as wallets already did, so a link change no longer fails the attempt with "X account changed" or "Account or integration changed". See [Linked X and Discord accounts stay pinned for the attempt](https://console.domino.run/docs/reference/upgrade-to-v1#linked-x-and-discord-accounts-stay-pinned-for-the-attempt).
851
+
852
+ **Who should act.** Evaluators that read `ctx.x`, `ctx.discord`, `ctx.wallet` or `ctx.trackers`, or that match capability error messages. The changed return types point TypeScript at each read to update. The `discord-membership`, `token-holder` and `community-screenshot` snippets and the Discord starter's `join-the-server` quest are updated.
853
+
854
+ ### Minor Changes
855
+
856
+ - afe1128: Operators can revoke an accepted completion with a reason: `management.completions.revoke({ completion, reason, freeOccurrence })` (`POST /management/v1/completions/<id>/revoke`, `review` permission). In one transaction the completion stops counting (progress, prerequisites of new attempts, counts, profiles and referral qualification), its point awards are reversed as far as the member's available points allow, tiered rewards it granted are voided or lowered to the tier another completion grants (collected ones are flagged), Discord role grants not yet delivered are stopped while delivered roles are flagged for the operator to remove in Discord (Relay never removes roles; automatic removal is DOM-99), referral programs that qualified on it are re-evaluated, and a streak milestone it paid is revoked while its days stay. `freeOccurrence` lets the member complete the quest again for the same completion key; by default later attempts for it are rejected. The result reports each part of the cascade. API keys with `review` permission can revoke in live. Console offers it on member detail and accepted attempts, and the attempt history and Audit history show the revoke and its reason. `domino capabilities --json` now marks `completion-revoke` as native.
857
+
858
+ Breaking: a reward delivery's `action` is an open string (`RewardDeliveryAction` lists `"grant-role"`), the worker fails a delivery whose action it does not know, and a grant still pending at the revoke fails. The revoke result's `outcome` fields are open strings with known values in `RevokedEntitlementOutcome`, `RevokedDeliveryOutcome` and `RevokedReferralOutcome`. To upgrade, see [Reward delivery actions are open strings](https://console.domino.run/docs/reference/upgrade-to-v1#reward-delivery-actions-are-open-strings).
859
+
860
+ Revoking preserves percentage bonuses from other valid completions and queues a new automatic attempt when a freed occurrence still meets its prerequisites. Delivery responses add optional `dispatchedAt` and `revokedAt` fields. Dispatch history survives retries, revoke fences failed generations, and late provider success stays visible without reopening delivery. Console uses the revoke field to stop retries and shows unknown delivered actions by their generic status.
861
+
862
+ Revocation refuses missing or unreadable grant history before changing member state. Collected entitlements carry a persistent recovery flag in ordinary reads and Console. Correcting collection clears the flag and voids an unearned reward instead of reopening it; a surviving grant reopens only its earned tier. Reused-completion attempt timelines include the shared completion’s revoke event.
863
+
864
+ - 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).
865
+ - 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).
866
+ - 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.
867
+ - 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`.
868
+
869
+ 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).
870
+
871
+ - 84feaf7: Declare evaluator protocol 2 on authored bundles and descriptions, including the pinned project calendar. The host selects a versioned adapter, derives readers from the capability registry and preserves captured capability requests for replay. Custom bundle builders must keep both protocol declarations. See the [v1 evaluator protocol upgrade guide](https://domino.run/docs/reference/upgrade-to-v1#evaluator-protocol).
872
+ - 71cf912: Add reviewable leaderboard settlements. Fixed and calendar earned/net leaderboards can declare point and tiered-reward prizes, choose shared-rank or earliest-score tie handling, and freeze each ended window with its eligibility and exclusions at cutoff. Entries received after the cutoff stay out even when backdated, and a definition with a changed window advances past an obsolete scheduled cutoff. Operators review and approve delivery in Console or with `management.leaderboards`, where the approval reason remains visible, and can correct an individual winner through completion revocation without awarding that settlement again.
873
+ - f2f1b75: Breaking: the management client moves from `@domino-sdk/relay-cli` to the new `@domino-sdk/relay/server` entrypoint, with `createManagementClient`, `hostedManagementClient`, `ManagementError`, the management schemas and types, and `starterTemplates`. Server code, including a hosted app's Worker, now needs only `@domino-sdk/relay`, without the CLI's command-line parser and TypeScript compiler. `@domino-sdk/relay-cli` is only the `domino` executable and its agent skills; it has nothing to import. `serverBoundary()` also fails a browser build that imports `@domino-sdk/relay/server`. To upgrade, see [Import the management client from `@domino-sdk/relay/server`](https://console.domino.run/docs/reference/upgrade-to-v1#import-the-management-client-from-domino-sdkrelayserver).
874
+
875
+ The capability catalog (`domino capabilities --json`) adds staff confirmation, staff handover, stock, point corrections, deploy and stage, operator member reads and lookup, project access, and the management API with `domino api`. It marks webhooks, bulk data export, completion revocation and tracker-scoped keys as unsupported, and lists the management methods it leaves out on purpose in `excludedManagementMethods`.
876
+
877
+ - 8c06912: Add operator reads and audited unlink for current X and Discord quest accounts. Reviewers provide the subject and selection revision to prevent a stale review from unlinking a newer connection. Identity ownership, sign-in grants and existing rewards remain unchanged. Console exposes the action on each member's Quest accounts section.
878
+ - 6c30583: Add member-only activity pages, `me.activity.observe()` and `useActivity({ onEvent })` for committed asynchronous outcomes. Cursor reads preserve concurrent commits, and observers pause while hidden and reset on session changes. Events begin after the additive activity-feed migration; existing snapshots remain authoritative for earlier state.
879
+ - 710e520: Participants read their own point balances and history. `relay.me.balances()` (`GET /v1/me/points`) returns `{ items }`: the signed-in member's `balance` and `available` points (after reservations) in every ledger of the project, each with its `ledger` reference. `relay.me.pointHistory({ ledger, cursor? })` (`GET /v1/me/points/:ledger/history`) pages through the member's own entries in one ledger, by key or ID, most recently recorded first, as `{ items, nextCursor }`. Each entry is `{ id, amount, kind, at, quest, reverses }`, without the operator's reason or actor. `kind` is an open string; the `PointEntryKind` constants (`award`, `spend`, `adjust`, `reverse`) name the kinds this SDK knows, and referral payouts are `adjust`. An unknown ledger answers 404 with code `point-ledger-not-found` and a member who has not joined the project 404 with `not-joined`, both added to `RelayErrorCode`. Neither read takes a member, and both are scoped to the session's project and environment. React apps use `useBalances()`, which reads again after a quest completes, and `usePointHistory(ledger)`. The participant proxy and identity integrations admit both routes. New schemas and types: `pointBalanceSchema`, `pointBalancesSchema`, `pointEntrySchema`, `pointHistoryQuerySchema`, `pointHistoryPageSchema`, `PointBalance`, `PointBalances`, `PointEntry`, `PointEntryKind`, `KnownPointEntryKind`, `PointHistoryQuery` and `PointHistoryPage`.
880
+
881
+ The classic starter's header now reads the balance with `useBalances()` instead of its `/api/me/points` server route, which is gone.
882
+
883
+ - 6c30583: Expose participant earned rewards through `rewards.entitlements()` and `useEntitlements()`, and include entitlements in experience reads. Add `rewards.deliveries()` and `useDeliveries()` for points, tiers and integration deliveries. Preserve unknown delivery providers, kinds and states in reads while keeping backend delivery writes validated. Role displays use current reconciliation state after revocation.
884
+
885
+ Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.
886
+
887
+ - c320586: A content quest's `performance.evaluate` can no longer select rewards. It gates the rewards the content evaluation already selected, and it always did: a `rewards` value in its result was silently ignored. Its type is now `PerformanceOutcome` (`accept`, `review` or `reject` with a reason only), so `decision.accept({ rewards })` and `decision.review({ reason, rewards })` are type errors there, and a runtime result that carries `rewards` fails the evaluation with "A performance evaluator cannot select rewards". To change what a post earns, select rewards in the quest's `evaluate`.
888
+
889
+ See [Performance evaluators no longer select rewards](/docs/reference/upgrade-to-v1#performance-evaluators-no-longer-select-rewards) in the upgrade guide.
890
+
891
+ - 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.
892
+
893
+ 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.
894
+
895
+ - 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.
896
+
897
+ Treat forbidden Discord membership reads as permanent delivery failures so operators can fix permissions and retry immediately.
898
+
899
+ Publish initial pending delivery events in the grant transaction so observers can see queued rewards before reconciliation starts.
900
+
901
+ - 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.
902
+ - 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).
903
+ - ddfc526: The participant client, the management client and the CLI share one set of deadline, cancellation and retry rules.
904
+
905
+ - 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.
906
+ - A call is sent again, at most three times in all, only when the server applies a repeat at most once: reads; participant writes with an action ID, submission evidence by its upload ID, `wallets.verify` (by its challenge) and `wallets.challenge`, but not avatar uploads, whose upload ID has no receipt; typed management writes whose input has an `actionId`; and the CLI's `deploy` publication, `stage` build request and `quests archive`/`unarchive`. Every attempt sends the same payload and session. Retries follow a lost answer, including a body that breaks off after successful headers, and HTTP 500, 502, 503 and 504, with a short jittered backoff. Invalid or unexpected answers, cancellations, rejected input, other refusals and every other write are sent once.
907
+ - A 429 is retried only when its `Retry-After` fits in the time left, and every wait honours `Retry-After`. The new `retryAfter` field of `RequestError` (and so `RelayError` and `ManagementError`) carries the wait the server asked for, in milliseconds; the CLI reports it as `details.retryAfter` on `RATE_LIMITED` and `SERVER_ERROR`.
908
+ - A session-bound participant call that reads the current session first shares one deadline with that read, and `auth.session()` honours `timeout`.
909
+ - An error status whose body breaks off, times out or is cancelled is now a `network`, `timeout` or `aborted` failure that keeps its `status` and `retryAfter`, in all three clients; an error body that is not JSON stays `http`.
910
+ - Cancelling with `signal` stops a retry wait at once. A write any attempt of which may have been applied stays `uncertain`, with its `operationId` and `recovery`, whatever later attempts answer.
911
+ - `management.request` retries only GET, and `management.raw` sends once. `useInvitation()` keeps a rate-limited code for a retry instead of discarding it as refused.
912
+
913
+ Avatar failures report `recovery: "reconcile"` even when they have an upload ID. Wallet address and signature waits reject when the caller cancels. See [Bounded requests and avatar recovery](https://console.domino.run/docs/reference/upgrade-to-v1#bounded-requests-and-avatar-recovery) for the recovery-handler change.
914
+
915
+ Generic management writes report `recovery: "reconcile"` even with a caller-supplied action ID. Uncertain `domino api` writes expose `details.recovery: "reconcile"`. See [Generic management writes require reconciliation](https://console.domino.run/docs/reference/upgrade-to-v1#generic-management-writes-require-reconciliation) for the recovery-handler change.
916
+
917
+ - 89ea6ba: Add reviewed live project publication. The management client can list deployment records, submit and reject previews, promote a published test deployment, and prepare a rollback to the previous live deployment. Promotion and rollback accept an action UUID, and the management client generates one when omitted so retries return the same deployment record. `domino deploy --environment live` now returns `status: "pending"`, and `--promote` submits test source for live review; only a person in Console can approve, reject, or prepare rollback. The new `pending` deploy status is a breaking addition for exhaustive automation. See [Live deploys return a pending review](https://console.domino.run/docs/reference/upgrade-to-v1#live-deploys-return-a-pending-review).
918
+ - 67d0d8c: Add bounded `decision.defer` and absolute `decision.progress` results, fresh evaluation runs scheduled by durable timers, participant progress and deferred status, and consistent completion-key authoring types. Reviewers can reject a deferred attempt to stop it.
919
+
920
+ Attempt response statuses accept future server values; use known `AttemptStatus` constants with a fallback. New manual submissions while deferred return `409 attempt-pending`. Custom decision/status switches must handle the new variants. See [Handle deferred evaluator decisions and progress](https://console.domino.run/docs/reference/upgrade-to-v1#handle-deferred-evaluator-decisions-and-progress) for old and new code, capture semantics, and search patterns.
921
+
922
+ - d3b6261: Restrict personal ingestion keys to selected custom trackers, inspect and edit their scope without changing the secret, and return TRACKER_SCOPE_DENIED for disallowed writes. Human CLI sign-ins can manage their own keys; project-scoped playground sessions and API keys cannot. Existing unrestricted keys retain project-wide ingestion.
923
+ - d7f9fe4: `streak(...)` completes manual and tracker-triggered quests. Each accepted attempt counts the project-calendar day of its trusted occurrence time, exposed as `ctx.eligibleAt`: a tracker observation's `occurredAt`, or the admission time of a manual submission. A rejected manual submission preserves committed streak progress and displays its reason. Once the streak milestone pays, later tracker observations do not pay it again.
924
+
925
+ Content-stage quests and prerequisite-only `automatic()` quests cannot use streak completion. A published quest cannot change between manual and tracker triggers under the same key. Observation triggers are removed; migrate them to trackers. See [Observation triggers become tracker triggers](https://console.domino.run/docs/reference/upgrade-to-v1#observation-triggers-become-tracker-triggers).
926
+
927
+ - 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.
928
+ - cf5b02c: Custom tracker records have a declared data schema. `customSource(namespace, { data })` requires a zod object schema (`z` is exported by `@domino-sdk/relay/authoring`), stores its JSON Schema form with the tracker, and throws, naming the field, for what ingestion cannot check (string formats and patterns, tuples, refinements, transforms, dates, bigints). Publication refuses a custom tracker without one. Ingestion rejects a batch whose record data does not match with 422 and `{ error, code: "invalid-record-data", record, path, reason }`, one too costly to validate with `record-data-too-complex`, and records for a tracker published without a schema with `tracker-schema-required`. Schemas are bounded (32 KB, 16 levels, 256 properties, required keys, enum values or key names per node, 16 branches per union, no wide union nested in another union's branch), and validation charges every unit of work to a per-record and per-batch step budget. A deployment whose new schema breaks a live quest's `where` filter, Console quests included, is refused. Stored records are not rechecked: tracker triggers skip non-matching records with the new skip code `invalid-record-data`, evaluators reject pinned records that do not match the schema the quest was built with, and `trackers.records(handle)` leaves them out and counts them in `excluded`. The schema can change under the same tracker key and applies to later records.
929
+
930
+ The data type flows into quests: `ctx.tracker` is non-null in a tracker-triggered quest's evaluator with `record.data` of the tracker's type (content triggers expose their captured body through `ctx.tracker.content`), `trackers.records(tracker)` returns typed records, and `where` paths, `equals` values and `{ setting }` bounds are type-checked against the data and the quest's integer settings. `where` also accepts a list of conditions that must all hold, and `automatic()` and publication check filter paths against the stored schema. `settings.object` and `defineQuest` accept only `settings.*` builders; a plain zod setting is a type error and `defineQuest` throws `Setting "<key>" is not a quest setting`.
931
+
932
+ See [Custom sources declare their record data](/docs/reference/upgrade-to-v1#custom-sources-declare-their-record-data) and [Quest settings come only from settings builders](/docs/reference/upgrade-to-v1#quest-settings-come-only-from-settings-builders) in the upgrade guide.
933
+
934
+ - 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).
935
+
936
+ ### Patch Changes
937
+
938
+ - ed4988c: Read Cosmos Hub and Osmosis bank balances with `ctx.wallet.balance()`, pinned to one verified block and the member's wallets on the exact Cosmos network.
939
+ - c320586: `defineQuest` throws at build time for two shapes that only failed when publishing: a bare `automatic()` quest with no `requires` entry, and a bare `automatic()` quest with `keyed()` completion. A bare `automatic()` runs once its prerequisites are complete, so it needs at least one typed completion, balance or group prerequisite and completes once. The backend still checks both when it publishes.
940
+ - 1141af4: Fix invalid action IDs in the tracker and leaderboard documentation examples. Validate action ID literals during documentation checks so copied examples use the identifier format the API accepts.
941
+ - 83c4426: Update Discord message tracker records when a message is edited or deleted. Turning text off or deleting a message now erases retained message text.
942
+ - c320586: `manual({ actor: "staff", input })` throws instead of dropping the actor. Its types never allowed the combination, but untyped callers got a quest that members could submit evidence to, not one staff confirm. Staff-captured evidence is not supported: use `manual({ actor: "staff" })` for a staff confirmation, or `manual({ input })` for evidence the participant submits.
943
+ - 7e37049: The SDK's optional React peer dependency is now `^19.0.0` instead of `>=19`, matching the React versions the React hooks are verified against. React is still optional: the runtime, browser and server entrypoints do not need it. Apps on React 19 need no change.
944
+ - 909f343: The browser submission store recovers from storage failures instead of caching them. A temporary IndexedDB opening error, a blocked open, a `versionchange` from another tab, or a connection the browser closes no longer fails every later call on the same client: the next `status()`, `resume()` or `submit()` opens storage again, and the saved operation and its operation ID are kept. A blocked open waits briefly for the other tab, then rejects with `submission-storage`, and the pending request is adopted if it later succeeds. Full browser storage rejects with the new `RelayErrorCode.SubmissionStorageQuota` (`submission-storage-quota`); an upgrade by a newer page asks for a reload. Both are `client` errors that are not `uncertain`, and a failed read never looks like an empty store, so a new claim is never started over a saved one. See [Storage failures](/docs/reference/participant-api#storage-failures) in the participant API reference.
945
+ - 8b706af: An attempt now records when its evaluator asked for human review on a quest with review off. `decision.review({ reason })` was already a rejection with the review reason in that case. The attempt now also has `requestedReview: true`, and Console attempt detail says "Would have gone to review; human review is off". A reject the evaluator returned itself keeps `requestedReview: false`. The `review` option, the quests concept page and the authoring reference describe it.
946
+ - e1e701b: Read added trigger and availability fields in participant progress and quest views without weakening publication input validation. Verify unknown delivery providers, actions and statuses through the wire compatibility fixtures.
947
+
948
+ ## 1.0.0-next.34
949
+
950
+ ### Major Changes
951
+
952
+ - ad91102: Record prerequisite-only automatic decisions, expose participant and operator outcomes, and add previewed catch-up for paused events with review permission and audited reasons. Point-entry changes schedule current eligibility asynchronously after commit, including refunds and referral or leaderboard awards. Operators can inspect and retry failed scheduling.
953
+
954
+ Remove the permanent silent pause block. Outcome pagination now merges prerequisite and tracker decisions; reset saved cursors and use the `automatic` identity on rows without a tracker record. See the [v1 upgrade guide](https://domino.run/docs/reference/upgrade-to-v1#prerequisite-automatic-outcomes).
955
+
956
+ - 4b10080: Add the protocol-2 `ctx.calendar.day(at?)` evaluator helper for replay-safe project-calendar keys and day bounds. Protocol-1 bundles remain executable. See the [v1 evaluator protocol upgrade guide](https://domino.run/docs/reference/upgrade-to-v1#evaluator-protocol).
957
+ - 4205914: Add code-authored campaigns with half-open availability windows and durable campaign-wide enrollment; typed completion, live balance and project-group prerequisites; typed availability blockers; and live quest-budget capacity on participant views. Participant and management clients can enroll a member in a required campaign, and Console shows campaign rules and member enrollment.
958
+
959
+ `completed(quest)` now returns a discriminated completion prerequisite instead of `{ quest }`, and `prerequisiteSchema` accepts only typed prerequisite definitions. Stored legacy releases remain readable. See [Prerequisites are typed eligibility rules](https://console.domino.run/docs/reference/upgrade-to-v1#prerequisites-are-typed-eligibility-rules) in the v1 upgrade guide.
960
+
961
+ - 6d66f96: Add reversible campaign enrollment. Participants can withdraw and re-enroll themselves; trusted servers and Console operators can withdraw a member with an audited reason and restore them. Withdrawal blocks new admissions while preserving the first enrollment time, admitted attempts, completions, and rewards.
962
+ - 6cd0ba7: Make protocol 2 the first stable evaluator contract, including the required pinned project calendar. Remove pre-release protocol 1 from publication and execution, and preserve an SDK-built protocol-2 compatibility fixture. Rebuild and republish pre-v1 bundles during the approved cutover; changing only their protocol field is insufficient. See the [evaluator protocol upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#evaluator-protocol).
963
+ - 3a38259: Run content qualification and performance gates on custom content sources as well as X. Declare each custom source's body schema and metric names, use `content({ tracker })`, and read the captured body through `ctx.tracker.content`. Content quests imply one completion per item. Replace retired content admission and approval calls with quest outcomes, claims and tracker outcome retries.
964
+
965
+ Upgrade steps: [Content stages](https://console.domino.run/docs/reference/upgrade-to-v1#content-stages).
966
+
967
+ - c832384: Replace raw authoring resource keys with typed quest, ledger, tracker, and group handles. Authoring constructors and nested budget, tier, milestone, and bonus definitions now use `key`; ledger declarations publish from modules; point-ledger settings return typed references. Stored releases and management, Console, and participant wire IDs remain compatible. See [Authoring references use typed handles](https://console.domino.run/docs/reference/upgrade-to-v1#primitive-handles) in the v1 upgrade guide.
968
+ - 2528cec: Every tracker trigger shares the admission rules of ADR 0006: enrollment, the earliest activity counted, and who releases historical activity, with skips decided again when the member event that clears them happens.
969
+
970
+ - **Admission options on `automatic({ tracker })`.** `enrollment: "required"` counts only members who enrolled in the quest, and `ctx.tracker.enrolledAt` carries their enrollment time. `since` is `"activation"` (the default), `"window"`, or, with required enrollment, `"enrollment"`. `historical` is `"staff"` (the default), `"claim"` or `"automatic"`.
971
+ - **Historical activity waits.** A record from before the member linked the account it pays, or with an unknown link time, is skipped as `historical` until a reviewer retries it, unless the quest sets `historical: "claim"` or `"automatic"`. Before, it was admitted. See [Tracker triggers hold activity from before an account was linked](https://console.domino.run/docs/reference/upgrade-to-v1#tracker-triggers-hold-activity-from-before-an-account-was-linked).
972
+ - **Skips are decided again by member events.** Linking an account, removing a conflicting link, joining the project and enrolling decide the matching skips again, through a durable workflow.
973
+ - **Every selected observation has one outcome.** New skip codes `outside-window`, `future-record`, `already-completed`, `not-enrolled`, `before-enrollment` and `historical`; `TrackerSkipCode` is an open string, with `KnownTrackerSkipCode` for the codes this SDK knows. A prerequisite completed after the record occurred no longer counts for it.
974
+ - **Participant APIs.** `relay.quests.enroll({ actionId, quest })`, `relay.quests.outcomes({ quest?, state?, code?, cursor? })` and `relay.quests.claim({ actionId, quest, tracker, record, observedAt })`, and `useQuestOutcomes()` in React. `relay.content.enroll` and `management.content.enroll` are replaced by `relay.quests.enroll` and `management.quests.enroll`. See [Enroll in quests with `relay.quests.enroll`](https://console.domino.run/docs/reference/upgrade-to-v1#enroll-in-quests-with-relayquestsenroll).
975
+ - **Console.** A tracker-triggered quest shows its enrollment, `since` and historical policy, a `not-enrolled` skip offers **Enroll the member**, and a member's claim shows as **Claimed**.
976
+
977
+ **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.
978
+
979
+ - 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.
980
+
981
+ ### Minor Changes
982
+
983
+ - 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).
984
+ - 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).
985
+ - 84feaf7: Declare evaluator protocol 1 on authored bundles and descriptions. The host selects a versioned adapter, derives readers from the capability registry and preserves captured capability requests for replay. Custom bundle builders must keep both protocol declarations. See the [v1 evaluator protocol upgrade guide](https://domino.run/docs/reference/upgrade-to-v1#evaluator-protocol).
986
+ - 71cf912: Add reviewable leaderboard settlements. Fixed and calendar earned/net leaderboards can declare point and tiered-reward prizes, choose shared-rank or earliest-score tie handling, and freeze each ended window with its eligibility and exclusions at cutoff. Entries received after the cutoff stay out even when backdated, and a definition with a changed window advances past an obsolete scheduled cutoff. Operators review and approve delivery in Console or with `management.leaderboards`, where the approval reason remains visible, and can correct an individual winner through completion revocation without awarding that settlement again.
987
+ - 6c30583: Add member-only activity pages, `me.activity.observe()` and `useActivity({ onEvent })` for committed asynchronous outcomes. Cursor reads preserve concurrent commits, and observers pause while hidden and reset on session changes. Events begin after the additive activity-feed migration; existing snapshots remain authoritative for earlier state.
988
+ - 6c30583: Expose participant earned rewards through `rewards.entitlements()` and `useEntitlements()`, and include entitlements in experience reads. Add `rewards.deliveries()` and `useDeliveries()` for points, tiers and integration deliveries. Preserve unknown delivery providers, kinds and states in reads while keeping backend delivery writes validated. Role displays use current reconciliation state after revocation.
989
+
990
+ Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.
991
+
992
+ - 8a8870e: Add points-priced offers with declared stock, participant redemption hooks, automatic or staff-handover fulfillment, and operator cancellation and refund controls.
993
+ - 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).
994
+ - 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.
995
+ - 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).
996
+
997
+ ### Patch Changes
998
+
999
+ - ed4988c: Read Cosmos Hub and Osmosis bank balances with `ctx.wallet.balance()`, pinned to one verified block and the member's wallets on the exact Cosmos network.
1000
+ - e1e701b: Read added trigger and availability fields in participant progress and quest views without weakening publication input validation. Verify unknown delivery providers, actions and statuses through the wire compatibility fixtures.
1001
+
1002
+ ## 1.0.0-next.33
1003
+
1004
+ ### Minor Changes
1005
+
1006
+ - 89ea6ba: Add reviewed live project publication. The management client can list deployment records, submit and reject previews, promote a published test deployment, and prepare a rollback to the previous live deployment. Promotion and rollback accept an action UUID, and the management client generates one when omitted so retries return the same deployment record. `domino deploy --environment live` now returns `status: "pending"`, and `--promote` submits test source for live review; only a person in Console can approve, reject, or prepare rollback. The new `pending` deploy status is a breaking addition for exhaustive automation. See [Live deploys return a pending review](https://console.domino.run/docs/reference/upgrade-to-v1#live-deploys-return-a-pending-review).
1007
+
1008
+ ## 1.0.0-next.32
1009
+
1010
+ ### Minor Changes
1011
+
1012
+ - 6c30583: Add member-only activity pages, `me.activity.observe()` and `useActivity({ onEvent })` for committed asynchronous outcomes. Cursor reads preserve concurrent commits, and observers pause while hidden and reset on session changes. Events begin after the additive activity-feed migration; existing snapshots remain authoritative for earlier state.
1013
+ - 6c30583: Expose participant earned rewards through `rewards.entitlements()` and `useEntitlements()`, and include entitlements in experience reads. Add `rewards.deliveries()` and `useDeliveries()` for points, tiers and integration deliveries. Preserve unknown delivery providers, kinds and states in reads while keeping backend delivery writes validated. Role displays use current reconciliation state after revocation.
1014
+
1015
+ Keep private operator revocation reasons out of participant reward, progress and experience reads while retaining visible collection recovery state.
1016
+
1017
+ ## 1.0.0-next.31
1018
+
1019
+ ### Minor Changes
1020
+
1021
+ - 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.
1022
+
1023
+ Treat forbidden Discord membership reads as permanent delivery failures so operators can fix permissions and retry immediately.
1024
+
1025
+ Publish initial pending delivery events in the grant transaction so observers can see queued rewards before reconciliation starts.
1026
+
1027
+ ## 1.0.0-next.30
1028
+
1029
+ ### Minor Changes
1030
+
1031
+ - afe1128: Operators can revoke an accepted completion with a reason: `management.completions.revoke({ completion, reason, freeOccurrence })` (`POST /management/v1/completions/<id>/revoke`, `review` permission). In one transaction the completion stops counting (progress, prerequisites of new attempts, counts, profiles and referral qualification), its point awards are reversed as far as the member's available points allow, tiered rewards it granted are voided or lowered to the tier another completion grants (collected ones are flagged), Discord role grants not yet delivered are stopped while delivered roles are flagged for the operator to remove in Discord (Relay never removes roles; automatic removal is DOM-99), referral programs that qualified on it are re-evaluated, and a streak milestone it paid is revoked while its days stay. `freeOccurrence` lets the member complete the quest again for the same completion key; by default later attempts for it are rejected. The result reports each part of the cascade. API keys with `review` permission can revoke in live. Console offers it on member detail and accepted attempts, and the attempt history and Audit history show the revoke and its reason. `domino capabilities --json` now marks `completion-revoke` as native.
1032
+
1033
+ Breaking: a reward delivery's `action` is an open string (`RewardDeliveryAction` lists `"grant-role"`), the worker fails a delivery whose action it does not know, and a grant still pending at the revoke fails. The revoke result's `outcome` fields are open strings with known values in `RevokedEntitlementOutcome`, `RevokedDeliveryOutcome` and `RevokedReferralOutcome`. To upgrade, see [Reward delivery actions are open strings](https://console.domino.run/docs/reference/upgrade-to-v1#reward-delivery-actions-are-open-strings).
1034
+
1035
+ Revoking preserves percentage bonuses from other valid completions and queues a new automatic attempt when a freed occurrence still meets its prerequisites. Delivery responses add optional `dispatchedAt` and `revokedAt` fields. Dispatch history survives retries, revoke fences failed generations, and late provider success stays visible without reopening delivery. Console uses the revoke field to stop retries and shows unknown delivered actions by their generic status.
1036
+
1037
+ Revocation refuses missing or unreadable grant history before changing member state. Collected entitlements carry a persistent recovery flag in ordinary reads and Console. Correcting collection clears the flag and voids an unearned reward instead of reopening it; a surviving grant reopens only its earned tier. Reused-completion attempt timelines include the shared completion’s revoke event.
1038
+
1039
+ ## 1.0.0-next.29
1040
+
1041
+ ### Minor Changes
1042
+
1043
+ - 67d0d8c: Add bounded `decision.defer` and absolute `decision.progress` results, fresh evaluation runs scheduled by durable timers, participant progress and deferred status, and consistent completion-key authoring types. Reviewers can reject a deferred attempt to stop it.
1044
+
1045
+ Attempt response statuses accept future server values; use known `AttemptStatus` constants with a fallback. New manual submissions while deferred return `409 attempt-pending`. Custom decision/status switches must handle the new variants. See [Handle deferred evaluator decisions and progress](https://console.domino.run/docs/reference/upgrade-to-v1#handle-deferred-evaluator-decisions-and-progress) for old and new code, capture semantics, and search patterns. This ships in the `1.0.0-next` prerelease series.
1046
+
1047
+ - d3b6261: Restrict personal ingestion keys to selected custom trackers, inspect and edit their scope without changing the secret, and return TRACKER_SCOPE_DENIED for disallowed writes. Human CLI sign-ins can manage their own keys; project-scoped playground sessions and API keys cannot. Existing unrestricted keys retain project-wide ingestion.
1048
+
1049
+ ## 1.0.0-next.28
1050
+
1051
+ ### Patch Changes
1052
+
1053
+ - 83c4426: Update Discord message tracker records when a message is edited or deleted. Turning text off or deleting a message now erases retained message text.
1054
+
1055
+ ## 1.0.0-next.27
1056
+
1057
+ ### Minor Changes
1058
+
1059
+ - 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.
1060
+
1061
+ ## 1.0.0-next.26
1062
+
1063
+ ### Minor Changes
1064
+
1065
+ - 8c06912: Add operator reads and audited unlink for current X and Discord quest accounts. Reviewers provide the subject and selection revision to prevent a stale review from unlinking a newer connection. Identity ownership, sign-in grants and existing rewards remain unchanged. Console exposes the action on each member's Quest accounts section.
1066
+
1067
+ ## 1.0.0-next.25
1068
+
1069
+ No changes in this release.
1070
+
1071
+ ## 1.0.0-next.24
1072
+
1073
+ ### Major Changes
1074
+
1075
+ - 2528cec: Every tracker trigger shares the admission rules of ADR 0006: enrollment, the earliest activity counted, and who releases historical activity, with skips decided again when the member event that clears them happens.
1076
+
1077
+ - **Admission options on `automatic({ tracker })`.** `enrollment: "required"` counts only members who enrolled in the quest, and `ctx.tracker.enrolledAt` carries their enrollment time. `since` is `"activation"` (the default), `"window"`, or, with required enrollment, `"enrollment"`. `historical` is `"staff"` (the default), `"claim"` or `"automatic"`.
1078
+ - **Historical activity waits.** A record from before the member linked the account it pays, or with an unknown link time, is skipped as `historical` until a reviewer retries it, unless the quest sets `historical: "claim"` or `"automatic"`. Before, it was admitted. See [Tracker triggers hold activity from before an account was linked](https://console.domino.run/docs/reference/upgrade-to-v1#tracker-triggers-hold-activity-from-before-an-account-was-linked).
1079
+ - **Skips are decided again by member events.** Linking an account, removing a conflicting link, joining the project and enrolling decide the matching skips again, through a durable workflow.
1080
+ - **Every selected observation has one outcome.** New skip codes `outside-window`, `future-record`, `already-completed`, `not-enrolled`, `before-enrollment` and `historical`; `TrackerSkipCode` is an open string, with `KnownTrackerSkipCode` for the codes this SDK knows. A prerequisite completed after the record occurred no longer counts for it.
1081
+ - **Participant APIs.** `relay.quests.enroll({ actionId, quest })`, `relay.quests.outcomes({ quest?, state?, code?, cursor? })` and `relay.quests.claim({ actionId, quest, tracker, record, observedAt })`, and `useQuestOutcomes()` in React. `relay.content.enroll` and `management.content.enroll` are replaced by `relay.quests.enroll` and `management.quests.enroll`. See [Enroll in quests with `relay.quests.enroll`](https://console.domino.run/docs/reference/upgrade-to-v1#enroll-in-quests-with-relayquestsenroll).
1082
+ - **Console.** A tracker-triggered quest shows its enrollment, `since` and historical policy, a `not-enrolled` skip offers **Enroll the member**, and a member's claim shows as **Claimed**.
1083
+
1084
+ **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.
1085
+
1086
+ ## 1.0.0-next.23
1087
+
1088
+ ### Major Changes
1089
+
1090
+ - 2149973: Read published trackers with future source kinds without rejecting a tracker list. Preserve unknown source payloads and expose `isKnownTrackerSource` for supported-source narrowing. Tracker authoring remains closed. Update code that assumes every response source is supported as described in the [v1 upgrade guide](https://console.domino.run/docs/reference/upgrade-to-v1#handle-future-tracker-sources).
1091
+
1092
+ ## 1.0.0-next.22
1093
+
1094
+ ### Major Changes
1095
+
1096
+ - 4626004: Identity kinds are open. Besides `wallet`, `x`, `discord` and `member`, a custom tracker can declare your app's own kinds, `app:<name>` such as `app:telegram`, and records name your app's user IDs. An identity integration created with `identityKind: "app:<name>"` (set only at creation, kept by rotation) links each subject it signs in to the member as an identity of that kind, and tracker triggers attribute records through it, with `{ provider: "app:<name>", subject, revision, linkedAt }` as `ctx.tracker.attribution.account`.
1097
+
1098
+ A kind belongs to the integration that first claimed it, also after a disconnect, which keeps its links attributing. A new integration takes a kind that has identities only by adopting them: `adoptIdentitiesFrom` (the old integration's ID, from the new `management.identityIntegrations.kinds()`) with a `reason` the audit log keeps; otherwise it is refused with 409. Reviewers unlink and relink a member's app identity (`management.members.unlinkIdentity` and `relinkIdentity`, and Console's **Tracker identities**), each audited with a reason: unlinking stops attribution, ends the member's sessions for that subject, and refuses its sign-in with 409 `APP_IDENTITY_UNLINKED`, which `createParticipantBackend` passes on, until it is relinked. A subject the integration already signs in as another member answers 409 `APP_IDENTITY_CONFLICT`.
1099
+
1100
+ Evaluators read the member's selected X and Discord accounts and app identities with `ctx.member.identities()`, pinned once per attempt and `null` when there are none. Management member reads return them as `identities`, and Console lists them on the member under **Tracker identities**.
1101
+
1102
+ Published tracker definitions in status, list, record-page, summary, and deployment responses use `trackerDefinitionResponseSchema` and `TrackerDefinitionResponse`. Authoring keeps the strict `trackerDefinitionSchema` and `TrackerDefinition`.
1103
+
1104
+ Responses and evaluator contexts read identity kinds as an open set, `OpenIdentityKind`, and ignore fields they do not know, so a kind a later Relay adds no longer fails them.
1105
+
1106
+ **Breaking:** `IdentityKind` is no longer a closed union, `identityKindSchema` is no longer a `z.enum`, response kinds are `OpenIdentityKind`, `trackerRecordSchema` is a separate response schema, `trackerAttributionSchema`'s account is a plain union with app and generic variants, and `ctx.member` is `{ id, identities() }`. See [Identity kinds are open, and evaluators read `ctx.member.identities()`](https://console.domino.run/docs/reference/upgrade-to-v1#identity-kinds-are-open-and-evaluators-read-ctxmemberidentities).
1107
+
1108
+ ## 1.0.0-next.21
1109
+
1110
+ ### Minor Changes
1111
+
1112
+ - ddfc526: The participant client, the management client and the CLI share one set of deadline, cancellation and retry rules.
1113
+
1114
+ - 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.
1115
+ - A call is sent again, at most three times in all, only when the server applies a repeat at most once: reads; participant writes with an action ID, submission evidence by its upload ID, `wallets.verify` (by its challenge) and `wallets.challenge`, but not avatar uploads, whose upload ID has no receipt; typed management writes whose input has an `actionId`; and the CLI's `deploy` publication, `stage` build request and `quests archive`/`unarchive`. Every attempt sends the same payload and session. Retries follow a lost answer, including a body that breaks off after successful headers, and HTTP 500, 502, 503 and 504, with a short jittered backoff. Invalid or unexpected answers, cancellations, rejected input, other refusals and every other write are sent once.
1116
+ - A 429 is retried only when its `Retry-After` fits in the time left, and every wait honours `Retry-After`. The new `retryAfter` field of `RequestError` (and so `RelayError` and `ManagementError`) carries the wait the server asked for, in milliseconds; the CLI reports it as `details.retryAfter` on `RATE_LIMITED` and `SERVER_ERROR`.
1117
+ - A session-bound participant call that reads the current session first shares one deadline with that read, and `auth.session()` honours `timeout`.
1118
+ - An error status whose body breaks off, times out or is cancelled is now a `network`, `timeout` or `aborted` failure that keeps its `status` and `retryAfter`, in all three clients; an error body that is not JSON stays `http`.
1119
+ - Cancelling with `signal` stops a retry wait at once. A write any attempt of which may have been applied stays `uncertain`, with its `operationId` and `recovery`, whatever later attempts answer.
1120
+ - `management.request` retries only GET, and `management.raw` sends once. `useInvitation()` keeps a rate-limited code for a retry instead of discarding it as refused.
1121
+
1122
+ Avatar failures report `recovery: "reconcile"` even when they have an upload ID. Wallet address and signature waits reject when the caller cancels. See [Bounded requests and avatar recovery](https://console.domino.run/docs/reference/upgrade-to-v1#bounded-requests-and-avatar-recovery) for the recovery-handler change.
1123
+
1124
+ Generic management writes report `recovery: "reconcile"` even with a caller-supplied action ID. Uncertain `domino api` writes expose `details.recovery: "reconcile"`. See [Generic management writes require reconciliation](https://console.domino.run/docs/reference/upgrade-to-v1#generic-management-writes-require-reconciliation) for the recovery-handler change.
1125
+
1126
+ ## 1.0.0-next.20
1127
+
1128
+ No changes in this release.
1129
+
1130
+ ## 1.0.0-next.19
1131
+
1132
+ ### Patch Changes
1133
+
1134
+ - 1141af4: Fix invalid action IDs in the tracker and leaderboard documentation examples. Validate action ID literals during documentation checks so copied examples use the identifier format the API accepts.
1135
+
1136
+ ## 1.0.0-next.18
1137
+
1138
+ ### Minor Changes
1139
+
1140
+ - 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`.
1141
+
1142
+ 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).
1143
+
1144
+ ## 1.0.0-next.17
1145
+
1146
+ ### Major Changes
1147
+
1148
+ - c919948: Follow-ups to the React hooks' shared query layer.
1149
+
1150
+ - `completionCountLabel(view)` is renamed `completionCountText(view)`, so the name says it returns English like `questStatusText`. For your own words, read `view.completionCount`.
1151
+ - `questStatus(view)` returns `QuestStatus`, whose `code` is the open `QuestStatusCode`, and `roleDeliveryStatus(deliveries, quest)` returns the open `RoleDeliveryStatus | null`. A `switch` that lists every code needs a `default`, so a newer SDK can add codes without breaking the signature.
1152
+ - `useReferralSummary({ intervalMs })` reads the summary again every 60 seconds by default while the page is visible, so friends who qualify show up without an app-side timer.
1153
+ - `useSignOut()` drops the signed-out member's cached values as soon as Relay confirms the sign-out, even when the next session read fails.
1154
+ - A `useMyProfile()` change that answers after another member signed in no longer writes into the new member's cache.
1155
+ - `useSignIn`, `useSignOut`, `useSignInOptions`, `useInvitation` and `useQuest` return objects and actions that keep their identity until something in them changes; `useQuest`'s actions act on the latest view. `useExperience().deliveries` is a `readonly RoleDelivery[]`, and keeps one frozen empty list while there are none.
1156
+ - `progressOf(quests)` is documented: `total` counts locked and paused quests and leaves out archived and repeatable ones.
1157
+
1158
+ Upgrade: [React hooks share one cache and return one state shape](https://console.domino.run/docs/reference/upgrade-to-v1#react-hooks-share-one-cache-and-return-one-state-shape) in the SDK v1 upgrade guide.
1159
+
1160
+ ## 1.0.0-next.16
1161
+
1162
+ ### Major Changes
1163
+
1164
+ - bdb89a4: Evaluator readers follow one rule: a missing subject is `null`, a failure throws `CapabilityError`, linked accounts are pinned once per attempt, and tracker reads are point in time. This applies to every evaluation Domino runs, including releases published with earlier SDK versions.
1165
+
1166
+ - **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).
1167
+ - **Failures throw `CapabilityError`**, new in `@domino-sdk/relay/authoring` (its wire shape, `capabilityFailureSchema`, is in `@domino-sdk/relay/schema`), with `code` (`"not-linked"`, `"provider-unavailable"`, `"author-error"`) and `retryable`. Invalid capability input is `author-error`; an image check of `{ x: "profile" }` without a linked X account is `not-linked`. Failures that are not retryable are captured and replay. A retryable failure that quest code handles and moves past is final for the attempt; one that escapes the evaluation is released, along with every fact captured after it, and retried, so the retry asks the provider and the later calls again. Rethrow a caught failure, or wrap it with `{ cause: error }`, to let it be retried. The failure shape is open: a newer Relay can add codes, and the SDK treats an unknown code as not retryable. See [Capability failures throw `CapabilityError`](https://console.domino.run/docs/reference/upgrade-to-v1#capability-failures-throw-capabilityerror).
1168
+ - **Tracker reads are point in time.** `ctx.trackers.records()` and `summary()` take `asOf`, defaulting to `ctx.eligibleAt`, and every tracker read of an attempt sees the observations stored at its first tracker read. Pass `asOf: ctx.now` to count everything stored by then. See [Tracker reads are point in time and pinned per attempt](https://console.domino.run/docs/reference/upgrade-to-v1#tracker-reads-are-point-in-time-and-pinned-per-attempt).
1169
+ - **Linked accounts stay pinned.** X and Discord readers pin the account at the attempt's first read and never recheck it, as wallets already did, so a link change no longer fails the attempt with "X account changed" or "Account or integration changed". See [Linked X and Discord accounts stay pinned for the attempt](https://console.domino.run/docs/reference/upgrade-to-v1#linked-x-and-discord-accounts-stay-pinned-for-the-attempt).
1170
+
1171
+ **Who should act.** Evaluators that read `ctx.x`, `ctx.discord`, `ctx.wallet` or `ctx.trackers`, or that match capability error messages. The changed return types point TypeScript at each read to update. The `discord-membership`, `token-holder` and `community-screenshot` snippets and the Discord starter's `join-the-server` quest are updated.
1172
+
1173
+ ## 1.0.0-next.15
1174
+
1175
+ ### Major Changes
1176
+
1177
+ - e1cba88: Observations are removed, and every automatic activity arrives as a tracker record. `automatic({ source, type })`, `ctx.observation`, `management.observations.submit`, `POST /management/v1/observations`, the `Observation` type, `observationSchema`, `observationInputSchema`, `observationReceiptSchema`, `observationTriggerSchema`, `discordReactionSchema`, `QuestView.trigger.observation` (and the release trigger's `observation`) and the `observation` field of the `automatic` availability are gone. `questStatus` and `runsSilently` from `@domino-sdk/relay/react` now label every tracker-triggered quest "Automatic", not only observation and keyed quests. A server that reported observations sends records to a custom tracker with `management.trackers.ingest` and triggers quests with `automatic({ tracker })`. Ingestion never joins a member to a project: a tracker trigger skips a member who has not joined with `not-joined`. Quests published with an observation trigger leave the active set when Relay upgrades. Republish them from code on a tracker; the quest key can stay the same.
1178
+
1179
+ Discord messages and reactions are two managed tracker sources, `discord.messages()` (with `{ text: true }` to store message text) and `discord.reactions()`, on the `discord` object from `@domino-sdk/relay/authoring`. Domino's Discord gateway writes each new message or reaction into every tracker of that kind in the projects connected to the server, and keeps a record only when one of its accounts is a Discord account a member has linked. Their record data is typed (`DiscordMessageData`, `DiscordMessageTextData`, `DiscordReactionData`), so `where` filters and `ctx.tracker.record.data` are checked. A reaction record names the message's author as the `messageAuthor` account: `automatic({ tracker: reactions, beneficiary: "messageAuthor" })` replaces `reaction.received`, and Relay skips reactions to one's own message with `beneficiary-is-author`. Discord trackers refuse ingestion (403) and refresh (422). Streaks work on these triggers.
1180
+
1181
+ The `observe` permission is removed. Ingesting needs `ingest`, and enrolling a member from a server (`management.content.enroll`) needs the new `enroll` permission. Editors hold `read`, `publish`, `inventory`, `points`, `ingest` and `enroll`; hosted-app keys hold `read`, `points` and `ingest` and cannot enroll. A personal API key issued with `observe` keeps its other permissions, and `observe` grants nothing: reissue it with **Ingest** or **Enroll**. The `observation-quests` capability is removed from `capabilities.json`; Discord tracker triggers are part of the `trackers` capability.
1182
+
1183
+ To upgrade, see [Observation triggers become tracker triggers](https://console.domino.run/docs/reference/upgrade-to-v1#observation-triggers-become-tracker-triggers) and [The `observe` permission is replaced by `enroll`](https://console.domino.run/docs/reference/upgrade-to-v1#the-observe-permission-is-replaced-by-enroll).
1184
+
1185
+ ## 1.0.0-next.14
1186
+
1187
+ ### Major Changes
1188
+
1189
+ - c2390b3: The React hooks in `@domino-sdk/relay/react` share one query cache and return one state shape.
1190
+
1191
+ - Every read hook returns `{ status, data, error, refresh }`: `status` is `loading`, `ready` or `error`, plus `signed-out` for hooks that need a member; `data` holds the value; `error` is always a `RelayError`, and while `ready` it says why the latest refresh failed and the last value stays. `useMe` and `useSignInMethods` return this shape instead of a bare value, `useMe` no longer swallows errors, and the profile hooks report the uniform 404 as `error` with status 404 instead of `missing`.
1192
+ - `<RelayProvider>` keeps one keyed cache. Components that read the same thing share one request. A read that is replaced, invalidated, or belongs to a previous participant or client is cancelled, and its late answer is dropped. Polling, including the quest experience observer and the Discord role-delivery watcher, pauses while the tab is hidden and catches up when it is shown. Reads wait for the first session read, public ones included, and a sign-in attempt that does not change the session drops nothing.
1193
+ - Changes invalidate what they affect: every new completion the experience shows, submitted or automatic, reads balances, point history, leaderboards, profiles and referral progress again; an accepted invitation, the referral summary and referral progress; wallet, account and profile changes, their own reads. The `PROFILE_CHANGED` window event is removed. `useInvalidate()` and `relayKeys` invalidate by key prefix after a change made with `useRelay()`.
1194
+ - The cache is scoped to the client, organization, project, environment, member and session. Signing in or out or switching accounts, projects or environments drops it, so one member never sees another's cached values. A new `client` or `baseUrl` passed to `RelayProvider` now takes effect: the old observer stops, reads in flight are cancelled, and the hooks start from an empty cache.
1195
+ - `useSession()` is new: who is signed in, independent of the experience read, so a failing `/me/experience` no longer blanks the profile, wallet, account and `useMe` hooks. `useExperience()` returns the shared shape with `data.quests` and `data.collections` and `deliveries`; its `member` moved to `useSession()`. `Experience` is replaced by `ExperienceData`, which `questGroups` takes.
1196
+ - `useBalances()` returns the `{ items }` answer in `data`, and `usePointHistory(ledger)` returns `data.items` with `loadMore()`; an unknown ledger or a member who has not joined is `error` with code `point-ledger-not-found` or `not-joined` instead of the `missing` and `not-joined` statuses. `BalancesState` and `PointHistoryState` are replaced by `MemberQuery` and `PagedQuery`.
1197
+ - `useRelayQuery`, `useMemberQuery`, `useRelayPages` and `useMemberPages` put an app's own reads on the same cache. `Page` is the root package's type; `PagedQuery` (public pages) and `MemberPagedQuery` (member pages) add `loadMore`, `loadingMore` and `loadMoreError`, which belong to one key and participant.
1198
+ - Every error the hooks report is a `RelayError`: `useQuest().error`, `useSignIn().error`, `useSignInOptions().error` and `methodsError`, `SignInMethods.error` (also from `signInOptions()` in `@domino-sdk/relay/browser`), and a rejected or failed invitation's `error`, which replaces `reason`.
1199
+ - `completionMark`, `hasPending`, `sanitizeDeliveries`, `watchRoleDeliveries`, `PENDING_POLL_MS`, `RETRY_DELAYS_MS`, `runsSilently` and `visibleQuests` are no longer exported from `@domino-sdk/relay/react`.
1200
+ - `questStatus(view)` returns `{ tone, code, reason? }`, a stable code to localize (`QuestStatusCode`, an open string like `RelayErrorCode`); `questStatusText(view)` adds the English `label` and `detail`. `roleDeliveryStatus(deliveries, quest)` returns `pending`, `failed`, `granted` or null, and `roleDeliveryLabel(status)` gives its English label.
1201
+
1202
+ Upgrade: [React hooks share one cache and return one state shape](https://console.domino.run/docs/reference/upgrade-to-v1#react-hooks-share-one-cache-and-return-one-state-shape) in the SDK v1 upgrade guide maps every hook.
1203
+
1204
+ ## 1.0.0-next.13
1205
+
1206
+ ### Major Changes
1207
+
1208
+ - 23c85ba: `defineEngagementReward` takes `budgets`, a list of `{ scope: "project" | "member", period: "day" | "week" | "month" | "lifetime", limit }`, instead of `budget: { projectDaily, memberDaily, projectLifetime }`. Periods are those of the project calendar at settlement time, so a rule can now cap weekly or monthly payouts, and accounts settle once per project calendar day instead of once per UTC day. A definition with `budget` fails validation; rules already published keep their limits in the new form.
1209
+
1210
+ See [Engagement budgets are a list of periods](https://console.domino.run/docs/reference/upgrade-to-v1#engagement-budgets-are-a-list-of-periods) in the v1 upgrade guide.
1211
+
1212
+ - 23c85ba: Each project environment has one project calendar: a timezone, UTC until you choose one, that sets where every day, week and month begins. Streak days, quest budgets, engagement budgets and calendar leaderboard windows all follow it, so a weekly budget and a weekly leaderboard cover the same week.
1213
+
1214
+ - Set it with `"calendar": { "timeZone": "Europe/Budapest" }` in relay.json, in Console **Settings → General → Project calendar**, or with the new `management.calendar.read()` and `management.calendar.configure({ timeZone, reason })` (`GET` and `POST /calendar`, `publish` permission; in live only from Console). A change takes effect at the next midnight in the current timezone: the open day, week or month finishes in the old zone, and streaks stay consecutive. A deployment applies relay.json's timezone only when it differs from the last one deployed, and the deployment preview's new `calendar` field shows the change it schedules.
1215
+ - `streak()` and calendar leaderboard windows no longer take a `timeZone`; both throw if given one. Streaks and boards already published with one still read.
1216
+ - Budgets reset at midnight in the project timezone instead of UTC, and accept `period: "month"`.
1217
+ - One validator, `timeZoneSchema`, accepts named IANA zones only. Fixed offsets such as `+02:00` and `Etc/GMT-2` are rejected, including in leaderboards, which used to accept them. The shared `calendarPeriodSchema` is `lifetime | day | week | month`.
1218
+
1219
+ **Who should act.** Projects that pass `timeZone` to `streak()` or a calendar leaderboard window, and projects that rely on budgets resetting at UTC midnight. See [Set the timezone once, on the project calendar](https://console.domino.run/docs/reference/upgrade-to-v1#set-the-timezone-once-on-the-project-calendar) in the v1 upgrade guide.
1220
+
1221
+ ## 1.0.0-next.12
1222
+
1223
+ ### Minor Changes
1224
+
1225
+ - 710e520: Participants read their own point balances and history. `relay.me.balances()` (`GET /v1/me/points`) returns `{ items }`: the signed-in member's `balance` and `available` points (after reservations) in every ledger of the project, each with its `ledger` reference. `relay.me.pointHistory({ ledger, cursor? })` (`GET /v1/me/points/:ledger/history`) pages through the member's own entries in one ledger, by key or ID, most recently recorded first, as `{ items, nextCursor }`. Each entry is `{ id, amount, kind, at, quest, reverses }`, without the operator's reason or actor. `kind` is an open string; the `PointEntryKind` constants (`award`, `spend`, `adjust`, `reverse`) name the kinds this SDK knows, and referral payouts are `adjust`. An unknown ledger answers 404 with code `point-ledger-not-found` and a member who has not joined the project 404 with `not-joined`, both added to `RelayErrorCode`. Neither read takes a member, and both are scoped to the session's project and environment. React apps use `useBalances()`, which reads again after a quest completes, and `usePointHistory(ledger)`. The participant proxy and identity integrations admit both routes. New schemas and types: `pointBalanceSchema`, `pointBalancesSchema`, `pointEntrySchema`, `pointHistoryQuerySchema`, `pointHistoryPageSchema`, `PointBalance`, `PointBalances`, `PointEntry`, `PointEntryKind`, `KnownPointEntryKind`, `PointHistoryQuery` and `PointHistoryPage`.
1226
+
1227
+ The classic starter's header now reads the balance with `useBalances()` instead of its `/api/me/points` server route, which is gone.
1228
+
1229
+ ## 1.0.0-next.11
1230
+
1231
+ ### Minor Changes
1232
+
1233
+ - f2f1b75: Breaking: the management client moves from `@domino-sdk/relay-cli` to the new `@domino-sdk/relay/server` entrypoint, with `createManagementClient`, `hostedManagementClient`, `ManagementError`, the management schemas and types, and `starterTemplates`. Server code, including a hosted app's Worker, now needs only `@domino-sdk/relay`, without the CLI's command-line parser and TypeScript compiler. `@domino-sdk/relay-cli` is only the `domino` executable and its agent skills; it has nothing to import. `serverBoundary()` also fails a browser build that imports `@domino-sdk/relay/server`. To upgrade, see [Import the management client from `@domino-sdk/relay/server`](https://console.domino.run/docs/reference/upgrade-to-v1#import-the-management-client-from-domino-sdkrelayserver).
1234
+
1235
+ The capability catalog (`domino capabilities --json`) adds staff confirmation, staff handover, stock, point corrections, deploy and stage, operator member reads and lookup, project access, and the management API with `domino api`. It marks webhooks, bulk data export, completion revocation and tracker-scoped keys as unsupported, and lists the management methods it leaves out on purpose in `excludedManagementMethods`.
1236
+
1237
+ ### Patch Changes
1238
+
1239
+ - 909f343: The browser submission store recovers from storage failures instead of caching them. A temporary IndexedDB opening error, a blocked open, a `versionchange` from another tab, or a connection the browser closes no longer fails every later call on the same client: the next `status()`, `resume()` or `submit()` opens storage again, and the saved operation and its operation ID are kept. A blocked open waits briefly for the other tab, then rejects with `submission-storage`, and the pending request is adopted if it later succeeds. Full browser storage rejects with the new `RelayErrorCode.SubmissionStorageQuota` (`submission-storage-quota`); an upgrade by a newer page asks for a reload. Both are `client` errors that are not `uncertain`, and a failed read never looks like an empty store, so a new claim is never started over a saved one. See [Storage failures](/docs/reference/participant-api#storage-failures) in the participant API reference.
1240
+
1241
+ ## 1.0.0-next.10
1242
+
1243
+ No changes in this release.
1244
+
1245
+ ## 1.0.0-next.9
1246
+
1247
+ ### Minor Changes
1248
+
1249
+ - cf5b02c: Custom tracker records have a declared data schema. `customSource(namespace, { data })` requires a zod object schema (`z` is exported by `@domino-sdk/relay/authoring`), stores its JSON Schema form with the tracker, and throws, naming the field, for what ingestion cannot check (string formats and patterns, tuples, refinements, transforms, dates, bigints). Publication refuses a custom tracker without one. Ingestion rejects a batch whose record data does not match with 422 and `{ error, code: "invalid-record-data", record, path, reason }`, one too costly to validate with `record-data-too-complex`, and records for a tracker published without a schema with `tracker-schema-required`. Schemas are bounded (32 KB, 16 levels, 256 properties, required keys, enum values or key names per node, 16 branches per union, no wide union nested in another union's branch), and validation charges every unit of work to a per-record and per-batch step budget. A deployment whose new schema breaks a live quest's `where` filter, Console quests included, is refused. Stored records are not rechecked: tracker triggers skip non-matching records with the new skip code `invalid-record-data`, evaluators reject pinned records that do not match the schema the quest was built with, and `trackers.records(handle)` leaves them out and counts them in `excluded`. The schema can change under the same tracker key and applies to later records.
1250
+
1251
+ The data type flows into quests: `ctx.tracker` is non-null in a tracker-triggered quest's evaluator with `record.data` of the tracker's type (and `ctx.content` and `ctx.observation` are non-null for the triggers that supply them, `null` otherwise), `trackers.records(tracker)` returns typed records, and `where` paths, `equals` values and `{ setting }` bounds are type-checked against the data and the quest's integer settings. `where` also accepts a list of conditions that must all hold, and `automatic()` and publication check filter paths against the stored schema. `settings.object` and `defineQuest` accept only `settings.*` builders; a plain zod setting is a type error and `defineQuest` throws `Setting "<key>" is not a quest setting`.
1252
+
1253
+ See [Custom sources declare their record data](/docs/reference/upgrade-to-v1#custom-sources-declare-their-record-data) and [Quest settings come only from settings builders](/docs/reference/upgrade-to-v1#quest-settings-come-only-from-settings-builders) in the upgrade guide.
1254
+
1255
+ ## 1.0.0-next.8
1256
+
1257
+ ### Major Changes
1258
+
1259
+ - 54fcf41: The participant client follows one convention for paging, sessions and errors.
1260
+
1261
+ - Every method takes an optional last argument, `{ session?, signal? }`. `session` replaces the positional session argument of `wallets.challenge`, `wallets.verify` and `me.collectionPass`. Calls Relay binds to a session (wallet linking, collection passes, `quests.retry`) read the current session when `session` is omitted.
1262
+ - Every paged read takes `{ cursor? }` and returns `{ items, nextCursor }`: `referrals.history`, `content.admissions`, `content.qualifications`, `content.performance`, `engagement.page` and `engagement.boundaries` changed from `before`, `after` and `next`. The participant routes answer `nextCursor` and read `cursor`, and so do the management routes and client methods that share them (`management.referrals.history(member, { cursor })`, `management.content.*`, `management.engagement.page` and `boundaries`).
1263
+ - Every rejection is a `RelayError`, which extends the new shared `RequestError`: `kind` (`http`, `network`, `timeout`, `aborted`, `invalid-response`, `client`), `uncertain` (true only for a write that may have taken effect: a failure after sending, a cut-off body, a 500, 502 or 504, or an unreadable success answer), `status` whenever response headers arrived, an open string `code` (known ones in `RelayErrorCode`), `operationId`, and `recovery` (`replay` with an `operationId`, else `reconcile`) when uncertain. `isRelayError` and `isRequestError` recognize these errors across duplicate copies of the package. `RelayError.status` is now `number | undefined`. Invalid input rejects with `invalid-input` instead of a `ZodError`, a success answer this SDK cannot read with `invalid-response`, a body cut off after its headers with kind `network` and its status, and a wallet signer's own failure with `wallet-signer`. The error body is read field by field, so Relay's message and a code the SDK does not know are always kept. Relay now sends `code: "not-signed-in"` and `code: "stale-session"` on those participant errors, and every paged read answers a malformed cursor with 400, code `invalid-cursor`, and one message.
1264
+ - Removed: `submit`, `retry(id)`, `progress(member)`, `upload(member, file)`, `auth.connections`, `me.linkPass`, the `wallet.*` namespace, and the staff calls the participant API refused (`lookupMember`, `snapshot`, `publish`, `publishBatch`, `beginHandover`, `confirmHandover`, `decide`, `journeyTemplate`, `template`).
1265
+ - Parameters take Zod input types, so defaulted fields are optional, and cursors are opaque strings the server validates. The session the client returns (`ParticipantSession`) accepts fields and principal kinds a newer Relay adds; `sessionMember(session)` reads its member.
1266
+ - Hosted proxies no longer forward the 0.x single-wallet routes (`/v1/me/wallet`, `/challenge`, `/verify`).
1267
+
1268
+ Upgrade: [One paging, session and error convention in the participant client](https://console.domino.run/docs/reference/upgrade-to-v1#one-paging-session-and-error-convention-in-the-participant-client) in the SDK v1 upgrade guide maps every method and has search commands.
1269
+
1270
+ ## 1.0.0-next.7
1271
+
1272
+ ### Major Changes
1273
+
1274
+ - 1084288: `@domino-sdk/relay` now exports only the participant runtime client: `createRelayClient`, `RelayError`, `QuestUpdatedError`, `formatUnits`, `parseUnits`, and the types the client returns. Schemas, builders and avatar helpers moved to their own entry points, a few implementation helpers are no longer public, and two are no longer exported at all, so an app that imports the client no longer loads authoring builders, deployment and management schemas, or DiceBear.
1275
+
1276
+ - Zod schemas (and helpers such as `identifier`, `caip10` and `xPostId`) come from `@domino-sdk/relay/schema`.
1277
+ - Builders (`defineTracker`, `defineLeaderboard`, `defineReferral`, `defineEngagementReward`, `content`, `perContent`, `streak`, `settings`, `discord`, `pointLedgers`, `x`, `customSource`) and evaluator reader types (`TrackerReader`, `XReader`, `ImageReader`) come only from `@domino-sdk/relay/authoring`.
1278
+ - `avatarSrc`, `defaultAvatar`, `defaultAvatarSvg` and `walletAvatarSeed` come from `@domino-sdk/relay/avatar`, and no longer from `@domino-sdk/relay` or `@domino-sdk/relay/react`.
1279
+ - No longer public: `identityIntegrationSchema`, `identityIntegrationInputSchema`, `identityIntegrationSecretSchema`, `participantExchangeSchema`, `questConfiguration`, `parseImageResult`, `parseImageExtraction`, `isContentParticipantRoute`, `isProfileParticipantRoute`, `isWalletParticipantRoute` and `isWalletSignInRoute` are exported only from `@domino-sdk/relay/internal`, which Relay's own services share and which is not a supported API.
1280
+ - No longer exported: `questActions` and `reconcileQuestDraft`. Open a quest controller with `relay.quests.open(view)`; its `quest-updated` result already carries the reconciled `draft`.
1281
+ - The package declares `sideEffects: false`.
1282
+ - `esbuild` is an optional peer dependency, needed only by `@domino-sdk/relay/build`. `@domino-sdk/relay-cli` depends on it directly, so `domino build` and `domino deploy` work as before. Code that calls `buildQuest` without the CLI must install `esbuild`.
1283
+
1284
+ Upgrade: [Runtime entry exports only the client](https://console.domino.run/docs/reference/upgrade-to-v1#runtime-entry-exports-only-the-client) in the SDK v1 upgrade guide has the import mapping and search commands.
1285
+
1286
+ ## 1.0.0-next.6
1287
+
1288
+ ### Patch Changes
1289
+
1290
+ - 8b706af: An attempt now records when its evaluator asked for human review on a quest with review off. `decision.review({ reason })` was already a rejection with the review reason in that case. The attempt now also has `requestedReview: true`, and Console attempt detail says "Would have gone to review; human review is off". A reject the evaluator returned itself keeps `requestedReview: false`. The `review` option, the quests concept page and the authoring reference describe it.
1291
+
1292
+ ## 1.0.0-next.5
1293
+
1294
+ No changes in this release.
1295
+
1296
+ ## 1.0.0-next.4
1297
+
1298
+ ### Minor Changes
1299
+
1300
+ - d7f9fe4: `streak(...)` now completes manual quests and tracker-triggered quests (`automatic({ tracker })`), not only automatic observation quests. Each accepted attempt counts the community day of its trusted occurrence time, the time `ctx.eligibleAt` reports: a tracker record observation's `occurredAt` (such as an X post's publication time), or, for a manual quest, when Relay admitted the submission, so a manual streak counts submission days. `defineQuest` no longer throws "Streaks require an automatic observation trigger" or "Tracker triggers complete once() or keyed()", and publication accepts these quests. Content quests and prerequisite-only `automatic()` quests still cannot be streaks, and a streak quest keeps its trigger kind (manual, observation or tracker): changing it needs a new quest key. Once a member's tracker streak milestone pays, the trigger stops admitting that member's observations for the quest, as for a completed `once()` quest. `questStatus` shows a member's own streak submission first while it is checking, in review or failed; a rejected submission keeps the "N / M days" progress and gives the reason as its detail. The new `packages/sdk/examples/posting-streak-quest.ts` pays members who post on X five days in a row. Nothing existing needs to change, so the v1 upgrade guide has no section for this.
1301
+
1302
+ ## 1.0.0-next.3
1303
+
1304
+ ### Major Changes
1305
+
1306
+ - a803b30: Evaluators now replay deterministically. Hosted evaluation freezes the clock for each attempt and seeds randomness from the attempt, so a retried or replayed attempt makes the same decision and `keyed()` completion key. This applies to every evaluation Domino runs, including releases published with earlier SDK versions.
1307
+
1308
+ - `ctx.now` is new: the attempt's evaluation time in epoch milliseconds, the same on every run of the attempt. `keyed()` receives it too. It is distinct from `ctx.eligibleAt`, which is when the activity happened.
1309
+ - `Date.now()`, `new Date()` and `Date()` return `ctx.now` and do not advance, even across awaited capability calls. `performance.now()` returns 0.
1310
+ - `Math.random()`, `crypto.getRandomValues()` and `crypto.randomUUID()` repeat the same sequence for one attempt and differ between attempts.
1311
+
1312
+ **Who should act.** Evaluators that measure elapsed time or rely on unpredictable random values. Use `ctx.now` for "as of evaluation" checks and `ctx.eligibleAt` for when the activity happened. See [Evaluators see a frozen clock and seeded randomness](https://console.domino.run/docs/reference/upgrade-to-v1#evaluators-see-a-frozen-clock-and-seeded-randomness) in the v1 upgrade guide.
1313
+
1314
+ ### Patch Changes
1315
+
1316
+ - 7e37049: The SDK's optional React peer dependency is now `^19.0.0` instead of `>=19`, matching the React versions the React hooks are verified against. React is still optional: the runtime, browser and server entrypoints do not need it. Apps on React 19 need no change.
1317
+
1318
+ ## 1.0.0-next.2
1319
+
1320
+ ### Minor Changes
1321
+
1322
+ - c320586: A content quest's `performance.evaluate` can no longer select rewards. It gates the rewards the content evaluation already selected, and it always did: a `rewards` value in its result was silently ignored. Its type is now `PerformanceOutcome` (`accept`, `review` or `reject` with a reason only), so `decision.accept({ rewards })` and `decision.review({ reason, rewards })` are type errors there, and a runtime result that carries `rewards` fails the evaluation with "A performance evaluator cannot select rewards". To change what a post earns, select rewards in the quest's `evaluate`.
1323
+
1324
+ See [Performance evaluators no longer select rewards](/docs/reference/upgrade-to-v1#performance-evaluators-no-longer-select-rewards) in the upgrade guide.
1325
+
1326
+ - 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)`.
1327
+
1328
+ See [Prerequisites no longer carry a scope](/docs/reference/upgrade-to-v1#prerequisites-no-longer-carry-a-scope) in the upgrade guide.
1329
+
1330
+ ### Patch Changes
1331
+
1332
+ - c320586: `defineQuest` throws at build time for two shapes that only failed when publishing: a bare `automatic()` quest with no `requires` entry, and a bare `automatic()` quest with `keyed()` completion. A bare `automatic()` runs once its prerequisites are complete, so it needs at least one `completed(quest)` and completes once. The backend still checks both when it publishes.
1333
+ - c320586: `manual({ actor: "staff", input })` throws instead of dropping the actor. Its types never allowed the combination, but untyped callers got a quest that members could submit evidence to, not one staff confirm. Staff-captured evidence is not supported: use `manual({ actor: "staff" })` for a staff confirmation, or `manual({ input })` for evidence the participant submits.
1334
+
1335
+ ## 1.0.0-next.1
1336
+
1337
+ No changes in this release.
1338
+
1339
+ ## 1.0.0-next.0
1340
+
1341
+ ### Major Changes
1342
+
1343
+ - 5d4ec3e: Start the SDK v1 prerelease. Relay SDK and CLI v1 is one breaking release, 1.0.0; until it ships, breaking changes publish as `1.0.0-next.N` on the npm `next` dist-tag. `latest` stays on the 0.x release, so `npm install @domino-sdk/relay` and new starters keep using 0.x.
1344
+
1345
+ **Who should upgrade.** Install `@domino-sdk/relay@next` and `@domino-sdk/relay-cli@next` together only to try v1 before it is stable. Prereleases can break each other: pin an exact `1.0.0-next.N` version with `npm install --save-exact @domino-sdk/relay@next @domino-sdk/relay-cli@next` (or `pnpm add -E @domino-sdk/relay@next @domino-sdk/relay-cli@next`), and upgrade the SDK and CLI to the same version.
1346
+
1347
+ **Upgrade steps.** Every breaking change in the prerelease adds a section to the SDK v1 upgrade guide in the Domino docs, and its changelog entry links to that section. Once 1.0.0 is published, stable APIs follow semantic versioning; experimental APIs are only in namespaces marked experimental.
1348
+
1349
+ ## 0.31.1
1350
+
1351
+ No changes in this release.
1352
+
1353
+ ## 0.31.0
1354
+
1355
+ ### Minor Changes
1356
+
1357
+ - 3d0bee0: Tracker triggers are composable primitives: a firing mode, an optional data filter, and the account role a quest pays, over records whose accounts can be any verified identity.
1358
+
1359
+ - `automatic({ tracker, on?, where?, beneficiary? })`. `on` is `"new-record"` (default for X trackers), `"new-observation"` (default for custom trackers) or `"data-changed"` (a new record, or `data` that differs from the previous observation). `where` is one condition on a path in the record's `data`: `{ path, equals }`, or `{ path, gt?, gte?, lt?, lte? }` comparing a number or an integer string exactly, with bounds that are numbers or `{ setting: "name" }` (an integer setting of the quest). Observations the mode or filter does not select record nothing; `ingest` counts those the filter dropped in `admissions.filtered`, present when nonzero.
1360
+ - `beneficiary` is an account role: `"author"` (default), `"inReplyTo"` on X trackers, or a role a custom source declares. A record never pays its author's member through another role: when the beneficiary account is the author's, or both belong to the same member (the same wallet on another chain, or another account the member linked), the observation is skipped with `beneficiary-is-author`.
1361
+ - `customSource(namespace, { identities, accounts? })`: record authors and named accounts are verified identities of the listed kinds, `"wallet"`, `"x"`, `"discord"` or `"member"`. Records carry `author: { id, kind? }` and `accounts: { <role>: { kind?, id } }`; `kind` may be left out when the source lists one kind. Record reads return them as ingested, and the records `member` filter matches authors of every declared kind. The ingesting server names the accounts, so keep ingest keys on trusted servers.
1362
+ - Trigger outcomes carry a stable skip `code` (`trackerSkipCodes`) beside the human `reason`, and `trackers.outcomes` filters by `code`. An X post without an author is no longer reported as "Post is not a reply".
1363
+ - `triggerMode(policy)`, `beneficiaryRole(policy)` and `sourceIdentities(source)` read the mode, role and identity kinds of published policies and trackers, including those stored by earlier SDKs.
1364
+
1365
+ **Compatibility.** Existing data keeps its stored shape on the wire, so SDKs and CLIs 0.25 to 0.27, and backend pods of the previous release during a rollout, keep reading it. New keys appear only where an author uses a new option:
1366
+
1367
+ - `automatic()` leaves `on` out when it is the source's default, so default quests publish the same policy as before. `on` (non-default), `where`, roles other than `author` and `inReplyTo`, `identities` on new trackers, `accounts`, `author.kind`, and `discord` or `member` attribution are rejected by older SDKs, whose schemas are strict: upgrade apps that read those trackers and quests.
1368
+ - Trackers published with `authors: "wallet"` read back as stored. Republishing one as `identities: ["wallet"]` is the same tracker: it keeps its key and stored definition.
1369
+ - Reply quests published with `beneficiary: "replied-to"` keep it: reads return it as stored, and their evaluators keep receiving `beneficiary: "replied-to"`, so they keep working until republished. Republishing with this SDK switches them to `inReplyTo`.
1370
+ - Trigger outcomes always carry `code`: `trackers.outcomes` needs this SDK.
1371
+
1372
+ **Breaking changes.** For code written against the tracker triggers of 0.25.0 to 0.27.0:
1373
+
1374
+ - Replace `customSource(namespace, { authors: "wallet" })` with `customSource(namespace, { identities: ["wallet"] })`. `authors` stays readable on published trackers and is deprecated.
1375
+ - Replace `automatic({ tracker, beneficiary: "replied-to" })` with `beneficiary: "inReplyTo"`.
1376
+ - `TrackerBeneficiary` and `trackerBeneficiarySchema` are removed; the beneficiary is a role string, or `"replied-to"` on releases published before roles.
1377
+
1378
+ **Upgrading.** Deploy the backend (migration `0074_tracker_trigger_primitives`) before publishing quests that use the new options; older servers refuse them.
1379
+
1380
+ ## 0.30.0
1381
+
1382
+ ### Minor Changes
1383
+
1384
+ - c6afc57: The EVM chains `ctx.wallet.balance()` reads are the platform's list, not an SDK export. Adding a chain no longer needs an SDK release.
1385
+
1386
+ - `WalletBalanceQuery.chain` for EVM is any CAIP-2 ID, typed `` `eip155:${number}` `` (new `EvmChainId` type). New `isEvmChainId(value)` (also from `@domino-sdk/relay/authoring`) and `evmChainIdSchema` check the syntax (`eip155:` and a chain ID in decimal, no leading zeros); the evaluator sandbox refuses anything else before reading.
1387
+ - The platform decides which chains it reads: the EVM networks `management.walletBalanceChains()` lists, generated from dRPC's chain list. On any other EVM chain the evaluation fails with 422 "Relay cannot read balances on eip155:<chain ID>: it is not a supported EVM chain. …".
1388
+ - New `management.walletBalanceChains()` (`GET /management/v1/wallet-balance-chains`, `read` permission) returns `{ chains: [{ chain, name, testnet }] }`: Solana mainnet, then each EVM chain the platform reads, by chain ID. Console names the chains of a member's balance checks from it.
1389
+
1390
+ **Breaking.** `evmChains`, `evmChainSchema`, `EvmChain` and `EvmChainInfo` are no longer exported from `@domino-sdk/relay`.
1391
+
1392
+ **Upgrading.** Quest code that passes a string literal such as `"eip155:8453"` compiles unchanged. Replace `EvmChain` with `EvmChainId`, `evmChainSchema` with `evmChainIdSchema` (it checks syntax, not support), and chain-name lookups in `evmChains` with `management.walletBalanceChains()` from your server.
1393
+
1394
+ - c6afc57: `ingest` is a normal management permission: it sends records to custom trackers (`management.trackers.ingest`) and allows nothing else.
1395
+
1396
+ - Editors hold `ingest`, and organization administrators hold every permission, so `management.access()` lists it for them. `rolePermissions.editor` includes it.
1397
+ - Personal API keys can carry it. In Console, **Settings → Keys → Personal API keys** now lets you choose a new key's permissions (all of yours by default) and lists each key's permissions; select only **Ingest** for an indexer. `POST /management/v1/tokens` accepts `ingest` for callers who hold it.
1398
+ - Hosted-app keys (`dhk_`) now list `ingest` in the minted key, the development key and `access()`, alongside `read` and `points`. They always held it.
1399
+ - `observe` still allows ingestion, so keys issued with only `observe` keep ingesting. A caller with neither gets 403 "Missing ingest permission" (before: "Missing observe permission").
1400
+ - `domino dev` needs your `read`, `points` and `ingest` permissions for its development key; editors have all three.
1401
+ - `tokenSchema` (the personal API key list) drops permissions this release does not know instead of rejecting the list, as `access()` already does.
1402
+
1403
+ **Upgrading.** The `domino` CLI reads `/access` and its development key without checking permission names, so every CLI version keeps working. Code that calls `management.access()` from `@domino-sdk/relay-cli` before 0.25 rejects a response that lists `ingest`, which now happens for every editor and administrator: upgrade `@domino-sdk/relay-cli` there. 0.25 and later drop permissions they do not know.
1404
+
1405
+ ## 0.29.0
1406
+
1407
+ ### Minor Changes
1408
+
1409
+ - 28f8bcf: Participant profiles are primitives: Domino serves the profile and separate reads keyed by it, and your app composes the profile page. Each read checks the profile's visibility and then its own resource's access.
1410
+
1411
+ - `relay.profiles.get(id)` returns the profile only: `id`, `name` and `avatar` (`profileSummarySchema`).
1412
+ - `relay.leaderboards.placement(board, profileId)` (`GET /v1/leaderboards/:board/profiles/:profile`) returns `rank`, `score` and the ranked `total` on one board, with `rank` and `score` null while the member is unranked. It needs the board's access, like `standings`. New `leaderboardPlacementSchema`.
1413
+ - `relay.profiles.completions(id, { limit, cursor })` (`GET /v1/profiles/:profile/completions`) returns completions grouped by quest, most recent first, paged with `nextCursor`. Only quests that opt in appear: `defineQuest({ profile: { visible: true } })`, hidden by default; the quest's active release decides, and archived quests do not appear. New `profileCompletionPageSchema`, `profileCompletionQuerySchema` and `questProfileSchema`; quest descriptions gain an optional `profile`, which `defineQuest` includes only when the quest sets it.
1414
+ - `relay.profiles.referral(id, program)` (`GET /v1/profiles/:profile/referrals/:program`) returns `invited`, `qualified` and `milestones` in one program, for signed-in viewers (401 when signed out). New `profileReferralSchema`.
1415
+ - React: `useLeaderboardPlacement(board, id)`, `useProfileCompletions(id, { limit })` with `loadMore()`, and `useProfileReferral(id, program)`. `useProfile(id)` returns the profile only.
1416
+ - Profile settings: `defaults` is an ordered list of the accounts that fill in names and pictures, `"x"` and `"discord"` (`[]` for generated). `profileDefaultProviderSchema` lists them.
1417
+ - Generated avatars are seeded by an HMAC of the member under the project's secret (`relay.me.get().avatarSeed`), no longer by the member's first wallet: linking a wallet never changes an avatar.
1418
+ - `proxyParticipant` and the API's scope selector admit the new routes (`isProfileParticipantRoute`).
1419
+ - Console: **Participant profiles** settings show who sees profiles and the default accounts, with arrow buttons to order them. The member's **Community profile** appears only when profiles are visible or the member or an operator changed the profile.
1420
+
1421
+ **Upgrading.** Breaking for apps, servers and CLIs on 0.25 to 0.27 that use profiles:
1422
+
1423
+ - `relay.profiles.get(id)` no longer returns `self`, `leaderboards`, `quests` or `referrals`, and `publicProfileSchema` and `PublicProfile` are removed. Read each part with the calls above. For `self`, compare `id` with `relay.profiles.mine().id`. `useProfile(id)`'s `ready` state holds a `ProfileSummary`.
1424
+ - Quest completions no longer appear anywhere until the quest opts in: add `profile: { visible: true }` to the quests whose completions profiles may show, and publish.
1425
+ - Referral progress is per program and needs a signed-in viewer. `attributed` is now `invited`, returned with each program; it still counts the member's invitations across the whole project, not only that program's.
1426
+ - `management.profiles.configure()` takes `defaults: ["x", "discord"]` instead of `"accounts"`, and `[]` instead of `"generated"`; `sections` is removed and refused. `settings()` returns the list. Stored settings migrate: `accounts` becomes `["x", "discord"]`, `generated` becomes `[]`.
1427
+ - Members who linked a wallet get a new generated avatar, the one members without a wallet already had. Uploaded and account pictures do not change.
1428
+ - SDK and CLI 0.25 to 0.27 cannot parse the new profile and settings responses: `relay.profiles.get()` and `management.profiles.settings()` throw until the app and CLI upgrade. Deploy the backend first; its migration only adds the new setting, so pods of the previous release keep working during the rollout.
1429
+ - Upgrade the CLI and finish rolling out the backend before publishing a quest with `profile: { visible: true }`. Only quests that set `profile` carry the key in their release. CLIs from 0.25 to 0.27 refuse deploy and preview responses that include such a release (`Unrecognized key: profile`), and pods of the previous backend release cannot read it, including after a rollback. Quests without it publish and roll back as before.
1430
+
1431
+ ## 0.28.0
1432
+
1433
+ ### Minor Changes
1434
+
1435
+ - 30dda1d: Add `useSignInOptions({ cosmos })` to `@domino-sdk/relay/react`: every way a participant can sign in, as one flat list. It holds the configured OAuth providers, the installed wallets of the families the project enabled, and email. Each option has an `id`, a `label`, a `kind` and an `icon` hint. OAuth and wallet options sign in with `start()`, and the email option with `sendCode(email)` then `verifyCode(code)`. Cosmos wallets are offered only on the chains the app passes as `cosmos`. `unavailable` explains the gaps with a machine-readable `reason`: `cosmos-chains-missing` when the app named no chains, `cosmos-chains-invalid` when a chain in `cosmos` is invalid (such as `"cosmoshub-4"` without `cosmos:`; the other methods still load), and `no-wallet-installed` with a message for the participant. OAuth and email are offered as soon as the methods are read; installed wallets join them when discovery finishes. The options keep their identity across renders, and their actions never reject: failures land in `error`. `methodsError` says when the methods could not be read and the list fell back to email. Apps without React get the same list from `signInOptions(relay, { cosmos })` in `@domino-sdk/relay/browser`, whose actions reject on failure; `@domino-sdk/relay/browser` also exports the option types, `discoverSignInWallets`, `CosmosChains` and `SignInWallet`.
1436
+
1437
+ `useSignIn()` actions now keep their identity across renders, and `verifyCode` always uses the latest code sent; `sendCode` forgets the previous code first. `useSignInMethods()` returns `error` when it fell back to email. Installed wallets with the same name and family get a number in their label. `cosmosChain()` now rejects a chain object without a string `chainId`.
1438
+
1439
+ The starter apps now build their sign-in on the hook, and no longer export `COSMOS_CHAINS`. To offer Cosmos wallets in an app made from a starter, pass your chains to the `useSignInOptions()` call in `src/components/sign-in.tsx`. In development the starters show a note when the project takes Cosmos wallets and the app names no chains, or an invalid one, and no longer tell participants the project has no sign-in method in that case. `useSignInMethods()`, `useSignIn()` and `useSignInWallets()` keep working.
1440
+
1441
+ Management member accounts now carry `kind`: `wallet` for wallet identities and `account` for the rest. Responses from older servers read as `account`.
1442
+
1443
+ ## 0.27.0
1444
+
1445
+ ### Minor Changes
1446
+
1447
+ - 9ef2629: Cosmos wallets work on any Cosmos SDK chain your app chooses. Relay no longer ships a list of Cosmos chains.
1448
+
1449
+ - A Cosmos wallet is `cosmos:<chain ID>` (a CAIP-2 reference: 1 to 32 letters, digits, `-` or `_`) and a bech32 address with any prefix, checked for its checksum and stored lowercase. `caip10()` and wallet tracker authors accept any Cosmos chain ID. A Cosmos wallet is its address: a Cosmos account ID is attributed to the member who links that address, and `trackers.records({ member })` returns the member's Cosmos records under any chain ID. The same key on chains with different prefixes is separate wallets; five Cosmos wallets per member and project, across all chains. 32-byte module and contract addresses cannot link or sign in. Unlinking a wallet and changing its sign-in accept a bech32 address in either case.
1450
+ - A Cosmos wallet's `network` is the chain the app requested when linking it. Relay does not verify it: any app can link an address under any `cosmos:<chain ID>`. Quests and servers should gate on the address, such as its bech32 prefix against their own list of chains, not on `network`.
1451
+ - Browser: `cosmosSigner(provider, chain)` and `discoverCosmosWallets(chain)` take the app's chain as `{ chainId, chainInfo? }` or `"cosmos:<chain ID>"`. When the wallet answers that it does not know the chain, the signer suggests it with `experimentalSuggestChain(chainInfo)` and retries. Without `chainInfo`, or with a wallet that has no `experimentalSuggestChain`, it throws an error naming the chain. Other errors, such as a rejected request, pass through. New `CosmosChain` type and `cosmosChain()` helper.
1452
+ - React: `useWalletDiscovery({ cosmos })` and `useSignInWallets(families, { cosmos })` take the app's chains (one or an array).
1453
+ - Wallet sign-in is enabled per family: `"solana"`, `"eip155"` and `"cosmos"`. `relay.auth.providers().wallets`, `participantSignIn()` and `configureParticipantSignIn()` use families, and a per-network value such as `cosmos:cosmoshub-4` is accepted and read as `cosmos`. The rule that refused a Cosmos testnet in the live environment is gone with the chain list. New `walletSignInFamilies` and `walletSignInNetworksSchema`.
1454
+ - Console: **Participant sign-in** offers **Cosmos SDK chains**, and member wallets show the chain ID, such as **Cosmos · cosmoshub-4**. Starters offer Cosmos wallets for the chains in `COSMOS_CHAINS` in `src/components/sign-in.tsx`, and say sign-in is not set up when the project enables only Cosmos wallets and `COSMOS_CHAINS` is empty.
1455
+
1456
+ **Upgrading.** Deploy the backend, then the API Worker right after it. The backend's migration turns stored per-network sign-in settings into `cosmos`. Until the API Worker is updated, it checks `ctx.wallet.list()` results against the previous release's two Cosmos networks, so a quest evaluation fails when the member has a wallet linked on any other Cosmos chain.
1457
+
1458
+ - **Projects that enabled Cosmos sign-in must upgrade their app to this release immediately after the backend deploys.** SDK 0.25 and 0.26 read `relay.auth.providers().wallets` against a fixed list of values. After the migration such a project answers `["cosmos"]`, which that list does not contain, so `relay.auth.providers()` throws for every visitor. `useSignInMethods()` then falls back to email alone: every OAuth and wallet sign-in button disappears, not only Cosmos wallets, and the email form appears even where email sign-in is off.
1459
+ - CLI and management SDK 0.25 and 0.26 reject the `cosmos` family in `participantSignIn()` and `configureParticipantSignIn()` responses. SDK 0.25 and 0.26 reject a linked wallet on any other Cosmos chain in `relay.wallets.list()` and `management.member()`. Upgrade the CLI with the app.
1460
+ - Apps made from a starter declare `NETWORK_NAMES: Record<WalletSignInNetwork, string>` in `src/components/sign-in.tsx` with a key per Cosmos network. After upgrading, `tsc` fails on those keys. Built anyway, the sign-in form of a project that enables Cosmos wallets lists no Cosmos wallet, and crashes when it shows the "Wallet sign-in accepts …" hint, because `NETWORK_NAMES.cosmos` is undefined. Replace the per-network keys with one `cosmos` key, declare your chains, and pass them to `useSignInWallets`:
1461
+
1462
+ ```tsx
1463
+ import type { CosmosChain, WalletSignInNetwork } from "@domino-sdk/relay"
1464
+
1465
+ /** The Cosmos SDK chains this app offers wallet sign-in on. */
1466
+ export const COSMOS_CHAINS: CosmosChain[] = [{ chainId: "cosmoshub-4" }]
1467
+
1468
+ const NETWORK_NAMES: Record<WalletSignInNetwork, string> = {
1469
+ solana: "Solana",
1470
+ eip155: "EVM",
1471
+ cosmos: "Cosmos",
1472
+ }
1473
+
1474
+ // In the sign-in component:
1475
+ const wallets = useSignInWallets(methods?.wallets, {
1476
+ cosmos: COSMOS_CHAINS,
1477
+ })
1478
+ ```
1479
+
1480
+ - `cosmosNetworks` is removed, and `CosmosNetwork` is any `cosmos:<chain ID>`. Pass your chain to the adapters instead of looking it up.
1481
+ - `useWalletDiscovery()` no longer lists Cosmos wallets on a default chain: pass `{ cosmos: { chainId: "…" } }`. `useSignInWallets(networks)` lists Cosmos wallets only with `{ cosmos }`.
1482
+ - `WalletSignInNetwork` is now `"solana" | "eip155" | "cosmos"`; a `SignInWallet`'s `network` is its family, and a Cosmos wallet's chain is `signer.network`.
1483
+
1484
+ ## 0.26.0
1485
+
1486
+ No changes in this release.
1487
+
1488
+ ## 0.25.1
1489
+
1490
+ No changes in this release.
1491
+
1492
+ ## 0.25.0
1493
+
1494
+ ### Minor Changes
1495
+
1496
+ - 6634992: Participants have project profiles: a display name and an avatar that other participants see on leaderboards and profile pages, where the project enables them.
1497
+
1498
+ - `relay.profiles.mine()`, `update({ displayName, avatar, hidden })`, `uploadAvatar(file, { uploadId })` and `get(profileId)`. Uploads accept JPEG, PNG or WebP up to 5 MB and are stored as 512×512 WebP without metadata.
1499
+ - React: `useMyProfile()` and `useProfile(id)`; `avatarSrc(avatar)` renders any profile avatar. Generated avatars use the same seed as `relay.me.get().avatarSeed`.
1500
+ - Contract: `profileSummarySchema`, `ownProfileSchema`, `publicProfileSchema`, `profileUpdateSchema`, `displayNameSchema`, `profileSettingsSchema`, `profileModerationSchema`, `managedProfileSchema` and `isProfileParticipantRoute()`. Leaderboard rows gain `profile` (null unless the viewer can see it; older servers omit it and it reads as null).
1501
+ - Management: `profiles.settings()`, `profiles.configure({ visibility, defaults, sections, expectedRevision, reason })` (publish; in live only from Console) and `profiles.moderate(member, { resetName, removeAvatar, lock, reason })` (review). `member(id)` returns `profile`, and account profiles gain `username`. Console has **Settings → Access → Participant profiles** and a Community profile section on the member.
1502
+
1503
+ **Upgrading.** Nothing is shown until an operator enables profiles: leaderboards keep their aliases and profile pages answer 404. Deploy the backend first; the database migration runs automatically and dates earlier completions. Apps must upgrade the SDK so their `/relay` proxy (`proxyParticipant` or `createParticipantBackend`) admits `/v1/me/profile`, `/v1/me/profile/avatar` and `/v1/profiles/:id`; older proxies answer 404 for them. X and Discord names and pictures fill in when members next sign in or connect those accounts. No breaking changes.
1504
+
1505
+ - 531f41e: Members can link several Solana, EVM and Provenance wallets to a project and unlink them.
1506
+
1507
+ - `relay.wallets.list()`, `link(signer)`, `unlink(wallet)`, and the lower-level `challenge()` and `verify()`.
1508
+ - Dependency-free signer adapters and discovery in `@domino-sdk/relay/browser`: `evmSigner`, `solanaSigner`, `walletStandardSolanaSigner`, `cosmosSigner`, `discoverEvmWallets`, `discoverSolanaWallets`, `discoverCosmosWallets`.
1509
+ - React: `useWallets()` and `useWalletDiscovery()`.
1510
+ - Evaluators: `ctx.wallet.list()` returns the member's linked wallets, pinned once per attempt.
1511
+ - Management: `unlinkMemberWallet(member, { chain, address, reason })` (requires `review`), and `wallets` in the member detail.
1512
+ - Contract helpers: `caip10()`, `cosmosNetworks`, `isWalletParticipantRoute()`, and the `LinkedWallet`, `WalletSigner` and related schemas.
1513
+
1514
+ **Upgrading.** Nothing breaks. `relay.wallet.connection()/challenge()/verify()` and `ctx.wallet.connection()/solanaBalance()` keep working and use the newest linked Solana wallet; linking through `relay.wallet.verify()` still replaces it, while wallets linked through `relay.wallets` sit alongside older ones. The legacy challenge text now uses the sign-in message format, and rate-limited wallet challenges answer 429 instead of 400. Deploy the backend and API Worker before publishing apps that use the new APIs. Apps that proxy through `proxyParticipant` forward `/v1/me/wallets*` after upgrading the SDK. The database migration runs automatically.
1515
+
1516
+ - c71712f: Quests can trigger from custom tracker records attributed to members' verified wallets.
1517
+
1518
+ - `customSource(namespace, { authors: "wallet" })`: each record's author is a canonical CAIP-10 account ID, attributed to the member who links that wallet. Ingestion refuses missing or non-canonical authors.
1519
+ - `automatic({ tracker })` admits one attempt per new record observation for the attributed member. Evaluators and `keyed()` receive the pinned observation and attribution as `ctx.tracker`; `ctx.eligibleAt` is the record's occurrence time.
1520
+ - `management.trackers.outcomes(tracker, query)` explains each admission or skip with its reason; `management.trackers.retryOutcome(tracker, input)` (requires `review`) retries a skipped admission.
1521
+ - `management.trackers.records(tracker, { member })` works on wallet trackers, returning the member's linked wallets' records. `trackers.ingest` results include `admissions` counts.
1522
+ - New `ingest` permission, granted to hosted-app keys: it allows custom tracker ingestion only. People keep ingesting with `observe`.
1523
+
1524
+ **Upgrading.** Nothing breaks for existing projects. Once a project publishes a tracker-triggered quest, release and progress reads include `trigger.tracker`, which older SDKs reject because the trigger schema is strict: upgrade participant apps before publishing one. Hosted-app keys hold `ingest`, but responses (the minted key, `access()`) keep listing only `read` and `points`, so older CLIs, API Workers and builders still parse them. Permission lists in `access()` projects and hosted-key responses now drop values this release does not know instead of rejecting the response. The database migration runs automatically.
1525
+
1526
+ - ea5d69e: Quests can check token balances of a member's linked wallets.
1527
+
1528
+ - Evaluators: `ctx.wallet.balance({ chain, token?, address? })` reads SOL and SPL tokens (including Token-2022) on Solana mainnet, and native and ERC-20 balances on any EVM chain in `evmChains` (the EVM networks dRPC serves, by CAIP-2 ID such as `"eip155:8453"`). It reads all of the member's pinned linked wallets of that family, or one of them, and returns integer amounts with the token's decimals, captured for replay. Each read is one of the evaluation's eight capability calls.
1529
+ - Authoring: `parseUnits()` and `formatUnits()` convert whole-token amounts, and `WalletBalanceQuery` and `WalletBalance` type the call.
1530
+ - Contract: `evmChains`, `evmChainSchema` and `walletBalanceSchema`.
1531
+ - Management: the member detail's `balanceChecks` lists the member's recent recorded balance reads; Console shows them under the member's wallets.
1532
+ - Snippet: `token-holder` checks that a member's linked wallets hold at least a minimum.
1533
+
1534
+ **Upgrading.** Nothing breaks. Deploy the backend and API Worker before publishing quests that call `ctx.wallet.balance()`. EVM chains need the platform's `DRPC_API_KEY`; without it, EVM balance reads fail the verification with "EVM balance checks are not configured". Solana reads need no new configuration. `evmChains` is generated from dRPC's chain list: a later release that removes a chain breaks quests that read it, and its changeset will say so.
1535
+
1536
+ - e7853fb: Participants can sign in, and sign up, with a Solana, EVM or Provenance wallet where a project enables it, and apps can draw generated avatars.
1537
+
1538
+ - `relay.auth.wallet.signIn(signer)`, and its two steps `start(input)` and `verify(proof)`. `relay.auth.providers()` returns `wallets`, the networks the project enabled.
1539
+ - React: `useSignIn().withWallet(signer)`, `useSignInMethods().wallets`, `useSignInWallets(networks)` (the installed wallets that can sign in on those networks), and `useMe()`. The starters offer installed wallets next to email and OAuth.
1540
+ - `relay.me.get()` returns the signed-in `member` and an `avatarSeed` derived from their first wallet without revealing it. `defaultAvatar(seed)` and `defaultAvatarSvg(seed)` draw a deterministic avatar (DiceBear Shapes, CC0); `walletAvatarSeed(wallet)` seeds a member's own wallet list.
1541
+ - Contract: `walletSignInNetworkSchema`, `walletSignInNetwork()`, `isWalletSignInRoute()` and `memberSummarySchema`.
1542
+ - Management: `participantSignIn()` and `configureParticipantSignIn({ wallets })` (publish) read and set the project's wallet sign-in per environment; `setMemberWalletSignIn(member, { chain, address, enabled, reason })` (review) turns sign-in with a member's wallet on or off. Console has both under Settings → Access and on the member.
1543
+
1544
+ **Upgrading.** Nothing changes until a project enables wallet sign-in. Deploy the backend and API Worker first; the database migration runs automatically. `/v1/auth/providers` gains `wallets`, which older SDKs ignore. Apps that proxy through `proxyParticipant` forward `/v1/auth/wallet/*` and `/v1/me` after upgrading the SDK. A platform without OAuth providers or email no longer answers 503 for projects that enable wallet sign-in.
1545
+
1546
+ - b22ead4: X trackers can trigger quests, and budgets can reset weekly.
1547
+
1548
+ - `automatic({ tracker })` accepts an X tracker. The quest runs once for each post the tracker collects for the first time; refreshes, metric changes and edits never run it again. `beneficiary: "author"` (the default) pays the member whose selected, verified X account wrote the post; `beneficiary: "replied-to"` pays the member whose selected, verified X account the post replies to. `"replied-to"` on a custom tracker throws.
1549
+ - `ctx.tracker.x` carries the captured post (`content` and `evidence`) for X trackers, and `ctx.tracker.attribution.account` can be `{ provider: "x", issuer, subject, revision, linkedAt }`.
1550
+ - `budget({ period: "week" })` limits completions or points per ISO week in UTC, starting Monday, by the attempt's eligibility time.
1551
+ - New trigger outcome reasons: "Post is not a reply", "Reply is to the author's own post", "No member has linked this X account" and "Captured post is unavailable".
1552
+
1553
+ **Upgrading.** Nothing breaks for existing projects. Release and progress reads that include an X tracker trigger (`source: "x-search"`, `beneficiary: "replied-to"`) or a `period: "week"` budget are rejected by older SDKs, whose schemas are strict: upgrade participant apps before publishing either.
1554
+
1555
+ ## 0.24.0
1556
+
1557
+ No changes in this release.
1558
+
1559
+ ## 0.23.1
1560
+
1561
+ No changes in this release.
1562
+
1563
+ ## 0.23.0
1564
+
1565
+ ### Minor Changes
1566
+
1567
+ - 49fa0fd: Apps can now match the ledger and tiered reward IDs that reads return to the keys they declared.
1568
+
1569
+ - `relay.pointLedgers.list()` (`GET /v1/point-ledgers`) and the React hook `usePointLedgers()` return each ledger's `id`, `key`, and `name`.
1570
+ - `relay.tieredRewards.list()` (`GET /v1/tiered-rewards`) and `useTieredRewards()` return each tiered reward's `id` and `key`.
1571
+ - Both reads are open to signed-out visitors. The hooks return the list in `items`. `key` is null for resources created in Console without one.
1572
+
1573
+ Reads name ledgers and tiered rewards by ID. Comparing a referral reward's, account's, or history entry's `balance` with a key such as `"credits"`, or an entitlement's `reward.id` with the key given to `defineTieredReward`, never matched, and apps could show "200 ledger_… points". Find the resource by key, compare IDs, and display its name or title.
1574
+
1575
+ Quest view rewards are now typed: `{ kind: "points", label, amount, ledger: { id, key, name } }`, `{ kind: "tier", label, reward: { id, key, title }, tier }`, or `{ kind: "integration", label }`. Labels read "10 XP" instead of "10 ledger_…". Tier labels show the reward title, followed by the tier title when the reward has several tiers, instead of the tier ID.
1576
+
1577
+ Upgrade notes:
1578
+
1579
+ - Code that reads `label` keeps working, and older SDKs ignore the new fields. Replace label parsing with `amount`. If you compared a tier reward's `label` with its tier ID, compare `tier` instead.
1580
+ - TypeScript code that builds quest views, such as test fixtures, must add `kind` and that kind's fields.
1581
+ - Deploy the updated backend and API Worker before upgrading the SDK: this SDK expects `kind` on quest view rewards. Apps that proxy participant requests with `proxyParticipant` or `handleHostedApp` forward the new routes once they upgrade.
1582
+ - No database migration is required.
1583
+
1584
+ ## 0.22.0
1585
+
1586
+ ### Minor Changes
1587
+
1588
+ - 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`.
1589
+
1590
+ Additive and backward compatible. Existing quests, including `ctx.review.photo()`, keep working. To use `ctx.images`, rebuild quests with this SDK version; bundles built with earlier versions do not pass it to `evaluate`. Deploying `images` needs a server with this release; older servers ignore the key, and quests that reference those images then fail verification with "Image … is not published".
1591
+
1592
+ ## 0.21.0
1593
+
1594
+ No changes in this release.
1595
+
1596
+ ## 0.20.0
1597
+
1598
+ ### Minor Changes
1599
+
1600
+ - 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.
1601
+
1602
+ Upgrade notes: working projects are unaffected, because these calls could not succeed. If your code still references them, delete the calls. List members from a server with the management client: `createManagementClient(...).page("members", { q, limit })`, or `member(id)` for one member. Review pending attempts in the Domino Console **Activity** page. Members can still download their own evidence with `evidence.download`.
1603
+
1604
+ ## 0.19.0
1605
+
1606
+ No changes in this release.
1607
+
1608
+ ## 0.18.4
1609
+
1610
+ No changes in this release.
1611
+
1612
+ ## 0.18.3
1613
+
1614
+ No changes in this release.
1615
+
1616
+ ## 0.18.2
1617
+
1618
+ No changes in this release.
1619
+
1620
+ ## 0.18.1
1621
+
1622
+ ### Patch Changes
1623
+
1624
+ - 2186fe3: `domino dev` no longer breaks participant requests when `--api-url` is Console's management proxy (`https://console.domino.run/relay`). The CLI used to pass that URL to the app as `DOMINO_PARTICIPANT_API`. The participant proxy refuses any upstream with a path, so every `/relay/*` request answered 503 "Participant API is not configured".
1625
+
1626
+ The API now reports its participant origin in `/preflight` (`participantApi`). `domino dev` sends participant requests there and gives the development management key the same origin, as deployed builds receive it. With an older API, the CLI uses `--api-url` if it is already an origin, then relay.json's `apiUrl`. If neither is, it stops with instructions instead of starting a broken preview.
1627
+
1628
+ `domino doctor --remote` has a new `participant-api` check, and reports the resolved origin as `scope.participantApi`. The check fails when no participant origin can be found, or when relay.json `apiUrl` is not that origin, which staged builds rely on. The proxy's 503 message now says to use the API origin rather than a management URL.
1629
+
1630
+ Upgrade note: if relay.json `apiUrl` is set to a management proxy URL, change it to the participant API origin (`https://relay.domino.run`).
1631
+
1632
+ ## 0.18.0
1633
+
1634
+ ### Minor Changes
1635
+
1636
+ - bf84375: Add `trackers.content(reference, { id, observation })` to hosted quest evaluators. The read returns a retained X body and verified account attribution to the attempt's member, captured together for replay. Engagement counters, mutable author profiles, usernames, and verification badges do not determine ownership. Explicit disconnects and ambiguous legacy connections return unlinked attribution.
1637
+
1638
+ Management clients expose the same read with a required `member` in the query and `read` permission. Deploy the updated backend and API runtime before using this method. Existing tracker methods and response shapes remain unchanged. This adds evidence reads, not automatic admission or engagement rewards.
1639
+
1640
+ - ac63da2: Add automatic X content quests with `content(tracker, policy)` and `perContent()`, pinned content context, quest enrollment, historical claims and staff approvals, and paginated admission explanations. Existing trigger and execution contracts remain readable. Upgrade the backend and collector workers before publishing content quests; update participant apps to use the new SDK proxy paths. Existing tracker matches are not backfilled, and edits after admission do not reevaluate or reverse prior rewards.
1641
+ - 0a0ac9a: Add independent daily engagement rewards for qualified X content. Author immutable cumulative formulas with `defineEngagementReward`, publish bundled code with `management.engagement.publish`, and read settlement history through `engagement.page`. Partial budgets retain paid and permanently excluded growth separately.
1642
+
1643
+ Deploy backend migration 0060, the API formula runtime, and workers before publishing engagement rules. Existing quests keep their contracts. This first version supports new discoveries and historical initial baselines; CLI manifest deployment, formula replacement, explicit backfill, enrollment baselines, and automated reversals remain unsupported.
1644
+
1645
+ - 40c9761: Add opt-in X evidence references to tracker records. Pass `includeEvidence: true` to receive a stable content revision and a separate provider observation identity. Engagement and author profile refreshes keep the content revision unchanged. Custom records return `evidence: null` when requested; default responses keep their existing shape.
1646
+
1647
+ Deploy backend migration 0056 and the updated API before requesting evidence from the new SDK. The migration backfills retained X observations and captures writes from previous backend versions during rolling deployment. Existing SDK callers need no changes. Automatic content qualification and engagement rewards remain unimplemented.
1648
+
1649
+ - b596a0a: Add content-only performance gates with declared engagement metrics and a configurable freshness limit. Content approval stays cached while new samples run performance. A body edit or change to the verified account selection requires fresh content approval. Missing or stale metrics wait without running the callback. The first passing performance decision resolves the content-approved reward selection; later metric changes cannot duplicate awards or retry budget exclusions.
1650
+
1651
+ Use `content.performance({ quest, content, member?, before? })` to read current eligibility and retained metric evaluations. Existing content qualification history remains content-only. Performance callbacks receive the declared metrics, sample time, pinned content, member, and settings. They gate rewards but cannot replace the content-approved selection.
1652
+
1653
+ Apply migration `0059_content_performance` and upgrade the backend, API evaluator, and workers before publishing quests with this stage. Older evaluator runtimes cannot dispatch performance safely. Existing quests need no source changes and omit the new optional fields. Upgrade the SDK to author performance stages or read their history. Automatic reversals and daily engagement settlement remain unsupported.
1654
+
1655
+ - e5b7091: Reevaluate admitted X posts after body edits and expose `content.qualifications({ quest, member?, content?, before? })` in participant and management clients. History keeps the original release, beneficiary, and evidence, with qualification state separate from the one-time reward outcome. Stale evaluations and reviews cannot award rewards; later qualifying edits cannot repeat rewards or retry a budget exclusion. Existing admission APIs retain their response shapes. Removal and rejection do not reverse prior awards.
1656
+
1657
+ Apply migration `0058_content_qualification` and upgrade backend and workers together before enabling content quests. Older decision workers cannot enforce stale-content guards during mixed-version deployment. Existing admissions are retained and backfilled; no quest source changes are needed. Upgrade clients to use the new history method.
1658
+
1659
+ - 243d9e7: Add explicit `engagement: [{ entry: "engagement.ts" }]` manifest entries, with optional `exportName`, to `domino build` and `domino deploy`. Preview and publish engagement rules with their tracker, qualifying quest, and points balance in one project transaction. Previews report new, unchanged, and changed formulas; formula changes preserve past earnings and budget spend through observed boundaries.
1660
+
1661
+ Resume the saved deployment ID to publish a reviewed preview or recover a committed result. Stale engagement changes reject the entire deployment. Missing entries do not delete rules, and unchanged code does not create another scoring version. New rules use actual publication time as their discovery cutoff.
1662
+
1663
+ Upgrade the backend and settlement workers before deploying engagement entries. Manifests and SDK requests without engagement entries keep their request shape, and existing hosted-build activation receipts remain replayable. CLI publication remains test-only; live previews remain available. Changes to metrics, budgets, or qualifying quests remain unsupported.
1664
+
1665
+ - 4cea332: Add preview/apply management workflows for engagement formula changes, immutable version history, and participant boundary history. Each post settles its governing formula through an available fresh eligible observation before the latest requested formula starts at the same counters. Existing budget spend and exclusions survive; superseded unapplied formulas do not invent intermediate earnings.
1666
+
1667
+ Preview totals describe retained accounting, not promised future payouts. Available observations may predate the request by up to the rule's freshness limit. Quest, balance, metrics, baseline policy, freshness policy, and budgets cannot change in this workflow.
1668
+
1669
+ Deploy migration 0061 and upgrade/drain all settlement workers before requesting a formula change. Older workers do not enforce version boundaries. Existing settlement response shapes remain compatible; histories may now include an additional formula-boundary settlement in the same UTC day.
1670
+
1671
+ Engagement creation and formula apply now follow the existing live-publication policy: management keys can publish in test, while live publication requires a Console session.
1672
+
1673
+ - 2a7ca88: Add an optional `content` filter to `content.admissions({ quest, content })` for direct post lookup. Management and participant reads retain their existing project and member isolation, response shapes, and unfiltered pagination behavior. Upgrade the backend before sending the new filter; older servers reject it. Existing callers need no changes.
1674
+
1675
+ ## 0.17.2
1676
+
1677
+ ### Patch Changes
1678
+
1679
+ - 664eb80: Share current X post content, author verification, and engagement across matching trackers in the same project and environment. Each tracker keeps its own matches and first-seen times. Concurrent collectors coordinate known-post refreshes, and captured quest reads retain their original results.
1680
+
1681
+ Pausing a tracker stops its collection; another active tracker can still update their shared posts. Custom datasets remain isolated per tracker. Existing tracker definitions and SDK calls need no changes.
1682
+
1683
+ Requires backend migration `0055_shared_tracker_content` and the matching backend deployment. The migration retains the previous release's write format for rolling upgrades and preserves existing observation history. Removing the legacy payload columns requires a later contract release after all old workers have stopped. Old workers may still perform duplicate refreshes until the rollout finishes.
1684
+
1685
+ ## 0.17.1
1686
+
1687
+ No changes in this release.
1688
+
1689
+ ## 0.17.0
1690
+
1691
+ No changes in this release.
1692
+
1693
+ ## 0.16.1
1694
+
1695
+ No changes in this release.
1696
+
1697
+ ## 0.16.0
1698
+
1699
+ ### Minor Changes
1700
+
1701
+ - 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.
1702
+
1703
+ The capability limits text to 16,000 JavaScript string units and criteria to 2,000, with a 12-second provider timeout. Review text is treated as untrusted evidence. Credentials stay server-side. This additive API requires a backend and evaluator bridge deployment that supports text review before publishing quests that use it. Existing photo quests remain compatible. Combine this capability with `evidence.text()` for submitted text or `evidence.url()` plus verified X post reads.
1704
+
1705
+ - 3280aa1: Add required or optional text and HTTP/HTTPS URL submissions with configurable text lengths and exact allowed hosts. Evaluators receive captured strings through ctx.input.value. Browser handles and campaign controls preserve the original values across interrupted delivery and evaluation retries. Backend admission enforces the published contract and returns correctable validation errors.
1706
+
1707
+ Submitted URLs never trigger automatic fetching. Use `xPostId` and `ctx.x.post({ postId })` to verify a post through the configured X provider; reads are captured for retries. Participants link X before verification. Compare the returned author ID with `userId` from `ctx.x.connection()` to establish authorship.
1708
+
1709
+ Existing photo and quiz request shapes and saved photo records remain compatible. Upgrade the SDK and CLI together before authoring text or URL quests. Existing participant apps must add controls and declare text and url in supportedInteractions; newly exported campaign starters include both. Deploy the updated backend and API Worker before publishing these quests. Text and URL storage need no additional migration; the integrated X capability requires the X execution-snapshot migration.
1710
+
1711
+ - 074c292: Add captured X verification to authored quest evaluators: `x.connection()`, `x.profile()`, `x.post({ postId })`, `x.follows({ userId })`, and `x.reposted({ postId })`.
1712
+
1713
+ Participants link their X accounts through the existing account flow. Platform operators configure the backend's `X_API_WRAPPER_API_KEY`, shared with trackers. Account changes invalidate unfinished reads; retries reuse captured evidence. Existing quests remain compatible. Apply the additive X execution-snapshot migration and deploy the updated backend and evaluator Worker before publishing quests that use the new methods, and update the SDK and installed CLI references.
1714
+
1715
+ ## 0.15.0
1716
+
1717
+ ### Minor Changes
1718
+
1719
+ - 6c309ea: New projects can start from a choice of starters: Campaign tour (the default), Classic questboard, Waitlist, Referral program, Discord activation, and API. In Console, choose one under **Start from** when creating something new. In a terminal, run `domino create NAME --template waitlist`, or answer the new prompt. Scripts without `--template` still get the campaign tour. `starterTemplates` and `starterIdSchema` in `@domino-sdk/relay-cli` list the offered starters. Projects now report the chosen starter as a top-level `starter` field, and `POST /repository` accepts any offered starter ID. Omit it to use the starter chosen at setup. At creation, Domino writes the project name to the starter's `src/project.json` and places the brief where the starter's `CAMPAIGN.md` marks it.
1720
+
1721
+ `@domino-sdk/relay/react` provides the React layer the starters share:
1722
+ - `RelayProvider`, `useExperience`, `questGroups`, `useQuest`, and the quest status helpers
1723
+ - `useSignIn`, `useSignInMethods`, `useSignOut`, and `useAccounts`
1724
+ - `useLeaderboard`, `useReferralSummary`, `useInvitation`, and `useRelay`
1725
+
1726
+ React 19 is an optional peer dependency. `@domino-sdk/relay/vite` adds `serverBoundary()`, which fails a Vite build when browser code imports `server/` or `@domino-sdk/relay-cli`. `@domino-sdk/relay/portal-proxy` adds `hostedAppRequest` and `handleHostedApp` for the Domino routes of a hosted app's Worker.
1727
+
1728
+ Upgrade notes: nothing breaks for existing projects. Apps built from the earlier campaign starter keep their own `src/relay` copies and can switch to `@domino-sdk/relay/react` hook by hook. Wrap the app in `RelayProvider`, which replaces `ExperienceProvider` and `src/relay/client.ts`, then change imports. The hook names and behavior are unchanged, except that `useRoleDeliveries` is now internal and `useExperience()` also returns `member`.
1729
+
1730
+ ## 0.14.1
1731
+
1732
+ No changes in this release.
1733
+
1734
+ ## 0.14.0
1735
+
1736
+ ### Minor Changes
1737
+
1738
+ - 11abaf5: Add a bounded X search preview for exploring tracker queries before defining them in code. `management.trackers.preview({ query, since })` returns at most 100 matching posts from 3 search pages, up to 30 days back, with author verification retained and `more` set when X had further matches. It requires `publish` permission, is rate limited, stores nothing, and never publishes a tracker or starts collection. Console's Trackers page uses it to show a sample and copy a tracker definition or coding-agent prompt.
1739
+
1740
+ Requires the matching backend deployment. Existing clients and manifests are unaffected.
1741
+
1742
+ ## 0.13.0
1743
+
1744
+ ### Minor Changes
1745
+
1746
+ - e969f1e: Add maintained tracker datasets with a managed X search source, preserved author verification, read-time filters, and custom indexer ingestion. Publish definitions through relay.json and the existing deployment preview flow. Query datasets from the management SDK or captured read-only capabilities in hosted quest evaluators.
1747
+
1748
+ Requires the matching backend migration and deployment before publishing or reading trackers. X collection requires the platform's X_API_WRAPPER_API_KEY credential. Existing project manifests remain valid. Custom evaluator test contexts that explicitly implement EvaluationContext must add a trackers reader. Collection does not automatically issue or reverse rewards.
1749
+
1750
+ ## 0.12.0
1751
+
1752
+ ### Minor Changes
1753
+
1754
+ - 9fdc113: Reward deliveries now end. `rewardDeliverySchema.status` adds a terminal `failed` state, reached when Discord permanently rejects a role grant (unknown role, member left the server, missing permissions) or transient errors persist for about a day. The management client adds `retryRewardDelivery(id)`, which moves a failed delivery back to `pending` and starts a new delivery round. Clients that parse deliveries with an older SDK reject `failed` records, so upgrade before reading deliveries.
1755
+
1756
+ ## 0.11.0
1757
+
1758
+ ### Minor Changes
1759
+
1760
+ - 5337971: Add `streak({ days, timeZone })` for automatic observation quests. Verified actions count once per community day, including daylight-saving boundaries. Late evidence repairs continuity; milestone rewards commit once with progress. Participant reads expose current/best runs, qualifying days, deadlines and reward status. Timezone changes take effect at the next community boundary without rewriting history.
1761
+
1762
+ Each quest awards one lifetime milestone. Configure separate quests for separate goals. All streak quests in a project/environment share one timezone. An existing goal length is immutable, and budget-exhausted milestones keep their evidence without later automatic payout.
1763
+
1764
+ Apply the backend migration and deploy the updated backend, SDK and participant app before publishing streak quests. Existing once/keyed quests retain their behavior. Older SDKs cannot parse the new `streak` completion kind or `progressed` attempt status; upgrade clients that read a project containing streak quests.
1765
+
1766
+ ## 0.10.0
1767
+
1768
+ ### Minor Changes
1769
+
1770
+ - 8a81fde: `proxyParticipant` now sends the app origin to the shared API by default, as `hosted: true` did. The shared API has no deployment-wide participant project, so calls without it failed with 503 and now get 404 “Participant requests must come through a registered app or identity integration”. Existing code that passes `hosted: true` keeps working and can drop the option. Pass `hosted: false` only for an upstream that applies its own project scope.
1771
+
1772
+ ### Patch Changes
1773
+
1774
+ - d5d530d: Mark the staff and operator methods on `createRelayClient` as deprecated: `reviews.list`, `members.list`, `lookupMember`, `snapshot`, `template`, `journeyTemplate`, `publish`, `publishBatch`, `beginHandover`, `confirmHandover`, `decide` and `me.linkPass`. The participant API accepts only member sessions, so these calls already fail. No upgrade is needed; move any remaining calls to the management API, `domino deploy`, or a server-side pass verifier that calls `POST /management/v1/passes/verify`.
1775
+
1776
+ ## 0.9.1
1777
+
1778
+ No changes in this release.
1779
+
1780
+ ## 0.9.0
1781
+
1782
+ ### Minor Changes
1783
+
1784
+ - 6a5b6d3: Remove the offline development runtime and the `dev --offline`, `dev --review-mode`, and `doctor --offline` options. Use `domino dev` with the hosted test environment and real sign-in. New campaign exports include only the SDK and CLI packages.
1785
+
1786
+ New release and catalog publication accept only Workers AI. Historical provider identifiers remain readable for retained records, but cannot select simulated evaluation.
1787
+
1788
+ ## 0.8.0
1789
+
1790
+ ### Minor Changes
1791
+
1792
+ - 25ea110: Assign persistent resource IDs on the server. Authoring declarations now use stable keys, project and draft creation use separate retry keys, and point ledgers must be created or declared before use. Existing IDs remain valid. Legacy authoring `id` remains supported as a key alias. Use `domino migrate` to preview a repeatable source and manifest upgrade, apply with `--write`, and inspect actionable diagnostics with `domino check --json`. Creation helpers generate retry keys, retry uncertain responses once, and expose the retry key on `CreationError`; raw HTTP creation still requires an action ID. Consume returned IDs for newly created resources.
1793
+
1794
+ ## 0.7.0
1795
+
1796
+ ### Minor Changes
1797
+
1798
+ - 0bbefc1: Add point ledger metadata with stable IDs, editable names and descriptions, management list/save methods, and a pointLedger setting for Console pickers. Existing balance keys retain their accounting history.
1799
+
1800
+ ## 0.6.0
1801
+
1802
+ ### Minor Changes
1803
+
1804
+ - 5cd39a2: Add observation-triggered quests, keyed completions, visibility, shared atomic budgets, and backend observation submission. Include an automatic Discord announcement reaction snippet and participant guidance for repeatable quests.
1805
+ - 5cd39a2: Add Discord message contributions, moderator recognition with separate actor and beneficiary identities, richer member and scoped message reads, and durable Discord role rewards with reusable role selectors and delivery status. Include three distinct capability snippets and updated authoring guidance.
1806
+ - 5cd39a2: Add reusable integration resource selectors and date/time settings. Include Discord channel selection, scoped resource lookup, publication validation, and paused publication for unconfigured resource settings.
1807
+
1808
+ ## 0.5.0
1809
+
1810
+ ### Minor Changes
1811
+
1812
+ - 85bb8aa: Add compact prefixed resource ID generation and expose globally unique project slugs. Hosted builds carry the slug used for stable staging addresses; development previews use the slug and a shorter workspace hash. Existing resource IDs remain valid.
1813
+ - c24042e: Remove the Phantom demo sign-in helper and campaign-specific API routes from the platform. Local authentication defaults to the generic local/campaign test scope. Event pass admission requires a verification integration instead of accepting Breakpoint-formatted codes.
1814
+
1815
+ Expose a generic management operation for app-owned pass verification. It requires confirmation permission and keeps unique pass ownership and retry-safe entry submission in Relay.
1816
+
1817
+ - 990c756: Support compiled Cloudflare Worker apps in connected previews and cloud staging, including SSR, server actions, and API routes. Add the worker app configuration and private module artifacts while preserving static hosting.
1818
+
1819
+ Add opt-in `app.routing="transparent"`: app requests and responses pass through unchanged, with no reserved routes. Move the starter participant proxy into application code and add an explicit hosted participant integration option. Omitted routing retains legacy behavior for existing projects, deployed builds, preview sessions, and older CLI requests. New starters opt in; existing apps migrate explicitly after adding their own participant handler.
1820
+
1821
+ ## 0.4.0
1822
+
1823
+ ### Minor Changes
1824
+
1825
+ - d8a2572: Add project integrations with managed Discord bot installation in Console. Quests declare integrations and read membership through ctx.discord.member() without a server ID. Participants use basic Discord identity sign-in or linking. Capture identity and integration revisions for recovery, provide starter identity controls and failed-submission discard, and include the membership snippet and operator/agent guides.
1826
+ - 8ead9ee: Add project-scoped points leaderboards with code-defined calculations, reviewed publication, participant rankings, and audited points updates. Support period windows, shared ranks, participation groups, and board exclusions.
1827
+ - 1fb2edd: Add code-defined referral programs, session-bound invitation attribution, qualification rewards, milestones, and selective inviter point bonuses with correction recovery. Include participant and management APIs, project publication, Console controls, and agent references.
1828
+
1829
+ ### Patch Changes
1830
+
1831
+ - c475d40: Use the complete non-quest deployment schema for hosted build artifacts, preserving leaderboard and referral modules during preview activation and starter verification.
1832
+ - 7204e04: Discover default-exported quests and quest types using patterns in relay.json. New projects include discovery patterns, and explicit entries can override deployment settings. Reject missing selected exports while bundling. Add editable photo, quiz, and staff-confirmation recipes through the public snippets registry.
1833
+
1834
+ ## 0.3.0
1835
+
1836
+ ### Minor Changes
1837
+
1838
+ - 53d6aef: Support existing application logins through a server participant adapter and project-scoped identity integration keys. Add Console provisioning, rotation and revocation, trusted participant routing, and an agent integration reference.
1839
+
1840
+ ## 0.2.0
1841
+
1842
+ No changes in this release.
1843
+
1844
+ ## 0.1.0
1845
+
1846
+ ### Minor Changes
1847
+
1848
+ - d46d47c: Introduce the Relay SDK, Domino CLI with bundled agent skills, and standalone local runtime.